oh-my-codex 0.19.1 → 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 (212) hide show
  1. package/Cargo.lock +6 -6
  2. package/Cargo.toml +1 -1
  3. package/README.md +3 -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__/plugin-marketplace-idempotent.test.js +3 -3
  59. package/dist/cli/__tests__/plugin-marketplace-idempotent.test.js.map +1 -1
  60. package/dist/cli/__tests__/resume.test.js +95 -6
  61. package/dist/cli/__tests__/resume.test.js.map +1 -1
  62. package/dist/cli/__tests__/setup-install-mode.test.js +46 -18
  63. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  64. package/dist/cli/__tests__/setup-prompts-overwrite.test.js +3 -3
  65. package/dist/cli/__tests__/setup-refresh.test.js +29 -7
  66. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  67. package/dist/cli/__tests__/uninstall.test.js +4 -4
  68. package/dist/cli/__tests__/uninstall.test.js.map +1 -1
  69. package/dist/cli/agents.js +1 -1
  70. package/dist/cli/agents.js.map +1 -1
  71. package/dist/cli/capabilities.d.ts +3 -0
  72. package/dist/cli/capabilities.d.ts.map +1 -0
  73. package/dist/cli/capabilities.js +118 -0
  74. package/dist/cli/capabilities.js.map +1 -0
  75. package/dist/cli/doctor.d.ts +1 -1
  76. package/dist/cli/doctor.d.ts.map +1 -1
  77. package/dist/cli/doctor.js +9 -4
  78. package/dist/cli/doctor.js.map +1 -1
  79. package/dist/cli/index.d.ts +7 -4
  80. package/dist/cli/index.d.ts.map +1 -1
  81. package/dist/cli/index.js +77 -19
  82. package/dist/cli/index.js.map +1 -1
  83. package/dist/cli/plugin-marketplace.d.ts +1 -0
  84. package/dist/cli/plugin-marketplace.d.ts.map +1 -1
  85. package/dist/cli/plugin-marketplace.js +31 -0
  86. package/dist/cli/plugin-marketplace.js.map +1 -1
  87. package/dist/cli/setup.d.ts.map +1 -1
  88. package/dist/cli/setup.js +7 -4
  89. package/dist/cli/setup.js.map +1 -1
  90. package/dist/config/__tests__/generator-idempotent.test.js +23 -23
  91. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  92. package/dist/config/__tests__/generator-notify.test.js +9 -9
  93. package/dist/config/__tests__/generator-notify.test.js.map +1 -1
  94. package/dist/config/__tests__/models.test.js +51 -26
  95. package/dist/config/__tests__/models.test.js.map +1 -1
  96. package/dist/config/generator.d.ts +1 -1
  97. package/dist/config/generator.d.ts.map +1 -1
  98. package/dist/config/models.d.ts +16 -6
  99. package/dist/config/models.d.ts.map +1 -1
  100. package/dist/config/models.js +19 -8
  101. package/dist/config/models.js.map +1 -1
  102. package/dist/hooks/__tests__/keyword-detector.test.js +12 -4
  103. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  104. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +1 -1
  105. package/dist/hooks/__tests__/prometheus-strict-contract.test.js +2 -2
  106. package/dist/hooks/__tests__/prometheus-strict-contract.test.js.map +1 -1
  107. package/dist/hooks/__tests__/tmux-hook-engine.test.js +2 -2
  108. package/dist/leader/__tests__/contract.test.js +108 -1
  109. package/dist/leader/__tests__/contract.test.js.map +1 -1
  110. package/dist/leader/contract.d.ts +26 -0
  111. package/dist/leader/contract.d.ts.map +1 -1
  112. package/dist/leader/contract.js +170 -0
  113. package/dist/leader/contract.js.map +1 -1
  114. package/dist/modes/base.d.ts +1 -1
  115. package/dist/modes/base.d.ts.map +1 -1
  116. package/dist/pipeline/__tests__/stages.test.js +27 -1
  117. package/dist/pipeline/__tests__/stages.test.js.map +1 -1
  118. package/dist/pipeline/stages/ralplan.d.ts +2 -1
  119. package/dist/pipeline/stages/ralplan.d.ts.map +1 -1
  120. package/dist/pipeline/stages/ralplan.js +1 -0
  121. package/dist/pipeline/stages/ralplan.js.map +1 -1
  122. package/dist/pipeline/stages/ultragoal.d.ts +5 -1
  123. package/dist/pipeline/stages/ultragoal.d.ts.map +1 -1
  124. package/dist/pipeline/stages/ultragoal.js +14 -4
  125. package/dist/pipeline/stages/ultragoal.js.map +1 -1
  126. package/dist/ralplan/__tests__/runtime.test.js +132 -0
  127. package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
  128. package/dist/ralplan/runtime.d.ts +17 -0
  129. package/dist/ralplan/runtime.d.ts.map +1 -1
  130. package/dist/ralplan/runtime.js +97 -1
  131. package/dist/ralplan/runtime.js.map +1 -1
  132. package/dist/scripts/__tests__/codex-native-hook.test.js +412 -0
  133. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  134. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  135. package/dist/scripts/codex-native-hook.js +238 -12
  136. package/dist/scripts/codex-native-hook.js.map +1 -1
  137. package/dist/state/__tests__/operations.test.js +450 -0
  138. package/dist/state/__tests__/operations.test.js.map +1 -1
  139. package/dist/state/__tests__/workflow-transition.test.js +14 -0
  140. package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
  141. package/dist/state/operations.d.ts.map +1 -1
  142. package/dist/state/operations.js +72 -11
  143. package/dist/state/operations.js.map +1 -1
  144. package/dist/state/terminal-normalization.d.ts +8 -0
  145. package/dist/state/terminal-normalization.d.ts.map +1 -0
  146. package/dist/state/terminal-normalization.js +127 -0
  147. package/dist/state/terminal-normalization.js.map +1 -0
  148. package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
  149. package/dist/state/workflow-transition-reconcile.js +3 -1
  150. package/dist/state/workflow-transition-reconcile.js.map +1 -1
  151. package/dist/team/__tests__/delegation-policy.test.js +2 -2
  152. package/dist/team/__tests__/delegation-policy.test.js.map +1 -1
  153. package/dist/team/__tests__/model-contract.test.js +25 -25
  154. package/dist/team/__tests__/model-contract.test.js.map +1 -1
  155. package/dist/team/__tests__/runtime.test.js +28 -28
  156. package/dist/team/__tests__/runtime.test.js.map +1 -1
  157. package/dist/team/__tests__/scaling.test.js +7 -7
  158. package/dist/team/__tests__/scaling.test.js.map +1 -1
  159. package/dist/team/__tests__/tmux-claude-workers-demo.test.js +5 -5
  160. package/dist/team/__tests__/tmux-claude-workers-demo.test.js.map +1 -1
  161. package/dist/team/__tests__/tmux-session.test.js +12 -12
  162. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  163. package/dist/team/__tests__/worker-bootstrap.test.js +57 -9
  164. package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
  165. package/dist/team/__tests__/worker-runtime-identity.test.js +3 -3
  166. package/dist/team/__tests__/worker-runtime-identity.test.js.map +1 -1
  167. package/dist/team/model-contract.d.ts +1 -1
  168. package/dist/team/model-contract.d.ts.map +1 -1
  169. package/dist/team/runtime.d.ts.map +1 -1
  170. package/dist/team/runtime.js +11 -0
  171. package/dist/team/runtime.js.map +1 -1
  172. package/dist/team/worker-bootstrap.d.ts +2 -0
  173. package/dist/team/worker-bootstrap.d.ts.map +1 -1
  174. package/dist/team/worker-bootstrap.js +6 -1
  175. package/dist/team/worker-bootstrap.js.map +1 -1
  176. package/dist/ultragoal/__tests__/artifacts.test.js +68 -1
  177. package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
  178. package/dist/ultragoal/artifacts.d.ts +5 -1
  179. package/dist/ultragoal/artifacts.d.ts.map +1 -1
  180. package/dist/ultragoal/artifacts.js +41 -9
  181. package/dist/ultragoal/artifacts.js.map +1 -1
  182. package/dist/utils/__tests__/agents-model-table.test.js +7 -7
  183. package/dist/utils/__tests__/agents-model-table.test.js.map +1 -1
  184. package/dist/utils/__tests__/worktree-tool-context.test.d.ts +2 -0
  185. package/dist/utils/__tests__/worktree-tool-context.test.d.ts.map +1 -0
  186. package/dist/utils/__tests__/worktree-tool-context.test.js +118 -0
  187. package/dist/utils/__tests__/worktree-tool-context.test.js.map +1 -0
  188. package/dist/utils/worktree-tool-context.d.ts +25 -0
  189. package/dist/utils/worktree-tool-context.d.ts.map +1 -0
  190. package/dist/utils/worktree-tool-context.js +116 -0
  191. package/dist/utils/worktree-tool-context.js.map +1 -0
  192. package/package.json +1 -1
  193. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  194. package/plugins/oh-my-codex/hooks/codex-native-hook.mjs +96 -10
  195. package/plugins/oh-my-codex/skills/ai-slop-cleaner/SKILL.md +1 -1
  196. package/plugins/oh-my-codex/skills/code-review/SKILL.md +1 -1
  197. package/plugins/oh-my-codex/skills/omx-setup/SKILL.md +5 -5
  198. package/plugins/oh-my-codex/skills/prometheus-strict/SKILL.md +3 -3
  199. package/plugins/oh-my-codex/skills/ralplan/SKILL.md +1 -1
  200. package/plugins/oh-my-codex/skills/team/SKILL.md +1 -1
  201. package/plugins/oh-my-codex/skills/ultrawork/references/agent-tiers.md +2 -2
  202. package/prompts/prometheus-strict-metis.md +1 -1
  203. package/skills/ai-slop-cleaner/SKILL.md +1 -1
  204. package/skills/code-review/SKILL.md +1 -1
  205. package/skills/ecomode/references/agent-tiers.md +1 -1
  206. package/skills/omx-setup/SKILL.md +5 -5
  207. package/skills/prometheus-strict/SKILL.md +3 -3
  208. package/skills/ralplan/SKILL.md +1 -1
  209. package/skills/team/SKILL.md +1 -1
  210. package/skills/ultrawork/references/agent-tiers.md +2 -2
  211. package/src/scripts/__tests__/codex-native-hook.test.ts +477 -0
  212. package/src/scripts/codex-native-hook.ts +272 -11
@@ -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",
@@ -3419,6 +3591,33 @@ function isProtectedPlanningStatePath(relativePath: string): boolean {
3419
3591
  const fileName = relativePath.split("/").pop() ?? "";
3420
3592
  return PROTECTED_PLANNING_STATE_FILE_NAMES.has(fileName);
3421
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
+
3422
3621
 
3423
3622
  function isPlanningTmpRelativePath(relativePath: string): boolean {
3424
3623
  return relativePath === ".omx/tmp" || relativePath.startsWith(".omx/tmp/");
@@ -3439,8 +3638,8 @@ function isAllowedPlanningArtifactPath(
3439
3638
  allowedPrefixes: readonly string[],
3440
3639
  ): boolean {
3441
3640
  const relativePath = normalizePlanningArtifactRelativePath(cwd, rawPath);
3442
- if (!relativePath) return false;
3443
- if (isProtectedPlanningStatePath(relativePath)) return false;
3641
+ if (!relativePath) return isAllowedAuthoritativeRuntimePlanningStatePath(cwd, rawPath);
3642
+ if (isProtectedPlanningStatePath(relativePath)) return isAllowedAuthoritativeRuntimePlanningStatePath(cwd, rawPath);
3444
3643
  if (isPlanningTmpRelativePath(relativePath)) {
3445
3644
  return allowedPrefixes.includes(".omx/tmp") && isAllowedPlanningTmpScratchPath(relativePath);
3446
3645
  }
@@ -5829,6 +6028,55 @@ function isPackageManagerCommandWord(word: string): boolean {
5829
6028
  return base === "npm" || base === "pnpm" || base === "yarn" || base === "npx" || base === "pnpx";
5830
6029
  }
5831
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
+
5832
6080
  function hasDynamicNestedShellExecution(command: string): boolean {
5833
6081
  const commands = splitShellCommandSegments(stripHeredocBodiesForCommandScan(command));
5834
6082
  for (const segment of commands) {
@@ -5851,12 +6099,12 @@ function hasDynamicNestedShellExecution(command: string): boolean {
5851
6099
  if (commandStringIndex === null && (hasUnquotedShellStdinFlowAroundShellWord(words, index) || segment.includes("<<"))) return true;
5852
6100
  if (commandStringIndex !== null) {
5853
6101
  const nestedCommand = words[commandStringIndex] ?? "";
5854
- if (isDynamicNestedCommandString(nestedCommand)) return true;
6102
+ if (isDynamicNestedCommandString(nestedCommand) && !isStructuredUltragoalSteeringShellCommand(nestedCommand)) return true;
5855
6103
  }
5856
6104
  }
5857
6105
  if (word === "eval") {
5858
6106
  const nestedCommand = words.slice(index + 1).join(" ");
5859
- if (isDynamicNestedCommandString(nestedCommand)) return true;
6107
+ if (isDynamicNestedCommandString(nestedCommand) && !isStructuredUltragoalSteeringShellCommand(nestedCommand)) return true;
5860
6108
  }
5861
6109
  }
5862
6110
  }
@@ -7865,6 +8113,14 @@ export async function buildConductorPreToolUseWriteGuardOutput(
7865
8113
  ): Promise<Record<string, unknown> | null> {
7866
8114
  const activeState = await readActiveMainRootConductorStateForPreToolUse(payload, cwd, stateDir, resolvedSessionId);
7867
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
+
7868
8124
 
7869
8125
  const toolName = safeString(payload.tool_name).trim();
7870
8126
  const command = readPreToolUseCommand(payload);
@@ -7891,6 +8147,9 @@ export async function buildConductorPreToolUseWriteGuardOutput(
7891
8147
 
7892
8148
  if (!blocked) return null;
7893
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
+ : "";
7894
8153
  return {
7895
8154
  decision: "block",
7896
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}.`,
@@ -7900,7 +8159,8 @@ export async function buildConductorPreToolUseWriteGuardOutput(
7900
8159
  `${LEADER_CONDUCTOR_GOLDEN_RULE} `
7901
8160
  + "Use specialized agents for source edits and plan/spec authorship. "
7902
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. `
7903
- + "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.",
7904
8164
  },
7905
8165
  };
7906
8166
  }
@@ -9643,6 +9903,7 @@ export async function dispatchCodexNativeHook(
9643
9903
  ?? buildNativePreToolUseOutput(payload);
9644
9904
  } else if (hookEventName === "PostToolUse") {
9645
9905
  await recordNativeSubagentCapacityBlocker(cwd, stateDir, payload).catch(() => {});
9906
+ await recordNativeSubagentSupportBlocker(cwd, stateDir, payload).catch(() => {});
9646
9907
  if (detectMcpTransportFailure(payload)) {
9647
9908
  await markTeamTransportFailure(cwd, payload);
9648
9909
  }