oh-my-codex 0.18.8 → 0.18.10
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.
- package/Cargo.lock +12 -12
- package/Cargo.toml +1 -1
- package/README.md +4 -0
- package/dist/autopilot/__tests__/deep-interview-gate.test.d.ts +2 -0
- package/dist/autopilot/__tests__/deep-interview-gate.test.d.ts.map +1 -0
- package/dist/autopilot/__tests__/deep-interview-gate.test.js +215 -0
- package/dist/autopilot/__tests__/deep-interview-gate.test.js.map +1 -0
- package/dist/autopilot/__tests__/fsm.test.js +3 -0
- package/dist/autopilot/__tests__/fsm.test.js.map +1 -1
- package/dist/autopilot/__tests__/ralplan-gate.test.js +148 -0
- package/dist/autopilot/__tests__/ralplan-gate.test.js.map +1 -1
- package/dist/autopilot/deep-interview-gate.d.ts.map +1 -1
- package/dist/autopilot/deep-interview-gate.js +140 -0
- package/dist/autopilot/deep-interview-gate.js.map +1 -1
- package/dist/autopilot/fsm.js +2 -2
- package/dist/autopilot/fsm.js.map +1 -1
- package/dist/cli/__tests__/auth.test.js +37 -2
- package/dist/cli/__tests__/auth.test.js.map +1 -1
- package/dist/cli/__tests__/codex-feature-probe.test.d.ts +2 -0
- package/dist/cli/__tests__/codex-feature-probe.test.d.ts.map +1 -0
- package/dist/cli/__tests__/codex-feature-probe.test.js +46 -0
- package/dist/cli/__tests__/codex-feature-probe.test.js.map +1 -0
- package/dist/cli/__tests__/codex-plugin-layout.test.js +1 -1
- package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-warning-copy.test.js +2 -0
- package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +288 -6
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/launch-fallback.test.js +19 -5
- package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
- package/dist/cli/__tests__/package-bin-contract.test.js +39 -10
- package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
- package/dist/cli/__tests__/question.test.js +26 -9
- package/dist/cli/__tests__/question.test.js.map +1 -1
- package/dist/cli/__tests__/resume.test.js +50 -1
- package/dist/cli/__tests__/resume.test.js.map +1 -1
- package/dist/cli/__tests__/setup-refresh.test.js +6 -2
- package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
- package/dist/cli/__tests__/sparkshell-packaging.test.js +45 -2
- package/dist/cli/__tests__/sparkshell-packaging.test.js.map +1 -1
- package/dist/cli/__tests__/team-decompose.test.js +10 -5
- package/dist/cli/__tests__/team-decompose.test.js.map +1 -1
- package/dist/cli/__tests__/team.test.js +45 -1
- package/dist/cli/__tests__/team.test.js.map +1 -1
- package/dist/cli/__tests__/ultragoal.test.js +75 -0
- package/dist/cli/__tests__/ultragoal.test.js.map +1 -1
- package/dist/cli/__tests__/update.test.js +214 -17
- package/dist/cli/__tests__/update.test.js.map +1 -1
- package/dist/cli/__tests__/windows-popup-loop-contract.test.js +1 -1
- package/dist/cli/auth.d.ts.map +1 -1
- package/dist/cli/auth.js +25 -1
- package/dist/cli/auth.js.map +1 -1
- package/dist/cli/codex-feature-probe.d.ts +5 -2
- package/dist/cli/codex-feature-probe.d.ts.map +1 -1
- package/dist/cli/codex-feature-probe.js +25 -9
- package/dist/cli/codex-feature-probe.js.map +1 -1
- package/dist/cli/index.d.ts +39 -5
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +184 -101
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +9 -1
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/team.d.ts +4 -0
- package/dist/cli/team.d.ts.map +1 -1
- package/dist/cli/team.js +43 -4
- package/dist/cli/team.js.map +1 -1
- package/dist/cli/ultragoal.d.ts.map +1 -1
- package/dist/cli/ultragoal.js +29 -0
- package/dist/cli/ultragoal.js.map +1 -1
- package/dist/cli/update.d.ts +20 -3
- package/dist/cli/update.d.ts.map +1 -1
- package/dist/cli/update.js +265 -23
- package/dist/cli/update.js.map +1 -1
- package/dist/cli/version.d.ts.map +1 -1
- package/dist/cli/version.js +5 -9
- package/dist/cli/version.js.map +1 -1
- package/dist/compat/__tests__/doctor-contract.test.js +12 -1
- package/dist/compat/__tests__/doctor-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/agents-overlay.test.js +1 -0
- package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
- package/dist/hooks/__tests__/autopilot-skill-contract.test.js +15 -0
- package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/code-review-skill-contract.test.js +7 -3
- package/dist/hooks/__tests__/code-review-skill-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/deep-interview-contract.test.js +46 -1
- package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/skill-guidance-contract.test.js +14 -5
- package/dist/hooks/__tests__/skill-guidance-contract.test.js.map +1 -1
- package/dist/hooks/agents-overlay.d.ts.map +1 -1
- package/dist/hooks/agents-overlay.js +2 -1
- package/dist/hooks/agents-overlay.js.map +1 -1
- package/dist/hooks/extensibility/__tests__/plugin-runner.test.js +112 -1
- package/dist/hooks/extensibility/__tests__/plugin-runner.test.js.map +1 -1
- package/dist/hooks/extensibility/plugin-runner-stdin.d.ts +2 -0
- package/dist/hooks/extensibility/plugin-runner-stdin.d.ts.map +1 -0
- package/dist/hooks/extensibility/plugin-runner-stdin.js +16 -0
- package/dist/hooks/extensibility/plugin-runner-stdin.js.map +1 -0
- package/dist/hooks/extensibility/plugin-runner.js +2 -4
- package/dist/hooks/extensibility/plugin-runner.js.map +1 -1
- package/dist/hud/__tests__/index.test.js +23 -2
- package/dist/hud/__tests__/index.test.js.map +1 -1
- package/dist/hud/__tests__/reconcile.test.js +387 -0
- package/dist/hud/__tests__/reconcile.test.js.map +1 -1
- package/dist/hud/__tests__/state.test.js +28 -0
- package/dist/hud/__tests__/state.test.js.map +1 -1
- package/dist/hud/__tests__/tmux.test.js +118 -7
- package/dist/hud/__tests__/tmux.test.js.map +1 -1
- package/dist/hud/index.d.ts +6 -1
- package/dist/hud/index.d.ts.map +1 -1
- package/dist/hud/index.js +12 -3
- package/dist/hud/index.js.map +1 -1
- package/dist/hud/reconcile.d.ts +6 -2
- package/dist/hud/reconcile.d.ts.map +1 -1
- package/dist/hud/reconcile.js +58 -28
- package/dist/hud/reconcile.js.map +1 -1
- package/dist/hud/state.d.ts.map +1 -1
- package/dist/hud/state.js +4 -18
- package/dist/hud/state.js.map +1 -1
- package/dist/hud/tmux.d.ts +14 -1
- package/dist/hud/tmux.d.ts.map +1 -1
- package/dist/hud/tmux.js +129 -15
- package/dist/hud/tmux.js.map +1 -1
- package/dist/question/__tests__/renderer.test.js +566 -1
- package/dist/question/__tests__/renderer.test.js.map +1 -1
- package/dist/question/renderer.d.ts +9 -1
- package/dist/question/renderer.d.ts.map +1 -1
- package/dist/question/renderer.js +246 -70
- package/dist/question/renderer.js.map +1 -1
- package/dist/ralplan/consensus-gate.js +9 -1
- package/dist/ralplan/consensus-gate.js.map +1 -1
- package/dist/scripts/__tests__/codex-native-hook.test.js +322 -15
- package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
- package/dist/scripts/__tests__/run-test-files.test.js +115 -1
- package/dist/scripts/__tests__/run-test-files.test.js.map +1 -1
- package/dist/scripts/codex-native-hook.d.ts.map +1 -1
- package/dist/scripts/codex-native-hook.js +94 -20
- package/dist/scripts/codex-native-hook.js.map +1 -1
- package/dist/scripts/notify-hook/team-worker-stop.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-worker-stop.js +54 -21
- package/dist/scripts/notify-hook/team-worker-stop.js.map +1 -1
- package/dist/scripts/run-test-files.js +218 -160
- package/dist/scripts/run-test-files.js.map +1 -1
- package/dist/state/__tests__/operations.test.js +463 -0
- package/dist/state/__tests__/operations.test.js.map +1 -1
- package/dist/team/__tests__/delivery-log.test.js +18 -0
- package/dist/team/__tests__/delivery-log.test.js.map +1 -1
- package/dist/team/__tests__/runtime.test.js +48 -0
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/tmux-session.test.js +107 -0
- package/dist/team/__tests__/tmux-session.test.js.map +1 -1
- package/dist/team/__tests__/tmux-test-fixture.d.ts.map +1 -1
- package/dist/team/__tests__/tmux-test-fixture.js +14 -2
- package/dist/team/__tests__/tmux-test-fixture.js.map +1 -1
- package/dist/team/__tests__/tmux-test-fixture.test.js +1 -0
- package/dist/team/__tests__/tmux-test-fixture.test.js.map +1 -1
- package/dist/team/__tests__/worker-bootstrap.test.js +54 -1
- package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
- package/dist/team/delivery-log.d.ts +1 -1
- package/dist/team/delivery-log.d.ts.map +1 -1
- package/dist/team/delivery-log.js.map +1 -1
- package/dist/team/repo-aware-decomposition.d.ts +4 -0
- package/dist/team/repo-aware-decomposition.d.ts.map +1 -1
- package/dist/team/repo-aware-decomposition.js.map +1 -1
- package/dist/team/runtime.d.ts.map +1 -1
- package/dist/team/runtime.js +78 -9
- package/dist/team/runtime.js.map +1 -1
- package/dist/team/tmux-session.d.ts +1 -0
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +16 -5
- package/dist/team/tmux-session.js.map +1 -1
- package/dist/team/ultragoal-context.d.ts +12 -0
- package/dist/team/ultragoal-context.d.ts.map +1 -1
- package/dist/team/ultragoal-context.js +32 -8
- package/dist/team/ultragoal-context.js.map +1 -1
- package/dist/utils/__tests__/paths.test.js +23 -0
- package/dist/utils/__tests__/paths.test.js.map +1 -1
- package/dist/utils/__tests__/platform-command.test.js +16 -1
- package/dist/utils/__tests__/platform-command.test.js.map +1 -1
- package/dist/utils/__tests__/version.test.d.ts +2 -0
- package/dist/utils/__tests__/version.test.d.ts.map +1 -0
- package/dist/utils/__tests__/version.test.js +51 -0
- package/dist/utils/__tests__/version.test.js.map +1 -0
- package/dist/utils/paths.d.ts +8 -1
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +20 -6
- package/dist/utils/paths.js.map +1 -1
- package/dist/utils/platform-command.d.ts +9 -0
- package/dist/utils/platform-command.d.ts.map +1 -1
- package/dist/utils/platform-command.js +15 -0
- package/dist/utils/platform-command.js.map +1 -1
- package/dist/utils/toml.d.ts +4 -0
- package/dist/utils/toml.d.ts.map +1 -0
- package/dist/utils/toml.js +75 -0
- package/dist/utils/toml.js.map +1 -0
- package/dist/utils/version.d.ts +7 -0
- package/dist/utils/version.d.ts.map +1 -0
- package/dist/utils/version.js +67 -0
- package/dist/utils/version.js.map +1 -0
- package/dist/verification/__tests__/ci-rust-gates.test.js +8 -0
- package/dist/verification/__tests__/ci-rust-gates.test.js.map +1 -1
- package/dist/verification/__tests__/dev-merge-issue-close-workflow.test.js +16 -2
- package/dist/verification/__tests__/dev-merge-issue-close-workflow.test.js.map +1 -1
- package/package.json +4 -3
- package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/oh-my-codex/skills/autopilot/SKILL.md +3 -0
- package/plugins/oh-my-codex/skills/code-review/SKILL.md +2 -2
- package/plugins/oh-my-codex/skills/deep-interview/SKILL.md +85 -11
- package/plugins/oh-my-codex/skills/ultrawork/SKILL.md +32 -17
- package/skills/autopilot/SKILL.md +3 -0
- package/skills/code-review/SKILL.md +2 -2
- package/skills/deep-interview/SKILL.md +85 -11
- package/skills/ultrawork/SKILL.md +32 -17
- package/src/scripts/__tests__/codex-native-hook.test.ts +391 -26
- package/src/scripts/__tests__/run-test-files.test.ts +138 -2
- package/src/scripts/codex-native-hook.ts +99 -17
- package/src/scripts/notify-hook/team-worker-stop.ts +58 -18
- package/src/scripts/prepare-build.js +83 -0
- package/src/scripts/run-test-files.ts +229 -150
- package/templates/AGENTS.md +40 -199
- package/src/scripts/postinstall-bootstrap.js +0 -23
|
@@ -4,22 +4,23 @@ description: Parallel execution engine for high-throughput task completion
|
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
<Purpose>
|
|
7
|
-
Ultrawork is a parallel execution engine for high-throughput task completion. It is a component, not a standalone persistence mode: it provides parallelism, context discipline, and smart delegation guidance, but not Ralph's persistence loop, architect sign-off, or long-running completion guarantees.
|
|
7
|
+
Ultrawork is a parallel execution engine for high-throughput task completion. It is a component, not a standalone persistence or verification mode: it provides parallelism, context discipline, and smart delegation guidance, but not durable goal tracking, Team's tmux worker lifecycle, Ralph's legacy persistence loop, architect sign-off, or long-running completion guarantees.
|
|
8
8
|
</Purpose>
|
|
9
9
|
|
|
10
10
|
<Use_When>
|
|
11
11
|
- Multiple independent tasks can run simultaneously
|
|
12
12
|
- User says "ulw", "ultrawork", or explicitly wants parallel execution
|
|
13
13
|
- Task benefits from concurrent execution plus lightweight evidence before wrap-up
|
|
14
|
-
- You need a direct-tool lane plus optional background evidence lanes without entering
|
|
14
|
+
- You need a direct-tool lane plus optional background evidence lanes without entering Team or a durable goal workflow
|
|
15
15
|
</Use_When>
|
|
16
16
|
|
|
17
17
|
<Do_Not_Use_When>
|
|
18
|
-
- Task
|
|
19
|
-
- Task
|
|
20
|
-
-
|
|
18
|
+
- Task needs durable goal tracking, ledger checkpoints, or resume across stories -- use `ultragoal` instead
|
|
19
|
+
- Task needs coordinated tmux workers, shared task state, mailbox/dispatch coordination, or long-running parallel execution -- use `team` instead
|
|
20
|
+
- Task requires a full autonomous pipeline -- use `autopilot` instead (default loop: `deep-interview -> ralplan -> ultragoal`, with `team` only when needed)
|
|
21
|
+
- Task intentionally requires the legacy persistent single-owner completion/verification loop -- use `ralph` explicitly; do not present it as the default durable path
|
|
22
|
+
- There is only one sequential task with no parallelism opportunity -- execute directly, use `ultragoal` for durable tracking, or delegate to a single `executor`
|
|
21
23
|
- The request is still in plan-consensus mode -- keep planning artifacts in `ralplan` until execution is explicitly authorized
|
|
22
|
-
- User needs session persistence for resume -- use `ralph`, which adds persistence on top of ultrawork
|
|
23
24
|
</Do_Not_Use_When>
|
|
24
25
|
|
|
25
26
|
<Why_This_Exists>
|
|
@@ -138,8 +139,12 @@ Why bad: No verification output, no acceptance evidence, and no manual QA note w
|
|
|
138
139
|
</Examples>
|
|
139
140
|
|
|
140
141
|
<Escalation_And_Stop_Conditions>
|
|
141
|
-
- When ultrawork is invoked directly
|
|
142
|
-
-
|
|
142
|
+
- When ultrawork is invoked directly, apply lightweight verification only -- build/typecheck passes when relevant, affected tests pass, and manual QA notes are captured when needed.
|
|
143
|
+
- Ultrawork does not own persistence, durable ledgers, architect verification, deslop, full QA, or the full verified-completion promise. Do not claim those guarantees from direct ultrawork alone.
|
|
144
|
+
- Escalate to `ultragoal` when the work needs durable goal state, story checkpoints, or resume across implementation steps.
|
|
145
|
+
- Escalate to `team` when the work needs coordinated tmux workers, shared task state, or durable multi-worker lifecycle control.
|
|
146
|
+
- Escalate to explicitly requested `ralph` only for the supported legacy single-owner persistence/verification fallback.
|
|
147
|
+
- Ralph owns persistence, architect verification, deslop, and the full verified-completion promise only when explicitly selected as the supported legacy fallback; direct ultrawork does not own those guarantees.
|
|
143
148
|
- If a task fails repeatedly across retries, report the issue rather than retrying indefinitely.
|
|
144
149
|
- Escalate to the user when tasks have unclear dependencies, conflicting requirements, or a materially branching acceptance target.
|
|
145
150
|
</Escalation_And_Stop_Conditions>
|
|
@@ -159,17 +164,27 @@ Why bad: No verification output, no acceptance evidence, and no manual QA note w
|
|
|
159
164
|
## Relationship to Other Modes
|
|
160
165
|
|
|
161
166
|
```
|
|
162
|
-
|
|
163
|
-
\--
|
|
164
|
-
\-- provides: high-throughput execution + lightweight evidence
|
|
167
|
+
ultrawork (this skill)
|
|
168
|
+
\-- provides: in-session parallel execution discipline + lightweight evidence
|
|
165
169
|
|
|
166
|
-
|
|
167
|
-
\--
|
|
168
|
-
|
|
170
|
+
ultragoal (durable goal execution)
|
|
171
|
+
\-- owns: goal ledger, checkpoints, resume across stories, final gate discipline
|
|
172
|
+
\-- may use: team for parallel lanes when a story benefits from coordinated workers
|
|
169
173
|
|
|
170
|
-
|
|
171
|
-
\--
|
|
174
|
+
team (tmux coordinated execution)
|
|
175
|
+
\-- owns: worker panes, shared task state, mailbox/dispatch, lifecycle control
|
|
176
|
+
\-- can return: checkpoint-ready evidence to an Ultragoal leader
|
|
177
|
+
|
|
178
|
+
autopilot (strict autonomous delivery loop)
|
|
179
|
+
\-- default flow: deep-interview -> ralplan -> ultragoal -> code-review -> ultraqa
|
|
180
|
+
\-- may use: team only when an Ultragoal story needs parallel execution
|
|
181
|
+
|
|
182
|
+
ralph (supported legacy explicit fallback)
|
|
183
|
+
\-- owns: single-owner persistence loop + architect verification when intentionally selected
|
|
184
|
+
|
|
185
|
+
ecomode (deprecated compatibility-only)
|
|
186
|
+
\-- do not route users there from ultrawork; it is not the current model-selection path
|
|
172
187
|
```
|
|
173
188
|
|
|
174
|
-
Ultrawork is the parallelism and execution-discipline layer.
|
|
189
|
+
Ultrawork is the parallelism and execution-discipline layer. Ultragoal is the current default durable goal/ledger follow-up. Team is the coordinated tmux parallel runtime, often nested under an Ultragoal story when durable work needs multiple lanes. Autopilot orchestrates the full default lifecycle through deep-interview, ralplan, ultragoal, code-review, and ultraqa. Ralph remains active as an explicit legacy fallback for persistent single-owner verification, but it is not the recommended default durable path. Ecomode is deprecated compatibility-only and should not be advertised as the ultrawork model-selection route.
|
|
175
190
|
</Advanced>
|
|
@@ -441,23 +441,77 @@ describe("codex native hook dispatch", () => {
|
|
|
441
441
|
);
|
|
442
442
|
});
|
|
443
443
|
|
|
444
|
-
it("emits schema-safe JSON
|
|
445
|
-
const
|
|
444
|
+
it("emits Stop-schema-safe block JSON when unidentifiable malformed stdin has native Stop runtime surface", async () => {
|
|
445
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-cli-malformed-stop-surface-"));
|
|
446
|
+
try {
|
|
447
|
+
await mkdir(join(cwd, ".omx"), { recursive: true });
|
|
448
|
+
const result = spawnSync(process.execPath, [nativeHookScriptPath()], {
|
|
449
|
+
cwd,
|
|
450
|
+
input: "{",
|
|
451
|
+
encoding: "utf-8",
|
|
452
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
453
|
+
});
|
|
446
454
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
455
|
+
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
456
|
+
assert.equal(result.stderr, "");
|
|
457
|
+
const output = parseSingleJsonStdout(result.stdout) as {
|
|
458
|
+
decision?: string;
|
|
459
|
+
continue?: boolean;
|
|
460
|
+
reason?: string;
|
|
461
|
+
stopReason?: string;
|
|
462
|
+
systemMessage?: string;
|
|
463
|
+
hookSpecificOutput?: unknown;
|
|
464
|
+
};
|
|
453
465
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
466
|
+
assert.equal(output.decision, "block");
|
|
467
|
+
assert.equal(output.continue, undefined);
|
|
468
|
+
assert.equal(
|
|
469
|
+
output.reason,
|
|
470
|
+
"OMX native hook received malformed JSON input. Preserve runtime state, inspect the emitting hook payload yourself, and retry with valid JSON.",
|
|
471
|
+
);
|
|
472
|
+
assert.equal(output.stopReason, "native_hook_stdin_parse_error");
|
|
473
|
+
assert.equal(output.hookSpecificOutput, undefined);
|
|
474
|
+
assert.match(
|
|
475
|
+
String(output.systemMessage ?? ""),
|
|
476
|
+
/stdin JSON parsing failed inside codex-native-hook:/,
|
|
477
|
+
);
|
|
478
|
+
} finally {
|
|
479
|
+
await rm(cwd, { recursive: true, force: true });
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
it("preserves non-Stop fail-closed JSON when malformed stdin identifies a non-Stop hook", async () => {
|
|
484
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-cli-malformed-nonstop-"));
|
|
485
|
+
try {
|
|
486
|
+
await mkdir(join(cwd, ".omx"), { recursive: true });
|
|
487
|
+
const result = spawnSync(process.execPath, [nativeHookScriptPath()], {
|
|
488
|
+
cwd,
|
|
489
|
+
input: '{hook_event_name:"PreToolUse",',
|
|
490
|
+
encoding: "utf-8",
|
|
491
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
495
|
+
assert.equal(result.stderr, "");
|
|
496
|
+
const output = parseSingleJsonStdout(result.stdout) as {
|
|
497
|
+
continue?: boolean;
|
|
498
|
+
decision?: string;
|
|
499
|
+
stopReason?: string;
|
|
500
|
+
systemMessage?: string;
|
|
501
|
+
hookSpecificOutput?: unknown;
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
assert.equal(output.continue, false);
|
|
505
|
+
assert.equal(output.decision, undefined);
|
|
506
|
+
assert.equal(output.stopReason, "native_hook_stdin_parse_error");
|
|
507
|
+
assert.equal(output.hookSpecificOutput, undefined);
|
|
508
|
+
assert.match(
|
|
509
|
+
String(output.systemMessage ?? ""),
|
|
510
|
+
/stdin JSON parsing failed inside codex-native-hook:/,
|
|
511
|
+
);
|
|
512
|
+
} finally {
|
|
513
|
+
await rm(cwd, { recursive: true, force: true });
|
|
514
|
+
}
|
|
461
515
|
});
|
|
462
516
|
|
|
463
517
|
it("redacts unterminated prompt-like malformed stdin fields", async () => {
|
|
@@ -1867,6 +1921,51 @@ describe("codex native hook dispatch", () => {
|
|
|
1867
1921
|
}
|
|
1868
1922
|
});
|
|
1869
1923
|
|
|
1924
|
+
it("includes repo-local .omx project-memory during SessionStart when OMX_ROOT is boxed", async () => {
|
|
1925
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-session-boxed-memory-"));
|
|
1926
|
+
const boxedRoot = await mkdtemp(join(tmpdir(), "omx-native-hook-boxed-root-"));
|
|
1927
|
+
const previousOmxRoot = process.env.OMX_ROOT;
|
|
1928
|
+
try {
|
|
1929
|
+
process.env.OMX_ROOT = boxedRoot;
|
|
1930
|
+
await writeJson(join(cwd, ".omx", "project-memory.json"), {
|
|
1931
|
+
techStack: "Repo-local CLI memory",
|
|
1932
|
+
conventions: "SessionStart should load CLI-written project memory",
|
|
1933
|
+
directives: [
|
|
1934
|
+
{ directive: "Prefer repo-local .omx project memory over boxed runtime fallback.", priority: "high" },
|
|
1935
|
+
],
|
|
1936
|
+
});
|
|
1937
|
+
await writeJson(join(boxedRoot, ".omx", "project-memory.json"), {
|
|
1938
|
+
techStack: "Boxed runtime memory should not win",
|
|
1939
|
+
notes: [{ category: "runtime", content: "stale boxed runtime note", timestamp: new Date().toISOString() }],
|
|
1940
|
+
});
|
|
1941
|
+
|
|
1942
|
+
const result = await dispatchCodexNativeHook(
|
|
1943
|
+
{
|
|
1944
|
+
hook_event_name: "SessionStart",
|
|
1945
|
+
cwd,
|
|
1946
|
+
session_id: "sess-boxed-memory-1",
|
|
1947
|
+
},
|
|
1948
|
+
{ cwd, sessionOwnerPid: 43210 },
|
|
1949
|
+
);
|
|
1950
|
+
|
|
1951
|
+
const additionalContext = String(
|
|
1952
|
+
(result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "",
|
|
1953
|
+
);
|
|
1954
|
+
assert.match(additionalContext, /\[Project memory\]/);
|
|
1955
|
+
assert.match(additionalContext, /source: \.omx\/project-memory\.json/);
|
|
1956
|
+
assert.match(additionalContext, /Repo-local CLI memory/);
|
|
1957
|
+
assert.match(additionalContext, /SessionStart should load CLI-written project memory/);
|
|
1958
|
+
assert.match(additionalContext, /Prefer repo-local \.omx project memory over boxed runtime fallback\./);
|
|
1959
|
+
assert.doesNotMatch(additionalContext, /Boxed runtime memory should not win/);
|
|
1960
|
+
assert.doesNotMatch(additionalContext, /stale boxed runtime note/);
|
|
1961
|
+
} finally {
|
|
1962
|
+
if (previousOmxRoot === undefined) delete process.env.OMX_ROOT;
|
|
1963
|
+
else process.env.OMX_ROOT = previousOmxRoot;
|
|
1964
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1965
|
+
await rm(boxedRoot, { recursive: true, force: true });
|
|
1966
|
+
}
|
|
1967
|
+
});
|
|
1968
|
+
|
|
1870
1969
|
it("prefers repository project-memory.json during SessionStart while preserving legacy wiki guidance", async () => {
|
|
1871
1970
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-session-root-memory-legacy-wiki-"));
|
|
1872
1971
|
try {
|
|
@@ -5804,6 +5903,76 @@ exit 0
|
|
|
5804
5903
|
}
|
|
5805
5904
|
});
|
|
5806
5905
|
|
|
5906
|
+
it("allows null-device fd redirects while deep-interview blocks real Bash writes", async () => {
|
|
5907
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-deep-interview-null-redirect-"));
|
|
5908
|
+
try {
|
|
5909
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
5910
|
+
const sessionDir = join(stateDir, "sessions", "sess-di-null-redirect");
|
|
5911
|
+
await mkdir(sessionDir, { recursive: true });
|
|
5912
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-di-null-redirect", cwd });
|
|
5913
|
+
await writeJson(join(sessionDir, "skill-active-state.json"), {
|
|
5914
|
+
version: 1,
|
|
5915
|
+
active: true,
|
|
5916
|
+
skill: "deep-interview",
|
|
5917
|
+
phase: "planning",
|
|
5918
|
+
session_id: "sess-di-null-redirect",
|
|
5919
|
+
active_skills: [{ skill: "deep-interview", phase: "planning", active: true, session_id: "sess-di-null-redirect" }],
|
|
5920
|
+
});
|
|
5921
|
+
await writeJson(join(sessionDir, "deep-interview-state.json"), {
|
|
5922
|
+
active: true,
|
|
5923
|
+
mode: "deep-interview",
|
|
5924
|
+
current_phase: "intent-first",
|
|
5925
|
+
session_id: "sess-di-null-redirect",
|
|
5926
|
+
});
|
|
5927
|
+
|
|
5928
|
+
const allowedCommands = [
|
|
5929
|
+
"find application -type d -name 'bug-tracking*' 2>/dev/null | head -20",
|
|
5930
|
+
"find application -type d -name 'bug-tracking*' 2> /dev/null | head -20",
|
|
5931
|
+
"find application -type d -name 'bug-tracking*' 2>NUL | head -20",
|
|
5932
|
+
"find application -type d -name 'bug-tracking*' 1>/dev/null",
|
|
5933
|
+
"find application -type d -name 'bug-tracking*' &>/dev/null",
|
|
5934
|
+
];
|
|
5935
|
+
|
|
5936
|
+
for (const [index, command] of allowedCommands.entries()) {
|
|
5937
|
+
const result = await dispatchCodexNativeHook(
|
|
5938
|
+
{
|
|
5939
|
+
hook_event_name: "PreToolUse",
|
|
5940
|
+
cwd,
|
|
5941
|
+
session_id: "sess-di-null-redirect",
|
|
5942
|
+
tool_name: "Bash",
|
|
5943
|
+
tool_use_id: `tool-di-null-redirect-${index}`,
|
|
5944
|
+
tool_input: { command },
|
|
5945
|
+
},
|
|
5946
|
+
{ cwd },
|
|
5947
|
+
);
|
|
5948
|
+
assert.equal(result.outputJson, null, command);
|
|
5949
|
+
}
|
|
5950
|
+
|
|
5951
|
+
const blockedCommands = [
|
|
5952
|
+
"find application -type d -name 'bug-tracking*' 2>errors.log | head -20",
|
|
5953
|
+
"find application -type d -name 'bug-tracking*' > /tmp/bug-tracking.txt",
|
|
5954
|
+
"find application -type d -name 'bug-tracking*' | tee /dev/null",
|
|
5955
|
+
];
|
|
5956
|
+
|
|
5957
|
+
for (const [index, command] of blockedCommands.entries()) {
|
|
5958
|
+
const result = await dispatchCodexNativeHook(
|
|
5959
|
+
{
|
|
5960
|
+
hook_event_name: "PreToolUse",
|
|
5961
|
+
cwd,
|
|
5962
|
+
session_id: "sess-di-null-redirect",
|
|
5963
|
+
tool_name: "Bash",
|
|
5964
|
+
tool_use_id: `tool-di-real-redirect-${index}`,
|
|
5965
|
+
tool_input: { command },
|
|
5966
|
+
},
|
|
5967
|
+
{ cwd },
|
|
5968
|
+
);
|
|
5969
|
+
assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block", command);
|
|
5970
|
+
}
|
|
5971
|
+
} finally {
|
|
5972
|
+
await rm(cwd, { recursive: true, force: true });
|
|
5973
|
+
}
|
|
5974
|
+
});
|
|
5975
|
+
|
|
5807
5976
|
it("allows implementation tools after an explicit deep-interview handoff deactivates the mode", async () => {
|
|
5808
5977
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-deep-interview-handoff-"));
|
|
5809
5978
|
try {
|
|
@@ -8296,6 +8465,52 @@ exit 0
|
|
|
8296
8465
|
}
|
|
8297
8466
|
});
|
|
8298
8467
|
|
|
8468
|
+
it("suppresses parent Autopilot Stop continuation in side conversations", async () => {
|
|
8469
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-autopilot-side-conversation-"));
|
|
8470
|
+
try {
|
|
8471
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
8472
|
+
const sessionId = "sess-stop-autopilot-side-conversation";
|
|
8473
|
+
const transcriptPath = join(cwd, "side-conversation-rollout.jsonl");
|
|
8474
|
+
await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
|
|
8475
|
+
await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
|
|
8476
|
+
active: true,
|
|
8477
|
+
mode: "autopilot",
|
|
8478
|
+
current_phase: "deep-interview",
|
|
8479
|
+
});
|
|
8480
|
+
await writeFile(
|
|
8481
|
+
transcriptPath,
|
|
8482
|
+
`${JSON.stringify({
|
|
8483
|
+
type: "message",
|
|
8484
|
+
role: "user",
|
|
8485
|
+
content: [
|
|
8486
|
+
"Side conversation boundary.",
|
|
8487
|
+
"Everything before this boundary is inherited history from the parent thread. It is reference context only. It is not your current task.",
|
|
8488
|
+
"Only messages submitted after this boundary are active user instructions for this side conversation.",
|
|
8489
|
+
"You are a side-conversation assistant, separate from the main thread.",
|
|
8490
|
+
].join("\n\n"),
|
|
8491
|
+
})}\n`,
|
|
8492
|
+
"utf-8",
|
|
8493
|
+
);
|
|
8494
|
+
|
|
8495
|
+
const result = await dispatchCodexNativeHook(
|
|
8496
|
+
{
|
|
8497
|
+
hook_event_name: "Stop",
|
|
8498
|
+
cwd,
|
|
8499
|
+
session_id: sessionId,
|
|
8500
|
+
thread_id: "thread-stop-autopilot-side-conversation",
|
|
8501
|
+
transcript_path: transcriptPath,
|
|
8502
|
+
last_assistant_message: "Waiting for a new side-conversation question.",
|
|
8503
|
+
},
|
|
8504
|
+
{ cwd },
|
|
8505
|
+
);
|
|
8506
|
+
|
|
8507
|
+
assert.equal(result.omxEventName, "stop");
|
|
8508
|
+
assert.equal(result.outputJson, null);
|
|
8509
|
+
} finally {
|
|
8510
|
+
await rm(cwd, { recursive: true, force: true });
|
|
8511
|
+
}
|
|
8512
|
+
});
|
|
8513
|
+
|
|
8299
8514
|
it("requires Autopilot code review after a compact-boundary Stop exemption", async () => {
|
|
8300
8515
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-autopilot-review-compact-"));
|
|
8301
8516
|
try {
|
|
@@ -9037,7 +9252,7 @@ exit 0
|
|
|
9037
9252
|
}
|
|
9038
9253
|
});
|
|
9039
9254
|
|
|
9040
|
-
it("
|
|
9255
|
+
it("steers worker Stop leader nudge directly when leader pane is busy", async () => {
|
|
9041
9256
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-worker-busy-leader-"));
|
|
9042
9257
|
const prevTeamWorker = process.env.OMX_TEAM_WORKER;
|
|
9043
9258
|
const prevTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
|
|
@@ -9110,14 +9325,11 @@ exit 0
|
|
|
9110
9325
|
assert.equal(result.outputJson, null);
|
|
9111
9326
|
const tmuxLog = await readFile(tmuxLogPath, "utf-8");
|
|
9112
9327
|
assert.match(tmuxLog, /send-keys -t %42 -l \[OMX\] worker-1 native Stop allowed/);
|
|
9113
|
-
assert.
|
|
9114
|
-
|
|
9115
|
-
assert.
|
|
9116
|
-
tmuxLog.indexOf("send-keys -t %42 Tab") < tmuxLog.indexOf("send-keys -t %42 C-m"),
|
|
9117
|
-
"busy worker-stop nudge should press Tab before C-m",
|
|
9118
|
-
);
|
|
9328
|
+
assert.doesNotMatch(tmuxLog, /send-keys -t %42 Tab/);
|
|
9329
|
+
const submits = tmuxLog.match(/send-keys -t %42 C-m/g) || [];
|
|
9330
|
+
assert.equal(submits.length, 2, "busy worker-stop nudge should submit directly as steering, not queue via Tab");
|
|
9119
9331
|
const nudgeState = JSON.parse(await readFile(join(workerDir, "worker-stop-nudge.json"), "utf-8"));
|
|
9120
|
-
assert.equal(nudgeState.delivery, "
|
|
9332
|
+
assert.equal(nudgeState.delivery, "steered");
|
|
9121
9333
|
} finally {
|
|
9122
9334
|
if (typeof prevTeamWorker === "string") process.env.OMX_TEAM_WORKER = prevTeamWorker;
|
|
9123
9335
|
else delete process.env.OMX_TEAM_WORKER;
|
|
@@ -9253,6 +9465,14 @@ exit 0
|
|
|
9253
9465
|
});
|
|
9254
9466
|
assert.equal(shutdownResult.result, "team_state_gone_or_shutdown");
|
|
9255
9467
|
assert.equal(existsSync(join(stateDir, "team", "shutdown-team", "worker-stop-nudge.json")), false);
|
|
9468
|
+
const deliveryLogPath = join(logsDir, `team-delivery-${new Date().toISOString().split("T")[0]}.jsonl`);
|
|
9469
|
+
const deliveryEvents = (await readFile(deliveryLogPath, "utf-8"))
|
|
9470
|
+
.trim()
|
|
9471
|
+
.split("\n")
|
|
9472
|
+
.map((line) => JSON.parse(line));
|
|
9473
|
+
const suppressedEvents = deliveryEvents.filter((event) => event.reason === "team_state_gone_or_shutdown");
|
|
9474
|
+
assert.equal(suppressedEvents.length, 2, "late closed-team Stop nudges should be diagnostics, not queued prompts");
|
|
9475
|
+
assert.equal(suppressedEvents.every((event) => event.result === "suppressed" && event.transport === "none"), true);
|
|
9256
9476
|
} finally {
|
|
9257
9477
|
await rm(cwd, { recursive: true, force: true });
|
|
9258
9478
|
}
|
|
@@ -9293,13 +9513,13 @@ exit 0
|
|
|
9293
9513
|
workerContext: { teamName, workerName: "worker-2" },
|
|
9294
9514
|
});
|
|
9295
9515
|
|
|
9296
|
-
assert.equal(result.result, "
|
|
9516
|
+
assert.equal(result.result, "steered");
|
|
9297
9517
|
const tmuxLog = await readFile(tmuxLogPath, "utf-8");
|
|
9298
9518
|
assert.match(tmuxLog, /send-keys -t %42 -l \[OMX\] worker-2 native Stop allowed/);
|
|
9299
|
-
assert.
|
|
9519
|
+
assert.doesNotMatch(tmuxLog, /send-keys -t %42 Tab/);
|
|
9300
9520
|
const teamNudgeState = JSON.parse(await readFile(join(teamDir, "worker-stop-nudge.json"), "utf-8"));
|
|
9301
9521
|
assert.equal(teamNudgeState.worker, "worker-2");
|
|
9302
|
-
assert.equal(teamNudgeState.delivery, "
|
|
9522
|
+
assert.equal(teamNudgeState.delivery, "steered");
|
|
9303
9523
|
} finally {
|
|
9304
9524
|
if (typeof prevPath === "string") process.env.PATH = prevPath;
|
|
9305
9525
|
else delete process.env.PATH;
|
|
@@ -9432,6 +9652,21 @@ exit 0
|
|
|
9432
9652
|
assert.equal(existsSync(teamDir), false, "deferred worker Stop recording must not recreate removed team state");
|
|
9433
9653
|
const tmuxLog = await readFile(tmuxLogPath, "utf-8");
|
|
9434
9654
|
assert.doesNotMatch(tmuxLog, /send-keys -t %42 -l \[OMX\] worker-1 native Stop allowed/);
|
|
9655
|
+
const deliveryLogPath = join(logsDir, `team-delivery-${new Date().toISOString().split("T")[0]}.jsonl`);
|
|
9656
|
+
const deliveryEvents = (await readFile(deliveryLogPath, "utf-8"))
|
|
9657
|
+
.trim()
|
|
9658
|
+
.split("\n")
|
|
9659
|
+
.map((line) => JSON.parse(line));
|
|
9660
|
+
assert.equal(
|
|
9661
|
+
deliveryEvents.some((event) =>
|
|
9662
|
+
event.team === teamName
|
|
9663
|
+
&& event.result === "suppressed"
|
|
9664
|
+
&& event.transport === "none"
|
|
9665
|
+
&& event.reason === "team_state_gone_or_shutdown"
|
|
9666
|
+
),
|
|
9667
|
+
true,
|
|
9668
|
+
"teardown-race worker Stop nudges should be diagnostic suppression events, not queued prompts",
|
|
9669
|
+
);
|
|
9435
9670
|
} finally {
|
|
9436
9671
|
if (typeof prevPath === "string") process.env.PATH = prevPath;
|
|
9437
9672
|
else delete process.env.PATH;
|
|
@@ -13932,6 +14167,136 @@ exit 0
|
|
|
13932
14167
|
}
|
|
13933
14168
|
});
|
|
13934
14169
|
|
|
14170
|
+
it("blocks implementation writes when Autopilot ralplan is visible only in skill-active phase", async () => {
|
|
14171
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-skill-ralplan-pretool-block-"));
|
|
14172
|
+
try {
|
|
14173
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
14174
|
+
const sessionId = "sess-autopilot-skill-ralplan-pretool-block";
|
|
14175
|
+
await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
|
|
14176
|
+
await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
|
|
14177
|
+
await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
|
|
14178
|
+
active: true,
|
|
14179
|
+
skill: "autopilot",
|
|
14180
|
+
phase: "autopilot:ralplan",
|
|
14181
|
+
session_id: sessionId,
|
|
14182
|
+
active_skills: [{ skill: "autopilot", phase: "autopilot:ralplan", active: true, session_id: sessionId }],
|
|
14183
|
+
});
|
|
14184
|
+
await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
|
|
14185
|
+
active: true,
|
|
14186
|
+
mode: "autopilot",
|
|
14187
|
+
current_phase: "planning",
|
|
14188
|
+
session_id: sessionId,
|
|
14189
|
+
state: {
|
|
14190
|
+
handoff_artifacts: {
|
|
14191
|
+
ralplan_consensus_gate: { required: true, complete: false },
|
|
14192
|
+
},
|
|
14193
|
+
},
|
|
14194
|
+
});
|
|
14195
|
+
|
|
14196
|
+
const result = await dispatchCodexNativeHook(
|
|
14197
|
+
{
|
|
14198
|
+
hook_event_name: "PreToolUse",
|
|
14199
|
+
cwd,
|
|
14200
|
+
session_id: sessionId,
|
|
14201
|
+
thread_id: "thread-autopilot-skill-ralplan-pretool-block",
|
|
14202
|
+
tool_name: "Edit",
|
|
14203
|
+
tool_input: { file_path: "src/runtime.ts" },
|
|
14204
|
+
},
|
|
14205
|
+
{ cwd },
|
|
14206
|
+
);
|
|
14207
|
+
|
|
14208
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
14209
|
+
assert.equal(result.outputJson?.decision, "block");
|
|
14210
|
+
assert.match(String(result.outputJson?.reason ?? ""), /Autopilot planning is active .*implementation\/write tools are blocked/i);
|
|
14211
|
+
} finally {
|
|
14212
|
+
await rm(cwd, { recursive: true, force: true });
|
|
14213
|
+
}
|
|
14214
|
+
});
|
|
14215
|
+
|
|
14216
|
+
it("ignores stale Autopilot ralplan skill mirrors after detail state leaves planning", async () => {
|
|
14217
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-stale-ralplan-mirror-"));
|
|
14218
|
+
try {
|
|
14219
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
14220
|
+
const sessionId = "sess-autopilot-stale-ralplan-mirror";
|
|
14221
|
+
await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
|
|
14222
|
+
await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
|
|
14223
|
+
await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
|
|
14224
|
+
active: true,
|
|
14225
|
+
skill: "autopilot",
|
|
14226
|
+
phase: "autopilot:ralplan",
|
|
14227
|
+
session_id: sessionId,
|
|
14228
|
+
active_skills: [{ skill: "autopilot", phase: "autopilot:ralplan", active: true, session_id: sessionId }],
|
|
14229
|
+
});
|
|
14230
|
+
|
|
14231
|
+
for (const phase of ["ultragoal", "code-review", "completing", "complete"]) {
|
|
14232
|
+
await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
|
|
14233
|
+
active: true,
|
|
14234
|
+
mode: "autopilot",
|
|
14235
|
+
current_phase: phase,
|
|
14236
|
+
session_id: sessionId,
|
|
14237
|
+
});
|
|
14238
|
+
|
|
14239
|
+
const result = await dispatchCodexNativeHook(
|
|
14240
|
+
{
|
|
14241
|
+
hook_event_name: "PreToolUse",
|
|
14242
|
+
cwd,
|
|
14243
|
+
session_id: sessionId,
|
|
14244
|
+
thread_id: "thread-autopilot-stale-ralplan-mirror",
|
|
14245
|
+
tool_name: "Edit",
|
|
14246
|
+
tool_input: { file_path: "src/runtime.ts" },
|
|
14247
|
+
},
|
|
14248
|
+
{ cwd },
|
|
14249
|
+
);
|
|
14250
|
+
|
|
14251
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
14252
|
+
assert.equal(result.outputJson, null, `stale skill-active ralplan mirror must not block when Autopilot detail phase is ${phase}`);
|
|
14253
|
+
}
|
|
14254
|
+
} finally {
|
|
14255
|
+
await rm(cwd, { recursive: true, force: true });
|
|
14256
|
+
}
|
|
14257
|
+
});
|
|
14258
|
+
|
|
14259
|
+
it("allows explicit blank Autopilot detail phase to use a ralplan skill mirror", async () => {
|
|
14260
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-blank-phase-mirror-"));
|
|
14261
|
+
try {
|
|
14262
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
14263
|
+
const sessionId = "sess-autopilot-blank-phase-mirror";
|
|
14264
|
+
await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
|
|
14265
|
+
await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
|
|
14266
|
+
await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
|
|
14267
|
+
active: true,
|
|
14268
|
+
skill: "autopilot",
|
|
14269
|
+
phase: "autopilot:ralplan",
|
|
14270
|
+
session_id: sessionId,
|
|
14271
|
+
active_skills: [{ skill: "autopilot", phase: "autopilot:ralplan", active: true, session_id: sessionId }],
|
|
14272
|
+
});
|
|
14273
|
+
await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
|
|
14274
|
+
active: true,
|
|
14275
|
+
mode: "autopilot",
|
|
14276
|
+
current_phase: "",
|
|
14277
|
+
session_id: sessionId,
|
|
14278
|
+
});
|
|
14279
|
+
|
|
14280
|
+
const result = await dispatchCodexNativeHook(
|
|
14281
|
+
{
|
|
14282
|
+
hook_event_name: "PreToolUse",
|
|
14283
|
+
cwd,
|
|
14284
|
+
session_id: sessionId,
|
|
14285
|
+
thread_id: "thread-autopilot-blank-phase-mirror",
|
|
14286
|
+
tool_name: "Edit",
|
|
14287
|
+
tool_input: { file_path: "src/runtime.ts" },
|
|
14288
|
+
},
|
|
14289
|
+
{ cwd },
|
|
14290
|
+
);
|
|
14291
|
+
|
|
14292
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
14293
|
+
assert.equal(result.outputJson?.decision, "block");
|
|
14294
|
+
assert.match(String(result.outputJson?.reason ?? ""), /Autopilot planning is active .*implementation\/write tools are blocked/i);
|
|
14295
|
+
} finally {
|
|
14296
|
+
await rm(cwd, { recursive: true, force: true });
|
|
14297
|
+
}
|
|
14298
|
+
});
|
|
14299
|
+
|
|
13935
14300
|
it("does not block implementation writes from Autopilot ralplan detail state without canonical skill state", async () => {
|
|
13936
14301
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-ralplan-no-canonical-"));
|
|
13937
14302
|
try {
|