@cjhyy/code-shell-core 0.6.0-rc.9 → 0.7.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.
Files changed (193) hide show
  1. package/THIRD_PARTY_NOTICES.md +206 -0
  2. package/dist/automation/scheduler.d.ts +13 -7
  3. package/dist/automation/scheduler.js +116 -37
  4. package/dist/capability-control/service.d.ts +2 -0
  5. package/dist/capability-control/service.js +4 -2
  6. package/dist/cc-orchestrator/agent-adapter.d.ts +4 -0
  7. package/dist/cc-orchestrator/agent-adapter.js +11 -1
  8. package/dist/cc-orchestrator/codex-session-history.d.ts +14 -1
  9. package/dist/cc-orchestrator/codex-session-history.js +65 -5
  10. package/dist/cc-orchestrator/cwd-normalize.d.ts +2 -0
  11. package/dist/cc-orchestrator/cwd-normalize.js +19 -0
  12. package/dist/cc-orchestrator/external-agent-bindings.d.ts +27 -0
  13. package/dist/cc-orchestrator/external-agent-bindings.js +150 -0
  14. package/dist/cc-orchestrator/external-agent-changes.js +22 -5
  15. package/dist/cc-orchestrator/external-agent-driver.d.ts +1 -0
  16. package/dist/cc-orchestrator/external-agent-driver.js +265 -50
  17. package/dist/cc-orchestrator/external-agent-session-store.d.ts +23 -0
  18. package/dist/cc-orchestrator/external-agent-session-store.js +146 -0
  19. package/dist/cc-orchestrator/session-history.d.ts +35 -0
  20. package/dist/cc-orchestrator/session-history.js +98 -15
  21. package/dist/cli/agent-server-stdio.js +9 -2
  22. package/dist/context/compaction.d.ts +8 -1
  23. package/dist/context/compaction.js +49 -4
  24. package/dist/context/manager.d.ts +16 -2
  25. package/dist/context/manager.js +103 -19
  26. package/dist/credentials/access.d.ts +57 -0
  27. package/dist/credentials/access.js +185 -0
  28. package/dist/credentials/index.d.ts +3 -1
  29. package/dist/credentials/index.js +2 -0
  30. package/dist/credentials/inject-credential-tool.d.ts +3 -1
  31. package/dist/credentials/inject-credential-tool.js +30 -11
  32. package/dist/credentials/oauth.d.ts +20 -0
  33. package/dist/credentials/oauth.js +114 -0
  34. package/dist/credentials/store.d.ts +1 -0
  35. package/dist/credentials/store.js +3 -1
  36. package/dist/credentials/types.d.ts +49 -3
  37. package/dist/credentials/use-credential-tool.d.ts +9 -1
  38. package/dist/credentials/use-credential-tool.js +58 -45
  39. package/dist/engine/engine.d.ts +23 -3
  40. package/dist/engine/engine.js +380 -255
  41. package/dist/engine/goal.d.ts +17 -0
  42. package/dist/engine/goal.js +16 -6
  43. package/dist/engine/image-policy.d.ts +6 -0
  44. package/dist/engine/image-policy.js +17 -6
  45. package/dist/engine/input-attachments.d.ts +13 -0
  46. package/dist/engine/input-attachments.js +398 -0
  47. package/dist/engine/model-facade.d.ts +5 -2
  48. package/dist/engine/model-facade.js +4 -4
  49. package/dist/engine/parse-task.d.ts +10 -0
  50. package/dist/engine/parse-task.js +5 -0
  51. package/dist/engine/run-image-input.d.ts +22 -0
  52. package/dist/engine/run-image-input.js +195 -0
  53. package/dist/engine/steer-queue.d.ts +3 -1
  54. package/dist/engine/steer-queue.js +10 -2
  55. package/dist/engine/streaming-tool-queue.d.ts +11 -7
  56. package/dist/engine/streaming-tool-queue.js +11 -7
  57. package/dist/engine/turn-loop.d.ts +37 -2
  58. package/dist/engine/turn-loop.js +228 -43
  59. package/dist/engine/types.d.ts +8 -0
  60. package/dist/git/worktree/crud.d.ts +69 -0
  61. package/dist/git/worktree/crud.js +206 -0
  62. package/dist/git/worktree/diff.d.ts +14 -0
  63. package/dist/git/worktree/diff.js +82 -0
  64. package/dist/git/worktree/git-exec.d.ts +7 -0
  65. package/dist/git/worktree/git-exec.js +51 -0
  66. package/dist/git/worktree/index.d.ts +5 -0
  67. package/dist/git/worktree/index.js +5 -0
  68. package/dist/git/worktree/query.d.ts +42 -0
  69. package/dist/git/worktree/query.js +121 -0
  70. package/dist/git/worktree/slug.d.ts +11 -0
  71. package/dist/git/worktree/slug.js +58 -0
  72. package/dist/git/worktree.d.ts +1 -84
  73. package/dist/git/worktree.js +5 -230
  74. package/dist/hooks/goal-stop-hook.d.ts +33 -1
  75. package/dist/hooks/goal-stop-hook.js +202 -34
  76. package/dist/index.d.ts +31 -28
  77. package/dist/index.js +27 -25
  78. package/dist/logging/logger.js +6 -6
  79. package/dist/logging/sanitize-messages.d.ts +10 -2
  80. package/dist/logging/sanitize-messages.js +21 -6
  81. package/dist/plugins/installer/checkUpdate.d.ts +4 -1
  82. package/dist/plugins/installer/checkUpdate.js +4 -2
  83. package/dist/plugins/installer/install.js +2 -0
  84. package/dist/plugins/installer/installFromSource.js +9 -1
  85. package/dist/plugins/installer/parseSource.d.ts +4 -1
  86. package/dist/plugins/installer/parseSource.js +28 -10
  87. package/dist/plugins/installer/sourcePath.d.ts +9 -0
  88. package/dist/plugins/installer/sourcePath.js +50 -0
  89. package/dist/plugins/installer/update.d.ts +4 -1
  90. package/dist/plugins/installer/update.js +5 -3
  91. package/dist/plugins/parseMarketplaceInput.d.ts +4 -1
  92. package/dist/plugins/parseMarketplaceInput.js +4 -3
  93. package/dist/plugins/pluginInstaller.js +24 -22
  94. package/dist/preset/index.d.ts +1 -0
  95. package/dist/preset/index.js +30 -13
  96. package/dist/prompt/sections/base.md +1 -1
  97. package/dist/protocol/chat-session-manager.d.ts +2 -0
  98. package/dist/protocol/chat-session-manager.js +38 -2
  99. package/dist/protocol/chat-session.d.ts +3 -0
  100. package/dist/protocol/chat-session.js +1 -0
  101. package/dist/protocol/client.d.ts +9 -4
  102. package/dist/protocol/client.js +18 -1
  103. package/dist/protocol/server.d.ts +30 -0
  104. package/dist/protocol/server.js +247 -47
  105. package/dist/protocol/types.d.ts +49 -0
  106. package/dist/protocol/types.js +6 -0
  107. package/dist/run/FileRunStore.js +10 -1
  108. package/dist/run/Heartbeat.js +12 -0
  109. package/dist/run/RunApprovalBackend.d.ts +3 -0
  110. package/dist/run/RunApprovalBackend.js +41 -6
  111. package/dist/run/RunLock.js +2 -0
  112. package/dist/run/RunManager.d.ts +2 -0
  113. package/dist/run/RunManager.js +64 -24
  114. package/dist/run/ids.d.ts +2 -0
  115. package/dist/run/ids.js +23 -0
  116. package/dist/runtime/background-shell.d.ts +1 -0
  117. package/dist/runtime/background-shell.js +23 -13
  118. package/dist/runtime/spawn-common.js +10 -0
  119. package/dist/services/auto-dream.d.ts +15 -4
  120. package/dist/services/auto-dream.js +20 -20
  121. package/dist/services/dream-consolidation.d.ts +2 -3
  122. package/dist/services/dream-consolidation.js +65 -15
  123. package/dist/services/extract-memories.d.ts +14 -5
  124. package/dist/services/extract-memories.js +20 -3
  125. package/dist/services/global-dream-promotion.d.ts +23 -0
  126. package/dist/services/global-dream-promotion.js +112 -0
  127. package/dist/services/memory-orchestrator.js +347 -34
  128. package/dist/session/memory.d.ts +61 -18
  129. package/dist/session/memory.js +342 -79
  130. package/dist/session/session-manager.d.ts +35 -1
  131. package/dist/session/session-manager.js +224 -4
  132. package/dist/session/transcript.d.ts +1 -1
  133. package/dist/session/transcript.js +17 -5
  134. package/dist/settings/manager.d.ts +1 -0
  135. package/dist/settings/manager.js +87 -37
  136. package/dist/settings/schema-export.d.ts +2 -3
  137. package/dist/settings/schema-export.js +2 -3
  138. package/dist/settings/schema.d.ts +21 -0
  139. package/dist/settings/schema.js +12 -0
  140. package/dist/skills/scanner.d.ts +3 -2
  141. package/dist/skills/scanner.js +12 -9
  142. package/dist/tool-system/builtin/agent-notifications.d.ts +11 -4
  143. package/dist/tool-system/builtin/agent-notifications.js +19 -7
  144. package/dist/tool-system/builtin/background-jobs.d.ts +35 -6
  145. package/dist/tool-system/builtin/background-jobs.js +116 -7
  146. package/dist/tool-system/builtin/background-work.d.ts +17 -18
  147. package/dist/tool-system/builtin/background-work.js +51 -5
  148. package/dist/tool-system/builtin/config.d.ts +2 -1
  149. package/dist/tool-system/builtin/config.js +16 -11
  150. package/dist/tool-system/builtin/cron-list.definition.d.ts +3 -0
  151. package/dist/tool-system/builtin/cron-list.definition.js +6 -0
  152. package/dist/tool-system/builtin/cron.d.ts +1 -2
  153. package/dist/tool-system/builtin/cron.js +9 -7
  154. package/dist/tool-system/builtin/drive-claude-code.d.ts +23 -2
  155. package/dist/tool-system/builtin/drive-claude-code.js +589 -48
  156. package/dist/tool-system/builtin/edit.js +5 -2
  157. package/dist/tool-system/builtin/generate-video.d.ts +1 -0
  158. package/dist/tool-system/builtin/generate-video.js +13 -4
  159. package/dist/tool-system/builtin/index.d.ts +8 -3
  160. package/dist/tool-system/builtin/index.js +50 -22
  161. package/dist/tool-system/builtin/lsp.d.ts +2 -1
  162. package/dist/tool-system/builtin/lsp.js +6 -3
  163. package/dist/tool-system/builtin/memory.js +40 -8
  164. package/dist/tool-system/builtin/notebook-edit.js +5 -2
  165. package/dist/tool-system/builtin/powershell.d.ts +5 -2
  166. package/dist/tool-system/builtin/powershell.js +11 -7
  167. package/dist/tool-system/builtin/read.js +118 -6
  168. package/dist/tool-system/builtin/sleep.d.ts +1 -2
  169. package/dist/tool-system/builtin/sleep.definition.d.ts +8 -0
  170. package/dist/tool-system/builtin/sleep.definition.js +28 -0
  171. package/dist/tool-system/builtin/sleep.js +1 -22
  172. package/dist/tool-system/builtin/view-image.d.ts +2 -2
  173. package/dist/tool-system/builtin/view-image.js +109 -19
  174. package/dist/tool-system/builtin/worktree.d.ts +4 -4
  175. package/dist/tool-system/builtin/worktree.js +297 -74
  176. package/dist/tool-system/builtin/write.js +5 -3
  177. package/dist/tool-system/context.d.ts +41 -1
  178. package/dist/tool-system/executor.d.ts +1 -5
  179. package/dist/tool-system/executor.js +94 -115
  180. package/dist/tool-system/mcp-manager.d.ts +31 -6
  181. package/dist/tool-system/mcp-manager.js +193 -78
  182. package/dist/tool-system/path-policy.d.ts +2 -0
  183. package/dist/tool-system/path-policy.js +41 -12
  184. package/dist/tool-system/permission.d.ts +28 -7
  185. package/dist/tool-system/permission.js +130 -49
  186. package/dist/tool-system/registry.js +11 -4
  187. package/dist/tool-system/tool-result-redaction.d.ts +7 -0
  188. package/dist/tool-system/tool-result-redaction.js +48 -0
  189. package/dist/tool-system/workspace-bridge.d.ts +11 -0
  190. package/dist/tool-system/workspace-bridge.js +1 -0
  191. package/dist/types.d.ts +78 -11
  192. package/dist/utils/toolDisplay.js +1 -1
  193. package/package.json +4 -3
@@ -2,7 +2,14 @@ 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 { existsSync, realpathSync, statSync } from "node:fs";
8
+ import { extname, isAbsolute, relative, resolve, sep } from "node:path";
9
+ import { externalAgentSessionStore, } from "../../cc-orchestrator/external-agent-session-store.js";
10
+ import { logger } from "../../logging/logger.js";
11
+ export const DRIVE_AGENT_FOREGROUND_HANDOFF_MS = 110_000;
12
+ export const DRIVE_AGENT_TOOL_TIMEOUT_MS = 1_800_000;
6
13
  const CLI_ADAPTERS = {
7
14
  claude: { adapter: claudeAdapter, command: "claude" },
8
15
  codex: { adapter: codexAdapter, command: "codex" },
@@ -15,7 +22,11 @@ export const driveAgentToolDef = {
15
22
  "Runs in the BACKGROUND by default — these tasks are typically long (minutes to hours), so this " +
16
23
  "returns immediately and the result is delivered to you later via a completion notification " +
17
24
  "that wakes you. Do NOT sleep-poll for it; just continue or end your turn — you'll be woken " +
18
- "with the result. For a quick task where you want the answer inline, pass background:false. " +
25
+ "with the result. " +
26
+ "Before launching writable work in a cwd, call DriveAgentJobs(action:'list', cwd) to see any " +
27
+ "already-running DriveAgent jobs there, their prompt summaries, owner sessions, CLI kind, and " +
28
+ "known changed files; use DriveAgentJobs(action:'cancel', jobId) if you must stop one. " +
29
+ "For a quick task where you want the answer inline, pass background:false. " +
19
30
  "It has NO time concept of its own: for 'in N minutes' / 'every N' / looping, use CronCreate " +
20
31
  "instead (never sleep). A scheduled CronCreate job runs one codeshell turn whose prompt can " +
21
32
  "instruct it to call DriveAgent; to continue a prior session across runs, have that turn pass " +
@@ -35,32 +46,380 @@ export const driveAgentToolDef = {
35
46
  inputSchema: {
36
47
  type: "object",
37
48
  properties: {
38
- prompt: { type: "string", description: "The task for the agent. Make it COMPLETE and self-contained: state the goal, a concrete definition of done, and (for open-ended work) an explicit scope bound so it finishes end-to-end and verifies its own work rather than sprawling or stopping half-way." },
39
- cli: { type: "string", enum: ["claude", "codex"], description: "Which external CLI to drive. 'claude' = Claude Code (default), 'codex' = OpenAI Codex. resumeSessionId is only valid against the same cli that produced it." },
40
- resumeSessionId: { type: "string", description: "Existing session id to resume (keeps context). Must come from a prior run of the SAME cli. Omit for a fresh session." },
49
+ prompt: {
50
+ type: "string",
51
+ description: "The task for the agent. Make it COMPLETE and self-contained: state the goal, a concrete definition of done, and (for open-ended work) an explicit scope bound so it finishes end-to-end and verifies its own work rather than sprawling or stopping half-way.",
52
+ },
53
+ cli: {
54
+ type: "string",
55
+ enum: ["claude", "codex"],
56
+ description: "Which external CLI to drive. 'claude' = Claude Code (default), 'codex' = OpenAI Codex. resumeSessionId is only valid against the same cli that produced it.",
57
+ },
58
+ resumeSessionId: {
59
+ type: "string",
60
+ description: "Existing session id to resume (keeps context). Must come from a prior run of the SAME cli. Omit for a fresh session.",
61
+ },
62
+ model: {
63
+ type: "string",
64
+ description: "Optional model override, passed through to `claude --model` / `codex exec --model`. Omit to use the CLI default; only pass when the user explicitly requests a model.",
65
+ },
41
66
  cwd: { type: "string", description: "Working directory the run operates in." },
42
- permissionMode: { type: "string", enum: ["default", "acceptEdits", "bypassPermissions"], description: "Permission/sandbox level. Defaults to 'bypassPermissions' (full auto — needed so the agent's tools run unattended; there is no interactive approval loop here). For codex: default→read-only sandbox, acceptEdits→workspace-write, bypassPermissions→no sandbox. Pass 'default'/'acceptEdits' to gate." },
43
- background: { type: "boolean", description: "Defaults to TRUE: run in the background and notify you on completion (right for long tasks). Pass false to run in the foreground and get the result inline (only for quick tasks)." },
67
+ attachmentPaths: {
68
+ type: "array",
69
+ items: { type: "string" },
70
+ description: "Optional local file paths to hand to the driven agent. Paths must resolve inside cwd. Images are also passed to Codex with -i when the installed Codex CLI supports it; otherwise all paths are listed in the prompt.",
71
+ },
72
+ permissionMode: {
73
+ type: "string",
74
+ enum: ["default", "acceptEdits", "bypassPermissions"],
75
+ description: "Permission/sandbox level. Defaults to 'bypassPermissions' (full auto — needed so the agent's tools run unattended; there is no interactive approval loop here). For codex: default→read-only sandbox, acceptEdits→workspace-write, bypassPermissions→no sandbox. Pass 'default'/'acceptEdits' to gate.",
76
+ },
77
+ background: {
78
+ type: "boolean",
79
+ description: "Defaults to TRUE: run in the background and notify you on completion (right for long tasks). Pass false to run in the foreground and get the result inline (only for quick tasks).",
80
+ },
44
81
  },
45
82
  required: ["prompt", "cwd"],
46
83
  },
47
84
  };
48
85
  const defaultRunner = (opts) => {
49
86
  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" });
87
+ return runAgentOnce(adapter, {
88
+ command,
89
+ prompt: opts.prompt,
90
+ resumeSessionId: opts.resumeSessionId,
91
+ model: opts.model,
92
+ cwd: opts.cwd,
93
+ permissionMode: opts.permissionMode ?? "default",
94
+ imagePaths: opts.imagePaths,
95
+ }, opts.signal);
51
96
  };
97
+ function newDriveJobId() {
98
+ return `cc-${process.hrtime.bigint().toString(36)}`;
99
+ }
100
+ function summarizePrompt(prompt, max = 120) {
101
+ const oneLine = prompt.replace(/\s+/g, " ").trim();
102
+ if (oneLine.length <= max)
103
+ return oneLine;
104
+ return `${oneLine.slice(0, Math.max(0, max - 3))}...`;
105
+ }
106
+ function isDriveAgentJob(job) {
107
+ return job.kind === "drive-agent" || job.description.startsWith("DriveAgent(");
108
+ }
109
+ function changedFilesSummary(job) {
110
+ const files = job.changedFiles ?? [];
111
+ if (files.length === 0)
112
+ return "unknown";
113
+ if (files.length <= 4)
114
+ return files.join(",");
115
+ return `${files.slice(0, 4).join(",")} (+${files.length - 4} more)`;
116
+ }
117
+ /**
118
+ * Canonicalize external transcript paths against the DriveAgent cwd. Claude
119
+ * normally records absolute paths while Codex apply_patch normally records
120
+ * relative paths; returning a cwd-relative display path for in-workspace files
121
+ * gives the renderer one stable identity and removes duplicate transcript hits.
122
+ */
123
+ function normalizeChangedFiles(cwd, files) {
124
+ const seen = new Set();
125
+ const out = [];
126
+ for (const file of files) {
127
+ if (typeof file !== "string" || !file.trim())
128
+ continue;
129
+ const absolute = resolve(cwd, file.trim());
130
+ if (seen.has(absolute))
131
+ continue;
132
+ seen.add(absolute);
133
+ const rel = relative(cwd, absolute);
134
+ out.push(rel && rel !== ".." && !rel.startsWith(`..${sep}`) ? rel : absolute);
135
+ }
136
+ return out;
137
+ }
138
+ function jobDurationSeconds(job) {
139
+ const end = job.finishedAt ?? Date.now();
140
+ return `${Math.max(0, (end - job.startedAt) / 1000).toFixed(1)}s`;
141
+ }
142
+ function formatDriveJobListLine(job) {
143
+ const prompt = job.promptSummary || job.description || "(no prompt summary)";
144
+ const cwd = job.cwd ?? "(unknown cwd)";
145
+ const cli = job.cli ?? "unknown";
146
+ return [
147
+ `${job.jobId}`,
148
+ `status=${job.status}`,
149
+ `cli=${cli}`,
150
+ `session=${job.sessionId}`,
151
+ `cwd=${cwd}`,
152
+ `startedAt=${new Date(job.startedAt).toISOString()}`,
153
+ `duration=${jobDurationSeconds(job)}`,
154
+ `changedFiles=${changedFilesSummary(job)}`,
155
+ `prompt="${prompt}"`,
156
+ ].join(" ");
157
+ }
158
+ function isValidSessionId(sessionId) {
159
+ return typeof sessionId === "string" && sessionId.length > 0;
160
+ }
161
+ function argSignal(args) {
162
+ const signal = args.__signal;
163
+ return signal &&
164
+ typeof signal === "object" &&
165
+ typeof signal.aborted === "boolean" &&
166
+ typeof signal.addEventListener === "function"
167
+ ? signal
168
+ : undefined;
169
+ }
170
+ function startRun(runner, opts) {
171
+ return Promise.resolve().then(() => runner(opts));
172
+ }
173
+ function resolveAttachmentPaths(raw, cwd) {
174
+ if (raw === undefined)
175
+ return { paths: [] };
176
+ if (!Array.isArray(raw))
177
+ return { paths: [], error: "attachmentPaths must be an array of strings" };
178
+ const cwdReal = realpathSync(cwd);
179
+ const paths = [];
180
+ for (const item of raw) {
181
+ if (typeof item !== "string" || !item.trim()) {
182
+ return { paths: [], error: "attachmentPaths must contain only non-empty strings" };
183
+ }
184
+ const candidate = isAbsolute(item) ? item : resolve(cwd, item);
185
+ if (!existsSync(candidate))
186
+ return { paths: [], error: `attachment path not found: ${item}` };
187
+ const real = realpathSync(candidate);
188
+ const rel = relative(cwdReal, real);
189
+ if (rel === "" || rel === ".." || rel.startsWith(`..${sep}`)) {
190
+ return { paths: [], error: `attachment path is outside cwd: ${item}` };
191
+ }
192
+ const info = statSync(real);
193
+ if (!info.isFile())
194
+ return { paths: [], error: `attachment path is not a file: ${item}` };
195
+ paths.push(real);
196
+ }
197
+ return { paths };
198
+ }
199
+ const DRIVE_IMAGE_EXTS = new Set([".png", ".jpg", ".jpeg", ".gif", ".webp"]);
200
+ function appendAttachmentPrompt(prompt, paths) {
201
+ if (paths.length === 0)
202
+ return prompt;
203
+ const lines = ["", "Attached files:"];
204
+ for (const path of paths) {
205
+ const kind = DRIVE_IMAGE_EXTS.has(extname(path).toLowerCase()) ? "image" : "file";
206
+ lines.push(`- ${path} (${kind})`);
207
+ }
208
+ return `${prompt}\n${lines.join("\n")}`;
209
+ }
210
+ function recordSuccessfulSession(store, cli, cwd, result, includeErroredSession = false) {
211
+ if ((!includeErroredSession && result.isError) || !result.sessionId)
212
+ return;
213
+ try {
214
+ store.record({ cli, sessionId: result.sessionId, cwd });
215
+ }
216
+ catch (err) {
217
+ logger.warn("drive_agent.session_binding_record_failed", {
218
+ cat: "cc",
219
+ cli,
220
+ sessionId: result.sessionId,
221
+ cwd,
222
+ error: err instanceof Error ? err.message : String(err),
223
+ });
224
+ }
225
+ }
226
+ function duplicateCwdWarning(cwd, writable) {
227
+ if (!writable)
228
+ return undefined;
229
+ const running = backgroundJobRegistry.listRunningByCwd(cwd).filter(isDriveAgentJob);
230
+ if (running.length === 0)
231
+ return undefined;
232
+ const jobs = running.map(formatDriveJobListLine).join("; ");
233
+ return (`Warning: another DriveAgent job is already running in cwd ${cwd}. ` +
234
+ "Concurrent writable agents in the same directory can overwrite each other's work. " +
235
+ `Run DriveAgentJobs(action:"list", cwd:"${cwd}") before dispatching parallel work for details/cancellation. ` +
236
+ `Running: ${jobs}`);
237
+ }
238
+ function attachDriveCompletion(params) {
239
+ const { jobId, sessionId, label, cli, cwd, run, sessionStore, readChangedFiles, recordExternalFileChanges, originClientMessageId, } = params;
240
+ void run
241
+ .then((r) => {
242
+ const jobStatus = backgroundJobRegistry.get(jobId)?.status;
243
+ if (jobStatus !== "running" && jobStatus !== "cancelling")
244
+ return;
245
+ const cancelling = jobStatus === "cancelling";
246
+ recordSuccessfulSession(sessionStore, cli, cwd, r, cancelling);
247
+ // Attribute external changes BEFORE publishing completion. Previously the
248
+ // notification event was emitted first and carried no files; changedFiles
249
+ // only reached the background-work registry/panel, so the chat turn card
250
+ // could never count DriveAgent edits.
251
+ const rawChangedFiles = r.sessionId ? readChangedFiles(cli, cwd, r.sessionId) : [];
252
+ const changedFiles = normalizeChangedFiles(cwd, rawChangedFiles);
253
+ if (changedFiles.length > 0) {
254
+ recordExternalFileChanges?.({
255
+ jobId,
256
+ description: label,
257
+ cli,
258
+ cwd,
259
+ status: cancelling ? "cancelled" : r.isError ? "failed" : "completed",
260
+ changedFiles,
261
+ ...(originClientMessageId ? { originClientMessageId } : {}),
262
+ });
263
+ }
264
+ logger.debug("changed_files.drive_completion", {
265
+ cat: "changed_files",
266
+ jobId,
267
+ sessionId,
268
+ externalSessionId: r.sessionId || undefined,
269
+ cli,
270
+ cwd,
271
+ originClientMessageId,
272
+ rawSize: rawChangedFiles.length,
273
+ size: changedFiles.length,
274
+ files: changedFiles,
275
+ });
276
+ if (cancelling) {
277
+ backgroundJobRegistry.recordArtifacts(jobId, {
278
+ ccSessionId: r.sessionId || undefined,
279
+ changedFiles,
280
+ });
281
+ return;
282
+ }
283
+ // Deliver the result back so the woken agent actually sees the answer
284
+ // (not just "a job finished"). Mirrors the video/sub-agent completion
285
+ // path — enqueue lands in the same notificationQueue the wakeup drains.
286
+ notificationQueue.enqueue(r.isError
287
+ ? {
288
+ agentId: jobId,
289
+ description: label,
290
+ status: "failed",
291
+ workKind: "cc",
292
+ error: r.finalText || "(no output)",
293
+ ccSessionId: r.sessionId || undefined,
294
+ ...(changedFiles.length ? { changedFiles, cwd } : {}),
295
+ ...(originClientMessageId ? { originClientMessageId } : {}),
296
+ enqueuedAt: Date.now(),
297
+ }
298
+ : {
299
+ agentId: jobId,
300
+ description: label,
301
+ status: "completed",
302
+ workKind: "cc",
303
+ finalText: r.finalText,
304
+ ccSessionId: r.sessionId || undefined,
305
+ ...(changedFiles.length ? { changedFiles, cwd } : {}),
306
+ ...(originClientMessageId ? { originClientMessageId } : {}),
307
+ enqueuedAt: Date.now(),
308
+ }, sessionId);
309
+ // Retain the job in the panel with its result + the external CLI
310
+ // session id + changed files.
311
+ backgroundJobRegistry.finish(jobId, {
312
+ status: r.isError ? "failed" : "completed",
313
+ finalText: r.finalText || undefined,
314
+ ccSessionId: r.sessionId || undefined,
315
+ ...(changedFiles.length ? { changedFiles } : {}),
316
+ });
317
+ })
318
+ .catch((err) => {
319
+ if (backgroundJobRegistry.get(jobId)?.status !== "running")
320
+ return;
321
+ const msg = err?.message ?? String(err);
322
+ notificationQueue.enqueue({
323
+ agentId: jobId,
324
+ description: label,
325
+ status: "failed",
326
+ workKind: "cc",
327
+ error: msg,
328
+ enqueuedAt: Date.now(),
329
+ }, sessionId);
330
+ backgroundJobRegistry.finish(jobId, { status: "failed", finalText: msg });
331
+ });
332
+ }
333
+ function trackBackgroundRun(params) {
334
+ const warning = duplicateCwdWarning(params.cwd, params.writable);
335
+ const jobId = newDriveJobId();
336
+ const run = params.start();
337
+ backgroundJobRegistry.start(jobId, params.sessionId, params.label, {
338
+ kind: "drive-agent",
339
+ cwd: params.cwd,
340
+ cli: params.cli,
341
+ promptSummary: params.promptSummary,
342
+ originClientMessageId: params.originClientMessageId,
343
+ abort: async () => {
344
+ params.abort();
345
+ await run.catch(() => undefined);
346
+ },
347
+ });
348
+ attachDriveCompletion({ ...params, jobId, run });
349
+ return { jobId, ...(warning ? { warning } : {}) };
350
+ }
351
+ async function waitForForegroundOrHandoff(run, handoffMs) {
352
+ if (handoffMs < 0) {
353
+ return { kind: "completed", result: await run };
354
+ }
355
+ let timer;
356
+ try {
357
+ return await Promise.race([
358
+ run.then((result) => ({ kind: "completed", result })),
359
+ new Promise((resolve) => {
360
+ timer = setTimeout(() => resolve({ kind: "handoff" }), handoffMs);
361
+ }),
362
+ ]);
363
+ }
364
+ finally {
365
+ if (timer)
366
+ clearTimeout(timer);
367
+ }
368
+ }
369
+ function makeAbortController(parent, linkParent = false) {
370
+ const controller = new AbortController();
371
+ if (!parent)
372
+ return controller;
373
+ if (parent.aborted) {
374
+ controller.abort(parent.reason);
375
+ }
376
+ else if (linkParent) {
377
+ parent.addEventListener("abort", () => controller.abort(parent.reason), { once: true });
378
+ }
379
+ return controller;
380
+ }
52
381
  /** Factory so tests can inject a fake runner. `fixedCli` (back-compat) forces a
53
382
  * cli and hides the `cli` arg — that's how DriveClaudeCode stays a thin alias. */
54
- export function makeDriveAgentTool(runner = defaultRunner, fixedCli) {
383
+ export function makeDriveAgentTool(runner = defaultRunner, fixedCli, options = {}) {
55
384
  return async (args, ctx) => {
56
385
  const prompt = typeof args.prompt === "string" ? args.prompt : "";
57
- const cwd = typeof args.cwd === "string" ? args.cwd : process.cwd();
386
+ const rawRequestedCwd = typeof args.cwd === "string" ? args.cwd : process.cwd();
387
+ const requestedCwd = normalizeCwdPath(rawRequestedCwd);
58
388
  if (!prompt)
59
389
  return "Error: prompt is required";
60
- const cli = fixedCli ?? (args.cli === "codex" ? "codex" : args.cli === "claude" || args.cli === undefined ? "claude" : "invalid");
390
+ const cli = fixedCli ??
391
+ (args.cli === "codex"
392
+ ? "codex"
393
+ : args.cli === "claude" || args.cli === undefined
394
+ ? "claude"
395
+ : "invalid");
61
396
  if (cli === "invalid")
62
397
  return `Error: unknown cli "${String(args.cli)}" (expected "claude" or "codex")`;
63
398
  const resumeSessionId = typeof args.resumeSessionId === "string" ? args.resumeSessionId : undefined;
399
+ const model = typeof args.model === "string" && args.model.trim() ? args.model : undefined;
400
+ const sessionStore = options.sessionStore ?? externalAgentSessionStore;
401
+ let cwd = requestedCwd;
402
+ let resumeNote = "";
403
+ if (resumeSessionId) {
404
+ const binding = sessionStore.get(cli, resumeSessionId);
405
+ if (binding) {
406
+ const storedCwd = normalizeCwdPath(binding.cwd);
407
+ if (!isExistingDirectory(storedCwd)) {
408
+ return `Error: cannot resume ${cli} session ${resumeSessionId}: stored cwd no longer exists or is not a directory: ${storedCwd}`;
409
+ }
410
+ if (storedCwd !== requestedCwd) {
411
+ cwd = storedCwd;
412
+ resumeNote = `Note: resume session ${resumeSessionId} is bound to stored cwd ${storedCwd}; ignoring requested cwd ${requestedCwd}.`;
413
+ logger.info("drive_agent.resume_forced_stored_cwd", {
414
+ cat: "cc",
415
+ cli,
416
+ sessionId: resumeSessionId,
417
+ requestedCwd,
418
+ storedCwd,
419
+ });
420
+ }
421
+ }
422
+ }
64
423
  // Default to bypassPermissions: this tool is a fire-one-turn delegation to
65
424
  // an external CLI with nobody watching for approvals, and there is no
66
425
  // interactive approval loop here — so under "default" a tool that needs
@@ -73,8 +432,29 @@ export function makeDriveAgentTool(runner = defaultRunner, fixedCli) {
73
432
  args.permissionMode === "bypassPermissions"
74
433
  ? args.permissionMode
75
434
  : "bypassPermissions";
435
+ const resolvedAttachmentPaths = resolveAttachmentPaths(args.attachmentPaths, cwd);
436
+ if (resolvedAttachmentPaths.error)
437
+ return `Error: ${resolvedAttachmentPaths.error}`;
438
+ const attachmentPaths = resolvedAttachmentPaths.paths;
439
+ const promptWithAttachments = appendAttachmentPrompt(prompt, attachmentPaths);
440
+ const imagePaths = cli === "codex"
441
+ ? attachmentPaths.filter((path) => DRIVE_IMAGE_EXTS.has(extname(path).toLowerCase()))
442
+ : [];
76
443
  const cliName = cli === "codex" ? "Codex" : "Claude Code";
77
444
  const label = `DriveAgent(${cli}): ${prompt.slice(0, 40)}`;
445
+ const promptSummary = summarizePrompt(prompt);
446
+ const callerSignal = ctx?.signal ?? argSignal(args);
447
+ const runOptsBase = {
448
+ cli,
449
+ prompt: promptWithAttachments,
450
+ resumeSessionId,
451
+ model,
452
+ cwd,
453
+ permissionMode,
454
+ imagePaths,
455
+ };
456
+ const foregroundHandoffMs = options.foregroundHandoffMs ?? DRIVE_AGENT_FOREGROUND_HANDOFF_MS;
457
+ const isWritableRun = permissionMode !== "default";
78
458
  // Background by default (these tasks are typically long). Only an explicit
79
459
  // background:false runs in the foreground and returns the result inline.
80
460
  const background = args.background !== false;
@@ -87,45 +467,200 @@ export function makeDriveAgentTool(runner = defaultRunner, fixedCli) {
87
467
  if (typeof sessionId !== "string" || sessionId.length === 0) {
88
468
  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
469
  }
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 });
470
+ const abortController = makeAbortController(callerSignal, false);
471
+ const tracked = trackBackgroundRun({
472
+ sessionId,
473
+ label,
474
+ cli,
475
+ cwd,
476
+ promptSummary,
477
+ start: () => startRun(runner, { ...runOptsBase, signal: abortController.signal }),
478
+ abort: () => abortController.abort(),
479
+ sessionStore,
480
+ writable: isWritableRun,
481
+ readChangedFiles: options.readChangedFiles ?? readExternalChangedFiles,
482
+ recordExternalFileChanges: ctx?.recordExternalFileChanges,
483
+ originClientMessageId: ctx?.originClientMessageId,
119
484
  });
120
- return `已在后台启动 ${cliName}(jobId ${jobId})。完成后会通知你结果,无需轮询。`;
485
+ return [
486
+ resumeNote,
487
+ `已在后台启动 ${cliName}(jobId ${tracked.jobId})。完成后会通知你结果,无需轮询。`,
488
+ tracked.warning,
489
+ ]
490
+ .filter(Boolean)
491
+ .join("\n");
121
492
  }
122
- const r = await runner({ cli, prompt, resumeSessionId, cwd, permissionMode });
493
+ const foregroundAbort = makeAbortController(callerSignal, true);
494
+ const run = startRun(runner, { ...runOptsBase, signal: foregroundAbort.signal });
495
+ const result = await waitForForegroundOrHandoff(run, foregroundHandoffMs);
496
+ if (result.kind === "handoff" && isValidSessionId(ctx?.sessionId)) {
497
+ const tracked = trackBackgroundRun({
498
+ sessionId: ctx.sessionId,
499
+ label,
500
+ cli,
501
+ cwd,
502
+ promptSummary,
503
+ start: () => run,
504
+ abort: () => foregroundAbort.abort(),
505
+ sessionStore,
506
+ writable: isWritableRun,
507
+ readChangedFiles: options.readChangedFiles ?? readExternalChangedFiles,
508
+ recordExternalFileChanges: ctx?.recordExternalFileChanges,
509
+ originClientMessageId: ctx?.originClientMessageId,
510
+ });
511
+ return [
512
+ resumeNote,
513
+ `${cliName} foreground run exceeded ${foregroundHandoffMs}ms; moved it to background (jobId ${tracked.jobId}). Completion will notify this session, so do not poll.`,
514
+ tracked.warning,
515
+ ]
516
+ .filter(Boolean)
517
+ .join("\n");
518
+ }
519
+ const r = result.kind === "completed" ? result.result : await run;
520
+ recordSuccessfulSession(sessionStore, cli, cwd, r);
521
+ const prefix = resumeNote ? `${resumeNote}\n` : "";
123
522
  if (r.isError)
124
- return `${cliName} 运行出错(session ${r.sessionId}):\n${r.finalText}`;
125
- return `${cliName} 完成(session ${r.sessionId}):\n${r.finalText}`;
523
+ return `${prefix}${cliName} 运行出错(session ${r.sessionId}):\n${r.finalText}`;
524
+ return `${prefix}${cliName} 完成(session ${r.sessionId}):\n${r.finalText}`;
126
525
  };
127
526
  }
128
527
  export const driveAgentTool = makeDriveAgentTool();
528
+ export const driveAgentJobsToolDef = {
529
+ name: "DriveAgentJobs",
530
+ description: "List, inspect, or cancel background DriveAgent jobs. Use action:'list' before launching " +
531
+ "writable DriveAgent work in a cwd to see already-running jobs there, including prompt " +
532
+ "summary, owner session, cwd, CLI kind, status, start time, and known changed files. " +
533
+ "Use action:'inspect' with jobId for full details, or action:'cancel' with jobId to abort " +
534
+ "a running DriveAgent external CLI process and deliver a cancellation notification.",
535
+ inputSchema: {
536
+ type: "object",
537
+ properties: {
538
+ action: {
539
+ type: "string",
540
+ enum: ["list", "inspect", "cancel"],
541
+ description: "What to do. Defaults to list.",
542
+ },
543
+ jobId: {
544
+ type: "string",
545
+ description: "DriveAgent jobId for inspect or cancel.",
546
+ },
547
+ cwd: {
548
+ type: "string",
549
+ description: "When listing, restrict to DriveAgent jobs in this cwd. This is cross-session so it can catch same-directory write conflicts before dispatch.",
550
+ },
551
+ status: {
552
+ type: "string",
553
+ enum: ["running", "all"],
554
+ description: "When listing: running (default) or all retained DriveAgent jobs.",
555
+ },
556
+ all: {
557
+ type: "boolean",
558
+ description: "When listing without cwd: true lists DriveAgent jobs from every session; default lists only this session when session context exists.",
559
+ },
560
+ },
561
+ },
562
+ };
563
+ function jobIdArg(args) {
564
+ const camel = typeof args.jobId === "string" ? args.jobId.trim() : "";
565
+ if (camel)
566
+ return camel;
567
+ const snake = typeof args.job_id === "string" ? args.job_id.trim() : "";
568
+ return snake || undefined;
569
+ }
570
+ function listDriveAgentJobs(args, ctx) {
571
+ const rawCwd = typeof args.cwd === "string" && args.cwd.trim() ? args.cwd : undefined;
572
+ const cwd = rawCwd ? normalizeCwdPath(rawCwd) : undefined;
573
+ const sessionId = ctx?.sessionId;
574
+ const listAllSessions = args.all === true || !!cwd || !sessionId;
575
+ const status = args.status === "all" ? "all" : "running";
576
+ let jobs = listAllSessions || !sessionId
577
+ ? backgroundJobRegistry.list()
578
+ : backgroundJobRegistry.listForSession(sessionId);
579
+ jobs = jobs.filter(isDriveAgentJob);
580
+ if (cwd)
581
+ jobs = jobs.filter((job) => job.cwd === cwd);
582
+ if (status === "running") {
583
+ jobs = jobs.filter((job) => job.status === "running" || job.status === "cancelling");
584
+ }
585
+ if (jobs.length === 0) {
586
+ if (cwd)
587
+ return `No ${status} DriveAgent jobs in cwd ${cwd}.`;
588
+ if (listAllSessions)
589
+ return `No ${status} DriveAgent jobs in this process.`;
590
+ return `No ${status} DriveAgent jobs in this session.`;
591
+ }
592
+ return jobs.map(formatDriveJobListLine).join("\n");
593
+ }
594
+ function inspectDriveAgentJob(jobId) {
595
+ if (!jobId)
596
+ return "Error: jobId is required.";
597
+ const job = backgroundJobRegistry.get(jobId);
598
+ if (!job || !isDriveAgentJob(job))
599
+ return `Error: DriveAgent jobId "${jobId}" not found.`;
600
+ const lines = [
601
+ `jobId: ${job.jobId}`,
602
+ `status: ${job.status}`,
603
+ `cli: ${job.cli ?? "unknown"}`,
604
+ `session: ${job.sessionId}`,
605
+ `cwd: ${job.cwd ?? "(unknown cwd)"}`,
606
+ `startedAt: ${new Date(job.startedAt).toISOString()}`,
607
+ `duration: ${jobDurationSeconds(job)}`,
608
+ `prompt: ${job.promptSummary || job.description || "(no prompt summary)"}`,
609
+ `description: ${job.description}`,
610
+ ];
611
+ if (job.finishedAt !== undefined)
612
+ lines.push(`finishedAt: ${new Date(job.finishedAt).toISOString()}`);
613
+ if (job.ccSessionId)
614
+ lines.push(`ccSessionId: ${job.ccSessionId}`);
615
+ if (job.changedFiles && job.changedFiles.length > 0) {
616
+ lines.push("changedFiles:", ...job.changedFiles.map((file) => `- ${file}`));
617
+ }
618
+ else {
619
+ lines.push("changedFiles: unknown");
620
+ }
621
+ if (job.finalText)
622
+ lines.push("finalText:", job.finalText);
623
+ return lines.join("\n");
624
+ }
625
+ async function cancelDriveAgentJob(jobId) {
626
+ if (!jobId)
627
+ return "Error: jobId is required.";
628
+ const job = backgroundJobRegistry.get(jobId);
629
+ if (!job || !isDriveAgentJob(job))
630
+ return `Error: DriveAgent jobId "${jobId}" not found.`;
631
+ if (job.status !== "running") {
632
+ return `DriveAgent job ${jobId} is already ${job.status}; nothing to cancel.`;
633
+ }
634
+ if (!job.abort) {
635
+ return `Error: DriveAgent job ${jobId} has no cancellation handle recorded.`;
636
+ }
637
+ const finalText = `DriveAgent job ${jobId} cancelled by DriveAgentJobs.`;
638
+ const ok = await backgroundJobRegistry.cancel(jobId, { finalText });
639
+ if (!ok)
640
+ return `Failed to cancel DriveAgent job ${jobId}.`;
641
+ notificationQueue.enqueue({
642
+ agentId: jobId,
643
+ description: job.description,
644
+ status: "cancelled",
645
+ workKind: "cc",
646
+ error: finalText,
647
+ ccSessionId: job.ccSessionId,
648
+ ...(job.changedFiles?.length ? { changedFiles: job.changedFiles, cwd: job.cwd } : {}),
649
+ ...(job.originClientMessageId ? { originClientMessageId: job.originClientMessageId } : {}),
650
+ enqueuedAt: Date.now(),
651
+ }, job.sessionId);
652
+ return `DriveAgent job ${jobId} cancelled.`;
653
+ }
654
+ export async function driveAgentJobsTool(args, ctx) {
655
+ const action = args.action === "inspect" || args.action === "cancel" || args.action === "list"
656
+ ? args.action
657
+ : "list";
658
+ if (action === "inspect")
659
+ return inspectDriveAgentJob(jobIdArg(args));
660
+ if (action === "cancel")
661
+ return await cancelDriveAgentJob(jobIdArg(args));
662
+ return listDriveAgentJobs(args, ctx);
663
+ }
129
664
  // ── Back-compat: DriveClaudeCode = DriveAgent pinned to cli:"claude" ──────────
130
665
  // Kept so old prompts / memories / call sites that reference DriveClaudeCode
131
666
  // keep working. It's a thin alias over the same machinery (fixedCli "claude").
@@ -134,24 +669,30 @@ export const driveClaudeCodeToolDef = {
134
669
  name: "DriveClaudeCode",
135
670
  description: "(Alias of DriveAgent with cli:claude.) Delegate a task to the external Claude Code CLI " +
136
671
  "(drives `claude` for one turn). Prefer DriveAgent for new calls; this name is kept for " +
137
- "compatibility. " + driveAgentToolDef.description,
672
+ "compatibility. " +
673
+ driveAgentToolDef.description,
138
674
  inputSchema: {
139
675
  type: "object",
140
676
  properties: {
141
677
  // intentionally omit `cli` — this alias is always claude
142
678
  prompt: driveAgentToolDef.inputSchema.properties.prompt,
143
- resumeSessionId: { type: "string", description: "Existing CC session id to resume (keeps context). Omit for a fresh session." },
679
+ resumeSessionId: {
680
+ type: "string",
681
+ description: "Existing CC session id to resume (keeps context). Omit for a fresh session.",
682
+ },
683
+ model: driveAgentToolDef.inputSchema.properties.model,
144
684
  cwd: driveAgentToolDef.inputSchema.properties.cwd,
685
+ attachmentPaths: driveAgentToolDef.inputSchema.properties.attachmentPaths,
145
686
  permissionMode: driveAgentToolDef.inputSchema.properties.permissionMode,
146
687
  background: driveAgentToolDef.inputSchema.properties.background,
147
688
  },
148
689
  required: ["prompt", "cwd"],
149
690
  },
150
691
  };
151
- export function makeDriveClaudeCodeTool(runner) {
692
+ export function makeDriveClaudeCodeTool(runner, options) {
152
693
  const generic = runner
153
- ? ({ prompt, resumeSessionId, cwd, permissionMode }) => runner({ prompt, resumeSessionId, cwd, permissionMode })
694
+ ? ({ prompt, resumeSessionId, model, cwd, permissionMode, signal }) => runner({ prompt, resumeSessionId, model, cwd, permissionMode, signal })
154
695
  : undefined;
155
- return makeDriveAgentTool(generic ?? defaultRunner, "claude");
696
+ return makeDriveAgentTool(generic ?? defaultRunner, "claude", options);
156
697
  }
157
698
  export const driveClaudeCodeTool = makeDriveClaudeCodeTool();