@gajae-code/coding-agent 0.16.7 → 0.17.1

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 (321) hide show
  1. package/CHANGELOG.md +180 -1
  2. package/dist/types/capability/fs.d.ts +37 -4
  3. package/dist/types/capability/index.d.ts +19 -0
  4. package/dist/types/capability/types.d.ts +9 -0
  5. package/dist/types/cli/doctor/args.d.ts +36 -0
  6. package/dist/types/cli/doctor/checks.d.ts +18 -0
  7. package/dist/types/cli/doctor/config-repairs.d.ts +38 -0
  8. package/dist/types/cli/doctor/context.d.ts +89 -0
  9. package/dist/types/cli/doctor/files.d.ts +27 -0
  10. package/dist/types/cli/doctor/ids.d.ts +20 -0
  11. package/dist/types/cli/doctor/install-repairs.d.ts +94 -0
  12. package/dist/types/cli/doctor/journal.d.ts +43 -0
  13. package/dist/types/cli/doctor/managed-link.d.ts +39 -0
  14. package/dist/types/cli/doctor/native-probe.d.ts +2 -0
  15. package/dist/types/cli/doctor/permission-repairs.d.ts +25 -0
  16. package/dist/types/cli/doctor/plugin-quarantine.d.ts +77 -0
  17. package/dist/types/cli/doctor/plugin-restore.d.ts +40 -0
  18. package/dist/types/cli/doctor/plugin-targets.d.ts +11 -0
  19. package/dist/types/cli/doctor/probe-client.d.ts +9 -0
  20. package/dist/types/cli/doctor/probe-types.d.ts +18 -0
  21. package/dist/types/cli/doctor/probe.d.ts +3 -0
  22. package/dist/types/cli/doctor/projection-probe.d.ts +2 -0
  23. package/dist/types/cli/doctor/repairs.d.ts +28 -0
  24. package/dist/types/cli/doctor/report.d.ts +27 -0
  25. package/dist/types/cli/doctor/runner.d.ts +4 -0
  26. package/dist/types/cli/doctor/safety.d.ts +10 -0
  27. package/dist/types/cli/doctor/service-repairs.d.ts +8 -0
  28. package/dist/types/cli/doctor/service-targets.d.ts +41 -0
  29. package/dist/types/cli/doctor/types.d.ts +145 -0
  30. package/dist/types/cli/doctor-cli.d.ts +7 -0
  31. package/dist/types/cli/doctor-supervisor.d.ts +121 -0
  32. package/dist/types/cli/doctor-worker.d.ts +29 -0
  33. package/dist/types/cli/install-activation.d.ts +158 -0
  34. package/dist/types/cli/macos-community-app.d.ts +41 -0
  35. package/dist/types/cli/mcp-cli.d.ts +0 -11
  36. package/dist/types/cli/update-cli.d.ts +41 -4
  37. package/dist/types/cli-main.d.ts +174 -0
  38. package/dist/types/cli.d.ts +1 -171
  39. package/dist/types/commands/doctor.d.ts +7 -0
  40. package/dist/types/commands/sdk.d.ts +17 -8
  41. package/dist/types/config/autorouting-tier-map.d.ts +19 -0
  42. package/dist/types/config/file-lock.d.ts +27 -3
  43. package/dist/types/config/model-preset-registry.d.ts +1 -0
  44. package/dist/types/config/model-registry.d.ts +12 -5
  45. package/dist/types/config/model-resolver.d.ts +16 -3
  46. package/dist/types/config/models-config-schema.d.ts +2 -0
  47. package/dist/types/config/settings-schema.d.ts +1 -1
  48. package/dist/types/coordinator-mcp/question-gate-codec.d.ts +1 -1
  49. package/dist/types/coordinator-mcp/session-reaper.d.ts +15 -5
  50. package/dist/types/cursor.d.ts +9 -9
  51. package/dist/types/discovery/agents-md.d.ts +6 -1
  52. package/dist/types/discovery/helpers.d.ts +36 -7
  53. package/dist/types/discovery/index.d.ts +1 -1
  54. package/dist/types/discovery/mcp-json.d.ts +2 -0
  55. package/dist/types/extensibility/extensions/loader.d.ts +22 -1
  56. package/dist/types/extensibility/gjc-plugins/lifecycle.d.ts +14 -1
  57. package/dist/types/extensibility/gjc-plugins/paths.d.ts +2 -1
  58. package/dist/types/extensibility/gjc-plugins/types.d.ts +25 -0
  59. package/dist/types/extensibility/plugins/manager.d.ts +33 -4
  60. package/dist/types/extensibility/plugins/marketplace/cache.d.ts +6 -0
  61. package/dist/types/extensibility/plugins/marketplace/manager.d.ts +186 -0
  62. package/dist/types/extensibility/plugins/marketplace/registry.d.ts +27 -0
  63. package/dist/types/extensibility/plugins/marketplace/source-resolver.d.ts +16 -1
  64. package/dist/types/extensibility/runtime-skill-discovery.d.ts +16 -3
  65. package/dist/types/gjc-runtime/autoresearch-runtime.d.ts +6 -0
  66. package/dist/types/gjc-runtime/launch-worktree.d.ts +53 -0
  67. package/dist/types/gjc-runtime/session-layout.d.ts +8 -0
  68. package/dist/types/gjc-runtime/session-state-lock.d.ts +15 -0
  69. package/dist/types/gjc-runtime/session-state-sidecar.d.ts +13 -0
  70. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +6 -0
  71. package/dist/types/main.d.ts +19 -1
  72. package/dist/types/modes/acp/acp-agent.d.ts +40 -0
  73. package/dist/types/modes/components/agent-dashboard.d.ts +1 -0
  74. package/dist/types/modes/components/model-selector.d.ts +2 -0
  75. package/dist/types/modes/interactive-mode.d.ts +1 -1
  76. package/dist/types/runtime-mcp/config-writer.d.ts +47 -0
  77. package/dist/types/runtime-mcp/policy-json-edit.d.ts +60 -0
  78. package/dist/types/runtime-mcp/startup-policy.d.ts +4 -0
  79. package/dist/types/runtime-mcp/types.d.ts +2 -2
  80. package/dist/types/sdk/acp/adapter.d.ts +5 -1
  81. package/dist/types/sdk/broker/broker.d.ts +51 -0
  82. package/dist/types/sdk/broker/daemon-entry.d.ts +39 -0
  83. package/dist/types/sdk/broker/discovery.d.ts +38 -0
  84. package/dist/types/sdk/broker/doctor-restart.d.ts +79 -0
  85. package/dist/types/sdk/broker/endpoint-authority.d.ts +7 -0
  86. package/dist/types/sdk/broker/ensure.d.ts +14 -0
  87. package/dist/types/sdk/broker/lifecycle.d.ts +2 -0
  88. package/dist/types/sdk/broker/process-incarnation.d.ts +50 -0
  89. package/dist/types/sdk/broker/runtime.d.ts +26 -0
  90. package/dist/types/sdk/broker/session-index.d.ts +13 -4
  91. package/dist/types/sdk/broker/worktree-occupancy.d.ts +14 -0
  92. package/dist/types/sdk/bus/chat-daemon-cli.d.ts +16 -0
  93. package/dist/types/sdk/bus/chat-daemon-control.d.ts +101 -6
  94. package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +16 -0
  95. package/dist/types/sdk/bus/daemon-startup-exclusion.d.ts +43 -0
  96. package/dist/types/sdk/bus/doctor-daemon-restart-client.d.ts +51 -0
  97. package/dist/types/sdk/bus/doctor-daemon-restart.d.ts +39 -0
  98. package/dist/types/sdk/bus/index.d.ts +2 -0
  99. package/dist/types/sdk/bus/notification-service.d.ts +3 -0
  100. package/dist/types/sdk/bus/telegram-daemon-cli.d.ts +11 -0
  101. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +1 -1
  102. package/dist/types/sdk/bus/telegram-daemon-control.d.ts +5 -0
  103. package/dist/types/sdk/bus/telegram-daemon-owner-registry.d.ts +1 -0
  104. package/dist/types/sdk/bus/telegram-daemon.d.ts +36 -5
  105. package/dist/types/sdk/cli/session-cli.d.ts +1 -0
  106. package/dist/types/sdk/host/session-runtime.d.ts +4 -0
  107. package/dist/types/sdk/host/surface-policy.d.ts +3 -1
  108. package/dist/types/sdk/lifecycle/service.d.ts +44 -10
  109. package/dist/types/sdk/prompt-deadline-lease.d.ts +6 -3
  110. package/dist/types/sdk/prompt-deadline-manager.d.ts +2 -2
  111. package/dist/types/sdk/prompt-failure.d.ts +47 -0
  112. package/dist/types/sdk/prompt-status.d.ts +19 -0
  113. package/dist/types/sdk/router/session-router.d.ts +3 -4
  114. package/dist/types/sdk/service-artifact-paths.d.ts +16 -0
  115. package/dist/types/sdk/session-authority.d.ts +9 -0
  116. package/dist/types/sdk/session.d.ts +3 -1
  117. package/dist/types/session/agent-session.d.ts +1 -1
  118. package/dist/types/session/blob-store.d.ts +1 -1
  119. package/dist/types/session/fallback-chain-controller.d.ts +3 -6
  120. package/dist/types/session/internal/managed-session-scope.d.ts +44 -0
  121. package/dist/types/session/primary-control-surface.d.ts +4 -0
  122. package/dist/types/session/session-manager.d.ts +6 -0
  123. package/dist/types/system-prompt.d.ts +6 -0
  124. package/dist/types/tools/ask.d.ts +25 -0
  125. package/dist/types/tools/image-gen.d.ts +0 -4
  126. package/dist/types/tools/skill-discovery.d.ts +7 -5
  127. package/dist/types/tools/ultragoal-ask-guard.d.ts +7 -4
  128. package/dist/types/utils/changelog.d.ts +0 -9
  129. package/dist/types/utils/mupdf-wasm.d.ts +1 -0
  130. package/package.json +9 -8
  131. package/scripts/compile-args.ts +8 -4
  132. package/src/capability/fs.ts +395 -17
  133. package/src/capability/index.ts +273 -10
  134. package/src/capability/types.ts +9 -0
  135. package/src/cli/customize-doctor.ts +18 -12
  136. package/src/cli/doctor/args.ts +236 -0
  137. package/src/cli/doctor/checks.ts +431 -0
  138. package/src/cli/doctor/config-repairs.ts +517 -0
  139. package/src/cli/doctor/context.ts +149 -0
  140. package/src/cli/doctor/files.ts +149 -0
  141. package/src/cli/doctor/ids.ts +115 -0
  142. package/src/cli/doctor/install-repairs.ts +335 -0
  143. package/src/cli/doctor/journal.ts +423 -0
  144. package/src/cli/doctor/managed-link.ts +514 -0
  145. package/src/cli/doctor/native-probe.ts +13 -0
  146. package/src/cli/doctor/permission-repairs.ts +242 -0
  147. package/src/cli/doctor/plugin-quarantine.ts +377 -0
  148. package/src/cli/doctor/plugin-restore.ts +59 -0
  149. package/src/cli/doctor/plugin-targets.ts +373 -0
  150. package/src/cli/doctor/probe-client.ts +195 -0
  151. package/src/cli/doctor/probe-types.ts +21 -0
  152. package/src/cli/doctor/probe.ts +42 -0
  153. package/src/cli/doctor/projection-probe.ts +249 -0
  154. package/src/cli/doctor/repairs.ts +1157 -0
  155. package/src/cli/doctor/report.ts +176 -0
  156. package/src/cli/doctor/runner.ts +342 -0
  157. package/src/cli/doctor/safety.ts +40 -0
  158. package/src/cli/doctor/service-repairs.ts +452 -0
  159. package/src/cli/doctor/service-targets.ts +274 -0
  160. package/src/cli/doctor/types.ts +164 -0
  161. package/src/cli/doctor-cli.ts +62 -0
  162. package/src/cli/doctor-supervisor.ts +571 -0
  163. package/src/cli/doctor-worker.ts +181 -0
  164. package/src/cli/install-activation.ts +666 -0
  165. package/src/cli/macos-community-app.ts +1449 -0
  166. package/src/cli/mcp-cli.ts +11 -21
  167. package/src/cli/nofile-limit.ts +5 -1
  168. package/src/cli/plugin-cli.ts +6 -1
  169. package/src/cli/update-cli.ts +362 -264
  170. package/src/cli-main.ts +517 -0
  171. package/src/cli.ts +64 -433
  172. package/src/commands/autoresearch.ts +11 -5
  173. package/src/commands/completion.ts +1 -1
  174. package/src/commands/doctor.ts +13 -0
  175. package/src/commands/sdk.ts +58 -10
  176. package/src/commands/ultragoal.ts +11 -5
  177. package/src/config/autorouting-tier-map.ts +7 -0
  178. package/src/config/file-lock.ts +736 -71
  179. package/src/config/mcp-schema.json +3 -3
  180. package/src/config/model-preset-registry.ts +1 -0
  181. package/src/config/model-registry.ts +80 -27
  182. package/src/config/model-resolver.ts +33 -4
  183. package/src/config/models-config-schema.ts +2 -0
  184. package/src/config/settings-schema.ts +6 -2
  185. package/src/coordinator-mcp/question-gate-codec.ts +1 -0
  186. package/src/coordinator-mcp/server.ts +534 -51
  187. package/src/coordinator-mcp/session-reaper.ts +64 -8
  188. package/src/cursor.ts +223 -53
  189. package/src/discovery/agents-md.ts +74 -8
  190. package/src/discovery/agents.ts +32 -7
  191. package/src/discovery/builtin.ts +184 -75
  192. package/src/discovery/claude-plugins.ts +85 -17
  193. package/src/discovery/cline.ts +38 -10
  194. package/src/discovery/cursor.ts +19 -6
  195. package/src/discovery/gemini.ts +62 -22
  196. package/src/discovery/github.ts +13 -3
  197. package/src/discovery/helpers.ts +483 -89
  198. package/src/discovery/index.ts +1 -0
  199. package/src/discovery/mcp-json.ts +36 -10
  200. package/src/discovery/opencode.ts +36 -19
  201. package/src/discovery/ssh.ts +11 -8
  202. package/src/discovery/windsurf.ts +12 -3
  203. package/src/exec/bash-executor.ts +10 -3
  204. package/src/exec/isolated-shell.ts +15 -2
  205. package/src/extensibility/extensions/loader.ts +88 -26
  206. package/src/extensibility/gjc-plugins/lifecycle.ts +189 -0
  207. package/src/extensibility/gjc-plugins/paths.ts +4 -1
  208. package/src/extensibility/gjc-plugins/types.ts +27 -0
  209. package/src/extensibility/plugins/manager.ts +200 -22
  210. package/src/extensibility/plugins/marketplace/cache.ts +55 -2
  211. package/src/extensibility/plugins/marketplace/manager.ts +1262 -162
  212. package/src/extensibility/plugins/marketplace/registry.ts +153 -0
  213. package/src/extensibility/plugins/marketplace/source-resolver.ts +116 -10
  214. package/src/extensibility/runtime-skill-discovery.ts +61 -9
  215. package/src/extensibility/skill-management.ts +7 -1
  216. package/src/gjc-runtime/autoresearch-runtime.ts +10 -1
  217. package/src/gjc-runtime/launch-worktree.test.ts +248 -0
  218. package/src/gjc-runtime/launch-worktree.ts +275 -38
  219. package/src/gjc-runtime/repository-binding.ts +26 -1
  220. package/src/gjc-runtime/session-layout.ts +10 -0
  221. package/src/gjc-runtime/session-state-lock.ts +330 -23
  222. package/src/gjc-runtime/session-state-sidecar.ts +328 -80
  223. package/src/gjc-runtime/ultragoal-runtime.ts +10 -1
  224. package/src/harness-control-plane/session-lease.ts +2 -2
  225. package/src/internal-urls/docs-index.generated.ts +28 -26
  226. package/src/main.ts +150 -29
  227. package/src/memories/index.ts +24 -2
  228. package/src/modes/acp/acp-agent.ts +522 -68
  229. package/src/modes/components/agent-dashboard.ts +3 -1
  230. package/src/modes/components/model-selector.ts +28 -19
  231. package/src/modes/components/plugin-settings.ts +2 -1
  232. package/src/modes/controllers/selector-controller.ts +15 -9
  233. package/src/modes/interactive-mode.ts +21 -3
  234. package/src/prompts/system/system-prompt.md +5 -1
  235. package/src/prompts/tools/browser.md +1 -1
  236. package/src/prompts/tools/move-session.md +4 -0
  237. package/src/prompts/tools/read.md +2 -1
  238. package/src/prompts/tools/skill-discovery.md +6 -5
  239. package/src/prompts/tools/task.md +1 -0
  240. package/src/runtime-mcp/config-writer.ts +228 -0
  241. package/src/runtime-mcp/oauth-discovery.ts +114 -14
  242. package/src/runtime-mcp/policy-json-edit.ts +129 -0
  243. package/src/runtime-mcp/startup-policy.ts +14 -0
  244. package/src/runtime-mcp/types.ts +2 -2
  245. package/src/sdk/acp/adapter.ts +27 -12
  246. package/src/sdk/broker/broker.ts +273 -44
  247. package/src/sdk/broker/daemon-entry.ts +119 -0
  248. package/src/sdk/broker/discovery.ts +173 -20
  249. package/src/sdk/broker/doctor-restart.ts +206 -0
  250. package/src/sdk/broker/endpoint-authority.ts +36 -0
  251. package/src/sdk/broker/ensure.ts +49 -14
  252. package/src/sdk/broker/lifecycle.ts +174 -42
  253. package/src/sdk/broker/process-incarnation.ts +86 -2
  254. package/src/sdk/broker/runtime.ts +145 -21
  255. package/src/sdk/broker/session-index.ts +136 -22
  256. package/src/sdk/broker/spawn-substrate.ts +4 -1
  257. package/src/sdk/broker/transport.ts +5 -0
  258. package/src/sdk/broker/worktree-occupancy.ts +21 -1
  259. package/src/sdk/bus/chat-daemon-cli.ts +63 -0
  260. package/src/sdk/bus/chat-daemon-control.ts +260 -64
  261. package/src/sdk/bus/chat-daemon-runtime.ts +141 -25
  262. package/src/sdk/bus/daemon-startup-exclusion.ts +119 -0
  263. package/src/sdk/bus/doctor-daemon-restart-client.ts +291 -0
  264. package/src/sdk/bus/doctor-daemon-restart.ts +92 -0
  265. package/src/sdk/bus/index.ts +252 -51
  266. package/src/sdk/bus/kind-aware-reconciliation.ts +76 -41
  267. package/src/sdk/bus/notification-service.ts +21 -3
  268. package/src/sdk/bus/reconciliation-store.ts +17 -11
  269. package/src/sdk/bus/telegram-daemon-cli.ts +91 -5
  270. package/src/sdk/bus/telegram-daemon-contract.ts +1 -1
  271. package/src/sdk/bus/telegram-daemon-control.ts +47 -0
  272. package/src/sdk/bus/telegram-daemon-owner-registry.ts +1 -1
  273. package/src/sdk/bus/telegram-daemon.ts +178 -13
  274. package/src/sdk/cli/session-cli.ts +100 -12
  275. package/src/sdk/host/host.ts +2 -7
  276. package/src/sdk/host/session-runtime.test.ts +329 -8
  277. package/src/sdk/host/session-runtime.ts +202 -50
  278. package/src/sdk/host/surface-policy.ts +8 -1
  279. package/src/sdk/lifecycle/service.ts +199 -22
  280. package/src/sdk/mcp/server.ts +1 -1
  281. package/src/sdk/prompt-deadline-lease.ts +12 -4
  282. package/src/sdk/prompt-deadline-manager.ts +5 -7
  283. package/src/sdk/prompt-failure.ts +148 -0
  284. package/src/sdk/prompt-status.ts +30 -1
  285. package/src/sdk/prompt-watchdog.ts +11 -3
  286. package/src/sdk/protocol/operation-inventory.generated.json +18 -0
  287. package/src/sdk/protocol/operation-registry.ts +20 -8
  288. package/src/sdk/router/session-router.ts +8 -3
  289. package/src/sdk/service-artifact-paths.ts +26 -0
  290. package/src/sdk/session-authority.ts +10 -0
  291. package/src/sdk/session.ts +361 -194
  292. package/src/session/agent-session.ts +398 -93
  293. package/src/session/blob-store.ts +9 -9
  294. package/src/session/fallback-chain-controller.ts +3 -7
  295. package/src/session/internal/managed-session-scope.ts +445 -29
  296. package/src/session/internal/managed-session-storage.ts +38 -6
  297. package/src/session/primary-control-surface.ts +12 -0
  298. package/src/session/session-manager.ts +11 -3
  299. package/src/session/session-storage.ts +1 -1
  300. package/src/session-import/codex.ts +6 -1
  301. package/src/session-import/redact.ts +7 -0
  302. package/src/skill-state/workflow-mutation-guard.ts +645 -39
  303. package/src/slash-commands/builtin-registry.ts +7 -3
  304. package/src/system-prompt.ts +8 -0
  305. package/src/task/executor.ts +36 -3
  306. package/src/task/types.ts +27 -5
  307. package/src/tools/ask.ts +114 -34
  308. package/src/tools/browser/actions.ts +2 -2
  309. package/src/tools/browser/tab-worker.ts +11 -8
  310. package/src/tools/descriptors.ts +6 -1
  311. package/src/tools/fetch.ts +7 -2
  312. package/src/tools/image-gen.ts +33 -34
  313. package/src/tools/read.ts +78 -32
  314. package/src/tools/skill-discovery.ts +17 -7
  315. package/src/tools/tool-catalog.generated.ts +8 -8
  316. package/src/tools/ultragoal-ask-guard.ts +16 -26
  317. package/src/utils/changelog.ts +7 -1
  318. package/src/utils/git.ts +3 -1
  319. package/src/utils/markit.ts +24 -2
  320. package/src/utils/mupdf-wasm.ts +26 -0
  321. package/src/web/search/providers/openai-compatible.ts +191 -14
@@ -1,4 +1,4 @@
1
- import { describe, expect, spyOn, test } from "bun:test";
1
+ import { describe, expect, setDefaultTimeout, spyOn, test } from "bun:test";
2
2
  import { createHash } from "node:crypto";
3
3
  import { mkdir, mkdtemp, readdir, readFile, rm, writeFile } from "node:fs/promises";
4
4
  import * as os from "node:os";
@@ -32,6 +32,15 @@ import { createSdkCapabilities, createSdkSurfacePolicy } from "./surface-policy"
32
32
  import type { SdkFrame } from "./types";
33
33
  import { SdkTransportLifecycleError } from "./websocket-transport";
34
34
 
35
+ setDefaultTimeout(30_000);
36
+
37
+ test("runtime capabilities preserve explicit primary control surface", () => {
38
+ const policy = createSdkSurfacePolicy({ bindings: [], workflowGateAvailable: false });
39
+ expect(createSdkCapabilities(policy)).toMatchObject({ primaryControlSurface: "sdk" });
40
+ expect(createSdkCapabilities(policy, false, "sdk")).toMatchObject({ primaryControlSurface: "sdk" });
41
+ expect(createSdkCapabilities(policy, false, "cli")).toMatchObject({ primaryControlSurface: "cli" });
42
+ });
43
+
35
44
  function memoryTransport(): SessionSdkTransport & {
36
45
  feed(connectionId: string, frame: SdkFrame): void;
37
46
  readonly sent: SdkFrame[];
@@ -429,7 +438,7 @@ test("SDK-only finalizeOutcome keeps legacy recordError positional compatibility
429
438
  });
430
439
  expect(compatReconciliation.lookup("prompt", { clientRef: "finalize-compat-ref" })).toMatchObject({
431
440
  status: "failed",
432
- error: { code: "legacy_error", message: "legacy message" },
441
+ error: { code: "legacy_error", message: "Prompt submission failed." },
433
442
  });
434
443
  // The isCurrent callback shape still fences a stale finalize to a no-op.
435
444
  const second = createInvocationReconciliation();
@@ -4040,7 +4049,16 @@ describe("post-acceptance invocation terminalization", () => {
4040
4049
  });
4041
4050
  expect(terminal).toMatchObject({
4042
4051
  status: "failed",
4043
- error: { code, message: "Prompt submission failed." },
4052
+ error: { code, message: "Provider failure after execution started." },
4053
+ outcome: {
4054
+ kind: "failed",
4055
+ code: "prompt_failed",
4056
+ message: "Provider failure after execution started.",
4057
+ provenance: "agent_failed",
4058
+ phase: "post_start",
4059
+ category: "provider_rejected",
4060
+ providerCode: code,
4061
+ },
4044
4062
  terminalAt: expect.any(Number),
4045
4063
  });
4046
4064
  const stable = await harness.query("turn.result", { kind: "prompt", ...failedIds });
@@ -4497,10 +4515,8 @@ describe("post-acceptance invocation terminalization", () => {
4497
4515
  messages: [{ role: "assistant", stopReason: "error", errorStatus: status }],
4498
4516
  });
4499
4517
  await entered.promise;
4500
- const startedAt = Date.now();
4501
4518
  const lifecycleChange = harness[operation](`${operation}-stall-successor`);
4502
4519
  await lifecycleChange;
4503
- expect(Date.now() - startedAt).toBeLessThan(1_500);
4504
4520
  expect(timeoutWarnings).toBe(1);
4505
4521
  release.resolve();
4506
4522
  firstInflight.resolve();
@@ -4662,7 +4678,16 @@ describe("post-acceptance invocation terminalization", () => {
4662
4678
  // the failure reason survives as the safe-token code.
4663
4679
  expect(await settledStatus(harness, "turn.prompt_status", { commandId, turnId })).toMatchObject({
4664
4680
  status: "failed",
4665
- error: { code: "upstream_stream_interrupted", message: "Prompt submission failed." },
4681
+ error: { code: "upstream_stream_interrupted", message: "Provider failure after execution started." },
4682
+ outcome: {
4683
+ kind: "failed",
4684
+ code: "prompt_failed",
4685
+ message: "Provider failure after execution started.",
4686
+ provenance: "agent_failed",
4687
+ phase: "post_start",
4688
+ category: "provider_transport",
4689
+ providerCode: "upstream_stream_interrupted",
4690
+ },
4666
4691
  });
4667
4692
  await harness.stop();
4668
4693
  } finally {
@@ -4955,7 +4980,7 @@ describe("post-acceptance invocation terminalization", () => {
4955
4980
  const cases = [
4956
4981
  { name: "short", agent: "SDK_OK", expected: "SDK_OK", bytes: 6, truncated: false },
4957
4982
  { name: "max", agent: "x".repeat(16_384), expected: "x".repeat(16_384), bytes: 16_384, truncated: false },
4958
- { name: "blank", agent: " ", expected: undefined, bytes: 0, truncated: false },
4983
+ { name: "blank", agent: " ", expected: undefined, bytes: 0, truncated: false, status: "failed" },
4959
4984
  {
4960
4985
  name: "overflow",
4961
4986
  agent: `${"😀".repeat(4_096)}tail`,
@@ -4982,7 +5007,7 @@ describe("post-acceptance invocation terminalization", () => {
4982
5007
  turnId: accepted.result?.turnId,
4983
5008
  });
4984
5009
  if (testCase.expected === undefined) {
4985
- expect(result).toMatchObject({ status: "failed" });
5010
+ expect(result).toMatchObject({ status: testCase.status });
4986
5011
  expect((result as Record<string, unknown>).content).toBeUndefined();
4987
5012
  } else {
4988
5013
  expect(result).toMatchObject({
@@ -5000,6 +5025,302 @@ describe("post-acceptance invocation terminalization", () => {
5000
5025
  }
5001
5026
  }
5002
5027
  });
5028
+ test("fails closed when an assistant turn has only empty reasoning and zero token usage", async () => {
5029
+ const cwd = await mkdtemp(path.join(os.tmpdir(), "gjc-terminal-zero-token-empty-"));
5030
+ try {
5031
+ const harness = await invocationHarness("terminal-zero-token-empty", cwd, {
5032
+ sendUserMessage: async (_content, options) => {
5033
+ await options?.onPreflightAcceptCommit?.();
5034
+ await Promise.withResolvers<void>().promise;
5035
+ },
5036
+ });
5037
+ const accepted = await harness.control("turn.prompt", { text: "hello" });
5038
+ await harness.emit("agent_start");
5039
+ await harness.emit("agent_end", {
5040
+ messages: [
5041
+ {
5042
+ role: "assistant",
5043
+ content: [{ type: "thinking", thinking: "" }],
5044
+ usage: {
5045
+ input: 0,
5046
+ output: 0,
5047
+ cacheRead: 0,
5048
+ cacheWrite: 0,
5049
+ totalTokens: 0,
5050
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
5051
+ },
5052
+ stopReason: "stop",
5053
+ },
5054
+ ],
5055
+ });
5056
+ const result = await settledStatus(harness, "turn.result", {
5057
+ kind: "prompt",
5058
+ commandId: accepted.result?.commandId,
5059
+ turnId: accepted.result?.turnId,
5060
+ });
5061
+ expect(result).toMatchObject({
5062
+ status: "failed",
5063
+ error: { code: "prompt_failed", message: "Agent run failed after execution started." },
5064
+ outcome: {
5065
+ kind: "failed",
5066
+ code: "prompt_failed",
5067
+ message: "Agent run failed after execution started.",
5068
+ provenance: "agent_failed",
5069
+ phase: "post_start",
5070
+ category: "agent_runtime",
5071
+ },
5072
+ });
5073
+ const successor = await harness.control("turn.prompt", { text: "successor" });
5074
+ expect(successor).toMatchObject({ ok: true, result: { accepted: true } });
5075
+ await harness.emit("agent_start");
5076
+ await harness.emit("agent_end", { messages: [{ role: "assistant", content: "completed" }] });
5077
+ expect(
5078
+ await settledStatus(harness, "turn.result", {
5079
+ kind: "prompt",
5080
+ commandId: successor.result?.commandId,
5081
+ turnId: successor.result?.turnId,
5082
+ }),
5083
+ ).toMatchObject({ status: "terminal_ok" });
5084
+ await harness.stop();
5085
+ } finally {
5086
+ await rm(cwd, { recursive: true, force: true });
5087
+ }
5088
+ });
5089
+ test("preserves valid textless terminal evidence", async () => {
5090
+ const cases = [
5091
+ {
5092
+ name: "reasoning-only",
5093
+ content: [{ type: "thinking", thinking: "private reasoning" }],
5094
+ usage: { totalTokens: 0 },
5095
+ },
5096
+ {
5097
+ name: "redacted-reasoning-only",
5098
+ content: [{ type: "redactedThinking", data: "encrypted reasoning" }],
5099
+ usage: { totalTokens: 0 },
5100
+ },
5101
+ {
5102
+ name: "tool-only",
5103
+ content: [{ type: "toolCall", id: "call-1", name: "read", arguments: {} }],
5104
+ usage: { totalTokens: 0 },
5105
+ },
5106
+ {
5107
+ name: "positive-token-usage",
5108
+ content: [{ type: "thinking", thinking: "" }],
5109
+ usage: { totalTokens: 1 },
5110
+ },
5111
+ ] as const;
5112
+ for (const testCase of cases) {
5113
+ const cwd = await mkdtemp(path.join(os.tmpdir(), `gjc-terminal-valid-${testCase.name}-`));
5114
+ try {
5115
+ const harness = await invocationHarness(`terminal-valid-${testCase.name}`, cwd, {
5116
+ sendUserMessage: async (_content, options) => {
5117
+ await options?.onPreflightAcceptCommit?.();
5118
+ await Promise.withResolvers<void>().promise;
5119
+ },
5120
+ });
5121
+ const accepted = await harness.control("turn.prompt", { text: "hello" });
5122
+ await harness.emit("agent_start");
5123
+ await harness.emit("agent_end", {
5124
+ messages: [
5125
+ {
5126
+ role: "assistant",
5127
+ content: testCase.content,
5128
+ ...(testCase.usage === undefined ? {} : { usage: testCase.usage }),
5129
+ },
5130
+ ],
5131
+ });
5132
+ expect(
5133
+ await settledStatus(harness, "turn.result", {
5134
+ kind: "prompt",
5135
+ commandId: accepted.result?.commandId,
5136
+ turnId: accepted.result?.turnId,
5137
+ }),
5138
+ ).toMatchObject({ status: "terminal_ok" });
5139
+ await harness.stop();
5140
+ } finally {
5141
+ await rm(cwd, { recursive: true, force: true });
5142
+ }
5143
+ }
5144
+ });
5145
+ test("preserves earlier complete tool activity before a trailing empty assistant", async () => {
5146
+ const cwd = await mkdtemp(path.join(os.tmpdir(), "gjc-terminal-earlier-tool-activity-"));
5147
+ try {
5148
+ const harness = await invocationHarness("terminal-earlier-tool-activity", cwd, {
5149
+ sendUserMessage: async (_content, options) => {
5150
+ await options?.onPreflightAcceptCommit?.();
5151
+ await Promise.withResolvers<void>().promise;
5152
+ },
5153
+ });
5154
+ const accepted = await harness.control("turn.prompt", { text: "hello" });
5155
+ await harness.emit("agent_start");
5156
+ await harness.emit("agent_end", {
5157
+ messages: [
5158
+ {
5159
+ role: "assistant",
5160
+ content: [{ type: "toolCall", id: "call-1", name: "read", arguments: { path: "file.txt" } }],
5161
+ usage: { totalTokens: 1 },
5162
+ },
5163
+ {
5164
+ role: "toolResult",
5165
+ toolCallId: "call-1",
5166
+ toolName: "read",
5167
+ content: [{ type: "text", text: "contents" }],
5168
+ },
5169
+ {
5170
+ role: "assistant",
5171
+ content: [{ type: "thinking", thinking: "" }],
5172
+ usage: { totalTokens: 0 },
5173
+ },
5174
+ ],
5175
+ });
5176
+ expect(
5177
+ await settledStatus(harness, "turn.result", {
5178
+ kind: "prompt",
5179
+ commandId: accepted.result?.commandId,
5180
+ turnId: accepted.result?.turnId,
5181
+ }),
5182
+ ).toMatchObject({ status: "terminal_ok" });
5183
+ await harness.stop();
5184
+ } finally {
5185
+ await rm(cwd, { recursive: true, force: true });
5186
+ }
5187
+ });
5188
+ test("fails closed without independent terminal evidence", async () => {
5189
+ const cases = [
5190
+ { name: "usage-omitted", content: [{ type: "thinking", thinking: "" }], omitUsage: true },
5191
+ { name: "usage-null", content: [{ type: "thinking", thinking: "" }], usage: null },
5192
+ { name: "primitive-usage", content: [{ type: "thinking", thinking: "" }], usage: "bad" },
5193
+ { name: "array-usage", content: [{ type: "thinking", thinking: "" }], usage: [] },
5194
+ { name: "missing-total-tokens", content: [{ type: "thinking", thinking: "" }], usage: { input: 0 } },
5195
+ {
5196
+ name: "undefined-total-tokens",
5197
+ content: [{ type: "thinking", thinking: "" }],
5198
+ usage: { totalTokens: undefined },
5199
+ },
5200
+ { name: "negative-total-tokens", content: [{ type: "thinking", thinking: "" }], usage: { totalTokens: -1 } },
5201
+ {
5202
+ name: "nan-total-tokens",
5203
+ content: [{ type: "thinking", thinking: "" }],
5204
+ usage: { totalTokens: Number.NaN },
5205
+ },
5206
+ {
5207
+ name: "infinite-total-tokens",
5208
+ content: [{ type: "thinking", thinking: "" }],
5209
+ usage: { totalTokens: Number.POSITIVE_INFINITY },
5210
+ },
5211
+ {
5212
+ name: "incomplete-tool-call",
5213
+ content: [
5214
+ {
5215
+ type: "toolCall",
5216
+ id: "call-1",
5217
+ name: "read",
5218
+ arguments: {},
5219
+ incompleteArguments: true,
5220
+ incompleteArgumentsReason: "truncated",
5221
+ },
5222
+ ],
5223
+ usage: { totalTokens: 0 },
5224
+ },
5225
+ {
5226
+ name: "malformed-tool-arguments",
5227
+ content: [{ type: "toolCall", id: "call-1", name: "read", arguments: null }],
5228
+ usage: { totalTokens: 0 },
5229
+ },
5230
+ {
5231
+ name: "orphaned-incomplete-reason",
5232
+ content: [
5233
+ {
5234
+ type: "toolCall",
5235
+ id: "call-1",
5236
+ name: "read",
5237
+ arguments: {},
5238
+ incompleteArgumentsReason: "malformed",
5239
+ },
5240
+ ],
5241
+ usage: { totalTokens: 0 },
5242
+ },
5243
+ ] as const;
5244
+ for (const testCase of cases) {
5245
+ const cwd = await mkdtemp(path.join(os.tmpdir(), `gjc-terminal-malformed-${testCase.name}-`));
5246
+ try {
5247
+ const harness = await invocationHarness(`terminal-malformed-${testCase.name}`, cwd, {
5248
+ sendUserMessage: async (_content, options) => {
5249
+ await options?.onPreflightAcceptCommit?.();
5250
+ await Promise.withResolvers<void>().promise;
5251
+ },
5252
+ });
5253
+ const accepted = await harness.control("turn.prompt", { text: "hello" });
5254
+ await harness.emit("agent_start");
5255
+ await harness.emit("agent_end", {
5256
+ messages: [
5257
+ {
5258
+ role: "assistant",
5259
+ content: testCase.content,
5260
+ ...("omitUsage" in testCase ? {} : { usage: testCase.usage }),
5261
+ },
5262
+ ],
5263
+ });
5264
+ expect(
5265
+ await settledStatus(harness, "turn.result", {
5266
+ kind: "prompt",
5267
+ commandId: accepted.result?.commandId,
5268
+ turnId: accepted.result?.turnId,
5269
+ }),
5270
+ ).toMatchObject({
5271
+ status: "failed",
5272
+ error: { code: "prompt_failed", message: "Agent run failed after execution started." },
5273
+ outcome: {
5274
+ kind: "failed",
5275
+ code: "prompt_failed",
5276
+ message: "Agent run failed after execution started.",
5277
+ provenance: "agent_failed",
5278
+ phase: "post_start",
5279
+ category: "agent_runtime",
5280
+ },
5281
+ });
5282
+ await harness.stop();
5283
+ } finally {
5284
+ await rm(cwd, { recursive: true, force: true });
5285
+ }
5286
+ }
5287
+ });
5288
+ test("preserves explicit cancellation for an empty zero-token turn", async () => {
5289
+ const cwd = await mkdtemp(path.join(os.tmpdir(), "gjc-terminal-empty-cancelled-"));
5290
+ try {
5291
+ const harness = await invocationHarness("terminal-empty-cancelled", cwd, {
5292
+ sendUserMessage: async (_content, options) => {
5293
+ await options?.onPreflightAcceptCommit?.();
5294
+ await Promise.withResolvers<void>().promise;
5295
+ },
5296
+ });
5297
+ const accepted = await harness.control("turn.prompt", { text: "hello" });
5298
+ await harness.emit("agent_start");
5299
+ await harness.emit("agent_end", {
5300
+ stopReason: "cancelled",
5301
+ messages: [
5302
+ {
5303
+ role: "assistant",
5304
+ content: [{ type: "thinking", thinking: "" }],
5305
+ usage: { totalTokens: 0 },
5306
+ },
5307
+ ],
5308
+ });
5309
+ expect(
5310
+ await settledStatus(harness, "turn.result", {
5311
+ kind: "prompt",
5312
+ commandId: accepted.result?.commandId,
5313
+ turnId: accepted.result?.turnId,
5314
+ }),
5315
+ ).toMatchObject({
5316
+ status: "terminal_ok",
5317
+ outcome: { kind: "stopped", reason: "cancelled", provenance: "client_cancel" },
5318
+ });
5319
+ await harness.stop();
5320
+ } finally {
5321
+ await rm(cwd, { recursive: true, force: true });
5322
+ }
5323
+ });
5003
5324
  test("does not reuse a previous branch assistant for a new textless prompt", async () => {
5004
5325
  const cwd = await mkdtemp(path.join(os.tmpdir(), "gjc-terminal-order-previous-assistant-"));
5005
5326
  try {