@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
@@ -9,6 +9,7 @@ import {
9
9
  scopeRequestV1,
10
10
  searchRowV1,
11
11
  } from "../broker/session-scope";
12
+ import type { SessionBindingAuthority } from "../session-authority";
12
13
  import {
13
14
  SessionListTraversalError,
14
15
  type SessionListTraversalPage,
@@ -16,6 +17,8 @@ import {
16
17
  traverseSessionList,
17
18
  } from "../session-list";
18
19
 
20
+ export type { SessionBindingAuthority, SessionEndpointAuthority } from "../session-authority";
21
+
19
22
  export type SessionLifecycleOperation =
20
23
  | "session.create"
21
24
  | "session.fork"
@@ -23,8 +26,12 @@ export type SessionLifecycleOperation =
23
26
  | "session.close"
24
27
  | "session.delete"
25
28
  | "session.reconcile_uncertain"
29
+ | "session.lookup"
26
30
  | "session.list";
27
31
 
32
+ export type SessionLifecycleMutationOperation = Exclude<SessionLifecycleOperation, "session.lookup" | "session.list">;
33
+ export type SessionLifecycleLookupOperation = "session.create";
34
+
28
35
  export interface SessionLifecycleActor {
29
36
  readonly id: string;
30
37
  readonly namespace: string;
@@ -122,11 +129,13 @@ export type SessionResumeTarget = SessionLifecycleCoordinatorTarget & {
122
129
  readonly readinessTimeoutMs?: number;
123
130
  };
124
131
 
125
- export interface SessionCloseTarget {
126
- readonly sessionId: string;
127
- readonly endpointGeneration?: number;
128
- readonly endpointIncarnation?: string;
129
- }
132
+ export type SessionCloseTarget =
133
+ | {
134
+ readonly sessionId: string;
135
+ readonly endpointGeneration?: never;
136
+ readonly endpointIncarnation?: never;
137
+ }
138
+ | SessionBindingAuthority;
130
139
 
131
140
  export interface SessionReconcileUncertainTarget {
132
141
  readonly sessionId: string;
@@ -155,10 +164,7 @@ export interface SessionListTarget {
155
164
  readonly cursor?: string;
156
165
  }
157
166
 
158
- interface SessionLifecycleMutationRequestBase<
159
- TOperation extends Exclude<SessionLifecycleOperation, "session.list">,
160
- TTarget,
161
- > {
167
+ interface SessionLifecycleMutationRequestBase<TOperation extends SessionLifecycleMutationOperation, TTarget> {
162
168
  readonly operation: TOperation;
163
169
  readonly actor: SessionLifecycleActor;
164
170
  readonly capability: TOperation;
@@ -184,7 +190,19 @@ export interface SessionListRequest {
184
190
  readonly timeoutMs?: number;
185
191
  }
186
192
 
187
- export type SessionLifecycleRequest = SessionLifecycleMutationRequest | SessionListRequest;
193
+ export interface SessionLifecycleLookupRequest {
194
+ readonly operation: SessionLifecycleLookupOperation;
195
+ readonly actor: SessionLifecycleActor;
196
+ readonly capability: "session.lookup";
197
+ readonly requestKey: string;
198
+ readonly target: Readonly<Record<string, unknown>>;
199
+ readonly timeoutMs?: number;
200
+ }
201
+
202
+ export type SessionLifecycleRequest =
203
+ | SessionLifecycleMutationRequest
204
+ | SessionListRequest
205
+ | SessionLifecycleLookupRequest;
188
206
 
189
207
  export type SessionLifecycleMutationRequest =
190
208
  | SessionCreateRequest
@@ -209,6 +227,7 @@ export interface SessionLifecycleListEntry {
209
227
  readonly sessionId: string;
210
228
  readonly live?: boolean;
211
229
  readonly endpointGeneration?: number;
230
+ readonly endpointIncarnation?: string;
212
231
  readonly terminalUncertain?: boolean;
213
232
  readonly cwd?: string;
214
233
  readonly locator?: { readonly cwd: string; readonly worktreeRoot: string | null; readonly stateRoot: string };
@@ -259,6 +278,30 @@ export interface SessionListSuccessResult {
259
278
  readonly result: SessionLifecycleListResult | SessionScopedListResult;
260
279
  }
261
280
 
281
+ export interface SessionLifecycleLookupIdentity {
282
+ readonly operation: SessionLifecycleLookupOperation;
283
+ readonly requestKey: string;
284
+ }
285
+
286
+ export type SessionLifecycleLookupStatus = "found" | "pending" | "not_found" | "conflict" | "uncertain" | "terminal";
287
+
288
+ export interface SessionLifecycleLookupSuccessResult {
289
+ readonly ok: true;
290
+ readonly operation: "session.lookup";
291
+ readonly status: "found";
292
+ readonly request: SessionLifecycleLookupIdentity;
293
+ readonly result: SessionLifecycleSessionResult;
294
+ }
295
+
296
+ export interface SessionLifecycleLookupFailure {
297
+ readonly ok: false;
298
+ readonly operation: "session.lookup";
299
+ readonly status: Exclude<SessionLifecycleLookupStatus, "found"> | "unavailable";
300
+ readonly request: SessionLifecycleLookupIdentity;
301
+ readonly certainty: SessionLifecycleCertainty;
302
+ readonly error: SessionLifecycleError;
303
+ }
304
+
262
305
  export interface SessionLifecycleError {
263
306
  readonly code: string;
264
307
  readonly message: string;
@@ -315,6 +358,7 @@ export type SessionCloseOutcome = SessionCloseResult | SessionCloseFailure;
315
358
  export type SessionDeleteOutcome = SessionDeleteResult | SessionDeleteFailure;
316
359
  export type SessionReconcileUncertainOutcome = SessionReconcileUncertainResult | SessionReconcileUncertainFailure;
317
360
  export type SessionListOutcome = SessionListSuccessResult | SessionListFailure;
361
+ export type SessionLifecycleLookupOutcome = SessionLifecycleLookupSuccessResult | SessionLifecycleLookupFailure;
318
362
  export type SessionLifecycleResult =
319
363
  | SessionCreateOutcome
320
364
  | SessionForkOutcome
@@ -322,13 +366,14 @@ export type SessionLifecycleResult =
322
366
  | SessionCloseOutcome
323
367
  | SessionDeleteOutcome
324
368
  | SessionReconcileUncertainOutcome
325
- | SessionListOutcome;
369
+ | SessionListOutcome
370
+ | SessionLifecycleLookupOutcome;
326
371
 
327
372
  /** Shared, side-effect-free validation for lifecycle mutation requests. */
328
373
  export type SessionLifecycleMutationValidation =
329
374
  | {
330
375
  readonly ok: true;
331
- readonly operation: Exclude<SessionLifecycleOperation, "session.list">;
376
+ readonly operation: SessionLifecycleMutationOperation;
332
377
  readonly actor: SessionLifecycleActor;
333
378
  readonly requestKey: string;
334
379
  readonly target: Readonly<Record<string, unknown>>;
@@ -381,7 +426,7 @@ export function deriveSessionLifecycleIdempotencyKey(
381
426
  return createHash("sha256").update(canonicalJson(identity), "utf8").digest("hex");
382
427
  }
383
428
 
384
- function operationOf(value: unknown): SessionLifecycleOperation {
429
+ function operationOf(value: unknown): SessionLifecycleMutationOperation | "session.list" {
385
430
  if (
386
431
  value === "session.create" ||
387
432
  value === "session.fork" ||
@@ -395,7 +440,7 @@ function operationOf(value: unknown): SessionLifecycleOperation {
395
440
  return "session.list";
396
441
  }
397
442
 
398
- function failure<TOperation extends SessionLifecycleOperation>(
443
+ function failure<TOperation extends SessionLifecycleMutationOperation | "session.list">(
399
444
  operation: TOperation,
400
445
  certainty: SessionLifecycleCertainty,
401
446
  code: string,
@@ -427,6 +472,20 @@ function validTarget(target: unknown): target is Readonly<Record<string, unknown
427
472
  return isRecord(target);
428
473
  }
429
474
 
475
+ function validSessionCloseTarget(target: Readonly<Record<string, unknown>>): boolean {
476
+ const hasGeneration = Object.hasOwn(target, "endpointGeneration");
477
+ const hasIncarnation = Object.hasOwn(target, "endpointIncarnation");
478
+ if (hasGeneration !== hasIncarnation) return false;
479
+ if (!hasGeneration) return true;
480
+ return (
481
+ typeof target.endpointGeneration === "number" &&
482
+ Number.isSafeInteger(target.endpointGeneration) &&
483
+ target.endpointGeneration > 0 &&
484
+ typeof target.endpointIncarnation === "string" &&
485
+ /^[a-f0-9]{64}$/u.test(target.endpointIncarnation)
486
+ );
487
+ }
488
+
430
489
  export function validateSessionReconcileUncertainTarget(value: unknown): value is SessionReconcileUncertainTarget {
431
490
  if (!isRecord(value)) return false;
432
491
  const target = value;
@@ -468,6 +527,8 @@ export function validateSessionLifecycleMutationRequest(request: unknown): Sessi
468
527
  return failure(operation, "terminal", "capability_denied", `capability does not authorize ${operation}`);
469
528
  if (!validTarget(record.target))
470
529
  return failure(operation, "terminal", "invalid_request", "target must be an object");
530
+ if (operation === "session.close" && !validSessionCloseTarget(record.target))
531
+ return failure(operation, "terminal", "invalid_input", "session.close endpoint authority is invalid");
471
532
  if (operation === "session.reconcile_uncertain" && !validateSessionReconcileUncertainTarget(record.target))
472
533
  return failure(
473
534
  operation,
@@ -503,6 +564,23 @@ function credentialFreeRecord(value: Record<string, unknown>): Record<string, un
503
564
  return output;
504
565
  }
505
566
 
567
+ function sessionEndpointProjection(value: Record<string, unknown>): {
568
+ endpointGeneration?: number;
569
+ endpointIncarnation?: string;
570
+ } {
571
+ const endpointGeneration = value.endpointGeneration;
572
+ const endpointIncarnation = value.endpointIncarnation;
573
+ if (
574
+ typeof endpointGeneration !== "number" ||
575
+ !Number.isSafeInteger(endpointGeneration) ||
576
+ endpointGeneration <= 0 ||
577
+ typeof endpointIncarnation !== "string" ||
578
+ !/^[a-f0-9]{64}$/u.test(endpointIncarnation)
579
+ )
580
+ return {};
581
+ return { endpointGeneration, endpointIncarnation };
582
+ }
583
+
506
584
  function sessionResult(value: unknown, expectedSessionId?: string): SessionLifecycleSessionResult | undefined {
507
585
  if (!isRecord(value)) return undefined;
508
586
  const record = credentialFreeRecord(value);
@@ -515,13 +593,8 @@ function sessionResult(value: unknown, expectedSessionId?: string): SessionLifec
515
593
  endpointIncarnation?: string;
516
594
  reused?: boolean;
517
595
  note?: string;
518
- } = { sessionId };
596
+ } = { sessionId, ...sessionEndpointProjection(record) };
519
597
  if (typeof record.cwd === "string") result.cwd = record.cwd;
520
- const endpointGeneration = record.endpointGeneration;
521
- if (typeof endpointGeneration === "number" && Number.isSafeInteger(endpointGeneration) && endpointGeneration > 0)
522
- result.endpointGeneration = endpointGeneration;
523
- if (typeof record.endpointIncarnation === "string" && /^[a-f0-9]{64}$/.test(record.endpointIncarnation))
524
- result.endpointIncarnation = record.endpointIncarnation;
525
598
  if (typeof record.reused === "boolean") result.reused = record.reused;
526
599
  if (typeof record.note === "string") result.note = record.note;
527
600
  return result;
@@ -601,12 +674,12 @@ function listResult(value: unknown): SessionLifecycleListResult | undefined {
601
674
  sessionId: string;
602
675
  live?: boolean;
603
676
  endpointGeneration?: number;
677
+ endpointIncarnation?: string;
604
678
  terminalUncertain?: boolean;
605
679
  cwd?: string;
606
680
  locator?: { cwd: string; worktreeRoot: string | null; stateRoot: string };
607
- } = { sessionId: entry.sessionId };
681
+ } = { sessionId: entry.sessionId, ...sessionEndpointProjection(entry) };
608
682
  if (typeof entry.live === "boolean") item.live = entry.live;
609
- if (typeof entry.endpointGeneration === "number") item.endpointGeneration = entry.endpointGeneration;
610
683
  if (typeof entry.terminalUncertain === "boolean") item.terminalUncertain = entry.terminalUncertain;
611
684
  if (isRecord(entry.locator) && typeof entry.locator.cwd === "string") item.cwd = entry.locator.cwd;
612
685
  if (
@@ -746,6 +819,26 @@ function certaintyForThrownError(error: {
746
819
  return "retryable";
747
820
  }
748
821
 
822
+ function lookupStatusForError(code: string): {
823
+ status: Exclude<SessionLifecycleLookupStatus, "found"> | "unavailable";
824
+ certainty: SessionLifecycleCertainty;
825
+ } {
826
+ if (code === "not_found") return { status: "not_found", certainty: "uncertain" };
827
+ if (code === "idempotency_conflict") return { status: "conflict", certainty: "uncertain" };
828
+ if (code === "lifecycle_pending") return { status: "pending", certainty: "uncertain" };
829
+ if (code === "terminal_uncertain") return { status: "uncertain", certainty: "uncertain" };
830
+ return { status: "terminal", certainty: certaintyForBrokerCode(code) };
831
+ }
832
+
833
+ function lookupFailure(
834
+ request: SessionLifecycleLookupIdentity,
835
+ status: Exclude<SessionLifecycleLookupStatus, "found"> | "unavailable",
836
+ certainty: SessionLifecycleCertainty,
837
+ error: SessionLifecycleError,
838
+ ): SessionLifecycleLookupFailure {
839
+ return { ok: false, operation: "session.lookup", status, request, certainty, error };
840
+ }
841
+
749
842
  function brokerSuccess(value: unknown): unknown | undefined {
750
843
  if (!isRecord(value) || value.ok !== true) return undefined;
751
844
  return value.result;
@@ -758,6 +851,90 @@ export class SessionLifecycleService {
758
851
  this.#client = client;
759
852
  }
760
853
 
854
+ async lookup(request: SessionLifecycleLookupRequest): Promise<SessionLifecycleLookupOutcome> {
855
+ const operation = request.operation;
856
+ const requestKey = typeof request.requestKey === "string" ? request.requestKey : "";
857
+ const identity: SessionLifecycleLookupIdentity = { operation, requestKey };
858
+ if (!validActor(request.actor))
859
+ return lookupFailure(identity, "terminal", "terminal", {
860
+ code: "unauthorized",
861
+ message: "authenticated actor is required",
862
+ });
863
+ if (request.capability !== "session.lookup")
864
+ return lookupFailure(identity, "terminal", "terminal", {
865
+ code: "capability_denied",
866
+ message: "capability does not authorize session.lookup",
867
+ });
868
+ if (!validRequestKey(request.requestKey))
869
+ return lookupFailure(identity, "terminal", "terminal", {
870
+ code: "invalid_request",
871
+ message: "requestKey is required",
872
+ });
873
+ if (!validTarget(request.target))
874
+ return lookupFailure(identity, "terminal", "terminal", {
875
+ code: "invalid_request",
876
+ message: "target must be an object",
877
+ });
878
+
879
+ const validation = validateSessionLifecycleMutationRequest({
880
+ operation,
881
+ actor: request.actor,
882
+ capability: operation,
883
+ requestKey: request.requestKey,
884
+ target: request.target,
885
+ });
886
+ if (!validation.ok) return lookupFailure(identity, "terminal", validation.certainty, validation.error);
887
+
888
+ let response: unknown;
889
+ try {
890
+ response = await this.#client.global(
891
+ "session.lookup",
892
+ { operation, target: { ...validation.target } },
893
+ {
894
+ idempotencyKey: deriveSessionLifecycleIdempotencyKey(request.actor, request.requestKey, operation),
895
+ ...(request.timeoutMs === undefined ? {} : { timeoutMs: request.timeoutMs }),
896
+ },
897
+ );
898
+ } catch (thrown) {
899
+ const error = brokerError(thrown) ?? brokerErrorFromThrown(thrown);
900
+ if (error) {
901
+ const mapped = lookupStatusForError(error.code);
902
+ const transport = TRANSPORT_ERROR_CODES.has(error.code);
903
+ return lookupFailure(
904
+ identity,
905
+ transport ? "unavailable" : mapped.status,
906
+ transport ? certaintyForThrownError(error) : mapped.certainty,
907
+ {
908
+ code: error.code,
909
+ message: error.message,
910
+ },
911
+ );
912
+ }
913
+ return lookupFailure(identity, "unavailable", "retryable", {
914
+ code: "unavailable",
915
+ message: "lifecycle broker request was unavailable",
916
+ });
917
+ }
918
+
919
+ const error = brokerError(response);
920
+ if (error) {
921
+ const mapped = lookupStatusForError(error.code);
922
+ return lookupFailure(identity, mapped.status, mapped.certainty, error);
923
+ }
924
+ if (!isRecord(response) || response.ok !== true)
925
+ return lookupFailure(identity, "uncertain", "uncertain", {
926
+ code: "malformed_response",
927
+ message: "lifecycle broker returned a malformed lookup result",
928
+ });
929
+ const result = sessionResult(brokerSuccess(response));
930
+ if (!result)
931
+ return lookupFailure(identity, "uncertain", "uncertain", {
932
+ code: "malformed_response",
933
+ message: "lifecycle broker returned a malformed session result",
934
+ });
935
+ return { ok: true, operation: "session.lookup", status: "found", request: identity, result };
936
+ }
937
+
761
938
  async scopedList(scope: ScopeRequestV1, limit?: number, cursor?: string): Promise<SessionListOutcome> {
762
939
  return await this.#list(
763
940
  {
@@ -150,7 +150,7 @@ function mcpOperationError(
150
150
  const MCP_LIFECYCLE_ACTOR = { id: "gjc-sdk-mcp", namespace: "sdk:mcp" } as const;
151
151
  const ROUTER_START_TIMEOUT_MS = 3_000;
152
152
  const ROUTER_STOP_TIMEOUT_MS = 5_000;
153
- type LifecycleMutationOperation = Exclude<SessionLifecycleOperation, "session.list">;
153
+ type LifecycleMutationOperation = Exclude<SessionLifecycleOperation, "session.lookup" | "session.list">;
154
154
 
155
155
  function isLifecycleOperation(operation: string): operation is LifecycleMutationOperation {
156
156
  return (
@@ -11,7 +11,12 @@
11
11
  */
12
12
 
13
13
  /** Agent session event types that constitute attributable agent/skill/tool progress. */
14
- const ATTRIBUTABLE_PROGRESS_EVENT_TYPES = new Set(["tool_execution_start", "tool_execution_end"]);
14
+ const ATTRIBUTABLE_PROGRESS_EVENT_TYPES = new Set([
15
+ "tool_execution_start",
16
+ /** Periodic output from a running tool; renews the deadline for long-running tools (e.g. Gradle compiles). */
17
+ "tool_execution_update",
18
+ "tool_execution_end",
19
+ ]);
15
20
 
16
21
  export interface PromptDeadlineLease {
17
22
  /** When the prompt was accepted; anchors the hard maximum runtime. */
@@ -57,9 +62,12 @@ export function recordAttributableProgress(lease: PromptDeadlineLease, now: numb
57
62
 
58
63
  /**
59
64
  * Whether an agent session event at the prompt/agent runtime boundary is
60
- * attributable progress for the accepted prompt. Only tool execution
61
- * boundaries qualify: skills do their work through tools, so skill progress is
62
- * covered; streaming text/thinking deltas, retries, and bookkeeping are not.
65
+ * attributable progress for the accepted prompt. Tool execution boundaries and
66
+ * streaming updates qualify: skills do their work through tools, so skill
67
+ * progress is covered; `tool_execution_update` keeps long-running tools (e.g.
68
+ * a multi-minute Gradle compile that streams stdout) from tripping the
69
+ * inactivity deadline mid-run. Streaming text/thinking deltas, retries, and
70
+ * bookkeeping are not attributable.
63
71
  */
64
72
  export function isAttributableProgressEventType(type: string): boolean {
65
73
  return ATTRIBUTABLE_PROGRESS_EVENT_TYPES.has(type);
@@ -7,6 +7,7 @@ import {
7
7
  promptDeadlineAt,
8
8
  recordAttributableProgress,
9
9
  } from "./prompt-deadline-lease";
10
+ import { failedPromptOutcome } from "./prompt-failure";
10
11
  import type { SdkPromptTerminalOutcome } from "./prompt-status";
11
12
  import type { TurnResultContent } from "./turn-result";
12
13
 
@@ -17,10 +18,8 @@ const UNCERTAINTY_RETRY_DELAY_MS = 1_000;
17
18
 
18
19
  type DeadlineReconciliation = InvocationReconciliation | KindAwareReconciliation;
19
20
 
20
- export type PromptDeadlineOutcome = {
21
- kind: "failed";
21
+ export type PromptDeadlineOutcome = Extract<SdkPromptTerminalOutcome, { kind: "failed" }> & {
22
22
  code: "prompt_deadline_exceeded";
23
- message: string;
24
23
  provenance: "deadline";
25
24
  };
26
25
 
@@ -161,12 +160,11 @@ export class PromptDeadlineManager {
161
160
  return;
162
161
  }
163
162
  const generation = lease.generation;
164
- const outcome: PromptDeadlineOutcome = {
165
- kind: "failed",
163
+ const outcome: PromptDeadlineOutcome = failedPromptOutcome({
166
164
  code: "prompt_deadline_exceeded",
167
- message: "Prompt deadline exceeded.",
168
165
  provenance: "deadline",
169
- };
166
+ evidence: {},
167
+ }) as PromptDeadlineOutcome;
170
168
  try {
171
169
  await this.#reconciliation.claimPendingOutcome("prompt", correlation, outcome);
172
170
  } catch {
@@ -3,9 +3,157 @@
3
3
  * Provider error text is retained only in the local diagnostic log; wire and
4
4
  * persisted reconciliation details expose a fixed redacted message.
5
5
  */
6
+ import type { SdkPromptFailureCategory, SdkPromptFailurePhase, SdkPromptTerminalOutcome } from "./prompt-status";
7
+
6
8
  export const PROMPT_FAILURE_CODE_MAX = 64;
7
9
  const LOCAL_FAILURE_LOG_MAX = 16_384;
8
10
 
11
+ export const PROMPT_FAILURE_MESSAGE_SUBMISSION = "Prompt submission failed.";
12
+ export const PROMPT_FAILURE_MESSAGE_POST_START_PROVIDER = "Provider failure after execution started.";
13
+ export const PROMPT_FAILURE_MESSAGE_POST_START_AGENT = "Agent run failed after execution started.";
14
+ export const PROMPT_FAILURE_MESSAGE_DEADLINE = "Prompt deadline exceeded.";
15
+
16
+ /** Evidence that decides whether a failure happened before or after the run started. */
17
+ export interface PromptFailureEvidence {
18
+ startedAt?: number;
19
+ hasActivity?: boolean;
20
+ }
21
+
22
+ /** Start/activity evidence for phase derivation from a reconciliation record. */
23
+ export function failureEvidence(record: { startedAt?: number }, hasActivity?: boolean): PromptFailureEvidence {
24
+ return {
25
+ ...(record.startedAt !== undefined ? { startedAt: record.startedAt } : {}),
26
+ ...(hasActivity === true ? { hasActivity: true } : {}),
27
+ };
28
+ }
29
+
30
+ /**
31
+ * Bounded classifier sets. Only these safe tokens may select a category; any
32
+ * other value (including `undefined`) stays `unknown` instead of guessing, so
33
+ * an unrecognized or provider-invented code cannot masquerade as a known class.
34
+ */
35
+ const PROVIDER_TRANSPORT_CODES = new Set([
36
+ "provider_down",
37
+ "provider_unavailable",
38
+ "upstream_stream_interrupted",
39
+ "upstream_error",
40
+ "transport_reset",
41
+ "stream_first_event_timeout",
42
+ "empty_response",
43
+ "server_is_overloaded",
44
+ ]);
45
+ const PROVIDER_REJECTED_CODES = new Set(["provider_rejected", "provider_http_402", "provider_http_429"]);
46
+ const AGENT_RUNTIME_CODES = new Set([
47
+ "agent_failed",
48
+ "internal",
49
+ "local_snapshot_failure",
50
+ "local_buffer_overflow",
51
+ "argument_validation",
52
+ "execution",
53
+ "io_error",
54
+ "skill_runtime",
55
+ "escaped_arguments_discarded",
56
+ "prompt_failed",
57
+ "aborted",
58
+ ]);
59
+
60
+ /** Whether the run had already started when the failure was recorded. */
61
+ export function promptFailurePhase(evidence: PromptFailureEvidence): SdkPromptFailurePhase {
62
+ return evidence.startedAt !== undefined || evidence.hasActivity === true ? "post_start" : "submission";
63
+ }
64
+
65
+ export function isSdkPromptFailurePhase(value: unknown): value is SdkPromptFailurePhase {
66
+ return value === "submission" || value === "post_start";
67
+ }
68
+
69
+ /** Allowlisted origin category for a bounded safe classifier + provenance. */
70
+ export function promptFailureCategory(
71
+ code: string | undefined,
72
+ provenance: "agent_failed" | "deadline",
73
+ ): SdkPromptFailureCategory {
74
+ if (provenance === "deadline" || code === "prompt_deadline_exceeded") return "deadline";
75
+ if (code === undefined) return "unknown";
76
+ if (PROVIDER_TRANSPORT_CODES.has(code) || /^provider_http_5\d\d$/.test(code)) return "provider_transport";
77
+ if (PROVIDER_REJECTED_CODES.has(code) || /^provider_http_4\d\d$/.test(code)) return "provider_rejected";
78
+ if (AGENT_RUNTIME_CODES.has(code)) return "agent_runtime";
79
+ return "unknown";
80
+ }
81
+
82
+ /**
83
+ * Phase- and category-aware safe wording. A post-start failure is never
84
+ * described as a submission rejection; the wording says only what the bounded
85
+ * category actually proves.
86
+ */
87
+ export function promptFailureMessage(phase: SdkPromptFailurePhase, category: SdkPromptFailureCategory): string {
88
+ if (category === "deadline") return PROMPT_FAILURE_MESSAGE_DEADLINE;
89
+ if (phase === "submission") return PROMPT_FAILURE_MESSAGE_SUBMISSION;
90
+ if (category === "provider_transport" || category === "provider_rejected")
91
+ return PROMPT_FAILURE_MESSAGE_POST_START_PROVIDER;
92
+ return PROMPT_FAILURE_MESSAGE_POST_START_AGENT;
93
+ }
94
+
95
+ /**
96
+ * Recompute `phase`, `category` and `message` for a failed outcome from the
97
+ * bounded classifier plus the record's start/activity evidence. Non-failed
98
+ * outcomes pass through untouched. Idempotent.
99
+ */
100
+ export function rephaseFailedOutcome(
101
+ outcome: SdkPromptTerminalOutcome,
102
+ evidence: PromptFailureEvidence,
103
+ ): SdkPromptTerminalOutcome {
104
+ if (outcome.kind !== "failed") return outcome;
105
+ const category = promptFailureCategory(outcome.providerCode ?? outcome.code, outcome.provenance);
106
+ const phase = promptFailurePhase(evidence);
107
+ const message = promptFailureMessage(phase, category);
108
+ if (category === outcome.category && phase === outcome.phase && message === outcome.message) return outcome;
109
+ return { ...outcome, category, phase, message };
110
+ }
111
+
112
+ /** Build a complete failed outcome from a bounded classifier and evidence. */
113
+ export function failedPromptOutcome(input: {
114
+ code: "prompt_failed" | "prompt_deadline_exceeded";
115
+ provenance: "agent_failed" | "deadline";
116
+ providerCode?: string;
117
+ phase?: SdkPromptFailurePhase;
118
+ evidence: PromptFailureEvidence;
119
+ }): Extract<SdkPromptTerminalOutcome, { kind: "failed" }> {
120
+ const category = promptFailureCategory(input.providerCode ?? input.code, input.provenance);
121
+ const phase = input.phase ?? promptFailurePhase(input.evidence);
122
+ return {
123
+ kind: "failed",
124
+ code: input.code,
125
+ message: promptFailureMessage(phase, category),
126
+ provenance: input.provenance,
127
+ phase,
128
+ category,
129
+ ...(input.providerCode !== undefined ? { providerCode: input.providerCode } : {}),
130
+ };
131
+ }
132
+
133
+ /**
134
+ * Bounded safe classifier read off a terminal assistant message: the provider's
135
+ * own `errorCode`, else the typed transport fact's `providerCode`. Only a safe
136
+ * token is accepted; anything else is dropped rather than forwarded.
137
+ */
138
+ export function assistantFailureCode(assistant: unknown): string | undefined {
139
+ try {
140
+ const candidate = assistant as { errorCode?: unknown; transportFailure?: { providerCode?: unknown } } | undefined;
141
+ const direct = candidate?.errorCode;
142
+ if (typeof direct === "string" && direct.length <= PROMPT_FAILURE_CODE_MAX && /^[A-Za-z0-9._-]+$/.test(direct))
143
+ return direct;
144
+ const transport = candidate?.transportFailure?.providerCode;
145
+ if (
146
+ typeof transport === "string" &&
147
+ transport.length <= PROMPT_FAILURE_CODE_MAX &&
148
+ /^[A-Za-z0-9._-]+$/.test(transport)
149
+ )
150
+ return transport;
151
+ return undefined;
152
+ } catch {
153
+ return undefined;
154
+ }
155
+ }
156
+
9
157
  /** Safe-token code capped at 64; arbitrary failure text is never retained. */
10
158
  export function sanitizePromptFailure(error: unknown): { code: string; message: string } {
11
159
  let rawCode = "";
@@ -57,9 +57,38 @@ import type { ReceiptState } from "./receipt-state";
57
57
  export type PromptReconciliationStatus = "accepted" | "in_flight" | "terminal_ok" | "failed";
58
58
  export type SdkPromptStopReason = "end_turn" | "max_tokens" | "max_turn_requests" | "refusal" | "cancelled";
59
59
  export type SdkPromptFailureCode = "prompt_failed" | "prompt_deadline_exceeded";
60
+ /**
61
+ * Stable, safe execution phase of a failed prompt. `submission` means the
62
+ * failure happened before the run started (a genuine submission rejection);
63
+ * `post_start` means the run had already started, so it must never be described
64
+ * as a submission rejection.
65
+ */
66
+ export type SdkPromptFailurePhase = "submission" | "post_start";
67
+ /**
68
+ * Allowlisted, bounded origin category for a failed prompt. It is derived only
69
+ * from safe classifiers (never raw provider text) and preserves `unknown` when
70
+ * attribution is uncertain instead of guessing.
71
+ */
72
+ export type SdkPromptFailureCategory =
73
+ | "provider_transport"
74
+ | "provider_rejected"
75
+ | "agent_runtime"
76
+ | "deadline"
77
+ | "unknown";
60
78
  export type SdkPromptTerminalOutcome =
61
79
  | { kind: "stopped"; reason: SdkPromptStopReason; provenance: "agent" | "client_cancel" }
62
- | { kind: "failed"; code: SdkPromptFailureCode; message: string; provenance: "agent_failed" | "deadline" };
80
+ | {
81
+ kind: "failed";
82
+ code: SdkPromptFailureCode;
83
+ message: string;
84
+ provenance: "agent_failed" | "deadline";
85
+ /** Execution phase at failure time; never a submission rejection when post_start. */
86
+ phase: SdkPromptFailurePhase;
87
+ /** Bounded failure-origin category; `unknown` when attribution is uncertain. */
88
+ category: SdkPromptFailureCategory;
89
+ /** Bounded safe provider/transport classifier when the provider supplied one. */
90
+ providerCode?: string;
91
+ };
63
92
 
64
93
  /** Exactly one selector per lookup. */
65
94
  export type TurnPromptStatusSelector = { clientRef: string } | { commandId: string; turnId: string };
@@ -115,9 +115,17 @@ export class PromptActivity {
115
115
  this.#awaitingModel = true;
116
116
  return;
117
117
  }
118
- // Streamed content/thinking/tool-call deltas, and the tool call the model asked for,
119
- // are only ever produced by an answering model.
120
- if (eventType === "message_update" || eventType === "tool_execution_start") {
118
+ // `message_update` is a streaming chunk: the model is still producing this response,
119
+ // so the gap that follows it is another inference gap, not a dead producer. Clearing
120
+ // `#awaitingModel` here would drop the turn to the narrow idle bound mid-stream, and a
121
+ // slow next chunk would be killed at that bound (issue #5571). The stream only truly
122
+ // ends at `message_end` (role=assistant) below, so `message_update` is intentionally
123
+ // left as an active model signal and does not clear inference state.
124
+ //
125
+ // The tool call the model asked for, by contrast, marks the model's turn as answered:
126
+ // inference has produced its result, and any following gap is governed by the tool
127
+ // bound while that tool runs.
128
+ if (eventType === "tool_execution_start") {
121
129
  this.#awaitingModel = false;
122
130
  return;
123
131
  }