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.
- package/Cargo.lock +6 -6
- package/Cargo.toml +1 -1
- package/README.md +8 -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__/mission.test.d.ts +2 -0
- package/dist/cli/__tests__/mission.test.d.ts.map +1 -0
- package/dist/cli/__tests__/mission.test.js +239 -0
- package/dist/cli/__tests__/mission.test.js.map +1 -0
- 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__/session-scoped-runtime.test.js +67 -0
- package/dist/cli/__tests__/session-scoped-runtime.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__/ultragoal.test.js +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 +107 -23
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/mission.d.ts +36 -0
- package/dist/cli/mission.d.ts.map +1 -0
- package/dist/cli/mission.js +443 -0
- package/dist/cli/mission.js.map +1 -0
- 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/hud/__tests__/state.test.js +31 -6
- package/dist/hud/__tests__/state.test.js.map +1 -1
- package/dist/hud/state.d.ts.map +1 -1
- package/dist/hud/state.js +7 -3
- package/dist/hud/state.js.map +1 -1
- 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/mcp/__tests__/state-paths.test.js +32 -1
- package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
- package/dist/mcp/state-paths.d.ts +4 -0
- package/dist/mcp/state-paths.d.ts.map +1 -1
- package/dist/mcp/state-paths.js +6 -15
- package/dist/mcp/state-paths.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__/consensus-gate.test.js +193 -0
- package/dist/ralplan/__tests__/consensus-gate.test.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/consensus-gate.js +24 -3
- package/dist/ralplan/consensus-gate.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 +762 -20
- 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 +306 -22
- package/dist/scripts/codex-native-hook.js.map +1 -1
- package/dist/scripts/generate-catalog-docs.js +1 -0
- package/dist/scripts/generate-catalog-docs.js.map +1 -1
- package/dist/state/__tests__/operations.test.js +737 -0
- package/dist/state/__tests__/operations.test.js.map +1 -1
- package/dist/state/__tests__/skill-active.test.js +59 -0
- package/dist/state/__tests__/skill-active.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 +80 -17
- package/dist/state/operations.js.map +1 -1
- package/dist/state/skill-active.js +3 -3
- package/dist/state/skill-active.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 +82 -5
- 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 +111 -14
- 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 +824 -7
- package/src/scripts/codex-native-hook.ts +347 -20
- 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
|
-
|
|
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
|
|
3435
|
-
if (isProtectedPlanningStatePath(relativePath)) return
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
6970
|
-
|
|
6971
|
-
|
|
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
|
|
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
|
-
+
|
|
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
|
}
|