oh-my-codex 0.21.0 → 0.21.2

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 (346) hide show
  1. package/Cargo.toml +1 -1
  2. package/README.md +4 -2
  3. package/dist/cli/__tests__/detached-launch-diagnostics-3578.test.d.ts +2 -0
  4. package/dist/cli/__tests__/detached-launch-diagnostics-3578.test.d.ts.map +1 -0
  5. package/dist/cli/__tests__/detached-launch-diagnostics-3578.test.js +592 -0
  6. package/dist/cli/__tests__/detached-launch-diagnostics-3578.test.js.map +1 -0
  7. package/dist/cli/__tests__/detached-leader-teardown.test.js +270 -33
  8. package/dist/cli/__tests__/detached-leader-teardown.test.js.map +1 -1
  9. package/dist/cli/__tests__/doctor-warning-copy.test.js +133 -5
  10. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  11. package/dist/cli/__tests__/index.test.js +543 -2
  12. package/dist/cli/__tests__/index.test.js.map +1 -1
  13. package/dist/cli/__tests__/issue-windows-cancel-identity.test.d.ts +2 -0
  14. package/dist/cli/__tests__/issue-windows-cancel-identity.test.d.ts.map +1 -0
  15. package/dist/cli/__tests__/issue-windows-cancel-identity.test.js +17 -0
  16. package/dist/cli/__tests__/issue-windows-cancel-identity.test.js.map +1 -0
  17. package/dist/cli/__tests__/launch-fallback.test.js +15 -3
  18. package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
  19. package/dist/cli/__tests__/package-bin-contract.test.js +20 -0
  20. package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
  21. package/dist/cli/__tests__/plugin-cache-stale-lock.test.d.ts +2 -0
  22. package/dist/cli/__tests__/plugin-cache-stale-lock.test.d.ts.map +1 -0
  23. package/dist/cli/__tests__/plugin-cache-stale-lock.test.js +76 -0
  24. package/dist/cli/__tests__/plugin-cache-stale-lock.test.js.map +1 -0
  25. package/dist/cli/__tests__/plugin-cache-symlink-provenance-3552.test.d.ts +2 -0
  26. package/dist/cli/__tests__/plugin-cache-symlink-provenance-3552.test.d.ts.map +1 -0
  27. package/dist/cli/__tests__/plugin-cache-symlink-provenance-3552.test.js +1662 -0
  28. package/dist/cli/__tests__/plugin-cache-symlink-provenance-3552.test.js.map +1 -0
  29. package/dist/cli/__tests__/plugin-launcher-provenance-3558.test.d.ts +2 -0
  30. package/dist/cli/__tests__/plugin-launcher-provenance-3558.test.d.ts.map +1 -0
  31. package/dist/cli/__tests__/plugin-launcher-provenance-3558.test.js +522 -0
  32. package/dist/cli/__tests__/plugin-launcher-provenance-3558.test.js.map +1 -0
  33. package/dist/cli/__tests__/ralplan-advisory.test.d.ts +2 -0
  34. package/dist/cli/__tests__/ralplan-advisory.test.d.ts.map +1 -0
  35. package/dist/cli/__tests__/ralplan-advisory.test.js +301 -0
  36. package/dist/cli/__tests__/ralplan-advisory.test.js.map +1 -0
  37. package/dist/cli/__tests__/ralplan.test.js +39 -0
  38. package/dist/cli/__tests__/ralplan.test.js.map +1 -1
  39. package/dist/cli/__tests__/resume.test.js +81 -6
  40. package/dist/cli/__tests__/resume.test.js.map +1 -1
  41. package/dist/cli/__tests__/session-scoped-runtime.test.js +423 -4
  42. package/dist/cli/__tests__/session-scoped-runtime.test.js.map +1 -1
  43. package/dist/cli/__tests__/setup-install-mode.test.js +60 -3
  44. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  45. package/dist/cli/__tests__/setup-refresh.test.js +9 -0
  46. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  47. package/dist/cli/__tests__/sparkshell-cli.test.js +4 -0
  48. package/dist/cli/__tests__/sparkshell-cli.test.js.map +1 -1
  49. package/dist/cli/__tests__/ultragoal.test.js +71 -0
  50. package/dist/cli/__tests__/ultragoal.test.js.map +1 -1
  51. package/dist/cli/__tests__/windows-popup-loop-contract.test.js +30 -4
  52. package/dist/cli/__tests__/windows-popup-loop-contract.test.js.map +1 -1
  53. package/dist/cli/doctor.d.ts.map +1 -1
  54. package/dist/cli/doctor.js +57 -8
  55. package/dist/cli/doctor.js.map +1 -1
  56. package/dist/cli/index.d.ts +113 -7
  57. package/dist/cli/index.d.ts.map +1 -1
  58. package/dist/cli/index.js +1850 -185
  59. package/dist/cli/index.js.map +1 -1
  60. package/dist/cli/native-assets.d.ts +2 -1
  61. package/dist/cli/native-assets.d.ts.map +1 -1
  62. package/dist/cli/native-assets.js +7 -7
  63. package/dist/cli/native-assets.js.map +1 -1
  64. package/dist/cli/plugin-marketplace.d.ts +89 -4
  65. package/dist/cli/plugin-marketplace.d.ts.map +1 -1
  66. package/dist/cli/plugin-marketplace.js +2135 -118
  67. package/dist/cli/plugin-marketplace.js.map +1 -1
  68. package/dist/cli/ralplan.d.ts +2 -1
  69. package/dist/cli/ralplan.d.ts.map +1 -1
  70. package/dist/cli/ralplan.js +149 -9
  71. package/dist/cli/ralplan.js.map +1 -1
  72. package/dist/cli/setup.d.ts.map +1 -1
  73. package/dist/cli/setup.js +7 -1
  74. package/dist/cli/setup.js.map +1 -1
  75. package/dist/cli/sparkshell.d.ts.map +1 -1
  76. package/dist/cli/sparkshell.js +9 -10
  77. package/dist/cli/sparkshell.js.map +1 -1
  78. package/dist/cli/update-worker.js +9 -0
  79. package/dist/cli/update-worker.js.map +1 -1
  80. package/dist/cli/update.d.ts +1 -0
  81. package/dist/cli/update.d.ts.map +1 -1
  82. package/dist/cli/update.js +9 -0
  83. package/dist/cli/update.js.map +1 -1
  84. package/dist/config/__tests__/generator-idempotent.test.js +118 -0
  85. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  86. package/dist/config/generator.d.ts.map +1 -1
  87. package/dist/config/generator.js +74 -4
  88. package/dist/config/generator.js.map +1 -1
  89. package/dist/hooks/__tests__/keyword-detector.test.js +74 -0
  90. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  91. package/dist/hooks/__tests__/ralplan-advisory-keyword.test.d.ts +2 -0
  92. package/dist/hooks/__tests__/ralplan-advisory-keyword.test.d.ts.map +1 -0
  93. package/dist/hooks/__tests__/ralplan-advisory-keyword.test.js +250 -0
  94. package/dist/hooks/__tests__/ralplan-advisory-keyword.test.js.map +1 -0
  95. package/dist/hooks/__tests__/session.test.js +10 -1
  96. package/dist/hooks/__tests__/session.test.js.map +1 -1
  97. package/dist/hooks/keyword-detector.d.ts +8 -1
  98. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  99. package/dist/hooks/keyword-detector.js +130 -9
  100. package/dist/hooks/keyword-detector.js.map +1 -1
  101. package/dist/hooks/session.d.ts +5 -0
  102. package/dist/hooks/session.d.ts.map +1 -1
  103. package/dist/hooks/session.js +17 -3
  104. package/dist/hooks/session.js.map +1 -1
  105. package/dist/hud/__tests__/index.test.js +16 -1
  106. package/dist/hud/__tests__/index.test.js.map +1 -1
  107. package/dist/hud/__tests__/reconcile.test.js +134 -7
  108. package/dist/hud/__tests__/reconcile.test.js.map +1 -1
  109. package/dist/hud/__tests__/render.test.js +57 -0
  110. package/dist/hud/__tests__/render.test.js.map +1 -1
  111. package/dist/hud/__tests__/session-attached.test.d.ts +2 -0
  112. package/dist/hud/__tests__/session-attached.test.d.ts.map +1 -0
  113. package/dist/hud/__tests__/session-attached.test.js +82 -0
  114. package/dist/hud/__tests__/session-attached.test.js.map +1 -0
  115. package/dist/hud/__tests__/state.test.js +94 -2
  116. package/dist/hud/__tests__/state.test.js.map +1 -1
  117. package/dist/hud/__tests__/tmux-split-realtmux.test.js +169 -2
  118. package/dist/hud/__tests__/tmux-split-realtmux.test.js.map +1 -1
  119. package/dist/hud/__tests__/tmux.test.js +104 -22
  120. package/dist/hud/__tests__/tmux.test.js.map +1 -1
  121. package/dist/hud/__tests__/types.test.js +9 -0
  122. package/dist/hud/__tests__/types.test.js.map +1 -1
  123. package/dist/hud/__tests__/watch-detached.test.d.ts +2 -0
  124. package/dist/hud/__tests__/watch-detached.test.d.ts.map +1 -0
  125. package/dist/hud/__tests__/watch-detached.test.js +383 -0
  126. package/dist/hud/__tests__/watch-detached.test.js.map +1 -0
  127. package/dist/hud/__tests__/watch-realtmux.test.d.ts +2 -0
  128. package/dist/hud/__tests__/watch-realtmux.test.d.ts.map +1 -0
  129. package/dist/hud/__tests__/watch-realtmux.test.js +322 -0
  130. package/dist/hud/__tests__/watch-realtmux.test.js.map +1 -0
  131. package/dist/hud/index.d.ts +3 -0
  132. package/dist/hud/index.d.ts.map +1 -1
  133. package/dist/hud/index.js +98 -20
  134. package/dist/hud/index.js.map +1 -1
  135. package/dist/hud/reconcile.d.ts.map +1 -1
  136. package/dist/hud/reconcile.js +68 -24
  137. package/dist/hud/reconcile.js.map +1 -1
  138. package/dist/hud/render.d.ts.map +1 -1
  139. package/dist/hud/render.js +20 -0
  140. package/dist/hud/render.js.map +1 -1
  141. package/dist/hud/session-attached.d.ts +15 -0
  142. package/dist/hud/session-attached.d.ts.map +1 -0
  143. package/dist/hud/session-attached.js +47 -0
  144. package/dist/hud/session-attached.js.map +1 -0
  145. package/dist/hud/state.d.ts +9 -1
  146. package/dist/hud/state.d.ts.map +1 -1
  147. package/dist/hud/state.js +140 -3
  148. package/dist/hud/state.js.map +1 -1
  149. package/dist/hud/tmux.d.ts +4 -0
  150. package/dist/hud/tmux.d.ts.map +1 -1
  151. package/dist/hud/tmux.js +86 -23
  152. package/dist/hud/tmux.js.map +1 -1
  153. package/dist/hud/types.d.ts +20 -0
  154. package/dist/hud/types.d.ts.map +1 -1
  155. package/dist/hud/types.js +3 -0
  156. package/dist/hud/types.js.map +1 -1
  157. package/dist/modes/base.d.ts +15 -2
  158. package/dist/modes/base.d.ts.map +1 -1
  159. package/dist/modes/base.js +105 -16
  160. package/dist/modes/base.js.map +1 -1
  161. package/dist/question/autopilot-wait.js +2 -2
  162. package/dist/question/autopilot-wait.js.map +1 -1
  163. package/dist/question/deep-interview.js +2 -2
  164. package/dist/question/deep-interview.js.map +1 -1
  165. package/dist/ralplan/__tests__/advisory-activation.test.d.ts +2 -0
  166. package/dist/ralplan/__tests__/advisory-activation.test.d.ts.map +1 -0
  167. package/dist/ralplan/__tests__/advisory-activation.test.js +700 -0
  168. package/dist/ralplan/__tests__/advisory-activation.test.js.map +1 -0
  169. package/dist/ralplan/__tests__/advisory-detached-transition.test.d.ts +2 -0
  170. package/dist/ralplan/__tests__/advisory-detached-transition.test.d.ts.map +1 -0
  171. package/dist/ralplan/__tests__/advisory-detached-transition.test.js +88 -0
  172. package/dist/ralplan/__tests__/advisory-detached-transition.test.js.map +1 -0
  173. package/dist/ralplan/__tests__/advisory.test.d.ts +2 -0
  174. package/dist/ralplan/__tests__/advisory.test.d.ts.map +1 -0
  175. package/dist/ralplan/__tests__/advisory.test.js +1087 -0
  176. package/dist/ralplan/__tests__/advisory.test.js.map +1 -0
  177. package/dist/ralplan/__tests__/runtime.test.js +416 -7
  178. package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
  179. package/dist/ralplan/advisory-activation-verifier.d.ts +19 -0
  180. package/dist/ralplan/advisory-activation-verifier.d.ts.map +1 -0
  181. package/dist/ralplan/advisory-activation-verifier.js +77 -0
  182. package/dist/ralplan/advisory-activation-verifier.js.map +1 -0
  183. package/dist/ralplan/advisory-activation.d.ts +34 -0
  184. package/dist/ralplan/advisory-activation.d.ts.map +1 -0
  185. package/dist/ralplan/advisory-activation.js +205 -0
  186. package/dist/ralplan/advisory-activation.js.map +1 -0
  187. package/dist/ralplan/advisory-contract.d.ts +103 -0
  188. package/dist/ralplan/advisory-contract.d.ts.map +1 -0
  189. package/dist/ralplan/advisory-contract.js +83 -0
  190. package/dist/ralplan/advisory-contract.js.map +1 -0
  191. package/dist/ralplan/advisory-detached-transition.d.ts +11 -0
  192. package/dist/ralplan/advisory-detached-transition.d.ts.map +1 -0
  193. package/dist/ralplan/advisory-detached-transition.js +21 -0
  194. package/dist/ralplan/advisory-detached-transition.js.map +1 -0
  195. package/dist/ralplan/advisory-evidence.d.ts +91 -0
  196. package/dist/ralplan/advisory-evidence.d.ts.map +1 -0
  197. package/dist/ralplan/advisory-evidence.js +393 -0
  198. package/dist/ralplan/advisory-evidence.js.map +1 -0
  199. package/dist/ralplan/advisory-lifecycle-validation.d.ts +11 -0
  200. package/dist/ralplan/advisory-lifecycle-validation.d.ts.map +1 -0
  201. package/dist/ralplan/advisory-lifecycle-validation.js +72 -0
  202. package/dist/ralplan/advisory-lifecycle-validation.js.map +1 -0
  203. package/dist/ralplan/advisory-postconditions.d.ts +3 -0
  204. package/dist/ralplan/advisory-postconditions.d.ts.map +1 -0
  205. package/dist/ralplan/advisory-postconditions.js +33 -0
  206. package/dist/ralplan/advisory-postconditions.js.map +1 -0
  207. package/dist/ralplan/advisory-recovery-journal.d.ts +56 -0
  208. package/dist/ralplan/advisory-recovery-journal.d.ts.map +1 -0
  209. package/dist/ralplan/advisory-recovery-journal.js +136 -0
  210. package/dist/ralplan/advisory-recovery-journal.js.map +1 -0
  211. package/dist/ralplan/advisory-storage.d.ts +24 -0
  212. package/dist/ralplan/advisory-storage.d.ts.map +1 -0
  213. package/dist/ralplan/advisory-storage.js +106 -0
  214. package/dist/ralplan/advisory-storage.js.map +1 -0
  215. package/dist/ralplan/advisory.d.ts +99 -0
  216. package/dist/ralplan/advisory.d.ts.map +1 -0
  217. package/dist/ralplan/advisory.js +914 -0
  218. package/dist/ralplan/advisory.js.map +1 -0
  219. package/dist/ralplan/documented-leader-preflight.d.ts +8 -0
  220. package/dist/ralplan/documented-leader-preflight.d.ts.map +1 -1
  221. package/dist/ralplan/documented-leader-preflight.js +60 -2
  222. package/dist/ralplan/documented-leader-preflight.js.map +1 -1
  223. package/dist/ralplan/runtime-advisory-lifecycle.d.ts +74 -0
  224. package/dist/ralplan/runtime-advisory-lifecycle.d.ts.map +1 -0
  225. package/dist/ralplan/runtime-advisory-lifecycle.js +352 -0
  226. package/dist/ralplan/runtime-advisory-lifecycle.js.map +1 -0
  227. package/dist/ralplan/runtime-contract.d.ts +126 -0
  228. package/dist/ralplan/runtime-contract.d.ts.map +1 -0
  229. package/dist/ralplan/runtime-contract.js +2 -0
  230. package/dist/ralplan/runtime-contract.js.map +1 -0
  231. package/dist/ralplan/runtime.d.ts +6 -109
  232. package/dist/ralplan/runtime.d.ts.map +1 -1
  233. package/dist/ralplan/runtime.js +174 -56
  234. package/dist/ralplan/runtime.js.map +1 -1
  235. package/dist/scripts/__tests__/postinstall.test.js +26 -0
  236. package/dist/scripts/__tests__/postinstall.test.js.map +1 -1
  237. package/dist/scripts/__tests__/ralplan-advisory-hook.test.d.ts +2 -0
  238. package/dist/scripts/__tests__/ralplan-advisory-hook.test.d.ts.map +1 -0
  239. package/dist/scripts/__tests__/ralplan-advisory-hook.test.js +299 -0
  240. package/dist/scripts/__tests__/ralplan-advisory-hook.test.js.map +1 -0
  241. package/dist/scripts/__tests__/smoke-packed-install.test.js +102 -8
  242. package/dist/scripts/__tests__/smoke-packed-install.test.js.map +1 -1
  243. package/dist/scripts/codex-native-hook.d.ts +20 -0
  244. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  245. package/dist/scripts/codex-native-hook.js +226 -15
  246. package/dist/scripts/codex-native-hook.js.map +1 -1
  247. package/dist/scripts/postinstall.d.ts +7 -0
  248. package/dist/scripts/postinstall.d.ts.map +1 -1
  249. package/dist/scripts/postinstall.js +40 -0
  250. package/dist/scripts/postinstall.js.map +1 -1
  251. package/dist/scripts/smoke-packed-install.d.ts +21 -5
  252. package/dist/scripts/smoke-packed-install.d.ts.map +1 -1
  253. package/dist/scripts/smoke-packed-install.js +157 -57
  254. package/dist/scripts/smoke-packed-install.js.map +1 -1
  255. package/dist/state/__tests__/mode-binding-lease.test.d.ts +2 -0
  256. package/dist/state/__tests__/mode-binding-lease.test.d.ts.map +1 -0
  257. package/dist/state/__tests__/mode-binding-lease.test.js +498 -0
  258. package/dist/state/__tests__/mode-binding-lease.test.js.map +1 -0
  259. package/dist/state/__tests__/operations.test.js +1 -0
  260. package/dist/state/__tests__/operations.test.js.map +1 -1
  261. package/dist/state/__tests__/pinned-atomic-file.test.d.ts +2 -0
  262. package/dist/state/__tests__/pinned-atomic-file.test.d.ts.map +1 -0
  263. package/dist/state/__tests__/pinned-atomic-file.test.js +438 -0
  264. package/dist/state/__tests__/pinned-atomic-file.test.js.map +1 -0
  265. package/dist/state/__tests__/skill-active-exact-session-transaction.test.d.ts +2 -0
  266. package/dist/state/__tests__/skill-active-exact-session-transaction.test.d.ts.map +1 -0
  267. package/dist/state/__tests__/skill-active-exact-session-transaction.test.js +179 -0
  268. package/dist/state/__tests__/skill-active-exact-session-transaction.test.js.map +1 -0
  269. package/dist/state/__tests__/skill-active.test.js +29 -1
  270. package/dist/state/__tests__/skill-active.test.js.map +1 -1
  271. package/dist/state/mode-binding-lease-helper.d.ts +2 -0
  272. package/dist/state/mode-binding-lease-helper.d.ts.map +1 -0
  273. package/dist/state/mode-binding-lease-helper.js +623 -0
  274. package/dist/state/mode-binding-lease-helper.js.map +1 -0
  275. package/dist/state/mode-binding-lease.d.ts +25 -0
  276. package/dist/state/mode-binding-lease.d.ts.map +1 -0
  277. package/dist/state/mode-binding-lease.js +380 -0
  278. package/dist/state/mode-binding-lease.js.map +1 -0
  279. package/dist/state/operations.d.ts +29 -1
  280. package/dist/state/operations.d.ts.map +1 -1
  281. package/dist/state/operations.js +203 -122
  282. package/dist/state/operations.js.map +1 -1
  283. package/dist/state/pinned-atomic-file-darwin-client.d.ts +34 -0
  284. package/dist/state/pinned-atomic-file-darwin-client.d.ts.map +1 -0
  285. package/dist/state/pinned-atomic-file-darwin-client.js +174 -0
  286. package/dist/state/pinned-atomic-file-darwin-client.js.map +1 -0
  287. package/dist/state/pinned-atomic-file-darwin-helper.d.ts +2 -0
  288. package/dist/state/pinned-atomic-file-darwin-helper.d.ts.map +1 -0
  289. package/dist/state/pinned-atomic-file-darwin-helper.js +275 -0
  290. package/dist/state/pinned-atomic-file-darwin-helper.js.map +1 -0
  291. package/dist/state/pinned-atomic-file-darwin-janitor.d.ts +2 -0
  292. package/dist/state/pinned-atomic-file-darwin-janitor.d.ts.map +1 -0
  293. package/dist/state/pinned-atomic-file-darwin-janitor.js +272 -0
  294. package/dist/state/pinned-atomic-file-darwin-janitor.js.map +1 -0
  295. package/dist/state/pinned-atomic-file.d.ts +29 -0
  296. package/dist/state/pinned-atomic-file.d.ts.map +1 -0
  297. package/dist/state/pinned-atomic-file.js +536 -0
  298. package/dist/state/pinned-atomic-file.js.map +1 -0
  299. package/dist/state/process-identity.d.ts +30 -0
  300. package/dist/state/process-identity.d.ts.map +1 -0
  301. package/dist/state/process-identity.js +99 -0
  302. package/dist/state/process-identity.js.map +1 -0
  303. package/dist/state/skill-active.d.ts +18 -1
  304. package/dist/state/skill-active.d.ts.map +1 -1
  305. package/dist/state/skill-active.js +82 -5
  306. package/dist/state/skill-active.js.map +1 -1
  307. package/dist/state/workflow-transition-reconcile.js +1 -1
  308. package/dist/state/workflow-transition-reconcile.js.map +1 -1
  309. package/dist/team/__tests__/tmux-session.test.js +109 -0
  310. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  311. package/dist/team/tmux-session.d.ts.map +1 -1
  312. package/dist/team/tmux-session.js +10 -1
  313. package/dist/team/tmux-session.js.map +1 -1
  314. package/dist/utils/__tests__/file-durability.test.js +20 -0
  315. package/dist/utils/__tests__/file-durability.test.js.map +1 -1
  316. package/dist/utils/file-durability.d.ts +1 -1
  317. package/dist/utils/file-durability.d.ts.map +1 -1
  318. package/dist/utils/file-durability.js.map +1 -1
  319. package/dist/verification/__tests__/ci-rust-gates.test.js +12 -4
  320. package/dist/verification/__tests__/ci-rust-gates.test.js.map +1 -1
  321. package/dist/verification/__tests__/ci-trusted-publish.test.d.ts +2 -0
  322. package/dist/verification/__tests__/ci-trusted-publish.test.d.ts.map +1 -0
  323. package/dist/verification/__tests__/ci-trusted-publish.test.js +205 -0
  324. package/dist/verification/__tests__/ci-trusted-publish.test.js.map +1 -0
  325. package/dist/verification/__tests__/explore-harness-release-workflow.test.js +10 -5
  326. package/dist/verification/__tests__/explore-harness-release-workflow.test.js.map +1 -1
  327. package/dist/verification/__tests__/publish-npm-manual-retired.test.d.ts +2 -0
  328. package/dist/verification/__tests__/publish-npm-manual-retired.test.d.ts.map +1 -0
  329. package/dist/verification/__tests__/publish-npm-manual-retired.test.js +22 -0
  330. package/dist/verification/__tests__/publish-npm-manual-retired.test.js.map +1 -0
  331. package/package.json +1 -1
  332. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  333. package/plugins/oh-my-codex/skills/doctor/SKILL.md +1 -1
  334. package/plugins/oh-my-codex/skills/omx-setup/SKILL.md +1 -1
  335. package/plugins/oh-my-codex/skills/ralplan/SKILL.md +10 -1
  336. package/plugins/oh-my-codex/skills/team/SKILL.md +2 -0
  337. package/skills/doctor/SKILL.md +1 -1
  338. package/skills/omx-setup/SKILL.md +1 -1
  339. package/skills/ralplan/SKILL.md +10 -1
  340. package/skills/team/SKILL.md +2 -0
  341. package/src/scripts/__tests__/postinstall.test.ts +28 -0
  342. package/src/scripts/__tests__/ralplan-advisory-hook.test.ts +320 -0
  343. package/src/scripts/__tests__/smoke-packed-install.test.ts +120 -7
  344. package/src/scripts/codex-native-hook.ts +220 -10
  345. package/src/scripts/postinstall.ts +46 -0
  346. package/src/scripts/smoke-packed-install.ts +175 -70
@@ -0,0 +1,623 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { constants as fsConstants } from 'node:fs';
3
+ import { lstat, mkdir, open, readdir, rename, stat, unlink, writeFile } from 'node:fs/promises';
4
+ import { basename } from 'node:path';
5
+ import { createInterface } from 'node:readline';
6
+ import { formatProcessOwnerToken, hashHistoricalProcessStartIdentity, hashProcessStartIdentity, parseProcessOwnerToken, readHistoricalProcessStartIdentity, readProcessStartIdentity, } from './process-identity.js';
7
+ const INITIAL_RETRY_MS = 20;
8
+ const MAX_RETRY_MS = 500;
9
+ const PARTIAL_OWNER_STALE_MS = 5_000;
10
+ const BOOTSTRAP_OWNER_TOKEN = `99999999-1-${'0'.repeat(24)}`;
11
+ function respond(value) { process.stdout.write(`${JSON.stringify(value)}\n`); }
12
+ function sameIdentity(value, expected) {
13
+ return value.dev === expected.dev && value.ino === expected.ino;
14
+ }
15
+ function validOwnerToken(value) {
16
+ return parseProcessOwnerToken(value) !== null;
17
+ }
18
+ async function readPinnedRecord(name, token) {
19
+ if (!validOwnerToken(token) || basename(name) !== name)
20
+ return { kind: 'invalid' };
21
+ let handle = null;
22
+ try {
23
+ handle = await open(name, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW);
24
+ const before = await handle.stat();
25
+ const visible = await lstat(name);
26
+ if (!before.isFile() || before.nlink !== 1 || !visible.isFile() || visible.isSymbolicLink()
27
+ || visible.nlink !== 1 || !sameIdentity(before, visible))
28
+ return { kind: 'changed' };
29
+ const value = await handle.readFile('utf8');
30
+ const after = await handle.stat();
31
+ if (!sameIdentity(before, after) || before.size !== after.size
32
+ || before.mtimeMs !== after.mtimeMs || before.ctimeMs !== after.ctimeMs)
33
+ return { kind: 'changed' };
34
+ const owner = {
35
+ name,
36
+ token,
37
+ identity: {
38
+ dev: before.dev,
39
+ ino: before.ino,
40
+ size: before.size,
41
+ mtimeMs: before.mtimeMs,
42
+ ctimeMs: before.ctimeMs,
43
+ },
44
+ };
45
+ if (value === token)
46
+ return { kind: 'valid', owner };
47
+ const publishedPrefix = value.length < token.length && token.startsWith(value);
48
+ return publishedPrefix ? { kind: 'partial', owner } : { kind: 'invalid' };
49
+ }
50
+ catch (error) {
51
+ if (error.code === 'ENOENT')
52
+ return { kind: 'missing' };
53
+ throw error;
54
+ }
55
+ finally {
56
+ await handle?.close();
57
+ }
58
+ }
59
+ async function readPinnedOwner() {
60
+ const entries = await readdir('.');
61
+ if (entries.length === 0)
62
+ return { kind: 'ownerless' };
63
+ const ownerNames = entries.filter((entry) => entry.startsWith('owner-'));
64
+ const displacedNames = entries.filter((entry) => entry.startsWith('.owner-reclaim-'));
65
+ const candidateNames = entries.filter((entry) => entry.startsWith('.owner-publish-'));
66
+ const bootstrapName = `owner-${BOOTSTRAP_OWNER_TOKEN}`;
67
+ if (ownerNames.length === 2 && ownerNames.includes(bootstrapName)
68
+ && displacedNames.length === 0 && candidateNames.length === 0)
69
+ return { kind: 'unstable' };
70
+ if (ownerNames.length > 1 || displacedNames.length > 1 || candidateNames.length > 1
71
+ || ownerNames.length + displacedNames.length + candidateNames.length !== entries.length)
72
+ return { kind: 'ambiguous', reason: `entries:${entries.join(',')}` };
73
+ const owner = ownerNames[0]
74
+ ? await readPinnedRecord(ownerNames[0], ownerNames[0].slice('owner-'.length))
75
+ : undefined;
76
+ const displacedName = displacedNames[0] ? parseDisplacedName(displacedNames[0]) : null;
77
+ const displacedRecord = displacedName
78
+ ? await readPinnedRecord(displacedNames[0], displacedName.originalToken)
79
+ : undefined;
80
+ const displaced = displacedRecord && (displacedRecord.kind === 'valid' || displacedRecord.kind === 'partial')
81
+ ? { ...displacedRecord, owner: { ...displacedRecord.owner, claimantToken: displacedName?.claimantToken } }
82
+ : displacedRecord;
83
+ const candidate = candidateNames[0]
84
+ ? await readPinnedRecord(candidateNames[0], candidateNames[0].slice('.owner-publish-'.length))
85
+ : undefined;
86
+ if (owner?.kind === 'missing' || owner?.kind === 'changed'
87
+ || displaced?.kind === 'missing' || displaced?.kind === 'changed'
88
+ || candidate?.kind === 'missing' || candidate?.kind === 'changed')
89
+ return { kind: 'unstable' };
90
+ if (owner?.kind === 'invalid' || displaced?.kind === 'invalid' || candidate?.kind === 'invalid')
91
+ return { kind: 'ambiguous', reason: 'invalid-record' };
92
+ const stableOwner = presentRecord(owner) ? owner : undefined;
93
+ const stableDisplaced = presentRecord(displaced) ? displaced : undefined;
94
+ const stableCandidate = presentRecord(candidate) ? candidate : undefined;
95
+ if (stableCandidate && stableOwner)
96
+ return { kind: 'unstable' };
97
+ const isDeadRecord = async (record) => record.kind === 'valid' && await tokenIsStaleAndDead(record.owner.token)
98
+ || record.kind === 'partial' && isOld(record.owner) && await tokenIsStaleAndDead(record.owner.token);
99
+ const isBoundOrDead = async (record) => record.owner.claimantToken !== undefined
100
+ || await isDeadRecord(record);
101
+ if (stableCandidate && stableDisplaced && !stableOwner) {
102
+ if (!await isBoundOrDead(stableDisplaced))
103
+ return { kind: 'unstable' };
104
+ if (stableDisplaced.owner.claimantToken !== stableCandidate.owner.token)
105
+ return { kind: 'ambiguous', reason: 'candidate-claimant-mismatch' };
106
+ return {
107
+ kind: 'preparing', candidate: stableCandidate.owner, candidateComplete: stableCandidate.kind === 'valid', displaced: stableDisplaced.owner,
108
+ };
109
+ }
110
+ if (stableOwner && stableDisplaced) {
111
+ if (!await isBoundOrDead(stableDisplaced))
112
+ return { kind: 'unstable' };
113
+ if (stableDisplaced.owner.claimantToken !== undefined && stableDisplaced.owner.claimantToken !== stableOwner.owner.token
114
+ || stableOwner.owner.identity.dev === stableDisplaced.owner.identity.dev
115
+ && stableOwner.owner.identity.ino === stableDisplaced.owner.identity.ino)
116
+ return { kind: 'ambiguous', reason: 'published-binding-mismatch' };
117
+ if (stableOwner.kind === 'valid')
118
+ return { kind: 'published', owner: stableOwner.owner, displaced: stableDisplaced.owner };
119
+ return isOld(stableOwner.owner) && await tokenIsStaleAndDead(stableOwner.owner.token)
120
+ ? { kind: 'partial-published', owner: stableOwner.owner, displaced: stableDisplaced.owner }
121
+ : { kind: 'unstable' };
122
+ }
123
+ if (stableDisplaced)
124
+ return await isBoundOrDead(stableDisplaced)
125
+ ? { kind: 'interrupted', displaced: stableDisplaced.owner }
126
+ : { kind: 'unstable' };
127
+ if (stableCandidate)
128
+ return { kind: 'unstable' };
129
+ if (stableOwner?.kind === 'valid')
130
+ return { kind: 'valid', owner: stableOwner.owner };
131
+ if (stableOwner?.kind === 'partial' && isOld(stableOwner.owner) && await tokenIsStaleAndDead(stableOwner.owner.token)) {
132
+ return { kind: 'recoverable', owner: stableOwner.owner };
133
+ }
134
+ return stableOwner ? { kind: 'unstable' } : { kind: 'ownerless' };
135
+ }
136
+ function presentRecord(record) {
137
+ return record?.kind === 'valid' || record?.kind === 'partial';
138
+ }
139
+ function parseDisplacedName(name) {
140
+ const encoded = name.slice('.owner-reclaim-'.length);
141
+ const separator = encoded.indexOf('~');
142
+ const originalToken = separator < 0 ? encoded : encoded.slice(0, separator);
143
+ const claimantToken = separator < 0 ? undefined : encoded.slice(separator + 1);
144
+ if (!validOwnerToken(originalToken) || claimantToken !== undefined && !validOwnerToken(claimantToken))
145
+ return null;
146
+ return { originalToken, ...(claimantToken ? { claimantToken } : {}) };
147
+ }
148
+ function isOld(owner) {
149
+ return Date.now() - owner.identity.mtimeMs >= PARTIAL_OWNER_STALE_MS;
150
+ }
151
+ function sameOwner(a, b) {
152
+ return a.name === b.name && a.token === b.token && a.claimantToken === b.claimantToken
153
+ && a.identity.dev === b.identity.dev && a.identity.ino === b.identity.ino
154
+ && a.identity.size === b.identity.size && a.identity.mtimeMs === b.identity.mtimeMs
155
+ && a.identity.ctimeMs === b.identity.ctimeMs;
156
+ }
157
+ async function replaceObservedOwner(observed, replacementToken) {
158
+ const quarantine = `.owner-reclaim-${observed.token}~${replacementToken}`;
159
+ try {
160
+ await rename(observed.name, quarantine);
161
+ }
162
+ catch (error) {
163
+ if (error.code === 'ENOENT')
164
+ return false;
165
+ throw error;
166
+ }
167
+ await maybeCrashReclaimPhase('after-quarantine');
168
+ const moved = await lstat(quarantine);
169
+ if (!moved.isFile() || moved.isSymbolicLink() || moved.nlink !== 1
170
+ || moved.dev !== observed.identity.dev || moved.ino !== observed.identity.ino
171
+ || moved.size !== observed.identity.size || moved.mtimeMs !== observed.identity.mtimeMs) {
172
+ return false;
173
+ }
174
+ await publishSuccessor(replacementToken);
175
+ const published = await readPinnedOwner();
176
+ if (published.kind !== 'published' || published.owner.token !== replacementToken
177
+ || published.displaced.name !== quarantine
178
+ || published.displaced.identity.dev !== observed.identity.dev
179
+ || published.displaced.identity.ino !== observed.identity.ino
180
+ || published.displaced.identity.size !== observed.identity.size
181
+ || published.displaced.identity.mtimeMs !== observed.identity.mtimeMs) {
182
+ return false;
183
+ }
184
+ await maybeCrashReclaimPhase('before-cleanup');
185
+ await unlink(quarantine);
186
+ return true;
187
+ }
188
+ async function publishSuccessor(replacementToken) {
189
+ const candidate = `.owner-publish-${replacementToken}`;
190
+ await writeFile(candidate, replacementToken, { flag: 'wx', mode: 0o600 });
191
+ const staged = await readPinnedRecord(candidate, replacementToken);
192
+ if (staged?.kind !== 'valid')
193
+ throw new Error('canonical state lock successor staging changed');
194
+ await rename(candidate, `owner-${replacementToken}`);
195
+ await maybeCrashReclaimPhase('after-successor-publish');
196
+ }
197
+ async function restoreDisplacedOwner(displaced) {
198
+ try {
199
+ await rename(displaced.name, `owner-${displaced.token}`);
200
+ return true;
201
+ }
202
+ catch (error) {
203
+ if (error.code === 'ENOENT')
204
+ return false;
205
+ throw error;
206
+ }
207
+ }
208
+ async function maybeCrashReclaimPhase(phase) {
209
+ if (process.env.OMX_TEST_MODE_BINDING_RECLAIM_CRASH_PHASE !== phase)
210
+ return;
211
+ const sentinel = process.env.OMX_TEST_MODE_BINDING_RECLAIM_CRASH_SENTINEL;
212
+ if (!sentinel)
213
+ return;
214
+ let handle = null;
215
+ try {
216
+ handle = await open(sentinel, fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL | fsConstants.O_NOFOLLOW, 0o600);
217
+ await handle.writeFile(phase);
218
+ await handle.sync();
219
+ }
220
+ catch (error) {
221
+ if (error.code === 'EEXIST')
222
+ return;
223
+ throw error;
224
+ }
225
+ finally {
226
+ await handle?.close();
227
+ }
228
+ process.kill(process.pid, 'SIGKILL');
229
+ }
230
+ async function ownerIsDead(value) {
231
+ const parsed = parseProcessOwnerToken(value);
232
+ if (!parsed)
233
+ return false;
234
+ try {
235
+ process.kill(parsed.pid, 0);
236
+ }
237
+ catch (error) {
238
+ return error.code === 'ESRCH';
239
+ }
240
+ if (parsed.version === 'v2' && parsed.processStartHash === 'unknown'
241
+ || parsed.version === 'v3' && parsed.processStartHash === 'unavailable')
242
+ return false;
243
+ if (parsed.version === 'v2' || parsed.version === 'v3') {
244
+ const reader = parsed.version === 'v2' ? readHistoricalProcessStartIdentity : readProcessStartIdentity;
245
+ const hash = parsed.version === 'v2' ? hashHistoricalProcessStartIdentity : hashProcessStartIdentity;
246
+ const current = await reader(parsed.pid);
247
+ if (current === null || hash(current) === parsed.processStartHash)
248
+ return false;
249
+ // A live PID identity mismatch is security-sensitive: re-sample before
250
+ // reclaim so a transient platform probe cannot evict the real owner.
251
+ await new Promise((resolve) => setTimeout(resolve, INITIAL_RETRY_MS));
252
+ const confirmed = await reader(parsed.pid);
253
+ return confirmed !== null
254
+ && hash(confirmed) === hash(current)
255
+ && hash(confirmed) !== parsed.processStartHash;
256
+ }
257
+ return false;
258
+ }
259
+ async function tokenIsStaleAndDead(value) {
260
+ const parsed = parseProcessOwnerToken(value);
261
+ return parsed !== null && Date.now() - parsed.issuedAtMs >= PARTIAL_OWNER_STALE_MS && await ownerIsDead(value);
262
+ }
263
+ async function runPinnedClaimMode(args) {
264
+ const [lockName = '', replacementToken = '', nsDev = '', nsIno = '', lockDev = '', lockIno = ''] = args;
265
+ if (!lockName || basename(lockName) !== lockName || !replacementToken || !nsDev || !nsIno || !lockDev || !lockIno) {
266
+ throw new Error('invalid pinned canonical state lock claim arguments');
267
+ }
268
+ const namespaceIdentity = { dev: Number(nsDev), ino: Number(nsIno) };
269
+ const lockIdentity = { dev: Number(lockDev), ino: Number(lockIno) };
270
+ const current = await stat('.');
271
+ const parent = await stat('..');
272
+ if (!current.isDirectory() || !parent.isDirectory()
273
+ || !sameIdentity(current, lockIdentity) || !sameIdentity(parent, namespaceIdentity)) {
274
+ respond({ claimed: false });
275
+ return;
276
+ }
277
+ const bootstrapName = `owner-${BOOTSTRAP_OWNER_TOKEN}`;
278
+ const initialEntries = await readdir('.');
279
+ if (initialEntries.includes(bootstrapName) && initialEntries.length > 1) {
280
+ const bootstrap = await readPinnedRecord(bootstrapName, BOOTSTRAP_OWNER_TOKEN);
281
+ if (bootstrap.kind === 'valid') {
282
+ const confirmedEntries = await readdir('.');
283
+ const confirmed = await readPinnedRecord(bootstrapName, BOOTSTRAP_OWNER_TOKEN);
284
+ if (confirmed.kind === 'valid' && sameOwner(bootstrap.owner, confirmed.owner)
285
+ && confirmedEntries.includes(bootstrapName) && confirmedEntries.length > 1) {
286
+ await unlink(bootstrapName);
287
+ respond({ claimed: false });
288
+ return;
289
+ }
290
+ }
291
+ }
292
+ const observed = await readPinnedOwner();
293
+ if (observed.kind === 'unstable') {
294
+ respond({ claimed: false });
295
+ return;
296
+ }
297
+ if (observed.kind === 'ambiguous') {
298
+ await new Promise((resolve) => setTimeout(resolve, INITIAL_RETRY_MS));
299
+ if ((await readPinnedOwner()).kind !== 'ambiguous') {
300
+ respond({ claimed: false });
301
+ return;
302
+ }
303
+ throw new Error(`canonical state lock owner ambiguous:${observed.reason}`);
304
+ }
305
+ if (observed.kind === 'valid') {
306
+ if (observed.owner.token === replacementToken) {
307
+ respond({ claimed: true });
308
+ return;
309
+ }
310
+ if (!await tokenIsStaleAndDead(observed.owner.token)) {
311
+ respond({ claimed: false });
312
+ return;
313
+ }
314
+ }
315
+ if (observed.kind === 'published' && observed.owner.token !== replacementToken
316
+ && !await tokenIsStaleAndDead(observed.owner.token)) {
317
+ respond({ claimed: false });
318
+ return;
319
+ }
320
+ if (observed.kind === 'preparing' && observed.candidate.token !== replacementToken
321
+ && !await tokenIsStaleAndDead(observed.candidate.token)) {
322
+ respond({ claimed: false });
323
+ return;
324
+ }
325
+ if (observed.kind === 'interrupted' && observed.displaced.claimantToken
326
+ && observed.displaced.claimantToken !== replacementToken
327
+ && !await tokenIsStaleAndDead(observed.displaced.claimantToken)) {
328
+ respond({ claimed: false });
329
+ return;
330
+ }
331
+ const confirmed = await readPinnedOwner();
332
+ const changed = !sameOwnerState(confirmed, observed);
333
+ if (changed || !sameIdentity(await stat('.'), lockIdentity) || !sameIdentity(await stat('..'), namespaceIdentity)) {
334
+ respond({ claimed: false });
335
+ return;
336
+ }
337
+ if (observed.kind === 'preparing') {
338
+ if (observed.candidate.token === replacementToken) {
339
+ if (!observed.candidateComplete)
340
+ await unlink(observed.candidate.name);
341
+ if (observed.candidateComplete) {
342
+ await rename(observed.candidate.name, `owner-${replacementToken}`);
343
+ await maybeCrashReclaimPhase('after-successor-publish');
344
+ }
345
+ else {
346
+ await publishSuccessor(replacementToken);
347
+ }
348
+ await maybeCrashReclaimPhase('before-cleanup');
349
+ await unlink(observed.displaced.name);
350
+ }
351
+ else {
352
+ await unlink(observed.candidate.name);
353
+ await restoreDisplacedOwner(observed.displaced);
354
+ respond({ claimed: false });
355
+ return;
356
+ }
357
+ }
358
+ else if (observed.kind === 'partial-published') {
359
+ await unlink(observed.owner.name);
360
+ await restoreDisplacedOwner(observed.displaced);
361
+ respond({ claimed: false });
362
+ return;
363
+ }
364
+ else if (observed.kind === 'published') {
365
+ if (observed.owner.token === replacementToken) {
366
+ await maybeCrashReclaimPhase('before-cleanup');
367
+ await unlink(observed.displaced.name);
368
+ }
369
+ else {
370
+ // Both recorded owners are dead. Collapse to the newer canonical owner;
371
+ // the next retry can reclaim that single inode through the normal path.
372
+ await unlink(observed.displaced.name);
373
+ respond({ claimed: false });
374
+ return;
375
+ }
376
+ }
377
+ else if (observed.kind === 'interrupted') {
378
+ if (observed.displaced.claimantToken === replacementToken) {
379
+ await publishSuccessor(replacementToken);
380
+ await maybeCrashReclaimPhase('before-cleanup');
381
+ await unlink(observed.displaced.name);
382
+ }
383
+ else {
384
+ // A legacy quarantine or a dead claimant is restored to the canonical
385
+ // owner name. Exactly one contender can win this rename; the next
386
+ // rename to a claimant-bound quarantine elects the successor.
387
+ await restoreDisplacedOwner(observed.displaced);
388
+ respond({ claimed: false });
389
+ return;
390
+ }
391
+ }
392
+ else if (observed.kind === 'valid' || observed.kind === 'recoverable') {
393
+ if (!await replaceObservedOwner(observed.owner, replacementToken)) {
394
+ respond({ claimed: false });
395
+ return;
396
+ }
397
+ }
398
+ else {
399
+ // Legacy empty persistent directories converge on one canonical owner
400
+ // pathname. This is an owner record, not a second election protocol.
401
+ try {
402
+ const bootstrapName = `owner-${BOOTSTRAP_OWNER_TOKEN}`;
403
+ await writeFile(bootstrapName, BOOTSTRAP_OWNER_TOKEN, { flag: 'wx', mode: 0o600 });
404
+ const bootstrap = await readPinnedRecord(bootstrapName, BOOTSTRAP_OWNER_TOKEN);
405
+ const visible = await readdir('.');
406
+ if (bootstrap.kind === 'valid' && (visible.length !== 1 || visible[0] !== bootstrapName)) {
407
+ const current = await readPinnedRecord(bootstrapName, BOOTSTRAP_OWNER_TOKEN);
408
+ if (current.kind === 'valid' && sameOwner(current.owner, bootstrap.owner))
409
+ await unlink(bootstrapName);
410
+ respond({ claimed: false });
411
+ return;
412
+ }
413
+ }
414
+ catch (error) {
415
+ if (error.code !== 'EEXIST')
416
+ throw error;
417
+ respond({ claimed: false });
418
+ return;
419
+ }
420
+ }
421
+ const finalOwner = await readPinnedOwner();
422
+ respond({ claimed: finalOwner.kind === 'valid' && finalOwner.owner.token === replacementToken });
423
+ }
424
+ function sameOwnerState(a, b) {
425
+ if (a.kind !== b.kind)
426
+ return false;
427
+ if ((a.kind === 'valid' || a.kind === 'recoverable') && (b.kind === 'valid' || b.kind === 'recoverable')) {
428
+ return sameOwner(a.owner, b.owner);
429
+ }
430
+ if (a.kind === 'interrupted' && b.kind === 'interrupted')
431
+ return sameOwner(a.displaced, b.displaced);
432
+ if (a.kind === 'preparing' && b.kind === 'preparing') {
433
+ return a.candidateComplete === b.candidateComplete
434
+ && sameOwner(a.candidate, b.candidate) && sameOwner(a.displaced, b.displaced);
435
+ }
436
+ if (a.kind === 'published' && b.kind === 'published') {
437
+ return sameOwner(a.owner, b.owner) && sameOwner(a.displaced, b.displaced);
438
+ }
439
+ if (a.kind === 'partial-published' && b.kind === 'partial-published') {
440
+ return sameOwner(a.owner, b.owner) && sameOwner(a.displaced, b.displaced);
441
+ }
442
+ return a.kind === 'ownerless' && b.kind === 'ownerless'
443
+ || a.kind === 'unstable' && b.kind === 'unstable'
444
+ || a.kind === 'ambiguous' && b.kind === 'ambiguous';
445
+ }
446
+ if (process.argv[2] === '--claim-existing') {
447
+ try {
448
+ await runPinnedClaimMode(process.argv.slice(3));
449
+ }
450
+ catch (error) {
451
+ respond({ claimed: false, error: error instanceof Error ? error.message : String(error) });
452
+ }
453
+ process.exit(0);
454
+ }
455
+ const [key = '', nonce = '', expectedDev = '', expectedIno = ''] = process.argv.slice(2);
456
+ if (!key || basename(key) !== key || !nonce || !expectedDev || !expectedIno) {
457
+ throw new Error('invalid canonical state lock helper arguments');
458
+ }
459
+ const [issuedAt = '', randomNonce = ''] = nonce.split('-', 2);
460
+ const processStartIdentity = await readProcessStartIdentity(process.pid);
461
+ const token = formatProcessOwnerToken({
462
+ pid: process.pid,
463
+ issuedAtMs: Number(issuedAt),
464
+ nonce: randomNonce,
465
+ processStartIdentity,
466
+ });
467
+ const lockName = `${key}.lock`;
468
+ const namespaceIdentity = { dev: Number(expectedDev), ino: Number(expectedIno) };
469
+ let lockIdentity = null;
470
+ async function assertPinnedNamespace() {
471
+ const current = await stat(lockIdentity ? '..' : '.');
472
+ if (!current.isDirectory() || !sameIdentity(current, namespaceIdentity)) {
473
+ throw new Error('canonical state lock namespace changed');
474
+ }
475
+ }
476
+ async function assertPinnedLock() {
477
+ if (!lockIdentity)
478
+ throw new Error('canonical state lock was not pinned');
479
+ await assertPinnedNamespace();
480
+ const pinned = await stat('.');
481
+ let visible;
482
+ try {
483
+ visible = await lstat(`../${lockName}`);
484
+ }
485
+ catch {
486
+ throw new Error('canonical state lock ownership lost');
487
+ }
488
+ if (!pinned.isDirectory() || !visible.isDirectory() || visible.isSymbolicLink()
489
+ || !sameIdentity(pinned, lockIdentity) || !sameIdentity(visible, lockIdentity)) {
490
+ throw new Error('canonical state lock ownership lost');
491
+ }
492
+ }
493
+ async function enterPinnedLock(expected) {
494
+ process.chdir(lockName);
495
+ const pinned = await stat('.');
496
+ const parent = await stat('..');
497
+ if (!pinned.isDirectory() || !sameIdentity(pinned, expected) || !sameIdentity(parent, namespaceIdentity)) {
498
+ throw new Error('canonical state lock changed while pinning');
499
+ }
500
+ lockIdentity = expected;
501
+ }
502
+ async function tryClaimExisting(expected) {
503
+ const child = spawn(process.execPath, [process.argv[1], '--claim-existing', lockName, token,
504
+ String(namespaceIdentity.dev), String(namespaceIdentity.ino), String(expected.dev), String(expected.ino)], {
505
+ cwd: lockName, stdio: ['ignore', 'pipe', 'ignore'], windowsHide: true,
506
+ });
507
+ let output = '';
508
+ child.stdout.setEncoding('utf8');
509
+ child.stdout.on('data', (chunk) => { output += chunk; });
510
+ const code = await new Promise((resolve) => child.once('close', resolve));
511
+ if (code !== 0)
512
+ return false;
513
+ const parsed = JSON.parse(output.trim());
514
+ if (typeof parsed.error === 'string')
515
+ throw new Error(parsed.error);
516
+ return parsed.claimed === true;
517
+ }
518
+ async function acquire() {
519
+ // Keep the helper below the caller's explicit 35s initialization ceiling,
520
+ // while leaving enough room for Darwin process-start revalidation under the
521
+ // documented 64-contender recovery stress.
522
+ const deadline = Date.now() + 34_000;
523
+ let retryMs = INITIAL_RETRY_MS;
524
+ for (;;) {
525
+ await assertPinnedNamespace();
526
+ try {
527
+ await mkdir(lockName);
528
+ const created = await lstat(lockName);
529
+ if (!created.isDirectory() || created.isSymbolicLink())
530
+ throw new Error('canonical state lock invalid');
531
+ const identity = { dev: created.dev, ino: created.ino };
532
+ await enterPinnedLock(identity);
533
+ await writeFile(`owner-${token}`, token, { flag: 'wx', mode: 0o600 });
534
+ return;
535
+ }
536
+ catch (error) {
537
+ if (lockIdentity)
538
+ throw error;
539
+ let visible = null;
540
+ try {
541
+ visible = await lstat(lockName);
542
+ }
543
+ catch (readError) {
544
+ if (readError.code !== 'ENOENT')
545
+ throw readError;
546
+ }
547
+ if (visible?.isDirectory() && !visible.isSymbolicLink()) {
548
+ const identity = { dev: visible.dev, ino: visible.ino };
549
+ if (await tryClaimExisting(identity)) {
550
+ const confirmed = await lstat(lockName);
551
+ if (!confirmed.isDirectory() || confirmed.isSymbolicLink() || !sameIdentity(confirmed, identity)) {
552
+ throw new Error('canonical state lock changed after stale claim');
553
+ }
554
+ await enterPinnedLock(identity);
555
+ const claimed = await readPinnedOwner();
556
+ if (claimed.kind !== 'valid' || claimed.owner.token !== token)
557
+ throw new Error('canonical state lock ownership lost');
558
+ return;
559
+ }
560
+ }
561
+ else if (visible) {
562
+ throw new Error('canonical state lock invalid');
563
+ }
564
+ if (error.code !== 'EEXIST')
565
+ throw error;
566
+ if (Date.now() >= deadline)
567
+ throw new Error(`timed out waiting for canonical state lock: ${lockName}`);
568
+ const jitter = Math.floor(Math.random() * retryMs);
569
+ const remaining = Math.max(0, deadline - Date.now());
570
+ await new Promise((resolve) => setTimeout(resolve, Math.min(remaining, retryMs + jitter)));
571
+ if (Date.now() >= deadline)
572
+ throw new Error(`timed out waiting for canonical state lock: ${lockName}`);
573
+ retryMs = Math.min(MAX_RETRY_MS, retryMs * 2);
574
+ }
575
+ }
576
+ }
577
+ async function release() {
578
+ await assertPinnedLock();
579
+ const observed = await readPinnedOwner();
580
+ if (observed.kind !== 'valid' || observed.owner.token !== token)
581
+ throw new Error('canonical state lock ownership lost');
582
+ await unlink(`owner-${token}`);
583
+ // The persistent directory remains pinned; a successor converges through
584
+ // the fixed bootstrap owner without depending on this process's liveness.
585
+ }
586
+ let acquired = false;
587
+ try {
588
+ await acquire();
589
+ acquired = true;
590
+ await new Promise((resolve) => process.stdout.write(`${JSON.stringify({ id: 0, ok: true, ready: true })}\n`, () => resolve()));
591
+ }
592
+ catch (error) {
593
+ const response = { id: 0, ok: false, error: error instanceof Error ? error.message : String(error) };
594
+ await new Promise((resolve) => process.stdout.write(`${JSON.stringify(response)}\n`, () => resolve()));
595
+ }
596
+ const lines = createInterface({ input: process.stdin, crlfDelay: Infinity });
597
+ if (!acquired)
598
+ lines.close();
599
+ for await (const line of acquired ? lines : []) {
600
+ let id;
601
+ try {
602
+ const request = JSON.parse(line);
603
+ id = request.id;
604
+ if (request.op === 'assert') {
605
+ await assertPinnedLock();
606
+ const observed = await readPinnedOwner();
607
+ if (observed.kind !== 'valid' || observed.owner.token !== token)
608
+ throw new Error('canonical state lock ownership lost');
609
+ respond({ id, ok: true });
610
+ }
611
+ else if (request.op === 'close') {
612
+ await release();
613
+ respond({ id, ok: true });
614
+ break;
615
+ }
616
+ else
617
+ throw new Error('unsupported canonical state lock operation');
618
+ }
619
+ catch (error) {
620
+ respond({ id, ok: false, error: error instanceof Error ? error.message : String(error) });
621
+ }
622
+ }
623
+ //# sourceMappingURL=mode-binding-lease-helper.js.map