@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
@@ -0,0 +1,92 @@
1
+ export type DoctorDaemonOwner = "telegram" | "discord" | "slack";
2
+
3
+ export interface DoctorDaemonIdentity {
4
+ owner: DoctorDaemonOwner;
5
+ ownerId: string;
6
+ generation: number;
7
+ incarnation: string;
8
+ }
9
+
10
+ export interface DoctorDaemonOccupancy {
11
+ attached: number;
12
+ inflight: number;
13
+ inbound: number;
14
+ outbound: number;
15
+ cleanup: number;
16
+ }
17
+
18
+ const DOCTOR_DAEMON_OCCUPANCY_KEYS = ["attached", "inflight", "inbound", "outbound", "cleanup"] as const;
19
+
20
+ export interface DoctorDaemonStatus extends DoctorDaemonIdentity {
21
+ requestId: string;
22
+ phase: "idle" | "prepared" | "committed" | "cancelled" | "expired";
23
+ admitting: boolean;
24
+ occupancy: DoctorDaemonOccupancy;
25
+ leaseExpiresAt: number;
26
+ }
27
+
28
+ export interface DoctorDaemonControlRequest extends DoctorDaemonIdentity {
29
+ version: 1;
30
+ requestId: string;
31
+ action: "prepare" | "commit" | "cancel" | "status";
32
+ createdAt: number;
33
+ leaseExpiresAt?: number;
34
+ }
35
+
36
+ export function doctorDaemonOccupancyEmpty(): DoctorDaemonOccupancy {
37
+ return { attached: 0, inflight: 0, inbound: 0, outbound: 0, cleanup: 0 };
38
+ }
39
+
40
+ /**
41
+ * True only when every named occupancy category is an explicit, valid,
42
+ * zero-valued integer. An empty object, a partial object, or an object with
43
+ * extra/malformed keys is never vacuously "settled" — `Object.values({}).
44
+ * every(...)` was previously `true` for `{}` because there is nothing to
45
+ * iterate; this checks the exact required key set instead.
46
+ */
47
+ export function doctorDaemonOccupancySettled(occupancy: DoctorDaemonOccupancy | undefined): boolean {
48
+ if (!occupancy || typeof occupancy !== "object") return false;
49
+ if (Object.keys(occupancy).length !== DOCTOR_DAEMON_OCCUPANCY_KEYS.length) return false;
50
+ return DOCTOR_DAEMON_OCCUPANCY_KEYS.every(key => {
51
+ const value = occupancy[key];
52
+ return typeof value === "number" && Number.isSafeInteger(value) && value === 0;
53
+ });
54
+ }
55
+
56
+ export function doctorDaemonIdentityMatches(
57
+ left: DoctorDaemonIdentity | undefined,
58
+ right: DoctorDaemonIdentity,
59
+ ): boolean {
60
+ return (
61
+ left?.owner === right.owner &&
62
+ left?.ownerId === right.ownerId &&
63
+ left?.generation === right.generation &&
64
+ left?.incarnation === right.incarnation
65
+ );
66
+ }
67
+
68
+ export function isDoctorDaemonControlRequest(value: unknown): value is DoctorDaemonControlRequest {
69
+ if (!value || typeof value !== "object") return false;
70
+ const candidate = value as Record<string, unknown>;
71
+ return (
72
+ candidate.version === 1 &&
73
+ typeof candidate.requestId === "string" &&
74
+ candidate.requestId.length > 0 &&
75
+ (candidate.action === "prepare" ||
76
+ candidate.action === "commit" ||
77
+ candidate.action === "cancel" ||
78
+ candidate.action === "status") &&
79
+ (candidate.owner === "telegram" || candidate.owner === "discord" || candidate.owner === "slack") &&
80
+ typeof candidate.ownerId === "string" &&
81
+ candidate.ownerId.length > 0 &&
82
+ typeof candidate.generation === "number" &&
83
+ Number.isSafeInteger(candidate.generation) &&
84
+ candidate.generation >= 0 &&
85
+ typeof candidate.incarnation === "string" &&
86
+ candidate.incarnation.length > 0 &&
87
+ typeof candidate.createdAt === "number" &&
88
+ Number.isFinite(candidate.createdAt) &&
89
+ (candidate.leaseExpiresAt === undefined ||
90
+ (typeof candidate.leaseExpiresAt === "number" && Number.isFinite(candidate.leaseExpiresAt)))
91
+ );
92
+ }
@@ -27,7 +27,7 @@ import * as fs from "node:fs";
27
27
  import * as os from "node:os";
28
28
  import * as path from "node:path";
29
29
  import { promisify } from "node:util";
30
- import { type RunSettlementProof, ThinkingLevel } from "@gajae-code/agent-core";
30
+ import { isNonDispatchedToolEvent, type RunSettlementProof, ThinkingLevel } from "@gajae-code/agent-core";
31
31
  import type { ImageContent, TextContent, Tool } from "@gajae-code/ai/core";
32
32
  import type { NotificationServer as NativeNotificationServer } from "@gajae-code/natives";
33
33
 
@@ -90,7 +90,7 @@ import { acpFinalTextFromMessage } from "../acp/final-text";
90
90
  import { ensureBroker } from "../broker/ensure";
91
91
  import { publishSessionHostRuntimeEvidence, type SessionHostRuntimePublication } from "../broker/lifecycle";
92
92
  import { processIncarnation } from "../broker/process-incarnation";
93
- import { resolveSessionLocator, SessionIndex } from "../broker/session-index";
93
+ import { resolveSessionLocator, SessionIndex, type SessionIndexEvent } from "../broker/session-index";
94
94
  import {
95
95
  CAP_GATED_FRAME_KINDS,
96
96
  createSdkSurfaceFactory,
@@ -114,7 +114,19 @@ import {
114
114
  syntheticModelInputError,
115
115
  syntheticNamespaceCollision,
116
116
  } from "../model-profile-model";
117
- import { formatPromptFailureForLocalLog, sanitizePromptFailure } from "../prompt-failure";
117
+ import {
118
+ createPromptDeadlineLease,
119
+ isAttributableProgressEventType,
120
+ type PromptDeadlineLease,
121
+ promptDeadlineAt,
122
+ recordAttributableProgress,
123
+ } from "../prompt-deadline-lease";
124
+ import {
125
+ assistantFailureCode,
126
+ failedPromptOutcome,
127
+ formatPromptFailureForLocalLog,
128
+ sanitizePromptFailure,
129
+ } from "../prompt-failure";
118
130
  import { PROMPT_CLIENT_REF_MAX_LENGTH, type SdkPromptTerminalOutcome } from "../prompt-status";
119
131
  import { OPERATIONS } from "../protocol/operation-registry";
120
132
  import {
@@ -2389,6 +2401,7 @@ function sdkQuerySurface(
2389
2401
  status: "unknown",
2390
2402
  }),
2391
2403
  getRuntimeHost: () => SessionSdkHost | undefined = () => undefined,
2404
+ primaryControlSurface: "cli" | "sdk" = "sdk",
2392
2405
  ): SessionSurface {
2393
2406
  return createSdkSurfaceFactory({
2394
2407
  ctx,
@@ -2402,6 +2415,7 @@ function sdkQuerySurface(
2402
2415
  steerStatusLookup,
2403
2416
  hostTools: () => getInstalledDefinitions("host_tools") !== undefined,
2404
2417
  getRuntimeHost,
2418
+ primaryControlSurface,
2405
2419
  }).query;
2406
2420
  }
2407
2421
 
@@ -4052,6 +4066,8 @@ export function createNotificationsExtension(
4052
4066
  controller?: NotificationSessionController;
4053
4067
  /** Whether this host mode can own the root SDK endpoint. Default: true. */
4054
4068
  sdkHostModeSupported?: boolean;
4069
+ /** Startup surface that owns host-level permission and lifecycle authority. */
4070
+ primaryControlSurface?: "cli" | "sdk";
4055
4071
  /** In-memory master capability for private broker verification only. */
4056
4072
  masterCapability?: string;
4057
4073
  /** Opaque direct-role epoch this effective host may adopt. */
@@ -4775,8 +4791,22 @@ export function createNotificationsExtension(
4775
4791
  /** Fatal/uncertain closure: transport-level only, never a semantic terminal. */
4776
4792
  fatal?: boolean;
4777
4793
  createdAt: number;
4778
- deadlineMs: number;
4794
+ /**
4795
+ * Bounded progress-aware terminal deadline: the inactivity window renews
4796
+ * on attributable tool progress correlated to this exact accepted prompt,
4797
+ * up to a hard maximum runtime anchored to acceptance.
4798
+ */
4799
+ deadlineLease: PromptDeadlineLease;
4779
4800
  deadlineTimer?: Parameters<typeof clearTimeout>[0];
4801
+ /**
4802
+ * Identity of the in-flight deadline expiry attempt (set synchronously
4803
+ * by the firing deadline timer, cleared when the attempt completes or
4804
+ * is superseded). Fresh attributable progress recorded while the
4805
+ * attempt awaits durable reconciliation advances the lease generation;
4806
+ * the attempt must then back off instead of terminalizing a live prompt
4807
+ * (mirrors PromptDeadlineManager).
4808
+ */
4809
+ deadlineAttempt?: { lease: PromptDeadlineLease; generation: number };
4780
4810
  phase: "active" | "outcome_claimed" | "terminalizing" | "publication_closed" | "delivered";
4781
4811
  outcome?: SdkPromptTerminalOutcome;
4782
4812
  /** Agent-owned resource run captured at acceptance; cleanup targets only this handle. */
@@ -4991,10 +5021,59 @@ export function createNotificationsExtension(
4991
5021
  finalizePrompt(key, correlation);
4992
5022
  }
4993
5023
  };
5024
+ const readFiniteSetting = (key: "sdk.promptDeadlineMs" | "sdk.promptMaxRuntimeMs", fallback: number): number => {
5025
+ const value = settings?.get(key);
5026
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
5027
+ };
5028
+ /**
5029
+ * (Re)arm the accepted prompt's terminal deadline from its current lease.
5030
+ * `terminalizePrompt` stays the single authoritative terminal owner; this
5031
+ * only decides when it is invoked.
5032
+ */
5033
+ const armPromptDeadline = (key: string, correlation: { commandId: string; turnId: string }) => {
5034
+ const submission = promptSubmissions.get(key);
5035
+ if (!submission) return;
5036
+ const lease = submission.deadlineLease;
5037
+ if (submission.deadlineTimer) clearTimeout(submission.deadlineTimer);
5038
+ submission.deadlineTimer = setTimeout(
5039
+ () => {
5040
+ const current = promptSubmissions.get(key);
5041
+ // Identity fencing: a discarded, evicted or re-accepted submission is
5042
+ // never terminalized by a predecessor's timer.
5043
+ if (!current || current.deadlineLease !== lease) return;
5044
+ // Progress delivered while this timer was pending renews the lease, so
5045
+ // re-arm instead of terminalizing a demonstrably live prompt.
5046
+ if (Date.now() < promptDeadlineAt(lease)) {
5047
+ armPromptDeadline(key, correlation);
5048
+ return;
5049
+ }
5050
+ // Register the expiry attempt BEFORE the awaited durable claim:
5051
+ // progress arriving while the claim is in flight must supersede
5052
+ // this attempt (see the post-claim fence in terminalizePrompt).
5053
+ current.deadlineAttempt = { lease, generation: lease.generation };
5054
+ void terminalizePrompt(
5055
+ correlation,
5056
+ {
5057
+ kind: "failed",
5058
+ code: "prompt_deadline_exceeded",
5059
+ message: "Prompt deadline exceeded.",
5060
+ provenance: "deadline",
5061
+ phase: "submission",
5062
+ category: "deadline",
5063
+ },
5064
+ { fence: true },
5065
+ { diagnostic: { reason: "Prompt deadline exceeded." } },
5066
+ );
5067
+ },
5068
+ Math.max(0, promptDeadlineAt(lease) - Date.now()),
5069
+ );
5070
+ };
4994
5071
  const emitPromptEvent = (event: AgentSessionEvent) => {
4995
5072
  if (!runtime?.activePromptCorrelation) return;
4996
5073
  cleanupPromptRecords();
4997
5074
  const correlation = runtime.activePromptCorrelation;
5075
+ // Renewal is attributed exactly like the correlated delivery below.
5076
+ renewPromptDeadline(correlation, event);
4998
5077
  const submission = promptSubmissions.get(promptSubmissionKey(correlation));
4999
5078
  if (!submission || submission.abandoned) return;
5000
5079
  const frame = {
@@ -5014,6 +5093,59 @@ export function createNotificationsExtension(
5014
5093
  abandonPrompt(submission);
5015
5094
  }
5016
5095
  };
5096
+ /**
5097
+ * Status of a registered deadline expiry attempt after an awaited
5098
+ * reconciliation transition (mirrors PromptDeadlineManager).
5099
+ * - "current": no superseding progress; the attempt may proceed.
5100
+ * - "superseded": fresh attributable progress renewed the lease past now.
5101
+ * The hard cap needs no special case: promptDeadlineAt is the minimum
5102
+ * of the renewed window and acceptedAt+maxMs, so progress at an
5103
+ * already-due cap still reads expired and the attempt proceeds.
5104
+ * - "stale": the submission was cleared, evicted or re-accepted under
5105
+ * this attempt; it must touch nothing.
5106
+ */
5107
+ type DeadlineAttemptStatus = "current" | "superseded" | "stale";
5108
+ const deadlineAttemptStatus = (
5109
+ key: string,
5110
+ submission: PromptSubmission,
5111
+ attempt: { lease: PromptDeadlineLease; generation: number },
5112
+ ): DeadlineAttemptStatus => {
5113
+ if (promptSubmissions.get(key) !== submission || submission.deadlineLease !== attempt.lease) return "stale";
5114
+ const lease = submission.deadlineLease;
5115
+ if (lease.generation !== attempt.generation && Date.now() < promptDeadlineAt(lease)) return "superseded";
5116
+ return "current";
5117
+ };
5118
+ /**
5119
+ * Renew the accepted prompt's deadline on attributable progress. The
5120
+ * allowlist stays owned by the shared lease module, so streaming chatter,
5121
+ * tool updates, heartbeats and retry bookkeeping never renew. Pairing-only
5122
+ * synthetic tool pairs (never dispatched, marked via the dispatch-identity
5123
+ * side channel rather than the wire) never renew either: they prove
5124
+ * pairing, not progress. Only a pre-terminal submission may re-arm behind
5125
+ * its terminal owner; progress arriving while a deadline expiry attempt
5126
+ * awaits durable reconciliation is recorded (advancing the attempt fence)
5127
+ * but never re-arms — the attempt itself reschedules on supersession.
5128
+ */
5129
+ const renewPromptDeadline = (correlation: { commandId: string; turnId: string }, event: { type: string }) => {
5130
+ if (!isAttributableProgressEventType(event.type) || isNonDispatchedToolEvent(event)) return;
5131
+ const key = promptSubmissionKey(correlation);
5132
+ const submission = promptSubmissions.get(key);
5133
+ if (!submission) return;
5134
+ if (
5135
+ submission.deadlineAttempt &&
5136
+ submission.deadlineLease === submission.deadlineAttempt.lease &&
5137
+ (submission.phase === "outcome_claimed" || submission.phase === "terminalizing")
5138
+ ) {
5139
+ recordAttributableProgress(submission.deadlineLease, Date.now());
5140
+ return;
5141
+ }
5142
+ if (submission.terminal || submission.phase !== "active") return;
5143
+ const generation = submission.deadlineLease.generation;
5144
+ recordAttributableProgress(submission.deadlineLease, Date.now());
5145
+ // Monotonic: stale or out-of-order progress never moves the lease.
5146
+ if (submission.deadlineLease.generation === generation) return;
5147
+ armPromptDeadline(key, correlation);
5148
+ };
5017
5149
  const flushPromptLifecycle = (key: string, submission: PromptSubmission) => {
5018
5150
  for (const frame of submission.bufferedFrames.splice(0)) {
5019
5151
  try {
@@ -5073,7 +5205,11 @@ export function createNotificationsExtension(
5073
5205
  terminal: false,
5074
5206
  retainCorrelation: trackReconciliation,
5075
5207
  createdAt: Date.now(),
5076
- deadlineMs: settings?.get("sdk.promptDeadlineMs") ?? 1_800_000,
5208
+ deadlineLease: createPromptDeadlineLease({
5209
+ now: Date.now(),
5210
+ leaseMs: readFiniteSetting("sdk.promptDeadlineMs", 1_800_000),
5211
+ maxMs: readFiniteSetting("sdk.promptMaxRuntimeMs", 21_600_000),
5212
+ }),
5077
5213
  phase: "active",
5078
5214
  // Bound to the Agent run at `agent_start`; acceptance precedes execution.
5079
5215
  executionHandle: undefined,
@@ -5081,21 +5217,12 @@ export function createNotificationsExtension(
5081
5217
  reconciliationKind,
5082
5218
  bufferedFrames: [],
5083
5219
  };
5084
- promptSubmissions.set(promptSubmissionKey(correlation), submission);
5220
+ const key = promptSubmissionKey(correlation);
5221
+ promptSubmissions.set(key, submission);
5085
5222
  if (trackReconciliation) await notePromptReconciliationAccepted(correlation, clientRef);
5086
- submission.deadlineTimer = setTimeout(() => {
5087
- void terminalizePrompt(
5088
- correlation,
5089
- {
5090
- kind: "failed",
5091
- code: "prompt_deadline_exceeded",
5092
- message: "Prompt deadline exceeded.",
5093
- provenance: "deadline",
5094
- },
5095
- { fence: true },
5096
- { diagnostic: { reason: "Prompt deadline exceeded." } },
5097
- );
5098
- }, submission.deadlineMs);
5223
+ // Armed only after the durable accept: a rolled-back acceptance
5224
+ // (`discardPromptAcceptance`) must leave no deadline behind.
5225
+ if (promptSubmissions.get(key) === submission) armPromptDeadline(key, correlation);
5099
5226
  };
5100
5227
  /** Roll back process-local registration when durable acceptance failed. */
5101
5228
  const discardPromptAcceptance = (correlation: { commandId: string; turnId: string }) => {
@@ -5123,6 +5250,7 @@ export function createNotificationsExtension(
5123
5250
  code: string,
5124
5251
  message: string,
5125
5252
  ) => {
5253
+ submission.deadlineAttempt = undefined;
5126
5254
  if (submission.deadlineTimer) clearTimeout(submission.deadlineTimer);
5127
5255
  if (!submission.terminal) {
5128
5256
  submission.terminal = true;
@@ -5220,10 +5348,29 @@ export function createNotificationsExtension(
5220
5348
  // The claim is the durability boundary: without it nothing may be published
5221
5349
  // and the endpoint must fail closed so the client rejects exactly once. The
5222
5350
  // last durable record stays active for restart recovery.
5351
+ submission.deadlineAttempt = undefined;
5223
5352
  logger.warn(`sdk: prompt claim persistence failed: ${String(error)}`);
5224
5353
  failPromptClosed(correlation, submission, "terminal_uncertain", "Prompt reconciliation is unavailable.");
5225
5354
  return;
5226
5355
  }
5356
+ // Deadline-attempt fence across the awaited claim (mirrors
5357
+ // PromptDeadlineManager): fresh attributable progress recorded while
5358
+ // the claim was in flight advances the lease generation. The pending
5359
+ // attempt must then back off — reset to active and reschedule —
5360
+ // instead of fencing and publishing a deadline terminal for a live
5361
+ // prompt. A re-accepted or evicted submission is never touched.
5362
+ const deadlineAttempt = submission.deadlineAttempt;
5363
+ if (deadlineAttempt) {
5364
+ const key = promptSubmissionKey(correlation);
5365
+ const status = deadlineAttemptStatus(key, submission, deadlineAttempt);
5366
+ if (status === "stale") return;
5367
+ if (status === "superseded") {
5368
+ submission.deadlineAttempt = undefined;
5369
+ submission.phase = "active";
5370
+ armPromptDeadline(key, correlation);
5371
+ return;
5372
+ }
5373
+ }
5227
5374
  submission.outcome = winner;
5228
5375
  submission.phase = "terminalizing";
5229
5376
  if (options.fence) {
@@ -5292,14 +5439,41 @@ export function createNotificationsExtension(
5292
5439
  }
5293
5440
  if (capture) capture.proof = proof;
5294
5441
  }
5442
+ // Second deadline-attempt fence across the awaited fencing transition:
5443
+ // progress recorded while fencing was in flight supersedes exactly
5444
+ // like progress during the claim. Non-deadline attempts skip this —
5445
+ // their attempt identity was never registered.
5446
+ if (deadlineAttempt) {
5447
+ const key = promptSubmissionKey(correlation);
5448
+ const status = deadlineAttemptStatus(key, submission, deadlineAttempt);
5449
+ if (status === "stale") return;
5450
+ if (status === "superseded") {
5451
+ submission.deadlineAttempt = undefined;
5452
+ submission.phase = "active";
5453
+ armPromptDeadline(key, correlation);
5454
+ return;
5455
+ }
5456
+ }
5295
5457
  try {
5296
- await kindReconciliation.finalizeOutcome(
5297
- submission.reconciliationKind,
5298
- correlation,
5299
- winner,
5300
- extra?.error,
5301
- extra?.finalText,
5302
- );
5458
+ if (deadlineAttempt) {
5459
+ await kindReconciliation.finalizeOutcome(
5460
+ submission.reconciliationKind,
5461
+ correlation,
5462
+ winner,
5463
+ () =>
5464
+ deadlineAttemptStatus(promptSubmissionKey(correlation), submission, deadlineAttempt) === "current",
5465
+ extra?.error,
5466
+ extra?.finalText,
5467
+ );
5468
+ } else {
5469
+ await kindReconciliation.finalizeOutcome(
5470
+ submission.reconciliationKind,
5471
+ correlation,
5472
+ winner,
5473
+ extra?.error,
5474
+ extra?.finalText,
5475
+ );
5476
+ }
5303
5477
  } catch (error) {
5304
5478
  // The durable pending claim survives; publishing an unpersisted terminal
5305
5479
  // would contradict it, so fail the endpoint closed instead.
@@ -5307,6 +5481,18 @@ export function createNotificationsExtension(
5307
5481
  failPromptClosed(correlation, submission, "terminal_uncertain", "Prompt reconciliation is unavailable.");
5308
5482
  return;
5309
5483
  }
5484
+ if (deadlineAttempt) {
5485
+ const key = promptSubmissionKey(correlation);
5486
+ const status = deadlineAttemptStatus(key, submission, deadlineAttempt);
5487
+ if (status === "stale") return;
5488
+ if (status === "superseded") {
5489
+ submission.deadlineAttempt = undefined;
5490
+ submission.phase = "active";
5491
+ armPromptDeadline(key, correlation);
5492
+ return;
5493
+ }
5494
+ submission.deadlineAttempt = undefined;
5495
+ }
5310
5496
  if (submission.deadlineTimer) clearTimeout(submission.deadlineTimer);
5311
5497
  if (!recordPromptTerminal(correlation)) return;
5312
5498
  if (!runtime) {
@@ -5343,7 +5529,10 @@ export function createNotificationsExtension(
5343
5529
  type: "agent_failed",
5344
5530
  sessionId: runtime.id,
5345
5531
  ...correlation,
5346
- error: extra?.error ?? { code: winner.code, message: winner.message },
5532
+ error:
5533
+ extra?.error !== undefined
5534
+ ? { code: extra.error.code, message: winner.message }
5535
+ : { code: winner.code, message: winner.message },
5347
5536
  outcome: winner,
5348
5537
  });
5349
5538
  } else {
@@ -5401,12 +5590,12 @@ export function createNotificationsExtension(
5401
5590
  error: formatPromptFailureForLocalLog(error),
5402
5591
  });
5403
5592
  const sanitized = sanitizePromptFailure(error);
5404
- const outcome: SdkPromptTerminalOutcome = {
5405
- kind: "failed",
5593
+ const outcome: SdkPromptTerminalOutcome = failedPromptOutcome({
5406
5594
  code: "prompt_failed",
5407
- message: sanitized.message,
5408
5595
  provenance: "agent_failed",
5409
- };
5596
+ providerCode: sanitized.code,
5597
+ evidence: {},
5598
+ });
5410
5599
  // This rejection bypasses `agent_end`, so record its local-only reason at
5411
5600
  // the accepted submission failure boundary before terminalization begins.
5412
5601
  logger.error("sdk_prompt_terminal_failed", {
@@ -5469,6 +5658,7 @@ export function createNotificationsExtension(
5469
5658
  selector => kindReconciliation.lookupResult(selector.kind, selector),
5470
5659
  selector => kindReconciliation.lookupSteer(selector),
5471
5660
  () => host,
5661
+ options.primaryControlSurface ?? "sdk",
5472
5662
  ),
5473
5663
  id,
5474
5664
  revisions,
@@ -7813,6 +8003,7 @@ export function createNotificationsExtension(
7813
8003
  processIncarnation: hostProcessIncarnation,
7814
8004
  });
7815
8005
  throwIfLifecycleStopped();
8006
+ let registration: SessionIndexEvent | undefined;
7816
8007
  await host.registerWithBroker({
7817
8008
  // The endpoint is written before registration. Its exact mtime
7818
8009
  // binds this index generation to that discovery record.
@@ -7826,7 +8017,7 @@ export function createNotificationsExtension(
7826
8017
  processIncarnation: hostProcessIncarnation,
7827
8018
  direct,
7828
8019
  });
7829
- await index.append({
8020
+ registration = await index.append({
7830
8021
  type: "host_registered",
7831
8022
  ...input,
7832
8023
  locator,
@@ -7843,13 +8034,16 @@ export function createNotificationsExtension(
7843
8034
  });
7844
8035
  },
7845
8036
  unregister: async input => {
7846
- await index.append({
7847
- type: "host_unregistered",
7848
- ...input,
7849
- locator,
7850
- pid: process.pid,
7851
- ...(lifecycleRequestId ? { lifecycleRequestId } : {}),
7852
- });
8037
+ const expected = registration;
8038
+ if (
8039
+ !expected ||
8040
+ expected.sessionId !== input.sessionId ||
8041
+ expected.endpointGeneration !== input.endpointGeneration ||
8042
+ path.resolve(expected.locator.stateRoot) !== path.resolve(input.stateRoot)
8043
+ )
8044
+ return;
8045
+ await index.unregisterIfCurrent(expected);
8046
+ if (registration === expected) registration = undefined;
7853
8047
  },
7854
8048
  });
7855
8049
  throwIfLifecycleStopped();
@@ -8768,8 +8962,15 @@ export function createNotificationsExtension(
8768
8962
  if (correlation) {
8769
8963
  const assistants = (Array.isArray(event.messages) ? [...event.messages].reverse() : []).filter(
8770
8964
  message => message && typeof message === "object" && (message as { role?: unknown }).role === "assistant",
8771
- ) as Array<{ stopReason?: unknown; errorKind?: unknown }>;
8965
+ ) as Array<{ stopReason?: unknown; errorKind?: unknown; errorCode?: unknown }>;
8772
8966
  const finalAssistant = assistants[0];
8967
+ const terminalAssistant = assistants.find(
8968
+ message =>
8969
+ (message as { stopReason?: unknown }).stopReason === "error" ||
8970
+ (message as { stopReason?: unknown }).stopReason === "aborted",
8971
+ ) as
8972
+ | { stopReason?: "error" | "aborted"; errorMessage?: unknown; errorKind?: unknown; errorCode?: unknown }
8973
+ | undefined;
8773
8974
  const pendingOutcome = rt.peekPromptPendingOutcome(correlation);
8774
8975
  let outcome: SdkPromptTerminalOutcome;
8775
8976
  if (pendingOutcome) outcome = pendingOutcome;
@@ -8786,24 +8987,24 @@ export function createNotificationsExtension(
8786
8987
  finalAssistant?.stopReason !== "aborted"
8787
8988
  )
8788
8989
  outcome = { kind: "stopped", reason: "end_turn", provenance: "agent" };
8789
- else
8790
- outcome = {
8791
- kind: "failed",
8990
+ else {
8991
+ // The provider/agent's own bounded classifier travels on the terminal
8992
+ // assistant message; the phase is finalized against the durable record
8993
+ // (startedAt) when the outcome is claimed.
8994
+ const providerCode = assistantFailureCode(terminalAssistant);
8995
+ outcome = failedPromptOutcome({
8792
8996
  code: "prompt_failed",
8793
- message: "Prompt submission failed.",
8794
8997
  provenance: "agent_failed",
8795
- };
8998
+ ...(providerCode !== undefined ? { providerCode } : {}),
8999
+ evidence: {},
9000
+ });
9001
+ }
8796
9002
  const successAssistant = assistants.find(
8797
9003
  message => (message as { stopReason?: unknown }).stopReason === "stop",
8798
9004
  );
8799
9005
  const finalText = successAssistant ? acpFinalTextFromMessage(successAssistant).text : "";
8800
9006
  // The normalized outcome is the ACP contract; existing SDK clients keep the
8801
9007
  // legacy failure discriminator on the wire and in the reconciliation record.
8802
- const terminalAssistant = assistants.find(
8803
- message =>
8804
- (message as { stopReason?: unknown }).stopReason === "error" ||
8805
- (message as { stopReason?: unknown }).stopReason === "aborted",
8806
- ) as { stopReason?: "error" | "aborted"; errorMessage?: unknown; errorKind?: unknown } | undefined;
8807
9008
  const legacyCode =
8808
9009
  event.stopReason === "cancelled"
8809
9010
  ? "cancelled"
@@ -8835,7 +9036,7 @@ export function createNotificationsExtension(
8835
9036
  ? {
8836
9037
  // Only the legacy discriminator is preserved; provider text is never
8837
9038
  // retained, matching `sanitizePromptFailure`.
8838
- error: { code: legacyCode, message: "Prompt submission failed." },
9039
+ error: { code: legacyCode, message: outcome.message },
8839
9040
  }
8840
9041
  : {}),
8841
9042
  });