@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
@@ -1,6 +1,7 @@
1
1
  /**
2
- * Skill scanner — discovers <base>/<name>/SKILL.md files from project + user
3
- * directories AND from installed plugins. Mirrors Claude Code's
2
+ * Skill scanner — discovers <base>/<name>/SKILL.md files from project
3
+ * `.code-shell/skills`, project `.agents/skills`, user `.code-shell/skills`,
4
+ * and installed plugins. Mirrors Claude Code's
4
5
  * `loadSkillsFromSkillsDir` (skills/loadSkillsDir.ts:407) plus plugin
5
6
  * integration (utils/plugins/pluginLoader.ts).
6
7
  */
@@ -1,6 +1,7 @@
1
1
  /**
2
- * Skill scanner — discovers <base>/<name>/SKILL.md files from project + user
3
- * directories AND from installed plugins. Mirrors Claude Code's
2
+ * Skill scanner — discovers <base>/<name>/SKILL.md files from project
3
+ * `.code-shell/skills`, project `.agents/skills`, user `.code-shell/skills`,
4
+ * and installed plugins. Mirrors Claude Code's
4
5
  * `loadSkillsFromSkillsDir` (skills/loadSkillsDir.ts:407) plus plugin
5
6
  * integration (utils/plugins/pluginLoader.ts).
6
7
  */
@@ -20,6 +21,7 @@ function userHome() {
20
21
  function bases(cwd) {
21
22
  return [
22
23
  { dir: join(cwd, ".code-shell", "skills"), source: "project" },
24
+ { dir: join(cwd, ".agents", "skills"), source: "project" },
23
25
  { dir: join(userHome(), ".code-shell", "skills"), source: "user" },
24
26
  ];
25
27
  }
@@ -158,13 +160,14 @@ function installedPluginsMtime() {
158
160
  }
159
161
  }
160
162
  /**
161
- * mtime of each local skills base dir (project + user). A directory's mtime
162
- * changes when a child entry is added/removed, so installing a new skill into
163
- * `<cwd>/.code-shell/skills` or `~/.code-shell/skills` busts the cache on the
164
- * next scan — the just-installed skill becomes visible to the running session
165
- * without a restart. (Editing an existing skill's *contents* does not bump the
166
- * dir mtime; install paths additionally call invalidateSkillCache() to cover
167
- * that, so this is the passive half of a two-part guard.)
163
+ * mtime of each local skills base dir (project .code-shell/.agents + user). A
164
+ * directory's mtime changes when a child entry is added/removed, so installing
165
+ * a new skill into `<cwd>/.code-shell/skills`, `<cwd>/.agents/skills`, or
166
+ * `~/.code-shell/skills` busts the cache on the next scan — the just-installed
167
+ * skill becomes visible to the running session without a restart. (Editing an
168
+ * existing skill's *contents* does not bump the dir mtime; install paths
169
+ * additionally call invalidateSkillCache() to cover that, so this is the
170
+ * passive half of a two-part guard.)
168
171
  */
169
172
  function skillsDirsMtime(cwd) {
170
173
  return bases(cwd)
@@ -6,8 +6,9 @@ import type { BackgroundAgentCompletedEvent, StreamEvent } from "../../types.js"
6
6
  * background sub-agent that finishes (completed | failed) enqueues an item
7
7
  * here; the UI layer subscribes and, when the main agent is idle, drains
8
8
  * the queue and submits the formatted XML as a new user turn so the LLM
9
- * sees the result. Cancellation does NOT enqueue (user explicitly stopped
10
- * the agent; no follow-up needed).
9
+ * sees the result. Sub-agent cancellation does NOT enqueue (user explicitly
10
+ * stopped the agent; no follow-up needed). DriveAgent cancellation does enqueue
11
+ * so a detached external CLI job never leaves the waiting session hanging.
11
12
  *
12
13
  * The result text lives only in this queue + the eventual user message —
13
14
  * not in `asyncAgentRegistry`. Registry stays metadata-only.
@@ -29,7 +30,7 @@ export type NotificationItem = {
29
30
  agentId: string;
30
31
  name?: string;
31
32
  description: string;
32
- status: "completed" | "failed";
33
+ status: "completed" | "failed" | "cancelled";
33
34
  /** What kind of background work this was (lets UIs localize the toast). */
34
35
  workKind?: "agent" | "shell" | "video" | "cc";
35
36
  /** For workKind === "shell": the command that ran. */
@@ -41,8 +42,14 @@ export type NotificationItem = {
41
42
  * if this notification is lost, and the user sees a real session id (not just
42
43
  * an opaque background jobId). */
43
44
  ccSessionId?: string;
44
- /** Error message (failed only). */
45
+ /** Error message (failed/cancelled only). */
45
46
  error?: string;
47
+ /** Files attributed to an external DriveAgent transcript. */
48
+ changedFiles?: string[];
49
+ /** DriveAgent cwd used to canonicalize path aliases in clients. */
50
+ cwd?: string;
51
+ /** Client id of the real user turn that launched this background work. */
52
+ originClientMessageId?: string;
46
53
  enqueuedAt: number;
47
54
  };
48
55
  type Listener = () => void;
@@ -139,6 +139,15 @@ export function notificationItemToStreamEvent(item) {
139
139
  event.finalText = item.finalText;
140
140
  if (item.error !== undefined)
141
141
  event.error = item.error;
142
+ if (item.ccSessionId !== undefined)
143
+ event.ccSessionId = item.ccSessionId;
144
+ if (item.changedFiles !== undefined)
145
+ event.changedFiles = item.changedFiles;
146
+ if (item.cwd !== undefined)
147
+ event.cwd = item.cwd;
148
+ if (item.originClientMessageId !== undefined) {
149
+ event.originClientMessageId = item.originClientMessageId;
150
+ }
142
151
  return event;
143
152
  }
144
153
  /**
@@ -147,10 +156,7 @@ export function notificationItemToStreamEvent(item) {
147
156
  * values get the quote escape too; element bodies don't need it.
148
157
  */
149
158
  function escapeXmlText(s) {
150
- return s
151
- .replace(/&/g, "&amp;")
152
- .replace(/</g, "&lt;")
153
- .replace(/>/g, "&gt;");
159
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
154
160
  }
155
161
  function escapeXmlAttr(s) {
156
162
  return escapeXmlText(s).replace(/"/g, "&quot;");
@@ -173,7 +179,9 @@ export function buildNotificationMessage(items) {
173
179
  const desc = ` <description>${escapeXmlText(item.description)}</description>`;
174
180
  const body = item.status === "completed"
175
181
  ? ` <result>\n${escapeXmlText(item.finalText ?? "")}\n </result>`
176
- : ` <error>${escapeXmlText(item.error ?? "")}</error>`;
182
+ : item.status === "cancelled"
183
+ ? ` <cancelled>${escapeXmlText(item.error ?? "cancelled")}</cancelled>`
184
+ : ` <error>${escapeXmlText(item.error ?? "")}</error>`;
177
185
  return [opening, desc, body, " </agent>"].join("\n");
178
186
  })
179
187
  .join("\n");
@@ -194,9 +202,13 @@ export function buildNotificationMessage(items) {
194
202
  export function buildNotificationSummary(items) {
195
203
  const header = "📨 background agents completed";
196
204
  const rows = items.map((item) => {
197
- const badge = item.status === "completed" ? "✓" : "✗";
205
+ const badge = item.status === "completed" ? "✓" : item.status === "cancelled" ? "cancelled" : "✗";
198
206
  const namePart = item.name ? `${item.name} · ` : "";
199
- const statusPart = item.status === "failed" ? ` · failed: ${item.error ?? "unknown"}` : "";
207
+ const statusPart = item.status === "failed"
208
+ ? ` · failed: ${item.error ?? "unknown"}`
209
+ : item.status === "cancelled"
210
+ ? ` · cancelled`
211
+ : "";
200
212
  return ` └─ ${namePart}${item.description} · ${badge}${statusPart}`;
201
213
  });
202
214
  return [header, ...rows].join("\n");
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Registry of NON-agent background jobs (GenerateVideo poll loops, DriveAgent
3
3
  * external CLI runs). Mirrors the role `asyncAgentRegistry` plays for background
4
- * sub-agents, but a job is not an agent — it has no transcript, no abort(), and
5
- * must NOT show up in AgentStatus.
4
+ * sub-agents, but a job is not an agent — it has no transcript and must NOT
5
+ * show up in AgentStatus.
6
6
  *
7
7
  * The engine's wait-for-background loop parks the turn until a session has no
8
8
  * more RUNNING jobs, so the goal-stop-hook doesn't force the model to busy-loop
@@ -21,11 +21,14 @@
21
21
  * loop observe the same instance.
22
22
  */
23
23
  type Listener = () => void;
24
- export type BackgroundJobStatus = "running" | "completed" | "failed";
24
+ export type BackgroundJobStatus = "running" | "cancelling" | "completed" | "failed" | "cancelled";
25
+ export type BackgroundJobKind = "drive-agent" | "video" | "job";
25
26
  /** A background job, running or finished. */
26
27
  export interface BackgroundJobEntry {
27
28
  jobId: string;
28
29
  sessionId: string;
30
+ /** Machine-readable kind for tools that need to target a subset of jobs. */
31
+ kind?: BackgroundJobKind;
29
32
  /** Human description (e.g. "Generating video: <prompt>"). Shown to the goal
30
33
  * judge (running) and in the background panel (all). */
31
34
  description: string;
@@ -40,28 +43,54 @@ export interface BackgroundJobEntry {
40
43
  ccSessionId?: string;
41
44
  /** Files the external agent changed (parsed from its transcript, #6). */
42
45
  changedFiles?: string[];
46
+ /** Working directory for jobs that operate on the filesystem, e.g. DriveAgent. */
47
+ cwd?: string;
48
+ /** DriveAgent prompt summary, separate from the UI-oriented description. */
49
+ promptSummary?: string;
50
+ /** External CLI kind for DriveAgent jobs. */
51
+ cli?: string;
52
+ /** Client message that launched this external job. */
53
+ originClientMessageId?: string;
54
+ /** Optional cancellation hook for jobs backed by a live process. */
55
+ abort?: () => void | Promise<void>;
43
56
  }
44
57
  /** Outcome passed to finish() to record how a job ended. */
45
58
  export interface BackgroundJobOutcome {
46
- status?: "completed" | "failed";
59
+ status?: "completed" | "failed" | "cancelled";
47
60
  finalText?: string;
48
61
  ccSessionId?: string;
49
62
  changedFiles?: string[];
50
63
  }
64
+ export interface BackgroundJobStartOptions {
65
+ kind?: BackgroundJobKind;
66
+ cwd?: string;
67
+ promptSummary?: string;
68
+ cli?: string;
69
+ originClientMessageId?: string;
70
+ abort?: () => void | Promise<void>;
71
+ }
51
72
  declare class BackgroundJobRegistry {
52
73
  private jobs;
53
74
  private listeners;
54
75
  /** Register a running job. Invalid sessionId is ignored (cannot be waited on). */
55
- start(jobId: string, sessionId: string, description?: string): void;
76
+ start(jobId: string, sessionId: string, description?: string, options?: BackgroundJobStartOptions): void;
56
77
  /** Mark a job terminal (retained, not deleted). Unknown id is a no-op (no
57
78
  * notify) so a double-finish or a finish after reset stays quiet. */
58
79
  finish(jobId: string, outcome?: BackgroundJobOutcome): void;
59
- /** True while any RUNNING job spawned by `sessionId` remains. */
80
+ get(jobId: string): BackgroundJobEntry | undefined;
81
+ /** Persist artifacts discovered while a running job is winding down. */
82
+ recordArtifacts(jobId: string, artifacts: Pick<BackgroundJobOutcome, "ccSessionId" | "changedFiles">): void;
83
+ cancel(jobId: string, outcome?: Omit<BackgroundJobOutcome, "status">): Promise<boolean>;
84
+ /** True while any running/cancelling job spawned by `sessionId` remains. */
60
85
  hasRunningForSession(sessionId: string): boolean;
61
86
  /** Running jobs spawned by `sessionId`. Feeds the goal judge's task list. */
62
87
  listRunningForSession(sessionId: string): BackgroundJobEntry[];
88
+ /** Running jobs, across all sessions, that are operating in the same cwd. */
89
+ listRunningByCwd(cwd: string): BackgroundJobEntry[];
63
90
  /** All jobs (running + retained terminal) for `sessionId`. Feeds the panel. */
64
91
  listForSession(sessionId: string): BackgroundJobEntry[];
92
+ /** All jobs (running + retained terminal) across sessions. Feeds all-scope UI. */
93
+ list(): BackgroundJobEntry[];
65
94
  /** Drop every job of a session — called when the session is deleted/closed. */
66
95
  dropForSession(sessionId: string): void;
67
96
  subscribe: (cb: Listener) => (() => void);
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Registry of NON-agent background jobs (GenerateVideo poll loops, DriveAgent
3
3
  * external CLI runs). Mirrors the role `asyncAgentRegistry` plays for background
4
- * sub-agents, but a job is not an agent — it has no transcript, no abort(), and
5
- * must NOT show up in AgentStatus.
4
+ * sub-agents, but a job is not an agent — it has no transcript and must NOT
5
+ * show up in AgentStatus.
6
6
  *
7
7
  * The engine's wait-for-background loop parks the turn until a session has no
8
8
  * more RUNNING jobs, so the goal-stop-hook doesn't force the model to busy-loop
@@ -20,25 +20,57 @@
20
20
  * loops run in the main engine process, so start/finish and the engine's wait
21
21
  * loop observe the same instance.
22
22
  */
23
+ import { normalizeCwdPath } from "../../cc-orchestrator/cwd-normalize.js";
23
24
  function isValidSessionId(sid) {
24
25
  return typeof sid === "string" && sid.length > 0;
25
26
  }
26
27
  /** Generous per-session cap on retained terminal jobs — a leak backstop, not a
27
28
  * UX limit; a human never spawns this many background jobs in one session. */
28
29
  const MAX_TERMINAL_JOBS_PER_SESSION = 50;
30
+ const CANCEL_WAIT_TIMEOUT_MS = 5_000;
31
+ function isActiveStatus(status) {
32
+ return status === "running" || status === "cancelling";
33
+ }
34
+ async function waitForAbort(abort) {
35
+ let timer;
36
+ try {
37
+ await Promise.race([
38
+ Promise.resolve().then(abort),
39
+ new Promise((resolve) => {
40
+ timer = setTimeout(resolve, CANCEL_WAIT_TIMEOUT_MS);
41
+ }),
42
+ ]);
43
+ }
44
+ catch {
45
+ // Cancellation is best-effort; the terminal state still closes after the
46
+ // abort hook settles or the hard deadline expires.
47
+ }
48
+ finally {
49
+ if (timer)
50
+ clearTimeout(timer);
51
+ }
52
+ }
29
53
  class BackgroundJobRegistry {
30
54
  jobs = new Map(); // jobId -> entry (insertion-ordered)
31
55
  listeners = new Set();
32
56
  /** Register a running job. Invalid sessionId is ignored (cannot be waited on). */
33
- start(jobId, sessionId, description = "") {
57
+ start(jobId, sessionId, description = "", options) {
34
58
  if (!isValidSessionId(sessionId))
35
59
  return;
36
60
  this.jobs.set(jobId, {
37
61
  jobId,
38
62
  sessionId,
63
+ ...(options?.kind ? { kind: options.kind } : {}),
39
64
  description,
40
65
  status: "running",
41
66
  startedAt: Date.now(),
67
+ ...(options?.cwd ? { cwd: normalizeCwdPath(options.cwd) } : {}),
68
+ ...(options?.promptSummary ? { promptSummary: options.promptSummary } : {}),
69
+ ...(options?.cli ? { cli: options.cli } : {}),
70
+ ...(options?.originClientMessageId
71
+ ? { originClientMessageId: options.originClientMessageId }
72
+ : {}),
73
+ ...(options?.abort ? { abort: options.abort } : {}),
42
74
  });
43
75
  this.notify();
44
76
  }
@@ -48,6 +80,8 @@ class BackgroundJobRegistry {
48
80
  const entry = this.jobs.get(jobId);
49
81
  if (!entry)
50
82
  return;
83
+ if (entry.status !== "running")
84
+ return;
51
85
  entry.status = outcome?.status ?? "completed";
52
86
  entry.finishedAt = Date.now();
53
87
  if (outcome?.finalText !== undefined)
@@ -59,31 +93,97 @@ class BackgroundJobRegistry {
59
93
  this.evictTerminalOverCap(entry.sessionId);
60
94
  this.notify();
61
95
  }
62
- /** True while any RUNNING job spawned by `sessionId` remains. */
96
+ get(jobId) {
97
+ return this.jobs.get(jobId);
98
+ }
99
+ /** Persist artifacts discovered while a running job is winding down. */
100
+ recordArtifacts(jobId, artifacts) {
101
+ const entry = this.jobs.get(jobId);
102
+ if (!entry || !isActiveStatus(entry.status))
103
+ return;
104
+ if (artifacts.ccSessionId !== undefined)
105
+ entry.ccSessionId = artifacts.ccSessionId;
106
+ if (artifacts.changedFiles !== undefined)
107
+ entry.changedFiles = artifacts.changedFiles;
108
+ }
109
+ async cancel(jobId, outcome) {
110
+ const entry = this.jobs.get(jobId);
111
+ if (!entry)
112
+ return false;
113
+ if (entry.status !== "running")
114
+ return false;
115
+ // Publish a non-terminal guard before invoking external code. A completion
116
+ // callback may synchronously re-enter finish(); it must not publish
117
+ // completed/failed, while cwd/session conflict checks must still see the
118
+ // process as active until the abort hook confirms exit.
119
+ entry.status = "cancelling";
120
+ this.notify();
121
+ if (entry.abort)
122
+ await waitForAbort(entry.abort);
123
+ // Session teardown/reset may have removed the entry while termination was
124
+ // in flight. Do not resurrect or notify for a closed session.
125
+ if (this.jobs.get(jobId) !== entry || entry.status !== "cancelling")
126
+ return false;
127
+ entry.status = "cancelled";
128
+ entry.finishedAt = Date.now();
129
+ if (outcome?.finalText !== undefined)
130
+ entry.finalText = outcome.finalText;
131
+ if (outcome?.ccSessionId !== undefined)
132
+ entry.ccSessionId = outcome.ccSessionId;
133
+ if (outcome?.changedFiles !== undefined)
134
+ entry.changedFiles = outcome.changedFiles;
135
+ this.evictTerminalOverCap(entry.sessionId);
136
+ this.notify();
137
+ return true;
138
+ }
139
+ /** True while any running/cancelling job spawned by `sessionId` remains. */
63
140
  hasRunningForSession(sessionId) {
64
141
  for (const e of this.jobs.values()) {
65
- if (e.sessionId === sessionId && e.status === "running")
142
+ if (e.sessionId === sessionId && isActiveStatus(e.status))
66
143
  return true;
67
144
  }
68
145
  return false;
69
146
  }
70
147
  /** Running jobs spawned by `sessionId`. Feeds the goal judge's task list. */
71
148
  listRunningForSession(sessionId) {
72
- return [...this.jobs.values()].filter((e) => e.sessionId === sessionId && e.status === "running");
149
+ return [...this.jobs.values()].filter((e) => e.sessionId === sessionId && isActiveStatus(e.status));
150
+ }
151
+ /** Running jobs, across all sessions, that are operating in the same cwd. */
152
+ listRunningByCwd(cwd) {
153
+ const normalized = normalizeCwdPath(cwd);
154
+ return [...this.jobs.values()].filter((e) => isActiveStatus(e.status) && e.cwd === normalized);
73
155
  }
74
156
  /** All jobs (running + retained terminal) for `sessionId`. Feeds the panel. */
75
157
  listForSession(sessionId) {
76
158
  return [...this.jobs.values()].filter((e) => e.sessionId === sessionId);
77
159
  }
160
+ /** All jobs (running + retained terminal) across sessions. Feeds all-scope UI. */
161
+ list() {
162
+ return [...this.jobs.values()];
163
+ }
78
164
  /** Drop every job of a session — called when the session is deleted/closed. */
79
165
  dropForSession(sessionId) {
80
166
  let removed = false;
167
+ const aborts = [];
81
168
  for (const [id, e] of this.jobs) {
82
169
  if (e.sessionId === sessionId) {
170
+ // Delete first so a synchronous/queued completion caused by abort sees
171
+ // no live registry entry and cannot publish a late result for a closed
172
+ // session. Terminal jobs have nothing left to stop.
83
173
  this.jobs.delete(id);
174
+ if (isActiveStatus(e.status) && e.abort)
175
+ aborts.push(e.abort);
84
176
  removed = true;
85
177
  }
86
178
  }
179
+ for (const abort of aborts) {
180
+ try {
181
+ void Promise.resolve(abort()).catch(() => undefined);
182
+ }
183
+ catch {
184
+ // teardown is best-effort across jobs; continue reaping the rest
185
+ }
186
+ }
87
187
  if (removed)
88
188
  this.notify();
89
189
  }
@@ -95,12 +195,21 @@ class BackgroundJobRegistry {
95
195
  };
96
196
  /** Test helper: drop all tracked jobs. */
97
197
  reset() {
198
+ const aborts = [...this.jobs.values()].flatMap((entry) => isActiveStatus(entry.status) && entry.abort ? [entry.abort] : []);
98
199
  this.jobs.clear();
200
+ for (const abort of aborts) {
201
+ try {
202
+ void Promise.resolve(abort()).catch(() => undefined);
203
+ }
204
+ catch {
205
+ // ignore
206
+ }
207
+ }
99
208
  }
100
209
  /** Backstop: keep at most MAX_TERMINAL_JOBS_PER_SESSION terminal jobs per
101
210
  * session, evicting the oldest (insertion order). Running jobs never evicted. */
102
211
  evictTerminalOverCap(sessionId) {
103
- const terminal = [...this.jobs.values()].filter((e) => e.sessionId === sessionId && e.status !== "running");
212
+ const terminal = [...this.jobs.values()].filter((e) => e.sessionId === sessionId && !isActiveStatus(e.status));
104
213
  const over = terminal.length - MAX_TERMINAL_JOBS_PER_SESSION;
105
214
  if (over <= 0)
106
215
  return;
@@ -1,16 +1,3 @@
1
- /**
2
- * Unified view over the THREE kinds of async background work — background
3
- * sub-agents (asyncAgentRegistry), background jobs / video polls
4
- * (backgroundJobRegistry), and background shells (backgroundShellManager).
5
- *
6
- * Used by the goal-stop-hook to show the judge LLM what's still running, so it
7
- * can tell "the goal is done except for a finite task that will wake me" (a
8
- * download / video render → allow stop, wait for the wakeup) from "a long-lived
9
- * service that the goal doesn't depend on" (a dev server → judge the goal
10
- * normally). Replacing the old mechanical `hasRunningForSession` short-circuit:
11
- * a boolean can't distinguish a finite download from a never-ending dev server,
12
- * but the judge — given each task's kind + command — can.
13
- */
14
1
  import { type AsyncAgentStatus } from "./agent-registry.js";
15
2
  import { type BackgroundJobStatus } from "./background-jobs.js";
16
3
  import { type BgShell } from "../../runtime/background-shell.js";
@@ -30,12 +17,21 @@ export interface BackgroundWorkItem {
30
17
  /** List every still-running background work item spawned by `sessionId`. */
31
18
  export declare function listRunningBackgroundWork(sessionId: string): BackgroundWorkItem[];
32
19
  /** One background-work row for the desktop panel, discriminated by `kind`. */
33
- export type BackgroundWorkEntry = {
20
+ export interface BackgroundWorkSourceSession {
21
+ sessionId: string;
22
+ shortId: string;
23
+ title?: string;
24
+ current: boolean;
25
+ }
26
+ type WithSource<T> = T & {
27
+ sourceSession: BackgroundWorkSourceSession;
28
+ };
29
+ export type BackgroundWorkEntry = WithSource<{
34
30
  kind: "shell";
35
31
  /** Full shell snapshot — the panel already renders this shape (output/kill
36
32
  * still go through the dedicated agent/backgroundShells RPC by shellId). */
37
33
  shell: BgShell;
38
- } | {
34
+ }> | WithSource<{
39
35
  kind: "subagent";
40
36
  agentId: string;
41
37
  name?: string;
@@ -44,7 +40,7 @@ export type BackgroundWorkEntry = {
44
40
  status: AsyncAgentStatus;
45
41
  startedAt: number;
46
42
  finishedAt?: number;
47
- } | {
43
+ }> | WithSource<{
48
44
  kind: "job";
49
45
  jobId: string;
50
46
  description: string;
@@ -55,7 +51,7 @@ export type BackgroundWorkEntry = {
55
51
  finalText?: string;
56
52
  /** Files an external agent (DriveAgent) changed, parsed from its transcript. */
57
53
  changedFiles?: string[];
58
- };
54
+ }>;
59
55
  /**
60
56
  * Every background-work item spawned by `sessionId`, with per-kind detail, for
61
57
  * the desktop panel. Includes finished sub-agents that are still within their
@@ -64,4 +60,7 @@ export type BackgroundWorkEntry = {
64
60
  * keep them. Jobs are only ever present while running (the registry drops them
65
61
  * on finish).
66
62
  */
67
- export declare function listBackgroundWorkForUI(sessionId: string): BackgroundWorkEntry[];
63
+ export declare function listBackgroundWorkForUI(sessionId: string, opts?: {
64
+ scope?: "session" | "all";
65
+ }): BackgroundWorkEntry[];
66
+ export {};
@@ -11,9 +11,12 @@
11
11
  * a boolean can't distinguish a finite download from a never-ending dev server,
12
12
  * but the judge — given each task's kind + command — can.
13
13
  */
14
+ import { readFileSync, statSync } from "node:fs";
15
+ import { join } from "node:path";
14
16
  import { asyncAgentRegistry } from "./agent-registry.js";
15
17
  import { backgroundJobRegistry } from "./background-jobs.js";
16
18
  import { backgroundShellManager } from "../../runtime/background-shell.js";
19
+ import { codeShellHome } from "../../session/session-manager.js";
17
20
  /** List every still-running background work item spawned by `sessionId`. */
18
21
  export function listRunningBackgroundWork(sessionId) {
19
22
  const items = [];
@@ -39,6 +42,38 @@ export function listRunningBackgroundWork(sessionId) {
39
42
  }
40
43
  return items;
41
44
  }
45
+ const sessionTitleCache = new Map();
46
+ function shortSessionId(sessionId) {
47
+ return sessionId.length <= 10 ? sessionId : sessionId.slice(0, 10);
48
+ }
49
+ function readSessionTitle(sessionId) {
50
+ try {
51
+ const statePath = join(codeShellHome(), "sessions", sessionId, "state.json");
52
+ const st = statSync(statePath);
53
+ const cached = sessionTitleCache.get(sessionId);
54
+ if (cached && cached.mtimeMs === st.mtimeMs)
55
+ return cached.title;
56
+ const raw = JSON.parse(readFileSync(statePath, "utf8"));
57
+ const title = typeof raw.title === "string" && raw.title.trim()
58
+ ? raw.title.trim()
59
+ : typeof raw.summary === "string" && raw.summary.trim()
60
+ ? raw.summary.trim()
61
+ : undefined;
62
+ sessionTitleCache.set(sessionId, { mtimeMs: st.mtimeMs, title });
63
+ return title;
64
+ }
65
+ catch {
66
+ return undefined;
67
+ }
68
+ }
69
+ function sourceSession(currentSessionId, ownerSessionId) {
70
+ return {
71
+ sessionId: ownerSessionId,
72
+ shortId: shortSessionId(ownerSessionId),
73
+ title: readSessionTitle(ownerSessionId),
74
+ current: ownerSessionId === currentSessionId,
75
+ };
76
+ }
42
77
  /**
43
78
  * Every background-work item spawned by `sessionId`, with per-kind detail, for
44
79
  * the desktop panel. Includes finished sub-agents that are still within their
@@ -47,18 +82,24 @@ export function listRunningBackgroundWork(sessionId) {
47
82
  * keep them. Jobs are only ever present while running (the registry drops them
48
83
  * on finish).
49
84
  */
50
- export function listBackgroundWorkForUI(sessionId) {
85
+ export function listBackgroundWorkForUI(sessionId, opts = {}) {
51
86
  const entries = [];
52
- for (const s of backgroundShellManager.listForSession(sessionId)) {
53
- entries.push({ kind: "shell", shell: s });
87
+ const scope = opts.scope ?? "session";
88
+ const shells = scope === "all"
89
+ ? backgroundShellManager.list()
90
+ : backgroundShellManager.listForSession(sessionId);
91
+ for (const s of shells) {
92
+ entries.push({ kind: "shell", shell: s, sourceSession: sourceSession(sessionId, s.sessionId) });
54
93
  }
55
94
  const now = Date.now();
56
- for (const a of asyncAgentRegistry.listForSession(sessionId)) {
95
+ const agents = scope === "all" ? asyncAgentRegistry.list() : asyncAgentRegistry.listForSession(sessionId);
96
+ for (const a of agents) {
57
97
  // Keep running agents, plus finished ones still inside their fade window so
58
98
  // a completion is briefly visible. (finishedFadeAt = finishedAt + 30s.)
59
99
  const fresh = a.status === "running" || (a.finishedFadeAt != null && a.finishedFadeAt > now);
60
100
  if (!fresh)
61
101
  continue;
102
+ const ownerSessionId = a.sessionId ?? sessionId;
62
103
  entries.push({
63
104
  kind: "subagent",
64
105
  agentId: a.agentId,
@@ -68,9 +109,13 @@ export function listBackgroundWorkForUI(sessionId) {
68
109
  status: a.status,
69
110
  startedAt: a.startedAt,
70
111
  finishedAt: a.finishedAt,
112
+ sourceSession: sourceSession(sessionId, ownerSessionId),
71
113
  });
72
114
  }
73
- for (const j of backgroundJobRegistry.listForSession(sessionId)) {
115
+ const jobs = scope === "all"
116
+ ? backgroundJobRegistry.list()
117
+ : backgroundJobRegistry.listForSession(sessionId);
118
+ for (const j of jobs) {
74
119
  entries.push({
75
120
  kind: "job",
76
121
  jobId: j.jobId,
@@ -80,6 +125,7 @@ export function listBackgroundWorkForUI(sessionId) {
80
125
  ...(j.finishedAt != null ? { finishedAt: j.finishedAt } : {}),
81
126
  ...(j.finalText != null ? { finalText: j.finalText } : {}),
82
127
  ...(j.changedFiles && j.changedFiles.length ? { changedFiles: j.changedFiles } : {}),
128
+ sourceSession: sourceSession(sessionId, j.sessionId),
83
129
  });
84
130
  }
85
131
  return entries;
@@ -2,5 +2,6 @@
2
2
  * ConfigTool — read or update project settings.
3
3
  */
4
4
  import type { ToolDefinition } from "../../types.js";
5
+ import type { ToolContext } from "../context.js";
5
6
  export declare const configToolDef: ToolDefinition;
6
- export declare function configTool(args: Record<string, unknown>): Promise<string>;
7
+ export declare function configTool(args: Record<string, unknown>, ctx?: ToolContext): Promise<string>;