@gajae-code/coding-agent 0.15.5 → 0.16.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 (272) hide show
  1. package/CHANGELOG.md +85 -1
  2. package/dist/types/async/job-manager.d.ts +83 -0
  3. package/dist/types/cli/args.d.ts +2 -0
  4. package/dist/types/cli/auth-gateway-cli.d.ts +17 -0
  5. package/dist/types/cli/model-presets-cli.d.ts +9 -0
  6. package/dist/types/cli/root-flags.d.ts +14 -0
  7. package/dist/types/cli/update-cli.d.ts +15 -0
  8. package/dist/types/cli.d.ts +7 -0
  9. package/dist/types/commands/auth-gateway.d.ts +3 -0
  10. package/dist/types/commands/gc.d.ts +13 -0
  11. package/dist/types/commands/launch.d.ts +10 -0
  12. package/dist/types/commands/model-presets.d.ts +21 -0
  13. package/dist/types/config/autorouting-tier-map.d.ts +120 -0
  14. package/dist/types/config/file-lock.d.ts +54 -2
  15. package/dist/types/config/internal/model-preset-registry-test-state.d.ts +4 -0
  16. package/dist/types/config/internal/model-preset-registry-test-support.d.ts +3 -0
  17. package/dist/types/config/keybindings.d.ts +3 -2
  18. package/dist/types/config/model-preset-registry.d.ts +368 -0
  19. package/dist/types/config/model-profile-activation.d.ts +39 -0
  20. package/dist/types/config/model-profiles.d.ts +5 -3
  21. package/dist/types/config/model-registry.d.ts +17 -5
  22. package/dist/types/config/settings-schema.d.ts +19 -1
  23. package/dist/types/config/settings.d.ts +2 -0
  24. package/dist/types/coordinator-mcp/model-preset.d.ts +7 -5
  25. package/dist/types/coordinator-mcp/projection-scan.d.ts +54 -0
  26. package/dist/types/crash/record-loader.d.ts +15 -0
  27. package/dist/types/extensibility/extensions/types.d.ts +2 -5
  28. package/dist/types/gjc-runtime/empty-delete-gc.d.ts +58 -0
  29. package/dist/types/gjc-runtime/gc-runtime.d.ts +2 -0
  30. package/dist/types/gjc-runtime/launch-worktree-reservation.d.ts +21 -0
  31. package/dist/types/gjc-runtime/managed-owner-supervisor.d.ts +2 -0
  32. package/dist/types/gjc-runtime/session-state-lock.d.ts +35 -6
  33. package/dist/types/gjc-runtime/session-state-sidecar.d.ts +72 -1
  34. package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +1 -0
  35. package/dist/types/gjc-runtime/tmux-sessions.d.ts +27 -0
  36. package/dist/types/index.d.ts +1 -0
  37. package/dist/types/main.d.ts +3 -1
  38. package/dist/types/master-mode/context.d.ts +21 -0
  39. package/dist/types/master-mode/first-request.d.ts +34 -0
  40. package/dist/types/master-mode/snapshot.d.ts +32 -0
  41. package/dist/types/modes/components/frictionless-onboarding-selector.d.ts +10 -0
  42. package/dist/types/modes/components/jobs-overlay-model.d.ts +4 -2
  43. package/dist/types/modes/components/oauth-selector.d.ts +1 -0
  44. package/dist/types/modes/components/status-line/model-name.d.ts +18 -0
  45. package/dist/types/modes/components/status-line/priority-row.d.ts +34 -0
  46. package/dist/types/modes/components/status-line/segments.d.ts +10 -0
  47. package/dist/types/modes/jobs-observer.d.ts +36 -0
  48. package/dist/types/modes/print-mode.d.ts +17 -0
  49. package/dist/types/modes/ui-language.d.ts +7 -1
  50. package/dist/types/sdk/broker/broker.d.ts +80 -1
  51. package/dist/types/sdk/broker/endpoint-authority.d.ts +24 -0
  52. package/dist/types/sdk/broker/index.d.ts +3 -0
  53. package/dist/types/sdk/broker/lifecycle.d.ts +26 -0
  54. package/dist/types/sdk/broker/master-capability.d.ts +10 -0
  55. package/dist/types/sdk/broker/session-index.d.ts +40 -11
  56. package/dist/types/sdk/broker/session-scope.d.ts +72 -0
  57. package/dist/types/sdk/broker/spawn-authority.d.ts +201 -0
  58. package/dist/types/sdk/broker/spawn-substrate.d.ts +30 -0
  59. package/dist/types/sdk/broker/state-version.d.ts +4 -1
  60. package/dist/types/sdk/broker/worktree-occupancy.d.ts +28 -0
  61. package/dist/types/sdk/bus/chat-daemon-control.d.ts +3 -0
  62. package/dist/types/sdk/bus/index.d.ts +6 -0
  63. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +3 -0
  64. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +4 -0
  65. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +1 -1
  66. package/dist/types/sdk/cli/index.d.ts +1 -0
  67. package/dist/types/sdk/cli/master-cli.d.ts +63 -0
  68. package/dist/types/sdk/cli/rows.d.ts +4 -3
  69. package/dist/types/sdk/cli/session-cli.d.ts +18 -2
  70. package/dist/types/sdk/client/client.d.ts +3 -2
  71. package/dist/types/sdk/client/discovery.d.ts +1 -0
  72. package/dist/types/sdk/host/control/dispatch.d.ts +10 -0
  73. package/dist/types/sdk/host/control/index.d.ts +1 -1
  74. package/dist/types/sdk/host/control/operations.d.ts +2 -0
  75. package/dist/types/sdk/host/control/runtime-gate.d.ts +21 -7
  76. package/dist/types/sdk/host/host.d.ts +10 -0
  77. package/dist/types/sdk/host/model-pin.d.ts +7 -2
  78. package/dist/types/sdk/host/sdk-run-capability.d.ts +1 -0
  79. package/dist/types/sdk/host/session-runtime.d.ts +67 -1
  80. package/dist/types/sdk/lifecycle/broker-client.d.ts +6 -0
  81. package/dist/types/sdk/lifecycle/service.d.ts +15 -3
  82. package/dist/types/sdk/lifecycle-session.d.ts +2 -0
  83. package/dist/types/sdk/prompt-deadline-manager.d.ts +11 -1
  84. package/dist/types/sdk/protocol/adapter-validation.d.ts +2 -0
  85. package/dist/types/sdk/router/session-router.d.ts +11 -0
  86. package/dist/types/sdk/session.d.ts +16 -0
  87. package/dist/types/sdk/startup-capability.d.ts +2 -1
  88. package/dist/types/session/account-inventory.d.ts +1 -0
  89. package/dist/types/session/agent-session.d.ts +24 -7
  90. package/dist/types/session/credential-store-errors.d.ts +1 -0
  91. package/dist/types/session/fold-coordinator.d.ts +201 -0
  92. package/dist/types/session/sdk-run-capability-internal.d.ts +7 -0
  93. package/dist/types/session/sdk-run-capability.d.ts +2 -0
  94. package/dist/types/session/session-manager.d.ts +65 -0
  95. package/dist/types/session/yield-queue.d.ts +5 -4
  96. package/dist/types/setup/frictionless-onboarding.d.ts +1 -0
  97. package/dist/types/telemetry/control.d.ts +11 -0
  98. package/dist/types/telemetry/events.d.ts +20 -0
  99. package/dist/types/telemetry/index.d.ts +3 -0
  100. package/dist/types/telemetry/transport.d.ts +22 -0
  101. package/dist/types/tools/bash-interactive.d.ts +23 -0
  102. package/dist/types/tools/bash.d.ts +4 -0
  103. package/dist/types/tools/gh.d.ts +8 -0
  104. package/dist/types/tools/index.d.ts +10 -5
  105. package/dist/types/web/search/providers/base.d.ts +9 -4
  106. package/dist/types/web/search/providers/xai.d.ts +2 -1
  107. package/dist/types/web/search/types.d.ts +14 -0
  108. package/package.json +10 -7
  109. package/scripts/generate-hotkeys-docs.ts +4 -0
  110. package/scripts/generate-sdk-operation-inventory.ts +9 -3
  111. package/scripts/verify-gjc-sdk-canonicalization.ts +35 -1
  112. package/src/async/job-manager.ts +671 -39
  113. package/src/cli/accounts-cli.ts +9 -5
  114. package/src/cli/args.ts +17 -0
  115. package/src/cli/auth-broker-cli.ts +40 -12
  116. package/src/cli/auth-gateway-cli.ts +273 -97
  117. package/src/cli/config-cli.ts +2 -2
  118. package/src/cli/fast-help.ts +4 -0
  119. package/src/cli/model-presets-cli.ts +100 -0
  120. package/src/cli/root-flags.ts +5 -0
  121. package/src/cli/update-cli.ts +117 -16
  122. package/src/cli.ts +6 -2
  123. package/src/commands/auth-gateway.ts +12 -8
  124. package/src/commands/gc.ts +19 -0
  125. package/src/commands/launch.ts +86 -18
  126. package/src/commands/model-presets.ts +24 -0
  127. package/src/commands/sdk.ts +113 -21
  128. package/src/config/autorouting-tier-map.ts +44 -0
  129. package/src/config/file-lock-gc.ts +9 -4
  130. package/src/config/file-lock.ts +890 -99
  131. package/src/config/internal/model-preset-registry-test-state.ts +28 -0
  132. package/src/config/internal/model-preset-registry-test-support.ts +11 -0
  133. package/src/config/keybindings.ts +6 -2
  134. package/src/config/model-preset-registry.ts +2795 -0
  135. package/src/config/model-profile-activation.ts +115 -28
  136. package/src/config/model-profile-contract.ts +4 -1
  137. package/src/config/model-profiles.ts +56 -14
  138. package/src/config/model-registry.ts +1044 -122
  139. package/src/config/model-resolver.ts +5 -0
  140. package/src/config/settings-schema.ts +19 -1
  141. package/src/config/settings.ts +11 -1
  142. package/src/coordinator-mcp/model-preset.ts +32 -17
  143. package/src/coordinator-mcp/projection-scan.ts +592 -0
  144. package/src/coordinator-mcp/question-state.ts +9 -11
  145. package/src/coordinator-mcp/server.ts +111 -42
  146. package/src/coordinator-mcp/session-reaper.ts +9 -3
  147. package/src/crash/index-store.ts +2 -0
  148. package/src/crash/record-loader.ts +10 -0
  149. package/src/extensibility/extensions/loader.ts +0 -1
  150. package/src/extensibility/extensions/types.ts +2 -3
  151. package/src/extensibility/gjc-plugins/runtime-adapters.ts +1 -0
  152. package/src/gjc-runtime/empty-delete-gc.ts +301 -0
  153. package/src/gjc-runtime/gc-render.ts +29 -0
  154. package/src/gjc-runtime/gc-runtime.ts +174 -11
  155. package/src/gjc-runtime/launch-worktree-reservation.ts +242 -0
  156. package/src/gjc-runtime/managed-owner-supervisor.ts +63 -51
  157. package/src/gjc-runtime/session-state-lock.ts +1321 -147
  158. package/src/gjc-runtime/session-state-sidecar.ts +1004 -25
  159. package/src/gjc-runtime/tmux-owner-isolation-cli.ts +4 -7
  160. package/src/gjc-runtime/tmux-owner-isolation.ts +2 -2
  161. package/src/gjc-runtime/tmux-sessions.ts +252 -13
  162. package/src/hooks/skill-state.ts +6 -0
  163. package/src/index.ts +1 -0
  164. package/src/internal-urls/docs-index.generated.ts +14 -13
  165. package/src/internal-urls/issue-pr-protocol.ts +4 -2
  166. package/src/lsp/config.ts +4 -3
  167. package/src/lsp/path-trust.ts +12 -6
  168. package/src/main.ts +66 -4
  169. package/src/master-mode/context.ts +50 -0
  170. package/src/master-mode/first-request.ts +65 -0
  171. package/src/master-mode/snapshot.ts +205 -0
  172. package/src/modes/acp/acp-agent.ts +616 -161
  173. package/src/modes/components/footer.ts +4 -2
  174. package/src/modes/components/frictionless-onboarding-selector.ts +48 -2
  175. package/src/modes/components/jobs-overlay-model.ts +99 -16
  176. package/src/modes/components/model-selector.ts +158 -18
  177. package/src/modes/components/oauth-selector.ts +6 -1
  178. package/src/modes/components/settings-selector.ts +1 -0
  179. package/src/modes/components/status-line/model-name.ts +53 -0
  180. package/src/modes/components/status-line/priority-row.ts +107 -0
  181. package/src/modes/components/status-line/segments.ts +29 -10
  182. package/src/modes/components/tool-status-header.ts +151 -15
  183. package/src/modes/controllers/extension-ui-controller.ts +1 -1
  184. package/src/modes/controllers/input-controller.ts +51 -6
  185. package/src/modes/controllers/selector-controller.ts +191 -96
  186. package/src/modes/interactive-mode.ts +14 -1
  187. package/src/modes/jobs-observer.ts +149 -16
  188. package/src/modes/print-mode.ts +64 -0
  189. package/src/modes/runtime-init.ts +1 -1
  190. package/src/modes/ui-language.ts +49 -2
  191. package/src/modes/utils/injected-user-submission.ts +15 -1
  192. package/src/prompts/system/master-mode.md +28 -0
  193. package/src/prompts/tools/bash.md +2 -2
  194. package/src/prompts/tools/fold-receipt.md +3 -0
  195. package/src/prompts/tools/job.md +1 -1
  196. package/src/sdk/acp/adapter.ts +17 -2
  197. package/src/sdk/broker/broker.ts +1671 -34
  198. package/src/sdk/broker/endpoint-authority.ts +86 -0
  199. package/src/sdk/broker/ensure.ts +5 -0
  200. package/src/sdk/broker/index.ts +3 -0
  201. package/src/sdk/broker/lifecycle.ts +217 -46
  202. package/src/sdk/broker/master-capability.ts +232 -0
  203. package/src/sdk/broker/session-index.ts +121 -17
  204. package/src/sdk/broker/session-scope.ts +272 -0
  205. package/src/sdk/broker/spawn-authority.ts +1031 -0
  206. package/src/sdk/broker/spawn-substrate.ts +548 -0
  207. package/src/sdk/broker/state-version.ts +26 -14
  208. package/src/sdk/broker/transport.ts +1 -0
  209. package/src/sdk/broker/worktree-occupancy.ts +90 -0
  210. package/src/sdk/bus/chat-daemon-control.ts +5 -2
  211. package/src/sdk/bus/chat-daemon-runtime.ts +46 -5
  212. package/src/sdk/bus/index.ts +51 -4
  213. package/src/sdk/bus/kind-aware-reconciliation.ts +36 -6
  214. package/src/sdk/bus/prompt-reconciliation.ts +28 -4
  215. package/src/sdk/bus/slack-conversation.ts +5 -1
  216. package/src/sdk/bus/telegram-daemon-contract.ts +1 -1
  217. package/src/sdk/cli/index.ts +1 -0
  218. package/src/sdk/cli/master-cli.ts +259 -0
  219. package/src/sdk/cli/rows.ts +11 -8
  220. package/src/sdk/cli/session-cli.ts +276 -6
  221. package/src/sdk/client/client.ts +14 -8
  222. package/src/sdk/client/discovery.ts +3 -1
  223. package/src/sdk/host/control/dispatch.ts +101 -37
  224. package/src/sdk/host/control/index.ts +2 -0
  225. package/src/sdk/host/control/operations.ts +2 -0
  226. package/src/sdk/host/control/runtime-gate.ts +203 -11
  227. package/src/sdk/host/host.ts +27 -2
  228. package/src/sdk/host/model-pin.ts +185 -25
  229. package/src/sdk/host/sdk-run-capability.ts +1 -0
  230. package/src/sdk/host/session-runtime.test.ts +581 -43
  231. package/src/sdk/host/session-runtime.ts +655 -101
  232. package/src/sdk/lifecycle/broker-client.ts +28 -0
  233. package/src/sdk/lifecycle/service.ts +279 -20
  234. package/src/sdk/lifecycle-session.ts +10 -2
  235. package/src/sdk/prompt-deadline-manager.ts +16 -1
  236. package/src/sdk/protocol/adapter-validation.ts +27 -0
  237. package/src/sdk/protocol/operation-inventory.generated.json +53 -2
  238. package/src/sdk/protocol/operation-registry.ts +17 -4
  239. package/src/sdk/router/session-router.ts +294 -154
  240. package/src/sdk/session.ts +440 -167
  241. package/src/sdk/startup-capability.ts +1 -0
  242. package/src/session/account-inventory.ts +27 -11
  243. package/src/session/agent-session.ts +1292 -180
  244. package/src/session/blob-store.ts +15 -5
  245. package/src/session/credential-store-errors.ts +2 -0
  246. package/src/session/fold-coordinator.ts +433 -0
  247. package/src/session/internal/managed-session-storage.ts +153 -78
  248. package/src/session/sdk-run-capability-internal.ts +15 -0
  249. package/src/session/sdk-run-capability.ts +2 -0
  250. package/src/session/session-manager.ts +342 -28
  251. package/src/session/yield-queue.ts +24 -10
  252. package/src/setup/frictionless-onboarding.ts +2 -0
  253. package/src/setup/provider-presets.json +1 -1
  254. package/src/slash-commands/helpers/credential-switch.ts +6 -1
  255. package/src/task/executor.ts +19 -3
  256. package/src/telemetry/control.ts +26 -0
  257. package/src/telemetry/events.ts +1268 -0
  258. package/src/telemetry/index.ts +3 -0
  259. package/src/telemetry/transport.ts +87 -0
  260. package/src/tools/bash-interactive.ts +182 -85
  261. package/src/tools/bash.ts +1028 -109
  262. package/src/tools/gh.ts +104 -48
  263. package/src/tools/github-cache.ts +2 -2
  264. package/src/tools/index.ts +11 -5
  265. package/src/tools/tool-catalog.generated.ts +1 -1
  266. package/src/web/search/provider.ts +120 -11
  267. package/src/web/search/providers/anthropic.ts +89 -17
  268. package/src/web/search/providers/base.ts +9 -4
  269. package/src/web/search/providers/gemini.ts +87 -15
  270. package/src/web/search/providers/openai-compatible.ts +23 -8
  271. package/src/web/search/providers/xai.ts +98 -12
  272. package/src/web/search/types.ts +16 -0
@@ -69,7 +69,7 @@ import {
69
69
  } from "./event-webhook";
70
70
  import {
71
71
  type CoordinatorModelProfileLoader,
72
- loadCoordinatorModelProfiles,
72
+ createCoordinatorModelProfileLoader,
73
73
  resolveCoordinatorMpreset,
74
74
  } from "./model-preset";
75
75
  import {
@@ -83,6 +83,7 @@ import {
83
83
  requireCoordinatorMutation,
84
84
  safeOpenCoordinatorArtifact,
85
85
  } from "./policy";
86
+ import { listCoordinatorJsonFilesWithRetry } from "./projection-scan";
86
87
  import {
87
88
  answerBindingMatches,
88
89
  buildCoordinatorAskAnswerSchema,
@@ -834,7 +835,7 @@ function toolSchema(name: CoordinatorToolName): {
834
835
  properties: {
835
836
  after_seq: { type: "integer", minimum: 0 },
836
837
  session_id: sessionId,
837
- event_types: { type: "array", items: { type: "string" } },
838
+ event_types: { type: "array", items: { type: "string", enum: [...COORDINATOR_EVENT_KINDS] } },
838
839
  timeout_ms: {
839
840
  type: "number",
840
841
  description: "Bounded event long-poll timeout in milliseconds, capped at 30 seconds.",
@@ -1497,26 +1498,17 @@ function publicSdkAcknowledgement(result: RuntimePromptAcknowledgement): Record<
1497
1498
  }
1498
1499
 
1499
1500
  async function listJsonFiles(dir: string): Promise<unknown[]> {
1500
- try {
1501
- const entries = await fs.readdir(dir);
1502
- const values = await Promise.all(
1503
- entries
1504
- .filter(entry => entry.endsWith(".json") && !entry.startsWith(".gjc-"))
1505
- .map(async entry => {
1506
- try {
1507
- return await readJsonFile(path.join(dir, entry));
1508
- } catch (error) {
1509
- if ((error as NodeJS.ErrnoException).code === "ENOENT") return null;
1510
- logger.warn("Coordinator projection read failed", { path: entry, error: String(error) });
1511
- throw error;
1512
- }
1513
- }),
1514
- );
1515
- return values.filter(value => value !== null);
1516
- } catch (error) {
1517
- if ((error as NodeJS.ErrnoException).code === "ENOENT") return [];
1518
- throw error;
1501
+ const scan = await listCoordinatorJsonFilesWithRetry(dir);
1502
+ if (scan.capped) throw new Error("coordinator_projection_scan_incomplete");
1503
+ if (scan.skippedEmpty > 0 || scan.skippedDebris > 0) {
1504
+ logger.warn("Coordinator projection scan skipped debris", {
1505
+ dir,
1506
+ parsed: scan.parsed,
1507
+ skippedDebris: scan.skippedDebris,
1508
+ skippedEmpty: scan.skippedEmpty,
1509
+ });
1519
1510
  }
1511
+ return scan.values;
1520
1512
  }
1521
1513
 
1522
1514
  const COORDINATOR_STATUS_EVENT_LIMIT = 100;
@@ -1538,7 +1530,10 @@ function brokerSessionId(record: Record<string, unknown>): string | null {
1538
1530
  }
1539
1531
 
1540
1532
  function brokerSessionScope(record: Record<string, unknown>): string | null {
1541
- return firstString(asRecord(record.locator) ?? {}, ["repo"]);
1533
+ // Locator v2 records the canonical workspace directory as `cwd`; the removed
1534
+ // `repo` field is never translated, so reading it here silently dropped every
1535
+ // broker row and failed open with an empty scoped listing.
1536
+ return firstString(asRecord(record.locator) ?? {}, ["cwd"]);
1542
1537
  }
1543
1538
 
1544
1539
  function sameCanonicalPath(left: string, right: string, platform: NodeJS.Platform): boolean {
@@ -2642,11 +2637,6 @@ async function writeActiveTurn(namespaceDir: string, turn: TurnRecord): Promise<
2642
2637
  });
2643
2638
  }
2644
2639
 
2645
- async function clearActiveTurn(namespaceDir: string, turn: TurnRecord): Promise<void> {
2646
- const active = asRecord(await readJsonFile(activeTurnFile(namespaceDir, turn.session_id)));
2647
- if (active?.turn_id === turn.turn_id) await removeCoordinatorFile(activeTurnFile(namespaceDir, turn.session_id));
2648
- }
2649
-
2650
2640
  async function readSessionState(namespaceDir: string, sessionId: string): Promise<CoordinatorSessionState | null> {
2651
2641
  return (await readJsonFile(sessionStateFile(namespaceDir, sessionId))) as CoordinatorSessionState | null;
2652
2642
  }
@@ -2666,9 +2656,6 @@ async function writeSessionStateUnlocked(
2666
2656
  } = {},
2667
2657
  ): Promise<CoordinatorSessionState> {
2668
2658
  const persisted = await readSessionState(namespaceDir, sessionId);
2669
- const session = asRecord(
2670
- await readJsonFile(path.join(namespaceDir, "sessions", `${safeExternalId("session", sessionId)}.json`)),
2671
- );
2672
2659
  // An overwrite is a canonical lifecycle repair, not proof that tool activity ended.
2673
2660
  const previous = options.overwrite ? null : persisted;
2674
2661
  const hasCurrentTurn = Object.hasOwn(options, "currentTurnId");
@@ -3260,7 +3247,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
3260
3247
  await routerReady;
3261
3248
  }
3262
3249
  const platform = options.platform ?? process.platform;
3263
- const loadModelProfiles = services.resolveModelProfiles ?? loadCoordinatorModelProfiles;
3250
+ const loadModelProfiles = services.resolveModelProfiles ?? createCoordinatorModelProfileLoader(routerAgentDir);
3264
3251
  // Every authoritative projection is scoped by the collision-resistant namespace identity.
3265
3252
  const namespaceDir = coordinatorNamespacePath(config);
3266
3253
  // The prior human-readable directory is migration input only; it is never authority.
@@ -5052,6 +5039,58 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
5052
5039
  const keyDigest = createHash("sha256").update(idempotencyKey).digest("hex");
5053
5040
  return path.join(namespaceDir, "idempotency", `${keyDigest}.json`);
5054
5041
  }
5042
+ const idempotencyFlights = new Map<string, { requestDigest: string; promise: Promise<Record<string, unknown>> }>();
5043
+ type IdempotencyFlightAdmission =
5044
+ | { kind: "conflict"; response: Record<string, unknown> }
5045
+ | { kind: "joined"; promise: Promise<Record<string, unknown>> }
5046
+ | { kind: "owner"; run: (operation: Promise<Record<string, unknown>>) => Promise<Record<string, unknown>> };
5047
+ function admitIdempotencyFlight(
5048
+ tool: string,
5049
+ idempotencyKey: string,
5050
+ canonicalArgs: Record<string, unknown>,
5051
+ ): IdempotencyFlightAdmission {
5052
+ const keyDigest = createHash("sha256").update(idempotencyKey).digest("hex");
5053
+ const requestDigest = createHash("sha256")
5054
+ .update(canonicalJson({ tool, args: canonicalArgs }))
5055
+ .digest("hex");
5056
+ const existingFlight = idempotencyFlights.get(keyDigest);
5057
+ if (existingFlight) {
5058
+ if (existingFlight.requestDigest !== requestDigest)
5059
+ return {
5060
+ kind: "conflict",
5061
+ response: {
5062
+ ok: false,
5063
+ error: { code: "idempotency_conflict", message: "idempotency key was used with a different request" },
5064
+ },
5065
+ };
5066
+ return { kind: "joined", promise: existingFlight.promise };
5067
+ }
5068
+ const result = Promise.withResolvers<Record<string, unknown>>();
5069
+ const flight = { requestDigest, promise: result.promise };
5070
+ idempotencyFlights.set(keyDigest, flight);
5071
+ return {
5072
+ kind: "owner",
5073
+ run: async operation => {
5074
+ void operation.then(result.resolve, result.reject);
5075
+ try {
5076
+ return await result.promise;
5077
+ } finally {
5078
+ if (idempotencyFlights.get(keyDigest) === flight) idempotencyFlights.delete(keyDigest);
5079
+ }
5080
+ },
5081
+ };
5082
+ }
5083
+ async function withIdempotencyFlight(
5084
+ tool: string,
5085
+ idempotencyKey: string,
5086
+ canonicalArgs: Record<string, unknown>,
5087
+ operation: () => Promise<Record<string, unknown>>,
5088
+ ): Promise<Record<string, unknown>> {
5089
+ const admission = admitIdempotencyFlight(tool, idempotencyKey, canonicalArgs);
5090
+ if (admission.kind === "conflict") return admission.response;
5091
+ if (admission.kind === "joined") return await admission.promise;
5092
+ return await admission.run(operation());
5093
+ }
5055
5094
  async function withOrderedSessionStateLocks<T>(
5056
5095
  lockFiles: readonly string[],
5057
5096
  operation: () => Promise<T>,
@@ -5083,6 +5122,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
5083
5122
  recoverInProgress = false,
5084
5123
  isNonterminal: (response: Record<string, unknown>) => boolean = isRouterRequestAmbiguous,
5085
5124
  lockAlreadyHeld = false,
5125
+ flightAlreadyOwned = false,
5086
5126
  ): Promise<Record<string, unknown>> {
5087
5127
  const keyDigest = createHash("sha256").update(idempotencyKey).digest("hex");
5088
5128
  const requestDigest = createHash("sha256")
@@ -5187,7 +5227,11 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
5187
5227
  });
5188
5228
  return response;
5189
5229
  };
5190
- return lockAlreadyHeld ? execute() : withSessionStateLock(lockFile, execute);
5230
+ const executeWithLock = async (): Promise<Record<string, unknown>> =>
5231
+ lockAlreadyHeld ? await execute() : await withSessionStateLock(lockFile, execute);
5232
+ return flightAlreadyOwned
5233
+ ? await executeWithLock()
5234
+ : await withIdempotencyFlight(tool, idempotencyKey, canonicalArgs, executeWithLock);
5191
5235
  }
5192
5236
 
5193
5237
  async function brokerSession(
@@ -5289,11 +5333,28 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
5289
5333
  sessionId,
5290
5334
  );
5291
5335
  } catch (closeError) {
5292
- throw new SdkClientError(
5293
- UNOBSERVED_COMPENSATION_CODE,
5294
- `Coordinator creation failed after remote session ${sessionId} was created, and compensation failed.`,
5295
- { primary: error, compensation: closeError, session_id: sessionId },
5296
- );
5336
+ let listed: Record<string, unknown>;
5337
+ try {
5338
+ listed = await paginatedBrokerSessionList(cwd, { cwd });
5339
+ } catch (listError) {
5340
+ throw new SdkClientError(
5341
+ UNOBSERVED_COMPENSATION_CODE,
5342
+ `Coordinator creation failed after remote session ${sessionId} was created, and compensation failed.`,
5343
+ { primary: error, compensation: closeError, list: listError, session_id: sessionId },
5344
+ );
5345
+ }
5346
+ const rows = jsonRecords(Array.isArray(listed.sessions) ? listed.sessions : []);
5347
+ const row = rows.find(candidate => brokerSessionId(candidate) === sessionId);
5348
+ const deadUncertain =
5349
+ row !== undefined &&
5350
+ row.live !== true &&
5351
+ (row.terminalUncertain === true || (row as Record<string, unknown>).terminal_uncertain === true);
5352
+ if (!deadUncertain)
5353
+ throw new SdkClientError(
5354
+ UNOBSERVED_COMPENSATION_CODE,
5355
+ `Coordinator creation failed after remote session ${sessionId} was created, and compensation failed.`,
5356
+ { primary: error, compensation: closeError, session_id: sessionId },
5357
+ );
5297
5358
  }
5298
5359
  throw error;
5299
5360
  }
@@ -6721,7 +6782,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
6721
6782
  // any stale pointer directly: once the turn projection is rewritten
6722
6783
  // terminal, readActiveTurn() intentionally rejects it and cannot
6723
6784
  // discover the legacy active-turn file to clear.
6724
- await fs.rm(activeTurnFile(namespaceDir, sessionId), { force: true });
6785
+ await removeCoordinatorFile(activeTurnFile(namespaceDir, sessionId));
6725
6786
  }
6726
6787
  // A runtime sidecar can advance a live session while the canonical WAL still
6727
6788
  // needs projection repair. Preserve that observed lifecycle state (and its
@@ -8412,6 +8473,9 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
8412
8473
  }
8413
8474
  if (name === "gjc_coordinator_start_session") {
8414
8475
  requireCoordinatorMutation(config, "sessions", args);
8476
+ const suppliedPrompt = Object.hasOwn(args, "prompt");
8477
+ if (suppliedPrompt && (typeof args.prompt !== "string" || args.prompt.trim().length === 0))
8478
+ return { ok: false, error: { code: "invalid_input", message: "Prompt must not be empty." } };
8415
8479
  const idempotencyKey = requiredIdempotencyKey(args);
8416
8480
  const cwd = await canonicalBrokerWorkspace(await assertCoordinatorWorkdir(config, args.cwd));
8417
8481
  const mpresetResolution = await resolveCoordinatorMpreset(args.mpreset, loadModelProfiles);
@@ -8434,7 +8498,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
8434
8498
  error: { code: "unknown_model", message: modelResolution.error },
8435
8499
  };
8436
8500
  }
8437
- const prompt = typeof args.prompt === "string" && args.prompt.length > 0 ? args.prompt : null;
8501
+ const prompt = suppliedPrompt ? (args.prompt as string) : null;
8438
8502
  /**
8439
8503
  * A prepared session is deliberately not ready for input: its readiness
8440
8504
  * is withheld until an operator-supplied thread is bound and activation
@@ -8785,7 +8849,10 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
8785
8849
  code === "live_session"
8786
8850
  );
8787
8851
  };
8788
- return await withOrderedSessionStateLocks(
8852
+ const flight = admitIdempotencyFlight(name, retirementKey, canonicalArgs);
8853
+ if (flight.kind === "conflict") return flight.response;
8854
+ if (flight.kind === "joined") return await flight.promise;
8855
+ const retirementOperation = withOrderedSessionStateLocks(
8789
8856
  [idempotencyLockFile(creationKey), idempotencyLockFile(retirementKey)],
8790
8857
  async () =>
8791
8858
  await withToolIdempotency(
@@ -8969,8 +9036,10 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
8969
9036
  true,
8970
9037
  isRetirementRetryable,
8971
9038
  true,
9039
+ true,
8972
9040
  ),
8973
9041
  );
9042
+ return await flight.run(retirementOperation);
8974
9043
  }
8975
9044
  if (name === "gjc_coordinator_activate_session") {
8976
9045
  requireCoordinatorMutation(config, "sessions", args);
@@ -9079,10 +9148,10 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
9079
9148
  }
9080
9149
  if (name === "gjc_coordinator_send_prompt") {
9081
9150
  requireCoordinatorMutation(config, "sessions", args);
9151
+ if (typeof args.prompt !== "string" || args.prompt.trim().length === 0)
9152
+ return { ok: false, error: { code: "invalid_input", message: "Prompt must not be empty." } };
9082
9153
  const idempotencyKey = requiredIdempotencyKey(args);
9083
9154
  const sessionId = safeExternalId("session", args.session_id);
9084
- if (typeof args.prompt !== "string" || args.prompt.length === 0)
9085
- return { ok: false, error: { code: "invalid_input", message: "prompt is required" } };
9086
9155
  const prompt = args.prompt;
9087
9156
  return await withToolIdempotency(
9088
9157
  name,
@@ -100,9 +100,15 @@ export function createSessionReaper(deps: SessionReaperDeps, policy: SessionReap
100
100
  function schedule(gen: number): void {
101
101
  timer = setTimeout(() => {
102
102
  if (gen !== generation) return; // stale timer from a prior start()
103
- void sweepOnce().finally(() => {
104
- if (gen === generation) schedule(gen);
105
- });
103
+ void sweepOnce()
104
+ .catch(error => {
105
+ // A refused sweep (e.g. a fail-closed capped projection scan) must not
106
+ // become an unhandled rejection; log and keep the schedule alive.
107
+ logger.warn(`session-reaper: sweep refused: ${error instanceof Error ? error.message : String(error)}`);
108
+ })
109
+ .finally(() => {
110
+ if (gen === generation) schedule(gen);
111
+ });
106
112
  }, sweepIntervalMs);
107
113
  // The reaper must never keep the coordinator process alive by itself.
108
114
  (timer as { unref?: () => void }).unref?.();
@@ -613,6 +613,7 @@ export function applyCrashEvent(index: CrashIndex, event: CrashEvent, now: numbe
613
613
  }
614
614
 
615
615
  // occurrence
616
+ if (event.provenance === "eval" || event.provenance === "bun_test") return false;
616
617
  // Clear before deduplication: a replayed occurrence after main-index publish
617
618
  // but before retirement-sidecar publish must still supersede a stale tombstone.
618
619
  index.retiredFingerprints = index.retiredFingerprints.filter(fingerprint => fingerprint !== event.fingerprint);
@@ -698,6 +699,7 @@ async function recoverAndRecomputeRetainedCounts(index: CrashIndex, crashLogPath
698
699
  const recordsByFingerprint = new Map<string, LoadedCrashRecord[]>();
699
700
  const seenRecordIds = new Set<string>();
700
701
  for (const record of parseRecoverableCrashRecords(contents)) {
702
+ if (record.provenance === "eval" || record.provenance === "bun_test") continue;
701
703
  if (seenRecordIds.has(record.recordId)) continue;
702
704
  seenRecordIds.add(record.recordId);
703
705
  const records = recordsByFingerprint.get(record.fingerprint) ?? [];
@@ -15,6 +15,7 @@ import {
15
15
  CRASH_FINGERPRINT_VERSION,
16
16
  CRASH_RECORD_MARKER,
17
17
  type CrashFingerprint,
18
+ type CrashProvenance,
18
19
  computeCrashFingerprint,
19
20
  parseCrashRecordMarker,
20
21
  } from "@gajae-code/utils";
@@ -33,6 +34,7 @@ export interface LoadedCrashRecord {
33
34
  readonly body: string;
34
35
  /** The `Name: message` part of the header line. */
35
36
  readonly headline: string;
37
+ readonly provenance?: CrashProvenance;
36
38
  }
37
39
 
38
40
  interface ParsedCrashRecord {
@@ -78,6 +80,7 @@ function parseCandidates(contents: string): ParsedCrashRecord[] {
78
80
  let at = 0;
79
81
  let buffer: string[] = [];
80
82
  let started = false;
83
+ let provenance: CrashProvenance | undefined;
81
84
  let pending: ParsedCrashRecord | undefined;
82
85
  for (const line of contents.split("\n")) {
83
86
  if (pending) {
@@ -92,6 +95,12 @@ function parseCandidates(contents: string): ParsedCrashRecord[] {
92
95
  continue;
93
96
  }
94
97
  headline = header[2] ?? "";
98
+ const label = line.slice(line.indexOf("[") + 1, line.indexOf("]"));
99
+ const provenanceValue = label
100
+ .split(";")
101
+ .find(part => part.startsWith("provenance="))
102
+ ?.slice(11);
103
+ provenance = provenanceValue === "eval" || provenanceValue === "bun_test" ? provenanceValue : undefined;
95
104
  at = parsedAt;
96
105
  buffer = [];
97
106
  started = true;
@@ -116,6 +125,7 @@ function parseCandidates(contents: string): ParsedCrashRecord[] {
116
125
  messageClass: fingerprint?.messageClass ?? "",
117
126
  body: lines.join("\n").trimEnd(),
118
127
  headline,
128
+ ...(provenance === undefined ? {} : { provenance }),
119
129
  },
120
130
  complete: false,
121
131
  bound: marker.version === CRASH_FINGERPRINT_VERSION && fingerprint !== undefined,
@@ -345,7 +345,6 @@ class ConcreteExtensionAPI implements ExtensionAPI {
345
345
  onQueuedPromoted?: (promotion: { startsOwnRun?: boolean; removed?: boolean }) => void;
346
346
  onDispatchDisposition?: (promotion: { startsOwnRun: boolean }) => void;
347
347
  preflightSignal?: AbortSignal;
348
- sdkRunToken?: string;
349
348
  },
350
349
  ): Promise<void> {
351
350
  return Promise.resolve(this.runtime.sendUserMessage(content, options));
@@ -536,7 +536,7 @@ export interface ExtensionContext {
536
536
  onPreflightAcceptCommit?: () => void | Promise<void>;
537
537
  onSkillPrepared?: (meta: { name: string; path: string; lineCount?: number; cleanedArgs?: string }) => void;
538
538
  preflightSignal?: AbortSignal;
539
- sdkRunToken?: string;
539
+ sdkRunCapability?: unknown;
540
540
  },
541
541
  ): Promise<unknown>;
542
542
  setPlanMode?(on: boolean): unknown;
@@ -1316,7 +1316,6 @@ export interface ExtensionAPI {
1316
1316
  onDispatchDisposition?: (promotion: { startsOwnRun: boolean }) => void;
1317
1317
  preflightSignal?: AbortSignal;
1318
1318
  /** Internal SDK correlation owner for an exact queued follow-up. */
1319
- sdkRunToken?: string;
1320
1319
  },
1321
1320
  ): Promise<void>;
1322
1321
 
@@ -1546,7 +1545,6 @@ export type SendUserMessageHandler = (
1546
1545
  onQueuedPromoted?: (promotion: { startsOwnRun?: boolean; removed?: boolean }) => void;
1547
1546
  preflightSignal?: AbortSignal;
1548
1547
  /** Internal SDK correlation owner for an exact queued follow-up. */
1549
- sdkRunToken?: string;
1550
1548
  },
1551
1549
  ) => void | Promise<void>;
1552
1550
 
@@ -1708,6 +1706,7 @@ export interface ExtensionContextActions {
1708
1706
  onPreflightAcceptCommit?: () => void | Promise<void>;
1709
1707
  onSkillPrepared?: (meta: { name: string; path: string; lineCount?: number; cleanedArgs?: string }) => void;
1710
1708
  preflightSignal?: AbortSignal;
1709
+ sdkRunCapability?: unknown;
1711
1710
  },
1712
1711
  ) => Promise<unknown>;
1713
1712
  setPlanMode?: (on: boolean) => unknown;
@@ -453,6 +453,7 @@ export async function buildPluginMcpConfigs(input: { cwd: string }): Promise<{
453
453
  command: cfg.command,
454
454
  args: cfg.args,
455
455
  cwd: cfg.cwd ? nodePath.resolve(entry.pluginRoot, cfg.cwd) : entry.pluginRoot,
456
+ timeout: 5_000,
456
457
  // Third-party plugin MCP processes must not inherit host secrets;
457
458
  // only a minimal OS allowlist (PATH/HOME/temp/locale) is provided.
458
459
  noInheritEnv: true,