oh-my-codex 0.19.1 → 0.20.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +264 -6
- package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-context-window-warning.test.js +83 -78
- package/dist/cli/__tests__/doctor-context-window-warning.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-invalid-config.test.js +10 -1
- package/dist/cli/__tests__/doctor-invalid-config.test.js.map +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__/doctor-warning-copy.test.js +87 -1
- package/dist/cli/__tests__/doctor-warning-copy.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-hooks-shared-ownership.test.js +54 -2
- package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js.map +1 -1
- package/dist/cli/__tests__/setup-install-mode.test.js +49 -21
- 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 +166 -12
- package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
- package/dist/cli/__tests__/setup-scope.test.js +27 -3
- package/dist/cli/__tests__/setup-scope.test.js.map +1 -1
- package/dist/cli/__tests__/uninstall.test.js +301 -7
- 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 +3 -1
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +50 -34
- 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 +9 -6
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/uninstall.d.ts.map +1 -1
- package/dist/cli/uninstall.js +3 -4
- package/dist/cli/uninstall.js.map +1 -1
- package/dist/config/__tests__/generator-idempotent.test.js +210 -114
- package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
- package/dist/config/__tests__/generator-notify.test.js +26 -52
- 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 +20 -11
- package/dist/config/generator.d.ts.map +1 -1
- package/dist/config/generator.js +169 -152
- package/dist/config/generator.js.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 +150 -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 +188 -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 +1528 -32
- 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 +377 -31
- 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-md.test.js +16 -0
- package/dist/utils/__tests__/agents-md.test.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/agents-md.d.ts.map +1 -1
- package/dist/utils/agents-md.js +4 -3
- package/dist/utils/agents-md.js.map +1 -1
- 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 +1818 -113
- package/src/scripts/codex-native-hook.ts +414 -27
|
@@ -6,7 +6,7 @@ import { pathToFileURL } from "url";
|
|
|
6
6
|
import { readModeStateForActiveDecision, readModeStateForSession, updateModeState } from "../modes/base.js";
|
|
7
7
|
import { redactAuthSecrets } from "../auth/redact.js";
|
|
8
8
|
import { SKILL_ACTIVE_STATE_FILE, extractSessionIdFromInitializedStatePath, getSkillActiveStatePathsForStateDir, listActiveSkills, readSkillActiveState, readVisibleSkillActiveStateForStateDir, } from "../state/skill-active.js";
|
|
9
|
-
import { isTrustedSubagentThread, readSubagentSessionSummary, readSubagentTrackingState, recordSubagentTurnForSession, } from "../subagents/tracker.js";
|
|
9
|
+
import { isTrustedSubagentThread, readSubagentSessionSummary, readSubagentSessionLedger, readSubagentTrackingState, recordSubagentTurnForSession, } from "../subagents/tracker.js";
|
|
10
10
|
import { resolveCanonicalTeamStateRoot, resolveWorkerNotifyTeamStateRootPath } from "../team/state-root.js";
|
|
11
11
|
import { inferTerminalLifecycleOutcome } from "../runtime/run-outcome.js";
|
|
12
12
|
import { appendToLog, isSessionStale, isSessionStateUsable, readSessionState, readUsableSessionState, reconcileNativeSessionStart, } from "../hooks/session.js";
|
|
@@ -31,7 +31,7 @@ import { onPreCompact as buildWikiPreCompactContext, onSessionStart as buildWiki
|
|
|
31
31
|
import { readAutoresearchCompletionStatus, readAutoresearchModeStateForActiveDecision } from "../autoresearch/skill-validation.js";
|
|
32
32
|
import { AGENT_DEFINITIONS } from "../agents/definitions.js";
|
|
33
33
|
import { deriveAutopilotChildPhase, normalizeAutopilotPhase } from "../autopilot/fsm.js";
|
|
34
|
-
import { CONDUCTOR_ORCHESTRATION_METADATA_PREFIXES, LEADER_CONDUCTOR_BLOCK, LEADER_CONDUCTOR_REUSE_AND_LEDGER_GUIDANCE, actionKindForConductorArtifact, authorizeConductorAction, classifyConductorArtifactKind, } from "../leader/contract.js";
|
|
34
|
+
import { CONDUCTOR_ORCHESTRATION_METADATA_PREFIXES, LEADER_CONDUCTOR_BLOCK, LEADER_CONDUCTOR_REUSE_AND_LEDGER_GUIDANCE, NATIVE_SUBAGENT_SUPPORT_BLOCKER_FILE, actionKindForConductorArtifact, authorizeConductorAction, buildUnsupportedNativeSubagentGuidance, classifyConductorArtifactKind, isUnsupportedNativeSubagentEvidence, resolveNativeSubagentSupportStatus, } from "../leader/contract.js";
|
|
35
35
|
import { readRunState } from "../runtime/run-state.js";
|
|
36
36
|
import { evaluateRalphCompletionAuditEvidence, isRalphCompletePhase } from "../ralph/completion-audit.js";
|
|
37
37
|
import { buildCodexGoalTerminalCleanupNotice, } from "../goal-workflows/codex-goal-snapshot.js";
|
|
@@ -42,7 +42,7 @@ import { readTriageConfig } from "../hooks/triage-config.js";
|
|
|
42
42
|
import { readTriageState, writeTriageState, shouldSuppressFollowup, promptSignature, } from "../hooks/triage-state.js";
|
|
43
43
|
import { isPendingDeepInterviewQuestionEnforcement, reconcileDeepInterviewQuestionEnforcementFromAnsweredRecords, } from "../question/deep-interview.js";
|
|
44
44
|
import { readAutopilotDeepInterviewQuestionWaitState } from "../question/autopilot-wait.js";
|
|
45
|
-
import {
|
|
45
|
+
import { evaluateFinalHandoffDocumentRefresh, isFinalHandoffDocumentRefreshCandidate, } from "../document-refresh/enforcer.js";
|
|
46
46
|
import { buildExecFollowupStopOutput } from "../exec/followup.js";
|
|
47
47
|
import { MAX_NATIVE_STDIN_JSON_BYTES, extractRawCodexHookEventName, } from "./hook-payload-guard.js";
|
|
48
48
|
const TERMINAL_MODE_PHASES = new Set(["complete", "completed", "failed", "cancelled"]);
|
|
@@ -1463,6 +1463,76 @@ async function ensureOmxLocalIgnoreEntry(cwd) {
|
|
|
1463
1463
|
await writeFile(excludePath, next);
|
|
1464
1464
|
return { changed: true, excludePath };
|
|
1465
1465
|
}
|
|
1466
|
+
function readSessionStartSource(payload) {
|
|
1467
|
+
return safeString(payload?.source ?? payload?.session_start_source ?? payload?.sessionStartSource).trim().toLowerCase();
|
|
1468
|
+
}
|
|
1469
|
+
function shouldBuildSubagentReopenContext(options) {
|
|
1470
|
+
if (options.hookEventName !== "SessionStart")
|
|
1471
|
+
return false;
|
|
1472
|
+
const source = readSessionStartSource(options.payload);
|
|
1473
|
+
return source === "startup" || source === "resume";
|
|
1474
|
+
}
|
|
1475
|
+
function formatSubagentLedgerMetadata(entry) {
|
|
1476
|
+
const metadata = [
|
|
1477
|
+
entry.role ? `role: ${entry.role}` : null,
|
|
1478
|
+
entry.laneId ? `lane: ${entry.laneId}` : null,
|
|
1479
|
+
entry.scope ? `scope: ${entry.scope}` : null,
|
|
1480
|
+
entry.status ? `status: ${entry.status}` : null,
|
|
1481
|
+
entry.lastHandoffSummary ? `handoff: ${entry.lastHandoffSummary.slice(0, 120)}` : null,
|
|
1482
|
+
entry.resumeFailureReason ? `last failure: ${entry.resumeFailureReason.slice(0, 120)}` : null,
|
|
1483
|
+
].filter((item) => Boolean(item));
|
|
1484
|
+
return metadata.length > 0 ? ` (${metadata.join("; ")})` : "";
|
|
1485
|
+
}
|
|
1486
|
+
async function buildPersistedSubagentReopenContext(cwd, sessionId, options) {
|
|
1487
|
+
if (!shouldBuildSubagentReopenContext(options))
|
|
1488
|
+
return null;
|
|
1489
|
+
const ledger = await readSubagentSessionLedger(cwd, sessionId).catch(() => null);
|
|
1490
|
+
if (!ledger || ledger.savedSubagents.length === 0)
|
|
1491
|
+
return null;
|
|
1492
|
+
const source = readSessionStartSource(options.payload);
|
|
1493
|
+
const reopenTargets = ledger.resumeTargets.filter((entry) => entry.status !== "unavailable");
|
|
1494
|
+
const unavailableTargets = ledger.unavailableSubagents;
|
|
1495
|
+
const failedTargets = ledger.savedSubagents.filter((entry) => entry.resumeFailedAt || entry.resumeFailureReason);
|
|
1496
|
+
const nowIso = new Date().toISOString();
|
|
1497
|
+
await Promise.all(reopenTargets.map((entry) => recordSubagentTurnForSession(cwd, {
|
|
1498
|
+
sessionId,
|
|
1499
|
+
threadId: entry.threadId,
|
|
1500
|
+
kind: "subagent",
|
|
1501
|
+
role: entry.role,
|
|
1502
|
+
laneId: entry.laneId,
|
|
1503
|
+
scope: entry.scope,
|
|
1504
|
+
agentNickname: entry.agentNickname,
|
|
1505
|
+
status: entry.status,
|
|
1506
|
+
resumeRequestedAt: nowIso,
|
|
1507
|
+
preserveCompletionEvidence: true,
|
|
1508
|
+
}).catch(() => null)));
|
|
1509
|
+
const lines = [
|
|
1510
|
+
"[Persisted subagent reopen]",
|
|
1511
|
+
`- SessionStart source: ${source}; saved subagent ids found: ${ledger.savedSubagents.length}.`,
|
|
1512
|
+
];
|
|
1513
|
+
if (reopenTargets.length > 0) {
|
|
1514
|
+
lines.push("- Reopen these persisted subagents by id before continuing work or spawning any same-role/same-lane replacement:");
|
|
1515
|
+
for (const entry of reopenTargets.slice(0, 12)) {
|
|
1516
|
+
lines.push(` - resume_agent(${JSON.stringify(entry.agentId)})${formatSubagentLedgerMetadata(entry)}`);
|
|
1517
|
+
}
|
|
1518
|
+
if (reopenTargets.length > 12) {
|
|
1519
|
+
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.`);
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
else {
|
|
1523
|
+
lines.push("- No compatible saved subagent id is currently marked reopenable; do not spawn a replacement merely because reopen was unavailable.");
|
|
1524
|
+
}
|
|
1525
|
+
lines.push("- Silver rule: when follow-up work targets an existing role/lane, reuse the matching reopened id; avoid duplicate same-type subagent spawns.");
|
|
1526
|
+
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.");
|
|
1527
|
+
const warningEntries = [...new Map([...unavailableTargets, ...failedTargets].map((entry) => [entry.agentId, entry])).values()];
|
|
1528
|
+
if (warningEntries.length > 0) {
|
|
1529
|
+
lines.push("- Reopen warnings:");
|
|
1530
|
+
for (const entry of warningEntries.slice(0, 8)) {
|
|
1531
|
+
lines.push(` - ${entry.agentId}${formatSubagentLedgerMetadata(entry)}`);
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
return lines.join("\n");
|
|
1535
|
+
}
|
|
1466
1536
|
async function buildSessionStartContext(cwd, sessionId, options = {}) {
|
|
1467
1537
|
const sections = [];
|
|
1468
1538
|
sections.push(buildExecutionEnvironmentSection(cwd, {
|
|
@@ -1560,6 +1630,13 @@ async function buildSessionStartContext(cwd, sessionId, options = {}) {
|
|
|
1560
1630
|
if (subagentSummary && subagentSummary.activeSubagentThreadIds.length > 0) {
|
|
1561
1631
|
sections.push(`[Subagents]\n- active subagent threads: ${subagentSummary.activeSubagentThreadIds.length}`);
|
|
1562
1632
|
}
|
|
1633
|
+
const persistedSubagentReopenContext = await buildPersistedSubagentReopenContext(cwd, sessionId, {
|
|
1634
|
+
hookEventName: options.hookEventName,
|
|
1635
|
+
payload: options.payload,
|
|
1636
|
+
});
|
|
1637
|
+
if (persistedSubagentReopenContext) {
|
|
1638
|
+
sections.push(persistedSubagentReopenContext);
|
|
1639
|
+
}
|
|
1563
1640
|
return sections.length > 0 ? sections.join("\n\n") : null;
|
|
1564
1641
|
}
|
|
1565
1642
|
function resolveExecutionEnvironment(cwd, options = {}) {
|
|
@@ -1722,6 +1799,17 @@ function buildAutopilotPromptActivationNote(skillState, options = {}) {
|
|
|
1722
1799
|
const teamHandoff = readTeamModeConfig(options.cwd).enabled
|
|
1723
1800
|
? " (+ $team if needed)"
|
|
1724
1801
|
: "";
|
|
1802
|
+
const stateDir = getBaseStateDir(options.cwd);
|
|
1803
|
+
const nativeSubagentSupport = resolveNativeSubagentSupportStatus({
|
|
1804
|
+
payload: options.payload,
|
|
1805
|
+
persistedSupportBlocker: readJsonSyncIfExists(nativeSubagentSupportBlockerPath(stateDir)),
|
|
1806
|
+
persistedCapacityBlocker: readJsonSyncIfExists(nativeSubagentCapacityBlockerPath(stateDir)),
|
|
1807
|
+
cwd: options.cwd,
|
|
1808
|
+
sessionId: options.sessionId,
|
|
1809
|
+
});
|
|
1810
|
+
const conductorGuidance = nativeSubagentSupport.status === "unsupported"
|
|
1811
|
+
? buildUnsupportedNativeSubagentGuidance(nativeSubagentSupport)
|
|
1812
|
+
: `${LEADER_CONDUCTOR_BLOCK} ${LEADER_CONDUCTOR_REUSE_AND_LEDGER_GUIDANCE}`;
|
|
1725
1813
|
return [
|
|
1726
1814
|
`Autopilot protocol: the durable default chain is $deep-interview -> $ralplan -> $ultragoal${teamHandoff} -> $code-review -> $ultraqa (deep-interview -> ralplan -> ultragoal -> code-review -> ultraqa).`,
|
|
1727
1815
|
"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.",
|
|
@@ -1733,8 +1821,7 @@ function buildAutopilotPromptActivationNote(skillState, options = {}) {
|
|
|
1733
1821
|
"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.",
|
|
1734
1822
|
"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.",
|
|
1735
1823
|
"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.",
|
|
1736
|
-
|
|
1737
|
-
LEADER_CONDUCTOR_REUSE_AND_LEDGER_GUIDANCE,
|
|
1824
|
+
conductorGuidance,
|
|
1738
1825
|
].filter(Boolean).join(" ");
|
|
1739
1826
|
}
|
|
1740
1827
|
function formatExecutionHandoffList(cwd) {
|
|
@@ -1761,7 +1848,7 @@ function buildAdditionalContextMessage(prompt, skillState, cwd = process.cwd(),
|
|
|
1761
1848
|
: null;
|
|
1762
1849
|
const deepInterviewConfigPromptActivationNote = buildDeepInterviewConfigInstruction(cwd, skillState);
|
|
1763
1850
|
const markedQuestionAnswer = /^\s*\[omx question answered\]/i.test(prompt);
|
|
1764
|
-
const autopilotPromptActivationNote = buildAutopilotPromptActivationNote(skillState, { markedQuestionAnswer, cwd });
|
|
1851
|
+
const autopilotPromptActivationNote = buildAutopilotPromptActivationNote(skillState, { markedQuestionAnswer, cwd, payload, sessionId: safeString(skillState?.session_id).trim() });
|
|
1765
1852
|
return [
|
|
1766
1853
|
`OMX native UserPromptSubmit continued active workflow skill "${continuedSkill}".`,
|
|
1767
1854
|
promptPriorityMessage,
|
|
@@ -1785,7 +1872,7 @@ function buildAdditionalContextMessage(prompt, skillState, cwd = process.cwd(),
|
|
|
1785
1872
|
? buildDeepInterviewQuestionBridgeInstruction(cwd, payload)
|
|
1786
1873
|
: null;
|
|
1787
1874
|
const deepInterviewConfigPromptActivationNote = buildDeepInterviewConfigInstruction(cwd, skillState);
|
|
1788
|
-
const autopilotPromptActivationNote = buildAutopilotPromptActivationNote(skillState, { markedQuestionAnswer: true, cwd });
|
|
1875
|
+
const autopilotPromptActivationNote = buildAutopilotPromptActivationNote(skillState, { markedQuestionAnswer: true, cwd, payload, sessionId: safeString(skillState?.session_id).trim() });
|
|
1789
1876
|
return [
|
|
1790
1877
|
`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.`,
|
|
1791
1878
|
promptPriorityMessage,
|
|
@@ -1818,7 +1905,7 @@ function buildAdditionalContextMessage(prompt, skillState, cwd = process.cwd(),
|
|
|
1818
1905
|
const ultragoalPromptActivationNote = match.skill === "ultragoal"
|
|
1819
1906
|
? "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."
|
|
1820
1907
|
: null;
|
|
1821
|
-
const autopilotPromptActivationNote = buildAutopilotPromptActivationNote(skillState, { cwd });
|
|
1908
|
+
const autopilotPromptActivationNote = buildAutopilotPromptActivationNote(skillState, { cwd, payload, sessionId: safeString(skillState?.session_id).trim() });
|
|
1822
1909
|
const combinedTransitionMessage = (() => {
|
|
1823
1910
|
if (!skillState?.transition_message)
|
|
1824
1911
|
return null;
|
|
@@ -2165,8 +2252,6 @@ async function buildModeBasedStopOutput(mode, cwd, sessionId) {
|
|
|
2165
2252
|
reason: `OMX ${mode} is still active (phase: ${phase}; ${diagnostic}); continue the task and gather fresh verification evidence before stopping.`,
|
|
2166
2253
|
stopReason: `${mode}_${phase}`,
|
|
2167
2254
|
systemMessage,
|
|
2168
|
-
statePath,
|
|
2169
|
-
canonicalDisagreement,
|
|
2170
2255
|
};
|
|
2171
2256
|
}
|
|
2172
2257
|
export function looksLikeGoalCompletionPrompt(text) {
|
|
@@ -2574,6 +2659,17 @@ function readPayloadTurnId(payload) {
|
|
|
2574
2659
|
function nativeSubagentCapacityBlockerPath(stateDir) {
|
|
2575
2660
|
return join(stateDir, NATIVE_SUBAGENT_CAPACITY_BLOCKER_FILE);
|
|
2576
2661
|
}
|
|
2662
|
+
function nativeSubagentSupportBlockerPath(stateDir) {
|
|
2663
|
+
return join(stateDir, NATIVE_SUBAGENT_SUPPORT_BLOCKER_FILE);
|
|
2664
|
+
}
|
|
2665
|
+
function readJsonSyncIfExists(path) {
|
|
2666
|
+
try {
|
|
2667
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
2668
|
+
}
|
|
2669
|
+
catch {
|
|
2670
|
+
return null;
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2577
2673
|
function stringifyUnknown(value) {
|
|
2578
2674
|
if (typeof value === "string")
|
|
2579
2675
|
return value;
|
|
@@ -2602,6 +2698,44 @@ function isNativeSubagentCapacityFailure(payload) {
|
|
|
2602
2698
|
const toolName = safeString(payload.tool_name).trim();
|
|
2603
2699
|
return !toolName || /(?:spawn_agent|multi_agent|subagent|collab|agent)/i.test(toolName);
|
|
2604
2700
|
}
|
|
2701
|
+
function nativeSubagentFailureReason(payload) {
|
|
2702
|
+
const evidence = payloadEvidenceText(payload);
|
|
2703
|
+
const toolName = safeString(payload.tool_name).trim();
|
|
2704
|
+
if (toolName && !/(?:spawn_agent|multi_agent|subagent|collab|agent)/i.test(toolName))
|
|
2705
|
+
return null;
|
|
2706
|
+
if (/\bagent thread limit reached\b/i.test(evidence))
|
|
2707
|
+
return null;
|
|
2708
|
+
if (/\bnative subagents? (?:unsupported|disabled|not enabled|unavailable|not found)\b/i.test(evidence))
|
|
2709
|
+
return "native_subagents_unsupported";
|
|
2710
|
+
if (/\bmulti_agent_v1\b/i.test(evidence) && /\b(?:unavailable|unknown tool|disabled|not enabled|not found|unsupported)\b/i.test(evidence))
|
|
2711
|
+
return "multi_agent_v1_unavailable";
|
|
2712
|
+
if (/\b(?:unknown tool|tool not found|not enabled|disabled|unavailable|unsupported)\b/i.test(evidence))
|
|
2713
|
+
return "multi_agent_v1_unavailable";
|
|
2714
|
+
return null;
|
|
2715
|
+
}
|
|
2716
|
+
function summarizeNativeSubagentSupportFailure(text) {
|
|
2717
|
+
const normalized = text.replace(/\s+/g, " ").trim();
|
|
2718
|
+
return (normalized || "native subagent support unavailable").slice(0, 500);
|
|
2719
|
+
}
|
|
2720
|
+
async function recordNativeSubagentSupportBlocker(cwd, stateDir, payload) {
|
|
2721
|
+
const reason = nativeSubagentFailureReason(payload);
|
|
2722
|
+
if (!reason)
|
|
2723
|
+
return;
|
|
2724
|
+
const nowIso = new Date().toISOString();
|
|
2725
|
+
await mkdir(stateDir, { recursive: true });
|
|
2726
|
+
await writeFile(nativeSubagentSupportBlockerPath(stateDir), JSON.stringify({
|
|
2727
|
+
schema_version: 1,
|
|
2728
|
+
status: "unsupported",
|
|
2729
|
+
reason,
|
|
2730
|
+
...(readPayloadSessionId(payload) ? { session_id: readPayloadSessionId(payload) } : {}),
|
|
2731
|
+
...(readPayloadThreadId(payload) ? { thread_id: readPayloadThreadId(payload) } : {}),
|
|
2732
|
+
...(readPayloadTurnId(payload) ? { turn_id: readPayloadTurnId(payload) } : {}),
|
|
2733
|
+
...(safeString(payload.tool_name).trim() ? { tool_name: safeString(payload.tool_name).trim() } : {}),
|
|
2734
|
+
evidence: summarizeNativeSubagentSupportFailure(payloadEvidenceText(payload)),
|
|
2735
|
+
observed_at: nowIso,
|
|
2736
|
+
cwd,
|
|
2737
|
+
}, null, 2));
|
|
2738
|
+
}
|
|
2605
2739
|
function summarizeCapacityFailure(text) {
|
|
2606
2740
|
const normalized = text.replace(/\s+/g, " ").trim();
|
|
2607
2741
|
if (!normalized)
|
|
@@ -2791,6 +2925,7 @@ const PROTECTED_PLANNING_STATE_FILE_NAMES = new Set([
|
|
|
2791
2925
|
"ralplan-state.json",
|
|
2792
2926
|
"skill-active-state.json",
|
|
2793
2927
|
]);
|
|
2928
|
+
const RUNTIME_SESSION_ID_SEGMENT_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;
|
|
2794
2929
|
const PLANNING_MODE_IMPLEMENTATION_TOOL_NAMES = new Set([
|
|
2795
2930
|
"Write",
|
|
2796
2931
|
"Edit",
|
|
@@ -2875,6 +3010,33 @@ function isProtectedPlanningStatePath(relativePath) {
|
|
|
2875
3010
|
const fileName = relativePath.split("/").pop() ?? "";
|
|
2876
3011
|
return PROTECTED_PLANNING_STATE_FILE_NAMES.has(fileName);
|
|
2877
3012
|
}
|
|
3013
|
+
function isAllowedAuthoritativeRuntimePlanningStatePath(cwd, rawPath) {
|
|
3014
|
+
const trimmed = rawPath.trim().replace(/^['"]|['"]$/g, "");
|
|
3015
|
+
if (!trimmed || trimmed.includes("\0"))
|
|
3016
|
+
return false;
|
|
3017
|
+
let targetPath;
|
|
3018
|
+
let baseStateDir;
|
|
3019
|
+
try {
|
|
3020
|
+
targetPath = resolve(cwd, trimmed);
|
|
3021
|
+
baseStateDir = resolve(getBaseStateDir(cwd));
|
|
3022
|
+
}
|
|
3023
|
+
catch {
|
|
3024
|
+
return false;
|
|
3025
|
+
}
|
|
3026
|
+
const localStateDir = resolve(cwd, ".omx", "state");
|
|
3027
|
+
if (baseStateDir === localStateDir)
|
|
3028
|
+
return false;
|
|
3029
|
+
const relativeToBase = relative(baseStateDir, targetPath).replace(/\\/g, "/");
|
|
3030
|
+
if (!relativeToBase || relativeToBase.startsWith("..") || relativeToBase.startsWith("/"))
|
|
3031
|
+
return false;
|
|
3032
|
+
const segments = relativeToBase.split("/");
|
|
3033
|
+
if (segments.length !== 3 || segments[0] !== "sessions")
|
|
3034
|
+
return false;
|
|
3035
|
+
const sessionId = segments[1] ?? "";
|
|
3036
|
+
const fileName = segments[2] ?? "";
|
|
3037
|
+
return RUNTIME_SESSION_ID_SEGMENT_PATTERN.test(sessionId)
|
|
3038
|
+
&& PROTECTED_PLANNING_STATE_FILE_NAMES.has(fileName);
|
|
3039
|
+
}
|
|
2878
3040
|
function isPlanningTmpRelativePath(relativePath) {
|
|
2879
3041
|
return relativePath === ".omx/tmp" || relativePath.startsWith(".omx/tmp/");
|
|
2880
3042
|
}
|
|
@@ -2890,9 +3052,9 @@ function isAllowedPlanningTmpScratchPath(relativePath) {
|
|
|
2890
3052
|
function isAllowedPlanningArtifactPath(cwd, rawPath, allowedPrefixes) {
|
|
2891
3053
|
const relativePath = normalizePlanningArtifactRelativePath(cwd, rawPath);
|
|
2892
3054
|
if (!relativePath)
|
|
2893
|
-
return
|
|
3055
|
+
return isAllowedAuthoritativeRuntimePlanningStatePath(cwd, rawPath);
|
|
2894
3056
|
if (isProtectedPlanningStatePath(relativePath))
|
|
2895
|
-
return
|
|
3057
|
+
return isAllowedAuthoritativeRuntimePlanningStatePath(cwd, rawPath);
|
|
2896
3058
|
if (isPlanningTmpRelativePath(relativePath)) {
|
|
2897
3059
|
return allowedPrefixes.includes(".omx/tmp") && isAllowedPlanningTmpScratchPath(relativePath);
|
|
2898
3060
|
}
|
|
@@ -2901,8 +3063,13 @@ function isAllowedPlanningArtifactPath(cwd, rawPath, allowedPrefixes) {
|
|
|
2901
3063
|
function isAllowedDeepInterviewArtifactPath(cwd, rawPath) {
|
|
2902
3064
|
return isAllowedPlanningArtifactPath(cwd, rawPath, DEEP_INTERVIEW_ALLOWED_WRITE_PREFIXES);
|
|
2903
3065
|
}
|
|
3066
|
+
function isAllowedRalplanDraftPath(cwd, rawPath) {
|
|
3067
|
+
const relativePath = normalizePlanningArtifactRelativePath(cwd, rawPath);
|
|
3068
|
+
return relativePath !== null && /^\.omx\/drafts\/[^/]+\.md$/.test(relativePath);
|
|
3069
|
+
}
|
|
2904
3070
|
function isAllowedRalplanArtifactPath(cwd, rawPath) {
|
|
2905
|
-
return
|
|
3071
|
+
return isAllowedRalplanDraftPath(cwd, rawPath)
|
|
3072
|
+
|| isAllowedPlanningArtifactPath(cwd, rawPath, RALPLAN_ALLOWED_WRITE_PREFIXES);
|
|
2906
3073
|
}
|
|
2907
3074
|
function shellTokenizeLiteralCommand(command) {
|
|
2908
3075
|
const tokens = [];
|
|
@@ -3110,9 +3277,73 @@ function resolveCommandRedirectTarget(target, assignments) {
|
|
|
3110
3277
|
const resolved = assignments.get(safeString(variableMatch[1]));
|
|
3111
3278
|
return resolved !== undefined ? resolved : target;
|
|
3112
3279
|
}
|
|
3280
|
+
// Masks redirect metacharacters (`<`/`>`) that appear INSIDE shell quotes so a
|
|
3281
|
+
// quoted regex/source value (e.g. `gh issue create --body '...>{1,2}...'` or
|
|
3282
|
+
// `omx state write --input '{"reason":"a>b"}'`) is not misread as a redirect
|
|
3283
|
+
// write target. Escaped quotes at the top level (`\'`, `\"`) are literal
|
|
3284
|
+
// characters and must NOT open a span, and `$'...'` ANSI-C quoting processes
|
|
3285
|
+
// backslash escapes (so `\'` does not close it) — otherwise a genuine `>`
|
|
3286
|
+
// redirect could be masked behind a false span and its write missed. Only
|
|
3287
|
+
// characters inside a real quoted span are masked; unquoted redirect operators
|
|
3288
|
+
// survive intact. Unterminated/ambiguous quoting fails closed: the original
|
|
3289
|
+
// command is returned unmasked so genuine redirects stay visible to the scan.
|
|
3290
|
+
function maskQuotedRedirectMetacharsForCommandScan(command) {
|
|
3291
|
+
let masked = "";
|
|
3292
|
+
// null = unquoted; "'" = single quotes (no escapes); '"' = double quotes
|
|
3293
|
+
// (backslash escapes); "$'" = ANSI-C $'...' (backslash escapes, incl. \').
|
|
3294
|
+
let quote = null;
|
|
3295
|
+
for (let index = 0; index < command.length; index += 1) {
|
|
3296
|
+
const char = command[index] ?? "";
|
|
3297
|
+
if (quote === null) {
|
|
3298
|
+
// A backslash escapes the next character at the top level, so an escaped
|
|
3299
|
+
// quote is a literal char and must not open a quoted span.
|
|
3300
|
+
if (char === "\\") {
|
|
3301
|
+
masked += char;
|
|
3302
|
+
const next = command[index + 1];
|
|
3303
|
+
if (next !== undefined) {
|
|
3304
|
+
masked += next;
|
|
3305
|
+
index += 1;
|
|
3306
|
+
}
|
|
3307
|
+
continue;
|
|
3308
|
+
}
|
|
3309
|
+
if (char === "$" && command[index + 1] === "'") {
|
|
3310
|
+
quote = "$'";
|
|
3311
|
+
masked += "$'";
|
|
3312
|
+
index += 1;
|
|
3313
|
+
continue;
|
|
3314
|
+
}
|
|
3315
|
+
if (char === "'" || char === "\"")
|
|
3316
|
+
quote = char;
|
|
3317
|
+
masked += char;
|
|
3318
|
+
continue;
|
|
3319
|
+
}
|
|
3320
|
+
if ((quote === "\"" || quote === "$'") && char === "\\") {
|
|
3321
|
+
// Escapes inside double/ANSI-C spans keep the backslash and its escaped
|
|
3322
|
+
// char (masking a redirect metachar so quoted data cannot be a false
|
|
3323
|
+
// target); the escaped char never closes the span.
|
|
3324
|
+
masked += char;
|
|
3325
|
+
const next = command[index + 1];
|
|
3326
|
+
if (next !== undefined) {
|
|
3327
|
+
masked += next === "<" || next === ">" ? "_" : next;
|
|
3328
|
+
index += 1;
|
|
3329
|
+
}
|
|
3330
|
+
continue;
|
|
3331
|
+
}
|
|
3332
|
+
const closesSpan = quote === "$'" ? char === "'" : char === quote;
|
|
3333
|
+
if (closesSpan) {
|
|
3334
|
+
quote = null;
|
|
3335
|
+
masked += char;
|
|
3336
|
+
continue;
|
|
3337
|
+
}
|
|
3338
|
+
masked += char === "<" || char === ">" ? "_" : char;
|
|
3339
|
+
}
|
|
3340
|
+
if (quote !== null)
|
|
3341
|
+
return command;
|
|
3342
|
+
return masked;
|
|
3343
|
+
}
|
|
3113
3344
|
function extractDeepInterviewCommandRedirectTargets(command) {
|
|
3114
3345
|
const targets = [];
|
|
3115
|
-
const commandOutsideHeredocBodies = stripHeredocBodiesForCommandScan(command);
|
|
3346
|
+
const commandOutsideHeredocBodies = maskQuotedRedirectMetacharsForCommandScan(stripHeredocBodiesForCommandScan(command));
|
|
3116
3347
|
for (const match of commandOutsideHeredocBodies.matchAll(/(?:^|[^>])>{1,2}\s*(["']?)([^\s&|;<>]+)\1/g)) {
|
|
3117
3348
|
const candidate = safeString(match[2]).trim();
|
|
3118
3349
|
if (candidate && !isNullDeviceRedirectTarget(candidate))
|
|
@@ -3224,7 +3455,7 @@ function findGitSubcommandIndex(words, startIndex) {
|
|
|
3224
3455
|
}
|
|
3225
3456
|
return null;
|
|
3226
3457
|
}
|
|
3227
|
-
function commandHasDeepInterviewWriteIntent(command) {
|
|
3458
|
+
function commandHasDeepInterviewWriteIntent(command, depth = 0) {
|
|
3228
3459
|
return commandInvokesApplyPatch(command)
|
|
3229
3460
|
|| extractDeepInterviewCommandRedirectTargets(command).length > 0
|
|
3230
3461
|
|| /\btee\s+(?:-a\s+)?[^\s&|;]+/.test(command)
|
|
@@ -3235,7 +3466,17 @@ function commandHasDeepInterviewWriteIntent(command) {
|
|
|
3235
3466
|
|| extractConductorBashMutations(command).length > 0
|
|
3236
3467
|
|| extractConductorInterpreterWrites(command).length > 0
|
|
3237
3468
|
|| commandHasDestructiveGitSubcommand(command)
|
|
3238
|
-
|| commandHasPackageInstallIntent(command)
|
|
3469
|
+
|| commandHasPackageInstallIntent(command)
|
|
3470
|
+
// Recurse into wrapped shells (`bash -lc "cat > f"`, `eval`, `env`) and
|
|
3471
|
+
// command substitutions so a real redirect INSIDE a nested command string
|
|
3472
|
+
// is still classified as write intent. This is required because quoted
|
|
3473
|
+
// redirect metacharacters are now masked at the outer scan (#3119): the
|
|
3474
|
+
// mask removes false positives from quoted DATA values while nested-command
|
|
3475
|
+
// recursion re-detects genuine redirects, preserving fail-closed blocking.
|
|
3476
|
+
// The depth guard mirrors evaluateConductorBashWrite's nesting bound;
|
|
3477
|
+
// extractors return strict substrings, so recursion always terminates.
|
|
3478
|
+
|| (depth < CONDUCTOR_BASH_MAX_NESTING_DEPTH && (extractNestedShellCommandStringsForStateScan(command).some((nested) => commandHasDeepInterviewWriteIntent(nested, depth + 1))
|
|
3479
|
+
|| extractNestedCommandSubstitutionStringsForStateScan(command).some((nested) => commandHasDeepInterviewWriteIntent(nested, depth + 1))));
|
|
3239
3480
|
}
|
|
3240
3481
|
function extractDeepInterviewCommandWriteTargets(command) {
|
|
3241
3482
|
const assignments = extractCommandLiteralAssignments(command);
|
|
@@ -5271,6 +5512,56 @@ function isPackageManagerCommandWord(word) {
|
|
|
5271
5512
|
const base = shellWordBaseName(word);
|
|
5272
5513
|
return base === "npm" || base === "pnpm" || base === "yarn" || base === "npx" || base === "pnpx";
|
|
5273
5514
|
}
|
|
5515
|
+
function isStructuredUltragoalSteeringShellCommand(command) {
|
|
5516
|
+
const segments = splitShellCommandSegments(stripHeredocBodiesForCommandScan(command));
|
|
5517
|
+
let sawStructuredSteer = false;
|
|
5518
|
+
for (const segment of segments) {
|
|
5519
|
+
const words = tokenizeShellWords(segment);
|
|
5520
|
+
let commandStart = true;
|
|
5521
|
+
let inForHeader = false;
|
|
5522
|
+
for (let index = 0; index < words.length; index += 1) {
|
|
5523
|
+
const word = words[index] ?? "";
|
|
5524
|
+
if (!word)
|
|
5525
|
+
continue;
|
|
5526
|
+
if (isShellCommandSeparator(word)) {
|
|
5527
|
+
commandStart = true;
|
|
5528
|
+
continue;
|
|
5529
|
+
}
|
|
5530
|
+
if (inForHeader) {
|
|
5531
|
+
if (word === "do") {
|
|
5532
|
+
inForHeader = false;
|
|
5533
|
+
commandStart = true;
|
|
5534
|
+
}
|
|
5535
|
+
continue;
|
|
5536
|
+
}
|
|
5537
|
+
if (isShellGroupingSyntaxWord(word))
|
|
5538
|
+
continue;
|
|
5539
|
+
if (commandStart && isEnvironmentAssignmentWord(word))
|
|
5540
|
+
continue;
|
|
5541
|
+
if (commandStart && CONDUCTOR_BASH_COMPOUND_SYNTAX_WORDS.has(word)) {
|
|
5542
|
+
if (word === "for")
|
|
5543
|
+
inForHeader = true;
|
|
5544
|
+
continue;
|
|
5545
|
+
}
|
|
5546
|
+
if (commandStart && word.startsWith("-"))
|
|
5547
|
+
continue;
|
|
5548
|
+
if (!commandStart)
|
|
5549
|
+
continue;
|
|
5550
|
+
const commandName = commandNameFromShellWord(word);
|
|
5551
|
+
if (commandName === "read" || commandName === "true" || commandName === ":") {
|
|
5552
|
+
commandStart = false;
|
|
5553
|
+
continue;
|
|
5554
|
+
}
|
|
5555
|
+
if (commandName === "omx" && words[index + 1] === "ultragoal" && words[index + 2] === "steer") {
|
|
5556
|
+
sawStructuredSteer = true;
|
|
5557
|
+
commandStart = false;
|
|
5558
|
+
continue;
|
|
5559
|
+
}
|
|
5560
|
+
return false;
|
|
5561
|
+
}
|
|
5562
|
+
}
|
|
5563
|
+
return sawStructuredSteer;
|
|
5564
|
+
}
|
|
5274
5565
|
function hasDynamicNestedShellExecution(command) {
|
|
5275
5566
|
const commands = splitShellCommandSegments(stripHeredocBodiesForCommandScan(command));
|
|
5276
5567
|
for (const segment of commands) {
|
|
@@ -5298,13 +5589,13 @@ function hasDynamicNestedShellExecution(command) {
|
|
|
5298
5589
|
return true;
|
|
5299
5590
|
if (commandStringIndex !== null) {
|
|
5300
5591
|
const nestedCommand = words[commandStringIndex] ?? "";
|
|
5301
|
-
if (isDynamicNestedCommandString(nestedCommand))
|
|
5592
|
+
if (isDynamicNestedCommandString(nestedCommand) && !isStructuredUltragoalSteeringShellCommand(nestedCommand))
|
|
5302
5593
|
return true;
|
|
5303
5594
|
}
|
|
5304
5595
|
}
|
|
5305
5596
|
if (word === "eval") {
|
|
5306
5597
|
const nestedCommand = words.slice(index + 1).join(" ");
|
|
5307
|
-
if (isDynamicNestedCommandString(nestedCommand))
|
|
5598
|
+
if (isDynamicNestedCommandString(nestedCommand) && !isStructuredUltragoalSteeringShellCommand(nestedCommand))
|
|
5308
5599
|
return true;
|
|
5309
5600
|
}
|
|
5310
5601
|
}
|
|
@@ -6185,7 +6476,7 @@ function buildDeepInterviewBashBlockedDetail(cwd, command) {
|
|
|
6185
6476
|
}
|
|
6186
6477
|
async function buildRalplanPreToolUseBoundaryOutput(payload, cwd, stateDir, resolvedSessionId) {
|
|
6187
6478
|
const sessionId = safeString(resolvedSessionId ?? readPayloadSessionId(payload)).trim();
|
|
6188
|
-
if (await hasTrustedTypedSubagentProvenanceForPreToolUse(payload, cwd, sessionId))
|
|
6479
|
+
if (await hasTrustedTypedSubagentProvenanceForPreToolUse(payload, cwd, stateDir, sessionId))
|
|
6189
6480
|
return null;
|
|
6190
6481
|
const threadId = readPayloadThreadId(payload);
|
|
6191
6482
|
const activeState = await readActiveRalplanStateForPreToolUse(cwd, stateDir, sessionId, threadId);
|
|
@@ -6236,7 +6527,7 @@ async function buildRalplanPreToolUseBoundaryOutput(payload, cwd, stateDir, reso
|
|
|
6236
6527
|
hookSpecificOutput: {
|
|
6237
6528
|
hookEventName: "PreToolUse",
|
|
6238
6529
|
additionalContext: `${planningModeDescription}. `
|
|
6239
|
-
+ "Write only planning artifacts under `.omx/context/`, `.omx/plans/`, `.omx/specs/`, `.omx/tmp/`, required `.omx/state/` files, or tracker metadata under `.beads/`. "
|
|
6530
|
+
+ "Write only planning artifacts under `.omx/context/`, `.omx/plans/`, `.omx/specs/`, `.omx/tmp/`, required `.omx/state/` files, Markdown drafts under `.omx/drafts/*.md`, or tracker metadata under `.beads/`. "
|
|
6240
6531
|
+ "Do not edit implementation files or run implementation-focused writes from planning phases. "
|
|
6241
6532
|
+ `To execute, first process an explicit handoff such as ${formatExecutionHandoffList(cwd)}, which must emit terminal planning state before implementation begins.`,
|
|
6242
6533
|
},
|
|
@@ -6244,7 +6535,7 @@ async function buildRalplanPreToolUseBoundaryOutput(payload, cwd, stateDir, reso
|
|
|
6244
6535
|
}
|
|
6245
6536
|
async function buildDeepInterviewPreToolUseBoundaryOutput(payload, cwd, stateDir, resolvedSessionId) {
|
|
6246
6537
|
const sessionId = safeString(resolvedSessionId ?? readPayloadSessionId(payload)).trim();
|
|
6247
|
-
if (await hasTrustedTypedSubagentProvenanceForPreToolUse(payload, cwd, sessionId))
|
|
6538
|
+
if (await hasTrustedTypedSubagentProvenanceForPreToolUse(payload, cwd, stateDir, sessionId))
|
|
6248
6539
|
return null;
|
|
6249
6540
|
const threadId = readPayloadThreadId(payload);
|
|
6250
6541
|
const activeState = await readActiveDeepInterviewStateForPreToolUse(cwd, stateDir, sessionId, threadId);
|
|
@@ -6362,18 +6653,64 @@ async function buildPlanningRootPointerConflictPreToolUseOutput(payload, cwd, st
|
|
|
6362
6653
|
}
|
|
6363
6654
|
return blocked ? buildRalplanRootPointerConflictBlock(ralplanState) : null;
|
|
6364
6655
|
}
|
|
6365
|
-
async function hasTrustedTypedSubagentProvenanceForPreToolUse(payload, cwd, sessionId) {
|
|
6656
|
+
async function hasTrustedTypedSubagentProvenanceForPreToolUse(payload, cwd, stateDir, sessionId, options = {}) {
|
|
6366
6657
|
if (hasTeamWorkerEnvironment())
|
|
6367
6658
|
return true;
|
|
6368
|
-
if (!isTypedAgentRolePayload(payload))
|
|
6369
|
-
return false;
|
|
6370
6659
|
const trackingState = await readSubagentTrackingState(cwd).catch(() => null);
|
|
6371
6660
|
const session = trackingState?.sessions?.[sessionId];
|
|
6372
6661
|
if (!session)
|
|
6373
6662
|
return false;
|
|
6374
6663
|
const payloadThreadId = readPayloadThreadId(payload);
|
|
6664
|
+
// Resolve the Main-root leader THREAD identity from the tracker's leader_thread_id
|
|
6665
|
+
// plus the canonical session's native_session_id (the leader's native thread). Only
|
|
6666
|
+
// genuine leader-thread identifiers may anchor the leader: owner_*_session_id are
|
|
6667
|
+
// session-level ids, not thread anchors, so they must NOT populate this set — their
|
|
6668
|
+
// mere presence would make it non-empty and suppress the fail-closed guard below
|
|
6669
|
+
// without ever excluding the leader thread (#3117 P4). The native_session_id anchor is
|
|
6670
|
+
// honored only when the root session pointer provably maps to the sessionId under
|
|
6671
|
+
// evaluation, so a stale/foreign session.json cannot supply another session's leader
|
|
6672
|
+
// anchor (#3117 P3); the tracker alone is insufficient because it can omit
|
|
6673
|
+
// leader_thread_id or corruptly label the leader kind:"subagent" (#3117 P2).
|
|
6674
|
+
const sessionState = await readRootSessionStateFromStateDir(stateDir).catch(() => null);
|
|
6675
|
+
const trackerLeaderThreadId = safeString(session.leader_thread_id).trim();
|
|
6676
|
+
const leaderIdentityAnchors = new Set();
|
|
6677
|
+
if (trackerLeaderThreadId)
|
|
6678
|
+
leaderIdentityAnchors.add(trackerLeaderThreadId);
|
|
6679
|
+
if (sessionState && sessionId && payloadMatchesSessionPointer(sessionId, sessionState)) {
|
|
6680
|
+
const nativeSessionId = safeString(sessionState.native_session_id).trim();
|
|
6681
|
+
if (nativeSessionId)
|
|
6682
|
+
leaderIdentityAnchors.add(nativeSessionId);
|
|
6683
|
+
}
|
|
6684
|
+
// Leader self-guard: the Main-root Conductor is never a delegated executor. Block it
|
|
6685
|
+
// ahead of every trust path, even when tracker or payload provenance is (possibly
|
|
6686
|
+
// corruptly) attached to the leader thread.
|
|
6687
|
+
if (payloadThreadId && leaderIdentityAnchors.has(payloadThreadId))
|
|
6688
|
+
return false;
|
|
6689
|
+
// Fail closed: without an authoritative leader anchor we cannot affirm the payload is
|
|
6690
|
+
// a non-leader delegated performer rather than a mislabeled leader, so we refuse trust
|
|
6691
|
+
// instead of inferring it from a corrupt tracker kind:"subagent" alone (#3117 P2).
|
|
6692
|
+
if (leaderIdentityAnchors.size === 0)
|
|
6693
|
+
return false;
|
|
6694
|
+
// Planning boundary guards (ralplan, deep-interview) still require a recognized typed
|
|
6695
|
+
// agent role, so an untyped collaboration.spawn_agent child cannot write before an
|
|
6696
|
+
// execution handoff/approval. Only the Main-root Conductor/Ralph executing guard opts
|
|
6697
|
+
// into untyped tracker/runtime provenance (#3116, #3117).
|
|
6698
|
+
if (options.allowUntypedProvenance !== true && !isTypedAgentRolePayload(payload)) {
|
|
6699
|
+
return false;
|
|
6700
|
+
}
|
|
6701
|
+
// Tracker-backed provenance: the payload's own thread is a recorded, non-leader
|
|
6702
|
+
// subagent for this session — the non-spoofable anchor that lets native
|
|
6703
|
+
// collaboration.spawn_agent children/descendants edit under the Conductor guard even
|
|
6704
|
+
// without a recognized typed role (#3116). subagent-tracking.json is derived from
|
|
6705
|
+
// child SessionStart transcript metadata, not the live tool-call payload.
|
|
6375
6706
|
if (payloadThreadId && isTrustedSubagentThread(session, payloadThreadId))
|
|
6376
6707
|
return true;
|
|
6708
|
+
// Runtime-attached spawn provenance: trust a genuine spawned subagent turn whose
|
|
6709
|
+
// declared parent maps to this session's leader or a tracked thread. parentThreadId
|
|
6710
|
+
// comes from the runtime-set source.subagent.thread_spawn (not agent-controlled tool
|
|
6711
|
+
// arguments); an absent parent is rejected, the leader self-guard above blocks the
|
|
6712
|
+
// main root, and cross-session parents fail because they are not in this session's
|
|
6713
|
+
// tracked threads (#3116).
|
|
6377
6714
|
const source = safeObject(payload.source);
|
|
6378
6715
|
const subagent = safeObject(source.subagent);
|
|
6379
6716
|
const threadSpawn = safeObject(subagent.thread_spawn);
|
|
@@ -6383,10 +6720,7 @@ async function hasTrustedTypedSubagentProvenanceForPreToolUse(payload, cwd, sess
|
|
|
6383
6720
|
?? threadSpawn.leaderThreadId).trim();
|
|
6384
6721
|
if (!parentThreadId)
|
|
6385
6722
|
return false;
|
|
6386
|
-
|
|
6387
|
-
if (payloadThreadId && leaderThreadId && payloadThreadId === leaderThreadId)
|
|
6388
|
-
return false;
|
|
6389
|
-
return leaderThreadId === parentThreadId || parentThreadId in session.threads;
|
|
6723
|
+
return leaderIdentityAnchors.has(parentThreadId) || parentThreadId in session.threads;
|
|
6390
6724
|
}
|
|
6391
6725
|
function isActiveConductorModeState(state, mode, sessionId) {
|
|
6392
6726
|
if (!state || state.active !== true)
|
|
@@ -6413,7 +6747,7 @@ async function readActiveMainRootConductorStateForPreToolUse(payload, cwd, state
|
|
|
6413
6747
|
const threadId = readPayloadThreadId(payload);
|
|
6414
6748
|
if (!sessionId)
|
|
6415
6749
|
return null;
|
|
6416
|
-
if (await hasTrustedTypedSubagentProvenanceForPreToolUse(payload, cwd, sessionId))
|
|
6750
|
+
if (await hasTrustedTypedSubagentProvenanceForPreToolUse(payload, cwd, stateDir, sessionId, { allowUntypedProvenance: true }))
|
|
6417
6751
|
return null;
|
|
6418
6752
|
const canonicalState = await readVisibleSkillActiveStateForStateDir(stateDir, sessionId);
|
|
6419
6753
|
if (!canonicalState)
|
|
@@ -7228,6 +7562,13 @@ export async function buildConductorPreToolUseWriteGuardOutput(payload, cwd, sta
|
|
|
7228
7562
|
const activeState = await readActiveMainRootConductorStateForPreToolUse(payload, cwd, stateDir, resolvedSessionId);
|
|
7229
7563
|
if (!activeState)
|
|
7230
7564
|
return null;
|
|
7565
|
+
const nativeSubagentSupport = resolveNativeSubagentSupportStatus({
|
|
7566
|
+
payload,
|
|
7567
|
+
persistedSupportBlocker: await readJsonIfExists(nativeSubagentSupportBlockerPath(stateDir)),
|
|
7568
|
+
persistedCapacityBlocker: await readJsonIfExists(nativeSubagentCapacityBlockerPath(stateDir)),
|
|
7569
|
+
cwd,
|
|
7570
|
+
sessionId: resolvedSessionId || readPayloadSessionId(payload),
|
|
7571
|
+
});
|
|
7231
7572
|
const toolName = safeString(payload.tool_name).trim();
|
|
7232
7573
|
const command = readPreToolUseCommand(payload);
|
|
7233
7574
|
const pathCandidates = readPreToolUsePathCandidates(payload);
|
|
@@ -7254,6 +7595,9 @@ export async function buildConductorPreToolUseWriteGuardOutput(payload, cwd, sta
|
|
|
7254
7595
|
}
|
|
7255
7596
|
if (!blocked)
|
|
7256
7597
|
return null;
|
|
7598
|
+
const unsupportedNativeGuidance = isUnsupportedNativeSubagentEvidence(nativeSubagentSupport)
|
|
7599
|
+
? ` ${buildUnsupportedNativeSubagentGuidance(nativeSubagentSupport)} Treat the active conductor workflow as blocked/cancelled for native delegation recovery; do not call multi_agent_v1.close_agent.`
|
|
7600
|
+
: "";
|
|
7257
7601
|
return {
|
|
7258
7602
|
decision: "block",
|
|
7259
7603
|
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}.`,
|
|
@@ -7262,7 +7606,8 @@ export async function buildConductorPreToolUseWriteGuardOutput(payload, cwd, sta
|
|
|
7262
7606
|
additionalContext: `${LEADER_CONDUCTOR_GOLDEN_RULE} `
|
|
7263
7607
|
+ "Use specialized agents for source edits and plan/spec authorship. "
|
|
7264
7608
|
+ `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. `
|
|
7265
|
-
+
|
|
7609
|
+
+ unsupportedNativeGuidance
|
|
7610
|
+
+ " Autopilot rework and typed subagent/worker lanes are exempt from this guard.",
|
|
7266
7611
|
},
|
|
7267
7612
|
};
|
|
7268
7613
|
}
|
|
@@ -8222,7 +8567,7 @@ async function buildStopHookOutput(payload, cwd, stateDir, options = {}) {
|
|
|
8222
8567
|
if (isFinalHandoffDocumentRefreshCandidate(lastAssistantMessage)) {
|
|
8223
8568
|
const documentRefreshWarning = evaluateFinalHandoffDocumentRefresh(cwd, lastAssistantMessage);
|
|
8224
8569
|
if (documentRefreshWarning) {
|
|
8225
|
-
return await maybeReturnRepeatableStopOutput(payload, stateDir, buildRepeatableStopSignature(payload, "document-refresh-stop", documentRefreshWarning.triggeringPaths.join("|"), canonicalSessionId),
|
|
8570
|
+
return await maybeReturnRepeatableStopOutput(payload, stateDir, buildRepeatableStopSignature(payload, "document-refresh-stop", documentRefreshWarning.triggeringPaths.join("|"), canonicalSessionId), { systemMessage: documentRefreshWarning.message }, canonicalSessionId, { allowRepeatDuringStopHook: false });
|
|
8226
8571
|
}
|
|
8227
8572
|
}
|
|
8228
8573
|
return null;
|
|
@@ -8512,6 +8857,7 @@ export async function dispatchCodexNativeHook(payload, options = {}) {
|
|
|
8512
8857
|
}
|
|
8513
8858
|
else if (hookEventName === "PostToolUse") {
|
|
8514
8859
|
await recordNativeSubagentCapacityBlocker(cwd, stateDir, payload).catch(() => { });
|
|
8860
|
+
await recordNativeSubagentSupportBlocker(cwd, stateDir, payload).catch(() => { });
|
|
8515
8861
|
if (detectMcpTransportFailure(payload)) {
|
|
8516
8862
|
await markTeamTransportFailure(cwd, payload);
|
|
8517
8863
|
}
|