oh-my-codex 0.12.0 → 0.12.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +5 -5
- package/Cargo.toml +1 -1
- package/README.md +7 -0
- package/dist/cli/__tests__/cleanup.test.js +15 -0
- package/dist/cli/__tests__/cleanup.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +147 -2
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/launch-fallback.test.js +95 -1
- package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
- package/dist/cli/cleanup.d.ts.map +1 -1
- package/dist/cli/cleanup.js +6 -3
- package/dist/cli/cleanup.js.map +1 -1
- package/dist/cli/index.d.ts +18 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +178 -23
- package/dist/cli/index.js.map +1 -1
- package/dist/hooks/__tests__/agents-overlay.test.js +18 -0
- package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
- package/dist/hooks/__tests__/keyword-detector.test.js +156 -1
- package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js +56 -2
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
- package/dist/hooks/agents-overlay.d.ts.map +1 -1
- package/dist/hooks/agents-overlay.js +25 -3
- package/dist/hooks/agents-overlay.js.map +1 -1
- package/dist/hooks/keyword-detector.d.ts +5 -1
- package/dist/hooks/keyword-detector.d.ts.map +1 -1
- package/dist/hooks/keyword-detector.js +104 -6
- package/dist/hooks/keyword-detector.js.map +1 -1
- package/dist/hud/__tests__/render.test.js +6 -0
- package/dist/hud/__tests__/render.test.js.map +1 -1
- package/dist/hud/__tests__/state.test.js +55 -1
- package/dist/hud/__tests__/state.test.js.map +1 -1
- package/dist/hud/render.d.ts.map +1 -1
- package/dist/hud/render.js +8 -3
- package/dist/hud/render.js.map +1 -1
- package/dist/hud/state.d.ts.map +1 -1
- package/dist/hud/state.js +71 -9
- package/dist/hud/state.js.map +1 -1
- package/dist/hud/types.d.ts +2 -2
- package/dist/hud/types.d.ts.map +1 -1
- package/dist/mcp/__tests__/state-server.test.js +82 -0
- package/dist/mcp/__tests__/state-server.test.js.map +1 -1
- package/dist/mcp/state-server.d.ts +4 -4
- package/dist/mcp/state-server.d.ts.map +1 -1
- package/dist/mcp/state-server.js +35 -0
- package/dist/mcp/state-server.js.map +1 -1
- package/dist/scripts/__tests__/codex-native-hook.test.js +336 -6
- 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 +221 -23
- package/dist/scripts/codex-native-hook.js.map +1 -1
- package/dist/scripts/notify-fallback-watcher.js +60 -3
- package/dist/scripts/notify-fallback-watcher.js.map +1 -1
- package/dist/state/__tests__/skill-active.test.d.ts +2 -0
- package/dist/state/__tests__/skill-active.test.d.ts.map +1 -0
- package/dist/state/__tests__/skill-active.test.js +84 -0
- package/dist/state/__tests__/skill-active.test.js.map +1 -0
- package/dist/state/skill-active.d.ts +55 -0
- package/dist/state/skill-active.d.ts.map +1 -0
- package/dist/state/skill-active.js +179 -0
- package/dist/state/skill-active.js.map +1 -0
- package/dist/team/__tests__/runtime-cli.test.js +85 -0
- package/dist/team/__tests__/runtime-cli.test.js.map +1 -1
- package/dist/team/__tests__/runtime.test.js +241 -7
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/state.test.js +7 -0
- package/dist/team/__tests__/state.test.js.map +1 -1
- package/dist/team/__tests__/tmux-session.test.js +143 -0
- package/dist/team/__tests__/tmux-session.test.js.map +1 -1
- package/dist/team/runtime-cli.d.ts +15 -0
- package/dist/team/runtime-cli.d.ts.map +1 -1
- package/dist/team/runtime-cli.js +37 -20
- package/dist/team/runtime-cli.js.map +1 -1
- package/dist/team/runtime.d.ts +1 -0
- package/dist/team/runtime.d.ts.map +1 -1
- package/dist/team/runtime.js +16 -8
- package/dist/team/runtime.js.map +1 -1
- package/dist/team/state/mailbox.d.ts.map +1 -1
- package/dist/team/state/mailbox.js +6 -0
- package/dist/team/state/mailbox.js.map +1 -1
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +32 -4
- package/dist/team/tmux-session.js.map +1 -1
- package/package.json +1 -1
- package/prompts/information-architect.md +15 -102
- package/src/scripts/__tests__/codex-native-hook.test.ts +421 -6
- package/src/scripts/codex-native-hook.ts +287 -25
- package/src/scripts/notify-fallback-watcher.ts +62 -5
|
@@ -18,6 +18,9 @@ async function writeJson(path: string, value: unknown): Promise<void> {
|
|
|
18
18
|
await writeFile(path, JSON.stringify(value, null, 2));
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
const TEAM_STOP_COMMIT_GUIDANCE =
|
|
22
|
+
" If system-generated worker auto-checkpoint commits exist, rewrite them into Lore-format final commits before merge/finalization.";
|
|
23
|
+
|
|
21
24
|
describe("codex native hook config", () => {
|
|
22
25
|
it("builds the expected managed hooks.json shape", () => {
|
|
23
26
|
const config = buildManagedCodexHooksConfig("/tmp/omx");
|
|
@@ -189,15 +192,19 @@ describe("codex native hook dispatch", () => {
|
|
|
189
192
|
assert.equal(result.omxEventName, "keyword-detector");
|
|
190
193
|
assert.equal(result.skillState?.skill, "ralplan");
|
|
191
194
|
assert.ok(result.outputJson, "UserPromptSubmit should emit developer context");
|
|
195
|
+
assert.match(JSON.stringify(result.outputJson), /skill: ralplan activated and initial state initialized at \.omx\/state\/sessions\/sess-1\/ralplan-state\.json; write subsequent updates via omx_state MCP\./);
|
|
192
196
|
|
|
193
197
|
const statePath = join(cwd, ".omx", "state", "skill-active-state.json");
|
|
194
198
|
assert.equal(existsSync(statePath), true);
|
|
195
199
|
const state = JSON.parse(await readFile(statePath, "utf-8")) as {
|
|
196
200
|
skill?: string;
|
|
197
201
|
active?: boolean;
|
|
202
|
+
initialized_mode?: string;
|
|
198
203
|
};
|
|
199
204
|
assert.equal(state.skill, "ralplan");
|
|
200
205
|
assert.equal(state.active, true);
|
|
206
|
+
assert.equal(state.initialized_mode, "ralplan");
|
|
207
|
+
assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", "sess-1", "ralplan-state.json")), true);
|
|
201
208
|
} finally {
|
|
202
209
|
await rm(cwd, { recursive: true, force: true });
|
|
203
210
|
}
|
|
@@ -480,7 +487,7 @@ describe("codex native hook dispatch", () => {
|
|
|
480
487
|
assert.deepEqual(result.outputJson, {
|
|
481
488
|
decision: "block",
|
|
482
489
|
reason:
|
|
483
|
-
|
|
490
|
+
`OMX team pipeline is still active (review-team) at phase team-verify; continue coordinating until the team reaches a terminal phase.${TEAM_STOP_COMMIT_GUIDANCE}`,
|
|
484
491
|
stopReason: "team_team-verify",
|
|
485
492
|
systemMessage: "OMX team pipeline is still active at phase team-verify.",
|
|
486
493
|
});
|
|
@@ -489,6 +496,118 @@ describe("codex native hook dispatch", () => {
|
|
|
489
496
|
}
|
|
490
497
|
});
|
|
491
498
|
|
|
499
|
+
it("blocks Stop for a team worker with a non-terminal assigned task via native worker context", async () => {
|
|
500
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-worker-"));
|
|
501
|
+
const prevTeamWorker = process.env.OMX_TEAM_WORKER;
|
|
502
|
+
const prevTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
|
|
503
|
+
const prevLeaderCwd = process.env.OMX_TEAM_LEADER_CWD;
|
|
504
|
+
try {
|
|
505
|
+
await initTeamState(
|
|
506
|
+
"worker-stop-team",
|
|
507
|
+
"worker stop fallback",
|
|
508
|
+
"executor",
|
|
509
|
+
1,
|
|
510
|
+
cwd,
|
|
511
|
+
undefined,
|
|
512
|
+
{ ...process.env, OMX_SESSION_ID: "sess-stop-team-worker" },
|
|
513
|
+
);
|
|
514
|
+
const workerDir = join(cwd, ".omx", "state", "team", "worker-stop-team", "workers", "worker-1");
|
|
515
|
+
await writeJson(join(workerDir, "status.json"), {
|
|
516
|
+
state: "idle",
|
|
517
|
+
current_task_id: "1",
|
|
518
|
+
updated_at: new Date().toISOString(),
|
|
519
|
+
});
|
|
520
|
+
await writeJson(join(cwd, ".omx", "state", "team", "worker-stop-team", "tasks", "task-1.json"), {
|
|
521
|
+
id: "1",
|
|
522
|
+
subject: "hook task",
|
|
523
|
+
description: "finish hook task",
|
|
524
|
+
status: "in_progress",
|
|
525
|
+
owner: "worker-1",
|
|
526
|
+
created_at: new Date().toISOString(),
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
process.env.OMX_TEAM_WORKER = "worker-stop-team/worker-1";
|
|
530
|
+
process.env.OMX_TEAM_STATE_ROOT = join(cwd, ".omx", "state");
|
|
531
|
+
process.env.OMX_TEAM_LEADER_CWD = cwd;
|
|
532
|
+
|
|
533
|
+
const result = await dispatchCodexNativeHook(
|
|
534
|
+
{
|
|
535
|
+
hook_event_name: "Stop",
|
|
536
|
+
cwd: join(cwd, ".omx", "team", "worker-stop-team", "worktrees", "worker-1"),
|
|
537
|
+
session_id: "sess-stop-team-worker",
|
|
538
|
+
},
|
|
539
|
+
{ cwd: join(cwd, ".omx", "team", "worker-stop-team", "worktrees", "worker-1") },
|
|
540
|
+
);
|
|
541
|
+
|
|
542
|
+
assert.deepEqual(result.outputJson, {
|
|
543
|
+
decision: "block",
|
|
544
|
+
reason:
|
|
545
|
+
"OMX team worker worker-1 is still assigned non-terminal task 1 (in_progress); continue the current assigned task or report a concrete blocker before stopping.",
|
|
546
|
+
stopReason: "team_worker_worker-1_1_in_progress",
|
|
547
|
+
systemMessage: "OMX team worker worker-1 is still assigned task 1 (in_progress).",
|
|
548
|
+
});
|
|
549
|
+
} finally {
|
|
550
|
+
if (typeof prevTeamWorker === "string") process.env.OMX_TEAM_WORKER = prevTeamWorker;
|
|
551
|
+
else delete process.env.OMX_TEAM_WORKER;
|
|
552
|
+
if (typeof prevTeamStateRoot === "string") process.env.OMX_TEAM_STATE_ROOT = prevTeamStateRoot;
|
|
553
|
+
else delete process.env.OMX_TEAM_STATE_ROOT;
|
|
554
|
+
if (typeof prevLeaderCwd === "string") process.env.OMX_TEAM_LEADER_CWD = prevLeaderCwd;
|
|
555
|
+
else delete process.env.OMX_TEAM_LEADER_CWD;
|
|
556
|
+
await rm(cwd, { recursive: true, force: true });
|
|
557
|
+
}
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
it("does not block Stop for a team worker when assigned task is terminal", async () => {
|
|
561
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-worker-terminal-"));
|
|
562
|
+
const prevTeamWorker = process.env.OMX_TEAM_WORKER;
|
|
563
|
+
const prevTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
|
|
564
|
+
try {
|
|
565
|
+
await initTeamState(
|
|
566
|
+
"worker-stop-team-terminal",
|
|
567
|
+
"worker stop terminal fallback",
|
|
568
|
+
"executor",
|
|
569
|
+
1,
|
|
570
|
+
cwd,
|
|
571
|
+
undefined,
|
|
572
|
+
{ ...process.env, OMX_SESSION_ID: "sess-stop-team-worker-terminal" },
|
|
573
|
+
);
|
|
574
|
+
const workerDir = join(cwd, ".omx", "state", "team", "worker-stop-team-terminal", "workers", "worker-1");
|
|
575
|
+
await writeJson(join(workerDir, "status.json"), {
|
|
576
|
+
state: "done",
|
|
577
|
+
current_task_id: "1",
|
|
578
|
+
updated_at: new Date().toISOString(),
|
|
579
|
+
});
|
|
580
|
+
await writeJson(join(cwd, ".omx", "state", "team", "worker-stop-team-terminal", "tasks", "task-1.json"), {
|
|
581
|
+
id: "1",
|
|
582
|
+
subject: "hook task",
|
|
583
|
+
description: "finish hook task",
|
|
584
|
+
status: "completed",
|
|
585
|
+
owner: "worker-1",
|
|
586
|
+
created_at: new Date().toISOString(),
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
process.env.OMX_TEAM_WORKER = "worker-stop-team-terminal/worker-1";
|
|
590
|
+
process.env.OMX_TEAM_STATE_ROOT = join(cwd, ".omx", "state");
|
|
591
|
+
|
|
592
|
+
const result = await dispatchCodexNativeHook(
|
|
593
|
+
{
|
|
594
|
+
hook_event_name: "Stop",
|
|
595
|
+
cwd,
|
|
596
|
+
session_id: "sess-stop-team-worker-terminal",
|
|
597
|
+
},
|
|
598
|
+
{ cwd },
|
|
599
|
+
);
|
|
600
|
+
|
|
601
|
+
assert.equal(result.outputJson, null);
|
|
602
|
+
} finally {
|
|
603
|
+
if (typeof prevTeamWorker === "string") process.env.OMX_TEAM_WORKER = prevTeamWorker;
|
|
604
|
+
else delete process.env.OMX_TEAM_WORKER;
|
|
605
|
+
if (typeof prevTeamStateRoot === "string") process.env.OMX_TEAM_STATE_ROOT = prevTeamStateRoot;
|
|
606
|
+
else delete process.env.OMX_TEAM_STATE_ROOT;
|
|
607
|
+
await rm(cwd, { recursive: true, force: true });
|
|
608
|
+
}
|
|
609
|
+
});
|
|
610
|
+
|
|
492
611
|
it("returns Stop continuation output from canonical team state when coarse mode state is missing", async () => {
|
|
493
612
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-canonical-"));
|
|
494
613
|
try {
|
|
@@ -515,7 +634,56 @@ describe("codex native hook dispatch", () => {
|
|
|
515
634
|
assert.deepEqual(result.outputJson, {
|
|
516
635
|
decision: "block",
|
|
517
636
|
reason:
|
|
518
|
-
|
|
637
|
+
`OMX team pipeline is still active (canonical-team) at phase team-exec; continue coordinating until the team reaches a terminal phase.${TEAM_STOP_COMMIT_GUIDANCE}`,
|
|
638
|
+
stopReason: "team_team-exec",
|
|
639
|
+
systemMessage: "OMX team pipeline is still active at phase team-exec.",
|
|
640
|
+
});
|
|
641
|
+
} finally {
|
|
642
|
+
await rm(cwd, { recursive: true, force: true });
|
|
643
|
+
}
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
it("re-fires canonical-team Stop output for a later fresh Stop reply when coarse mode state is missing", async () => {
|
|
647
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-canonical-refire-"));
|
|
648
|
+
try {
|
|
649
|
+
await initTeamState(
|
|
650
|
+
"canonical-team-refire",
|
|
651
|
+
"canonical stop fallback refire",
|
|
652
|
+
"executor",
|
|
653
|
+
1,
|
|
654
|
+
cwd,
|
|
655
|
+
undefined,
|
|
656
|
+
{ ...process.env, OMX_SESSION_ID: "sess-stop-team-canonical-refire" },
|
|
657
|
+
);
|
|
658
|
+
|
|
659
|
+
await dispatchCodexNativeHook(
|
|
660
|
+
{
|
|
661
|
+
hook_event_name: "Stop",
|
|
662
|
+
cwd,
|
|
663
|
+
session_id: "sess-stop-team-canonical-refire",
|
|
664
|
+
thread_id: "thread-stop-team-canonical-refire",
|
|
665
|
+
turn_id: "turn-stop-team-canonical-refire-1",
|
|
666
|
+
},
|
|
667
|
+
{ cwd },
|
|
668
|
+
);
|
|
669
|
+
|
|
670
|
+
const result = await dispatchCodexNativeHook(
|
|
671
|
+
{
|
|
672
|
+
hook_event_name: "Stop",
|
|
673
|
+
cwd,
|
|
674
|
+
session_id: "sess-stop-team-canonical-refire",
|
|
675
|
+
thread_id: "thread-stop-team-canonical-refire",
|
|
676
|
+
turn_id: "turn-stop-team-canonical-refire-2",
|
|
677
|
+
stop_hook_active: true,
|
|
678
|
+
},
|
|
679
|
+
{ cwd },
|
|
680
|
+
);
|
|
681
|
+
|
|
682
|
+
assert.equal(result.omxEventName, "stop");
|
|
683
|
+
assert.deepEqual(result.outputJson, {
|
|
684
|
+
decision: "block",
|
|
685
|
+
reason:
|
|
686
|
+
`OMX team pipeline is still active (canonical-team-refire) at phase team-exec; continue coordinating until the team reaches a terminal phase.${TEAM_STOP_COMMIT_GUIDANCE}`,
|
|
519
687
|
stopReason: "team_team-exec",
|
|
520
688
|
systemMessage: "OMX team pipeline is still active at phase team-exec.",
|
|
521
689
|
});
|
|
@@ -595,7 +763,7 @@ describe("codex native hook dispatch", () => {
|
|
|
595
763
|
assert.deepEqual(result.outputJson, {
|
|
596
764
|
decision: "block",
|
|
597
765
|
reason:
|
|
598
|
-
|
|
766
|
+
`OMX team pipeline is still active (legacy-team) at phase team-exec; continue coordinating until the team reaches a terminal phase.${TEAM_STOP_COMMIT_GUIDANCE}`,
|
|
599
767
|
stopReason: "team_team-exec",
|
|
600
768
|
systemMessage: "OMX team pipeline is still active at phase team-exec.",
|
|
601
769
|
});
|
|
@@ -634,7 +802,7 @@ describe("codex native hook dispatch", () => {
|
|
|
634
802
|
assert.deepEqual(result.outputJson, {
|
|
635
803
|
decision: "block",
|
|
636
804
|
reason:
|
|
637
|
-
|
|
805
|
+
`OMX team pipeline is still active (canonical-root-team) at phase team-exec; continue coordinating until the team reaches a terminal phase.${TEAM_STOP_COMMIT_GUIDANCE}`,
|
|
638
806
|
stopReason: "team_team-exec",
|
|
639
807
|
systemMessage: "OMX team pipeline is still active at phase team-exec.",
|
|
640
808
|
});
|
|
@@ -678,7 +846,7 @@ describe("codex native hook dispatch", () => {
|
|
|
678
846
|
assert.deepEqual(result.outputJson, {
|
|
679
847
|
decision: "block",
|
|
680
848
|
reason:
|
|
681
|
-
|
|
849
|
+
`OMX team pipeline is still active (env-root-team) at phase team-exec; continue coordinating until the team reaches a terminal phase.${TEAM_STOP_COMMIT_GUIDANCE}`,
|
|
682
850
|
stopReason: "team_team-exec",
|
|
683
851
|
systemMessage: "OMX team pipeline is still active at phase team-exec.",
|
|
684
852
|
});
|
|
@@ -810,6 +978,35 @@ describe("codex native hook dispatch", () => {
|
|
|
810
978
|
}
|
|
811
979
|
});
|
|
812
980
|
|
|
981
|
+
it("ignores root skill-active fallback from a different thread when evaluating Stop", async () => {
|
|
982
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-foreign-thread-"));
|
|
983
|
+
try {
|
|
984
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
985
|
+
await mkdir(stateDir, { recursive: true });
|
|
986
|
+
await writeJson(join(stateDir, "skill-active-state.json"), {
|
|
987
|
+
active: true,
|
|
988
|
+
skill: "deep-interview",
|
|
989
|
+
phase: "planning",
|
|
990
|
+
session_id: "",
|
|
991
|
+
thread_id: "other-thread",
|
|
992
|
+
});
|
|
993
|
+
|
|
994
|
+
const result = await dispatchCodexNativeHook(
|
|
995
|
+
{
|
|
996
|
+
hook_event_name: "Stop",
|
|
997
|
+
cwd,
|
|
998
|
+
session_id: "sess-stop-main",
|
|
999
|
+
thread_id: "main-thread",
|
|
1000
|
+
},
|
|
1001
|
+
{ cwd },
|
|
1002
|
+
);
|
|
1003
|
+
|
|
1004
|
+
assert.equal(result.outputJson, null);
|
|
1005
|
+
} finally {
|
|
1006
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1007
|
+
}
|
|
1008
|
+
});
|
|
1009
|
+
|
|
813
1010
|
it("returns Stop continuation output while Ralph is active", async () => {
|
|
814
1011
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-"));
|
|
815
1012
|
try {
|
|
@@ -905,17 +1102,31 @@ describe("codex native hook dispatch", () => {
|
|
|
905
1102
|
}
|
|
906
1103
|
});
|
|
907
1104
|
|
|
908
|
-
it("
|
|
1105
|
+
it("suppresses duplicate native auto-nudge replays for the same Stop reply", async () => {
|
|
909
1106
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-auto-nudge-once-"));
|
|
910
1107
|
try {
|
|
911
1108
|
const stateDir = join(cwd, ".omx", "state");
|
|
912
1109
|
await mkdir(stateDir, { recursive: true });
|
|
913
1110
|
|
|
1111
|
+
await dispatchCodexNativeHook(
|
|
1112
|
+
{
|
|
1113
|
+
hook_event_name: "Stop",
|
|
1114
|
+
cwd,
|
|
1115
|
+
session_id: "sess-stop-auto-once",
|
|
1116
|
+
thread_id: "thread-stop-auto",
|
|
1117
|
+
turn_id: "turn-stop-auto-1",
|
|
1118
|
+
last_assistant_message: "Would you like me to continue?",
|
|
1119
|
+
},
|
|
1120
|
+
{ cwd },
|
|
1121
|
+
);
|
|
1122
|
+
|
|
914
1123
|
const result = await dispatchCodexNativeHook(
|
|
915
1124
|
{
|
|
916
1125
|
hook_event_name: "Stop",
|
|
917
1126
|
cwd,
|
|
918
1127
|
session_id: "sess-stop-auto-once",
|
|
1128
|
+
thread_id: "thread-stop-auto",
|
|
1129
|
+
turn_id: "turn-stop-auto-1",
|
|
919
1130
|
stop_hook_active: true,
|
|
920
1131
|
last_assistant_message: "Would you like me to continue?",
|
|
921
1132
|
},
|
|
@@ -928,4 +1139,208 @@ describe("codex native hook dispatch", () => {
|
|
|
928
1139
|
await rm(cwd, { recursive: true, force: true });
|
|
929
1140
|
}
|
|
930
1141
|
});
|
|
1142
|
+
|
|
1143
|
+
it("re-fires native auto-nudge for a later fresh Stop reply even when stop_hook_active is true", async () => {
|
|
1144
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-auto-nudge-refire-"));
|
|
1145
|
+
try {
|
|
1146
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
1147
|
+
await mkdir(stateDir, { recursive: true });
|
|
1148
|
+
|
|
1149
|
+
await dispatchCodexNativeHook(
|
|
1150
|
+
{
|
|
1151
|
+
hook_event_name: "Stop",
|
|
1152
|
+
cwd,
|
|
1153
|
+
session_id: "sess-stop-auto-refire",
|
|
1154
|
+
thread_id: "thread-stop-auto-refire",
|
|
1155
|
+
turn_id: "turn-stop-auto-refire-1",
|
|
1156
|
+
last_assistant_message: "Would you like me to continue?",
|
|
1157
|
+
},
|
|
1158
|
+
{ cwd },
|
|
1159
|
+
);
|
|
1160
|
+
|
|
1161
|
+
const result = await dispatchCodexNativeHook(
|
|
1162
|
+
{
|
|
1163
|
+
hook_event_name: "Stop",
|
|
1164
|
+
cwd,
|
|
1165
|
+
session_id: "sess-stop-auto-refire",
|
|
1166
|
+
thread_id: "thread-stop-auto-refire",
|
|
1167
|
+
turn_id: "turn-stop-auto-refire-2",
|
|
1168
|
+
stop_hook_active: true,
|
|
1169
|
+
last_assistant_message: "If you want, I can keep going and finish the cleanup.",
|
|
1170
|
+
},
|
|
1171
|
+
{ cwd },
|
|
1172
|
+
);
|
|
1173
|
+
|
|
1174
|
+
assert.equal(result.omxEventName, "stop");
|
|
1175
|
+
assert.deepEqual(result.outputJson, {
|
|
1176
|
+
decision: "block",
|
|
1177
|
+
reason: "yes, proceed",
|
|
1178
|
+
stopReason: "auto_nudge",
|
|
1179
|
+
systemMessage:
|
|
1180
|
+
"OMX native Stop detected a stall/permission-style handoff and continued the turn automatically.",
|
|
1181
|
+
});
|
|
1182
|
+
} finally {
|
|
1183
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1184
|
+
}
|
|
1185
|
+
});
|
|
1186
|
+
|
|
1187
|
+
it("suppresses native auto-nudge when only the deep-interview input lock is active", async () => {
|
|
1188
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-auto-nudge-deep-interview-lock-"));
|
|
1189
|
+
try {
|
|
1190
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
1191
|
+
await mkdir(join(stateDir, "sessions", "sess-stop-auto-lock"), { recursive: true });
|
|
1192
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-stop-auto-lock" });
|
|
1193
|
+
await writeJson(join(stateDir, "sessions", "sess-stop-auto-lock", "skill-active-state.json"), {
|
|
1194
|
+
version: 1,
|
|
1195
|
+
active: true,
|
|
1196
|
+
skill: "deep-interview",
|
|
1197
|
+
phase: "planning",
|
|
1198
|
+
input_lock: {
|
|
1199
|
+
active: true,
|
|
1200
|
+
scope: "deep-interview-auto-approval",
|
|
1201
|
+
blocked_inputs: ["yes", "proceed"],
|
|
1202
|
+
message: "Deep interview is active; auto-approval shortcuts are blocked until the interview finishes.",
|
|
1203
|
+
},
|
|
1204
|
+
});
|
|
1205
|
+
|
|
1206
|
+
const result = await dispatchCodexNativeHook(
|
|
1207
|
+
{
|
|
1208
|
+
hook_event_name: "Stop",
|
|
1209
|
+
cwd,
|
|
1210
|
+
session_id: "sess-stop-auto-lock",
|
|
1211
|
+
thread_id: "thread-stop-auto-lock",
|
|
1212
|
+
turn_id: "turn-stop-auto-lock-1",
|
|
1213
|
+
last_assistant_message: "Would you like me to continue with the next step?",
|
|
1214
|
+
},
|
|
1215
|
+
{ cwd },
|
|
1216
|
+
);
|
|
1217
|
+
|
|
1218
|
+
assert.equal(result.omxEventName, "stop");
|
|
1219
|
+
assert.deepEqual(result.outputJson, {
|
|
1220
|
+
decision: "block",
|
|
1221
|
+
reason:
|
|
1222
|
+
"OMX skill deep-interview is still active (phase: planning); continue until the current deep-interview workflow reaches a terminal state.",
|
|
1223
|
+
stopReason: "skill_deep-interview_planning",
|
|
1224
|
+
systemMessage: "OMX skill deep-interview is still active (phase: planning).",
|
|
1225
|
+
});
|
|
1226
|
+
} finally {
|
|
1227
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1228
|
+
}
|
|
1229
|
+
});
|
|
1230
|
+
|
|
1231
|
+
it("suppresses native auto-nudge re-fire while session-scoped deep-interview state is still active", async () => {
|
|
1232
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-auto-nudge-deep-interview-state-"));
|
|
1233
|
+
try {
|
|
1234
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
1235
|
+
await mkdir(join(stateDir, "sessions", "sess-stop-auto-interview"), { recursive: true });
|
|
1236
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-stop-auto-interview" });
|
|
1237
|
+
await writeJson(join(stateDir, "sessions", "sess-stop-auto-interview", "deep-interview-state.json"), {
|
|
1238
|
+
active: true,
|
|
1239
|
+
mode: "deep-interview",
|
|
1240
|
+
current_phase: "intent-first",
|
|
1241
|
+
});
|
|
1242
|
+
|
|
1243
|
+
const result = await dispatchCodexNativeHook(
|
|
1244
|
+
{
|
|
1245
|
+
hook_event_name: "Stop",
|
|
1246
|
+
cwd,
|
|
1247
|
+
session_id: "sess-stop-auto-interview",
|
|
1248
|
+
thread_id: "thread-stop-auto-interview",
|
|
1249
|
+
turn_id: "turn-stop-auto-interview-2",
|
|
1250
|
+
stop_hook_active: true,
|
|
1251
|
+
last_assistant_message: "If you want, I can keep going from here.",
|
|
1252
|
+
},
|
|
1253
|
+
{ cwd },
|
|
1254
|
+
);
|
|
1255
|
+
|
|
1256
|
+
assert.equal(result.omxEventName, "stop");
|
|
1257
|
+
assert.equal(result.outputJson, null);
|
|
1258
|
+
} finally {
|
|
1259
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1260
|
+
}
|
|
1261
|
+
});
|
|
1262
|
+
|
|
1263
|
+
it("suppresses native auto-nudge when deep-interview mode state is active without a scoped skill state", async () => {
|
|
1264
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-auto-nudge-deep-interview-mode-"));
|
|
1265
|
+
try {
|
|
1266
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
1267
|
+
await mkdir(stateDir, { recursive: true });
|
|
1268
|
+
await writeJson(join(stateDir, "deep-interview-state.json"), {
|
|
1269
|
+
active: true,
|
|
1270
|
+
mode: "deep-interview",
|
|
1271
|
+
current_phase: "intent-first",
|
|
1272
|
+
});
|
|
1273
|
+
|
|
1274
|
+
const result = await dispatchCodexNativeHook(
|
|
1275
|
+
{
|
|
1276
|
+
hook_event_name: "Stop",
|
|
1277
|
+
cwd,
|
|
1278
|
+
session_id: "sess-stop-auto-mode",
|
|
1279
|
+
thread_id: "thread-stop-auto-mode",
|
|
1280
|
+
turn_id: "turn-stop-auto-mode-1",
|
|
1281
|
+
last_assistant_message: "Would you like me to continue with the next step?",
|
|
1282
|
+
},
|
|
1283
|
+
{ cwd },
|
|
1284
|
+
);
|
|
1285
|
+
|
|
1286
|
+
assert.equal(result.omxEventName, "stop");
|
|
1287
|
+
assert.equal(result.outputJson, null);
|
|
1288
|
+
} finally {
|
|
1289
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1290
|
+
}
|
|
1291
|
+
});
|
|
1292
|
+
|
|
1293
|
+
it("re-fires team Stop output for a later fresh Stop reply while the team is still active", async () => {
|
|
1294
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-refire-"));
|
|
1295
|
+
try {
|
|
1296
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
1297
|
+
await mkdir(stateDir, { recursive: true });
|
|
1298
|
+
await writeJson(join(stateDir, "team-state.json"), {
|
|
1299
|
+
active: true,
|
|
1300
|
+
current_phase: "team-exec",
|
|
1301
|
+
team_name: "review-team",
|
|
1302
|
+
});
|
|
1303
|
+
await writeJson(join(stateDir, "team", "review-team", "phase.json"), {
|
|
1304
|
+
current_phase: "team-verify",
|
|
1305
|
+
max_fix_attempts: 3,
|
|
1306
|
+
current_fix_attempt: 0,
|
|
1307
|
+
transitions: [],
|
|
1308
|
+
updated_at: new Date().toISOString(),
|
|
1309
|
+
});
|
|
1310
|
+
|
|
1311
|
+
await dispatchCodexNativeHook(
|
|
1312
|
+
{
|
|
1313
|
+
hook_event_name: "Stop",
|
|
1314
|
+
cwd,
|
|
1315
|
+
session_id: "sess-stop-team-refire",
|
|
1316
|
+
thread_id: "thread-stop-team-refire",
|
|
1317
|
+
turn_id: "turn-stop-team-refire-1",
|
|
1318
|
+
},
|
|
1319
|
+
{ cwd },
|
|
1320
|
+
);
|
|
1321
|
+
|
|
1322
|
+
const result = await dispatchCodexNativeHook(
|
|
1323
|
+
{
|
|
1324
|
+
hook_event_name: "Stop",
|
|
1325
|
+
cwd,
|
|
1326
|
+
session_id: "sess-stop-team-refire",
|
|
1327
|
+
thread_id: "thread-stop-team-refire",
|
|
1328
|
+
turn_id: "turn-stop-team-refire-2",
|
|
1329
|
+
stop_hook_active: true,
|
|
1330
|
+
},
|
|
1331
|
+
{ cwd },
|
|
1332
|
+
);
|
|
1333
|
+
|
|
1334
|
+
assert.equal(result.omxEventName, "stop");
|
|
1335
|
+
assert.deepEqual(result.outputJson, {
|
|
1336
|
+
decision: "block",
|
|
1337
|
+
reason:
|
|
1338
|
+
`OMX team pipeline is still active (review-team) at phase team-verify; continue coordinating until the team reaches a terminal phase.${TEAM_STOP_COMMIT_GUIDANCE}`,
|
|
1339
|
+
stopReason: "team_team-verify",
|
|
1340
|
+
systemMessage: "OMX team pipeline is still active at phase team-verify.",
|
|
1341
|
+
});
|
|
1342
|
+
} finally {
|
|
1343
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1344
|
+
}
|
|
1345
|
+
});
|
|
931
1346
|
});
|