oh-my-codex 0.19.0 → 0.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/Cargo.lock +6 -6
  2. package/Cargo.toml +1 -1
  3. package/README.md +5 -0
  4. package/dist/cli/__tests__/mission.test.d.ts +2 -0
  5. package/dist/cli/__tests__/mission.test.d.ts.map +1 -0
  6. package/dist/cli/__tests__/mission.test.js +239 -0
  7. package/dist/cli/__tests__/mission.test.js.map +1 -0
  8. package/dist/cli/__tests__/session-scoped-runtime.test.js +67 -0
  9. package/dist/cli/__tests__/session-scoped-runtime.test.js.map +1 -1
  10. package/dist/cli/__tests__/ultragoal.test.js +1 -1
  11. package/dist/cli/index.d.ts +2 -2
  12. package/dist/cli/index.d.ts.map +1 -1
  13. package/dist/cli/index.js +30 -4
  14. package/dist/cli/index.js.map +1 -1
  15. package/dist/cli/mission.d.ts +36 -0
  16. package/dist/cli/mission.d.ts.map +1 -0
  17. package/dist/cli/mission.js +443 -0
  18. package/dist/cli/mission.js.map +1 -0
  19. package/dist/hud/__tests__/state.test.js +31 -6
  20. package/dist/hud/__tests__/state.test.js.map +1 -1
  21. package/dist/hud/state.d.ts.map +1 -1
  22. package/dist/hud/state.js +7 -3
  23. package/dist/hud/state.js.map +1 -1
  24. package/dist/mcp/__tests__/state-paths.test.js +32 -1
  25. package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
  26. package/dist/mcp/state-paths.d.ts +4 -0
  27. package/dist/mcp/state-paths.d.ts.map +1 -1
  28. package/dist/mcp/state-paths.js +6 -15
  29. package/dist/mcp/state-paths.js.map +1 -1
  30. package/dist/ralplan/__tests__/consensus-gate.test.js +193 -0
  31. package/dist/ralplan/__tests__/consensus-gate.test.js.map +1 -1
  32. package/dist/ralplan/consensus-gate.js +24 -3
  33. package/dist/ralplan/consensus-gate.js.map +1 -1
  34. package/dist/scripts/__tests__/codex-native-hook.test.js +330 -0
  35. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  36. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  37. package/dist/scripts/codex-native-hook.js +68 -10
  38. package/dist/scripts/codex-native-hook.js.map +1 -1
  39. package/dist/scripts/generate-catalog-docs.js +1 -0
  40. package/dist/scripts/generate-catalog-docs.js.map +1 -1
  41. package/dist/state/__tests__/operations.test.js +287 -0
  42. package/dist/state/__tests__/operations.test.js.map +1 -1
  43. package/dist/state/__tests__/skill-active.test.js +59 -0
  44. package/dist/state/__tests__/skill-active.test.js.map +1 -1
  45. package/dist/state/operations.d.ts.map +1 -1
  46. package/dist/state/operations.js +8 -6
  47. package/dist/state/operations.js.map +1 -1
  48. package/dist/state/skill-active.js +3 -3
  49. package/dist/state/skill-active.js.map +1 -1
  50. package/dist/ultragoal/__tests__/artifacts.test.js +14 -4
  51. package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
  52. package/dist/ultragoal/artifacts.d.ts.map +1 -1
  53. package/dist/ultragoal/artifacts.js +70 -5
  54. package/dist/ultragoal/artifacts.js.map +1 -1
  55. package/package.json +1 -1
  56. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  57. package/src/scripts/__tests__/codex-native-hook.test.ts +340 -0
  58. package/src/scripts/codex-native-hook.ts +75 -9
  59. package/src/scripts/generate-catalog-docs.ts +1 -0
@@ -7253,6 +7253,79 @@ exit 0
7253
7253
  }
7254
7254
  });
7255
7255
 
7256
+ it("does not self-lock PreToolUse after completed deep-interview handoff leaves Autopilot in deep-interview phase", async () => {
7257
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-deep-interview-completed-autopilot-"));
7258
+ try {
7259
+ const stateDir = join(cwd, ".omx", "state");
7260
+ const sessionId = "sess-di-completed-autopilot";
7261
+ const threadId = "thread-di-completed-autopilot";
7262
+ const sessionDir = join(stateDir, "sessions", sessionId);
7263
+ await mkdir(sessionDir, { recursive: true });
7264
+ await writeJson(join(stateDir, "session.json"), { session_id: sessionId, cwd });
7265
+ await writeJson(join(sessionDir, "skill-active-state.json"), {
7266
+ version: 1,
7267
+ active: true,
7268
+ skill: "autopilot",
7269
+ keyword: "$autopilot",
7270
+ phase: "deep-interview",
7271
+ initialized_mode: "autopilot",
7272
+ initialized_state_path: `.omx/state/sessions/${sessionId}/autopilot-state.json`,
7273
+ session_id: sessionId,
7274
+ thread_id: threadId,
7275
+ active_skills: [{ skill: "autopilot", phase: "deep-interview", active: true, session_id: sessionId, thread_id: threadId }],
7276
+ });
7277
+ await writeJson(join(sessionDir, "deep-interview-state.json"), {
7278
+ active: false,
7279
+ mode: "deep-interview",
7280
+ current_phase: "completed",
7281
+ session_id: sessionId,
7282
+ thread_id: threadId,
7283
+ deep_interview_gate: {
7284
+ status: "complete",
7285
+ handoff_summary: "Requirements were clarified before the Autopilot handoff.",
7286
+ },
7287
+ });
7288
+ await writeJson(join(sessionDir, "autopilot-state.json"), {
7289
+ active: true,
7290
+ mode: "autopilot",
7291
+ current_phase: "deep-interview",
7292
+ session_id: sessionId,
7293
+ thread_id: threadId,
7294
+ });
7295
+
7296
+ const implementationEdit = await dispatchCodexNativeHook(
7297
+ {
7298
+ hook_event_name: "PreToolUse",
7299
+ cwd,
7300
+ session_id: sessionId,
7301
+ thread_id: threadId,
7302
+ tool_name: "Edit",
7303
+ tool_use_id: "tool-di-completed-autopilot-edit",
7304
+ tool_input: { file_path: "src/runtime.ts", old_string: "a", new_string: "b" },
7305
+ },
7306
+ { cwd },
7307
+ );
7308
+ assert.equal(implementationEdit.omxEventName, "pre-tool-use");
7309
+ assert.equal(implementationEdit.outputJson, null);
7310
+
7311
+ const stateRepair = await dispatchCodexNativeHook(
7312
+ {
7313
+ hook_event_name: "PreToolUse",
7314
+ cwd,
7315
+ session_id: sessionId,
7316
+ thread_id: threadId,
7317
+ tool_name: "mcp__omx_state__state_write",
7318
+ tool_use_id: "tool-di-completed-autopilot-state-repair",
7319
+ tool_input: { mode: "autopilot", current_phase: "ralplan", active: true },
7320
+ },
7321
+ { cwd },
7322
+ );
7323
+ assert.equal(stateRepair.outputJson, null);
7324
+ } finally {
7325
+ await rm(cwd, { recursive: true, force: true });
7326
+ }
7327
+ });
7328
+
7256
7329
  it("allows deep-interview artifact and state writes while blocking implementation Bash writes", async () => {
7257
7330
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-deep-interview-artifact-"));
7258
7331
  try {
@@ -16368,6 +16441,79 @@ PY`,
16368
16441
  }
16369
16442
  });
16370
16443
 
16444
+ it("clears stale ralplan Stop cache when authoritative state is terminal inactive", async () => {
16445
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ralplan-terminal-cache-"));
16446
+ try {
16447
+ const stateDir = join(cwd, ".omx", "state");
16448
+ const sessionId = "sess-stop-ralplan-terminal-cache";
16449
+ const threadId = "thread-stop-ralplan-terminal-cache";
16450
+ await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
16451
+ await writeJson(join(stateDir, "session.json"), { session_id: sessionId, cwd });
16452
+ await writeJson(join(stateDir, "skill-active-state.json"), {
16453
+ active: false,
16454
+ skill: "ralplan",
16455
+ phase: "complete",
16456
+ session_id: sessionId,
16457
+ active_skills: [],
16458
+ });
16459
+ await writeJson(join(stateDir, "ralplan-state.json"), {
16460
+ active: false,
16461
+ mode: "ralplan",
16462
+ current_phase: "complete",
16463
+ status: "complete",
16464
+ run_outcome: "complete",
16465
+ session_id: sessionId,
16466
+ cwd,
16467
+ });
16468
+ await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
16469
+ active: false,
16470
+ skill: "ralplan",
16471
+ phase: "complete",
16472
+ session_id: sessionId,
16473
+ active_skills: [],
16474
+ });
16475
+ await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), {
16476
+ active: false,
16477
+ mode: "ralplan",
16478
+ current_phase: "complete",
16479
+ status: "complete",
16480
+ run_outcome: "complete",
16481
+ session_id: sessionId,
16482
+ cwd,
16483
+ });
16484
+ await writeJson(join(stateDir, "native-stop-state.json"), {
16485
+ sessions: {
16486
+ [sessionId]: {
16487
+ last_signature: `skill-stop|${sessionId}|${threadId}|no-message|skill_ralplan_planning_continue_artifact`,
16488
+ updated_at: "2026-07-04T00:00:00.000Z",
16489
+ },
16490
+ [threadId]: {
16491
+ last_signature: `skill-stop|${sessionId}|${threadId}|no-message|skill_ralplan_planning_continue_artifact`,
16492
+ updated_at: "2026-07-04T00:00:00.000Z",
16493
+ },
16494
+ },
16495
+ });
16496
+
16497
+ const result = await dispatchCodexNativeHook(
16498
+ {
16499
+ hook_event_name: "Stop",
16500
+ cwd,
16501
+ session_id: sessionId,
16502
+ thread_id: threadId,
16503
+ stop_hook_active: true,
16504
+ },
16505
+ { cwd },
16506
+ );
16507
+
16508
+ assert.equal(result.omxEventName, "stop");
16509
+ assert.equal(result.outputJson, null);
16510
+ const stopState = JSON.parse(await readFile(join(stateDir, "native-stop-state.json"), "utf-8")) as { sessions?: Record<string, unknown> };
16511
+ assert.deepEqual(stopState.sessions, {});
16512
+ } finally {
16513
+ await rm(cwd, { recursive: true, force: true });
16514
+ }
16515
+ });
16516
+
16371
16517
  it("keeps blocking current session ralplan when canonical root inactive ralplan state lacks project context", async () => {
16372
16518
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ralplan-canonical-root-no-cwd-"));
16373
16519
  try {
@@ -21453,6 +21599,200 @@ PY`,
21453
21599
  }
21454
21600
  });
21455
21601
 
21602
+ it("allows conductor writes from tracked typed native subagents when PreToolUse omits thread_spawn source", async () => {
21603
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-conductor-tracked-subagent-no-source-"));
21604
+ const originalOmxRoot = process.env.OMX_ROOT;
21605
+ const originalOmxStateRoot = process.env.OMX_STATE_ROOT;
21606
+ const originalOmxTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
21607
+ try {
21608
+ process.env.OMX_ROOT = cwd;
21609
+ delete process.env.OMX_STATE_ROOT;
21610
+ delete process.env.OMX_TEAM_STATE_ROOT;
21611
+ const stateDir = join(cwd, ".omx", "state");
21612
+ const sessionId = "sess-conductor-tracked-subagent-no-source";
21613
+ const leaderThreadId = "thread-conductor-leader-no-source";
21614
+ const childThreadId = "thread-conductor-child-no-source";
21615
+ const nowIso = new Date().toISOString();
21616
+ await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
21617
+ await writeJson(join(stateDir, "session.json"), { session_id: sessionId, native_session_id: leaderThreadId });
21618
+ await writeSessionSkillActiveState(stateDir, sessionId, "ultragoal", "executing");
21619
+ await writeJson(join(stateDir, "sessions", sessionId, "ultragoal-state.json"), {
21620
+ active: true,
21621
+ mode: "ultragoal",
21622
+ current_phase: "executing",
21623
+ session_id: sessionId,
21624
+ });
21625
+ await writeJson(join(stateDir, "subagent-tracking.json"), {
21626
+ schemaVersion: 1,
21627
+ sessions: {
21628
+ [sessionId]: {
21629
+ session_id: sessionId,
21630
+ leader_thread_id: leaderThreadId,
21631
+ updated_at: nowIso,
21632
+ threads: {
21633
+ [leaderThreadId]: { thread_id: leaderThreadId, kind: "leader", first_seen_at: nowIso, last_seen_at: nowIso, turn_count: 1 },
21634
+ [childThreadId]: { thread_id: childThreadId, kind: "subagent", mode: "executor", first_seen_at: nowIso, last_seen_at: nowIso, turn_count: 1 },
21635
+ },
21636
+ },
21637
+ },
21638
+ });
21639
+
21640
+ const result = await dispatchCodexNativeHook(
21641
+ {
21642
+ hook_event_name: "PreToolUse",
21643
+ cwd,
21644
+ session_id: sessionId,
21645
+ thread_id: childThreadId,
21646
+ agent_role: "executor",
21647
+ tool_name: "apply_patch",
21648
+ tool_input: { file_path: "configs/ws/kalshi_ws_auth_evidence.json" },
21649
+ },
21650
+ { cwd },
21651
+ );
21652
+
21653
+ assert.equal(result.outputJson, null);
21654
+ } finally {
21655
+ if (originalOmxRoot === undefined) delete process.env.OMX_ROOT;
21656
+ else process.env.OMX_ROOT = originalOmxRoot;
21657
+ if (originalOmxStateRoot === undefined) delete process.env.OMX_STATE_ROOT;
21658
+ else process.env.OMX_STATE_ROOT = originalOmxStateRoot;
21659
+ if (originalOmxTeamStateRoot === undefined) delete process.env.OMX_TEAM_STATE_ROOT;
21660
+ else process.env.OMX_TEAM_STATE_ROOT = originalOmxTeamStateRoot;
21661
+ await rm(cwd, { recursive: true, force: true });
21662
+ }
21663
+ });
21664
+
21665
+ it("blocks conductor writes when corrupt tracker state labels the leader as a subagent", async () => {
21666
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-conductor-corrupt-leader-subagent-"));
21667
+ const originalOmxRoot = process.env.OMX_ROOT;
21668
+ const originalOmxStateRoot = process.env.OMX_STATE_ROOT;
21669
+ const originalOmxTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
21670
+ try {
21671
+ process.env.OMX_ROOT = cwd;
21672
+ delete process.env.OMX_STATE_ROOT;
21673
+ delete process.env.OMX_TEAM_STATE_ROOT;
21674
+ const stateDir = join(cwd, ".omx", "state");
21675
+ const sessionId = "sess-conductor-corrupt-leader-subagent";
21676
+ const leaderThreadId = "thread-conductor-corrupt-leader";
21677
+ const nowIso = new Date().toISOString();
21678
+ await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
21679
+ await writeJson(join(stateDir, "session.json"), { session_id: sessionId, native_session_id: leaderThreadId });
21680
+ await writeSessionSkillActiveState(stateDir, sessionId, "ultragoal", "executing");
21681
+ await writeJson(join(stateDir, "sessions", sessionId, "ultragoal-state.json"), {
21682
+ active: true,
21683
+ mode: "ultragoal",
21684
+ current_phase: "executing",
21685
+ session_id: sessionId,
21686
+ });
21687
+ await writeJson(join(stateDir, "subagent-tracking.json"), {
21688
+ schemaVersion: 1,
21689
+ sessions: {
21690
+ [sessionId]: {
21691
+ session_id: sessionId,
21692
+ leader_thread_id: leaderThreadId,
21693
+ updated_at: nowIso,
21694
+ threads: {
21695
+ [leaderThreadId]: { thread_id: leaderThreadId, kind: "subagent", mode: "executor", first_seen_at: nowIso, last_seen_at: nowIso, turn_count: 1 },
21696
+ },
21697
+ },
21698
+ },
21699
+ });
21700
+
21701
+ const result = await dispatchCodexNativeHook(
21702
+ {
21703
+ hook_event_name: "PreToolUse",
21704
+ cwd,
21705
+ session_id: sessionId,
21706
+ thread_id: leaderThreadId,
21707
+ agent_role: "executor",
21708
+ tool_name: "apply_patch",
21709
+ tool_input: { file_path: "configs/ws/kalshi_ws_auth_evidence.json" },
21710
+ },
21711
+ { cwd },
21712
+ );
21713
+
21714
+ assert.equal(result.outputJson?.decision, "block");
21715
+ assert.match(String(result.outputJson?.reason ?? ""), /Main-root Conductor mode is active \(ultragoal phase: executing\)/);
21716
+ } finally {
21717
+ if (originalOmxRoot === undefined) delete process.env.OMX_ROOT;
21718
+ else process.env.OMX_ROOT = originalOmxRoot;
21719
+ if (originalOmxStateRoot === undefined) delete process.env.OMX_STATE_ROOT;
21720
+ else process.env.OMX_STATE_ROOT = originalOmxStateRoot;
21721
+ if (originalOmxTeamStateRoot === undefined) delete process.env.OMX_TEAM_STATE_ROOT;
21722
+ else process.env.OMX_TEAM_STATE_ROOT = originalOmxTeamStateRoot;
21723
+ await rm(cwd, { recursive: true, force: true });
21724
+ }
21725
+ });
21726
+
21727
+ it("blocks conductor writes when thread_spawn provenance is attached to the leader thread", async () => {
21728
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-conductor-thread-spawn-leader-"));
21729
+ const originalOmxRoot = process.env.OMX_ROOT;
21730
+ const originalOmxStateRoot = process.env.OMX_STATE_ROOT;
21731
+ const originalOmxTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
21732
+ try {
21733
+ process.env.OMX_ROOT = cwd;
21734
+ delete process.env.OMX_STATE_ROOT;
21735
+ delete process.env.OMX_TEAM_STATE_ROOT;
21736
+ const stateDir = join(cwd, ".omx", "state");
21737
+ const sessionId = "sess-conductor-thread-spawn-leader";
21738
+ const leaderThreadId = "thread-conductor-thread-spawn-leader";
21739
+ const nowIso = new Date().toISOString();
21740
+ await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
21741
+ await writeJson(join(stateDir, "session.json"), { session_id: sessionId, native_session_id: leaderThreadId });
21742
+ await writeSessionSkillActiveState(stateDir, sessionId, "ultragoal", "executing");
21743
+ await writeJson(join(stateDir, "sessions", sessionId, "ultragoal-state.json"), {
21744
+ active: true,
21745
+ mode: "ultragoal",
21746
+ current_phase: "executing",
21747
+ session_id: sessionId,
21748
+ });
21749
+ await writeJson(join(stateDir, "subagent-tracking.json"), {
21750
+ schemaVersion: 1,
21751
+ sessions: {
21752
+ [sessionId]: {
21753
+ session_id: sessionId,
21754
+ leader_thread_id: leaderThreadId,
21755
+ updated_at: nowIso,
21756
+ threads: {
21757
+ [leaderThreadId]: { thread_id: leaderThreadId, kind: "leader", first_seen_at: nowIso, last_seen_at: nowIso, turn_count: 1 },
21758
+ },
21759
+ },
21760
+ },
21761
+ });
21762
+
21763
+ const result = await dispatchCodexNativeHook(
21764
+ {
21765
+ hook_event_name: "PreToolUse",
21766
+ cwd,
21767
+ session_id: sessionId,
21768
+ thread_id: leaderThreadId,
21769
+ agent_role: "executor",
21770
+ source: {
21771
+ subagent: {
21772
+ thread_spawn: {
21773
+ parent_thread_id: leaderThreadId,
21774
+ },
21775
+ },
21776
+ },
21777
+ tool_name: "apply_patch",
21778
+ tool_input: { file_path: "configs/ws/kalshi_ws_auth_evidence.json" },
21779
+ },
21780
+ { cwd },
21781
+ );
21782
+
21783
+ assert.equal(result.outputJson?.decision, "block");
21784
+ assert.match(String(result.outputJson?.reason ?? ""), /Main-root Conductor mode is active \(ultragoal phase: executing\)/);
21785
+ } finally {
21786
+ if (originalOmxRoot === undefined) delete process.env.OMX_ROOT;
21787
+ else process.env.OMX_ROOT = originalOmxRoot;
21788
+ if (originalOmxStateRoot === undefined) delete process.env.OMX_STATE_ROOT;
21789
+ else process.env.OMX_STATE_ROOT = originalOmxStateRoot;
21790
+ if (originalOmxTeamStateRoot === undefined) delete process.env.OMX_TEAM_STATE_ROOT;
21791
+ else process.env.OMX_TEAM_STATE_ROOT = originalOmxTeamStateRoot;
21792
+ await rm(cwd, { recursive: true, force: true });
21793
+ }
21794
+ });
21795
+
21456
21796
  it("blocks Main-root ralph conductor source and planning artifact writes while allowing .omx workflow state writes", async () => {
21457
21797
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralph-conductor-write-"));
21458
21798
  try {
@@ -3361,6 +3361,14 @@ function isActiveDeepInterviewPhase(state: Record<string, unknown> | null): bool
3361
3361
  return true;
3362
3362
  }
3363
3363
 
3364
+ function isInactiveCompletedDeepInterviewPhase(state: Record<string, unknown> | null): boolean {
3365
+ if (!state || state.active !== false) return false;
3366
+ const mode = safeString(state.mode).trim();
3367
+ if (mode && mode !== "deep-interview") return false;
3368
+ const phase = safeString(state.current_phase ?? state.currentPhase).trim().toLowerCase();
3369
+ return phase === "complete" || phase === "completed";
3370
+ }
3371
+
3364
3372
  function isActiveRalplanPhase(state: Record<string, unknown> | null): boolean {
3365
3373
  if (!state || state.active !== true) return false;
3366
3374
  const mode = safeString(state.mode).trim();
@@ -6555,6 +6563,7 @@ async function readActiveDeepInterviewStateForPreToolUse(
6555
6563
  ));
6556
6564
  if (hasActiveDeepInterviewSkill) return modeState;
6557
6565
  }
6566
+ if (isInactiveCompletedDeepInterviewPhase(modeState)) return null;
6558
6567
 
6559
6568
  const autopilotState = sessionId
6560
6569
  ? await readStopSessionPinnedState("autopilot-state.json", cwd, sessionId, stateDir)
@@ -6714,7 +6723,7 @@ async function buildRalplanPreToolUseBoundaryOutput(
6714
6723
  resolvedSessionId?: string,
6715
6724
  ): Promise<Record<string, unknown> | null> {
6716
6725
  const sessionId = safeString(resolvedSessionId ?? readPayloadSessionId(payload)).trim();
6717
- if (await hasTrustedTypedSubagentThreadSpawnProvenanceForPreToolUse(payload, cwd, sessionId)) return null;
6726
+ if (await hasTrustedTypedSubagentProvenanceForPreToolUse(payload, cwd, sessionId)) return null;
6718
6727
  const threadId = readPayloadThreadId(payload);
6719
6728
  const activeState = await readActiveRalplanStateForPreToolUse(cwd, stateDir, sessionId, threadId);
6720
6729
  if (!activeState) return null;
@@ -6782,7 +6791,7 @@ async function buildDeepInterviewPreToolUseBoundaryOutput(
6782
6791
  resolvedSessionId?: string,
6783
6792
  ): Promise<Record<string, unknown> | null> {
6784
6793
  const sessionId = safeString(resolvedSessionId ?? readPayloadSessionId(payload)).trim();
6785
- if (await hasTrustedTypedSubagentThreadSpawnProvenanceForPreToolUse(payload, cwd, sessionId)) return null;
6794
+ if (await hasTrustedTypedSubagentProvenanceForPreToolUse(payload, cwd, sessionId)) return null;
6786
6795
  const threadId = readPayloadThreadId(payload);
6787
6796
  const activeState = await readActiveDeepInterviewStateForPreToolUse(cwd, stateDir, sessionId, threadId);
6788
6797
  if (!activeState) return null;
@@ -6950,12 +6959,20 @@ interface ActiveConductorState {
6950
6959
  phase: string;
6951
6960
  }
6952
6961
 
6953
- async function hasTrustedTypedSubagentThreadSpawnProvenanceForPreToolUse(
6962
+ async function hasTrustedTypedSubagentProvenanceForPreToolUse(
6954
6963
  payload: CodexHookPayload,
6955
6964
  cwd: string,
6956
6965
  sessionId: string,
6957
6966
  ): Promise<boolean> {
6958
6967
  if (hasTeamWorkerEnvironment()) return true;
6968
+ if (!isTypedAgentRolePayload(payload)) return false;
6969
+ const trackingState = await readSubagentTrackingState(cwd).catch(() => null);
6970
+ const session = trackingState?.sessions?.[sessionId];
6971
+ if (!session) return false;
6972
+
6973
+ const payloadThreadId = readPayloadThreadId(payload);
6974
+ if (payloadThreadId && isTrustedSubagentThread(session, payloadThreadId)) return true;
6975
+
6959
6976
  const source = safeObject(payload.source);
6960
6977
  const subagent = safeObject(source.subagent);
6961
6978
  const threadSpawn = safeObject(subagent.thread_spawn);
@@ -6966,11 +6983,9 @@ async function hasTrustedTypedSubagentThreadSpawnProvenanceForPreToolUse(
6966
6983
  ?? threadSpawn.leaderThreadId,
6967
6984
  ).trim();
6968
6985
  if (!parentThreadId) return false;
6969
- if (!isTypedAgentRolePayload(payload)) return false;
6970
- const trackingState = await readSubagentTrackingState(cwd).catch(() => null);
6971
- const session = trackingState?.sessions?.[sessionId];
6972
- if (!session) return false;
6973
- return session.leader_thread_id === parentThreadId || parentThreadId in session.threads;
6986
+ const leaderThreadId = safeString(session.leader_thread_id).trim();
6987
+ if (payloadThreadId && leaderThreadId && payloadThreadId === leaderThreadId) return false;
6988
+ return leaderThreadId === parentThreadId || parentThreadId in session.threads;
6974
6989
  }
6975
6990
 
6976
6991
  function isActiveConductorModeState(state: Record<string, unknown> | null, mode: string, sessionId: string): boolean {
@@ -6999,7 +7014,7 @@ async function readActiveMainRootConductorStateForPreToolUse(
6999
7014
  }
7000
7015
  const threadId = readPayloadThreadId(payload);
7001
7016
  if (!sessionId) return null;
7002
- if (await hasTrustedTypedSubagentThreadSpawnProvenanceForPreToolUse(payload, cwd, sessionId)) return null;
7017
+ if (await hasTrustedTypedSubagentProvenanceForPreToolUse(payload, cwd, sessionId)) return null;
7003
7018
 
7004
7019
  const canonicalState = await readVisibleSkillActiveStateForStateDir(stateDir, sessionId);
7005
7020
  if (!canonicalState) return null;
@@ -8031,6 +8046,54 @@ function modeStateHasExplicitMatchingCwd(state: Record<string, unknown>, cwd: st
8031
8046
  }
8032
8047
  }
8033
8048
 
8049
+ async function clearNativeStopSessionEntries(
8050
+ stateDir: string,
8051
+ payload: CodexHookPayload,
8052
+ canonicalSessionId?: string,
8053
+ ): Promise<void> {
8054
+ const statePath = join(stateDir, NATIVE_STOP_STATE_FILE);
8055
+ const state = await readJsonIfExists(statePath);
8056
+ if (!state) return;
8057
+
8058
+ const sessions = safeObject(state.sessions);
8059
+ const keys = new Set(uniqueNonEmpty([
8060
+ readNativeStopSessionKey(payload, canonicalSessionId),
8061
+ canonicalSessionId,
8062
+ readPayloadSessionId(payload),
8063
+ readPayloadThreadId(payload),
8064
+ ]));
8065
+ let changed = false;
8066
+ for (const key of keys) {
8067
+ if (Object.prototype.hasOwnProperty.call(sessions, key)) {
8068
+ delete sessions[key];
8069
+ changed = true;
8070
+ }
8071
+ }
8072
+ if (!changed) return;
8073
+
8074
+ await writeFile(statePath, JSON.stringify({ ...state, sessions }, null, 2));
8075
+ }
8076
+
8077
+ async function hasAuthoritativeInactiveSkillStopState(
8078
+ cwd: string,
8079
+ stateDir: string,
8080
+ skill: string,
8081
+ sessionId: string,
8082
+ threadId: string,
8083
+ ): Promise<boolean> {
8084
+ const sessionModeState = await readStopSessionPinnedState(`${skill}-state.json`, cwd, sessionId, stateDir);
8085
+ if (!sessionModeState || !isTerminalOrInactiveModeState(sessionModeState)) return false;
8086
+
8087
+ const canonicalState = await readVisibleSkillActiveStateForStateDir(stateDir, sessionId);
8088
+ if (canonicalState && !rootSkillStateHasNoActiveSkillForStopContext(canonicalState, skill, sessionId, threadId)) {
8089
+ return false;
8090
+ }
8091
+
8092
+ const rootModeState = await readJsonIfExists(join(stateDir, `${skill}-state.json`));
8093
+ if (!rootModeState) return true;
8094
+ if (!modeStateMatchesSkillStopContext(rootModeState, cwd, sessionId)) return true;
8095
+ return isTerminalOrInactiveModeState(rootModeState);
8096
+ }
8034
8097
  async function readBlockingSkillForStop(
8035
8098
  cwd: string,
8036
8099
  stateDir: string,
@@ -8939,6 +9002,9 @@ async function buildStopHookOutput(
8939
9002
  const suppressParentWorkflowStop = shouldSuppressParentWorkflowStopForSideConversation(payload);
8940
9003
  if (canonicalSessionId) {
8941
9004
  await reconcileStaleRootSkillActiveStateForStop(cwd, stateDir, canonicalSessionId);
9005
+ if (await hasAuthoritativeInactiveSkillStopState(cwd, stateDir, "ralplan", canonicalSessionId, threadId)) {
9006
+ await clearNativeStopSessionEntries(stateDir, payload, canonicalSessionId);
9007
+ }
8942
9008
  }
8943
9009
  if (suppressParentWorkflowStop) {
8944
9010
  return null;
@@ -17,6 +17,7 @@ const docsToScan = [
17
17
  join(root, 'docs', 'skills.html'),
18
18
  join(root, 'docs', 'agents.html'),
19
19
  join(root, 'README.md'),
20
+ join(root, 'CONTRIBUTING.md'),
20
21
  join(root, 'src', 'cli', 'setup.ts'),
21
22
  join(root, 'src', 'cli', 'doctor.ts'),
22
23
  ];