summaryrefslogtreecommitdiffhomepage
path: root/packages/api/src/agent-manager.ts
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-05-19 23:20:41 +0900
committerAdam Malczewski <[email protected]>2026-05-19 23:20:41 +0900
commita38d5b1279db6f9de5228c173019fc2ac08daec3 (patch)
tree32c3a535d0b74872ef952b4a44d4d5ba2ec9d638 /packages/api/src/agent-manager.ts
parent0ae805b28b5160b8d9fb43635fa172961f6550cc (diff)
downloaddispatch-a38d5b1279db6f9de5228c173019fc2ac08daec3.tar.gz
dispatch-a38d5b1279db6f9de5228c173019fc2ac08daec3.zip
feat: Phase 2 — shell permissions, tree-sitter analysis, permission UI
Permission engine: - Rule-based engine: wildcard matching, last-match-wins, reject cascade - PermissionService with pending/approved state, PermissionChecker interface - dispatch.yaml config loader with per-permission pattern rules Shell tool: - run_shell tool with child_process spawn, timeout, streaming output - Tree-sitter static analysis (web-tree-sitter + tree-sitter-bash WASM) - BashArity command normalization for 'always allow' patterns - FILE_COMMANDS set: rm, cp, mv, mkdir, ls, find, grep, cat, etc. Agent loop refactored: - Removed maxSteps, manual step loop with tool execution - Permission checks on shell commands (external_directory only) - Permission checks on file tools outside workspace boundary - Symlink bypass fix (realpathSync), .. false positive fix - Shell output streaming via Promise.race + setImmediate polling API layer: - PermissionManager wraps PermissionService, broadcasts via WebSocket - WebSocket handles permission-reply messages from frontend - Config loaded from dispatch.yaml, converted to ruleset Frontend: - Permission prompt modal (native dialog, focus trap, ARIA) - Always-allow confirmation flow with pattern preview - Shell output display (live streaming + final parsed result) - Permission log panel (fixed bottom-right overlay) - Exit code badge (green 0, red non-zero) 134 tests, typecheck clean on all 3 packages
Diffstat (limited to 'packages/api/src/agent-manager.ts')
-rw-r--r--packages/api/src/agent-manager.ts20
1 files changed, 20 insertions, 0 deletions
diff --git a/packages/api/src/agent-manager.ts b/packages/api/src/agent-manager.ts
index 45e195c..817ddbf 100644
--- a/packages/api/src/agent-manager.ts
+++ b/packages/api/src/agent-manager.ts
@@ -4,14 +4,19 @@ import {
type AgentStatus,
createListFilesTool,
createReadFileTool,
+ createRunShellTool,
createWriteFileTool,
+ loadConfig,
+ configToRuleset,
} from "@dispatch/core";
+import type { PermissionManager } from "./permission-manager.js";
const SYSTEM_PROMPT = `You are Dispatch, a helpful AI coding assistant. You have access to the following tools for working with files in the current working directory:
- read_file: Read the contents of a file
- write_file: Write content to a file (creates parent directories if needed)
- list_files: List files and directories
+- run_shell: Execute shell commands in the working directory (bash). Returns stdout, stderr, and exit code. Use for running tests, builds, git operations, package management, and other development tasks. Do NOT run destructive or irreversible commands unless the user explicitly requests them.
When asked to work with files, use these tools. Always confirm what you did after completing an action. Be concise and helpful.`;
@@ -20,6 +25,15 @@ export class AgentManager {
private status: AgentStatus = "idle";
private messageCount = 0;
private eventListeners: Set<(event: AgentEvent) => void> = new Set();
+ private permissionManager: PermissionManager | undefined;
+
+ constructor(permissionManager?: PermissionManager) {
+ this.permissionManager = permissionManager;
+ }
+
+ getPermissionManager(): PermissionManager | undefined {
+ return this.permissionManager;
+ }
private getOrCreateAgent(): Agent {
if (!this.agent) {
@@ -31,8 +45,12 @@ export class AgentManager {
createReadFileTool(workingDirectory),
createWriteFileTool(workingDirectory),
createListFilesTool(workingDirectory),
+ createRunShellTool(workingDirectory),
];
+ const config = loadConfig(workingDirectory);
+ const ruleset = configToRuleset(config);
+
this.agent = new Agent({
model,
apiKey,
@@ -40,6 +58,8 @@ export class AgentManager {
systemPrompt: SYSTEM_PROMPT,
tools,
workingDirectory,
+ permissionChecker: this.permissionManager ?? undefined,
+ ruleset,
});
}
return this.agent;