@gajae-code/coding-agent 0.14.2 → 0.15.0

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 (345) hide show
  1. package/CHANGELOG.md +120 -1
  2. package/dist/types/async/job-manager.d.ts +4 -1
  3. package/dist/types/autoresearch/dashboard.d.ts +29 -4
  4. package/dist/types/autoresearch/data-context.d.ts +25 -0
  5. package/dist/types/autoresearch/git.d.ts +65 -7
  6. package/dist/types/autoresearch/harness.d.ts +58 -0
  7. package/dist/types/autoresearch/prompts.d.ts +51 -0
  8. package/dist/types/autoresearch/runs.d.ts +173 -0
  9. package/dist/types/autoresearch/types.d.ts +9 -139
  10. package/dist/types/capability/types.d.ts +20 -0
  11. package/dist/types/cli/accounts-cli.d.ts +1 -0
  12. package/dist/types/commands/{rlm.d.ts → autoresearch.d.ts} +2 -4
  13. package/dist/types/config/autorouting-contract.d.ts +146 -0
  14. package/dist/types/config/autorouting-generator.d.ts +40 -0
  15. package/dist/types/config/autorouting-tier-map.d.ts +16844 -0
  16. package/dist/types/config/autorouting.d.ts +35 -0
  17. package/dist/types/config/model-discovery-manager.d.ts +8 -1
  18. package/dist/types/config/model-registry.d.ts +30 -1
  19. package/dist/types/config/provider-selection-policy.d.ts +23 -0
  20. package/dist/types/config/settings-schema.d.ts +61 -3
  21. package/dist/types/config/settings.d.ts +3 -0
  22. package/dist/types/coordinator/contract.d.ts +1 -1
  23. package/dist/types/coordinator-mcp/durability.d.ts +29 -0
  24. package/dist/types/coordinator-mcp/event-webhook.d.ts +50 -0
  25. package/dist/types/coordinator-mcp/question-state.d.ts +1 -1
  26. package/dist/types/coordinator-mcp/server.d.ts +14 -0
  27. package/dist/types/crash/index-store.d.ts +3 -0
  28. package/dist/types/crash/record-loader.d.ts +2 -0
  29. package/dist/types/crash/upstream/relay.d.ts +5 -3
  30. package/dist/types/cursor.d.ts +12 -0
  31. package/dist/types/defaults/gjc-defaults.d.ts +35 -1
  32. package/dist/types/defaults/gjc-skills.generated.d.ts +1 -1
  33. package/dist/types/discovery/helpers.d.ts +1 -1
  34. package/dist/types/edit/index.d.ts +2 -1
  35. package/dist/types/eval/py/executor.d.ts +2 -0
  36. package/dist/types/extensibility/extensions/loader.d.ts +32 -0
  37. package/dist/types/extensibility/gjc-plugins/types.d.ts +1 -1
  38. package/dist/types/extensibility/skills.d.ts +2 -0
  39. package/dist/types/gjc-runtime/autoresearch-runtime.d.ts +194 -0
  40. package/dist/types/gjc-runtime/gc-runtime.d.ts +1 -1
  41. package/dist/types/gjc-runtime/psmux-detect.d.ts +1 -1
  42. package/dist/types/gjc-runtime/python-transcript.d.ts +19 -0
  43. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +2 -8
  44. package/dist/types/gjc-runtime/session-layout.d.ts +6 -3
  45. package/dist/types/gjc-runtime/state-renderer.d.ts +0 -22
  46. package/dist/types/gjc-runtime/state-schema.d.ts +7 -7
  47. package/dist/types/gjc-runtime/tmux-common.d.ts +2 -2
  48. package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +38 -1
  49. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +2 -24
  50. package/dist/types/gjc-runtime/ultragoal-validation-policy.d.ts +73 -0
  51. package/dist/types/gjc-runtime/workflow-recovery-projection.d.ts +100 -0
  52. package/dist/types/hooks/skill-keywords.d.ts +1 -1
  53. package/dist/types/main.d.ts +0 -10
  54. package/dist/types/modes/acp/acp-agent.d.ts +13 -6
  55. package/dist/types/modes/components/model-selector.d.ts +14 -0
  56. package/dist/types/modes/components/skill-hud/render.d.ts +6 -1
  57. package/dist/types/modes/components/smart-routing-panel.d.ts +82 -0
  58. package/dist/types/modes/controllers/selector-controller.d.ts +14 -0
  59. package/dist/types/modes/interactive-mode.d.ts +3 -0
  60. package/dist/types/modes/types.d.ts +9 -0
  61. package/dist/types/rlm/artifacts.d.ts +4 -4
  62. package/dist/types/runtime/optional-runtime-services.d.ts +2 -1
  63. package/dist/types/runtime/workspace-tree-service.d.ts +5 -1
  64. package/dist/types/runtime-mcp/config.d.ts +6 -0
  65. package/dist/types/runtime-mcp/manager.d.ts +10 -0
  66. package/dist/types/sdk/acp/adapter.d.ts +0 -4
  67. package/dist/types/sdk/broker/broker.d.ts +1 -1
  68. package/dist/types/sdk/broker/lifecycle.d.ts +57 -2
  69. package/dist/types/sdk/broker/session-index.d.ts +15 -0
  70. package/dist/types/sdk/bus/chat-daemon-control.d.ts +6 -0
  71. package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +4 -0
  72. package/dist/types/sdk/bus/index.d.ts +8 -0
  73. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +2 -0
  74. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +12 -2
  75. package/dist/types/sdk/client/client.d.ts +39 -4
  76. package/dist/types/sdk/host/host.d.ts +1 -0
  77. package/dist/types/sdk/host/internal-autorouting-state.d.ts +3 -0
  78. package/dist/types/sdk/host/model-pin.d.ts +29 -0
  79. package/dist/types/sdk/host/session-runtime.d.ts +2 -1
  80. package/dist/types/sdk/lifecycle-session.d.ts +13 -0
  81. package/dist/types/sdk/router/session-router.d.ts +27 -1
  82. package/dist/types/sdk/session.d.ts +11 -1
  83. package/dist/types/session/agent-session.d.ts +36 -6
  84. package/dist/types/session/artifacts.d.ts +16 -1
  85. package/dist/types/session/internal/managed-session-storage.d.ts +7 -0
  86. package/dist/types/session/session-manager.d.ts +84 -2
  87. package/dist/types/session/session-staging-paths.d.ts +3 -0
  88. package/dist/types/skill-state/canonical-skills.d.ts +1 -1
  89. package/dist/types/skill-state/workflow-hud.d.ts +12 -21
  90. package/dist/types/skill-state/workflow-mutation-guard.d.ts +1 -0
  91. package/dist/types/slash-commands/helpers/autorouting-status.d.ts +8 -0
  92. package/dist/types/system-prompt.d.ts +23 -0
  93. package/dist/types/task/executor.d.ts +35 -4
  94. package/dist/types/task/index.d.ts +17 -3
  95. package/dist/types/task/receipt.d.ts +3 -1
  96. package/dist/types/task/types.d.ts +144 -0
  97. package/dist/types/tools/atomic-file-write.d.ts +72 -0
  98. package/dist/types/tools/descriptors.d.ts +1 -0
  99. package/dist/types/tools/index.d.ts +36 -1
  100. package/dist/types/tools/move-session.d.ts +40 -0
  101. package/dist/types/tools/python.d.ts +12 -0
  102. package/dist/types/tools/resource-gc.d.ts +0 -3
  103. package/dist/types/tools/search.d.ts +8 -1
  104. package/dist/types/tools/skill.d.ts +9 -4
  105. package/dist/types/tools/subagent.d.ts +33 -3
  106. package/dist/types/utils/xml-escape.d.ts +2 -0
  107. package/dist/types/workflow/workflow-intent-diff.d.ts +2 -2
  108. package/dist/types/workflow/workflow-intent-report-consumer.d.ts +1 -1
  109. package/package.json +11 -12
  110. package/scripts/build-sdk-package-smoke.ts +7 -5
  111. package/scripts/check-autorouting-tier-map.ts +183 -0
  112. package/scripts/generate-sdk-operation-inventory.ts +12 -0
  113. package/scripts/generate-tool-catalog.ts +40 -5
  114. package/src/async/job-manager.ts +5 -2
  115. package/src/autoresearch/dashboard.ts +134 -314
  116. package/src/autoresearch/data-context.ts +35 -0
  117. package/src/autoresearch/git.ts +186 -26
  118. package/src/autoresearch/{helpers.ts → harness.ts} +65 -40
  119. package/src/autoresearch/{prompt-setup.md → prompts/prompt-setup.md} +7 -7
  120. package/src/autoresearch/prompts/prompt.md +103 -0
  121. package/src/autoresearch/prompts.ts +126 -0
  122. package/src/autoresearch/runs.ts +708 -0
  123. package/src/autoresearch/types.ts +9 -150
  124. package/src/capability/index.ts +4 -4
  125. package/src/capability/types.ts +20 -0
  126. package/src/cli/accounts-cli.ts +21 -5
  127. package/src/cli/config-cli.ts +25 -1
  128. package/src/cli/fast-help.ts +1 -2
  129. package/src/cli/list-models.ts +5 -8
  130. package/src/cli/setup-cli.ts +8 -0
  131. package/src/cli.ts +2 -21
  132. package/src/commands/autoresearch.ts +33 -0
  133. package/src/commands/coordinator.ts +15 -0
  134. package/src/commands/launch.ts +2 -4
  135. package/src/commands/quick-lane.ts +1 -1
  136. package/src/commands/sdk.ts +24 -0
  137. package/src/commands/state.ts +1 -1
  138. package/src/config/autorouting-contract.ts +474 -0
  139. package/src/config/autorouting-generator.ts +238 -0
  140. package/src/config/autorouting-tier-map.ts +6108 -0
  141. package/src/config/autorouting.ts +120 -0
  142. package/src/config/model-discovery-manager.ts +42 -11
  143. package/src/config/model-registry.ts +348 -68
  144. package/src/config/provider-selection-policy.ts +60 -9
  145. package/src/config/settings-schema.ts +195 -6
  146. package/src/config/settings.ts +62 -1
  147. package/src/config.ts +2 -3
  148. package/src/coordinator/contract.ts +0 -1
  149. package/src/coordinator-mcp/codex-handoff.ts +68 -42
  150. package/src/coordinator-mcp/durability.ts +200 -0
  151. package/src/coordinator-mcp/event-webhook.ts +278 -0
  152. package/src/coordinator-mcp/question-state.ts +109 -46
  153. package/src/coordinator-mcp/server.ts +932 -504
  154. package/src/crash/index-store.ts +126 -22
  155. package/src/crash/record-loader.ts +7 -0
  156. package/src/crash/upstream/relay.ts +82 -34
  157. package/src/cursor.ts +84 -1
  158. package/src/debug/index.ts +26 -0
  159. package/src/defaults/gjc/skills/autoresearch/SKILL.md +116 -0
  160. package/src/defaults/gjc/skills/autoresearch/auto-critic.md +46 -0
  161. package/src/defaults/gjc/skills/autoresearch/auto-iterate.md +42 -0
  162. package/src/defaults/gjc/skills/deep-interview/SKILL.md +17 -37
  163. package/src/defaults/gjc/skills/ralplan/SKILL.md +21 -18
  164. package/src/defaults/gjc/skills/ultragoal/SKILL.md +6 -17
  165. package/src/defaults/gjc/skills/ultragoal/ai-slop-cleaner.md +2 -2
  166. package/src/defaults/gjc-defaults.ts +89 -2
  167. package/src/defaults/gjc-skills.generated.ts +24 -17
  168. package/src/discovery/builtin.ts +18 -5
  169. package/src/discovery/helpers.ts +6 -6
  170. package/src/edit/index.ts +5 -2
  171. package/src/eval/py/executor.ts +8 -0
  172. package/src/extensibility/extensions/loader.ts +135 -13
  173. package/src/extensibility/extensions/wrapper.ts +1 -2
  174. package/src/extensibility/gjc-plugins/schema.ts +1 -1
  175. package/src/extensibility/runtime-skill-discovery.ts +2 -2
  176. package/src/extensibility/skill-management.ts +2 -3
  177. package/src/extensibility/skills.ts +16 -8
  178. package/src/gjc-runtime/autoresearch-runtime.ts +1509 -0
  179. package/src/gjc-runtime/gc-render.ts +0 -1
  180. package/src/gjc-runtime/gc-runtime.ts +1 -11
  181. package/src/gjc-runtime/launch-tmux.ts +3 -3
  182. package/src/gjc-runtime/psmux-detect.ts +2 -2
  183. package/src/gjc-runtime/python-transcript.ts +58 -0
  184. package/src/gjc-runtime/ralplan-runtime.ts +6 -18
  185. package/src/gjc-runtime/session-layout.ts +18 -7
  186. package/src/gjc-runtime/session-state-sidecar.ts +58 -9
  187. package/src/gjc-runtime/state-renderer.ts +0 -38
  188. package/src/gjc-runtime/state-runtime.ts +43 -22
  189. package/src/gjc-runtime/state-schema.ts +1 -1
  190. package/src/gjc-runtime/tmux-common.ts +7 -7
  191. package/src/gjc-runtime/ultragoal-change-set.ts +232 -11
  192. package/src/gjc-runtime/ultragoal-runtime.ts +309 -114
  193. package/src/gjc-runtime/ultragoal-validation-policy.ts +247 -0
  194. package/src/gjc-runtime/workflow-command-ref.ts +15 -11
  195. package/src/gjc-runtime/workflow-manifest.generated.json +413 -532
  196. package/src/gjc-runtime/workflow-manifest.ts +43 -83
  197. package/src/gjc-runtime/workflow-recovery-projection.ts +616 -0
  198. package/src/hooks/skill-keywords.ts +3 -3
  199. package/src/hooks/skill-state.ts +2 -2
  200. package/src/internal-urls/docs-index.generated.ts +26 -24
  201. package/src/lsp/index.ts +25 -5
  202. package/src/main.ts +2 -27
  203. package/src/modes/acp/acp-agent.ts +200 -75
  204. package/src/modes/components/model-selector.ts +216 -13
  205. package/src/modes/components/oauth-selector.ts +5 -5
  206. package/src/modes/components/skill-hud/render.ts +173 -35
  207. package/src/modes/components/smart-routing-panel.ts +531 -0
  208. package/src/modes/components/tool-status-header.ts +6 -3
  209. package/src/modes/controllers/command-controller.ts +17 -7
  210. package/src/modes/controllers/input-controller.ts +44 -9
  211. package/src/modes/controllers/runtime-mcp-command-controller.ts +4 -0
  212. package/src/modes/controllers/selector-controller.ts +298 -65
  213. package/src/modes/interactive-mode.ts +8 -1
  214. package/src/modes/shared/agent-wire/workflow-gate-types.ts +1 -1
  215. package/src/modes/types.ts +9 -1
  216. package/src/prompts/agents/planner.md +1 -1
  217. package/src/prompts/system/system-prompt.md +1 -1
  218. package/src/prompts/system/volatile-project-context.md +2 -1
  219. package/src/prompts/tools/move-session.md +15 -0
  220. package/src/prompts/tools/python.md +12 -0
  221. package/src/prompts/tools/skill-discovery.md +1 -1
  222. package/src/prompts/tools/skill.md +2 -2
  223. package/src/prompts/tools/task-summary.md +1 -0
  224. package/src/prompts/tools/task.md +6 -0
  225. package/src/rlm/artifacts.ts +12 -10
  226. package/src/runtime/optional-runtime-services.ts +3 -2
  227. package/src/runtime/workspace-tree-service.ts +11 -3
  228. package/src/runtime-mcp/config.ts +8 -1
  229. package/src/runtime-mcp/manager.ts +33 -0
  230. package/src/sdk/acp/adapter.ts +48 -1
  231. package/src/sdk/broker/broker.ts +30 -2
  232. package/src/sdk/broker/discovery.ts +217 -2
  233. package/src/sdk/broker/lifecycle.ts +385 -59
  234. package/src/sdk/broker/session-index.ts +243 -48
  235. package/src/sdk/broker/transport.ts +1 -0
  236. package/src/sdk/bus/chat-daemon-control.ts +8 -2
  237. package/src/sdk/bus/chat-daemon-runtime.ts +5 -0
  238. package/src/sdk/bus/index.ts +208 -11
  239. package/src/sdk/bus/kind-aware-reconciliation.ts +16 -0
  240. package/src/sdk/bus/reconciliation-store.ts +27 -5
  241. package/src/sdk/bus/telegram-daemon-contract.ts +12 -2
  242. package/src/sdk/client/client.ts +204 -22
  243. package/src/sdk/host/host.ts +16 -0
  244. package/src/sdk/host/internal-autorouting-state.ts +13 -0
  245. package/src/sdk/host/model-pin.ts +101 -0
  246. package/src/sdk/host/reverse-leases.ts +2 -1
  247. package/src/sdk/host/session-runtime.test.ts +224 -1
  248. package/src/sdk/host/session-runtime.ts +125 -37
  249. package/src/sdk/lifecycle/service.ts +3 -1
  250. package/src/sdk/lifecycle-session.ts +42 -1
  251. package/src/sdk/protocol/operation-inventory.generated.json +66 -0
  252. package/src/sdk/router/session-router.ts +375 -36
  253. package/src/sdk/session.ts +547 -47
  254. package/src/session/account-inventory.ts +1 -1
  255. package/src/session/agent-session.ts +765 -189
  256. package/src/session/artifacts.ts +234 -4
  257. package/src/session/internal/managed-session-storage.ts +91 -4
  258. package/src/session/provider-safety-stop-hint.ts +3 -2
  259. package/src/session/session-manager.ts +775 -16
  260. package/src/session/session-staging-paths.ts +8 -0
  261. package/src/setup/hermes/templates/operator-instructions.v1.md +0 -1
  262. package/src/setup/host-plugin-setup.ts +2 -2
  263. package/src/skill-state/active-state.ts +2 -2
  264. package/src/skill-state/canonical-skills.ts +1 -1
  265. package/src/skill-state/initial-phase.ts +1 -1
  266. package/src/skill-state/workflow-hud.ts +41 -41
  267. package/src/skill-state/workflow-mutation-guard.ts +26 -7
  268. package/src/slash-commands/builtin-registry.ts +77 -0
  269. package/src/slash-commands/helpers/autorouting-status.ts +55 -0
  270. package/src/system-prompt.ts +102 -5
  271. package/src/task/discovery.ts +2 -3
  272. package/src/task/executor.ts +617 -43
  273. package/src/task/index.ts +262 -15
  274. package/src/task/receipt.ts +30 -0
  275. package/src/task/render.ts +11 -1
  276. package/src/task/types.ts +316 -0
  277. package/src/tools/atomic-file-write.ts +594 -0
  278. package/src/tools/bisect.ts +3 -2
  279. package/src/tools/descriptors.test.ts +3 -0
  280. package/src/tools/descriptors.ts +24 -0
  281. package/src/tools/image-gen.ts +5 -1
  282. package/src/tools/index.ts +62 -5
  283. package/src/tools/move-session.ts +107 -0
  284. package/src/tools/python.ts +205 -0
  285. package/src/tools/read.ts +88 -15
  286. package/src/tools/renderers.ts +2 -0
  287. package/src/tools/resource-gc.ts +8 -177
  288. package/src/tools/search.ts +30 -6
  289. package/src/tools/skill-discovery.ts +1 -0
  290. package/src/tools/skill.ts +37 -7
  291. package/src/tools/subagent-render.ts +119 -13
  292. package/src/tools/subagent.ts +91 -74
  293. package/src/tools/tool-catalog.generated.ts +64 -2
  294. package/src/tools/write.ts +16 -12
  295. package/src/utils/xml-escape.ts +14 -0
  296. package/src/web/search/providers/codex.ts +2 -2
  297. package/src/workflow/workflow-intent-diff.ts +13 -12
  298. package/src/workflow/workflow-intent-report-consumer.ts +6 -6
  299. package/dist/types/autoresearch/helpers.d.ts +0 -24
  300. package/dist/types/autoresearch/index.d.ts +0 -2
  301. package/dist/types/autoresearch/state.d.ts +0 -17
  302. package/dist/types/autoresearch/storage.d.ts +0 -142
  303. package/dist/types/autoresearch/tools/init-experiment.d.ts +0 -31
  304. package/dist/types/autoresearch/tools/log-experiment.d.ts +0 -23
  305. package/dist/types/autoresearch/tools/run-experiment.d.ts +0 -8
  306. package/dist/types/autoresearch/tools/update-notes.d.ts +0 -12
  307. package/dist/types/commands/team.d.ts +0 -24
  308. package/dist/types/gjc-runtime/team-gc.d.ts +0 -7
  309. package/dist/types/gjc-runtime/team-launch.d.ts +0 -61
  310. package/dist/types/gjc-runtime/team-notify.d.ts +0 -45
  311. package/dist/types/gjc-runtime/team-runtime.d.ts +0 -336
  312. package/dist/types/gjc-runtime/team-store.d.ts +0 -168
  313. package/dist/types/gjc-runtime/team-worker-heartbeat.d.ts +0 -43
  314. package/dist/types/gjc-runtime/team-worker-memory-guard.d.ts +0 -92
  315. package/dist/types/gjc-runtime/team-workers.d.ts +0 -83
  316. package/dist/types/rlm/index.d.ts +0 -47
  317. package/dist/types/rlm/preset.d.ts +0 -23
  318. package/dist/types/rlm/python-tool.d.ts +0 -16
  319. package/src/autoresearch/command-resume.md +0 -14
  320. package/src/autoresearch/index.ts +0 -536
  321. package/src/autoresearch/prompt.md +0 -103
  322. package/src/autoresearch/resume-message.md +0 -10
  323. package/src/autoresearch/state.ts +0 -273
  324. package/src/autoresearch/storage.ts +0 -699
  325. package/src/autoresearch/tools/init-experiment.ts +0 -272
  326. package/src/autoresearch/tools/log-experiment.ts +0 -524
  327. package/src/autoresearch/tools/run-experiment.ts +0 -475
  328. package/src/autoresearch/tools/update-notes.ts +0 -109
  329. package/src/commands/rlm.ts +0 -19
  330. package/src/commands/team.ts +0 -238
  331. package/src/defaults/gjc/skills/deep-interview/auto-research-greenfield.md +0 -42
  332. package/src/defaults/gjc/skills/team/SKILL.md +0 -467
  333. package/src/gjc-runtime/team-gc.ts +0 -49
  334. package/src/gjc-runtime/team-launch.ts +0 -404
  335. package/src/gjc-runtime/team-notify.ts +0 -326
  336. package/src/gjc-runtime/team-runtime.ts +0 -5923
  337. package/src/gjc-runtime/team-store.ts +0 -878
  338. package/src/gjc-runtime/team-worker-heartbeat.ts +0 -149
  339. package/src/gjc-runtime/team-worker-memory-guard.ts +0 -363
  340. package/src/gjc-runtime/team-workers.ts +0 -1123
  341. package/src/prompts/system/rlm-report-command.md +0 -1
  342. package/src/prompts/system/rlm-research.md +0 -23
  343. package/src/rlm/index.ts +0 -365
  344. package/src/rlm/preset.ts +0 -76
  345. package/src/rlm/python-tool.ts +0 -68
@@ -48,6 +48,7 @@ function sdkBusNatives(): NativeSdkBusBindings {
48
48
  type NotificationServer = NativeNotificationServer;
49
49
 
50
50
  import { $credentialEnv, logger, postmortem, VERSION } from "@gajae-code/utils";
51
+
51
52
  import { AsyncJobManager } from "../../async";
52
53
  import { Settings, validateSettingPatch } from "../../config/settings";
53
54
  import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "../../extensibility/extensions";
@@ -100,6 +101,7 @@ import {
100
101
  } from "../host";
101
102
  import { type AbortScope, type ControlSurface, dispatchControl, TypedControlError } from "../host/control";
102
103
  import { BROKER_RUNTIME_CLOSE_CAPABILITY_FIELD } from "../host/control/runtime-gate";
104
+ import { isAutoroutingInactive, markAutoroutingInactive } from "../host/internal-autorouting-state";
103
105
  import { CursorRegistry, QueryHandlers, RevisionStore, type SessionSurface } from "../host/query";
104
106
  import type { SdkFrame } from "../host/types";
105
107
  import {
@@ -209,6 +211,39 @@ const PROMPT_SETTLEMENT_DIAGNOSTIC_MAX_AGE_MS = 86_400_000;
209
211
  * provider error cannot flood the log file.
210
212
  */
211
213
  const PROMPT_TERMINAL_FAILURE_REASON_LOG_MAX = 512;
214
+ /**
215
+ * #4743: bounded wait for durable reconciliation quiescence during session
216
+ * teardown. Expiry is OBSERVABLE (owner-release failure), never silently
217
+ * treated as drained. Read per drain so the env override is effective for
218
+ * deterministic tests.
219
+ */
220
+ const sdkReconciliationDrainTimeoutMs = (): number => {
221
+ const override = Number(process.env.GJC_SDK_RECONCILIATION_DRAIN_TIMEOUT_MS);
222
+ return override > 0 ? override : 5_000;
223
+ };
224
+ /**
225
+ * #4743: the two reconciliation failure codes that mean durable state may be
226
+ * lost. Both must reach the teardown owner; every other owner-release failure
227
+ * stays retryable through the retained `cleanupRetries` entry.
228
+ */
229
+ const RECONCILIATION_DURABILITY_FAILURE_CODES: ReadonlySet<string> = new Set([
230
+ "reconciliation_persist_failed",
231
+ "reconciliation_drain_timeout",
232
+ ]);
233
+ function reconciliationFailureCode(value: unknown): string | undefined {
234
+ if (typeof value !== "object" || value === null) return undefined;
235
+ const code = (value as { code?: unknown }).code;
236
+ return typeof code === "string" && RECONCILIATION_DURABILITY_FAILURE_CODES.has(code) ? code : undefined;
237
+ }
238
+ /**
239
+ * Flatten an owner-release failure to its durability-failure members. Aggregates
240
+ * nest (the store batches a drained window's failures, and owner release batches
241
+ * every release failure), so recursion is required to reach the coded leaves.
242
+ */
243
+ function reconciliationDurabilityFailures(error: unknown): unknown[] {
244
+ if (error instanceof AggregateError) return error.errors.flatMap(member => reconciliationDurabilityFailures(member));
245
+ return reconciliationFailureCode(error) === undefined ? [] : [error];
246
+ }
212
247
  type PromptTerminalDiagnostic = {
213
248
  reason?: unknown;
214
249
  loopStopReason?: string;
@@ -1186,6 +1221,11 @@ interface SessionRuntime {
1186
1221
  /** Aborts and fences side turns while notification delivery is disabled. */
1187
1222
  disableEphemeralTurns: () => void;
1188
1223
  waitForGateResolutionQuiescence: () => Promise<void>;
1224
+ /** Awaits durable quiescence of every reconciliation transaction this runtime
1225
+ * admitted, joining their producers first. Never swallows evidence: producer
1226
+ * and store rejections are returned in `failures`, and a bounded-deadline
1227
+ * expiry is returned as `timedOut` (#4743). */
1228
+ drainDurableReconciliation: () => Promise<{ timedOut: boolean; failures: unknown[] }>;
1189
1229
  trackGateResolution: <T>(resolution: Promise<T>) => Promise<T>;
1190
1230
  workflowGate?: WorkflowGateEmitter;
1191
1231
  gatePresentations?: PresentationArbiter;
@@ -2476,6 +2516,10 @@ function sdkControlSurface(
2476
2516
  discardTerminalAbortSteeringSnapshot?: (token: number) => void;
2477
2517
  rebindTerminalAbortSteeringSnapshot?: (token: number) => void;
2478
2518
  },
2519
+ // #4743: registers fire-and-forget reconciliation producers (accepted
2520
+ // executions and post-response publications) so session teardown can join
2521
+ // them before reporting durable quiescence.
2522
+ trackReconciliationProducer?: (producer: Promise<unknown>) => void,
2479
2523
  ): ControlSurface & {
2480
2524
  cancelPendingPreflights(): Promise<void>;
2481
2525
  cancelPendingPreflightsForConnection(connectionId: string): Promise<void>;
@@ -2750,6 +2794,10 @@ function sdkControlSurface(
2750
2794
  }
2751
2795
  accepting = false;
2752
2796
  accepted = true;
2797
+ // #4743: an accepted run owns a durable terminal publication when it
2798
+ // settles; teardown joins it via this latch (never-accepted preflights
2799
+ // produce no durable write and stay unjoined by design).
2800
+ trackReconciliationProducer?.(submissionSettled.promise);
2753
2801
  pendingPreflightCancellations.delete(key);
2754
2802
  settlePreflight({ status: "accepted" });
2755
2803
  if (preflightController.signal.aborted) throw cancellationError;
@@ -2775,10 +2823,14 @@ function sdkControlSurface(
2775
2823
  );
2776
2824
  } catch (error) {
2777
2825
  submissionSettled.resolve();
2778
- if (accepted && !preflightController.signal.aborted) void onPromptFailed(correlation, error);
2826
+ if (accepted && !preflightController.signal.aborted)
2827
+ trackReconciliationProducer?.(Promise.resolve(onPromptFailed(correlation, error)));
2779
2828
  else settlePreflight({ status: "rejected", error });
2780
2829
  }
2781
2830
  if (submission) {
2831
+ // #4743: the continuation may settle long after teardown (a cancelled
2832
+ // preflight stays pending forever by design), so the EXECUTION is never
2833
+ // joined — only its reconciliation publications are tracked below.
2782
2834
  void submission.then(
2783
2835
  () => {
2784
2836
  if (!accepted)
@@ -2791,7 +2843,8 @@ function sdkControlSurface(
2791
2843
  submissionSettled.resolve();
2792
2844
  },
2793
2845
  error => {
2794
- if (accepted && !preflightController.signal.aborted) void onPromptFailed(correlation, error);
2846
+ if (accepted && !preflightController.signal.aborted)
2847
+ trackReconciliationProducer?.(Promise.resolve(onPromptFailed(correlation, error)));
2795
2848
  else settlePreflight({ status: "rejected", error });
2796
2849
  submissionSettled.resolve();
2797
2850
  },
@@ -3287,6 +3340,10 @@ function sdkControlSurface(
3287
3340
  if (skillRecon) {
3288
3341
  await skillRecon.noteAccepted(correlation, trimmedClientRef, { skillName: meta.name });
3289
3342
  durableSkillAccepted = true;
3343
+ // #4743: a durably accepted skill owns a terminal
3344
+ // publication; teardown joins this latch (bounded by the
3345
+ // drain deadline).
3346
+ trackReconciliationProducer?.(executionSettled.promise);
3290
3347
  }
3291
3348
  } catch (error) {
3292
3349
  onPromptAcceptFailed(correlation);
@@ -3305,6 +3362,11 @@ function sdkControlSurface(
3305
3362
  settleReject(error);
3306
3363
  run = Promise.reject(error);
3307
3364
  }
3365
+ // #4743: the skill execution itself may outlive teardown (a cancelled
3366
+ // preflight never settles by design), so only its reconciliation
3367
+ // PUBLICATIONS are tracked — created synchronously the moment the
3368
+ // execution settles, so a publication fired during teardown is always
3369
+ // joined before the drain reports quiescence.
3308
3370
  void run.then(
3309
3371
  result => {
3310
3372
  if (phase === "pending") {
@@ -3317,12 +3379,14 @@ function sdkControlSurface(
3317
3379
  ...(trimmedClientRef ? { clientRef: trimmedClientRef } : {}),
3318
3380
  });
3319
3381
  } else if (durableSkillAccepted && skillRecon) {
3320
- void skillRecon.noteTransition(correlation, {
3321
- type: "agent_end",
3322
- ...(typeof result === "string"
3323
- ? { content: { version: 1, type: "text", text: result, byteLength: 0, truncated: false } }
3324
- : {}),
3325
- });
3382
+ trackReconciliationProducer?.(
3383
+ skillRecon.noteTransition(correlation, {
3384
+ type: "agent_end",
3385
+ ...(typeof result === "string"
3386
+ ? { content: { version: 1, type: "text", text: result, byteLength: 0, truncated: false } }
3387
+ : {}),
3388
+ }),
3389
+ );
3326
3390
  }
3327
3391
  executionSettled.resolve();
3328
3392
  },
@@ -3331,10 +3395,12 @@ function sdkControlSurface(
3331
3395
  releaseAdmission();
3332
3396
  settleReject(error);
3333
3397
  } else if (phase === "accepted" && promptOwned && !preflightController.signal.aborted) {
3334
- void onPromptFailed(correlation, error);
3398
+ trackReconciliationProducer?.(Promise.resolve(onPromptFailed(correlation, error)));
3335
3399
  }
3336
3400
  if (durableSkillAccepted && skillRecon && !preflightController.signal.aborted)
3337
- void skillRecon.noteTransition(correlation, { type: "agent_failed", error });
3401
+ trackReconciliationProducer?.(
3402
+ skillRecon.noteTransition(correlation, { type: "agent_failed", error }),
3403
+ );
3338
3404
  executionSettled.resolve();
3339
3405
  },
3340
3406
  );
@@ -3953,7 +4019,6 @@ export function createNotificationsExtension(
3953
4019
  controller?: NotificationSessionController;
3954
4020
  /** Whether this host mode can own the root SDK endpoint. Default: true. */
3955
4021
  sdkHostModeSupported?: boolean;
3956
-
3957
4022
  onSdkRequest?: (kind: "control" | "query", connectionId: string, frame: Record<string, unknown>) => void;
3958
4023
  runBtwTurn?: (question: string, signal: AbortSignal) => Promise<{ replyText: string }>;
3959
4024
  /** Observes settlement of optional session-branch startup after reconciliation completes. */
@@ -4343,6 +4408,34 @@ export function createNotificationsExtension(
4343
4408
  logger.warn(`notifications: stop failed: ${String(e)}`);
4344
4409
  }
4345
4410
  }
4411
+ // #4743: terminal release must not be reported while a reconciliation
4412
+ // publication this runtime admitted — or the execution that will produce it —
4413
+ // is still in flight. Observe durable quiescence AFTER the endpoint stopped
4414
+ // serving (no new admissions) and BEFORE the terminal release is recorded.
4415
+ // Both failure shapes stay observable as owner-release failures: a drained
4416
+ // window containing a failed write (reconciliation_persist_failed evidence
4417
+ // preserved through the store drain) and a deadline expiry (non-quiescent,
4418
+ // never silently treated as drained).
4419
+ try {
4420
+ const drained = await rt.drainDurableReconciliation();
4421
+ for (const failure of drained.failures) {
4422
+ ownerReleaseFailures.push(failure);
4423
+ logger.warn(`sdk reconciliation publication failed during teardown: ${String(failure)}`);
4424
+ }
4425
+ if (drained.timedOut) {
4426
+ const timeoutError = Object.assign(
4427
+ new Error(
4428
+ `SDK reconciliation drain timed out after ${sdkReconciliationDrainTimeoutMs()}ms; reconciliation state may be non-quiescent.`,
4429
+ ),
4430
+ { code: "reconciliation_drain_timeout" },
4431
+ );
4432
+ ownerReleaseFailures.push(timeoutError);
4433
+ logger.warn("sdk reconciliation drain timed out; proceeding with non-quiescent state");
4434
+ }
4435
+ } catch (e) {
4436
+ ownerReleaseFailures.push(e);
4437
+ logger.warn(`sdk reconciliation drain failed: ${String(e)}`);
4438
+ }
4346
4439
  lifecycleStartupCapability?.rollback?.recordStop(rt.host.generation, {
4347
4440
  runtimeRemoved: true,
4348
4441
  hostStopped: rt.hostStopped && rt.serverStopped,
@@ -4687,6 +4780,26 @@ export function createNotificationsExtension(
4687
4780
  ? createReconciliationStore({ sessionFile, sessionId: String(reconciliationSessionId) })
4688
4781
  : null;
4689
4782
  const kindReconciliation = createKindAwareReconciliation({ store: durableStore });
4783
+ // #4743: reconciliation publications and the executions that produce them
4784
+ // are fire-and-forget at their call sites; without joining the PRODUCERS a
4785
+ // drain can observe an empty queue and return just before a still-running
4786
+ // skill enqueues its terminal publication — the teardown race again, by
4787
+ // another route. Every void-ed producer registers here and the teardown
4788
+ // drain joins the set before awaiting store quiescence.
4789
+ const reconciliationProducers = new Set<Promise<unknown>>();
4790
+ const trackReconciliationProducer = (producer: Promise<unknown>): void => {
4791
+ reconciliationProducers.add(producer);
4792
+ // Deliberately a two-arm handler, NOT `finally`: `finally` returns a derived
4793
+ // promise that re-rejects when the producer rejects, and nothing observes
4794
+ // that derived promise — a failed durable write would become an unhandled
4795
+ // rejection and kill the resident process. The two-arm form always resolves,
4796
+ // while the ORIGINAL producer keeps its rejection for the teardown drain to
4797
+ // inspect (#4743).
4798
+ const forget = (): void => {
4799
+ reconciliationProducers.delete(producer);
4800
+ };
4801
+ void producer.then(forget, forget);
4802
+ };
4690
4803
  // Restart recovery must commit before any prompt is admitted; otherwise a new
4691
4804
  // admission can race the hydrated full-state replacement.
4692
4805
  let reconciliationReady: Promise<void> = durableStore ? kindReconciliation.hydrateFromStore() : Promise.resolve();
@@ -6316,6 +6429,8 @@ export function createNotificationsExtension(
6316
6429
  settleSteer: kindReconciliation.settleSteer,
6317
6430
  },
6318
6431
  terminalAbortSeams,
6432
+ // #4743: join fire-and-forget reconciliation producers at teardown.
6433
+ trackReconciliationProducer,
6319
6434
  );
6320
6435
  cancelPreflightsForConnection = controlSurface.cancelPendingPreflightsForConnection;
6321
6436
  const abandonPromptResponse = (connectionId: string, frame: Record<string, unknown>) => {
@@ -6712,6 +6827,7 @@ export function createNotificationsExtension(
6712
6827
  return { type: "query_response", ...response };
6713
6828
  },
6714
6829
  });
6830
+ if (isAutoroutingInactive(api)) markAutoroutingInactive(sdkRuntime.host);
6715
6831
  host = sdkRuntime.host;
6716
6832
 
6717
6833
  // Install the runtime before either transport can expose the host. session_start
@@ -6742,6 +6858,66 @@ export function createNotificationsExtension(
6742
6858
  enableNotifications: () => {},
6743
6859
  disposeGateTerminalController: () => {},
6744
6860
  disposeAckRecoveryParticipant: () => {},
6861
+ // #4743: session teardown must observe durable quiescence of every
6862
+ // reconciliation transaction this runtime admitted, INCLUDING the ones
6863
+ // whose producers (accepted skill/prompt executions and their post-
6864
+ // response publications) are still running when teardown starts — an
6865
+ // empty queue proves nothing about work that has not been enqueued yet.
6866
+ // Order: join producers → join kind-aware mutations → join the store
6867
+ // chain (which also surfaces persistence-failure evidence). All under a
6868
+ // finite deadline whose expiry is reported as a NON-QUIESCENT result so
6869
+ // the caller records an owner-release failure instead of silently
6870
+ // treating a hung write as drained.
6871
+ drainDurableReconciliation: async (): Promise<{ timedOut: boolean; failures: unknown[] }> => {
6872
+ // One failed write surfaces twice by design — once as the producer's
6873
+ // rejection and once through the store's failure window — and both carry
6874
+ // the same coded error object, so identity dedupe keeps the reported
6875
+ // evidence at one entry per real failure without dropping distinct ones.
6876
+ const seen = new Set<unknown>();
6877
+ const failures: unknown[] = [];
6878
+ const record = (failure: unknown): void => {
6879
+ if (seen.has(failure)) return;
6880
+ seen.add(failure);
6881
+ failures.push(failure);
6882
+ };
6883
+ const quiescent = (async () => {
6884
+ while (true) {
6885
+ const producers = [...reconciliationProducers];
6886
+ // `allSettled` joins ALL producers even when one rejects, but its
6887
+ // results are INSPECTED: a rejected terminal publication is evidence
6888
+ // the caller must see, not something the join consumes before the
6889
+ // store's own failure window opens (#4743).
6890
+ for (const settled of await Promise.allSettled(producers))
6891
+ if (settled.status === "rejected") record(settled.reason);
6892
+ // Both drains below surface store-recorded persistence failures by
6893
+ // rejecting; collect and keep draining so one failed write cannot
6894
+ // hide a later producer's work from the join.
6895
+ try {
6896
+ await kindReconciliation.drain();
6897
+ } catch (e) {
6898
+ record(e);
6899
+ }
6900
+ // Direct store writes (terminal-scope response-state advance) bypass
6901
+ // the kind-aware chain; join them too.
6902
+ try {
6903
+ await durableStore?.drain?.();
6904
+ } catch (e) {
6905
+ record(e);
6906
+ }
6907
+ await Bun.sleep(0);
6908
+ if ([...reconciliationProducers].every(producer => producers.includes(producer))) {
6909
+ return { timedOut: false, failures };
6910
+ }
6911
+ }
6912
+ })();
6913
+ return await Promise.race([
6914
+ quiescent,
6915
+ // A deadline expiry reports the failures observed SO FAR alongside the
6916
+ // timeout: evidence already collected must not be dropped because the
6917
+ // remaining wait ran out.
6918
+ Bun.sleep(sdkReconciliationDrainTimeoutMs()).then(() => ({ timedOut: true, failures })),
6919
+ ]);
6920
+ },
6745
6921
  disposeGateEmitterListener: () => {},
6746
6922
  trackGateResolution,
6747
6923
  waitForGateResolutionQuiescence: async () => {
@@ -8889,6 +9065,11 @@ export function createNotificationsExtension(
8889
9065
  return false;
8890
9066
  });
8891
9067
  if (startupWasPending) void settledControllerStop;
9068
+ // #4743: a durability failure has no later retry cycle at terminal quit, so it
9069
+ // must reach the teardown owner instead of being logged into silence. Held
9070
+ // until the controller queue below is joined so the failure never truncates
9071
+ // the rest of teardown.
9072
+ let reconciliationTeardownFailure: Error | undefined;
8892
9073
  try {
8893
9074
  await stopSession(id);
8894
9075
  } catch (error) {
@@ -8898,11 +9079,27 @@ export function createNotificationsExtension(
8898
9079
  // shutdown. On terminal quit there is no later retry cycle, so log at
8899
9080
  // error severity (matching the postmortem cleanup precedent).
8900
9081
  logger.error(`notifications: SDK notification runtime cleanup failed: ${String(error)}`);
9082
+ // Endpoint-release failures (host/server stop) stay swallowed: they are
9083
+ // retryable through the retained cleanupRetries entry and observable in
9084
+ // lifecycle rollback proof. Reconciliation durability failures are not —
9085
+ // a committed publication that never reached disk, or a drain whose
9086
+ // deadline expired, is state loss the owner must be told about.
9087
+ const durable = reconciliationDurabilityFailures(error);
9088
+ if (durable.length > 0)
9089
+ reconciliationTeardownFailure = Object.assign(
9090
+ new Error(
9091
+ `SDK reconciliation teardown failed for session ${id}: ${durable
9092
+ .map(failure => reconciliationFailureCode(failure) ?? "unknown")
9093
+ .join(", ")}.`,
9094
+ ),
9095
+ { code: "sdk_reconciliation_teardown_failed", failures: durable },
9096
+ );
8901
9097
  }
8902
9098
  // Keep shutdown nonblocking only while native startup is genuinely
8903
9099
  // pending (the `/notify on` path); otherwise await the controller queue so
8904
9100
  // completed-start reconciliation and its replacement-token cleanup are
8905
9101
  // joined before lifecycle shutdown returns.
8906
9102
  if (!startupWasPending) await settledControllerStop;
9103
+ if (reconciliationTeardownFailure) throw reconciliationTeardownFailure;
8907
9104
  });
8908
9105
  }
@@ -89,6 +89,8 @@ export interface KindAwareReconciliation {
89
89
  activeCount(kind: ReconciliationKind): number;
90
90
  /** Hydrate from durable store (call once at session host start). */
91
91
  hydrateFromStore(): Promise<void>;
92
+ /** Await quiescence of every admitted mutation and the durable store (#4743). */
93
+ drain(): Promise<void>;
92
94
  }
93
95
 
94
96
  export function createKindAwareReconciliation(
@@ -534,6 +536,19 @@ export function createKindAwareReconciliation(
534
536
  );
535
537
  await pending;
536
538
  };
539
+ const drain = async (): Promise<void> => {
540
+ while (true) {
541
+ const observed = mutationChain;
542
+ await observed;
543
+ // A noteTransition/noteAccepted admitted in a later microtask must still
544
+ // be joined before teardown reports durable quiescence (#4743).
545
+ await Bun.sleep(0);
546
+ if (mutationChain === observed) {
547
+ await store?.drain?.();
548
+ return;
549
+ }
550
+ }
551
+ };
537
552
 
538
553
  return {
539
554
  admit,
@@ -551,6 +566,7 @@ export function createKindAwareReconciliation(
551
566
  reserveSteer,
552
567
  settleSteer,
553
568
  lookupSteer,
569
+ drain,
554
570
  };
555
571
  }
556
572
 
@@ -625,13 +625,21 @@ export function createReconciliationStore(options: {
625
625
  let terminalMemory: DurableTerminalScopeRecord[] = [];
626
626
  let terminalKeyMemory: EvictedTerminalKeyEntry[] = [];
627
627
  let chain: Promise<void> = Promise.resolve();
628
+ // #4743: transaction tails neutralize rejection on the serialization chain and
629
+ // publications are enqueued fire-and-forget, so a failed write has no observer
630
+ // at all. Retain every persistence failure until a drain reports it, then clear
631
+ // it: surface-once means no failure is lost (even one that landed before
632
+ // teardown began) and no later drain is permanently poisoned by a failure an
633
+ // owner has already been told about.
634
+ let unreportedPersistFailures: unknown[] = [];
628
635
 
629
636
  const writeAtomic = async (document: ReconciliationStoreDocument): Promise<void> => {
630
637
  if (!filePath) return;
631
638
  const directory = path.dirname(filePath);
632
- await fileFs.mkdir(directory, { recursive: true, mode: 0o700 });
633
- const temporary = `${filePath}.${process.pid}.${Date.now()}.${randomUUID()}.tmp`;
639
+ let temporary: string | undefined;
634
640
  try {
641
+ await fileFs.mkdir(directory, { recursive: true, mode: 0o700 });
642
+ temporary = `${filePath}.${process.pid}.${Date.now()}.${randomUUID()}.tmp`;
635
643
  await fileFs.writeFile(temporary, `${JSON.stringify(document)}\n`, { mode: 0o600 });
636
644
  try {
637
645
  const handle = await fileFs.open(temporary, "r+");
@@ -645,10 +653,14 @@ export function createReconciliationStore(options: {
645
653
  }
646
654
  await fileFs.rename(temporary, filePath);
647
655
  } catch (error) {
648
- await fileFs.unlink(temporary).catch(() => {});
649
- throw Object.assign(error instanceof Error ? error : new Error("reconciliation persist failed"), {
656
+ // Every persistence-path failure (mkdir included) is evidence a drained
657
+ // window must surface, never silently treat as quiescent (#4743).
658
+ if (temporary !== undefined) await fileFs.unlink(temporary).catch(() => {});
659
+ const coded = Object.assign(error instanceof Error ? error : new Error("reconciliation persist failed"), {
650
660
  code: "reconciliation_persist_failed",
651
661
  });
662
+ unreportedPersistFailures.push(coded);
663
+ throw coded;
652
664
  }
653
665
  };
654
666
 
@@ -834,7 +846,17 @@ export function createReconciliationStore(options: {
834
846
  // a later microtask. Yield once and require the queue tail to remain
835
847
  // stable before reporting durable quiescence.
836
848
  await Bun.sleep(0);
837
- if (chain === observed) return;
849
+ if (chain === observed) {
850
+ // Transaction tails neutralize rejections on `chain`, so a failed
851
+ // publication would otherwise be indistinguishable from a completed
852
+ // one. Claim the retained evidence (clearing it so exactly one drain
853
+ // reports each failure) and reject instead of claiming quiescence.
854
+ if (unreportedPersistFailures.length === 0) return;
855
+ const claimed = unreportedPersistFailures;
856
+ unreportedPersistFailures = [];
857
+ if (claimed.length === 1) throw claimed[0];
858
+ throw new AggregateError(claimed, "Reconciliation persistence failed during the drained window.");
859
+ }
838
860
  }
839
861
  },
840
862
  loadTerminalScopes: async () => {
@@ -273,8 +273,15 @@ export const SDK_LIFECYCLE_ROUTER_PROTOCOL_VERSION = 1;
273
273
  * reaction settlement for Telegram notification delivery (#4528).
274
274
  * Generation 169 delivers every ring-positioned session event live through the
275
275
  * bounded, capability-gated directed subscriber leg used by replay.
276
+ * Generation 172 fences the SessionRouter idle-poll/change-stamp rollout (#4689).
277
+ * The daemon constructs a SessionRouter, and the router's idle tick no longer
278
+ * re-acquires the machine-global session-index lock on every 2s pass: unchanged
279
+ * polls are stat-only and staleness retirement moved to a 30s sweep. A daemon
280
+ * still running the pre-#4689 router keeps the old hot polling loop and its
281
+ * lock contention, so an already-running owner must be replaced rather than
282
+ * retained across this upgrade.
276
283
  */
277
- export const DAEMON_GENERATION = 171;
284
+ export const DAEMON_GENERATION = 172;
278
285
 
279
286
  /**
280
287
  * Serving-compatibility boundary for daemon lifecycle requests. Epoch 7
@@ -360,5 +367,8 @@ export const DAEMON_GENERATION = 171;
360
367
  * Epoch 85 removes direct relay attachment and prohibits lifecycle controls on every adapter.
361
368
  * Epoch 86 removes public discovery/client exports and retires cross-process raw transports.
362
369
  * Epoch 87 preserves accepted Telegram delivery ambiguity and advances poisoned poll cursors.
370
+ * Epoch 88 requires the #4689 SessionRouter idle-poll contract: generation alone
371
+ * does not force replacement, so the serving boundary advances to guarantee a
372
+ * pre-#4689 daemon cannot keep serving with the old per-tick locked rescan.
363
373
  */
364
- export const SERVING_EPOCH = 87;
374
+ export const SERVING_EPOCH = 88;