oh-my-copilot 5.6.2 → 5.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (264) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +2 -0
  4. package/bridge/__pycache__/gyoshu_bridge.cpython-312.pyc +0 -0
  5. package/bridge/claude-md-coordinator.cjs +2 -2
  6. package/bridge/cli.cjs +81405 -76559
  7. package/bridge/mcp-server.cjs +40048 -32187
  8. package/bridge/runtime-cli.cjs +943 -199
  9. package/bridge/team-mcp.cjs +716 -125
  10. package/bridge/team.js +1066 -290
  11. package/dist/__tests__/cli-win32-warning.test.js +7 -0
  12. package/dist/__tests__/cli-win32-warning.test.js.map +1 -1
  13. package/dist/__tests__/run-cjs-copilot-worker-routing.test.d.ts +2 -0
  14. package/dist/__tests__/run-cjs-copilot-worker-routing.test.d.ts.map +1 -0
  15. package/dist/__tests__/run-cjs-copilot-worker-routing.test.js +294 -0
  16. package/dist/__tests__/run-cjs-copilot-worker-routing.test.js.map +1 -0
  17. package/dist/__tests__/run-cjs-fail-closed-timeout.test.d.ts +2 -0
  18. package/dist/__tests__/run-cjs-fail-closed-timeout.test.d.ts.map +1 -0
  19. package/dist/__tests__/run-cjs-fail-closed-timeout.test.js +68 -0
  20. package/dist/__tests__/run-cjs-fail-closed-timeout.test.js.map +1 -0
  21. package/dist/__tests__/run-cjs-session-end-budget.test.d.ts +2 -0
  22. package/dist/__tests__/run-cjs-session-end-budget.test.d.ts.map +1 -0
  23. package/dist/__tests__/run-cjs-session-end-budget.test.js +171 -0
  24. package/dist/__tests__/run-cjs-session-end-budget.test.js.map +1 -0
  25. package/dist/__tests__/session-end-process-exit.test.js +70 -2
  26. package/dist/__tests__/session-end-process-exit.test.js.map +1 -1
  27. package/dist/cli/__tests__/doctor-conflicts-json.test.d.ts +2 -0
  28. package/dist/cli/__tests__/doctor-conflicts-json.test.d.ts.map +1 -0
  29. package/dist/cli/__tests__/doctor-conflicts-json.test.js +46 -0
  30. package/dist/cli/__tests__/doctor-conflicts-json.test.js.map +1 -0
  31. package/dist/cli/__tests__/team.test.js +67 -0
  32. package/dist/cli/__tests__/team.test.js.map +1 -1
  33. package/dist/cli/__tests__/wait-teleport-json.test.d.ts +2 -0
  34. package/dist/cli/__tests__/wait-teleport-json.test.d.ts.map +1 -0
  35. package/dist/cli/__tests__/wait-teleport-json.test.js +64 -0
  36. package/dist/cli/__tests__/wait-teleport-json.test.js.map +1 -0
  37. package/dist/cli/commands/__tests__/smoke-tier2.test.d.ts +2 -0
  38. package/dist/cli/commands/__tests__/smoke-tier2.test.d.ts.map +1 -0
  39. package/dist/cli/commands/__tests__/smoke-tier2.test.js +110 -0
  40. package/dist/cli/commands/__tests__/smoke-tier2.test.js.map +1 -0
  41. package/dist/cli/commands/__tests__/team-status-json.test.d.ts +2 -0
  42. package/dist/cli/commands/__tests__/team-status-json.test.d.ts.map +1 -0
  43. package/dist/cli/commands/__tests__/team-status-json.test.js +141 -0
  44. package/dist/cli/commands/__tests__/team-status-json.test.js.map +1 -0
  45. package/dist/cli/commands/factory.d.ts +2 -0
  46. package/dist/cli/commands/factory.d.ts.map +1 -1
  47. package/dist/cli/commands/factory.js +8 -1
  48. package/dist/cli/commands/factory.js.map +1 -1
  49. package/dist/cli/commands/smoke.d.ts +36 -0
  50. package/dist/cli/commands/smoke.d.ts.map +1 -0
  51. package/dist/cli/commands/smoke.js +169 -0
  52. package/dist/cli/commands/smoke.js.map +1 -0
  53. package/dist/cli/commands/team.d.ts +2 -0
  54. package/dist/cli/commands/team.d.ts.map +1 -1
  55. package/dist/cli/commands/team.js +124 -10
  56. package/dist/cli/commands/team.js.map +1 -1
  57. package/dist/cli/index.d.ts.map +1 -1
  58. package/dist/cli/index.js +70 -10
  59. package/dist/cli/index.js.map +1 -1
  60. package/dist/cli/win32-warning.d.ts +7 -1
  61. package/dist/cli/win32-warning.d.ts.map +1 -1
  62. package/dist/cli/win32-warning.js +9 -1
  63. package/dist/cli/win32-warning.js.map +1 -1
  64. package/dist/constants/names.d.ts +1 -0
  65. package/dist/constants/names.d.ts.map +1 -1
  66. package/dist/constants/names.js +1 -0
  67. package/dist/constants/names.js.map +1 -1
  68. package/dist/factory/__tests__/check-run.test.js +2 -2
  69. package/dist/factory/__tests__/check-run.test.js.map +1 -1
  70. package/dist/factory/__tests__/listener.test.js +1 -1
  71. package/dist/factory/__tests__/listener.test.js.map +1 -1
  72. package/dist/factory/__tests__/status.test.js +42 -0
  73. package/dist/factory/__tests__/status.test.js.map +1 -1
  74. package/dist/factory/listener.d.ts.map +1 -1
  75. package/dist/factory/listener.js +7 -8
  76. package/dist/factory/listener.js.map +1 -1
  77. package/dist/factory/status.d.ts +19 -1
  78. package/dist/factory/status.d.ts.map +1 -1
  79. package/dist/factory/status.js +49 -8
  80. package/dist/factory/status.js.map +1 -1
  81. package/dist/factory/watchdog.d.ts.map +1 -1
  82. package/dist/factory/watchdog.js +14 -0
  83. package/dist/factory/watchdog.js.map +1 -1
  84. package/dist/factory/watchdog.test.js +45 -0
  85. package/dist/factory/watchdog.test.js.map +1 -1
  86. package/dist/hooks/session-end/__tests__/action-runner.test.js +5 -0
  87. package/dist/hooks/session-end/__tests__/action-runner.test.js.map +1 -1
  88. package/dist/hooks/session-end/__tests__/chain-enqueuer.test.js +186 -1
  89. package/dist/hooks/session-end/__tests__/chain-enqueuer.test.js.map +1 -1
  90. package/dist/hooks/session-end/__tests__/chain-link-env-passthrough.test.d.ts +2 -0
  91. package/dist/hooks/session-end/__tests__/chain-link-env-passthrough.test.d.ts.map +1 -0
  92. package/dist/hooks/session-end/__tests__/chain-link-env-passthrough.test.js +49 -0
  93. package/dist/hooks/session-end/__tests__/chain-link-env-passthrough.test.js.map +1 -0
  94. package/dist/hooks/session-end/__tests__/foreground-bootstrap.test.js +30 -1
  95. package/dist/hooks/session-end/__tests__/foreground-bootstrap.test.js.map +1 -1
  96. package/dist/hooks/session-end/__tests__/spawn-next.test.js +104 -3
  97. package/dist/hooks/session-end/__tests__/spawn-next.test.js.map +1 -1
  98. package/dist/hooks/session-end/__tests__/worker.test.js +6 -0
  99. package/dist/hooks/session-end/__tests__/worker.test.js.map +1 -1
  100. package/dist/hooks/session-end/action-runner.d.ts.map +1 -1
  101. package/dist/hooks/session-end/action-runner.js +2 -1
  102. package/dist/hooks/session-end/action-runner.js.map +1 -1
  103. package/dist/hooks/session-end/chain-enqueuer.d.ts +70 -7
  104. package/dist/hooks/session-end/chain-enqueuer.d.ts.map +1 -1
  105. package/dist/hooks/session-end/chain-enqueuer.js +354 -82
  106. package/dist/hooks/session-end/chain-enqueuer.js.map +1 -1
  107. package/dist/hooks/session-end/foreground-bootstrap.d.ts.map +1 -1
  108. package/dist/hooks/session-end/foreground-bootstrap.js +24 -16
  109. package/dist/hooks/session-end/foreground-bootstrap.js.map +1 -1
  110. package/dist/hooks/session-end/index.d.ts +2 -3
  111. package/dist/hooks/session-end/index.d.ts.map +1 -1
  112. package/dist/hooks/session-end/index.js +11 -13
  113. package/dist/hooks/session-end/index.js.map +1 -1
  114. package/dist/hooks/session-end/spawn-next.d.ts +81 -4
  115. package/dist/hooks/session-end/spawn-next.d.ts.map +1 -1
  116. package/dist/hooks/session-end/spawn-next.js +141 -14
  117. package/dist/hooks/session-end/spawn-next.js.map +1 -1
  118. package/dist/hooks/session-end/wiki-foreground-bootstrap.d.ts +17 -0
  119. package/dist/hooks/session-end/wiki-foreground-bootstrap.d.ts.map +1 -0
  120. package/dist/hooks/session-end/wiki-foreground-bootstrap.js +20 -0
  121. package/dist/hooks/session-end/wiki-foreground-bootstrap.js.map +1 -0
  122. package/dist/hooks/session-end/worker.d.ts.map +1 -1
  123. package/dist/hooks/session-end/worker.js +11 -6
  124. package/dist/hooks/session-end/worker.js.map +1 -1
  125. package/dist/lib/__tests__/atomic-write.test.js +32 -1
  126. package/dist/lib/__tests__/atomic-write.test.js.map +1 -1
  127. package/dist/lib/__tests__/mode-state-lock.test.js +121 -12
  128. package/dist/lib/__tests__/mode-state-lock.test.js.map +1 -1
  129. package/dist/lib/atomic-write.d.ts +13 -3
  130. package/dist/lib/atomic-write.d.ts.map +1 -1
  131. package/dist/lib/atomic-write.js +29 -15
  132. package/dist/lib/atomic-write.js.map +1 -1
  133. package/dist/lib/mode-state-io.d.ts +2 -2
  134. package/dist/lib/mode-state-io.d.ts.map +1 -1
  135. package/dist/lib/mode-state-io.js +16 -10
  136. package/dist/lib/mode-state-io.js.map +1 -1
  137. package/dist/mcp/__tests__/host-smoke-tool.test.d.ts +2 -0
  138. package/dist/mcp/__tests__/host-smoke-tool.test.d.ts.map +1 -0
  139. package/dist/mcp/__tests__/host-smoke-tool.test.js +174 -0
  140. package/dist/mcp/__tests__/host-smoke-tool.test.js.map +1 -0
  141. package/dist/mcp/disable-tools.d.ts.map +1 -1
  142. package/dist/mcp/disable-tools.js +1 -0
  143. package/dist/mcp/disable-tools.js.map +1 -1
  144. package/dist/mcp/tool-registry.d.ts.map +1 -1
  145. package/dist/mcp/tool-registry.js +2 -0
  146. package/dist/mcp/tool-registry.js.map +1 -1
  147. package/dist/shared/types.d.ts +11 -0
  148. package/dist/shared/types.d.ts.map +1 -1
  149. package/dist/smoke/__tests__/copilot-chain-scenario.test.d.ts +2 -0
  150. package/dist/smoke/__tests__/copilot-chain-scenario.test.d.ts.map +1 -0
  151. package/dist/smoke/__tests__/copilot-chain-scenario.test.js +154 -0
  152. package/dist/smoke/__tests__/copilot-chain-scenario.test.js.map +1 -0
  153. package/dist/smoke/__tests__/copilot-sdk-driver.test.d.ts +2 -0
  154. package/dist/smoke/__tests__/copilot-sdk-driver.test.d.ts.map +1 -0
  155. package/dist/smoke/__tests__/copilot-sdk-driver.test.js +591 -0
  156. package/dist/smoke/__tests__/copilot-sdk-driver.test.js.map +1 -0
  157. package/dist/smoke/__tests__/copilot-sdk-scenarios.test.d.ts +2 -0
  158. package/dist/smoke/__tests__/copilot-sdk-scenarios.test.d.ts.map +1 -0
  159. package/dist/smoke/__tests__/copilot-sdk-scenarios.test.js +362 -0
  160. package/dist/smoke/__tests__/copilot-sdk-scenarios.test.js.map +1 -0
  161. package/dist/smoke/__tests__/copilot-session-eval.test.d.ts +2 -0
  162. package/dist/smoke/__tests__/copilot-session-eval.test.d.ts.map +1 -0
  163. package/dist/smoke/__tests__/copilot-session-eval.test.js +264 -0
  164. package/dist/smoke/__tests__/copilot-session-eval.test.js.map +1 -0
  165. package/dist/smoke/__tests__/copilot-smoke.test.d.ts +2 -0
  166. package/dist/smoke/__tests__/copilot-smoke.test.d.ts.map +1 -0
  167. package/dist/smoke/__tests__/copilot-smoke.test.js +482 -0
  168. package/dist/smoke/__tests__/copilot-smoke.test.js.map +1 -0
  169. package/dist/smoke/copilot-binary.d.ts +21 -0
  170. package/dist/smoke/copilot-binary.d.ts.map +1 -0
  171. package/dist/smoke/copilot-binary.js +46 -0
  172. package/dist/smoke/copilot-binary.js.map +1 -0
  173. package/dist/smoke/copilot-chain-scenario.d.ts +61 -0
  174. package/dist/smoke/copilot-chain-scenario.d.ts.map +1 -0
  175. package/dist/smoke/copilot-chain-scenario.js +253 -0
  176. package/dist/smoke/copilot-chain-scenario.js.map +1 -0
  177. package/dist/smoke/copilot-sdk-driver.d.ts +189 -0
  178. package/dist/smoke/copilot-sdk-driver.d.ts.map +1 -0
  179. package/dist/smoke/copilot-sdk-driver.js +637 -0
  180. package/dist/smoke/copilot-sdk-driver.js.map +1 -0
  181. package/dist/smoke/copilot-sdk-scenarios.d.ts +200 -0
  182. package/dist/smoke/copilot-sdk-scenarios.d.ts.map +1 -0
  183. package/dist/smoke/copilot-sdk-scenarios.js +620 -0
  184. package/dist/smoke/copilot-sdk-scenarios.js.map +1 -0
  185. package/dist/smoke/copilot-session-env.d.ts +75 -0
  186. package/dist/smoke/copilot-session-env.d.ts.map +1 -0
  187. package/dist/smoke/copilot-session-env.js +154 -0
  188. package/dist/smoke/copilot-session-env.js.map +1 -0
  189. package/dist/smoke/copilot-session-eval.d.ts +90 -0
  190. package/dist/smoke/copilot-session-eval.d.ts.map +1 -0
  191. package/dist/smoke/copilot-session-eval.js +323 -0
  192. package/dist/smoke/copilot-session-eval.js.map +1 -0
  193. package/dist/smoke/copilot-smoke.d.ts +166 -0
  194. package/dist/smoke/copilot-smoke.d.ts.map +1 -0
  195. package/dist/smoke/copilot-smoke.js +688 -0
  196. package/dist/smoke/copilot-smoke.js.map +1 -0
  197. package/dist/smoke/process-utils.d.ts +47 -0
  198. package/dist/smoke/process-utils.d.ts.map +1 -0
  199. package/dist/smoke/process-utils.js +99 -0
  200. package/dist/smoke/process-utils.js.map +1 -0
  201. package/dist/team/__tests__/runtime-v2.monitor.test.js +35 -0
  202. package/dist/team/__tests__/runtime-v2.monitor.test.js.map +1 -1
  203. package/dist/team/__tests__/scaling.test.js +13 -0
  204. package/dist/team/__tests__/scaling.test.js.map +1 -1
  205. package/dist/team/__tests__/sdk-transport.runtime.test.d.ts +2 -0
  206. package/dist/team/__tests__/sdk-transport.runtime.test.d.ts.map +1 -0
  207. package/dist/team/__tests__/sdk-transport.runtime.test.js +96 -0
  208. package/dist/team/__tests__/sdk-transport.runtime.test.js.map +1 -0
  209. package/dist/team/__tests__/sdk-transport.test.d.ts +2 -0
  210. package/dist/team/__tests__/sdk-transport.test.d.ts.map +1 -0
  211. package/dist/team/__tests__/sdk-transport.test.js +650 -0
  212. package/dist/team/__tests__/sdk-transport.test.js.map +1 -0
  213. package/dist/team/__tests__/tmux-session.pane-env.test.d.ts +2 -0
  214. package/dist/team/__tests__/tmux-session.pane-env.test.d.ts.map +1 -0
  215. package/dist/team/__tests__/tmux-session.pane-env.test.js +52 -0
  216. package/dist/team/__tests__/tmux-session.pane-env.test.js.map +1 -0
  217. package/dist/team/api-interop.d.ts.map +1 -1
  218. package/dist/team/api-interop.js +8 -0
  219. package/dist/team/api-interop.js.map +1 -1
  220. package/dist/team/runtime-v2.d.ts +33 -0
  221. package/dist/team/runtime-v2.d.ts.map +1 -1
  222. package/dist/team/runtime-v2.js +338 -24
  223. package/dist/team/runtime-v2.js.map +1 -1
  224. package/dist/team/scaling.d.ts.map +1 -1
  225. package/dist/team/scaling.js +5 -0
  226. package/dist/team/scaling.js.map +1 -1
  227. package/dist/team/sdk-host.d.ts +48 -0
  228. package/dist/team/sdk-host.d.ts.map +1 -0
  229. package/dist/team/sdk-host.js +604 -0
  230. package/dist/team/sdk-host.js.map +1 -0
  231. package/dist/team/sdk-policy.d.ts +79 -0
  232. package/dist/team/sdk-policy.d.ts.map +1 -0
  233. package/dist/team/sdk-policy.js +186 -0
  234. package/dist/team/sdk-policy.js.map +1 -0
  235. package/dist/team/sdk-transport.d.ts +164 -0
  236. package/dist/team/sdk-transport.d.ts.map +1 -0
  237. package/dist/team/sdk-transport.js +266 -0
  238. package/dist/team/sdk-transport.js.map +1 -0
  239. package/dist/team/tmux-session.d.ts +7 -0
  240. package/dist/team/tmux-session.d.ts.map +1 -1
  241. package/dist/team/tmux-session.js +80 -18
  242. package/dist/team/tmux-session.js.map +1 -1
  243. package/dist/team/types.d.ts +2 -0
  244. package/dist/team/types.d.ts.map +1 -1
  245. package/dist/team/types.js.map +1 -1
  246. package/dist/tools/host-smoke-tool.d.ts +34 -0
  247. package/dist/tools/host-smoke-tool.d.ts.map +1 -0
  248. package/dist/tools/host-smoke-tool.js +133 -0
  249. package/dist/tools/host-smoke-tool.js.map +1 -0
  250. package/docs/CLAUDE.md +1 -1
  251. package/docs/DEVELOPERS.md +122 -0
  252. package/docs/HOOKS.md +34 -2
  253. package/docs/MIGRATION.md +194 -3
  254. package/docs/REFERENCE.md +95 -1
  255. package/docs/TOOLS.md +32 -1
  256. package/package.json +11 -2
  257. package/plugin.json +1 -1
  258. package/scripts/dev/repro-state-lock.mjs +1 -1
  259. package/scripts/lib/atomic-write.mjs +4 -2
  260. package/scripts/lib/state-lock.mjs +21 -12
  261. package/scripts/run.cjs +141 -18
  262. package/scripts/session-start.mjs +13 -0
  263. package/scripts/wiki-session-end.mjs +2 -1
  264. package/templates/hooks/lib/atomic-write.mjs +4 -2
package/scripts/run.cjs CHANGED
@@ -270,9 +270,41 @@ function resolveTrustedSessionEndTarget(resolution, extraArgs) {
270
270
  }
271
271
  }
272
272
 
273
+ // Fork (oh-my-copilot): SessionEnd foreground budget per host. Claude Code
274
+ // kills SessionEnd hooks at exit, so upstream caps the foreground at 300ms.
275
+ // Copilot (OMC_HOOK_EVENT=SessionEnd from copilot/hooks.json) fires SessionEnd
276
+ // per turn and waits up to timeoutSec, so 300ms only drops the work under load.
277
+ // 1500ms is chosen (~5x the idle foreground cost), not derived.
278
+ // OMC_SESSION_END_BUDGET_MS overrides both hosts (integer 1..60000); the
279
+ // manifest-derived timeout still caps the result at the call site.
280
+ const SESSION_END_BUDGET_MS = 300;
281
+ const COPILOT_SESSION_END_BUDGET_MS = 1500;
282
+ function resolveSessionEndBudgetMs(env = process.env) {
283
+ // Test-only widening (upstream contract) wins, and only under NODE_ENV=test.
284
+ const requestedTestTimeout = env.NODE_ENV === 'test'
285
+ ? Number(env.OMC_SESSION_END_TEST_FOREGROUND_TIMEOUT_MS)
286
+ : NaN;
287
+ if (Number.isFinite(requestedTestTimeout) && requestedTestTimeout > 0) return requestedTestTimeout;
288
+ const override = String(env.OMC_SESSION_END_BUDGET_MS ?? '').trim();
289
+ if (/^\d+$/.test(override)) {
290
+ const value = Number(override);
291
+ if (value >= 1 && value <= MAX_DECLARED_GENERIC_TIMEOUT_MS) return value;
292
+ }
293
+ return env.OMC_HOOK_EVENT === 'SessionEnd' ? COPILOT_SESSION_END_BUDGET_MS : SESSION_END_BUDGET_MS;
294
+ }
295
+
296
+
297
+ // oh-my-copilot fail-closed contract (fork-only): with OMC_HOOK_FAIL_CLOSED=1
298
+ // a hook timeout exits 124 so the host records a failed hook. Unset keeps the
299
+ // upstream fail-open exit 0.
300
+ const HOOK_TIMEOUT_FAIL_CLOSED_STATUS = 124;
301
+ function hookTimeoutStatus() {
302
+ return process.env.OMC_HOOK_FAIL_CLOSED === '1' ? HOOK_TIMEOUT_FAIL_CLOSED_STATUS : 0;
303
+ }
273
304
 
274
305
  function writeTimeoutDiagnostic(targetPath, manifestHook, timeoutMs, sink) {
275
- const message = `[run.cjs] Hook ${basename(targetPath)} timed out after ${timeoutMs}ms; exiting fail-open.\n`;
306
+ const outcome = hookTimeoutStatus() === 0 ? 'fail-open' : `fail-closed (${HOOK_TIMEOUT_FAIL_CLOSED_STATUS})`;
307
+ const message = `[run.cjs] Hook ${basename(targetPath)} timed out after ${timeoutMs}ms; exiting ${outcome}.\n`;
276
308
  if (manifestHook?.event !== 'UserPromptSubmit' || isDebugHooksEnabled()) {
277
309
  if (sink) return sink.write(process.stderr, Buffer.from(message));
278
310
  try { process.stderr.write(message); } catch { /* protocol dest may already be closed */ }
@@ -842,7 +874,7 @@ function runGenericChild(targetPath, extraArgs, timeoutMs, manifestHook, options
842
874
  if (require.main === module) {
843
875
  sink.closeDestinations();
844
876
  }
845
- finish(0);
877
+ finish(hookTimeoutStatus()); // fork: fail-closed contract, see hookTimeoutStatus
846
878
  });
847
879
  }, timeoutMs);
848
880
 
@@ -940,7 +972,80 @@ function gitGuardrailsPreCheck() {
940
972
  return null;
941
973
  }
942
974
 
943
- async function runWorker(targetPath, manifestHook, timeoutMs) {
975
+ // Fork (oh-my-copilot): generic hooks that run in a Worker under Copilot
976
+ // (OMC_HOOK_EVENT set by copilot/hooks.json). On win32 the generic child path
977
+ // costs run.cjs -> --generic-child-supervisor -> hook = 3 Node processes
978
+ // (~170 ms for a no-op hook); a Worker costs ~25 ms. Each script was audited:
979
+ // no process.chdir, signal handlers, isMainThread gating, or children that
980
+ // inherit stdio. process.exit ends only the Worker and its code propagates.
981
+ // Add a new upstream hook only after the same audit; the routing test pins
982
+ // this set to the generic entries of copilot/hooks.json.
983
+ //
984
+ // A Worker cannot preempt a hook blocked inside a synchronous call: several
985
+ // of these scripts (context-guard-stop.mjs, workflow-drift-guard.mjs,
986
+ // code-simplifier.mjs, and the shared resolveToWorktreeRoot git probes) make
987
+ // sync spawnSync git calls with their own ~2s timeouts, and `worker.terminate()`
988
+ // only tears the Worker down once that sync call returns; it does not reap
989
+ // any async grandchildren either. The supervised child path's `taskkill /T`
990
+ // can still kill a blocked sync call and its children, so
991
+ // resolveCopilotWorkerTarget below keeps any entry whose own manifest budget
992
+ // is <= COPILOT_WORKER_MIN_TIMEOUT_MS on that child path instead of the
993
+ // Worker, trading its ~90ms Worker saving for a host timeout that cannot be
994
+ // overshot by a hung sync call. See docs/HOOKS.md "Worker routing".
995
+ const COPILOT_WORKER_MIN_TIMEOUT_MS = 3000;
996
+ const COPILOT_WORKER_HOOKS = new Set([
997
+ 'session-start.mjs',
998
+ 'project-memory-session.mjs',
999
+ 'wiki-session-start.mjs',
1000
+ 'stale-run-reporter.mjs',
1001
+ 'runs-reconciler.mjs',
1002
+ 'permission-handler.mjs',
1003
+ 'post-tool-directory-context-injector.mjs',
1004
+ 'post-tool-use-failure.mjs',
1005
+ 'subagent-tracker.mjs',
1006
+ 'verify-deliverables.mjs',
1007
+ 'pre-compact.mjs',
1008
+ 'project-memory-precompact.mjs',
1009
+ 'wiki-pre-compact.mjs',
1010
+ 'context-guard-stop.mjs',
1011
+ 'workflow-drift-guard.mjs',
1012
+ 'persistent-mode.mjs',
1013
+ 'budget-guard.mjs',
1014
+ 'code-simplifier.mjs',
1015
+ ]);
1016
+
1017
+ /**
1018
+ * Fork: the manifest hook when this generic hook may run in a Worker under
1019
+ * Copilot, else null (generic child path). Requires OMC_HOOK_EVENT, the
1020
+ * OMC_COPILOT_HOOK_WORKER=0 kill switch unset, an audited script at the exact
1021
+ * canonical trusted-root path, and a manifest entry (with these extra args)
1022
+ * whose event is OMC_HOOK_EVENT. Claude Code never sets OMC_HOOK_EVENT.
1023
+ */
1024
+ function resolveCopilotWorkerTarget(resolution, extraArgs, env = process.env) {
1025
+ const hookEvent = env.OMC_HOOK_EVENT;
1026
+ const trustedRoot = resolution.trustedPluginRoot;
1027
+ if (!hookEvent || env.OMC_COPILOT_HOOK_WORKER === '0' || !trustedRoot) return null;
1028
+ try {
1029
+ const scriptName = basename(resolution.targetPath);
1030
+ if (!COPILOT_WORKER_HOOKS.has(scriptName)) return null;
1031
+ const canonicalTarget = normalizedComparisonPath(resolution.targetPath);
1032
+ if (!isContainedBy(normalizedComparisonPath(trustedRoot), canonicalTarget)) return null;
1033
+ if (canonicalTarget !== normalizedComparisonPath(join(trustedRoot, 'scripts', scriptName))) return null;
1034
+ const manifestHook = resolveHookTimeoutMsFromRoot(trustedRoot, resolution.targetPath, extraArgs);
1035
+ if (manifestHook?.event !== hookEvent) return null;
1036
+ // See COPILOT_WORKER_MIN_TIMEOUT_MS above: a tight manifest budget stays
1037
+ // on the supervised child path, where a blocked sync call cannot overshoot
1038
+ // the host timeout.
1039
+ if (manifestHook.timeoutMs <= COPILOT_WORKER_MIN_TIMEOUT_MS) return null;
1040
+ return manifestHook;
1041
+ } catch {
1042
+ return null;
1043
+ }
1044
+ }
1045
+
1046
+ // options (fork): { argv, env } for the Copilot generic Worker path; the
1047
+ // trusted and SessionEnd Worker callers pass none and keep upstream behaviour.
1048
+ async function runWorker(targetPath, manifestHook, timeoutMs, options = {}) {
944
1049
  let worker;
945
1050
  let terminal = false;
946
1051
  let timer;
@@ -996,7 +1101,8 @@ async function runWorker(targetPath, manifestHook, timeoutMs) {
996
1101
  }
997
1102
  await writeTimeoutDiagnostic(targetPath, manifestHook, timeoutMs, sink);
998
1103
  sink.uninstall();
999
- resolve(0);
1104
+ // Also the SessionEnd foreground-budget path. Fork: fail-closed contract, see hookTimeoutStatus.
1105
+ resolve(hookTimeoutStatus());
1000
1106
  }, timeoutMs);
1001
1107
 
1002
1108
  try {
@@ -1005,7 +1111,9 @@ async function runWorker(targetPath, manifestHook, timeoutMs) {
1005
1111
  stdin: true,
1006
1112
  stdout: true,
1007
1113
  stderr: true,
1008
- env: process.env,
1114
+ env: options.env || process.env,
1115
+ // Appended after [worker eval] → argv[1] is the hook path, argv[2..] the extra args.
1116
+ ...(options.argv && options.argv.length ? { argv: options.argv } : {}),
1009
1117
  workerData: { omcWorkerTarget: pathToFileURL(targetPath).href },
1010
1118
  });
1011
1119
  if (process.stdin.readableEnded) worker.stdin.end();
@@ -1059,24 +1167,34 @@ if (require.main === module) {
1059
1167
  } else {
1060
1168
  const sessionEndManifestHook = resolveTrustedSessionEndTarget(resolution, extraArgs);
1061
1169
  if (sessionEndManifestHook) {
1062
- // The shipped foreground budget stays 300ms unconditionally; only the
1063
- // test harness may widen it, and only under NODE_ENV=test.
1064
- const requestedTestTimeout = process.env.NODE_ENV === 'test'
1065
- ? Number(process.env.OMC_SESSION_END_TEST_FOREGROUND_TIMEOUT_MS)
1066
- : NaN;
1067
- const budgetMs = Number.isFinite(requestedTestTimeout) && requestedTestTimeout > 0
1068
- ? requestedTestTimeout
1069
- : 300;
1170
+ // Fork: 300ms on Claude Code, 1500ms under Copilot; see resolveSessionEndBudgetMs.
1171
+ const budgetMs = resolveSessionEndBudgetMs();
1070
1172
  const timeoutMs = Math.min(resolveGenericTimeoutMs(sessionEndManifestHook), budgetMs);
1071
1173
  runWorker(resolution.targetPath, sessionEndManifestHook, timeoutMs).then(status => {
1072
1174
  process.exitCode = status;
1073
1175
  });
1074
1176
  } else {
1075
- const manifestHook = resolveHookTimeoutMs(resolution.targetPath, extraArgs);
1076
- const timeoutMs = resolveGenericTimeoutMs(manifestHook);
1077
- runGenericChild(resolution.targetPath, extraArgs, timeoutMs, manifestHook).then(status => {
1078
- process.exitCode = status;
1079
- });
1177
+ const copilotWorkerHook = resolveCopilotWorkerTarget(resolution, extraArgs);
1178
+ if (copilotWorkerHook) {
1179
+ // Fork: Copilot generic hooks in a Worker, same timeout and owner-pid
1180
+ // env as runGenericChild; see COPILOT_WORKER_HOOKS.
1181
+ const timeoutMs = resolveGenericTimeoutMs(copilotWorkerHook);
1182
+ runWorker(resolution.targetPath, copilotWorkerHook, timeoutMs, {
1183
+ argv: extraArgs,
1184
+ env: {
1185
+ ...process.env,
1186
+ OMC_SESSION_OWNER_PID: process.env.OMC_SESSION_OWNER_PID || String(process.ppid),
1187
+ },
1188
+ }).then(status => {
1189
+ process.exitCode = status;
1190
+ });
1191
+ } else {
1192
+ const manifestHook = resolveHookTimeoutMs(resolution.targetPath, extraArgs);
1193
+ const timeoutMs = resolveGenericTimeoutMs(manifestHook);
1194
+ runGenericChild(resolution.targetPath, extraArgs, timeoutMs, manifestHook).then(status => {
1195
+ process.exitCode = status;
1196
+ });
1197
+ }
1080
1198
  }
1081
1199
  }
1082
1200
  }
@@ -1109,4 +1227,9 @@ module.exports = {
1109
1227
  NESTED_INNER_FLOOR_MS,
1110
1228
  MAX_DECLARED_GENERIC_TIMEOUT_MS,
1111
1229
  resolveTrustedSessionEndTarget,
1230
+ resolveSessionEndBudgetMs,
1231
+ resolveCopilotWorkerTarget,
1232
+ COPILOT_WORKER_HOOKS,
1233
+ COPILOT_WORKER_MIN_TIMEOUT_MS,
1234
+ TRUSTED_WORKER_HOOKS,
1112
1235
  };
@@ -1071,6 +1071,19 @@ async function main() {
1071
1071
  return;
1072
1072
  }
1073
1073
  const sessionId = data.session_id || data.sessionId || '';
1074
+ // Factory chain link (Copilot): bind OMC_CHAIN_LINK to this host session
1075
+ // first, so a nested session that inherits the variable cannot end the
1076
+ // link (bindChainLink in src/hooks/session-end/chain-enqueuer.ts). A failed
1077
+ // bind makes this link's SessionEnd reject the claim; the watchdog flags it.
1078
+ if (process.env.OMC_CHAIN_LINK && sessionId) {
1079
+ try {
1080
+ const [{ bindChainLink }, { resolveToWorktreeRoot }] = await Promise.all([
1081
+ import(pathToFileURL(join(__dirname, '..', 'dist', 'hooks', 'session-end', 'chain-enqueuer.js')).href),
1082
+ import(pathToFileURL(join(__dirname, '..', 'dist', 'lib', 'worktree-paths.js')).href),
1083
+ ]);
1084
+ bindChainLink(resolveToWorktreeRoot(directory), sessionId);
1085
+ } catch { /* dist unavailable: the link stays unbound */ }
1086
+ }
1074
1087
  const omcRoot = await resolveOmcStateRoot(directory);
1075
1088
  let messages = [];
1076
1089
  const userMessages = [];
@@ -15,7 +15,8 @@ export async function runWikiSessionEndHook() {
15
15
  }
16
16
 
17
17
  try {
18
- const { processWikiSessionEnd } = await import('../dist/hooks/session-end/index.js');
18
+ // Lean entry (not index.js): the full SessionEnd graph costs ~100ms of the 300ms budget.
19
+ const { processWikiSessionEnd } = await import('../dist/hooks/session-end/wiki-foreground-bootstrap.js');
19
20
  const result = await processWikiSessionEnd(frame.value);
20
21
  console.log(JSON.stringify(result));
21
22
  } catch (error) {
@@ -137,9 +137,11 @@ function readEmergencyJournal(path) {
137
137
  } catch { return null; }
138
138
  }
139
139
 
140
+ // BigInt ids: NTFS file IDs exceed 2^53 once the MFT sequence number reaches
141
+ // 32, and a Number ino rounds distinct files onto the same value.
140
142
  function fileIdentity(path) {
141
143
  try {
142
- const stat = statSync(path);
144
+ const stat = statSync(path, { bigint: true });
143
145
  return { dev: stat.dev, ino: stat.ino };
144
146
  } catch { return null; }
145
147
  }
@@ -157,7 +159,7 @@ function sameFileIdentity(a, b) {
157
159
  // On Windows, lstat returns dev=0, so skip dev comparison when on Windows
158
160
  // unless both are non-zero (indicating a real comparison is possible)
159
161
  const isWindows = process.platform === 'win32';
160
- if (isWindows && (a.dev === 0 || b.dev === 0)) {
162
+ if (isWindows && (a.dev === 0n || b.dev === 0n)) {
161
163
  return true; // Skip dev comparison on Windows when either is 0
162
164
  }
163
165