@cjhyy/code-shell-core 0.6.0-rc.11 → 0.6.0-rc.13

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.
Files changed (65) hide show
  1. package/dist/cc-orchestrator/cwd-normalize.d.ts +2 -0
  2. package/dist/cc-orchestrator/cwd-normalize.js +19 -0
  3. package/dist/cc-orchestrator/external-agent-bindings.d.ts +27 -0
  4. package/dist/cc-orchestrator/external-agent-bindings.js +150 -0
  5. package/dist/cc-orchestrator/external-agent-session-store.d.ts +23 -0
  6. package/dist/cc-orchestrator/external-agent-session-store.js +144 -0
  7. package/dist/context/manager.d.ts +3 -1
  8. package/dist/context/manager.js +24 -15
  9. package/dist/credentials/types.d.ts +2 -2
  10. package/dist/engine/engine.d.ts +5 -1
  11. package/dist/engine/engine.js +92 -51
  12. package/dist/engine/turn-loop.js +1 -1
  13. package/dist/git/worktree.d.ts +49 -6
  14. package/dist/git/worktree.js +265 -31
  15. package/dist/index.d.ts +4 -3
  16. package/dist/index.js +3 -2
  17. package/dist/logging/logger.js +6 -6
  18. package/dist/plugins/installer/installFromSource.js +9 -1
  19. package/dist/plugins/installer/sourcePath.d.ts +9 -0
  20. package/dist/plugins/installer/sourcePath.js +50 -0
  21. package/dist/plugins/pluginInstaller.js +24 -22
  22. package/dist/protocol/chat-session-manager.d.ts +1 -0
  23. package/dist/protocol/chat-session-manager.js +13 -0
  24. package/dist/protocol/server.d.ts +8 -0
  25. package/dist/protocol/server.js +45 -8
  26. package/dist/protocol/types.d.ts +4 -0
  27. package/dist/protocol/types.js +4 -0
  28. package/dist/run/FileRunStore.js +10 -1
  29. package/dist/run/Heartbeat.js +12 -0
  30. package/dist/run/RunApprovalBackend.d.ts +3 -0
  31. package/dist/run/RunApprovalBackend.js +41 -6
  32. package/dist/run/RunLock.js +2 -0
  33. package/dist/run/RunManager.d.ts +2 -0
  34. package/dist/run/RunManager.js +64 -24
  35. package/dist/run/ids.d.ts +2 -0
  36. package/dist/run/ids.js +23 -0
  37. package/dist/session/session-manager.d.ts +35 -1
  38. package/dist/session/session-manager.js +189 -2
  39. package/dist/settings/manager.d.ts +1 -0
  40. package/dist/settings/manager.js +45 -26
  41. package/dist/settings/schema-export.d.ts +2 -3
  42. package/dist/settings/schema-export.js +2 -3
  43. package/dist/tool-system/builtin/background-jobs.d.ts +8 -1
  44. package/dist/tool-system/builtin/background-jobs.js +8 -1
  45. package/dist/tool-system/builtin/config.d.ts +2 -1
  46. package/dist/tool-system/builtin/config.js +16 -11
  47. package/dist/tool-system/builtin/drive-claude-code.d.ts +15 -2
  48. package/dist/tool-system/builtin/drive-claude-code.js +174 -39
  49. package/dist/tool-system/builtin/edit.js +5 -2
  50. package/dist/tool-system/builtin/generate-video.d.ts +1 -0
  51. package/dist/tool-system/builtin/generate-video.js +13 -4
  52. package/dist/tool-system/builtin/index.js +5 -1
  53. package/dist/tool-system/builtin/lsp.d.ts +2 -1
  54. package/dist/tool-system/builtin/lsp.js +6 -3
  55. package/dist/tool-system/builtin/notebook-edit.js +5 -2
  56. package/dist/tool-system/builtin/read.js +5 -2
  57. package/dist/tool-system/builtin/worktree.d.ts +2 -4
  58. package/dist/tool-system/builtin/worktree.js +250 -75
  59. package/dist/tool-system/builtin/write.js +5 -3
  60. package/dist/tool-system/context.d.ts +12 -0
  61. package/dist/tool-system/mcp-manager.d.ts +8 -0
  62. package/dist/tool-system/mcp-manager.js +32 -11
  63. package/dist/types.d.ts +12 -0
  64. package/dist/utils/toolDisplay.js +1 -1
  65. package/package.json +1 -1
@@ -2,7 +2,12 @@ import { runAgentOnce } from "../../cc-orchestrator/external-agent-driver.js";
2
2
  import { claudeAdapter, codexAdapter } from "../../cc-orchestrator/agent-adapter.js";
3
3
  import { backgroundJobRegistry } from "./background-jobs.js";
4
4
  import { readExternalChangedFiles } from "../../cc-orchestrator/external-agent-changes.js";
5
+ import { isExistingDirectory, normalizeCwdPath } from "../../cc-orchestrator/cwd-normalize.js";
5
6
  import { notificationQueue } from "./agent-notifications.js";
7
+ import { externalAgentSessionStore, } from "../../cc-orchestrator/external-agent-session-store.js";
8
+ import { logger } from "../../logging/logger.js";
9
+ export const DRIVE_AGENT_FOREGROUND_HANDOFF_MS = 110_000;
10
+ export const DRIVE_AGENT_TOOL_TIMEOUT_MS = 1_800_000;
6
11
  const CLI_ADAPTERS = {
7
12
  claude: { adapter: claudeAdapter, command: "claude" },
8
13
  codex: { adapter: codexAdapter, command: "codex" },
@@ -47,20 +52,144 @@ export const driveAgentToolDef = {
47
52
  };
48
53
  const defaultRunner = (opts) => {
49
54
  const { adapter, command } = CLI_ADAPTERS[opts.cli];
50
- return runAgentOnce(adapter, { command, prompt: opts.prompt, resumeSessionId: opts.resumeSessionId, cwd: opts.cwd, permissionMode: opts.permissionMode ?? "default" });
55
+ return runAgentOnce(adapter, { command, prompt: opts.prompt, resumeSessionId: opts.resumeSessionId, cwd: opts.cwd, permissionMode: opts.permissionMode ?? "default" }, opts.signal);
51
56
  };
57
+ function newDriveJobId() {
58
+ return `cc-${process.hrtime.bigint().toString(36)}`;
59
+ }
60
+ function isValidSessionId(sessionId) {
61
+ return typeof sessionId === "string" && sessionId.length > 0;
62
+ }
63
+ function argSignal(args) {
64
+ const signal = args.__signal;
65
+ return signal &&
66
+ typeof signal === "object" &&
67
+ typeof signal.aborted === "boolean" &&
68
+ typeof signal.addEventListener === "function"
69
+ ? signal
70
+ : undefined;
71
+ }
72
+ function startRun(runner, opts) {
73
+ return Promise.resolve().then(() => runner(opts));
74
+ }
75
+ function recordSuccessfulSession(store, cli, cwd, result) {
76
+ if (result.isError || !result.sessionId)
77
+ return;
78
+ try {
79
+ store.record({ cli, sessionId: result.sessionId, cwd });
80
+ }
81
+ catch (err) {
82
+ logger.warn("drive_agent.session_binding_record_failed", {
83
+ cat: "cc",
84
+ cli,
85
+ sessionId: result.sessionId,
86
+ cwd,
87
+ error: err instanceof Error ? err.message : String(err),
88
+ });
89
+ }
90
+ }
91
+ function duplicateCwdWarning(cwd, writable) {
92
+ if (!writable)
93
+ return undefined;
94
+ const running = backgroundJobRegistry.listRunningByCwd(cwd);
95
+ if (running.length === 0)
96
+ return undefined;
97
+ const ids = running.map((j) => j.jobId).join(", ");
98
+ return `Warning: another DriveAgent job is already running in cwd ${cwd} (jobId ${ids}). Concurrent writable agents in the same directory can overwrite each other's work.`;
99
+ }
100
+ function attachDriveCompletion(params) {
101
+ const { jobId, sessionId, label, cli, cwd, run, sessionStore } = params;
102
+ void run
103
+ .then((r) => {
104
+ recordSuccessfulSession(sessionStore, cli, cwd, r);
105
+ // Deliver the result back so the woken agent actually sees the answer
106
+ // (not just "a job finished"). Mirrors the video/sub-agent completion
107
+ // path — enqueue lands in the same notificationQueue the wakeup drains.
108
+ notificationQueue.enqueue(r.isError
109
+ ? { agentId: jobId, description: label, status: "failed", workKind: "cc", error: r.finalText || "(no output)", ccSessionId: r.sessionId || undefined, enqueuedAt: Date.now() }
110
+ : { agentId: jobId, description: label, status: "completed", workKind: "cc", finalText: r.finalText, ccSessionId: r.sessionId || undefined, enqueuedAt: Date.now() }, sessionId);
111
+ // Attribute the files the external agent changed by parsing its own
112
+ // transcript (#6) — those Edit/Write calls are invisible to the host's
113
+ // in-session aggregator. Best-effort; [] on any failure.
114
+ const changedFiles = r.sessionId
115
+ ? readExternalChangedFiles(cli, cwd, r.sessionId)
116
+ : [];
117
+ // Retain the job in the panel with its result + the external CLI
118
+ // session id + changed files.
119
+ backgroundJobRegistry.finish(jobId, {
120
+ status: r.isError ? "failed" : "completed",
121
+ finalText: r.finalText || undefined,
122
+ ccSessionId: r.sessionId || undefined,
123
+ ...(changedFiles.length ? { changedFiles } : {}),
124
+ });
125
+ })
126
+ .catch((err) => {
127
+ const msg = err?.message ?? String(err);
128
+ notificationQueue.enqueue({ agentId: jobId, description: label, status: "failed", workKind: "cc", error: msg, enqueuedAt: Date.now() }, sessionId);
129
+ backgroundJobRegistry.finish(jobId, { status: "failed", finalText: msg });
130
+ });
131
+ }
132
+ function trackBackgroundRun(params) {
133
+ const warning = duplicateCwdWarning(params.cwd, params.writable);
134
+ const jobId = newDriveJobId();
135
+ backgroundJobRegistry.start(jobId, params.sessionId, params.label, { cwd: params.cwd });
136
+ attachDriveCompletion({ ...params, jobId });
137
+ return { jobId, ...(warning ? { warning } : {}) };
138
+ }
139
+ async function waitForForegroundOrHandoff(run, handoffMs) {
140
+ if (handoffMs < 0) {
141
+ return { kind: "completed", result: await run };
142
+ }
143
+ let timer;
144
+ try {
145
+ return await Promise.race([
146
+ run.then((result) => ({ kind: "completed", result })),
147
+ new Promise((resolve) => {
148
+ timer = setTimeout(() => resolve({ kind: "handoff" }), handoffMs);
149
+ }),
150
+ ]);
151
+ }
152
+ finally {
153
+ if (timer)
154
+ clearTimeout(timer);
155
+ }
156
+ }
52
157
  /** Factory so tests can inject a fake runner. `fixedCli` (back-compat) forces a
53
158
  * cli and hides the `cli` arg — that's how DriveClaudeCode stays a thin alias. */
54
- export function makeDriveAgentTool(runner = defaultRunner, fixedCli) {
159
+ export function makeDriveAgentTool(runner = defaultRunner, fixedCli, options = {}) {
55
160
  return async (args, ctx) => {
56
161
  const prompt = typeof args.prompt === "string" ? args.prompt : "";
57
- const cwd = typeof args.cwd === "string" ? args.cwd : process.cwd();
162
+ const rawRequestedCwd = typeof args.cwd === "string" ? args.cwd : process.cwd();
163
+ const requestedCwd = normalizeCwdPath(rawRequestedCwd);
58
164
  if (!prompt)
59
165
  return "Error: prompt is required";
60
166
  const cli = fixedCli ?? (args.cli === "codex" ? "codex" : args.cli === "claude" || args.cli === undefined ? "claude" : "invalid");
61
167
  if (cli === "invalid")
62
168
  return `Error: unknown cli "${String(args.cli)}" (expected "claude" or "codex")`;
63
169
  const resumeSessionId = typeof args.resumeSessionId === "string" ? args.resumeSessionId : undefined;
170
+ const sessionStore = options.sessionStore ?? externalAgentSessionStore;
171
+ let cwd = requestedCwd;
172
+ let resumeNote = "";
173
+ if (resumeSessionId) {
174
+ const binding = sessionStore.get(cli, resumeSessionId);
175
+ if (binding) {
176
+ const storedCwd = normalizeCwdPath(binding.cwd);
177
+ if (!isExistingDirectory(storedCwd)) {
178
+ return `Error: cannot resume ${cli} session ${resumeSessionId}: stored cwd no longer exists or is not a directory: ${storedCwd}`;
179
+ }
180
+ if (storedCwd !== requestedCwd) {
181
+ cwd = storedCwd;
182
+ resumeNote = `Note: resume session ${resumeSessionId} is bound to stored cwd ${storedCwd}; ignoring requested cwd ${requestedCwd}.`;
183
+ logger.info("drive_agent.resume_forced_stored_cwd", {
184
+ cat: "cc",
185
+ cli,
186
+ sessionId: resumeSessionId,
187
+ requestedCwd,
188
+ storedCwd,
189
+ });
190
+ }
191
+ }
192
+ }
64
193
  // Default to bypassPermissions: this tool is a fire-one-turn delegation to
65
194
  // an external CLI with nobody watching for approvals, and there is no
66
195
  // interactive approval loop here — so under "default" a tool that needs
@@ -75,6 +204,9 @@ export function makeDriveAgentTool(runner = defaultRunner, fixedCli) {
75
204
  : "bypassPermissions";
76
205
  const cliName = cli === "codex" ? "Codex" : "Claude Code";
77
206
  const label = `DriveAgent(${cli}): ${prompt.slice(0, 40)}`;
207
+ const runOpts = { cli, prompt, resumeSessionId, cwd, permissionMode, signal: ctx?.signal ?? argSignal(args) };
208
+ const foregroundHandoffMs = options.foregroundHandoffMs ?? DRIVE_AGENT_FOREGROUND_HANDOFF_MS;
209
+ const isWritableRun = permissionMode !== "default";
78
210
  // Background by default (these tasks are typically long). Only an explicit
79
211
  // background:false runs in the foreground and returns the result inline.
80
212
  const background = args.background !== false;
@@ -87,42 +219,45 @@ export function makeDriveAgentTool(runner = defaultRunner, fixedCli) {
87
219
  if (typeof sessionId !== "string" || sessionId.length === 0) {
88
220
  return `Error: cannot start a background ${cliName} job without a session — its result notification would be dropped. Retry with background:false, or ensure the tool runs inside a session.`;
89
221
  }
90
- const jobId = `cc-${process.hrtime.bigint().toString(36)}`;
91
- backgroundJobRegistry.start(jobId, sessionId, label);
92
- void runner({ cli, prompt, resumeSessionId, cwd, permissionMode })
93
- .then((r) => {
94
- // Deliver the result back so the woken agent actually sees the answer
95
- // (not just "a job finished"). Mirrors the video/sub-agent completion
96
- // path — enqueue lands in the same notificationQueue the wakeup drains.
97
- notificationQueue.enqueue(r.isError
98
- ? { agentId: jobId, description: label, status: "failed", workKind: "cc", error: r.finalText || "(no output)", ccSessionId: r.sessionId || undefined, enqueuedAt: Date.now() }
99
- : { agentId: jobId, description: label, status: "completed", workKind: "cc", finalText: r.finalText, ccSessionId: r.sessionId || undefined, enqueuedAt: Date.now() }, sessionId);
100
- // Attribute the files the external agent changed by parsing its own
101
- // transcript (#6) — those Edit/Write calls are invisible to the host's
102
- // in-session aggregator. Best-effort; [] on any failure.
103
- const changedFiles = r.sessionId
104
- ? readExternalChangedFiles(cli, cwd, r.sessionId)
105
- : [];
106
- // Retain the job in the panel with its result + the external CLI
107
- // session id + changed files.
108
- backgroundJobRegistry.finish(jobId, {
109
- status: r.isError ? "failed" : "completed",
110
- finalText: r.finalText || undefined,
111
- ccSessionId: r.sessionId || undefined,
112
- ...(changedFiles.length ? { changedFiles } : {}),
113
- });
114
- })
115
- .catch((err) => {
116
- const msg = err?.message ?? String(err);
117
- notificationQueue.enqueue({ agentId: jobId, description: label, status: "failed", workKind: "cc", error: msg, enqueuedAt: Date.now() }, sessionId);
118
- backgroundJobRegistry.finish(jobId, { status: "failed", finalText: msg });
222
+ const tracked = trackBackgroundRun({
223
+ sessionId,
224
+ label,
225
+ cli,
226
+ cwd,
227
+ run: startRun(runner, runOpts),
228
+ sessionStore,
229
+ writable: isWritableRun,
230
+ });
231
+ return [
232
+ resumeNote,
233
+ `已在后台启动 ${cliName}(jobId ${tracked.jobId})。完成后会通知你结果,无需轮询。`,
234
+ tracked.warning,
235
+ ].filter(Boolean).join("\n");
236
+ }
237
+ const run = startRun(runner, runOpts);
238
+ const result = await waitForForegroundOrHandoff(run, foregroundHandoffMs);
239
+ if (result.kind === "handoff" && isValidSessionId(ctx?.sessionId)) {
240
+ const tracked = trackBackgroundRun({
241
+ sessionId: ctx.sessionId,
242
+ label,
243
+ cli,
244
+ cwd,
245
+ run,
246
+ sessionStore,
247
+ writable: isWritableRun,
119
248
  });
120
- return `已在后台启动 ${cliName}(jobId ${jobId})。完成后会通知你结果,无需轮询。`;
249
+ return [
250
+ resumeNote,
251
+ `${cliName} foreground run exceeded ${foregroundHandoffMs}ms; moved it to background (jobId ${tracked.jobId}). Completion will notify this session, so do not poll.`,
252
+ tracked.warning,
253
+ ].filter(Boolean).join("\n");
121
254
  }
122
- const r = await runner({ cli, prompt, resumeSessionId, cwd, permissionMode });
255
+ const r = result.kind === "completed" ? result.result : await run;
256
+ recordSuccessfulSession(sessionStore, cli, cwd, r);
257
+ const prefix = resumeNote ? `${resumeNote}\n` : "";
123
258
  if (r.isError)
124
- return `${cliName} 运行出错(session ${r.sessionId}):\n${r.finalText}`;
125
- return `${cliName} 完成(session ${r.sessionId}):\n${r.finalText}`;
259
+ return `${prefix}${cliName} 运行出错(session ${r.sessionId}):\n${r.finalText}`;
260
+ return `${prefix}${cliName} 完成(session ${r.sessionId}):\n${r.finalText}`;
126
261
  };
127
262
  }
128
263
  export const driveAgentTool = makeDriveAgentTool();
@@ -148,10 +283,10 @@ export const driveClaudeCodeToolDef = {
148
283
  required: ["prompt", "cwd"],
149
284
  },
150
285
  };
151
- export function makeDriveClaudeCodeTool(runner) {
286
+ export function makeDriveClaudeCodeTool(runner, options) {
152
287
  const generic = runner
153
- ? ({ prompt, resumeSessionId, cwd, permissionMode }) => runner({ prompt, resumeSessionId, cwd, permissionMode })
288
+ ? ({ prompt, resumeSessionId, cwd, permissionMode, signal }) => runner({ prompt, resumeSessionId, cwd, permissionMode, signal })
154
289
  : undefined;
155
- return makeDriveAgentTool(generic ?? defaultRunner, "claude");
290
+ return makeDriveAgentTool(generic ?? defaultRunner, "claude", options);
156
291
  }
157
292
  export const driveClaudeCodeTool = makeDriveClaudeCodeTool();
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import { readFile, writeFile } from "node:fs/promises";
5
5
  import { existsSync } from "node:fs";
6
+ import { isAbsolute, resolve } from "node:path";
6
7
  import { fileCache } from "./file-cache.js";
7
8
  import { detectEol, toLf, applyEol } from "./eol.js";
8
9
  export const editToolDef = {
@@ -32,11 +33,11 @@ export const editToolDef = {
32
33
  },
33
34
  };
34
35
  export async function editTool(args, ctx) {
35
- const filePath = args.file_path;
36
+ const rawPath = args.file_path;
36
37
  const oldString = args.old_string;
37
38
  const newString = args.new_string;
38
39
  const replaceAll = args.replace_all ?? false;
39
- if (!filePath)
40
+ if (!rawPath)
40
41
  return "Error: file_path is required";
41
42
  if (oldString === undefined)
42
43
  return "Error: old_string is required";
@@ -44,6 +45,8 @@ export async function editTool(args, ctx) {
44
45
  return "Error: new_string is required";
45
46
  if (oldString === newString)
46
47
  return "Error: old_string and new_string must be different";
48
+ const cwd = ctx?.cwd ?? process.cwd();
49
+ const filePath = isAbsolute(rawPath) ? rawPath : resolve(cwd, rawPath);
47
50
  if (!existsSync(filePath))
48
51
  return `Error: File not found: ${filePath}`;
49
52
  try {
@@ -18,6 +18,7 @@ import type { ToolDefinition } from "../../types.js";
18
18
  import type { ToolContext } from "../context.js";
19
19
  import { type VideoProvider, type VideoProviderCreds } from "./video-providers.js";
20
20
  import { type ImageUploader, type UploaderCreds } from "./image-uploader.js";
21
+ export declare function __resolveLocalImageInputForTests(pathOrUrl: string, cwd: string): string;
21
22
  /**
22
23
  * Normalize image inputs (URLs or local paths) into public URLs the provider
23
24
  * can consume. `images[]` wins over the single `image`. Local paths are
@@ -15,17 +15,22 @@
15
15
  * "no video provider configured" message) rather than half-working.
16
16
  */
17
17
  import { writeFile, mkdir } from "node:fs/promises";
18
- import { join } from "node:path";
18
+ import { isAbsolute, join, resolve } from "node:path";
19
19
  import { SettingsManager } from "../../settings/manager.js";
20
20
  import { notificationQueue } from "./agent-notifications.js";
21
21
  import { backgroundJobRegistry } from "./background-jobs.js";
22
22
  import { logger } from "../../logging/logger.js";
23
23
  import { getVideoProvider, DEFAULT_VIDEO_MODEL, } from "./video-providers.js";
24
- import { getImageUploader } from "./image-uploader.js";
24
+ import { getImageUploader, isHttpUrl } from "./image-uploader.js";
25
25
  import { effectiveApiKey } from "./generate-image.js";
26
26
  import { getMergedCatalog, findCatalogEntry } from "../../model-catalog/index.js";
27
27
  import { genInstancesFromConnections } from "../../model-catalog/gen-connections.js";
28
28
  const MAX_IMAGES = 9;
29
+ export function __resolveLocalImageInputForTests(pathOrUrl, cwd) {
30
+ if (isHttpUrl(pathOrUrl))
31
+ return pathOrUrl;
32
+ return isAbsolute(pathOrUrl) ? pathOrUrl : resolve(cwd, pathOrUrl);
33
+ }
29
34
  /**
30
35
  * Normalize image inputs (URLs or local paths) into public URLs the provider
31
36
  * can consume. `images[]` wins over the single `image`. Local paths are
@@ -230,9 +235,13 @@ export async function generateVideoTool(args, ctx) {
230
235
  const sessionId = ctx?.sessionId;
231
236
  const preferKind = typeof args.provider === "string" && args.provider ? args.provider : undefined;
232
237
  const overrideModel = typeof args.model === "string" && args.model ? args.model : undefined;
233
- const image = typeof args.image === "string" && args.image ? args.image : undefined;
238
+ const image = typeof args.image === "string" && args.image
239
+ ? __resolveLocalImageInputForTests(args.image, cwd)
240
+ : undefined;
234
241
  const imagesArg = Array.isArray(args.images)
235
- ? args.images.filter((x) => typeof x === "string")
242
+ ? args.images
243
+ .filter((x) => typeof x === "string")
244
+ .map((x) => __resolveLocalImageInputForTests(x, cwd))
236
245
  : undefined;
237
246
  // videos pass through verbatim to fal's video_urls (no upload) — http(s) only.
238
247
  const videosArg = Array.isArray(args.videos)
@@ -25,7 +25,7 @@ import { configToolDef, configTool } from "./config.js";
25
25
  import { notebookEditToolDef, notebookEditTool } from "./notebook-edit.js";
26
26
  import { lspToolDef, lspTool } from "./lsp.js";
27
27
  import { cronCreateToolDef, cronCreateTool, cronDeleteToolDef, cronDeleteTool, cronListToolDef, cronListTool } from "./cron.js";
28
- import { driveClaudeCodeToolDef, driveClaudeCodeTool, driveAgentToolDef, driveAgentTool } from "./drive-claude-code.js";
28
+ import { driveClaudeCodeToolDef, driveClaudeCodeTool, driveAgentToolDef, driveAgentTool, DRIVE_AGENT_TOOL_TIMEOUT_MS } from "./drive-claude-code.js";
29
29
  import { checkQuotaToolDef, checkQuotaTool } from "./check-quota.js";
30
30
  import { skillToolDef, skillTool } from "./skill.js";
31
31
  import { mcpToolDef, mcpToolExecute, listMcpResourcesToolDef, listMcpResourcesTool, readMcpResourceToolDef, readMcpResourceTool } from "./mcp-tools.js";
@@ -433,6 +433,7 @@ export const BUILTIN_TOOLS = [
433
433
  permissionDefault: "ask",
434
434
  isReadOnly: false,
435
435
  isConcurrencySafe: false,
436
+ timeoutMs: DRIVE_AGENT_TOOL_TIMEOUT_MS,
436
437
  },
437
438
  execute: driveAgentTool,
438
439
  },
@@ -445,6 +446,7 @@ export const BUILTIN_TOOLS = [
445
446
  permissionDefault: "ask",
446
447
  isReadOnly: false,
447
448
  isConcurrencySafe: false,
449
+ timeoutMs: DRIVE_AGENT_TOOL_TIMEOUT_MS,
448
450
  },
449
451
  execute: driveClaudeCodeTool,
450
452
  },
@@ -722,6 +724,8 @@ export const BUILTIN_TOOL_GUARDS = new Map([
722
724
  // InjectCredential hidden until ≥1 cookie credential exists (browser injection
723
725
  // is cookie-only). Also degrades at call time if no browser bridge is wired.
724
726
  [injectCredentialToolDef.name, (ctx) => isInjectCredentialAvailable(ctx.cwd)],
727
+ [completeGoalToolDef.name, (ctx) => ctx.hasGoal === true],
728
+ [cancelGoalToolDef.name, (ctx) => ctx.hasGoal === true],
725
729
  ]);
726
730
  /** UseCredential is available when the cwd's CredentialStore has ≥1 credential. */
727
731
  export function isUseCredentialAvailable(cwd) {
@@ -2,5 +2,6 @@
2
2
  * LSPTool — language server protocol operations for code intelligence.
3
3
  */
4
4
  import type { ToolDefinition } from "../../types.js";
5
+ import type { ToolContext } from "../context.js";
5
6
  export declare const lspToolDef: ToolDefinition;
6
- export declare function lspTool(args: Record<string, unknown>): Promise<string>;
7
+ export declare function lspTool(args: Record<string, unknown>, ctx?: ToolContext): Promise<string>;
@@ -4,6 +4,7 @@
4
4
  import { getLSPManager } from "../../lsp/manager.js";
5
5
  import { detectLSPServer } from "../../lsp/servers.js";
6
6
  import { pathToFileURL } from "node:url";
7
+ import { isAbsolute, resolve } from "node:path";
7
8
  export const lspToolDef = {
8
9
  name: "LSP",
9
10
  description: "Use Language Server Protocol for code intelligence operations. " +
@@ -33,13 +34,15 @@ export const lspToolDef = {
33
34
  required: ["action", "file_path"],
34
35
  },
35
36
  };
36
- export async function lspTool(args) {
37
+ export async function lspTool(args, ctx) {
37
38
  const action = args.action;
38
- const filePath = args.file_path;
39
+ const rawPath = args.file_path;
39
40
  const line = args.line ?? 0;
40
41
  const character = args.character ?? 0;
41
- if (!filePath)
42
+ if (!rawPath)
42
43
  return "Error: file_path is required";
44
+ const cwd = ctx?.cwd ?? process.cwd();
45
+ const filePath = isAbsolute(rawPath) ? rawPath : resolve(cwd, rawPath);
43
46
  const manager = getLSPManager();
44
47
  if (!manager)
45
48
  return "Error: LSP is not initialized. Language servers are not available.";
@@ -2,6 +2,7 @@
2
2
  * NotebookEditTool — read and edit Jupyter notebook (.ipynb) cells.
3
3
  */
4
4
  import { readFileSync, writeFileSync, existsSync } from "node:fs";
5
+ import { isAbsolute, resolve } from "node:path";
5
6
  export const notebookEditToolDef = {
6
7
  name: "NotebookEdit",
7
8
  description: "Edit a Jupyter notebook (.ipynb) file. Supports actions: " +
@@ -37,10 +38,12 @@ export const notebookEditToolDef = {
37
38
  },
38
39
  };
39
40
  export async function notebookEditTool(args, ctx) {
40
- const filePath = args.file_path;
41
+ const rawPath = args.file_path;
41
42
  const action = args.action;
42
- if (!filePath)
43
+ if (!rawPath)
43
44
  return "Error: file_path is required";
45
+ const cwd = ctx?.cwd ?? process.cwd();
46
+ const filePath = isAbsolute(rawPath) ? rawPath : resolve(cwd, rawPath);
44
47
  if (!filePath.endsWith(".ipynb"))
45
48
  return "Error: file must be a .ipynb file";
46
49
  if (action === "read") {
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import { readFile, stat } from "node:fs/promises";
5
5
  import { existsSync } from "node:fs";
6
+ import { isAbsolute, resolve } from "node:path";
6
7
  import { fileCache } from "./file-cache.js";
7
8
  import { toLf } from "./eol.js";
8
9
  export const readToolDef = {
@@ -28,9 +29,11 @@ export const readToolDef = {
28
29
  };
29
30
  const MAX_CONTENT_CHARS = 200_000;
30
31
  export async function readTool(args, ctx) {
31
- const filePath = args.file_path;
32
- if (!filePath)
32
+ const rawPath = args.file_path;
33
+ if (!rawPath)
33
34
  return "Error: file_path is required";
35
+ const cwd = ctx?.cwd ?? process.cwd();
36
+ const filePath = isAbsolute(rawPath) ? rawPath : resolve(cwd, rawPath);
34
37
  if (!existsSync(filePath))
35
38
  return `Error: File not found: ${filePath}`;
36
39
  try {
@@ -1,11 +1,9 @@
1
1
  /**
2
- * EnterWorktree / ExitWorktree tools — create and manage isolated git worktrees.
2
+ * EnterWorktree / ExitWorktree tools — switch a session between git workspaces.
3
3
  */
4
4
  import type { ToolDefinition } from "../../types.js";
5
5
  import type { ToolContext } from "../context.js";
6
- import { type WorktreeSession } from "../../git/worktree.js";
7
- export declare function getActiveWorktree(): WorktreeSession | undefined;
8
6
  export declare const enterWorktreeToolDef: ToolDefinition;
9
7
  export declare function enterWorktreeTool(args: Record<string, unknown>, ctx?: ToolContext): Promise<string>;
10
8
  export declare const exitWorktreeToolDef: ToolDefinition;
11
- export declare function exitWorktreeTool(args: Record<string, unknown>): Promise<string>;
9
+ export declare function exitWorktreeTool(args: Record<string, unknown>, ctx?: ToolContext): Promise<string>;