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.
- package/Cargo.lock +6 -6
- package/Cargo.toml +1 -1
- package/README.md +3 -1
- package/crates/omx-api/src/lib.rs +3 -3
- package/crates/omx-api/tests/cli.rs +4 -4
- package/crates/omx-explore/src/main.rs +4 -4
- package/crates/omx-sparkshell/src/codex_bridge.rs +2 -2
- package/dist/agents/__tests__/definitions.test.js +3 -3
- package/dist/agents/__tests__/definitions.test.js.map +1 -1
- package/dist/agents/__tests__/native-config.test.js +71 -42
- package/dist/agents/__tests__/native-config.test.js.map +1 -1
- package/dist/agents/definitions.d.ts +1 -1
- package/dist/agents/definitions.d.ts.map +1 -1
- package/dist/agents/definitions.js +3 -3
- package/dist/agents/definitions.js.map +1 -1
- package/dist/agents/native-config.d.ts +2 -2
- package/dist/agents/native-config.d.ts.map +1 -1
- package/dist/agents/native-config.js +7 -5
- package/dist/agents/native-config.js.map +1 -1
- package/dist/autopilot/__tests__/planner-routing.test.js +19 -12
- package/dist/autopilot/__tests__/planner-routing.test.js.map +1 -1
- package/dist/autopilot/__tests__/ralplan-gate.test.js +151 -0
- package/dist/autopilot/__tests__/ralplan-gate.test.js.map +1 -1
- package/dist/autopilot/planner-routing.d.ts.map +1 -1
- package/dist/autopilot/planner-routing.js +4 -2
- package/dist/autopilot/planner-routing.js.map +1 -1
- package/dist/autopilot/ralplan-gate.d.ts +1 -0
- package/dist/autopilot/ralplan-gate.d.ts.map +1 -1
- package/dist/autopilot/ralplan-gate.js +38 -2
- package/dist/autopilot/ralplan-gate.js.map +1 -1
- package/dist/autoresearch/runtime.d.ts +2 -0
- package/dist/autoresearch/runtime.d.ts.map +1 -1
- package/dist/autoresearch/runtime.js +8 -0
- package/dist/autoresearch/runtime.js.map +1 -1
- package/dist/capabilities/__tests__/lockfile.test.d.ts +2 -0
- package/dist/capabilities/__tests__/lockfile.test.d.ts.map +1 -0
- package/dist/capabilities/__tests__/lockfile.test.js +115 -0
- package/dist/capabilities/__tests__/lockfile.test.js.map +1 -0
- package/dist/capabilities/lockfile.d.ts +102 -0
- package/dist/capabilities/lockfile.d.ts.map +1 -0
- package/dist/capabilities/lockfile.js +334 -0
- package/dist/capabilities/lockfile.js.map +1 -0
- package/dist/cli/__tests__/agents.test.js +5 -5
- package/dist/cli/__tests__/agents.test.js.map +1 -1
- package/dist/cli/__tests__/capabilities.test.d.ts +2 -0
- package/dist/cli/__tests__/capabilities.test.d.ts.map +1 -0
- package/dist/cli/__tests__/capabilities.test.js +135 -0
- package/dist/cli/__tests__/capabilities.test.js.map +1 -0
- package/dist/cli/__tests__/codex-plugin-layout.test.js +237 -6
- package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-context-window-warning.test.js +9 -9
- package/dist/cli/__tests__/doctor-context-window-warning.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-invalid-config.test.js +1 -1
- package/dist/cli/__tests__/doctor-spark-routing.test.js +22 -6
- package/dist/cli/__tests__/doctor-spark-routing.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +53 -18
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/plugin-marketplace-idempotent.test.js +3 -3
- package/dist/cli/__tests__/plugin-marketplace-idempotent.test.js.map +1 -1
- package/dist/cli/__tests__/resume.test.js +95 -6
- package/dist/cli/__tests__/resume.test.js.map +1 -1
- package/dist/cli/__tests__/setup-install-mode.test.js +46 -18
- package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
- package/dist/cli/__tests__/setup-prompts-overwrite.test.js +3 -3
- package/dist/cli/__tests__/setup-refresh.test.js +29 -7
- package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
- package/dist/cli/__tests__/uninstall.test.js +4 -4
- package/dist/cli/__tests__/uninstall.test.js.map +1 -1
- package/dist/cli/agents.js +1 -1
- package/dist/cli/agents.js.map +1 -1
- package/dist/cli/capabilities.d.ts +3 -0
- package/dist/cli/capabilities.d.ts.map +1 -0
- package/dist/cli/capabilities.js +118 -0
- package/dist/cli/capabilities.js.map +1 -0
- package/dist/cli/doctor.d.ts +1 -1
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +9 -4
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.d.ts +7 -4
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +77 -19
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/plugin-marketplace.d.ts +1 -0
- package/dist/cli/plugin-marketplace.d.ts.map +1 -1
- package/dist/cli/plugin-marketplace.js +31 -0
- package/dist/cli/plugin-marketplace.js.map +1 -1
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +7 -4
- package/dist/cli/setup.js.map +1 -1
- package/dist/config/__tests__/generator-idempotent.test.js +23 -23
- package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
- package/dist/config/__tests__/generator-notify.test.js +9 -9
- package/dist/config/__tests__/generator-notify.test.js.map +1 -1
- package/dist/config/__tests__/models.test.js +51 -26
- package/dist/config/__tests__/models.test.js.map +1 -1
- package/dist/config/generator.d.ts +1 -1
- package/dist/config/generator.d.ts.map +1 -1
- package/dist/config/models.d.ts +16 -6
- package/dist/config/models.d.ts.map +1 -1
- package/dist/config/models.js +19 -8
- package/dist/config/models.js.map +1 -1
- package/dist/hooks/__tests__/keyword-detector.test.js +12 -4
- package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +1 -1
- package/dist/hooks/__tests__/prometheus-strict-contract.test.js +2 -2
- package/dist/hooks/__tests__/prometheus-strict-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/tmux-hook-engine.test.js +2 -2
- package/dist/leader/__tests__/contract.test.js +108 -1
- package/dist/leader/__tests__/contract.test.js.map +1 -1
- package/dist/leader/contract.d.ts +26 -0
- package/dist/leader/contract.d.ts.map +1 -1
- package/dist/leader/contract.js +170 -0
- package/dist/leader/contract.js.map +1 -1
- package/dist/modes/base.d.ts +1 -1
- package/dist/modes/base.d.ts.map +1 -1
- package/dist/pipeline/__tests__/stages.test.js +27 -1
- package/dist/pipeline/__tests__/stages.test.js.map +1 -1
- package/dist/pipeline/stages/ralplan.d.ts +2 -1
- package/dist/pipeline/stages/ralplan.d.ts.map +1 -1
- package/dist/pipeline/stages/ralplan.js +1 -0
- package/dist/pipeline/stages/ralplan.js.map +1 -1
- package/dist/pipeline/stages/ultragoal.d.ts +5 -1
- package/dist/pipeline/stages/ultragoal.d.ts.map +1 -1
- package/dist/pipeline/stages/ultragoal.js +14 -4
- package/dist/pipeline/stages/ultragoal.js.map +1 -1
- package/dist/ralplan/__tests__/runtime.test.js +132 -0
- package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
- package/dist/ralplan/runtime.d.ts +17 -0
- package/dist/ralplan/runtime.d.ts.map +1 -1
- package/dist/ralplan/runtime.js +97 -1
- package/dist/ralplan/runtime.js.map +1 -1
- package/dist/scripts/__tests__/codex-native-hook.test.js +412 -0
- package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
- package/dist/scripts/codex-native-hook.d.ts.map +1 -1
- package/dist/scripts/codex-native-hook.js +238 -12
- package/dist/scripts/codex-native-hook.js.map +1 -1
- package/dist/state/__tests__/operations.test.js +450 -0
- package/dist/state/__tests__/operations.test.js.map +1 -1
- package/dist/state/__tests__/workflow-transition.test.js +14 -0
- package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
- package/dist/state/operations.d.ts.map +1 -1
- package/dist/state/operations.js +72 -11
- package/dist/state/operations.js.map +1 -1
- package/dist/state/terminal-normalization.d.ts +8 -0
- package/dist/state/terminal-normalization.d.ts.map +1 -0
- package/dist/state/terminal-normalization.js +127 -0
- package/dist/state/terminal-normalization.js.map +1 -0
- package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
- package/dist/state/workflow-transition-reconcile.js +3 -1
- package/dist/state/workflow-transition-reconcile.js.map +1 -1
- package/dist/team/__tests__/delegation-policy.test.js +2 -2
- package/dist/team/__tests__/delegation-policy.test.js.map +1 -1
- package/dist/team/__tests__/model-contract.test.js +25 -25
- package/dist/team/__tests__/model-contract.test.js.map +1 -1
- package/dist/team/__tests__/runtime.test.js +28 -28
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/scaling.test.js +7 -7
- package/dist/team/__tests__/scaling.test.js.map +1 -1
- package/dist/team/__tests__/tmux-claude-workers-demo.test.js +5 -5
- package/dist/team/__tests__/tmux-claude-workers-demo.test.js.map +1 -1
- package/dist/team/__tests__/tmux-session.test.js +12 -12
- package/dist/team/__tests__/tmux-session.test.js.map +1 -1
- package/dist/team/__tests__/worker-bootstrap.test.js +57 -9
- package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
- package/dist/team/__tests__/worker-runtime-identity.test.js +3 -3
- package/dist/team/__tests__/worker-runtime-identity.test.js.map +1 -1
- package/dist/team/model-contract.d.ts +1 -1
- package/dist/team/model-contract.d.ts.map +1 -1
- package/dist/team/runtime.d.ts.map +1 -1
- package/dist/team/runtime.js +11 -0
- package/dist/team/runtime.js.map +1 -1
- package/dist/team/worker-bootstrap.d.ts +2 -0
- package/dist/team/worker-bootstrap.d.ts.map +1 -1
- package/dist/team/worker-bootstrap.js +6 -1
- package/dist/team/worker-bootstrap.js.map +1 -1
- package/dist/ultragoal/__tests__/artifacts.test.js +68 -1
- package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
- package/dist/ultragoal/artifacts.d.ts +5 -1
- package/dist/ultragoal/artifacts.d.ts.map +1 -1
- package/dist/ultragoal/artifacts.js +41 -9
- package/dist/ultragoal/artifacts.js.map +1 -1
- package/dist/utils/__tests__/agents-model-table.test.js +7 -7
- package/dist/utils/__tests__/agents-model-table.test.js.map +1 -1
- package/dist/utils/__tests__/worktree-tool-context.test.d.ts +2 -0
- package/dist/utils/__tests__/worktree-tool-context.test.d.ts.map +1 -0
- package/dist/utils/__tests__/worktree-tool-context.test.js +118 -0
- package/dist/utils/__tests__/worktree-tool-context.test.js.map +1 -0
- package/dist/utils/worktree-tool-context.d.ts +25 -0
- package/dist/utils/worktree-tool-context.d.ts.map +1 -0
- package/dist/utils/worktree-tool-context.js +116 -0
- package/dist/utils/worktree-tool-context.js.map +1 -0
- package/package.json +1 -1
- package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/oh-my-codex/hooks/codex-native-hook.mjs +96 -10
- package/plugins/oh-my-codex/skills/ai-slop-cleaner/SKILL.md +1 -1
- package/plugins/oh-my-codex/skills/code-review/SKILL.md +1 -1
- package/plugins/oh-my-codex/skills/omx-setup/SKILL.md +5 -5
- package/plugins/oh-my-codex/skills/prometheus-strict/SKILL.md +3 -3
- package/plugins/oh-my-codex/skills/ralplan/SKILL.md +1 -1
- package/plugins/oh-my-codex/skills/team/SKILL.md +1 -1
- package/plugins/oh-my-codex/skills/ultrawork/references/agent-tiers.md +2 -2
- package/prompts/prometheus-strict-metis.md +1 -1
- package/skills/ai-slop-cleaner/SKILL.md +1 -1
- package/skills/code-review/SKILL.md +1 -1
- package/skills/ecomode/references/agent-tiers.md +1 -1
- package/skills/omx-setup/SKILL.md +5 -5
- package/skills/prometheus-strict/SKILL.md +3 -3
- package/skills/ralplan/SKILL.md +1 -1
- package/skills/team/SKILL.md +1 -1
- package/skills/ultrawork/references/agent-tiers.md +2 -2
- package/src/scripts/__tests__/codex-native-hook.test.ts +477 -0
- 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
|
-
|
|
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
|
|
3443
|
-
if (isProtectedPlanningStatePath(relativePath)) return
|
|
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
|
-
+
|
|
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
|
}
|