@gajae-code/coding-agent 0.18.6 → 0.18.8

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 (302) hide show
  1. package/CHANGELOG.md +318 -1
  2. package/dist/types/async/job-manager.d.ts +1 -1
  3. package/dist/types/capability/mcp.d.ts +2 -0
  4. package/dist/types/capability/skill.d.ts +2 -0
  5. package/dist/types/capability/types.d.ts +6 -0
  6. package/dist/types/cli/public-command-errors.d.ts +3 -1
  7. package/dist/types/cli/setup-cli.d.ts +1 -1
  8. package/dist/types/commands/deep-interview.d.ts +3 -0
  9. package/dist/types/config/autorouting-tier-map.d.ts +348 -9
  10. package/dist/types/config/file-lock.d.ts +1 -0
  11. package/dist/types/config/model-bindings-applier.d.ts +2 -0
  12. package/dist/types/config/model-profile-activation.d.ts +11 -3
  13. package/dist/types/config/model-registry.d.ts +4 -9
  14. package/dist/types/config/settings.d.ts +4 -0
  15. package/dist/types/config/skill-settings-defaults.d.ts +2 -0
  16. package/dist/types/cursor-delete-path.d.ts +1 -0
  17. package/dist/types/cursor-mcp-name.d.ts +8 -0
  18. package/dist/types/customization/inventory.d.ts +2 -0
  19. package/dist/types/debug/report-bundle-parent.d.ts +2 -0
  20. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.d.ts +1 -0
  21. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/version-manager.d.ts +31 -0
  22. package/dist/types/defaults/gjc-skills.generated.d.ts +5 -6
  23. package/dist/types/discovery/helpers.d.ts +8 -3
  24. package/dist/types/export/html/css-color.d.ts +2 -0
  25. package/dist/types/export/html/template.generated.d.ts +1 -1
  26. package/dist/types/extensibility/extensions/types.d.ts +7 -0
  27. package/dist/types/extensibility/gjc-plugins/runtime-adapters.d.ts +14 -1
  28. package/dist/types/extensibility/plugins/marketplace/cache.d.ts +2 -0
  29. package/dist/types/extensibility/plugins/plugin-link-path.d.ts +2 -0
  30. package/dist/types/extensibility/runtime-skill-discovery.d.ts +3 -1
  31. package/dist/types/extensibility/skill-management.d.ts +3 -1
  32. package/dist/types/extensibility/skills.d.ts +2 -0
  33. package/dist/types/gjc-runtime/deep-interview-crystallize.d.ts +74 -0
  34. package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +11 -0
  35. package/dist/types/gjc-runtime/gc-runtime.d.ts +5 -0
  36. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +0 -5
  37. package/dist/types/gjc-runtime/state-argv.d.ts +1 -1
  38. package/dist/types/gjc-runtime/state-migrations.d.ts +8 -0
  39. package/dist/types/gjc-runtime/state-runtime.d.ts +95 -0
  40. package/dist/types/gjc-runtime/state-writer.d.ts +11 -1
  41. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +8 -0
  42. package/dist/types/internal-urls/skill-path.d.ts +1 -0
  43. package/dist/types/lsp/workspace-path.d.ts +1 -0
  44. package/dist/types/modes/acp/acp-agent.d.ts +7 -0
  45. package/dist/types/modes/components/custom-editor.d.ts +6 -0
  46. package/dist/types/modes/components/customization/customization-dashboard.d.ts +1 -0
  47. package/dist/types/modes/components/extensions/state-manager.d.ts +4 -3
  48. package/dist/types/modes/controllers/input-controller.d.ts +9 -0
  49. package/dist/types/modes/interactive-mode.d.ts +1 -0
  50. package/dist/types/modes/utils/interrupt-ownership.d.ts +3 -0
  51. package/dist/types/modes/utils/keybinding-matchers.d.ts +1 -0
  52. package/dist/types/modes/utils/macos-option-forwarding.d.ts +17 -0
  53. package/dist/types/progress/collect-project-progress.d.ts +38 -0
  54. package/dist/types/progress/progress-contract.d.ts +154 -0
  55. package/dist/types/progress/progress-snapshot.d.ts +11 -0
  56. package/dist/types/progress/project-progress.d.ts +141 -0
  57. package/dist/types/progress/render-progress.d.ts +28 -0
  58. package/dist/types/runtime-mcp/elicitation.d.ts +12 -0
  59. package/dist/types/runtime-mcp/oauth-public-url.d.ts +1 -0
  60. package/dist/types/runtime-mcp/plugin-network-boundary.d.ts +1 -0
  61. package/dist/types/runtime-mcp/smithery-registry.d.ts +24 -0
  62. package/dist/types/runtime-mcp/types.d.ts +6 -0
  63. package/dist/types/sdk/acp/adapter.d.ts +1 -1
  64. package/dist/types/sdk/broker/broker-exit.d.ts +6 -2
  65. package/dist/types/sdk/broker/broker.d.ts +18 -1
  66. package/dist/types/sdk/broker/lifecycle.d.ts +9 -0
  67. package/dist/types/sdk/broker/session-index.d.ts +12 -6
  68. package/dist/types/sdk/broker/task-artifact-owner-validation.d.ts +55 -0
  69. package/dist/types/sdk/bus/index.d.ts +12 -1
  70. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +6 -1
  71. package/dist/types/sdk/bus/telegram-daemon.d.ts +3 -2
  72. package/dist/types/sdk/client/client.d.ts +2 -0
  73. package/dist/types/sdk/host/host.d.ts +6 -0
  74. package/dist/types/sdk/host/query/handlers.d.ts +3 -0
  75. package/dist/types/sdk/index.d.ts +1 -0
  76. package/dist/types/sdk/router/session-router.d.ts +9 -0
  77. package/dist/types/sdk/session-list.d.ts +1 -1
  78. package/dist/types/sdk/session.d.ts +8 -0
  79. package/dist/types/session/agent-session.d.ts +38 -9
  80. package/dist/types/session/artifacts.d.ts +2 -2
  81. package/dist/types/session/internal/managed-gc-retirement-codec.d.ts +32 -0
  82. package/dist/types/session/internal/managed-session-scope.d.ts +79 -2
  83. package/dist/types/session/internal/managed-session-storage.d.ts +36 -1
  84. package/dist/types/session/internal/managed-task-owner-cleanup.d.ts +62 -0
  85. package/dist/types/session/internal/task-artifact-owner-access.d.ts +19 -0
  86. package/dist/types/session/internal/task-artifact-owner-transcript.d.ts +11 -0
  87. package/dist/types/session/session-manager.d.ts +31 -5
  88. package/dist/types/session/session-retirement.d.ts +71 -0
  89. package/dist/types/session/session-storage.d.ts +62 -48
  90. package/dist/types/session/session-transcript-header.d.ts +2 -0
  91. package/dist/types/session/task-artifact-owner-codec.d.ts +109 -0
  92. package/dist/types/session/task-artifact-owner-retirement.d.ts +7 -0
  93. package/dist/types/session/task-artifact-owner.d.ts +10 -0
  94. package/dist/types/setup/credential-auto-import.d.ts +1 -1
  95. package/dist/types/setup/credential-import.d.ts +6 -4
  96. package/dist/types/skill-state/active-state.d.ts +38 -0
  97. package/dist/types/slash-commands/helpers/progress-report.d.ts +8 -0
  98. package/dist/types/task/executor.d.ts +3 -0
  99. package/dist/types/tools/bash-skill-urls.d.ts +1 -1
  100. package/dist/types/tools/index.d.ts +3 -0
  101. package/dist/types/tools/python.d.ts +9 -1
  102. package/dist/types/tools/recipe/recipe-guard.d.ts +7 -0
  103. package/dist/types/tools/restricted-cwd.d.ts +1 -0
  104. package/dist/types/tools/todo-write.d.ts +6 -0
  105. package/dist/types/utils/tools-checksum.d.ts +7 -0
  106. package/package.json +7 -7
  107. package/scripts/generate-sdk-operation-inventory.ts +21 -0
  108. package/scripts/resident-memory-bench.ts +174 -20
  109. package/scripts/verify-managed-empty-stop.ts +28 -0
  110. package/src/async/job-manager.ts +228 -122
  111. package/src/capability/index.ts +18 -0
  112. package/src/capability/mcp.ts +2 -0
  113. package/src/capability/skill.ts +2 -0
  114. package/src/capability/types.ts +6 -0
  115. package/src/cli/auth-broker-cli.ts +1 -1
  116. package/src/cli/config-cli.ts +8 -18
  117. package/src/cli/customize-doctor.ts +15 -2
  118. package/src/cli/public-command-errors.ts +21 -1
  119. package/src/cli/setup-cli.ts +4 -4
  120. package/src/cli/skills-cli.ts +5 -0
  121. package/src/cli-main.ts +7 -8
  122. package/src/commands/deep-interview.ts +47 -8
  123. package/src/commands/sdk.ts +140 -29
  124. package/src/config/autorouting-tier-map.ts +117 -7
  125. package/src/config/file-lock.ts +307 -68
  126. package/src/config/mcp-schema.json +4 -0
  127. package/src/config/model-bindings-applier.ts +20 -0
  128. package/src/config/model-profile-activation.ts +240 -46
  129. package/src/config/model-registry.ts +47 -153
  130. package/src/config/settings.ts +44 -4
  131. package/src/config/skill-settings-defaults.ts +7 -0
  132. package/src/coordinator-mcp/policy.ts +3 -2
  133. package/src/cursor-delete-path.ts +10 -0
  134. package/src/cursor-mcp-name.ts +15 -0
  135. package/src/cursor.ts +13 -2
  136. package/src/customization/inventory.ts +3 -0
  137. package/src/debug/report-bundle-parent.ts +22 -0
  138. package/src/debug/report-bundle.ts +3 -0
  139. package/src/debug/system-info.ts +13 -1
  140. package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.ts +67 -4
  141. package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/version-manager.ts +80 -0
  142. package/src/defaults/gjc/skills/deep-interview/SKILL.md +105 -10
  143. package/src/defaults/gjc/skills/ralplan/SKILL.md +6 -4
  144. package/src/defaults/gjc-defaults.test.ts +18 -1
  145. package/src/defaults/gjc-defaults.ts +3 -0
  146. package/src/defaults/gjc-skills.generated.ts +58 -36
  147. package/src/discovery/agents.ts +6 -1
  148. package/src/discovery/builtin.ts +10 -0
  149. package/src/discovery/helpers.ts +168 -23
  150. package/src/discovery/mcp-json.ts +6 -0
  151. package/src/discovery/ssh.ts +9 -4
  152. package/src/discovery/substitute-plugin-root.ts +3 -1
  153. package/src/edit/file-read-cache.ts +1 -1
  154. package/src/eval/py/executor.ts +380 -120
  155. package/src/export/html/css-color.ts +9 -0
  156. package/src/export/html/index.ts +5 -4
  157. package/src/export/html/template.generated.ts +1 -1
  158. package/src/extensibility/extensions/runner.ts +4 -0
  159. package/src/extensibility/extensions/types.ts +7 -0
  160. package/src/extensibility/gjc-plugins/runtime-adapters.ts +73 -8
  161. package/src/extensibility/plugins/manager.ts +3 -2
  162. package/src/extensibility/plugins/marketplace/cache.ts +15 -0
  163. package/src/extensibility/plugins/marketplace/manager.ts +11 -3
  164. package/src/extensibility/plugins/plugin-link-path.ts +12 -0
  165. package/src/extensibility/runtime-skill-discovery.ts +20 -2
  166. package/src/extensibility/skill-management.ts +6 -2
  167. package/src/extensibility/skills.ts +6 -1
  168. package/src/gjc-runtime/deep-interview-crystallize.ts +1814 -0
  169. package/src/gjc-runtime/deep-interview-recorder.ts +20 -2
  170. package/src/gjc-runtime/deep-interview-runtime.ts +1415 -13
  171. package/src/gjc-runtime/deep-interview-stage.ts +98 -21
  172. package/src/gjc-runtime/gc-runtime.ts +206 -8
  173. package/src/gjc-runtime/launch-worktree.ts +14 -4
  174. package/src/gjc-runtime/ralplan-runtime.ts +447 -52
  175. package/src/gjc-runtime/state-argv.ts +2 -0
  176. package/src/gjc-runtime/state-migrations.ts +91 -29
  177. package/src/gjc-runtime/state-runtime.ts +3716 -135
  178. package/src/gjc-runtime/state-writer.ts +83 -8
  179. package/src/gjc-runtime/ultragoal-runtime.ts +16 -4
  180. package/src/gjc-runtime/workflow-manifest.generated.json +70 -42
  181. package/src/gjc-runtime/workflow-manifest.ts +54 -21
  182. package/src/harness-control-plane/operate.ts +12 -4
  183. package/src/harness-control-plane/owner.ts +5 -1
  184. package/src/hashline/parser.ts +48 -9
  185. package/src/hashline/recovery.ts +13 -4
  186. package/src/hindsight/config.ts +9 -3
  187. package/src/internal-urls/docs-index.generated.ts +19 -18
  188. package/src/internal-urls/skill-path.ts +19 -0
  189. package/src/internal-urls/skill-protocol.ts +2 -0
  190. package/src/lsp/edits.ts +6 -0
  191. package/src/lsp/workspace-path.ts +51 -0
  192. package/src/main.ts +195 -31
  193. package/src/modes/acp/acp-agent.ts +163 -7
  194. package/src/modes/components/custom-editor.ts +14 -0
  195. package/src/modes/components/customization/customization-dashboard.ts +12 -0
  196. package/src/modes/components/extensions/extension-dashboard.ts +2 -2
  197. package/src/modes/components/extensions/state-manager.ts +15 -5
  198. package/src/modes/components/model-selector.ts +1 -4
  199. package/src/modes/components/provider-onboarding-selector.ts +1 -1
  200. package/src/modes/components/status-line/segments.ts +4 -0
  201. package/src/modes/controllers/event-controller.ts +11 -4
  202. package/src/modes/controllers/input-controller.ts +74 -29
  203. package/src/modes/controllers/runtime-mcp-command-controller.ts +8 -3
  204. package/src/modes/controllers/selector-controller.ts +28 -15
  205. package/src/modes/interactive-mode.ts +4 -0
  206. package/src/modes/runtime-init.ts +4 -0
  207. package/src/modes/utils/abort-message.ts +3 -1
  208. package/src/modes/utils/interrupt-ownership.ts +12 -0
  209. package/src/modes/utils/keybinding-matchers.ts +4 -0
  210. package/src/modes/utils/macos-option-forwarding.ts +80 -0
  211. package/src/progress/collect-project-progress.ts +153 -0
  212. package/src/progress/progress-contract.ts +172 -0
  213. package/src/progress/progress-snapshot.ts +140 -0
  214. package/src/progress/project-progress.ts +446 -0
  215. package/src/progress/render-progress.ts +209 -0
  216. package/src/prompts/system/master-mode.md +1 -0
  217. package/src/prompts/tools/skill-discovery.md +1 -1
  218. package/src/runtime-mcp/client.ts +13 -4
  219. package/src/runtime-mcp/config-writer.ts +10 -6
  220. package/src/runtime-mcp/config.ts +1 -0
  221. package/src/runtime-mcp/elicitation.ts +142 -0
  222. package/src/runtime-mcp/oauth-flow.ts +5 -0
  223. package/src/runtime-mcp/oauth-public-url.ts +8 -0
  224. package/src/runtime-mcp/plugin-network-boundary.ts +4 -0
  225. package/src/runtime-mcp/smithery-registry.ts +22 -3
  226. package/src/runtime-mcp/transports/http.ts +2 -2
  227. package/src/runtime-mcp/types.ts +6 -0
  228. package/src/sdk/acp/adapter.ts +93 -8
  229. package/src/sdk/broker/broker-exit.ts +52 -23
  230. package/src/sdk/broker/broker.ts +83 -7
  231. package/src/sdk/broker/ensure.ts +2 -0
  232. package/src/sdk/broker/lifecycle.ts +820 -73
  233. package/src/sdk/broker/session-index.ts +191 -81
  234. package/src/sdk/broker/task-artifact-owner-validation.ts +259 -0
  235. package/src/sdk/bus/chat-daemon-cli.ts +38 -1
  236. package/src/sdk/bus/chat-daemon-control.ts +2 -2
  237. package/src/sdk/bus/chat-daemon-runtime.ts +3 -0
  238. package/src/sdk/bus/index.ts +241 -46
  239. package/src/sdk/bus/telegram-daemon-contract.ts +6 -1
  240. package/src/sdk/bus/telegram-daemon.ts +4 -2
  241. package/src/sdk/cli/session-cli.ts +17 -3
  242. package/src/sdk/client/client.ts +28 -8
  243. package/src/sdk/host/host.ts +6 -0
  244. package/src/sdk/host/query/handlers.ts +9 -1
  245. package/src/sdk/host/session-runtime.test.ts +2805 -411
  246. package/src/sdk/host/session-runtime.ts +428 -111
  247. package/src/sdk/host/surface-policy.ts +1 -0
  248. package/src/sdk/index.ts +8 -0
  249. package/src/sdk/lifecycle-session.ts +3 -1
  250. package/src/sdk/mcp/server.ts +15 -3
  251. package/src/sdk/prompt-deadline-manager.ts +15 -0
  252. package/src/sdk/protocol/operation-inventory.generated.json +146 -0
  253. package/src/sdk/protocol/operation-registry.ts +9 -2
  254. package/src/sdk/router/session-router.ts +94 -14
  255. package/src/sdk/session-list.ts +1 -0
  256. package/src/sdk/session.ts +81 -38
  257. package/src/session/agent-session.ts +674 -224
  258. package/src/session/artifacts.ts +174 -70
  259. package/src/session/internal/managed-gc-retirement-codec.ts +319 -0
  260. package/src/session/internal/managed-session-scope.ts +3457 -462
  261. package/src/session/internal/managed-session-storage.ts +482 -61
  262. package/src/session/internal/managed-task-owner-cleanup.ts +473 -0
  263. package/src/session/internal/task-artifact-owner-access.ts +245 -0
  264. package/src/session/internal/task-artifact-owner-transcript.ts +614 -0
  265. package/src/session/session-manager.ts +939 -133
  266. package/src/session/session-retirement.ts +714 -0
  267. package/src/session/session-storage.ts +418 -174
  268. package/src/session/session-transcript-header.ts +17 -0
  269. package/src/session/task-artifact-owner-codec.ts +641 -0
  270. package/src/session/task-artifact-owner-retirement.ts +477 -0
  271. package/src/session/task-artifact-owner.ts +289 -0
  272. package/src/setup/credential-auto-import.ts +12 -3
  273. package/src/setup/credential-import.ts +176 -5
  274. package/src/setup/paseo/announce.ts +6 -1
  275. package/src/skill-state/active-state.ts +132 -2
  276. package/src/slash-commands/builtin-registry.ts +39 -9
  277. package/src/slash-commands/helpers/progress-report.ts +23 -0
  278. package/src/stt/setup.ts +3 -5
  279. package/src/task/commands.ts +3 -1
  280. package/src/task/executor.ts +4 -0
  281. package/src/task/index.ts +6 -0
  282. package/src/tools/ask.ts +205 -6
  283. package/src/tools/ast-edit.ts +4 -0
  284. package/src/tools/bash-allowed-prefixes.ts +15 -0
  285. package/src/tools/bash-skill-urls.ts +56 -3
  286. package/src/tools/bash.ts +48 -79
  287. package/src/tools/descriptors.ts +5 -1
  288. package/src/tools/eval.ts +56 -36
  289. package/src/tools/image-gen.ts +1 -1
  290. package/src/tools/index.ts +3 -0
  291. package/src/tools/python.ts +187 -66
  292. package/src/tools/recipe/index.ts +9 -0
  293. package/src/tools/recipe/recipe-guard.ts +19 -0
  294. package/src/tools/restricted-cwd.ts +10 -0
  295. package/src/tools/skill-discovery.ts +5 -0
  296. package/src/tools/skill.ts +28 -1
  297. package/src/tools/todo-write.ts +10 -1
  298. package/src/tools/tool-catalog.generated.ts +1 -1
  299. package/src/tools/write.ts +1 -0
  300. package/src/utils/tools-checksum.ts +34 -0
  301. package/src/utils/tools-manager.ts +24 -6
  302. package/src/web/search/providers/codex.ts +3 -2
@@ -146,6 +146,29 @@ async function loadConfig(agentDir: string, kind: ChatDaemonKind): Promise<ChatD
146
146
  };
147
147
  }
148
148
 
149
+ /**
150
+ * Consecutive definite-disabled config reads required before a running owner
151
+ * retires. A non-atomic rewrite can briefly expose a truncated file that parses
152
+ * as valid YAML with `notifications.enabled` defaulted off, so one read is not
153
+ * a settled verdict. With the 5 s heartbeat tick this retires within ~10 s.
154
+ */
155
+ const DISABLED_READS_BEFORE_RETIRE = 2;
156
+
157
+ /**
158
+ * True only when the current config definitely disables this provider:
159
+ * `notifications.enabled: false`, the provider switched off, or a missing
160
+ * config file (the same results that keep a new owner from starting). Read or
161
+ * validation errors (half-written file, incomplete or quarantined provider) are
162
+ * indeterminate and keep the owner serving.
163
+ */
164
+ async function configDisablesProvider(agentDir: string, kind: ChatDaemonKind): Promise<boolean> {
165
+ try {
166
+ return (await loadConfig(agentDir, kind)) === undefined;
167
+ } catch {
168
+ return false;
169
+ }
170
+ }
171
+
149
172
  function ownerPid(ownerId: string): number | undefined {
150
173
  const match = /^(\d+)(?:-|$)/.exec(ownerId);
151
174
  const pid = Number(match?.[1]);
@@ -231,10 +254,24 @@ export async function runChatDaemonInternal(
231
254
  return;
232
255
  }
233
256
  await runtime.start();
257
+ let disabledReads = 0;
258
+ const retireIfDisabled = async (): Promise<void> => {
259
+ if (!(await configDisablesProvider(agentDir, kind))) {
260
+ disabledReads = 0;
261
+ return;
262
+ }
263
+ if (++disabledReads < DISABLED_READS_BEFORE_RETIRE || stopping) return;
264
+ // Same orderly path as an operator stop: stop the transport, let the
265
+ // serve loop exit, and release ownership as stopped in `finally`.
266
+ stopping = true;
267
+ await stopRuntime();
268
+ };
269
+ // The returned promise never rejects; tests await it to drive one tick.
234
270
  interval = (deps.setInterval ?? setInterval)(() => {
235
- void (async () => {
271
+ return (async () => {
236
272
  try {
237
273
  if (!(await renewHeartbeat())) await terminateForLostOwnership();
274
+ else await retireIfDisabled();
238
275
  } catch (error) {
239
276
  terminalError ??= error;
240
277
  stopping = true;
@@ -141,8 +141,8 @@ export type ChatDaemonAction = "stop" | "reload";
141
141
  * crashes, recovery, and attachment retirement cannot replay ambiguous work.
142
142
  */
143
143
  export const CHAT_DAEMON_GENERATIONS: Readonly<Record<ChatDaemonKind, number>> = {
144
- discord: 82,
145
- slack: 89,
144
+ discord: 84,
145
+ slack: 91,
146
146
  };
147
147
 
148
148
  export function chatDaemonGeneration(kind: ChatDaemonKind): number {
@@ -318,6 +318,9 @@ export class ChatDaemonRuntime {
318
318
  this.#router = new SessionRouter({
319
319
  agentDir: input.agentDir,
320
320
  observer: true,
321
+ // A lean final answer carries no messageRef, so the raw native copy of a
322
+ // positioned frame has no identity to collapse onto and would post twice.
323
+ positionedNotificationEffects: true,
321
324
  correlateFrame,
322
325
  deps: {
323
326
  ...deps.routerDeps,
@@ -27,7 +27,13 @@ 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 { isNonDispatchedToolEvent, type RunSettlementProof, ThinkingLevel } from "@gajae-code/agent-core";
30
+ import {
31
+ type AgentTerminalOwnerContext,
32
+ isNonDispatchedToolEvent,
33
+ type RunCancellationDomain,
34
+ type RunSettlementProof,
35
+ ThinkingLevel,
36
+ } from "@gajae-code/agent-core";
31
37
  import type { ImageContent, TextContent, Tool } from "@gajae-code/ai/core";
32
38
  import type { NotificationServer as NativeNotificationServer } from "@gajae-code/natives";
33
39
 
@@ -105,6 +111,7 @@ import {
105
111
  canDeliverSdkEvent,
106
112
  createSdkSurfaceFactory,
107
113
  masterAttestationForEffectiveHost,
114
+ POSITIONED_NOTIFICATION_EFFECTS_CAPABILITY,
108
115
  reattestMasterSessionIdentity,
109
116
  SESSION_HOST_OBSERVER_CAPABILITY,
110
117
  type SessionSdkHost,
@@ -204,7 +211,6 @@ import {
204
211
  ASK_SELECTED_ACK_CAPABILITY,
205
212
  type EnsureDaemonResult,
206
213
  ensureTelegramDaemonRunningDetailed,
207
- POSITIONED_NOTIFICATION_EFFECTS_CAPABILITY,
208
214
  } from "./telegram-daemon";
209
215
 
210
216
  export type {
@@ -1229,6 +1235,9 @@ export class PresentationArbiter {
1229
1235
  interface SessionRuntime {
1230
1236
  server: NotificationServer;
1231
1237
  host: SessionSdkHost;
1238
+ getJoinedPromptCorrelations: (owner: AgentTerminalOwnerContext) => Array<{ commandId: string; turnId: string }>;
1239
+ isPromptRunOwner: (correlation: { commandId: string; turnId: string }, owner: AgentTerminalOwnerContext) => boolean;
1240
+ retireJoinedPromptOwners: () => void;
1232
1241
  /** Delivers one ring-positioned event envelope to every attached subscriber
1233
1242
  * connection, applying the same capability gate as event replay. */
1234
1243
  broadcastEventFrame: (event: SdkFrame) => string[];
@@ -2618,6 +2627,12 @@ function sdkControlSurface(
2618
2627
  error: unknown,
2619
2628
  ) => void | Promise<void> = () => {},
2620
2629
  onPromptAcceptFailed: (correlation: { commandId: string; turnId: string }) => void = () => {},
2630
+ onPromptDiverted: (correlation: { commandId: string; turnId: string }) => void,
2631
+ onPromptPromoted: (
2632
+ correlation: { commandId: string; turnId: string },
2633
+ promotion: { startsOwnRun?: boolean; removed?: boolean },
2634
+ handle: string | undefined,
2635
+ ) => void,
2621
2636
  acceptGateResolution: () => boolean,
2622
2637
  trackGateResolution: <T>(resolution: Promise<T>) => Promise<T>,
2623
2638
  admitPrompt: (clientRef?: string) => void,
@@ -3019,6 +3034,7 @@ function sdkControlSurface(
3019
3034
  admission.accepted();
3020
3035
  accepting = false;
3021
3036
  accepted = true;
3037
+ if (effectiveDeliverAs !== undefined) onPromptDiverted(correlation);
3022
3038
  // #4743: an accepted run owns a durable terminal publication when it
3023
3039
  // settles; teardown joins it via this latch (never-accepted preflights
3024
3040
  // produce no durable write and stay unjoined by design).
@@ -3044,7 +3060,14 @@ function sdkControlSurface(
3044
3060
  ...(effectiveDeliverAs ? { deliverAs: effectiveDeliverAs } : {}),
3045
3061
  onPreflightAcceptCommit,
3046
3062
  onPreflightAccepted,
3047
- ...admission.hooks,
3063
+ onQueuedPromoted: promotion => {
3064
+ admission.hooks.onQueuedPromoted(promotion);
3065
+ onPromptPromoted(correlation, promotion, ctx.getActivePromptHandle());
3066
+ },
3067
+ onDispatchDisposition: disposition => {
3068
+ admission.hooks.onDispatchDisposition(disposition);
3069
+ if (!disposition.startsOwnRun) onPromptDiverted(correlation);
3070
+ },
3048
3071
  preflightSignal: preflightController.signal,
3049
3072
  ...(sdkRunToken ? { sdkRunToken } : {}),
3050
3073
  }),
@@ -3722,8 +3745,8 @@ function sdkControlSurface(
3722
3745
  return unavailable("model.cycle", "no session model-cycle seam is installed")();
3723
3746
  return { changed: (await ctx.cycleModel()) !== undefined };
3724
3747
  },
3725
- setThinking: level => {
3726
- api.setThinkingLevel(level as ThinkingLevel);
3748
+ setThinking: async level => {
3749
+ await api.setThinkingLevelForControl(level as ThinkingLevel, false);
3727
3750
  return { changed: true };
3728
3751
  },
3729
3752
  cycleThinking: () => {
@@ -4290,6 +4313,8 @@ export function createNotificationsExtension(
4290
4313
  * preflight outside the durable admission path (review thread P2).
4291
4314
  */
4292
4315
  terminalAbortSeams?: {
4316
+ getTerminalRunOwnerForEvent?: (event: object) => AgentTerminalOwnerContext | undefined;
4317
+ getRunOwnerDomain?: (handle: string) => RunCancellationDomain | undefined;
4293
4318
  getTerminalTurnEpoch: () => number | undefined;
4294
4319
  cancelPendingPreflightForTerminalAbort: () => void;
4295
4320
  captureTerminalAbortSteeringSnapshot?: () => void;
@@ -4577,6 +4602,7 @@ export function createNotificationsExtension(
4577
4602
  if (reason === "session" && requestedRuntime) {
4578
4603
  requestedRuntime.inboundFenced = true;
4579
4604
  requestedRuntime.stopping = true;
4605
+ requestedRuntime.retireJoinedPromptOwners();
4580
4606
  requestedRuntime.abortEphemeralTurns();
4581
4607
  }
4582
4608
  if (reason === "session" && requestedRuntime) requestedRuntime.stopSessionNameObserver();
@@ -4845,6 +4871,19 @@ export function createNotificationsExtension(
4845
4871
  return failLifecycleStartup("failed", "Lifecycle SDK startup requires an agent directory.");
4846
4872
 
4847
4873
  const pendingInteractive = new Map<string, PendingInteractiveAsk>();
4874
+ const queuedRemovalTerminals = new Map<string, Promise<boolean>>();
4875
+ const joinedPromptOwners = new Map<
4876
+ string,
4877
+ { correlation: { commandId: string; turnId: string }; owner: AgentTerminalOwnerContext }
4878
+ >();
4879
+ const getJoinedPromptCorrelations = (terminalOwner: AgentTerminalOwnerContext) =>
4880
+ [...joinedPromptOwners.values()]
4881
+ .filter(
4882
+ ({ owner }) =>
4883
+ owner.resourceRunId === terminalOwner.resourceRunId && owner.domain === terminalOwner.domain,
4884
+ )
4885
+ .map(({ correlation }) => correlation);
4886
+ const retireJoinedPromptOwners = (): void => joinedPromptOwners.clear();
4848
4887
  const pendingPromptCorrelations: Array<{ commandId: string; turnId: string }> = [];
4849
4888
  const pendingPromptCorrelationsBySdkRunToken = new Map<string, { commandId: string; turnId: string }>();
4850
4889
  const workLease = ctx.getSessionWorkLease?.() ?? createSessionWorkLease();
@@ -5106,6 +5145,8 @@ export function createNotificationsExtension(
5106
5145
  * up to a hard maximum runtime anchored to acceptance.
5107
5146
  */
5108
5147
  deadlineLease: PromptDeadlineLease;
5148
+ deadlineSuspended?: true;
5149
+ sdkRunToken?: string;
5109
5150
  deadlineTimer?: Parameters<typeof clearTimeout>[0];
5110
5151
  /**
5111
5152
  * Identity of the in-flight deadline expiry attempt (set synchronously
@@ -5314,10 +5355,12 @@ export function createNotificationsExtension(
5314
5355
  submission.workLease = undefined;
5315
5356
  }
5316
5357
  promptSubmissions.delete(key);
5358
+ const [commandId, turnId] = key.split(":", 2);
5317
5359
  // A fatal closure is transport-level, not a committed semantic terminal: the
5318
- // durable record stays authoritative, so it must never leave a tombstone.
5360
+ // durable record stays authoritative, so it must never leave a tombstone
5361
+ // or release images still retained by an unsettled run. Runtime teardown
5362
+ // releases those reservations even if this delivery record expires.
5319
5363
  if (submission.fatal) return;
5320
- const [commandId, turnId] = key.split(":", 2);
5321
5364
  if (!commandId || !turnId) return;
5322
5365
  removePendingPromptCorrelation({ commandId, turnId });
5323
5366
  addTerminalTombstone(key, submission.connectionId);
@@ -5664,7 +5707,7 @@ export function createNotificationsExtension(
5664
5707
  */
5665
5708
  const armPromptDeadline = (key: string, correlation: { commandId: string; turnId: string }) => {
5666
5709
  const submission = promptSubmissions.get(key);
5667
- if (!submission) return;
5710
+ if (!submission || submission.deadlineSuspended) return;
5668
5711
  const lease = submission.deadlineLease;
5669
5712
  if (submission.deadlineTimer) clearTimeout(submission.deadlineTimer);
5670
5713
  submission.deadlineTimer = setTimeout(
@@ -5772,25 +5815,37 @@ export function createNotificationsExtension(
5772
5815
  );
5773
5816
  };
5774
5817
  const emitPromptEvent = (event: AgentSessionEvent) => {
5775
- if (!runtime?.activePromptCorrelation) return;
5818
+ if (!runtime) return;
5776
5819
  cleanupPromptRecords();
5777
- const correlation = runtime.activePromptCorrelation;
5778
- // Renewal is attributed exactly like the correlated delivery below.
5779
- renewPromptDeadline(correlation, event);
5780
- noteToolDispatchBoundary(correlation, event);
5781
- const submission = promptSubmissions.get(promptSubmissionKey(correlation));
5782
- if (!submission || submission.abandoned) return;
5783
- const frame = {
5784
- type: "event",
5785
- kind: event.type,
5786
- payload: toAgentWireEventPayload(event),
5787
- ...correlation,
5788
- };
5789
- if (!submission.acknowledged) {
5790
- submission.bufferedFrames.push(frame);
5791
- return;
5820
+ const handle = ctx.getActivePromptHandle();
5821
+ const domain = handle ? terminalAbortSeams?.getRunOwnerDomain?.(handle) : undefined;
5822
+ const correlations = handle && domain ? getJoinedPromptCorrelations({ resourceRunId: handle, domain }) : [];
5823
+ const root = runtime.activePromptCorrelation;
5824
+ if (
5825
+ root &&
5826
+ !correlations.some(
5827
+ correlation => correlation.commandId === root.commandId && correlation.turnId === root.turnId,
5828
+ )
5829
+ )
5830
+ correlations.unshift(root);
5831
+ for (const correlation of correlations) {
5832
+ // Renewal is attributed exactly like the correlated delivery below.
5833
+ renewPromptDeadline(correlation, event);
5834
+ noteToolDispatchBoundary(correlation, event);
5835
+ const submission = promptSubmissions.get(promptSubmissionKey(correlation));
5836
+ if (!submission || submission.abandoned) continue;
5837
+ const frame = {
5838
+ type: "event",
5839
+ kind: event.type,
5840
+ payload: toAgentWireEventPayload(event),
5841
+ ...correlation,
5842
+ };
5843
+ if (!submission.acknowledged) {
5844
+ submission.bufferedFrames.push(frame);
5845
+ continue;
5846
+ }
5847
+ deliverCorrelatedFrame(submission, frame);
5792
5848
  }
5793
- deliverCorrelatedFrame(submission, frame);
5794
5849
  };
5795
5850
  /**
5796
5851
  * Status of a registered deadline expiry attempt after an awaited
@@ -5809,7 +5864,10 @@ export function createNotificationsExtension(
5809
5864
  submission: PromptSubmission,
5810
5865
  attempt: { lease: PromptDeadlineLease; generation: number; selfAbortStarted?: true },
5811
5866
  ): DeadlineAttemptStatus => {
5812
- if (promptSubmissions.get(key) !== submission || submission.deadlineLease !== attempt.lease) return "stale";
5867
+ if (promptSubmissions.get(key) !== submission) return "stale";
5868
+ if (!attempt.selfAbortStarted && (submission.deadlineSuspended || submission.deadlineLease !== attempt.lease))
5869
+ return "superseded";
5870
+ if (submission.deadlineLease !== attempt.lease) return "stale";
5813
5871
  const lease = submission.deadlineLease;
5814
5872
  // A deadline expiry attempt must not be superseded by progress that its
5815
5873
  // OWN abort caused. Terminal fencing aborts the running tool, and the
@@ -6018,6 +6076,7 @@ export function createNotificationsExtension(
6018
6076
  maxMs: resolveSdkPromptMaxRuntimeMs(settings?.get("sdk.promptMaxRuntimeMs")),
6019
6077
  }),
6020
6078
  phase: "active",
6079
+ ...(sdkRunToken ? { sdkRunToken } : {}),
6021
6080
  ...(workLeaseHandle ? { workLease: workLeaseHandle } : {}),
6022
6081
  // Bound to the Agent run at `agent_start`; acceptance precedes execution.
6023
6082
  executionHandle: undefined,
@@ -6123,12 +6182,75 @@ export function createNotificationsExtension(
6123
6182
  correlation: { commandId: string; turnId: string },
6124
6183
  handle: string | undefined,
6125
6184
  ) => {
6126
- const submission = promptSubmissions.get(promptSubmissionKey(correlation));
6185
+ const key = promptSubmissionKey(correlation);
6186
+ const submission = promptSubmissions.get(key);
6127
6187
  if (submission) {
6188
+ if (submission.deadlineSuspended) {
6189
+ submission.deadlineSuspended = undefined;
6190
+ submission.deadlineLease = createPromptDeadlineLease({
6191
+ now: Date.now(),
6192
+ leaseMs: submission.deadlineLease.leaseMs,
6193
+ maxMs: submission.deadlineLease.maxMs,
6194
+ });
6195
+ armPromptDeadline(key, correlation);
6196
+ }
6128
6197
  submission.executionHandle = handle;
6129
6198
  submission.preflightAbort = undefined;
6130
6199
  }
6131
6200
  };
6201
+ const onPromptDiverted = (correlation: { commandId: string; turnId: string }) => {
6202
+ const submission = promptSubmissions.get(promptSubmissionKey(correlation));
6203
+ // A follow-up's private token can only be adopted by its exact future run.
6204
+ // Implicit steering has no such future-run authority and must leave pending.
6205
+ if (!submission?.sdkRunToken) removePendingPromptCorrelation(correlation);
6206
+ if (!submission || submission.terminal) return;
6207
+ submission.deadlineSuspended = true;
6208
+ if (submission.deadlineTimer) clearTimeout(submission.deadlineTimer);
6209
+ submission.deadlineTimer = undefined;
6210
+ };
6211
+ const onPromptPromoted = (
6212
+ correlation: { commandId: string; turnId: string },
6213
+ promotion: { startsOwnRun?: boolean; removed?: boolean },
6214
+ handle: string | undefined,
6215
+ ) => {
6216
+ const key = promptSubmissionKey(correlation);
6217
+ removePendingPromptCorrelation(correlation);
6218
+ if (promotion.removed) {
6219
+ const completion: { terminalized?: boolean } = {};
6220
+ const removal = terminalizePrompt(
6221
+ correlation,
6222
+ { kind: "stopped", reason: "cancelled", provenance: "client_cancel" },
6223
+ {},
6224
+ undefined,
6225
+ completion,
6226
+ ).then(() => completion.terminalized === true);
6227
+ queuedRemovalTerminals.set(key, removal);
6228
+ trackReconciliationProducer(
6229
+ removal.finally(() => {
6230
+ if (queuedRemovalTerminals.get(key) === removal) queuedRemovalTerminals.delete(key);
6231
+ }),
6232
+ );
6233
+ return;
6234
+ }
6235
+ const submission = promptSubmissions.get(key);
6236
+ if (!submission || submission.terminal) return;
6237
+ submission.deadlineSuspended = undefined;
6238
+ submission.deadlineLease = createPromptDeadlineLease({
6239
+ now: Date.now(),
6240
+ leaseMs: submission.deadlineLease.leaseMs,
6241
+ maxMs: submission.deadlineLease.maxMs,
6242
+ });
6243
+ armPromptDeadline(key, correlation);
6244
+ if (promotion.startsOwnRun === true) {
6245
+ if (submission.sdkRunToken) pendingPromptCorrelationsBySdkRunToken.set(submission.sdkRunToken, correlation);
6246
+ else pendingPromptCorrelations.push(correlation);
6247
+ } else {
6248
+ bindPromptExecutionHandle(correlation, handle);
6249
+ const domain = handle ? terminalAbortSeams?.getRunOwnerDomain?.(handle) : undefined;
6250
+ if (handle && domain)
6251
+ joinedPromptOwners.set(key, { correlation, owner: { resourceRunId: handle, domain } });
6252
+ }
6253
+ };
6132
6254
  const terminalizePrompt = async (
6133
6255
  correlation: { commandId: string; turnId: string },
6134
6256
  requestedOutcome: SdkPromptTerminalOutcome,
@@ -6156,6 +6278,7 @@ export function createNotificationsExtension(
6156
6278
  // the seam must refuse to fence the successor's lineage (review
6157
6279
  // thread P1).
6158
6280
  const terminalExpectedEpoch = options.terminal ? terminalAbortSeams?.getTerminalTurnEpoch?.() : undefined;
6281
+ const deadlineAttempt = submission.deadlineAttempt;
6159
6282
  let winner: SdkPromptTerminalOutcome;
6160
6283
  try {
6161
6284
  winner = await kindReconciliation.claimPendingOutcome(
@@ -6179,7 +6302,6 @@ export function createNotificationsExtension(
6179
6302
  // attempt must then back off — reset to active and reschedule —
6180
6303
  // instead of fencing and publishing a deadline terminal for a live
6181
6304
  // prompt. A re-accepted or evicted submission is never touched.
6182
- const deadlineAttempt = submission.deadlineAttempt;
6183
6305
  if (deadlineAttempt) {
6184
6306
  const key = promptSubmissionKey(correlation);
6185
6307
  const status = deadlineAttemptStatus(key, submission, deadlineAttempt);
@@ -6366,6 +6488,7 @@ export function createNotificationsExtension(
6366
6488
  }
6367
6489
  if (submission.deadlineTimer) clearTimeout(submission.deadlineTimer);
6368
6490
  if (!recordPromptTerminal(correlation)) return;
6491
+ joinedPromptOwners.delete(promptSubmissionKey(correlation));
6369
6492
  if (!runtime) {
6370
6493
  // The runtime (and with it the positioned ring) is gone, so the
6371
6494
  // terminal can never be published from this process; expire the
@@ -6547,6 +6670,8 @@ export function createNotificationsExtension(
6547
6670
  recordPromptAccepted,
6548
6671
  recordPromptFailure,
6549
6672
  discardPromptAcceptance,
6673
+ onPromptDiverted,
6674
+ onPromptPromoted,
6550
6675
  () => runtime?.stopping !== true,
6551
6676
  trackGateResolution,
6552
6677
  admitPromptSubmission,
@@ -6599,9 +6724,15 @@ export function createNotificationsExtension(
6599
6724
  return { aborted: true, disposition: "cancelled" as const };
6600
6725
  }
6601
6726
  }
6602
- await terminalizePrompt({ commandId, turnId }, cancellationOutcome, {
6603
- fence: hasExecutionHandle || !submission.preflightAbort,
6604
- });
6727
+ const removal = queuedRemovalTerminals.get(key);
6728
+ if (removal) {
6729
+ if (!(await removal))
6730
+ throw new TypedControlError("busy", "Queued prompt cancellation could not be durably confirmed.");
6731
+ } else {
6732
+ await terminalizePrompt({ commandId, turnId }, cancellationOutcome, {
6733
+ fence: hasExecutionHandle || !submission.preflightAbort,
6734
+ });
6735
+ }
6605
6736
  }
6606
6737
  return { aborted: true, disposition: "cancelled" as const };
6607
6738
  },
@@ -7107,6 +7238,20 @@ export function createNotificationsExtension(
7107
7238
  );
7108
7239
  return already_terminalResult;
7109
7240
  }
7241
+ if (currentSubmission.preflightAbort) {
7242
+ try {
7243
+ await currentSubmission.preflightAbort();
7244
+ } catch (error) {
7245
+ logger.warn(`sdk: accepted queued cancellation failed: ${String(error)}`);
7246
+ failPromptClosed(
7247
+ { commandId, turnId },
7248
+ currentSubmission,
7249
+ "terminal_uncertain",
7250
+ "Accepted queued prompt could not be settled before cancellation.",
7251
+ );
7252
+ return { ok: false as const, reason: "worker_unsettled" as const };
7253
+ }
7254
+ }
7110
7255
  if (preflightCancel?.hasPending()) {
7111
7256
  preflightCancel.cancel();
7112
7257
  // The seam cancels the SESSION-WIDE preflight controller:
@@ -7115,11 +7260,30 @@ export function createNotificationsExtension(
7115
7260
  if (noOtherConnectionPreflights?.() !== false)
7116
7261
  terminalAbortSeams?.cancelPendingPreflightForTerminalAbort?.();
7117
7262
  }
7118
- await terminalizePrompt(
7119
- { commandId, turnId },
7120
- { kind: "stopped", reason: "cancelled", provenance: "client_cancel" },
7121
- {},
7122
- );
7263
+ const removal = queuedRemovalTerminals.get(promptSubmissionKey({ commandId, turnId }));
7264
+ const completion: { terminalized?: boolean } = {};
7265
+ if (removal) completion.terminalized = await removal;
7266
+ else
7267
+ await terminalizePrompt(
7268
+ { commandId, turnId },
7269
+ { kind: "stopped", reason: "cancelled", provenance: "client_cancel" },
7270
+ { fence: Boolean(currentSubmission.executionHandle) },
7271
+ undefined,
7272
+ completion,
7273
+ );
7274
+ const durable = kindReconciliation.lookupResult(currentSubmission.reconciliationKind, {
7275
+ commandId,
7276
+ turnId,
7277
+ });
7278
+ if (
7279
+ completion.terminalized !== true &&
7280
+ (durable.terminalAt === undefined ||
7281
+ (durable.status !== "terminal_ok" && durable.status !== "failed"))
7282
+ ) {
7283
+ // The reserved marker remains uncertain; never commit deterministic no-effect
7284
+ // authority while the accepted submission still lacks a durable terminal.
7285
+ return { ok: false as const, reason: "worker_unsettled" as const };
7286
+ }
7123
7287
  // No prompt won the race: finalize the reserved row so a later
7124
7288
  // same-key retry replays this deterministic no_active_turn result
7125
7289
  // instead of reservation uncertainty (review thread P2).
@@ -7993,6 +8157,12 @@ export function createNotificationsExtension(
7993
8157
  runtime = {
7994
8158
  server,
7995
8159
  host,
8160
+ getJoinedPromptCorrelations,
8161
+ isPromptRunOwner: (correlation, owner) => {
8162
+ const handle = promptSubmissions.get(promptSubmissionKey(correlation))?.executionHandle;
8163
+ return handle === owner.resourceRunId && terminalAbortSeams?.getRunOwnerDomain?.(handle) === owner.domain;
8164
+ },
8165
+ retireJoinedPromptOwners,
7996
8166
  broadcastEventFrame,
7997
8167
  broadcastEventFrameWithReceipts,
7998
8168
  revisions,
@@ -9896,13 +10066,25 @@ export function createNotificationsExtension(
9896
10066
  const id = sessionId(ctx);
9897
10067
  const rt = runtimes.get(id);
9898
10068
  if (!rt) return;
9899
- void rt.host
9900
- .reportActivity("idle")
9901
- .catch(error => logger.warn(`notifications: idle activity checkpoint failed: ${String(error)}`));
9902
- // Clear the streaming flag for SDK consumers even when notifications are off.
9903
- rt.busy = false;
9904
- const correlation = rt.activePromptCorrelation;
9905
- if (correlation) {
10069
+ // The Agent's terminal owner is independent of delivery correlation. Never
10070
+ // borrow a successor's run handle to settle a predecessor's SDK prompts.
10071
+ const terminalOwner = terminalAbortSeams?.getTerminalRunOwnerForEvent?.(event);
10072
+ const rootCorrelation = rt.activePromptCorrelation;
10073
+ const correlations = terminalOwner ? rt.getJoinedPromptCorrelations(terminalOwner) : [];
10074
+ if (
10075
+ rootCorrelation &&
10076
+ (!terminalOwner || rt.isPromptRunOwner(rootCorrelation, terminalOwner)) &&
10077
+ !correlations.some(
10078
+ correlation =>
10079
+ correlation.commandId === rootCorrelation.commandId && correlation.turnId === rootCorrelation.turnId,
10080
+ )
10081
+ )
10082
+ correlations.unshift(rootCorrelation);
10083
+ for (const correlation of correlations) {
10084
+ // This attributed agent_end is an execution boundary even when the
10085
+ // subsequent durable terminal claim fails. A fatal transport closure
10086
+ // clears attribution, so an unrelated later agent_end cannot settle a
10087
+ // predecessor's prompt correlation.
9906
10088
  const assistants = (Array.isArray(event.messages) ? [...event.messages].reverse() : []).filter(
9907
10089
  message => message && typeof message === "object" && (message as { role?: unknown }).role === "assistant",
9908
10090
  ) as Array<{ stopReason?: unknown; errorKind?: unknown; errorCode?: unknown }>;
@@ -9990,11 +10172,24 @@ export function createNotificationsExtension(
9990
10172
  }
9991
10173
  : {}),
9992
10174
  });
9993
- } else {
9994
- rt.emitPromptLifecycle(undefined, { type: "agent_end", sessionId: id });
9995
10175
  }
10176
+ if (correlations.length === 0) rt.emitPromptLifecycle(undefined, { type: "agent_end", sessionId: id });
10177
+ if (
10178
+ rootCorrelation &&
10179
+ terminalOwner &&
10180
+ !rt.isPromptRunOwner(rootCorrelation, terminalOwner) &&
10181
+ !correlations.some(
10182
+ correlation =>
10183
+ correlation.commandId === rootCorrelation.commandId && correlation.turnId === rootCorrelation.turnId,
10184
+ )
10185
+ )
10186
+ return;
10187
+ rt.busy = false;
10188
+ void rt.host
10189
+ .reportActivity("idle")
10190
+ .catch(error => logger.warn(`notifications: idle activity checkpoint failed: ${String(error)}`));
9996
10191
  finishAgentWork(rt, event.stopReason === "paused");
9997
- rt.activePromptCorrelation = undefined;
10192
+ if (rt.activePromptCorrelation === rootCorrelation) rt.activePromptCorrelation = undefined;
9998
10193
  terminalizeInFlightTools(rt, id, event.stopReason === "cancelled" ? "cancelled" : "failed");
9999
10194
  try {
10000
10195
  pushSessionFrame(rt, { type: "activity", sessionId: id, state: "idle" });
@@ -296,8 +296,13 @@ export const SDK_LIFECYCLE_ROUTER_PROTOCOL_VERSION = 1;
296
296
  * so generation-191 owners cannot reap hosts with queued or promoted work.
297
297
  * Generation 193 gates streamed content on negotiated observer capabilities,
298
298
  * so existing owners are replaced before exposing the new observer path.
299
+ * Generation 201 binds queued cancellation and terminal deadlines to the exact
300
+ * consuming run, so pre-fix owners cannot serve the new generic lifecycle path.
301
+ * Generation 202 joins queued-removal terminal publication before ordinary
302
+ * abort acknowledgement, replacing owners that still acknowledge cancellation
303
+ * before its terminal record is durable.
299
304
  */
300
- export const DAEMON_GENERATION = 200;
305
+ export const DAEMON_GENERATION = 202;
301
306
 
302
307
  /**
303
308
  * Serving-compatibility boundary for daemon lifecycle requests. Epoch 7
@@ -71,6 +71,7 @@ export {
71
71
  TELEGRAM_TRANSPORT_GENERATION,
72
72
  } from "./telegram-daemon-contract";
73
73
 
74
+ import { POSITIONED_NOTIFICATION_EFFECTS_CAPABILITY } from "../host/host";
74
75
  import {
75
76
  type AgentDirSessionLifecycleService,
76
77
  createSessionLifecycleService,
@@ -303,8 +304,9 @@ export const ASK_CONTROLS_CAPABILITY = "ask_controls_v1";
303
304
  export const TOOL_ACTIVITY_CAPABILITY = "tool_activity_v2";
304
305
  /** Receive-only compatibility capability for pre-v2 hosts. */
305
306
  export const LEGACY_TOOL_ACTIVITY_CAPABILITY = "tool_activity_v1";
306
- /** Opts notification adapters into positioned-only delivery for matching live effects. */
307
- export const POSITIONED_NOTIFICATION_EFFECTS_CAPABILITY = "positioned_notification_effects_v1";
307
+ /** Public `sdk/bus/telegram-daemon` path for the canonical `sdk/host` capability token. */
308
+ export { POSITIONED_NOTIFICATION_EFFECTS_CAPABILITY };
309
+
308
310
  type ToolActivityCapability = "v1" | "v2";
309
311
 
310
312
  function negotiateToolActivityCapability(
@@ -167,6 +167,8 @@ export function sdkPublicFailure(code: string, details?: unknown, proof?: Public
167
167
  const kinds: Record<string, PublicFailureKind> = {
168
168
  usage: "usage",
169
169
  invalid_input: "usage",
170
+ unknown_operation: "usage",
171
+ not_a_repository: "usage",
170
172
  invalid_json: "invalid_json",
171
173
  broker_unavailable: "broker_unavailable",
172
174
  session_unavailable: "endpoint_stale",
@@ -177,6 +179,7 @@ export function sdkPublicFailure(code: string, details?: unknown, proof?: Public
177
179
  tail_timeout: "timeout",
178
180
  wait_timeout: "wait_timeout",
179
181
  uncertain_after_send: "uncertain_after_send",
182
+ busy: "busy",
180
183
  authorization_denied: "authorization_denied",
181
184
  unauthorized: "authorization_denied",
182
185
  forbidden: "authorization_denied",
@@ -203,8 +206,15 @@ export function sdkPublicFailure(code: string, details?: unknown, proof?: Public
203
206
  references,
204
207
  // A typed `resource_gone` keeps its public classification; the fixed
205
208
  // diagnostic only separates absent resource state from an empty result.
206
- // It asserts no cause and echoes no host-supplied text.
207
- ...(code === "resource_gone" ? { diagnostics: ["sdk_resource_gone" as const] } : {}),
209
+ // It asserts no cause and echoes no host-supplied text. The usage-mapped
210
+ // codes likewise attach fixed guidance instead of the original message.
211
+ ...(code === "resource_gone"
212
+ ? { diagnostics: ["sdk_resource_gone" as const] }
213
+ : code === "unknown_operation"
214
+ ? { diagnostics: ["sdk_unknown_operation" as const] }
215
+ : code === "not_a_repository"
216
+ ? { diagnostics: ["sdk_scope_requires_repository" as const] }
217
+ : {}),
208
218
  });
209
219
  }
210
220
 
@@ -1248,7 +1258,11 @@ async function runSend(agentDir: string, sessionId: string, args: SdkSessionCliA
1248
1258
  ? ("wait_timeout" as const)
1249
1259
  : failure.input.kind,
1250
1260
  }
1251
- : {}),
1261
+ : failure.input.kind === "busy"
1262
+ ? // turn.prompt is refused while a turn runs and its admission is released
1263
+ // before any execution, so the prompt definitively did not apply.
1264
+ { proof: "pre-effect" as const }
1265
+ : {}),
1252
1266
  references: failure.input.references,
1253
1267
  });
1254
1268
  }