oh-my-codex 0.14.0 → 0.14.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 (185) hide show
  1. package/Cargo.lock +5 -5
  2. package/Cargo.toml +1 -1
  3. package/README.md +14 -8
  4. package/crates/omx-explore/src/main.rs +94 -1
  5. package/crates/omx-sparkshell/src/codex_bridge.rs +59 -12
  6. package/crates/omx-sparkshell/tests/execution.rs +48 -0
  7. package/dist/cli/__tests__/explore.test.js +33 -1
  8. package/dist/cli/__tests__/explore.test.js.map +1 -1
  9. package/dist/cli/__tests__/index.test.js +11 -2
  10. package/dist/cli/__tests__/index.test.js.map +1 -1
  11. package/dist/cli/__tests__/package-bin-contract.test.js +5 -0
  12. package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
  13. package/dist/cli/__tests__/question.test.js +139 -25
  14. package/dist/cli/__tests__/question.test.js.map +1 -1
  15. package/dist/cli/__tests__/session-scoped-runtime.test.js +30 -0
  16. package/dist/cli/__tests__/session-scoped-runtime.test.js.map +1 -1
  17. package/dist/cli/__tests__/setup-agents-overwrite.test.js +32 -7
  18. package/dist/cli/__tests__/setup-agents-overwrite.test.js.map +1 -1
  19. package/dist/cli/__tests__/setup-refresh.test.js +8 -6
  20. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  21. package/dist/cli/__tests__/sparkshell-cli.test.js +23 -0
  22. package/dist/cli/__tests__/sparkshell-cli.test.js.map +1 -1
  23. package/dist/cli/__tests__/uninstall.test.js +65 -5
  24. package/dist/cli/__tests__/uninstall.test.js.map +1 -1
  25. package/dist/cli/__tests__/update.test.js +360 -26
  26. package/dist/cli/__tests__/update.test.js.map +1 -1
  27. package/dist/cli/explore.d.ts.map +1 -1
  28. package/dist/cli/explore.js +18 -3
  29. package/dist/cli/explore.js.map +1 -1
  30. package/dist/cli/index.d.ts +2 -1
  31. package/dist/cli/index.d.ts.map +1 -1
  32. package/dist/cli/index.js +7 -2
  33. package/dist/cli/index.js.map +1 -1
  34. package/dist/cli/setup.d.ts.map +1 -1
  35. package/dist/cli/setup.js +25 -3
  36. package/dist/cli/setup.js.map +1 -1
  37. package/dist/cli/sparkshell.d.ts.map +1 -1
  38. package/dist/cli/sparkshell.js +11 -1
  39. package/dist/cli/sparkshell.js.map +1 -1
  40. package/dist/cli/team.d.ts.map +1 -1
  41. package/dist/cli/team.js +159 -394
  42. package/dist/cli/team.js.map +1 -1
  43. package/dist/cli/uninstall.d.ts.map +1 -1
  44. package/dist/cli/uninstall.js +3 -1
  45. package/dist/cli/uninstall.js.map +1 -1
  46. package/dist/cli/update.d.ts +37 -9
  47. package/dist/cli/update.d.ts.map +1 -1
  48. package/dist/cli/update.js +204 -26
  49. package/dist/cli/update.js.map +1 -1
  50. package/dist/config/__tests__/generator-idempotent.test.js +51 -14
  51. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  52. package/dist/config/__tests__/generator-notify.test.js +35 -10
  53. package/dist/config/__tests__/generator-notify.test.js.map +1 -1
  54. package/dist/config/generator.d.ts +1 -0
  55. package/dist/config/generator.d.ts.map +1 -1
  56. package/dist/config/generator.js +61 -7
  57. package/dist/config/generator.js.map +1 -1
  58. package/dist/hooks/__tests__/code-review-skill-contract.test.d.ts +2 -0
  59. package/dist/hooks/__tests__/code-review-skill-contract.test.d.ts.map +1 -0
  60. package/dist/hooks/__tests__/code-review-skill-contract.test.js +56 -0
  61. package/dist/hooks/__tests__/code-review-skill-contract.test.js.map +1 -0
  62. package/dist/hooks/__tests__/deep-interview-contract.test.js +31 -0
  63. package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
  64. package/dist/hooks/__tests__/explicit-terminal-stop-docs-contract.test.d.ts +2 -0
  65. package/dist/hooks/__tests__/explicit-terminal-stop-docs-contract.test.d.ts.map +1 -0
  66. package/dist/hooks/__tests__/explicit-terminal-stop-docs-contract.test.js +43 -0
  67. package/dist/hooks/__tests__/explicit-terminal-stop-docs-contract.test.js.map +1 -0
  68. package/dist/hooks/__tests__/explicit-terminal-stop-model-docs-contract.test.d.ts +2 -0
  69. package/dist/hooks/__tests__/explicit-terminal-stop-model-docs-contract.test.d.ts.map +1 -0
  70. package/dist/hooks/__tests__/explicit-terminal-stop-model-docs-contract.test.js +38 -0
  71. package/dist/hooks/__tests__/explicit-terminal-stop-model-docs-contract.test.js.map +1 -0
  72. package/dist/hooks/__tests__/keyword-detector.test.js +108 -0
  73. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  74. package/dist/hooks/__tests__/prompt-guidance-test-helpers.d.ts.map +1 -1
  75. package/dist/hooks/__tests__/prompt-guidance-test-helpers.js +16 -1
  76. package/dist/hooks/__tests__/prompt-guidance-test-helpers.js.map +1 -1
  77. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  78. package/dist/hooks/keyword-detector.js +34 -8
  79. package/dist/hooks/keyword-detector.js.map +1 -1
  80. package/dist/mcp/__tests__/bootstrap.test.js +7 -25
  81. package/dist/mcp/__tests__/bootstrap.test.js.map +1 -1
  82. package/dist/mcp/__tests__/server-lifecycle.test.js +60 -0
  83. package/dist/mcp/__tests__/server-lifecycle.test.js.map +1 -1
  84. package/dist/mcp/__tests__/state-server.test.js +177 -0
  85. package/dist/mcp/__tests__/state-server.test.js.map +1 -1
  86. package/dist/mcp/bootstrap.d.ts.map +1 -1
  87. package/dist/mcp/bootstrap.js +36 -18
  88. package/dist/mcp/bootstrap.js.map +1 -1
  89. package/dist/mcp/state-server.d.ts +17 -0
  90. package/dist/mcp/state-server.d.ts.map +1 -1
  91. package/dist/mcp/state-server.js +55 -1
  92. package/dist/mcp/state-server.js.map +1 -1
  93. package/dist/notifications/__tests__/index.test.js +0 -3
  94. package/dist/notifications/__tests__/index.test.js.map +1 -1
  95. package/dist/notifications/__tests__/session-status.test.js +90 -0
  96. package/dist/notifications/__tests__/session-status.test.js.map +1 -1
  97. package/dist/notifications/session-status.d.ts +2 -0
  98. package/dist/notifications/session-status.d.ts.map +1 -1
  99. package/dist/notifications/session-status.js +19 -4
  100. package/dist/notifications/session-status.js.map +1 -1
  101. package/dist/question/__tests__/deep-interview.test.js +44 -0
  102. package/dist/question/__tests__/deep-interview.test.js.map +1 -1
  103. package/dist/question/__tests__/renderer.test.js +192 -12
  104. package/dist/question/__tests__/renderer.test.js.map +1 -1
  105. package/dist/question/__tests__/state.test.js +21 -1
  106. package/dist/question/__tests__/state.test.js.map +1 -1
  107. package/dist/question/deep-interview.d.ts +3 -0
  108. package/dist/question/deep-interview.d.ts.map +1 -1
  109. package/dist/question/deep-interview.js +18 -1
  110. package/dist/question/deep-interview.js.map +1 -1
  111. package/dist/question/renderer.d.ts +4 -2
  112. package/dist/question/renderer.d.ts.map +1 -1
  113. package/dist/question/renderer.js +87 -18
  114. package/dist/question/renderer.js.map +1 -1
  115. package/dist/runtime/__tests__/run-outcome.test.js +38 -0
  116. package/dist/runtime/__tests__/run-outcome.test.js.map +1 -1
  117. package/dist/runtime/__tests__/run-state.test.d.ts +2 -0
  118. package/dist/runtime/__tests__/run-state.test.d.ts.map +1 -0
  119. package/dist/runtime/__tests__/run-state.test.js +37 -0
  120. package/dist/runtime/__tests__/run-state.test.js.map +1 -0
  121. package/dist/runtime/run-loop.d.ts +5 -1
  122. package/dist/runtime/run-loop.d.ts.map +1 -1
  123. package/dist/runtime/run-loop.js +8 -3
  124. package/dist/runtime/run-loop.js.map +1 -1
  125. package/dist/runtime/run-outcome.d.ts +18 -0
  126. package/dist/runtime/run-outcome.d.ts.map +1 -1
  127. package/dist/runtime/run-outcome.js +156 -7
  128. package/dist/runtime/run-outcome.js.map +1 -1
  129. package/dist/runtime/run-state.d.ts +5 -1
  130. package/dist/runtime/run-state.d.ts.map +1 -1
  131. package/dist/runtime/run-state.js +13 -3
  132. package/dist/runtime/run-state.js.map +1 -1
  133. package/dist/runtime/terminal-lifecycle.d.ts +11 -0
  134. package/dist/runtime/terminal-lifecycle.d.ts.map +1 -0
  135. package/dist/runtime/terminal-lifecycle.js +52 -0
  136. package/dist/runtime/terminal-lifecycle.js.map +1 -0
  137. package/dist/scripts/__tests__/codex-native-hook.test.js +370 -56
  138. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  139. package/dist/scripts/__tests__/postinstall.test.d.ts +2 -0
  140. package/dist/scripts/__tests__/postinstall.test.d.ts.map +1 -0
  141. package/dist/scripts/__tests__/postinstall.test.js +178 -0
  142. package/dist/scripts/__tests__/postinstall.test.js.map +1 -0
  143. package/dist/scripts/codex-native-hook.d.ts +1 -0
  144. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  145. package/dist/scripts/codex-native-hook.js +115 -56
  146. package/dist/scripts/codex-native-hook.js.map +1 -1
  147. package/dist/scripts/postinstall.d.ts +22 -0
  148. package/dist/scripts/postinstall.d.ts.map +1 -0
  149. package/dist/scripts/postinstall.js +105 -0
  150. package/dist/scripts/postinstall.js.map +1 -0
  151. package/dist/state/__tests__/operations.test.js +60 -0
  152. package/dist/state/__tests__/operations.test.js.map +1 -1
  153. package/dist/state/operations.d.ts.map +1 -1
  154. package/dist/state/operations.js +18 -1
  155. package/dist/state/operations.js.map +1 -1
  156. package/dist/team/__tests__/role-router.test.js +6 -0
  157. package/dist/team/__tests__/role-router.test.js.map +1 -1
  158. package/dist/team/__tests__/runtime.test.js +108 -2
  159. package/dist/team/__tests__/runtime.test.js.map +1 -1
  160. package/dist/team/runtime.d.ts.map +1 -1
  161. package/dist/team/runtime.js +18 -4
  162. package/dist/team/runtime.js.map +1 -1
  163. package/dist/utils/__tests__/dep-versions.test.js +25 -8
  164. package/dist/utils/__tests__/dep-versions.test.js.map +1 -1
  165. package/dist/utils/__tests__/paths.test.js +45 -0
  166. package/dist/utils/__tests__/paths.test.js.map +1 -1
  167. package/dist/utils/paths.d.ts +2 -0
  168. package/dist/utils/paths.d.ts.map +1 -1
  169. package/dist/utils/paths.js +22 -7
  170. package/dist/utils/paths.js.map +1 -1
  171. package/dist/verification/__tests__/ci-rust-gates.test.js +1 -1
  172. package/dist/verification/__tests__/ci-rust-gates.test.js.map +1 -1
  173. package/package.json +3 -2
  174. package/prompts/architect.md +4 -0
  175. package/prompts/code-reviewer.md +3 -0
  176. package/skills/code-review/SKILL.md +94 -28
  177. package/skills/deep-interview/SKILL.md +91 -0
  178. package/src/scripts/__tests__/codex-native-hook.test.ts +468 -64
  179. package/src/scripts/__tests__/postinstall.test.ts +210 -0
  180. package/src/scripts/codex-native-hook.ts +136 -53
  181. package/src/scripts/postinstall-bootstrap.js +23 -0
  182. package/src/scripts/postinstall.ts +161 -0
  183. package/templates/AGENTS.md +1 -1
  184. package/templates/model-instructions/explore-lightweight-AGENTS.md +11 -0
  185. package/templates/model-instructions/sparkshell-lightweight-AGENTS.md +10 -0
@@ -1 +1 @@
1
- {"version":3,"file":"run-state.js","sourceRoot":"","sources":["../../src/runtime/run-state.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"run-state.js","sourceRoot":"","sources":["../../src/runtime/run-state.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import { TERMINAL_LIFECYCLE_OUTCOMES, compatibilityRunOutcomeFromTerminalLifecycleOutcome, type RunOutcome, type TerminalLifecycleOutcome, type TerminalLifecycleOutcomeNormalizationResult } from './run-outcome.js';
2
+ export { TERMINAL_LIFECYCLE_OUTCOMES, compatibilityRunOutcomeFromTerminalLifecycleOutcome, };
3
+ export type { TerminalLifecycleOutcome };
4
+ export type TerminalLifecycleNormalizationResult = TerminalLifecycleOutcomeNormalizationResult;
5
+ export declare function normalizeTerminalLifecycleOutcome(value: unknown): TerminalLifecycleNormalizationResult;
6
+ export declare function inferTerminalLifecycleOutcome(candidate: {
7
+ lifecycle_outcome?: unknown;
8
+ run_outcome?: unknown;
9
+ }): TerminalLifecycleNormalizationResult;
10
+ export declare function preferredRunOutcomeForLifecycleOutcome(outcome: TerminalLifecycleOutcome): RunOutcome;
11
+ //# sourceMappingURL=terminal-lifecycle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminal-lifecycle.d.ts","sourceRoot":"","sources":["../../src/runtime/terminal-lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,2BAA2B,EAC3B,mDAAmD,EAInD,KAAK,UAAU,EACf,KAAK,wBAAwB,EAC7B,KAAK,2CAA2C,EACjD,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,2BAA2B,EAC3B,mDAAmD,GACpD,CAAC;AACF,YAAY,EAAE,wBAAwB,EAAE,CAAC;AACzC,MAAM,MAAM,oCAAoC,GAAG,2CAA2C,CAAC;AAU/F,wBAAgB,iCAAiC,CAAC,KAAK,EAAE,OAAO,GAAG,oCAAoC,CAatG;AAED,wBAAgB,6BAA6B,CAAC,SAAS,EAAE;IACvD,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,GAAG,oCAAoC,CAyBvC;AAED,wBAAgB,sCAAsC,CACpD,OAAO,EAAE,wBAAwB,GAChC,UAAU,CAEZ"}
@@ -0,0 +1,52 @@
1
+ import { TERMINAL_LIFECYCLE_OUTCOMES, compatibilityRunOutcomeFromTerminalLifecycleOutcome, normalizeRunOutcome, normalizeTerminalLifecycleOutcome as normalizeTerminalLifecycleOutcomeContract, terminalLifecycleOutcomeFromRunOutcome, } from './run-outcome.js';
2
+ export { TERMINAL_LIFECYCLE_OUTCOMES, compatibilityRunOutcomeFromTerminalLifecycleOutcome, };
3
+ function rewriteWarning(warning) {
4
+ return warning?.replace('legacy terminal lifecycle outcome', 'legacy lifecycle outcome');
5
+ }
6
+ function isCanonicalTerminalLifecycleOutcome(value) {
7
+ return typeof value === 'string' && TERMINAL_LIFECYCLE_OUTCOMES.includes(value.trim());
8
+ }
9
+ export function normalizeTerminalLifecycleOutcome(value) {
10
+ if (isCanonicalTerminalLifecycleOutcome(value)) {
11
+ return { outcome: value.trim() };
12
+ }
13
+ const result = normalizeTerminalLifecycleOutcomeContract(value, {
14
+ blockedOnUserStrategy: 'blocked',
15
+ });
16
+ return {
17
+ ...(result.outcome ? { outcome: result.outcome } : {}),
18
+ ...(result.warning ? { warning: rewriteWarning(result.warning) } : {}),
19
+ ...(result.error ? { error: result.error } : {}),
20
+ };
21
+ }
22
+ export function inferTerminalLifecycleOutcome(candidate) {
23
+ const explicit = normalizeTerminalLifecycleOutcome(candidate.lifecycle_outcome);
24
+ if (explicit.outcome || explicit.error)
25
+ return explicit;
26
+ const runOutcome = normalizeRunOutcome(candidate.run_outcome);
27
+ if (runOutcome.error)
28
+ return { error: runOutcome.error };
29
+ switch (runOutcome.outcome) {
30
+ case 'finish':
31
+ case 'blocked_on_user':
32
+ case 'failed':
33
+ return {
34
+ outcome: terminalLifecycleOutcomeFromRunOutcome(runOutcome.outcome, {
35
+ blockedOnUserStrategy: 'blocked',
36
+ }),
37
+ };
38
+ case 'cancelled':
39
+ return {
40
+ outcome: terminalLifecycleOutcomeFromRunOutcome('cancelled', {
41
+ blockedOnUserStrategy: 'blocked',
42
+ }),
43
+ warning: 'normalized legacy run outcome "cancelled" -> "userinterlude"',
44
+ };
45
+ default:
46
+ return {};
47
+ }
48
+ }
49
+ export function preferredRunOutcomeForLifecycleOutcome(outcome) {
50
+ return compatibilityRunOutcomeFromTerminalLifecycleOutcome(outcome);
51
+ }
52
+ //# sourceMappingURL=terminal-lifecycle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminal-lifecycle.js","sourceRoot":"","sources":["../../src/runtime/terminal-lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,2BAA2B,EAC3B,mDAAmD,EACnD,mBAAmB,EACnB,iCAAiC,IAAI,yCAAyC,EAC9E,sCAAsC,GAIvC,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,2BAA2B,EAC3B,mDAAmD,GACpD,CAAC;AAIF,SAAS,cAAc,CAAC,OAA2B;IACjD,OAAO,OAAO,EAAE,OAAO,CAAC,mCAAmC,EAAE,0BAA0B,CAAC,CAAC;AAC3F,CAAC;AAED,SAAS,mCAAmC,CAAC,KAAc;IACzD,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAK,2BAAiD,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;AAChH,CAAC;AAED,MAAM,UAAU,iCAAiC,CAAC,KAAc;IAC9D,IAAI,mCAAmC,CAAC,KAAK,CAAC,EAAE,CAAC;QAC/C,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,IAAI,EAA8B,EAAE,CAAC;IAC/D,CAAC;IAED,MAAM,MAAM,GAAG,yCAAyC,CAAC,KAAK,EAAE;QAC9D,qBAAqB,EAAE,SAAS;KACjC,CAAC,CAAC;IACH,OAAO;QACL,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACtD,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,cAAc,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACtE,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACjD,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,6BAA6B,CAAC,SAG7C;IACC,MAAM,QAAQ,GAAG,iCAAiC,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC;IAChF,IAAI,QAAQ,CAAC,OAAO,IAAI,QAAQ,CAAC,KAAK;QAAE,OAAO,QAAQ,CAAC;IAExD,MAAM,UAAU,GAAG,mBAAmB,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;IAC9D,IAAI,UAAU,CAAC,KAAK;QAAE,OAAO,EAAE,KAAK,EAAE,UAAU,CAAC,KAAK,EAAE,CAAC;IACzD,QAAQ,UAAU,CAAC,OAAO,EAAE,CAAC;QAC3B,KAAK,QAAQ,CAAC;QACd,KAAK,iBAAiB,CAAC;QACvB,KAAK,QAAQ;YACX,OAAO;gBACL,OAAO,EAAE,sCAAsC,CAAC,UAAU,CAAC,OAAO,EAAE;oBAClE,qBAAqB,EAAE,SAAS;iBACjC,CAAC;aACH,CAAC;QACJ,KAAK,WAAW;YACd,OAAO;gBACL,OAAO,EAAE,sCAAsC,CAAC,WAAW,EAAE;oBAC3D,qBAAqB,EAAE,SAAS;iBACjC,CAAC;gBACF,OAAO,EAAE,8DAA8D;aACxE,CAAC;QACJ;YACE,OAAO,EAAE,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,sCAAsC,CACpD,OAAiC;IAEjC,OAAO,mDAAmD,CAAC,OAAO,CAAC,CAAC;AACtE,CAAC"}
@@ -4,16 +4,36 @@ import { existsSync } from "node:fs";
4
4
  import { chmod, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
5
5
  import { tmpdir } from "node:os";
6
6
  import { dirname, join } from "node:path";
7
+ import { pathToFileURL } from "node:url";
7
8
  import { afterEach, beforeEach, describe, it } from "node:test";
8
9
  import { buildManagedCodexHooksConfig } from "../../config/codex-hooks.js";
9
10
  import { initTeamState, readTeamLeaderAttention, readTeamPhase, writeTeamLeaderAttention, } from "../../team/state.js";
10
- import { dispatchCodexNativeHook, mapCodexHookEventToOmxEvent, resolveSessionOwnerPidFromAncestry, } from "../codex-native-hook.js";
11
+ import { dispatchCodexNativeHook, isCodexNativeHookMainModule, mapCodexHookEventToOmxEvent, resolveSessionOwnerPidFromAncestry, } from "../codex-native-hook.js";
11
12
  import { writeSessionStart } from "../../hooks/session.js";
12
13
  import { resetTriageConfigCache } from "../../hooks/triage-config.js";
13
14
  async function writeJson(path, value) {
14
15
  await mkdir(dirname(path), { recursive: true }).catch(() => { });
15
16
  await writeFile(path, JSON.stringify(value, null, 2));
16
17
  }
18
+ async function writeHookCounterPlugin(cwd) {
19
+ const markerPath = join(cwd, ".omx", "stop-hook-counter.json");
20
+ await mkdir(join(cwd, ".omx", "hooks"), { recursive: true });
21
+ await writeFile(join(cwd, ".omx", "hooks", "count-stop-hook.mjs"), `import { mkdir, readFile, writeFile } from "node:fs/promises";
22
+ import { dirname, join } from "node:path";
23
+
24
+ export async function onHookEvent(event) {
25
+ if (event.event !== "stop") return;
26
+ const outPath = join(process.cwd(), ".omx", "stop-hook-counter.json");
27
+ await mkdir(dirname(outPath), { recursive: true });
28
+ let count = 0;
29
+ try {
30
+ count = JSON.parse(await readFile(outPath, "utf-8")).count || 0;
31
+ } catch {}
32
+ await writeFile(outPath, JSON.stringify({ count: count + 1 }, null, 2));
33
+ }
34
+ `, "utf-8");
35
+ return markerPath;
36
+ }
17
37
  async function writeReleaseReadinessLeaderAttention(teamName, sessionId, cwd, options) {
18
38
  await writeTeamLeaderAttention(teamName, {
19
39
  team_name: teamName,
@@ -91,6 +111,13 @@ describe("codex native hook config", () => {
91
111
  });
92
112
  });
93
113
  describe("codex native hook dispatch", () => {
114
+ it("treats space-containing argv entry paths as the main module", () => {
115
+ const entryPath = "/tmp/omx native/codex-native-hook.js";
116
+ assert.equal(isCodexNativeHookMainModule(pathToFileURL(entryPath).href, entryPath), true);
117
+ });
118
+ it("does not treat a different module url as the main module", () => {
119
+ assert.equal(isCodexNativeHookMainModule(pathToFileURL("/tmp/omx native/other-script.js").href, "/tmp/omx native/codex-native-hook.js"), false);
120
+ });
94
121
  it("emits deterministic JSON stdout when CLI stdin is malformed", () => {
95
122
  const stdout = execFileSync(process.execPath, [join(process.cwd(), "dist", "scripts", "codex-native-hook.js")], {
96
123
  cwd: process.cwd(),
@@ -209,7 +236,7 @@ describe("codex native hook dispatch", () => {
209
236
  await rm(cwd, { recursive: true, force: true });
210
237
  }
211
238
  });
212
- it("appends .omx/ to repo-root .gitignore during SessionStart when missing", async () => {
239
+ it("adds .omx/ to git info/exclude during SessionStart instead of mutating repo .gitignore", async () => {
213
240
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-session-gitignore-"));
214
241
  try {
215
242
  await writeFile(join(cwd, ".gitignore"), "node_modules/\n");
@@ -221,8 +248,55 @@ describe("codex native hook dispatch", () => {
221
248
  }, { cwd, sessionOwnerPid: 43210 });
222
249
  assert.equal(result.omxEventName, "session-start");
223
250
  const gitignore = await readFile(join(cwd, ".gitignore"), "utf-8");
224
- assert.match(gitignore, /^node_modules\/\n\.omx\/\n$/);
225
- assert.match(JSON.stringify(result.outputJson), /Added \.omx\/ to .*\.gitignore/);
251
+ assert.equal(gitignore, "node_modules/\n");
252
+ const exclude = await readFile(join(cwd, ".git", "info", "exclude"), "utf-8");
253
+ assert.match(exclude, /(?:^|\n)\.omx\/\n/);
254
+ assert.match(JSON.stringify(result.outputJson), /Added \.omx\/ to .*\.git[\/]info[\/]exclude/);
255
+ }
256
+ finally {
257
+ await rm(cwd, { recursive: true, force: true });
258
+ }
259
+ });
260
+ it("keeps SessionStart quiet when .omx/ is already ignored by repo-level gitignore", async () => {
261
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-session-existing-ignore-"));
262
+ try {
263
+ await writeFile(join(cwd, ".gitignore"), "node_modules/\n.omx/\n");
264
+ execFileSync("git", ["init"], { cwd, stdio: "pipe" });
265
+ const result = await dispatchCodexNativeHook({
266
+ hook_event_name: "SessionStart",
267
+ cwd,
268
+ session_id: "sess-gitignore-existing",
269
+ }, { cwd, sessionOwnerPid: 43210 });
270
+ assert.equal(result.omxEventName, "session-start");
271
+ const gitignore = await readFile(join(cwd, ".gitignore"), "utf-8");
272
+ assert.equal(gitignore, "node_modules/\n.omx/\n");
273
+ const exclude = await readFile(join(cwd, ".git", "info", "exclude"), "utf-8");
274
+ assert.doesNotMatch(exclude, /(?:^|\n)\.omx\/\n/);
275
+ assert.doesNotMatch(JSON.stringify(result.outputJson), /Added \.omx\//);
276
+ }
277
+ finally {
278
+ await rm(cwd, { recursive: true, force: true });
279
+ }
280
+ });
281
+ it("respects existing Git ignore resolution before writing local excludes", async () => {
282
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-session-global-ignore-"));
283
+ const excludesFile = join(cwd, "global-ignore");
284
+ try {
285
+ await writeFile(join(cwd, ".gitignore"), "node_modules/\n");
286
+ await writeFile(excludesFile, ".omx/\n");
287
+ execFileSync("git", ["init"], { cwd, stdio: "pipe" });
288
+ execFileSync("git", ["config", "core.excludesfile", excludesFile], { cwd, stdio: "pipe" });
289
+ const result = await dispatchCodexNativeHook({
290
+ hook_event_name: "SessionStart",
291
+ cwd,
292
+ session_id: "sess-gitignore-global",
293
+ }, { cwd, sessionOwnerPid: 43210 });
294
+ assert.equal(result.omxEventName, "session-start");
295
+ const gitignore = await readFile(join(cwd, ".gitignore"), "utf-8");
296
+ assert.equal(gitignore, "node_modules/\n");
297
+ const exclude = await readFile(join(cwd, ".git", "info", "exclude"), "utf-8");
298
+ assert.doesNotMatch(exclude, /(?:^|\n)\.omx\/\n/);
299
+ assert.doesNotMatch(JSON.stringify(result.outputJson), /Added \.omx\//);
226
300
  }
227
301
  finally {
228
302
  await rm(cwd, { recursive: true, force: true });
@@ -373,6 +447,29 @@ describe("codex native hook dispatch", () => {
373
447
  await rm(cwd, { recursive: true, force: true });
374
448
  }
375
449
  });
450
+ it("normalizes the Korean keyboard typo for ulw during UserPromptSubmit activation", async () => {
451
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ulw-ko-"));
452
+ try {
453
+ await mkdir(join(cwd, ".omx", "state"), { recursive: true });
454
+ const result = await dispatchCodexNativeHook({
455
+ hook_event_name: "UserPromptSubmit",
456
+ cwd,
457
+ session_id: "sess-ulw-ko",
458
+ thread_id: "thread-ulw-ko",
459
+ turn_id: "turn-ulw-ko",
460
+ prompt: "ㅕㅣㅈ로 병렬 처리해줘",
461
+ }, { cwd });
462
+ assert.equal(result.omxEventName, "keyword-detector");
463
+ assert.equal(result.skillState?.skill, "ultrawork");
464
+ assert.equal(result.skillState?.keyword, "ulw");
465
+ const additionalContext = String(result.outputJson?.hookSpecificOutput?.additionalContext || "");
466
+ assert.match(additionalContext, /workflow keyword \"ulw\" -> ultrawork/);
467
+ assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", "sess-ulw-ko", "ultrawork-state.json")), true);
468
+ }
469
+ finally {
470
+ await rm(cwd, { recursive: true, force: true });
471
+ }
472
+ });
376
473
  it("does not activate Ralph workflow state from a plain conversational mention", async () => {
377
474
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralph-plain-text-"));
378
475
  try {
@@ -538,6 +635,9 @@ describe("codex native hook dispatch", () => {
538
635
  assert.match(message, /skill: deep-interview activated and initial state initialized at \.omx\/state\/sessions\/sess-deep-interview-msg\/deep-interview-state\.json; write subsequent updates via omx_state MCP\./);
539
636
  assert.match(message, /Deep-interview must ask each interview round via `omx question`/);
540
637
  assert.match(message, /do not fall back to `request_user_input` or plain-text questioning/i);
638
+ assert.match(message, /After starting `omx question` in a background terminal, wait for that terminal to finish and read the JSON answer before continuing the interview\./);
639
+ assert.match(message, /If bare `omx question` is unavailable in this reused session, use the current-session CLI bridge command:/);
640
+ assert.match(message, /`'.+' '.+dist\/cli\/omx\.js' question`/);
541
641
  assert.match(message, /Stop remains blocked while a deep-interview question obligation is pending\./);
542
642
  }
543
643
  finally {
@@ -2470,6 +2570,55 @@ esac
2470
2570
  await rm(cwd, { recursive: true, force: true });
2471
2571
  }
2472
2572
  });
2573
+ it("blocks Stop when a same-session deep-interview question obligation is pending even after the mode marked itself inactive", async () => {
2574
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-deep-interview-question-inactive-"));
2575
+ try {
2576
+ const stateDir = join(cwd, ".omx", "state");
2577
+ await mkdir(join(stateDir, "sessions", "sess-stop-deep-interview-question-inactive"), { recursive: true });
2578
+ await writeJson(join(stateDir, "session.json"), { session_id: "sess-stop-deep-interview-question-inactive" });
2579
+ await writeJson(join(stateDir, "sessions", "sess-stop-deep-interview-question-inactive", "skill-active-state.json"), {
2580
+ version: 1,
2581
+ active: true,
2582
+ skill: "deep-interview",
2583
+ phase: "planning",
2584
+ session_id: "sess-stop-deep-interview-question-inactive",
2585
+ thread_id: "thread-stop-deep-interview-question-inactive",
2586
+ });
2587
+ await writeJson(join(stateDir, "sessions", "sess-stop-deep-interview-question-inactive", "deep-interview-state.json"), {
2588
+ active: false,
2589
+ mode: "deep-interview",
2590
+ current_phase: "intent-first",
2591
+ lifecycle_outcome: "askuserQuestion",
2592
+ run_outcome: "blocked_on_user",
2593
+ completed_at: "2026-04-19T03:20:30.000Z",
2594
+ session_id: "sess-stop-deep-interview-question-inactive",
2595
+ thread_id: "thread-stop-deep-interview-question-inactive",
2596
+ question_enforcement: {
2597
+ obligation_id: "obligation-inactive",
2598
+ source: "omx-question",
2599
+ status: "pending",
2600
+ lifecycle_outcome: "askuserQuestion",
2601
+ requested_at: "2026-04-19T03:20:00.000Z",
2602
+ },
2603
+ });
2604
+ const result = await dispatchCodexNativeHook({
2605
+ hook_event_name: "Stop",
2606
+ cwd,
2607
+ session_id: "sess-stop-deep-interview-question-inactive",
2608
+ thread_id: "thread-stop-deep-interview-question-inactive",
2609
+ }, { cwd });
2610
+ assert.equal(result.omxEventName, "stop");
2611
+ assert.deepEqual(result.outputJson, {
2612
+ decision: "block",
2613
+ reason: "Deep interview is still active (phase: intent-first) and has a pending structured question obligation; use `omx question` before stopping.",
2614
+ stopReason: "deep_interview_question_required",
2615
+ systemMessage: "OMX deep-interview is still active (phase: intent-first) and requires a structured question via omx question before stopping.",
2616
+ });
2617
+ }
2618
+ finally {
2619
+ await rm(cwd, { recursive: true, force: true });
2620
+ }
2621
+ });
2473
2622
  it("keeps blocking pending deep-interview question Stop replays until the obligation changes", async () => {
2474
2623
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-deep-interview-question-replay-"));
2475
2624
  try {
@@ -2738,6 +2887,50 @@ esac
2738
2887
  await rm(cwd, { recursive: true, force: true });
2739
2888
  }
2740
2889
  });
2890
+ it("does not block Stop from stale current-session Ralph state when session.json points to a dead owner", async () => {
2891
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-current-session-ralph-"));
2892
+ try {
2893
+ const stateDir = join(cwd, ".omx", "state");
2894
+ await mkdir(join(stateDir, "sessions", "sess-dead"), { recursive: true });
2895
+ await writeJson(join(stateDir, "session.json"), {
2896
+ session_id: "sess-dead",
2897
+ cwd,
2898
+ pid: Number.MAX_SAFE_INTEGER,
2899
+ started_at: "2026-01-01T00:00:00.000Z",
2900
+ });
2901
+ await writeJson(join(stateDir, "sessions", "sess-dead", "ralph-state.json"), {
2902
+ active: true,
2903
+ current_phase: "verifying",
2904
+ session_id: "sess-dead",
2905
+ });
2906
+ await writeJson(join(stateDir, "skill-active-state.json"), {
2907
+ active: true,
2908
+ skill: "team",
2909
+ phase: "team-exec",
2910
+ active_skills: [{ skill: "team", phase: "team-exec", active: true, session_id: "sess-dead" }],
2911
+ });
2912
+ await writeJson(join(stateDir, "native-stop-state.json"), {
2913
+ sessions: {
2914
+ "sess-dead": {
2915
+ last_signature: "ralph-stop|sess-dead|thread-1|no-message|verifying",
2916
+ updated_at: "2026-04-20T21:00:00.000Z",
2917
+ },
2918
+ },
2919
+ });
2920
+ const result = await dispatchCodexNativeHook({
2921
+ hook_event_name: "Stop",
2922
+ cwd,
2923
+ session_id: "sess-dead",
2924
+ thread_id: "thread-1",
2925
+ stop_hook_active: true,
2926
+ }, { cwd });
2927
+ assert.equal(result.omxEventName, "stop");
2928
+ assert.equal(result.outputJson, null);
2929
+ }
2930
+ finally {
2931
+ await rm(cwd, { recursive: true, force: true });
2932
+ }
2933
+ });
2741
2934
  it("does not block Stop from another session-scoped Ralph state when an explicit session_id has no active Ralph state", async () => {
2742
2935
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-explicit-session-ralph-"));
2743
2936
  try {
@@ -2875,6 +3068,91 @@ esac
2875
3068
  await rm(cwd, { recursive: true, force: true });
2876
3069
  }
2877
3070
  });
3071
+ it("lets dispatcher dedupe identical native stop hook replays after Stop payload normalization", async () => {
3072
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ralph-hook-dedupe-"));
3073
+ const previousOmxSessionId = process.env.OMX_SESSION_ID;
3074
+ try {
3075
+ const stateDir = join(cwd, ".omx", "state");
3076
+ await mkdir(join(stateDir, "sessions", "sess-stop-ralph-hook-dedupe"), { recursive: true });
3077
+ await writeHookCounterPlugin(cwd);
3078
+ await writeFile(join(stateDir, "sessions", "sess-stop-ralph-hook-dedupe", "ralph-state.json"), JSON.stringify({
3079
+ active: true,
3080
+ current_phase: "executing",
3081
+ session_id: "sess-stop-ralph-hook-dedupe",
3082
+ }));
3083
+ process.env.OMX_SESSION_ID = "sess-stop-ralph-hook-dedupe";
3084
+ const payload = {
3085
+ hook_event_name: "Stop",
3086
+ cwd,
3087
+ session_id: "sess-stop-ralph-hook-dedupe",
3088
+ thread_id: "thread-stop-ralph-hook-dedupe",
3089
+ turn_id: "turn-stop-ralph-hook-dedupe-1",
3090
+ last_assistant_message: "Next active targets:\n\n1. scheduler integration\n\nI am continuing.",
3091
+ };
3092
+ await dispatchCodexNativeHook(payload, { cwd });
3093
+ await dispatchCodexNativeHook({
3094
+ ...payload,
3095
+ stop_hook_active: true,
3096
+ }, { cwd });
3097
+ const marker = JSON.parse(await readFile(join(cwd, ".omx", "stop-hook-counter.json"), "utf-8"));
3098
+ assert.equal(marker.count, 1);
3099
+ }
3100
+ finally {
3101
+ if (typeof previousOmxSessionId === "string")
3102
+ process.env.OMX_SESSION_ID = previousOmxSessionId;
3103
+ else
3104
+ delete process.env.OMX_SESSION_ID;
3105
+ await rm(cwd, { recursive: true, force: true });
3106
+ }
3107
+ });
3108
+ it("preserves per-turn native stop hook delivery even when stop_hook_active remains true", async () => {
3109
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ralph-hook-refire-"));
3110
+ const previousOmxSessionId = process.env.OMX_SESSION_ID;
3111
+ try {
3112
+ const stateDir = join(cwd, ".omx", "state");
3113
+ await mkdir(join(stateDir, "sessions", "sess-stop-ralph-hook-refire"), { recursive: true });
3114
+ await writeHookCounterPlugin(cwd);
3115
+ await writeFile(join(stateDir, "sessions", "sess-stop-ralph-hook-refire", "ralph-state.json"), JSON.stringify({
3116
+ active: true,
3117
+ current_phase: "executing",
3118
+ session_id: "sess-stop-ralph-hook-refire",
3119
+ }));
3120
+ process.env.OMX_SESSION_ID = "sess-stop-ralph-hook-refire";
3121
+ const payload = {
3122
+ hook_event_name: "Stop",
3123
+ cwd,
3124
+ session_id: "sess-stop-ralph-hook-refire",
3125
+ thread_id: "thread-stop-ralph-hook-refire",
3126
+ turn_id: "turn-stop-ralph-hook-refire-1",
3127
+ last_assistant_message: "Continuing current task.",
3128
+ };
3129
+ await dispatchCodexNativeHook(payload, { cwd });
3130
+ await dispatchCodexNativeHook({
3131
+ ...payload,
3132
+ turn_id: "turn-stop-ralph-hook-refire-2",
3133
+ stop_hook_active: true,
3134
+ }, { cwd });
3135
+ await writeFile(join(stateDir, "sessions", "sess-stop-ralph-hook-refire", "ralph-state.json"), JSON.stringify({
3136
+ active: true,
3137
+ current_phase: "executing",
3138
+ session_id: "sess-stop-ralph-hook-refire",
3139
+ }));
3140
+ await dispatchCodexNativeHook({
3141
+ ...payload,
3142
+ turn_id: "turn-stop-ralph-hook-refire-3",
3143
+ stop_hook_active: true,
3144
+ }, { cwd });
3145
+ const marker = JSON.parse(await readFile(join(cwd, ".omx", "stop-hook-counter.json"), "utf-8"));
3146
+ assert.equal(marker.count, 3);
3147
+ }
3148
+ finally {
3149
+ if (typeof previousOmxSessionId === "string")
3150
+ process.env.OMX_SESSION_ID = previousOmxSessionId;
3151
+ else
3152
+ delete process.env.OMX_SESSION_ID;
3153
+ await rm(cwd, { recursive: true, force: true });
3154
+ }
3155
+ });
2878
3156
  it("returns Stop continuation output for native auto-nudge stall prompts", async () => {
2879
3157
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-auto-nudge-"));
2880
3158
  try {
@@ -2975,6 +3253,51 @@ esac
2975
3253
  await rm(cwd, { recursive: true, force: true });
2976
3254
  }
2977
3255
  });
3256
+ it("dedupes native stop hook replay across owner launch SessionStart reconciliation drift", async () => {
3257
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-dispatch-session-drift-"));
3258
+ try {
3259
+ const stateDir = join(cwd, ".omx", "state");
3260
+ await mkdir(join(stateDir, "sessions", "omx-canonical"), { recursive: true });
3261
+ await writeHookCounterPlugin(cwd);
3262
+ process.env.OMX_SESSION_ID = "omx-canonical";
3263
+ await writeSessionStart(cwd, "omx-canonical");
3264
+ await writeJson(join(stateDir, "sessions", "omx-canonical", "ralph-state.json"), {
3265
+ active: true,
3266
+ current_phase: "executing",
3267
+ session_id: "omx-canonical",
3268
+ });
3269
+ await dispatchCodexNativeHook({
3270
+ hook_event_name: "SessionStart",
3271
+ cwd,
3272
+ session_id: "codex-native-new",
3273
+ }, { cwd, sessionOwnerPid: process.pid });
3274
+ await dispatchCodexNativeHook({
3275
+ hook_event_name: "Stop",
3276
+ cwd,
3277
+ session_id: "codex-native-new",
3278
+ thread_id: "thread-stop-hook-drift",
3279
+ turn_id: "turn-stop-hook-drift-1",
3280
+ last_assistant_message: "Keep going and finish the cleanup.",
3281
+ }, { cwd });
3282
+ await dispatchCodexNativeHook({
3283
+ hook_event_name: "Stop",
3284
+ cwd,
3285
+ session_id: "omx-canonical",
3286
+ thread_id: "thread-stop-hook-drift",
3287
+ turn_id: "turn-stop-hook-drift-1",
3288
+ stop_hook_active: true,
3289
+ last_assistant_message: "Keep going and finish the cleanup.",
3290
+ }, { cwd });
3291
+ const marker = JSON.parse(await readFile(join(cwd, ".omx", "stop-hook-counter.json"), "utf-8"));
3292
+ assert.equal(marker.count, 1);
3293
+ const sessionState = JSON.parse(await readFile(join(stateDir, "session.json"), "utf-8"));
3294
+ assert.equal(sessionState.session_id, "omx-canonical");
3295
+ assert.equal(sessionState.native_session_id, "codex-native-new");
3296
+ }
3297
+ finally {
3298
+ await rm(cwd, { recursive: true, force: true });
3299
+ }
3300
+ });
2978
3301
  it("re-fires native auto-nudge for a later fresh Stop reply even when stop_hook_active is true", async () => {
2979
3302
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-auto-nudge-refire-"));
2980
3303
  try {
@@ -3432,59 +3755,50 @@ esac
3432
3755
  await rm(cwd, { recursive: true, force: true });
3433
3756
  }
3434
3757
  });
3435
- it("re-blocks active execution modes on repeated Stop hooks", async () => {
3436
- const cases = [
3437
- {
3438
- mode: "autopilot",
3439
- phase: "execution",
3440
- reason: "OMX autopilot is still active (phase: execution); continue the task and gather fresh verification evidence before stopping.",
3441
- },
3442
- {
3443
- mode: "ultrawork",
3444
- phase: "executing",
3758
+ it("suppresses duplicate ultrawork Stop replays while stop_hook_active stays true", async () => {
3759
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ultrawork-repeat-"));
3760
+ try {
3761
+ const stateDir = join(cwd, ".omx", "state");
3762
+ await mkdir(stateDir, { recursive: true });
3763
+ await writeJson(join(stateDir, "ultrawork-state.json"), {
3764
+ active: true,
3765
+ current_phase: "executing",
3766
+ });
3767
+ const first = await dispatchCodexNativeHook({
3768
+ hook_event_name: "Stop",
3769
+ cwd,
3770
+ session_id: "sess-stop-ultrawork-repeat",
3771
+ thread_id: "thread-stop-ultrawork-repeat",
3772
+ turn_id: "turn-stop-ultrawork-repeat-1",
3773
+ }, { cwd });
3774
+ const repeated = await dispatchCodexNativeHook({
3775
+ hook_event_name: "Stop",
3776
+ cwd,
3777
+ session_id: "sess-stop-ultrawork-repeat",
3778
+ thread_id: "thread-stop-ultrawork-repeat",
3779
+ turn_id: "turn-stop-ultrawork-repeat-1",
3780
+ stop_hook_active: true,
3781
+ }, { cwd });
3782
+ const fresh = await dispatchCodexNativeHook({
3783
+ hook_event_name: "Stop",
3784
+ cwd,
3785
+ session_id: "sess-stop-ultrawork-repeat",
3786
+ thread_id: "thread-stop-ultrawork-repeat",
3787
+ turn_id: "turn-stop-ultrawork-repeat-2",
3788
+ stop_hook_active: true,
3789
+ }, { cwd });
3790
+ assert.equal(first.omxEventName, "stop");
3791
+ assert.deepEqual(repeated.outputJson, null);
3792
+ assert.equal(fresh.omxEventName, "stop");
3793
+ assert.deepEqual(fresh.outputJson, {
3794
+ decision: "block",
3445
3795
  reason: "OMX ultrawork is still active (phase: executing); continue the task and gather fresh verification evidence before stopping.",
3446
- },
3447
- {
3448
- mode: "ultraqa",
3449
- phase: "diagnose",
3450
- reason: "OMX ultraqa is still active (phase: diagnose); continue the task and gather fresh verification evidence before stopping.",
3451
- },
3452
- ];
3453
- for (const testCase of cases) {
3454
- const cwd = await mkdtemp(join(tmpdir(), `omx-native-hook-stop-${testCase.mode}-repeat-`));
3455
- try {
3456
- const stateDir = join(cwd, ".omx", "state");
3457
- await mkdir(stateDir, { recursive: true });
3458
- await writeJson(join(stateDir, `${testCase.mode}-state.json`), {
3459
- active: true,
3460
- current_phase: testCase.phase,
3461
- });
3462
- await dispatchCodexNativeHook({
3463
- hook_event_name: "Stop",
3464
- cwd,
3465
- session_id: `sess-stop-${testCase.mode}-repeat`,
3466
- thread_id: `thread-stop-${testCase.mode}-repeat`,
3467
- turn_id: `turn-stop-${testCase.mode}-repeat-1`,
3468
- }, { cwd });
3469
- const repeated = await dispatchCodexNativeHook({
3470
- hook_event_name: "Stop",
3471
- cwd,
3472
- session_id: `sess-stop-${testCase.mode}-repeat`,
3473
- thread_id: `thread-stop-${testCase.mode}-repeat`,
3474
- turn_id: `turn-stop-${testCase.mode}-repeat-1`,
3475
- stop_hook_active: true,
3476
- }, { cwd });
3477
- assert.equal(repeated.omxEventName, "stop");
3478
- assert.deepEqual(repeated.outputJson, {
3479
- decision: "block",
3480
- reason: testCase.reason,
3481
- stopReason: `${testCase.mode}_${testCase.phase}`,
3482
- systemMessage: `OMX ${testCase.mode} is still active (phase: ${testCase.phase}).`,
3483
- });
3484
- }
3485
- finally {
3486
- await rm(cwd, { recursive: true, force: true });
3487
- }
3796
+ stopReason: "ultrawork_executing",
3797
+ systemMessage: "OMX ultrawork is still active (phase: executing).",
3798
+ });
3799
+ }
3800
+ finally {
3801
+ await rm(cwd, { recursive: true, force: true });
3488
3802
  }
3489
3803
  });
3490
3804
  it("re-blocks active ralplan skill state on repeated Stop hooks", async () => {