oh-my-codex 0.18.16 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. package/Cargo.lock +6 -6
  2. package/Cargo.toml +1 -1
  3. package/crates/omx-sparkshell/tests/execution.rs +8 -1
  4. package/dist/agents/__tests__/definitions.test.js +5 -5
  5. package/dist/agents/__tests__/definitions.test.js.map +1 -1
  6. package/dist/agents/__tests__/native-config.test.js +18 -14
  7. package/dist/agents/__tests__/native-config.test.js.map +1 -1
  8. package/dist/agents/definitions.d.ts +2 -2
  9. package/dist/agents/definitions.d.ts.map +1 -1
  10. package/dist/agents/definitions.js +4 -4
  11. package/dist/agents/definitions.js.map +1 -1
  12. package/dist/agents/native-config.d.ts +1 -0
  13. package/dist/agents/native-config.d.ts.map +1 -1
  14. package/dist/agents/native-config.js +18 -16
  15. package/dist/agents/native-config.js.map +1 -1
  16. package/dist/auth/hotswap.d.ts.map +1 -1
  17. package/dist/auth/hotswap.js +17 -4
  18. package/dist/auth/hotswap.js.map +1 -1
  19. package/dist/autopilot/__tests__/planner-routing.test.js +2 -2
  20. package/dist/autopilot/__tests__/planner-routing.test.js.map +1 -1
  21. package/dist/autopilot/fsm.d.ts +1 -1
  22. package/dist/autopilot/fsm.d.ts.map +1 -1
  23. package/dist/autopilot/fsm.js +1 -1
  24. package/dist/autopilot/fsm.js.map +1 -1
  25. package/dist/cli/__tests__/auth.test.js +78 -3
  26. package/dist/cli/__tests__/auth.test.js.map +1 -1
  27. package/dist/cli/__tests__/codex-plugin-layout.test.js +30 -5
  28. package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
  29. package/dist/cli/__tests__/doctor-warning-copy.test.js +2 -0
  30. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  31. package/dist/cli/__tests__/index.test.js +96 -8
  32. package/dist/cli/__tests__/index.test.js.map +1 -1
  33. package/dist/cli/__tests__/launch-fallback.test.js +135 -1
  34. package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
  35. package/dist/cli/__tests__/mcp-parity.test.js +2 -4
  36. package/dist/cli/__tests__/mcp-parity.test.js.map +1 -1
  37. package/dist/cli/__tests__/plugin-marketplace-idempotent.test.js +14 -0
  38. package/dist/cli/__tests__/plugin-marketplace-idempotent.test.js.map +1 -1
  39. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js +8 -0
  40. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js.map +1 -1
  41. package/dist/cli/__tests__/resume.test.js +229 -1
  42. package/dist/cli/__tests__/resume.test.js.map +1 -1
  43. package/dist/cli/__tests__/session-scoped-runtime.test.js +46 -0
  44. package/dist/cli/__tests__/session-scoped-runtime.test.js.map +1 -1
  45. package/dist/cli/__tests__/setup-install-mode.test.js +37 -0
  46. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  47. package/dist/cli/__tests__/ultragoal.test.js +21 -0
  48. package/dist/cli/__tests__/ultragoal.test.js.map +1 -1
  49. package/dist/cli/__tests__/version.test.js +13 -1
  50. package/dist/cli/__tests__/version.test.js.map +1 -1
  51. package/dist/cli/auth.d.ts +1 -1
  52. package/dist/cli/auth.d.ts.map +1 -1
  53. package/dist/cli/auth.js +49 -9
  54. package/dist/cli/auth.js.map +1 -1
  55. package/dist/cli/index.d.ts +25 -2
  56. package/dist/cli/index.d.ts.map +1 -1
  57. package/dist/cli/index.js +233 -32
  58. package/dist/cli/index.js.map +1 -1
  59. package/dist/cli/plugin-marketplace.d.ts.map +1 -1
  60. package/dist/cli/plugin-marketplace.js +22 -1
  61. package/dist/cli/plugin-marketplace.js.map +1 -1
  62. package/dist/cli/ralph.d.ts.map +1 -1
  63. package/dist/cli/ralph.js +4 -0
  64. package/dist/cli/ralph.js.map +1 -1
  65. package/dist/cli/setup.d.ts.map +1 -1
  66. package/dist/cli/setup.js +3 -1
  67. package/dist/cli/setup.js.map +1 -1
  68. package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.js +7 -0
  69. package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.js.map +1 -1
  70. package/dist/goal-workflows/codex-goal-snapshot.js +1 -1
  71. package/dist/goal-workflows/codex-goal-snapshot.js.map +1 -1
  72. package/dist/hooks/__tests__/autopilot-skill-contract.test.js +12 -0
  73. package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
  74. package/dist/hooks/__tests__/keyword-detector.test.js +267 -7
  75. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  76. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  77. package/dist/hooks/keyword-detector.js +143 -30
  78. package/dist/hooks/keyword-detector.js.map +1 -1
  79. package/dist/hud/__tests__/reconcile.test.js +121 -0
  80. package/dist/hud/__tests__/reconcile.test.js.map +1 -1
  81. package/dist/hud/__tests__/state.test.js +110 -0
  82. package/dist/hud/__tests__/state.test.js.map +1 -1
  83. package/dist/hud/__tests__/tmux.test.js +1 -1
  84. package/dist/hud/__tests__/tmux.test.js.map +1 -1
  85. package/dist/hud/reconcile.d.ts +3 -1
  86. package/dist/hud/reconcile.d.ts.map +1 -1
  87. package/dist/hud/reconcile.js +281 -151
  88. package/dist/hud/reconcile.js.map +1 -1
  89. package/dist/hud/render.d.ts.map +1 -1
  90. package/dist/hud/render.js +4 -2
  91. package/dist/hud/render.js.map +1 -1
  92. package/dist/hud/state.d.ts.map +1 -1
  93. package/dist/hud/state.js +44 -12
  94. package/dist/hud/state.js.map +1 -1
  95. package/dist/hud/tmux.d.ts.map +1 -1
  96. package/dist/hud/tmux.js +1 -4
  97. package/dist/hud/tmux.js.map +1 -1
  98. package/dist/hud/types.d.ts +5 -0
  99. package/dist/hud/types.d.ts.map +1 -1
  100. package/dist/hud/types.js.map +1 -1
  101. package/dist/leader/__tests__/contract.test.d.ts +2 -0
  102. package/dist/leader/__tests__/contract.test.d.ts.map +1 -0
  103. package/dist/leader/__tests__/contract.test.js +75 -0
  104. package/dist/leader/__tests__/contract.test.js.map +1 -0
  105. package/dist/leader/contract.d.ts +26 -0
  106. package/dist/leader/contract.d.ts.map +1 -0
  107. package/dist/leader/contract.js +97 -0
  108. package/dist/leader/contract.js.map +1 -0
  109. package/dist/modes/base.d.ts.map +1 -1
  110. package/dist/modes/base.js +22 -7
  111. package/dist/modes/base.js.map +1 -1
  112. package/dist/notifications/__tests__/dispatcher.test.js +16 -0
  113. package/dist/notifications/__tests__/dispatcher.test.js.map +1 -1
  114. package/dist/notifications/__tests__/profiles.test.js +87 -0
  115. package/dist/notifications/__tests__/profiles.test.js.map +1 -1
  116. package/dist/notifications/config.d.ts +1 -0
  117. package/dist/notifications/config.d.ts.map +1 -1
  118. package/dist/notifications/config.js +60 -22
  119. package/dist/notifications/config.js.map +1 -1
  120. package/dist/notifications/dispatcher.d.ts.map +1 -1
  121. package/dist/notifications/dispatcher.js +6 -16
  122. package/dist/notifications/dispatcher.js.map +1 -1
  123. package/dist/pipeline/__tests__/stages.test.js +2 -0
  124. package/dist/pipeline/__tests__/stages.test.js.map +1 -1
  125. package/dist/pipeline/stages/ultragoal.d.ts.map +1 -1
  126. package/dist/pipeline/stages/ultragoal.js +6 -1
  127. package/dist/pipeline/stages/ultragoal.js.map +1 -1
  128. package/dist/question/__tests__/renderer.test.js +58 -4
  129. package/dist/question/__tests__/renderer.test.js.map +1 -1
  130. package/dist/question/renderer.d.ts +1 -1
  131. package/dist/question/renderer.d.ts.map +1 -1
  132. package/dist/question/renderer.js +73 -5
  133. package/dist/question/renderer.js.map +1 -1
  134. package/dist/ralplan/__tests__/runtime.test.js +202 -0
  135. package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
  136. package/dist/ralplan/runtime.d.ts +7 -0
  137. package/dist/ralplan/runtime.d.ts.map +1 -1
  138. package/dist/ralplan/runtime.js +63 -4
  139. package/dist/ralplan/runtime.js.map +1 -1
  140. package/dist/scripts/__tests__/codex-native-hook.test.js +8484 -4560
  141. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  142. package/dist/scripts/codex-native-hook.d.ts +1 -0
  143. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  144. package/dist/scripts/codex-native-hook.js +4387 -190
  145. package/dist/scripts/codex-native-hook.js.map +1 -1
  146. package/dist/state/__tests__/operations.test.js +1137 -0
  147. package/dist/state/__tests__/operations.test.js.map +1 -1
  148. package/dist/state/__tests__/planning-gate.test.js +410 -0
  149. package/dist/state/__tests__/planning-gate.test.js.map +1 -1
  150. package/dist/state/operations.d.ts +10 -0
  151. package/dist/state/operations.d.ts.map +1 -1
  152. package/dist/state/operations.js +242 -8
  153. package/dist/state/operations.js.map +1 -1
  154. package/dist/state/skill-active.d.ts +4 -1
  155. package/dist/state/skill-active.d.ts.map +1 -1
  156. package/dist/state/skill-active.js +5 -3
  157. package/dist/state/skill-active.js.map +1 -1
  158. package/dist/state/workflow-transition.d.ts.map +1 -1
  159. package/dist/state/workflow-transition.js +642 -1
  160. package/dist/state/workflow-transition.js.map +1 -1
  161. package/dist/subagents/__tests__/tracker.test.js +248 -1
  162. package/dist/subagents/__tests__/tracker.test.js.map +1 -1
  163. package/dist/subagents/tracker.d.ts +60 -0
  164. package/dist/subagents/tracker.d.ts.map +1 -1
  165. package/dist/subagents/tracker.js +225 -1
  166. package/dist/subagents/tracker.js.map +1 -1
  167. package/dist/team/__tests__/model-contract.test.js +72 -4
  168. package/dist/team/__tests__/model-contract.test.js.map +1 -1
  169. package/dist/team/__tests__/runtime.test.js +44 -2
  170. package/dist/team/__tests__/runtime.test.js.map +1 -1
  171. package/dist/team/__tests__/tmux-session.test.js +127 -8
  172. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  173. package/dist/team/__tests__/worker-runtime-identity.test.js +89 -0
  174. package/dist/team/__tests__/worker-runtime-identity.test.js.map +1 -1
  175. package/dist/team/model-contract.d.ts +3 -0
  176. package/dist/team/model-contract.d.ts.map +1 -1
  177. package/dist/team/model-contract.js +42 -5
  178. package/dist/team/model-contract.js.map +1 -1
  179. package/dist/team/runtime.d.ts +2 -1
  180. package/dist/team/runtime.d.ts.map +1 -1
  181. package/dist/team/runtime.js +21 -11
  182. package/dist/team/runtime.js.map +1 -1
  183. package/dist/team/scaling.d.ts.map +1 -1
  184. package/dist/team/scaling.js +13 -11
  185. package/dist/team/scaling.js.map +1 -1
  186. package/dist/team/tmux-session.d.ts +1 -0
  187. package/dist/team/tmux-session.d.ts.map +1 -1
  188. package/dist/team/tmux-session.js +63 -13
  189. package/dist/team/tmux-session.js.map +1 -1
  190. package/dist/ultragoal/__tests__/artifacts.test.js +7 -0
  191. package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
  192. package/dist/ultragoal/artifacts.d.ts.map +1 -1
  193. package/dist/ultragoal/artifacts.js +5 -0
  194. package/dist/ultragoal/artifacts.js.map +1 -1
  195. package/dist/utils/__tests__/agents-model-table.test.js +2 -2
  196. package/dist/utils/__tests__/agents-model-table.test.js.map +1 -1
  197. package/package.json +1 -1
  198. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  199. package/plugins/oh-my-codex/hooks/codex-native-hook.mjs +19 -7
  200. package/plugins/oh-my-codex/skills/code-review/SKILL.md +16 -0
  201. package/plugins/oh-my-codex/skills/ultragoal/SKILL.md +23 -0
  202. package/plugins/oh-my-codex/skills/ultrawork/references/agent-tiers.md +3 -2
  203. package/skills/code-review/SKILL.md +16 -0
  204. package/skills/ecomode/references/agent-tiers.md +1 -1
  205. package/skills/ultragoal/SKILL.md +23 -0
  206. package/skills/ultrawork/references/agent-tiers.md +3 -2
  207. package/src/scripts/__tests__/codex-native-hook.test.ts +14014 -9097
  208. package/src/scripts/codex-native-hook.ts +4459 -199
@@ -1,16 +1,17 @@
1
1
  import { execFileSync } from "child_process";
2
- import { closeSync, existsSync, openSync, readFileSync, readSync, statSync } from "fs";
2
+ import { closeSync, existsSync, openSync, readFileSync, readSync, readdirSync, statSync } from "fs";
3
3
  import { appendFile, mkdir, readFile, readdir, stat, writeFile } from "fs/promises";
4
- import { extname, join, relative, resolve } from "path";
4
+ import { extname, isAbsolute, join, relative, resolve } from "path";
5
5
  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
9
  import { isTrustedSubagentThread, readSubagentSessionSummary, readSubagentTrackingState, recordSubagentTurnForSession, } from "../subagents/tracker.js";
10
10
  import { resolveCanonicalTeamStateRoot, resolveWorkerNotifyTeamStateRootPath } from "../team/state-root.js";
11
- import { appendToLog, isSessionStateUsable, readSessionState, readUsableSessionState, reconcileNativeSessionStart, } from "../hooks/session.js";
11
+ import { inferTerminalLifecycleOutcome } from "../runtime/run-outcome.js";
12
+ import { appendToLog, isSessionStale, isSessionStateUsable, readSessionState, readUsableSessionState, reconcileNativeSessionStart, } from "../hooks/session.js";
12
13
  import { appendTeamEvent, readTeamLeaderAttention, readTeamConfig, readTeamManifestV2, readTeamPhase, writeTeamLeaderAttention, writeTeamPhase, } from "../team/state.js";
13
- import { omxNotepadPath, resolveProjectMemoryPath } from "../utils/paths.js";
14
+ import { codexAgentsDir, omxNotepadPath, projectCodexAgentsDir, resolveProjectMemoryPath } from "../utils/paths.js";
14
15
  import { findGitLayout } from "../utils/git-layout.js";
15
16
  import { getBaseStateDir, getStateFilePath, getStatePath, getAuthoritativeActiveStatePaths } from "../mcp/state-paths.js";
16
17
  import { detectKeywords, detectPrimaryKeyword, recordSkillActivation, } from "../hooks/keyword-detector.js";
@@ -22,12 +23,15 @@ import { handleTeamWorkerPostToolUseSuccess } from "./notify-hook/team-worker-po
22
23
  import { maybeNudgeLeaderForAllowedWorkerStop } from "./notify-hook/team-worker-stop.js";
23
24
  import { resolveCodexExecutionSurface, } from "./codex-execution-surface.js";
24
25
  import { buildNativeHookEvent, } from "../hooks/extensibility/events.js";
26
+ import { isTrackedWorkflowMode } from "../state/workflow-transition.js";
25
27
  import { dispatchHookEventRuntime } from "../hooks/extensibility/runtime.js";
26
28
  import { getNotificationConfig, getVerbosity } from "../notifications/config.js";
27
29
  import { reconcileHudForPromptSubmit } from "../hud/reconcile.js";
28
30
  import { onPreCompact as buildWikiPreCompactContext, onSessionStart as buildWikiSessionStartContext, } from "../wiki/lifecycle.js";
29
31
  import { readAutoresearchCompletionStatus, readAutoresearchModeStateForActiveDecision } from "../autoresearch/skill-validation.js";
30
- import { normalizeAutopilotPhase } from "../autopilot/fsm.js";
32
+ import { AGENT_DEFINITIONS } from "../agents/definitions.js";
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";
31
35
  import { readRunState } from "../runtime/run-state.js";
32
36
  import { evaluateRalphCompletionAuditEvidence, isRalphCompletePhase } from "../ralph/completion-audit.js";
33
37
  import { buildCodexGoalTerminalCleanupNotice, } from "../goal-workflows/codex-goal-snapshot.js";
@@ -45,6 +49,7 @@ const TERMINAL_MODE_PHASES = new Set(["complete", "completed", "failed", "cancel
45
49
  const SKILL_STOP_BLOCKERS = new Set(["ralplan"]);
46
50
  const TEAM_STOP_BLOCKING_TASK_STATUSES = new Set(["pending", "in_progress", "blocked"]);
47
51
  const TEAM_WORKER_TERMINAL_RUN_STATES = new Set(["done", "complete", "completed", "failed", "stopped", "cancelled"]);
52
+ const LEADER_CONDUCTOR_GOLDEN_RULE = "Main-root Conductor golden rule: delegate implementation work; do not self-execute source or plan edits.";
48
53
  const NATIVE_STOP_STATE_FILE = "native-stop-state.json";
49
54
  const NATIVE_SUBAGENT_CAPACITY_BLOCKER_FILE = "native-subagent-capacity-blocker.json";
50
55
  const NATIVE_SUBAGENT_CAPACITY_BLOCKER_TTL_MS = 30 * 60_000;
@@ -84,6 +89,9 @@ const RALPH_LIVE_RISK_PATTERNS = [
84
89
  /\b(?:database|db|terraform|kubectl|kubernetes|aws|gcp|azure|external|destructive)\b/i,
85
90
  /\b(?:telegram|vps|service|restart|send|notify|notification|notifications|cron)\b/i,
86
91
  ];
92
+ const KNOWN_TYPED_AGENT_ROLES = new Set(Object.keys(AGENT_DEFINITIONS));
93
+ let installedTypedAgentRoleNamesCacheKey = "";
94
+ let installedTypedAgentRoleNamesCache = new Set();
87
95
  const RALPH_TASK_TEXT_FIELDS = [
88
96
  "task_description",
89
97
  "taskDescription",
@@ -418,6 +426,9 @@ function readHookEventName(payload) {
418
426
  function sanitizeCodexHookOutput(hookEventName, output) {
419
427
  if (!output || hookEventName !== "PreToolUse")
420
428
  return output;
429
+ const preToolUseDenyOutput = toPreToolUseDenyOutput(output);
430
+ if (preToolUseDenyOutput)
431
+ return preToolUseDenyOutput;
421
432
  const systemMessage = safeString(output.systemMessage).trim();
422
433
  if (systemMessage)
423
434
  return { systemMessage };
@@ -429,6 +440,33 @@ function sanitizeCodexHookOutput(hookEventName, output) {
429
440
  const derivedSystemMessage = [reason, additionalContext].filter(Boolean).join("\n\n");
430
441
  return derivedSystemMessage ? { systemMessage: derivedSystemMessage } : {};
431
442
  }
443
+ function toPreToolUseDenyOutput(output) {
444
+ const sourceHookSpecificOutput = safeObject(output.hookSpecificOutput);
445
+ const legacyBlock = output.decision === "block";
446
+ const hookSpecificDeny = sourceHookSpecificOutput.permissionDecision === "deny";
447
+ if (!legacyBlock && !hookSpecificDeny)
448
+ return null;
449
+ const permissionDecisionReason = safeString(sourceHookSpecificOutput.permissionDecisionReason).trim();
450
+ const legacyReason = safeString(output.reason).trim();
451
+ const additionalContext = safeString(sourceHookSpecificOutput.additionalContext).trim();
452
+ const systemMessage = safeString(output.systemMessage).trim();
453
+ const reason = permissionDecisionReason || legacyReason || systemMessage;
454
+ if (!reason) {
455
+ throw new Error("Malformed PreToolUse block output: explicit deny/block requires non-empty permissionDecisionReason, reason, or systemMessage.");
456
+ }
457
+ const hookSpecificOutput = {
458
+ hookEventName: "PreToolUse",
459
+ permissionDecision: "deny",
460
+ permissionDecisionReason: reason,
461
+ };
462
+ if (additionalContext) {
463
+ hookSpecificOutput.additionalContext = additionalContext;
464
+ }
465
+ return {
466
+ ...(systemMessage ? { systemMessage } : {}),
467
+ hookSpecificOutput,
468
+ };
469
+ }
432
470
  export function mapCodexHookEventToOmxEvent(hookEventName) {
433
471
  switch (hookEventName) {
434
472
  case "SessionStart":
@@ -1201,6 +1239,28 @@ const SOURCE_DIFF_EXTENSIONS = new Set([
1201
1239
  ".ts",
1202
1240
  ".tsx",
1203
1241
  ]);
1242
+ const PLANNING_TMP_SCRIPT_LIKE_EXTENSIONS = new Set([
1243
+ ".bash",
1244
+ ".bat",
1245
+ ".cjs",
1246
+ ".cmd",
1247
+ ".cts",
1248
+ ".fish",
1249
+ ".js",
1250
+ ".jsx",
1251
+ ".ksh",
1252
+ ".mjs",
1253
+ ".mts",
1254
+ ".php",
1255
+ ".pl",
1256
+ ".ps1",
1257
+ ".py",
1258
+ ".rb",
1259
+ ".sh",
1260
+ ".ts",
1261
+ ".tsx",
1262
+ ".zsh",
1263
+ ]);
1204
1264
  function gitOutput(cwd, args) {
1205
1265
  try {
1206
1266
  return execFileSync("git", args, {
@@ -1673,6 +1733,8 @@ function buildAutopilotPromptActivationNote(skillState, options = {}) {
1673
1733
  "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.",
1674
1734
  "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.",
1675
1735
  "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
+ LEADER_CONDUCTOR_BLOCK,
1737
+ LEADER_CONDUCTOR_REUSE_AND_LEDGER_GUIDANCE,
1676
1738
  ].filter(Boolean).join(" ");
1677
1739
  }
1678
1740
  function formatExecutionHandoffList(cwd) {
@@ -1684,6 +1746,9 @@ function buildAdditionalContextMessage(prompt, skillState, cwd = process.cwd(),
1684
1746
  if (!prompt)
1685
1747
  return null;
1686
1748
  const promptPriorityMessage = buildPromptPriorityMessage(prompt);
1749
+ if (payload && isTypedAgentRolePayload(payload)) {
1750
+ return promptPriorityMessage;
1751
+ }
1687
1752
  const teamMode = readTeamModeConfig(cwd);
1688
1753
  const matches = detectKeywords(prompt).filter((entry) => teamMode.enabled || entry.skill !== "team");
1689
1754
  const match = matches[0] ?? null;
@@ -1845,6 +1910,11 @@ function parseTeamWorkerEnv(rawValue) {
1845
1910
  workerName: match[2] || "",
1846
1911
  };
1847
1912
  }
1913
+ function hasTeamWorkerEnvironment() {
1914
+ return parseTeamWorkerEnv(safeString(process.env.OMX_TEAM_INTERNAL_WORKER))
1915
+ !== null
1916
+ || parseTeamWorkerEnv(safeString(process.env.OMX_TEAM_WORKER)) !== null;
1917
+ }
1848
1918
  async function resolveTeamStateDirForWorkerContext(cwd, workerContext) {
1849
1919
  const resolved = await resolveWorkerNotifyTeamStateRootPath(cwd, workerContext, process.env).catch(() => null);
1850
1920
  if (resolved)
@@ -2457,6 +2527,47 @@ function readPayloadSessionId(payload) {
2457
2527
  function readPayloadThreadId(payload) {
2458
2528
  return safeString(payload.owner_codex_thread_id ?? payload.thread_id ?? payload.threadId).trim();
2459
2529
  }
2530
+ function readPayloadAgentRole(payload) {
2531
+ const directRole = safeString(payload.agent_role
2532
+ ?? payload.agentRole
2533
+ ?? payload.agent_type
2534
+ ?? payload.agentType).trim().toLowerCase();
2535
+ if (directRole)
2536
+ return directRole;
2537
+ const source = safeObject(payload.source);
2538
+ const subagent = safeObject(source?.subagent);
2539
+ const threadSpawn = safeObject(subagent?.thread_spawn);
2540
+ return safeString(threadSpawn?.agent_role
2541
+ ?? threadSpawn?.agentRole
2542
+ ?? threadSpawn?.agent_type
2543
+ ?? threadSpawn?.agentType).trim().toLowerCase();
2544
+ }
2545
+ function readInstalledTypedAgentRoleNames() {
2546
+ const cacheKey = [codexAgentsDir(), projectCodexAgentsDir()].join("|");
2547
+ if (installedTypedAgentRoleNamesCacheKey === cacheKey)
2548
+ return installedTypedAgentRoleNamesCache;
2549
+ const installedRoleNames = new Set();
2550
+ for (const agentsDir of [codexAgentsDir(), projectCodexAgentsDir()]) {
2551
+ try {
2552
+ for (const entry of readdirSync(agentsDir, { withFileTypes: true })) {
2553
+ if (!entry.isFile() || !entry.name.endsWith(".toml"))
2554
+ continue;
2555
+ installedRoleNames.add(entry.name.slice(0, -5).trim().toLowerCase());
2556
+ }
2557
+ }
2558
+ catch {
2559
+ // Ignore missing or unreadable agent directories; built-in definitions remain authoritative.
2560
+ }
2561
+ }
2562
+ installedTypedAgentRoleNamesCacheKey = cacheKey;
2563
+ installedTypedAgentRoleNamesCache = installedRoleNames;
2564
+ return installedTypedAgentRoleNamesCache;
2565
+ }
2566
+ function isTypedAgentRolePayload(payload) {
2567
+ const agentRole = readPayloadAgentRole(payload);
2568
+ return agentRole !== "" && (KNOWN_TYPED_AGENT_ROLES.has(agentRole)
2569
+ || readInstalledTypedAgentRoleNames().has(agentRole));
2570
+ }
2460
2571
  function readPayloadTurnId(payload) {
2461
2572
  return safeString(payload.turn_id ?? payload.turnId).trim();
2462
2573
  }
@@ -2598,6 +2709,48 @@ async function resolveInternalSessionIdForPayload(cwd, payloadSessionId, stateDi
2598
2709
  return canonicalSessionId;
2599
2710
  return payloadSessionId;
2600
2711
  }
2712
+ async function readRootSessionStateFromStateDir(stateDir) {
2713
+ const sessionPath = join(stateDir, "session.json");
2714
+ if (!existsSync(sessionPath))
2715
+ return null;
2716
+ try {
2717
+ const content = await readFile(sessionPath, "utf-8");
2718
+ return JSON.parse(content);
2719
+ }
2720
+ catch {
2721
+ return null;
2722
+ }
2723
+ }
2724
+ function payloadMatchesSessionPointer(payloadSessionId, state) {
2725
+ const canonicalSessionId = safeString(state.session_id).trim();
2726
+ const nativeSessionId = safeString(state.native_session_id).trim();
2727
+ const ownerOmxSessionId = safeString(state.owner_omx_session_id).trim();
2728
+ if (!payloadSessionId)
2729
+ return true;
2730
+ return payloadSessionId === canonicalSessionId
2731
+ || (nativeSessionId !== "" && payloadSessionId === nativeSessionId)
2732
+ || (ownerOmxSessionId !== "" && payloadSessionId === ownerOmxSessionId);
2733
+ }
2734
+ function isRootSessionPointerLive(state) {
2735
+ const hasPidMetadata = Number.isInteger(state.pid) && state.pid > 0;
2736
+ if (!hasPidMetadata)
2737
+ return false;
2738
+ return !isSessionStale(state, {
2739
+ ...(state.platform ? { platform: state.platform } : {}),
2740
+ });
2741
+ }
2742
+ async function readLiveRootSessionPointerConflict(stateDir, payloadSessionId) {
2743
+ if (!payloadSessionId)
2744
+ return null;
2745
+ const rootState = await readRootSessionStateFromStateDir(stateDir);
2746
+ if (!rootState)
2747
+ return null;
2748
+ if (payloadMatchesSessionPointer(payloadSessionId, rootState))
2749
+ return null;
2750
+ if (!isRootSessionPointerLive(rootState))
2751
+ return null;
2752
+ return rootState;
2753
+ }
2601
2754
  async function readUsableSessionStateFromStateDir(cwd, stateDir) {
2602
2755
  const sessionPath = join(stateDir, "session.json");
2603
2756
  if (!existsSync(sessionPath))
@@ -2621,15 +2774,23 @@ const DEEP_INTERVIEW_ALLOWED_WRITE_PREFIXES = [
2621
2774
  ".omx/context",
2622
2775
  ".omx/interviews",
2623
2776
  ".omx/specs",
2777
+ ".omx/tmp",
2624
2778
  ".omx/state",
2625
2779
  ];
2626
2780
  const RALPLAN_ALLOWED_WRITE_PREFIXES = [
2627
2781
  ".omx/context",
2628
2782
  ".omx/plans",
2629
2783
  ".omx/specs",
2784
+ ".omx/tmp",
2630
2785
  ".omx/state",
2631
2786
  ".beads",
2632
2787
  ];
2788
+ const PROTECTED_PLANNING_STATE_FILE_NAMES = new Set([
2789
+ "autopilot-state.json",
2790
+ "deep-interview-state.json",
2791
+ "ralplan-state.json",
2792
+ "skill-active-state.json",
2793
+ ]);
2633
2794
  const PLANNING_MODE_IMPLEMENTATION_TOOL_NAMES = new Set([
2634
2795
  "Write",
2635
2796
  "Edit",
@@ -2684,20 +2845,48 @@ function hasExplicitExecutionHandoffSkill(state, sessionId, threadId) {
2684
2845
  return listActiveSkills(state ?? {}).some((entry) => (RALPLAN_EXECUTION_HANDOFF_SKILLS.has(entry.skill)
2685
2846
  && matchesSkillStopContext(entry, state ?? {}, sessionId, threadId)));
2686
2847
  }
2687
- function isAllowedPlanningArtifactPath(cwd, rawPath, allowedPrefixes) {
2848
+ function normalizePlanningArtifactRelativePath(cwd, rawPath) {
2688
2849
  const trimmed = rawPath.trim().replace(/^['"]|['"]$/g, "");
2689
2850
  if (!trimmed || trimmed.includes("\0"))
2690
- return false;
2691
- let relativePath;
2851
+ return null;
2692
2852
  try {
2693
2853
  const absolute = resolve(cwd, trimmed);
2694
- relativePath = relative(cwd, absolute).replace(/\\/g, "/");
2854
+ const relativePath = relative(cwd, absolute).replace(/\\/g, "/");
2855
+ if (!relativePath || relativePath.startsWith("..") || relativePath.startsWith("/"))
2856
+ return null;
2857
+ return relativePath;
2695
2858
  }
2696
2859
  catch {
2697
- return false;
2860
+ return null;
2698
2861
  }
2699
- if (!relativePath || relativePath.startsWith("..") || relativePath.startsWith("/"))
2862
+ }
2863
+ function isProtectedPlanningStatePath(relativePath) {
2864
+ if (relativePath !== ".omx/state" && !relativePath.startsWith(".omx/state/"))
2700
2865
  return false;
2866
+ const fileName = relativePath.split("/").pop() ?? "";
2867
+ return PROTECTED_PLANNING_STATE_FILE_NAMES.has(fileName);
2868
+ }
2869
+ function isPlanningTmpRelativePath(relativePath) {
2870
+ return relativePath === ".omx/tmp" || relativePath.startsWith(".omx/tmp/");
2871
+ }
2872
+ function isAllowedPlanningTmpScratchPath(relativePath) {
2873
+ if (!isPlanningTmpRelativePath(relativePath))
2874
+ return true;
2875
+ const fileName = relativePath.split("/").pop() ?? "";
2876
+ if (!fileName || fileName === "tmp")
2877
+ return true;
2878
+ const extension = extname(fileName).toLowerCase();
2879
+ return !PLANNING_TMP_SCRIPT_LIKE_EXTENSIONS.has(extension);
2880
+ }
2881
+ function isAllowedPlanningArtifactPath(cwd, rawPath, allowedPrefixes) {
2882
+ const relativePath = normalizePlanningArtifactRelativePath(cwd, rawPath);
2883
+ if (!relativePath)
2884
+ return false;
2885
+ if (isProtectedPlanningStatePath(relativePath))
2886
+ return false;
2887
+ if (isPlanningTmpRelativePath(relativePath)) {
2888
+ return allowedPrefixes.includes(".omx/tmp") && isAllowedPlanningTmpScratchPath(relativePath);
2889
+ }
2701
2890
  return allowedPrefixes.some((prefix) => (relativePath === prefix || relativePath.startsWith(`${prefix}/`)));
2702
2891
  }
2703
2892
  function isAllowedDeepInterviewArtifactPath(cwd, rawPath) {
@@ -2914,29 +3103,174 @@ function resolveCommandRedirectTarget(target, assignments) {
2914
3103
  }
2915
3104
  function extractDeepInterviewCommandRedirectTargets(command) {
2916
3105
  const targets = [];
2917
- for (const match of command.matchAll(/(?:^|[^>])>{1,2}\s*(["']?)([^\s&|;<>]+)\1/g)) {
3106
+ const commandOutsideHeredocBodies = stripHeredocBodiesForCommandScan(command);
3107
+ for (const match of commandOutsideHeredocBodies.matchAll(/(?:^|[^>])>{1,2}\s*(["']?)([^\s&|;<>]+)\1/g)) {
2918
3108
  const candidate = safeString(match[2]).trim();
2919
3109
  if (candidate && !isNullDeviceRedirectTarget(candidate))
2920
3110
  targets.push(candidate);
2921
3111
  }
2922
3112
  return targets;
2923
3113
  }
3114
+ function commandHasDestructiveGitSubcommand(command) {
3115
+ const destructiveSubcommands = new Set([
3116
+ "am",
3117
+ "apply",
3118
+ "checkout",
3119
+ "clean",
3120
+ "merge",
3121
+ "rebase",
3122
+ "reset",
3123
+ "restore",
3124
+ "rm",
3125
+ "switch",
3126
+ ]);
3127
+ for (const segment of splitShellCommandSegments(stripHeredocBodiesForCommandScan(command))) {
3128
+ const words = tokenizeShellWords(segment);
3129
+ for (let index = 0; index < words.length; index += 1) {
3130
+ if (shellWordBaseName(words[index] ?? "") !== "git")
3131
+ continue;
3132
+ const subcommandIndex = findGitSubcommandIndex(words, index + 1);
3133
+ if (subcommandIndex === null)
3134
+ continue;
3135
+ const subcommand = words[subcommandIndex] ?? "";
3136
+ if (destructiveSubcommands.has(subcommand))
3137
+ return true;
3138
+ if (subcommand === "worktree") {
3139
+ const worktreeSubcommandIndex = findGitSubcommandIndex(words, subcommandIndex + 1);
3140
+ if (worktreeSubcommandIndex === null)
3141
+ continue;
3142
+ const worktreeSubcommand = words[worktreeSubcommandIndex] ?? "";
3143
+ if (worktreeSubcommand === "add" || worktreeSubcommand === "move" || worktreeSubcommand === "mv" || worktreeSubcommand === "remove" || worktreeSubcommand === "rm" || worktreeSubcommand === "prune" || worktreeSubcommand === "repair" || worktreeSubcommand === "clean")
3144
+ return true;
3145
+ }
3146
+ if (subcommand.startsWith("checkout-"))
3147
+ return true;
3148
+ if (subcommand.startsWith("merge-") && subcommand !== "merge-base")
3149
+ return true;
3150
+ }
3151
+ }
3152
+ return false;
3153
+ }
3154
+ function commandHasPackageInstallIntent(command) {
3155
+ for (const segment of splitShellCommandSegments(stripHeredocBodiesForCommandScan(command))) {
3156
+ const words = tokenizeShellWords(segment);
3157
+ for (let index = 0; index < words.length; index += 1) {
3158
+ const headBase = shellWordBaseName(words[index] ?? "");
3159
+ if (headBase !== "npm" && headBase !== "pnpm" && headBase !== "yarn")
3160
+ continue;
3161
+ const subcommandIndex = findPackageInstallSubcommandIndex(words, index + 1);
3162
+ if (subcommandIndex === null)
3163
+ continue;
3164
+ const subcommand = words[subcommandIndex] ?? "";
3165
+ if (headBase === "npm" && (subcommand === "install" || subcommand === "i" || subcommand === "ci"))
3166
+ return true;
3167
+ if (headBase === "pnpm" && (subcommand === "install" || subcommand === "i" || subcommand === "add"))
3168
+ return true;
3169
+ if (headBase === "yarn" && (subcommand === "install" || subcommand === "add"))
3170
+ return true;
3171
+ }
3172
+ }
3173
+ return false;
3174
+ }
3175
+ function findPackageInstallSubcommandIndex(words, startIndex) {
3176
+ for (let index = startIndex; index < words.length; index += 1) {
3177
+ const word = words[index] ?? "";
3178
+ if (!word || word === "--")
3179
+ continue;
3180
+ if (isShellAssignmentWord(word))
3181
+ continue;
3182
+ if (word === "-C" || word === "--prefix" || word === "--dir" || word === "--cwd" || word === "-w" || word === "--workspace") {
3183
+ index += 1;
3184
+ continue;
3185
+ }
3186
+ if (word.startsWith("--prefix=") || word.startsWith("--dir=") || word.startsWith("--cwd=") || word.startsWith("--workspace="))
3187
+ continue;
3188
+ if (/^-C.+/.test(word) || /^-w.+/.test(word))
3189
+ continue;
3190
+ if (word.startsWith("-"))
3191
+ continue;
3192
+ return index;
3193
+ }
3194
+ return null;
3195
+ }
3196
+ function commandHasUntargetedPlanningForbiddenIntent(command) {
3197
+ return commandHasDestructiveGitSubcommand(command) || commandHasPackageInstallIntent(command);
3198
+ }
3199
+ function findGitSubcommandIndex(words, startIndex) {
3200
+ for (let index = startIndex; index < words.length; index += 1) {
3201
+ const word = words[index] ?? "";
3202
+ if (!word || word === "--")
3203
+ continue;
3204
+ if (isShellAssignmentWord(word))
3205
+ continue;
3206
+ if (word === "-C" || word === "-c" || word === "--git-dir" || word === "--work-tree" || word === "--namespace") {
3207
+ index += 1;
3208
+ continue;
3209
+ }
3210
+ if (word.startsWith("--git-dir=") || word.startsWith("--work-tree=") || word.startsWith("--namespace="))
3211
+ continue;
3212
+ if (word.startsWith("-"))
3213
+ continue;
3214
+ return index;
3215
+ }
3216
+ return null;
3217
+ }
2924
3218
  function commandHasDeepInterviewWriteIntent(command) {
2925
3219
  return commandInvokesApplyPatch(command)
2926
3220
  || extractDeepInterviewCommandRedirectTargets(command).length > 0
2927
3221
  || /\btee\s+(?:-a\s+)?[^\s&|;]+/.test(command)
3222
+ || extractConductorEditorWriteTargets(command).length > 0
2928
3223
  || /\bsed\s+(?:[^\n;&|]*\s)?-i(?:\b|['"])/.test(command)
3224
+ || /\bperl\s+(?:[^\n;&|]*\s)?-[^-\s]*i(?:\b|['"])/.test(command)
2929
3225
  || /\b(?:python3?|node|perl|ruby)\b[\s\S]{0,260}\b(?:writeFileSync|writeFile|write_text|open\([^)]*["']w|File\.write|Path\()/.test(command)
2930
- || /\b(?:git\s+(?:checkout|switch|restore|reset|apply|am|merge|rebase)|npm\s+(?:install|i|ci)|pnpm\s+(?:install|i)|yarn\s+(?:install|add))\b/.test(command);
3226
+ || extractConductorBashMutations(command).length > 0
3227
+ || extractConductorInterpreterWrites(command).length > 0
3228
+ || commandHasDestructiveGitSubcommand(command)
3229
+ || commandHasPackageInstallIntent(command);
2931
3230
  }
2932
3231
  function extractDeepInterviewCommandWriteTargets(command) {
2933
3232
  const assignments = extractCommandLiteralAssignments(command);
2934
3233
  const targets = extractDeepInterviewCommandRedirectTargets(command)
2935
3234
  .map((target) => resolveCommandRedirectTarget(target, assignments));
2936
- for (const match of command.matchAll(/\btee\s+(?:-a\s+)?(["']?)([^\s&|;<>]+)\1/g)) {
2937
- const candidate = safeString(match[2]).trim();
2938
- if (candidate)
2939
- targets.push(resolveCommandRedirectTarget(candidate, assignments));
3235
+ targets.push(...extractConductorEditorWriteTargets(command));
3236
+ for (const mutation of extractConductorBashMutations(command)) {
3237
+ targets.push(...mutation.targets);
3238
+ }
3239
+ for (const write of extractConductorInterpreterWrites(command)) {
3240
+ targets.push(...write.targets);
3241
+ }
3242
+ for (const segment of splitShellCommandSegments(stripHeredocBodiesForCommandScan(command))) {
3243
+ const words = tokenizeShellWords(segment);
3244
+ for (let index = 0; index < words.length; index += 1) {
3245
+ if (shellWordBaseName(words[index] ?? "") !== "tee")
3246
+ continue;
3247
+ let afterDoubleDash = false;
3248
+ for (let targetIndex = index + 1; targetIndex < words.length; targetIndex += 1) {
3249
+ const word = words[targetIndex] ?? "";
3250
+ if (!word || word === "|" || word === "|&" || word === "&&" || word === "||" || word === ";")
3251
+ break;
3252
+ if (!afterDoubleDash && word === "--") {
3253
+ afterDoubleDash = true;
3254
+ continue;
3255
+ }
3256
+ if (!afterDoubleDash && word.startsWith("-"))
3257
+ continue;
3258
+ if (word === ">" || word === ">>" || word === ">&" || word === "<" || word === "<<" || word === "<<<") {
3259
+ targetIndex += 1;
3260
+ continue;
3261
+ }
3262
+ if (/^(?:>{1,2}|<)\S+/.test(word))
3263
+ continue;
3264
+ if (!isNullDeviceRedirectTarget(word))
3265
+ targets.push(resolveCommandRedirectTarget(word, assignments));
3266
+ }
3267
+ }
3268
+ }
3269
+ for (const nestedCommand of extractNestedShellCommandStringsForStateScan(command)) {
3270
+ targets.push(...extractDeepInterviewCommandWriteTargets(nestedCommand));
3271
+ }
3272
+ for (const nestedCommand of extractNestedCommandSubstitutionStringsForStateScan(command)) {
3273
+ targets.push(...extractDeepInterviewCommandWriteTargets(nestedCommand));
2940
3274
  }
2941
3275
  return targets;
2942
3276
  }
@@ -2948,224 +3282,4057 @@ function isUnresolvedVariableTarget(target) {
2948
3282
  const normalized = target.trim().replace(/^['"]|['"]$/g, "");
2949
3283
  return /^\$\{?[A-Za-z_][A-Za-z0-9_]*\}?$/.test(normalized);
2950
3284
  }
2951
- function describeImplementationToolBlock(toolName, blockedPath, pathCount) {
2952
- if (pathCount === 0) {
2953
- const operationClass = isApplyPatchToolName(toolName) ? "apply_patch target extraction failed" : `${toolName} path`;
2954
- return `${operationClass} target <unresolved>; only planning artifact paths are allowed (${RALPLAN_ALLOWED_WRITE_PREFIXES.join(", ")})`;
2955
- }
2956
- const operationClass = isApplyPatchToolName(toolName) ? "apply_patch target" : `${toolName} path`;
2957
- return formatPlanningWriteBlockDetail(operationClass, blockedPath, RALPLAN_ALLOWED_WRITE_PREFIXES);
2958
- }
2959
- function isAllowedDeepInterviewBashWrite(cwd, command) {
2960
- if (!commandHasDeepInterviewWriteIntent(command))
2961
- return true;
2962
- if (/\bomx\s+(?:state\s+(?:write|read|clear)|question)\b/.test(command))
2963
- return true;
2964
- const targets = extractDeepInterviewCommandWriteTargets(command);
2965
- return targets.length > 0 && targets.every((target) => isAllowedDeepInterviewArtifactPath(cwd, target));
2966
- }
2967
- async function readActiveDeepInterviewStateForPreToolUse(cwd, stateDir, sessionId, threadId) {
2968
- const canonicalState = sessionId
2969
- ? await readVisibleSkillActiveStateForStateDir(stateDir, sessionId)
2970
- : await readSkillActiveState(join(stateDir, SKILL_ACTIVE_STATE_FILE));
2971
- if (!canonicalState)
3285
+ function normalizeSameCommandScriptTarget(cwd, rawPath, assignments) {
3286
+ const trimmed = resolveCommandRedirectTarget(rawPath.trim().replace(/^['"]|['"]$/g, ""), assignments);
3287
+ if (!trimmed || trimmed.includes("\0") || isUnresolvedVariableTarget(trimmed))
2972
3288
  return null;
2973
- const modeState = sessionId
2974
- ? await readStopSessionPinnedState("deep-interview-state.json", cwd, sessionId, stateDir)
2975
- : await readJsonIfExists(join(stateDir, "deep-interview-state.json"));
2976
- if (isActiveDeepInterviewPhase(modeState) && modeState && modeStateMatchesSkillStopContext(modeState, cwd, sessionId)) {
2977
- const hasActiveDeepInterviewSkill = listActiveSkills(canonicalState).some((entry) => (entry.skill === "deep-interview"
2978
- && matchesSkillStopContext(entry, canonicalState, sessionId, threadId)));
2979
- if (hasActiveDeepInterviewSkill)
2980
- return modeState;
3289
+ try {
3290
+ return resolve(cwd, trimmed);
2981
3291
  }
2982
- const autopilotState = sessionId
2983
- ? await readStopSessionPinnedState("autopilot-state.json", cwd, sessionId, stateDir)
2984
- : await readJsonIfExists(join(stateDir, "autopilot-state.json"));
2985
- if (!autopilotState || autopilotState.active !== true)
2986
- return null;
2987
- const autopilotMode = safeString(autopilotState.mode).trim();
2988
- if (autopilotMode && autopilotMode !== "autopilot")
2989
- return null;
2990
- if (!modeStateMatchesSkillStopContext(autopilotState, cwd, sessionId))
2991
- return null;
2992
- const terminalAutopilotRunState = await readCanonicalTerminalRunStateForStop(cwd, sessionId, "autopilot");
2993
- if (terminalAutopilotRunState)
2994
- return null;
2995
- const autopilotStatePhase = safeString(autopilotState.current_phase ?? autopilotState.currentPhase).trim().toLowerCase();
2996
- const autopilotIsDeepInterview = normalizeAutopilotPhase(autopilotStatePhase) === "deep-interview";
2997
- const hasDeepInterviewScopedAutopilotSkill = listActiveSkills(canonicalState).some((entry) => (entry.skill === "autopilot"
2998
- && normalizeAutopilotPhase(safeString(entry.phase).trim().toLowerCase()) === "deep-interview"
2999
- && matchesSkillStopContext(entry, canonicalState, sessionId, threadId)));
3000
- const hasActiveAutopilotSkill = listActiveSkills(canonicalState).some((entry) => (entry.skill === "autopilot"
3001
- && matchesSkillStopContext(entry, canonicalState, sessionId, threadId)));
3002
- if (!hasActiveAutopilotSkill)
3292
+ catch {
3003
3293
  return null;
3004
- if (!autopilotIsDeepInterview && !hasDeepInterviewScopedAutopilotSkill)
3294
+ }
3295
+ }
3296
+ function normalizeCommandDirectoryTarget(rawPath) {
3297
+ const trimmed = rawPath.trim().replace(/^['"]|['"]$/g, "");
3298
+ if (!trimmed
3299
+ || trimmed.includes("\0")
3300
+ || trimmed.startsWith("~")
3301
+ || trimmed.startsWith("-")
3302
+ || isUnresolvedVariableTarget(trimmed)
3303
+ || /[`$]/.test(trimmed)) {
3005
3304
  return null;
3006
- return autopilotState;
3305
+ }
3306
+ return trimmed;
3007
3307
  }
3008
- async function readActiveRalplanStateForPreToolUse(cwd, stateDir, sessionId, threadId) {
3009
- const modeState = sessionId
3010
- ? await readStopSessionPinnedState("ralplan-state.json", cwd, sessionId, stateDir)
3011
- : await readJsonIfExists(join(stateDir, "ralplan-state.json"));
3012
- const canonicalState = sessionId
3013
- ? await readVisibleSkillActiveStateForStateDir(stateDir, sessionId)
3014
- : await readSkillActiveState(join(stateDir, SKILL_ACTIVE_STATE_FILE));
3015
- if (isActiveRalplanPhase(modeState) && modeState && modeStateMatchesSkillStopContext(modeState, cwd, sessionId)) {
3016
- if (hasExplicitExecutionHandoffSkill(canonicalState, sessionId, threadId))
3308
+ function resolveSimpleCdCommandCwd(currentCwd, words) {
3309
+ const commandIndex = findWrappedCommandPositionIndex(words, 0);
3310
+ if (commandIndex === null || shellWordBaseName(words[commandIndex] ?? "") !== "cd")
3311
+ return null;
3312
+ for (let index = commandIndex + 1; index < words.length; index += 1) {
3313
+ const word = words[index] ?? "";
3314
+ if (!word || word === "--")
3315
+ continue;
3316
+ if (word === "-L" || word === "-P" || word === "-e")
3317
+ continue;
3318
+ if (word.startsWith("-"))
3017
3319
  return null;
3018
- if (!canonicalState)
3320
+ const normalizedTarget = normalizeCommandDirectoryTarget(word);
3321
+ if (normalizedTarget === null)
3019
3322
  return null;
3020
- const hasActiveRalplanSkill = listActiveSkills(canonicalState).some((entry) => (entry.skill === "ralplan"
3021
- && matchesSkillStopContext(entry, canonicalState, sessionId, threadId)));
3022
- if (hasActiveRalplanSkill)
3023
- return modeState;
3323
+ try {
3324
+ return resolve(currentCwd, normalizedTarget);
3325
+ }
3326
+ catch {
3327
+ return null;
3328
+ }
3024
3329
  }
3025
- const autopilotState = sessionId
3026
- ? await readStopSessionPinnedState("autopilot-state.json", cwd, sessionId, stateDir)
3027
- : await readJsonIfExists(join(stateDir, "autopilot-state.json"));
3028
- if (!autopilotState || autopilotState.active !== true)
3029
- return null;
3030
- const autopilotMode = safeString(autopilotState.mode).trim();
3031
- if (autopilotMode && autopilotMode !== "autopilot")
3032
- return null;
3033
- if (!modeStateMatchesSkillStopContext(autopilotState, cwd, sessionId))
3034
- return null;
3035
- const terminalAutopilotRunState = await readCanonicalTerminalRunStateForStop(cwd, sessionId, "autopilot");
3036
- if (terminalAutopilotRunState)
3037
- return null;
3038
- if (!canonicalState)
3039
- return null;
3040
- const hasActiveAutopilotSkill = listActiveSkills(canonicalState).some((entry) => (entry.skill === "autopilot"
3041
- && matchesSkillStopContext(entry, canonicalState, sessionId, threadId)));
3042
- if (!hasActiveAutopilotSkill)
3043
- return null;
3044
- const autopilotStatePhase = safeString(autopilotState.current_phase ?? autopilotState.currentPhase).trim().toLowerCase();
3045
- if (isAutopilotReviewReworkPhase(autopilotStatePhase))
3046
- return null;
3047
- if (!canAutopilotSkillMirrorSupplyRalplanPhase(autopilotStatePhase))
3048
- return null;
3049
- const hasRalplanScopedAutopilotSkill = listActiveSkills(canonicalState).some((entry) => (entry.skill === "autopilot"
3050
- && isAutopilotRalplanLikePhase(safeString(entry.phase).trim().toLowerCase())
3051
- && matchesSkillStopContext(entry, canonicalState, sessionId, threadId)));
3052
- if (!isAutopilotRalplanLikePhase(autopilotStatePhase) && !hasRalplanScopedAutopilotSkill)
3053
- return null;
3054
- return hasActiveAutopilotSkill ? autopilotState : null;
3330
+ return null;
3055
3331
  }
3056
- function isAllowedRalplanBashWrite(cwd, command) {
3057
- const beadsCommand = classifyRalplanBeadsMetadataCommand(cwd, command);
3058
- const targets = extractDeepInterviewCommandWriteTargets(command);
3059
- const hasAllowedTargets = targets.length > 0
3060
- && targets.every((target) => isAllowedRalplanArtifactPath(cwd, target));
3061
- if (beadsCommand.present) {
3062
- return beadsCommand.allowed && (targets.length === 0 || hasAllowedTargets);
3332
+ function isEnvCwdChangingOption(word) {
3333
+ return word === "-C"
3334
+ || word === "--chdir"
3335
+ || word.startsWith("--chdir=")
3336
+ || /^-C.+/.test(word);
3337
+ }
3338
+ function resolveEnvWrappedCommandCwd(currentCwd, words, envWordIndex, operandIndex) {
3339
+ let effectiveCwd = currentCwd;
3340
+ for (let index = envWordIndex + 1; index < operandIndex; index += 1) {
3341
+ const word = words[index] ?? "";
3342
+ if (!word || word === "--" || isShellAssignmentWord(word))
3343
+ continue;
3344
+ if (word === "-S" || word === "--split-string" || word.startsWith("-S") || word.startsWith("--split-string="))
3345
+ return null;
3346
+ if (word === "-u" || word === "--unset" || word === "-a" || word === "--argv0") {
3347
+ index += 1;
3348
+ continue;
3349
+ }
3350
+ if (word.startsWith("--unset=") || word.startsWith("--argv0=") || /^-u.+/.test(word) || /^-a.+/.test(word))
3351
+ continue;
3352
+ if (isEnvCwdChangingOption(word)) {
3353
+ let target = "";
3354
+ if (word === "-C" || word === "--chdir") {
3355
+ target = words[index + 1] ?? "";
3356
+ index += 1;
3357
+ }
3358
+ else if (word.startsWith("--chdir=")) {
3359
+ target = word.slice("--chdir=".length);
3360
+ }
3361
+ else if (word.startsWith("-C") && word.length > 2) {
3362
+ target = word.slice(2);
3363
+ }
3364
+ const normalizedTarget = normalizeCommandDirectoryTarget(target);
3365
+ if (normalizedTarget === null)
3366
+ return null;
3367
+ try {
3368
+ effectiveCwd = resolve(effectiveCwd, normalizedTarget);
3369
+ }
3370
+ catch {
3371
+ return null;
3372
+ }
3373
+ }
3063
3374
  }
3064
- if (!commandHasDeepInterviewWriteIntent(command))
3065
- return true;
3066
- if (/\bomx\s+(?:state\s+(?:write|read|clear)|question)\b/.test(command))
3067
- return true;
3068
- return hasAllowedTargets;
3375
+ return effectiveCwd;
3069
3376
  }
3070
- function buildRalplanBashBlockedDetail(cwd, command) {
3071
- const targets = extractDeepInterviewCommandWriteTargets(command);
3072
- const blockedTarget = targets.find((target) => !isAllowedRalplanArtifactPath(cwd, target));
3073
- if (blockedTarget && isUnresolvedVariableTarget(blockedTarget)) {
3074
- return `unresolved Bash write target ${blockedTarget} is not under allowed planning artifact paths or metadata paths (${RALPLAN_ALLOWED_WRITE_PREFIXES.join(", ")})`;
3075
- }
3076
- if (blockedTarget) {
3377
+ function resolveWrappedCommandExecutionContext(words, currentCwd, startIndex = 0) {
3378
+ let commandWordIndex = skipShellCommandPositionPrefixWords(words, startIndex);
3379
+ let effectiveCwd = currentCwd;
3380
+ for (let unwrapCount = 0; unwrapCount < 8; unwrapCount += 1) {
3381
+ const commandWord = words[commandWordIndex] ?? "";
3382
+ if (!commandWord)
3383
+ return null;
3384
+ const commandWordBase = shellWordBaseName(commandWord);
3385
+ const operandIndex = commandWordBase === "env"
3386
+ ? findEnvDispatchOperandIndex(words, commandWordIndex + 1)
3387
+ : commandWordBase === "command"
3388
+ ? findCommandDispatchOperandIndex(words, commandWordIndex + 1)
3389
+ : commandWordBase === "exec"
3390
+ ? findExecDispatchOperandIndex(words, commandWordIndex + 1)
3391
+ : commandWordBase === "time"
3392
+ ? findTimeDispatchOperandIndex(words, commandWordIndex + 1)
3393
+ : commandWordBase === "timeout"
3394
+ ? findTimeoutDispatchOperandIndex(words, commandWordIndex + 1)
3395
+ : commandWordBase === "nohup" || commandWordBase === "setsid"
3396
+ ? findCommandDispatchOperandIndex(words, commandWordIndex + 1)
3397
+ : commandWordBase === "coproc"
3398
+ ? findCoprocDispatchOperandIndex(words, commandWordIndex + 1)
3399
+ : commandWordBase === "xargs"
3400
+ ? findXargsDispatchOperandIndex(words, commandWordIndex + 1)
3401
+ : commandWordBase === "nice"
3402
+ ? findNiceDispatchOperandIndex(words, commandWordIndex + 1)
3403
+ : commandWordBase === "stdbuf"
3404
+ ? findStdbufDispatchOperandIndex(words, commandWordIndex + 1)
3405
+ : null;
3406
+ if (operandIndex === null)
3407
+ return { index: commandWordIndex, cwd: effectiveCwd };
3408
+ if (commandWordBase === "env") {
3409
+ const envCwd = resolveEnvWrappedCommandCwd(effectiveCwd, words, commandWordIndex, operandIndex);
3410
+ if (envCwd === null)
3411
+ return null;
3412
+ effectiveCwd = envCwd;
3413
+ }
3414
+ const nextCommandWordIndex = skipShellCommandPositionPrefixWords(words, operandIndex);
3415
+ if (nextCommandWordIndex === commandWordIndex)
3416
+ return { index: commandWordIndex, cwd: effectiveCwd };
3417
+ commandWordIndex = nextCommandWordIndex;
3418
+ }
3419
+ return null;
3420
+ }
3421
+ function resolveStateWriteInputFileCwd(cwd, commandPrefix) {
3422
+ const words = tokenizeShellWords(stripHeredocBodiesForCommandScan(commandPrefix));
3423
+ let effectiveCwd = cwd;
3424
+ let activeWrapper = null;
3425
+ for (let index = 0; index < words.length; index += 1) {
3426
+ const word = words[index] ?? "";
3427
+ if (!word || isShellAssignmentWord(word))
3428
+ continue;
3429
+ const wordBase = shellWordBaseName(word);
3430
+ if (wordBase === "env" || wordBase === "npm" || wordBase === "pnpm" || wordBase === "yarn") {
3431
+ activeWrapper = wordBase;
3432
+ continue;
3433
+ }
3434
+ if (!activeWrapper)
3435
+ continue;
3436
+ const isEnvChdirOption = activeWrapper === "env" && isEnvCwdChangingOption(word);
3437
+ const isPackageManagerCwdOption = activeWrapper !== "env"
3438
+ && (word === "-C"
3439
+ || word === "--prefix"
3440
+ || word === "--dir"
3441
+ || word === "--cwd"
3442
+ || word.startsWith("--prefix=")
3443
+ || word.startsWith("--dir=")
3444
+ || word.startsWith("--cwd=")
3445
+ || /^-C.+/.test(word));
3446
+ if (isEnvChdirOption || isPackageManagerCwdOption) {
3447
+ let target = "";
3448
+ if (word === "-C" || word === "--chdir" || word === "--prefix" || word === "--dir" || word === "--cwd") {
3449
+ target = words[index + 1] ?? "";
3450
+ }
3451
+ else if (word.startsWith("--chdir=")) {
3452
+ target = word.slice("--chdir=".length);
3453
+ }
3454
+ else if (word.startsWith("--prefix=")) {
3455
+ target = word.slice("--prefix=".length);
3456
+ }
3457
+ else if (word.startsWith("--dir=")) {
3458
+ target = word.slice("--dir=".length);
3459
+ }
3460
+ else if (word.startsWith("--cwd=")) {
3461
+ target = word.slice("--cwd=".length);
3462
+ }
3463
+ else if (word.startsWith("-C") && word.length > 2) {
3464
+ target = word.slice(2);
3465
+ }
3466
+ const normalizedTarget = normalizeCommandDirectoryTarget(target);
3467
+ if (normalizedTarget === null)
3468
+ return null;
3469
+ effectiveCwd = resolve(effectiveCwd, normalizedTarget);
3470
+ if (word === "-C" || word === "--chdir" || word === "--prefix" || word === "--dir" || word === "--cwd") {
3471
+ index += 1;
3472
+ }
3473
+ }
3474
+ }
3475
+ return effectiveCwd;
3476
+ }
3477
+ function findShellFunctionDefinitionAt(command, index) {
3478
+ if (index > 0) {
3479
+ let previous = index - 1;
3480
+ while (previous >= 0 && /\s/.test(command[previous] ?? ""))
3481
+ previous -= 1;
3482
+ if (previous >= 0 && !/[;&|(){}]/.test(command[previous] ?? ""))
3483
+ return null;
3484
+ }
3485
+ let cursor = index;
3486
+ while (/\s/.test(command[cursor] ?? ""))
3487
+ cursor += 1;
3488
+ const candidate = command.slice(cursor);
3489
+ const functionKeywordMatch = candidate.match(/^function\s+([A-Za-z_][A-Za-z0-9_]*)\s*(?:\(\s*\))?\s*([\{(])/);
3490
+ if (functionKeywordMatch) {
3491
+ return {
3492
+ name: functionKeywordMatch[1],
3493
+ openBraceIndex: cursor + functionKeywordMatch[0].length - 1,
3494
+ bodyOpenChar: functionKeywordMatch[2] === "(" ? "(" : "{",
3495
+ };
3496
+ }
3497
+ const bareFunctionMatch = candidate.match(/^([A-Za-z_][A-Za-z0-9_]*)\s*\(\s*\)\s*([\{(])/);
3498
+ if (bareFunctionMatch) {
3499
+ return {
3500
+ name: bareFunctionMatch[1],
3501
+ openBraceIndex: cursor + bareFunctionMatch[0].length - 1,
3502
+ bodyOpenChar: bareFunctionMatch[2] === "(" ? "(" : "{",
3503
+ };
3504
+ }
3505
+ return null;
3506
+ }
3507
+ function findShellFunctionBodyEnd(command, openBraceIndex, bodyOpenChar) {
3508
+ let depth = 1;
3509
+ let quote = null;
3510
+ for (let index = openBraceIndex + 1; index < command.length; index += 1) {
3511
+ const char = command[index] ?? "";
3512
+ if (char === "\\" && quote !== "'") {
3513
+ index += 1;
3514
+ continue;
3515
+ }
3516
+ if (char === "'" || char === "\"") {
3517
+ if (quote === char) {
3518
+ quote = null;
3519
+ }
3520
+ else if (!quote) {
3521
+ quote = char;
3522
+ }
3523
+ continue;
3524
+ }
3525
+ if (quote)
3526
+ continue;
3527
+ if (bodyOpenChar === "{" && char === "{") {
3528
+ depth += 1;
3529
+ continue;
3530
+ }
3531
+ if (bodyOpenChar === "{" && char === "}") {
3532
+ depth -= 1;
3533
+ if (depth === 0)
3534
+ return index;
3535
+ }
3536
+ if (bodyOpenChar === "(" && char === "(") {
3537
+ depth += 1;
3538
+ continue;
3539
+ }
3540
+ if (bodyOpenChar === "(" && char === ")") {
3541
+ depth -= 1;
3542
+ if (depth === 0)
3543
+ return index;
3544
+ }
3545
+ }
3546
+ return -1;
3547
+ }
3548
+ function isShellFunctionInvokedLater(command, functionName) {
3549
+ for (const segment of splitShellCommandSegments(command)) {
3550
+ const words = tokenizeShellWords(segment);
3551
+ if (isShellFunctionInvokedFromWords(words, 0, functionName))
3552
+ return true;
3553
+ }
3554
+ return false;
3555
+ }
3556
+ function isShellFunctionInvokedFromWords(words, startIndex, functionName) {
3557
+ const index = skipShellCommandPositionPrefixWords(words, startIndex);
3558
+ const head = words[index] ?? "";
3559
+ if (head === functionName)
3560
+ return true;
3561
+ if (shellWordBaseName(head) === "time") {
3562
+ const timeOperandIndex = findTimeDispatchOperandIndex(words, index + 1);
3563
+ if (timeOperandIndex !== null) {
3564
+ const timeCommandIndex = skipShellCommandPositionPrefixWords(words, timeOperandIndex);
3565
+ if ((words[timeCommandIndex] ?? "") === functionName)
3566
+ return true;
3567
+ }
3568
+ }
3569
+ if (shellWordBaseName(head) === "coproc") {
3570
+ const coprocOperandIndex = findCoprocDispatchOperandIndex(words, index + 1);
3571
+ if (coprocOperandIndex !== null) {
3572
+ return isShellFunctionInvokedFromWords(words, coprocOperandIndex, functionName);
3573
+ }
3574
+ }
3575
+ if (shellWordBaseName(head) === "setsid") {
3576
+ const setsidOperandIndex = findCommandDispatchOperandIndex(words, index + 1);
3577
+ if (setsidOperandIndex !== null) {
3578
+ return isShellFunctionInvokedFromWords(words, setsidOperandIndex, functionName);
3579
+ }
3580
+ }
3581
+ if (shellWordBaseName(head) === "command") {
3582
+ const commandOperandIndex = findCommandDispatchOperandIndex(words, index + 1);
3583
+ if (commandOperandIndex !== null && shellWordBaseName(words[commandOperandIndex] ?? "") === "time") {
3584
+ return isShellFunctionInvokedFromWords(words, commandOperandIndex, functionName);
3585
+ }
3586
+ }
3587
+ return false;
3588
+ }
3589
+ function firstNonOptionSourceOperand(words, sourceWordIndex) {
3590
+ for (let index = sourceWordIndex + 1; index < words.length; index += 1) {
3591
+ const word = words[index] ?? "";
3592
+ if (!word || word === "--")
3593
+ continue;
3594
+ if (word.startsWith("-"))
3595
+ continue;
3596
+ return word;
3597
+ }
3598
+ return "";
3599
+ }
3600
+ function stdinRedirectOperands(words) {
3601
+ const operands = [];
3602
+ for (let index = 0; index < words.length; index += 1) {
3603
+ const word = words[index] ?? "";
3604
+ if (word === "<") {
3605
+ const operand = words[index + 1] ?? "";
3606
+ if (operand)
3607
+ operands.push(operand);
3608
+ index += 1;
3609
+ continue;
3610
+ }
3611
+ if (/^\d*<[^<&].+/.test(word)) {
3612
+ operands.push(word.replace(/^\d*</, ""));
3613
+ }
3614
+ }
3615
+ return operands;
3616
+ }
3617
+ function firstShellScriptOperands(words, shellWordIndex) {
3618
+ const operands = [];
3619
+ for (let index = shellWordIndex + 1; index < words.length; index += 1) {
3620
+ const word = words[index] ?? "";
3621
+ if (!word || word === "--")
3622
+ continue;
3623
+ if (isShellCommandStringOption(word))
3624
+ return operands;
3625
+ if (isShellOptionWithSeparateValue(word)) {
3626
+ const value = words[index + 1] ?? "";
3627
+ if (value)
3628
+ operands.push(value);
3629
+ index += 1;
3630
+ continue;
3631
+ }
3632
+ if (word.startsWith("--init-file=") || word.startsWith("--rcfile=")) {
3633
+ operands.push(word.slice(word.indexOf("=") + 1));
3634
+ continue;
3635
+ }
3636
+ if (word.startsWith("-"))
3637
+ continue;
3638
+ operands.push(word);
3639
+ return operands;
3640
+ }
3641
+ return operands;
3642
+ }
3643
+ function firstShellScriptOperand(words, shellWordIndex) {
3644
+ return firstShellScriptOperands(words, shellWordIndex)[0] ?? "";
3645
+ }
3646
+ function isPythonInterpreterCommandWord(base) {
3647
+ return /^python(?:[0-9]+(?:\.[0-9]+)*)?$/.test(base);
3648
+ }
3649
+ function isNodeInterpreterCommandWord(base) {
3650
+ return /^(?:node|nodejs)$/.test(base);
3651
+ }
3652
+ function isPythonRuntimeOptionWithSeparateValue(word) {
3653
+ return word === "-B"
3654
+ || word === "-b"
3655
+ || word === "-d"
3656
+ || word === "-E"
3657
+ || word === "-i"
3658
+ || word === "-I"
3659
+ || word === "-O"
3660
+ || word === "-OO"
3661
+ || word === "-P"
3662
+ || word === "-q"
3663
+ || word === "-R"
3664
+ || word === "-s"
3665
+ || word === "-S"
3666
+ || word === "-u"
3667
+ || word === "-v"
3668
+ || word === "-V"
3669
+ || word === "--check-hash-based-pycs"
3670
+ || word === "-W"
3671
+ || word === "-X";
3672
+ }
3673
+ function isScriptInterpreterCommandWord(word) {
3674
+ const base = shellWordBaseName(word);
3675
+ return isNestedShellCommandWord(base)
3676
+ || isNodeInterpreterCommandWord(base)
3677
+ || base === "bun"
3678
+ || base === "tsx"
3679
+ || base === "deno"
3680
+ || isPythonInterpreterCommandWord(base)
3681
+ || base === "perl"
3682
+ || base === "ruby"
3683
+ || base === "php"
3684
+ || base === "lua"
3685
+ || base === "go"
3686
+ || base === "npx"
3687
+ || base === "npm"
3688
+ || base === "pnpm"
3689
+ || base === "yarn";
3690
+ }
3691
+ function isTsxRuntimeOptionWithSeparateValue(word) {
3692
+ return word === "--tsconfig";
3693
+ }
3694
+ function isTsxRuntimeModeWord(word) {
3695
+ return word === "watch";
3696
+ }
3697
+ function firstInterpreterScriptOperands(words, interpreterWordIndex) {
3698
+ const base = shellWordBaseName(words[interpreterWordIndex] ?? "");
3699
+ if (isNestedShellCommandWord(base))
3700
+ return firstShellScriptOperands(words, interpreterWordIndex);
3701
+ const operands = [];
3702
+ let sawGoRun = false;
3703
+ let sawPackageRunner = base === "npx";
3704
+ for (let index = interpreterWordIndex + 1; index < words.length; index += 1) {
3705
+ const word = words[index] ?? "";
3706
+ if (!word || word === "--")
3707
+ continue;
3708
+ if (isPythonInterpreterCommandWord(base)) {
3709
+ if (word === "-c" || word === "-m")
3710
+ return operands;
3711
+ if (isPythonRuntimeOptionWithSeparateValue(word)) {
3712
+ index += 1;
3713
+ continue;
3714
+ }
3715
+ if (word.startsWith("-W") || word.startsWith("-X"))
3716
+ continue;
3717
+ if (word.startsWith("-"))
3718
+ continue;
3719
+ operands.push(word);
3720
+ return operands;
3721
+ }
3722
+ if (isNodeInterpreterCommandWord(base) || base === "bun" || base === "tsx") {
3723
+ if (word === "-e" || word === "--eval" || word === "-p" || word === "--print")
3724
+ return operands;
3725
+ if (runtimeOptionConsumesNextWord(word) || (base === "tsx" && isTsxRuntimeOptionWithSeparateValue(word))) {
3726
+ const value = words[index + 1] ?? "";
3727
+ if (value && (word === "-r" || word === "--require" || word === "--import" || word === "--loader" || word === "--experimental-loader")) {
3728
+ operands.push(value);
3729
+ }
3730
+ index += 1;
3731
+ continue;
3732
+ }
3733
+ if (word.startsWith("--require=") || word.startsWith("--import=") || word.startsWith("--loader=") || word.startsWith("--experimental-loader=")) {
3734
+ operands.push(word.slice(word.indexOf("=") + 1));
3735
+ continue;
3736
+ }
3737
+ if (word.startsWith("--eval=") || word.startsWith("--print="))
3738
+ return operands;
3739
+ if (base === "tsx" && isTsxRuntimeModeWord(word))
3740
+ continue;
3741
+ if (word.startsWith("-"))
3742
+ continue;
3743
+ operands.push(word);
3744
+ return operands;
3745
+ }
3746
+ if (base === "deno") {
3747
+ if (word === "eval" || word === "repl")
3748
+ return operands;
3749
+ if (word === "run")
3750
+ continue;
3751
+ if (word.startsWith("-"))
3752
+ continue;
3753
+ operands.push(word);
3754
+ return operands;
3755
+ }
3756
+ if (base === "go") {
3757
+ if (!sawGoRun) {
3758
+ if (word === "run") {
3759
+ sawGoRun = true;
3760
+ continue;
3761
+ }
3762
+ return operands;
3763
+ }
3764
+ if (word.startsWith("-"))
3765
+ continue;
3766
+ operands.push(word);
3767
+ return operands;
3768
+ }
3769
+ if (base === "npm" || base === "pnpm" || base === "yarn" || base === "npx") {
3770
+ if (!sawPackageRunner) {
3771
+ if (word === "exec" || word === "x" || word === "dlx") {
3772
+ sawPackageRunner = true;
3773
+ continue;
3774
+ }
3775
+ return operands;
3776
+ }
3777
+ if (word.startsWith("-"))
3778
+ continue;
3779
+ if (word === "tsx" || word === "node" || word === "bun" || word === "deno")
3780
+ continue;
3781
+ operands.push(word);
3782
+ return operands;
3783
+ }
3784
+ if (base === "perl" || base === "ruby" || base === "php" || base === "lua") {
3785
+ if (word === "-e" || word.startsWith("-e"))
3786
+ return operands;
3787
+ if (word.startsWith("-"))
3788
+ continue;
3789
+ operands.push(word);
3790
+ return operands;
3791
+ }
3792
+ }
3793
+ return operands;
3794
+ }
3795
+ function firstPlanningTmpScriptExecutionTarget(cwd, command) {
3796
+ const scanCommand = (currentCwd, currentCommand, activeCommands) => {
3797
+ const normalizedCommand = stripHeredocBodiesForCommandScan(normalizeShellLineContinuations(currentCommand));
3798
+ const commandKey = `${currentCwd}\0${normalizedCommand.trim()}`;
3799
+ if (!normalizedCommand.trim() || activeCommands.has(commandKey))
3800
+ return null;
3801
+ const assignments = extractCommandLiteralAssignments(normalizedCommand);
3802
+ const nextActiveCommands = new Set(activeCommands);
3803
+ nextActiveCommands.add(commandKey);
3804
+ let effectiveCwd = currentCwd;
3805
+ const normalizeExecutionTarget = (operandCwd, rawPath) => {
3806
+ const absoluteTarget = normalizeSameCommandScriptTarget(operandCwd, rawPath, assignments);
3807
+ if (!absoluteTarget)
3808
+ return null;
3809
+ const relativePath = relative(cwd, absoluteTarget).replace(/\\/g, "/");
3810
+ if (!relativePath || relativePath.startsWith("..") || relativePath.startsWith("/"))
3811
+ return null;
3812
+ return relativePath;
3813
+ };
3814
+ for (const segment of splitShellCommandSegments(normalizedCommand)) {
3815
+ const words = tokenizeShellWords(segment);
3816
+ const cdCwd = resolveSimpleCdCommandCwd(effectiveCwd, words);
3817
+ if (cdCwd !== null) {
3818
+ effectiveCwd = cdCwd;
3819
+ continue;
3820
+ }
3821
+ const wrappedCommandContext = resolveWrappedCommandExecutionContext(words, effectiveCwd);
3822
+ for (let index = 0; index < words.length; index += 1) {
3823
+ const word = words[index] ?? "";
3824
+ const operandCwd = wrappedCommandContext && index >= wrappedCommandContext.index ? wrappedCommandContext.cwd : effectiveCwd;
3825
+ const operands = word === "source" || word === "."
3826
+ ? [firstNonOptionSourceOperand(words, index)].filter(Boolean)
3827
+ : isScriptInterpreterCommandWord(word)
3828
+ ? [...firstInterpreterScriptOperands(words, index), ...stdinRedirectOperands(words)]
3829
+ : [];
3830
+ for (const operand of operands) {
3831
+ const relativePath = normalizeExecutionTarget(operandCwd, operand);
3832
+ if (relativePath && isPlanningTmpRelativePath(relativePath))
3833
+ return relativePath;
3834
+ }
3835
+ }
3836
+ if (wrappedCommandContext !== null) {
3837
+ const directTarget = words[wrappedCommandContext.index] ?? "";
3838
+ const relativePath = normalizeExecutionTarget(wrappedCommandContext.cwd, directTarget);
3839
+ if (relativePath && isPlanningTmpRelativePath(relativePath))
3840
+ return relativePath;
3841
+ }
3842
+ for (const nestedCommand of extractNestedShellCommandStringsForStateScan(segment)) {
3843
+ const nestedTarget = scanCommand(effectiveCwd, nestedCommand, nextActiveCommands);
3844
+ if (nestedTarget)
3845
+ return nestedTarget;
3846
+ }
3847
+ for (const nestedCommand of extractNestedCommandSubstitutionStringsForStateScan(segment)) {
3848
+ const nestedTarget = scanCommand(effectiveCwd, nestedCommand, nextActiveCommands);
3849
+ if (nestedTarget)
3850
+ return nestedTarget;
3851
+ }
3852
+ }
3853
+ return null;
3854
+ };
3855
+ return scanCommand(cwd, command, new Set());
3856
+ }
3857
+ function sourcesFileWrittenEarlierInSameCommand(cwd, command) {
3858
+ const scanCommand = (currentCwd, currentCommand, activeCommands, writtenTargets) => {
3859
+ const normalizedCommand = stripHeredocBodiesForCommandScan(normalizeShellLineContinuations(currentCommand));
3860
+ const commandKey = `${currentCwd}\0${normalizedCommand.trim()}`;
3861
+ if (!normalizedCommand.trim() || activeCommands.has(commandKey))
3862
+ return false;
3863
+ const assignments = extractCommandLiteralAssignments(normalizedCommand);
3864
+ const nextActiveCommands = new Set(activeCommands);
3865
+ nextActiveCommands.add(commandKey);
3866
+ for (const write of extractConductorInterpreterWrites(currentCommand)) {
3867
+ for (const target of write.targets) {
3868
+ const normalizedTarget = normalizeSameCommandScriptTarget(currentCwd, target, assignments);
3869
+ if (normalizedTarget)
3870
+ writtenTargets.add(normalizedTarget);
3871
+ }
3872
+ }
3873
+ let effectiveCwd = currentCwd;
3874
+ for (const segment of splitShellCommandSegments(normalizedCommand)) {
3875
+ const words = tokenizeShellWords(segment);
3876
+ const cdCwd = resolveSimpleCdCommandCwd(effectiveCwd, words);
3877
+ if (cdCwd !== null) {
3878
+ effectiveCwd = cdCwd;
3879
+ continue;
3880
+ }
3881
+ const wrappedCommandContext = resolveWrappedCommandExecutionContext(words, effectiveCwd);
3882
+ for (let index = 0; index < words.length; index += 1) {
3883
+ const word = words[index] ?? "";
3884
+ const operandCwd = wrappedCommandContext && index >= wrappedCommandContext.index ? wrappedCommandContext.cwd : effectiveCwd;
3885
+ const operand = word === "source" || word === "."
3886
+ ? normalizeSameCommandScriptTarget(operandCwd, firstNonOptionSourceOperand(words, index), assignments)
3887
+ : isNestedShellCommandWord(word)
3888
+ ? normalizeSameCommandScriptTarget(operandCwd, firstShellScriptOperand(words, index), assignments)
3889
+ : null;
3890
+ if (operand && writtenTargets.has(operand))
3891
+ return true;
3892
+ }
3893
+ const directExecutionTarget = wrappedCommandContext === null
3894
+ ? null
3895
+ : normalizeSameCommandScriptTarget(wrappedCommandContext.cwd, words[wrappedCommandContext.index] ?? "", assignments);
3896
+ if (directExecutionTarget && writtenTargets.has(directExecutionTarget))
3897
+ return true;
3898
+ for (const nestedCommand of extractNestedShellCommandStringsForStateScan(segment)) {
3899
+ if (scanCommand(effectiveCwd, nestedCommand, nextActiveCommands, writtenTargets))
3900
+ return true;
3901
+ }
3902
+ for (const target of extractDeepInterviewCommandWriteTargets(segment)) {
3903
+ const normalizedTarget = normalizeSameCommandScriptTarget(effectiveCwd, target, assignments);
3904
+ if (normalizedTarget)
3905
+ writtenTargets.add(normalizedTarget);
3906
+ }
3907
+ }
3908
+ return false;
3909
+ };
3910
+ return scanCommand(cwd, command, new Set(), new Set());
3911
+ }
3912
+ function describeImplementationToolBlock(toolName, blockedPath, pathCount) {
3913
+ if (pathCount === 0) {
3914
+ const operationClass = isApplyPatchToolName(toolName) ? "apply_patch target extraction failed" : `${toolName} path`;
3915
+ return `${operationClass} target <unresolved>; only planning artifact paths are allowed (${RALPLAN_ALLOWED_WRITE_PREFIXES.join(", ")})`;
3916
+ }
3917
+ const operationClass = isApplyPatchToolName(toolName) ? "apply_patch target" : `${toolName} path`;
3918
+ return formatPlanningWriteBlockDetail(operationClass, blockedPath, RALPLAN_ALLOWED_WRITE_PREFIXES);
3919
+ }
3920
+ // `omx state` mutations normally route through the gate-enforcing `state_write`
3921
+ // backend, so the hook defers to that gate rather than blocking the transport.
3922
+ // The backend does NOT gate generic standalone deep-interview/ralplan
3923
+ // *deactivation*, and it normalizes non-terminal tracked-workflow writes to
3924
+ // `active=true`, so commands that would implicitly activate a tracked workflow
3925
+ // while planning is still protected are blocked here. Ralplan terminal closeout
3926
+ // is the narrow exception: the backend has a dedicated completeRalplanSession
3927
+ // path that coherently terminalizes root and session state when the payload is a
3928
+ // complete consensus-approved terminal state.
3929
+ function readStateWriteInputPayload(cwd, command, sourceCommand = command) {
3930
+ const stateWriteOperations = collectOmxStateCommandOperations(command, "write");
3931
+ if (stateWriteOperations.length === 0)
3932
+ return null;
3933
+ if (stateWriteOperations.length > 1)
3934
+ return null;
3935
+ const stateWriteOperation = stateWriteOperations[0];
3936
+ if (!stateWriteOperation)
3937
+ return null;
3938
+ const stateWriteArgs = stateWriteOperation.args;
3939
+ const mergeModeFlag = (payload) => {
3940
+ const mode = readStateWriteFlagValue(stateWriteArgs, "--mode");
3941
+ return normalizeStateWriteClassificationPayload(mode ? { ...payload, mode } : payload);
3942
+ };
3943
+ const inlineInput = readStateWriteFlagValue(stateWriteArgs, "--input");
3944
+ const inputFile = readStateWriteFlagValue(stateWriteArgs, "--input-file");
3945
+ if (inlineInput !== undefined && inputFile !== undefined)
3946
+ return null;
3947
+ if (inlineInput !== undefined) {
3948
+ try {
3949
+ const parsed = JSON.parse(inlineInput);
3950
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed)
3951
+ ? mergeModeFlag(parsed)
3952
+ : null;
3953
+ }
3954
+ catch {
3955
+ return null;
3956
+ }
3957
+ }
3958
+ if (inputFile === undefined)
3959
+ return null;
3960
+ if (stateWriteOperation.nested)
3961
+ return null;
3962
+ if (hasPriorExecutableCommand(stateWriteOperation.prefix))
3963
+ return null;
3964
+ const canonicalCommandPrefix = stateWriteOperation.commandPrefix || stateWriteOperation.prefix || "";
3965
+ const rawCommandPrefix = sourceCommand !== command
3966
+ ? (() => {
3967
+ const sourceCommandIndex = sourceCommand.lastIndexOf(command);
3968
+ return sourceCommandIndex >= 0 ? sourceCommand.slice(0, sourceCommandIndex) : "";
3969
+ })()
3970
+ : "";
3971
+ const resolvedInputFileCwd = resolveStateWriteInputFileCwd(cwd, canonicalCommandPrefix || rawCommandPrefix);
3972
+ if (resolvedInputFileCwd === null)
3973
+ return null;
3974
+ try {
3975
+ const raw = readFileSync(resolve(resolvedInputFileCwd, inputFile.trim()), "utf-8");
3976
+ const parsed = JSON.parse(raw);
3977
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed)
3978
+ ? mergeModeFlag(parsed)
3979
+ : null;
3980
+ }
3981
+ catch {
3982
+ return null;
3983
+ }
3984
+ }
3985
+ function envCommandHasCwdChangingOption(words, startIndex) {
3986
+ for (let index = startIndex; index < words.length; index += 1) {
3987
+ const token = words[index] ?? "";
3988
+ if (!token || token === "--")
3989
+ continue;
3990
+ if (isShellAssignmentWord(token))
3991
+ continue;
3992
+ if (isEnvCwdChangingOption(token))
3993
+ return true;
3994
+ if (token === "-S" || token === "--split-string" || token.startsWith("-S") || token.startsWith("--split-string=")) {
3995
+ return false;
3996
+ }
3997
+ if (token === "-u" || token === "--unset" || token === "-a" || token === "--argv0") {
3998
+ index += 1;
3999
+ continue;
4000
+ }
4001
+ if (token.startsWith("--unset=") || token.startsWith("--argv0="))
4002
+ continue;
4003
+ if (/^-u.+/.test(token) || /^-a.+/.test(token))
4004
+ continue;
4005
+ if (token.startsWith("-"))
4006
+ continue;
4007
+ return false;
4008
+ }
4009
+ return false;
4010
+ }
4011
+ function hasPriorExecutableCommand(commandPrefix) {
4012
+ const words = tokenizeShellWords(stripHeredocBodiesForCommandScan(commandPrefix));
4013
+ let commandStart = true;
4014
+ for (let index = 0; index < words.length; index += 1) {
4015
+ const word = words[index] ?? "";
4016
+ if (!word)
4017
+ continue;
4018
+ if (word === "&&" || word === "||" || word === ";" || word === "&" || word === "|" || word === "|&") {
4019
+ commandStart = true;
4020
+ continue;
4021
+ }
4022
+ if (/^[A-Za-z_][A-Za-z0-9_]*=/.test(word))
4023
+ continue;
4024
+ if (commandStart)
4025
+ return true;
4026
+ commandStart = false;
4027
+ }
4028
+ return false;
4029
+ }
4030
+ function tokenizeShellWords(segment) {
4031
+ segment = normalizeShellLineContinuations(segment);
4032
+ const words = [];
4033
+ let current = "";
4034
+ let quote = null;
4035
+ for (let index = 0; index < segment.length; index += 1) {
4036
+ const char = segment[index];
4037
+ if (char === "\\" && quote !== "'") {
4038
+ index += 1;
4039
+ current += segment[index] ?? "";
4040
+ continue;
4041
+ }
4042
+ if (char === "'" || char === "\"") {
4043
+ if (quote === char) {
4044
+ quote = null;
4045
+ }
4046
+ else if (!quote) {
4047
+ quote = char;
4048
+ }
4049
+ else {
4050
+ current += char;
4051
+ }
4052
+ continue;
4053
+ }
4054
+ if (!quote && (char === ";" || char === "&")) {
4055
+ if (current) {
4056
+ words.push(current);
4057
+ current = "";
4058
+ }
4059
+ const next = segment[index + 1] ?? "";
4060
+ if (char === "&" && next === "&") {
4061
+ words.push("&&");
4062
+ index += 1;
4063
+ }
4064
+ else {
4065
+ words.push(char);
4066
+ }
4067
+ continue;
4068
+ }
4069
+ if (!quote && /\s/.test(char)) {
4070
+ if (current) {
4071
+ words.push(current);
4072
+ current = "";
4073
+ }
4074
+ continue;
4075
+ }
4076
+ if (!quote && (char === "(" || char === ")" || char === "{" || char === "}")) {
4077
+ if (current) {
4078
+ words.push(current);
4079
+ current = "";
4080
+ }
4081
+ words.push(char);
4082
+ continue;
4083
+ }
4084
+ if (!quote && char === "|") {
4085
+ if (current) {
4086
+ words.push(current);
4087
+ current = "";
4088
+ }
4089
+ const next = segment[index + 1] ?? "";
4090
+ if (next === "&" || next === "|") {
4091
+ words.push(`${char}${next}`);
4092
+ index += 1;
4093
+ }
4094
+ else {
4095
+ words.push(char);
4096
+ }
4097
+ continue;
4098
+ }
4099
+ if (!quote && (char === "<" || char === ">")) {
4100
+ if (current) {
4101
+ words.push(current);
4102
+ current = "";
4103
+ }
4104
+ const next = segment[index + 1] ?? "";
4105
+ if (next === char) {
4106
+ const third = segment[index + 2] ?? "";
4107
+ if (char === "<" && third === "<") {
4108
+ words.push("<<<");
4109
+ index += 2;
4110
+ }
4111
+ else {
4112
+ words.push(`${char}${next}`);
4113
+ index += 1;
4114
+ }
4115
+ }
4116
+ else if (char === ">" && next === "&") {
4117
+ words.push(">&");
4118
+ index += 1;
4119
+ }
4120
+ else {
4121
+ words.push(char);
4122
+ }
4123
+ continue;
4124
+ }
4125
+ current += char;
4126
+ }
4127
+ if (current)
4128
+ words.push(current);
4129
+ return words;
4130
+ }
4131
+ function isOmxCliEntryPath(token, runtimeWrapper) {
4132
+ const trimmed = token.trim().replace(/^['"]|['"]$/g, "");
4133
+ if (!trimmed || trimmed.includes("\0"))
4134
+ return false;
4135
+ const normalized = trimmed.replace(/\\/g, "/");
4136
+ const entryBasename = normalized.split("/").filter(Boolean).pop() ?? "";
4137
+ if (entryBasename === "omx" || entryBasename === "omx.js")
4138
+ return true;
4139
+ if (normalized.endsWith("/node_modules/.bin/omx") || normalized === "node_modules/.bin/omx")
4140
+ return true;
4141
+ if (normalized.endsWith("/dist/cli/omx.js") || normalized === "dist/cli/omx.js")
4142
+ return true;
4143
+ if (runtimeWrapper === "tsx" && (normalized.endsWith("/src/cli/omx.ts") || normalized === "src/cli/omx.ts"))
4144
+ return true;
4145
+ if (runtimeWrapper === "tsx" && (normalized.endsWith("/dist/cli/omx.js") || normalized === "dist/cli/omx.js"))
4146
+ return true;
4147
+ return false;
4148
+ }
4149
+ function extractEnvSplitStringCommand(words, startIndex) {
4150
+ for (let index = startIndex; index < words.length; index += 1) {
4151
+ const token = words[index] ?? "";
4152
+ if (!token)
4153
+ continue;
4154
+ if (token === "-S" || token === "--split-string") {
4155
+ const operand = words[index + 1] ?? "";
4156
+ if (!operand)
4157
+ return "";
4158
+ const tail = words.slice(index + 2).join(" ");
4159
+ return tail ? `env ${operand} ${tail}` : `env ${operand}`;
4160
+ }
4161
+ if (token.startsWith("--split-string=") || (token.startsWith("-S") && token.length > 2)) {
4162
+ const operand = token.startsWith("--split-string=")
4163
+ ? token.slice("--split-string=".length)
4164
+ : token.slice(2);
4165
+ if (!operand)
4166
+ return "";
4167
+ const tail = words.slice(index + 1).join(" ");
4168
+ return tail ? `env ${operand} ${tail}` : `env ${operand}`;
4169
+ }
4170
+ if (isShellAssignmentWord(token))
4171
+ continue;
4172
+ if (token === "-u" || token === "--unset" || token === "-C" || token === "--chdir" || token === "-a" || token === "--argv0") {
4173
+ index += 1;
4174
+ continue;
4175
+ }
4176
+ if (token.startsWith("--unset=") || token.startsWith("--chdir=") || token.startsWith("--argv0="))
4177
+ continue;
4178
+ if (/^-u.+/.test(token) || /^-C.+/.test(token) || /^-a.+/.test(token))
4179
+ continue;
4180
+ if (token.startsWith("-"))
4181
+ continue;
4182
+ break;
4183
+ }
4184
+ return "";
4185
+ }
4186
+ function extractPackageManagerExecCommand(command, words, startIndex, headBase) {
4187
+ let index = startIndex;
4188
+ if (headBase === "npm" || headBase === "pnpm" || headBase === "yarn") {
4189
+ const subcommandIndex = findPackageManagerExecSubcommandIndex(words, startIndex, headBase);
4190
+ if (subcommandIndex === null)
4191
+ return "";
4192
+ index = subcommandIndex + 1;
4193
+ }
4194
+ else if (headBase !== "npx" && headBase !== "pnpx") {
4195
+ return "";
4196
+ }
4197
+ for (; index < words.length; index += 1) {
4198
+ const token = words[index] ?? "";
4199
+ if (!token)
4200
+ continue;
4201
+ if (token === "--") {
4202
+ const tail = sliceShellWordsTailPreservingQuoting(command, index + 1);
4203
+ return tail || "";
4204
+ }
4205
+ if (isShellAssignmentWord(token))
4206
+ continue;
4207
+ if (token === "-c" || token === "--call") {
4208
+ return words[index + 1] ?? "";
4209
+ }
4210
+ if (token.startsWith("-c") && token.length > 2)
4211
+ return token.slice(2);
4212
+ if (token.startsWith("--call="))
4213
+ return token.slice("--call=".length);
4214
+ if (token === "--package" || token === "-w" || token === "--workspace" || token === "--allow-scripts") {
4215
+ index += 1;
4216
+ continue;
4217
+ }
4218
+ if (token.startsWith("--package=") || token.startsWith("--workspace=") || token.startsWith("--allow-scripts="))
4219
+ continue;
4220
+ if (token.startsWith("-w") && token.length > 2)
4221
+ continue;
4222
+ if (token.startsWith("-"))
4223
+ continue;
4224
+ const tail = sliceShellWordsTailPreservingQuoting(command, index);
4225
+ return tail || "";
4226
+ }
4227
+ return "";
4228
+ }
4229
+ function findPackageManagerExecSubcommandIndex(words, startIndex, headBase) {
4230
+ for (let index = startIndex; index < words.length; index += 1) {
4231
+ const token = words[index] ?? "";
4232
+ if (!token)
4233
+ continue;
4234
+ if (isShellAssignmentWord(token))
4235
+ continue;
4236
+ if (token === "-C" || token === "--prefix" || token === "--dir" || token === "-w" || token === "--workspace" || token === "--package" || token === "--allow-scripts") {
4237
+ index += 1;
4238
+ continue;
4239
+ }
4240
+ if (token.startsWith("--prefix=") || token.startsWith("--dir=") || token.startsWith("--workspace=") || token.startsWith("--package=") || token.startsWith("--allow-scripts="))
4241
+ continue;
4242
+ if (/^-C.+/.test(token) || /^-w.+/.test(token))
4243
+ continue;
4244
+ if (token.startsWith("-"))
4245
+ continue;
4246
+ if (token === "exec" || (headBase === "npm" && token === "x") || ((headBase === "pnpm" || headBase === "yarn") && token === "dlx")) {
4247
+ return index;
4248
+ }
4249
+ return null;
4250
+ }
4251
+ return null;
4252
+ }
4253
+ function unwrapOmxStateTransportCommandOnce(command) {
4254
+ const words = tokenizeShellWords(normalizeShellLineContinuations(command).trim());
4255
+ if (words.length === 0)
4256
+ return null;
4257
+ let index = 0;
4258
+ while (index < words.length && isShellAssignmentWord(words[index] ?? ""))
4259
+ index += 1;
4260
+ if (index >= words.length)
4261
+ return null;
4262
+ const head = words[index] ?? "";
4263
+ if (!head)
4264
+ return null;
4265
+ const headBase = shellWordBaseName(head);
4266
+ if (headBase === "command" || headBase === "builtin" || headBase === "exec" || headBase === "nohup" || headBase === "setsid") {
4267
+ let remainderIndex = index + 1;
4268
+ while (remainderIndex < words.length) {
4269
+ const token = words[remainderIndex] ?? "";
4270
+ if (!token) {
4271
+ remainderIndex += 1;
4272
+ continue;
4273
+ }
4274
+ if (token === "--") {
4275
+ remainderIndex += 1;
4276
+ continue;
4277
+ }
4278
+ if (head === "exec" && token === "-a") {
4279
+ remainderIndex += 2;
4280
+ continue;
4281
+ }
4282
+ if (token.startsWith("-")) {
4283
+ remainderIndex += 1;
4284
+ continue;
4285
+ }
4286
+ break;
4287
+ }
4288
+ const remainder = sliceShellWordsTailPreservingQuoting(command, remainderIndex);
4289
+ return remainder || null;
4290
+ }
4291
+ if (headBase === "env") {
4292
+ if (envCommandHasCwdChangingOption(words, index + 1)) {
4293
+ return null;
4294
+ }
4295
+ const splitOperand = extractEnvSplitStringCommand(words, index + 1);
4296
+ if (splitOperand) {
4297
+ return splitOperand;
4298
+ }
4299
+ const envCommandIndex = findEnvDispatchOperandIndex(words, index + 1);
4300
+ if (envCommandIndex !== null) {
4301
+ const remainder = sliceShellWordsTailPreservingQuoting(command, envCommandIndex);
4302
+ return remainder || null;
4303
+ }
4304
+ const remainder = sliceShellWordsTailPreservingQuoting(command, index + 1);
4305
+ return remainder || null;
4306
+ }
4307
+ if (headBase === "time") {
4308
+ const operandIndex = findTimeDispatchOperandIndex(words, index + 1);
4309
+ if (operandIndex !== null) {
4310
+ const remainder = sliceShellWordsTailPreservingQuoting(command, operandIndex);
4311
+ return remainder || null;
4312
+ }
4313
+ return null;
4314
+ }
4315
+ if (headBase === "nice") {
4316
+ const operandIndex = findNiceDispatchOperandIndex(words, index + 1);
4317
+ if (operandIndex !== null) {
4318
+ const remainder = sliceShellWordsTailPreservingQuoting(command, operandIndex);
4319
+ return remainder || null;
4320
+ }
4321
+ return null;
4322
+ }
4323
+ if (headBase === "stdbuf") {
4324
+ const operandIndex = findStdbufDispatchOperandIndex(words, index + 1);
4325
+ if (operandIndex !== null) {
4326
+ const remainder = sliceShellWordsTailPreservingQuoting(command, operandIndex);
4327
+ return remainder || null;
4328
+ }
4329
+ return null;
4330
+ }
4331
+ if (headBase === "npm" || headBase === "pnpm" || headBase === "yarn" || headBase === "npx" || headBase === "pnpx") {
4332
+ const packageManagerCommand = extractPackageManagerExecCommand(command, words, index + 1, headBase);
4333
+ if (packageManagerCommand) {
4334
+ return packageManagerCommand;
4335
+ }
4336
+ }
4337
+ if (isNestedShellCommandWord(headBase)) {
4338
+ const commandStringIndex = findShellCommandStringArgIndex(words, index + 1);
4339
+ if (commandStringIndex !== null) {
4340
+ const nestedCommand = words[commandStringIndex] ?? "";
4341
+ if (nestedCommand && !isDynamicNestedCommandString(nestedCommand)) {
4342
+ const remainder = sliceShellWordsTailPreservingQuoting(command, commandStringIndex + 1);
4343
+ return remainder ? `${nestedCommand} ${remainder}` : nestedCommand;
4344
+ }
4345
+ }
4346
+ }
4347
+ if (headBase === "eval") {
4348
+ const nestedCommand = words.slice(index + 1).join(" ");
4349
+ if (nestedCommand && !isDynamicNestedCommandString(nestedCommand))
4350
+ return nestedCommand;
4351
+ }
4352
+ if (headBase === "node" || headBase === "bun" || headBase === "tsx") {
4353
+ const entryIndex = (() => {
4354
+ for (let candidateIndex = index + 1; candidateIndex < words.length; candidateIndex += 1) {
4355
+ const candidate = words[candidateIndex] ?? "";
4356
+ if (!candidate)
4357
+ continue;
4358
+ if (candidate.startsWith("-"))
4359
+ continue;
4360
+ return candidateIndex;
4361
+ }
4362
+ return -1;
4363
+ })();
4364
+ if (entryIndex >= 0) {
4365
+ const entryPath = words[entryIndex] ?? "";
4366
+ if (entryPath && isOmxCliEntryPath(entryPath, headBase)) {
4367
+ const remainder = sliceShellWordsTailPreservingQuoting(command, entryIndex + 1);
4368
+ return remainder ? `omx ${remainder}` : "omx";
4369
+ }
4370
+ }
4371
+ return null;
4372
+ }
4373
+ if (isOmxCliEntryPath(head, null)) {
4374
+ const remainder = sliceShellWordsTailPreservingQuoting(command, index + 1);
4375
+ return remainder ? `omx ${remainder}` : "omx";
4376
+ }
4377
+ return null;
4378
+ }
4379
+ function canonicalizeOmxStateTransportCommand(command) {
4380
+ let current = normalizeShellLineContinuations(command).trim();
4381
+ for (let passes = 0; passes < 8; passes += 1) {
4382
+ const next = unwrapOmxStateTransportCommandOnce(current);
4383
+ if (!next || next === current)
4384
+ return current;
4385
+ current = next.trim();
4386
+ }
4387
+ return current;
4388
+ }
4389
+ function sliceShellWordsTailPreservingQuoting(command, startWordIndex) {
4390
+ const normalized = normalizeShellLineContinuations(command);
4391
+ let quote = null;
4392
+ let wordIndex = 0;
4393
+ let currentWordStart = null;
4394
+ for (let index = 0; index < normalized.length; index += 1) {
4395
+ const char = normalized[index] ?? "";
4396
+ if (char === "\\" && quote !== "'") {
4397
+ if (currentWordStart === null)
4398
+ currentWordStart = index;
4399
+ index += 1;
4400
+ continue;
4401
+ }
4402
+ if (char === "'" || char === "\"") {
4403
+ if (quote === char) {
4404
+ quote = null;
4405
+ }
4406
+ else if (!quote) {
4407
+ quote = char;
4408
+ if (currentWordStart === null)
4409
+ currentWordStart = index;
4410
+ }
4411
+ else if (currentWordStart === null) {
4412
+ currentWordStart = index;
4413
+ }
4414
+ continue;
4415
+ }
4416
+ if (!quote && /\s/.test(char)) {
4417
+ if (currentWordStart !== null) {
4418
+ if (wordIndex === startWordIndex)
4419
+ return normalized.slice(currentWordStart);
4420
+ wordIndex += 1;
4421
+ currentWordStart = null;
4422
+ }
4423
+ continue;
4424
+ }
4425
+ if (currentWordStart === null)
4426
+ currentWordStart = index;
4427
+ }
4428
+ if (currentWordStart !== null && wordIndex === startWordIndex) {
4429
+ return normalized.slice(currentWordStart);
4430
+ }
4431
+ return null;
4432
+ }
4433
+ function normalizeShellLineContinuations(command) {
4434
+ let normalized = "";
4435
+ let quote = null;
4436
+ for (let index = 0; index < command.length; index += 1) {
4437
+ const char = command[index] ?? "";
4438
+ if (char === "'" || char === "\"") {
4439
+ if (quote === char) {
4440
+ quote = null;
4441
+ }
4442
+ else if (!quote) {
4443
+ quote = char;
4444
+ }
4445
+ normalized += char;
4446
+ continue;
4447
+ }
4448
+ if (char === "\\" && quote !== "'") {
4449
+ const next = command[index + 1] ?? "";
4450
+ if (next === "\r" && command[index + 2] === "\n") {
4451
+ index += 2;
4452
+ continue;
4453
+ }
4454
+ if (next === "\n") {
4455
+ index += 1;
4456
+ continue;
4457
+ }
4458
+ }
4459
+ normalized += char;
4460
+ }
4461
+ return normalized;
4462
+ }
4463
+ function readStateWriteFlagValue(args, flagName) {
4464
+ let value;
4465
+ for (let index = 0; index < args.length; index += 1) {
4466
+ const arg = args[index] ?? "";
4467
+ if (arg === flagName) {
4468
+ value = args[index + 1];
4469
+ index += 1;
4470
+ continue;
4471
+ }
4472
+ if (arg.startsWith(`${flagName}=`))
4473
+ value = arg.slice(flagName.length + 1);
4474
+ }
4475
+ return value;
4476
+ }
4477
+ function shellWordBaseName(word) {
4478
+ return word.replace(/\\/g, "/").split("/").pop() ?? word;
4479
+ }
4480
+ function isOmxCliWrapperRuntime(word) {
4481
+ const base = shellWordBaseName(word);
4482
+ return base === "node" || base === "bun" || base === "tsx";
4483
+ }
4484
+ function isOmxCliWrapperScript(word) {
4485
+ const base = shellWordBaseName(word);
4486
+ return base === "omx" || base === "omx.js";
4487
+ }
4488
+ function runtimeOptionConsumesNextWord(option) {
4489
+ return option === "-r"
4490
+ || option === "--require"
4491
+ || option === "--import"
4492
+ || option === "--loader"
4493
+ || option === "--experimental-loader"
4494
+ || option === "--env-file"
4495
+ || option === "--conditions"
4496
+ || option === "--title"
4497
+ || option === "-C";
4498
+ }
4499
+ function runtimeOptionIsInlineCode(option) {
4500
+ return option === "-e"
4501
+ || option === "--eval"
4502
+ || option.startsWith("--eval=")
4503
+ || option === "-p"
4504
+ || option === "--print"
4505
+ || option.startsWith("--print=");
4506
+ }
4507
+ function findOmxCliWrapperRuntimeScriptIndex(words) {
4508
+ if (!isOmxCliWrapperRuntime(words[0] ?? ""))
4509
+ return null;
4510
+ for (let index = 1; index < words.length; index += 1) {
4511
+ const word = words[index] ?? "";
4512
+ if (!word)
4513
+ continue;
4514
+ if (word === "--")
4515
+ continue;
4516
+ if (runtimeOptionIsInlineCode(word))
4517
+ return null;
4518
+ if (runtimeOptionConsumesNextWord(word)) {
4519
+ index += 1;
4520
+ continue;
4521
+ }
4522
+ if (word.startsWith("-r") && word.length > 2)
4523
+ continue;
4524
+ if (word.startsWith("--") && word.includes("="))
4525
+ continue;
4526
+ if (word.startsWith("-"))
4527
+ continue;
4528
+ return isOmxCliWrapperScript(word) ? index : null;
4529
+ }
4530
+ return null;
4531
+ }
4532
+ function readOmxStateCommandArgsFromWords(words, operation) {
4533
+ if (isOmxCliWrapperScript(words[0] ?? "") && words[1] === "state" && words[2] === operation) {
4534
+ return words.slice(3);
4535
+ }
4536
+ const runtimeScriptIndex = findOmxCliWrapperRuntimeScriptIndex(words);
4537
+ if (runtimeScriptIndex !== null && words[runtimeScriptIndex + 1] === "state" && words[runtimeScriptIndex + 2] === operation) {
4538
+ return words.slice(runtimeScriptIndex + 3);
4539
+ }
4540
+ return null;
4541
+ }
4542
+ // Shell compound-command introducers can occupy command position after wrappers
4543
+ // such as `time`/`command`; skip them before matching the protected `omx state`
4544
+ // operation so wrapper-unwrapping keeps scanning the actual command body.
4545
+ function isShellCommandPositionPrefixWord(word) {
4546
+ return word === "("
4547
+ || word === "{"
4548
+ || word === "!"
4549
+ || word === "if"
4550
+ || word === "then"
4551
+ || word === "else"
4552
+ || word === "elif"
4553
+ || word === "do"
4554
+ || word === "while"
4555
+ || word === "until";
4556
+ }
4557
+ function findEnvDispatchOperandIndex(words, startIndex) {
4558
+ for (let index = startIndex; index < words.length; index += 1) {
4559
+ const option = words[index] ?? "";
4560
+ if (!option || option === "--")
4561
+ continue;
4562
+ if (isShellAssignmentWord(option))
4563
+ continue;
4564
+ if (option === "-S" || option === "--split-string" || option.startsWith("-S") || option.startsWith("--split-string=")) {
4565
+ return null;
4566
+ }
4567
+ if (option === "-u" || option === "--unset" || option === "-C" || option === "--chdir" || option === "-a" || option === "--argv0") {
4568
+ index += 1;
4569
+ continue;
4570
+ }
4571
+ if (option.startsWith("--unset=") || option.startsWith("--chdir=") || option.startsWith("--argv0="))
4572
+ continue;
4573
+ if (/^-u.+/.test(option) || /^-C.+/.test(option) || /^-a.+/.test(option))
4574
+ continue;
4575
+ if (option.startsWith("-"))
4576
+ continue;
4577
+ return index;
4578
+ }
4579
+ return null;
4580
+ }
4581
+ function findCommandDispatchOperandIndex(words, startIndex) {
4582
+ for (let index = startIndex; index < words.length; index += 1) {
4583
+ const option = words[index] ?? "";
4584
+ if (!option || option === "--")
4585
+ continue;
4586
+ if (isShellAssignmentWord(option))
4587
+ continue;
4588
+ if (option.startsWith("-"))
4589
+ continue;
4590
+ return index;
4591
+ }
4592
+ return null;
4593
+ }
4594
+ function findExecDispatchOperandIndex(words, startIndex) {
4595
+ for (let index = startIndex; index < words.length; index += 1) {
4596
+ const option = words[index] ?? "";
4597
+ if (!option || option === "--")
4598
+ continue;
4599
+ if (isShellAssignmentWord(option))
4600
+ continue;
4601
+ if (option === "-a") {
4602
+ index += 1;
4603
+ continue;
4604
+ }
4605
+ if (option.startsWith("-"))
4606
+ continue;
4607
+ return index;
4608
+ }
4609
+ return null;
4610
+ }
4611
+ function findTimeDispatchOperandIndex(words, startIndex) {
4612
+ for (let index = startIndex; index < words.length; index += 1) {
4613
+ const option = words[index] ?? "";
4614
+ if (!option || option === "--")
4615
+ continue;
4616
+ if (isShellAssignmentWord(option))
4617
+ continue;
4618
+ if (option === "-f" || option === "--format" || option === "-o" || option === "--output") {
4619
+ index += 1;
4620
+ continue;
4621
+ }
4622
+ if (option.startsWith("--format=") || option.startsWith("--output="))
4623
+ continue;
4624
+ if (/^-[^-]/.test(option)) {
4625
+ const shortOptions = option.slice(1);
4626
+ const lastValueOptionIndex = Math.max(shortOptions.lastIndexOf("f"), shortOptions.lastIndexOf("o"));
4627
+ if (lastValueOptionIndex >= 0 && lastValueOptionIndex === shortOptions.length - 1) {
4628
+ index += 1;
4629
+ }
4630
+ continue;
4631
+ }
4632
+ if (option.startsWith("-"))
4633
+ continue;
4634
+ return index;
4635
+ }
4636
+ return null;
4637
+ }
4638
+ function findTimeoutDispatchOperandIndex(words, startIndex) {
4639
+ let durationSeen = false;
4640
+ for (let index = startIndex; index < words.length; index += 1) {
4641
+ const token = words[index] ?? "";
4642
+ if (!token || token === "--")
4643
+ continue;
4644
+ if (isShellAssignmentWord(token))
4645
+ continue;
4646
+ if (!durationSeen) {
4647
+ if (token === "-k"
4648
+ || token === "--kill-after"
4649
+ || token === "-s"
4650
+ || token === "--signal"
4651
+ || token.startsWith("--kill-after=")
4652
+ || token.startsWith("--signal=")
4653
+ || token.startsWith("-k")
4654
+ || token.startsWith("-s")) {
4655
+ if (token === "-k"
4656
+ || token === "--kill-after"
4657
+ || token === "-s"
4658
+ || token === "--signal") {
4659
+ index += 1;
4660
+ }
4661
+ continue;
4662
+ }
4663
+ if (token === "-f"
4664
+ || token === "--foreground"
4665
+ || token === "-p"
4666
+ || token === "--preserve-status"
4667
+ || token === "-v"
4668
+ || token === "--verbose") {
4669
+ continue;
4670
+ }
4671
+ if (token.startsWith("-"))
4672
+ continue;
4673
+ durationSeen = true;
4674
+ continue;
4675
+ }
4676
+ return index;
4677
+ }
4678
+ return null;
4679
+ }
4680
+ function findNiceDispatchOperandIndex(words, startIndex) {
4681
+ for (let index = startIndex; index < words.length; index += 1) {
4682
+ const token = words[index] ?? "";
4683
+ if (!token || token === "--")
4684
+ continue;
4685
+ if (isShellAssignmentWord(token))
4686
+ continue;
4687
+ if (token === "-n" || token === "--adjustment") {
4688
+ index += 1;
4689
+ continue;
4690
+ }
4691
+ if (token.startsWith("--adjustment=") || /^-n.+/.test(token))
4692
+ continue;
4693
+ if (token.startsWith("-"))
4694
+ continue;
4695
+ return index;
4696
+ }
4697
+ return null;
4698
+ }
4699
+ function findStdbufDispatchOperandIndex(words, startIndex) {
4700
+ for (let index = startIndex; index < words.length; index += 1) {
4701
+ const token = words[index] ?? "";
4702
+ if (!token || token === "--")
4703
+ continue;
4704
+ if (isShellAssignmentWord(token))
4705
+ continue;
4706
+ if (token === "-i" || token === "-o" || token === "-e" || token === "--input" || token === "--output" || token === "--error") {
4707
+ index += 1;
4708
+ continue;
4709
+ }
4710
+ if (token.startsWith("--input=")
4711
+ || token.startsWith("--output=")
4712
+ || token.startsWith("--error=")
4713
+ || /^-[ioe].+/.test(token)) {
4714
+ continue;
4715
+ }
4716
+ if (token.startsWith("-"))
4717
+ continue;
4718
+ return index;
4719
+ }
4720
+ return null;
4721
+ }
4722
+ function isXargsOptionWithNextValue(option) {
4723
+ return option === "-a"
4724
+ || option === "--arg-file"
4725
+ || option === "-d"
4726
+ || option === "--delimiter"
4727
+ || option === "-E"
4728
+ || option === "--eof"
4729
+ || option === "-I"
4730
+ || option === "--replace"
4731
+ || option === "-J"
4732
+ || option === "-L"
4733
+ || option === "--max-lines"
4734
+ || option === "-n"
4735
+ || option === "--max-args"
4736
+ || option === "-P"
4737
+ || option === "--max-procs"
4738
+ || option === "-s"
4739
+ || option === "--max-chars";
4740
+ }
4741
+ function isXargsStandaloneOption(option) {
4742
+ return option === "-0"
4743
+ || option === "--null"
4744
+ || option === "-r"
4745
+ || option === "--no-run-if-empty"
4746
+ || option === "-t"
4747
+ || option === "--verbose"
4748
+ || option === "-p"
4749
+ || option === "--interactive"
4750
+ || option === "-x"
4751
+ || option === "--exit"
4752
+ || option === "-o"
4753
+ || option === "--open-tty"
4754
+ || option === "--show-limits";
4755
+ }
4756
+ function findXargsDispatchOperandIndex(words, startIndex) {
4757
+ for (let index = startIndex; index < words.length; index += 1) {
4758
+ const option = words[index] ?? "";
4759
+ if (!option || option === "--")
4760
+ continue;
4761
+ if (isShellAssignmentWord(option))
4762
+ continue;
4763
+ if (isXargsOptionWithNextValue(option)) {
4764
+ index += 1;
4765
+ continue;
4766
+ }
4767
+ if (option.startsWith("--arg-file=")
4768
+ || option.startsWith("--delimiter=")
4769
+ || option.startsWith("--eof=")
4770
+ || option.startsWith("--replace=")
4771
+ || option.startsWith("--max-lines=")
4772
+ || option.startsWith("--max-args=")
4773
+ || option.startsWith("--max-procs=")
4774
+ || option.startsWith("--max-chars=")
4775
+ || /^-[aAdDEIJLnPs][^-\s]*$/.test(option)) {
4776
+ continue;
4777
+ }
4778
+ if (isXargsStandaloneOption(option))
4779
+ continue;
4780
+ if (option.startsWith("-"))
4781
+ continue;
4782
+ return index;
4783
+ }
4784
+ return null;
4785
+ }
4786
+ function findCoprocDispatchOperandIndex(words, startIndex) {
4787
+ const firstIndex = findDispatchWordIndex(words, startIndex);
4788
+ if (firstIndex === null)
4789
+ return null;
4790
+ const firstWord = words[firstIndex] ?? "";
4791
+ if (isShellCommandPositionPrefixWord(firstWord))
4792
+ return firstIndex;
4793
+ const secondIndex = findDispatchWordIndex(words, firstIndex + 1);
4794
+ if (secondIndex !== null && isShellCommandPositionPrefixWord(words[secondIndex] ?? "")) {
4795
+ return secondIndex;
4796
+ }
4797
+ return firstIndex;
4798
+ }
4799
+ function findCaseArmCommandIndex(words, startIndex) {
4800
+ let index = startIndex;
4801
+ while (index < words.length && isShellAssignmentWord(words[index] ?? ""))
4802
+ index += 1;
4803
+ const head = words[index] ?? "";
4804
+ if (!head)
4805
+ return null;
4806
+ if (head === "case") {
4807
+ let inIndex = -1;
4808
+ for (let scanIndex = index + 1; scanIndex < words.length; scanIndex += 1) {
4809
+ const token = words[scanIndex] ?? "";
4810
+ if (!token)
4811
+ continue;
4812
+ if (token === "in") {
4813
+ inIndex = scanIndex;
4814
+ break;
4815
+ }
4816
+ if (token === "esac")
4817
+ return null;
4818
+ }
4819
+ if (inIndex < 0)
4820
+ return null;
4821
+ for (let scanIndex = inIndex + 1; scanIndex < words.length; scanIndex += 1) {
4822
+ const token = words[scanIndex] ?? "";
4823
+ if (!token)
4824
+ continue;
4825
+ if (token === "esac")
4826
+ return null;
4827
+ if (token === ")" || token.endsWith(")"))
4828
+ return scanIndex + 1;
4829
+ }
4830
+ return null;
4831
+ }
4832
+ if (head === ")" || head.endsWith(")")) {
4833
+ return index + 1;
4834
+ }
4835
+ if (words[index + 1] === ")") {
4836
+ return index + 2;
4837
+ }
4838
+ return null;
4839
+ }
4840
+ function isShellRedirectionWord(word) {
4841
+ return word === ">"
4842
+ || word === ">>"
4843
+ || word === "<"
4844
+ || word === "<<"
4845
+ || word === "<<<"
4846
+ || word === ">&"
4847
+ || word === "<&";
4848
+ }
4849
+ function skipShellCommandPositionPrefixWords(words, startIndex) {
4850
+ let commandWordIndex = startIndex;
4851
+ while (isShellAssignmentWord(words[commandWordIndex] ?? "")
4852
+ || isShellCommandPositionPrefixWord(words[commandWordIndex] ?? "")) {
4853
+ commandWordIndex += 1;
4854
+ }
4855
+ while (commandWordIndex < words.length) {
4856
+ const word = words[commandWordIndex] ?? "";
4857
+ const nextWord = words[commandWordIndex + 1] ?? "";
4858
+ if (isShellRedirectionWord(word)) {
4859
+ commandWordIndex += 2;
4860
+ continue;
4861
+ }
4862
+ if (/^\d+$/.test(word) && isShellRedirectionWord(nextWord)) {
4863
+ commandWordIndex += 3;
4864
+ continue;
4865
+ }
4866
+ break;
4867
+ }
4868
+ return commandWordIndex;
4869
+ }
4870
+ function findWrappedCommandPositionIndex(words, startIndex) {
4871
+ let commandWordIndex = skipShellCommandPositionPrefixWords(words, startIndex);
4872
+ for (let unwrapCount = 0; unwrapCount < 8; unwrapCount += 1) {
4873
+ const commandWord = words[commandWordIndex] ?? "";
4874
+ if (!commandWord)
4875
+ return null;
4876
+ const commandWordBase = shellWordBaseName(commandWord);
4877
+ const operandIndex = commandWordBase === "env"
4878
+ ? findEnvDispatchOperandIndex(words, commandWordIndex + 1)
4879
+ : commandWordBase === "command" || commandWordBase === "builtin"
4880
+ ? findCommandDispatchOperandIndex(words, commandWordIndex + 1)
4881
+ : commandWordBase === "exec"
4882
+ ? findExecDispatchOperandIndex(words, commandWordIndex + 1)
4883
+ : commandWordBase === "time"
4884
+ ? findTimeDispatchOperandIndex(words, commandWordIndex + 1)
4885
+ : commandWordBase === "timeout"
4886
+ ? findTimeoutDispatchOperandIndex(words, commandWordIndex + 1)
4887
+ : commandWordBase === "nohup" || commandWordBase === "setsid"
4888
+ ? findCommandDispatchOperandIndex(words, commandWordIndex + 1)
4889
+ : commandWordBase === "coproc"
4890
+ ? findCoprocDispatchOperandIndex(words, commandWordIndex + 1)
4891
+ : commandWordBase === "xargs"
4892
+ ? findXargsDispatchOperandIndex(words, commandWordIndex + 1)
4893
+ : commandWordBase === "nice"
4894
+ ? findNiceDispatchOperandIndex(words, commandWordIndex + 1)
4895
+ : commandWordBase === "stdbuf"
4896
+ ? findStdbufDispatchOperandIndex(words, commandWordIndex + 1)
4897
+ : null;
4898
+ if (operandIndex === null)
4899
+ return commandWordIndex;
4900
+ const nextCommandWordIndex = skipShellCommandPositionPrefixWords(words, operandIndex);
4901
+ if (nextCommandWordIndex === commandWordIndex)
4902
+ return commandWordIndex;
4903
+ commandWordIndex = nextCommandWordIndex;
4904
+ }
4905
+ return null;
4906
+ }
4907
+ function readOmxStateCommandFromSegmentWords(words, operation) {
4908
+ let commandWordIndex = skipShellCommandPositionPrefixWords(words, 0);
4909
+ for (let unwrapCount = 0; unwrapCount < 8; unwrapCount += 1) {
4910
+ commandWordIndex = skipShellCommandPositionPrefixWords(words, commandWordIndex);
4911
+ const caseArmCommandIndex = findCaseArmCommandIndex(words, commandWordIndex);
4912
+ if (caseArmCommandIndex !== null) {
4913
+ commandWordIndex = skipShellCommandPositionPrefixWords(words, caseArmCommandIndex);
4914
+ }
4915
+ const directArgs = readOmxStateCommandArgsFromWords(words.slice(commandWordIndex), operation);
4916
+ if (directArgs) {
4917
+ return {
4918
+ args: directArgs,
4919
+ commandWords: words.slice(commandWordIndex),
4920
+ prefixWords: words.slice(0, commandWordIndex),
4921
+ };
4922
+ }
4923
+ const commandWord = words[commandWordIndex] ?? "";
4924
+ const operandIndex = shellWordBaseName(commandWord) === "env"
4925
+ ? findEnvDispatchOperandIndex(words, commandWordIndex + 1)
4926
+ : shellWordBaseName(commandWord) === "command"
4927
+ ? findCommandDispatchOperandIndex(words, commandWordIndex + 1)
4928
+ : shellWordBaseName(commandWord) === "exec"
4929
+ ? findExecDispatchOperandIndex(words, commandWordIndex + 1)
4930
+ : shellWordBaseName(commandWord) === "nohup"
4931
+ ? findCommandDispatchOperandIndex(words, commandWordIndex + 1)
4932
+ : shellWordBaseName(commandWord) === "timeout"
4933
+ ? findTimeoutDispatchOperandIndex(words, commandWordIndex + 1)
4934
+ : shellWordBaseName(commandWord) === "coproc"
4935
+ ? findCoprocDispatchOperandIndex(words, commandWordIndex + 1)
4936
+ : shellWordBaseName(commandWord) === "xargs"
4937
+ ? findXargsDispatchOperandIndex(words, commandWordIndex + 1)
4938
+ : shellWordBaseName(commandWord) === "time"
4939
+ ? findTimeDispatchOperandIndex(words, commandWordIndex + 1)
4940
+ : null;
4941
+ if (operandIndex === null)
4942
+ return null;
4943
+ commandWordIndex = operandIndex;
4944
+ }
4945
+ return null;
4946
+ }
4947
+ function splitStateScanSegments(command) {
4948
+ const segments = [];
4949
+ let current = "";
4950
+ let segmentStart = 0;
4951
+ let quote = null;
4952
+ const pushSegment = (endIndex) => {
4953
+ if (current.trim()) {
4954
+ segments.push({
4955
+ segment: current,
4956
+ prefix: command.slice(0, segmentStart),
4957
+ });
4958
+ }
4959
+ current = "";
4960
+ segmentStart = endIndex;
4961
+ };
4962
+ for (let index = 0; index < command.length; index += 1) {
4963
+ const char = command[index] ?? "";
4964
+ if (char === "'" || char === "\"") {
4965
+ if (quote === char) {
4966
+ quote = null;
4967
+ }
4968
+ else if (!quote) {
4969
+ quote = char;
4970
+ }
4971
+ current += char;
4972
+ continue;
4973
+ }
4974
+ if (char === "\\" && quote !== "'") {
4975
+ current += char;
4976
+ index += 1;
4977
+ current += command[index] ?? "";
4978
+ continue;
4979
+ }
4980
+ if (quote) {
4981
+ current += char;
4982
+ continue;
4983
+ }
4984
+ if (char === ";" || char === "\n" || char === "\r") {
4985
+ pushSegment(index + 1);
4986
+ continue;
4987
+ }
4988
+ if (char === "&" && command[index + 1] === "&") {
4989
+ pushSegment(index + 2);
4990
+ index += 1;
4991
+ continue;
4992
+ }
4993
+ if (char === "&") {
4994
+ const previous = command[index - 1] ?? "";
4995
+ const next = command[index + 1] ?? "";
4996
+ if (previous === ">" || previous === "<" || next === ">") {
4997
+ current += char;
4998
+ }
4999
+ else {
5000
+ pushSegment(index + 1);
5001
+ }
5002
+ continue;
5003
+ }
5004
+ if (char === "|" && command[index + 1] === "|") {
5005
+ pushSegment(index + 2);
5006
+ index += 1;
5007
+ continue;
5008
+ }
5009
+ if (char === "|") {
5010
+ const next = command[index + 1] ?? "";
5011
+ if (next === "&") {
5012
+ pushSegment(index + 2);
5013
+ index += 1;
5014
+ }
5015
+ else {
5016
+ pushSegment(index + 1);
5017
+ }
5018
+ continue;
5019
+ }
5020
+ current += char;
5021
+ }
5022
+ if (current.trim()) {
5023
+ segments.push({
5024
+ segment: current,
5025
+ prefix: command.slice(0, segmentStart),
5026
+ });
5027
+ }
5028
+ return segments.length > 0 ? segments : [{ segment: command, prefix: "" }];
5029
+ }
5030
+ function collectOmxStateCommandOperations(command, operation, nested = false) {
5031
+ command = normalizeShellLineContinuations(stripHeredocBodiesForCommandScan(command));
5032
+ const operations = [];
5033
+ const addOperation = (candidate) => {
5034
+ operations.push(candidate);
5035
+ };
5036
+ const scanNestedSubstitutions = () => {
5037
+ let quote = null;
5038
+ for (let index = 0; index < command.length; index += 1) {
5039
+ const char = command[index];
5040
+ if (char === "\\" && quote !== "'") {
5041
+ index += 1;
5042
+ continue;
5043
+ }
5044
+ if (quote !== "'" && char === "$" && command[index + 1] === "(") {
5045
+ const substitutionEnd = findCommandSubstitutionEnd(command, index + 2);
5046
+ const substitutionBodyEnd = substitutionEnd >= 0 ? substitutionEnd : command.length;
5047
+ const substitutionBody = command.slice(index + 2, substitutionBodyEnd);
5048
+ for (const nestedOperation of collectOmxStateCommandOperations(substitutionBody, operation, true)) {
5049
+ addOperation(nestedOperation);
5050
+ }
5051
+ index = substitutionEnd >= 0 ? substitutionEnd : command.length;
5052
+ continue;
5053
+ }
5054
+ if (quote !== "'" && char === "<" && command[index + 1] === "(") {
5055
+ const substitutionEnd = findProcessSubstitutionEnd(command, index + 2);
5056
+ const substitutionBodyEnd = substitutionEnd >= 0 ? substitutionEnd : command.length;
5057
+ const substitutionBody = command.slice(index + 2, substitutionBodyEnd);
5058
+ for (const nestedOperation of collectOmxStateCommandOperations(substitutionBody, operation, true)) {
5059
+ addOperation(nestedOperation);
5060
+ }
5061
+ index = substitutionEnd >= 0 ? substitutionEnd : command.length;
5062
+ continue;
5063
+ }
5064
+ if (quote !== "'" && char === ">" && command[index + 1] === "(") {
5065
+ const substitutionEnd = findProcessSubstitutionEnd(command, index + 2);
5066
+ const substitutionBodyEnd = substitutionEnd >= 0 ? substitutionEnd : command.length;
5067
+ const substitutionBody = command.slice(index + 2, substitutionBodyEnd);
5068
+ for (const nestedOperation of collectOmxStateCommandOperations(substitutionBody, operation, true)) {
5069
+ addOperation(nestedOperation);
5070
+ }
5071
+ index = substitutionEnd >= 0 ? substitutionEnd : command.length;
5072
+ continue;
5073
+ }
5074
+ if (quote !== "'" && char === "`") {
5075
+ const substitutionEnd = findBacktickCommandSubstitutionEnd(command, index + 1);
5076
+ const substitutionBodyEnd = substitutionEnd >= 0 ? substitutionEnd : command.length;
5077
+ const substitutionBody = command.slice(index + 1, substitutionBodyEnd);
5078
+ for (const nestedOperation of collectOmxStateCommandOperations(substitutionBody, operation, true)) {
5079
+ addOperation(nestedOperation);
5080
+ }
5081
+ index = substitutionEnd >= 0 ? substitutionEnd : command.length;
5082
+ continue;
5083
+ }
5084
+ if (char === "'" || char === "\"") {
5085
+ if (quote === char) {
5086
+ quote = null;
5087
+ }
5088
+ else if (!quote) {
5089
+ quote = char;
5090
+ }
5091
+ }
5092
+ }
5093
+ };
5094
+ scanNestedSubstitutions();
5095
+ for (const functionBody of extractInvokedShellFunctionBodiesForStateScan(command)) {
5096
+ for (const nestedOperation of collectOmxStateCommandOperations(functionBody, operation, true)) {
5097
+ addOperation(nestedOperation);
5098
+ }
5099
+ }
5100
+ for (const scanSegment of splitStateScanSegments(command)) {
5101
+ const words = tokenizeShellWords(scanSegment.segment);
5102
+ const stateCommand = readOmxStateCommandFromSegmentWords(words, operation);
5103
+ if (stateCommand) {
5104
+ addOperation({
5105
+ args: stateCommand.args,
5106
+ prefix: scanSegment.prefix,
5107
+ commandPrefix: stateCommand.prefixWords.join(" "),
5108
+ nested,
5109
+ });
5110
+ }
5111
+ }
5112
+ for (const nestedCommand of extractNestedShellCommandStringsForStateScan(command)) {
5113
+ for (const nestedOperation of collectOmxStateCommandOperations(nestedCommand, operation, true)) {
5114
+ addOperation(nestedOperation);
5115
+ }
5116
+ }
5117
+ return operations;
5118
+ }
5119
+ function extractInvokedShellFunctionBodiesForStateScan(command) {
5120
+ const bodies = [];
5121
+ command = stripHeredocBodiesForCommandScan(normalizeShellLineContinuations(command));
5122
+ let quote = null;
5123
+ for (let index = 0; index < command.length; index += 1) {
5124
+ const char = command[index] ?? "";
5125
+ if (char === "\\" && quote !== "'") {
5126
+ index += 1;
5127
+ continue;
5128
+ }
5129
+ if (char === "'" || char === "\"") {
5130
+ if (quote === char) {
5131
+ quote = null;
5132
+ }
5133
+ else if (!quote) {
5134
+ quote = char;
5135
+ }
5136
+ continue;
5137
+ }
5138
+ if (quote)
5139
+ continue;
5140
+ const functionDefinition = findShellFunctionDefinitionAt(command, index);
5141
+ if (!functionDefinition)
5142
+ continue;
5143
+ const functionBodyEnd = findShellFunctionBodyEnd(command, functionDefinition.openBraceIndex, functionDefinition.bodyOpenChar);
5144
+ if (functionBodyEnd < 0)
5145
+ continue;
5146
+ if (isShellFunctionInvokedLater(command.slice(functionBodyEnd + 1), functionDefinition.name)) {
5147
+ bodies.push(command.slice(functionDefinition.openBraceIndex + 1, functionBodyEnd));
5148
+ }
5149
+ index = functionBodyEnd;
5150
+ }
5151
+ return bodies;
5152
+ }
5153
+ function findUnquotedOmxStateCommandIndexes(command, operation) {
5154
+ return collectOmxStateCommandOperations(command, operation).map((_, index) => index);
5155
+ }
5156
+ function extractNestedShellCommandStringsForStateScan(command) {
5157
+ const words = tokenizeShellWords(stripHeredocBodiesForCommandScan(command));
5158
+ const nested = [];
5159
+ for (let index = 0; index < words.length; index += 1) {
5160
+ const word = words[index] ?? "";
5161
+ if (isNestedShellCommandWord(word)) {
5162
+ const commandStringIndex = findShellCommandStringArgIndex(words, index + 1);
5163
+ if (commandStringIndex !== null) {
5164
+ const nestedCommand = words[commandStringIndex];
5165
+ if (nestedCommand) {
5166
+ nested.push(nestedCommand);
5167
+ const positionalArgs = words.slice(commandStringIndex + 1);
5168
+ const expandedNestedCommand = expandShellPositionalParameters(nestedCommand, positionalArgs);
5169
+ if (expandedNestedCommand !== nestedCommand)
5170
+ nested.push(expandedNestedCommand);
5171
+ }
5172
+ }
5173
+ }
5174
+ if (word === "eval") {
5175
+ const nestedCommand = words.slice(index + 1).join(" ");
5176
+ if (nestedCommand)
5177
+ nested.push(nestedCommand);
5178
+ }
5179
+ if (shellWordBaseName(word) === "env") {
5180
+ const splitStringCommand = extractEnvSplitStringCommand(words, index + 1);
5181
+ if (splitStringCommand) {
5182
+ nested.push(splitStringCommand);
5183
+ }
5184
+ }
5185
+ else if (isPackageManagerCommandWord(word)) {
5186
+ const packageManagerCommand = extractPackageManagerExecCommand(command, words, index + 1, shellWordBaseName(word));
5187
+ if (packageManagerCommand) {
5188
+ nested.push(packageManagerCommand);
5189
+ }
5190
+ }
5191
+ }
5192
+ return nested;
5193
+ }
5194
+ function extractNestedCommandSubstitutionStringsForStateScan(command) {
5195
+ const nested = [];
5196
+ let quote = null;
5197
+ for (let index = 0; index < command.length; index += 1) {
5198
+ const char = command[index];
5199
+ if (char === "\\" && quote !== "'") {
5200
+ index += 1;
5201
+ continue;
5202
+ }
5203
+ if (quote !== "'" && char === "$" && command[index + 1] === "(") {
5204
+ const substitutionEnd = findCommandSubstitutionEnd(command, index + 2);
5205
+ const substitutionBodyEnd = substitutionEnd >= 0 ? substitutionEnd : command.length;
5206
+ const substitutionBody = command.slice(index + 2, substitutionBodyEnd);
5207
+ if (substitutionBody)
5208
+ nested.push(substitutionBody);
5209
+ index = substitutionEnd >= 0 ? substitutionEnd : command.length;
5210
+ continue;
5211
+ }
5212
+ if (quote !== "'" && char === "`") {
5213
+ const substitutionEnd = findBacktickCommandSubstitutionEnd(command, index + 1);
5214
+ const substitutionBodyEnd = substitutionEnd >= 0 ? substitutionEnd : command.length;
5215
+ const substitutionBody = command.slice(index + 1, substitutionBodyEnd);
5216
+ if (substitutionBody)
5217
+ nested.push(substitutionBody);
5218
+ index = substitutionEnd >= 0 ? substitutionEnd : command.length;
5219
+ continue;
5220
+ }
5221
+ if (char === "'" || char === "\"") {
5222
+ if (quote === char) {
5223
+ quote = null;
5224
+ }
5225
+ else if (!quote) {
5226
+ quote = char;
5227
+ }
5228
+ }
5229
+ }
5230
+ return nested;
5231
+ }
5232
+ function extractNestedProcessSubstitutionStringsForStateScan(command) {
5233
+ const nested = [];
5234
+ let quote = null;
5235
+ for (let index = 0; index < command.length; index += 1) {
5236
+ const char = command[index];
5237
+ if (char === "\\" && quote !== "'") {
5238
+ index += 1;
5239
+ continue;
5240
+ }
5241
+ if (quote !== "'" && (char === "<" || char === ">") && command[index + 1] === "(") {
5242
+ const substitutionEnd = findProcessSubstitutionEnd(command, index + 2);
5243
+ const substitutionBodyEnd = substitutionEnd >= 0 ? substitutionEnd : command.length;
5244
+ const substitutionBody = command.slice(index + 2, substitutionBodyEnd);
5245
+ if (substitutionBody)
5246
+ nested.push(substitutionBody);
5247
+ index = substitutionEnd >= 0 ? substitutionEnd : command.length;
5248
+ continue;
5249
+ }
5250
+ if (char === "'" || char === "\"") {
5251
+ if (quote === char) {
5252
+ quote = null;
5253
+ }
5254
+ else if (!quote) {
5255
+ quote = char;
5256
+ }
5257
+ }
5258
+ }
5259
+ return nested;
5260
+ }
5261
+ function isPackageManagerCommandWord(word) {
5262
+ const base = shellWordBaseName(word);
5263
+ return base === "npm" || base === "pnpm" || base === "yarn" || base === "npx" || base === "pnpx";
5264
+ }
5265
+ function hasDynamicNestedShellExecution(command) {
5266
+ const commands = splitShellCommandSegments(stripHeredocBodiesForCommandScan(command));
5267
+ for (const segment of commands) {
5268
+ const words = tokenizeShellWords(segment);
5269
+ let sawCommandWord = false;
5270
+ for (let index = 0; index < words.length; index += 1) {
5271
+ const word = words[index] ?? "";
5272
+ if (!sawCommandWord) {
5273
+ if (/^[A-Za-z_][A-Za-z0-9_]*=/.test(word))
5274
+ continue;
5275
+ sawCommandWord = true;
5276
+ if (isDynamicNestedCommandString(word))
5277
+ return true;
5278
+ }
5279
+ if (isCommandDispatchBuiltin(word)) {
5280
+ const dispatchedCommand = extractDispatchBuiltinOperand(words, index + 1, word);
5281
+ if (dispatchedCommand && hasDynamicNestedShellExecution(dispatchedCommand))
5282
+ return true;
5283
+ }
5284
+ if (containsUnquotedProcessSubstitution(segment) && (isNestedShellCommandWord(word) || word === "." || word === "source"))
5285
+ return true;
5286
+ if (isNestedShellCommandWord(word)) {
5287
+ const commandStringIndex = findShellCommandStringArgIndex(words, index + 1);
5288
+ if (commandStringIndex === null && (hasUnquotedShellStdinFlowAroundShellWord(words, index) || segment.includes("<<")))
5289
+ return true;
5290
+ if (commandStringIndex !== null) {
5291
+ const nestedCommand = words[commandStringIndex] ?? "";
5292
+ if (isDynamicNestedCommandString(nestedCommand))
5293
+ return true;
5294
+ }
5295
+ }
5296
+ if (word === "eval") {
5297
+ const nestedCommand = words.slice(index + 1).join(" ");
5298
+ if (isDynamicNestedCommandString(nestedCommand))
5299
+ return true;
5300
+ }
5301
+ }
5302
+ }
5303
+ return false;
5304
+ }
5305
+ function isCommandDispatchBuiltin(word) {
5306
+ return word === "exec" || word === "command" || word === "." || word === "source" || word === "env" || word === "nohup";
5307
+ }
5308
+ function extractDispatchBuiltinOperand(words, startIndex, builtin) {
5309
+ for (let index = startIndex; index < words.length; index += 1) {
5310
+ const option = words[index] ?? "";
5311
+ if (!option)
5312
+ continue;
5313
+ if (option === "--")
5314
+ continue;
5315
+ if (isShellAssignmentWord(option))
5316
+ continue;
5317
+ if (builtin === "exec" && option === "-a") {
5318
+ index += 1;
5319
+ continue;
5320
+ }
5321
+ if (builtin === "env" && (option === "-S" || option === "--split-string")) {
5322
+ const splitOperand = words[index + 1] ?? "";
5323
+ return splitOperand;
5324
+ }
5325
+ if (builtin === "env" && option.startsWith("--split-string=")) {
5326
+ return option.slice("--split-string=".length);
5327
+ }
5328
+ if (builtin === "env" && option.startsWith("-S") && option.length > 2) {
5329
+ return option.slice(2);
5330
+ }
5331
+ if (option.startsWith("-"))
5332
+ continue;
5333
+ return words.slice(index).join(" ");
5334
+ }
5335
+ return "";
5336
+ }
5337
+ function isNestedShellCommandWord(word) {
5338
+ const base = word.split(/[\\/]/).pop() ?? word;
5339
+ return /^(?:bash|sh|zsh|dash|ksh|mksh|ash)$/.test(base);
5340
+ }
5341
+ function findShellCommandStringArgIndex(words, optionStartIndex) {
5342
+ for (let index = optionStartIndex; index < words.length; index += 1) {
5343
+ const option = words[index] ?? "";
5344
+ if (option === "--")
5345
+ return null;
5346
+ if (isShellCommandStringOption(option))
5347
+ return index + 1;
5348
+ if (isShellOptionWithSeparateValue(option)) {
5349
+ index += 1;
5350
+ continue;
5351
+ }
5352
+ if (option.startsWith("-"))
5353
+ continue;
5354
+ return null;
5355
+ }
5356
+ return null;
5357
+ }
5358
+ function isShellCommandStringOption(option) {
5359
+ return /^-[^-]*c[^-]*$/.test(option);
5360
+ }
5361
+ function isShellOptionWithSeparateValue(option) {
5362
+ return option === "--rcfile" || option === "--init-file" || option === "-o" || option === "-O";
5363
+ }
5364
+ function isDynamicNestedCommandString(command) {
5365
+ return /(?:^|[^\\])\$(?:[A-Za-z_][A-Za-z0-9_]*|\{[^}]+\}|\()/.test(command)
5366
+ || /(?:^|[^\\])`/.test(command);
5367
+ }
5368
+ function shellQuoteForStateScan(value) {
5369
+ if (value === "")
5370
+ return "''";
5371
+ return `'${value.replace(/'/g, "'\"'\"'")}'`;
5372
+ }
5373
+ function expandShellPositionalParameters(command, positionalArgs) {
5374
+ if (!command.includes("$") || positionalArgs.length === 0)
5375
+ return command;
5376
+ let expanded = "";
5377
+ let replaced = false;
5378
+ for (let index = 0; index < command.length; index += 1) {
5379
+ const char = command[index] ?? "";
5380
+ if (char === "\\" && index + 1 < command.length) {
5381
+ expanded += char;
5382
+ index += 1;
5383
+ expanded += command[index] ?? "";
5384
+ continue;
5385
+ }
5386
+ if (char !== "$") {
5387
+ expanded += char;
5388
+ continue;
5389
+ }
5390
+ const next = command[index + 1] ?? "";
5391
+ if (next === "{") {
5392
+ let endIndex = index + 2;
5393
+ let digits = "";
5394
+ while (endIndex < command.length) {
5395
+ const bodyChar = command[endIndex] ?? "";
5396
+ if (bodyChar === "}")
5397
+ break;
5398
+ digits += bodyChar;
5399
+ endIndex += 1;
5400
+ }
5401
+ if (command[endIndex] === "}" && /^\d+$/.test(digits)) {
5402
+ const positionalIndex = Number.parseInt(digits, 10);
5403
+ const replacement = positionalArgs[positionalIndex];
5404
+ if (replacement !== undefined) {
5405
+ expanded += shellQuoteForStateScan(replacement);
5406
+ replaced = true;
5407
+ index = endIndex;
5408
+ continue;
5409
+ }
5410
+ }
5411
+ if (command[endIndex] === "}" && (digits === "@" || digits === "*")) {
5412
+ const expansionArgs = positionalArgs.slice(1);
5413
+ const replacement = expansionArgs.map((arg) => shellQuoteForStateScan(arg)).join(" ");
5414
+ expanded += replacement;
5415
+ replaced = true;
5416
+ index = endIndex;
5417
+ continue;
5418
+ }
5419
+ }
5420
+ else if (next === "@" || next === "*") {
5421
+ const replacement = positionalArgs.slice(1).map((arg) => shellQuoteForStateScan(arg)).join(" ");
5422
+ if (replacement) {
5423
+ expanded += replacement;
5424
+ replaced = true;
5425
+ index += 1;
5426
+ continue;
5427
+ }
5428
+ }
5429
+ else if (/[0-9]/.test(next)) {
5430
+ let endIndex = index + 1;
5431
+ let digits = "";
5432
+ while (endIndex < command.length && /[0-9]/.test(command[endIndex] ?? "")) {
5433
+ digits += command[endIndex] ?? "";
5434
+ endIndex += 1;
5435
+ }
5436
+ const positionalIndex = Number.parseInt(digits, 10);
5437
+ const replacement = positionalArgs[positionalIndex];
5438
+ if (replacement !== undefined) {
5439
+ expanded += shellQuoteForStateScan(replacement);
5440
+ replaced = true;
5441
+ index = endIndex - 1;
5442
+ continue;
5443
+ }
5444
+ }
5445
+ expanded += char;
5446
+ }
5447
+ return replaced ? expanded : command;
5448
+ }
5449
+ function splitShellCommandSegments(command) {
5450
+ const segments = [];
5451
+ let current = "";
5452
+ let quote = null;
5453
+ for (let index = 0; index < command.length; index += 1) {
5454
+ const char = command[index] ?? "";
5455
+ if (char === "'" || char === "\"") {
5456
+ if (quote === char) {
5457
+ quote = null;
5458
+ }
5459
+ else if (!quote) {
5460
+ quote = char;
5461
+ }
5462
+ current += char;
5463
+ continue;
5464
+ }
5465
+ if (char === "\\" && quote !== "'") {
5466
+ current += char;
5467
+ index += 1;
5468
+ current += command[index] ?? "";
5469
+ continue;
5470
+ }
5471
+ if (quote) {
5472
+ current += char;
5473
+ continue;
5474
+ }
5475
+ if (char === ";" || char === "\n" || char === "\r") {
5476
+ if (current.trim())
5477
+ segments.push(current);
5478
+ current = "";
5479
+ continue;
5480
+ }
5481
+ if (char === "&" && command[index - 1] !== "|") {
5482
+ if (current.trim())
5483
+ segments.push(current);
5484
+ current = "";
5485
+ if (command[index + 1] === "&")
5486
+ index += 1;
5487
+ continue;
5488
+ }
5489
+ if (char === "|" && command[index + 1] === "|") {
5490
+ if (current.trim())
5491
+ segments.push(current);
5492
+ current = "";
5493
+ index += 1;
5494
+ continue;
5495
+ }
5496
+ current += char;
5497
+ }
5498
+ if (current.trim())
5499
+ segments.push(current);
5500
+ return segments.length > 0 ? segments : [command];
5501
+ }
5502
+ function findDispatchWordIndex(words, startIndex) {
5503
+ for (let index = startIndex; index < words.length; index += 1) {
5504
+ const token = words[index] ?? "";
5505
+ if (!token || token === "--")
5506
+ continue;
5507
+ if (isShellAssignmentWord(token))
5508
+ continue;
5509
+ return index;
5510
+ }
5511
+ return null;
5512
+ }
5513
+ function isShellAssignmentWord(word) {
5514
+ return /^[A-Za-z_][A-Za-z0-9_]*=/.test(word);
5515
+ }
5516
+ function hasUnquotedShellStdinFlowAroundShellWord(words, wordIndex) {
5517
+ for (let index = 0; index < wordIndex; index += 1) {
5518
+ const word = words[index] ?? "";
5519
+ if (word === "|" || word === "|&")
5520
+ return true;
5521
+ if (word === "<" || word === "<<" || word === "<<<" || word.startsWith("<"))
5522
+ return true;
5523
+ }
5524
+ for (let index = wordIndex + 1; index < words.length; index += 1) {
5525
+ const word = words[index] ?? "";
5526
+ if (word === "<" || word === "<<" || word === "<<<" || word.startsWith("<"))
5527
+ return true;
5528
+ }
5529
+ return false;
5530
+ }
5531
+ function findCommandSubstitutionEnd(command, bodyStartIndex) {
5532
+ let depth = 1;
5533
+ let quote = null;
5534
+ for (let index = bodyStartIndex; index < command.length; index += 1) {
5535
+ const char = command[index];
5536
+ if (char === "\\" && quote !== "'") {
5537
+ index += 1;
5538
+ continue;
5539
+ }
5540
+ if (char === "'" || char === "\"") {
5541
+ if (quote === char) {
5542
+ quote = null;
5543
+ }
5544
+ else if (!quote) {
5545
+ quote = char;
5546
+ }
5547
+ continue;
5548
+ }
5549
+ if (quote)
5550
+ continue;
5551
+ if (char === "$" && command[index + 1] === "(") {
5552
+ depth += 1;
5553
+ index += 1;
5554
+ continue;
5555
+ }
5556
+ if (char === ")") {
5557
+ depth -= 1;
5558
+ if (depth === 0)
5559
+ return index;
5560
+ }
5561
+ }
5562
+ return -1;
5563
+ }
5564
+ function findBacktickCommandSubstitutionEnd(command, bodyStartIndex) {
5565
+ for (let index = bodyStartIndex; index < command.length; index += 1) {
5566
+ const char = command[index];
5567
+ if (char === "\\") {
5568
+ index += 1;
5569
+ continue;
5570
+ }
5571
+ if (char === "`")
5572
+ return index;
5573
+ }
5574
+ return -1;
5575
+ }
5576
+ function findProcessSubstitutionEnd(command, bodyStartIndex) {
5577
+ let depth = 1;
5578
+ let quote = null;
5579
+ for (let index = bodyStartIndex; index < command.length; index += 1) {
5580
+ const char = command[index];
5581
+ if (char === "\\" && quote !== "'") {
5582
+ index += 1;
5583
+ continue;
5584
+ }
5585
+ if (char === "'" || char === "\"") {
5586
+ if (quote === char) {
5587
+ quote = null;
5588
+ }
5589
+ else if (!quote) {
5590
+ quote = char;
5591
+ }
5592
+ continue;
5593
+ }
5594
+ if (quote)
5595
+ continue;
5596
+ if (char === "(") {
5597
+ depth += 1;
5598
+ continue;
5599
+ }
5600
+ if (char === ")") {
5601
+ depth -= 1;
5602
+ if (depth === 0)
5603
+ return index;
5604
+ }
5605
+ }
5606
+ return -1;
5607
+ }
5608
+ function containsUnquotedProcessSubstitution(command) {
5609
+ command = normalizeShellLineContinuations(command);
5610
+ let quote = null;
5611
+ for (let index = 0; index < command.length - 1; index += 1) {
5612
+ const char = command[index];
5613
+ if (char === "\\" && quote !== "'") {
5614
+ index += 1;
5615
+ continue;
5616
+ }
5617
+ if (char === "'" || char === "\"") {
5618
+ if (quote === char) {
5619
+ quote = null;
5620
+ }
5621
+ else if (!quote) {
5622
+ quote = char;
5623
+ }
5624
+ continue;
5625
+ }
5626
+ if (quote)
5627
+ continue;
5628
+ if ((char === "<" || char === ">") && command[index + 1] === "(")
5629
+ return true;
5630
+ }
5631
+ return false;
5632
+ }
5633
+ function parseShellHeredocDelimiter(line, startIndex) {
5634
+ let index = startIndex;
5635
+ while (/\s/.test(line[index] ?? ""))
5636
+ index += 1;
5637
+ let delimiter = "";
5638
+ let quoted = false;
5639
+ let quote = null;
5640
+ for (; index < line.length; index += 1) {
5641
+ const char = line[index] ?? "";
5642
+ if (char === "\\" && quote !== "'") {
5643
+ quoted = true;
5644
+ index += 1;
5645
+ delimiter += line[index] ?? "";
5646
+ continue;
5647
+ }
5648
+ if (char === "'" || char === "\"") {
5649
+ if (quote === char) {
5650
+ quote = null;
5651
+ }
5652
+ else if (!quote) {
5653
+ quote = char;
5654
+ quoted = true;
5655
+ }
5656
+ else {
5657
+ delimiter += char;
5658
+ }
5659
+ continue;
5660
+ }
5661
+ if (!quote && (/\s/.test(char) || char === "|" || char === ";" || char === "&" || char === "<" || char === ">"))
5662
+ break;
5663
+ delimiter += char;
5664
+ }
5665
+ return delimiter ? { delimiter, quoted } : null;
5666
+ }
5667
+ function extractShellHeredocOpeners(line) {
5668
+ const openers = [];
5669
+ let quote = null;
5670
+ for (let index = 0; index < line.length - 1; index += 1) {
5671
+ const char = line[index] ?? "";
5672
+ if (char === "\\" && quote !== "'") {
5673
+ index += 1;
5674
+ continue;
5675
+ }
5676
+ if (char === "'" || char === "\"") {
5677
+ if (quote === char) {
5678
+ quote = null;
5679
+ }
5680
+ else if (!quote) {
5681
+ quote = char;
5682
+ }
5683
+ continue;
5684
+ }
5685
+ if (quote)
5686
+ continue;
5687
+ if (char !== "<" || line[index + 1] !== "<" || line[index + 2] === "<")
5688
+ continue;
5689
+ const delimiterStart = line[index + 2] === "-" ? index + 3 : index + 2;
5690
+ const opener = parseShellHeredocDelimiter(line, delimiterStart);
5691
+ if (opener)
5692
+ openers.push(opener);
5693
+ index = delimiterStart;
5694
+ }
5695
+ return openers;
5696
+ }
5697
+ function stripHeredocBodiesForCommandScan(command) {
5698
+ const lines = command.split("\n");
5699
+ const kept = [];
5700
+ for (let index = 0; index < lines.length; index += 1) {
5701
+ const line = lines[index] ?? "";
5702
+ kept.push(line);
5703
+ const openers = extractShellHeredocOpeners(line);
5704
+ for (const { delimiter } of openers) {
5705
+ index += 1;
5706
+ while (index < lines.length && (lines[index] ?? "").trim() !== delimiter) {
5707
+ kept.push("");
5708
+ index += 1;
5709
+ }
5710
+ if (index < lines.length)
5711
+ kept.push(lines[index] ?? "");
5712
+ }
5713
+ }
5714
+ return kept.join("\n");
5715
+ }
5716
+ function hasUnsafeUnquotedHeredocExpansion(command) {
5717
+ const lines = normalizeShellLineContinuations(command).split("\n");
5718
+ for (let index = 0; index < lines.length; index += 1) {
5719
+ const line = lines[index] ?? "";
5720
+ for (const { delimiter, quoted } of extractShellHeredocOpeners(line)) {
5721
+ index += 1;
5722
+ const bodyLines = [];
5723
+ while (index < lines.length && (lines[index] ?? "").trim() !== delimiter) {
5724
+ bodyLines.push(lines[index] ?? "");
5725
+ index += 1;
5726
+ }
5727
+ if (!quoted && isDynamicNestedCommandString(bodyLines.join("\n")))
5728
+ return true;
5729
+ }
5730
+ }
5731
+ return false;
5732
+ }
5733
+ function hasUnquotedShellSubstitution(command) {
5734
+ command = normalizeShellLineContinuations(command);
5735
+ let quote = null;
5736
+ for (let index = 0; index < command.length; index += 1) {
5737
+ const char = command[index] ?? "";
5738
+ if (char === "\\" && quote !== "'") {
5739
+ index += 1;
5740
+ continue;
5741
+ }
5742
+ if (char === "'" || char === "\"") {
5743
+ if (quote === char) {
5744
+ quote = null;
5745
+ }
5746
+ else if (!quote) {
5747
+ quote = char;
5748
+ }
5749
+ continue;
5750
+ }
5751
+ if (quote === "'")
5752
+ continue;
5753
+ if (char === "$" && command[index + 1] === "(")
5754
+ return true;
5755
+ if (char === "`")
5756
+ return true;
5757
+ if ((char === "<" || char === ">") && command[index + 1] === "(")
5758
+ return true;
5759
+ }
5760
+ return false;
5761
+ }
5762
+ function normalizeStateWriteClassificationPayload(payload) {
5763
+ const targetMode = safeString(payload.mode).trim();
5764
+ const targetSessionId = safeString(payload.session_id).trim();
5765
+ const { mode: _mode, workingDirectory: _workingDirectory, state, ...fields } = payload;
5766
+ const normalized = {
5767
+ ...fields,
5768
+ ...safeObject(state),
5769
+ ...(targetMode ? { mode: targetMode } : {}),
5770
+ ...(targetSessionId ? { session_id: targetSessionId } : {}),
5771
+ };
5772
+ if (normalized.current_phase === undefined && normalized.currentPhase !== undefined) {
5773
+ normalized.current_phase = normalized.currentPhase;
5774
+ }
5775
+ if (normalized.run_outcome === undefined && normalized.runOutcome !== undefined) {
5776
+ normalized.run_outcome = normalized.runOutcome;
5777
+ }
5778
+ if (normalized.lifecycle_outcome === undefined && normalized.lifecycleOutcome !== undefined) {
5779
+ normalized.lifecycle_outcome = normalized.lifecycleOutcome;
5780
+ }
5781
+ if (normalized.terminal_outcome === undefined && normalized.terminalOutcome !== undefined) {
5782
+ normalized.terminal_outcome = normalized.terminalOutcome;
5783
+ }
5784
+ return {
5785
+ ...normalized,
5786
+ };
5787
+ }
5788
+ function isPlanningPhaseDeactivationPayload(payload) {
5789
+ const mode = safeString(payload.mode).trim().toLowerCase();
5790
+ if (!mode)
5791
+ return false;
5792
+ if (mode !== "deep-interview" && mode !== "ralplan") {
5793
+ if (!isTrackedWorkflowMode(mode))
5794
+ return false;
5795
+ const currentPhase = safeString(payload.current_phase ?? payload.currentPhase).trim().toLowerCase();
5796
+ const normalizedAutopilotPhase = normalizeAutopilotPhase(currentPhase);
5797
+ if (mode === "autopilot" && (normalizedAutopilotPhase === "deep-interview" || normalizedAutopilotPhase === "ralplan" || normalizedAutopilotPhase === "ultragoal")) {
5798
+ return payload.active === false;
5799
+ }
5800
+ if (payload.active === true)
5801
+ return true;
5802
+ return inferTerminalLifecycleOutcome(payload, { includeQuestionEnforcement: false }) === undefined;
5803
+ }
5804
+ if (payload.active === false)
5805
+ return true;
5806
+ return inferTerminalLifecycleOutcome(payload, { includeQuestionEnforcement: false }) !== undefined;
5807
+ }
5808
+ function hasCompleteDeepInterviewGateMetadata(state) {
5809
+ const nestedState = safeObject(state.state);
5810
+ const gate = safeObject(state.deep_interview_gate) ?? safeObject(nestedState?.deep_interview_gate);
5811
+ if (!gate)
5812
+ return false;
5813
+ const status = safeString(gate.status).trim().toLowerCase().replace(/_/g, "-");
5814
+ if (status === "complete" || gate.complete === true) {
5815
+ return ["rationale", "completion_rationale", "handoff_summary", "summary", "reason"]
5816
+ .some((key) => safeString(gate[key]).trim().length > 0);
5817
+ }
5818
+ if (status !== "skipped")
5819
+ return false;
5820
+ const reason = safeString(gate.reason).trim() || safeString(gate.skip_reason).trim() || safeString(gate.rationale).trim();
5821
+ const timestamp = safeString(gate.skipped_at).trim() || safeString(gate.timestamp).trim() || safeString(gate.updated_at).trim();
5822
+ const source = safeString(gate.source).trim();
5823
+ return (gate.skip_authorized_by_user === true || gate.authorized_by_user === true)
5824
+ && reason.length > 0
5825
+ && timestamp.length > 0
5826
+ && source.length > 0;
5827
+ }
5828
+ function isDeepInterviewRalplanHandoffStatePayload(payload) {
5829
+ const mode = safeString(payload.mode).trim().toLowerCase();
5830
+ if (mode !== "autopilot")
5831
+ return false;
5832
+ const phase = normalizeAutopilotPhase(safeString(payload.current_phase ?? payload.currentPhase).trim().toLowerCase());
5833
+ if (phase !== "ralplan" || payload.active === false)
5834
+ return false;
5835
+ return hasCompleteDeepInterviewGateMetadata(payload);
5836
+ }
5837
+ function hasOnlyAllowedDeepInterviewRalplanHandoffMutations(cwd, command) {
5838
+ for (const mutation of extractConductorBashMutations(command)) {
5839
+ if (mutation.targets.length === 0)
5840
+ return false;
5841
+ if (mutation.targets.some((target) => !isAllowedDeepInterviewArtifactPath(cwd, target)))
5842
+ return false;
5843
+ }
5844
+ for (const write of extractConductorInterpreterWrites(command)) {
5845
+ if (write.unresolved || write.targets.length === 0)
5846
+ return false;
5847
+ if (write.targets.some((target) => !isAllowedDeepInterviewArtifactPath(cwd, target)))
5848
+ return false;
5849
+ }
5850
+ return true;
5851
+ }
5852
+ function isDurableDeepInterviewHandoffEvidencePath(cwd, rawPath) {
5853
+ const relativePath = normalizePlanningArtifactRelativePath(cwd, rawPath);
5854
+ if (!relativePath)
5855
+ return false;
5856
+ return relativePath === ".omx/context"
5857
+ || relativePath.startsWith(".omx/context/")
5858
+ || relativePath === ".omx/interviews"
5859
+ || relativePath.startsWith(".omx/interviews/")
5860
+ || relativePath === ".omx/specs"
5861
+ || relativePath.startsWith(".omx/specs/");
5862
+ }
5863
+ function hasExistingDurableDeepInterviewHandoffEvidence(cwd) {
5864
+ const roots = [".omx/context", ".omx/interviews", ".omx/specs"];
5865
+ const maxEntries = 2_000;
5866
+ let visited = 0;
5867
+ for (const root of roots) {
5868
+ const stack = [resolve(cwd, root)];
5869
+ while (stack.length > 0 && visited < maxEntries) {
5870
+ const current = stack.pop();
5871
+ if (!current)
5872
+ continue;
5873
+ visited += 1;
5874
+ try {
5875
+ const stat = statSync(current);
5876
+ if (stat.isFile())
5877
+ return true;
5878
+ if (!stat.isDirectory())
5879
+ continue;
5880
+ for (const entry of readdirSync(current)) {
5881
+ stack.push(join(current, entry));
5882
+ }
5883
+ }
5884
+ catch {
5885
+ // Missing or unreadable roots are not completion evidence.
5886
+ }
5887
+ }
5888
+ }
5889
+ return false;
5890
+ }
5891
+ function isAllowedDeepInterviewRalplanHandoffCommand(cwd, command) {
5892
+ const canonicalCommand = canonicalizeOmxStateTransportCommand(command);
5893
+ if (hasUnsafeUnquotedHeredocExpansion(canonicalCommand))
5894
+ return false;
5895
+ if (hasUnquotedShellSubstitution(canonicalCommand))
5896
+ return false;
5897
+ if (findUnquotedOmxStateCommandIndexes(canonicalCommand, "clear").length > 0)
5898
+ return false;
5899
+ if (hasDynamicNestedShellExecution(canonicalCommand))
5900
+ return false;
5901
+ if (commandHasUntargetedPlanningForbiddenIntent(canonicalCommand))
5902
+ return false;
5903
+ if (sourcesFileWrittenEarlierInSameCommand(cwd, canonicalCommand))
5904
+ return false;
5905
+ const stateWriteOperations = collectOmxStateCommandOperations(canonicalCommand, "write");
5906
+ if (stateWriteOperations.length !== 1)
5907
+ return false;
5908
+ const stateWriteOperation = stateWriteOperations[0];
5909
+ if (!stateWriteOperation || stateWriteOperation.nested)
5910
+ return false;
5911
+ const payload = readStateWriteInputPayload(cwd, canonicalCommand, command);
5912
+ if (!payload || !isDeepInterviewRalplanHandoffStatePayload(payload))
5913
+ return false;
5914
+ const targets = extractDeepInterviewCommandWriteTargets(command);
5915
+ if (targets.length === 0) {
5916
+ return !hasPriorExecutableCommand(stateWriteOperation.prefix)
5917
+ && hasExistingDurableDeepInterviewHandoffEvidence(cwd);
5918
+ }
5919
+ if (!targets.some((target) => isDurableDeepInterviewHandoffEvidencePath(cwd, target)))
5920
+ return false;
5921
+ if (!hasOnlyAllowedDeepInterviewRalplanHandoffMutations(cwd, command))
5922
+ return false;
5923
+ return targets.every((target) => isAllowedDeepInterviewArtifactPath(cwd, target));
5924
+ }
5925
+ function hasDeepInterviewRalplanHandoffStateMutation(cwd, command) {
5926
+ const canonicalCommand = canonicalizeOmxStateTransportCommand(command);
5927
+ const stateWriteOperations = collectOmxStateCommandOperations(canonicalCommand, "write");
5928
+ if (stateWriteOperations.length === 0)
5929
+ return false;
5930
+ const payload = readStateWriteInputPayload(cwd, canonicalCommand, command);
5931
+ return payload ? isDeepInterviewRalplanHandoffStatePayload(payload) : false;
5932
+ }
5933
+ function isCompleteRalplanTerminalWritePayload(payload, activeState, sessionId) {
5934
+ if (!sessionId)
5935
+ return false;
5936
+ const mode = safeString(payload.mode).trim().toLowerCase();
5937
+ if (mode !== "ralplan")
5938
+ return false;
5939
+ const phase = safeString(payload.current_phase ?? payload.currentPhase).trim().toLowerCase();
5940
+ if (payload.active !== false || phase !== "complete")
5941
+ return false;
5942
+ const payloadSessionId = safeString(payload.session_id).trim();
5943
+ const activeSessionId = safeString(activeState.session_id
5944
+ ?? activeState.owner_omx_session_id
5945
+ ?? activeState.codex_session_id
5946
+ ?? activeState.owner_codex_session_id).trim();
5947
+ if (payloadSessionId && sessionId && payloadSessionId !== sessionId)
5948
+ return false;
5949
+ if (payloadSessionId && activeSessionId && payloadSessionId !== activeSessionId)
5950
+ return false;
5951
+ return true;
5952
+ }
5953
+ function isAllowedRalplanTerminalStateWriteCommand(cwd, command, activeState, sessionId) {
5954
+ const canonicalCommand = canonicalizeOmxStateTransportCommand(command);
5955
+ if (hasUnsafeUnquotedHeredocExpansion(canonicalCommand))
5956
+ return false;
5957
+ if (hasUnquotedShellSubstitution(canonicalCommand))
5958
+ return false;
5959
+ if (splitStateScanSegments(canonicalCommand).length !== 1)
5960
+ return false;
5961
+ if (sourcesFileWrittenEarlierInSameCommand(cwd, canonicalCommand))
5962
+ return false;
5963
+ if (findUnquotedOmxStateCommandIndexes(canonicalCommand, "clear").length > 0)
5964
+ return false;
5965
+ if (hasDynamicNestedShellExecution(canonicalCommand))
5966
+ return false;
5967
+ if (commandHasDeepInterviewWriteIntent(canonicalCommand))
5968
+ return false;
5969
+ const operations = collectOmxStateCommandOperations(canonicalCommand, "write");
5970
+ if (operations.length !== 1)
5971
+ return false;
5972
+ const operation = operations[0];
5973
+ if (!operation || operation.nested || hasPriorExecutableCommand(operation.prefix))
5974
+ return false;
5975
+ const payload = readStateWriteInputPayload(cwd, canonicalCommand, command);
5976
+ return payload ? isCompleteRalplanTerminalWritePayload(payload, activeState, sessionId) : false;
5977
+ }
5978
+ function commandEndsPlanningPhase(cwd, command) {
5979
+ if (findUnquotedOmxStateCommandIndexes(command, "clear").length > 0)
5980
+ return true;
5981
+ const canonicalCommand = canonicalizeOmxStateTransportCommand(command);
5982
+ if (hasUnsafeUnquotedHeredocExpansion(canonicalCommand))
5983
+ return true;
5984
+ if (sourcesFileWrittenEarlierInSameCommand(cwd, canonicalCommand))
5985
+ return true;
5986
+ if (findUnquotedOmxStateCommandIndexes(canonicalCommand, "clear").length > 0)
5987
+ return true;
5988
+ if (hasDynamicNestedShellExecution(canonicalCommand))
5989
+ return true;
5990
+ const stateWriteCount = findUnquotedOmxStateCommandIndexes(canonicalCommand, "write").length;
5991
+ if (stateWriteCount > 1)
5992
+ return true;
5993
+ if (stateWriteCount === 0)
5994
+ return false;
5995
+ const payload = readStateWriteInputPayload(cwd, canonicalCommand, command);
5996
+ return payload ? isPlanningPhaseDeactivationPayload(payload) : true;
5997
+ }
5998
+ function isAllowedDeepInterviewBashWrite(cwd, command) {
5999
+ if (isAllowedDeepInterviewRalplanHandoffCommand(cwd, command))
6000
+ return true;
6001
+ if (hasDeepInterviewRalplanHandoffStateMutation(cwd, command))
6002
+ return false;
6003
+ if (commandEndsPlanningPhase(cwd, command))
6004
+ return false;
6005
+ if (commandHasUntargetedPlanningForbiddenIntent(command))
6006
+ return false;
6007
+ if (firstPlanningTmpScriptExecutionTarget(cwd, command))
6008
+ return false;
6009
+ if (!commandHasDeepInterviewWriteIntent(command))
6010
+ return true;
6011
+ if (hasUnresolvedConductorInterpreterWrite(command))
6012
+ return false;
6013
+ const targets = extractDeepInterviewCommandWriteTargets(command);
6014
+ if (targets.some((target) => !isAllowedDeepInterviewArtifactPath(cwd, target)))
6015
+ return false;
6016
+ return targets.length > 0 && targets.every((target) => isAllowedDeepInterviewArtifactPath(cwd, target));
6017
+ }
6018
+ async function readActiveDeepInterviewStateForPreToolUse(cwd, stateDir, sessionId, threadId) {
6019
+ const canonicalState = sessionId
6020
+ ? await readVisibleSkillActiveStateForStateDir(stateDir, sessionId)
6021
+ : await readSkillActiveState(join(stateDir, SKILL_ACTIVE_STATE_FILE));
6022
+ if (!canonicalState)
6023
+ return null;
6024
+ const modeState = sessionId
6025
+ ? await readStopSessionPinnedState("deep-interview-state.json", cwd, sessionId, stateDir)
6026
+ : await readJsonIfExists(join(stateDir, "deep-interview-state.json"));
6027
+ if (isActiveDeepInterviewPhase(modeState) && modeState && modeStateMatchesSkillStopContext(modeState, cwd, sessionId)) {
6028
+ const hasActiveDeepInterviewSkill = listActiveSkills(canonicalState).some((entry) => (entry.skill === "deep-interview"
6029
+ && matchesSkillStopContext(entry, canonicalState, sessionId, threadId)));
6030
+ if (hasActiveDeepInterviewSkill)
6031
+ return modeState;
6032
+ }
6033
+ const autopilotState = sessionId
6034
+ ? await readStopSessionPinnedState("autopilot-state.json", cwd, sessionId, stateDir)
6035
+ : await readJsonIfExists(join(stateDir, "autopilot-state.json"));
6036
+ if (!autopilotState || autopilotState.active !== true)
6037
+ return null;
6038
+ const autopilotMode = safeString(autopilotState.mode).trim();
6039
+ if (autopilotMode && autopilotMode !== "autopilot")
6040
+ return null;
6041
+ if (!modeStateMatchesSkillStopContext(autopilotState, cwd, sessionId))
6042
+ return null;
6043
+ const autopilotStatePhase = safeString(autopilotState.current_phase ?? autopilotState.currentPhase).trim().toLowerCase();
6044
+ const autopilotIsDeepInterview = normalizeAutopilotPhase(autopilotStatePhase) === "deep-interview";
6045
+ const hasDeepInterviewScopedAutopilotSkill = listActiveSkills(canonicalState).some((entry) => (entry.skill === "autopilot"
6046
+ && normalizeAutopilotPhase(safeString(entry.phase).trim().toLowerCase()) === "deep-interview"
6047
+ && matchesSkillStopContext(entry, canonicalState, sessionId, threadId)));
6048
+ const hasActiveAutopilotSkill = listActiveSkills(canonicalState).some((entry) => (entry.skill === "autopilot"
6049
+ && matchesSkillStopContext(entry, canonicalState, sessionId, threadId)));
6050
+ if (!hasActiveAutopilotSkill)
6051
+ return null;
6052
+ if (!autopilotIsDeepInterview && !hasDeepInterviewScopedAutopilotSkill)
6053
+ return null;
6054
+ return autopilotState;
6055
+ }
6056
+ async function readActiveRalplanStateForPreToolUse(cwd, stateDir, sessionId, threadId) {
6057
+ const modeState = sessionId
6058
+ ? await readStopSessionPinnedState("ralplan-state.json", cwd, sessionId, stateDir)
6059
+ : await readJsonIfExists(join(stateDir, "ralplan-state.json"));
6060
+ const canonicalState = sessionId
6061
+ ? await readVisibleSkillActiveStateForStateDir(stateDir, sessionId)
6062
+ : await readSkillActiveState(join(stateDir, SKILL_ACTIVE_STATE_FILE));
6063
+ if (isActiveRalplanPhase(modeState) && modeState && modeStateMatchesSkillStopContext(modeState, cwd, sessionId)) {
6064
+ if (hasExplicitExecutionHandoffSkill(canonicalState, sessionId, threadId))
6065
+ return null;
6066
+ if (!canonicalState)
6067
+ return null;
6068
+ const hasActiveRalplanSkill = listActiveSkills(canonicalState).some((entry) => (entry.skill === "ralplan"
6069
+ && matchesSkillStopContext(entry, canonicalState, sessionId, threadId)));
6070
+ if (hasActiveRalplanSkill)
6071
+ return modeState;
6072
+ }
6073
+ const autopilotState = sessionId
6074
+ ? await readStopSessionPinnedState("autopilot-state.json", cwd, sessionId, stateDir)
6075
+ : await readJsonIfExists(join(stateDir, "autopilot-state.json"));
6076
+ if (!autopilotState || autopilotState.active !== true)
6077
+ return null;
6078
+ const autopilotMode = safeString(autopilotState.mode).trim();
6079
+ if (autopilotMode && autopilotMode !== "autopilot")
6080
+ return null;
6081
+ if (!modeStateMatchesSkillStopContext(autopilotState, cwd, sessionId))
6082
+ return null;
6083
+ if (!canonicalState)
6084
+ return null;
6085
+ const hasActiveAutopilotSkill = listActiveSkills(canonicalState).some((entry) => (entry.skill === "autopilot"
6086
+ && matchesSkillStopContext(entry, canonicalState, sessionId, threadId)));
6087
+ if (!hasActiveAutopilotSkill)
6088
+ return null;
6089
+ const autopilotStatePhase = safeString(autopilotState.current_phase ?? autopilotState.currentPhase).trim().toLowerCase();
6090
+ if (isAutopilotReviewReworkPhase(autopilotStatePhase))
6091
+ return null;
6092
+ if (!canAutopilotSkillMirrorSupplyRalplanPhase(autopilotStatePhase))
6093
+ return null;
6094
+ const hasRalplanScopedAutopilotSkill = listActiveSkills(canonicalState).some((entry) => (entry.skill === "autopilot"
6095
+ && isAutopilotRalplanLikePhase(safeString(entry.phase).trim().toLowerCase())
6096
+ && matchesSkillStopContext(entry, canonicalState, sessionId, threadId)));
6097
+ if (!isAutopilotRalplanLikePhase(autopilotStatePhase) && !hasRalplanScopedAutopilotSkill)
6098
+ return null;
6099
+ return hasActiveAutopilotSkill ? autopilotState : null;
6100
+ }
6101
+ function isAllowedRalplanBashWrite(cwd, command, activeState, sessionId) {
6102
+ const beadsCommand = classifyRalplanBeadsMetadataCommand(cwd, command);
6103
+ const targets = extractDeepInterviewCommandWriteTargets(command);
6104
+ const hasAllowedTargets = targets.length > 0
6105
+ && targets.every((target) => isAllowedRalplanArtifactPath(cwd, target));
6106
+ if (beadsCommand.present) {
6107
+ return beadsCommand.allowed && (targets.length === 0 || hasAllowedTargets);
6108
+ }
6109
+ if (commandEndsPlanningPhase(cwd, command)) {
6110
+ return isAllowedRalplanTerminalStateWriteCommand(cwd, command, activeState, sessionId);
6111
+ }
6112
+ if (commandHasUntargetedPlanningForbiddenIntent(command))
6113
+ return false;
6114
+ if (firstPlanningTmpScriptExecutionTarget(cwd, command))
6115
+ return false;
6116
+ if (!commandHasDeepInterviewWriteIntent(command))
6117
+ return true;
6118
+ if (hasUnresolvedConductorInterpreterWrite(command))
6119
+ return false;
6120
+ if (targets.some((target) => !isAllowedRalplanArtifactPath(cwd, target)))
6121
+ return false;
6122
+ return hasAllowedTargets;
6123
+ }
6124
+ function buildRalplanBashBlockedDetail(cwd, command) {
6125
+ const targets = extractDeepInterviewCommandWriteTargets(command);
6126
+ const blockedTarget = targets.find((target) => !isAllowedRalplanArtifactPath(cwd, target));
6127
+ if (blockedTarget && isUnresolvedVariableTarget(blockedTarget)) {
6128
+ return `unresolved Bash write target ${blockedTarget} is not under allowed planning artifact paths or metadata paths (${RALPLAN_ALLOWED_WRITE_PREFIXES.join(", ")})`;
6129
+ }
6130
+ if (blockedTarget) {
3077
6131
  const operationClass = /\btee\s+(?:-a\s+)?/.test(command) ? "Bash tee write" : "Bash redirect write";
3078
6132
  return `${operationClass} target ${blockedTarget} is not under allowed planning artifact paths or metadata paths (${RALPLAN_ALLOWED_WRITE_PREFIXES.join(", ")})`;
3079
6133
  }
3080
- const beadsCommand = classifyRalplanBeadsMetadataCommand(cwd, command);
3081
- if (beadsCommand.present && !beadsCommand.allowed) {
3082
- return beadsCommand.reason ?? "Beads tracker command is not an allowed planning metadata mutation";
6134
+ const executedTmpTarget = firstPlanningTmpScriptExecutionTarget(cwd, command);
6135
+ if (executedTmpTarget) {
6136
+ return `execution target ${executedTmpTarget} is under .omx/tmp; planning tmp artifacts must not be used as generated-script transport`;
6137
+ }
6138
+ if (commandHasPackageInstallIntent(command)) {
6139
+ return "package installation commands are implementation actions and cannot be combined with allowed planning artifact writes";
6140
+ }
6141
+ if (commandHasDestructiveGitSubcommand(command)) {
6142
+ return "destructive git commands are implementation actions and cannot be combined with allowed planning artifact writes";
6143
+ }
6144
+ const beadsCommand = classifyRalplanBeadsMetadataCommand(cwd, command);
6145
+ if (beadsCommand.present && !beadsCommand.allowed) {
6146
+ return beadsCommand.reason ?? "Beads tracker command is not an allowed planning metadata mutation";
6147
+ }
6148
+ if (beadsCommand.present) {
6149
+ return "Beads tracker command also performs an implementation write outside allowed planning metadata";
6150
+ }
6151
+ return "Bash write intent did not identify an allowed planning artifact path or metadata path";
6152
+ }
6153
+ function buildDeepInterviewBashBlockedDetail(cwd, command) {
6154
+ const targets = extractDeepInterviewCommandWriteTargets(command);
6155
+ const blockedTarget = targets.find((target) => !isAllowedDeepInterviewArtifactPath(cwd, target));
6156
+ if (blockedTarget && isUnresolvedVariableTarget(blockedTarget)) {
6157
+ return `unresolved Bash write target ${blockedTarget} is not under allowed deep-interview artifact paths or metadata paths (${DEEP_INTERVIEW_ALLOWED_WRITE_PREFIXES.join(", ")})`;
6158
+ }
6159
+ if (blockedTarget) {
6160
+ const operationClass = /\btee\s+(?:-a\s+)?/.test(command) ? "Bash tee write" : "Bash write";
6161
+ return `${operationClass} target ${blockedTarget} is not under allowed deep-interview artifact paths or metadata paths (${DEEP_INTERVIEW_ALLOWED_WRITE_PREFIXES.join(", ")})`;
6162
+ }
6163
+ const executedTmpTarget = firstPlanningTmpScriptExecutionTarget(cwd, command);
6164
+ if (executedTmpTarget) {
6165
+ return `execution target ${executedTmpTarget} is under .omx/tmp; deep-interview tmp artifacts must not be used as generated-script transport`;
6166
+ }
6167
+ if (commandHasPackageInstallIntent(command)) {
6168
+ return "package installation commands are implementation actions and cannot be combined with allowed deep-interview artifact writes";
6169
+ }
6170
+ if (commandHasDestructiveGitSubcommand(command)) {
6171
+ return "destructive git commands are implementation actions and cannot be combined with allowed deep-interview artifact writes";
6172
+ }
6173
+ return "Bash write intent did not identify an allowed deep-interview artifact path or metadata path";
6174
+ }
6175
+ async function buildRalplanPreToolUseBoundaryOutput(payload, cwd, stateDir, resolvedSessionId) {
6176
+ const sessionId = safeString(resolvedSessionId ?? readPayloadSessionId(payload)).trim();
6177
+ if (await hasTrustedTypedSubagentThreadSpawnProvenanceForPreToolUse(payload, cwd, sessionId))
6178
+ return null;
6179
+ const threadId = readPayloadThreadId(payload);
6180
+ const activeState = await readActiveRalplanStateForPreToolUse(cwd, stateDir, sessionId, threadId);
6181
+ if (!activeState)
6182
+ return null;
6183
+ const toolName = safeString(payload.tool_name).trim();
6184
+ const command = readPreToolUseCommand(payload);
6185
+ const pathCandidates = readPreToolUsePathCandidates(payload);
6186
+ let blocked = false;
6187
+ let blockedDetail = "implementation/write tools are blocked until an explicit execution handoff workflow is activated";
6188
+ if (toolName === "Bash") {
6189
+ blocked = !isAllowedRalplanBashWrite(cwd, command, activeState, sessionId);
6190
+ if (blocked) {
6191
+ blockedDetail = buildRalplanBashBlockedDetail(cwd, command);
6192
+ }
6193
+ }
6194
+ else if (toolName === "mcp__omx_state__state_clear"
6195
+ || (toolName === "mcp__omx_state__state_write"
6196
+ && isPlanningPhaseDeactivationPayload(normalizeStateWriteClassificationPayload(safeObject(payload.tool_input))))) {
6197
+ blocked = true;
6198
+ blockedDetail = `${toolName} would deactivate protected planning state`;
6199
+ }
6200
+ else if (PLANNING_MODE_IMPLEMENTATION_TOOL_NAMES.has(toolName)) {
6201
+ const toolPathCandidates = collectImplementationToolPathCandidates(payload, toolName, pathCandidates);
6202
+ if (toolPathCandidates.length === 0) {
6203
+ blocked = true;
6204
+ blockedDetail = describeImplementationToolBlock(toolName, undefined, toolPathCandidates.length);
6205
+ }
6206
+ else {
6207
+ const blockedPath = toolPathCandidates.find((candidate) => !isAllowedRalplanArtifactPath(cwd, candidate));
6208
+ blocked = blockedPath !== undefined;
6209
+ if (blockedPath !== undefined) {
6210
+ blockedDetail = describeImplementationToolBlock(toolName, blockedPath, toolPathCandidates.length);
6211
+ }
6212
+ }
6213
+ }
6214
+ if (!blocked)
6215
+ return null;
6216
+ const phase = formatPhase(activeState.current_phase ?? activeState.currentPhase, "planning");
6217
+ const activeMode = safeString(activeState.mode).trim().toLowerCase();
6218
+ const planningModeLabel = activeMode === "autopilot" ? "Autopilot planning" : "Ralplan";
6219
+ const planningModeDescription = activeMode === "autopilot"
6220
+ ? "Autopilot is supervising a planning phase"
6221
+ : "Ralplan is consensus-planning mode";
6222
+ return {
6223
+ decision: "block",
6224
+ reason: `${planningModeLabel} is active (phase: ${phase}); implementation/write tools are blocked until an explicit execution handoff workflow is activated; ${blockedDetail}.`,
6225
+ hookSpecificOutput: {
6226
+ hookEventName: "PreToolUse",
6227
+ additionalContext: `${planningModeDescription}. `
6228
+ + "Write only planning artifacts under `.omx/context/`, `.omx/plans/`, `.omx/specs/`, `.omx/tmp/`, required `.omx/state/` files, or tracker metadata under `.beads/`. "
6229
+ + "Do not edit implementation files or run implementation-focused writes from planning phases. "
6230
+ + `To execute, first process an explicit handoff such as ${formatExecutionHandoffList(cwd)}, which must emit terminal planning state before implementation begins.`,
6231
+ },
6232
+ };
6233
+ }
6234
+ async function buildDeepInterviewPreToolUseBoundaryOutput(payload, cwd, stateDir, resolvedSessionId) {
6235
+ const sessionId = safeString(resolvedSessionId ?? readPayloadSessionId(payload)).trim();
6236
+ if (await hasTrustedTypedSubagentThreadSpawnProvenanceForPreToolUse(payload, cwd, sessionId))
6237
+ return null;
6238
+ const threadId = readPayloadThreadId(payload);
6239
+ const activeState = await readActiveDeepInterviewStateForPreToolUse(cwd, stateDir, sessionId, threadId);
6240
+ if (!activeState)
6241
+ return null;
6242
+ const toolName = safeString(payload.tool_name).trim();
6243
+ const command = readPreToolUseCommand(payload);
6244
+ const pathCandidates = readPreToolUsePathCandidates(payload);
6245
+ let blocked = false;
6246
+ let blockedDetail = "implementation/write tools are blocked until an explicit handoff workflow is activated";
6247
+ if (toolName === "Bash") {
6248
+ blocked = !isAllowedDeepInterviewBashWrite(cwd, command);
6249
+ if (blocked) {
6250
+ blockedDetail = buildDeepInterviewBashBlockedDetail(cwd, command);
6251
+ }
6252
+ }
6253
+ else if (toolName === "mcp__omx_state__state_clear"
6254
+ || (toolName === "mcp__omx_state__state_write"
6255
+ && isPlanningPhaseDeactivationPayload(normalizeStateWriteClassificationPayload(safeObject(payload.tool_input))))) {
6256
+ blocked = true;
6257
+ blockedDetail = `${toolName} would deactivate protected deep-interview planning state`;
6258
+ }
6259
+ else if (DEEP_INTERVIEW_IMPLEMENTATION_TOOL_NAMES.has(toolName)) {
6260
+ const candidates = collectImplementationToolPathCandidates(payload, toolName, pathCandidates);
6261
+ blocked = candidates.length === 0
6262
+ || !candidates.every((candidate) => isAllowedDeepInterviewArtifactPath(cwd, candidate));
6263
+ if (blocked) {
6264
+ const blockedPath = candidates.find((candidate) => !isAllowedDeepInterviewArtifactPath(cwd, candidate));
6265
+ blockedDetail = describeImplementationToolBlock(toolName, blockedPath, candidates.length);
6266
+ }
6267
+ }
6268
+ if (!blocked)
6269
+ return null;
6270
+ const phase = formatPhase(activeState.current_phase ?? activeState.currentPhase, "planning");
6271
+ return {
6272
+ decision: "block",
6273
+ reason: `Deep-interview is active (phase: ${phase}); implementation/write tools are blocked until an explicit handoff workflow is activated; ${blockedDetail}.`,
6274
+ hookSpecificOutput: {
6275
+ hookEventName: "PreToolUse",
6276
+ additionalContext: `Deep-interview is requirements/spec mode. Treat detailed user answers as interview/spec material, not implicit implementation authorization. You may write only deep-interview artifacts under \`.omx/context/\`, \`.omx/interviews/\`, \`.omx/specs/\`, \`.omx/tmp/\`, or required \`.omx/state/\` files. To implement, first ask for or process an explicit transition such as \`$ralplan\`, \`$autopilot\`, ${formatExecutionHandoffList(cwd)}.`,
6277
+ },
6278
+ };
6279
+ }
6280
+ function blocksDeepInterviewImplementationWrite(payload, cwd) {
6281
+ const toolName = safeString(payload.tool_name).trim();
6282
+ if (toolName === "Bash") {
6283
+ return !isAllowedDeepInterviewBashWrite(cwd, readPreToolUseCommand(payload));
6284
+ }
6285
+ if (!DEEP_INTERVIEW_IMPLEMENTATION_TOOL_NAMES.has(toolName))
6286
+ return false;
6287
+ const candidates = collectImplementationToolPathCandidates(payload, toolName, readPreToolUsePathCandidates(payload));
6288
+ return candidates.length === 0
6289
+ || !candidates.every((candidate) => isAllowedDeepInterviewArtifactPath(cwd, candidate));
6290
+ }
6291
+ // Shared builder for the "live root session pointer owned by another session"
6292
+ // fail-closed block. Deep-interview and ralplan/autopilot only differ in the
6293
+ // human-readable mode label and phase-description fragment; the decision,
6294
+ // structure, and guidance are identical.
6295
+ function buildRootPointerConflictBlock(activeState, planningModeLabel, planningPhaseDescription) {
6296
+ const phase = formatPhase(activeState.current_phase ?? activeState.currentPhase, "planning");
6297
+ return {
6298
+ decision: "block",
6299
+ reason: `${planningModeLabel} is active in the live root session pointer (phase: ${phase}), but the current native session could not be authoritatively resolved to that owner; failing closed for planning-write protection.`,
6300
+ hookSpecificOutput: {
6301
+ hookEventName: "PreToolUse",
6302
+ additionalContext: `OMX detected a live root session pointer owned by another session while a ${planningPhaseDescription} is active. `
6303
+ + "This indicates collapsed session-root isolation. Do not perform implementation writes from this unresolved session; use the owning OMX session or restart with an isolated OMX_ROOT.",
6304
+ },
6305
+ };
6306
+ }
6307
+ function buildDeepInterviewRootPointerConflictBlock(activeState) {
6308
+ return buildRootPointerConflictBlock(activeState, "Deep-interview", "deep-interview planning phase");
6309
+ }
6310
+ function buildRalplanRootPointerConflictBlock(activeState) {
6311
+ const activeMode = safeString(activeState.mode).trim().toLowerCase();
6312
+ const planningModeLabel = activeMode === "autopilot" ? "Autopilot planning" : "Ralplan";
6313
+ return buildRootPointerConflictBlock(activeState, planningModeLabel, "ralplan/autopilot planning phase");
6314
+ }
6315
+ async function buildPlanningRootPointerConflictPreToolUseOutput(payload, cwd, stateDir, rootState) {
6316
+ const rootSessionId = safeString(rootState?.session_id).trim();
6317
+ if (!rootSessionId)
6318
+ return null;
6319
+ const ownerCwd = safeString(rootState?.cwd).trim() || cwd;
6320
+ const deepInterviewState = await readActiveDeepInterviewStateForPreToolUse(ownerCwd, stateDir, rootSessionId, "");
6321
+ if (deepInterviewState) {
6322
+ const conflictToolName = safeString(payload.tool_name).trim();
6323
+ if (conflictToolName === "mcp__omx_state__state_clear"
6324
+ || (conflictToolName === "mcp__omx_state__state_write"
6325
+ && isPlanningPhaseDeactivationPayload(normalizeStateWriteClassificationPayload(safeObject(payload.tool_input))))) {
6326
+ return buildDeepInterviewRootPointerConflictBlock(deepInterviewState);
6327
+ }
6328
+ if (blocksDeepInterviewImplementationWrite(payload, cwd)) {
6329
+ return buildDeepInterviewRootPointerConflictBlock(deepInterviewState);
6330
+ }
6331
+ }
6332
+ const ralplanState = await readActiveRalplanStateForPreToolUse(ownerCwd, stateDir, rootSessionId, "");
6333
+ if (!ralplanState)
6334
+ return null;
6335
+ const toolName = safeString(payload.tool_name).trim();
6336
+ let blocked = false;
6337
+ if (toolName === "Bash") {
6338
+ const command = readPreToolUseCommand(payload);
6339
+ blocked = commandEndsPlanningPhase(cwd, command)
6340
+ || !isAllowedRalplanBashWrite(cwd, command, ralplanState, rootSessionId);
6341
+ }
6342
+ else if (toolName === "mcp__omx_state__state_clear"
6343
+ || (toolName === "mcp__omx_state__state_write"
6344
+ && isPlanningPhaseDeactivationPayload(normalizeStateWriteClassificationPayload(safeObject(payload.tool_input))))) {
6345
+ blocked = true;
6346
+ }
6347
+ else if (PLANNING_MODE_IMPLEMENTATION_TOOL_NAMES.has(toolName)) {
6348
+ const toolPathCandidates = collectImplementationToolPathCandidates(payload, toolName, readPreToolUsePathCandidates(payload));
6349
+ blocked = toolPathCandidates.length === 0
6350
+ || toolPathCandidates.some((candidate) => !isAllowedRalplanArtifactPath(cwd, candidate));
6351
+ }
6352
+ return blocked ? buildRalplanRootPointerConflictBlock(ralplanState) : null;
6353
+ }
6354
+ async function hasTrustedTypedSubagentThreadSpawnProvenanceForPreToolUse(payload, cwd, sessionId) {
6355
+ if (hasTeamWorkerEnvironment())
6356
+ return true;
6357
+ const source = safeObject(payload.source);
6358
+ const subagent = safeObject(source.subagent);
6359
+ const threadSpawn = safeObject(subagent.thread_spawn);
6360
+ const parentThreadId = safeString(threadSpawn.parent_thread_id
6361
+ ?? threadSpawn.parentThreadId
6362
+ ?? threadSpawn.leader_thread_id
6363
+ ?? threadSpawn.leaderThreadId).trim();
6364
+ if (!parentThreadId)
6365
+ return false;
6366
+ if (!isTypedAgentRolePayload(payload))
6367
+ return false;
6368
+ const trackingState = await readSubagentTrackingState(cwd).catch(() => null);
6369
+ const session = trackingState?.sessions?.[sessionId];
6370
+ if (!session)
6371
+ return false;
6372
+ return session.leader_thread_id === parentThreadId || parentThreadId in session.threads;
6373
+ }
6374
+ function isActiveConductorModeState(state, mode, sessionId) {
6375
+ if (!state || state.active !== true)
6376
+ return false;
6377
+ const stateMode = safeString(state.mode).trim();
6378
+ if (stateMode && stateMode !== mode)
6379
+ return false;
6380
+ const stateSessionId = safeString(state.session_id).trim();
6381
+ if (sessionId && stateSessionId && stateSessionId !== sessionId)
6382
+ return false;
6383
+ return isNonTerminalPhase(state.current_phase ?? state.currentPhase);
6384
+ }
6385
+ async function readActiveMainRootConductorStateForPreToolUse(payload, cwd, stateDir, resolvedSessionId) {
6386
+ const sessionId = safeString(resolvedSessionId ?? readPayloadSessionId(payload)).trim();
6387
+ const payloadSessionId = readPayloadSessionId(payload);
6388
+ if (payloadSessionId && sessionId && payloadSessionId !== sessionId) {
6389
+ const currentSession = await readUsableSessionStateFromStateDir(cwd, stateDir).catch(() => null);
6390
+ const payloadMatchesMappedSession = payloadSessionId === safeString(currentSession?.native_session_id).trim()
6391
+ || payloadSessionId === safeString(currentSession?.owner_omx_session_id).trim()
6392
+ || payloadSessionId === safeString(currentSession?.owner_codex_session_id).trim();
6393
+ if (!payloadMatchesMappedSession)
6394
+ return null;
6395
+ }
6396
+ const threadId = readPayloadThreadId(payload);
6397
+ if (!sessionId)
6398
+ return null;
6399
+ if (await hasTrustedTypedSubagentThreadSpawnProvenanceForPreToolUse(payload, cwd, sessionId))
6400
+ return null;
6401
+ const canonicalState = await readVisibleSkillActiveStateForStateDir(stateDir, sessionId);
6402
+ if (!canonicalState)
6403
+ return null;
6404
+ const activeEntries = listActiveSkills(canonicalState).filter((entry) => (matchesSkillStopContext(entry, canonicalState, sessionId, threadId)));
6405
+ const hasActiveSkill = (skill) => activeEntries.some((entry) => entry.skill === skill);
6406
+ if (hasActiveSkill("autopilot")) {
6407
+ const state = await readStopSessionPinnedState("autopilot-state.json", cwd, sessionId, stateDir);
6408
+ const childPhase = deriveAutopilotChildPhase(state);
6409
+ const hasMatchingAutopilotEntry = activeEntries.some((entry) => (entry.skill === "autopilot"
6410
+ && normalizeAutopilotPhase(safeString(entry.phase).trim().toLowerCase()) === childPhase));
6411
+ if (state
6412
+ && childPhase
6413
+ && childPhase !== "deep-interview"
6414
+ && childPhase !== "ralplan"
6415
+ && childPhase !== "rework"
6416
+ && hasMatchingAutopilotEntry
6417
+ && isActiveConductorModeState(state, "autopilot", sessionId)) {
6418
+ return { mode: "autopilot", phase: childPhase };
6419
+ }
6420
+ }
6421
+ if (hasActiveSkill("ralph")) {
6422
+ const state = await readStopSessionPinnedState("ralph-state.json", cwd, sessionId, stateDir);
6423
+ if (isActiveConductorModeState(state, "ralph", sessionId)) {
6424
+ const phase = safeString(state?.current_phase ?? state?.currentPhase) || "active";
6425
+ if (phase.toLowerCase() !== "starting")
6426
+ return { mode: "ralph", phase };
6427
+ }
6428
+ }
6429
+ if (hasActiveSkill("ultragoal")) {
6430
+ const state = await readStopSessionPinnedState("ultragoal-state.json", cwd, sessionId, stateDir);
6431
+ if (isActiveConductorModeState(state, "ultragoal", sessionId)) {
6432
+ return { mode: "ultragoal", phase: safeString(state?.current_phase ?? state?.currentPhase) || "active" };
6433
+ }
6434
+ }
6435
+ if (hasActiveSkill("team") && !hasTeamWorkerEnvironment()) {
6436
+ const teamStateForStop = await readTeamModeStateForStop(cwd, stateDir, sessionId, threadId);
6437
+ const state = teamStateForStop?.state ?? null;
6438
+ if (isActiveConductorModeState(state, "team", sessionId)) {
6439
+ const teamName = safeString(state?.team_name).trim();
6440
+ const phase = teamName ? (await readTeamPhase(teamName, cwd).catch(() => null))?.current_phase ?? state?.current_phase : state?.current_phase;
6441
+ if (isNonTerminalPhase(phase))
6442
+ return { mode: "team", phase: safeString(phase) || "active" };
6443
+ }
6444
+ }
6445
+ return null;
6446
+ }
6447
+ function normalizeRepoRelativePath(cwd, rawPath) {
6448
+ const candidate = rawPath.trim().replace(/^['"]|['"]$/g, "");
6449
+ if (!candidate || isUnresolvedVariableTarget(candidate))
6450
+ return null;
6451
+ const absolute = isAbsolute(candidate) ? resolve(candidate) : resolve(cwd, candidate);
6452
+ let relativePath = relative(cwd, absolute).replace(/\\/g, "/");
6453
+ if (!relativePath || relativePath === ".")
6454
+ return null;
6455
+ if (relativePath.startsWith("../") || relativePath === "..") {
6456
+ relativePath = candidate.replace(/\\/g, "/");
6457
+ }
6458
+ return relativePath.replace(/^\.\//, "");
6459
+ }
6460
+ function isAllowedConductorMetadataPath(cwd, rawPath) {
6461
+ const relativePath = normalizeRepoRelativePath(cwd, rawPath);
6462
+ if (!relativePath)
6463
+ return false;
6464
+ if (isProtectedPlanningStatePath(relativePath))
6465
+ return false;
6466
+ const artifactKind = classifyConductorArtifactKind(relativePath);
6467
+ const actionKind = actionKindForConductorArtifact(artifactKind);
6468
+ return authorizeConductorAction({
6469
+ phase: "autopilot-supervision",
6470
+ laneKind: "main-conductor",
6471
+ actionKind,
6472
+ artifactKind,
6473
+ }).allowed;
6474
+ }
6475
+ function describeConductorBlockedWrite(toolName, blockedPath, pathCount) {
6476
+ if (pathCount === 0) {
6477
+ const operationClass = isApplyPatchToolName(toolName) ? "apply_patch target extraction failed" : `${toolName} path`;
6478
+ return `${operationClass} target <unresolved>; Main-root Conductor may write only workflow state/ledger/mailbox/handoff metadata`;
6479
+ }
6480
+ const operationClass = isApplyPatchToolName(toolName) ? "apply_patch target" : `${toolName} path`;
6481
+ return `${operationClass} target ${blockedPath ?? "<unresolved>"} is not workflow state/ledger/mailbox/handoff metadata`;
6482
+ }
6483
+ const CONDUCTOR_BASH_MUTATION_COMMANDS = new Set([
6484
+ "cp",
6485
+ "mv",
6486
+ "rm",
6487
+ "touch",
6488
+ "mkdir",
6489
+ "rmdir",
6490
+ "install",
6491
+ "ln",
6492
+ "chmod",
6493
+ "chown",
6494
+ "chgrp",
6495
+ "truncate",
6496
+ "sed",
6497
+ "perl",
6498
+ "dd",
6499
+ "rsync",
6500
+ ]);
6501
+ const CONDUCTOR_BASH_TRANSPARENT_WRAPPERS = new Set([
6502
+ "builtin",
6503
+ "noglob",
6504
+ ]);
6505
+ const CONDUCTOR_BASH_DOWNLOADER_COMMANDS = new Set([
6506
+ "curl",
6507
+ "wget",
6508
+ ]);
6509
+ const CONDUCTOR_BASH_COMPOUND_SYNTAX_WORDS = new Set([
6510
+ "if",
6511
+ "then",
6512
+ "else",
6513
+ "elif",
6514
+ "fi",
6515
+ "for",
6516
+ "while",
6517
+ "until",
6518
+ "do",
6519
+ "done",
6520
+ "case",
6521
+ "esac",
6522
+ "in",
6523
+ "{",
6524
+ "}",
6525
+ "(",
6526
+ ")",
6527
+ ]);
6528
+ const CONDUCTOR_BASH_OPTIONS_WITH_VALUES = new Set([
6529
+ "-S",
6530
+ "--suffix",
6531
+ "-t",
6532
+ "--target-directory",
6533
+ "-m",
6534
+ "--mode",
6535
+ "-o",
6536
+ "--owner",
6537
+ "-g",
6538
+ "--group",
6539
+ "--reference",
6540
+ "--preserve",
6541
+ "--size",
6542
+ "-if",
6543
+ "if",
6544
+ "-of",
6545
+ "of",
6546
+ ]);
6547
+ function extractConductorInterpreterWrites(command) {
6548
+ const writes = [];
6549
+ const scanCommand = normalizeShellLineContinuations(command);
6550
+ let recognizedPythonOpenWrites = 0;
6551
+ let recognizedPythonPathMutations = 0;
6552
+ let recognizedPythonShutilMutations = 0;
6553
+ for (const match of scanCommand.matchAll(/\bnode\b[\s\S]{0,520}\b(?:appendFileSync|writeFileSync|appendFile|writeFile|createWriteStream)\s*\(\s*(["'])([^"']+)\1/g)) {
6554
+ const target = safeString(match[2]).trim();
6555
+ writes.push({ runtime: "node", targets: target ? [target] : [], unresolved: !target });
6556
+ }
6557
+ if (/\bnode\b[\s\S]{0,520}\b(?:appendFileSync|writeFileSync|appendFile|writeFile|createWriteStream)\s*\(/.test(scanCommand)
6558
+ && !writes.some((write) => write.runtime === "node")) {
6559
+ writes.push({ runtime: "node", targets: [], unresolved: true });
6560
+ }
6561
+ for (const match of scanCommand.matchAll(/\bpython3?\b[\s\S]{0,520}\bopen\s*\(\s*(["'])([^"']+)\1\s*,\s*(["'])([^"']*[wax+][^"']*)\3/g)) {
6562
+ const target = safeString(match[2]).trim();
6563
+ writes.push({ runtime: "python", targets: target ? [target] : [], unresolved: !target });
6564
+ recognizedPythonOpenWrites += 1;
6565
+ }
6566
+ for (const match of scanCommand.matchAll(/\bpython3?\b[\s\S]{0,520}\bPath\s*\(\s*(["'])([^"']+)\1\s*\)\s*\.\s*(?:write_text|write_bytes)\s*\(/g)) {
6567
+ const target = safeString(match[2]).trim();
6568
+ writes.push({ runtime: "python", targets: target ? [target] : [], unresolved: !target });
6569
+ recognizedPythonPathMutations += 1;
6570
+ }
6571
+ for (const match of scanCommand.matchAll(/\bpython3?\b[\s\S]{0,520}\bPath\s*\(\s*(["'])([^"']+)\1\s*\)\s*\.\s*mkdir\s*\(/g)) {
6572
+ const target = safeString(match[2]).trim();
6573
+ writes.push({ runtime: "python", targets: target ? [target] : [], unresolved: !target });
6574
+ recognizedPythonPathMutations += 1;
6575
+ }
6576
+ for (const match of scanCommand.matchAll(/\bpython3?\b[\s\S]{0,520}\bshutil\s*\.\s*(?:copyfile|copy|copy2|copytree|move)\s*\(\s*(["'])([^"']+)\1\s*,\s*(["'])([^"']+)\3/g)) {
6577
+ const target = safeString(match[4]).trim();
6578
+ writes.push({ runtime: "python", targets: target ? [target] : [], unresolved: !target });
6579
+ recognizedPythonShutilMutations += 1;
6580
+ }
6581
+ const hasPythonRuntime = /\bpython3?\b/.test(scanCommand);
6582
+ const pythonOpenWriteCalls = hasPythonRuntime
6583
+ ? [...scanCommand.matchAll(/\bopen\s*\([^\n;)]*,\s*["'][^"']*[wax+][^"']*["']/g)].length
6584
+ : 0;
6585
+ const pythonPathMutationCalls = hasPythonRuntime
6586
+ ? [...scanCommand.matchAll(/(?:^|[\n;])\s*(?:\([^\n;#]*\bPath\s*\([^\n;#]*\)[^\n;#]*\)|\bPath\s*\([^\n;#]*\))\s*\.\s*(?:write_text|write_bytes|mkdir)\s*\(/g)].length
6587
+ : 0;
6588
+ const pythonShutilMutationCalls = hasPythonRuntime
6589
+ ? [...scanCommand.matchAll(/\bshutil\s*\.\s*(?:copyfile|copy|copy2|copytree|move)\s*\(/g)].length
6590
+ : 0;
6591
+ if (pythonOpenWriteCalls > recognizedPythonOpenWrites
6592
+ || pythonPathMutationCalls > recognizedPythonPathMutations
6593
+ || pythonShutilMutationCalls > recognizedPythonShutilMutations) {
6594
+ writes.push({ runtime: "python", targets: [], unresolved: true });
6595
+ }
6596
+ return writes;
6597
+ }
6598
+ function hasUnresolvedConductorInterpreterWrite(command) {
6599
+ return extractConductorInterpreterWrites(command).some((write) => write.unresolved || write.targets.length === 0);
6600
+ }
6601
+ function commandNameFromShellWord(word) {
6602
+ const base = word.trim().split(/[\\/]/).pop() ?? word.trim();
6603
+ return base.toLowerCase();
6604
+ }
6605
+ function isShellCommandSeparator(word) {
6606
+ return word === "&&" || word === "||" || word === ";" || word === "&" || word === "|" || word === "|&";
6607
+ }
6608
+ function isShellGroupingSyntaxWord(word) {
6609
+ return word === "(" || word === ")" || word === "{" || word === "}";
6610
+ }
6611
+ function isShellCommandTerminatorOrGroupClose(word) {
6612
+ return isShellCommandSeparator(word) || word === ")" || word === "}";
6613
+ }
6614
+ function commandUsesTargetDirectoryOption(commandName) {
6615
+ return commandName === "cp" || commandName === "mv" || commandName === "install" || commandName === "ln";
6616
+ }
6617
+ function isEnvironmentAssignmentWord(word) {
6618
+ return /^[A-Za-z_][A-Za-z0-9_]*=/.test(word);
6619
+ }
6620
+ function findSudoDispatchOperandIndex(words, startIndex) {
6621
+ for (let index = startIndex; index < words.length; index += 1) {
6622
+ const token = words[index] ?? "";
6623
+ if (!token || token === "--")
6624
+ continue;
6625
+ if (isShellAssignmentWord(token))
6626
+ continue;
6627
+ if (token === "-u"
6628
+ || token === "--user"
6629
+ || token === "-g"
6630
+ || token === "--group"
6631
+ || token === "-h"
6632
+ || token === "--host"
6633
+ || token === "-p"
6634
+ || token === "--prompt"
6635
+ || token === "-C"
6636
+ || token === "--close-from") {
6637
+ index += 1;
6638
+ continue;
6639
+ }
6640
+ if (token.startsWith("--user=")
6641
+ || token.startsWith("--group=")
6642
+ || token.startsWith("--host=")
6643
+ || token.startsWith("--prompt=")
6644
+ || token.startsWith("--close-from=")
6645
+ || /^-[ughpC].+/.test(token)) {
6646
+ continue;
6647
+ }
6648
+ if (token.startsWith("-"))
6649
+ continue;
6650
+ return index;
6651
+ }
6652
+ return null;
6653
+ }
6654
+ function findConductorWrapperOperandIndex(commandName, words, startIndex) {
6655
+ switch (commandName) {
6656
+ case "env":
6657
+ return findEnvDispatchOperandIndex(words, startIndex);
6658
+ case "command":
6659
+ return findCommandDispatchOperandIndex(words, startIndex);
6660
+ case "exec":
6661
+ return findExecDispatchOperandIndex(words, startIndex);
6662
+ case "sudo":
6663
+ return findSudoDispatchOperandIndex(words, startIndex);
6664
+ case "nohup":
6665
+ return findCommandDispatchOperandIndex(words, startIndex);
6666
+ case "time":
6667
+ return findTimeDispatchOperandIndex(words, startIndex);
6668
+ case "timeout":
6669
+ return findTimeoutDispatchOperandIndex(words, startIndex);
6670
+ case "nice":
6671
+ return findNiceDispatchOperandIndex(words, startIndex);
6672
+ case "stdbuf":
6673
+ return findStdbufDispatchOperandIndex(words, startIndex);
6674
+ default:
6675
+ return undefined;
6676
+ }
6677
+ }
6678
+ function isConductorDestinationOnlyMutationCommand(commandName) {
6679
+ return commandName === "cp" || commandName === "install" || commandName === "ln" || commandName === "rsync";
6680
+ }
6681
+ function isConductorInstallDirectoryMode(words, commandIndex) {
6682
+ for (let index = commandIndex + 1; index < words.length; index += 1) {
6683
+ const word = words[index] ?? "";
6684
+ if (!word || isShellCommandTerminatorOrGroupClose(word))
6685
+ break;
6686
+ if (word === "-d" || word === "--directory")
6687
+ return true;
6688
+ if (word.startsWith("-") && !word.startsWith("--") && word.includes("d"))
6689
+ return true;
6690
+ }
6691
+ return false;
6692
+ }
6693
+ function collectConductorDownloaderOutputTargets(commandName, words, commandIndex) {
6694
+ const targets = [];
6695
+ const explicitCurlTargets = [];
6696
+ let sawOutputFlag = false;
6697
+ let sawCurlRemoteName = false;
6698
+ const curlOutputDirs = [];
6699
+ const curlShortOptionsWithArgument = new Set("AbcCdDeEFHhKmMoPQrTuwxXyYz");
6700
+ const isCurlRemoteNameWord = (word) => {
6701
+ if (word === "--remote-name" || word === "--remote-name-all")
6702
+ return true;
6703
+ if (!word.startsWith("-") || word.startsWith("--"))
6704
+ return false;
6705
+ for (const option of word.slice(1)) {
6706
+ if (option === "O")
6707
+ return true;
6708
+ if (curlShortOptionsWithArgument.has(option))
6709
+ return false;
6710
+ }
6711
+ return false;
6712
+ };
6713
+ const pushTarget = (rawTarget) => {
6714
+ const target = safeString(rawTarget).trim();
6715
+ if (!target)
6716
+ return;
6717
+ if (commandName === "curl")
6718
+ explicitCurlTargets.push(target);
6719
+ else
6720
+ targets.push(target);
6721
+ };
6722
+ for (let index = commandIndex + 1; index < words.length; index += 1) {
6723
+ const word = words[index] ?? "";
6724
+ if (!word || isShellCommandTerminatorOrGroupClose(word))
6725
+ break;
6726
+ if (isEnvironmentAssignmentWord(word))
6727
+ continue;
6728
+ if (word === "--")
6729
+ continue;
6730
+ const inlineCurlOutput = commandName === "curl" ? word.match(/^--output=(.+)$/) : null;
6731
+ const inlineWgetOutput = commandName === "wget" ? word.match(/^--(?:output-document|output-file|append-output)=(.+)$/) : null;
6732
+ const inlineWgetDirectoryPrefix = commandName === "wget" ? word.match(/^--directory-prefix=(.+)$/) : null;
6733
+ const inlineCurlOutputDir = commandName === "curl" ? word.match(/^--output-dir=(.+)$/) : null;
6734
+ const inlineTarget = inlineCurlOutput?.[1] ?? inlineWgetOutput?.[1] ?? inlineWgetDirectoryPrefix?.[1];
6735
+ if (inlineTarget !== undefined) {
6736
+ sawOutputFlag = true;
6737
+ pushTarget(inlineTarget);
6738
+ continue;
6739
+ }
6740
+ if (inlineCurlOutputDir?.[1] !== undefined) {
6741
+ curlOutputDirs.push(inlineCurlOutputDir[1]);
6742
+ continue;
6743
+ }
6744
+ if (commandName === "curl" && isCurlRemoteNameWord(word)) {
6745
+ sawCurlRemoteName = true;
6746
+ continue;
6747
+ }
6748
+ if (commandName === "curl" && word.startsWith("-o") && word.length > 2) {
6749
+ sawOutputFlag = true;
6750
+ pushTarget(word.slice(2));
6751
+ continue;
6752
+ }
6753
+ if (commandName === "wget" && (word.startsWith("-O") || word.startsWith("-o") || word.startsWith("-a")) && word.length > 2) {
6754
+ sawOutputFlag = true;
6755
+ pushTarget(word.slice(2));
6756
+ continue;
6757
+ }
6758
+ const separateOutputFlag = (commandName === "curl" && (word === "-o" || word === "--output" || word === "--append-output"))
6759
+ || (commandName === "wget" && (word === "-O" || word === "-o" || word === "-a" || word === "--output-document" || word === "--output-file" || word === "--append-output" || word === "-P" || word === "--directory-prefix"));
6760
+ if (!separateOutputFlag) {
6761
+ if (commandName === "curl" && word === "--output-dir") {
6762
+ const nextWord = words[index + 1] ?? "";
6763
+ if (nextWord && !isShellCommandTerminatorOrGroupClose(nextWord)) {
6764
+ curlOutputDirs.push(nextWord);
6765
+ index += 1;
6766
+ }
6767
+ }
6768
+ continue;
6769
+ }
6770
+ sawOutputFlag = true;
6771
+ const nextWord = words[index + 1] ?? "";
6772
+ if (nextWord && !isShellCommandTerminatorOrGroupClose(nextWord)) {
6773
+ pushTarget(nextWord);
6774
+ index += 1;
6775
+ }
6776
+ }
6777
+ if (commandName === "curl" && sawCurlRemoteName) {
6778
+ sawOutputFlag = true;
6779
+ targets.push(...(curlOutputDirs.length > 0 ? curlOutputDirs : ["."]));
3083
6780
  }
3084
- if (beadsCommand.present) {
3085
- return "Beads tracker command also performs an implementation write outside allowed planning metadata";
6781
+ if (commandName === "curl" && explicitCurlTargets.length > 0) {
6782
+ const outputDir = curlOutputDirs[curlOutputDirs.length - 1];
6783
+ const effectiveTargets = outputDir
6784
+ ? explicitCurlTargets.map((target) => (isAbsolute(target) ? target : join(outputDir, target)))
6785
+ : explicitCurlTargets;
6786
+ targets.push(...effectiveTargets);
3086
6787
  }
3087
- return "Bash write intent did not identify an allowed planning artifact path or metadata path";
6788
+ return { sawOutputFlag, targets };
3088
6789
  }
3089
- async function buildRalplanPreToolUseBoundaryOutput(payload, cwd, stateDir, resolvedSessionId) {
3090
- const sessionId = safeString(resolvedSessionId ?? readPayloadSessionId(payload)).trim();
3091
- const threadId = readPayloadThreadId(payload);
3092
- const activeState = await readActiveRalplanStateForPreToolUse(cwd, stateDir, sessionId, threadId);
6790
+ function isConductorSedInPlaceOption(word) {
6791
+ return word === "-i" || word === "--in-place" || word.startsWith("-i") || word.startsWith("--in-place=") || /^-[^-]*i/.test(word);
6792
+ }
6793
+ function collectConductorSedTargets(words, commandIndex) {
6794
+ const targets = [];
6795
+ let sawInPlace = false;
6796
+ let sawExplicitScript = false;
6797
+ let consumedImplicitScript = false;
6798
+ for (let index = commandIndex + 1; index < words.length; index += 1) {
6799
+ const word = words[index] ?? "";
6800
+ if (!word || isShellCommandTerminatorOrGroupClose(word))
6801
+ break;
6802
+ if (isEnvironmentAssignmentWord(word))
6803
+ continue;
6804
+ if (word === "--") {
6805
+ consumedImplicitScript = true;
6806
+ continue;
6807
+ }
6808
+ if (isConductorSedInPlaceOption(word)) {
6809
+ sawInPlace = true;
6810
+ continue;
6811
+ }
6812
+ if (word === "-e" || word === "--expression" || word === "-f" || word === "--file") {
6813
+ sawExplicitScript = true;
6814
+ index += 1;
6815
+ continue;
6816
+ }
6817
+ if (word.startsWith("-e") || word.startsWith("--expression=") || word.startsWith("-f") || word.startsWith("--file=")) {
6818
+ sawExplicitScript = true;
6819
+ continue;
6820
+ }
6821
+ if (word.startsWith("-"))
6822
+ continue;
6823
+ if (!sawExplicitScript && !consumedImplicitScript) {
6824
+ consumedImplicitScript = true;
6825
+ continue;
6826
+ }
6827
+ targets.push(word);
6828
+ }
6829
+ return sawInPlace ? targets : null;
6830
+ }
6831
+ function collectConductorPerlTargets(words, commandIndex) {
6832
+ const targets = [];
6833
+ let sawInPlace = false;
6834
+ for (let index = commandIndex + 1; index < words.length; index += 1) {
6835
+ const word = words[index] ?? "";
6836
+ if (!word || isShellCommandTerminatorOrGroupClose(word))
6837
+ break;
6838
+ if (isEnvironmentAssignmentWord(word))
6839
+ continue;
6840
+ if (word === "--")
6841
+ continue;
6842
+ if (word === "-i" || word.startsWith("-i") || /^-[^-]*i/.test(word)) {
6843
+ sawInPlace = true;
6844
+ continue;
6845
+ }
6846
+ if (word === "-e") {
6847
+ index += 1;
6848
+ continue;
6849
+ }
6850
+ if (word.startsWith("-e") || /^-[^-]*e/.test(word))
6851
+ continue;
6852
+ if (word.startsWith("-"))
6853
+ continue;
6854
+ targets.push(word);
6855
+ }
6856
+ return sawInPlace ? targets : null;
6857
+ }
6858
+ const CONDUCTOR_XARGS_LONG_OPTIONS_WITH_REQUIRED_VALUES = new Set([
6859
+ "--arg-file",
6860
+ "--delimiter",
6861
+ "--max-args",
6862
+ "--max-chars",
6863
+ "--max-lines",
6864
+ "--max-procs",
6865
+ "--process-slot-var",
6866
+ ]);
6867
+ const CONDUCTOR_XARGS_SHORT_OPTIONS_WITH_REQUIRED_VALUES = new Set(["a", "d", "E", "I", "L", "n", "P", "s"]);
6868
+ const CONDUCTOR_XARGS_SHORT_OPTIONS_WITH_OPTIONAL_ATTACHED_VALUES = new Set(["e", "i", "l"]);
6869
+ function conductorXargsOptionValueWordCount(words, optionIndex) {
6870
+ const word = words[optionIndex] ?? "";
6871
+ if (!word.startsWith("-") || word === "-")
6872
+ return 0;
6873
+ if (word.startsWith("--")) {
6874
+ const [option, inlineValue] = word.split("=", 2);
6875
+ return CONDUCTOR_XARGS_LONG_OPTIONS_WITH_REQUIRED_VALUES.has(option) && inlineValue === undefined ? 1 : 0;
6876
+ }
6877
+ const shortOptions = word.slice(1);
6878
+ for (let offset = 0; offset < shortOptions.length; offset += 1) {
6879
+ const option = shortOptions[offset] ?? "";
6880
+ if (CONDUCTOR_XARGS_SHORT_OPTIONS_WITH_REQUIRED_VALUES.has(option)) {
6881
+ if (offset < shortOptions.length - 1)
6882
+ return 0;
6883
+ return option === "I" && (words[optionIndex + 1] ?? "") === "{" && (words[optionIndex + 2] ?? "") === "}" ? 2 : 1;
6884
+ }
6885
+ if (CONDUCTOR_XARGS_SHORT_OPTIONS_WITH_OPTIONAL_ATTACHED_VALUES.has(option))
6886
+ return 0;
6887
+ }
6888
+ return 0;
6889
+ }
6890
+ function collectConductorXargsMutationTargets(words, commandIndex) {
6891
+ for (let index = commandIndex + 1; index < words.length; index += 1) {
6892
+ const word = words[index] ?? "";
6893
+ if (!word || isShellCommandTerminatorOrGroupClose(word))
6894
+ break;
6895
+ if (word === "--")
6896
+ continue;
6897
+ if (word.startsWith("-")) {
6898
+ index += conductorXargsOptionValueWordCount(words, index);
6899
+ continue;
6900
+ }
6901
+ let commandWordIndex = index;
6902
+ for (let unwrapCount = 0; unwrapCount < 8; unwrapCount += 1) {
6903
+ const commandName = commandNameFromShellWord(words[commandWordIndex] ?? "");
6904
+ if (CONDUCTOR_BASH_DOWNLOADER_COMMANDS.has(commandName) || CONDUCTOR_BASH_MUTATION_COMMANDS.has(commandName))
6905
+ return [];
6906
+ const wrapperOperandIndex = findConductorWrapperOperandIndex(commandName, words, commandWordIndex + 1);
6907
+ if (wrapperOperandIndex === undefined)
6908
+ return null;
6909
+ if (wrapperOperandIndex === null)
6910
+ return [];
6911
+ commandWordIndex = wrapperOperandIndex;
6912
+ }
6913
+ return [];
6914
+ }
6915
+ return null;
6916
+ }
6917
+ function collectConductorMutationCommandTargets(commandName, words, commandIndex) {
6918
+ const targets = [];
6919
+ const positionalTargets = [];
6920
+ const targetDirectoryTargets = [];
6921
+ let sawTargetDirectory = false;
6922
+ let positionalCount = 0;
6923
+ let rsyncRemovesSourceFiles = false;
6924
+ if (commandName === "sed")
6925
+ return collectConductorSedTargets(words, commandIndex);
6926
+ if (commandName === "perl")
6927
+ return collectConductorPerlTargets(words, commandIndex);
6928
+ const installDirectoryMode = commandName === "install" && isConductorInstallDirectoryMode(words, commandIndex);
6929
+ for (let index = commandIndex + 1; index < words.length; index += 1) {
6930
+ const word = words[index] ?? "";
6931
+ if (!word || isShellCommandTerminatorOrGroupClose(word))
6932
+ break;
6933
+ if (CONDUCTOR_BASH_COMPOUND_SYNTAX_WORDS.has(word))
6934
+ continue;
6935
+ if (isEnvironmentAssignmentWord(word))
6936
+ continue;
6937
+ if (commandName === "dd") {
6938
+ const ofMatch = word.match(/^of=(.+)$/);
6939
+ if (ofMatch)
6940
+ targets.push(safeString(ofMatch[1]).trim());
6941
+ continue;
6942
+ }
6943
+ if (word === "--")
6944
+ continue;
6945
+ if (commandName === "rsync" && word === "--remove-source-files") {
6946
+ rsyncRemovesSourceFiles = true;
6947
+ continue;
6948
+ }
6949
+ if (word.startsWith("--")) {
6950
+ const [option, inlineValue] = word.split("=", 2);
6951
+ if (option === "--target-directory" && commandUsesTargetDirectoryOption(commandName)) {
6952
+ sawTargetDirectory = true;
6953
+ if (inlineValue !== undefined) {
6954
+ const target = safeString(inlineValue).trim();
6955
+ if (target)
6956
+ targetDirectoryTargets.push(target);
6957
+ }
6958
+ else {
6959
+ const target = words[index + 1] ?? "";
6960
+ if (target && !isShellCommandTerminatorOrGroupClose(target))
6961
+ targetDirectoryTargets.push(target);
6962
+ index += 1;
6963
+ }
6964
+ continue;
6965
+ }
6966
+ if (CONDUCTOR_BASH_OPTIONS_WITH_VALUES.has(option) && inlineValue === undefined)
6967
+ index += 1;
6968
+ if ((option === "--backup" || option === "--suffix" || option === "--reference") && inlineValue) {
6969
+ targets.push(inlineValue);
6970
+ }
6971
+ continue;
6972
+ }
6973
+ if (word === "-t" && commandUsesTargetDirectoryOption(commandName)) {
6974
+ sawTargetDirectory = true;
6975
+ const target = words[index + 1] ?? "";
6976
+ if (target && !isShellCommandTerminatorOrGroupClose(target))
6977
+ targetDirectoryTargets.push(target);
6978
+ index += 1;
6979
+ continue;
6980
+ }
6981
+ if (word.startsWith("-t") && word.length > 2 && commandUsesTargetDirectoryOption(commandName)) {
6982
+ sawTargetDirectory = true;
6983
+ targetDirectoryTargets.push(word.slice(2));
6984
+ continue;
6985
+ }
6986
+ if (word.startsWith("-") && word.length > 1) {
6987
+ if (CONDUCTOR_BASH_OPTIONS_WITH_VALUES.has(word))
6988
+ index += 1;
6989
+ continue;
6990
+ }
6991
+ positionalCount += 1;
6992
+ if ((commandName === "chmod" || commandName === "chown" || commandName === "chgrp")
6993
+ && positionalCount === 1) {
6994
+ continue;
6995
+ }
6996
+ positionalTargets.push(word);
6997
+ }
6998
+ if (installDirectoryMode)
6999
+ return [...targets, ...positionalTargets];
7000
+ if (sawTargetDirectory) {
7001
+ return commandName === "mv"
7002
+ ? [...targets, ...targetDirectoryTargets, ...positionalTargets]
7003
+ : [...targets, ...targetDirectoryTargets];
7004
+ }
7005
+ if (commandName === "rsync" && rsyncRemovesSourceFiles)
7006
+ return [...targets, ...positionalTargets];
7007
+ if (isConductorDestinationOnlyMutationCommand(commandName))
7008
+ return [...targets, ...positionalTargets.slice(-1)];
7009
+ return [...targets, ...positionalTargets];
7010
+ }
7011
+ function extractConductorBashMutations(command) {
7012
+ const mutations = [];
7013
+ for (const segment of splitShellCommandSegments(stripHeredocBodiesForCommandScan(command))) {
7014
+ const words = tokenizeShellWords(segment);
7015
+ let commandStart = true;
7016
+ for (let index = 0; index < words.length; index += 1) {
7017
+ const word = words[index] ?? "";
7018
+ if (!word)
7019
+ continue;
7020
+ if (isShellCommandSeparator(word)) {
7021
+ commandStart = true;
7022
+ continue;
7023
+ }
7024
+ if (isShellGroupingSyntaxWord(word)) {
7025
+ continue;
7026
+ }
7027
+ if (commandStart && isEnvironmentAssignmentWord(word))
7028
+ continue;
7029
+ if (commandStart && CONDUCTOR_BASH_COMPOUND_SYNTAX_WORDS.has(word))
7030
+ continue;
7031
+ if (commandStart && word.startsWith("-"))
7032
+ continue;
7033
+ if (!commandStart)
7034
+ continue;
7035
+ const commandName = commandNameFromShellWord(word);
7036
+ if (CONDUCTOR_BASH_TRANSPARENT_WRAPPERS.has(commandName)) {
7037
+ continue;
7038
+ }
7039
+ const wrapperOperandIndex = findConductorWrapperOperandIndex(commandName, words, index + 1);
7040
+ if (wrapperOperandIndex !== undefined) {
7041
+ if (wrapperOperandIndex === null) {
7042
+ mutations.push({ command: commandName, targets: [] });
7043
+ commandStart = false;
7044
+ }
7045
+ else {
7046
+ index = wrapperOperandIndex - 1;
7047
+ }
7048
+ continue;
7049
+ }
7050
+ if (CONDUCTOR_BASH_DOWNLOADER_COMMANDS.has(commandName)) {
7051
+ const downloaderTargets = collectConductorDownloaderOutputTargets(commandName, words, index);
7052
+ if (downloaderTargets.sawOutputFlag) {
7053
+ mutations.push({
7054
+ command: commandName,
7055
+ targets: downloaderTargets.targets,
7056
+ });
7057
+ }
7058
+ }
7059
+ else if (commandName === "xargs") {
7060
+ const xargsTargets = collectConductorXargsMutationTargets(words, index);
7061
+ if (xargsTargets !== null)
7062
+ mutations.push({ command: commandName, targets: xargsTargets });
7063
+ }
7064
+ else if (CONDUCTOR_BASH_MUTATION_COMMANDS.has(commandName)) {
7065
+ const mutationTargets = collectConductorMutationCommandTargets(commandName, words, index);
7066
+ if (mutationTargets !== null)
7067
+ mutations.push({ command: commandName, targets: mutationTargets });
7068
+ }
7069
+ commandStart = false;
7070
+ }
7071
+ }
7072
+ return mutations;
7073
+ }
7074
+ const CONDUCTOR_BASH_MAX_NESTING_DEPTH = 5;
7075
+ function evaluateConductorBashWrite(cwd, command, depth = 0) {
7076
+ const commandWithHeredocBodies = normalizeShellLineContinuations(command);
7077
+ const normalizedCommand = stripHeredocBodiesForCommandScan(commandWithHeredocBodies);
7078
+ if (depth > CONDUCTOR_BASH_MAX_NESTING_DEPTH) {
7079
+ return {
7080
+ allowed: false,
7081
+ blockedDetail: "Bash nested shell depth exceeded Main-root Conductor validation limits",
7082
+ };
7083
+ }
7084
+ if (hasDynamicNestedShellExecution(normalizedCommand)) {
7085
+ return {
7086
+ allowed: false,
7087
+ blockedDetail: "Bash nested shell execution is dynamic and cannot be validated for Main-root Conductor writes",
7088
+ };
7089
+ }
7090
+ if (hasUnsafeUnquotedHeredocExpansion(commandWithHeredocBodies)) {
7091
+ return {
7092
+ allowed: false,
7093
+ blockedDetail: "Bash unquoted heredoc expansion is not workflow state/ledger/mailbox/handoff metadata",
7094
+ };
7095
+ }
7096
+ const shellMutations = extractConductorBashMutations(normalizedCommand);
7097
+ if (shellMutations.length > 0) {
7098
+ for (const mutation of shellMutations) {
7099
+ if (mutation.targets.length === 0) {
7100
+ return {
7101
+ allowed: false,
7102
+ blockedDetail: `Bash ${mutation.command} mutation target <unresolved>; Main-root Conductor may write only workflow state/ledger/mailbox/handoff metadata`,
7103
+ };
7104
+ }
7105
+ const blockedTarget = mutation.targets.find((target) => !isAllowedConductorMetadataPath(cwd, target));
7106
+ if (blockedTarget) {
7107
+ return {
7108
+ allowed: false,
7109
+ blockedDetail: `Bash ${mutation.command} mutation target ${blockedTarget} is not workflow state/ledger/mailbox/handoff metadata`,
7110
+ };
7111
+ }
7112
+ }
7113
+ }
7114
+ for (const functionBody of extractInvokedShellFunctionBodiesForStateScan(normalizedCommand)) {
7115
+ const nestedDecision = evaluateConductorBashWrite(cwd, functionBody, depth + 1);
7116
+ if (!nestedDecision.allowed)
7117
+ return nestedDecision;
7118
+ }
7119
+ for (const nestedCommand of extractNestedShellCommandStringsForStateScan(normalizedCommand)) {
7120
+ const nestedDecision = evaluateConductorBashWrite(cwd, nestedCommand, depth + 1);
7121
+ if (!nestedDecision.allowed)
7122
+ return nestedDecision;
7123
+ }
7124
+ for (const nestedCommand of extractNestedCommandSubstitutionStringsForStateScan(normalizedCommand)) {
7125
+ const nestedDecision = evaluateConductorBashWrite(cwd, nestedCommand, depth + 1);
7126
+ if (!nestedDecision.allowed)
7127
+ return nestedDecision;
7128
+ }
7129
+ for (const nestedCommand of extractNestedProcessSubstitutionStringsForStateScan(normalizedCommand)) {
7130
+ const nestedDecision = evaluateConductorBashWrite(cwd, nestedCommand, depth + 1);
7131
+ if (!nestedDecision.allowed)
7132
+ return nestedDecision;
7133
+ }
7134
+ const editorTargets = extractConductorEditorWriteTargets(commandWithHeredocBodies);
7135
+ if (editorTargets.length > 0) {
7136
+ const blockedTarget = editorTargets.find((target) => !isAllowedConductorMetadataPath(cwd, target));
7137
+ if (blockedTarget) {
7138
+ return {
7139
+ allowed: false,
7140
+ blockedDetail: `Bash editor mutation target ${blockedTarget} is not workflow state/ledger/mailbox/handoff metadata`,
7141
+ };
7142
+ }
7143
+ }
7144
+ if (commandHasDestructiveGitSubcommand(normalizedCommand)) {
7145
+ return {
7146
+ allowed: false,
7147
+ blockedDetail: "Bash git worktree mutation is not workflow state/ledger/mailbox/handoff metadata",
7148
+ };
7149
+ }
7150
+ if (commandHasPackageInstallIntent(normalizedCommand)) {
7151
+ return {
7152
+ allowed: false,
7153
+ blockedDetail: "Bash package manager install is not workflow state/ledger/mailbox/handoff metadata",
7154
+ };
7155
+ }
7156
+ const interpreterWrites = extractConductorInterpreterWrites(commandWithHeredocBodies);
7157
+ for (const write of interpreterWrites) {
7158
+ if (write.unresolved || write.targets.length === 0) {
7159
+ return {
7160
+ allowed: false,
7161
+ blockedDetail: `Bash ${write.runtime} write target <unresolved>; Main-root Conductor may write only workflow state/ledger/mailbox/handoff metadata`,
7162
+ };
7163
+ }
7164
+ const blockedTarget = write.targets.find((target) => !isAllowedConductorMetadataPath(cwd, target));
7165
+ if (blockedTarget) {
7166
+ return {
7167
+ allowed: false,
7168
+ blockedDetail: `Bash ${write.runtime} write target ${blockedTarget} is not workflow state/ledger/mailbox/handoff metadata`,
7169
+ };
7170
+ }
7171
+ }
7172
+ const hasGenericWriteIntent = commandHasDeepInterviewWriteIntent(commandWithHeredocBodies);
7173
+ if (!hasGenericWriteIntent)
7174
+ return { allowed: true };
7175
+ const targets = extractDeepInterviewCommandWriteTargets(commandWithHeredocBodies);
7176
+ const accountedShellOnlyWriteIntent = shellMutations.length > 0
7177
+ && targets.length === 0
7178
+ && shellMutations.every((mutation) => mutation.command === "sed" || mutation.command === "perl");
7179
+ if (accountedShellOnlyWriteIntent)
7180
+ return { allowed: true };
7181
+ if (commandInvokesApplyPatch(normalizedCommand) && targets.length === 0) {
7182
+ return {
7183
+ allowed: false,
7184
+ blockedDetail: "apply_patch target extraction failed for Main-root Conductor write",
7185
+ };
7186
+ }
7187
+ if (targets.length === 0) {
7188
+ return {
7189
+ allowed: false,
7190
+ blockedDetail: "Bash write intent target <unresolved>; Main-root Conductor may write only workflow state/ledger/mailbox/handoff metadata",
7191
+ };
7192
+ }
7193
+ const blockedTarget = targets.find((target) => !isAllowedConductorMetadataPath(cwd, target));
7194
+ if (blockedTarget) {
7195
+ const operationClass = /\btee\s+(?:-a\s+)?/.test(commandWithHeredocBodies) ? "Bash tee write" : "Bash write";
7196
+ return {
7197
+ allowed: false,
7198
+ blockedDetail: `${operationClass} target ${blockedTarget} is not workflow state/ledger/mailbox/handoff metadata`,
7199
+ };
7200
+ }
7201
+ return { allowed: true };
7202
+ }
7203
+ function isAllowedConductorBashWrite(cwd, command) {
7204
+ return evaluateConductorBashWrite(cwd, command).allowed;
7205
+ }
7206
+ function buildConductorBashBlockedDetail(cwd, command) {
7207
+ return evaluateConductorBashWrite(cwd, command).blockedDetail
7208
+ ?? "Bash write intent target <unresolved>; Main-root Conductor may write only workflow state/ledger/mailbox/handoff metadata";
7209
+ }
7210
+ export async function buildConductorPreToolUseWriteGuardOutput(payload, cwd, stateDir, resolvedSessionId) {
7211
+ const activeState = await readActiveMainRootConductorStateForPreToolUse(payload, cwd, stateDir, resolvedSessionId);
3093
7212
  if (!activeState)
3094
7213
  return null;
3095
7214
  const toolName = safeString(payload.tool_name).trim();
3096
7215
  const command = readPreToolUseCommand(payload);
3097
7216
  const pathCandidates = readPreToolUsePathCandidates(payload);
3098
7217
  let blocked = false;
3099
- let blockedDetail = "implementation/write tools are blocked until an explicit execution handoff workflow is activated";
7218
+ let blockedDetail = "Main-root Conductor write is not delegated";
3100
7219
  if (toolName === "Bash") {
3101
- blocked = !isAllowedRalplanBashWrite(cwd, command);
3102
- if (blocked) {
3103
- blockedDetail = buildRalplanBashBlockedDetail(cwd, command);
3104
- }
7220
+ blocked = !isAllowedConductorBashWrite(cwd, command);
7221
+ if (blocked)
7222
+ blockedDetail = buildConductorBashBlockedDetail(cwd, command);
3105
7223
  }
3106
7224
  else if (PLANNING_MODE_IMPLEMENTATION_TOOL_NAMES.has(toolName)) {
3107
7225
  const toolPathCandidates = collectImplementationToolPathCandidates(payload, toolName, pathCandidates);
3108
7226
  if (toolPathCandidates.length === 0) {
3109
7227
  blocked = true;
3110
- blockedDetail = describeImplementationToolBlock(toolName, undefined, toolPathCandidates.length);
7228
+ blockedDetail = describeConductorBlockedWrite(toolName, undefined, toolPathCandidates.length);
3111
7229
  }
3112
7230
  else {
3113
- const blockedPath = toolPathCandidates.find((candidate) => !isAllowedRalplanArtifactPath(cwd, candidate));
7231
+ const blockedPath = toolPathCandidates.find((candidate) => !isAllowedConductorMetadataPath(cwd, candidate));
3114
7232
  blocked = blockedPath !== undefined;
3115
7233
  if (blockedPath !== undefined) {
3116
- blockedDetail = describeImplementationToolBlock(toolName, blockedPath, toolPathCandidates.length);
7234
+ blockedDetail = describeConductorBlockedWrite(toolName, blockedPath, toolPathCandidates.length);
3117
7235
  }
3118
7236
  }
3119
7237
  }
3120
7238
  if (!blocked)
3121
7239
  return null;
3122
- const phase = formatPhase(activeState.current_phase ?? activeState.currentPhase, "planning");
3123
- const activeMode = safeString(activeState.mode).trim().toLowerCase();
3124
- const planningModeLabel = activeMode === "autopilot" ? "Autopilot planning" : "Ralplan";
3125
- const planningModeDescription = activeMode === "autopilot"
3126
- ? "Autopilot is supervising a planning phase"
3127
- : "Ralplan is consensus-planning mode";
3128
7240
  return {
3129
7241
  decision: "block",
3130
- reason: `${planningModeLabel} is active (phase: ${phase}); implementation/write tools are blocked until an explicit execution handoff workflow is activated; ${blockedDetail}.`,
7242
+ 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}.`,
3131
7243
  hookSpecificOutput: {
3132
7244
  hookEventName: "PreToolUse",
3133
- additionalContext: `${planningModeDescription}. `
3134
- + "Write only planning artifacts under `.omx/context/`, `.omx/plans/`, `.omx/specs/`, required `.omx/state/` files, or tracker metadata under `.beads/`. "
3135
- + "Do not edit implementation files or run implementation-focused writes from planning phases. "
3136
- + `To execute, first process an explicit handoff such as ${formatExecutionHandoffList(cwd)}, which must emit terminal planning state before implementation begins.`,
7245
+ additionalContext: `${LEADER_CONDUCTOR_GOLDEN_RULE} `
7246
+ + "Use specialized agents for source edits and plan/spec authorship. "
7247
+ + `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. `
7248
+ + "Autopilot rework and typed subagent/worker lanes are exempt from this guard.",
3137
7249
  },
3138
7250
  };
3139
7251
  }
3140
- async function buildDeepInterviewPreToolUseBoundaryOutput(payload, cwd, stateDir, resolvedSessionId) {
3141
- const sessionId = safeString(resolvedSessionId ?? readPayloadSessionId(payload)).trim();
3142
- const threadId = readPayloadThreadId(payload);
3143
- const activeState = await readActiveDeepInterviewStateForPreToolUse(cwd, stateDir, sessionId, threadId);
3144
- if (!activeState)
3145
- return null;
3146
- const toolName = safeString(payload.tool_name).trim();
3147
- const command = readPreToolUseCommand(payload);
3148
- const pathCandidates = readPreToolUsePathCandidates(payload);
3149
- let blocked = false;
3150
- if (toolName === "Bash") {
3151
- blocked = !isAllowedDeepInterviewBashWrite(cwd, command);
7252
+ function isInPlaceEditorCommand(word, commandName) {
7253
+ if (commandName === "sed")
7254
+ return word === "--in-place" || /^--in-place(?:=.+)?$/.test(word) || /^-[^-\s]*i(?:.*)?$/.test(word);
7255
+ if (commandName === "perl")
7256
+ return word === "-i" || word === "-pi" || /^-pi(?:\..+)?$/.test(word) || /^-p.*i(?:\..+)?$/.test(word);
7257
+ return false;
7258
+ }
7259
+ function collectInPlaceEditorWriteTargets(commandName, words, commandIndex) {
7260
+ const targets = [];
7261
+ let sawInPlaceEdit = false;
7262
+ let awaitingOptionValue = false;
7263
+ let consumedImplicitSedScript = commandName !== "sed";
7264
+ for (let index = commandIndex + 1; index < words.length; index += 1) {
7265
+ const word = words[index] ?? "";
7266
+ if (!word || isShellCommandSeparator(word))
7267
+ break;
7268
+ if (CONDUCTOR_BASH_COMPOUND_SYNTAX_WORDS.has(word))
7269
+ continue;
7270
+ if (isEnvironmentAssignmentWord(word))
7271
+ continue;
7272
+ if (awaitingOptionValue) {
7273
+ awaitingOptionValue = false;
7274
+ continue;
7275
+ }
7276
+ if (word === "--") {
7277
+ consumedImplicitSedScript = true;
7278
+ continue;
7279
+ }
7280
+ if (word === "-e" || word === "-f" || word === "--expression" || word === "--file") {
7281
+ awaitingOptionValue = true;
7282
+ consumedImplicitSedScript = true;
7283
+ continue;
7284
+ }
7285
+ if (word.startsWith("-e") || word.startsWith("-f") || word.startsWith("--expression=") || word.startsWith("--file=")) {
7286
+ consumedImplicitSedScript = true;
7287
+ continue;
7288
+ }
7289
+ if (isInPlaceEditorCommand(word, commandName)) {
7290
+ sawInPlaceEdit = true;
7291
+ continue;
7292
+ }
7293
+ if (word.startsWith("-"))
7294
+ continue;
7295
+ if (sawInPlaceEdit) {
7296
+ if (commandName === "sed" && !consumedImplicitSedScript) {
7297
+ consumedImplicitSedScript = true;
7298
+ continue;
7299
+ }
7300
+ targets.push(word);
7301
+ }
3152
7302
  }
3153
- else if (DEEP_INTERVIEW_IMPLEMENTATION_TOOL_NAMES.has(toolName)) {
3154
- const candidates = collectImplementationToolPathCandidates(payload, toolName, pathCandidates);
3155
- blocked = candidates.length === 0
3156
- || !candidates.every((candidate) => isAllowedDeepInterviewArtifactPath(cwd, candidate));
7303
+ return targets;
7304
+ }
7305
+ function extractConductorEditorWriteTargets(command) {
7306
+ const targets = [];
7307
+ for (const segment of splitShellCommandSegments(stripHeredocBodiesForCommandScan(command))) {
7308
+ const words = tokenizeShellWords(segment);
7309
+ let commandStart = true;
7310
+ for (let index = 0; index < words.length; index += 1) {
7311
+ const word = words[index] ?? "";
7312
+ if (!word)
7313
+ continue;
7314
+ if (isShellCommandSeparator(word)) {
7315
+ commandStart = true;
7316
+ continue;
7317
+ }
7318
+ if (isShellGroupingSyntaxWord(word))
7319
+ continue;
7320
+ if (commandStart && isEnvironmentAssignmentWord(word))
7321
+ continue;
7322
+ if (commandStart && CONDUCTOR_BASH_COMPOUND_SYNTAX_WORDS.has(word))
7323
+ continue;
7324
+ if (commandStart && word.startsWith("-"))
7325
+ continue;
7326
+ if (!commandStart)
7327
+ continue;
7328
+ const commandName = commandNameFromShellWord(word);
7329
+ if (commandName === "sed" || commandName === "perl") {
7330
+ targets.push(...collectInPlaceEditorWriteTargets(commandName, words, index));
7331
+ }
7332
+ commandStart = false;
7333
+ }
3157
7334
  }
3158
- if (!blocked)
3159
- return null;
3160
- const phase = formatPhase(activeState.current_phase ?? activeState.currentPhase, "planning");
3161
- return {
3162
- decision: "block",
3163
- reason: `Deep-interview is active (phase: ${phase}); implementation/write tools are blocked until an explicit handoff workflow is activated.`,
3164
- hookSpecificOutput: {
3165
- hookEventName: "PreToolUse",
3166
- additionalContext: `Deep-interview is requirements/spec mode. Treat detailed user answers as interview/spec material, not implicit implementation authorization. You may write only deep-interview artifacts under \`.omx/context/\`, \`.omx/interviews/\`, \`.omx/specs/\`, or required \`.omx/state/\` files. To implement, first ask for or process an explicit transition such as \`$ralplan\`, \`$autopilot\`, ${formatExecutionHandoffList(cwd)}.`,
3167
- },
3168
- };
7335
+ return targets;
3169
7336
  }
3170
7337
  function matchesSkillStopContext(entry, state, sessionId, threadId) {
3171
7338
  const entrySessionId = safeString(entry.session_id ?? state.session_id).trim();
@@ -4102,8 +8269,9 @@ export async function dispatchCodexNativeHook(payload, options = {}) {
4102
8269
  const eventSessionId = canonicalSessionId || nativeSessionId || undefined;
4103
8270
  const sessionIdForState = canonicalSessionId || nativeSessionId;
4104
8271
  let outputJson = null;
8272
+ const typedAgentRolePayload = isTypedAgentRolePayload(payload);
4105
8273
  const isSubagentPromptSubmit = hookEventName === "UserPromptSubmit"
4106
- ? await isNativeSubagentHook(cwd, canonicalSessionId, nativeSessionId, threadId, safeString(currentSessionState?.native_session_id).trim())
8274
+ ? typedAgentRolePayload || await isNativeSubagentHook(cwd, canonicalSessionId, nativeSessionId, threadId, safeString(currentSessionState?.native_session_id).trim())
4107
8275
  : false;
4108
8276
  const isSubagentStop = hookEventName === "Stop"
4109
8277
  ? (await Promise.all([...new Set([
@@ -4272,12 +8440,16 @@ export async function dispatchCodexNativeHook(payload, options = {}) {
4272
8440
  }
4273
8441
  else if (hookEventName === "PreToolUse") {
4274
8442
  const payloadSessionId = readPayloadSessionId(payload);
8443
+ const rootPointerConflict = await readLiveRootSessionPointerConflict(stateDir, payloadSessionId);
4275
8444
  const preToolUseSessionId = payloadSessionId
4276
8445
  ? await resolveInternalSessionIdForPayload(cwd, payloadSessionId, stateDir)
4277
8446
  : "";
4278
8447
  outputJson = await buildDeepInterviewPreToolUseBoundaryOutput(payload, cwd, stateDir, preToolUseSessionId)
4279
8448
  ?? await buildRalplanPreToolUseBoundaryOutput(payload, cwd, stateDir, preToolUseSessionId)
8449
+ ?? await buildPlanningRootPointerConflictPreToolUseOutput(payload, cwd, stateDir, rootPointerConflict)
8450
+ ?? await buildConductorPreToolUseWriteGuardOutput(payload, cwd, stateDir, preToolUseSessionId)
4280
8451
  ?? await buildNativeSubagentCapacityCloseGuardOutput(payload, cwd, stateDir)
8452
+ ?? buildMalformedPreToolUseBlockTestOutput(payload)
4281
8453
  ?? buildNativePreToolUseOutput(payload);
4282
8454
  }
4283
8455
  else if (hookEventName === "PostToolUse") {
@@ -4425,13 +8597,16 @@ async function buildOversizedStopActiveWorkflowOutput(cwd) {
4425
8597
  systemMessage: "OMX native Stop rejected oversized stdin before parsing; active current-session workflow state is present, so Stop is blocked instead of silently allowing termination.",
4426
8598
  };
4427
8599
  }
8600
+ function buildOversizedStopInactiveWorkflowOutput() {
8601
+ return {};
8602
+ }
4428
8603
  async function buildOversizedStdinHookOutput(rawHookEventName, cwd) {
4429
8604
  const systemMessage = `OMX native hook rejected oversized stdin JSON before parsing; maxBytes=${MAX_NATIVE_STDIN_JSON_BYTES}.`;
4430
8605
  if (rawHookEventName === "PreToolUse") {
4431
8606
  return { systemMessage };
4432
8607
  }
4433
8608
  if (rawHookEventName === "Stop") {
4434
- return await buildOversizedStopActiveWorkflowOutput(cwd) ?? {};
8609
+ return await buildOversizedStopActiveWorkflowOutput(cwd) ?? buildOversizedStopInactiveWorkflowOutput();
4435
8610
  }
4436
8611
  return {
4437
8612
  continue: false,
@@ -4482,6 +8657,28 @@ function isDispatchFailureTestTrigger() {
4482
8657
  return process.env.NODE_ENV === "test"
4483
8658
  && process.env.OMX_NATIVE_HOOK_TEST_THROW_DISPATCH === "1";
4484
8659
  }
8660
+ function buildMalformedPreToolUseBlockTestOutput(payload) {
8661
+ if (process.env.NODE_ENV !== "test" || readHookEventName(payload) !== "PreToolUse")
8662
+ return null;
8663
+ switch (process.env.OMX_NATIVE_HOOK_TEST_MALFORMED_PRETOOL_BLOCK) {
8664
+ case "legacy":
8665
+ return {
8666
+ decision: "block",
8667
+ systemMessage: "This advisory text must not validate a malformed legacy PreToolUse block.",
8668
+ };
8669
+ case "deny":
8670
+ return {
8671
+ hookSpecificOutput: {
8672
+ hookEventName: "PreToolUse",
8673
+ permissionDecision: "deny",
8674
+ permissionDecisionReason: " ",
8675
+ },
8676
+ systemMessage: "This advisory text must not validate a malformed deny PreToolUse block.",
8677
+ };
8678
+ default:
8679
+ return null;
8680
+ }
8681
+ }
4485
8682
  function buildStopDispatchFailureOutput(error) {
4486
8683
  const detail = error instanceof Error ? error.message : String(error);
4487
8684
  const reason = "OMX native Stop hook failed before normal continuation handling. Continue once more, preserve runtime state, inspect the hook logs, and retry with a valid Stop JSON response.";