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,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 安全路径解析:确保 requestPath 解析后落在 root 内,防止路径穿越
|
|
3
|
+
* (如 `../etc/passwd`、绝对路径 `/etc/passwd`)。
|
|
4
|
+
*
|
|
5
|
+
* - `resolve(root, requestPath)` 解析后,若结果不在 root 子树内则返回 null。
|
|
6
|
+
* - 判定逻辑:`relative(root, resolved)` 以 `..` 开头即越界;
|
|
7
|
+
* 再用 `resolve(root, rel) === resolved` 兜底,防止相对段恰好名为 root 的边角情况。
|
|
8
|
+
*
|
|
9
|
+
* 返回解析后的绝对路径;越界返回 null。
|
|
10
|
+
*
|
|
11
|
+
* 注意:本函数基于 `relative`,不解析符号链接(symlink)。若 root 内存在指向
|
|
12
|
+
* root 外的符号链接,本函数无法阻止经由该链接的逃逸——symlink 逃逸不在本次范围内。
|
|
13
|
+
*/
|
|
14
|
+
declare function safeResolve(root: string, requestPath: string): string | null;
|
|
15
|
+
export { safeResolve };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { relative, resolve } from 'node:path';
|
|
2
|
+
/**
|
|
3
|
+
* 安全路径解析:确保 requestPath 解析后落在 root 内,防止路径穿越
|
|
4
|
+
* (如 `../etc/passwd`、绝对路径 `/etc/passwd`)。
|
|
5
|
+
*
|
|
6
|
+
* - `resolve(root, requestPath)` 解析后,若结果不在 root 子树内则返回 null。
|
|
7
|
+
* - 判定逻辑:`relative(root, resolved)` 以 `..` 开头即越界;
|
|
8
|
+
* 再用 `resolve(root, rel) === resolved` 兜底,防止相对段恰好名为 root 的边角情况。
|
|
9
|
+
*
|
|
10
|
+
* 返回解析后的绝对路径;越界返回 null。
|
|
11
|
+
*
|
|
12
|
+
* 注意:本函数基于 `relative`,不解析符号链接(symlink)。若 root 内存在指向
|
|
13
|
+
* root 外的符号链接,本函数无法阻止经由该链接的逃逸——symlink 逃逸不在本次范围内。
|
|
14
|
+
*/
|
|
15
|
+
function safeResolve(root, requestPath) {
|
|
16
|
+
const resolved = resolve(root, requestPath);
|
|
17
|
+
const rel = relative(root, resolved);
|
|
18
|
+
if (rel.startsWith('..') || resolve(root, rel) !== resolved) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
return resolved;
|
|
22
|
+
}
|
|
23
|
+
export { safeResolve };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
/** Generate a UUID v4 string. */
|
|
3
|
+
function generateId() {
|
|
4
|
+
return randomUUID();
|
|
5
|
+
}
|
|
6
|
+
/** Current timestamp in milliseconds since epoch. */
|
|
7
|
+
function now() {
|
|
8
|
+
return Date.now();
|
|
9
|
+
}
|
|
10
|
+
export { generateId, now };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ToolDef } from '../../shared/types/tool.js';
|
|
2
|
+
/**
|
|
3
|
+
* bash tool: execute a shell command synchronously.
|
|
4
|
+
* Permission: ask (can modify filesystem, run arbitrary code).
|
|
5
|
+
*
|
|
6
|
+
* Features:
|
|
7
|
+
* - Merges stdout + stderr
|
|
8
|
+
* - Process tree kill on abort
|
|
9
|
+
* - Timeout kills the process tree
|
|
10
|
+
* - Returns exit code in metadata
|
|
11
|
+
*/
|
|
12
|
+
export declare const bashTool: ToolDef;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
/** Default timeout: 120 seconds. */
|
|
4
|
+
const DEFAULT_TIMEOUT = 120_000;
|
|
5
|
+
/** Kill an entire process tree (the child and all its descendants). */
|
|
6
|
+
function killProcessTree(child) {
|
|
7
|
+
try {
|
|
8
|
+
// On Linux/macOS, negative PID kills the process group.
|
|
9
|
+
// We use `detached: true` at spawn to create a new process group.
|
|
10
|
+
if (child.pid) {
|
|
11
|
+
process.kill(-child.pid, 'SIGKILL');
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
// Process may have already exited — ignore
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* bash tool: execute a shell command synchronously.
|
|
20
|
+
* Permission: ask (can modify filesystem, run arbitrary code).
|
|
21
|
+
*
|
|
22
|
+
* Features:
|
|
23
|
+
* - Merges stdout + stderr
|
|
24
|
+
* - Process tree kill on abort
|
|
25
|
+
* - Timeout kills the process tree
|
|
26
|
+
* - Returns exit code in metadata
|
|
27
|
+
*/
|
|
28
|
+
export const bashTool = {
|
|
29
|
+
name: 'bash',
|
|
30
|
+
description: 'Execute a shell command. Merges stdout+stderr. Supports custom cwd, env, and timeout (default 120s). Returns exit code in metadata.',
|
|
31
|
+
parameters: {
|
|
32
|
+
type: 'object',
|
|
33
|
+
properties: {
|
|
34
|
+
command: { type: 'string', description: 'Shell command to execute.' },
|
|
35
|
+
cwd: { type: 'string', description: 'Working directory (default: ctx.cwd).' },
|
|
36
|
+
timeout: { type: 'number', description: 'Timeout in milliseconds (default: 120000).' },
|
|
37
|
+
env: {
|
|
38
|
+
type: 'object',
|
|
39
|
+
description: 'Additional environment variables.',
|
|
40
|
+
additionalProperties: { type: 'string' },
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
required: ['command'],
|
|
44
|
+
},
|
|
45
|
+
permission: 'ask',
|
|
46
|
+
execute: async (input, ctx) => {
|
|
47
|
+
const { command, cwd, timeout = DEFAULT_TIMEOUT, env } = input;
|
|
48
|
+
const workDir = cwd ? resolve(ctx.cwd, cwd) : ctx.cwd;
|
|
49
|
+
return new Promise((resolvePromise) => {
|
|
50
|
+
const childEnv = { ...process.env, ...env };
|
|
51
|
+
const child = spawn(command, {
|
|
52
|
+
cwd: workDir,
|
|
53
|
+
shell: true,
|
|
54
|
+
env: childEnv,
|
|
55
|
+
detached: true,
|
|
56
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
57
|
+
});
|
|
58
|
+
let stdout = '';
|
|
59
|
+
let stderr = '';
|
|
60
|
+
let timedOut = false;
|
|
61
|
+
child.stdout?.on('data', (data) => {
|
|
62
|
+
stdout += data.toString();
|
|
63
|
+
});
|
|
64
|
+
child.stderr?.on('data', (data) => {
|
|
65
|
+
stderr += data.toString();
|
|
66
|
+
});
|
|
67
|
+
// Timeout handler
|
|
68
|
+
const timer = setTimeout(() => {
|
|
69
|
+
timedOut = true;
|
|
70
|
+
killProcessTree(child);
|
|
71
|
+
}, timeout);
|
|
72
|
+
// Abort handler
|
|
73
|
+
const onAbort = () => {
|
|
74
|
+
clearTimeout(timer);
|
|
75
|
+
killProcessTree(child);
|
|
76
|
+
};
|
|
77
|
+
if (ctx.abort.aborted) {
|
|
78
|
+
killProcessTree(child);
|
|
79
|
+
resolvePromise({ _tag: 'error', error: 'Operation aborted before execution' });
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
ctx.abort.addEventListener('abort', onAbort, { once: true });
|
|
83
|
+
child.on('close', (code) => {
|
|
84
|
+
clearTimeout(timer);
|
|
85
|
+
ctx.abort.removeEventListener('abort', onAbort);
|
|
86
|
+
if (ctx.abort.aborted) {
|
|
87
|
+
resolvePromise({ _tag: 'error', error: 'Command aborted by user' });
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (timedOut) {
|
|
91
|
+
resolvePromise({
|
|
92
|
+
_tag: 'error',
|
|
93
|
+
error: `Command timeout after ${timeout}ms\nPartial output:\n${stdout}${stderr}`,
|
|
94
|
+
});
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const output = stdout + (stderr ? `\n${stderr}` : '');
|
|
98
|
+
if (code !== null && code !== 0) {
|
|
99
|
+
resolvePromise({
|
|
100
|
+
_tag: 'error',
|
|
101
|
+
error: `Command failed with exit code: ${code}\n${output}`,
|
|
102
|
+
});
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
resolvePromise({
|
|
106
|
+
_tag: 'success',
|
|
107
|
+
output: output || '(no output)',
|
|
108
|
+
metadata: { exitCode: code ?? 0 },
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
child.on('error', (err) => {
|
|
112
|
+
clearTimeout(timer);
|
|
113
|
+
ctx.abort.removeEventListener('abort', onAbort);
|
|
114
|
+
resolvePromise({
|
|
115
|
+
_tag: 'error',
|
|
116
|
+
error: `Failed to spawn command: ${err.message}`,
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
},
|
|
121
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { encodeMessage } from '../../dap/protocol.js';
|
|
2
|
+
import type { ToolDef } from '../../shared/types/tool.js';
|
|
3
|
+
/**
|
|
4
|
+
* debug_* 工具(spec §21):暴露调试器能力给 agent。
|
|
5
|
+
*
|
|
6
|
+
* 依赖反转(同 task 工具):调试适配器的 spawn 由 host 通过
|
|
7
|
+
* `ctx.debugSpawn` 注入,本包不依赖 child_process,也不依赖 core。
|
|
8
|
+
* 无注入时 debug_start 返回 error。
|
|
9
|
+
*
|
|
10
|
+
* 模块级 manager 持有活跃会话;sessionId 由 debug_start 返回,agent
|
|
11
|
+
* 将其传给后续工具。
|
|
12
|
+
*/
|
|
13
|
+
declare const manager: import("../../dap/session.js").DebugSessionManager;
|
|
14
|
+
declare const debugStartTool: ToolDef;
|
|
15
|
+
declare const debugBreakpointTool: ToolDef;
|
|
16
|
+
declare const debugContinueTool: ToolDef;
|
|
17
|
+
declare const debugStepTool: ToolDef;
|
|
18
|
+
declare const debugStackTool: ToolDef;
|
|
19
|
+
declare const debugVarsTool: ToolDef;
|
|
20
|
+
declare const debugEvalTool: ToolDef;
|
|
21
|
+
declare const debugStopTool: ToolDef;
|
|
22
|
+
declare const dapTools: ToolDef[];
|
|
23
|
+
export { dapTools, debugBreakpointTool, debugContinueTool, debugEvalTool, debugStackTool, debugStartTool, debugStepTool, debugStopTool, debugVarsTool, encodeMessage, manager, };
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { encodeMessage } from '../../dap/protocol.js';
|
|
2
|
+
import { createDebugSessionManager } from '../../dap/session.js';
|
|
3
|
+
/**
|
|
4
|
+
* debug_* 工具(spec §21):暴露调试器能力给 agent。
|
|
5
|
+
*
|
|
6
|
+
* 依赖反转(同 task 工具):调试适配器的 spawn 由 host 通过
|
|
7
|
+
* `ctx.debugSpawn` 注入,本包不依赖 child_process,也不依赖 core。
|
|
8
|
+
* 无注入时 debug_start 返回 error。
|
|
9
|
+
*
|
|
10
|
+
* 模块级 manager 持有活跃会话;sessionId 由 debug_start 返回,agent
|
|
11
|
+
* 将其传给后续工具。
|
|
12
|
+
*/
|
|
13
|
+
const manager = createDebugSessionManager();
|
|
14
|
+
function ok(output) {
|
|
15
|
+
return {
|
|
16
|
+
_tag: 'success',
|
|
17
|
+
output: typeof output === 'string' ? output : JSON.stringify(output, null, 2),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function fail(message) {
|
|
21
|
+
return { _tag: 'error', error: message };
|
|
22
|
+
}
|
|
23
|
+
// debug_start — 启动调试会话(权限 ask:启动调试器是重操作)
|
|
24
|
+
const debugStartTool = {
|
|
25
|
+
name: 'debug_start',
|
|
26
|
+
description: 'Start a debug session by launching a debug adapter and the target program. Returns { sessionId, threadId }. Other debug_* tools take the sessionId.',
|
|
27
|
+
parameters: {
|
|
28
|
+
type: 'object',
|
|
29
|
+
properties: {
|
|
30
|
+
adapter: { type: 'string', description: 'Debug adapter id, e.g. "node", "python".' },
|
|
31
|
+
program: { type: 'string', description: 'Path to the program to debug.' },
|
|
32
|
+
args: { type: 'array', items: { type: 'string' }, description: 'Program arguments.' },
|
|
33
|
+
cwd: { type: 'string', description: 'Working directory for the program.' },
|
|
34
|
+
stopOnEntry: { type: 'boolean', description: 'Break on entry (default false).' },
|
|
35
|
+
},
|
|
36
|
+
required: ['adapter', 'program'],
|
|
37
|
+
},
|
|
38
|
+
permission: 'ask',
|
|
39
|
+
execute: async (input, ctx) => {
|
|
40
|
+
if (!ctx.debugSpawn) {
|
|
41
|
+
return fail('debug_start unavailable: no debug adapter spawn is wired into this context');
|
|
42
|
+
}
|
|
43
|
+
const i = input;
|
|
44
|
+
const config = {
|
|
45
|
+
adapter: i.adapter,
|
|
46
|
+
program: i.program,
|
|
47
|
+
args: i.args,
|
|
48
|
+
cwd: i.cwd,
|
|
49
|
+
launchArgs: { stopOnEntry: i.stopOnEntry ?? false },
|
|
50
|
+
};
|
|
51
|
+
try {
|
|
52
|
+
const spawn = ctx.debugSpawn;
|
|
53
|
+
const result = await manager.start(spawn, config);
|
|
54
|
+
return ok(result);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
return fail(`debug_start failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
// debug_breakpoint — 设置断点(auto)
|
|
62
|
+
const debugBreakpointTool = {
|
|
63
|
+
name: 'debug_breakpoint',
|
|
64
|
+
description: 'Set a breakpoint at a file:line (optional condition).',
|
|
65
|
+
parameters: {
|
|
66
|
+
type: 'object',
|
|
67
|
+
properties: {
|
|
68
|
+
sessionId: { type: 'string' },
|
|
69
|
+
file: { type: 'string' },
|
|
70
|
+
line: { type: 'number' },
|
|
71
|
+
condition: { type: 'string', description: 'Optional hit condition expression.' },
|
|
72
|
+
},
|
|
73
|
+
required: ['sessionId', 'file', 'line'],
|
|
74
|
+
},
|
|
75
|
+
permission: 'auto',
|
|
76
|
+
execute: async (input) => {
|
|
77
|
+
const { sessionId, file, line, condition } = input;
|
|
78
|
+
const bp = { file, line, ...(condition ? { condition } : {}) };
|
|
79
|
+
try {
|
|
80
|
+
const result = await manager.setBreakpoint(sessionId, bp);
|
|
81
|
+
return ok(result);
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
return fail(`debug_breakpoint failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
// debug_continue — 继续执行(auto)
|
|
89
|
+
const debugContinueTool = {
|
|
90
|
+
name: 'debug_continue',
|
|
91
|
+
description: 'Continue execution of the given thread until the next stop.',
|
|
92
|
+
parameters: {
|
|
93
|
+
type: 'object',
|
|
94
|
+
properties: { sessionId: { type: 'string' }, threadId: { type: 'number' } },
|
|
95
|
+
required: ['sessionId', 'threadId'],
|
|
96
|
+
},
|
|
97
|
+
permission: 'auto',
|
|
98
|
+
execute: async (input) => {
|
|
99
|
+
const { sessionId, threadId } = input;
|
|
100
|
+
try {
|
|
101
|
+
return ok(await manager.continue(sessionId, threadId));
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
return fail(`debug_continue failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
// debug_step — 单步 over/in/out(auto)
|
|
109
|
+
const debugStepTool = {
|
|
110
|
+
name: 'debug_step',
|
|
111
|
+
description: 'Step execution: kind = "over" | "in" | "out".',
|
|
112
|
+
parameters: {
|
|
113
|
+
type: 'object',
|
|
114
|
+
properties: {
|
|
115
|
+
sessionId: { type: 'string' },
|
|
116
|
+
threadId: { type: 'number' },
|
|
117
|
+
kind: { type: 'string', enum: ['over', 'in', 'out'] },
|
|
118
|
+
},
|
|
119
|
+
required: ['sessionId', 'threadId', 'kind'],
|
|
120
|
+
},
|
|
121
|
+
permission: 'auto',
|
|
122
|
+
execute: async (input) => {
|
|
123
|
+
const { sessionId, threadId, kind } = input;
|
|
124
|
+
try {
|
|
125
|
+
return ok(await manager.step(sessionId, threadId, kind));
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
return fail(`debug_step failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
// debug_stack — 调用栈(auto)
|
|
133
|
+
const debugStackTool = {
|
|
134
|
+
name: 'debug_stack',
|
|
135
|
+
description: 'Get the stack trace of a thread. Returns StackFrame[].',
|
|
136
|
+
parameters: {
|
|
137
|
+
type: 'object',
|
|
138
|
+
properties: { sessionId: { type: 'string' }, threadId: { type: 'number' } },
|
|
139
|
+
required: ['sessionId', 'threadId'],
|
|
140
|
+
},
|
|
141
|
+
permission: 'auto',
|
|
142
|
+
execute: async (input) => {
|
|
143
|
+
const { sessionId, threadId } = input;
|
|
144
|
+
try {
|
|
145
|
+
const frames = await manager.stack(sessionId, threadId);
|
|
146
|
+
return ok(frames);
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
return fail(`debug_stack failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
// debug_vars — 查看变量(auto)
|
|
154
|
+
const debugVarsTool = {
|
|
155
|
+
name: 'debug_vars',
|
|
156
|
+
description: 'Get variables visible in a stack frame (frameId from debug_stack). Returns Variable[].',
|
|
157
|
+
parameters: {
|
|
158
|
+
type: 'object',
|
|
159
|
+
properties: { sessionId: { type: 'string' }, frameId: { type: 'number' } },
|
|
160
|
+
required: ['sessionId', 'frameId'],
|
|
161
|
+
},
|
|
162
|
+
permission: 'auto',
|
|
163
|
+
execute: async (input) => {
|
|
164
|
+
const { sessionId, frameId } = input;
|
|
165
|
+
try {
|
|
166
|
+
const vars = await manager.variables(sessionId, frameId);
|
|
167
|
+
return ok(vars);
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
return fail(`debug_vars failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
// debug_eval — 求值表达式(auto)
|
|
175
|
+
const debugEvalTool = {
|
|
176
|
+
name: 'debug_eval',
|
|
177
|
+
description: 'Evaluate an expression in the context of a stack frame. Returns the result string.',
|
|
178
|
+
parameters: {
|
|
179
|
+
type: 'object',
|
|
180
|
+
properties: {
|
|
181
|
+
sessionId: { type: 'string' },
|
|
182
|
+
frameId: { type: 'number' },
|
|
183
|
+
expression: { type: 'string' },
|
|
184
|
+
},
|
|
185
|
+
required: ['sessionId', 'frameId', 'expression'],
|
|
186
|
+
},
|
|
187
|
+
permission: 'auto',
|
|
188
|
+
execute: async (input) => {
|
|
189
|
+
const { sessionId, frameId, expression } = input;
|
|
190
|
+
try {
|
|
191
|
+
const result = await manager.evaluate(sessionId, frameId, expression);
|
|
192
|
+
return ok(result);
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
return fail(`debug_eval failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
// debug_stop — 停止调试(auto)
|
|
200
|
+
const debugStopTool = {
|
|
201
|
+
name: 'debug_stop',
|
|
202
|
+
description: 'Stop the debug session and disconnect the adapter.',
|
|
203
|
+
parameters: {
|
|
204
|
+
type: 'object',
|
|
205
|
+
properties: { sessionId: { type: 'string' } },
|
|
206
|
+
required: ['sessionId'],
|
|
207
|
+
},
|
|
208
|
+
permission: 'auto',
|
|
209
|
+
execute: async (input) => {
|
|
210
|
+
const { sessionId } = input;
|
|
211
|
+
try {
|
|
212
|
+
await manager.stop(sessionId);
|
|
213
|
+
return ok(`debug session ${sessionId} stopped`);
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
return fail(`debug_stop failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
const dapTools = [
|
|
221
|
+
debugStartTool,
|
|
222
|
+
debugBreakpointTool,
|
|
223
|
+
debugContinueTool,
|
|
224
|
+
debugStepTool,
|
|
225
|
+
debugStackTool,
|
|
226
|
+
debugVarsTool,
|
|
227
|
+
debugEvalTool,
|
|
228
|
+
debugStopTool,
|
|
229
|
+
];
|
|
230
|
+
// re-export for tests/host wiring
|
|
231
|
+
export { dapTools, debugBreakpointTool, debugContinueTool, debugEvalTool, debugStackTool, debugStartTool, debugStepTool, debugStopTool, debugVarsTool, encodeMessage, manager, };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ToolDef } from '../../shared/types/tool.js';
|
|
2
|
+
/**
|
|
3
|
+
* edit tool: file editing in two modes (spec §16.4).
|
|
4
|
+
* - diff mode: search-and-replace with fuzzy whitespace matching. Provide
|
|
5
|
+
* `oldText` + `newText`. Returns error if oldText is absent or ambiguous.
|
|
6
|
+
* - hashline mode: content-hash-anchored patch language (spec §16). Provide
|
|
7
|
+
* `patch`. If the file changed since the patch was generated, the hash
|
|
8
|
+
* mismatch is rejected rather than misapplied.
|
|
9
|
+
*
|
|
10
|
+
* Permission: ask (modifies filesystem).
|
|
11
|
+
*/
|
|
12
|
+
export declare const editTool: ToolDef;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { safeResolve } from '../../shared/utils/path.js';
|
|
3
|
+
import { applyPatch, parsePatch } from '../hashline/index.js';
|
|
4
|
+
/**
|
|
5
|
+
* edit tool: file editing in two modes (spec §16.4).
|
|
6
|
+
* - diff mode: search-and-replace with fuzzy whitespace matching. Provide
|
|
7
|
+
* `oldText` + `newText`. Returns error if oldText is absent or ambiguous.
|
|
8
|
+
* - hashline mode: content-hash-anchored patch language (spec §16). Provide
|
|
9
|
+
* `patch`. If the file changed since the patch was generated, the hash
|
|
10
|
+
* mismatch is rejected rather than misapplied.
|
|
11
|
+
*
|
|
12
|
+
* Permission: ask (modifies filesystem).
|
|
13
|
+
*/
|
|
14
|
+
export const editTool = {
|
|
15
|
+
name: 'edit',
|
|
16
|
+
description: 'Edit a file. Two modes: (1) diff — provide oldText+newText for fuzzy search-and-replace; (2) hashline — provide `patch`, a content-hash-anchored patch that fails safely if the file changed since the patch was generated.',
|
|
17
|
+
parameters: {
|
|
18
|
+
type: 'object',
|
|
19
|
+
properties: {
|
|
20
|
+
path: { type: 'string', description: 'File path (relative to cwd or absolute).' },
|
|
21
|
+
oldText: {
|
|
22
|
+
type: 'string',
|
|
23
|
+
description: 'diff mode: text to find in the file (fuzzy whitespace matching).',
|
|
24
|
+
},
|
|
25
|
+
newText: { type: 'string', description: 'diff mode: replacement text.' },
|
|
26
|
+
patch: {
|
|
27
|
+
type: 'string',
|
|
28
|
+
description: 'hashline mode: a patch block like `[path#hash]\\nSWAP start-end\\nnew content\\n---`. Generate the hash from the CURRENT file content; a stale hash is rejected.',
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
required: ['path'],
|
|
32
|
+
},
|
|
33
|
+
permission: 'ask',
|
|
34
|
+
execute: async (input, ctx) => {
|
|
35
|
+
const raw = input;
|
|
36
|
+
const path = raw.path;
|
|
37
|
+
const fullPath = safeResolve(ctx.cwd, path);
|
|
38
|
+
if (fullPath === null) {
|
|
39
|
+
return { _tag: 'error', error: `Path "${path}" escapes the working directory` };
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
// ── hashline 模式 ──────────────────────────────────
|
|
43
|
+
if ('patch' in raw && typeof raw.patch === 'string') {
|
|
44
|
+
const patches = parsePatch(raw.patch);
|
|
45
|
+
if (patches.length === 0) {
|
|
46
|
+
return { _tag: 'error', error: `hashline: empty patch for "${path}"` };
|
|
47
|
+
}
|
|
48
|
+
let current = await readFile(fullPath, 'utf-8');
|
|
49
|
+
for (const p of patches) {
|
|
50
|
+
const r = applyPatch(current, p);
|
|
51
|
+
if (r._tag !== 'success') {
|
|
52
|
+
return { _tag: 'error', error: formatHashlineError(path, r) };
|
|
53
|
+
}
|
|
54
|
+
current = r.content;
|
|
55
|
+
}
|
|
56
|
+
await writeFile(fullPath, current, 'utf-8');
|
|
57
|
+
return {
|
|
58
|
+
_tag: 'success',
|
|
59
|
+
output: `Edited "${path}" via hashline patch (${patches.length} block(s))`,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
// ── diff 模式 ──────────────────────────────────────
|
|
63
|
+
if (!('oldText' in raw) || !('newText' in raw)) {
|
|
64
|
+
return {
|
|
65
|
+
_tag: 'error',
|
|
66
|
+
error: `edit: provide either 'patch' (hashline) or 'oldText'+'newText' (diff) for "${path}"`,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const oldText = raw.oldText;
|
|
70
|
+
const newText = raw.newText;
|
|
71
|
+
const content = await readFile(fullPath, 'utf-8');
|
|
72
|
+
// Fuzzy whitespace matching: normalize whitespace runs
|
|
73
|
+
const normalize = (s) => s.replace(/[ \t]+/g, ' ');
|
|
74
|
+
const normalizedContent = normalize(content);
|
|
75
|
+
const normalizedOld = normalize(oldText);
|
|
76
|
+
// Find all match positions
|
|
77
|
+
const matches = [];
|
|
78
|
+
let searchFrom = 0;
|
|
79
|
+
while (true) {
|
|
80
|
+
const idx = normalizedContent.indexOf(normalizedOld, searchFrom);
|
|
81
|
+
if (idx === -1)
|
|
82
|
+
break;
|
|
83
|
+
matches.push(idx);
|
|
84
|
+
searchFrom = idx + normalizedOld.length;
|
|
85
|
+
}
|
|
86
|
+
if (matches.length === 0) {
|
|
87
|
+
return { _tag: 'error', error: `oldText not found in "${path}"` };
|
|
88
|
+
}
|
|
89
|
+
if (matches.length > 1) {
|
|
90
|
+
return {
|
|
91
|
+
_tag: 'error',
|
|
92
|
+
error: `oldText matches ${matches.length} times in "${path}" — multiple matches found, provide more context to disambiguate`,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
// Map normalized match back to original content
|
|
96
|
+
const matchIdx = matches[0] ?? 0;
|
|
97
|
+
const prefix = normalizedContent.slice(0, matchIdx);
|
|
98
|
+
const charCount = prefix.length;
|
|
99
|
+
const mapping = buildPositionMapping(content, normalizedContent);
|
|
100
|
+
const origStart = mapping.get(charCount) ?? charCount;
|
|
101
|
+
const origEnd = mapping.get(charCount + normalizedOld.length) ?? charCount + normalizedOld.length;
|
|
102
|
+
const newContent = content.slice(0, origStart) + newText + content.slice(origEnd);
|
|
103
|
+
await writeFile(fullPath, newContent, 'utf-8');
|
|
104
|
+
return {
|
|
105
|
+
_tag: 'success',
|
|
106
|
+
output: `Edited "${path}": replaced ${origEnd - origStart} chars with ${newText.length} chars`,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
111
|
+
return { _tag: 'error', error: `Failed to edit "${path}": ${message}` };
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
/** 把 hashline ApplyResult 失败分支格式化为对 agent 有指导意义的错误信息。 */
|
|
116
|
+
function formatHashlineError(path, result) {
|
|
117
|
+
if (result._tag === 'hash_mismatch') {
|
|
118
|
+
return `"${path}": hashline hash mismatch — the file changed since this patch was generated (expected ${result.expected}, actual ${result.actual}). Re-read the file and regenerate the patch with the current hash.`;
|
|
119
|
+
}
|
|
120
|
+
const op = result.operation;
|
|
121
|
+
const range = 'start' in op ? `${op.start}${op.end !== op.start ? `-${op.end}` : ''}` : '';
|
|
122
|
+
return `"${path}": hashline line range ${range} out of bounds for ${op._tag} operation.`;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Build a mapping from normalized string positions to original string positions.
|
|
126
|
+
* Used to map fuzzy match results back to the original content.
|
|
127
|
+
*/
|
|
128
|
+
function buildPositionMapping(original, normalized) {
|
|
129
|
+
const map = new Map();
|
|
130
|
+
let origIdx = 0;
|
|
131
|
+
let normIdx = 0;
|
|
132
|
+
while (origIdx < original.length && normIdx < normalized.length) {
|
|
133
|
+
map.set(normIdx, origIdx);
|
|
134
|
+
if (original[origIdx] === normalized[normIdx]) {
|
|
135
|
+
origIdx++;
|
|
136
|
+
normIdx++;
|
|
137
|
+
}
|
|
138
|
+
else if (original[origIdx] === ' ' || original[origIdx] === '\t') {
|
|
139
|
+
// Original has whitespace that was collapsed
|
|
140
|
+
origIdx++;
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
// Shouldn't happen with proper normalization
|
|
144
|
+
origIdx++;
|
|
145
|
+
normIdx++;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
// Map the end position
|
|
149
|
+
map.set(normIdx, origIdx);
|
|
150
|
+
return map;
|
|
151
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ToolDef } from '../../shared/types/tool.js';
|
|
2
|
+
/**
|
|
3
|
+
* Convert a glob pattern to a RegExp.
|
|
4
|
+
* Supports: * (single segment), ** (across segments), ? (single char), {a,b} (alternation), [abc] (char class).
|
|
5
|
+
*/
|
|
6
|
+
export declare function globToRegex(pattern: string): RegExp;
|
|
7
|
+
/**
|
|
8
|
+
* glob tool: find files matching a glob pattern.
|
|
9
|
+
* Permission: auto (read-only).
|
|
10
|
+
*/
|
|
11
|
+
export declare const globTool: ToolDef;
|