@gajae-code/coding-agent 0.18.11 → 0.18.12

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 (270) hide show
  1. package/CHANGELOG.md +293 -0
  2. package/dist/types/async/job-manager.d.ts +7 -3
  3. package/dist/types/async/result-formatting.d.ts +2 -0
  4. package/dist/types/autoresearch/git.d.ts +1 -1
  5. package/dist/types/capability/fs.d.ts +2 -2
  6. package/dist/types/cli/accounts-cli.d.ts +2 -2
  7. package/dist/types/cli/doctor/install-repairs.d.ts +4 -1
  8. package/dist/types/cli/install-activation.d.ts +10 -4
  9. package/dist/types/cli/public-command-errors.d.ts +1 -0
  10. package/dist/types/cli/update-cli.d.ts +14 -1
  11. package/dist/types/cli-ordinary.d.ts +2 -2
  12. package/dist/types/commands/accounts.d.ts +2 -1
  13. package/dist/types/commands/migrate.d.ts +1 -1
  14. package/dist/types/commands/sdk.d.ts +5 -0
  15. package/dist/types/commit/changelog/index.d.ts +1 -0
  16. package/dist/types/config/autorouting-tier-map.d.ts +30 -0
  17. package/dist/types/config/config-hot-reload-trust.d.ts +12 -0
  18. package/dist/types/config/config-hot-reload.d.ts +39 -0
  19. package/dist/types/config/file-lock.d.ts +8 -1
  20. package/dist/types/config/model-discovery-manager.d.ts +2 -0
  21. package/dist/types/config/model-profile-activation.d.ts +62 -6
  22. package/dist/types/config/model-registry.d.ts +56 -5
  23. package/dist/types/config/model-resolver.d.ts +2 -0
  24. package/dist/types/config/models-config-schema.d.ts +10 -0
  25. package/dist/types/config/settings-schema.d.ts +46 -3
  26. package/dist/types/config/settings.d.ts +33 -1
  27. package/dist/types/dap/config.d.ts +8 -4
  28. package/dist/types/debug/system-info.d.ts +6 -1
  29. package/dist/types/discovery/mcp-compat.d.ts +3 -1
  30. package/dist/types/edit/modes/patch.d.ts +5 -0
  31. package/dist/types/edit/modes/replace.d.ts +3 -0
  32. package/dist/types/exec/bash-executor.d.ts +12 -0
  33. package/dist/types/gjc-runtime/managed-owner-admission.d.ts +3 -0
  34. package/dist/types/gjc-runtime/session-state-lock.d.ts +2 -0
  35. package/dist/types/gjc-runtime/state-runtime.d.ts +0 -2
  36. package/dist/types/migrate/adapters/codex.d.ts +2 -1
  37. package/dist/types/migrate/adapters/index.d.ts +10 -1
  38. package/dist/types/migrate/executor.d.ts +3 -1
  39. package/dist/types/migrate/types.d.ts +15 -3
  40. package/dist/types/modes/components/settings-selector.d.ts +6 -0
  41. package/dist/types/modes/controllers/selector-controller.d.ts +2 -0
  42. package/dist/types/modes/theme/theme.d.ts +1 -0
  43. package/dist/types/runtime-mcp/content-limits.d.ts +4 -0
  44. package/dist/types/runtime-mcp/manager.d.ts +7 -0
  45. package/dist/types/runtime-mcp/plugin-mcp-name-filter.d.ts +22 -0
  46. package/dist/types/runtime-mcp/tool-bridge.d.ts +4 -0
  47. package/dist/types/sdk/broker/broker-exit.d.ts +2 -2
  48. package/dist/types/sdk/broker/broker.d.ts +7 -1
  49. package/dist/types/sdk/broker/daemon-entry.d.ts +11 -1
  50. package/dist/types/sdk/broker/doctor-restart.d.ts +1 -1
  51. package/dist/types/sdk/broker/ensure.d.ts +136 -7
  52. package/dist/types/sdk/broker/hop.d.ts +36 -0
  53. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +18 -10
  54. package/dist/types/sdk/broker/lock-host-identity.d.ts +5 -0
  55. package/dist/types/sdk/broker/session-index.d.ts +4 -1
  56. package/dist/types/sdk/bus/chat-command-policy.d.ts +2 -1
  57. package/dist/types/sdk/bus/index.d.ts +23 -0
  58. package/dist/types/sdk/bus/reconciliation-store.d.ts +10 -0
  59. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +3 -1
  60. package/dist/types/sdk/cli/session-cli.d.ts +0 -1
  61. package/dist/types/sdk/host/session-runtime.d.ts +32 -0
  62. package/dist/types/sdk/prompt-deadline-flush.d.ts +42 -4
  63. package/dist/types/sdk/prompt-deadline-manager.d.ts +2 -0
  64. package/dist/types/sdk/protocol/adapter-validation.d.ts +10 -4
  65. package/dist/types/sdk/reconciliation-extensions.d.ts +1 -1
  66. package/dist/types/sdk/session-list.d.ts +1 -1
  67. package/dist/types/session/agent-session.d.ts +58 -2
  68. package/dist/types/session/internal/managed-session-scope.d.ts +2 -2
  69. package/dist/types/session/internal/managed-session-storage.d.ts +1 -1
  70. package/dist/types/session/internal/native-publish-outcome.d.ts +1 -1
  71. package/dist/types/session/messages.d.ts +5 -1
  72. package/dist/types/session/session-manager.d.ts +13 -3
  73. package/dist/types/session/startup-auth-config.d.ts +24 -2
  74. package/dist/types/session/streaming-output.d.ts +11 -0
  75. package/dist/types/session/terminal-abort.d.ts +3 -1
  76. package/dist/types/session-import/service.d.ts +1 -1
  77. package/dist/types/skill-state/workflow-mutation-guard.d.ts +7 -3
  78. package/dist/types/slash-commands/helpers/provider-auth-status.d.ts +8 -0
  79. package/dist/types/task/executor.d.ts +19 -2
  80. package/dist/types/tools/ast-edit-write-path.d.ts +10 -0
  81. package/dist/types/tools/atomic-file-write.d.ts +9 -7
  82. package/dist/types/tools/bash-allowed-prefixes.d.ts +11 -0
  83. package/dist/types/tools/descriptors.d.ts +2 -0
  84. package/dist/types/tools/fetch.d.ts +1 -1
  85. package/dist/types/tools/index.d.ts +2 -0
  86. package/dist/types/tools/python.d.ts +1 -1
  87. package/dist/types/tools/report-issue.d.ts +41 -0
  88. package/dist/types/tools/subagent.d.ts +23 -0
  89. package/dist/types/web/scrapers/types.d.ts +8 -1
  90. package/dist/types/web/search/providers/insane.d.ts +7 -1
  91. package/dist/types/web/search/providers/searxng.d.ts +5 -0
  92. package/package.json +8 -8
  93. package/scripts/build-binary.ts +2 -1
  94. package/scripts/capture-subagent-compact-panel-showcase.ts +105 -0
  95. package/scripts/generate-docs-index.ts +8 -1
  96. package/scripts/generate-sdk-operation-inventory.ts +22 -4
  97. package/src/async/job-manager.ts +356 -56
  98. package/src/async/result-formatting.ts +40 -0
  99. package/src/autoresearch/git.ts +9 -2
  100. package/src/autoresearch/prompts/prompt-setup.md +1 -1
  101. package/src/capability/fs.ts +27 -19
  102. package/src/capability/index.ts +2 -2
  103. package/src/cli/accounts-cli.ts +176 -8
  104. package/src/cli/auth-broker-cli.ts +21 -11
  105. package/src/cli/doctor/install-repairs.ts +35 -5
  106. package/src/cli/fast-help.ts +1 -1
  107. package/src/cli/install-activation.ts +47 -4
  108. package/src/cli/migrate-cli.ts +7 -5
  109. package/src/cli/public-command-errors.ts +4 -0
  110. package/src/cli/root-flags.ts +2 -2
  111. package/src/cli/stats-cli.ts +9 -5
  112. package/src/cli/update-cli.ts +224 -7
  113. package/src/cli-ordinary.ts +7 -2
  114. package/src/commands/accounts.ts +10 -5
  115. package/src/commands/migrate.ts +3 -3
  116. package/src/commands/sdk.ts +199 -42
  117. package/src/commands/ultragoal.ts +16 -8
  118. package/src/commit/changelog/index.ts +39 -1
  119. package/src/commit/message.ts +10 -3
  120. package/src/config/autorouting-tier-map.ts +30 -0
  121. package/src/config/config-hot-reload-trust.ts +148 -0
  122. package/src/config/config-hot-reload.ts +734 -0
  123. package/src/config/file-lock.ts +216 -56
  124. package/src/config/model-bindings-applier.ts +85 -47
  125. package/src/config/model-discovery-manager.ts +10 -0
  126. package/src/config/model-profile-activation.ts +716 -113
  127. package/src/config/model-registry.ts +1334 -158
  128. package/src/config/model-resolver.ts +3 -1
  129. package/src/config/models-config-schema.ts +6 -0
  130. package/src/config/settings-schema.ts +60 -3
  131. package/src/config/settings.ts +458 -24
  132. package/src/coordinator-mcp/policy.ts +26 -1
  133. package/src/crash/sanitize.ts +4 -0
  134. package/src/customization/import.ts +3 -1
  135. package/src/dap/config.ts +31 -11
  136. package/src/dap/defaults.json +1 -1
  137. package/src/debug/crash-diagnostics.ts +2 -1
  138. package/src/debug/report-bundle.ts +19 -0
  139. package/src/debug/system-info.ts +8 -3
  140. package/src/defaults/gjc/extensions/grok-cli-vendor/src/shared/base-url.ts +21 -7
  141. package/src/discovery/agents-md.ts +1 -1
  142. package/src/discovery/helpers.ts +1 -2
  143. package/src/discovery/mcp-compat.ts +120 -17
  144. package/src/edit/apply-patch/index.ts +12 -1
  145. package/src/edit/index.ts +13 -17
  146. package/src/edit/modes/patch.ts +187 -8
  147. package/src/edit/modes/replace.ts +136 -0
  148. package/src/eval/js/context-manager.ts +29 -3
  149. package/src/eval/js/worker-entry.ts +51 -18
  150. package/src/eval/py/executor.ts +14 -4
  151. package/src/eval/py/runner.py +144 -5
  152. package/src/exec/bash-executor.ts +81 -4
  153. package/src/exec/bash-shell-guardian.ts +4 -1
  154. package/src/exec/bash-shell-supervisor.ts +8 -4
  155. package/src/exec/isolated-shell.ts +5 -1
  156. package/src/export/html/index.ts +46 -6
  157. package/src/extensibility/custom-tools/wrapper.ts +3 -1
  158. package/src/extensibility/extensions/loader.ts +8 -1
  159. package/src/extensibility/plugins/marketplace/fetcher.ts +2 -2
  160. package/src/gjc-runtime/gc-runtime.ts +33 -6
  161. package/src/gjc-runtime/launch-tmux.ts +7 -2
  162. package/src/gjc-runtime/launch-worktree.test.ts +148 -0
  163. package/src/gjc-runtime/launch-worktree.ts +101 -14
  164. package/src/gjc-runtime/managed-owner-admission.ts +46 -2
  165. package/src/gjc-runtime/session-resolution.ts +38 -6
  166. package/src/gjc-runtime/session-state-lock.ts +59 -14
  167. package/src/gjc-runtime/state-runtime.ts +23 -19
  168. package/src/hooks/native-skill-hook.ts +14 -11
  169. package/src/internal-urls/docs-index.generated.ts +14 -13
  170. package/src/internal-urls/local-protocol.ts +142 -8
  171. package/src/lsp/index.ts +29 -0
  172. package/src/lsp/workspace-path.ts +23 -0
  173. package/src/migrate/action-planner.ts +57 -0
  174. package/src/migrate/adapters/claude-code.ts +23 -4
  175. package/src/migrate/adapters/codex.ts +22 -4
  176. package/src/migrate/adapters/index.ts +22 -4
  177. package/src/migrate/adapters/opencode.ts +6 -1
  178. package/src/migrate/executor.ts +84 -7
  179. package/src/migrate/report.ts +19 -2
  180. package/src/migrate/types.ts +14 -4
  181. package/src/modes/DESIGN.md +51 -0
  182. package/src/modes/acp/permission-mode.ts +18 -1
  183. package/src/modes/components/assistant-message.ts +55 -1
  184. package/src/modes/components/settings-selector.ts +103 -37
  185. package/src/modes/controllers/command-controller.ts +38 -2
  186. package/src/modes/controllers/plan-mode-controller.ts +17 -7
  187. package/src/modes/controllers/selector-controller.ts +62 -6
  188. package/src/modes/interactive-mode.ts +68 -0
  189. package/src/modes/theme/theme.ts +15 -0
  190. package/src/prompts/tools/debug.md +1 -1
  191. package/src/prompts/tools/read.md +1 -1
  192. package/src/prompts/tools/report-issue.md +13 -0
  193. package/src/prompts/tools/task.md +1 -1
  194. package/src/runtime-credential-selector.ts +2 -2
  195. package/src/runtime-mcp/content-limits.ts +7 -0
  196. package/src/runtime-mcp/manager-pool.test.ts +5 -4
  197. package/src/runtime-mcp/manager.ts +32 -0
  198. package/src/runtime-mcp/oauth-flow.ts +32 -6
  199. package/src/runtime-mcp/plugin-mcp-name-filter.ts +62 -0
  200. package/src/runtime-mcp/render.ts +16 -1
  201. package/src/runtime-mcp/tool-bridge.ts +126 -17
  202. package/src/sdk/broker/broker-exit.ts +8 -5
  203. package/src/sdk/broker/broker.ts +342 -82
  204. package/src/sdk/broker/daemon-entry.ts +338 -116
  205. package/src/sdk/broker/doctor-restart.ts +2 -1
  206. package/src/sdk/broker/ensure.ts +1143 -265
  207. package/src/sdk/broker/hop.ts +274 -0
  208. package/src/sdk/broker/lifecycle-ledger.ts +35 -11
  209. package/src/sdk/broker/lock-host-identity.ts +29 -0
  210. package/src/sdk/broker/session-index.ts +62 -12
  211. package/src/sdk/bus/chat-command-policy.ts +16 -3
  212. package/src/sdk/bus/index.ts +52 -21
  213. package/src/sdk/bus/reconciliation-store.ts +201 -8
  214. package/src/sdk/bus/telegram-daemon-contract.ts +3 -1
  215. package/src/sdk/cli/session-cli.ts +21 -6
  216. package/src/sdk/host/session-runtime.test.ts +118 -23
  217. package/src/sdk/host/session-runtime.ts +352 -47
  218. package/src/sdk/lifecycle/service.ts +1 -0
  219. package/src/sdk/prompt-deadline-flush.ts +78 -5
  220. package/src/sdk/prompt-deadline-manager.ts +68 -14
  221. package/src/sdk/protocol/adapter-validation.ts +53 -11
  222. package/src/sdk/protocol/operation-inventory.generated.json +101 -2
  223. package/src/sdk/reconciliation-extensions.ts +1 -0
  224. package/src/sdk/session-list.ts +1 -0
  225. package/src/sdk/session.ts +114 -61
  226. package/src/session/agent-session.ts +1043 -81
  227. package/src/session/internal/managed-session-scope.ts +126 -39
  228. package/src/session/internal/managed-session-storage.ts +14 -11
  229. package/src/session/internal/native-publish-outcome.ts +5 -1
  230. package/src/session/messages.ts +47 -6
  231. package/src/session/session-manager.ts +236 -14
  232. package/src/session/startup-auth-config.ts +116 -8
  233. package/src/session/streaming-output.ts +262 -4
  234. package/src/session/terminal-abort.ts +14 -2
  235. package/src/session-import/service.ts +1 -0
  236. package/src/skill-state/workflow-mutation-guard.ts +491 -32
  237. package/src/slash-commands/builtin-registry.ts +15 -3
  238. package/src/slash-commands/helpers/credential-switch.ts +10 -5
  239. package/src/slash-commands/helpers/provider-auth-status.ts +456 -0
  240. package/src/task/executor.ts +103 -12
  241. package/src/task/index.ts +389 -175
  242. package/src/tools/ast-edit-write-path.ts +64 -0
  243. package/src/tools/ast-edit.ts +18 -1
  244. package/src/tools/atomic-file-write.ts +46 -15
  245. package/src/tools/auto-generated-guard.ts +3 -0
  246. package/src/tools/bash-allowed-prefixes.ts +153 -5
  247. package/src/tools/bash-interactive.ts +14 -2
  248. package/src/tools/bash.ts +14 -0
  249. package/src/tools/debug.ts +7 -6
  250. package/src/tools/descriptors.test.ts +44 -0
  251. package/src/tools/descriptors.ts +34 -2
  252. package/src/tools/fetch.ts +3 -2
  253. package/src/tools/index.ts +2 -0
  254. package/src/tools/output-meta.ts +23 -2
  255. package/src/tools/python.ts +1 -1
  256. package/src/tools/read.ts +17 -35
  257. package/src/tools/report-issue.ts +184 -0
  258. package/src/tools/subagent-render.ts +114 -12
  259. package/src/tools/subagent.ts +108 -1
  260. package/src/tools/todo-write.ts +41 -3
  261. package/src/tools/tool-catalog.generated.ts +31 -3
  262. package/src/tools/write.ts +35 -3
  263. package/src/utils/commit-message-generator.ts +9 -2
  264. package/src/utils/file-mentions.ts +29 -1
  265. package/src/utils/title-generator.ts +7 -2
  266. package/src/web/scrapers/index.ts +3 -1
  267. package/src/web/scrapers/types.ts +8 -0
  268. package/src/web/scrapers/youtube.ts +3 -2
  269. package/src/web/search/providers/insane.ts +91 -28
  270. package/src/web/search/providers/searxng.ts +62 -10
@@ -45,7 +45,9 @@ import {
45
45
  createInvocationReconciliation,
46
46
  createSdkSessionRuntimeExtension,
47
47
  createSdkSurfaceFactory,
48
+ type PersistenceInstrumentationEvent,
48
49
  RetainedTerminalBoundaryRegistry,
50
+ type SdkOnlyDeadlineRecoveryCheckpoint,
49
51
  type SdkOnlyInvocationRecord,
50
52
  type SdkOnlyReconciliationStore,
51
53
  type SdkOnlyTerminalAbortSeams,
@@ -143,7 +145,11 @@ test("thinking.set uses the control setter and reports the applied session level
143
145
  const transport = memoryTransport(frame => {
144
146
  if (frame.type === "control_response" && frame.id === "thinking-set") response.resolve(frame);
145
147
  });
146
- createSdkSessionRuntimeExtension(api, { agentDir: cwd, createTransport: async () => transport });
148
+ createSdkSessionRuntimeExtension(api, {
149
+ agentDir: cwd,
150
+ ensureBrokerImpl: async () => testBrokerDiscovery,
151
+ createTransport: async () => transport,
152
+ });
147
153
  const ctx = extensionContext(transport.sessionId, cwd);
148
154
  try {
149
155
  await handlers.get("session_start")?.({}, ctx);
@@ -4443,7 +4449,10 @@ describe("SessionSdkSessionRuntime", () => {
4443
4449
  function createTestRuntimeExtension(api: ExtensionAPI, options: CreateSdkSessionRuntimeOptions): void {
4444
4450
  createSdkSessionRuntimeExtension(api, {
4445
4451
  ...options,
4446
- ensureBrokerImpl: options.ensureBrokerImpl ?? (async () => testBrokerDiscovery),
4452
+ ensureBrokerImpl: async input => {
4453
+ if (options.ensureBrokerImpl) return await options.ensureBrokerImpl(input);
4454
+ return testBrokerDiscovery;
4455
+ },
4447
4456
  });
4448
4457
  }
4449
4458
 
@@ -4609,6 +4618,8 @@ async function invocationHarness(
4609
4618
  }) => void;
4610
4619
  branch?: unknown[];
4611
4620
  onInvocationCompletionReconciled?: (kind: string, correlation: { commandId: string; turnId: string }) => void;
4621
+ /** Test-only instrumentation of the persistence chain. */
4622
+ onPersistenceInstrumentation?: (event: PersistenceInstrumentationEvent) => void;
4612
4623
  /** Override/extend the INTERNAL terminal-abort seams the runtime is threaded. */
4613
4624
  terminalAbortSeams?: Partial<SdkOnlyTerminalAbortSeams>;
4614
4625
  },
@@ -4640,7 +4651,7 @@ async function invocationHarness(
4640
4651
  createTestRuntimeExtension(api, {
4641
4652
  agentDir: cwd,
4642
4653
  ...(hooks.onLifecycleDrainTimeout ? { onLifecycleDrainTimeoutForTests: hooks.onLifecycleDrainTimeout } : {}),
4643
- ...(interceptorStore || hooks.terminalAbortSeams
4654
+ ...(interceptorStore || hooks.terminalAbortSeams || hooks.onPersistenceInstrumentation
4644
4655
  ? {
4645
4656
  terminalAbortSeams: {
4646
4657
  getTerminalTurnEpoch: () => undefined,
@@ -4648,6 +4659,9 @@ async function invocationHarness(
4648
4659
  cancelPendingPreflightForTerminalAbort: () => {},
4649
4660
  abortPromptAndWaitWithTerminal: async () => ({ status: "settled", terminalScope: {} }),
4650
4661
  ...(interceptorStore ? { getReconciliationStore: () => interceptorStore } : {}),
4662
+ ...(hooks.onPersistenceInstrumentation
4663
+ ? { onPersistenceInstrumentationForTests: hooks.onPersistenceInstrumentation }
4664
+ : {}),
4651
4665
  ...hooks.terminalAbortSeams,
4652
4666
  },
4653
4667
  }
@@ -6176,6 +6190,7 @@ describe("post-acceptance invocation terminalization", () => {
6176
6190
  let harness: InvocationHarness | undefined;
6177
6191
  let session: AgentSession | undefined;
6178
6192
  let authStorage: AuthStorage | undefined;
6193
+ let restoreEmission: (() => void) | undefined;
6179
6194
  let providerCalls = 0;
6180
6195
  try {
6181
6196
  const real = await createTerminalizationSession(
@@ -6186,6 +6201,7 @@ describe("post-acceptance invocation terminalization", () => {
6186
6201
  return createMockModel({ responses: [{ content: ["started"] }] }).stream(model, context, options);
6187
6202
  },
6188
6203
  { "todo.enabled": true, "todo.reminders": true, "todo.reminders.max": 1, "retry.enabled": false },
6204
+ true,
6189
6205
  );
6190
6206
  session = real.session;
6191
6207
  session.setConfiguredModelChain("default", [selector(real.model)], "terminal-throw-test");
@@ -6197,9 +6213,16 @@ describe("post-acceptance invocation terminalization", () => {
6197
6213
  isIdle: () => !session?.isStreaming,
6198
6214
  sendUserMessage: realSendUserMessage(session),
6199
6215
  });
6200
- session.subscribe(async event => {
6201
- await harness?.emit(event.type, event);
6216
+ const runner = session.extensionRunner;
6217
+ if (!runner) throw new Error("Expected the real awaited terminal extension bridge.");
6218
+ const emit = runner.emit.bind(runner);
6219
+ const emission = spyOn(runner, "emit").mockImplementation(async event => {
6220
+ const result = await emit(event);
6221
+ if (event.type === "agent_start" || event.type === "agent_failed" || event.type === "agent_end")
6222
+ await harness?.emit(event.type, event);
6223
+ return result;
6202
6224
  });
6225
+ restoreEmission = () => emission.mockRestore();
6203
6226
  const accepted = await harness.control("turn.prompt", { text: "finish the outstanding work" });
6204
6227
  expect(accepted.ok).toBe(true);
6205
6228
  const correlation = { commandId: accepted.result?.commandId, turnId: accepted.result?.turnId };
@@ -6226,6 +6249,7 @@ describe("post-acceptance invocation terminalization", () => {
6226
6249
  outcome: { kind: "failed" },
6227
6250
  });
6228
6251
  } finally {
6252
+ restoreEmission?.();
6229
6253
  await session?.dispose();
6230
6254
  authStorage?.close();
6231
6255
  await harness?.stop();
@@ -8521,8 +8545,23 @@ describe("accepted-control zero-execution bound (#4668)", () => {
8521
8545
  const sessionId = "deadline-captured-uncertain";
8522
8546
  const sessionFile = path.join(cwd, ".gjc", "state", `${sessionId}.jsonl`);
8523
8547
  await Bun.write(sessionFile, "");
8524
- const store = createReconciliationStore({ sessionFile, sessionId });
8548
+ // Chain-level evidence for the CI-only hang: every serialized store op
8549
+ // (including the terminal variants the runtime persistence log cannot see).
8550
+ const storeLog: string[] = [];
8551
+ const store = createReconciliationStore({
8552
+ sessionFile,
8553
+ sessionId,
8554
+ onInstrumentationForTests: event => {
8555
+ storeLog.push(
8556
+ `${event.at}:${event.phase}:${event.operation ?? ""}:${event.substep ?? ""}:${event.detail ?? ""}${event.error ? `:${event.error}` : ""}`,
8557
+ );
8558
+ },
8559
+ });
8525
8560
  const activeTools = new Set(["unfenced-tool"]);
8561
+ const toolDrainObserved = Promise.withResolvers<void>();
8562
+ const terminalPublication = Promise.withResolvers<SdkOnlyDeadlineRecoveryCheckpoint>();
8563
+ const recoveryCheckpoints: SdkOnlyDeadlineRecoveryCheckpoint[] = [];
8564
+ const persistenceLog: Array<PersistenceInstrumentationEvent & { label: string }> = [];
8526
8565
  let boundaryWaitStarted = false;
8527
8566
  let abortCalls = 0;
8528
8567
  let harness: InvocationHarness | undefined;
@@ -8531,25 +8570,30 @@ describe("accepted-control zero-execution bound (#4668)", () => {
8531
8570
  settings: {
8532
8571
  // This test covers deferred terminal recovery, not deadline-triggered worktree persistence.
8533
8572
  get: (key: string) =>
8534
- key === "sdk.promptDeadlineMs"
8535
- ? 150
8536
- : key === "sdk.promptMaxRuntimeMs"
8537
- ? 60_000
8538
- : key === "sdk.flushWorktreeOnDeadline"
8539
- ? false
8540
- : undefined,
8573
+ key === "sdk.promptDeadlineMs" ? 150 : key === "sdk.promptMaxRuntimeMs" ? 60_000 : undefined,
8541
8574
  } as unknown as Settings,
8542
8575
  sendUserMessage: async (_content, options) => {
8543
8576
  await options?.onPreflightAcceptCommit?.();
8544
8577
  await neverSettlingPromise();
8545
8578
  },
8579
+ onPersistenceInstrumentation: event => {
8580
+ const label = `${event.phase}:${event.operation ?? "main"}:${event.substep ?? ""}`;
8581
+ persistenceLog.push({ ...event, label });
8582
+ },
8546
8583
  terminalAbortSeams: {
8547
8584
  getReconciliationStore: () => store,
8548
8585
  getTerminalTurnEpoch: () => 109,
8549
8586
  getActivePromptHandle: () => "deadline-captured-uncertain-run",
8587
+ onDeadlineRecoveryCheckpointForTests: checkpoint => {
8588
+ recoveryCheckpoints.push(checkpoint);
8589
+ if (checkpoint.stage === "publication-result" && checkpoint.result === "published")
8590
+ terminalPublication.resolve(checkpoint);
8591
+ },
8550
8592
  pendingToolExecutions: () => {
8551
8593
  if (activeTools.size > 0) boundaryWaitStarted = true;
8552
- return [...activeTools];
8594
+ const pending = [...activeTools];
8595
+ if (pending.length === 0) toolDrainObserved.resolve();
8596
+ return pending;
8553
8597
  },
8554
8598
  abortPromptAndWaitWithTerminal: async () => {
8555
8599
  abortCalls += 1;
@@ -8559,7 +8603,10 @@ describe("accepted-control zero-execution bound (#4668)", () => {
8559
8603
  });
8560
8604
  const accepted = await harness.control("turn.prompt", { text: "capture uncertain end" });
8561
8605
  expect(accepted.ok).toBe(true);
8562
- const correlation = { commandId: accepted.result?.commandId, turnId: accepted.result?.turnId };
8606
+ const commandId = accepted.result?.commandId;
8607
+ const turnId = accepted.result?.turnId;
8608
+ if (commandId === undefined || turnId === undefined) throw new Error("Expected prompt correlation");
8609
+ const correlation = { commandId, turnId };
8563
8610
  await harness.emit("agent_start");
8564
8611
  await harness.emit("tool_execution_start", {
8565
8612
  type: "tool_execution_start",
@@ -8597,8 +8644,37 @@ describe("accepted-control zero-execution bound (#4668)", () => {
8597
8644
  });
8598
8645
 
8599
8646
  activeTools.clear();
8600
- // Keep this inside the 60s hard-runtime budget while tolerating a slow CI retry tick.
8601
- expect(await settledStatus(harness, "turn.prompt_status", correlation, 30_000)).toMatchObject({
8647
+ // Wait until the deadline recovery loop has actually observed the exact
8648
+ // tool-set drain; clearing the test seam alone does not wake its bounded
8649
+ // retry timer.
8650
+ await toolDrainObserved.promise;
8651
+ // Wait for the durable terminal boundary itself, not an arbitrary status
8652
+ // polling budget that can expire while a slow CI filesystem is flushing it.
8653
+ const published = await terminalPublication.promise;
8654
+ expect(published.correlation).toEqual(correlation);
8655
+ let terminalStatus: NonNullable<ResponseFrame["result"]>;
8656
+ try {
8657
+ terminalStatus = await settledStatus(harness, "turn.prompt_status", correlation);
8658
+ } catch (error) {
8659
+ const finalRecord = store.snapshot().find(record => record.commandId === correlation.commandId) as
8660
+ | (SdkOnlyInvocationRecord & {
8661
+ pendingOutcome?: unknown;
8662
+ deadlineRecoveryPending?: boolean;
8663
+ terminalAt?: number;
8664
+ })
8665
+ | undefined;
8666
+ throw new Error(
8667
+ `${error instanceof Error ? error.message : String(error)}; persistence_log=${JSON.stringify(persistenceLog)}; store_log=${JSON.stringify(storeLog)}; deadline checkpoints=${JSON.stringify(recoveryCheckpoints)}; durable=${JSON.stringify(
8668
+ {
8669
+ status: finalRecord?.status,
8670
+ terminalAt: finalRecord?.terminalAt,
8671
+ deadlineRecoveryPending: finalRecord?.deadlineRecoveryPending,
8672
+ pendingOutcome: finalRecord?.pendingOutcome,
8673
+ },
8674
+ )}`,
8675
+ );
8676
+ }
8677
+ expect(terminalStatus).toMatchObject({
8602
8678
  status: "terminal_ok",
8603
8679
  outcome: { kind: "stopped", reason: "cancelled" },
8604
8680
  });
@@ -8618,6 +8694,7 @@ describe("accepted-control zero-execution bound (#4668)", () => {
8618
8694
  const sessionFile = path.join(cwd, "session.json");
8619
8695
  await Bun.write(sessionFile, "");
8620
8696
  const store = createReconciliationStore({ sessionFile, sessionId });
8697
+ const deadlineStarted = Promise.withResolvers<void>();
8621
8698
  let abortCalls = 0;
8622
8699
  let harness: InvocationHarness | undefined;
8623
8700
  try {
@@ -8633,7 +8710,10 @@ describe("accepted-control zero-execution bound (#4668)", () => {
8633
8710
  terminalAbortSeams: {
8634
8711
  getReconciliationStore: () => store,
8635
8712
  getTerminalTurnEpoch: () => 17,
8636
- getActivePromptHandle: () => "unobservable-tool-run",
8713
+ getActivePromptHandle: () => {
8714
+ deadlineStarted.resolve();
8715
+ return "unobservable-tool-run";
8716
+ },
8637
8717
  abortPromptAndWaitWithTerminal: async () => {
8638
8718
  abortCalls += 1;
8639
8719
  return { status: "settled" };
@@ -8647,7 +8727,10 @@ describe("accepted-control zero-execution bound (#4668)", () => {
8647
8727
  await harness.emit("agent_failed", {
8648
8728
  error: Object.assign(new Error("provider unavailable"), { code: "provider_unavailable" }),
8649
8729
  });
8650
- await Bun.sleep(150);
8730
+ // Expiry must install its recoverable observation before the real end
8731
+ // arrives; otherwise an end racing the durable uncertainty write can
8732
+ // terminalize from the diagnostic alone.
8733
+ await deadlineStarted.promise;
8651
8734
  expect(abortCalls).toBe(0);
8652
8735
  expect(await harness.query("turn.prompt_status", correlation)).toMatchObject({
8653
8736
  result: { status: "in_flight" },
@@ -8655,7 +8738,6 @@ describe("accepted-control zero-execution bound (#4668)", () => {
8655
8738
  expect(correlatedFrames(harness, correlation).filter(frame => frame.kind === "agent_failed")).toHaveLength(1);
8656
8739
  expect(correlatedFrames(harness, correlation).filter(frame => frame.kind === "agent_end")).toEqual([]);
8657
8740
  await harness.emit("agent_end", { stopReason: "cancelled" });
8658
- await Bun.sleep(100);
8659
8741
  expect(await harness.query("turn.prompt_status", correlation)).toMatchObject({
8660
8742
  result: { status: "in_flight" },
8661
8743
  });
@@ -8794,7 +8876,9 @@ describe("accepted-control zero-execution bound (#4668)", () => {
8794
8876
  : key === "sdk.flushWorktreeOnDeadline"
8795
8877
  ? true
8796
8878
  : undefined,
8797
- has: (key: string) => key === "sdk.flushWorktreeOnDeadline",
8879
+ // Primary checkout: only a user/global true opts in. `has()` would
8880
+ // also be true for a project file, which must not authorize this.
8881
+ getGlobal: (key: string) => (key === "sdk.flushWorktreeOnDeadline" ? true : undefined),
8798
8882
  getAgentDir: () => path.join(cwd, ".gjc", "agent"),
8799
8883
  } as unknown as Settings,
8800
8884
  sendUserMessage: async (_content, options) => {
@@ -9003,8 +9087,11 @@ describe("accepted-control zero-execution bound (#4668)", () => {
9003
9087
  expect(await harness.query("turn.prompt_status", queuedIds)).toMatchObject({ result: { status: "accepted" } });
9004
9088
  expect(abortCalls).toBe(0);
9005
9089
  expect(correlatedFrames(harness, queuedIds).filter(frame => frame.kind === "agent_failed")).toHaveLength(0);
9006
- promoted?.({ startsOwnRun: true });
9090
+ // The lease is captured when the follow-up is promoted, not when the
9091
+ // setting later changes. Restore the long deadline first so this run
9092
+ // does not inherit the 100ms queue probe and expire under settledStatus.
9007
9093
  promptDeadlineMs = 600_000;
9094
+ promoted?.({ startsOwnRun: true });
9008
9095
  await Bun.sleep(150);
9009
9096
  expect((await harness.query("turn.prompt_status", queuedIds)).result?.status).toMatch(/accepted|in_flight/);
9010
9097
  expect(await harness.query("turn.prompt_status", activeIds)).toMatchObject({
@@ -11489,6 +11576,7 @@ test.each([
11489
11576
  let transportCreations = 0;
11490
11577
  createSdkSessionRuntimeExtension(api, {
11491
11578
  agentDir: cwd,
11579
+ ensureBrokerImpl: async () => testBrokerDiscovery,
11492
11580
  createTransport: async () => {
11493
11581
  transportCreations++;
11494
11582
  return transport;
@@ -11631,6 +11719,7 @@ test.each([
11631
11719
  ])("SDK-only queued terminal recovery permits replacement after EIO (early shutdown=%s)", async earlyShutdown => {
11632
11720
  const cwd = await mkdtemp(path.join(os.tmpdir(), "gjc-sdk-terminal-recovery-"));
11633
11721
  const handlers = new Map<string, (event: unknown, ctx: ExtensionContext) => Promise<void> | void>();
11722
+ const recoveryRetired = Promise.withResolvers<SdkOnlyDeadlineRecoveryCheckpoint>();
11634
11723
  let transport = memoryTransport();
11635
11724
  const sessionId = transport.sessionId;
11636
11725
  const sessionFile = path.join(cwd, `${sessionId}.json`);
@@ -11665,6 +11754,7 @@ test.each([
11665
11754
  get: (key: string) =>
11666
11755
  key === "sdk.promptDeadlineMs" ? 1_500 : key === "sdk.promptMaxRuntimeMs" ? 15_000 : undefined,
11667
11756
  } as unknown as Settings,
11757
+ ensureBrokerImpl: async () => testBrokerDiscovery,
11668
11758
  createTransport: async () => {
11669
11759
  creations++;
11670
11760
  transport = memoryTransport();
@@ -11676,6 +11766,9 @@ test.each([
11676
11766
  getActivePromptHandle: () => "unrelated-handle",
11677
11767
  cancelPendingPreflightForTerminalAbort: () => {},
11678
11768
  abortPromptAndWaitWithTerminal: async () => ({ status: "settled", terminalScope: {} }),
11769
+ onDeadlineRecoveryCheckpointForTests: checkpoint => {
11770
+ if (checkpoint.stage === "deadline-recovery-retired") recoveryRetired.resolve(checkpoint);
11771
+ },
11679
11772
  },
11680
11773
  });
11681
11774
  const ctx = { ...extensionContext(sessionId, cwd), isIdle: () => true } as unknown as ExtensionContext;
@@ -11717,7 +11810,7 @@ test.each([
11717
11810
  expect(accepted.ok).toBe(true);
11718
11811
  commandId = accepted.result?.commandId;
11719
11812
  const turnId = accepted.result?.turnId;
11720
- expect(commandId).toBeDefined();
11813
+ if (commandId === undefined || turnId === undefined) throw new Error("Expected queued prompt correlation");
11721
11814
  await waitFor(() => queued && queueSignal !== undefined, "queued admission");
11722
11815
  expect(await request({ type: "control_request", operation: "turn.abort", input: {} })).toMatchObject({
11723
11816
  ok: true,
@@ -11742,6 +11835,8 @@ test.each([
11742
11835
  status: "failed",
11743
11836
  error: { code: "cancelled" },
11744
11837
  });
11838
+ const retired = await recoveryRetired.promise;
11839
+ expect(retired.correlation).toEqual({ commandId, turnId });
11745
11840
  if (!earlyShutdown) await handlers.get("session_shutdown")?.({}, ctx);
11746
11841
  await handlers.get("session_start")?.({}, ctx);
11747
11842
  expect(creations).toBe(2);