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.
Files changed (245) hide show
  1. package/Cargo.lock +6 -6
  2. package/Cargo.toml +1 -1
  3. package/README.md +8 -1
  4. package/crates/omx-api/src/lib.rs +3 -3
  5. package/crates/omx-api/tests/cli.rs +4 -4
  6. package/crates/omx-explore/src/main.rs +4 -4
  7. package/crates/omx-sparkshell/src/codex_bridge.rs +2 -2
  8. package/dist/agents/__tests__/definitions.test.js +3 -3
  9. package/dist/agents/__tests__/definitions.test.js.map +1 -1
  10. package/dist/agents/__tests__/native-config.test.js +71 -42
  11. package/dist/agents/__tests__/native-config.test.js.map +1 -1
  12. package/dist/agents/definitions.d.ts +1 -1
  13. package/dist/agents/definitions.d.ts.map +1 -1
  14. package/dist/agents/definitions.js +3 -3
  15. package/dist/agents/definitions.js.map +1 -1
  16. package/dist/agents/native-config.d.ts +2 -2
  17. package/dist/agents/native-config.d.ts.map +1 -1
  18. package/dist/agents/native-config.js +7 -5
  19. package/dist/agents/native-config.js.map +1 -1
  20. package/dist/autopilot/__tests__/planner-routing.test.js +19 -12
  21. package/dist/autopilot/__tests__/planner-routing.test.js.map +1 -1
  22. package/dist/autopilot/__tests__/ralplan-gate.test.js +151 -0
  23. package/dist/autopilot/__tests__/ralplan-gate.test.js.map +1 -1
  24. package/dist/autopilot/planner-routing.d.ts.map +1 -1
  25. package/dist/autopilot/planner-routing.js +4 -2
  26. package/dist/autopilot/planner-routing.js.map +1 -1
  27. package/dist/autopilot/ralplan-gate.d.ts +1 -0
  28. package/dist/autopilot/ralplan-gate.d.ts.map +1 -1
  29. package/dist/autopilot/ralplan-gate.js +38 -2
  30. package/dist/autopilot/ralplan-gate.js.map +1 -1
  31. package/dist/autoresearch/runtime.d.ts +2 -0
  32. package/dist/autoresearch/runtime.d.ts.map +1 -1
  33. package/dist/autoresearch/runtime.js +8 -0
  34. package/dist/autoresearch/runtime.js.map +1 -1
  35. package/dist/capabilities/__tests__/lockfile.test.d.ts +2 -0
  36. package/dist/capabilities/__tests__/lockfile.test.d.ts.map +1 -0
  37. package/dist/capabilities/__tests__/lockfile.test.js +115 -0
  38. package/dist/capabilities/__tests__/lockfile.test.js.map +1 -0
  39. package/dist/capabilities/lockfile.d.ts +102 -0
  40. package/dist/capabilities/lockfile.d.ts.map +1 -0
  41. package/dist/capabilities/lockfile.js +334 -0
  42. package/dist/capabilities/lockfile.js.map +1 -0
  43. package/dist/cli/__tests__/agents.test.js +5 -5
  44. package/dist/cli/__tests__/agents.test.js.map +1 -1
  45. package/dist/cli/__tests__/capabilities.test.d.ts +2 -0
  46. package/dist/cli/__tests__/capabilities.test.d.ts.map +1 -0
  47. package/dist/cli/__tests__/capabilities.test.js +135 -0
  48. package/dist/cli/__tests__/capabilities.test.js.map +1 -0
  49. package/dist/cli/__tests__/codex-plugin-layout.test.js +237 -6
  50. package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
  51. package/dist/cli/__tests__/doctor-context-window-warning.test.js +9 -9
  52. package/dist/cli/__tests__/doctor-context-window-warning.test.js.map +1 -1
  53. package/dist/cli/__tests__/doctor-invalid-config.test.js +1 -1
  54. package/dist/cli/__tests__/doctor-spark-routing.test.js +22 -6
  55. package/dist/cli/__tests__/doctor-spark-routing.test.js.map +1 -1
  56. package/dist/cli/__tests__/index.test.js +53 -18
  57. package/dist/cli/__tests__/index.test.js.map +1 -1
  58. package/dist/cli/__tests__/mission.test.d.ts +2 -0
  59. package/dist/cli/__tests__/mission.test.d.ts.map +1 -0
  60. package/dist/cli/__tests__/mission.test.js +239 -0
  61. package/dist/cli/__tests__/mission.test.js.map +1 -0
  62. package/dist/cli/__tests__/plugin-marketplace-idempotent.test.js +3 -3
  63. package/dist/cli/__tests__/plugin-marketplace-idempotent.test.js.map +1 -1
  64. package/dist/cli/__tests__/resume.test.js +95 -6
  65. package/dist/cli/__tests__/resume.test.js.map +1 -1
  66. package/dist/cli/__tests__/session-scoped-runtime.test.js +67 -0
  67. package/dist/cli/__tests__/session-scoped-runtime.test.js.map +1 -1
  68. package/dist/cli/__tests__/setup-install-mode.test.js +46 -18
  69. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  70. package/dist/cli/__tests__/setup-prompts-overwrite.test.js +3 -3
  71. package/dist/cli/__tests__/setup-refresh.test.js +29 -7
  72. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  73. package/dist/cli/__tests__/ultragoal.test.js +1 -1
  74. package/dist/cli/__tests__/uninstall.test.js +4 -4
  75. package/dist/cli/__tests__/uninstall.test.js.map +1 -1
  76. package/dist/cli/agents.js +1 -1
  77. package/dist/cli/agents.js.map +1 -1
  78. package/dist/cli/capabilities.d.ts +3 -0
  79. package/dist/cli/capabilities.d.ts.map +1 -0
  80. package/dist/cli/capabilities.js +118 -0
  81. package/dist/cli/capabilities.js.map +1 -0
  82. package/dist/cli/doctor.d.ts +1 -1
  83. package/dist/cli/doctor.d.ts.map +1 -1
  84. package/dist/cli/doctor.js +9 -4
  85. package/dist/cli/doctor.js.map +1 -1
  86. package/dist/cli/index.d.ts +7 -4
  87. package/dist/cli/index.d.ts.map +1 -1
  88. package/dist/cli/index.js +107 -23
  89. package/dist/cli/index.js.map +1 -1
  90. package/dist/cli/mission.d.ts +36 -0
  91. package/dist/cli/mission.d.ts.map +1 -0
  92. package/dist/cli/mission.js +443 -0
  93. package/dist/cli/mission.js.map +1 -0
  94. package/dist/cli/plugin-marketplace.d.ts +1 -0
  95. package/dist/cli/plugin-marketplace.d.ts.map +1 -1
  96. package/dist/cli/plugin-marketplace.js +31 -0
  97. package/dist/cli/plugin-marketplace.js.map +1 -1
  98. package/dist/cli/setup.d.ts.map +1 -1
  99. package/dist/cli/setup.js +7 -4
  100. package/dist/cli/setup.js.map +1 -1
  101. package/dist/config/__tests__/generator-idempotent.test.js +23 -23
  102. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  103. package/dist/config/__tests__/generator-notify.test.js +9 -9
  104. package/dist/config/__tests__/generator-notify.test.js.map +1 -1
  105. package/dist/config/__tests__/models.test.js +51 -26
  106. package/dist/config/__tests__/models.test.js.map +1 -1
  107. package/dist/config/generator.d.ts +1 -1
  108. package/dist/config/generator.d.ts.map +1 -1
  109. package/dist/config/models.d.ts +16 -6
  110. package/dist/config/models.d.ts.map +1 -1
  111. package/dist/config/models.js +19 -8
  112. package/dist/config/models.js.map +1 -1
  113. package/dist/hooks/__tests__/keyword-detector.test.js +12 -4
  114. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  115. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +1 -1
  116. package/dist/hooks/__tests__/prometheus-strict-contract.test.js +2 -2
  117. package/dist/hooks/__tests__/prometheus-strict-contract.test.js.map +1 -1
  118. package/dist/hooks/__tests__/tmux-hook-engine.test.js +2 -2
  119. package/dist/hud/__tests__/state.test.js +31 -6
  120. package/dist/hud/__tests__/state.test.js.map +1 -1
  121. package/dist/hud/state.d.ts.map +1 -1
  122. package/dist/hud/state.js +7 -3
  123. package/dist/hud/state.js.map +1 -1
  124. package/dist/leader/__tests__/contract.test.js +108 -1
  125. package/dist/leader/__tests__/contract.test.js.map +1 -1
  126. package/dist/leader/contract.d.ts +26 -0
  127. package/dist/leader/contract.d.ts.map +1 -1
  128. package/dist/leader/contract.js +170 -0
  129. package/dist/leader/contract.js.map +1 -1
  130. package/dist/mcp/__tests__/state-paths.test.js +32 -1
  131. package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
  132. package/dist/mcp/state-paths.d.ts +4 -0
  133. package/dist/mcp/state-paths.d.ts.map +1 -1
  134. package/dist/mcp/state-paths.js +6 -15
  135. package/dist/mcp/state-paths.js.map +1 -1
  136. package/dist/modes/base.d.ts +1 -1
  137. package/dist/modes/base.d.ts.map +1 -1
  138. package/dist/pipeline/__tests__/stages.test.js +27 -1
  139. package/dist/pipeline/__tests__/stages.test.js.map +1 -1
  140. package/dist/pipeline/stages/ralplan.d.ts +2 -1
  141. package/dist/pipeline/stages/ralplan.d.ts.map +1 -1
  142. package/dist/pipeline/stages/ralplan.js +1 -0
  143. package/dist/pipeline/stages/ralplan.js.map +1 -1
  144. package/dist/pipeline/stages/ultragoal.d.ts +5 -1
  145. package/dist/pipeline/stages/ultragoal.d.ts.map +1 -1
  146. package/dist/pipeline/stages/ultragoal.js +14 -4
  147. package/dist/pipeline/stages/ultragoal.js.map +1 -1
  148. package/dist/ralplan/__tests__/consensus-gate.test.js +193 -0
  149. package/dist/ralplan/__tests__/consensus-gate.test.js.map +1 -1
  150. package/dist/ralplan/__tests__/runtime.test.js +132 -0
  151. package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
  152. package/dist/ralplan/consensus-gate.js +24 -3
  153. package/dist/ralplan/consensus-gate.js.map +1 -1
  154. package/dist/ralplan/runtime.d.ts +17 -0
  155. package/dist/ralplan/runtime.d.ts.map +1 -1
  156. package/dist/ralplan/runtime.js +97 -1
  157. package/dist/ralplan/runtime.js.map +1 -1
  158. package/dist/scripts/__tests__/codex-native-hook.test.js +762 -20
  159. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  160. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  161. package/dist/scripts/codex-native-hook.js +306 -22
  162. package/dist/scripts/codex-native-hook.js.map +1 -1
  163. package/dist/scripts/generate-catalog-docs.js +1 -0
  164. package/dist/scripts/generate-catalog-docs.js.map +1 -1
  165. package/dist/state/__tests__/operations.test.js +737 -0
  166. package/dist/state/__tests__/operations.test.js.map +1 -1
  167. package/dist/state/__tests__/skill-active.test.js +59 -0
  168. package/dist/state/__tests__/skill-active.test.js.map +1 -1
  169. package/dist/state/__tests__/workflow-transition.test.js +14 -0
  170. package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
  171. package/dist/state/operations.d.ts.map +1 -1
  172. package/dist/state/operations.js +80 -17
  173. package/dist/state/operations.js.map +1 -1
  174. package/dist/state/skill-active.js +3 -3
  175. package/dist/state/skill-active.js.map +1 -1
  176. package/dist/state/terminal-normalization.d.ts +8 -0
  177. package/dist/state/terminal-normalization.d.ts.map +1 -0
  178. package/dist/state/terminal-normalization.js +127 -0
  179. package/dist/state/terminal-normalization.js.map +1 -0
  180. package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
  181. package/dist/state/workflow-transition-reconcile.js +3 -1
  182. package/dist/state/workflow-transition-reconcile.js.map +1 -1
  183. package/dist/team/__tests__/delegation-policy.test.js +2 -2
  184. package/dist/team/__tests__/delegation-policy.test.js.map +1 -1
  185. package/dist/team/__tests__/model-contract.test.js +25 -25
  186. package/dist/team/__tests__/model-contract.test.js.map +1 -1
  187. package/dist/team/__tests__/runtime.test.js +28 -28
  188. package/dist/team/__tests__/runtime.test.js.map +1 -1
  189. package/dist/team/__tests__/scaling.test.js +7 -7
  190. package/dist/team/__tests__/scaling.test.js.map +1 -1
  191. package/dist/team/__tests__/tmux-claude-workers-demo.test.js +5 -5
  192. package/dist/team/__tests__/tmux-claude-workers-demo.test.js.map +1 -1
  193. package/dist/team/__tests__/tmux-session.test.js +12 -12
  194. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  195. package/dist/team/__tests__/worker-bootstrap.test.js +57 -9
  196. package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
  197. package/dist/team/__tests__/worker-runtime-identity.test.js +3 -3
  198. package/dist/team/__tests__/worker-runtime-identity.test.js.map +1 -1
  199. package/dist/team/model-contract.d.ts +1 -1
  200. package/dist/team/model-contract.d.ts.map +1 -1
  201. package/dist/team/runtime.d.ts.map +1 -1
  202. package/dist/team/runtime.js +11 -0
  203. package/dist/team/runtime.js.map +1 -1
  204. package/dist/team/worker-bootstrap.d.ts +2 -0
  205. package/dist/team/worker-bootstrap.d.ts.map +1 -1
  206. package/dist/team/worker-bootstrap.js +6 -1
  207. package/dist/team/worker-bootstrap.js.map +1 -1
  208. package/dist/ultragoal/__tests__/artifacts.test.js +82 -5
  209. package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
  210. package/dist/ultragoal/artifacts.d.ts +5 -1
  211. package/dist/ultragoal/artifacts.d.ts.map +1 -1
  212. package/dist/ultragoal/artifacts.js +111 -14
  213. package/dist/ultragoal/artifacts.js.map +1 -1
  214. package/dist/utils/__tests__/agents-model-table.test.js +7 -7
  215. package/dist/utils/__tests__/agents-model-table.test.js.map +1 -1
  216. package/dist/utils/__tests__/worktree-tool-context.test.d.ts +2 -0
  217. package/dist/utils/__tests__/worktree-tool-context.test.d.ts.map +1 -0
  218. package/dist/utils/__tests__/worktree-tool-context.test.js +118 -0
  219. package/dist/utils/__tests__/worktree-tool-context.test.js.map +1 -0
  220. package/dist/utils/worktree-tool-context.d.ts +25 -0
  221. package/dist/utils/worktree-tool-context.d.ts.map +1 -0
  222. package/dist/utils/worktree-tool-context.js +116 -0
  223. package/dist/utils/worktree-tool-context.js.map +1 -0
  224. package/package.json +1 -1
  225. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  226. package/plugins/oh-my-codex/hooks/codex-native-hook.mjs +96 -10
  227. package/plugins/oh-my-codex/skills/ai-slop-cleaner/SKILL.md +1 -1
  228. package/plugins/oh-my-codex/skills/code-review/SKILL.md +1 -1
  229. package/plugins/oh-my-codex/skills/omx-setup/SKILL.md +5 -5
  230. package/plugins/oh-my-codex/skills/prometheus-strict/SKILL.md +3 -3
  231. package/plugins/oh-my-codex/skills/ralplan/SKILL.md +1 -1
  232. package/plugins/oh-my-codex/skills/team/SKILL.md +1 -1
  233. package/plugins/oh-my-codex/skills/ultrawork/references/agent-tiers.md +2 -2
  234. package/prompts/prometheus-strict-metis.md +1 -1
  235. package/skills/ai-slop-cleaner/SKILL.md +1 -1
  236. package/skills/code-review/SKILL.md +1 -1
  237. package/skills/ecomode/references/agent-tiers.md +1 -1
  238. package/skills/omx-setup/SKILL.md +5 -5
  239. package/skills/prometheus-strict/SKILL.md +3 -3
  240. package/skills/ralplan/SKILL.md +1 -1
  241. package/skills/team/SKILL.md +1 -1
  242. package/skills/ultrawork/references/agent-tiers.md +2 -2
  243. package/src/scripts/__tests__/codex-native-hook.test.ts +824 -7
  244. package/src/scripts/codex-native-hook.ts +347 -20
  245. package/src/scripts/generate-catalog-docs.ts +1 -0
@@ -18,6 +18,7 @@ import {
18
18
  import {
19
19
  isTrustedSubagentThread,
20
20
  readSubagentSessionSummary,
21
+ readSubagentSessionLedger,
21
22
  readSubagentTrackingState,
22
23
  recordSubagentTurnForSession,
23
24
  } from "../subagents/tracker.js";
@@ -94,9 +95,14 @@ import {
94
95
  CONDUCTOR_ORCHESTRATION_METADATA_PREFIXES,
95
96
  LEADER_CONDUCTOR_BLOCK,
96
97
  LEADER_CONDUCTOR_REUSE_AND_LEDGER_GUIDANCE,
98
+ NATIVE_SUBAGENT_SUPPORT_BLOCKER_FILE,
97
99
  actionKindForConductorArtifact,
98
100
  authorizeConductorAction,
101
+ buildUnsupportedNativeSubagentGuidance,
99
102
  classifyConductorArtifactKind,
103
+ isUnsupportedNativeSubagentEvidence,
104
+ resolveNativeSubagentSupportStatus,
105
+ type NativeSubagentUnsupportedReason,
100
106
  } from "../leader/contract.js";
101
107
  import { readRunState } from "../runtime/run-state.js";
102
108
  import { evaluateRalphCompletionAuditEvidence, isRalphCompletePhase } from "../ralph/completion-audit.js";
@@ -1792,6 +1798,101 @@ async function ensureOmxLocalIgnoreEntry(cwd: string): Promise<{ changed: boolea
1792
1798
  return { changed: true, excludePath };
1793
1799
  }
1794
1800
 
1801
+ function readSessionStartSource(payload: CodexHookPayload | undefined): string {
1802
+ return safeString(payload?.source ?? payload?.session_start_source ?? payload?.sessionStartSource).trim().toLowerCase();
1803
+ }
1804
+
1805
+ function shouldBuildSubagentReopenContext(options: {
1806
+ hookEventName?: CodexHookEventName | null;
1807
+ payload?: CodexHookPayload;
1808
+ }): boolean {
1809
+ if (options.hookEventName !== "SessionStart") return false;
1810
+ const source = readSessionStartSource(options.payload);
1811
+ return source === "startup" || source === "resume";
1812
+ }
1813
+
1814
+ function formatSubagentLedgerMetadata(entry: {
1815
+ role?: string;
1816
+ laneId?: string;
1817
+ scope?: string;
1818
+ status?: string;
1819
+ lastHandoffSummary?: string;
1820
+ resumeFailureReason?: string;
1821
+ }): string {
1822
+ const metadata = [
1823
+ entry.role ? `role: ${entry.role}` : null,
1824
+ entry.laneId ? `lane: ${entry.laneId}` : null,
1825
+ entry.scope ? `scope: ${entry.scope}` : null,
1826
+ entry.status ? `status: ${entry.status}` : null,
1827
+ entry.lastHandoffSummary ? `handoff: ${entry.lastHandoffSummary.slice(0, 120)}` : null,
1828
+ entry.resumeFailureReason ? `last failure: ${entry.resumeFailureReason.slice(0, 120)}` : null,
1829
+ ].filter((item): item is string => Boolean(item));
1830
+ return metadata.length > 0 ? ` (${metadata.join("; ")})` : "";
1831
+ }
1832
+
1833
+ async function buildPersistedSubagentReopenContext(
1834
+ cwd: string,
1835
+ sessionId: string,
1836
+ options: {
1837
+ hookEventName?: CodexHookEventName | null;
1838
+ payload?: CodexHookPayload;
1839
+ },
1840
+ ): Promise<string | null> {
1841
+ if (!shouldBuildSubagentReopenContext(options)) return null;
1842
+
1843
+ const ledger = await readSubagentSessionLedger(cwd, sessionId).catch(() => null);
1844
+ if (!ledger || ledger.savedSubagents.length === 0) return null;
1845
+
1846
+ const source = readSessionStartSource(options.payload);
1847
+ const reopenTargets = ledger.resumeTargets.filter((entry) => entry.status !== "unavailable");
1848
+ const unavailableTargets = ledger.unavailableSubagents;
1849
+ const failedTargets = ledger.savedSubagents.filter((entry) => entry.resumeFailedAt || entry.resumeFailureReason);
1850
+ const nowIso = new Date().toISOString();
1851
+
1852
+ await Promise.all(reopenTargets.map((entry) => recordSubagentTurnForSession(cwd, {
1853
+ sessionId,
1854
+ threadId: entry.threadId,
1855
+ kind: "subagent",
1856
+ role: entry.role,
1857
+ laneId: entry.laneId,
1858
+ scope: entry.scope,
1859
+ agentNickname: entry.agentNickname,
1860
+ status: entry.status,
1861
+ resumeRequestedAt: nowIso,
1862
+ preserveCompletionEvidence: true,
1863
+ }).catch(() => null)));
1864
+
1865
+ const lines = [
1866
+ "[Persisted subagent reopen]",
1867
+ `- SessionStart source: ${source}; saved subagent ids found: ${ledger.savedSubagents.length}.`,
1868
+ ];
1869
+
1870
+ if (reopenTargets.length > 0) {
1871
+ lines.push("- Reopen these persisted subagents by id before continuing work or spawning any same-role/same-lane replacement:");
1872
+ for (const entry of reopenTargets.slice(0, 12)) {
1873
+ lines.push(` - resume_agent(${JSON.stringify(entry.agentId)})${formatSubagentLedgerMetadata(entry)}`);
1874
+ }
1875
+ if (reopenTargets.length > 12) {
1876
+ lines.push(` - ... ${reopenTargets.length - 12} more saved subagent id(s) omitted from this compact SessionStart context; consult .omx/state/subagent-tracking.json before spawning replacements.`);
1877
+ }
1878
+ } else {
1879
+ lines.push("- No compatible saved subagent id is currently marked reopenable; do not spawn a replacement merely because reopen was unavailable.");
1880
+ }
1881
+
1882
+ lines.push("- Silver rule: when follow-up work targets an existing role/lane, reuse the matching reopened id; avoid duplicate same-type subagent spawns.");
1883
+ lines.push("- If resume_agent fails, surface a clear warning with the id and reason, then continue in the root or another compatible existing lane; do not spawn a new agent solely because reopen failed.");
1884
+
1885
+ const warningEntries = [...new Map([...unavailableTargets, ...failedTargets].map((entry) => [entry.agentId, entry])).values()];
1886
+ if (warningEntries.length > 0) {
1887
+ lines.push("- Reopen warnings:");
1888
+ for (const entry of warningEntries.slice(0, 8)) {
1889
+ lines.push(` - ${entry.agentId}${formatSubagentLedgerMetadata(entry)}`);
1890
+ }
1891
+ }
1892
+
1893
+ return lines.join("\n");
1894
+ }
1895
+
1795
1896
  async function buildSessionStartContext(
1796
1897
  cwd: string,
1797
1898
  sessionId: string,
@@ -1901,6 +2002,14 @@ async function buildSessionStartContext(
1901
2002
  sections.push(`[Subagents]\n- active subagent threads: ${subagentSummary.activeSubagentThreadIds.length}`);
1902
2003
  }
1903
2004
 
2005
+ const persistedSubagentReopenContext = await buildPersistedSubagentReopenContext(cwd, sessionId, {
2006
+ hookEventName: options.hookEventName,
2007
+ payload: options.payload,
2008
+ });
2009
+ if (persistedSubagentReopenContext) {
2010
+ sections.push(persistedSubagentReopenContext);
2011
+ }
2012
+
1904
2013
  return sections.length > 0 ? sections.join("\n\n") : null;
1905
2014
  }
1906
2015
 
@@ -2111,12 +2220,23 @@ function buildSkillStateCliInstruction(mode: string, statePath: string): string
2111
2220
 
2112
2221
  function buildAutopilotPromptActivationNote(
2113
2222
  skillState?: SkillActiveState | null,
2114
- options: { markedQuestionAnswer?: boolean; cwd?: string } = {},
2223
+ options: { markedQuestionAnswer?: boolean; cwd?: string; payload?: CodexHookPayload; sessionId?: string } = {},
2115
2224
  ): string | null {
2116
2225
  if (skillState?.initialized_mode !== "autopilot") return null;
2117
2226
  const teamHandoff = readTeamModeConfig(options.cwd).enabled
2118
2227
  ? " (+ $team if needed)"
2119
2228
  : "";
2229
+ const stateDir = getBaseStateDir(options.cwd);
2230
+ const nativeSubagentSupport = resolveNativeSubagentSupportStatus({
2231
+ payload: options.payload,
2232
+ persistedSupportBlocker: readJsonSyncIfExists(nativeSubagentSupportBlockerPath(stateDir)),
2233
+ persistedCapacityBlocker: readJsonSyncIfExists(nativeSubagentCapacityBlockerPath(stateDir)),
2234
+ cwd: options.cwd,
2235
+ sessionId: options.sessionId,
2236
+ });
2237
+ const conductorGuidance = nativeSubagentSupport.status === "unsupported"
2238
+ ? buildUnsupportedNativeSubagentGuidance(nativeSubagentSupport)
2239
+ : `${LEADER_CONDUCTOR_BLOCK} ${LEADER_CONDUCTOR_REUSE_AND_LEDGER_GUIDANCE}`;
2120
2240
  return [
2121
2241
  `Autopilot protocol: the durable default chain is $deep-interview -> $ralplan -> $ultragoal${teamHandoff} -> $code-review -> $ultraqa (deep-interview -> ralplan -> ultragoal -> code-review -> ultraqa).`,
2122
2242
  "Start/resume at current_phase=deep-interview unless the task is clear and bounded; if deep-interview is intentionally skipped, persist and state an explicit deep_interview_gate.skip_reason before moving to ralplan.",
@@ -2128,8 +2248,7 @@ function buildAutopilotPromptActivationNote(
2128
2248
  "The ralplan phase is not complete until Planner output has been reviewed sequentially by Architect and then Critic; do not hand off to Ultragoal or implementation until the ralplan state/artifact records both ralplan_architect_review and ralplan_critic_review with approval or an explicit blocker.",
2129
2249
  "Do not silently fall back to ordinary $plan/ralplan-only handling; keep autopilot-state.json, skill-active-state.json, HUD/statusline, and Codex goal-mode handoff guidance visible while the workflow is active.",
2130
2250
  "When Codex goal tools are available, call get_goal/create_goal only from the active thread handoff and treat the active goal as the completion contract until code-review and ultraqa are clean.",
2131
- LEADER_CONDUCTOR_BLOCK,
2132
- LEADER_CONDUCTOR_REUSE_AND_LEDGER_GUIDANCE,
2251
+ conductorGuidance,
2133
2252
  ].filter(Boolean).join(" ");
2134
2253
  }
2135
2254
 
@@ -2161,7 +2280,7 @@ function buildAdditionalContextMessage(
2161
2280
  : null;
2162
2281
  const deepInterviewConfigPromptActivationNote = buildDeepInterviewConfigInstruction(cwd, skillState);
2163
2282
  const markedQuestionAnswer = /^\s*\[omx question answered\]/i.test(prompt);
2164
- const autopilotPromptActivationNote = buildAutopilotPromptActivationNote(skillState, { markedQuestionAnswer, cwd });
2283
+ const autopilotPromptActivationNote = buildAutopilotPromptActivationNote(skillState, { markedQuestionAnswer, cwd, payload, sessionId: safeString(skillState?.session_id).trim() });
2165
2284
  return [
2166
2285
  `OMX native UserPromptSubmit continued active workflow skill "${continuedSkill}".`,
2167
2286
  promptPriorityMessage,
@@ -2187,7 +2306,7 @@ function buildAdditionalContextMessage(
2187
2306
  ? buildDeepInterviewQuestionBridgeInstruction(cwd, payload)
2188
2307
  : null;
2189
2308
  const deepInterviewConfigPromptActivationNote = buildDeepInterviewConfigInstruction(cwd, skillState);
2190
- const autopilotPromptActivationNote = buildAutopilotPromptActivationNote(skillState, { markedQuestionAnswer: true, cwd });
2309
+ const autopilotPromptActivationNote = buildAutopilotPromptActivationNote(skillState, { markedQuestionAnswer: true, cwd, payload, sessionId: safeString(skillState?.session_id).trim() });
2191
2310
  return [
2192
2311
  `OMX native UserPromptSubmit continued active workflow skill "${continuedSkill}"; workflow-like tokens inside the marked omx question answer are treated as answer text, not a new workflow activation.`,
2193
2312
  promptPriorityMessage,
@@ -2220,7 +2339,7 @@ function buildAdditionalContextMessage(
2220
2339
  const ultragoalPromptActivationNote = match.skill === "ultragoal"
2221
2340
  ? "Ultragoal protocol: use `omx ultragoal create-goals` / `complete-goals` / `checkpoint` for `.omx/ultragoal` artifacts, then use Codex goal model tools only from the active agent handoff (`get_goal`, `create_goal`, `update_goal`) and never overwrite a different active Codex goal. Ultragoal does not call `/goal clear`; for multiple sequential ultragoal runs in one Codex session/thread, manually clear the completed Codex goal in the UI before creating the next aggregate goal."
2222
2341
  : null;
2223
- const autopilotPromptActivationNote = buildAutopilotPromptActivationNote(skillState, { cwd });
2342
+ const autopilotPromptActivationNote = buildAutopilotPromptActivationNote(skillState, { cwd, payload, sessionId: safeString(skillState?.session_id).trim() });
2224
2343
  const combinedTransitionMessage = (() => {
2225
2344
  if (!skillState?.transition_message) return null;
2226
2345
  if (matches.length <= 1 || activeSkills.length <= 1) return skillState.transition_message;
@@ -3094,6 +3213,18 @@ function nativeSubagentCapacityBlockerPath(stateDir: string): string {
3094
3213
  return join(stateDir, NATIVE_SUBAGENT_CAPACITY_BLOCKER_FILE);
3095
3214
  }
3096
3215
 
3216
+ function nativeSubagentSupportBlockerPath(stateDir: string): string {
3217
+ return join(stateDir, NATIVE_SUBAGENT_SUPPORT_BLOCKER_FILE);
3218
+ }
3219
+
3220
+ function readJsonSyncIfExists(path: string): Record<string, unknown> | null {
3221
+ try {
3222
+ return JSON.parse(readFileSync(path, "utf-8")) as Record<string, unknown>;
3223
+ } catch {
3224
+ return null;
3225
+ }
3226
+ }
3227
+
3097
3228
  function stringifyUnknown(value: unknown): string {
3098
3229
  if (typeof value === "string") return value;
3099
3230
  if (value === undefined || value === null) return "";
@@ -3121,6 +3252,45 @@ function isNativeSubagentCapacityFailure(payload: CodexHookPayload): boolean {
3121
3252
  return !toolName || /(?:spawn_agent|multi_agent|subagent|collab|agent)/i.test(toolName);
3122
3253
  }
3123
3254
 
3255
+ function nativeSubagentFailureReason(payload: CodexHookPayload): NativeSubagentUnsupportedReason | null {
3256
+ const evidence = payloadEvidenceText(payload);
3257
+ const toolName = safeString(payload.tool_name).trim();
3258
+ if (toolName && !/(?:spawn_agent|multi_agent|subagent|collab|agent)/i.test(toolName)) return null;
3259
+ if (/\bagent thread limit reached\b/i.test(evidence)) return null;
3260
+ if (/\bnative subagents? (?:unsupported|disabled|not enabled|unavailable|not found)\b/i.test(evidence)) return "native_subagents_unsupported";
3261
+ if (/\bmulti_agent_v1\b/i.test(evidence) && /\b(?:unavailable|unknown tool|disabled|not enabled|not found|unsupported)\b/i.test(evidence)) return "multi_agent_v1_unavailable";
3262
+ if (/\b(?:unknown tool|tool not found|not enabled|disabled|unavailable|unsupported)\b/i.test(evidence)) return "multi_agent_v1_unavailable";
3263
+ return null;
3264
+ }
3265
+
3266
+ function summarizeNativeSubagentSupportFailure(text: string): string {
3267
+ const normalized = text.replace(/\s+/g, " ").trim();
3268
+ return (normalized || "native subagent support unavailable").slice(0, 500);
3269
+ }
3270
+
3271
+ async function recordNativeSubagentSupportBlocker(
3272
+ cwd: string,
3273
+ stateDir: string,
3274
+ payload: CodexHookPayload,
3275
+ ): Promise<void> {
3276
+ const reason = nativeSubagentFailureReason(payload);
3277
+ if (!reason) return;
3278
+ const nowIso = new Date().toISOString();
3279
+ await mkdir(stateDir, { recursive: true });
3280
+ await writeFile(nativeSubagentSupportBlockerPath(stateDir), JSON.stringify({
3281
+ schema_version: 1,
3282
+ status: "unsupported",
3283
+ reason,
3284
+ ...(readPayloadSessionId(payload) ? { session_id: readPayloadSessionId(payload) } : {}),
3285
+ ...(readPayloadThreadId(payload) ? { thread_id: readPayloadThreadId(payload) } : {}),
3286
+ ...(readPayloadTurnId(payload) ? { turn_id: readPayloadTurnId(payload) } : {}),
3287
+ ...(safeString(payload.tool_name).trim() ? { tool_name: safeString(payload.tool_name).trim() } : {}),
3288
+ evidence: summarizeNativeSubagentSupportFailure(payloadEvidenceText(payload)),
3289
+ observed_at: nowIso,
3290
+ cwd,
3291
+ }, null, 2));
3292
+ }
3293
+
3124
3294
  function summarizeCapacityFailure(text: string): string {
3125
3295
  const normalized = text.replace(/\s+/g, " ").trim();
3126
3296
  if (!normalized) return "agent thread limit reached";
@@ -3329,6 +3499,8 @@ const PROTECTED_PLANNING_STATE_FILE_NAMES = new Set([
3329
3499
  "ralplan-state.json",
3330
3500
  "skill-active-state.json",
3331
3501
  ]);
3502
+ const RUNTIME_SESSION_ID_SEGMENT_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;
3503
+
3332
3504
 
3333
3505
  const PLANNING_MODE_IMPLEMENTATION_TOOL_NAMES = new Set([
3334
3506
  "Write",
@@ -3361,6 +3533,14 @@ function isActiveDeepInterviewPhase(state: Record<string, unknown> | null): bool
3361
3533
  return true;
3362
3534
  }
3363
3535
 
3536
+ function isInactiveCompletedDeepInterviewPhase(state: Record<string, unknown> | null): boolean {
3537
+ if (!state || state.active !== false) return false;
3538
+ const mode = safeString(state.mode).trim();
3539
+ if (mode && mode !== "deep-interview") return false;
3540
+ const phase = safeString(state.current_phase ?? state.currentPhase).trim().toLowerCase();
3541
+ return phase === "complete" || phase === "completed";
3542
+ }
3543
+
3364
3544
  function isActiveRalplanPhase(state: Record<string, unknown> | null): boolean {
3365
3545
  if (!state || state.active !== true) return false;
3366
3546
  const mode = safeString(state.mode).trim();
@@ -3411,6 +3591,33 @@ function isProtectedPlanningStatePath(relativePath: string): boolean {
3411
3591
  const fileName = relativePath.split("/").pop() ?? "";
3412
3592
  return PROTECTED_PLANNING_STATE_FILE_NAMES.has(fileName);
3413
3593
  }
3594
+ function isAllowedAuthoritativeRuntimePlanningStatePath(cwd: string, rawPath: string): boolean {
3595
+ const trimmed = rawPath.trim().replace(/^['"]|['"]$/g, "");
3596
+ if (!trimmed || trimmed.includes("\0")) return false;
3597
+
3598
+ let targetPath: string;
3599
+ let baseStateDir: string;
3600
+ try {
3601
+ targetPath = resolve(cwd, trimmed);
3602
+ baseStateDir = resolve(getBaseStateDir(cwd));
3603
+ } catch {
3604
+ return false;
3605
+ }
3606
+
3607
+ const localStateDir = resolve(cwd, ".omx", "state");
3608
+ if (baseStateDir === localStateDir) return false;
3609
+
3610
+ const relativeToBase = relative(baseStateDir, targetPath).replace(/\\/g, "/");
3611
+ if (!relativeToBase || relativeToBase.startsWith("..") || relativeToBase.startsWith("/")) return false;
3612
+
3613
+ const segments = relativeToBase.split("/");
3614
+ if (segments.length !== 3 || segments[0] !== "sessions") return false;
3615
+ const sessionId = segments[1] ?? "";
3616
+ const fileName = segments[2] ?? "";
3617
+ return RUNTIME_SESSION_ID_SEGMENT_PATTERN.test(sessionId)
3618
+ && PROTECTED_PLANNING_STATE_FILE_NAMES.has(fileName);
3619
+ }
3620
+
3414
3621
 
3415
3622
  function isPlanningTmpRelativePath(relativePath: string): boolean {
3416
3623
  return relativePath === ".omx/tmp" || relativePath.startsWith(".omx/tmp/");
@@ -3431,8 +3638,8 @@ function isAllowedPlanningArtifactPath(
3431
3638
  allowedPrefixes: readonly string[],
3432
3639
  ): boolean {
3433
3640
  const relativePath = normalizePlanningArtifactRelativePath(cwd, rawPath);
3434
- if (!relativePath) return false;
3435
- if (isProtectedPlanningStatePath(relativePath)) return false;
3641
+ if (!relativePath) return isAllowedAuthoritativeRuntimePlanningStatePath(cwd, rawPath);
3642
+ if (isProtectedPlanningStatePath(relativePath)) return isAllowedAuthoritativeRuntimePlanningStatePath(cwd, rawPath);
3436
3643
  if (isPlanningTmpRelativePath(relativePath)) {
3437
3644
  return allowedPrefixes.includes(".omx/tmp") && isAllowedPlanningTmpScratchPath(relativePath);
3438
3645
  }
@@ -5821,6 +6028,55 @@ function isPackageManagerCommandWord(word: string): boolean {
5821
6028
  return base === "npm" || base === "pnpm" || base === "yarn" || base === "npx" || base === "pnpx";
5822
6029
  }
5823
6030
 
6031
+ function isStructuredUltragoalSteeringShellCommand(command: string): boolean {
6032
+ const segments = splitShellCommandSegments(stripHeredocBodiesForCommandScan(command));
6033
+ let sawStructuredSteer = false;
6034
+
6035
+ for (const segment of segments) {
6036
+ const words = tokenizeShellWords(segment);
6037
+ let commandStart = true;
6038
+ let inForHeader = false;
6039
+
6040
+ for (let index = 0; index < words.length; index += 1) {
6041
+ const word = words[index] ?? "";
6042
+ if (!word) continue;
6043
+ if (isShellCommandSeparator(word)) {
6044
+ commandStart = true;
6045
+ continue;
6046
+ }
6047
+ if (inForHeader) {
6048
+ if (word === "do") {
6049
+ inForHeader = false;
6050
+ commandStart = true;
6051
+ }
6052
+ continue;
6053
+ }
6054
+ if (isShellGroupingSyntaxWord(word)) continue;
6055
+ if (commandStart && isEnvironmentAssignmentWord(word)) continue;
6056
+ if (commandStart && CONDUCTOR_BASH_COMPOUND_SYNTAX_WORDS.has(word)) {
6057
+ if (word === "for") inForHeader = true;
6058
+ continue;
6059
+ }
6060
+ if (commandStart && word.startsWith("-")) continue;
6061
+ if (!commandStart) continue;
6062
+
6063
+ const commandName = commandNameFromShellWord(word);
6064
+ if (commandName === "read" || commandName === "true" || commandName === ":") {
6065
+ commandStart = false;
6066
+ continue;
6067
+ }
6068
+ if (commandName === "omx" && words[index + 1] === "ultragoal" && words[index + 2] === "steer") {
6069
+ sawStructuredSteer = true;
6070
+ commandStart = false;
6071
+ continue;
6072
+ }
6073
+ return false;
6074
+ }
6075
+ }
6076
+
6077
+ return sawStructuredSteer;
6078
+ }
6079
+
5824
6080
  function hasDynamicNestedShellExecution(command: string): boolean {
5825
6081
  const commands = splitShellCommandSegments(stripHeredocBodiesForCommandScan(command));
5826
6082
  for (const segment of commands) {
@@ -5843,12 +6099,12 @@ function hasDynamicNestedShellExecution(command: string): boolean {
5843
6099
  if (commandStringIndex === null && (hasUnquotedShellStdinFlowAroundShellWord(words, index) || segment.includes("<<"))) return true;
5844
6100
  if (commandStringIndex !== null) {
5845
6101
  const nestedCommand = words[commandStringIndex] ?? "";
5846
- if (isDynamicNestedCommandString(nestedCommand)) return true;
6102
+ if (isDynamicNestedCommandString(nestedCommand) && !isStructuredUltragoalSteeringShellCommand(nestedCommand)) return true;
5847
6103
  }
5848
6104
  }
5849
6105
  if (word === "eval") {
5850
6106
  const nestedCommand = words.slice(index + 1).join(" ");
5851
- if (isDynamicNestedCommandString(nestedCommand)) return true;
6107
+ if (isDynamicNestedCommandString(nestedCommand) && !isStructuredUltragoalSteeringShellCommand(nestedCommand)) return true;
5852
6108
  }
5853
6109
  }
5854
6110
  }
@@ -6555,6 +6811,7 @@ async function readActiveDeepInterviewStateForPreToolUse(
6555
6811
  ));
6556
6812
  if (hasActiveDeepInterviewSkill) return modeState;
6557
6813
  }
6814
+ if (isInactiveCompletedDeepInterviewPhase(modeState)) return null;
6558
6815
 
6559
6816
  const autopilotState = sessionId
6560
6817
  ? await readStopSessionPinnedState("autopilot-state.json", cwd, sessionId, stateDir)
@@ -6714,7 +6971,7 @@ async function buildRalplanPreToolUseBoundaryOutput(
6714
6971
  resolvedSessionId?: string,
6715
6972
  ): Promise<Record<string, unknown> | null> {
6716
6973
  const sessionId = safeString(resolvedSessionId ?? readPayloadSessionId(payload)).trim();
6717
- if (await hasTrustedTypedSubagentThreadSpawnProvenanceForPreToolUse(payload, cwd, sessionId)) return null;
6974
+ if (await hasTrustedTypedSubagentProvenanceForPreToolUse(payload, cwd, sessionId)) return null;
6718
6975
  const threadId = readPayloadThreadId(payload);
6719
6976
  const activeState = await readActiveRalplanStateForPreToolUse(cwd, stateDir, sessionId, threadId);
6720
6977
  if (!activeState) return null;
@@ -6782,7 +7039,7 @@ async function buildDeepInterviewPreToolUseBoundaryOutput(
6782
7039
  resolvedSessionId?: string,
6783
7040
  ): Promise<Record<string, unknown> | null> {
6784
7041
  const sessionId = safeString(resolvedSessionId ?? readPayloadSessionId(payload)).trim();
6785
- if (await hasTrustedTypedSubagentThreadSpawnProvenanceForPreToolUse(payload, cwd, sessionId)) return null;
7042
+ if (await hasTrustedTypedSubagentProvenanceForPreToolUse(payload, cwd, sessionId)) return null;
6786
7043
  const threadId = readPayloadThreadId(payload);
6787
7044
  const activeState = await readActiveDeepInterviewStateForPreToolUse(cwd, stateDir, sessionId, threadId);
6788
7045
  if (!activeState) return null;
@@ -6950,12 +7207,20 @@ interface ActiveConductorState {
6950
7207
  phase: string;
6951
7208
  }
6952
7209
 
6953
- async function hasTrustedTypedSubagentThreadSpawnProvenanceForPreToolUse(
7210
+ async function hasTrustedTypedSubagentProvenanceForPreToolUse(
6954
7211
  payload: CodexHookPayload,
6955
7212
  cwd: string,
6956
7213
  sessionId: string,
6957
7214
  ): Promise<boolean> {
6958
7215
  if (hasTeamWorkerEnvironment()) return true;
7216
+ if (!isTypedAgentRolePayload(payload)) return false;
7217
+ const trackingState = await readSubagentTrackingState(cwd).catch(() => null);
7218
+ const session = trackingState?.sessions?.[sessionId];
7219
+ if (!session) return false;
7220
+
7221
+ const payloadThreadId = readPayloadThreadId(payload);
7222
+ if (payloadThreadId && isTrustedSubagentThread(session, payloadThreadId)) return true;
7223
+
6959
7224
  const source = safeObject(payload.source);
6960
7225
  const subagent = safeObject(source.subagent);
6961
7226
  const threadSpawn = safeObject(subagent.thread_spawn);
@@ -6966,11 +7231,9 @@ async function hasTrustedTypedSubagentThreadSpawnProvenanceForPreToolUse(
6966
7231
  ?? threadSpawn.leaderThreadId,
6967
7232
  ).trim();
6968
7233
  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;
7234
+ const leaderThreadId = safeString(session.leader_thread_id).trim();
7235
+ if (payloadThreadId && leaderThreadId && payloadThreadId === leaderThreadId) return false;
7236
+ return leaderThreadId === parentThreadId || parentThreadId in session.threads;
6974
7237
  }
6975
7238
 
6976
7239
  function isActiveConductorModeState(state: Record<string, unknown> | null, mode: string, sessionId: string): boolean {
@@ -6999,7 +7262,7 @@ async function readActiveMainRootConductorStateForPreToolUse(
6999
7262
  }
7000
7263
  const threadId = readPayloadThreadId(payload);
7001
7264
  if (!sessionId) return null;
7002
- if (await hasTrustedTypedSubagentThreadSpawnProvenanceForPreToolUse(payload, cwd, sessionId)) return null;
7265
+ if (await hasTrustedTypedSubagentProvenanceForPreToolUse(payload, cwd, sessionId)) return null;
7003
7266
 
7004
7267
  const canonicalState = await readVisibleSkillActiveStateForStateDir(stateDir, sessionId);
7005
7268
  if (!canonicalState) return null;
@@ -7850,6 +8113,14 @@ export async function buildConductorPreToolUseWriteGuardOutput(
7850
8113
  ): Promise<Record<string, unknown> | null> {
7851
8114
  const activeState = await readActiveMainRootConductorStateForPreToolUse(payload, cwd, stateDir, resolvedSessionId);
7852
8115
  if (!activeState) return null;
8116
+ const nativeSubagentSupport = resolveNativeSubagentSupportStatus({
8117
+ payload,
8118
+ persistedSupportBlocker: await readJsonIfExists(nativeSubagentSupportBlockerPath(stateDir)),
8119
+ persistedCapacityBlocker: await readJsonIfExists(nativeSubagentCapacityBlockerPath(stateDir)),
8120
+ cwd,
8121
+ sessionId: resolvedSessionId || readPayloadSessionId(payload),
8122
+ });
8123
+
7853
8124
 
7854
8125
  const toolName = safeString(payload.tool_name).trim();
7855
8126
  const command = readPreToolUseCommand(payload);
@@ -7876,6 +8147,9 @@ export async function buildConductorPreToolUseWriteGuardOutput(
7876
8147
 
7877
8148
  if (!blocked) return null;
7878
8149
 
8150
+ const unsupportedNativeGuidance = isUnsupportedNativeSubagentEvidence(nativeSubagentSupport)
8151
+ ? ` ${buildUnsupportedNativeSubagentGuidance(nativeSubagentSupport)} Treat the active conductor workflow as blocked/cancelled for native delegation recovery; do not call multi_agent_v1.close_agent.`
8152
+ : "";
7879
8153
  return {
7880
8154
  decision: "block",
7881
8155
  reason: `Main-root Conductor mode is active (${activeState.mode} phase: ${formatPhase(activeState.phase, "active")}); direct plan/code writes are blocked and must be delegated; ${blockedDetail}.`,
@@ -7885,7 +8159,8 @@ export async function buildConductorPreToolUseWriteGuardOutput(
7885
8159
  `${LEADER_CONDUCTOR_GOLDEN_RULE} `
7886
8160
  + "Use specialized agents for source edits and plan/spec authorship. "
7887
8161
  + `Main-root Conductor may write only orchestration metadata/transport/ledger artifacts under ${CONDUCTOR_ORCHESTRATION_METADATA_PREFIXES.join(", ")}; path location alone is not authorization for substantive deliverables. `
7888
- + "Autopilot rework and typed subagent/worker lanes are exempt from this guard.",
8162
+ + unsupportedNativeGuidance
8163
+ + " Autopilot rework and typed subagent/worker lanes are exempt from this guard.",
7889
8164
  },
7890
8165
  };
7891
8166
  }
@@ -8031,6 +8306,54 @@ function modeStateHasExplicitMatchingCwd(state: Record<string, unknown>, cwd: st
8031
8306
  }
8032
8307
  }
8033
8308
 
8309
+ async function clearNativeStopSessionEntries(
8310
+ stateDir: string,
8311
+ payload: CodexHookPayload,
8312
+ canonicalSessionId?: string,
8313
+ ): Promise<void> {
8314
+ const statePath = join(stateDir, NATIVE_STOP_STATE_FILE);
8315
+ const state = await readJsonIfExists(statePath);
8316
+ if (!state) return;
8317
+
8318
+ const sessions = safeObject(state.sessions);
8319
+ const keys = new Set(uniqueNonEmpty([
8320
+ readNativeStopSessionKey(payload, canonicalSessionId),
8321
+ canonicalSessionId,
8322
+ readPayloadSessionId(payload),
8323
+ readPayloadThreadId(payload),
8324
+ ]));
8325
+ let changed = false;
8326
+ for (const key of keys) {
8327
+ if (Object.prototype.hasOwnProperty.call(sessions, key)) {
8328
+ delete sessions[key];
8329
+ changed = true;
8330
+ }
8331
+ }
8332
+ if (!changed) return;
8333
+
8334
+ await writeFile(statePath, JSON.stringify({ ...state, sessions }, null, 2));
8335
+ }
8336
+
8337
+ async function hasAuthoritativeInactiveSkillStopState(
8338
+ cwd: string,
8339
+ stateDir: string,
8340
+ skill: string,
8341
+ sessionId: string,
8342
+ threadId: string,
8343
+ ): Promise<boolean> {
8344
+ const sessionModeState = await readStopSessionPinnedState(`${skill}-state.json`, cwd, sessionId, stateDir);
8345
+ if (!sessionModeState || !isTerminalOrInactiveModeState(sessionModeState)) return false;
8346
+
8347
+ const canonicalState = await readVisibleSkillActiveStateForStateDir(stateDir, sessionId);
8348
+ if (canonicalState && !rootSkillStateHasNoActiveSkillForStopContext(canonicalState, skill, sessionId, threadId)) {
8349
+ return false;
8350
+ }
8351
+
8352
+ const rootModeState = await readJsonIfExists(join(stateDir, `${skill}-state.json`));
8353
+ if (!rootModeState) return true;
8354
+ if (!modeStateMatchesSkillStopContext(rootModeState, cwd, sessionId)) return true;
8355
+ return isTerminalOrInactiveModeState(rootModeState);
8356
+ }
8034
8357
  async function readBlockingSkillForStop(
8035
8358
  cwd: string,
8036
8359
  stateDir: string,
@@ -8939,6 +9262,9 @@ async function buildStopHookOutput(
8939
9262
  const suppressParentWorkflowStop = shouldSuppressParentWorkflowStopForSideConversation(payload);
8940
9263
  if (canonicalSessionId) {
8941
9264
  await reconcileStaleRootSkillActiveStateForStop(cwd, stateDir, canonicalSessionId);
9265
+ if (await hasAuthoritativeInactiveSkillStopState(cwd, stateDir, "ralplan", canonicalSessionId, threadId)) {
9266
+ await clearNativeStopSessionEntries(stateDir, payload, canonicalSessionId);
9267
+ }
8942
9268
  }
8943
9269
  if (suppressParentWorkflowStop) {
8944
9270
  return null;
@@ -9577,6 +9903,7 @@ export async function dispatchCodexNativeHook(
9577
9903
  ?? buildNativePreToolUseOutput(payload);
9578
9904
  } else if (hookEventName === "PostToolUse") {
9579
9905
  await recordNativeSubagentCapacityBlocker(cwd, stateDir, payload).catch(() => {});
9906
+ await recordNativeSubagentSupportBlocker(cwd, stateDir, payload).catch(() => {});
9580
9907
  if (detectMcpTransportFailure(payload)) {
9581
9908
  await markTeamTransportFailure(cwd, payload);
9582
9909
  }
@@ -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
  ];