c0de-agent 0.0.0 → 1.0.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/LICENSE +21 -0
- package/README.md +101 -0
- package/dist/cli/commands/acp.d.ts +19 -0
- package/dist/cli/commands/acp.js +50 -0
- package/dist/cli/commands/chat.d.ts +13 -0
- package/dist/cli/commands/chat.js +26 -0
- package/dist/cli/commands/config.d.ts +17 -0
- package/dist/cli/commands/config.js +77 -0
- package/dist/cli/commands/init.d.ts +9 -0
- package/dist/cli/commands/init.js +14 -0
- package/dist/cli/commands/plugin.d.ts +11 -0
- package/dist/cli/commands/plugin.js +31 -0
- package/dist/cli/commands/serve.d.ts +18 -0
- package/dist/cli/commands/serve.js +35 -0
- package/dist/cli/commands/update.d.ts +24 -0
- package/dist/cli/commands/update.js +40 -0
- package/dist/cli/deps.d.ts +19 -0
- package/dist/cli/deps.js +57 -0
- package/dist/cli/index.d.ts +8 -0
- package/dist/cli/index.js +149 -0
- package/dist/cli/modes/acp.d.ts +18 -0
- package/dist/cli/modes/acp.js +57 -0
- package/dist/cli/modes/print.d.ts +16 -0
- package/dist/cli/modes/print.js +30 -0
- package/dist/cli/parser.d.ts +19 -0
- package/dist/cli/parser.js +28 -0
- package/dist/cli/utils/output.d.ts +11 -0
- package/dist/cli/utils/output.js +23 -0
- package/dist/cli/utils/prompt.d.ts +12 -0
- package/dist/cli/utils/prompt.js +18 -0
- package/dist/core/agent.d.ts +12 -0
- package/dist/core/agent.js +95 -0
- package/dist/core/agents/builtin.d.ts +6 -0
- package/dist/core/agents/builtin.js +76 -0
- package/dist/core/agents/discovery.d.ts +6 -0
- package/dist/core/agents/discovery.js +96 -0
- package/dist/core/agents/index.d.ts +6 -0
- package/dist/core/agents/index.js +4 -0
- package/dist/core/agents/parallel.d.ts +13 -0
- package/dist/core/agents/parallel.js +37 -0
- package/dist/core/agents/registry.d.ts +4 -0
- package/dist/core/agents/registry.js +23 -0
- package/dist/core/agents/types.d.ts +37 -0
- package/dist/core/agents/types.js +1 -0
- package/dist/core/compact.d.ts +11 -0
- package/dist/core/compact.js +31 -0
- package/dist/core/config.d.ts +7 -0
- package/dist/core/config.js +84 -0
- package/dist/core/context.d.ts +23 -0
- package/dist/core/context.js +93 -0
- package/dist/core/index.d.ts +13 -0
- package/dist/core/index.js +11 -0
- package/dist/core/loop.d.ts +25 -0
- package/dist/core/loop.js +651 -0
- package/dist/core/metrics.d.ts +27 -0
- package/dist/core/metrics.js +94 -0
- package/dist/core/prompt-registry.d.ts +21 -0
- package/dist/core/prompt-registry.js +200 -0
- package/dist/core/prompt.d.ts +11 -0
- package/dist/core/prompt.js +13 -0
- package/dist/core/secret.d.ts +12 -0
- package/dist/core/secret.js +51 -0
- package/dist/core/slash.d.ts +15 -0
- package/dist/core/slash.js +114 -0
- package/dist/core/steering.d.ts +8 -0
- package/dist/core/steering.js +15 -0
- package/dist/core/title.d.ts +42 -0
- package/dist/core/title.js +115 -0
- package/dist/core/tool-exec.d.ts +20 -0
- package/dist/core/tool-exec.js +64 -0
- package/dist/core/types.d.ts +95 -0
- package/dist/core/types.js +1 -0
- package/dist/core/worktree.d.ts +24 -0
- package/dist/core/worktree.js +68 -0
- package/dist/dap/index.d.ts +6 -0
- package/dist/dap/index.js +4 -0
- package/dist/dap/protocol.d.ts +40 -0
- package/dist/dap/protocol.js +132 -0
- package/dist/dap/session.d.ts +23 -0
- package/dist/dap/session.js +130 -0
- package/dist/dap/transport.d.ts +8 -0
- package/dist/dap/transport.js +30 -0
- package/dist/dap/types.d.ts +50 -0
- package/dist/dap/types.js +2 -0
- package/dist/db/client.d.ts +22 -0
- package/dist/db/client.js +39 -0
- package/dist/db/index.d.ts +6 -0
- package/dist/db/index.js +4 -0
- package/dist/db/migrate.d.ts +9 -0
- package/dist/db/migrate.js +15 -0
- package/dist/db/schema.d.ts +965 -0
- package/dist/db/schema.js +112 -0
- package/dist/db/types.d.ts +11 -0
- package/dist/db/types.js +1 -0
- package/dist/llm/index.d.ts +13 -0
- package/dist/llm/index.js +8 -0
- package/dist/llm/protocols/openai-compat.d.ts +139 -0
- package/dist/llm/protocols/openai-compat.js +214 -0
- package/dist/llm/protocols/utils/index.d.ts +2 -0
- package/dist/llm/protocols/utils/index.js +2 -0
- package/dist/llm/protocols/utils/lifecycle.d.ts +23 -0
- package/dist/llm/protocols/utils/lifecycle.js +46 -0
- package/dist/llm/protocols/utils/tool-stream.d.ts +46 -0
- package/dist/llm/protocols/utils/tool-stream.js +94 -0
- package/dist/llm/provider-error.d.ts +5 -0
- package/dist/llm/provider-error.js +33 -0
- package/dist/llm/provider.d.ts +32 -0
- package/dist/llm/provider.js +191 -0
- package/dist/llm/registry.d.ts +44 -0
- package/dist/llm/registry.js +107 -0
- package/dist/llm/retry.d.ts +37 -0
- package/dist/llm/retry.js +82 -0
- package/dist/llm/routing.d.ts +36 -0
- package/dist/llm/routing.js +53 -0
- package/dist/llm/schema/errors.d.ts +88 -0
- package/dist/llm/schema/errors.js +33 -0
- package/dist/llm/schema/events.d.ts +135 -0
- package/dist/llm/schema/events.js +35 -0
- package/dist/llm/schema/ids.d.ts +22 -0
- package/dist/llm/schema/ids.js +3 -0
- package/dist/llm/schema/index.d.ts +5 -0
- package/dist/llm/schema/index.js +5 -0
- package/dist/llm/schema/messages.d.ts +97 -0
- package/dist/llm/schema/messages.js +18 -0
- package/dist/llm/schema/options.d.ts +54 -0
- package/dist/llm/schema/options.js +54 -0
- package/dist/llm/token.d.ts +2 -0
- package/dist/llm/token.js +13 -0
- package/dist/llm/transport.d.ts +31 -0
- package/dist/llm/transport.js +165 -0
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.js +3 -0
- package/dist/plugins/builtin.d.ts +6 -0
- package/dist/plugins/builtin.js +54 -0
- package/dist/plugins/hooks.d.ts +3 -0
- package/dist/plugins/hooks.js +79 -0
- package/dist/plugins/index.d.ts +9 -0
- package/dist/plugins/index.js +8 -0
- package/dist/plugins/init.d.ts +23 -0
- package/dist/plugins/init.js +30 -0
- package/dist/plugins/lifecycle.d.ts +6 -0
- package/dist/plugins/lifecycle.js +74 -0
- package/dist/plugins/loader.d.ts +16 -0
- package/dist/plugins/loader.js +68 -0
- package/dist/plugins/logger.d.ts +4 -0
- package/dist/plugins/logger.js +31 -0
- package/dist/plugins/registry.d.ts +7 -0
- package/dist/plugins/registry.js +24 -0
- package/dist/plugins/types.d.ts +113 -0
- package/dist/plugins/types.js +1 -0
- package/dist/project/detect.d.ts +4 -0
- package/dist/project/detect.js +131 -0
- package/dist/project/index.d.ts +4 -0
- package/dist/project/index.js +2 -0
- package/dist/project/project.d.ts +23 -0
- package/dist/project/project.js +87 -0
- package/dist/project/resolve.d.ts +8 -0
- package/dist/project/resolve.js +81 -0
- package/dist/server/agent-manager.d.ts +32 -0
- package/dist/server/agent-manager.js +54 -0
- package/dist/server/app.d.ts +5 -0
- package/dist/server/app.js +75 -0
- package/dist/server/context.d.ts +20 -0
- package/dist/server/context.js +47 -0
- package/dist/server/dev.d.ts +11 -0
- package/dist/server/dev.js +83 -0
- package/dist/server/index.d.ts +11 -0
- package/dist/server/index.js +7 -0
- package/dist/server/middleware/auth.d.ts +15 -0
- package/dist/server/middleware/auth.js +29 -0
- package/dist/server/middleware/cors.d.ts +16 -0
- package/dist/server/middleware/cors.js +44 -0
- package/dist/server/middleware/error.d.ts +15 -0
- package/dist/server/middleware/error.js +11 -0
- package/dist/server/permission/interactive.d.ts +24 -0
- package/dist/server/permission/interactive.js +56 -0
- package/dist/server/permission/store.d.ts +30 -0
- package/dist/server/permission/store.js +47 -0
- package/dist/server/routes/agent.d.ts +4 -0
- package/dist/server/routes/agent.js +19 -0
- package/dist/server/routes/catalog.d.ts +42 -0
- package/dist/server/routes/catalog.js +194 -0
- package/dist/server/routes/chat.d.ts +8 -0
- package/dist/server/routes/chat.js +282 -0
- package/dist/server/routes/commands.d.ts +5 -0
- package/dist/server/routes/commands.js +17 -0
- package/dist/server/routes/config.d.ts +4 -0
- package/dist/server/routes/config.js +27 -0
- package/dist/server/routes/files.d.ts +4 -0
- package/dist/server/routes/files.js +183 -0
- package/dist/server/routes/filesystem.d.ts +29 -0
- package/dist/server/routes/filesystem.js +108 -0
- package/dist/server/routes/health.d.ts +3 -0
- package/dist/server/routes/health.js +13 -0
- package/dist/server/routes/permissions.d.ts +5 -0
- package/dist/server/routes/permissions.js +23 -0
- package/dist/server/routes/project.d.ts +4 -0
- package/dist/server/routes/project.js +51 -0
- package/dist/server/routes/provider.d.ts +16 -0
- package/dist/server/routes/provider.js +66 -0
- package/dist/server/routes/session.d.ts +4 -0
- package/dist/server/routes/session.js +130 -0
- package/dist/server/routes/tool.d.ts +4 -0
- package/dist/server/routes/tool.js +28 -0
- package/dist/server/routes/update.d.ts +14 -0
- package/dist/server/routes/update.js +49 -0
- package/dist/server/server.d.ts +43 -0
- package/dist/server/server.js +197 -0
- package/dist/server/types.d.ts +73 -0
- package/dist/server/types.js +2 -0
- package/dist/server/util/safe-path.d.ts +7 -0
- package/dist/server/util/safe-path.js +7 -0
- package/dist/session/archive.d.ts +15 -0
- package/dist/session/archive.js +129 -0
- package/dist/session/branch.d.ts +9 -0
- package/dist/session/branch.js +63 -0
- package/dist/session/compaction.d.ts +27 -0
- package/dist/session/compaction.js +192 -0
- package/dist/session/context.d.ts +16 -0
- package/dist/session/context.js +173 -0
- package/dist/session/index.d.ts +10 -0
- package/dist/session/index.js +10 -0
- package/dist/session/message.d.ts +22 -0
- package/dist/session/message.js +145 -0
- package/dist/session/session.d.ts +37 -0
- package/dist/session/session.js +144 -0
- package/dist/session/snapshot.d.ts +11 -0
- package/dist/session/snapshot.js +60 -0
- package/dist/session/squash.d.ts +9 -0
- package/dist/session/squash.js +88 -0
- package/dist/session/token.d.ts +10 -0
- package/dist/session/token.js +33 -0
- package/dist/session/types.d.ts +122 -0
- package/dist/session/types.js +1 -0
- package/dist/shared/index.d.ts +2 -0
- package/dist/shared/index.js +1 -0
- package/dist/shared/types/agent.d.ts +202 -0
- package/dist/shared/types/agent.js +1 -0
- package/dist/shared/types/base.d.ts +32 -0
- package/dist/shared/types/base.js +1 -0
- package/dist/shared/types/config.d.ts +104 -0
- package/dist/shared/types/config.js +1 -0
- package/dist/shared/types/index.d.ts +6 -0
- package/dist/shared/types/index.js +1 -0
- package/dist/shared/types/llm.d.ts +122 -0
- package/dist/shared/types/llm.js +1 -0
- package/dist/shared/types/message.d.ts +71 -0
- package/dist/shared/types/message.js +1 -0
- package/dist/shared/types/tool.d.ts +134 -0
- package/dist/shared/types/tool.js +1 -0
- package/dist/shared/utils/path.d.ts +15 -0
- package/dist/shared/utils/path.js +23 -0
- package/dist/shared/utils.d.ts +5 -0
- package/dist/shared/utils.js +10 -0
- package/dist/tools/builtin/bash.d.ts +12 -0
- package/dist/tools/builtin/bash.js +121 -0
- package/dist/tools/builtin/dap.d.ts +23 -0
- package/dist/tools/builtin/dap.js +231 -0
- package/dist/tools/builtin/edit.d.ts +12 -0
- package/dist/tools/builtin/edit.js +151 -0
- package/dist/tools/builtin/glob.d.ts +11 -0
- package/dist/tools/builtin/glob.js +120 -0
- package/dist/tools/builtin/grep.d.ts +6 -0
- package/dist/tools/builtin/grep.js +141 -0
- package/dist/tools/builtin/read.d.ts +6 -0
- package/dist/tools/builtin/read.js +73 -0
- package/dist/tools/builtin/resolvers.d.ts +12 -0
- package/dist/tools/builtin/resolvers.js +79 -0
- package/dist/tools/builtin/task.d.ts +9 -0
- package/dist/tools/builtin/task.js +100 -0
- package/dist/tools/builtin/write.d.ts +6 -0
- package/dist/tools/builtin/write.js +36 -0
- package/dist/tools/builtin/yield.d.ts +9 -0
- package/dist/tools/builtin/yield.js +40 -0
- package/dist/tools/executor.d.ts +9 -0
- package/dist/tools/executor.js +54 -0
- package/dist/tools/hashline/index.d.ts +2 -0
- package/dist/tools/hashline/index.js +3 -0
- package/dist/tools/hashline/patch.d.ts +48 -0
- package/dist/tools/hashline/patch.js +201 -0
- package/dist/tools/index.d.ts +30 -0
- package/dist/tools/index.js +55 -0
- package/dist/tools/permission.d.ts +13 -0
- package/dist/tools/permission.js +39 -0
- package/dist/tools/registry.d.ts +15 -0
- package/dist/tools/registry.js +54 -0
- package/dist/tools/resolver.d.ts +15 -0
- package/dist/tools/resolver.js +47 -0
- package/dist/tools/truncate.d.ts +8 -0
- package/dist/tools/truncate.js +50 -0
- package/dist/tools/types.d.ts +101 -0
- package/dist/tools/types.js +1 -0
- package/dist/tools/validate.d.ts +8 -0
- package/dist/tools/validate.js +130 -0
- package/dist/tools/websearch/fetch.d.ts +11 -0
- package/dist/tools/websearch/fetch.js +21 -0
- package/dist/tools/websearch/index.d.ts +19 -0
- package/dist/tools/websearch/index.js +78 -0
- package/dist/tools/websearch/providers/brave.d.ts +5 -0
- package/dist/tools/websearch/providers/brave.js +53 -0
- package/dist/tools/websearch/providers/duckduckgo.d.ts +3 -0
- package/dist/tools/websearch/providers/duckduckgo.js +75 -0
- package/dist/tools/websearch/providers/tavily.d.ts +11 -0
- package/dist/tools/websearch/providers/tavily.js +89 -0
- package/dist/tools/websearch/types.d.ts +51 -0
- package/dist/tools/websearch/types.js +18 -0
- package/dist/tools/websearch/websearch.d.ts +10 -0
- package/dist/tools/websearch/websearch.js +48 -0
- package/dist/update/index.d.ts +10 -0
- package/dist/update/index.js +6 -0
- package/dist/update/ipc.d.ts +15 -0
- package/dist/update/ipc.js +41 -0
- package/dist/update/scheduler.d.ts +34 -0
- package/dist/update/scheduler.js +57 -0
- package/dist/update/snapshot.d.ts +39 -0
- package/dist/update/snapshot.js +99 -0
- package/dist/update/updater.d.ts +48 -0
- package/dist/update/updater.js +83 -0
- package/dist/update/version.d.ts +18 -0
- package/dist/update/version.js +49 -0
- package/drizzle/0000_magical_nightshade.sql +55 -0
- package/drizzle/0001_chemical_doctor_faustus.sql +13 -0
- package/drizzle/0002_amusing_micromacro.sql +12 -0
- package/drizzle/0003_regular_purifiers.sql +2 -0
- package/drizzle/meta/0000_snapshot.json +448 -0
- package/drizzle/meta/0001_snapshot.json +539 -0
- package/drizzle/meta/0002_snapshot.json +633 -0
- package/drizzle/meta/0003_snapshot.json +645 -0
- package/drizzle/meta/_journal.json +34 -0
- package/package.json +100 -8
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { readdir } from 'node:fs/promises';
|
|
2
|
+
import { join, relative, resolve } from 'node:path';
|
|
3
|
+
/** Directories always skipped during glob traversal. */
|
|
4
|
+
const IGNORE_DIRS = new Set(['node_modules', '.git', 'dist', '.next', 'build', '.turbo']);
|
|
5
|
+
/**
|
|
6
|
+
* Convert a glob pattern to a RegExp.
|
|
7
|
+
* Supports: * (single segment), ** (across segments), ? (single char), {a,b} (alternation), [abc] (char class).
|
|
8
|
+
*/
|
|
9
|
+
export function globToRegex(pattern) {
|
|
10
|
+
let re = '';
|
|
11
|
+
let i = 0;
|
|
12
|
+
while (i < pattern.length) {
|
|
13
|
+
const c = pattern[i] ?? '';
|
|
14
|
+
if (c === '*') {
|
|
15
|
+
if (pattern[i + 1] === '*') {
|
|
16
|
+
re += '.*';
|
|
17
|
+
i += 2;
|
|
18
|
+
if (pattern[i] === '/')
|
|
19
|
+
i++; // skip separator after **
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
re += '[^/]*';
|
|
23
|
+
i++;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
else if (c === '?') {
|
|
27
|
+
re += '[^/]';
|
|
28
|
+
i++;
|
|
29
|
+
}
|
|
30
|
+
else if (c === '{') {
|
|
31
|
+
const end = pattern.indexOf('}', i);
|
|
32
|
+
if (end === -1) {
|
|
33
|
+
re += '\\{';
|
|
34
|
+
i++;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
const inner = pattern.slice(i + 1, end);
|
|
38
|
+
re += `(?:${inner.split(',').map(escapeRegex).join('|')})`;
|
|
39
|
+
i = end + 1;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else if (c === '[') {
|
|
43
|
+
const end = pattern.indexOf(']', i);
|
|
44
|
+
if (end === -1) {
|
|
45
|
+
re += '\\[';
|
|
46
|
+
i++;
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
re += pattern.slice(i, end + 1);
|
|
50
|
+
i = end + 1;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
else if ('.+^$()|\\'.includes(c)) {
|
|
54
|
+
re += `\\${c}`;
|
|
55
|
+
i++;
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
re += c;
|
|
59
|
+
i++;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return new RegExp(`^${re}$`);
|
|
63
|
+
}
|
|
64
|
+
function escapeRegex(s) {
|
|
65
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
66
|
+
}
|
|
67
|
+
/** Recursively walk a directory, skipping IGNORE_DIRS. Returns relative file paths. */
|
|
68
|
+
async function walkDir(dir, base) {
|
|
69
|
+
const results = [];
|
|
70
|
+
const entries = await readdir(dir, { withFileTypes: true }).catch(() => null);
|
|
71
|
+
if (!entries)
|
|
72
|
+
return [];
|
|
73
|
+
for (const entry of entries) {
|
|
74
|
+
if (entry.isDirectory()) {
|
|
75
|
+
if (IGNORE_DIRS.has(entry.name))
|
|
76
|
+
continue;
|
|
77
|
+
const sub = await walkDir(join(dir, entry.name), base);
|
|
78
|
+
results.push(...sub);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
results.push(relative(base, join(dir, entry.name)));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return results;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* glob tool: find files matching a glob pattern.
|
|
88
|
+
* Permission: auto (read-only).
|
|
89
|
+
*/
|
|
90
|
+
export const globTool = {
|
|
91
|
+
name: 'glob',
|
|
92
|
+
description: 'Find files matching a glob pattern. Supports *, **, ?, {a,b}. Searches recursively, skipping node_modules/.git/dist.',
|
|
93
|
+
parameters: {
|
|
94
|
+
type: 'object',
|
|
95
|
+
properties: {
|
|
96
|
+
pattern: { type: 'string', description: 'Glob pattern (e.g. "src/**/*.ts").' },
|
|
97
|
+
path: { type: 'string', description: 'Base directory to search (default: cwd).' },
|
|
98
|
+
},
|
|
99
|
+
required: ['pattern'],
|
|
100
|
+
},
|
|
101
|
+
permission: 'auto',
|
|
102
|
+
execute: async (input, ctx) => {
|
|
103
|
+
const { pattern, path } = input;
|
|
104
|
+
const basePath = path ? resolve(ctx.cwd, path) : ctx.cwd;
|
|
105
|
+
try {
|
|
106
|
+
const regex = globToRegex(pattern);
|
|
107
|
+
const files = await walkDir(basePath, basePath);
|
|
108
|
+
const matched = files.filter((f) => regex.test(f)).sort();
|
|
109
|
+
return {
|
|
110
|
+
_tag: 'success',
|
|
111
|
+
output: matched.join('\n'),
|
|
112
|
+
metadata: { count: matched.length },
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
117
|
+
return { _tag: 'error', error: `Glob failed: ${message}` };
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { readdir, readFile } from 'node:fs/promises';
|
|
2
|
+
import { join, relative, resolve } from 'node:path';
|
|
3
|
+
/** Directories always skipped during search. */
|
|
4
|
+
const IGNORE_DIRS = new Set(['node_modules', '.git', 'dist', '.next', 'build', '.turbo']);
|
|
5
|
+
/** File extensions treated as text (skip binary files). */
|
|
6
|
+
const TEXT_EXTENSIONS = new Set([
|
|
7
|
+
'.ts',
|
|
8
|
+
'.tsx',
|
|
9
|
+
'.js',
|
|
10
|
+
'.jsx',
|
|
11
|
+
'.mjs',
|
|
12
|
+
'.cjs',
|
|
13
|
+
'.json',
|
|
14
|
+
'.md',
|
|
15
|
+
'.mdx',
|
|
16
|
+
'.txt',
|
|
17
|
+
'.css',
|
|
18
|
+
'.scss',
|
|
19
|
+
'.html',
|
|
20
|
+
'.htm',
|
|
21
|
+
'.xml',
|
|
22
|
+
'.yaml',
|
|
23
|
+
'.yml',
|
|
24
|
+
'.toml',
|
|
25
|
+
'.ini',
|
|
26
|
+
'.env',
|
|
27
|
+
'.sh',
|
|
28
|
+
'.bash',
|
|
29
|
+
'.zsh',
|
|
30
|
+
'.py',
|
|
31
|
+
'.rb',
|
|
32
|
+
'.go',
|
|
33
|
+
'.rs',
|
|
34
|
+
'.java',
|
|
35
|
+
'.kt',
|
|
36
|
+
'.c',
|
|
37
|
+
'.cpp',
|
|
38
|
+
'.h',
|
|
39
|
+
'.hpp',
|
|
40
|
+
'.cs',
|
|
41
|
+
'.php',
|
|
42
|
+
'.swift',
|
|
43
|
+
'.sql',
|
|
44
|
+
'.graphql',
|
|
45
|
+
'.gql',
|
|
46
|
+
'.vue',
|
|
47
|
+
'.svelte',
|
|
48
|
+
'.astro',
|
|
49
|
+
]);
|
|
50
|
+
/** Maximum file size to search (skip files > 1MB). */
|
|
51
|
+
const MAX_FILE_SIZE = 1024 * 1024;
|
|
52
|
+
/** Recursively walk a directory for text files. */
|
|
53
|
+
async function walkForFiles(dir, base) {
|
|
54
|
+
const results = [];
|
|
55
|
+
const entries = await readdir(dir, { withFileTypes: true }).catch(() => null);
|
|
56
|
+
if (!entries)
|
|
57
|
+
return [];
|
|
58
|
+
for (const entry of entries) {
|
|
59
|
+
if (entry.isDirectory()) {
|
|
60
|
+
if (IGNORE_DIRS.has(entry.name))
|
|
61
|
+
continue;
|
|
62
|
+
const sub = await walkForFiles(join(dir, entry.name), base);
|
|
63
|
+
results.push(...sub);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
const ext = entry.name.slice(entry.name.lastIndexOf('.'));
|
|
67
|
+
if (TEXT_EXTENSIONS.has(ext) || ext === '') {
|
|
68
|
+
results.push(join(dir, entry.name));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return results;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* grep tool: search file contents with regex.
|
|
76
|
+
* Permission: auto (read-only).
|
|
77
|
+
*/
|
|
78
|
+
export const grepTool = {
|
|
79
|
+
name: 'grep',
|
|
80
|
+
description: 'Search file contents using regex. Searches recursively across text files, skipping node_modules/.git. Returns matching lines with file and line number.',
|
|
81
|
+
parameters: {
|
|
82
|
+
type: 'object',
|
|
83
|
+
properties: {
|
|
84
|
+
pattern: { type: 'string', description: 'Regular expression pattern.' },
|
|
85
|
+
path: { type: 'string', description: 'Base directory to search (default: cwd).' },
|
|
86
|
+
caseSensitive: { type: 'boolean', description: 'Case-sensitive search (default: true).' },
|
|
87
|
+
maxResults: { type: 'number', description: 'Maximum number of matches to return.' },
|
|
88
|
+
},
|
|
89
|
+
required: ['pattern'],
|
|
90
|
+
},
|
|
91
|
+
permission: 'auto',
|
|
92
|
+
execute: async (input, ctx) => {
|
|
93
|
+
const { pattern, path, caseSensitive = true, maxResults = 200 } = input;
|
|
94
|
+
const basePath = path ? resolve(ctx.cwd, path) : ctx.cwd;
|
|
95
|
+
let regex;
|
|
96
|
+
try {
|
|
97
|
+
regex = new RegExp(pattern, caseSensitive ? '' : 'i');
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
101
|
+
return { _tag: 'error', error: `Invalid regex pattern: ${message}` };
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
const files = await walkForFiles(basePath, basePath);
|
|
105
|
+
const matches = [];
|
|
106
|
+
const max = maxResults;
|
|
107
|
+
outer: for (const filePath of files) {
|
|
108
|
+
const stat = await readFile(filePath);
|
|
109
|
+
if (stat.length > MAX_FILE_SIZE)
|
|
110
|
+
continue;
|
|
111
|
+
const content = stat.toString('utf-8');
|
|
112
|
+
const lines = content.split('\n');
|
|
113
|
+
const relPath = relative(basePath, filePath);
|
|
114
|
+
for (let i = 0; i < lines.length; i++) {
|
|
115
|
+
const line = lines[i] ?? '';
|
|
116
|
+
const match = line.match(regex);
|
|
117
|
+
if (match) {
|
|
118
|
+
matches.push({
|
|
119
|
+
file: relPath,
|
|
120
|
+
line: i + 1,
|
|
121
|
+
text: line.trim(),
|
|
122
|
+
match: match[0] ?? '',
|
|
123
|
+
});
|
|
124
|
+
if (matches.length >= max)
|
|
125
|
+
break outer;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const output = matches.map((m) => `${m.file}:${m.line}: ${m.text}`).join('\n');
|
|
130
|
+
return {
|
|
131
|
+
_tag: 'success',
|
|
132
|
+
output,
|
|
133
|
+
metadata: { count: matches.length, truncated: matches.length >= max },
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
138
|
+
return { _tag: 'error', error: `Grep failed: ${message}` };
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { readdir, readFile, stat } from 'node:fs/promises';
|
|
2
|
+
import { safeResolve } from '../../shared/utils/path.js';
|
|
3
|
+
import { isURLPath, resolveURL } from '../resolver.js';
|
|
4
|
+
/** Apply optional offset/limit (1-indexed) to text content. */
|
|
5
|
+
function applyRange(content, offset, limit) {
|
|
6
|
+
if (offset === undefined && limit === undefined)
|
|
7
|
+
return content;
|
|
8
|
+
const lines = content.split('\n');
|
|
9
|
+
const start = (offset ?? 1) - 1; // convert to 0-indexed
|
|
10
|
+
const end = limit !== undefined ? start + limit : lines.length;
|
|
11
|
+
return lines.slice(start, end).join('\n');
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* read tool: read file content with optional line range.
|
|
15
|
+
* Permission: auto (read-only).
|
|
16
|
+
*/
|
|
17
|
+
export const readTool = {
|
|
18
|
+
name: 'read',
|
|
19
|
+
description: 'Read file content, or list a directory (entries with trailing `/` on subdirectories). Supports optional offset (1-indexed line number) and limit (number of lines) for files. Also resolves internal URL schemes (skill://, agent://, pr://, issue://) when a resolver registry is configured.',
|
|
20
|
+
parameters: {
|
|
21
|
+
type: 'object',
|
|
22
|
+
properties: {
|
|
23
|
+
path: { type: 'string', description: 'File path (relative to cwd or absolute).' },
|
|
24
|
+
offset: { type: 'number', description: 'Starting line number (1-indexed). Default: 1.' },
|
|
25
|
+
limit: { type: 'number', description: 'Maximum number of lines to read.' },
|
|
26
|
+
},
|
|
27
|
+
required: ['path'],
|
|
28
|
+
},
|
|
29
|
+
permission: 'auto',
|
|
30
|
+
execute: async (input, ctx) => {
|
|
31
|
+
const { path, offset, limit } = input;
|
|
32
|
+
// 内部 URL scheme(spec §3.10):skill://, agent://, pr:// 等。
|
|
33
|
+
// 命中时走 resolver 拿到文本内容,再统一应用 offset/limit;目录列举对 URL 无意义。
|
|
34
|
+
if (isURLPath(path)) {
|
|
35
|
+
if (!ctx.urlRegistry) {
|
|
36
|
+
return {
|
|
37
|
+
_tag: 'error',
|
|
38
|
+
error: `Cannot resolve URL "${path}": no resolver registry available`,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
const res = await resolveURL(ctx.urlRegistry, path, { cwd: ctx.cwd, session: ctx.session });
|
|
42
|
+
if (res._tag === 'error')
|
|
43
|
+
return { _tag: 'error', error: res.error };
|
|
44
|
+
return { _tag: 'success', output: applyRange(res.content, offset, limit) };
|
|
45
|
+
}
|
|
46
|
+
const fullPath = safeResolve(ctx.cwd, path);
|
|
47
|
+
if (fullPath === null) {
|
|
48
|
+
return { _tag: 'error', error: `Path "${path}" escapes the working directory` };
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
// 目录:列出条目(子目录加 `/` 后缀),与 system prompt 的「listing a
|
|
52
|
+
// directory → read」契约一致。否则 readFile 遇到目录会抛 EISDIR,
|
|
53
|
+
// 而 prompt 却告诉模型 read 能列目录——模型拿到这个矛盾的错误信号后
|
|
54
|
+
// 会在 glob/read/bash 之间反复试探,最终放弃调用工具而提前 stop。
|
|
55
|
+
const info = await stat(fullPath);
|
|
56
|
+
if (info.isDirectory()) {
|
|
57
|
+
const entries = await readdir(fullPath, { withFileTypes: true });
|
|
58
|
+
if (entries.length === 0) {
|
|
59
|
+
return { _tag: 'success', output: `(empty directory) ${path}` };
|
|
60
|
+
}
|
|
61
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
62
|
+
const lines = entries.map((e) => (e.isDirectory() ? `${e.name}/` : e.name));
|
|
63
|
+
return { _tag: 'success', output: lines.join('\n') };
|
|
64
|
+
}
|
|
65
|
+
const content = await readFile(fullPath, 'utf-8');
|
|
66
|
+
return { _tag: 'success', output: applyRange(content, offset, limit) };
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
70
|
+
return { _tag: 'error', error: `Failed to read "${path}": ${message}` };
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { URLRegistry, URLResolver } from '../../shared/types/tool.js';
|
|
2
|
+
/** file:// 解析器:`file://rel/path` 相对 cwd;`file:///abs/path` 用绝对路径。 */
|
|
3
|
+
declare function createFileResolver(): URLResolver;
|
|
4
|
+
/** skill:// 解析器:技能文件候选路径(项目优先于全局)。 */
|
|
5
|
+
declare function createSkillResolver(opts?: {
|
|
6
|
+
homeDir?: string;
|
|
7
|
+
}): URLResolver;
|
|
8
|
+
/** 内置 URL 解析器注册表:预装 file + skill。 */
|
|
9
|
+
declare function createDefaultURLRegistry(opts?: {
|
|
10
|
+
homeDir?: string;
|
|
11
|
+
}): URLRegistry;
|
|
12
|
+
export { createDefaultURLRegistry, createFileResolver, createSkillResolver };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// 内置 URL 解析器(spec §3.10)。
|
|
2
|
+
//
|
|
3
|
+
// registry 框架(createURLRegistry / registerURLResolver / resolveURL 分发)见
|
|
4
|
+
// ../resolver.ts。这里实现具体 scheme 的内容来源:
|
|
5
|
+
// - file:// → 本地文件(相对 ctx.cwd 或绝对路径)
|
|
6
|
+
// - skill:// → 技能文件,项目 .c0de/skills/<name>(.md|/SKILL.md) 优先于
|
|
7
|
+
// 全局 ~/.c0de/skills/<name>(.md|/SKILL.md)
|
|
8
|
+
//
|
|
9
|
+
// agent:// pr:// issue:// 依赖未实现的子 agent 输出与 GitHub 访问,暂不内置。
|
|
10
|
+
import { readFile } from 'node:fs/promises';
|
|
11
|
+
import { homedir } from 'node:os';
|
|
12
|
+
import { resolve as resolvePath } from 'node:path';
|
|
13
|
+
import { createURLRegistry, registerURLResolver } from '../resolver.js';
|
|
14
|
+
/** 从 `scheme://rest` 中取出 rest(scheme 已校验过,直接切首个 `://`)。 */
|
|
15
|
+
function stripScheme(url) {
|
|
16
|
+
const idx = url.indexOf('://');
|
|
17
|
+
return idx < 0 ? url : url.slice(idx + 3);
|
|
18
|
+
}
|
|
19
|
+
/** 读文件,读不到抛错(由 resolveURL 包装成 error result)。 */
|
|
20
|
+
async function readOrFail(path) {
|
|
21
|
+
return readFile(path, 'utf-8');
|
|
22
|
+
}
|
|
23
|
+
/** file:// 解析器:`file://rel/path` 相对 cwd;`file:///abs/path` 用绝对路径。 */
|
|
24
|
+
function createFileResolver() {
|
|
25
|
+
return {
|
|
26
|
+
scheme: 'file',
|
|
27
|
+
resolve: async (url, ctx) => {
|
|
28
|
+
const rest = stripScheme(url);
|
|
29
|
+
// file:///abs → rest 形如 /abs(已含前导斜杠);file://rel → rest 形如 rel。
|
|
30
|
+
const path = resolvePath(ctx.cwd, rest);
|
|
31
|
+
return readOrFail(path);
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/** 按优先级返回第一个可读候选文件的路径,全部缺失时返回 null。 */
|
|
36
|
+
async function firstExistingFile(candidates) {
|
|
37
|
+
for (const c of candidates) {
|
|
38
|
+
try {
|
|
39
|
+
// 用 readFile 探测:能读到即返回。失败的候选静默跳过(ENOENT 等)。
|
|
40
|
+
await readFile(c, 'utf-8');
|
|
41
|
+
return c;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// 继续尝试下一个候选路径
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
/** skill:// 解析器:技能文件候选路径(项目优先于全局)。 */
|
|
50
|
+
function createSkillResolver(opts) {
|
|
51
|
+
const home = opts?.homeDir ?? homedir();
|
|
52
|
+
return {
|
|
53
|
+
scheme: 'skill',
|
|
54
|
+
resolve: async (url, ctx) => {
|
|
55
|
+
const name = stripScheme(url);
|
|
56
|
+
if (!name)
|
|
57
|
+
throw new Error('skill URL requires a name, e.g. skill://brainstorming');
|
|
58
|
+
const candidates = [
|
|
59
|
+
resolvePath(ctx.cwd, '.c0de', 'skills', `${name}.md`),
|
|
60
|
+
resolvePath(ctx.cwd, '.c0de', 'skills', name, 'SKILL.md'),
|
|
61
|
+
resolvePath(home, '.c0de', 'skills', `${name}.md`),
|
|
62
|
+
resolvePath(home, '.c0de', 'skills', name, 'SKILL.md'),
|
|
63
|
+
];
|
|
64
|
+
const found = await firstExistingFile(candidates);
|
|
65
|
+
if (!found) {
|
|
66
|
+
throw new Error(`Skill "${name}" not found. Looked in:\n${candidates.map((c) => ` - ${c}`).join('\n')}`);
|
|
67
|
+
}
|
|
68
|
+
return readOrFail(found);
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/** 内置 URL 解析器注册表:预装 file + skill。 */
|
|
73
|
+
function createDefaultURLRegistry(opts) {
|
|
74
|
+
const reg = createURLRegistry();
|
|
75
|
+
registerURLResolver(reg, createFileResolver());
|
|
76
|
+
registerURLResolver(reg, createSkillResolver(opts));
|
|
77
|
+
return reg;
|
|
78
|
+
}
|
|
79
|
+
export { createDefaultURLRegistry, createFileResolver, createSkillResolver };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ToolDef } from '../../shared/types/tool.js';
|
|
2
|
+
/**
|
|
3
|
+
* task 工具:按 agent 类型派发子 agent。
|
|
4
|
+
*
|
|
5
|
+
* 两种形态:单任务(subagent_type + prompt)或批量并行(subagent_type + context + tasks[])。
|
|
6
|
+
* 依赖反转:实际子 agent 运行由 host(core loop 的 runSubAgent)通过 ctx.runSubAgent 执行。
|
|
7
|
+
* permission: auto(子 agent 是隔离 session,不扩宽父权限)。
|
|
8
|
+
*/
|
|
9
|
+
export declare const taskTool: ToolDef;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* task 工具:按 agent 类型派发子 agent。
|
|
3
|
+
*
|
|
4
|
+
* 两种形态:单任务(subagent_type + prompt)或批量并行(subagent_type + context + tasks[])。
|
|
5
|
+
* 依赖反转:实际子 agent 运行由 host(core loop 的 runSubAgent)通过 ctx.runSubAgent 执行。
|
|
6
|
+
* permission: auto(子 agent 是隔离 session,不扩宽父权限)。
|
|
7
|
+
*/
|
|
8
|
+
export const taskTool = {
|
|
9
|
+
name: 'task',
|
|
10
|
+
description: 'Launch specialized sub-agents to handle delegated tasks. Specify subagent_type to select a specialist (e.g. researcher for read-only investigation, coder for implementation, reviewer for code review). Launch multiple agents concurrently by using the batch form with tasks[]. The sub-agent runs in an isolated session with its own context. When done, the sub-agent returns its result via the yield tool.',
|
|
11
|
+
parameters: {
|
|
12
|
+
type: 'object',
|
|
13
|
+
properties: {
|
|
14
|
+
subagent_type: {
|
|
15
|
+
type: 'string',
|
|
16
|
+
description: "The specialist agent type (e.g. researcher, coder, reviewer). Defaults to 'general'.",
|
|
17
|
+
},
|
|
18
|
+
description: { type: 'string', description: 'Short label for the task (display only).' },
|
|
19
|
+
prompt: { type: 'string', description: 'Self-contained assignment (single-task mode).' },
|
|
20
|
+
model: { type: 'string', description: 'Optional model override.' },
|
|
21
|
+
background: { type: 'boolean', description: 'Run in background (default false).' },
|
|
22
|
+
context: { type: 'string', description: 'Shared context (batch mode).' },
|
|
23
|
+
tasks: {
|
|
24
|
+
type: 'array',
|
|
25
|
+
items: {
|
|
26
|
+
type: 'object',
|
|
27
|
+
properties: {
|
|
28
|
+
description: { type: 'string' },
|
|
29
|
+
role: { type: 'string', description: 'Specialist role for this sub-task.' },
|
|
30
|
+
assignment: { type: 'string' },
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
description: 'Parallel sub-tasks (batch mode).',
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
required: ['prompt'],
|
|
37
|
+
anyOf: [{ required: ['prompt'] }, { required: ['context', 'tasks'] }],
|
|
38
|
+
},
|
|
39
|
+
permission: 'auto',
|
|
40
|
+
execute: async (input, ctx) => {
|
|
41
|
+
if (!ctx.runSubAgent) {
|
|
42
|
+
return {
|
|
43
|
+
_tag: 'error',
|
|
44
|
+
error: 'task tool unavailable: no sub-agent runner is wired into this context',
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const inp = input;
|
|
48
|
+
// 批量模式
|
|
49
|
+
if ('tasks' in inp && Array.isArray(inp.tasks) && inp.tasks.length > 0) {
|
|
50
|
+
const agentType = inp.subagent_type ?? 'general';
|
|
51
|
+
const results = [];
|
|
52
|
+
for (const item of inp.tasks) {
|
|
53
|
+
const req = {
|
|
54
|
+
agentType,
|
|
55
|
+
prompt: item.assignment,
|
|
56
|
+
...(item.description ? { description: item.description } : {}),
|
|
57
|
+
...(item.role ? { role: item.role } : {}),
|
|
58
|
+
...(inp.context ? { context: inp.context } : {}),
|
|
59
|
+
};
|
|
60
|
+
const res = await ctx.runSubAgent(req);
|
|
61
|
+
if (res._tag === 'error') {
|
|
62
|
+
return { _tag: 'error', error: `Sub-agent failed: ${res.error}` };
|
|
63
|
+
}
|
|
64
|
+
const label = item.description ?? item.role ?? 'task';
|
|
65
|
+
if (res._tag === 'running') {
|
|
66
|
+
results.push(`[${label}] background started (jobId: ${res.jobId})`);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
results.push(`[${label}] ${res.output}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return { _tag: 'success', output: results.join('\n\n') };
|
|
73
|
+
}
|
|
74
|
+
// 单任务模式
|
|
75
|
+
const single = inp;
|
|
76
|
+
const req = {
|
|
77
|
+
agentType: single.subagent_type ?? 'general',
|
|
78
|
+
prompt: single.prompt,
|
|
79
|
+
...(single.description ? { description: single.description } : {}),
|
|
80
|
+
...(single.model ? { model: single.model } : {}),
|
|
81
|
+
...(single.background ? { background: true } : {}),
|
|
82
|
+
};
|
|
83
|
+
const result = await ctx.runSubAgent(req);
|
|
84
|
+
if (result._tag === 'error') {
|
|
85
|
+
return { _tag: 'error', error: `Sub-agent failed: ${result.error}` };
|
|
86
|
+
}
|
|
87
|
+
if (result._tag === 'running') {
|
|
88
|
+
return {
|
|
89
|
+
_tag: 'success',
|
|
90
|
+
output: `Background task started (jobId: ${result.jobId}). You will be notified on completion.`,
|
|
91
|
+
metadata: { sessionId: result.sessionId, background: true, jobId: result.jobId },
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
_tag: 'success',
|
|
96
|
+
output: result.output,
|
|
97
|
+
metadata: { sessionId: result.sessionId, ...(result.data ? { data: result.data } : {}) },
|
|
98
|
+
};
|
|
99
|
+
},
|
|
100
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
import { safeResolve } from '../../shared/utils/path.js';
|
|
4
|
+
/**
|
|
5
|
+
* write tool: create or overwrite a file.
|
|
6
|
+
* Permission: ask (modifies filesystem).
|
|
7
|
+
*/
|
|
8
|
+
export const writeTool = {
|
|
9
|
+
name: 'write',
|
|
10
|
+
description: 'Create or overwrite a file with the given content. Creates parent directories.',
|
|
11
|
+
parameters: {
|
|
12
|
+
type: 'object',
|
|
13
|
+
properties: {
|
|
14
|
+
path: { type: 'string', description: 'File path (relative to cwd or absolute).' },
|
|
15
|
+
content: { type: 'string', description: 'Content to write.' },
|
|
16
|
+
},
|
|
17
|
+
required: ['path', 'content'],
|
|
18
|
+
},
|
|
19
|
+
permission: 'ask',
|
|
20
|
+
execute: async (input, ctx) => {
|
|
21
|
+
const { path, content } = input;
|
|
22
|
+
const fullPath = safeResolve(ctx.cwd, path);
|
|
23
|
+
if (fullPath === null) {
|
|
24
|
+
return { _tag: 'error', error: `Path "${path}" escapes the working directory` };
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
await mkdir(dirname(fullPath), { recursive: true });
|
|
28
|
+
await writeFile(fullPath, content, 'utf-8');
|
|
29
|
+
return { _tag: 'success', output: `Wrote ${content.length} bytes to ${path}` };
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
33
|
+
return { _tag: 'error', error: `Failed to write "${path}": ${message}` };
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ToolDef } from '../../shared/types/tool.js';
|
|
2
|
+
/**
|
|
3
|
+
* yield 工具:子 agent 专用,提交结构化最终结果。
|
|
4
|
+
*
|
|
5
|
+
* 这是子 agent 返回结果的唯一方式。调用后,runSubAgent 的 collectYield
|
|
6
|
+
* 回调收集结果,子 agent loop 检测到 yield 后优雅终止。
|
|
7
|
+
* 主 agent 不注册此工具。permission: auto(纯结果收集,不改外部状态)。
|
|
8
|
+
*/
|
|
9
|
+
export declare const yieldTool: ToolDef;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* yield 工具:子 agent 专用,提交结构化最终结果。
|
|
3
|
+
*
|
|
4
|
+
* 这是子 agent 返回结果的唯一方式。调用后,runSubAgent 的 collectYield
|
|
5
|
+
* 回调收集结果,子 agent loop 检测到 yield 后优雅终止。
|
|
6
|
+
* 主 agent 不注册此工具。permission: auto(纯结果收集,不改外部状态)。
|
|
7
|
+
*/
|
|
8
|
+
export const yieldTool = {
|
|
9
|
+
name: 'yield',
|
|
10
|
+
description: 'Submit your final structured result. This is the ONLY way to return a result from a sub-agent task. Call this once when your work is complete.',
|
|
11
|
+
parameters: {
|
|
12
|
+
type: 'object',
|
|
13
|
+
properties: {
|
|
14
|
+
data: {
|
|
15
|
+
type: 'object',
|
|
16
|
+
description: 'Your structured result. Must match the outputSchema if the agent declared one.',
|
|
17
|
+
},
|
|
18
|
+
type: {
|
|
19
|
+
type: 'string',
|
|
20
|
+
description: 'Optional section label for incremental yields.',
|
|
21
|
+
},
|
|
22
|
+
status: {
|
|
23
|
+
type: 'string',
|
|
24
|
+
enum: ['success', 'aborted'],
|
|
25
|
+
description: 'Outcome status. Use "aborted" if blocked.',
|
|
26
|
+
},
|
|
27
|
+
error: {
|
|
28
|
+
type: 'string',
|
|
29
|
+
description: 'If blocked (status=aborted), describe what you tried and the exact blocker.',
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
required: ['data'],
|
|
33
|
+
},
|
|
34
|
+
permission: 'auto',
|
|
35
|
+
execute: async (input, ctx) => {
|
|
36
|
+
const { data } = input;
|
|
37
|
+
ctx.collectYield?.(data);
|
|
38
|
+
return { _tag: 'success', output: 'Result submitted.' };
|
|
39
|
+
},
|
|
40
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ToolContext, ToolResult } from '../shared/types/tool.js';
|
|
2
|
+
import type { PermissionChecker, ToolRegistry } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Execute a tool by name with full pipeline:
|
|
5
|
+
* find tool → validate input → check permission → execute → truncate output.
|
|
6
|
+
*
|
|
7
|
+
* Returns the ToolResult. Never throws — all errors become { _tag: 'error' }.
|
|
8
|
+
*/
|
|
9
|
+
export declare function executeTool(registry: ToolRegistry, name: string, input: unknown, ctx: ToolContext, permissionChecker: PermissionChecker): Promise<ToolResult>;
|