oh-my-codex 0.19.0 → 0.20.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.
- package/Cargo.lock +6 -6
- package/Cargo.toml +1 -1
- package/README.md +8 -1
- package/crates/omx-api/src/lib.rs +3 -3
- package/crates/omx-api/tests/cli.rs +4 -4
- package/crates/omx-explore/src/main.rs +4 -4
- package/crates/omx-sparkshell/src/codex_bridge.rs +2 -2
- package/dist/agents/__tests__/definitions.test.js +3 -3
- package/dist/agents/__tests__/definitions.test.js.map +1 -1
- package/dist/agents/__tests__/native-config.test.js +71 -42
- package/dist/agents/__tests__/native-config.test.js.map +1 -1
- package/dist/agents/definitions.d.ts +1 -1
- package/dist/agents/definitions.d.ts.map +1 -1
- package/dist/agents/definitions.js +3 -3
- package/dist/agents/definitions.js.map +1 -1
- package/dist/agents/native-config.d.ts +2 -2
- package/dist/agents/native-config.d.ts.map +1 -1
- package/dist/agents/native-config.js +7 -5
- package/dist/agents/native-config.js.map +1 -1
- package/dist/autopilot/__tests__/planner-routing.test.js +19 -12
- package/dist/autopilot/__tests__/planner-routing.test.js.map +1 -1
- package/dist/autopilot/__tests__/ralplan-gate.test.js +151 -0
- package/dist/autopilot/__tests__/ralplan-gate.test.js.map +1 -1
- package/dist/autopilot/planner-routing.d.ts.map +1 -1
- package/dist/autopilot/planner-routing.js +4 -2
- package/dist/autopilot/planner-routing.js.map +1 -1
- package/dist/autopilot/ralplan-gate.d.ts +1 -0
- package/dist/autopilot/ralplan-gate.d.ts.map +1 -1
- package/dist/autopilot/ralplan-gate.js +38 -2
- package/dist/autopilot/ralplan-gate.js.map +1 -1
- package/dist/autoresearch/runtime.d.ts +2 -0
- package/dist/autoresearch/runtime.d.ts.map +1 -1
- package/dist/autoresearch/runtime.js +8 -0
- package/dist/autoresearch/runtime.js.map +1 -1
- package/dist/capabilities/__tests__/lockfile.test.d.ts +2 -0
- package/dist/capabilities/__tests__/lockfile.test.d.ts.map +1 -0
- package/dist/capabilities/__tests__/lockfile.test.js +115 -0
- package/dist/capabilities/__tests__/lockfile.test.js.map +1 -0
- package/dist/capabilities/lockfile.d.ts +102 -0
- package/dist/capabilities/lockfile.d.ts.map +1 -0
- package/dist/capabilities/lockfile.js +334 -0
- package/dist/capabilities/lockfile.js.map +1 -0
- package/dist/cli/__tests__/agents.test.js +5 -5
- package/dist/cli/__tests__/agents.test.js.map +1 -1
- package/dist/cli/__tests__/capabilities.test.d.ts +2 -0
- package/dist/cli/__tests__/capabilities.test.d.ts.map +1 -0
- package/dist/cli/__tests__/capabilities.test.js +135 -0
- package/dist/cli/__tests__/capabilities.test.js.map +1 -0
- package/dist/cli/__tests__/codex-plugin-layout.test.js +237 -6
- package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-context-window-warning.test.js +9 -9
- package/dist/cli/__tests__/doctor-context-window-warning.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-invalid-config.test.js +1 -1
- package/dist/cli/__tests__/doctor-spark-routing.test.js +22 -6
- package/dist/cli/__tests__/doctor-spark-routing.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +53 -18
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/mission.test.d.ts +2 -0
- package/dist/cli/__tests__/mission.test.d.ts.map +1 -0
- package/dist/cli/__tests__/mission.test.js +239 -0
- package/dist/cli/__tests__/mission.test.js.map +1 -0
- package/dist/cli/__tests__/plugin-marketplace-idempotent.test.js +3 -3
- package/dist/cli/__tests__/plugin-marketplace-idempotent.test.js.map +1 -1
- package/dist/cli/__tests__/resume.test.js +95 -6
- package/dist/cli/__tests__/resume.test.js.map +1 -1
- package/dist/cli/__tests__/session-scoped-runtime.test.js +67 -0
- package/dist/cli/__tests__/session-scoped-runtime.test.js.map +1 -1
- package/dist/cli/__tests__/setup-install-mode.test.js +46 -18
- package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
- package/dist/cli/__tests__/setup-prompts-overwrite.test.js +3 -3
- package/dist/cli/__tests__/setup-refresh.test.js +29 -7
- package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
- package/dist/cli/__tests__/ultragoal.test.js +1 -1
- package/dist/cli/__tests__/uninstall.test.js +4 -4
- package/dist/cli/__tests__/uninstall.test.js.map +1 -1
- package/dist/cli/agents.js +1 -1
- package/dist/cli/agents.js.map +1 -1
- package/dist/cli/capabilities.d.ts +3 -0
- package/dist/cli/capabilities.d.ts.map +1 -0
- package/dist/cli/capabilities.js +118 -0
- package/dist/cli/capabilities.js.map +1 -0
- package/dist/cli/doctor.d.ts +1 -1
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +9 -4
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.d.ts +7 -4
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +107 -23
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/mission.d.ts +36 -0
- package/dist/cli/mission.d.ts.map +1 -0
- package/dist/cli/mission.js +443 -0
- package/dist/cli/mission.js.map +1 -0
- package/dist/cli/plugin-marketplace.d.ts +1 -0
- package/dist/cli/plugin-marketplace.d.ts.map +1 -1
- package/dist/cli/plugin-marketplace.js +31 -0
- package/dist/cli/plugin-marketplace.js.map +1 -1
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +7 -4
- package/dist/cli/setup.js.map +1 -1
- package/dist/config/__tests__/generator-idempotent.test.js +23 -23
- package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
- package/dist/config/__tests__/generator-notify.test.js +9 -9
- package/dist/config/__tests__/generator-notify.test.js.map +1 -1
- package/dist/config/__tests__/models.test.js +51 -26
- package/dist/config/__tests__/models.test.js.map +1 -1
- package/dist/config/generator.d.ts +1 -1
- package/dist/config/generator.d.ts.map +1 -1
- package/dist/config/models.d.ts +16 -6
- package/dist/config/models.d.ts.map +1 -1
- package/dist/config/models.js +19 -8
- package/dist/config/models.js.map +1 -1
- package/dist/hooks/__tests__/keyword-detector.test.js +12 -4
- package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +1 -1
- package/dist/hooks/__tests__/prometheus-strict-contract.test.js +2 -2
- package/dist/hooks/__tests__/prometheus-strict-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/tmux-hook-engine.test.js +2 -2
- package/dist/hud/__tests__/state.test.js +31 -6
- package/dist/hud/__tests__/state.test.js.map +1 -1
- package/dist/hud/state.d.ts.map +1 -1
- package/dist/hud/state.js +7 -3
- package/dist/hud/state.js.map +1 -1
- package/dist/leader/__tests__/contract.test.js +108 -1
- package/dist/leader/__tests__/contract.test.js.map +1 -1
- package/dist/leader/contract.d.ts +26 -0
- package/dist/leader/contract.d.ts.map +1 -1
- package/dist/leader/contract.js +170 -0
- package/dist/leader/contract.js.map +1 -1
- package/dist/mcp/__tests__/state-paths.test.js +32 -1
- package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
- package/dist/mcp/state-paths.d.ts +4 -0
- package/dist/mcp/state-paths.d.ts.map +1 -1
- package/dist/mcp/state-paths.js +6 -15
- package/dist/mcp/state-paths.js.map +1 -1
- package/dist/modes/base.d.ts +1 -1
- package/dist/modes/base.d.ts.map +1 -1
- package/dist/pipeline/__tests__/stages.test.js +27 -1
- package/dist/pipeline/__tests__/stages.test.js.map +1 -1
- package/dist/pipeline/stages/ralplan.d.ts +2 -1
- package/dist/pipeline/stages/ralplan.d.ts.map +1 -1
- package/dist/pipeline/stages/ralplan.js +1 -0
- package/dist/pipeline/stages/ralplan.js.map +1 -1
- package/dist/pipeline/stages/ultragoal.d.ts +5 -1
- package/dist/pipeline/stages/ultragoal.d.ts.map +1 -1
- package/dist/pipeline/stages/ultragoal.js +14 -4
- package/dist/pipeline/stages/ultragoal.js.map +1 -1
- package/dist/ralplan/__tests__/consensus-gate.test.js +193 -0
- package/dist/ralplan/__tests__/consensus-gate.test.js.map +1 -1
- package/dist/ralplan/__tests__/runtime.test.js +132 -0
- package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
- package/dist/ralplan/consensus-gate.js +24 -3
- package/dist/ralplan/consensus-gate.js.map +1 -1
- package/dist/ralplan/runtime.d.ts +17 -0
- package/dist/ralplan/runtime.d.ts.map +1 -1
- package/dist/ralplan/runtime.js +97 -1
- package/dist/ralplan/runtime.js.map +1 -1
- package/dist/scripts/__tests__/codex-native-hook.test.js +762 -20
- package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
- package/dist/scripts/codex-native-hook.d.ts.map +1 -1
- package/dist/scripts/codex-native-hook.js +306 -22
- package/dist/scripts/codex-native-hook.js.map +1 -1
- package/dist/scripts/generate-catalog-docs.js +1 -0
- package/dist/scripts/generate-catalog-docs.js.map +1 -1
- package/dist/state/__tests__/operations.test.js +737 -0
- package/dist/state/__tests__/operations.test.js.map +1 -1
- package/dist/state/__tests__/skill-active.test.js +59 -0
- package/dist/state/__tests__/skill-active.test.js.map +1 -1
- package/dist/state/__tests__/workflow-transition.test.js +14 -0
- package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
- package/dist/state/operations.d.ts.map +1 -1
- package/dist/state/operations.js +80 -17
- package/dist/state/operations.js.map +1 -1
- package/dist/state/skill-active.js +3 -3
- package/dist/state/skill-active.js.map +1 -1
- package/dist/state/terminal-normalization.d.ts +8 -0
- package/dist/state/terminal-normalization.d.ts.map +1 -0
- package/dist/state/terminal-normalization.js +127 -0
- package/dist/state/terminal-normalization.js.map +1 -0
- package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
- package/dist/state/workflow-transition-reconcile.js +3 -1
- package/dist/state/workflow-transition-reconcile.js.map +1 -1
- package/dist/team/__tests__/delegation-policy.test.js +2 -2
- package/dist/team/__tests__/delegation-policy.test.js.map +1 -1
- package/dist/team/__tests__/model-contract.test.js +25 -25
- package/dist/team/__tests__/model-contract.test.js.map +1 -1
- package/dist/team/__tests__/runtime.test.js +28 -28
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/scaling.test.js +7 -7
- package/dist/team/__tests__/scaling.test.js.map +1 -1
- package/dist/team/__tests__/tmux-claude-workers-demo.test.js +5 -5
- package/dist/team/__tests__/tmux-claude-workers-demo.test.js.map +1 -1
- package/dist/team/__tests__/tmux-session.test.js +12 -12
- package/dist/team/__tests__/tmux-session.test.js.map +1 -1
- package/dist/team/__tests__/worker-bootstrap.test.js +57 -9
- package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
- package/dist/team/__tests__/worker-runtime-identity.test.js +3 -3
- package/dist/team/__tests__/worker-runtime-identity.test.js.map +1 -1
- package/dist/team/model-contract.d.ts +1 -1
- package/dist/team/model-contract.d.ts.map +1 -1
- package/dist/team/runtime.d.ts.map +1 -1
- package/dist/team/runtime.js +11 -0
- package/dist/team/runtime.js.map +1 -1
- package/dist/team/worker-bootstrap.d.ts +2 -0
- package/dist/team/worker-bootstrap.d.ts.map +1 -1
- package/dist/team/worker-bootstrap.js +6 -1
- package/dist/team/worker-bootstrap.js.map +1 -1
- package/dist/ultragoal/__tests__/artifacts.test.js +82 -5
- package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
- package/dist/ultragoal/artifacts.d.ts +5 -1
- package/dist/ultragoal/artifacts.d.ts.map +1 -1
- package/dist/ultragoal/artifacts.js +111 -14
- package/dist/ultragoal/artifacts.js.map +1 -1
- package/dist/utils/__tests__/agents-model-table.test.js +7 -7
- package/dist/utils/__tests__/agents-model-table.test.js.map +1 -1
- package/dist/utils/__tests__/worktree-tool-context.test.d.ts +2 -0
- package/dist/utils/__tests__/worktree-tool-context.test.d.ts.map +1 -0
- package/dist/utils/__tests__/worktree-tool-context.test.js +118 -0
- package/dist/utils/__tests__/worktree-tool-context.test.js.map +1 -0
- package/dist/utils/worktree-tool-context.d.ts +25 -0
- package/dist/utils/worktree-tool-context.d.ts.map +1 -0
- package/dist/utils/worktree-tool-context.js +116 -0
- package/dist/utils/worktree-tool-context.js.map +1 -0
- package/package.json +1 -1
- package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/oh-my-codex/hooks/codex-native-hook.mjs +96 -10
- package/plugins/oh-my-codex/skills/ai-slop-cleaner/SKILL.md +1 -1
- package/plugins/oh-my-codex/skills/code-review/SKILL.md +1 -1
- package/plugins/oh-my-codex/skills/omx-setup/SKILL.md +5 -5
- package/plugins/oh-my-codex/skills/prometheus-strict/SKILL.md +3 -3
- package/plugins/oh-my-codex/skills/ralplan/SKILL.md +1 -1
- package/plugins/oh-my-codex/skills/team/SKILL.md +1 -1
- package/plugins/oh-my-codex/skills/ultrawork/references/agent-tiers.md +2 -2
- package/prompts/prometheus-strict-metis.md +1 -1
- package/skills/ai-slop-cleaner/SKILL.md +1 -1
- package/skills/code-review/SKILL.md +1 -1
- package/skills/ecomode/references/agent-tiers.md +1 -1
- package/skills/omx-setup/SKILL.md +5 -5
- package/skills/prometheus-strict/SKILL.md +3 -3
- package/skills/ralplan/SKILL.md +1 -1
- package/skills/team/SKILL.md +1 -1
- package/skills/ultrawork/references/agent-tiers.md +2 -2
- package/src/scripts/__tests__/codex-native-hook.test.ts +824 -7
- package/src/scripts/codex-native-hook.ts +347 -20
- package/src/scripts/generate-catalog-docs.ts +1 -0
|
@@ -1479,6 +1479,166 @@ PY`,
|
|
|
1479
1479
|
await rm(cwd, { recursive: true, force: true });
|
|
1480
1480
|
}
|
|
1481
1481
|
});
|
|
1482
|
+
it("adds resume-by-id instructions for persisted subagents on SessionStart resume", async () => {
|
|
1483
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-reopen-"));
|
|
1484
|
+
try {
|
|
1485
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
1486
|
+
const sessionId = "omx-reopen-session";
|
|
1487
|
+
await writeSessionStart(cwd, sessionId, { nativeSessionId: "codex-leader-reopen", pid: process.pid });
|
|
1488
|
+
await writeJson(join(stateDir, "subagent-tracking.json"), {
|
|
1489
|
+
schemaVersion: 1,
|
|
1490
|
+
sessions: {
|
|
1491
|
+
[sessionId]: {
|
|
1492
|
+
session_id: sessionId,
|
|
1493
|
+
leader_thread_id: "codex-leader-reopen",
|
|
1494
|
+
updated_at: "2026-07-09T00:00:00.000Z",
|
|
1495
|
+
threads: {
|
|
1496
|
+
"codex-leader-reopen": {
|
|
1497
|
+
thread_id: "codex-leader-reopen",
|
|
1498
|
+
kind: "leader",
|
|
1499
|
+
first_seen_at: "2026-07-09T00:00:00.000Z",
|
|
1500
|
+
last_seen_at: "2026-07-09T00:00:00.000Z",
|
|
1501
|
+
turn_count: 1,
|
|
1502
|
+
},
|
|
1503
|
+
"thread-architect-reopen": {
|
|
1504
|
+
thread_id: "thread-architect-reopen",
|
|
1505
|
+
kind: "subagent",
|
|
1506
|
+
first_seen_at: "2026-07-09T00:01:00.000Z",
|
|
1507
|
+
last_seen_at: "2026-07-09T00:01:00.000Z",
|
|
1508
|
+
turn_count: 2,
|
|
1509
|
+
role: "architect",
|
|
1510
|
+
lane_id: "plan-review",
|
|
1511
|
+
scope: "SessionStart reopen",
|
|
1512
|
+
status: "available",
|
|
1513
|
+
last_handoff_summary: "reviewed the restart plan",
|
|
1514
|
+
},
|
|
1515
|
+
},
|
|
1516
|
+
},
|
|
1517
|
+
},
|
|
1518
|
+
});
|
|
1519
|
+
const result = await dispatchCodexNativeHook({
|
|
1520
|
+
hook_event_name: "SessionStart",
|
|
1521
|
+
cwd,
|
|
1522
|
+
session_id: "codex-leader-reopen",
|
|
1523
|
+
source: "resume",
|
|
1524
|
+
}, { cwd, sessionOwnerPid: process.pid });
|
|
1525
|
+
const additionalContext = String(result.outputJson?.hookSpecificOutput?.additionalContext ?? "");
|
|
1526
|
+
assert.match(additionalContext, /\[Persisted subagent reopen\]/);
|
|
1527
|
+
assert.match(additionalContext, /resume_agent\("thread-architect-reopen"\)/);
|
|
1528
|
+
assert.match(additionalContext, /role: architect; lane: plan-review; scope: SessionStart reopen; status: available/);
|
|
1529
|
+
assert.match(additionalContext, /saved subagent ids found: 1/);
|
|
1530
|
+
const tracking = JSON.parse(await readFile(join(stateDir, "subagent-tracking.json"), "utf-8"));
|
|
1531
|
+
assert.match(tracking.sessions?.[sessionId]?.threads?.["thread-architect-reopen"]?.resume_requested_at ?? "", /^\d{4}-\d{2}-\d{2}T/);
|
|
1532
|
+
}
|
|
1533
|
+
finally {
|
|
1534
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1535
|
+
}
|
|
1536
|
+
});
|
|
1537
|
+
it("does not suggest duplicate same-role subagent spawns when reopen ids exist", async () => {
|
|
1538
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-no-duplicates-"));
|
|
1539
|
+
try {
|
|
1540
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
1541
|
+
const sessionId = "omx-reuse-session";
|
|
1542
|
+
await writeSessionStart(cwd, sessionId, { nativeSessionId: "codex-leader-reuse", pid: process.pid });
|
|
1543
|
+
await writeJson(join(stateDir, "subagent-tracking.json"), {
|
|
1544
|
+
schemaVersion: 1,
|
|
1545
|
+
sessions: {
|
|
1546
|
+
[sessionId]: {
|
|
1547
|
+
session_id: sessionId,
|
|
1548
|
+
leader_thread_id: "codex-leader-reuse",
|
|
1549
|
+
updated_at: "2026-07-09T00:00:00.000Z",
|
|
1550
|
+
threads: {
|
|
1551
|
+
"codex-leader-reuse": {
|
|
1552
|
+
thread_id: "codex-leader-reuse",
|
|
1553
|
+
kind: "leader",
|
|
1554
|
+
first_seen_at: "2026-07-09T00:00:00.000Z",
|
|
1555
|
+
last_seen_at: "2026-07-09T00:00:00.000Z",
|
|
1556
|
+
turn_count: 1,
|
|
1557
|
+
},
|
|
1558
|
+
"thread-critic-reuse": {
|
|
1559
|
+
thread_id: "thread-critic-reuse",
|
|
1560
|
+
kind: "subagent",
|
|
1561
|
+
first_seen_at: "2026-07-09T00:01:00.000Z",
|
|
1562
|
+
last_seen_at: "2026-07-09T00:01:00.000Z",
|
|
1563
|
+
turn_count: 1,
|
|
1564
|
+
role: "critic",
|
|
1565
|
+
lane_id: "risk-review",
|
|
1566
|
+
status: "closed",
|
|
1567
|
+
},
|
|
1568
|
+
},
|
|
1569
|
+
},
|
|
1570
|
+
},
|
|
1571
|
+
});
|
|
1572
|
+
const result = await dispatchCodexNativeHook({
|
|
1573
|
+
hook_event_name: "SessionStart",
|
|
1574
|
+
cwd,
|
|
1575
|
+
session_id: "codex-leader-reuse",
|
|
1576
|
+
source: "startup",
|
|
1577
|
+
}, { cwd, sessionOwnerPid: process.pid });
|
|
1578
|
+
const additionalContext = String(result.outputJson?.hookSpecificOutput?.additionalContext ?? "");
|
|
1579
|
+
assert.match(additionalContext, /resume_agent\("thread-critic-reuse"\)/);
|
|
1580
|
+
assert.match(additionalContext, /avoid duplicate same-type subagent spawns/);
|
|
1581
|
+
assert.match(additionalContext, /do not spawn a new agent solely because reopen failed/);
|
|
1582
|
+
assert.doesNotMatch(additionalContext, /spawn.*critic.*replacement/i);
|
|
1583
|
+
}
|
|
1584
|
+
finally {
|
|
1585
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1586
|
+
}
|
|
1587
|
+
});
|
|
1588
|
+
it("surfaces clear warnings for unavailable persisted subagents without spawning replacements", async () => {
|
|
1589
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-warning-"));
|
|
1590
|
+
try {
|
|
1591
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
1592
|
+
const sessionId = "omx-warning-session";
|
|
1593
|
+
await writeSessionStart(cwd, sessionId, { nativeSessionId: "codex-leader-warning", pid: process.pid });
|
|
1594
|
+
await writeJson(join(stateDir, "subagent-tracking.json"), {
|
|
1595
|
+
schemaVersion: 1,
|
|
1596
|
+
sessions: {
|
|
1597
|
+
[sessionId]: {
|
|
1598
|
+
session_id: sessionId,
|
|
1599
|
+
leader_thread_id: "codex-leader-warning",
|
|
1600
|
+
updated_at: "2026-07-09T00:00:00.000Z",
|
|
1601
|
+
threads: {
|
|
1602
|
+
"codex-leader-warning": {
|
|
1603
|
+
thread_id: "codex-leader-warning",
|
|
1604
|
+
kind: "leader",
|
|
1605
|
+
first_seen_at: "2026-07-09T00:00:00.000Z",
|
|
1606
|
+
last_seen_at: "2026-07-09T00:00:00.000Z",
|
|
1607
|
+
turn_count: 1,
|
|
1608
|
+
},
|
|
1609
|
+
"thread-executor-unavailable": {
|
|
1610
|
+
thread_id: "thread-executor-unavailable",
|
|
1611
|
+
kind: "subagent",
|
|
1612
|
+
first_seen_at: "2026-07-09T00:01:00.000Z",
|
|
1613
|
+
last_seen_at: "2026-07-09T00:01:00.000Z",
|
|
1614
|
+
turn_count: 1,
|
|
1615
|
+
role: "executor",
|
|
1616
|
+
lane_id: "implementation",
|
|
1617
|
+
status: "unavailable",
|
|
1618
|
+
resume_failed_at: "2026-07-09T00:02:00.000Z",
|
|
1619
|
+
resume_failure_reason: "Codex reported missing thread id",
|
|
1620
|
+
},
|
|
1621
|
+
},
|
|
1622
|
+
},
|
|
1623
|
+
},
|
|
1624
|
+
});
|
|
1625
|
+
const result = await dispatchCodexNativeHook({
|
|
1626
|
+
hook_event_name: "SessionStart",
|
|
1627
|
+
cwd,
|
|
1628
|
+
session_id: "codex-leader-warning",
|
|
1629
|
+
source: "resume",
|
|
1630
|
+
}, { cwd, sessionOwnerPid: process.pid });
|
|
1631
|
+
const additionalContext = String(result.outputJson?.hookSpecificOutput?.additionalContext ?? "");
|
|
1632
|
+
assert.match(additionalContext, /No compatible saved subagent id is currently marked reopenable/);
|
|
1633
|
+
assert.match(additionalContext, /Reopen warnings:/);
|
|
1634
|
+
assert.match(additionalContext, /thread-executor-unavailable/);
|
|
1635
|
+
assert.match(additionalContext, /last failure: Codex reported missing thread id/);
|
|
1636
|
+
assert.doesNotMatch(additionalContext, /resume_agent\("thread-executor-unavailable"\)/);
|
|
1637
|
+
}
|
|
1638
|
+
finally {
|
|
1639
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1640
|
+
}
|
|
1641
|
+
});
|
|
1482
1642
|
it("preserves canonical OMX session scope when native SessionStart arrives with a different id", async () => {
|
|
1483
1643
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-session-reconcile-"));
|
|
1484
1644
|
try {
|
|
@@ -4150,6 +4310,66 @@ standardMaxRounds = 15
|
|
|
4150
4310
|
await rm(cwd, { recursive: true, force: true });
|
|
4151
4311
|
}
|
|
4152
4312
|
});
|
|
4313
|
+
it("keeps conductor guidance on autopilot activation after capacity-only native subagent evidence", async () => {
|
|
4314
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-capacity-native-"));
|
|
4315
|
+
try {
|
|
4316
|
+
await mkdir(join(cwd, ".omx", "state"), { recursive: true });
|
|
4317
|
+
await dispatchCodexNativeHook({
|
|
4318
|
+
hook_event_name: "PostToolUse",
|
|
4319
|
+
cwd,
|
|
4320
|
+
session_id: "sess-autopilot-capacity-native",
|
|
4321
|
+
thread_id: "thread-autopilot-capacity-native",
|
|
4322
|
+
turn_id: "turn-autopilot-capacity-native-spawn",
|
|
4323
|
+
tool_name: "multi_agent_v1.spawn_agent",
|
|
4324
|
+
tool_response: { error: "collab spawn failed: agent thread limit reached" },
|
|
4325
|
+
}, { cwd });
|
|
4326
|
+
const result = await dispatchCodexNativeHook({
|
|
4327
|
+
hook_event_name: "UserPromptSubmit",
|
|
4328
|
+
cwd,
|
|
4329
|
+
session_id: "sess-autopilot-capacity-native",
|
|
4330
|
+
thread_id: "thread-autopilot-capacity-native",
|
|
4331
|
+
turn_id: "turn-autopilot-capacity-native-prompt",
|
|
4332
|
+
prompt: "$autopilot implement issue #3078",
|
|
4333
|
+
}, { cwd });
|
|
4334
|
+
assert.equal(result.omxEventName, "keyword-detector");
|
|
4335
|
+
assert.equal(result.skillState?.skill, "autopilot");
|
|
4336
|
+
const message = String(result.outputJson?.hookSpecificOutput?.additionalContext || "");
|
|
4337
|
+
assert.match(message, /Autopilot protocol:/);
|
|
4338
|
+
assert.match(message, /Conductor mode contract:/);
|
|
4339
|
+
assert.match(message, /Golden Rule: When the Main agent is acting in Conductor mode/);
|
|
4340
|
+
assert.match(message, /Conductor reuse and ledger guidance:/);
|
|
4341
|
+
assert.doesNotMatch(message, /Native subagent support is unavailable in this environment/);
|
|
4342
|
+
assert.doesNotMatch(message, /Reason: agent_thread_limit_reached/);
|
|
4343
|
+
}
|
|
4344
|
+
finally {
|
|
4345
|
+
await rm(cwd, { recursive: true, force: true });
|
|
4346
|
+
}
|
|
4347
|
+
});
|
|
4348
|
+
it("omits conductor block and emits unsupported native guidance for unsupported autopilot first-run payload", async () => {
|
|
4349
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-unsupported-native-"));
|
|
4350
|
+
try {
|
|
4351
|
+
await mkdir(join(cwd, ".omx", "state"), { recursive: true });
|
|
4352
|
+
const result = await dispatchCodexNativeHook({
|
|
4353
|
+
hook_event_name: "UserPromptSubmit",
|
|
4354
|
+
cwd,
|
|
4355
|
+
session_id: "sess-autopilot-unsupported-native",
|
|
4356
|
+
thread_id: "thread-autopilot-unsupported-native",
|
|
4357
|
+
turn_id: "turn-autopilot-unsupported-native",
|
|
4358
|
+
prompt: "$autopilot implement issue #3078",
|
|
4359
|
+
capabilities: { native_subagents: false, multi_agent_v1: false },
|
|
4360
|
+
}, { cwd });
|
|
4361
|
+
const message = String(result.outputJson?.hookSpecificOutput?.additionalContext || "");
|
|
4362
|
+
assert.match(message, /Autopilot protocol:/);
|
|
4363
|
+
assert.doesNotMatch(message, /Conductor mode contract:/);
|
|
4364
|
+
assert.doesNotMatch(message, /Conductor reuse and ledger guidance:/);
|
|
4365
|
+
assert.match(message, /Native subagent support is unavailable in this environment/);
|
|
4366
|
+
assert.match(message, /Reason: native_subagents_unsupported/);
|
|
4367
|
+
assert.match(message, /Do not call multi_agent_v1\.close_agent/);
|
|
4368
|
+
}
|
|
4369
|
+
finally {
|
|
4370
|
+
await rm(cwd, { recursive: true, force: true });
|
|
4371
|
+
}
|
|
4372
|
+
});
|
|
4153
4373
|
it("records ultragoal prompt skill activation with goal-tool handoff guidance", async () => {
|
|
4154
4374
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-"));
|
|
4155
4375
|
try {
|
|
@@ -6035,6 +6255,71 @@ exit 0
|
|
|
6035
6255
|
await rm(cwd, { recursive: true, force: true });
|
|
6036
6256
|
}
|
|
6037
6257
|
});
|
|
6258
|
+
it("does not self-lock PreToolUse after completed deep-interview handoff leaves Autopilot in deep-interview phase", async () => {
|
|
6259
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-deep-interview-completed-autopilot-"));
|
|
6260
|
+
try {
|
|
6261
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
6262
|
+
const sessionId = "sess-di-completed-autopilot";
|
|
6263
|
+
const threadId = "thread-di-completed-autopilot";
|
|
6264
|
+
const sessionDir = join(stateDir, "sessions", sessionId);
|
|
6265
|
+
await mkdir(sessionDir, { recursive: true });
|
|
6266
|
+
await writeJson(join(stateDir, "session.json"), { session_id: sessionId, cwd });
|
|
6267
|
+
await writeJson(join(sessionDir, "skill-active-state.json"), {
|
|
6268
|
+
version: 1,
|
|
6269
|
+
active: true,
|
|
6270
|
+
skill: "autopilot",
|
|
6271
|
+
keyword: "$autopilot",
|
|
6272
|
+
phase: "deep-interview",
|
|
6273
|
+
initialized_mode: "autopilot",
|
|
6274
|
+
initialized_state_path: `.omx/state/sessions/${sessionId}/autopilot-state.json`,
|
|
6275
|
+
session_id: sessionId,
|
|
6276
|
+
thread_id: threadId,
|
|
6277
|
+
active_skills: [{ skill: "autopilot", phase: "deep-interview", active: true, session_id: sessionId, thread_id: threadId }],
|
|
6278
|
+
});
|
|
6279
|
+
await writeJson(join(sessionDir, "deep-interview-state.json"), {
|
|
6280
|
+
active: false,
|
|
6281
|
+
mode: "deep-interview",
|
|
6282
|
+
current_phase: "completed",
|
|
6283
|
+
session_id: sessionId,
|
|
6284
|
+
thread_id: threadId,
|
|
6285
|
+
deep_interview_gate: {
|
|
6286
|
+
status: "complete",
|
|
6287
|
+
handoff_summary: "Requirements were clarified before the Autopilot handoff.",
|
|
6288
|
+
},
|
|
6289
|
+
});
|
|
6290
|
+
await writeJson(join(sessionDir, "autopilot-state.json"), {
|
|
6291
|
+
active: true,
|
|
6292
|
+
mode: "autopilot",
|
|
6293
|
+
current_phase: "deep-interview",
|
|
6294
|
+
session_id: sessionId,
|
|
6295
|
+
thread_id: threadId,
|
|
6296
|
+
});
|
|
6297
|
+
const implementationEdit = await dispatchCodexNativeHook({
|
|
6298
|
+
hook_event_name: "PreToolUse",
|
|
6299
|
+
cwd,
|
|
6300
|
+
session_id: sessionId,
|
|
6301
|
+
thread_id: threadId,
|
|
6302
|
+
tool_name: "Edit",
|
|
6303
|
+
tool_use_id: "tool-di-completed-autopilot-edit",
|
|
6304
|
+
tool_input: { file_path: "src/runtime.ts", old_string: "a", new_string: "b" },
|
|
6305
|
+
}, { cwd });
|
|
6306
|
+
assert.equal(implementationEdit.omxEventName, "pre-tool-use");
|
|
6307
|
+
assert.equal(implementationEdit.outputJson, null);
|
|
6308
|
+
const stateRepair = await dispatchCodexNativeHook({
|
|
6309
|
+
hook_event_name: "PreToolUse",
|
|
6310
|
+
cwd,
|
|
6311
|
+
session_id: sessionId,
|
|
6312
|
+
thread_id: threadId,
|
|
6313
|
+
tool_name: "mcp__omx_state__state_write",
|
|
6314
|
+
tool_use_id: "tool-di-completed-autopilot-state-repair",
|
|
6315
|
+
tool_input: { mode: "autopilot", current_phase: "ralplan", active: true },
|
|
6316
|
+
}, { cwd });
|
|
6317
|
+
assert.equal(stateRepair.outputJson, null);
|
|
6318
|
+
}
|
|
6319
|
+
finally {
|
|
6320
|
+
await rm(cwd, { recursive: true, force: true });
|
|
6321
|
+
}
|
|
6322
|
+
});
|
|
6038
6323
|
it("allows deep-interview artifact and state writes while blocking implementation Bash writes", async () => {
|
|
6039
6324
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-deep-interview-artifact-"));
|
|
6040
6325
|
try {
|
|
@@ -6457,6 +6742,69 @@ exit 0
|
|
|
6457
6742
|
}, { cwd });
|
|
6458
6743
|
assert.equal(protectedWrite.outputJson?.decision, "block", `${filePath} should not be model-writable during deep-interview`);
|
|
6459
6744
|
}
|
|
6745
|
+
const runtimeRoot = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-deep-interview-runtime-root-"));
|
|
6746
|
+
const previousOmxRoot = process.env.OMX_ROOT;
|
|
6747
|
+
const runtimeSessionId = "sess-di-runtime-artifact";
|
|
6748
|
+
const runtimeStateDir = join(runtimeRoot, ".omx", "state");
|
|
6749
|
+
const runtimeSessionDir = join(runtimeStateDir, "sessions", runtimeSessionId);
|
|
6750
|
+
try {
|
|
6751
|
+
process.env.OMX_ROOT = runtimeRoot;
|
|
6752
|
+
await mkdir(runtimeSessionDir, { recursive: true });
|
|
6753
|
+
await writeJson(join(runtimeStateDir, "session.json"), { session_id: runtimeSessionId, cwd });
|
|
6754
|
+
await writeJson(join(runtimeSessionDir, "skill-active-state.json"), {
|
|
6755
|
+
version: 1,
|
|
6756
|
+
active: true,
|
|
6757
|
+
skill: "deep-interview",
|
|
6758
|
+
phase: "planning",
|
|
6759
|
+
session_id: runtimeSessionId,
|
|
6760
|
+
thread_id: threadId,
|
|
6761
|
+
active_skills: [{ skill: "deep-interview", phase: "planning", active: true, session_id: runtimeSessionId, thread_id: threadId }],
|
|
6762
|
+
});
|
|
6763
|
+
await writeJson(join(runtimeSessionDir, "deep-interview-state.json"), {
|
|
6764
|
+
active: true,
|
|
6765
|
+
mode: "deep-interview",
|
|
6766
|
+
current_phase: "intent-first",
|
|
6767
|
+
session_id: runtimeSessionId,
|
|
6768
|
+
thread_id: threadId,
|
|
6769
|
+
});
|
|
6770
|
+
const allowedRuntimeStateWrite = await dispatchCodexNativeHook({
|
|
6771
|
+
hook_event_name: "PreToolUse",
|
|
6772
|
+
cwd,
|
|
6773
|
+
session_id: runtimeSessionId,
|
|
6774
|
+
thread_id: threadId,
|
|
6775
|
+
tool_name: "Write",
|
|
6776
|
+
tool_use_id: "tool-di-runtime-state-write",
|
|
6777
|
+
tool_input: { file_path: join(runtimeSessionDir, "deep-interview-state.json"), content: "{}\n" },
|
|
6778
|
+
}, { cwd });
|
|
6779
|
+
assert.equal(allowedRuntimeStateWrite.outputJson, null);
|
|
6780
|
+
const blockedRuntimeStatePeerWrite = await dispatchCodexNativeHook({
|
|
6781
|
+
hook_event_name: "PreToolUse",
|
|
6782
|
+
cwd,
|
|
6783
|
+
session_id: runtimeSessionId,
|
|
6784
|
+
thread_id: threadId,
|
|
6785
|
+
tool_name: "Write",
|
|
6786
|
+
tool_use_id: "tool-di-runtime-peer-state-write",
|
|
6787
|
+
tool_input: { file_path: join(runtimeRoot, ".omx", "state", "sessions", "../outside", "deep-interview-state.json"), content: "{}\n" },
|
|
6788
|
+
}, { cwd });
|
|
6789
|
+
assert.equal(blockedRuntimeStatePeerWrite.outputJson?.decision, "block");
|
|
6790
|
+
const blockedRuntimeUnrelatedWrite = await dispatchCodexNativeHook({
|
|
6791
|
+
hook_event_name: "PreToolUse",
|
|
6792
|
+
cwd,
|
|
6793
|
+
session_id: runtimeSessionId,
|
|
6794
|
+
thread_id: threadId,
|
|
6795
|
+
tool_name: "Write",
|
|
6796
|
+
tool_use_id: "tool-di-runtime-unrelated-write",
|
|
6797
|
+
tool_input: { file_path: join(runtimeRoot, "unrelated", "deep-interview-state.json"), content: "{}\n" },
|
|
6798
|
+
}, { cwd });
|
|
6799
|
+
assert.equal(blockedRuntimeUnrelatedWrite.outputJson?.decision, "block");
|
|
6800
|
+
}
|
|
6801
|
+
finally {
|
|
6802
|
+
if (typeof previousOmxRoot === "string")
|
|
6803
|
+
process.env.OMX_ROOT = previousOmxRoot;
|
|
6804
|
+
else
|
|
6805
|
+
delete process.env.OMX_ROOT;
|
|
6806
|
+
await rm(runtimeRoot, { recursive: true, force: true });
|
|
6807
|
+
}
|
|
6460
6808
|
// Cross-mode non-terminal `omx state write` payloads are activations,
|
|
6461
6809
|
// because state_write normalizes them to active=true after the hook.
|
|
6462
6810
|
const blockedStateCliMutation = await preToolUse({
|
|
@@ -9306,6 +9654,32 @@ PY`,
|
|
|
9306
9654
|
assert.equal(blocker.tool_name, "multi_agent_v1.spawn_agent");
|
|
9307
9655
|
assert.match(String(blocker.error_summary), /agent thread limit reached/);
|
|
9308
9656
|
assert.ok(Date.parse(String(blocker.expires_at)) > Date.parse(String(blocker.observed_at)));
|
|
9657
|
+
assert.equal(existsSync(join(cwd, ".omx", "state", "native-subagent-support.json")), false);
|
|
9658
|
+
}
|
|
9659
|
+
finally {
|
|
9660
|
+
await rm(cwd, { recursive: true, force: true });
|
|
9661
|
+
}
|
|
9662
|
+
});
|
|
9663
|
+
it("records unsupported native subagent support blocker from spawn_agent PostToolUse output", async () => {
|
|
9664
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-support-record-"));
|
|
9665
|
+
try {
|
|
9666
|
+
const result = await dispatchCodexNativeHook({
|
|
9667
|
+
hook_event_name: "PostToolUse",
|
|
9668
|
+
cwd,
|
|
9669
|
+
session_id: "sess-subagent-support-record",
|
|
9670
|
+
thread_id: "thread-subagent-support-record",
|
|
9671
|
+
turn_id: "turn-subagent-support-record",
|
|
9672
|
+
tool_name: "multi_agent_v1.spawn_agent",
|
|
9673
|
+
tool_response: { error: "unknown tool: multi_agent_v1.spawn_agent is unavailable" },
|
|
9674
|
+
}, { cwd });
|
|
9675
|
+
assert.equal(result.omxEventName, "post-tool-use");
|
|
9676
|
+
const blocker = JSON.parse(await readFile(join(cwd, ".omx", "state", "native-subagent-support.json"), "utf-8"));
|
|
9677
|
+
assert.equal(blocker.status, "unsupported");
|
|
9678
|
+
assert.equal(blocker.reason, "multi_agent_v1_unavailable");
|
|
9679
|
+
assert.equal(blocker.session_id, "sess-subagent-support-record");
|
|
9680
|
+
assert.equal(blocker.thread_id, "thread-subagent-support-record");
|
|
9681
|
+
assert.equal(blocker.tool_name, "multi_agent_v1.spawn_agent");
|
|
9682
|
+
assert.match(String(blocker.evidence), /unknown tool/);
|
|
9309
9683
|
}
|
|
9310
9684
|
finally {
|
|
9311
9685
|
await rm(cwd, { recursive: true, force: true });
|
|
@@ -13406,13 +13780,14 @@ PY`,
|
|
|
13406
13780
|
await rm(cwd, { recursive: true, force: true });
|
|
13407
13781
|
}
|
|
13408
13782
|
});
|
|
13409
|
-
it("
|
|
13410
|
-
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ralplan-
|
|
13783
|
+
it("clears stale ralplan Stop cache when authoritative state is terminal inactive", async () => {
|
|
13784
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ralplan-terminal-cache-"));
|
|
13411
13785
|
try {
|
|
13412
13786
|
const stateDir = join(cwd, ".omx", "state");
|
|
13413
|
-
const sessionId = "sess-stop-ralplan-
|
|
13787
|
+
const sessionId = "sess-stop-ralplan-terminal-cache";
|
|
13788
|
+
const threadId = "thread-stop-ralplan-terminal-cache";
|
|
13414
13789
|
await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
|
|
13415
|
-
await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
|
|
13790
|
+
await writeJson(join(stateDir, "session.json"), { session_id: sessionId, cwd });
|
|
13416
13791
|
await writeJson(join(stateDir, "skill-active-state.json"), {
|
|
13417
13792
|
active: false,
|
|
13418
13793
|
skill: "ralplan",
|
|
@@ -13424,52 +13799,119 @@ PY`,
|
|
|
13424
13799
|
active: false,
|
|
13425
13800
|
mode: "ralplan",
|
|
13426
13801
|
current_phase: "complete",
|
|
13802
|
+
status: "complete",
|
|
13803
|
+
run_outcome: "complete",
|
|
13427
13804
|
session_id: sessionId,
|
|
13805
|
+
cwd,
|
|
13428
13806
|
});
|
|
13429
13807
|
await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
|
|
13430
|
-
active:
|
|
13808
|
+
active: false,
|
|
13431
13809
|
skill: "ralplan",
|
|
13432
|
-
phase: "
|
|
13810
|
+
phase: "complete",
|
|
13433
13811
|
session_id: sessionId,
|
|
13434
|
-
active_skills: [
|
|
13435
|
-
skill: "ralplan",
|
|
13436
|
-
phase: "planning",
|
|
13437
|
-
active: true,
|
|
13438
|
-
session_id: sessionId,
|
|
13439
|
-
}],
|
|
13812
|
+
active_skills: [],
|
|
13440
13813
|
});
|
|
13441
13814
|
await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), {
|
|
13442
|
-
active:
|
|
13815
|
+
active: false,
|
|
13443
13816
|
mode: "ralplan",
|
|
13444
|
-
current_phase: "
|
|
13817
|
+
current_phase: "complete",
|
|
13818
|
+
status: "complete",
|
|
13819
|
+
run_outcome: "complete",
|
|
13445
13820
|
session_id: sessionId,
|
|
13446
13821
|
cwd,
|
|
13447
13822
|
});
|
|
13823
|
+
await writeJson(join(stateDir, "native-stop-state.json"), {
|
|
13824
|
+
sessions: {
|
|
13825
|
+
[sessionId]: {
|
|
13826
|
+
last_signature: `skill-stop|${sessionId}|${threadId}|no-message|skill_ralplan_planning_continue_artifact`,
|
|
13827
|
+
updated_at: "2026-07-04T00:00:00.000Z",
|
|
13828
|
+
},
|
|
13829
|
+
[threadId]: {
|
|
13830
|
+
last_signature: `skill-stop|${sessionId}|${threadId}|no-message|skill_ralplan_planning_continue_artifact`,
|
|
13831
|
+
updated_at: "2026-07-04T00:00:00.000Z",
|
|
13832
|
+
},
|
|
13833
|
+
},
|
|
13834
|
+
});
|
|
13448
13835
|
const result = await dispatchCodexNativeHook({
|
|
13449
13836
|
hook_event_name: "Stop",
|
|
13450
13837
|
cwd,
|
|
13451
13838
|
session_id: sessionId,
|
|
13839
|
+
thread_id: threadId,
|
|
13840
|
+
stop_hook_active: true,
|
|
13452
13841
|
}, { cwd });
|
|
13453
13842
|
assert.equal(result.omxEventName, "stop");
|
|
13454
|
-
assert.equal(result.outputJson
|
|
13455
|
-
|
|
13843
|
+
assert.equal(result.outputJson, null);
|
|
13844
|
+
const stopState = JSON.parse(await readFile(join(stateDir, "native-stop-state.json"), "utf-8"));
|
|
13845
|
+
assert.deepEqual(stopState.sessions, {});
|
|
13456
13846
|
}
|
|
13457
13847
|
finally {
|
|
13458
13848
|
await rm(cwd, { recursive: true, force: true });
|
|
13459
13849
|
}
|
|
13460
13850
|
});
|
|
13461
|
-
it("keeps blocking current session ralplan when root inactive ralplan state
|
|
13462
|
-
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-
|
|
13851
|
+
it("keeps blocking current session ralplan when canonical root inactive ralplan state lacks project context", async () => {
|
|
13852
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ralplan-canonical-root-no-cwd-"));
|
|
13463
13853
|
try {
|
|
13464
13854
|
const stateDir = join(cwd, ".omx", "state");
|
|
13465
|
-
const sessionId = "sess-stop-
|
|
13855
|
+
const sessionId = "sess-stop-ralplan-canonical-root-no-cwd";
|
|
13466
13856
|
await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
|
|
13467
13857
|
await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
|
|
13468
13858
|
await writeJson(join(stateDir, "skill-active-state.json"), {
|
|
13469
13859
|
active: false,
|
|
13470
13860
|
skill: "ralplan",
|
|
13471
13861
|
phase: "complete",
|
|
13472
|
-
session_id:
|
|
13862
|
+
session_id: sessionId,
|
|
13863
|
+
active_skills: [],
|
|
13864
|
+
});
|
|
13865
|
+
await writeJson(join(stateDir, "ralplan-state.json"), {
|
|
13866
|
+
active: false,
|
|
13867
|
+
mode: "ralplan",
|
|
13868
|
+
current_phase: "complete",
|
|
13869
|
+
session_id: sessionId,
|
|
13870
|
+
});
|
|
13871
|
+
await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
|
|
13872
|
+
active: true,
|
|
13873
|
+
skill: "ralplan",
|
|
13874
|
+
phase: "planning",
|
|
13875
|
+
session_id: sessionId,
|
|
13876
|
+
active_skills: [{
|
|
13877
|
+
skill: "ralplan",
|
|
13878
|
+
phase: "planning",
|
|
13879
|
+
active: true,
|
|
13880
|
+
session_id: sessionId,
|
|
13881
|
+
}],
|
|
13882
|
+
});
|
|
13883
|
+
await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), {
|
|
13884
|
+
active: true,
|
|
13885
|
+
mode: "ralplan",
|
|
13886
|
+
current_phase: "planning",
|
|
13887
|
+
session_id: sessionId,
|
|
13888
|
+
cwd,
|
|
13889
|
+
});
|
|
13890
|
+
const result = await dispatchCodexNativeHook({
|
|
13891
|
+
hook_event_name: "Stop",
|
|
13892
|
+
cwd,
|
|
13893
|
+
session_id: sessionId,
|
|
13894
|
+
}, { cwd });
|
|
13895
|
+
assert.equal(result.omxEventName, "stop");
|
|
13896
|
+
assert.equal(result.outputJson?.decision, "block");
|
|
13897
|
+
assert.equal(result.outputJson?.stopReason, "skill_ralplan_planning_continue_artifact");
|
|
13898
|
+
}
|
|
13899
|
+
finally {
|
|
13900
|
+
await rm(cwd, { recursive: true, force: true });
|
|
13901
|
+
}
|
|
13902
|
+
});
|
|
13903
|
+
it("keeps blocking current session ralplan when root inactive ralplan state belongs to another session", async () => {
|
|
13904
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-session-ralplan-root-other-session-"));
|
|
13905
|
+
try {
|
|
13906
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
13907
|
+
const sessionId = "sess-stop-current-active-ralplan";
|
|
13908
|
+
await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
|
|
13909
|
+
await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
|
|
13910
|
+
await writeJson(join(stateDir, "skill-active-state.json"), {
|
|
13911
|
+
active: false,
|
|
13912
|
+
skill: "ralplan",
|
|
13913
|
+
phase: "complete",
|
|
13914
|
+
session_id: "sess-stop-old-ralplan",
|
|
13473
13915
|
active_skills: [],
|
|
13474
13916
|
});
|
|
13475
13917
|
await writeJson(join(stateDir, "ralplan-state.json"), {
|
|
@@ -17635,6 +18077,50 @@ PY`,
|
|
|
17635
18077
|
await rm(cwd, { recursive: true, force: true });
|
|
17636
18078
|
}
|
|
17637
18079
|
});
|
|
18080
|
+
it("blocks unsupported active conductor source edits with native delegation recovery guidance", async () => {
|
|
18081
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-unsupported-conductor-source-"));
|
|
18082
|
+
try {
|
|
18083
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
18084
|
+
const sessionId = "sess-unsupported-conductor-source";
|
|
18085
|
+
await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
|
|
18086
|
+
await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
|
|
18087
|
+
await writeSessionSkillActiveState(stateDir, sessionId, "ultragoal", "planning");
|
|
18088
|
+
await writeJson(join(stateDir, "sessions", sessionId, "ultragoal-state.json"), {
|
|
18089
|
+
active: true,
|
|
18090
|
+
mode: "ultragoal",
|
|
18091
|
+
current_phase: "planning",
|
|
18092
|
+
session_id: sessionId,
|
|
18093
|
+
});
|
|
18094
|
+
await writeJson(join(stateDir, "native-subagent-support.json"), {
|
|
18095
|
+
schema_version: 1,
|
|
18096
|
+
status: "unsupported",
|
|
18097
|
+
reason: "multi_agent_v1_unavailable",
|
|
18098
|
+
session_id: sessionId,
|
|
18099
|
+
evidence: "unknown tool: multi_agent_v1.spawn_agent",
|
|
18100
|
+
observed_at: new Date().toISOString(),
|
|
18101
|
+
cwd,
|
|
18102
|
+
});
|
|
18103
|
+
const result = await dispatchCodexNativeHook({
|
|
18104
|
+
hook_event_name: "PreToolUse",
|
|
18105
|
+
cwd,
|
|
18106
|
+
session_id: sessionId,
|
|
18107
|
+
thread_id: "thread-unsupported-conductor-source",
|
|
18108
|
+
tool_name: "Write",
|
|
18109
|
+
tool_input: { file_path: "src/runtime.ts", content: "export const value = 1;\n" },
|
|
18110
|
+
}, { cwd });
|
|
18111
|
+
const output = result.outputJson;
|
|
18112
|
+
const context = String(output?.hookSpecificOutput?.additionalContext ?? "");
|
|
18113
|
+
assert.equal(output?.decision, "block");
|
|
18114
|
+
assert.match(String(result.outputJson?.reason ?? ""), /Main-root Conductor mode is active \(ultragoal phase: planning\)/);
|
|
18115
|
+
assert.match(context, /Native subagent support is unavailable in this environment/);
|
|
18116
|
+
assert.match(context, /Reason: multi_agent_v1_unavailable/);
|
|
18117
|
+
assert.match(context, /blocked\/cancelled/);
|
|
18118
|
+
assert.match(context, /do not call multi_agent_v1\.close_agent/i);
|
|
18119
|
+
}
|
|
18120
|
+
finally {
|
|
18121
|
+
await rm(cwd, { recursive: true, force: true });
|
|
18122
|
+
}
|
|
18123
|
+
});
|
|
17638
18124
|
it("blocks Main-root ralplan writes even when payload has only a typed agent_role", async () => {
|
|
17639
18125
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-agent-role-main-"));
|
|
17640
18126
|
try {
|
|
@@ -17766,6 +18252,203 @@ PY`,
|
|
|
17766
18252
|
await rm(cwd, { recursive: true, force: true });
|
|
17767
18253
|
}
|
|
17768
18254
|
});
|
|
18255
|
+
it("allows conductor writes from tracked typed native subagents when PreToolUse omits thread_spawn source", async () => {
|
|
18256
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-conductor-tracked-subagent-no-source-"));
|
|
18257
|
+
const originalOmxRoot = process.env.OMX_ROOT;
|
|
18258
|
+
const originalOmxStateRoot = process.env.OMX_STATE_ROOT;
|
|
18259
|
+
const originalOmxTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
|
|
18260
|
+
try {
|
|
18261
|
+
process.env.OMX_ROOT = cwd;
|
|
18262
|
+
delete process.env.OMX_STATE_ROOT;
|
|
18263
|
+
delete process.env.OMX_TEAM_STATE_ROOT;
|
|
18264
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
18265
|
+
const sessionId = "sess-conductor-tracked-subagent-no-source";
|
|
18266
|
+
const leaderThreadId = "thread-conductor-leader-no-source";
|
|
18267
|
+
const childThreadId = "thread-conductor-child-no-source";
|
|
18268
|
+
const nowIso = new Date().toISOString();
|
|
18269
|
+
await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
|
|
18270
|
+
await writeJson(join(stateDir, "session.json"), { session_id: sessionId, native_session_id: leaderThreadId });
|
|
18271
|
+
await writeSessionSkillActiveState(stateDir, sessionId, "ultragoal", "executing");
|
|
18272
|
+
await writeJson(join(stateDir, "sessions", sessionId, "ultragoal-state.json"), {
|
|
18273
|
+
active: true,
|
|
18274
|
+
mode: "ultragoal",
|
|
18275
|
+
current_phase: "executing",
|
|
18276
|
+
session_id: sessionId,
|
|
18277
|
+
});
|
|
18278
|
+
await writeJson(join(stateDir, "subagent-tracking.json"), {
|
|
18279
|
+
schemaVersion: 1,
|
|
18280
|
+
sessions: {
|
|
18281
|
+
[sessionId]: {
|
|
18282
|
+
session_id: sessionId,
|
|
18283
|
+
leader_thread_id: leaderThreadId,
|
|
18284
|
+
updated_at: nowIso,
|
|
18285
|
+
threads: {
|
|
18286
|
+
[leaderThreadId]: { thread_id: leaderThreadId, kind: "leader", first_seen_at: nowIso, last_seen_at: nowIso, turn_count: 1 },
|
|
18287
|
+
[childThreadId]: { thread_id: childThreadId, kind: "subagent", mode: "executor", first_seen_at: nowIso, last_seen_at: nowIso, turn_count: 1 },
|
|
18288
|
+
},
|
|
18289
|
+
},
|
|
18290
|
+
},
|
|
18291
|
+
});
|
|
18292
|
+
const result = await dispatchCodexNativeHook({
|
|
18293
|
+
hook_event_name: "PreToolUse",
|
|
18294
|
+
cwd,
|
|
18295
|
+
session_id: sessionId,
|
|
18296
|
+
thread_id: childThreadId,
|
|
18297
|
+
agent_role: "executor",
|
|
18298
|
+
tool_name: "apply_patch",
|
|
18299
|
+
tool_input: { file_path: "configs/ws/kalshi_ws_auth_evidence.json" },
|
|
18300
|
+
}, { cwd });
|
|
18301
|
+
assert.equal(result.outputJson, null);
|
|
18302
|
+
}
|
|
18303
|
+
finally {
|
|
18304
|
+
if (originalOmxRoot === undefined)
|
|
18305
|
+
delete process.env.OMX_ROOT;
|
|
18306
|
+
else
|
|
18307
|
+
process.env.OMX_ROOT = originalOmxRoot;
|
|
18308
|
+
if (originalOmxStateRoot === undefined)
|
|
18309
|
+
delete process.env.OMX_STATE_ROOT;
|
|
18310
|
+
else
|
|
18311
|
+
process.env.OMX_STATE_ROOT = originalOmxStateRoot;
|
|
18312
|
+
if (originalOmxTeamStateRoot === undefined)
|
|
18313
|
+
delete process.env.OMX_TEAM_STATE_ROOT;
|
|
18314
|
+
else
|
|
18315
|
+
process.env.OMX_TEAM_STATE_ROOT = originalOmxTeamStateRoot;
|
|
18316
|
+
await rm(cwd, { recursive: true, force: true });
|
|
18317
|
+
}
|
|
18318
|
+
});
|
|
18319
|
+
it("blocks conductor writes when corrupt tracker state labels the leader as a subagent", async () => {
|
|
18320
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-conductor-corrupt-leader-subagent-"));
|
|
18321
|
+
const originalOmxRoot = process.env.OMX_ROOT;
|
|
18322
|
+
const originalOmxStateRoot = process.env.OMX_STATE_ROOT;
|
|
18323
|
+
const originalOmxTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
|
|
18324
|
+
try {
|
|
18325
|
+
process.env.OMX_ROOT = cwd;
|
|
18326
|
+
delete process.env.OMX_STATE_ROOT;
|
|
18327
|
+
delete process.env.OMX_TEAM_STATE_ROOT;
|
|
18328
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
18329
|
+
const sessionId = "sess-conductor-corrupt-leader-subagent";
|
|
18330
|
+
const leaderThreadId = "thread-conductor-corrupt-leader";
|
|
18331
|
+
const nowIso = new Date().toISOString();
|
|
18332
|
+
await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
|
|
18333
|
+
await writeJson(join(stateDir, "session.json"), { session_id: sessionId, native_session_id: leaderThreadId });
|
|
18334
|
+
await writeSessionSkillActiveState(stateDir, sessionId, "ultragoal", "executing");
|
|
18335
|
+
await writeJson(join(stateDir, "sessions", sessionId, "ultragoal-state.json"), {
|
|
18336
|
+
active: true,
|
|
18337
|
+
mode: "ultragoal",
|
|
18338
|
+
current_phase: "executing",
|
|
18339
|
+
session_id: sessionId,
|
|
18340
|
+
});
|
|
18341
|
+
await writeJson(join(stateDir, "subagent-tracking.json"), {
|
|
18342
|
+
schemaVersion: 1,
|
|
18343
|
+
sessions: {
|
|
18344
|
+
[sessionId]: {
|
|
18345
|
+
session_id: sessionId,
|
|
18346
|
+
leader_thread_id: leaderThreadId,
|
|
18347
|
+
updated_at: nowIso,
|
|
18348
|
+
threads: {
|
|
18349
|
+
[leaderThreadId]: { thread_id: leaderThreadId, kind: "subagent", mode: "executor", first_seen_at: nowIso, last_seen_at: nowIso, turn_count: 1 },
|
|
18350
|
+
},
|
|
18351
|
+
},
|
|
18352
|
+
},
|
|
18353
|
+
});
|
|
18354
|
+
const result = await dispatchCodexNativeHook({
|
|
18355
|
+
hook_event_name: "PreToolUse",
|
|
18356
|
+
cwd,
|
|
18357
|
+
session_id: sessionId,
|
|
18358
|
+
thread_id: leaderThreadId,
|
|
18359
|
+
agent_role: "executor",
|
|
18360
|
+
tool_name: "apply_patch",
|
|
18361
|
+
tool_input: { file_path: "configs/ws/kalshi_ws_auth_evidence.json" },
|
|
18362
|
+
}, { cwd });
|
|
18363
|
+
assert.equal(result.outputJson?.decision, "block");
|
|
18364
|
+
assert.match(String(result.outputJson?.reason ?? ""), /Main-root Conductor mode is active \(ultragoal phase: executing\)/);
|
|
18365
|
+
}
|
|
18366
|
+
finally {
|
|
18367
|
+
if (originalOmxRoot === undefined)
|
|
18368
|
+
delete process.env.OMX_ROOT;
|
|
18369
|
+
else
|
|
18370
|
+
process.env.OMX_ROOT = originalOmxRoot;
|
|
18371
|
+
if (originalOmxStateRoot === undefined)
|
|
18372
|
+
delete process.env.OMX_STATE_ROOT;
|
|
18373
|
+
else
|
|
18374
|
+
process.env.OMX_STATE_ROOT = originalOmxStateRoot;
|
|
18375
|
+
if (originalOmxTeamStateRoot === undefined)
|
|
18376
|
+
delete process.env.OMX_TEAM_STATE_ROOT;
|
|
18377
|
+
else
|
|
18378
|
+
process.env.OMX_TEAM_STATE_ROOT = originalOmxTeamStateRoot;
|
|
18379
|
+
await rm(cwd, { recursive: true, force: true });
|
|
18380
|
+
}
|
|
18381
|
+
});
|
|
18382
|
+
it("blocks conductor writes when thread_spawn provenance is attached to the leader thread", async () => {
|
|
18383
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-conductor-thread-spawn-leader-"));
|
|
18384
|
+
const originalOmxRoot = process.env.OMX_ROOT;
|
|
18385
|
+
const originalOmxStateRoot = process.env.OMX_STATE_ROOT;
|
|
18386
|
+
const originalOmxTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
|
|
18387
|
+
try {
|
|
18388
|
+
process.env.OMX_ROOT = cwd;
|
|
18389
|
+
delete process.env.OMX_STATE_ROOT;
|
|
18390
|
+
delete process.env.OMX_TEAM_STATE_ROOT;
|
|
18391
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
18392
|
+
const sessionId = "sess-conductor-thread-spawn-leader";
|
|
18393
|
+
const leaderThreadId = "thread-conductor-thread-spawn-leader";
|
|
18394
|
+
const nowIso = new Date().toISOString();
|
|
18395
|
+
await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
|
|
18396
|
+
await writeJson(join(stateDir, "session.json"), { session_id: sessionId, native_session_id: leaderThreadId });
|
|
18397
|
+
await writeSessionSkillActiveState(stateDir, sessionId, "ultragoal", "executing");
|
|
18398
|
+
await writeJson(join(stateDir, "sessions", sessionId, "ultragoal-state.json"), {
|
|
18399
|
+
active: true,
|
|
18400
|
+
mode: "ultragoal",
|
|
18401
|
+
current_phase: "executing",
|
|
18402
|
+
session_id: sessionId,
|
|
18403
|
+
});
|
|
18404
|
+
await writeJson(join(stateDir, "subagent-tracking.json"), {
|
|
18405
|
+
schemaVersion: 1,
|
|
18406
|
+
sessions: {
|
|
18407
|
+
[sessionId]: {
|
|
18408
|
+
session_id: sessionId,
|
|
18409
|
+
leader_thread_id: leaderThreadId,
|
|
18410
|
+
updated_at: nowIso,
|
|
18411
|
+
threads: {
|
|
18412
|
+
[leaderThreadId]: { thread_id: leaderThreadId, kind: "leader", first_seen_at: nowIso, last_seen_at: nowIso, turn_count: 1 },
|
|
18413
|
+
},
|
|
18414
|
+
},
|
|
18415
|
+
},
|
|
18416
|
+
});
|
|
18417
|
+
const result = await dispatchCodexNativeHook({
|
|
18418
|
+
hook_event_name: "PreToolUse",
|
|
18419
|
+
cwd,
|
|
18420
|
+
session_id: sessionId,
|
|
18421
|
+
thread_id: leaderThreadId,
|
|
18422
|
+
agent_role: "executor",
|
|
18423
|
+
source: {
|
|
18424
|
+
subagent: {
|
|
18425
|
+
thread_spawn: {
|
|
18426
|
+
parent_thread_id: leaderThreadId,
|
|
18427
|
+
},
|
|
18428
|
+
},
|
|
18429
|
+
},
|
|
18430
|
+
tool_name: "apply_patch",
|
|
18431
|
+
tool_input: { file_path: "configs/ws/kalshi_ws_auth_evidence.json" },
|
|
18432
|
+
}, { cwd });
|
|
18433
|
+
assert.equal(result.outputJson?.decision, "block");
|
|
18434
|
+
assert.match(String(result.outputJson?.reason ?? ""), /Main-root Conductor mode is active \(ultragoal phase: executing\)/);
|
|
18435
|
+
}
|
|
18436
|
+
finally {
|
|
18437
|
+
if (originalOmxRoot === undefined)
|
|
18438
|
+
delete process.env.OMX_ROOT;
|
|
18439
|
+
else
|
|
18440
|
+
process.env.OMX_ROOT = originalOmxRoot;
|
|
18441
|
+
if (originalOmxStateRoot === undefined)
|
|
18442
|
+
delete process.env.OMX_STATE_ROOT;
|
|
18443
|
+
else
|
|
18444
|
+
process.env.OMX_STATE_ROOT = originalOmxStateRoot;
|
|
18445
|
+
if (originalOmxTeamStateRoot === undefined)
|
|
18446
|
+
delete process.env.OMX_TEAM_STATE_ROOT;
|
|
18447
|
+
else
|
|
18448
|
+
process.env.OMX_TEAM_STATE_ROOT = originalOmxTeamStateRoot;
|
|
18449
|
+
await rm(cwd, { recursive: true, force: true });
|
|
18450
|
+
}
|
|
18451
|
+
});
|
|
17769
18452
|
it("blocks Main-root ralph conductor source and planning artifact writes while allowing .omx workflow state writes", async () => {
|
|
17770
18453
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralph-conductor-write-"));
|
|
17771
18454
|
try {
|
|
@@ -17837,6 +18520,65 @@ PY`,
|
|
|
17837
18520
|
await rm(cwd, { recursive: true, force: true });
|
|
17838
18521
|
}
|
|
17839
18522
|
});
|
|
18523
|
+
it("allows structured ultragoal steering cleanup through dynamic nested shell loops", async () => {
|
|
18524
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-steer-cleanup-"));
|
|
18525
|
+
try {
|
|
18526
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
18527
|
+
const sessionId = "sess-ultragoal-steer-cleanup";
|
|
18528
|
+
await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
|
|
18529
|
+
await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
|
|
18530
|
+
await writeSessionSkillActiveState(stateDir, sessionId, "ultragoal", "planning");
|
|
18531
|
+
await writeJson(join(stateDir, "sessions", sessionId, "ultragoal-state.json"), {
|
|
18532
|
+
active: true,
|
|
18533
|
+
mode: "ultragoal",
|
|
18534
|
+
current_phase: "planning",
|
|
18535
|
+
session_id: sessionId,
|
|
18536
|
+
});
|
|
18537
|
+
const result = await dispatchCodexNativeHook({
|
|
18538
|
+
hook_event_name: "PreToolUse",
|
|
18539
|
+
cwd,
|
|
18540
|
+
session_id: sessionId,
|
|
18541
|
+
thread_id: "thread-ultragoal-steer-cleanup",
|
|
18542
|
+
tool_name: "Bash",
|
|
18543
|
+
tool_input: {
|
|
18544
|
+
command: `bash -lc 'for goal_id in G001-atomized G002-atomized; do omx ultragoal steer --kind mark_blocked_superseded --target-goal-id "$goal_id" --evidence ".omx/ultragoal cleanup supersedes atomized pseudo-goals." --rationale "Structured steering cleanup keeps durable Ultragoal metadata auditable." --json; done'`,
|
|
18545
|
+
},
|
|
18546
|
+
}, { cwd });
|
|
18547
|
+
assert.equal(result.outputJson, null);
|
|
18548
|
+
}
|
|
18549
|
+
finally {
|
|
18550
|
+
await rm(cwd, { recursive: true, force: true });
|
|
18551
|
+
}
|
|
18552
|
+
});
|
|
18553
|
+
it("blocks unsafe dynamic nested shell writes in conductor mode", async () => {
|
|
18554
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-dynamic-shell-write-block-"));
|
|
18555
|
+
try {
|
|
18556
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
18557
|
+
const sessionId = "sess-dynamic-shell-write-block";
|
|
18558
|
+
await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
|
|
18559
|
+
await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
|
|
18560
|
+
await writeSessionSkillActiveState(stateDir, sessionId, "ultragoal", "planning");
|
|
18561
|
+
await writeJson(join(stateDir, "sessions", sessionId, "ultragoal-state.json"), {
|
|
18562
|
+
active: true,
|
|
18563
|
+
mode: "ultragoal",
|
|
18564
|
+
current_phase: "planning",
|
|
18565
|
+
session_id: sessionId,
|
|
18566
|
+
});
|
|
18567
|
+
const result = await dispatchCodexNativeHook({
|
|
18568
|
+
hook_event_name: "PreToolUse",
|
|
18569
|
+
cwd,
|
|
18570
|
+
session_id: sessionId,
|
|
18571
|
+
thread_id: "thread-dynamic-shell-write-block",
|
|
18572
|
+
tool_name: "Bash",
|
|
18573
|
+
tool_input: { command: `bash -lc 'cp "$SOURCE_FILE" src/runtime.ts'` },
|
|
18574
|
+
}, { cwd });
|
|
18575
|
+
assert.equal(result.outputJson?.decision, "block");
|
|
18576
|
+
assert.match(String(result.outputJson?.reason ?? ""), /Bash nested shell execution is dynamic and cannot be validated/);
|
|
18577
|
+
}
|
|
18578
|
+
finally {
|
|
18579
|
+
await rm(cwd, { recursive: true, force: true });
|
|
18580
|
+
}
|
|
18581
|
+
});
|
|
17840
18582
|
it("blocks common Bash file mutations in Main-root conductor states unless they target workflow metadata", async () => {
|
|
17841
18583
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-conductor-bash-mutations-"));
|
|
17842
18584
|
try {
|