@cdoing/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/coordinator.d.ts +114 -0
- package/dist/agents/coordinator.d.ts.map +1 -0
- package/dist/agents/coordinator.js +158 -0
- package/dist/agents/coordinator.js.map +1 -0
- package/dist/context-providers/clipboard.d.ts +13 -0
- package/dist/context-providers/clipboard.d.ts.map +1 -0
- package/dist/context-providers/clipboard.js +53 -0
- package/dist/context-providers/clipboard.js.map +1 -0
- package/dist/context-providers/codebase.d.ts +46 -0
- package/dist/context-providers/codebase.d.ts.map +1 -0
- package/dist/context-providers/codebase.js +273 -0
- package/dist/context-providers/codebase.js.map +1 -0
- package/dist/context-providers/diff.d.ts +18 -0
- package/dist/context-providers/diff.d.ts.map +1 -0
- package/dist/context-providers/diff.js +63 -0
- package/dist/context-providers/diff.js.map +1 -0
- package/dist/context-providers/docs.d.ts +21 -0
- package/dist/context-providers/docs.d.ts.map +1 -0
- package/dist/context-providers/docs.js +180 -0
- package/dist/context-providers/docs.js.map +1 -0
- package/dist/context-providers/file-include.d.ts +13 -0
- package/dist/context-providers/file-include.d.ts.map +1 -0
- package/dist/context-providers/file-include.js +82 -0
- package/dist/context-providers/file-include.js.map +1 -0
- package/dist/context-providers/folder.d.ts +19 -0
- package/dist/context-providers/folder.d.ts.map +1 -0
- package/dist/context-providers/folder.js +130 -0
- package/dist/context-providers/folder.js.map +1 -0
- package/dist/context-providers/git.d.ts +19 -0
- package/dist/context-providers/git.d.ts.map +1 -0
- package/dist/context-providers/git.js +74 -0
- package/dist/context-providers/git.js.map +1 -0
- package/dist/context-providers/index.d.ts +26 -0
- package/dist/context-providers/index.d.ts.map +1 -0
- package/dist/context-providers/index.js +37 -0
- package/dist/context-providers/index.js.map +1 -0
- package/dist/context-providers/open-files.d.ts +25 -0
- package/dist/context-providers/open-files.d.ts.map +1 -0
- package/dist/context-providers/open-files.js +134 -0
- package/dist/context-providers/open-files.js.map +1 -0
- package/dist/context-providers/problems.d.ts +24 -0
- package/dist/context-providers/problems.d.ts.map +1 -0
- package/dist/context-providers/problems.js +97 -0
- package/dist/context-providers/problems.js.map +1 -0
- package/dist/context-providers/registry.d.ts +61 -0
- package/dist/context-providers/registry.d.ts.map +1 -0
- package/dist/context-providers/registry.js +92 -0
- package/dist/context-providers/registry.js.map +1 -0
- package/dist/context-providers/terminal.d.ts +25 -0
- package/dist/context-providers/terminal.d.ts.map +1 -0
- package/dist/context-providers/terminal.js +55 -0
- package/dist/context-providers/terminal.js.map +1 -0
- package/dist/context-providers/tree.d.ts +29 -0
- package/dist/context-providers/tree.d.ts.map +1 -0
- package/dist/context-providers/tree.js +172 -0
- package/dist/context-providers/tree.js.map +1 -0
- package/dist/context-providers/types.d.ts +72 -0
- package/dist/context-providers/types.d.ts.map +1 -0
- package/dist/context-providers/types.js +10 -0
- package/dist/context-providers/types.js.map +1 -0
- package/dist/context-providers/url.d.ts +27 -0
- package/dist/context-providers/url.d.ts.map +1 -0
- package/dist/context-providers/url.js +131 -0
- package/dist/context-providers/url.js.map +1 -0
- package/dist/effort/index.d.ts +78 -0
- package/dist/effort/index.d.ts.map +1 -0
- package/dist/effort/index.js +146 -0
- package/dist/effort/index.js.map +1 -0
- package/dist/hooks/index.d.ts +47 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +151 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/index.d.ts +75 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +152 -0
- package/dist/index.js.map +1 -0
- package/dist/indexing/chunker.d.ts +25 -0
- package/dist/indexing/chunker.d.ts.map +1 -0
- package/dist/indexing/chunker.js +217 -0
- package/dist/indexing/chunker.js.map +1 -0
- package/dist/indexing/database.d.ts +49 -0
- package/dist/indexing/database.d.ts.map +1 -0
- package/dist/indexing/database.js +287 -0
- package/dist/indexing/database.js.map +1 -0
- package/dist/indexing/index.d.ts +9 -0
- package/dist/indexing/index.d.ts.map +1 -0
- package/dist/indexing/index.js +13 -0
- package/dist/indexing/index.js.map +1 -0
- package/dist/indexing/indexer.d.ts +63 -0
- package/dist/indexing/indexer.d.ts.map +1 -0
- package/dist/indexing/indexer.js +352 -0
- package/dist/indexing/indexer.js.map +1 -0
- package/dist/indexing/recent-edits-cache.d.ts +77 -0
- package/dist/indexing/recent-edits-cache.d.ts.map +1 -0
- package/dist/indexing/recent-edits-cache.js +123 -0
- package/dist/indexing/recent-edits-cache.js.map +1 -0
- package/dist/indexing/types.d.ts +39 -0
- package/dist/indexing/types.d.ts.map +1 -0
- package/dist/indexing/types.js +6 -0
- package/dist/indexing/types.js.map +1 -0
- package/dist/mcp/index.d.ts +33 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +37 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/manager.d.ts +123 -0
- package/dist/mcp/manager.d.ts.map +1 -0
- package/dist/mcp/manager.js +331 -0
- package/dist/mcp/manager.js.map +1 -0
- package/dist/oauth.d.ts +33 -0
- package/dist/oauth.d.ts.map +1 -0
- package/dist/oauth.js +312 -0
- package/dist/oauth.js.map +1 -0
- package/dist/permissions/index.d.ts +216 -0
- package/dist/permissions/index.d.ts.map +1 -0
- package/dist/permissions/index.js +938 -0
- package/dist/permissions/index.js.map +1 -0
- package/dist/plan/index.d.ts +20 -0
- package/dist/plan/index.d.ts.map +1 -0
- package/dist/plan/index.js +24 -0
- package/dist/plan/index.js.map +1 -0
- package/dist/plan/manager.d.ts +101 -0
- package/dist/plan/manager.d.ts.map +1 -0
- package/dist/plan/manager.js +170 -0
- package/dist/plan/manager.js.map +1 -0
- package/dist/rules/index.d.ts +28 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +31 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/manager.d.ts +77 -0
- package/dist/rules/manager.d.ts.map +1 -0
- package/dist/rules/manager.js +279 -0
- package/dist/rules/manager.js.map +1 -0
- package/dist/rules/types.d.ts +34 -0
- package/dist/rules/types.d.ts.map +1 -0
- package/dist/rules/types.js +9 -0
- package/dist/rules/types.js.map +1 -0
- package/dist/sandbox/filesystem.d.ts +20 -0
- package/dist/sandbox/filesystem.d.ts.map +1 -0
- package/dist/sandbox/filesystem.js +141 -0
- package/dist/sandbox/filesystem.js.map +1 -0
- package/dist/sandbox/index.d.ts +4 -0
- package/dist/sandbox/index.d.ts.map +1 -0
- package/dist/sandbox/index.js +8 -0
- package/dist/sandbox/index.js.map +1 -0
- package/dist/sandbox/manager.d.ts +47 -0
- package/dist/sandbox/manager.d.ts.map +1 -0
- package/dist/sandbox/manager.js +220 -0
- package/dist/sandbox/manager.js.map +1 -0
- package/dist/sandbox/network.d.ts +14 -0
- package/dist/sandbox/network.d.ts.map +1 -0
- package/dist/sandbox/network.js +87 -0
- package/dist/sandbox/network.js.map +1 -0
- package/dist/sandbox/types.d.ts +42 -0
- package/dist/sandbox/types.d.ts.map +1 -0
- package/dist/sandbox/types.js +25 -0
- package/dist/sandbox/types.js.map +1 -0
- package/dist/tools/ast-edit.d.ts +57 -0
- package/dist/tools/ast-edit.d.ts.map +1 -0
- package/dist/tools/ast-edit.js +443 -0
- package/dist/tools/ast-edit.js.map +1 -0
- package/dist/tools/code-verify.d.ts +8 -0
- package/dist/tools/code-verify.d.ts.map +1 -0
- package/dist/tools/code-verify.js +159 -0
- package/dist/tools/code-verify.js.map +1 -0
- package/dist/tools/codebase-search.d.ts +17 -0
- package/dist/tools/codebase-search.d.ts.map +1 -0
- package/dist/tools/codebase-search.js +104 -0
- package/dist/tools/codebase-search.js.map +1 -0
- package/dist/tools/file-delete.d.ts +26 -0
- package/dist/tools/file-delete.d.ts.map +1 -0
- package/dist/tools/file-delete.js +179 -0
- package/dist/tools/file-delete.js.map +1 -0
- package/dist/tools/file-edit.d.ts +10 -0
- package/dist/tools/file-edit.d.ts.map +1 -0
- package/dist/tools/file-edit.js +138 -0
- package/dist/tools/file-edit.js.map +1 -0
- package/dist/tools/file-read.d.ts +12 -0
- package/dist/tools/file-read.d.ts.map +1 -0
- package/dist/tools/file-read.js +211 -0
- package/dist/tools/file-read.js.map +1 -0
- package/dist/tools/file-run.d.ts +10 -0
- package/dist/tools/file-run.d.ts.map +1 -0
- package/dist/tools/file-run.js +179 -0
- package/dist/tools/file-run.js.map +1 -0
- package/dist/tools/file-write.d.ts +10 -0
- package/dist/tools/file-write.d.ts.map +1 -0
- package/dist/tools/file-write.js +134 -0
- package/dist/tools/file-write.js.map +1 -0
- package/dist/tools/glob-search.d.ts +8 -0
- package/dist/tools/glob-search.d.ts.map +1 -0
- package/dist/tools/glob-search.js +108 -0
- package/dist/tools/glob-search.js.map +1 -0
- package/dist/tools/grep-search.d.ts +8 -0
- package/dist/tools/grep-search.d.ts.map +1 -0
- package/dist/tools/grep-search.js +139 -0
- package/dist/tools/grep-search.js.map +1 -0
- package/dist/tools/list-dir.d.ts +16 -0
- package/dist/tools/list-dir.d.ts.map +1 -0
- package/dist/tools/list-dir.js +183 -0
- package/dist/tools/list-dir.js.map +1 -0
- package/dist/tools/multi-edit.d.ts +16 -0
- package/dist/tools/multi-edit.d.ts.map +1 -0
- package/dist/tools/multi-edit.js +163 -0
- package/dist/tools/multi-edit.js.map +1 -0
- package/dist/tools/notebook-edit.d.ts +31 -0
- package/dist/tools/notebook-edit.d.ts.map +1 -0
- package/dist/tools/notebook-edit.js +321 -0
- package/dist/tools/notebook-edit.js.map +1 -0
- package/dist/tools/registry.d.ts +16 -0
- package/dist/tools/registry.d.ts.map +1 -0
- package/dist/tools/registry.js +41 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/shell-exec.d.ts +12 -0
- package/dist/tools/shell-exec.d.ts.map +1 -0
- package/dist/tools/shell-exec.js +261 -0
- package/dist/tools/shell-exec.js.map +1 -0
- package/dist/tools/sub-agent-manager.d.ts +57 -0
- package/dist/tools/sub-agent-manager.d.ts.map +1 -0
- package/dist/tools/sub-agent-manager.js +153 -0
- package/dist/tools/sub-agent-manager.js.map +1 -0
- package/dist/tools/sub-agent-status.d.ts +12 -0
- package/dist/tools/sub-agent-status.d.ts.map +1 -0
- package/dist/tools/sub-agent-status.js +59 -0
- package/dist/tools/sub-agent-status.js.map +1 -0
- package/dist/tools/sub-agent-terminate.d.ts +12 -0
- package/dist/tools/sub-agent-terminate.d.ts.map +1 -0
- package/dist/tools/sub-agent-terminate.js +55 -0
- package/dist/tools/sub-agent-terminate.js.map +1 -0
- package/dist/tools/sub-agent.d.ts +34 -0
- package/dist/tools/sub-agent.d.ts.map +1 -0
- package/dist/tools/sub-agent.js +140 -0
- package/dist/tools/sub-agent.js.map +1 -0
- package/dist/tools/system-info.d.ts +24 -0
- package/dist/tools/system-info.d.ts.map +1 -0
- package/dist/tools/system-info.js +220 -0
- package/dist/tools/system-info.js.map +1 -0
- package/dist/tools/todo.d.ts +16 -0
- package/dist/tools/todo.d.ts.map +1 -0
- package/dist/tools/todo.js +144 -0
- package/dist/tools/todo.js.map +1 -0
- package/dist/tools/types.d.ts +20 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +3 -0
- package/dist/tools/types.js.map +1 -0
- package/dist/tools/view-diff.d.ts +11 -0
- package/dist/tools/view-diff.d.ts.map +1 -0
- package/dist/tools/view-diff.js +88 -0
- package/dist/tools/view-diff.js.map +1 -0
- package/dist/tools/view-repo-map.d.ts +18 -0
- package/dist/tools/view-repo-map.d.ts.map +1 -0
- package/dist/tools/view-repo-map.js +245 -0
- package/dist/tools/view-repo-map.js.map +1 -0
- package/dist/tools/web-fetch.d.ts +13 -0
- package/dist/tools/web-fetch.d.ts.map +1 -0
- package/dist/tools/web-fetch.js +106 -0
- package/dist/tools/web-fetch.js.map +1 -0
- package/dist/tools/web-search.d.ts +10 -0
- package/dist/tools/web-search.d.ts.map +1 -0
- package/dist/tools/web-search.js +106 -0
- package/dist/tools/web-search.js.map +1 -0
- package/dist/utils/gitignore.d.ts +10 -0
- package/dist/utils/gitignore.d.ts.map +1 -0
- package/dist/utils/gitignore.js +104 -0
- package/dist/utils/gitignore.js.map +1 -0
- package/dist/utils/lazy-apply.d.ts +45 -0
- package/dist/utils/lazy-apply.d.ts.map +1 -0
- package/dist/utils/lazy-apply.js +164 -0
- package/dist/utils/lazy-apply.js.map +1 -0
- package/dist/utils/memory.d.ts +36 -0
- package/dist/utils/memory.d.ts.map +1 -0
- package/dist/utils/memory.js +136 -0
- package/dist/utils/memory.js.map +1 -0
- package/dist/utils/path-matching.d.ts +24 -0
- package/dist/utils/path-matching.d.ts.map +1 -0
- package/dist/utils/path-matching.js +116 -0
- package/dist/utils/path-matching.js.map +1 -0
- package/dist/utils/path-safety.d.ts +13 -0
- package/dist/utils/path-safety.d.ts.map +1 -0
- package/dist/utils/path-safety.js +54 -0
- package/dist/utils/path-safety.js.map +1 -0
- package/dist/utils/project-config.d.ts +18 -0
- package/dist/utils/project-config.d.ts.map +1 -0
- package/dist/utils/project-config.js +76 -0
- package/dist/utils/project-config.js.map +1 -0
- package/dist/utils/search-match.d.ts +63 -0
- package/dist/utils/search-match.d.ts.map +1 -0
- package/dist/utils/search-match.js +426 -0
- package/dist/utils/search-match.js.map +1 -0
- package/dist/utils/shell-paths.d.ts +17 -0
- package/dist/utils/shell-paths.d.ts.map +1 -0
- package/dist/utils/shell-paths.js +107 -0
- package/dist/utils/shell-paths.js.map +1 -0
- package/dist/utils/streaming-diff.d.ts +45 -0
- package/dist/utils/streaming-diff.d.ts.map +1 -0
- package/dist/utils/streaming-diff.js +230 -0
- package/dist/utils/streaming-diff.js.map +1 -0
- package/dist/utils/todo.d.ts +47 -0
- package/dist/utils/todo.d.ts.map +1 -0
- package/dist/utils/todo.js +102 -0
- package/dist/utils/todo.js.map +1 -0
- package/package.json +23 -0
- package/src/agents/coordinator.ts +240 -0
- package/src/context-providers/clipboard.ts +48 -0
- package/src/context-providers/codebase.ts +274 -0
- package/src/context-providers/diff.ts +66 -0
- package/src/context-providers/docs.ts +160 -0
- package/src/context-providers/file-include.ts +54 -0
- package/src/context-providers/folder.ts +106 -0
- package/src/context-providers/git.ts +72 -0
- package/src/context-providers/index.ts +26 -0
- package/src/context-providers/open-files.ts +113 -0
- package/src/context-providers/problems.ts +100 -0
- package/src/context-providers/registry.ts +99 -0
- package/src/context-providers/terminal.ts +58 -0
- package/src/context-providers/tree.ts +161 -0
- package/src/context-providers/types.ts +84 -0
- package/src/context-providers/url.ts +138 -0
- package/src/effort/index.ts +177 -0
- package/src/hooks/index.ts +148 -0
- package/src/index.ts +114 -0
- package/src/indexing/README.md +267 -0
- package/src/indexing/chunker.ts +206 -0
- package/src/indexing/database.ts +299 -0
- package/src/indexing/index.ts +15 -0
- package/src/indexing/indexer.ts +383 -0
- package/src/indexing/recent-edits-cache.ts +150 -0
- package/src/indexing/types.ts +44 -0
- package/src/mcp/index.ts +33 -0
- package/src/mcp/manager.ts +385 -0
- package/src/oauth.ts +330 -0
- package/src/permissions/index.ts +1011 -0
- package/src/plan/index.ts +20 -0
- package/src/plan/manager.ts +233 -0
- package/src/rules/index.ts +28 -0
- package/src/rules/manager.ts +276 -0
- package/src/rules/types.ts +40 -0
- package/src/sandbox/filesystem.ts +135 -0
- package/src/sandbox/index.ts +9 -0
- package/src/sandbox/manager.ts +213 -0
- package/src/sandbox/network.ts +101 -0
- package/src/sandbox/types.ts +63 -0
- package/src/tools/ast-edit.ts +493 -0
- package/src/tools/code-verify.ts +143 -0
- package/src/tools/codebase-search.ts +117 -0
- package/src/tools/file-delete.ts +155 -0
- package/src/tools/file-edit.ts +115 -0
- package/src/tools/file-read.ts +195 -0
- package/src/tools/file-run.ts +158 -0
- package/src/tools/file-write.ts +104 -0
- package/src/tools/glob-search.ts +80 -0
- package/src/tools/grep-search.ts +120 -0
- package/src/tools/list-dir.ts +172 -0
- package/src/tools/multi-edit.ts +138 -0
- package/src/tools/notebook-edit.ts +342 -0
- package/src/tools/registry.ts +43 -0
- package/src/tools/shell-exec.ts +251 -0
- package/src/tools/sub-agent-manager.ts +183 -0
- package/src/tools/sub-agent-status.ts +67 -0
- package/src/tools/sub-agent-terminate.ts +62 -0
- package/src/tools/sub-agent.ts +162 -0
- package/src/tools/system-info.ts +248 -0
- package/src/tools/todo.ts +149 -0
- package/src/tools/types.ts +21 -0
- package/src/tools/view-diff.ts +99 -0
- package/src/tools/view-repo-map.ts +249 -0
- package/src/tools/web-fetch.ts +118 -0
- package/src/tools/web-search.ts +129 -0
- package/src/utils/gitignore.ts +73 -0
- package/src/utils/lazy-apply.ts +189 -0
- package/src/utils/memory.ts +124 -0
- package/src/utils/path-matching.ts +84 -0
- package/src/utils/path-safety.ts +19 -0
- package/src/utils/project-config.ts +41 -0
- package/src/utils/search-match.ts +495 -0
- package/src/utils/shell-paths.ts +79 -0
- package/src/utils/streaming-diff.ts +260 -0
- package/src/utils/todo.ts +115 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sub-Agent Tool — allows the agent to spawn child agents for parallel research.
|
|
3
|
+
*
|
|
4
|
+
* The sub-agent gets its own conversation context and the same tools,
|
|
5
|
+
* but cannot spawn further sub-agents (prevents infinite recursion).
|
|
6
|
+
*
|
|
7
|
+
* Security:
|
|
8
|
+
* - Sub-agents inherit the parent's PermissionManager — all tool calls
|
|
9
|
+
* inside the child go through the same deny/ask/allow rules.
|
|
10
|
+
* - Task descriptions are screened for destructive intent patterns.
|
|
11
|
+
* - Permission prompt shows a ⚠ warning for destructive tasks.
|
|
12
|
+
*
|
|
13
|
+
* Features:
|
|
14
|
+
* - Custom timeout (useful for long-running tasks like npm install)
|
|
15
|
+
* - Background mode (returns agent ID immediately, check status later)
|
|
16
|
+
* - Foreground mode (waits for completion, default)
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type { BaseTool, ToolDefinition, ToolResult } from "./types";
|
|
20
|
+
import { SubAgentManager } from "./sub-agent-manager";
|
|
21
|
+
|
|
22
|
+
// ── Destructive task detection ──────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Patterns in task descriptions that indicate destructive intent.
|
|
26
|
+
* These trigger an elevated permission prompt (⚠ DESTRUCTIVE) so the
|
|
27
|
+
* user is clearly warned before the sub-agent is spawned.
|
|
28
|
+
*/
|
|
29
|
+
const DESTRUCTIVE_TASK_PATTERNS = [
|
|
30
|
+
// File deletion
|
|
31
|
+
/\bdelete\b.*\bfiles?\b/i, /\bremove\b.*\bfiles?\b/i, /\brm\s+-rf\b/i,
|
|
32
|
+
/\bwipe\b/i, /\bpurge\b/i, /\bclean\s*up\b/i, /\bnuke\b/i,
|
|
33
|
+
// Git destructive
|
|
34
|
+
/\bforce\s*push\b/i, /\bgit\s+reset\s+--hard\b/i, /\bgit\s+clean\b/i,
|
|
35
|
+
/\brewrite\s+history\b/i, /\bdelete\b.*\bbranch/i,
|
|
36
|
+
// Database
|
|
37
|
+
/\bdrop\b.*\b(table|database|collection)\b/i, /\btruncate\b/i,
|
|
38
|
+
// System
|
|
39
|
+
/\bkill\b.*\bprocess/i, /\bshutdown\b/i, /\brestart\b.*\bserver/i,
|
|
40
|
+
/\bformat\b.*\bdisk\b/i,
|
|
41
|
+
// Destructive ops
|
|
42
|
+
/\boverwrite\b/i, /\bdestroy\b/i, /\buninstall\b/i,
|
|
43
|
+
/\bdrop\b.*\bpermission/i, /\brevoke\b/i,
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Factory function signature for creating sub-agent runners.
|
|
48
|
+
* Now accepts an AbortSignal so the parent can cancel child agents.
|
|
49
|
+
*/
|
|
50
|
+
export interface SubAgentRunnerFactory {
|
|
51
|
+
(prompt: string, signal?: AbortSignal): Promise<string>;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export class SubAgentTool implements BaseTool {
|
|
55
|
+
definition: ToolDefinition = {
|
|
56
|
+
name: "sub_agent",
|
|
57
|
+
description:
|
|
58
|
+
"Spawn a sub-agent to handle a specific task. The sub-agent has access to the same file and search tools but runs in its own context. Use this for independent tasks like 'find all usages of X', 'read and summarize file Y', or long-running commands like 'npm install'. Supports custom timeout and background mode. The sub-agent cannot spawn further sub-agents.",
|
|
59
|
+
inputSchema: {
|
|
60
|
+
type: "object",
|
|
61
|
+
properties: {
|
|
62
|
+
task: {
|
|
63
|
+
type: "string",
|
|
64
|
+
description: "A clear, specific task for the sub-agent to perform",
|
|
65
|
+
},
|
|
66
|
+
timeout: {
|
|
67
|
+
type: "number",
|
|
68
|
+
description:
|
|
69
|
+
"Custom timeout in milliseconds. Use for long-running tasks (e.g., 300000 for 5 minutes). Default: no timeout (runs until completion).",
|
|
70
|
+
},
|
|
71
|
+
background: {
|
|
72
|
+
type: "boolean",
|
|
73
|
+
description:
|
|
74
|
+
"If true, returns immediately with an agent ID. Use sub_agent_status to check progress and sub_agent_terminate to stop it. Default: false (waits for completion).",
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
required: ["task"],
|
|
78
|
+
},
|
|
79
|
+
requiresPermission: true,
|
|
80
|
+
permissionMessage: (input) => {
|
|
81
|
+
const task = String(input.task || "").slice(0, 200);
|
|
82
|
+
if (DESTRUCTIVE_TASK_PATTERNS.some((p) => p.test(task))) {
|
|
83
|
+
return `⚠ DESTRUCTIVE sub-agent task: ${task}`;
|
|
84
|
+
}
|
|
85
|
+
return `Spawn sub-agent: ${task}`;
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
private runnerFactory: SubAgentRunnerFactory;
|
|
90
|
+
private manager: SubAgentManager;
|
|
91
|
+
|
|
92
|
+
constructor(runnerFactory: SubAgentRunnerFactory, manager: SubAgentManager) {
|
|
93
|
+
this.runnerFactory = runnerFactory;
|
|
94
|
+
this.manager = manager;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async execute(input: Record<string, unknown>): Promise<ToolResult> {
|
|
98
|
+
const task = input.task as string;
|
|
99
|
+
const timeout = input.timeout as number | undefined;
|
|
100
|
+
const background = input.background as boolean | undefined;
|
|
101
|
+
|
|
102
|
+
if (!task || task.trim().length === 0) {
|
|
103
|
+
return { success: false, output: "", error: "Task cannot be empty" };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Prepend a safety instruction if the task looks destructive.
|
|
107
|
+
// The sub-agent inherits the parent's PermissionManager so each tool call
|
|
108
|
+
// will still go through deny/ask/allow checks, but this extra context
|
|
109
|
+
// makes the LLM aware it should proceed cautiously.
|
|
110
|
+
const isDestructive = DESTRUCTIVE_TASK_PATTERNS.some((p) => p.test(task));
|
|
111
|
+
const safeTask = isDestructive
|
|
112
|
+
? `[CAUTION: This task involves potentially destructive operations. ` +
|
|
113
|
+
`You MUST ask for explicit user confirmation before executing any ` +
|
|
114
|
+
`destructive commands (rm, delete, drop, force push, reset --hard, etc.). ` +
|
|
115
|
+
`Prefer safe alternatives when possible.]\n\n${task}`
|
|
116
|
+
: task;
|
|
117
|
+
|
|
118
|
+
const runnerFn = (signal: AbortSignal) => this.runnerFactory(safeTask, signal);
|
|
119
|
+
|
|
120
|
+
if (background) {
|
|
121
|
+
// Background mode: return immediately with agent ID
|
|
122
|
+
const agentId = this.manager.spawn(task, runnerFn, timeout);
|
|
123
|
+
return {
|
|
124
|
+
success: true,
|
|
125
|
+
output: JSON.stringify({
|
|
126
|
+
agent_id: agentId,
|
|
127
|
+
status: "running",
|
|
128
|
+
message: `Sub-agent spawned in background. Use sub_agent_status with agent_id "${agentId}" to check progress, or sub_agent_terminate to stop it.`,
|
|
129
|
+
}),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Foreground mode: wait for completion
|
|
134
|
+
try {
|
|
135
|
+
const entry = await this.manager.spawnAndWait(task, runnerFn, timeout);
|
|
136
|
+
|
|
137
|
+
if (entry.status === "completed") {
|
|
138
|
+
return {
|
|
139
|
+
success: true,
|
|
140
|
+
output: entry.output || "(sub-agent returned no output)",
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (entry.status === "timed_out") {
|
|
145
|
+
return {
|
|
146
|
+
success: false,
|
|
147
|
+
output: entry.output || "",
|
|
148
|
+
error: `Sub-agent timed out after ${timeout}ms. Partial output (if any) is included. You can retry with a longer timeout or run in background mode.`,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
success: false,
|
|
154
|
+
output: entry.output || "",
|
|
155
|
+
error: `Sub-agent ${entry.status}: ${entry.error || "unknown error"}`,
|
|
156
|
+
};
|
|
157
|
+
} catch (err) {
|
|
158
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
159
|
+
return { success: false, output: "", error: `Sub-agent failed: ${message}` };
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* System Info Tool — gives the LLM runtime awareness of its own
|
|
3
|
+
* permission mode, settings rules, sandbox config, and available tools.
|
|
4
|
+
*
|
|
5
|
+
* This replaces hardcoded permission docs in the system prompt with
|
|
6
|
+
* a live, queryable tool the agent can call at any time.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { BaseTool, ToolDefinition, ToolResult } from "./types";
|
|
10
|
+
import type { PermissionManager } from "../permissions";
|
|
11
|
+
import type { SandboxManager } from "../sandbox";
|
|
12
|
+
import type { ToolRegistry } from "./registry";
|
|
13
|
+
|
|
14
|
+
export class SystemInfoTool implements BaseTool {
|
|
15
|
+
definition: ToolDefinition = {
|
|
16
|
+
name: "system_info",
|
|
17
|
+
description:
|
|
18
|
+
"Get information about your current permissions, sandbox restrictions, available tools, and system configuration. Call this when you need to understand what you can or cannot do, or when the user asks about your access level. No arguments required — returns a full status report.",
|
|
19
|
+
inputSchema: {
|
|
20
|
+
type: "object",
|
|
21
|
+
properties: {
|
|
22
|
+
section: {
|
|
23
|
+
type: "string",
|
|
24
|
+
description:
|
|
25
|
+
"Optional: request a specific section. Values: 'permissions', 'sandbox', 'tools', 'all'. Default: 'all'.",
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
required: [],
|
|
29
|
+
},
|
|
30
|
+
requiresPermission: false,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
private permissionManager: PermissionManager;
|
|
34
|
+
private sandboxManager?: SandboxManager;
|
|
35
|
+
private toolRegistry: ToolRegistry;
|
|
36
|
+
|
|
37
|
+
constructor(
|
|
38
|
+
permissionManager: PermissionManager,
|
|
39
|
+
toolRegistry: ToolRegistry,
|
|
40
|
+
sandboxManager?: SandboxManager,
|
|
41
|
+
) {
|
|
42
|
+
this.permissionManager = permissionManager;
|
|
43
|
+
this.toolRegistry = toolRegistry;
|
|
44
|
+
this.sandboxManager = sandboxManager;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async execute(input: Record<string, unknown>): Promise<ToolResult> {
|
|
48
|
+
const section = (input.section as string) || "all";
|
|
49
|
+
const parts: string[] = [];
|
|
50
|
+
|
|
51
|
+
if (section === "all" || section === "permissions") {
|
|
52
|
+
parts.push(this.buildPermissionsSection());
|
|
53
|
+
}
|
|
54
|
+
if (section === "all" || section === "sandbox") {
|
|
55
|
+
parts.push(this.buildSandboxSection());
|
|
56
|
+
}
|
|
57
|
+
if (section === "all" || section === "tools") {
|
|
58
|
+
parts.push(this.buildToolsSection());
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return { success: true, output: parts.join("\n\n") };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private buildPermissionsSection(): string {
|
|
65
|
+
const mode = this.permissionManager.getMode();
|
|
66
|
+
const rules = this.permissionManager.getSettingsRules();
|
|
67
|
+
const stored = this.permissionManager.getStoredRules();
|
|
68
|
+
|
|
69
|
+
const lines: string[] = [
|
|
70
|
+
"## Permission System Status",
|
|
71
|
+
"",
|
|
72
|
+
`**Current Mode:** ${mode}`,
|
|
73
|
+
this.describeModeEffect(mode),
|
|
74
|
+
"",
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
// Settings rules
|
|
78
|
+
if (rules.deny.length || rules.allow.length || rules.ask.length) {
|
|
79
|
+
lines.push("### Settings Rules (from .claude/settings.json)");
|
|
80
|
+
lines.push("Rules are evaluated: deny → ask → allow. Deny always wins.");
|
|
81
|
+
lines.push("");
|
|
82
|
+
if (rules.deny.length) {
|
|
83
|
+
lines.push("**Deny (always blocked):**");
|
|
84
|
+
for (const r of rules.deny) lines.push(` - ${r}`);
|
|
85
|
+
}
|
|
86
|
+
if (rules.ask.length) {
|
|
87
|
+
lines.push("**Ask (prompt user, even if allow matches):**");
|
|
88
|
+
for (const r of rules.ask) lines.push(` - ${r}`);
|
|
89
|
+
}
|
|
90
|
+
if (rules.allow.length) {
|
|
91
|
+
lines.push("**Allow (auto-approved):**");
|
|
92
|
+
for (const r of rules.allow) lines.push(` - ${r}`);
|
|
93
|
+
}
|
|
94
|
+
lines.push("");
|
|
95
|
+
} else {
|
|
96
|
+
lines.push("No settings-based rules configured. All tool calls fall through to the mode-based behavior.");
|
|
97
|
+
lines.push("");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Stored rules
|
|
101
|
+
const globalCount = stored.global.length;
|
|
102
|
+
const projectCount = stored.project.length;
|
|
103
|
+
if (globalCount || projectCount) {
|
|
104
|
+
lines.push("### Previously Approved Rules");
|
|
105
|
+
if (globalCount) {
|
|
106
|
+
lines.push(`Global (always): ${globalCount} rule(s)`);
|
|
107
|
+
for (const r of stored.global) {
|
|
108
|
+
lines.push(` - ${r.tool}${r.inputMatch ? ` (${r.inputMatch})` : ""}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (projectCount) {
|
|
112
|
+
lines.push(`Project: ${projectCount} rule(s)`);
|
|
113
|
+
for (const r of stored.project) {
|
|
114
|
+
lines.push(` - ${r.tool}${r.inputMatch ? ` (${r.inputMatch})` : ""}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
lines.push("");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Decision flow
|
|
121
|
+
lines.push("### Decision Flow");
|
|
122
|
+
lines.push("When you call a tool, the system evaluates in this order:");
|
|
123
|
+
lines.push("1. Bypass mode → allow all");
|
|
124
|
+
lines.push("2. Tool doesn't require permission (file_read, glob_search, grep_search, system_info) → allow");
|
|
125
|
+
lines.push("3. Settings deny rule matches → BLOCKED");
|
|
126
|
+
lines.push("4. Settings ask rule matches → prompt user");
|
|
127
|
+
lines.push("5. Settings allow rule matches → auto-approve");
|
|
128
|
+
lines.push("6. Previously approved stored rule → auto-approve");
|
|
129
|
+
lines.push("7. Mode fallback (plan blocks writes, acceptEdits allows edits, default prompts user)");
|
|
130
|
+
|
|
131
|
+
return lines.join("\n");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private describeModeEffect(mode: string): string {
|
|
135
|
+
switch (mode) {
|
|
136
|
+
case "default":
|
|
137
|
+
return "Effect: You must get user approval for every write/exec tool call. Read-only tools (file_read, glob_search, grep_search) work freely.";
|
|
138
|
+
case "acceptEdits":
|
|
139
|
+
return "Effect: File writes and edits are auto-approved. Shell commands and file_run still require user approval.";
|
|
140
|
+
case "plan":
|
|
141
|
+
return "Effect: READ-ONLY mode. file_write, file_edit, shell_exec, and file_run are completely blocked. You can only read files and search code.";
|
|
142
|
+
case "dontAsk":
|
|
143
|
+
return "Effect: Everything is denied unless it matches an explicit allow rule in settings. No user prompts are shown.";
|
|
144
|
+
case "bypassPermissions":
|
|
145
|
+
return "Effect: All tools are auto-approved without any prompts. The user has opted into this unsafe mode.";
|
|
146
|
+
default:
|
|
147
|
+
return `Effect: Unknown mode "${mode}" — behaves like default.`;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
private buildSandboxSection(): string {
|
|
152
|
+
const lines: string[] = ["## Sandbox Status", ""];
|
|
153
|
+
|
|
154
|
+
if (!this.sandboxManager || !this.sandboxManager.isEnabled()) {
|
|
155
|
+
lines.push("**Sandbox: DISABLED**");
|
|
156
|
+
lines.push("No filesystem or network restrictions are enforced beyond the permission system.");
|
|
157
|
+
return lines.join("\n");
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const config = this.sandboxManager.getConfig();
|
|
161
|
+
lines.push(`**Sandbox: ENABLED (${config.mode} mode)**`);
|
|
162
|
+
lines.push("");
|
|
163
|
+
|
|
164
|
+
// Mode explanation
|
|
165
|
+
if (config.mode === "auto-allow") {
|
|
166
|
+
lines.push("Mode: **auto-allow** — Commands that pass sandbox checks are auto-approved without a permission prompt.");
|
|
167
|
+
} else {
|
|
168
|
+
lines.push("Mode: **regular** — Sandbox enforces restrictions, but permission prompts are still shown.");
|
|
169
|
+
}
|
|
170
|
+
lines.push("");
|
|
171
|
+
|
|
172
|
+
// Filesystem
|
|
173
|
+
lines.push("### Filesystem Restrictions");
|
|
174
|
+
lines.push("Writes are allowed to: the working directory" +
|
|
175
|
+
(config.filesystem.allowWrite.length ? ` + ${config.filesystem.allowWrite.join(", ")}` : " (only)"));
|
|
176
|
+
|
|
177
|
+
if (config.filesystem.denyWrite.length) {
|
|
178
|
+
lines.push("Writes DENIED to: " + config.filesystem.denyWrite.join(", "));
|
|
179
|
+
}
|
|
180
|
+
if (config.filesystem.denyRead.length) {
|
|
181
|
+
lines.push("Reads DENIED from: " + config.filesystem.denyRead.join(", "));
|
|
182
|
+
}
|
|
183
|
+
if (!config.filesystem.denyWrite.length && !config.filesystem.denyRead.length) {
|
|
184
|
+
lines.push("No explicit deny rules for filesystem paths.");
|
|
185
|
+
}
|
|
186
|
+
lines.push("");
|
|
187
|
+
|
|
188
|
+
// Network
|
|
189
|
+
lines.push("### Network Restrictions");
|
|
190
|
+
if (config.network.allowedDomains.length) {
|
|
191
|
+
lines.push("Allowed domains: " + config.network.allowedDomains.join(", "));
|
|
192
|
+
} else {
|
|
193
|
+
lines.push("No domains pre-allowed.");
|
|
194
|
+
}
|
|
195
|
+
if (config.network.allowManagedDomainsOnly) {
|
|
196
|
+
lines.push("Policy: **strict** — non-allowed domains are blocked silently (no user prompt).");
|
|
197
|
+
} else {
|
|
198
|
+
lines.push("Policy: **prompt** — non-allowed domains trigger a user prompt for approval.");
|
|
199
|
+
}
|
|
200
|
+
lines.push("");
|
|
201
|
+
|
|
202
|
+
// Excluded commands
|
|
203
|
+
if (config.excludedCommands.length) {
|
|
204
|
+
lines.push("### Excluded Commands (bypass sandbox, still need permission)");
|
|
205
|
+
lines.push(config.excludedCommands.join(", "));
|
|
206
|
+
lines.push("");
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Escape hatch
|
|
210
|
+
lines.push(`dangerouslyDisableSandbox: ${config.allowUnsandboxedCommands ? "available (user allows)" : "DISABLED (blocked by user)"}`);
|
|
211
|
+
|
|
212
|
+
return lines.join("\n");
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
private buildToolsSection(): string {
|
|
216
|
+
const tools = this.toolRegistry.getAll();
|
|
217
|
+
const lines: string[] = ["## Available Tools", ""];
|
|
218
|
+
|
|
219
|
+
const permissionRequired: string[] = [];
|
|
220
|
+
const noPermission: string[] = [];
|
|
221
|
+
|
|
222
|
+
for (const tool of tools) {
|
|
223
|
+
const def = tool.definition;
|
|
224
|
+
const entry = `- **${def.name}**: ${def.description.split(".")[0]}.`;
|
|
225
|
+
if (def.requiresPermission) {
|
|
226
|
+
permissionRequired.push(entry);
|
|
227
|
+
} else {
|
|
228
|
+
noPermission.push(entry);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (noPermission.length) {
|
|
233
|
+
lines.push("### Free to use (no permission needed):");
|
|
234
|
+
lines.push(...noPermission);
|
|
235
|
+
lines.push("");
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (permissionRequired.length) {
|
|
239
|
+
lines.push("### Requires permission:");
|
|
240
|
+
lines.push(...permissionRequired);
|
|
241
|
+
lines.push("");
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
lines.push(`Total: ${tools.length} tools (${noPermission.length} free, ${permissionRequired.length} permission-required)`);
|
|
245
|
+
|
|
246
|
+
return lines.join("\n");
|
|
247
|
+
}
|
|
248
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Todo Tool
|
|
3
|
+
*
|
|
4
|
+
* Allows the LLM to create, update, and manage tasks during a session.
|
|
5
|
+
* This helps users see what the agent is working on and track progress.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { BaseTool, ToolDefinition, ToolResult } from "./types";
|
|
9
|
+
import { TodoStore, type TodoStatus } from "../utils/todo";
|
|
10
|
+
|
|
11
|
+
export class TodoTool implements BaseTool {
|
|
12
|
+
definition: ToolDefinition = {
|
|
13
|
+
name: "todo",
|
|
14
|
+
description: `Manage tasks for the current session. Use this to:
|
|
15
|
+
- Create tasks to track what needs to be done
|
|
16
|
+
- Update task status as you work (pending -> in_progress -> completed)
|
|
17
|
+
- Help the user see your progress
|
|
18
|
+
|
|
19
|
+
Actions:
|
|
20
|
+
- create: Create a new task (returns task ID)
|
|
21
|
+
- update: Update a task's status or details
|
|
22
|
+
- list: Show all tasks
|
|
23
|
+
- delete: Remove a task
|
|
24
|
+
|
|
25
|
+
Always mark tasks as in_progress when starting work, and completed when done.`,
|
|
26
|
+
inputSchema: {
|
|
27
|
+
type: "object",
|
|
28
|
+
properties: {
|
|
29
|
+
action: {
|
|
30
|
+
type: "string",
|
|
31
|
+
enum: ["create", "update", "list", "delete"],
|
|
32
|
+
description: "The action to perform",
|
|
33
|
+
},
|
|
34
|
+
subject: {
|
|
35
|
+
type: "string",
|
|
36
|
+
description: "Task title (for create)",
|
|
37
|
+
},
|
|
38
|
+
description: {
|
|
39
|
+
type: "string",
|
|
40
|
+
description: "Task description (for create/update)",
|
|
41
|
+
},
|
|
42
|
+
id: {
|
|
43
|
+
type: "string",
|
|
44
|
+
description: "Task ID (for update/delete)",
|
|
45
|
+
},
|
|
46
|
+
status: {
|
|
47
|
+
type: "string",
|
|
48
|
+
enum: ["pending", "in_progress", "completed", "blocked"],
|
|
49
|
+
description: "Task status (for update)",
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
required: ["action"],
|
|
53
|
+
},
|
|
54
|
+
requiresPermission: false,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
constructor(private todoStore: TodoStore) {}
|
|
58
|
+
|
|
59
|
+
async execute(input: Record<string, unknown>): Promise<ToolResult> {
|
|
60
|
+
const action = input.action as string;
|
|
61
|
+
|
|
62
|
+
switch (action) {
|
|
63
|
+
case "create": {
|
|
64
|
+
const subject = input.subject as string;
|
|
65
|
+
if (!subject) {
|
|
66
|
+
return { success: false, output: "", error: "Subject is required for create" };
|
|
67
|
+
}
|
|
68
|
+
const description = input.description as string | undefined;
|
|
69
|
+
const todo = this.todoStore.create(subject, description);
|
|
70
|
+
return {
|
|
71
|
+
success: true,
|
|
72
|
+
output: `Created task #${todo.id}: ${todo.subject}`,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
case "update": {
|
|
77
|
+
const id = input.id as string;
|
|
78
|
+
if (!id) {
|
|
79
|
+
return { success: false, output: "", error: "Task ID is required for update" };
|
|
80
|
+
}
|
|
81
|
+
const todo = this.todoStore.get(id);
|
|
82
|
+
if (!todo) {
|
|
83
|
+
return { success: false, output: "", error: `Task #${id} not found` };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const updates: Partial<{ subject: string; description: string; status: TodoStatus }> = {};
|
|
87
|
+
if (input.status) updates.status = input.status as TodoStatus;
|
|
88
|
+
if (input.subject) updates.subject = input.subject as string;
|
|
89
|
+
if (input.description) updates.description = input.description as string;
|
|
90
|
+
|
|
91
|
+
const updated = this.todoStore.update(id, updates);
|
|
92
|
+
if (!updated) {
|
|
93
|
+
return { success: false, output: "", error: `Failed to update task #${id}` };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const statusIcon = this.getStatusIcon(updated.status);
|
|
97
|
+
return {
|
|
98
|
+
success: true,
|
|
99
|
+
output: `Updated task #${id}: ${statusIcon} ${updated.subject} [${updated.status}]`,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
case "list": {
|
|
104
|
+
const todos = this.todoStore.getAll();
|
|
105
|
+
if (todos.length === 0) {
|
|
106
|
+
return { success: true, output: "No tasks" };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const lines = todos.map(t => {
|
|
110
|
+
const icon = this.getStatusIcon(t.status);
|
|
111
|
+
const desc = t.description ? ` - ${t.description}` : "";
|
|
112
|
+
return `${icon} #${t.id} [${t.status}] ${t.subject}${desc}`;
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const summary = this.todoStore.getSummary();
|
|
116
|
+
lines.push("");
|
|
117
|
+
lines.push(`Summary: ${summary.completed}/${summary.total} completed`);
|
|
118
|
+
if (summary.in_progress > 0) lines.push(` In progress: ${summary.in_progress}`);
|
|
119
|
+
if (summary.blocked > 0) lines.push(` Blocked: ${summary.blocked}`);
|
|
120
|
+
|
|
121
|
+
return { success: true, output: lines.join("\n") };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
case "delete": {
|
|
125
|
+
const id = input.id as string;
|
|
126
|
+
if (!id) {
|
|
127
|
+
return { success: false, output: "", error: "Task ID is required for delete" };
|
|
128
|
+
}
|
|
129
|
+
const deleted = this.todoStore.delete(id);
|
|
130
|
+
if (!deleted) {
|
|
131
|
+
return { success: false, output: "", error: `Task #${id} not found` };
|
|
132
|
+
}
|
|
133
|
+
return { success: true, output: `Deleted task #${id}` };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
default:
|
|
137
|
+
return { success: false, output: "", error: `Unknown action: ${action}` };
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private getStatusIcon(status: TodoStatus): string {
|
|
142
|
+
switch (status) {
|
|
143
|
+
case "pending": return "[ ]";
|
|
144
|
+
case "in_progress": return "[~]";
|
|
145
|
+
case "completed": return "[x]";
|
|
146
|
+
case "blocked": return "[!]";
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Schema for a tool the agent can call */
|
|
2
|
+
export interface ToolDefinition {
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
inputSchema: Record<string, unknown>; // JSON Schema
|
|
6
|
+
requiresPermission: boolean;
|
|
7
|
+
permissionMessage?: (input: Record<string, unknown>) => string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Result returned after executing a tool */
|
|
11
|
+
export interface ToolResult {
|
|
12
|
+
success: boolean;
|
|
13
|
+
output: string;
|
|
14
|
+
error?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Every tool implements this interface */
|
|
18
|
+
export interface BaseTool {
|
|
19
|
+
definition: ToolDefinition;
|
|
20
|
+
execute(input: Record<string, unknown>): Promise<ToolResult>;
|
|
21
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* View Diff Tool — show git diff (working changes, staged, between commits).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { exec } from "child_process";
|
|
6
|
+
import type { BaseTool, ToolDefinition, ToolResult } from "./types";
|
|
7
|
+
|
|
8
|
+
export class ViewDiffTool implements BaseTool {
|
|
9
|
+
definition: ToolDefinition = {
|
|
10
|
+
name: "view_diff",
|
|
11
|
+
description:
|
|
12
|
+
`Show git diff of changes. Supports working changes, staged changes, and diff between commits/branches.
|
|
13
|
+
|
|
14
|
+
Examples:
|
|
15
|
+
- No args: shows unstaged working changes
|
|
16
|
+
- staged=true: shows staged changes (what would be committed)
|
|
17
|
+
- target="main": diff between current branch and main
|
|
18
|
+
- target="HEAD~3": diff against 3 commits ago
|
|
19
|
+
- file_path="src/app.ts": diff for a specific file only`,
|
|
20
|
+
inputSchema: {
|
|
21
|
+
type: "object",
|
|
22
|
+
properties: {
|
|
23
|
+
staged: {
|
|
24
|
+
type: "boolean",
|
|
25
|
+
description: "Show staged changes instead of working changes. Default: false.",
|
|
26
|
+
},
|
|
27
|
+
target: {
|
|
28
|
+
type: "string",
|
|
29
|
+
description: "Compare against a branch, commit, or ref. E.g. 'main', 'HEAD~1', 'abc123'.",
|
|
30
|
+
},
|
|
31
|
+
file_path: {
|
|
32
|
+
type: "string",
|
|
33
|
+
description: "Limit diff to a specific file or directory.",
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
required: [],
|
|
37
|
+
},
|
|
38
|
+
requiresPermission: false,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
private workingDir: string;
|
|
42
|
+
|
|
43
|
+
constructor(workingDir: string) {
|
|
44
|
+
this.workingDir = workingDir;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async execute(input: Record<string, unknown>): Promise<ToolResult> {
|
|
48
|
+
const staged = (input.staged as boolean) || false;
|
|
49
|
+
const target = (input.target as string) || "";
|
|
50
|
+
const filePath = (input.file_path as string) || "";
|
|
51
|
+
|
|
52
|
+
// Build git diff command
|
|
53
|
+
const parts = ["git", "diff"];
|
|
54
|
+
|
|
55
|
+
if (staged && !target) {
|
|
56
|
+
parts.push("--cached");
|
|
57
|
+
} else if (target) {
|
|
58
|
+
// Sanitize target — only allow safe git ref characters
|
|
59
|
+
if (!/^[a-zA-Z0-9_.\/~^@{}\-]+$/.test(target)) {
|
|
60
|
+
return { success: false, output: "", error: `Invalid git ref: "${target}"` };
|
|
61
|
+
}
|
|
62
|
+
parts.push(target);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
parts.push("--stat", "--patch");
|
|
66
|
+
|
|
67
|
+
if (filePath) {
|
|
68
|
+
parts.push("--", filePath);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const command = parts.join(" ");
|
|
72
|
+
|
|
73
|
+
return new Promise((resolve) => {
|
|
74
|
+
exec(command, { cwd: this.workingDir, timeout: 30000, maxBuffer: 5 * 1024 * 1024 },
|
|
75
|
+
(error, stdout, stderr) => {
|
|
76
|
+
if (error && !stdout) {
|
|
77
|
+
const msg = stderr?.trim() || error.message;
|
|
78
|
+
resolve({ success: false, output: "", error: `git diff failed: ${msg}` });
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const output = stdout.trim();
|
|
83
|
+
if (!output) {
|
|
84
|
+
const context = staged ? "staged" : target ? `vs ${target}` : "working";
|
|
85
|
+
resolve({ success: true, output: `No changes (${context})` });
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Truncate if too large
|
|
90
|
+
const maxLen = 50000;
|
|
91
|
+
const truncated = output.length > maxLen
|
|
92
|
+
? output.substring(0, maxLen) + `\n\n... [truncated at ${maxLen} characters]`
|
|
93
|
+
: output;
|
|
94
|
+
|
|
95
|
+
resolve({ success: true, output: truncated });
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|