oh-my-codex 0.20.3 → 0.20.5

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 (661) hide show
  1. package/Cargo.lock +35 -18
  2. package/Cargo.toml +1 -1
  3. package/README.md +2 -2
  4. package/crates/omx-runtime/Cargo.toml +3 -0
  5. package/crates/omx-runtime/src/main.rs +390 -0
  6. package/crates/omx-runtime/tests/execution.rs +264 -0
  7. package/dist/adapt/__tests__/herdr-target.test.d.ts +2 -0
  8. package/dist/adapt/__tests__/herdr-target.test.d.ts.map +1 -0
  9. package/dist/adapt/__tests__/herdr-target.test.js +72 -0
  10. package/dist/adapt/__tests__/herdr-target.test.js.map +1 -0
  11. package/dist/adapt/contracts.d.ts +1 -1
  12. package/dist/adapt/contracts.d.ts.map +1 -1
  13. package/dist/adapt/contracts.js +1 -1
  14. package/dist/adapt/contracts.js.map +1 -1
  15. package/dist/adapt/herdr/__tests__/authority.test.d.ts +2 -0
  16. package/dist/adapt/herdr/__tests__/authority.test.d.ts.map +1 -0
  17. package/dist/adapt/herdr/__tests__/authority.test.js +49 -0
  18. package/dist/adapt/herdr/__tests__/authority.test.js.map +1 -0
  19. package/dist/adapt/herdr/__tests__/bridge.test.d.ts +2 -0
  20. package/dist/adapt/herdr/__tests__/bridge.test.d.ts.map +1 -0
  21. package/dist/adapt/herdr/__tests__/bridge.test.js +176 -0
  22. package/dist/adapt/herdr/__tests__/bridge.test.js.map +1 -0
  23. package/dist/adapt/herdr/__tests__/sanitize.test.d.ts +2 -0
  24. package/dist/adapt/herdr/__tests__/sanitize.test.d.ts.map +1 -0
  25. package/dist/adapt/herdr/__tests__/sanitize.test.js +45 -0
  26. package/dist/adapt/herdr/__tests__/sanitize.test.js.map +1 -0
  27. package/dist/adapt/herdr/__tests__/semantic.test.d.ts +2 -0
  28. package/dist/adapt/herdr/__tests__/semantic.test.d.ts.map +1 -0
  29. package/dist/adapt/herdr/__tests__/semantic.test.js +130 -0
  30. package/dist/adapt/herdr/__tests__/semantic.test.js.map +1 -0
  31. package/dist/adapt/herdr/__tests__/seq-store.test.d.ts +2 -0
  32. package/dist/adapt/herdr/__tests__/seq-store.test.d.ts.map +1 -0
  33. package/dist/adapt/herdr/__tests__/seq-store.test.js +54 -0
  34. package/dist/adapt/herdr/__tests__/seq-store.test.js.map +1 -0
  35. package/dist/adapt/herdr/__tests__/socket-live.test.d.ts +2 -0
  36. package/dist/adapt/herdr/__tests__/socket-live.test.d.ts.map +1 -0
  37. package/dist/adapt/herdr/__tests__/socket-live.test.js +104 -0
  38. package/dist/adapt/herdr/__tests__/socket-live.test.js.map +1 -0
  39. package/dist/adapt/herdr/__tests__/team-map.test.d.ts +2 -0
  40. package/dist/adapt/herdr/__tests__/team-map.test.d.ts.map +1 -0
  41. package/dist/adapt/herdr/__tests__/team-map.test.js +46 -0
  42. package/dist/adapt/herdr/__tests__/team-map.test.js.map +1 -0
  43. package/dist/adapt/herdr/__tests__/transport.test.d.ts +2 -0
  44. package/dist/adapt/herdr/__tests__/transport.test.d.ts.map +1 -0
  45. package/dist/adapt/herdr/__tests__/transport.test.js +160 -0
  46. package/dist/adapt/herdr/__tests__/transport.test.js.map +1 -0
  47. package/dist/adapt/herdr/__tests__/wiring.test.d.ts +2 -0
  48. package/dist/adapt/herdr/__tests__/wiring.test.d.ts.map +1 -0
  49. package/dist/adapt/herdr/__tests__/wiring.test.js +118 -0
  50. package/dist/adapt/herdr/__tests__/wiring.test.js.map +1 -0
  51. package/dist/adapt/herdr/authority.d.ts +34 -0
  52. package/dist/adapt/herdr/authority.d.ts.map +1 -0
  53. package/dist/adapt/herdr/authority.js +71 -0
  54. package/dist/adapt/herdr/authority.js.map +1 -0
  55. package/dist/adapt/herdr/bridge.d.ts +84 -0
  56. package/dist/adapt/herdr/bridge.d.ts.map +1 -0
  57. package/dist/adapt/herdr/bridge.js +222 -0
  58. package/dist/adapt/herdr/bridge.js.map +1 -0
  59. package/dist/adapt/herdr/sanitize.d.ts +20 -0
  60. package/dist/adapt/herdr/sanitize.d.ts.map +1 -0
  61. package/dist/adapt/herdr/sanitize.js +70 -0
  62. package/dist/adapt/herdr/sanitize.js.map +1 -0
  63. package/dist/adapt/herdr/semantic.d.ts +67 -0
  64. package/dist/adapt/herdr/semantic.d.ts.map +1 -0
  65. package/dist/adapt/herdr/semantic.js +170 -0
  66. package/dist/adapt/herdr/semantic.js.map +1 -0
  67. package/dist/adapt/herdr/seq-store.d.ts +32 -0
  68. package/dist/adapt/herdr/seq-store.d.ts.map +1 -0
  69. package/dist/adapt/herdr/seq-store.js +126 -0
  70. package/dist/adapt/herdr/seq-store.js.map +1 -0
  71. package/dist/adapt/herdr/team-map.d.ts +30 -0
  72. package/dist/adapt/herdr/team-map.d.ts.map +1 -0
  73. package/dist/adapt/herdr/team-map.js +37 -0
  74. package/dist/adapt/herdr/team-map.js.map +1 -0
  75. package/dist/adapt/herdr/transport.d.ts +115 -0
  76. package/dist/adapt/herdr/transport.d.ts.map +1 -0
  77. package/dist/adapt/herdr/transport.js +260 -0
  78. package/dist/adapt/herdr/transport.js.map +1 -0
  79. package/dist/adapt/herdr/wiring.d.ts +24 -0
  80. package/dist/adapt/herdr/wiring.d.ts.map +1 -0
  81. package/dist/adapt/herdr/wiring.js +62 -0
  82. package/dist/adapt/herdr/wiring.js.map +1 -0
  83. package/dist/adapt/herdr.d.ts +19 -0
  84. package/dist/adapt/herdr.d.ts.map +1 -0
  85. package/dist/adapt/herdr.js +115 -0
  86. package/dist/adapt/herdr.js.map +1 -0
  87. package/dist/adapt/index.d.ts +4 -0
  88. package/dist/adapt/index.d.ts.map +1 -1
  89. package/dist/adapt/index.js +48 -0
  90. package/dist/adapt/index.js.map +1 -1
  91. package/dist/adapt/registry.d.ts.map +1 -1
  92. package/dist/adapt/registry.js +11 -0
  93. package/dist/adapt/registry.js.map +1 -1
  94. package/dist/auth/__tests__/hotswap.test.js +60 -1
  95. package/dist/auth/__tests__/hotswap.test.js.map +1 -1
  96. package/dist/auth/__tests__/storage.test.js +33 -1
  97. package/dist/auth/__tests__/storage.test.js.map +1 -1
  98. package/dist/auth/hotswap.d.ts +16 -2
  99. package/dist/auth/hotswap.d.ts.map +1 -1
  100. package/dist/auth/hotswap.js +22 -7
  101. package/dist/auth/hotswap.js.map +1 -1
  102. package/dist/auth/storage.js +1 -1
  103. package/dist/auth/storage.js.map +1 -1
  104. package/dist/autopilot/__tests__/ralplan-gate.test.js +86 -1333
  105. package/dist/autopilot/__tests__/ralplan-gate.test.js.map +1 -1
  106. package/dist/autopilot/ralplan-gate.d.ts.map +1 -1
  107. package/dist/autopilot/ralplan-gate.js +7 -6
  108. package/dist/autopilot/ralplan-gate.js.map +1 -1
  109. package/dist/cli/__tests__/agents-init.test.js +4 -23
  110. package/dist/cli/__tests__/agents-init.test.js.map +1 -1
  111. package/dist/cli/__tests__/api.test.js +61 -24
  112. package/dist/cli/__tests__/api.test.js.map +1 -1
  113. package/dist/cli/__tests__/auth.test.js +3 -1
  114. package/dist/cli/__tests__/auth.test.js.map +1 -1
  115. package/dist/cli/__tests__/codex-feature-probe.test.js +66 -1
  116. package/dist/cli/__tests__/codex-feature-probe.test.js.map +1 -1
  117. package/dist/cli/__tests__/codex-plugin-layout.test.js +70 -2
  118. package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
  119. package/dist/cli/__tests__/detached-leader-teardown.test.d.ts +2 -0
  120. package/dist/cli/__tests__/detached-leader-teardown.test.d.ts.map +1 -0
  121. package/dist/cli/__tests__/detached-leader-teardown.test.js +473 -0
  122. package/dist/cli/__tests__/detached-leader-teardown.test.js.map +1 -0
  123. package/dist/cli/__tests__/doctor-process-identity-readiness.test.d.ts +2 -0
  124. package/dist/cli/__tests__/doctor-process-identity-readiness.test.d.ts.map +1 -0
  125. package/dist/cli/__tests__/doctor-process-identity-readiness.test.js +103 -0
  126. package/dist/cli/__tests__/doctor-process-identity-readiness.test.js.map +1 -0
  127. package/dist/cli/__tests__/doctor-state-root-binding.test.d.ts +2 -0
  128. package/dist/cli/__tests__/doctor-state-root-binding.test.d.ts.map +1 -0
  129. package/dist/cli/__tests__/doctor-state-root-binding.test.js +139 -0
  130. package/dist/cli/__tests__/doctor-state-root-binding.test.js.map +1 -0
  131. package/dist/cli/__tests__/doctor-warning-copy.test.js +35 -3
  132. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  133. package/dist/cli/__tests__/exec.test.js +75 -0
  134. package/dist/cli/__tests__/exec.test.js.map +1 -1
  135. package/dist/cli/__tests__/index.test.js +621 -460
  136. package/dist/cli/__tests__/index.test.js.map +1 -1
  137. package/dist/cli/__tests__/launch-fallback.test.js +1020 -143
  138. package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
  139. package/dist/cli/__tests__/mcp-parity.test.js +4 -3
  140. package/dist/cli/__tests__/mcp-parity.test.js.map +1 -1
  141. package/dist/cli/__tests__/native-assets.test.js +398 -16
  142. package/dist/cli/__tests__/native-assets.test.js.map +1 -1
  143. package/dist/cli/__tests__/native-hook-claim-journal.test.js +53 -5
  144. package/dist/cli/__tests__/native-hook-claim-journal.test.js.map +1 -1
  145. package/dist/cli/__tests__/ralph.test.js +2 -2
  146. package/dist/cli/__tests__/ralph.test.js.map +1 -1
  147. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.js +57 -57
  148. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.js.map +1 -1
  149. package/dist/cli/__tests__/ralplan-child-binding-3212.test.d.ts +2 -0
  150. package/dist/cli/__tests__/ralplan-child-binding-3212.test.d.ts.map +1 -0
  151. package/dist/cli/__tests__/ralplan-child-binding-3212.test.js +56 -0
  152. package/dist/cli/__tests__/ralplan-child-binding-3212.test.js.map +1 -0
  153. package/dist/cli/__tests__/ralplan.test.js +84 -12
  154. package/dist/cli/__tests__/ralplan.test.js.map +1 -1
  155. package/dist/cli/__tests__/session-scoped-runtime.test.js +1118 -43
  156. package/dist/cli/__tests__/session-scoped-runtime.test.js.map +1 -1
  157. package/dist/cli/__tests__/setup-agents-overwrite.test.js +8 -55
  158. package/dist/cli/__tests__/setup-agents-overwrite.test.js.map +1 -1
  159. package/dist/cli/__tests__/setup-install-mode.test.js +183 -4
  160. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  161. package/dist/cli/__tests__/sparkshell-cli.test.js +93 -41
  162. package/dist/cli/__tests__/sparkshell-cli.test.js.map +1 -1
  163. package/dist/cli/__tests__/team.test.js +45 -1
  164. package/dist/cli/__tests__/team.test.js.map +1 -1
  165. package/dist/cli/__tests__/ultragoal.test.js +117 -2
  166. package/dist/cli/__tests__/ultragoal.test.js.map +1 -1
  167. package/dist/cli/__tests__/uninstall.test.js +50 -5
  168. package/dist/cli/__tests__/uninstall.test.js.map +1 -1
  169. package/dist/cli/__tests__/update.test.js +669 -177
  170. package/dist/cli/__tests__/update.test.js.map +1 -1
  171. package/dist/cli/__tests__/windows-popup-loop-contract.test.js +32 -2
  172. package/dist/cli/__tests__/windows-popup-loop-contract.test.js.map +1 -1
  173. package/dist/cli/api.d.ts.map +1 -1
  174. package/dist/cli/api.js +9 -10
  175. package/dist/cli/api.js.map +1 -1
  176. package/dist/cli/codex-feature-probe.d.ts +16 -0
  177. package/dist/cli/codex-feature-probe.d.ts.map +1 -1
  178. package/dist/cli/codex-feature-probe.js +44 -0
  179. package/dist/cli/codex-feature-probe.js.map +1 -1
  180. package/dist/cli/codex-home.d.ts.map +1 -1
  181. package/dist/cli/codex-home.js +7 -4
  182. package/dist/cli/codex-home.js.map +1 -1
  183. package/dist/cli/doctor.d.ts +17 -0
  184. package/dist/cli/doctor.d.ts.map +1 -1
  185. package/dist/cli/doctor.js +277 -3
  186. package/dist/cli/doctor.js.map +1 -1
  187. package/dist/cli/index.d.ts +231 -12
  188. package/dist/cli/index.d.ts.map +1 -1
  189. package/dist/cli/index.js +2298 -735
  190. package/dist/cli/index.js.map +1 -1
  191. package/dist/cli/native-assets.d.ts +25 -0
  192. package/dist/cli/native-assets.d.ts.map +1 -1
  193. package/dist/cli/native-assets.js +448 -51
  194. package/dist/cli/native-assets.js.map +1 -1
  195. package/dist/cli/native-hook-claim-journal.d.ts +8 -4
  196. package/dist/cli/native-hook-claim-journal.d.ts.map +1 -1
  197. package/dist/cli/native-hook-claim-journal.js +30 -8
  198. package/dist/cli/native-hook-claim-journal.js.map +1 -1
  199. package/dist/cli/package-manager-ownership.d.ts +63 -0
  200. package/dist/cli/package-manager-ownership.d.ts.map +1 -0
  201. package/dist/cli/package-manager-ownership.js +323 -0
  202. package/dist/cli/package-manager-ownership.js.map +1 -0
  203. package/dist/cli/ralph.js +2 -2
  204. package/dist/cli/ralph.js.map +1 -1
  205. package/dist/cli/ralplan.d.ts +4 -2
  206. package/dist/cli/ralplan.d.ts.map +1 -1
  207. package/dist/cli/ralplan.js +76 -34
  208. package/dist/cli/ralplan.js.map +1 -1
  209. package/dist/cli/setup-preferences.d.ts +15 -0
  210. package/dist/cli/setup-preferences.d.ts.map +1 -1
  211. package/dist/cli/setup-preferences.js +27 -1
  212. package/dist/cli/setup-preferences.js.map +1 -1
  213. package/dist/cli/setup.d.ts +3 -0
  214. package/dist/cli/setup.d.ts.map +1 -1
  215. package/dist/cli/setup.js +55 -32
  216. package/dist/cli/setup.js.map +1 -1
  217. package/dist/cli/sparkshell.d.ts.map +1 -1
  218. package/dist/cli/sparkshell.js +52 -9
  219. package/dist/cli/sparkshell.js.map +1 -1
  220. package/dist/cli/ultragoal.d.ts.map +1 -1
  221. package/dist/cli/ultragoal.js +2 -2
  222. package/dist/cli/ultragoal.js.map +1 -1
  223. package/dist/cli/uninstall.d.ts.map +1 -1
  224. package/dist/cli/uninstall.js +27 -28
  225. package/dist/cli/uninstall.js.map +1 -1
  226. package/dist/cli/update-worker.d.ts +2 -0
  227. package/dist/cli/update-worker.d.ts.map +1 -0
  228. package/dist/cli/update-worker.js +142 -0
  229. package/dist/cli/update-worker.js.map +1 -0
  230. package/dist/cli/update.d.ts +14 -22
  231. package/dist/cli/update.d.ts.map +1 -1
  232. package/dist/cli/update.js +192 -241
  233. package/dist/cli/update.js.map +1 -1
  234. package/dist/config/__tests__/generator-idempotent.test.js +173 -29
  235. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  236. package/dist/config/__tests__/generator-notify.test.js +3 -1
  237. package/dist/config/__tests__/generator-notify.test.js.map +1 -1
  238. package/dist/config/generator.d.ts +25 -3
  239. package/dist/config/generator.d.ts.map +1 -1
  240. package/dist/config/generator.js +113 -35
  241. package/dist/config/generator.js.map +1 -1
  242. package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.js +25 -0
  243. package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.js.map +1 -1
  244. package/dist/goal-workflows/codex-goal-snapshot.d.ts +1 -1
  245. package/dist/goal-workflows/codex-goal-snapshot.d.ts.map +1 -1
  246. package/dist/goal-workflows/codex-goal-snapshot.js +2 -0
  247. package/dist/goal-workflows/codex-goal-snapshot.js.map +1 -1
  248. package/dist/hooks/__tests__/agents-overlay.test.js +3 -1
  249. package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
  250. package/dist/hooks/__tests__/autopilot-skill-contract.test.js +27 -4
  251. package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
  252. package/dist/hooks/__tests__/issue-3293-autopilot-handoff.test.d.ts +2 -0
  253. package/dist/hooks/__tests__/issue-3293-autopilot-handoff.test.d.ts.map +1 -0
  254. package/dist/hooks/__tests__/issue-3293-autopilot-handoff.test.js +172 -0
  255. package/dist/hooks/__tests__/issue-3293-autopilot-handoff.test.js.map +1 -0
  256. package/dist/hooks/__tests__/keyword-detector.test.js +172 -33
  257. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  258. package/dist/hooks/__tests__/notify-fallback-watcher.test.js +173 -11
  259. package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
  260. package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js +1 -0
  261. package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js.map +1 -1
  262. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +65 -17
  263. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
  264. package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js +27 -0
  265. package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js.map +1 -1
  266. package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js +10 -118
  267. package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js.map +1 -1
  268. package/dist/hooks/__tests__/notify-hook-regression-205.test.js +7 -0
  269. package/dist/hooks/__tests__/notify-hook-regression-205.test.js.map +1 -1
  270. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js +2 -2
  271. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
  272. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +4 -1
  273. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
  274. package/dist/hooks/__tests__/notify-hook-team-worker-fail-closed.test.js +2 -1
  275. package/dist/hooks/__tests__/notify-hook-team-worker-fail-closed.test.js.map +1 -1
  276. package/dist/hooks/__tests__/notify-hook-worker-idle.test.js +1 -0
  277. package/dist/hooks/__tests__/notify-hook-worker-idle.test.js.map +1 -1
  278. package/dist/hooks/__tests__/prompt-guidance-contract.test.js +9 -0
  279. package/dist/hooks/__tests__/prompt-guidance-contract.test.js.map +1 -1
  280. package/dist/hooks/__tests__/session.test.js +2976 -557
  281. package/dist/hooks/__tests__/session.test.js.map +1 -1
  282. package/dist/hooks/agents-overlay.js +1 -1
  283. package/dist/hooks/agents-overlay.js.map +1 -1
  284. package/dist/hooks/extensibility/__tests__/dispatcher.test.js +108 -2
  285. package/dist/hooks/extensibility/__tests__/dispatcher.test.js.map +1 -1
  286. package/dist/hooks/extensibility/__tests__/sdk.test.js +151 -1
  287. package/dist/hooks/extensibility/__tests__/sdk.test.js.map +1 -1
  288. package/dist/hooks/extensibility/dispatcher.d.ts.map +1 -1
  289. package/dist/hooks/extensibility/dispatcher.js +20 -3
  290. package/dist/hooks/extensibility/dispatcher.js.map +1 -1
  291. package/dist/hooks/extensibility/plugin-runner.js +1 -0
  292. package/dist/hooks/extensibility/plugin-runner.js.map +1 -1
  293. package/dist/hooks/extensibility/runtime.d.ts.map +1 -1
  294. package/dist/hooks/extensibility/runtime.js.map +1 -1
  295. package/dist/hooks/extensibility/sdk/paths.d.ts +1 -1
  296. package/dist/hooks/extensibility/sdk/paths.d.ts.map +1 -1
  297. package/dist/hooks/extensibility/sdk/paths.js +2 -2
  298. package/dist/hooks/extensibility/sdk/paths.js.map +1 -1
  299. package/dist/hooks/extensibility/sdk/runtime-state.d.ts +1 -1
  300. package/dist/hooks/extensibility/sdk/runtime-state.d.ts.map +1 -1
  301. package/dist/hooks/extensibility/sdk/runtime-state.js +55 -15
  302. package/dist/hooks/extensibility/sdk/runtime-state.js.map +1 -1
  303. package/dist/hooks/extensibility/sdk.d.ts +1 -0
  304. package/dist/hooks/extensibility/sdk.d.ts.map +1 -1
  305. package/dist/hooks/extensibility/sdk.js +1 -1
  306. package/dist/hooks/extensibility/sdk.js.map +1 -1
  307. package/dist/hooks/extensibility/types.d.ts +7 -0
  308. package/dist/hooks/extensibility/types.d.ts.map +1 -1
  309. package/dist/hooks/keyword-detector.d.ts +10 -3
  310. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  311. package/dist/hooks/keyword-detector.js +116 -20
  312. package/dist/hooks/keyword-detector.js.map +1 -1
  313. package/dist/hooks/session.d.ts +180 -14
  314. package/dist/hooks/session.d.ts.map +1 -1
  315. package/dist/hooks/session.js +1402 -281
  316. package/dist/hooks/session.js.map +1 -1
  317. package/dist/hud/__tests__/hud-tmux-injection.test.js +6 -6
  318. package/dist/hud/__tests__/hud-tmux-injection.test.js.map +1 -1
  319. package/dist/hud/__tests__/index.test.js +26 -16
  320. package/dist/hud/__tests__/index.test.js.map +1 -1
  321. package/dist/hud/__tests__/reconcile.test.js +83 -78
  322. package/dist/hud/__tests__/reconcile.test.js.map +1 -1
  323. package/dist/hud/__tests__/state.test.js +36 -31
  324. package/dist/hud/__tests__/state.test.js.map +1 -1
  325. package/dist/hud/__tests__/tmux.test.js +155 -198
  326. package/dist/hud/__tests__/tmux.test.js.map +1 -1
  327. package/dist/hud/tmux.d.ts +42 -4
  328. package/dist/hud/tmux.d.ts.map +1 -1
  329. package/dist/hud/tmux.js +1017 -139
  330. package/dist/hud/tmux.js.map +1 -1
  331. package/dist/leader/__tests__/contract.test.js +71 -34
  332. package/dist/leader/__tests__/contract.test.js.map +1 -1
  333. package/dist/leader/contract.d.ts +1 -6
  334. package/dist/leader/contract.d.ts.map +1 -1
  335. package/dist/leader/contract.js +25 -24
  336. package/dist/leader/contract.js.map +1 -1
  337. package/dist/mcp/__tests__/hermes-bridge.test.js +17 -0
  338. package/dist/mcp/__tests__/hermes-bridge.test.js.map +1 -1
  339. package/dist/mcp/__tests__/server-lifecycle.test.js +4 -4
  340. package/dist/mcp/__tests__/state-paths.test.js +735 -4
  341. package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
  342. package/dist/mcp/__tests__/state-server.test.js +1 -1
  343. package/dist/mcp/__tests__/state-server.test.js.map +1 -1
  344. package/dist/mcp/__tests__/trace-server.test.js +2 -2
  345. package/dist/mcp/__tests__/trace-server.test.js.map +1 -1
  346. package/dist/mcp/hermes-bridge.d.ts.map +1 -1
  347. package/dist/mcp/hermes-bridge.js +2 -2
  348. package/dist/mcp/hermes-bridge.js.map +1 -1
  349. package/dist/mcp/state-paths.d.ts +78 -4
  350. package/dist/mcp/state-paths.d.ts.map +1 -1
  351. package/dist/mcp/state-paths.js +543 -40
  352. package/dist/mcp/state-paths.js.map +1 -1
  353. package/dist/mcp/state-server.d.ts +4 -4
  354. package/dist/mcp/state-server.d.ts.map +1 -1
  355. package/dist/modes/__tests__/base-autopilot-gates.test.js +36 -7
  356. package/dist/modes/__tests__/base-autopilot-gates.test.js.map +1 -1
  357. package/dist/modes/__tests__/base-multi-state-compat.test.js +1 -1
  358. package/dist/modes/__tests__/base-multi-state-compat.test.js.map +1 -1
  359. package/dist/modes/__tests__/base-session-scope.test.js +17 -8
  360. package/dist/modes/__tests__/base-session-scope.test.js.map +1 -1
  361. package/dist/modes/base.d.ts +5 -5
  362. package/dist/modes/base.d.ts.map +1 -1
  363. package/dist/modes/base.js +76 -21
  364. package/dist/modes/base.js.map +1 -1
  365. package/dist/native-assets/archive.d.ts +17 -0
  366. package/dist/native-assets/archive.d.ts.map +1 -0
  367. package/dist/native-assets/archive.js +238 -0
  368. package/dist/native-assets/archive.js.map +1 -0
  369. package/dist/native-assets/policy.d.ts +42 -0
  370. package/dist/native-assets/policy.d.ts.map +1 -0
  371. package/dist/native-assets/policy.js +155 -0
  372. package/dist/native-assets/policy.js.map +1 -0
  373. package/dist/pipeline/__tests__/orchestrator.test.js +162 -40
  374. package/dist/pipeline/__tests__/orchestrator.test.js.map +1 -1
  375. package/dist/pipeline/__tests__/stages.test.js +66 -149
  376. package/dist/pipeline/__tests__/stages.test.js.map +1 -1
  377. package/dist/pipeline/orchestrator.d.ts.map +1 -1
  378. package/dist/pipeline/orchestrator.js +124 -18
  379. package/dist/pipeline/orchestrator.js.map +1 -1
  380. package/dist/pipeline/stages/ralplan.d.ts.map +1 -1
  381. package/dist/pipeline/stages/ralplan.js +36 -8
  382. package/dist/pipeline/stages/ralplan.js.map +1 -1
  383. package/dist/ralplan/__tests__/consensus-gate.test.js +70 -1437
  384. package/dist/ralplan/__tests__/consensus-gate.test.js.map +1 -1
  385. package/dist/ralplan/__tests__/documented-leader-preflight.test.js +328 -39
  386. package/dist/ralplan/__tests__/documented-leader-preflight.test.js.map +1 -1
  387. package/dist/ralplan/__tests__/runtime.test.js +82 -102
  388. package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
  389. package/dist/ralplan/consensus-gate.d.ts +9 -1
  390. package/dist/ralplan/consensus-gate.d.ts.map +1 -1
  391. package/dist/ralplan/consensus-gate.js +40 -304
  392. package/dist/ralplan/consensus-gate.js.map +1 -1
  393. package/dist/ralplan/documented-leader-preflight.d.ts +15 -8
  394. package/dist/ralplan/documented-leader-preflight.d.ts.map +1 -1
  395. package/dist/ralplan/documented-leader-preflight.js +356 -54
  396. package/dist/ralplan/documented-leader-preflight.js.map +1 -1
  397. package/dist/ralplan/runtime.d.ts +1 -1
  398. package/dist/ralplan/runtime.d.ts.map +1 -1
  399. package/dist/ralplan/runtime.js +68 -173
  400. package/dist/ralplan/runtime.js.map +1 -1
  401. package/dist/runtime/run-state.d.ts +5 -1
  402. package/dist/runtime/run-state.d.ts.map +1 -1
  403. package/dist/runtime/run-state.js +15 -8
  404. package/dist/runtime/run-state.js.map +1 -1
  405. package/dist/scripts/__tests__/codex-native-hook.test.js +14071 -8859
  406. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  407. package/dist/scripts/__tests__/issue-3293-callsite-parity.test.d.ts +2 -0
  408. package/dist/scripts/__tests__/issue-3293-callsite-parity.test.d.ts.map +1 -0
  409. package/dist/scripts/__tests__/issue-3293-callsite-parity.test.js +225 -0
  410. package/dist/scripts/__tests__/issue-3293-callsite-parity.test.js.map +1 -0
  411. package/dist/scripts/__tests__/issue-3293-cancel-transaction.test.d.ts +2 -0
  412. package/dist/scripts/__tests__/issue-3293-cancel-transaction.test.d.ts.map +1 -0
  413. package/dist/scripts/__tests__/issue-3293-cancel-transaction.test.js +188 -0
  414. package/dist/scripts/__tests__/issue-3293-cancel-transaction.test.js.map +1 -0
  415. package/dist/scripts/__tests__/issue-3293-hook-owned-cancel.test.d.ts +2 -0
  416. package/dist/scripts/__tests__/issue-3293-hook-owned-cancel.test.d.ts.map +1 -0
  417. package/dist/scripts/__tests__/issue-3293-hook-owned-cancel.test.js +259 -0
  418. package/dist/scripts/__tests__/issue-3293-hook-owned-cancel.test.js.map +1 -0
  419. package/dist/scripts/__tests__/issue-3311-ultragoal-native-outside-tmux.test.d.ts +2 -0
  420. package/dist/scripts/__tests__/issue-3311-ultragoal-native-outside-tmux.test.d.ts.map +1 -0
  421. package/dist/scripts/__tests__/issue-3311-ultragoal-native-outside-tmux.test.js +491 -0
  422. package/dist/scripts/__tests__/issue-3311-ultragoal-native-outside-tmux.test.js.map +1 -0
  423. package/dist/scripts/__tests__/issue-3358-ultragoal-cancel-lifecycle.test.d.ts +2 -0
  424. package/dist/scripts/__tests__/issue-3358-ultragoal-cancel-lifecycle.test.d.ts.map +1 -0
  425. package/dist/scripts/__tests__/issue-3358-ultragoal-cancel-lifecycle.test.js +708 -0
  426. package/dist/scripts/__tests__/issue-3358-ultragoal-cancel-lifecycle.test.js.map +1 -0
  427. package/dist/scripts/__tests__/issue-3369-autopilot-ralplan-state-machine.test.d.ts +2 -0
  428. package/dist/scripts/__tests__/issue-3369-autopilot-ralplan-state-machine.test.d.ts.map +1 -0
  429. package/dist/scripts/__tests__/issue-3369-autopilot-ralplan-state-machine.test.js +208 -0
  430. package/dist/scripts/__tests__/issue-3369-autopilot-ralplan-state-machine.test.js.map +1 -0
  431. package/dist/scripts/__tests__/notify-state-io.test.js +26 -0
  432. package/dist/scripts/__tests__/notify-state-io.test.js.map +1 -1
  433. package/dist/scripts/__tests__/notify-tmux-injection.test.js +23 -1
  434. package/dist/scripts/__tests__/notify-tmux-injection.test.js.map +1 -1
  435. package/dist/scripts/__tests__/postinstall.test.js +19 -1
  436. package/dist/scripts/__tests__/postinstall.test.js.map +1 -1
  437. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.js +25 -8
  438. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.js.map +1 -1
  439. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.js +60 -25
  440. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.js.map +1 -1
  441. package/dist/scripts/__tests__/smoke-packed-install.test.js +155 -16
  442. package/dist/scripts/__tests__/smoke-packed-install.test.js.map +1 -1
  443. package/dist/scripts/__tests__/verify-native-release-assets.test.d.ts +2 -0
  444. package/dist/scripts/__tests__/verify-native-release-assets.test.d.ts.map +1 -0
  445. package/dist/scripts/__tests__/verify-native-release-assets.test.js +235 -0
  446. package/dist/scripts/__tests__/verify-native-release-assets.test.js.map +1 -0
  447. package/dist/scripts/codex-native-hook.d.ts +50 -1
  448. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  449. package/dist/scripts/codex-native-hook.js +3559 -451
  450. package/dist/scripts/codex-native-hook.js.map +1 -1
  451. package/dist/scripts/codex-native-pre-post.d.ts +9 -0
  452. package/dist/scripts/codex-native-pre-post.d.ts.map +1 -1
  453. package/dist/scripts/codex-native-pre-post.js +35 -12
  454. package/dist/scripts/codex-native-pre-post.js.map +1 -1
  455. package/dist/scripts/generate-native-release-manifest.js +80 -51
  456. package/dist/scripts/generate-native-release-manifest.js.map +1 -1
  457. package/dist/scripts/notify-fallback-watcher.js +28 -39
  458. package/dist/scripts/notify-fallback-watcher.js.map +1 -1
  459. package/dist/scripts/notify-hook/state-io.d.ts.map +1 -1
  460. package/dist/scripts/notify-hook/state-io.js +5 -0
  461. package/dist/scripts/notify-hook/state-io.js.map +1 -1
  462. package/dist/scripts/notify-hook/team-leader-nudge.d.ts.map +1 -1
  463. package/dist/scripts/notify-hook/team-leader-nudge.js +32 -3
  464. package/dist/scripts/notify-hook/team-leader-nudge.js.map +1 -1
  465. package/dist/scripts/notify-hook/team-worker-stop.d.ts.map +1 -1
  466. package/dist/scripts/notify-hook/team-worker-stop.js +23 -3
  467. package/dist/scripts/notify-hook/team-worker-stop.js.map +1 -1
  468. package/dist/scripts/notify-hook/team-worker.d.ts.map +1 -1
  469. package/dist/scripts/notify-hook/team-worker.js +45 -5
  470. package/dist/scripts/notify-hook/team-worker.js.map +1 -1
  471. package/dist/scripts/notify-hook/tmux-injection.d.ts +1 -0
  472. package/dist/scripts/notify-hook/tmux-injection.d.ts.map +1 -1
  473. package/dist/scripts/notify-hook/tmux-injection.js +4 -1
  474. package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
  475. package/dist/scripts/notify-hook.js +78 -82
  476. package/dist/scripts/notify-hook.js.map +1 -1
  477. package/dist/scripts/postinstall-advisory.d.ts +14 -0
  478. package/dist/scripts/postinstall-advisory.d.ts.map +1 -0
  479. package/dist/scripts/postinstall-advisory.js +49 -0
  480. package/dist/scripts/postinstall-advisory.js.map +1 -0
  481. package/dist/scripts/postinstall.d.ts +1 -1
  482. package/dist/scripts/postinstall.d.ts.map +1 -1
  483. package/dist/scripts/postinstall.js +1 -1
  484. package/dist/scripts/postinstall.js.map +1 -1
  485. package/dist/scripts/smoke-packed-install.d.ts +32 -1
  486. package/dist/scripts/smoke-packed-install.d.ts.map +1 -1
  487. package/dist/scripts/smoke-packed-install.js +413 -60
  488. package/dist/scripts/smoke-packed-install.js.map +1 -1
  489. package/dist/scripts/sync-plugin-mirror.d.ts.map +1 -1
  490. package/dist/scripts/sync-plugin-mirror.js +16 -1
  491. package/dist/scripts/sync-plugin-mirror.js.map +1 -1
  492. package/dist/scripts/verify-native-release-assets.js +37 -23
  493. package/dist/scripts/verify-native-release-assets.js.map +1 -1
  494. package/dist/state/__tests__/mode-state-context.test.js +12 -5
  495. package/dist/state/__tests__/mode-state-context.test.js.map +1 -1
  496. package/dist/state/__tests__/operations.test.js +460 -850
  497. package/dist/state/__tests__/operations.test.js.map +1 -1
  498. package/dist/state/__tests__/skill-active.test.js +400 -2
  499. package/dist/state/__tests__/skill-active.test.js.map +1 -1
  500. package/dist/state/mode-state-context.d.ts +3 -0
  501. package/dist/state/mode-state-context.d.ts.map +1 -1
  502. package/dist/state/mode-state-context.js +27 -13
  503. package/dist/state/mode-state-context.js.map +1 -1
  504. package/dist/state/operations.d.ts +3 -0
  505. package/dist/state/operations.d.ts.map +1 -1
  506. package/dist/state/operations.js +144 -40
  507. package/dist/state/operations.js.map +1 -1
  508. package/dist/state/skill-active.d.ts +18 -1
  509. package/dist/state/skill-active.d.ts.map +1 -1
  510. package/dist/state/skill-active.js +420 -23
  511. package/dist/state/skill-active.js.map +1 -1
  512. package/dist/state/workflow-transition-reconcile.d.ts +3 -0
  513. package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
  514. package/dist/state/workflow-transition-reconcile.js +10 -4
  515. package/dist/state/workflow-transition-reconcile.js.map +1 -1
  516. package/dist/state/workflow-transition.d.ts.map +1 -1
  517. package/dist/state/workflow-transition.js +3 -1
  518. package/dist/state/workflow-transition.js.map +1 -1
  519. package/dist/subagents/__tests__/leader-bootstrap-3181.test.js +27 -0
  520. package/dist/subagents/__tests__/leader-bootstrap-3181.test.js.map +1 -1
  521. package/dist/subagents/__tests__/tracker.test.js +837 -1048
  522. package/dist/subagents/__tests__/tracker.test.js.map +1 -1
  523. package/dist/subagents/tracker.d.ts +64 -60
  524. package/dist/subagents/tracker.d.ts.map +1 -1
  525. package/dist/subagents/tracker.js +809 -358
  526. package/dist/subagents/tracker.js.map +1 -1
  527. package/dist/team/__tests__/delivery-e2e-smoke.test.js +6 -7
  528. package/dist/team/__tests__/delivery-e2e-smoke.test.js.map +1 -1
  529. package/dist/team/__tests__/mcp-comm.test.js +104 -3
  530. package/dist/team/__tests__/mcp-comm.test.js.map +1 -1
  531. package/dist/team/__tests__/notice-ledger.test.d.ts +2 -0
  532. package/dist/team/__tests__/notice-ledger.test.d.ts.map +1 -0
  533. package/dist/team/__tests__/notice-ledger.test.js +131 -0
  534. package/dist/team/__tests__/notice-ledger.test.js.map +1 -0
  535. package/dist/team/__tests__/pane-teardown-residue-e2e.test.d.ts +2 -0
  536. package/dist/team/__tests__/pane-teardown-residue-e2e.test.d.ts.map +1 -0
  537. package/dist/team/__tests__/pane-teardown-residue-e2e.test.js +410 -0
  538. package/dist/team/__tests__/pane-teardown-residue-e2e.test.js.map +1 -0
  539. package/dist/team/__tests__/phase-controller.test.js +20 -0
  540. package/dist/team/__tests__/phase-controller.test.js.map +1 -1
  541. package/dist/team/__tests__/runtime.test.js +725 -59
  542. package/dist/team/__tests__/runtime.test.js.map +1 -1
  543. package/dist/team/__tests__/scaling.test.js +222 -237
  544. package/dist/team/__tests__/scaling.test.js.map +1 -1
  545. package/dist/team/__tests__/state-root.test.js +117 -2
  546. package/dist/team/__tests__/state-root.test.js.map +1 -1
  547. package/dist/team/__tests__/tmux-session.test.js +887 -92
  548. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  549. package/dist/team/__tests__/tmux-source-authority-realtmux.test.d.ts +2 -0
  550. package/dist/team/__tests__/tmux-source-authority-realtmux.test.d.ts.map +1 -0
  551. package/dist/team/__tests__/tmux-source-authority-realtmux.test.js +149 -0
  552. package/dist/team/__tests__/tmux-source-authority-realtmux.test.js.map +1 -0
  553. package/dist/team/__tests__/tmux-test-fixture.d.ts +69 -0
  554. package/dist/team/__tests__/tmux-test-fixture.d.ts.map +1 -1
  555. package/dist/team/__tests__/tmux-test-fixture.js +242 -15
  556. package/dist/team/__tests__/tmux-test-fixture.js.map +1 -1
  557. package/dist/team/__tests__/tmux-test-fixture.test.js +177 -1
  558. package/dist/team/__tests__/tmux-test-fixture.test.js.map +1 -1
  559. package/dist/team/__tests__/worker-runtime-identity.test.js +45 -72
  560. package/dist/team/__tests__/worker-runtime-identity.test.js.map +1 -1
  561. package/dist/team/notice-ledger.d.ts +54 -0
  562. package/dist/team/notice-ledger.d.ts.map +1 -0
  563. package/dist/team/notice-ledger.js +259 -0
  564. package/dist/team/notice-ledger.js.map +1 -0
  565. package/dist/team/phase-controller.d.ts.map +1 -1
  566. package/dist/team/phase-controller.js +2 -0
  567. package/dist/team/phase-controller.js.map +1 -1
  568. package/dist/team/runtime.d.ts +3 -1
  569. package/dist/team/runtime.d.ts.map +1 -1
  570. package/dist/team/runtime.js +314 -142
  571. package/dist/team/runtime.js.map +1 -1
  572. package/dist/team/scaling.d.ts.map +1 -1
  573. package/dist/team/scaling.js +161 -91
  574. package/dist/team/scaling.js.map +1 -1
  575. package/dist/team/state/monitor.d.ts +10 -0
  576. package/dist/team/state/monitor.d.ts.map +1 -1
  577. package/dist/team/state/monitor.js +13 -0
  578. package/dist/team/state/monitor.js.map +1 -1
  579. package/dist/team/state/types.d.ts +15 -0
  580. package/dist/team/state/types.d.ts.map +1 -1
  581. package/dist/team/state/types.js.map +1 -1
  582. package/dist/team/state-root.d.ts +12 -0
  583. package/dist/team/state-root.d.ts.map +1 -1
  584. package/dist/team/state-root.js +177 -0
  585. package/dist/team/state-root.js.map +1 -1
  586. package/dist/team/state.d.ts +18 -0
  587. package/dist/team/state.d.ts.map +1 -1
  588. package/dist/team/state.js +36 -0
  589. package/dist/team/state.js.map +1 -1
  590. package/dist/team/tmux-session.d.ts +40 -4
  591. package/dist/team/tmux-session.d.ts.map +1 -1
  592. package/dist/team/tmux-session.js +513 -166
  593. package/dist/team/tmux-session.js.map +1 -1
  594. package/dist/ultragoal/__tests__/artifacts.test.js +950 -2
  595. package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
  596. package/dist/ultragoal/__tests__/docs-contract.test.js +1 -0
  597. package/dist/ultragoal/__tests__/docs-contract.test.js.map +1 -1
  598. package/dist/ultragoal/artifacts.d.ts +35 -1
  599. package/dist/ultragoal/artifacts.d.ts.map +1 -1
  600. package/dist/ultragoal/artifacts.js +232 -46
  601. package/dist/ultragoal/artifacts.js.map +1 -1
  602. package/dist/utils/__tests__/file-durability.test.js +58 -1
  603. package/dist/utils/__tests__/file-durability.test.js.map +1 -1
  604. package/dist/utils/__tests__/paths.test.js +21 -1
  605. package/dist/utils/__tests__/paths.test.js.map +1 -1
  606. package/dist/utils/file-durability.d.ts +10 -0
  607. package/dist/utils/file-durability.d.ts.map +1 -1
  608. package/dist/utils/file-durability.js +34 -4
  609. package/dist/utils/file-durability.js.map +1 -1
  610. package/dist/utils/paths.d.ts.map +1 -1
  611. package/dist/utils/paths.js +14 -6
  612. package/dist/utils/paths.js.map +1 -1
  613. package/dist/verification/__tests__/ci-rust-gates.test.js +49 -4
  614. package/dist/verification/__tests__/ci-rust-gates.test.js.map +1 -1
  615. package/dist/verification/__tests__/explore-harness-release-workflow.test.js +60 -0
  616. package/dist/verification/__tests__/explore-harness-release-workflow.test.js.map +1 -1
  617. package/dist/verification/__tests__/native-release-manifest.test.js +157 -4
  618. package/dist/verification/__tests__/native-release-manifest.test.js.map +1 -1
  619. package/package.json +13 -5
  620. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  621. package/plugins/oh-my-codex/hooks/codex-native-hook.mjs +81 -17
  622. package/plugins/oh-my-codex/skills/autopilot/SKILL.md +13 -5
  623. package/plugins/oh-my-codex/skills/cancel/SKILL.md +30 -85
  624. package/plugins/oh-my-codex/skills/ralph/SKILL.md +5 -5
  625. package/plugins/oh-my-codex/skills/ralplan/SKILL.md +15 -17
  626. package/plugins/oh-my-codex/skills/ultragoal/SKILL.md +3 -2
  627. package/skills/autopilot/SKILL.md +13 -5
  628. package/skills/cancel/SKILL.md +30 -85
  629. package/skills/ralph/SKILL.md +5 -5
  630. package/skills/ralplan/SKILL.md +15 -17
  631. package/skills/ultragoal/SKILL.md +3 -2
  632. package/src/scripts/__tests__/codex-native-hook.test.ts +6091 -540
  633. package/src/scripts/__tests__/issue-3293-callsite-parity.test.ts +187 -0
  634. package/src/scripts/__tests__/issue-3293-cancel-transaction.test.ts +189 -0
  635. package/src/scripts/__tests__/issue-3293-hook-owned-cancel.test.ts +187 -0
  636. package/src/scripts/__tests__/issue-3311-ultragoal-native-outside-tmux.test.ts +591 -0
  637. package/src/scripts/__tests__/issue-3358-ultragoal-cancel-lifecycle.test.ts +701 -0
  638. package/src/scripts/__tests__/issue-3369-autopilot-ralplan-state-machine.test.ts +211 -0
  639. package/src/scripts/__tests__/notify-state-io.test.ts +29 -0
  640. package/src/scripts/__tests__/notify-tmux-injection.test.ts +26 -1
  641. package/src/scripts/__tests__/postinstall.test.ts +23 -1
  642. package/src/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.ts +27 -8
  643. package/src/scripts/__tests__/role-intent-durable-recovery-3181.test.ts +61 -27
  644. package/src/scripts/__tests__/smoke-packed-install.test.ts +209 -18
  645. package/src/scripts/__tests__/verify-native-release-assets.test.ts +240 -0
  646. package/src/scripts/codex-native-hook.ts +3638 -475
  647. package/src/scripts/codex-native-pre-post.ts +43 -12
  648. package/src/scripts/generate-native-release-manifest.ts +83 -79
  649. package/src/scripts/notify-fallback-watcher.ts +38 -38
  650. package/src/scripts/notify-hook/state-io.ts +11 -0
  651. package/src/scripts/notify-hook/team-leader-nudge.ts +32 -4
  652. package/src/scripts/notify-hook/team-worker-stop.ts +23 -4
  653. package/src/scripts/notify-hook/team-worker.ts +43 -5
  654. package/src/scripts/notify-hook/tmux-injection.ts +5 -1
  655. package/src/scripts/notify-hook.ts +83 -82
  656. package/src/scripts/postinstall-advisory.ts +67 -0
  657. package/src/scripts/postinstall.ts +1 -1
  658. package/src/scripts/smoke-packed-install.ts +446 -59
  659. package/src/scripts/sync-plugin-mirror.ts +17 -1
  660. package/src/scripts/verify-native-release-assets.ts +39 -31
  661. package/templates/AGENTS.md +1 -0
@@ -1,20 +1,54 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { closeSync, existsSync, ftruncateSync, linkSync, mkdirSync, openSync, readdirSync, readFileSync, renameSync, unlinkSync, writeFileSync, writeSync } from 'node:fs';
3
- import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
3
+ import { readFile } from 'node:fs/promises';
4
4
  import { hostname } from 'node:os';
5
5
  import { basename, dirname, join } from 'node:path';
6
6
  import { AGENT_DEFINITIONS } from '../agents/definitions.js';
7
- import { getBaseStateDir, getBaseStateDirWithSource } from '../state/paths.js';
8
- import { canonicalizeOriginCwd } from '../leader/contract.js';
7
+ import { getBaseStateDir } from '../state/paths.js';
9
8
  import { codexAgentsDir, projectCodexAgentsDir } from '../utils/paths.js';
10
9
  export const SUBAGENT_TRACKING_SCHEMA_VERSION = 1;
11
10
  export const DEFAULT_SUBAGENT_ACTIVE_WINDOW_MS = 120_000;
12
- export const OMX_ADAPTED_PROVENANCE = 'omx_adapted';
13
11
  export const NATIVE_SUBAGENT_PROVENANCE = 'native_subagent';
12
+ // Legacy descriptive provenance written by pre-authority releases. Tolerated as
13
+ // descriptive-only evidence: it never grants reopen authority and never blocks a
14
+ // newly attested valid direct child, but it is preserved so authority-consistency
15
+ // checks can distinguish recognized legacy data from unknown descriptive views.
16
+ export const DESCRIPTIVE_ADAPTED_PROVENANCE = 'adapted';
14
17
  const KNOWN_TYPED_AGENT_ROLES = new Set(Object.keys(AGENT_DEFINITIONS).map((role) => role.toLowerCase()));
15
18
  export function subagentTrackingPath(cwd) {
16
19
  return join(getBaseStateDir(cwd), 'subagent-tracking.json');
17
20
  }
21
+ /**
22
+ * Authority-relevant projection of a tracker state. The generic descriptive
23
+ * writer is allowed to publish descriptive churn, but it must never roll back
24
+ * an authority decision that another process committed after the caller read
25
+ * its snapshot. Comparing this projection is the durable equivalent of a CAS
26
+ * on the authority surface without forcing every descriptive caller to hold
27
+ * the tracker lock across its own read/modify/write.
28
+ */
29
+ function authorityRevisionOf(state) {
30
+ const sessions = Object.keys(state.sessions).sort().map((sessionId) => {
31
+ const session = state.sessions[sessionId];
32
+ const threads = Object.keys(session.threads).sort().map((threadId) => {
33
+ const thread = session.threads[threadId];
34
+ return [
35
+ threadId,
36
+ thread.kind,
37
+ thread.provenance_kind ?? '',
38
+ thread.direct_child_root_id ?? '',
39
+ thread.direct_child_parent_id ?? '',
40
+ thread.reopen_authority_revoked === true ? '1' : '',
41
+ thread.reopen_authority_conflict_reason ?? '',
42
+ thread.reopen_authority_conflict_at ?? '',
43
+ // Availability gates whether an eligible child is actually emitted as
44
+ // a reopen target, so it is part of the authority surface.
45
+ thread.status ?? '',
46
+ ].join('\u0001');
47
+ });
48
+ return [sessionId, session.leader_thread_id ?? '', ...threads].join('\u0002');
49
+ });
50
+ return sessions.join('\u0003');
51
+ }
18
52
  export function resolveInstalledRoleName(role, codexHomeOverride, projectRootOverride) {
19
53
  const normalizedRole = role.trim().toLowerCase();
20
54
  if (!normalizedRole)
@@ -41,7 +75,6 @@ export function createSubagentTrackingState() {
41
75
  return {
42
76
  schemaVersion: SUBAGENT_TRACKING_SCHEMA_VERSION,
43
77
  sessions: {},
44
- pending_role_intents: [],
45
78
  };
46
79
  }
47
80
  function normalizeSubagentStatus(value) {
@@ -119,40 +152,6 @@ export function isTrustedSubagentThread(session, threadId) {
119
152
  return false;
120
153
  return session.threads[normalizedThreadId]?.kind === 'subagent';
121
154
  }
122
- function normalizePendingRoleIntent(value) {
123
- if (!value || typeof value !== 'object')
124
- return null;
125
- const candidate = value;
126
- const role = readOptionalTrimmedString(candidate.role);
127
- const sessionId = readOptionalTrimmedString(candidate.session_id);
128
- const parentThreadId = readOptionalTrimmedString(candidate.parent_thread_id);
129
- const createdAt = readOptionalTrimmedString(candidate.created_at);
130
- const expiresAt = readOptionalTrimmedString(candidate.expires_at);
131
- if (!role || !sessionId || !parentThreadId || !Object.hasOwn(candidate, 'correlation_token') || !createdAt || !expiresAt)
132
- return null;
133
- if (!Number.isFinite(Date.parse(createdAt)) || !Number.isFinite(Date.parse(expiresAt)))
134
- return null;
135
- const bindingState = candidate.binding_state === 'bound' ? 'bound' : undefined;
136
- const boundAt = readOptionalTrimmedString(candidate.bound_at);
137
- const hasValidBoundAt = Boolean(boundAt && Number.isFinite(Date.parse(boundAt)));
138
- const originCwd = readOptionalTrimmedString(candidate.origin_cwd);
139
- return {
140
- role,
141
- session_id: sessionId,
142
- parent_thread_id: parentThreadId,
143
- // Bound journals are durable security records. Retain malformed credentials verbatim so
144
- // completion can reject them rather than silently converting them into claimant-less data.
145
- correlation_token: candidate.correlation_token,
146
- created_at: createdAt,
147
- expires_at: expiresAt,
148
- ...(bindingState ? { binding_state: bindingState } : {}),
149
- ...(bindingState && Object.hasOwn(candidate, 'binding_claimant_token')
150
- ? { binding_claimant_token: candidate.binding_claimant_token }
151
- : {}),
152
- ...(bindingState && hasValidBoundAt ? { bound_at: boundAt } : {}),
153
- ...(originCwd ? { origin_cwd: originCwd } : {}),
154
- };
155
- }
156
155
  export function normalizeSubagentTrackingState(input) {
157
156
  const base = createSubagentTrackingState();
158
157
  if (!input || typeof input !== 'object')
@@ -190,9 +189,11 @@ export function normalizeSubagentTrackingState(input) {
190
189
  turn_count: typeof candidate.turn_count === 'number' && Number.isFinite(candidate.turn_count) && candidate.turn_count > 0 ? candidate.turn_count : 1,
191
190
  ...(typeof candidate.mode === 'string' && candidate.mode.trim().length > 0 ? { mode: candidate.mode } : {}),
192
191
  ...(typeof candidate.role === 'string' && candidate.role.trim().length > 0 ? { role: candidate.role.trim() } : {}),
193
- ...(typeof candidate.provenance_kind === 'string' && candidate.provenance_kind.trim().length > 0
194
- ? { provenance_kind: candidate.provenance_kind.trim() }
195
- : {}),
192
+ ...(candidate.provenance_kind === NATIVE_SUBAGENT_PROVENANCE
193
+ ? { provenance_kind: NATIVE_SUBAGENT_PROVENANCE }
194
+ : candidate.provenance_kind === DESCRIPTIVE_ADAPTED_PROVENANCE
195
+ ? { provenance_kind: DESCRIPTIVE_ADAPTED_PROVENANCE }
196
+ : {}),
196
197
  ...(typeof candidate.lane_id === 'string' && candidate.lane_id.trim().length > 0 ? { lane_id: candidate.lane_id.trim() } : {}),
197
198
  ...(typeof candidate.scope === 'string' && candidate.scope.trim().length > 0 ? { scope: candidate.scope.trim() } : {}),
198
199
  ...(typeof candidate.agent_nickname === 'string' && candidate.agent_nickname.trim().length > 0
@@ -217,6 +218,19 @@ export function normalizeSubagentTrackingState(input) {
217
218
  ...(typeof candidate.resume_failure_reason === 'string' && candidate.resume_failure_reason.trim().length > 0
218
219
  ? { resume_failure_reason: candidate.resume_failure_reason.trim() }
219
220
  : {}),
221
+ ...(typeof candidate.direct_child_root_id === 'string' && candidate.direct_child_root_id.trim().length > 0
222
+ ? { direct_child_root_id: candidate.direct_child_root_id.trim() }
223
+ : {}),
224
+ ...(typeof candidate.direct_child_parent_id === 'string' && candidate.direct_child_parent_id.trim().length > 0
225
+ ? { direct_child_parent_id: candidate.direct_child_parent_id.trim() }
226
+ : {}),
227
+ ...(candidate.reopen_authority_revoked === true ? { reopen_authority_revoked: true } : {}),
228
+ ...(typeof candidate.reopen_authority_conflict_reason === 'string' && candidate.reopen_authority_conflict_reason.trim()
229
+ ? { reopen_authority_conflict_reason: candidate.reopen_authority_conflict_reason.trim() }
230
+ : {}),
231
+ ...(typeof candidate.reopen_authority_conflict_at === 'string' && candidate.reopen_authority_conflict_at.trim()
232
+ ? { reopen_authority_conflict_at: candidate.reopen_authority_conflict_at.trim() }
233
+ : {}),
220
234
  };
221
235
  }
222
236
  const sessionCandidate = rawSession;
@@ -226,20 +240,95 @@ export function normalizeSubagentTrackingState(input) {
226
240
  : new Date(0).toISOString();
227
241
  sessions[sessionId] = {
228
242
  session_id: sessionId,
229
- leader_thread_id: leaderThreadId,
243
+ ...(leaderThreadId ? { leader_thread_id: leaderThreadId } : {}),
230
244
  updated_at: updatedAt,
231
245
  threads,
232
246
  };
233
247
  }
234
- const pendingRoleIntents = Array.isArray(parsed.pending_role_intents)
235
- ? parsed.pending_role_intents.map((intent) => normalizePendingRoleIntent(intent)).filter((intent) => intent !== null)
236
- : [];
237
248
  return {
238
249
  schemaVersion: SUBAGENT_TRACKING_SCHEMA_VERSION,
239
250
  sessions,
240
- pending_role_intents: pendingRoleIntents,
241
251
  };
242
252
  }
253
+ function parseStrictSubagentTrackingState(raw) {
254
+ let parsed;
255
+ try {
256
+ parsed = JSON.parse(raw);
257
+ }
258
+ catch {
259
+ return null;
260
+ }
261
+ if (!parsed || typeof parsed !== 'object')
262
+ return null;
263
+ const candidate = parsed;
264
+ if (candidate.schemaVersion !== SUBAGENT_TRACKING_SCHEMA_VERSION || !candidate.sessions || typeof candidate.sessions !== 'object' || Array.isArray(candidate.sessions))
265
+ return null;
266
+ const normalizedSessionIds = new Set();
267
+ for (const [sessionId, sessionValue] of Object.entries(candidate.sessions)) {
268
+ if (!sessionId || sessionId.trim() !== sessionId || normalizedSessionIds.has(sessionId.trim()))
269
+ return null;
270
+ normalizedSessionIds.add(sessionId.trim());
271
+ if (!sessionValue || typeof sessionValue !== 'object' || Array.isArray(sessionValue))
272
+ return null;
273
+ const session = sessionValue;
274
+ if (session.session_id !== sessionId || typeof session.updated_at !== 'string' || !session.updated_at.trim())
275
+ return null;
276
+ if (!session.threads || typeof session.threads !== 'object' || Array.isArray(session.threads))
277
+ return null;
278
+ if (session.leader_thread_id !== undefined && (typeof session.leader_thread_id !== 'string' || !session.leader_thread_id.trim() || session.leader_thread_id.trim() !== session.leader_thread_id))
279
+ return null;
280
+ const normalizedThreadIds = new Set();
281
+ for (const [threadId, threadValue] of Object.entries(session.threads)) {
282
+ if (!threadId || threadId.trim() !== threadId || normalizedThreadIds.has(threadId.trim()))
283
+ return null;
284
+ normalizedThreadIds.add(threadId.trim());
285
+ if (!threadValue || typeof threadValue !== 'object' || Array.isArray(threadValue))
286
+ return null;
287
+ const thread = threadValue;
288
+ if (thread.thread_id !== threadId || (thread.kind !== 'leader' && thread.kind !== 'subagent'))
289
+ return null;
290
+ if (typeof thread.first_seen_at !== 'string' || !thread.first_seen_at.trim())
291
+ return null;
292
+ if (typeof thread.last_seen_at !== 'string' || !thread.last_seen_at.trim())
293
+ return null;
294
+ if (typeof thread.turn_count !== 'number' || !Number.isFinite(thread.turn_count) || thread.turn_count < 1)
295
+ return null;
296
+ const hasAuthorityEvidence = thread.provenance_kind === NATIVE_SUBAGENT_PROVENANCE
297
+ || thread.direct_child_root_id !== undefined
298
+ || thread.direct_child_parent_id !== undefined
299
+ || thread.reopen_authority_revoked !== undefined
300
+ || thread.reopen_authority_conflict_reason !== undefined
301
+ || thread.reopen_authority_conflict_at !== undefined;
302
+ if (hasAuthorityEvidence && thread.status !== undefined && normalizeSubagentStatus(thread.status) === undefined)
303
+ return null;
304
+ if (hasAuthorityEvidence && thread.provenance_kind !== undefined && thread.provenance_kind !== NATIVE_SUBAGENT_PROVENANCE)
305
+ return null;
306
+ for (const field of ['direct_child_root_id', 'direct_child_parent_id', 'reopen_authority_conflict_reason', 'reopen_authority_conflict_at']) {
307
+ if (thread[field] !== undefined && (typeof thread[field] !== 'string' || !thread[field].trim() || thread[field].trim() !== thread[field]))
308
+ return null;
309
+ }
310
+ if (thread.reopen_authority_revoked !== undefined && typeof thread.reopen_authority_revoked !== 'boolean')
311
+ return null;
312
+ if ((thread.direct_child_root_id === undefined) !== (thread.direct_child_parent_id === undefined))
313
+ return null;
314
+ if ((thread.reopen_authority_conflict_reason !== undefined || thread.reopen_authority_conflict_at !== undefined) && thread.reopen_authority_revoked !== true)
315
+ return null;
316
+ }
317
+ }
318
+ return normalizeSubagentTrackingState(parsed);
319
+ }
320
+ function readSubagentTrackingStateStrictSync(cwd) {
321
+ const path = subagentTrackingPath(cwd);
322
+ if (!existsSync(path))
323
+ return { ok: true, state: createSubagentTrackingState() };
324
+ try {
325
+ const state = parseStrictSubagentTrackingState(readFileSync(path, 'utf-8'));
326
+ return state ? { ok: true, state } : { ok: false };
327
+ }
328
+ catch {
329
+ return { ok: false };
330
+ }
331
+ }
243
332
  function atomicTrackingTempPath(path) {
244
333
  return `${path}.${process.pid}.${randomUUID()}.tmp`;
245
334
  }
@@ -579,16 +668,12 @@ export function withCrossProcessFileLockSync(resourcePath, operation, options =
579
668
  releaseCrossProcessFileLock(lockPath, token);
580
669
  }
581
670
  }
582
- function readSubagentTrackingStateSync(cwd) {
583
- const path = subagentTrackingPath(cwd);
584
- if (!existsSync(path))
585
- return createSubagentTrackingState();
586
- try {
587
- return normalizeSubagentTrackingState(JSON.parse(readFileSync(path, 'utf-8')));
588
- }
589
- catch {
590
- return createSubagentTrackingState();
591
- }
671
+ function threadIsTrackedAsSubagent(state, threadId) {
672
+ const id = threadId.trim();
673
+ return Boolean(id) && Object.values(state.sessions).some((session) => isTrustedSubagentThread(session, id));
674
+ }
675
+ export function hasLeaderSubagentCollision(state, leaderThreadId) {
676
+ return threadIsTrackedAsSubagent(state, leaderThreadId);
592
677
  }
593
678
  // Strict reads preserve fail-closed behavior for security-sensitive tracker decisions.
594
679
  export async function readSubagentTrackingStateStrict(cwd) {
@@ -602,12 +687,8 @@ export async function readSubagentTrackingStateStrict(cwd) {
602
687
  return { ok: true, state: createSubagentTrackingState() };
603
688
  return { ok: false };
604
689
  }
605
- try {
606
- return { ok: true, state: normalizeSubagentTrackingState(JSON.parse(raw)) };
607
- }
608
- catch {
609
- return { ok: false };
610
- }
690
+ const state = parseStrictSubagentTrackingState(raw);
691
+ return state ? { ok: true, state } : { ok: false };
611
692
  }
612
693
  function writeSubagentTrackingStateSync(cwd, state, publish) {
613
694
  const normalized = normalizeSubagentTrackingState(state);
@@ -635,300 +716,61 @@ export async function readSubagentTrackingState(cwd) {
635
716
  }
636
717
  }
637
718
  export async function writeSubagentTrackingState(cwd, state) {
638
- const normalized = normalizeSubagentTrackingState(state);
639
719
  const path = subagentTrackingPath(cwd);
640
- await mkdir(dirname(path), { recursive: true });
641
- const temporaryPath = atomicTrackingTempPath(path);
642
- await writeFile(temporaryPath, `${JSON.stringify(normalized, null, 2)}\n`);
643
- await rename(temporaryPath, path);
644
- return path;
645
- }
646
- function normalizeNowMs(nowMs) {
647
- return typeof nowMs === 'number' && Number.isFinite(nowMs) ? nowMs : Date.now();
648
- }
649
- function isExpiredPendingRoleIntent(intent, nowMs) {
650
- if (intent.binding_state === 'bound')
651
- return false;
652
- const expiresAtMs = Date.parse(intent.expires_at);
653
- return !Number.isFinite(expiresAtMs) || expiresAtMs <= nowMs;
654
- }
655
- function pendingRoleIntentPredicates(cwd, canonicalOrigin, nowMs) {
656
- const isCwdPartitionedStateRoot = getBaseStateDirWithSource(cwd).rootSource === 'cwd-default';
657
- const isOwn = (intent) => (intent.origin_cwd
658
- ? canonicalizeOriginCwd(intent.origin_cwd) === canonicalOrigin
659
- : isCwdPartitionedStateRoot);
660
- const shouldPruneExpired = (intent) => (isOwn(intent)
661
- && intent.binding_state !== 'bound'
662
- && isExpiredPendingRoleIntent(intent, nowMs));
663
- return { isOwn, shouldPruneExpired };
664
- }
665
- export function isRoleIntentOwnedByCwd(cwd, intent) {
666
- return pendingRoleIntentPredicates(cwd, canonicalizeOriginCwd(cwd), Date.now()).isOwn(intent);
667
- }
668
- function sameLogicalRoleIntent(intent, sessionId, parentThreadId, correlationToken) {
669
- return (intent.session_id === sessionId
670
- && intent.parent_thread_id === parentThreadId
671
- && (correlationToken === undefined || intent.correlation_token === correlationToken));
672
- }
673
- export function isCanonicalCorrelationToken(value) {
674
- return typeof value === 'string' && /^[0-9a-f]{32}$/.test(value);
675
- }
676
- export function isCanonicalClaimantToken(value) {
677
- return typeof value === 'string' && /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(value);
678
- }
679
- function hasOwnBoundLogicalIntent(all, isOwn, sessionId, parentThreadId, correlationToken) {
680
- return all.some((intent) => (isOwn(intent)
681
- && intent.binding_state === 'bound'
682
- && sameLogicalRoleIntent(intent, sessionId, parentThreadId, correlationToken)));
683
- }
684
- function selectDominantRoleIntent(candidates, canonicalOrigin) {
685
- return [...candidates].sort((left, right) => {
686
- const leftIsBound = left.binding_state === 'bound';
687
- const rightIsBound = right.binding_state === 'bound';
688
- if (leftIsBound !== rightIsBound)
689
- return leftIsBound ? -1 : 1;
690
- const leftIsExactOrigin = left.origin_cwd !== undefined
691
- && canonicalizeOriginCwd(left.origin_cwd) === canonicalOrigin;
692
- const rightIsExactOrigin = right.origin_cwd !== undefined
693
- && canonicalizeOriginCwd(right.origin_cwd) === canonicalOrigin;
694
- if (leftIsExactOrigin !== rightIsExactOrigin)
695
- return leftIsExactOrigin ? -1 : 1;
696
- const leftStableKey = [
697
- left.role,
698
- left.correlation_token,
699
- left.created_at,
700
- left.expires_at,
701
- left.binding_state ?? '',
702
- left.binding_claimant_token ?? '',
703
- left.bound_at ?? '',
704
- left.origin_cwd ?? '',
705
- ].join('\u0000');
706
- const rightStableKey = [
707
- right.role,
708
- right.correlation_token,
709
- right.created_at,
710
- right.expires_at,
711
- right.binding_state ?? '',
712
- right.binding_claimant_token ?? '',
713
- right.bound_at ?? '',
714
- right.origin_cwd ?? '',
715
- ].join('\u0000');
716
- return leftStableKey.localeCompare(rightStableKey);
717
- })[0] ?? null;
718
- }
719
- export function recordPendingRoleIntent(cwd, input) {
720
- const role = resolveInstalledRoleName(input.role);
721
- if (!role)
722
- return { ok: false, reason: 'unknown_role' };
723
- const correlationToken = input.correlationToken;
724
- if (!isCanonicalCorrelationToken(correlationToken)) {
725
- return { ok: false, reason: 'invalid_correlation_token' };
726
- }
727
- const nowMs = normalizeNowMs(input.nowMs);
728
- const sessionId = input.sessionId.trim();
729
- const parentThreadId = input.parentThreadId.trim();
730
- const canonicalOrigin = canonicalizeOriginCwd(cwd);
731
- if (canonicalOrigin === null)
732
- return { ok: false, reason: 'invalid_origin' };
733
- const { isOwn, shouldPruneExpired } = pendingRoleIntentPredicates(cwd, canonicalOrigin, nowMs);
734
- return withCrossProcessFileLockSync(subagentTrackingPath(cwd), (context) => {
735
- const state = readSubagentTrackingStateSync(cwd);
736
- const all = state.pending_role_intents;
737
- if (all.some((intent) => (isOwn(intent)
738
- && intent.session_id === sessionId
739
- && intent.parent_thread_id === parentThreadId
740
- && (intent.binding_state === 'bound' || !isExpiredPendingRoleIntent(intent, nowMs))))) {
741
- return { ok: false, reason: 'single_flight_conflict' };
742
- }
743
- const ttlMs = typeof input.ttlMs === 'number' && Number.isFinite(input.ttlMs) ? input.ttlMs : 10 * 60_000;
744
- const intent = {
745
- role,
746
- session_id: sessionId,
747
- parent_thread_id: parentThreadId,
748
- correlation_token: correlationToken,
749
- created_at: new Date(nowMs).toISOString(),
750
- expires_at: new Date(nowMs + ttlMs).toISOString(),
751
- // Store the canonical origin workspace so it authenticates future bind/complete/recover.
752
- ...(canonicalOrigin ? { origin_cwd: canonicalOrigin } : {}),
753
- };
754
- state.pending_role_intents = [...all.filter((candidate) => !shouldPruneExpired(candidate)), intent];
755
- context.assertOwnership();
756
- writeSubagentTrackingStateSync(cwd, state, context.publish);
757
- return { ok: true, intent };
758
- });
759
- }
760
- export function bindPendingRoleIntentUnderLock(cwd, input, bind) {
761
- const nowMs = normalizeNowMs(input.nowMs);
762
- const sessionId = input.sessionId.trim();
763
- const parentThreadId = input.parentThreadId.trim();
764
- const correlationToken = input.correlationToken;
765
- if (!isCanonicalCorrelationToken(correlationToken))
766
- return null;
767
- // Fail-closed origin authentication: establish the caller's canonical origin workspace up
768
- // front. A malformed/unavailable origin can never disclose role/claimant, run the bind
769
- // callback, mutate pending->bound, or acquire the lock.
770
- const canonicalOrigin = canonicalizeOriginCwd(cwd);
771
- if (canonicalOrigin === null)
772
- return null;
773
- return withCrossProcessFileLockSync(subagentTrackingPath(cwd), (context) => {
774
- const state = readSubagentTrackingStateSync(cwd);
775
- const all = state.pending_role_intents;
776
- const { isOwn, shouldPruneExpired } = pendingRoleIntentPredicates(cwd, canonicalOrigin, nowMs);
777
- const matchedIntent = selectDominantRoleIntent(all.filter((intent) => (isOwn(intent)
778
- && correlationToken !== undefined
779
- && sameLogicalRoleIntent(intent, sessionId, parentThreadId, correlationToken)
780
- && (intent.binding_state === 'bound' || !isExpiredPendingRoleIntent(intent, nowMs)))), canonicalOrigin);
781
- if (!matchedIntent) {
782
- const retained = all.filter((intent) => !shouldPruneExpired(intent));
783
- if (retained.length !== all.length) {
784
- state.pending_role_intents = retained;
785
- context.assertOwnership();
786
- writeSubagentTrackingStateSync(cwd, state, context.publish);
787
- }
788
- return null;
789
- }
790
- const adaptedIntent = {
791
- role: matchedIntent.role,
792
- provenanceKind: OMX_ADAPTED_PROVENANCE,
793
- };
794
- if (matchedIntent.binding_state === 'bound') {
795
- const next = all
796
- .filter((intent) => (intent.binding_state === 'bound'
797
- || !(isOwn(intent) && sameLogicalRoleIntent(intent, sessionId, parentThreadId, correlationToken))))
798
- .map((intent) => (intent === matchedIntent && !intent.origin_cwd
799
- ? { ...intent, origin_cwd: canonicalOrigin }
800
- : intent));
801
- if (next.length !== all.length || !matchedIntent.origin_cwd) {
802
- state.pending_role_intents = next;
803
- context.assertOwnership();
804
- writeSubagentTrackingStateSync(cwd, state, context.publish);
720
+ // Fail closed on every side of the write. The proposed state must survive
721
+ // strict authority validation (tolerant inputs must not launder malformed
722
+ // authority into a valid-looking attestation); existing on-disk bytes must
723
+ // not be overwritten when they fail strict parsing; and the publication must
724
+ // not roll back an authority decision committed after the caller read its
725
+ // snapshot. The whole read/validate/compare/publish transaction is
726
+ // serialized under the tracker lock.
727
+ const proposedRaw = JSON.stringify(state);
728
+ if (!parseStrictSubagentTrackingState(proposedRaw))
729
+ throw new Error('Malformed subagent tracker authority state');
730
+ const normalized = normalizeSubagentTrackingState(state);
731
+ const contents = `${JSON.stringify(normalized, null, 2)}\n`;
732
+ const proposedAuthorityRevision = authorityRevisionOf(normalized);
733
+ return withCrossProcessFileLockSync(path, (lock) => {
734
+ if (existsSync(path)) {
735
+ let raw;
736
+ try {
737
+ raw = readFileSync(path, 'utf-8');
805
738
  }
806
- return {
807
- ...adaptedIntent,
808
- claimantToken: undefined,
809
- alreadyBound: true,
810
- };
811
- }
812
- const claimantToken = randomUUID();
813
- const boundState = bind(state, adaptedIntent);
814
- boundState.pending_role_intents = all
815
- .filter((intent) => !shouldPruneExpired(intent))
816
- .filter((intent) => (intent === matchedIntent
817
- || !(isOwn(intent) && sameLogicalRoleIntent(intent, sessionId, parentThreadId, correlationToken))))
818
- .map((intent) => (intent === matchedIntent
819
- ? {
820
- ...matchedIntent,
821
- binding_state: 'bound',
822
- binding_claimant_token: claimantToken,
823
- bound_at: new Date(nowMs).toISOString(),
824
- origin_cwd: matchedIntent.origin_cwd ?? canonicalOrigin,
739
+ catch {
740
+ throw new Error('Malformed subagent tracker authority state');
825
741
  }
826
- : intent));
827
- context.assertOwnership();
828
- writeSubagentTrackingStateSync(cwd, boundState, context.publish);
829
- return { ...adaptedIntent, claimantToken, alreadyBound: false };
830
- });
831
- }
832
- export function consumePendingRoleIntent(cwd, input) {
833
- const nowMs = normalizeNowMs(input.nowMs);
834
- const sessionId = input.sessionId.trim();
835
- const parentThreadId = input.parentThreadId.trim();
836
- const correlationToken = input.correlationToken;
837
- if (!isCanonicalCorrelationToken(correlationToken))
838
- return null;
839
- const canonicalOrigin = canonicalizeOriginCwd(cwd);
840
- if (canonicalOrigin === null)
841
- return null;
842
- return withCrossProcessFileLockSync(subagentTrackingPath(cwd), (context) => {
843
- const state = readSubagentTrackingStateSync(cwd);
844
- const all = state.pending_role_intents;
845
- const { isOwn, shouldPruneExpired } = pendingRoleIntentPredicates(cwd, canonicalOrigin, nowMs);
846
- if (hasOwnBoundLogicalIntent(all, isOwn, sessionId, parentThreadId, correlationToken))
847
- return null;
848
- const consumed = selectDominantRoleIntent(all.filter((intent) => (isOwn(intent)
849
- && intent.binding_state !== 'bound'
850
- && !isExpiredPendingRoleIntent(intent, nowMs)
851
- && correlationToken !== undefined
852
- && sameLogicalRoleIntent(intent, sessionId, parentThreadId, correlationToken))), canonicalOrigin);
853
- if (!consumed) {
854
- const retained = all.filter((intent) => !shouldPruneExpired(intent));
855
- if (retained.length !== all.length) {
856
- state.pending_role_intents = retained;
857
- context.assertOwnership();
858
- writeSubagentTrackingStateSync(cwd, state, context.publish);
742
+ const current = parseStrictSubagentTrackingState(raw);
743
+ if (!current)
744
+ throw new Error('Malformed subagent tracker authority state');
745
+ // The generic writer is descriptive-only: it may publish descriptive
746
+ // churn, but it must never CHANGE the authority surface in any
747
+ // direction. Granting, revoking, or rolling back authority is reserved
748
+ // for the lock-owning authority transactions. Requiring the proposed
749
+ // authority projection to equal the current on-disk projection makes a
750
+ // stale snapshot, a fresh-read escalation, and a concurrent interleaving
751
+ // all fail closed with one rule.
752
+ if (authorityRevisionOf(current) !== proposedAuthorityRevision) {
753
+ throw new Error('Stale subagent tracker authority state');
859
754
  }
860
- return null;
861
755
  }
862
- state.pending_role_intents = all
863
- .filter((intent) => !shouldPruneExpired(intent))
864
- .filter((intent) => (intent.binding_state === 'bound'
865
- || !(isOwn(intent) && sameLogicalRoleIntent(intent, sessionId, parentThreadId, correlationToken))));
866
- context.assertOwnership();
867
- writeSubagentTrackingStateSync(cwd, state, context.publish);
868
- return { role: consumed.role, provenanceKind: OMX_ADAPTED_PROVENANCE };
869
- });
870
- }
871
- export function completeAdaptedRoleBinding(cwd, input) {
872
- const nowMs = normalizeNowMs(input.nowMs);
873
- const sessionId = input.sessionId.trim();
874
- const parentThreadId = input.parentThreadId.trim();
875
- const correlationToken = input.correlationToken;
876
- const claimantToken = input.claimantToken;
877
- const canonicalOrigin = canonicalizeOriginCwd(cwd);
878
- if (canonicalOrigin === null)
879
- return 'not_found';
880
- return withCrossProcessFileLockSync(subagentTrackingPath(cwd), (context) => {
881
- const state = readSubagentTrackingStateSync(cwd);
882
- const all = state.pending_role_intents;
883
- const { isOwn, shouldPruneExpired } = pendingRoleIntentPredicates(cwd, canonicalOrigin, nowMs);
884
- // Select the owned bound scope before authenticating it. Credentials are not a lookup key:
885
- // otherwise an invalid dominant duplicate could be bypassed by a lower valid duplicate.
886
- const boundIntent = selectDominantRoleIntent(all.filter((intent) => (isOwn(intent)
887
- && intent.binding_state === 'bound'
888
- && sameLogicalRoleIntent(intent, sessionId, parentThreadId))), canonicalOrigin);
889
- if (!boundIntent) {
890
- const retained = all.filter((intent) => !shouldPruneExpired(intent));
891
- if (retained.length !== all.length) {
892
- state.pending_role_intents = retained;
893
- context.assertOwnership();
894
- writeSubagentTrackingStateSync(cwd, state, context.publish);
895
- }
896
- return 'not_found';
756
+ else if (proposedAuthorityRevision !== authorityRevisionOf(createSubagentTrackingState())) {
757
+ // Creating the tracker from nothing must not mint reopen authority.
758
+ // `leader_thread_id` is deliberately NOT included: it is ordinary
759
+ // descriptive lifecycle data that legitimate first writers set, and it
760
+ // never by itself makes a thread an eligible reopen target.
761
+ const mintsAuthority = Object.values(normalized.sessions).some((session) => Object.values(session.threads).some((thread) => thread.provenance_kind === NATIVE_SUBAGENT_PROVENANCE
762
+ || thread.direct_child_root_id !== undefined
763
+ || thread.direct_child_parent_id !== undefined
764
+ || thread.reopen_authority_revoked !== undefined
765
+ || thread.reopen_authority_conflict_reason !== undefined
766
+ || thread.reopen_authority_conflict_at !== undefined));
767
+ if (mintsAuthority)
768
+ throw new Error('Stale subagent tracker authority state');
897
769
  }
898
- const hasClaimant = Object.hasOwn(boundIntent, 'binding_claimant_token');
899
- if (!isCanonicalCorrelationToken(boundIntent.correlation_token)
900
- || !isCanonicalCorrelationToken(correlationToken)
901
- || boundIntent.correlation_token !== correlationToken
902
- || (hasClaimant && (!isCanonicalClaimantToken(boundIntent.binding_claimant_token)
903
- || !isCanonicalClaimantToken(claimantToken)
904
- || boundIntent.binding_claimant_token !== claimantToken)))
905
- return 'claimant_mismatch';
906
- state.pending_role_intents = all
907
- .filter((intent) => !shouldPruneExpired(intent))
908
- .filter((intent) => !(isOwn(intent) && sameLogicalRoleIntent(intent, sessionId, parentThreadId, boundIntent.correlation_token)));
909
- context.assertOwnership();
910
- writeSubagentTrackingStateSync(cwd, state, context.publish);
911
- return 'completed';
912
- });
913
- }
914
- export function listBoundAdaptedRoleIntents(cwd, _nowMs, ownedDominant = false) {
915
- const allBound = readSubagentTrackingStateSync(cwd).pending_role_intents.filter((intent) => intent.binding_state === 'bound');
916
- if (!ownedDominant)
917
- return allBound;
918
- const canonicalOrigin = canonicalizeOriginCwd(cwd);
919
- if (canonicalOrigin === null)
920
- return [];
921
- const { isOwn } = pendingRoleIntentPredicates(cwd, canonicalOrigin, Date.now());
922
- const scopes = new Map();
923
- for (const intent of allBound) {
924
- if (!isOwn(intent))
925
- continue;
926
- const key = `${intent.session_id}\u0000${intent.parent_thread_id}`;
927
- scopes.set(key, [...(scopes.get(key) ?? []), intent]);
928
- }
929
- return [...scopes.values()].flatMap((candidates) => {
930
- const dominant = selectDominantRoleIntent(candidates, canonicalOrigin);
931
- return dominant ? [dominant] : [];
770
+ mkdirSync(dirname(path), { recursive: true });
771
+ lock.assertOwnership();
772
+ lock.publish(contents);
773
+ return path;
932
774
  });
933
775
  }
934
776
  export function recordSubagentTurn(state, input) {
@@ -996,11 +838,13 @@ export function recordSubagentTurn(state, input) {
996
838
  : preservedCompletion),
997
839
  ...(input.mode?.trim() ? { mode: input.mode.trim() } : existingThread?.mode ? { mode: existingThread.mode } : {}),
998
840
  ...(input.role?.trim() ? { role: input.role.trim() } : existingThread?.role ? { role: existingThread.role } : {}),
999
- ...(input.provenanceKind?.trim()
1000
- ? { provenance_kind: input.provenanceKind.trim() }
1001
- : existingThread?.provenance_kind
1002
- ? { provenance_kind: existingThread.provenance_kind }
1003
- : {}),
841
+ ...(input.provenanceKind === NATIVE_SUBAGENT_PROVENANCE
842
+ ? { provenance_kind: NATIVE_SUBAGENT_PROVENANCE }
843
+ : existingThread?.provenance_kind === NATIVE_SUBAGENT_PROVENANCE
844
+ ? { provenance_kind: NATIVE_SUBAGENT_PROVENANCE }
845
+ : existingThread?.provenance_kind === DESCRIPTIVE_ADAPTED_PROVENANCE
846
+ ? { provenance_kind: DESCRIPTIVE_ADAPTED_PROVENANCE }
847
+ : {}),
1004
848
  ...(input.laneId?.trim() ? { lane_id: input.laneId.trim() } : existingThread?.lane_id ? { lane_id: existingThread.lane_id } : {}),
1005
849
  ...(input.scope?.trim() ? { scope: input.scope.trim() } : existingThread?.scope ? { scope: existingThread.scope } : {}),
1006
850
  ...(input.agentNickname?.trim()
@@ -1034,6 +878,11 @@ export function recordSubagentTurn(state, input) {
1034
878
  : existingThread?.resume_failure_reason
1035
879
  ? { resume_failure_reason: existingThread.resume_failure_reason }
1036
880
  : {}),
881
+ ...(existingThread?.direct_child_root_id ? { direct_child_root_id: existingThread.direct_child_root_id } : {}),
882
+ ...(existingThread?.direct_child_parent_id ? { direct_child_parent_id: existingThread.direct_child_parent_id } : {}),
883
+ ...(existingThread?.reopen_authority_revoked ? { reopen_authority_revoked: true } : {}),
884
+ ...(existingThread?.reopen_authority_conflict_reason ? { reopen_authority_conflict_reason: existingThread.reopen_authority_conflict_reason } : {}),
885
+ ...(existingThread?.reopen_authority_conflict_at ? { reopen_authority_conflict_at: existingThread.reopen_authority_conflict_at } : {}),
1037
886
  };
1038
887
  const threads = {
1039
888
  ...existingSession.threads,
@@ -1055,13 +904,615 @@ export function recordSubagentTurn(state, input) {
1055
904
  }
1056
905
  export async function recordSubagentTurnForSession(cwd, input) {
1057
906
  return withCrossProcessFileLockSync(subagentTrackingPath(cwd), (context) => {
1058
- const current = readSubagentTrackingStateSync(cwd);
1059
- const next = recordSubagentTurn(current, input);
907
+ const strict = readSubagentTrackingStateStrictSync(cwd);
908
+ if (!strict.ok)
909
+ throw new Error('Malformed subagent tracker authority state');
910
+ const next = recordSubagentTurn(strict.state, input);
1060
911
  context.assertOwnership();
1061
912
  writeSubagentTrackingStateSync(cwd, next, context.publish);
1062
913
  return next;
1063
914
  });
1064
915
  }
916
+ export function recordNativeSubagentAuthorityObservation(cwd, observation) {
917
+ const requestedSessionIds = [...new Set(observation.sessionIds.map((value) => value.trim()).filter(Boolean))];
918
+ const childThreadId = observation.childThreadId.trim();
919
+ const parentThreadId = observation.parentThreadId.trim();
920
+ const rootNativeSessionId = observation.rootNativeSessionId?.trim() ?? '';
921
+ const implicatedChildThreadIds = [...new Set([
922
+ childThreadId,
923
+ ...(observation.implicatedChildThreadIds ?? []).map((value) => value.trim()),
924
+ ].filter(Boolean))];
925
+ return withCrossProcessFileLockSync(subagentTrackingPath(cwd), (context) => {
926
+ const strict = readSubagentTrackingStateStrictSync(cwd);
927
+ if (!strict.ok)
928
+ throw new Error('Malformed subagent tracker authority state');
929
+ let next = strict.state;
930
+ const existingSessionIds = Object.entries(next.sessions)
931
+ .filter(([, session]) => implicatedChildThreadIds.some((id) => Boolean(session.threads[id]?.direct_child_root_id || session.threads[id]?.reopen_authority_revoked)))
932
+ .map(([sessionId]) => sessionId);
933
+ const sessionIds = [...new Set([...requestedSessionIds, ...existingSessionIds])];
934
+ const existingBindings = Object.values(next.sessions)
935
+ .map((session) => session.threads[childThreadId])
936
+ .filter((thread) => Boolean(thread?.direct_child_root_id));
937
+ const authorityEvidence = observation.authorityEvidence ?? (rootNativeSessionId ? 'valid' : 'absent');
938
+ const globalConflict = authorityEvidence === 'untrusted' && existingBindings.length > 0
939
+ || authorityEvidence === 'valid' && existingBindings.some((thread) => thread.direct_child_root_id !== rootNativeSessionId || thread.direct_child_parent_id !== parentThreadId);
940
+ const timestamp = observation.timestamp ?? new Date().toISOString();
941
+ if (authorityEvidence === 'untrusted') {
942
+ for (const session of Object.values(next.sessions)) {
943
+ for (const implicatedId of implicatedChildThreadIds) {
944
+ const implicatedThread = session.threads[implicatedId];
945
+ if (!implicatedThread?.direct_child_root_id && !implicatedThread?.reopen_authority_revoked)
946
+ continue;
947
+ implicatedThread.reopen_authority_revoked = true;
948
+ implicatedThread.reopen_authority_conflict_reason = 'identity_untrusted';
949
+ implicatedThread.reopen_authority_conflict_at = timestamp;
950
+ }
951
+ }
952
+ }
953
+ for (const sessionId of sessionIds) {
954
+ if (parentThreadId && parentThreadId !== childThreadId) {
955
+ next = recordSubagentTurn(next, { sessionId, threadId: parentThreadId, kind: 'leader', timestamp });
956
+ }
957
+ next = recordSubagentTurn(next, {
958
+ sessionId,
959
+ threadId: childThreadId,
960
+ kind: 'subagent',
961
+ ...(parentThreadId && parentThreadId !== childThreadId ? { leaderThreadId: parentThreadId } : {}),
962
+ mode: observation.mode,
963
+ timestamp,
964
+ });
965
+ const thread = next.sessions[sessionId]?.threads[childThreadId];
966
+ if (!thread)
967
+ continue;
968
+ const mayAttest = authorityEvidence === 'valid'
969
+ && Boolean(rootNativeSessionId)
970
+ && childThreadId !== rootNativeSessionId
971
+ && parentThreadId === rootNativeSessionId;
972
+ if (globalConflict) {
973
+ thread.reopen_authority_revoked = true;
974
+ thread.reopen_authority_conflict_reason = 'root_or_parent_mismatch';
975
+ thread.reopen_authority_conflict_at = timestamp;
976
+ }
977
+ else if (mayAttest) {
978
+ thread.direct_child_root_id = rootNativeSessionId;
979
+ thread.direct_child_parent_id = parentThreadId;
980
+ thread.provenance_kind = NATIVE_SUBAGENT_PROVENANCE;
981
+ delete thread.reopen_authority_revoked;
982
+ delete thread.reopen_authority_conflict_reason;
983
+ delete thread.reopen_authority_conflict_at;
984
+ }
985
+ }
986
+ context.assertOwnership();
987
+ writeSubagentTrackingStateSync(cwd, next, context.publish);
988
+ return next;
989
+ });
990
+ }
991
+ /**
992
+ * Durable fence for authority revocations that could not be published.
993
+ *
994
+ * A negative identity observation is only meaningful if reopen consumption can
995
+ * see it. When the revocation transaction fails (lock exhaustion, malformed
996
+ * bytes, I/O error) the affected ids are recorded here instead of being
997
+ * silently dropped, and `consumeDirectChildReopenContext` treats any fenced id
998
+ * as denied until the revocation is durably applied.
999
+ */
1000
+ function authorityFencePath(cwd) {
1001
+ return `${subagentTrackingPath(cwd)}.authority-fence.json`;
1002
+ }
1003
+ /** Read the fenced ids. An unreadable/malformed fence fails closed for all ids. */
1004
+ function readAuthorityFence(cwd) {
1005
+ const path = authorityFencePath(cwd);
1006
+ if (!existsSync(path))
1007
+ return { ok: true, ids: new Set() };
1008
+ try {
1009
+ const parsed = JSON.parse(readFileSync(path, 'utf-8'));
1010
+ if (!Array.isArray(parsed.ids))
1011
+ return { ok: false, ids: new Set() };
1012
+ const ids = parsed.ids.filter((id) => typeof id === 'string' && Boolean(id.trim()));
1013
+ if (ids.length !== parsed.ids.length)
1014
+ return { ok: false, ids: new Set() };
1015
+ return { ok: true, ids: new Set(ids.map((id) => id.trim())) };
1016
+ }
1017
+ catch {
1018
+ return { ok: false, ids: new Set() };
1019
+ }
1020
+ }
1021
+ /** Upper bound on fenced ids, so repeated failures cannot grow the fence without limit. */
1022
+ export const AUTHORITY_FENCE_MAX_IDS = 512;
1023
+ /**
1024
+ * Record ids whose revocation could not be persisted, so reopen stays denied.
1025
+ *
1026
+ * Every fence mutation runs under a dedicated cross-process lock on the fence
1027
+ * path so read/merge/publish is atomic with respect to concurrent fencing and
1028
+ * clearing. Returns true when the denial is durable; the caller must treat
1029
+ * false as an unresolved fail-closed condition.
1030
+ */
1031
+ export function fenceNativeSubagentAuthorities(cwd, childThreadIds, reason) {
1032
+ const ids = childThreadIds.map((value) => value.trim()).filter(Boolean);
1033
+ if (!ids.length)
1034
+ return true;
1035
+ const path = authorityFencePath(cwd);
1036
+ try {
1037
+ return withCrossProcessFileLockSync(path, (lock) => {
1038
+ const existing = readAuthorityFence(cwd);
1039
+ // An unreadable fence already denies every id; do not weaken it by
1040
+ // rewriting it into a narrower explicit id list.
1041
+ if (!existing.ok)
1042
+ return true;
1043
+ const merged = [...new Set([...existing.ids, ...ids])].sort();
1044
+ if (merged.length > AUTHORITY_FENCE_MAX_IDS) {
1045
+ // Refuse to grow past the cap by writing a deliberately unreadable
1046
+ // sentinel: a global denial is strictly stronger than a partial list.
1047
+ mkdirSync(dirname(path), { recursive: true });
1048
+ lock.assertOwnership();
1049
+ lock.publish(`${JSON.stringify({ ids: 'all', reason: 'fence_capacity_exceeded', fenced_at: new Date().toISOString() }, null, 2)}\n`);
1050
+ return true;
1051
+ }
1052
+ mkdirSync(dirname(path), { recursive: true });
1053
+ lock.assertOwnership();
1054
+ lock.publish(`${JSON.stringify({ ids: merged, reason, fenced_at: new Date().toISOString() }, null, 2)}\n`);
1055
+ return true;
1056
+ });
1057
+ }
1058
+ catch {
1059
+ // The denial could not be made durable. The caller must surface this
1060
+ // rather than continue as if the revocation had been applied.
1061
+ return false;
1062
+ }
1063
+ }
1064
+ /**
1065
+ * Last-resort global denial used when BOTH the tracker revocation transaction
1066
+ * and the locked fence publication fail. It deliberately avoids the fence lock
1067
+ * (which may be exactly what is unavailable) and writes an intentionally
1068
+ * unreadable sentinel directly, because `readAuthorityFence` treats any
1069
+ * unreadable fence as a denial of every id. Returns true when the denial is
1070
+ * durable on disk.
1071
+ */
1072
+ export function forceGlobalAuthorityDenial(cwd, reason) {
1073
+ const path = authorityFencePath(cwd);
1074
+ const contents = `${JSON.stringify({ ids: 'all', reason, fenced_at: new Date().toISOString() }, null, 2)}\n`;
1075
+ try {
1076
+ mkdirSync(dirname(path), { recursive: true });
1077
+ const temporaryPath = atomicTrackingTempPath(path);
1078
+ writeFileSync(temporaryPath, contents);
1079
+ renameSync(temporaryPath, path);
1080
+ return true;
1081
+ }
1082
+ catch {
1083
+ try {
1084
+ // Even a partial/truncated write is safe here: unreadable means denied.
1085
+ writeFileSync(path, contents);
1086
+ return true;
1087
+ }
1088
+ catch {
1089
+ return false;
1090
+ }
1091
+ }
1092
+ }
1093
+ /**
1094
+ * Drop ids from the fence once their revocation is durably published.
1095
+ *
1096
+ * Runs under the fence lock and re-reads inside it, so it can never clear
1097
+ * based on a stale snapshot. An unreadable fence is a global denial and is
1098
+ * NEVER destroyed here: only an explicit, fully reconciled id list is removed.
1099
+ */
1100
+ function clearAuthorityFence(cwd, childThreadIds) {
1101
+ const path = authorityFencePath(cwd);
1102
+ if (!existsSync(path))
1103
+ return;
1104
+ const cleared = new Set(childThreadIds.map((value) => value.trim()).filter(Boolean));
1105
+ if (!cleared.size)
1106
+ return;
1107
+ try {
1108
+ withCrossProcessFileLockSync(path, (lock) => {
1109
+ const existing = readAuthorityFence(cwd);
1110
+ // Preserve an unreadable/global denial. Reconciling it requires explicit
1111
+ // operator action, not an incidental unrelated revocation.
1112
+ if (!existing.ok)
1113
+ return;
1114
+ const remaining = [...existing.ids].filter((id) => !cleared.has(id)).sort();
1115
+ if (remaining.length === existing.ids.size)
1116
+ return;
1117
+ lock.assertOwnership();
1118
+ if (!remaining.length) {
1119
+ unlinkSync(path);
1120
+ return;
1121
+ }
1122
+ lock.publish(`${JSON.stringify({ ids: remaining, reason: 'partial_clear', fenced_at: new Date().toISOString() }, null, 2)}\n`);
1123
+ });
1124
+ }
1125
+ catch {
1126
+ // Leaving a stale fence entry is fail-closed and therefore acceptable.
1127
+ }
1128
+ }
1129
+ export function revokeNativeSubagentAuthorities(cwd, childThreadIds, reason = 'identity_untrusted') {
1130
+ const ids = [...new Set(childThreadIds.map((value) => value.trim()).filter(Boolean))];
1131
+ return withCrossProcessFileLockSync(subagentTrackingPath(cwd), (context) => {
1132
+ const strict = readSubagentTrackingStateStrictSync(cwd);
1133
+ if (!strict.ok)
1134
+ throw new Error('Malformed subagent tracker authority state');
1135
+ const next = strict.state;
1136
+ const timestamp = new Date().toISOString();
1137
+ let changed = false;
1138
+ for (const session of Object.values(next.sessions)) {
1139
+ for (const id of ids) {
1140
+ const thread = session.threads[id];
1141
+ if (!thread?.direct_child_root_id && !thread?.reopen_authority_revoked)
1142
+ continue;
1143
+ thread.reopen_authority_revoked = true;
1144
+ thread.reopen_authority_conflict_reason = reason;
1145
+ thread.reopen_authority_conflict_at = timestamp;
1146
+ changed = true;
1147
+ }
1148
+ }
1149
+ if (changed) {
1150
+ context.assertOwnership();
1151
+ writeSubagentTrackingStateSync(cwd, next, context.publish);
1152
+ }
1153
+ // The revocation is now durable for these ids (either just published, or
1154
+ // already present on disk), so any prior fence for them can be released.
1155
+ clearAuthorityFence(cwd, ids);
1156
+ return next;
1157
+ });
1158
+ }
1159
+ function formatReopenMetadata(entry) {
1160
+ const values = [
1161
+ entry.role ? `role: ${entry.role}` : null,
1162
+ entry.laneId ? `lane: ${entry.laneId}` : null,
1163
+ entry.scope ? `scope: ${entry.scope}` : null,
1164
+ `status: ${entry.status}`,
1165
+ entry.lastHandoffSummary ? `handoff: ${entry.lastHandoffSummary.slice(0, 120)}` : null,
1166
+ entry.resumeFailureReason ? `last failure: ${entry.resumeFailureReason.slice(0, 120)}` : null,
1167
+ ].filter((value) => Boolean(value));
1168
+ return values.length ? ` (${values.join('; ')})` : '';
1169
+ }
1170
+ function persistedReopenAuthorityWarning(reason) {
1171
+ return [
1172
+ '[Persisted subagent reopen]',
1173
+ `- Warning: persisted subagent authority was excluded (${reason}); no resume authority or bookkeeping was granted.`,
1174
+ '- Silver rule: do not spawn a same-role/same-lane replacement solely because persisted reopen authority was unavailable; continue in the root or another compatible existing lane.',
1175
+ ].join('\n');
1176
+ }
1177
+ function persistedReopenLegacyNotice(excludedCount, source) {
1178
+ return [
1179
+ '[Persisted subagent reopen]',
1180
+ `- SessionStart source: ${source}; saved subagent ids found: 0.`,
1181
+ `- Notice: ${excludedCount} persisted subagent record(s) were excluded as non-authoritative or legacy; no resume authority or bookkeeping was granted.`,
1182
+ '- Silver rule: do not spawn a same-role/same-lane replacement solely because persisted reopen authority was unavailable; continue in the root or another compatible existing lane.',
1183
+ ].join('\n');
1184
+ }
1185
+ /**
1186
+ * True when a stored partition is provably about the current root, and not an
1187
+ * unrelated/historical workspace session that merely happens to contain a
1188
+ * thread id equal to the current canonical session key. Canonical-key collision
1189
+ * quarantine is scoped to correlated partitions so it cannot destroy authority
1190
+ * or resume bookkeeping belonging to a different root.
1191
+ */
1192
+ function partitionCorrelatesToRoot(session, sessionId, rootNativeSessionId) {
1193
+ if (session.session_id === sessionId || session.session_id === rootNativeSessionId)
1194
+ return true;
1195
+ if (session.leader_thread_id === rootNativeSessionId)
1196
+ return true;
1197
+ if (session.threads[rootNativeSessionId] !== undefined)
1198
+ return true;
1199
+ return Object.values(session.threads).some((thread) => thread.direct_child_root_id === rootNativeSessionId
1200
+ || thread.direct_child_parent_id === rootNativeSessionId);
1201
+ }
1202
+ /**
1203
+ * Repair persisted root-as-subagent identity inversion inside an already-parsed
1204
+ * state. Only a thread whose id equals the exact native root id is conclusively
1205
+ * the root and is reclassified as leader. A thread whose id equals only the
1206
+ * canonical session key is always ambiguous — the storage key is not identity
1207
+ * proof — so it is never destructively reclassified: authority-bearing records
1208
+ * are revoked (fail closed) and every ambiguous record loses resume bookkeeping.
1209
+ * That ambiguity quarantine only applies inside partitions that correlate to
1210
+ * this root; unrelated partitions are left untouched.
1211
+ * Returns true when any record changed.
1212
+ */
1213
+ function repairPersistedRootIdentityInState(state, sessionId, rootNativeSessionId, repairTimestamp) {
1214
+ let changed = false;
1215
+ for (const correlatedSession of Object.values(state.sessions)) {
1216
+ let sessionChanged = false;
1217
+ const correlated = partitionCorrelatesToRoot(correlatedSession, sessionId, rootNativeSessionId);
1218
+ for (const rootId of new Set([sessionId, rootNativeSessionId])) {
1219
+ const rootThread = correlatedSession.threads[rootId];
1220
+ if (!rootThread || rootThread.kind !== 'subagent')
1221
+ continue;
1222
+ const ambiguousCanonicalCollision = rootId === sessionId && sessionId !== rootNativeSessionId;
1223
+ if (ambiguousCanonicalCollision) {
1224
+ // An uncorrelated partition's identically-named thread belongs to a
1225
+ // different root: leave its authority and bookkeeping alone.
1226
+ if (!correlated)
1227
+ continue;
1228
+ const carriesAuthority = rootThread.provenance_kind === NATIVE_SUBAGENT_PROVENANCE
1229
+ || rootThread.direct_child_root_id !== undefined
1230
+ || rootThread.direct_child_parent_id !== undefined;
1231
+ if (carriesAuthority && rootThread.reopen_authority_revoked !== true) {
1232
+ // Fail closed without rewriting identity: the id collides with the
1233
+ // canonical root key but carries child-shaped authority evidence, so
1234
+ // it stays a subagent whose reopen authority is revoked until root
1235
+ // identity is independently established.
1236
+ rootThread.reopen_authority_revoked = true;
1237
+ rootThread.reopen_authority_conflict_reason = 'canonical_root_collision';
1238
+ rootThread.reopen_authority_conflict_at = repairTimestamp;
1239
+ sessionChanged = true;
1240
+ }
1241
+ if (rootThread.resume_requested_at !== undefined
1242
+ || rootThread.resume_completed_at !== undefined
1243
+ || rootThread.resume_failed_at !== undefined
1244
+ || rootThread.resume_failure_reason !== undefined) {
1245
+ delete rootThread.resume_requested_at;
1246
+ delete rootThread.resume_completed_at;
1247
+ delete rootThread.resume_failed_at;
1248
+ delete rootThread.resume_failure_reason;
1249
+ sessionChanged = true;
1250
+ }
1251
+ continue;
1252
+ }
1253
+ if (rootId !== rootNativeSessionId)
1254
+ continue;
1255
+ rootThread.kind = 'leader';
1256
+ delete rootThread.direct_child_root_id;
1257
+ delete rootThread.direct_child_parent_id;
1258
+ delete rootThread.reopen_authority_revoked;
1259
+ delete rootThread.reopen_authority_conflict_reason;
1260
+ delete rootThread.reopen_authority_conflict_at;
1261
+ delete rootThread.resume_requested_at;
1262
+ delete rootThread.resume_completed_at;
1263
+ delete rootThread.resume_failed_at;
1264
+ delete rootThread.resume_failure_reason;
1265
+ sessionChanged = true;
1266
+ }
1267
+ if (correlatedSession.threads[rootNativeSessionId] && correlatedSession.leader_thread_id !== rootNativeSessionId) {
1268
+ correlatedSession.leader_thread_id = rootNativeSessionId;
1269
+ sessionChanged = true;
1270
+ }
1271
+ if (sessionChanged) {
1272
+ correlatedSession.updated_at = repairTimestamp;
1273
+ changed = true;
1274
+ }
1275
+ }
1276
+ return changed;
1277
+ }
1278
+ /**
1279
+ * Pointer-authoritative root identity repair, decoupled from reopen candidate
1280
+ * output. Runs under the tracker lock and fails closed on malformed bytes.
1281
+ */
1282
+ export function repairPersistedRootIdentity(cwd, context) {
1283
+ const sessionId = context.sessionId.trim();
1284
+ const rootNativeSessionId = context.rootNativeSessionId.trim();
1285
+ if (!sessionId || !rootNativeSessionId)
1286
+ return false;
1287
+ const path = subagentTrackingPath(cwd);
1288
+ return withCrossProcessFileLockSync(path, (lock) => {
1289
+ let raw;
1290
+ try {
1291
+ raw = readFileSync(path, 'utf-8');
1292
+ }
1293
+ catch {
1294
+ return false;
1295
+ }
1296
+ const state = parseStrictSubagentTrackingState(raw);
1297
+ if (!state)
1298
+ return false;
1299
+ const changed = repairPersistedRootIdentityInState(state, sessionId, rootNativeSessionId, new Date().toISOString());
1300
+ if (changed) {
1301
+ lock.assertOwnership();
1302
+ writeSubagentTrackingStateSync(cwd, state, lock.publish);
1303
+ }
1304
+ return changed;
1305
+ });
1306
+ }
1307
+ /**
1308
+ * Quarantine reopen authority for a root that is persisted as a subagent when
1309
+ * the identity evidence is contradictory (for example a self-parented
1310
+ * transcript marker). Unlike full repair this never asserts leader identity, so
1311
+ * legitimate descriptive subagent evidence is preserved; it only strips the
1312
+ * authority and resume bookkeeping that a root must never carry. Returns true
1313
+ * when any record changed.
1314
+ */
1315
+ export function quarantinePersistedRootAuthority(cwd, context) {
1316
+ const sessionId = context.sessionId.trim();
1317
+ const rootNativeSessionId = context.rootNativeSessionId.trim();
1318
+ if (!sessionId || !rootNativeSessionId)
1319
+ return false;
1320
+ const path = subagentTrackingPath(cwd);
1321
+ return withCrossProcessFileLockSync(path, (lock) => {
1322
+ let raw;
1323
+ try {
1324
+ raw = readFileSync(path, 'utf-8');
1325
+ }
1326
+ catch {
1327
+ return false;
1328
+ }
1329
+ const state = parseStrictSubagentTrackingState(raw);
1330
+ if (!state)
1331
+ return false;
1332
+ const timestamp = new Date().toISOString();
1333
+ let changed = false;
1334
+ for (const session of Object.values(state.sessions)) {
1335
+ if (!partitionCorrelatesToRoot(session, sessionId, rootNativeSessionId))
1336
+ continue;
1337
+ const rootThread = session.threads[rootNativeSessionId];
1338
+ if (!rootThread || rootThread.kind !== 'subagent')
1339
+ continue;
1340
+ const carriesAuthority = rootThread.provenance_kind === NATIVE_SUBAGENT_PROVENANCE
1341
+ || rootThread.direct_child_root_id !== undefined
1342
+ || rootThread.direct_child_parent_id !== undefined;
1343
+ let sessionChanged = false;
1344
+ if (carriesAuthority && rootThread.reopen_authority_revoked !== true) {
1345
+ rootThread.reopen_authority_revoked = true;
1346
+ rootThread.reopen_authority_conflict_reason = 'contradictory_root_child_evidence';
1347
+ rootThread.reopen_authority_conflict_at = timestamp;
1348
+ sessionChanged = true;
1349
+ }
1350
+ if (rootThread.resume_requested_at !== undefined
1351
+ || rootThread.resume_completed_at !== undefined
1352
+ || rootThread.resume_failed_at !== undefined
1353
+ || rootThread.resume_failure_reason !== undefined) {
1354
+ delete rootThread.resume_requested_at;
1355
+ delete rootThread.resume_completed_at;
1356
+ delete rootThread.resume_failed_at;
1357
+ delete rootThread.resume_failure_reason;
1358
+ sessionChanged = true;
1359
+ }
1360
+ if (sessionChanged) {
1361
+ session.updated_at = timestamp;
1362
+ changed = true;
1363
+ }
1364
+ }
1365
+ if (changed) {
1366
+ lock.assertOwnership();
1367
+ writeSubagentTrackingStateSync(cwd, state, lock.publish);
1368
+ }
1369
+ return changed;
1370
+ });
1371
+ }
1372
+ /** Strict, lock-serialized authority consumption for SessionStart persisted reopen output. */
1373
+ export function consumeDirectChildReopenContext(cwd, context) {
1374
+ const sessionId = context.sessionId.trim();
1375
+ const rootNativeSessionId = context.rootNativeSessionId.trim();
1376
+ if (!sessionId || !rootNativeSessionId)
1377
+ return null;
1378
+ const path = subagentTrackingPath(cwd);
1379
+ return withCrossProcessFileLockSync(path, (lock) => {
1380
+ let raw;
1381
+ try {
1382
+ raw = readFileSync(path, 'utf-8');
1383
+ }
1384
+ catch (error) {
1385
+ if (error.code === 'ENOENT')
1386
+ return null;
1387
+ return persistedReopenAuthorityWarning('tracker_read_failed');
1388
+ }
1389
+ const state = parseStrictSubagentTrackingState(raw);
1390
+ if (!state)
1391
+ return persistedReopenAuthorityWarning('malformed_tracker_state');
1392
+ // A revocation that could not be published leaves a durable fence. Fenced
1393
+ // ids stay denied here even though the on-disk record still looks valid,
1394
+ // so a failed negative observation can never leave old authority usable.
1395
+ const fence = readAuthorityFence(cwd);
1396
+ if (!fence.ok)
1397
+ return persistedReopenAuthorityWarning('unreadable_authority_fence');
1398
+ const repairTimestamp = new Date().toISOString();
1399
+ // Root identity repair runs before candidate eligibility so a missing
1400
+ // canonical partition cannot leave a known root inversion unrepaired.
1401
+ const changed = repairPersistedRootIdentityInState(state, sessionId, rootNativeSessionId, repairTimestamp);
1402
+ const session = state.sessions[sessionId];
1403
+ if (!session) {
1404
+ if (changed) {
1405
+ lock.assertOwnership();
1406
+ writeSubagentTrackingStateSync(cwd, state, lock.publish);
1407
+ }
1408
+ return null;
1409
+ }
1410
+ const savedSubagents = Object.values(session.threads)
1411
+ .filter((thread) => {
1412
+ if (thread.kind !== 'subagent'
1413
+ || thread.thread_id === sessionId
1414
+ || thread.thread_id === rootNativeSessionId
1415
+ || session.leader_thread_id === thread.thread_id
1416
+ || fence.ids.has(thread.thread_id)
1417
+ || thread.provenance_kind !== NATIVE_SUBAGENT_PROVENANCE
1418
+ || thread.direct_child_root_id !== rootNativeSessionId
1419
+ || thread.direct_child_parent_id !== rootNativeSessionId
1420
+ || thread.reopen_authority_revoked === true)
1421
+ return false;
1422
+ return Object.values(state.sessions).every((correlationSession) => {
1423
+ const view = correlationSession.threads[thread.thread_id];
1424
+ if (!view)
1425
+ return true;
1426
+ // Recognized legacy adapted records are descriptive-only: they never
1427
+ // grant authority and never block a newly attested valid child. The
1428
+ // tolerance is shape-constrained: an adapted view may only waive
1429
+ // missing attestation for a non-leader subagent view and never waives
1430
+ // leader-kind or leader_thread_id contradictions.
1431
+ if (view.provenance_kind === DESCRIPTIVE_ADAPTED_PROVENANCE
1432
+ && view.kind === 'subagent'
1433
+ && correlationSession.leader_thread_id !== thread.thread_id)
1434
+ return true;
1435
+ return correlationSession.leader_thread_id !== thread.thread_id
1436
+ && view.kind === 'subagent'
1437
+ && view.provenance_kind === NATIVE_SUBAGENT_PROVENANCE
1438
+ && view.direct_child_root_id === rootNativeSessionId
1439
+ && view.direct_child_parent_id === rootNativeSessionId
1440
+ && view.reopen_authority_revoked !== true;
1441
+ });
1442
+ })
1443
+ .map((thread) => normalizeLedgerEntry(thread, thread.status ?? 'closed'))
1444
+ .sort(compareResumeEntries);
1445
+ if (!savedSubagents.length) {
1446
+ if (changed) {
1447
+ session.updated_at = new Date().toISOString();
1448
+ lock.assertOwnership();
1449
+ writeSubagentTrackingStateSync(cwd, state, lock.publish);
1450
+ }
1451
+ // Surface intentional exclusion of legacy/non-authoritative records instead
1452
+ // of silently returning no context. The notice fires only when every
1453
+ // relevant view across every correlation partition is genuinely
1454
+ // descriptive/legacy: any authority-bearing, leader-kind, or
1455
+ // leader_thread_id-colliding view keeps the denial fail-closed null.
1456
+ const excludedIds = new Set(Object.values(session.threads)
1457
+ .filter((thread) => thread.kind === 'subagent')
1458
+ .map((thread) => thread.thread_id));
1459
+ const everyRelevantViewIsLegacy = excludedIds.size > 0
1460
+ && Object.values(state.sessions).every((correlationSession) => {
1461
+ if (correlationSession.leader_thread_id && excludedIds.has(correlationSession.leader_thread_id))
1462
+ return false;
1463
+ return [...excludedIds].every((threadId) => {
1464
+ const view = correlationSession.threads[threadId];
1465
+ if (!view)
1466
+ return true;
1467
+ return view.kind === 'subagent'
1468
+ && view.provenance_kind !== NATIVE_SUBAGENT_PROVENANCE
1469
+ && view.direct_child_root_id === undefined
1470
+ && view.direct_child_parent_id === undefined
1471
+ && view.reopen_authority_revoked !== true;
1472
+ });
1473
+ });
1474
+ if (everyRelevantViewIsLegacy) {
1475
+ return persistedReopenLegacyNotice(excludedIds.size, context.source);
1476
+ }
1477
+ return null;
1478
+ }
1479
+ const reopenTargets = savedSubagents.filter((entry) => entry.status !== 'unavailable');
1480
+ const unavailableTargets = savedSubagents.filter((entry) => entry.status === 'unavailable');
1481
+ const failedTargets = savedSubagents.filter((entry) => entry.resumeFailedAt || entry.resumeFailureReason);
1482
+ const now = new Date().toISOString();
1483
+ for (const target of reopenTargets) {
1484
+ session.threads[target.threadId] = { ...session.threads[target.threadId], resume_requested_at: now };
1485
+ }
1486
+ if (reopenTargets.length || changed) {
1487
+ session.updated_at = now;
1488
+ lock.assertOwnership();
1489
+ writeSubagentTrackingStateSync(cwd, state, lock.publish);
1490
+ }
1491
+ const lines = [
1492
+ '[Persisted subagent reopen]',
1493
+ `- SessionStart source: ${context.source}; saved subagent ids found: ${savedSubagents.length}.`,
1494
+ ];
1495
+ if (reopenTargets.length) {
1496
+ lines.push('- Reopen these persisted subagents by id before continuing work or spawning any same-role/same-lane replacement:');
1497
+ lines.push(...reopenTargets.slice(0, 12).map((entry) => ` - resume_agent(${JSON.stringify(entry.agentId)})${formatReopenMetadata(entry)}`));
1498
+ if (reopenTargets.length > 12)
1499
+ lines.push(` - ... ${reopenTargets.length - 12} more saved subagent id(s) omitted from this compact SessionStart context; consult .omx/state/subagent-tracking.json before spawning replacements.`);
1500
+ }
1501
+ else {
1502
+ lines.push('- No compatible saved subagent id is currently marked reopenable; do not spawn a replacement merely because reopen was unavailable.');
1503
+ }
1504
+ lines.push('- Silver rule: when follow-up work targets an existing role/lane, reuse the matching reopened id; avoid duplicate same-type subagent spawns.');
1505
+ lines.push('- If resume_agent fails, surface a clear warning with the id and reason, then continue in the root or another compatible existing lane; do not spawn a new agent solely because reopen failed.');
1506
+ const warnings = [...new Map([...unavailableTargets, ...failedTargets].map((entry) => [entry.agentId, entry])).values()];
1507
+ if (warnings.length) {
1508
+ lines.push('- Reopen warnings:');
1509
+ lines.push(...warnings.slice(0, 8).map((entry) => ` - ${entry.agentId}${formatReopenMetadata(entry)}`));
1510
+ if (warnings.length > 8)
1511
+ lines.push(` - ... ${warnings.length - 8} more warning(s) omitted from this compact SessionStart context.`);
1512
+ }
1513
+ return lines.join('\n');
1514
+ });
1515
+ }
1065
1516
  export function summarizeSubagentSession(state, sessionId, options = {}) {
1066
1517
  const normalized = normalizeSubagentTrackingState(state);
1067
1518
  const session = normalized.sessions[sessionId];