diff options
| author | Adam Malczewski <[email protected]> | 2026-05-22 15:24:13 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-05-22 15:24:13 +0900 |
| commit | 9ecaabd87c0e51b8a7408dabb0133a9344586859 (patch) | |
| tree | 4b5809ab23948a5e3f558f3aa34c52d5038f4e19 /packages/core/src/tools | |
| parent | 8f1dd855f0c4c877bff8d3a4ba193432b268b1c2 (diff) | |
| download | dispatch-9ecaabd87c0e51b8a7408dabb0133a9344586859.tar.gz dispatch-9ecaabd87c0e51b8a7408dabb0133a9344586859.zip | |
feat: agent builder, CWD support, auto-save, UI polish, unavailable tool handling
- Agent Builder: full CRUD with card grid, drag-and-drop model reorder, edit/delete
- Auto-save on edit with 600ms debounce, AbortController for concurrency, fieldset disabled until name entered
- Agent definitions stored as TOML with cwd field, loaded from global/project dirs
- Working directory: per-tab CWD override in Chat Settings, agent default CWD, auto-create on first message
- CWD validation: check-dir endpoint with ~ expansion, real-time validity indicator
- Subagent CWD validated against parent's effective CWD using path.relative
- Unavailable tool calls: caught gracefully, shown as tool call with error badge, model retries
- UI: tab bar border radius, sidebar border removed, chat input ghost style, scroll-to-bottom rectangle
- Skills dir collapse uses CSS rotation, Model Choice renamed to Chat Settings, System Prompt view removed
- Reusable SkillsBrowser/ToolPermissions with external mode for Agent Builder
- ModelSelector: Agent/Manual toggle, agent list, Agent Settings link
- Page router, skills recursive scanning, bin/up gopass removed, docker volume mounts
Diffstat (limited to 'packages/core/src/tools')
| -rw-r--r-- | packages/core/src/tools/bash-arity.ts | 60 | ||||
| -rw-r--r-- | packages/core/src/tools/run-shell.ts | 14 | ||||
| -rw-r--r-- | packages/core/src/tools/shell-analyze.ts | 29 |
3 files changed, 60 insertions, 43 deletions
diff --git a/packages/core/src/tools/bash-arity.ts b/packages/core/src/tools/bash-arity.ts index 5dde955..1aaba8e 100644 --- a/packages/core/src/tools/bash-arity.ts +++ b/packages/core/src/tools/bash-arity.ts @@ -1,37 +1,37 @@ // Hardcoded dictionary of well-known commands and their arity // (number of tokens that form the "human-understandable" prefix) const ARITY: Record<string, number> = { - "git": 2, // "git checkout", "git commit", etc. - "npm": 3, // "npm run dev", "npm install -g" - "docker": 2, // "docker compose", "docker build" - "kubectl": 2, // "kubectl get", "kubectl apply" - "bun": 2, // "bun install", "bun test" - "cargo": 2, // "cargo build", "cargo test" - "go": 2, // "go build", "go test" - "python": 2, // "python -m", "python script.py" - "python3": 2, - "pip": 2, - "pip3": 2, - "brew": 2, - "apt": 2, + git: 2, // "git checkout", "git commit", etc. + npm: 3, // "npm run dev", "npm install -g" + docker: 2, // "docker compose", "docker build" + kubectl: 2, // "kubectl get", "kubectl apply" + bun: 2, // "bun install", "bun test" + cargo: 2, // "cargo build", "cargo test" + go: 2, // "go build", "go test" + python: 2, // "python -m", "python script.py" + python3: 2, + pip: 2, + pip3: 2, + brew: 2, + apt: 2, "apt-get": 2, - "dnf": 2, - "yum": 2, - "pacman": 2, - "systemctl": 2, - "journalctl": 2, - "ssh": 2, - "scp": 2, - "rsync": 2, - "curl": 2, // "curl -X", "curl https://" - "wget": 2, - "tar": 2, // "tar -xzf", "tar -czf" - "zip": 2, - "unzip": 2, - "chown": 2, - "chmod": 2, - "mount": 2, - "umount": 2, + dnf: 2, + yum: 2, + pacman: 2, + systemctl: 2, + journalctl: 2, + ssh: 2, + scp: 2, + rsync: 2, + curl: 2, // "curl -X", "curl https://" + wget: 2, + tar: 2, // "tar -xzf", "tar -czf" + zip: 2, + unzip: 2, + chown: 2, + chmod: 2, + mount: 2, + umount: 2, // Default: all other commands are arity 1 }; diff --git a/packages/core/src/tools/run-shell.ts b/packages/core/src/tools/run-shell.ts index d549316..608c91d 100644 --- a/packages/core/src/tools/run-shell.ts +++ b/packages/core/src/tools/run-shell.ts @@ -11,12 +11,12 @@ export function createRunShellTool(workingDirectory: string): ToolDefinition { "Execute a shell command in the working directory. Returns stdout, stderr, and exit code. Use for running tests, builds, git operations, package management, and other development tasks.", parameters: z.object({ command: z.string().describe("The shell command to execute"), - timeout: z - .number() - .optional() - .describe("Timeout in milliseconds (default 2 minutes)"), + timeout: z.number().optional().describe("Timeout in milliseconds (default 2 minutes)"), }), - execute: async (args: Record<string, unknown>, context?: ToolExecuteContext): Promise<string> => { + execute: async ( + args: Record<string, unknown>, + context?: ToolExecuteContext, + ): Promise<string> => { const command = args.command as string; const timeout = (args.timeout as number | undefined) ?? DEFAULT_TIMEOUT; @@ -68,7 +68,5 @@ export function createRunShellTool(workingDirectory: string): ToolDefinition { } function getShell(): [string, string[]] { - return process.platform === "win32" - ? ["powershell", ["-Command"]] - : ["bash", ["-c"]]; + return process.platform === "win32" ? ["powershell", ["-Command"]] : ["bash", ["-c"]]; } diff --git a/packages/core/src/tools/shell-analyze.ts b/packages/core/src/tools/shell-analyze.ts index 23bbfc9..b70108b 100644 --- a/packages/core/src/tools/shell-analyze.ts +++ b/packages/core/src/tools/shell-analyze.ts @@ -1,6 +1,6 @@ -import { dirname, isAbsolute, relative, resolve, sep } from "node:path"; import { readFile } from "node:fs/promises"; import { createRequire } from "node:module"; +import { dirname, isAbsolute, relative, resolve, sep } from "node:path"; import * as BashArity from "./bash-arity.js"; // Commands that touch files — triggers external_directory check. @@ -12,9 +12,27 @@ import * as BashArity from "./bash-arity.js"; // - `cd` state changes: we don't track cwd mutations across pipeline stages // - Interpreter escapes: `python -c "open('/etc/passwd')"`, `node -e "..."` bypass this entirely const FILE_COMMANDS = new Set([ - "rm", "cp", "mv", "mkdir", "touch", "chmod", "chown", - "cat", "ls", "find", "grep", - "head", "tail", "less", "more", "wc", "diff", "file", "stat", "du", "df", + "rm", + "cp", + "mv", + "mkdir", + "touch", + "chmod", + "chown", + "cat", + "ls", + "find", + "grep", + "head", + "tail", + "less", + "more", + "wc", + "diff", + "file", + "stat", + "du", + "df", ]); // Lazy-initialized parser @@ -142,6 +160,7 @@ function isInsideWorkspace(filePath: string, wd: string): boolean { // rel === "" means filePath IS the workspace root — that is inside. // If relative path starts with "../" or is ".." exactly, or is an absolute path // (on Windows when drives differ), the file is outside the workspace. - const isOutside = rel.startsWith(`..${sep}`) || rel.startsWith("../") || rel === ".." || isAbsolute(rel); + const isOutside = + rel.startsWith(`..${sep}`) || rel.startsWith("../") || rel === ".." || isAbsolute(rel); return !isOutside; } |
