oh-my-codex 0.18.5 → 0.18.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +6 -6
- package/Cargo.toml +1 -1
- package/README.md +56 -7
- package/dist/agents/__tests__/definitions.test.js +11 -0
- package/dist/agents/__tests__/definitions.test.js.map +1 -1
- package/dist/agents/__tests__/native-config.test.js +14 -5
- package/dist/agents/__tests__/native-config.test.js.map +1 -1
- package/dist/agents/definitions.d.ts +2 -0
- package/dist/agents/definitions.d.ts.map +1 -1
- package/dist/agents/definitions.js +4 -1
- package/dist/agents/definitions.js.map +1 -1
- package/dist/agents/native-config.js +2 -2
- package/dist/agents/native-config.js.map +1 -1
- package/dist/autopilot/__tests__/fsm.test.d.ts +2 -0
- package/dist/autopilot/__tests__/fsm.test.d.ts.map +1 -0
- package/dist/autopilot/__tests__/fsm.test.js +75 -0
- package/dist/autopilot/__tests__/fsm.test.js.map +1 -0
- package/dist/autopilot/__tests__/ralplan-gate.test.d.ts +2 -0
- package/dist/autopilot/__tests__/ralplan-gate.test.d.ts.map +1 -0
- package/dist/autopilot/__tests__/ralplan-gate.test.js +79 -0
- package/dist/autopilot/__tests__/ralplan-gate.test.js.map +1 -0
- package/dist/autopilot/deep-interview-gate.d.ts +18 -0
- package/dist/autopilot/deep-interview-gate.d.ts.map +1 -0
- package/dist/autopilot/deep-interview-gate.js +256 -0
- package/dist/autopilot/deep-interview-gate.js.map +1 -0
- package/dist/autopilot/fsm.d.ts +13 -0
- package/dist/autopilot/fsm.d.ts.map +1 -0
- package/dist/autopilot/fsm.js +70 -0
- package/dist/autopilot/fsm.js.map +1 -0
- package/dist/autopilot/ralplan-gate.d.ts +17 -0
- package/dist/autopilot/ralplan-gate.d.ts.map +1 -0
- package/dist/autopilot/ralplan-gate.js +61 -0
- package/dist/autopilot/ralplan-gate.js.map +1 -0
- package/dist/cli/__tests__/index.test.js +24 -4
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/launch-fallback.test.js +175 -6
- package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
- package/dist/cli/__tests__/question.test.js +100 -0
- package/dist/cli/__tests__/question.test.js.map +1 -1
- package/dist/cli/__tests__/setup-refresh.test.js +18 -0
- package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
- package/dist/cli/__tests__/team.test.js +2 -2
- package/dist/cli/__tests__/team.test.js.map +1 -1
- package/dist/cli/index.d.ts +3 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +191 -36
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/question.d.ts.map +1 -1
- package/dist/cli/question.js +36 -5
- package/dist/cli/question.js.map +1 -1
- package/dist/config/__tests__/deep-interview.test.js +7 -6
- package/dist/config/__tests__/deep-interview.test.js.map +1 -1
- package/dist/config/deep-interview.d.ts.map +1 -1
- package/dist/config/deep-interview.js +14 -4
- package/dist/config/deep-interview.js.map +1 -1
- package/dist/hooks/__tests__/autopilot-skill-contract.test.js +8 -0
- package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/deep-interview-contract.test.js +10 -0
- package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/keyword-detector.test.js +649 -11
- package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js +63 -0
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
- package/dist/hooks/__tests__/session.test.js +25 -0
- package/dist/hooks/__tests__/session.test.js.map +1 -1
- package/dist/hooks/deep-interview-config-instruction.js +1 -1
- package/dist/hooks/deep-interview-config-instruction.js.map +1 -1
- package/dist/hooks/keyword-detector.d.ts +1 -0
- package/dist/hooks/keyword-detector.d.ts.map +1 -1
- package/dist/hooks/keyword-detector.js +171 -21
- package/dist/hooks/keyword-detector.js.map +1 -1
- package/dist/hooks/keyword-registry.d.ts.map +1 -1
- package/dist/hooks/keyword-registry.js +1 -0
- package/dist/hooks/keyword-registry.js.map +1 -1
- package/dist/hooks/session.d.ts +2 -0
- package/dist/hooks/session.d.ts.map +1 -1
- package/dist/hooks/session.js +13 -5
- package/dist/hooks/session.js.map +1 -1
- package/dist/hud/__tests__/authority.test.js +35 -0
- package/dist/hud/__tests__/authority.test.js.map +1 -1
- package/dist/hud/__tests__/index.test.js +320 -3
- package/dist/hud/__tests__/index.test.js.map +1 -1
- package/dist/hud/__tests__/reconcile.test.js +117 -14
- package/dist/hud/__tests__/reconcile.test.js.map +1 -1
- package/dist/hud/__tests__/render.test.js +117 -8
- package/dist/hud/__tests__/render.test.js.map +1 -1
- package/dist/hud/__tests__/state.test.js +80 -0
- package/dist/hud/__tests__/state.test.js.map +1 -1
- package/dist/hud/__tests__/tmux.test.js +134 -1
- package/dist/hud/__tests__/tmux.test.js.map +1 -1
- package/dist/hud/authority.d.ts.map +1 -1
- package/dist/hud/authority.js +13 -2
- package/dist/hud/authority.js.map +1 -1
- package/dist/hud/colors.d.ts +1 -0
- package/dist/hud/colors.d.ts.map +1 -1
- package/dist/hud/colors.js +4 -0
- package/dist/hud/colors.js.map +1 -1
- package/dist/hud/constants.d.ts +3 -2
- package/dist/hud/constants.d.ts.map +1 -1
- package/dist/hud/constants.js +3 -2
- package/dist/hud/constants.js.map +1 -1
- package/dist/hud/index.d.ts +20 -1
- package/dist/hud/index.d.ts.map +1 -1
- package/dist/hud/index.js +99 -18
- package/dist/hud/index.js.map +1 -1
- package/dist/hud/reconcile.d.ts +2 -1
- package/dist/hud/reconcile.d.ts.map +1 -1
- package/dist/hud/reconcile.js +6 -3
- package/dist/hud/reconcile.js.map +1 -1
- package/dist/hud/render.d.ts +1 -0
- package/dist/hud/render.d.ts.map +1 -1
- package/dist/hud/render.js +69 -17
- package/dist/hud/render.js.map +1 -1
- package/dist/hud/state.d.ts.map +1 -1
- package/dist/hud/state.js +16 -1
- package/dist/hud/state.js.map +1 -1
- package/dist/hud/tmux.d.ts +2 -0
- package/dist/hud/tmux.d.ts.map +1 -1
- package/dist/hud/tmux.js +39 -2
- package/dist/hud/tmux.js.map +1 -1
- package/dist/mcp/__tests__/hermes-bridge.test.js +203 -7
- package/dist/mcp/__tests__/hermes-bridge.test.js.map +1 -1
- package/dist/mcp/__tests__/state-server.test.js +13 -1
- package/dist/mcp/__tests__/state-server.test.js.map +1 -1
- package/dist/mcp/hermes-bridge.d.ts +12 -2
- package/dist/mcp/hermes-bridge.d.ts.map +1 -1
- package/dist/mcp/hermes-bridge.js +83 -9
- package/dist/mcp/hermes-bridge.js.map +1 -1
- package/dist/modes/__tests__/base-autoresearch-contract.test.js +7 -1
- package/dist/modes/__tests__/base-autoresearch-contract.test.js.map +1 -1
- package/dist/pipeline/__tests__/stages.test.js +130 -0
- package/dist/pipeline/__tests__/stages.test.js.map +1 -1
- package/dist/pipeline/orchestrator.js +1 -1
- package/dist/pipeline/orchestrator.js.map +1 -1
- package/dist/pipeline/stages/ralplan.d.ts +1 -0
- package/dist/pipeline/stages/ralplan.d.ts.map +1 -1
- package/dist/pipeline/stages/ralplan.js +14 -5
- package/dist/pipeline/stages/ralplan.js.map +1 -1
- package/dist/question/__tests__/deep-interview.test.js +160 -2
- package/dist/question/__tests__/deep-interview.test.js.map +1 -1
- package/dist/question/__tests__/policy.test.js +63 -3
- package/dist/question/__tests__/policy.test.js.map +1 -1
- package/dist/question/__tests__/renderer.test.js +191 -2
- package/dist/question/__tests__/renderer.test.js.map +1 -1
- package/dist/question/__tests__/state.test.js +94 -3
- package/dist/question/__tests__/state.test.js.map +1 -1
- package/dist/question/__tests__/ui.test.js +4 -0
- package/dist/question/__tests__/ui.test.js.map +1 -1
- package/dist/question/autopilot-wait.d.ts +12 -2
- package/dist/question/autopilot-wait.d.ts.map +1 -1
- package/dist/question/autopilot-wait.js +158 -47
- package/dist/question/autopilot-wait.js.map +1 -1
- package/dist/question/deep-interview.d.ts.map +1 -1
- package/dist/question/deep-interview.js +22 -6
- package/dist/question/deep-interview.js.map +1 -1
- package/dist/question/policy.d.ts.map +1 -1
- package/dist/question/policy.js +2 -5
- package/dist/question/policy.js.map +1 -1
- package/dist/question/renderer.d.ts +12 -0
- package/dist/question/renderer.d.ts.map +1 -1
- package/dist/question/renderer.js +87 -3
- package/dist/question/renderer.js.map +1 -1
- package/dist/question/state.d.ts +8 -1
- package/dist/question/state.d.ts.map +1 -1
- package/dist/question/state.js +54 -14
- package/dist/question/state.js.map +1 -1
- package/dist/question/types.d.ts +1 -1
- package/dist/question/types.d.ts.map +1 -1
- package/dist/question/ui.d.ts +1 -0
- package/dist/question/ui.d.ts.map +1 -1
- package/dist/question/ui.js +1 -0
- package/dist/question/ui.js.map +1 -1
- package/dist/ralplan/__tests__/runtime.test.js +191 -0
- package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
- package/dist/ralplan/consensus-gate.d.ts +9 -1
- package/dist/ralplan/consensus-gate.d.ts.map +1 -1
- package/dist/ralplan/consensus-gate.js +84 -2
- package/dist/ralplan/consensus-gate.js.map +1 -1
- package/dist/ralplan/runtime.d.ts +9 -0
- package/dist/ralplan/runtime.d.ts.map +1 -1
- package/dist/ralplan/runtime.js +32 -11
- package/dist/ralplan/runtime.js.map +1 -1
- package/dist/scripts/__tests__/codex-native-hook.test.js +1487 -34
- package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
- package/dist/scripts/check-version-sync.js +8 -4
- package/dist/scripts/check-version-sync.js.map +1 -1
- package/dist/scripts/codex-native-hook.d.ts.map +1 -1
- package/dist/scripts/codex-native-hook.js +356 -38
- package/dist/scripts/codex-native-hook.js.map +1 -1
- package/dist/scripts/codex-native-pre-post.d.ts.map +1 -1
- package/dist/scripts/codex-native-pre-post.js +79 -1
- package/dist/scripts/codex-native-pre-post.js.map +1 -1
- package/dist/scripts/hook-payload-guard.d.ts +9 -0
- package/dist/scripts/hook-payload-guard.d.ts.map +1 -0
- package/dist/scripts/hook-payload-guard.js +111 -0
- package/dist/scripts/hook-payload-guard.js.map +1 -0
- package/dist/scripts/notify-fallback-watcher.js +8 -1
- package/dist/scripts/notify-fallback-watcher.js.map +1 -1
- package/dist/scripts/notify-hook/__tests__/payload-guard.test.d.ts +2 -0
- package/dist/scripts/notify-hook/__tests__/payload-guard.test.d.ts.map +1 -0
- package/dist/scripts/notify-hook/__tests__/payload-guard.test.js +39 -0
- package/dist/scripts/notify-hook/__tests__/payload-guard.test.js.map +1 -0
- package/dist/scripts/notify-hook/team-worker-stop.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-worker-stop.js +234 -86
- package/dist/scripts/notify-hook/team-worker-stop.js.map +1 -1
- package/dist/scripts/notify-hook.js +11 -2
- package/dist/scripts/notify-hook.js.map +1 -1
- package/dist/state/__tests__/operations.test.js +1012 -1
- package/dist/state/__tests__/operations.test.js.map +1 -1
- package/dist/state/__tests__/skill-active.test.js +59 -1
- package/dist/state/__tests__/skill-active.test.js.map +1 -1
- package/dist/state/__tests__/workflow-transition.test.js +73 -7
- package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
- package/dist/state/operations.d.ts.map +1 -1
- package/dist/state/operations.js +102 -0
- package/dist/state/operations.js.map +1 -1
- package/dist/state/skill-active.d.ts.map +1 -1
- package/dist/state/skill-active.js +33 -3
- package/dist/state/skill-active.js.map +1 -1
- package/dist/state/workflow-transition-reconcile.d.ts +6 -0
- package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
- package/dist/state/workflow-transition-reconcile.js +28 -1
- package/dist/state/workflow-transition-reconcile.js.map +1 -1
- package/dist/state/workflow-transition.d.ts.map +1 -1
- package/dist/state/workflow-transition.js +10 -3
- package/dist/state/workflow-transition.js.map +1 -1
- package/dist/subagents/__tests__/tracker.test.js +139 -0
- package/dist/subagents/__tests__/tracker.test.js.map +1 -1
- package/dist/subagents/tracker.d.ts +3 -0
- package/dist/subagents/tracker.d.ts.map +1 -1
- package/dist/subagents/tracker.js +41 -4
- package/dist/subagents/tracker.js.map +1 -1
- package/dist/team/__tests__/coordination-protocol.test.d.ts +2 -0
- package/dist/team/__tests__/coordination-protocol.test.d.ts.map +1 -0
- package/dist/team/__tests__/coordination-protocol.test.js +173 -0
- package/dist/team/__tests__/coordination-protocol.test.js.map +1 -0
- package/dist/team/__tests__/runtime.test.js +51 -2
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/state.test.js +83 -0
- package/dist/team/__tests__/state.test.js.map +1 -1
- package/dist/team/__tests__/tmux-session.test.js +45 -0
- package/dist/team/__tests__/tmux-session.test.js.map +1 -1
- package/dist/team/__tests__/worker-bootstrap.test.js +84 -0
- package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
- package/dist/team/coordination-protocol.d.ts +14 -0
- package/dist/team/coordination-protocol.d.ts.map +1 -0
- package/dist/team/coordination-protocol.js +244 -0
- package/dist/team/coordination-protocol.js.map +1 -0
- package/dist/team/runtime.d.ts +1 -0
- package/dist/team/runtime.d.ts.map +1 -1
- package/dist/team/runtime.js +19 -3
- package/dist/team/runtime.js.map +1 -1
- package/dist/team/state/tasks.d.ts.map +1 -1
- package/dist/team/state/tasks.js +24 -0
- package/dist/team/state/tasks.js.map +1 -1
- package/dist/team/state/types.d.ts +21 -1
- package/dist/team/state/types.d.ts.map +1 -1
- package/dist/team/state/types.js.map +1 -1
- package/dist/team/state.d.ts +17 -1
- package/dist/team/state.d.ts.map +1 -1
- package/dist/team/state.js +12 -5
- package/dist/team/state.js.map +1 -1
- package/dist/team/team-ops.d.ts +1 -1
- package/dist/team/team-ops.d.ts.map +1 -1
- package/dist/team/team-ops.js.map +1 -1
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +19 -1
- package/dist/team/tmux-session.js.map +1 -1
- package/dist/team/worker-bootstrap.d.ts.map +1 -1
- package/dist/team/worker-bootstrap.js +63 -0
- package/dist/team/worker-bootstrap.js.map +1 -1
- package/dist/utils/__tests__/agents-model-table.test.js +4 -2
- package/dist/utils/__tests__/agents-model-table.test.js.map +1 -1
- package/dist/utils/agents-model-table.d.ts.map +1 -1
- package/dist/utils/agents-model-table.js +3 -0
- package/dist/utils/agents-model-table.js.map +1 -1
- package/package.json +1 -1
- package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/oh-my-codex/skills/autopilot/SKILL.md +10 -5
- package/plugins/oh-my-codex/skills/deep-interview/SKILL.md +9 -4
- package/plugins/oh-my-codex/skills/ralplan/SKILL.md +12 -0
- package/plugins/oh-my-codex/skills/team/SKILL.md +16 -0
- package/plugins/oh-my-codex/skills/worker/SKILL.md +14 -0
- package/skills/autopilot/SKILL.md +10 -5
- package/skills/deep-interview/SKILL.md +9 -4
- package/skills/ralplan/SKILL.md +12 -0
- package/skills/team/SKILL.md +16 -0
- package/skills/worker/SKILL.md +14 -0
- package/src/scripts/__tests__/codex-native-hook.test.ts +2202 -523
- package/src/scripts/check-version-sync.ts +8 -4
- package/src/scripts/codex-native-hook.ts +444 -36
- package/src/scripts/codex-native-pre-post.ts +80 -0
- package/src/scripts/hook-payload-guard.ts +113 -0
- package/src/scripts/notify-fallback-watcher.ts +8 -1
- package/src/scripts/notify-hook/__tests__/payload-guard.test.ts +41 -0
- package/src/scripts/notify-hook/team-worker-stop.ts +193 -52
- package/src/scripts/notify-hook.ts +14 -2
|
@@ -4,8 +4,9 @@ import { appendFile, mkdir, readFile, readdir, stat, writeFile } from "fs/promis
|
|
|
4
4
|
import { extname, join, relative, resolve } from "path";
|
|
5
5
|
import { pathToFileURL } from "url";
|
|
6
6
|
import { readModeStateForActiveDecision, readModeStateForSession, updateModeState } from "../modes/base.js";
|
|
7
|
+
import { redactAuthSecrets } from "../auth/redact.js";
|
|
7
8
|
import { SKILL_ACTIVE_STATE_FILE, extractSessionIdFromInitializedStatePath, getSkillActiveStatePathsForStateDir, listActiveSkills, readSkillActiveState, readVisibleSkillActiveStateForStateDir, } from "../state/skill-active.js";
|
|
8
|
-
import { readSubagentSessionSummary, readSubagentTrackingState, recordSubagentTurnForSession, } from "../subagents/tracker.js";
|
|
9
|
+
import { isTrustedSubagentThread, readSubagentSessionSummary, readSubagentTrackingState, recordSubagentTurnForSession, } from "../subagents/tracker.js";
|
|
9
10
|
import { resolveCanonicalTeamStateRoot, resolveWorkerNotifyTeamStateRootPath } from "../team/state-root.js";
|
|
10
11
|
import { appendToLog, isSessionStateUsable, readSessionState, readUsableSessionState, reconcileNativeSessionStart, } from "../hooks/session.js";
|
|
11
12
|
import { appendTeamEvent, readTeamLeaderAttention, readTeamConfig, readTeamManifestV2, readTeamPhase, writeTeamLeaderAttention, writeTeamPhase, } from "../team/state.js";
|
|
@@ -36,6 +37,7 @@ import { isPendingDeepInterviewQuestionEnforcement, reconcileDeepInterviewQuesti
|
|
|
36
37
|
import { readAutopilotDeepInterviewQuestionWaitState } from "../question/autopilot-wait.js";
|
|
37
38
|
import { buildDocumentRefreshAdvisoryOutput, evaluateFinalHandoffDocumentRefresh, isFinalHandoffDocumentRefreshCandidate, } from "../document-refresh/enforcer.js";
|
|
38
39
|
import { buildExecFollowupStopOutput } from "../exec/followup.js";
|
|
40
|
+
import { MAX_NATIVE_STDIN_JSON_BYTES, extractRawCodexHookEventName, } from "./hook-payload-guard.js";
|
|
39
41
|
const TERMINAL_MODE_PHASES = new Set(["complete", "completed", "failed", "cancelled"]);
|
|
40
42
|
const SKILL_STOP_BLOCKERS = new Set(["ralplan"]);
|
|
41
43
|
const TEAM_STOP_BLOCKING_TASK_STATUSES = new Set(["pending", "in_progress", "blocked"]);
|
|
@@ -45,6 +47,7 @@ const ORDINARY_STOP_NO_PROGRESS_DEFAULT_MAX_REPEATS = 8;
|
|
|
45
47
|
const RALPH_ORPHANED_STARTING_STALE_MS = 15 * 60_000;
|
|
46
48
|
const ORDINARY_STOP_NO_PROGRESS_DEFAULT_IDLE_MS = 10 * 60_000;
|
|
47
49
|
const ORDINARY_STOP_NO_PROGRESS_MAX_MESSAGE_LENGTH = 240;
|
|
50
|
+
const OMX_OWNER_SESSION_ID_PATTERN = /^omx-[A-Za-z0-9_-]{1,60}$/;
|
|
48
51
|
const STABLE_FINAL_RECOMMENDATION_PATTERNS = [
|
|
49
52
|
/^\s*(?:launch|release|ship)-?ready\s*:\s*(?:yes|no)\b[^\n\r]*/im,
|
|
50
53
|
/^\s*ready to release\s*:\s*(?:yes|no)\b[^\n\r]*/im,
|
|
@@ -71,6 +74,12 @@ function safeString(value) {
|
|
|
71
74
|
function safeObject(value) {
|
|
72
75
|
return value && typeof value === "object" ? value : {};
|
|
73
76
|
}
|
|
77
|
+
function resolveHudReconcileSessionId(currentSessionState, canonicalSessionId, sessionIdForState) {
|
|
78
|
+
const ownerOmxSessionId = safeString(currentSessionState?.owner_omx_session_id).trim();
|
|
79
|
+
if (OMX_OWNER_SESSION_ID_PATTERN.test(ownerOmxSessionId))
|
|
80
|
+
return ownerOmxSessionId;
|
|
81
|
+
return canonicalSessionId || sessionIdForState || undefined;
|
|
82
|
+
}
|
|
74
83
|
function safeContextSnippet(value, maxLength = 300) {
|
|
75
84
|
const text = safeString(value).replace(/\s+/g, " ").trim();
|
|
76
85
|
if (text.length <= maxLength)
|
|
@@ -134,18 +143,25 @@ function readNativeSubagentSessionStartMetadata(transcriptPath) {
|
|
|
134
143
|
}
|
|
135
144
|
}
|
|
136
145
|
async function recordNativeSubagentSessionStart(cwd, canonicalSessionId, childSessionId, metadata, transcriptPath) {
|
|
146
|
+
const parentThreadId = metadata.parentThreadId.trim();
|
|
147
|
+
const childThreadId = childSessionId.trim();
|
|
137
148
|
const trackingSessionIds = [...new Set([
|
|
138
149
|
canonicalSessionId.trim(),
|
|
139
|
-
|
|
150
|
+
parentThreadId,
|
|
140
151
|
].filter(Boolean))];
|
|
141
152
|
for (const sessionId of trackingSessionIds) {
|
|
153
|
+
if (parentThreadId && parentThreadId !== childThreadId) {
|
|
154
|
+
await recordSubagentTurnForSession(cwd, {
|
|
155
|
+
sessionId,
|
|
156
|
+
threadId: parentThreadId,
|
|
157
|
+
kind: 'leader',
|
|
158
|
+
}).catch(() => { });
|
|
159
|
+
}
|
|
142
160
|
await recordSubagentTurnForSession(cwd, {
|
|
143
161
|
sessionId,
|
|
144
|
-
threadId:
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
sessionId,
|
|
148
|
-
threadId: childSessionId,
|
|
162
|
+
threadId: childThreadId,
|
|
163
|
+
kind: 'subagent',
|
|
164
|
+
...(parentThreadId && parentThreadId !== childThreadId ? { leaderThreadId: parentThreadId } : {}),
|
|
149
165
|
mode: metadata.agentRole,
|
|
150
166
|
}).catch(() => { });
|
|
151
167
|
}
|
|
@@ -176,17 +192,42 @@ async function nativeSubagentSessionStartBelongsToCanonicalSession(cwd, canonica
|
|
|
176
192
|
return true;
|
|
177
193
|
return summary.allThreadIds.includes(parentThreadId);
|
|
178
194
|
}
|
|
179
|
-
async function isNativeSubagentHook(cwd, canonicalSessionId, nativeSessionId, threadId) {
|
|
180
|
-
const
|
|
195
|
+
async function isNativeSubagentHook(cwd, canonicalSessionId, nativeSessionId, threadId, canonicalLeaderNativeSessionId = "") {
|
|
196
|
+
const nativeId = nativeSessionId.trim();
|
|
197
|
+
const promptThreadId = threadId.trim();
|
|
198
|
+
const candidateIds = [nativeId, promptThreadId]
|
|
181
199
|
.map((value) => value.trim())
|
|
182
200
|
.filter(Boolean);
|
|
183
201
|
if (candidateIds.length === 0)
|
|
184
202
|
return false;
|
|
185
203
|
const sessionId = canonicalSessionId.trim();
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
204
|
+
const currentLeaderNativeSessionId = canonicalLeaderNativeSessionId.trim();
|
|
205
|
+
const summary = sessionId
|
|
206
|
+
? await readSubagentSessionSummary(cwd, sessionId).catch(() => null)
|
|
207
|
+
: null;
|
|
208
|
+
const currentLeaderIds = new Set([
|
|
209
|
+
currentLeaderNativeSessionId,
|
|
210
|
+
summary?.leaderThreadId?.trim(),
|
|
211
|
+
].filter(Boolean));
|
|
212
|
+
if (summary
|
|
213
|
+
&& candidateIds.some((id) => !currentLeaderIds.has(id) && summary.allSubagentThreadIds.includes(id))) {
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
// Native UserPromptSubmit can carry a per-turn thread_id that differs from
|
|
217
|
+
// the long-lived native session id. Treat the current canonical native
|
|
218
|
+
// session as the leader before consulting stale/global tracker state.
|
|
219
|
+
if (sessionId
|
|
220
|
+
&& currentLeaderNativeSessionId
|
|
221
|
+
&& (nativeId === currentLeaderNativeSessionId
|
|
222
|
+
|| (!nativeId && promptThreadId === currentLeaderNativeSessionId))) {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
if (summary) {
|
|
226
|
+
const leaderThreadId = summary.leaderThreadId?.trim();
|
|
227
|
+
if (leaderThreadId
|
|
228
|
+
&& (nativeId === leaderThreadId
|
|
229
|
+
|| (!nativeId && promptThreadId === leaderThreadId))) {
|
|
230
|
+
return false;
|
|
190
231
|
}
|
|
191
232
|
}
|
|
192
233
|
// Native Codex resume can report the child native session as the canonical
|
|
@@ -199,7 +240,7 @@ async function isNativeSubagentHook(cwd, canonicalSessionId, nativeSessionId, th
|
|
|
199
240
|
const trackingState = await readSubagentTrackingState(cwd).catch(() => null);
|
|
200
241
|
if (!trackingState)
|
|
201
242
|
return false;
|
|
202
|
-
return Object.values(trackingState.sessions).some((session) => (candidateIds.some((id) => session
|
|
243
|
+
return Object.values(trackingState.sessions).some((session) => (candidateIds.some((id) => isTrustedSubagentThread(session, id))));
|
|
203
244
|
}
|
|
204
245
|
function shouldSuppressSubagentLifecycleHookDispatch() {
|
|
205
246
|
const config = getNotificationConfig();
|
|
@@ -1374,16 +1415,21 @@ function buildNativeOutsideTmuxTeamPromptBlockState(prompt, cwd, payload, sessio
|
|
|
1374
1415
|
function buildSkillStateCliInstruction(mode, statePath) {
|
|
1375
1416
|
return `skill: ${mode} activated and initial state initialized at ${statePath}; use CLI-first state updates via \`omx state write/read/clear --input '<json>' --json\`; use omx_state MCP only when explicit MCP compatibility is enabled.`;
|
|
1376
1417
|
}
|
|
1377
|
-
function buildAutopilotPromptActivationNote(skillState) {
|
|
1418
|
+
function buildAutopilotPromptActivationNote(skillState, options = {}) {
|
|
1378
1419
|
if (skillState?.initialized_mode !== "autopilot")
|
|
1379
1420
|
return null;
|
|
1380
1421
|
return [
|
|
1381
1422
|
"Autopilot protocol: the durable default chain is $deep-interview -> $ralplan -> $ultragoal (+ $team if needed) -> $code-review -> $ultraqa (deep-interview -> ralplan -> ultragoal -> code-review -> ultraqa).",
|
|
1382
1423
|
"Start/resume at current_phase=deep-interview unless the task is clear and bounded; if deep-interview is intentionally skipped, persist and state an explicit deep_interview_gate.skip_reason before moving to ralplan.",
|
|
1424
|
+
"Deep-interview is a structured question chain, not a one-question gate: after an omx question answer, re-score ambiguity against the active threshold, treat max_rounds as a cap, and crystallize once ambiguity is at or below threshold and readiness gates pass.",
|
|
1425
|
+
options.markedQuestionAnswer
|
|
1426
|
+
? "This turn is a marked omx question answer. Treat ordinary selected option/freeform answer text as interview input, then re-score. Do not close merely because the first question was answered; if ambiguity is at or below threshold and readiness gates pass, write interview_complete evidence and hand off. Ask another deep-interview follow-up only when a readiness gate remains unresolved and the answer would materially change execution."
|
|
1427
|
+
: null,
|
|
1428
|
+
"Do not advance from deep-interview to ralplan merely because the first question was answered; persist explicit interview_complete evidence before setting current_phase=ralplan, and do advance when threshold plus readiness gates are satisfied.",
|
|
1383
1429
|
"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.",
|
|
1384
1430
|
"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.",
|
|
1385
1431
|
"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.",
|
|
1386
|
-
].join(" ");
|
|
1432
|
+
].filter(Boolean).join(" ");
|
|
1387
1433
|
}
|
|
1388
1434
|
function buildAdditionalContextMessage(prompt, skillState, cwd = process.cwd(), payload) {
|
|
1389
1435
|
if (!prompt)
|
|
@@ -1399,6 +1445,8 @@ function buildAdditionalContextMessage(prompt, skillState, cwd = process.cwd(),
|
|
|
1399
1445
|
? buildDeepInterviewQuestionBridgeInstruction(cwd, payload)
|
|
1400
1446
|
: null;
|
|
1401
1447
|
const deepInterviewConfigPromptActivationNote = buildDeepInterviewConfigInstruction(cwd, skillState);
|
|
1448
|
+
const markedQuestionAnswer = /^\s*\[omx question answered\]/i.test(prompt);
|
|
1449
|
+
const autopilotPromptActivationNote = buildAutopilotPromptActivationNote(skillState, { markedQuestionAnswer });
|
|
1402
1450
|
return [
|
|
1403
1451
|
`OMX native UserPromptSubmit continued active workflow skill "${continuedSkill}".`,
|
|
1404
1452
|
promptPriorityMessage,
|
|
@@ -1407,12 +1455,34 @@ function buildAdditionalContextMessage(prompt, skillState, cwd = process.cwd(),
|
|
|
1407
1455
|
: null,
|
|
1408
1456
|
deepInterviewPromptActivationNote,
|
|
1409
1457
|
deepInterviewConfigPromptActivationNote,
|
|
1458
|
+
autopilotPromptActivationNote,
|
|
1410
1459
|
"Follow AGENTS.md routing and preserve workflow transition and planning-safety rules.",
|
|
1411
1460
|
].filter(Boolean).join(" ");
|
|
1412
1461
|
}
|
|
1413
1462
|
const detectedKeywordMessage = matches.length > 1
|
|
1414
1463
|
? `OMX native UserPromptSubmit detected workflow keywords ${matches.map((entry) => `"${entry.keyword}" -> ${entry.skill}`).join(", ")}.`
|
|
1415
1464
|
: `OMX native UserPromptSubmit detected workflow keyword "${match.keyword}" -> ${match.skill}.`;
|
|
1465
|
+
const continuedSkill = safeString(skillState?.skill).trim();
|
|
1466
|
+
if (continuedSkill
|
|
1467
|
+
&& continuedSkill !== match.skill
|
|
1468
|
+
&& /^\s*\[omx question answered\]/i.test(prompt)) {
|
|
1469
|
+
const deepInterviewPromptActivationNote = skillState?.initialized_mode === "deep-interview"
|
|
1470
|
+
? buildDeepInterviewQuestionBridgeInstruction(cwd, payload)
|
|
1471
|
+
: null;
|
|
1472
|
+
const deepInterviewConfigPromptActivationNote = buildDeepInterviewConfigInstruction(cwd, skillState);
|
|
1473
|
+
const autopilotPromptActivationNote = buildAutopilotPromptActivationNote(skillState, { markedQuestionAnswer: true });
|
|
1474
|
+
return [
|
|
1475
|
+
`OMX native UserPromptSubmit continued active workflow skill "${continuedSkill}"; workflow-like tokens inside the marked omx question answer are treated as answer text, not a new workflow activation.`,
|
|
1476
|
+
promptPriorityMessage,
|
|
1477
|
+
skillState?.initialized_mode && skillState.initialized_state_path
|
|
1478
|
+
? buildSkillStateCliInstruction(skillState.initialized_mode, skillState.initialized_state_path)
|
|
1479
|
+
: null,
|
|
1480
|
+
deepInterviewPromptActivationNote,
|
|
1481
|
+
deepInterviewConfigPromptActivationNote,
|
|
1482
|
+
autopilotPromptActivationNote,
|
|
1483
|
+
"Follow AGENTS.md routing and preserve workflow transition and planning-safety rules.",
|
|
1484
|
+
].filter(Boolean).join(" ");
|
|
1485
|
+
}
|
|
1416
1486
|
const activeSkills = Array.isArray(skillState?.active_skills)
|
|
1417
1487
|
? skillState.active_skills.map((entry) => entry.skill)
|
|
1418
1488
|
: [];
|
|
@@ -1968,13 +2038,30 @@ const DEEP_INTERVIEW_ALLOWED_WRITE_PREFIXES = [
|
|
|
1968
2038
|
".omx/specs",
|
|
1969
2039
|
".omx/state",
|
|
1970
2040
|
];
|
|
1971
|
-
const
|
|
2041
|
+
const RALPLAN_ALLOWED_WRITE_PREFIXES = [
|
|
2042
|
+
".omx/context",
|
|
2043
|
+
".omx/plans",
|
|
2044
|
+
".omx/specs",
|
|
2045
|
+
".omx/state",
|
|
2046
|
+
];
|
|
2047
|
+
const PLANNING_MODE_IMPLEMENTATION_TOOL_NAMES = new Set([
|
|
1972
2048
|
"Write",
|
|
1973
2049
|
"Edit",
|
|
1974
2050
|
"MultiEdit",
|
|
2051
|
+
"NotebookEdit",
|
|
1975
2052
|
"apply_patch",
|
|
1976
2053
|
"ApplyPatch",
|
|
1977
2054
|
]);
|
|
2055
|
+
const DEEP_INTERVIEW_IMPLEMENTATION_TOOL_NAMES = PLANNING_MODE_IMPLEMENTATION_TOOL_NAMES;
|
|
2056
|
+
const RALPLAN_EXECUTION_HANDOFF_SKILLS = new Set([
|
|
2057
|
+
"autopilot",
|
|
2058
|
+
"autoresearch",
|
|
2059
|
+
"ralph",
|
|
2060
|
+
"team",
|
|
2061
|
+
"ultragoal",
|
|
2062
|
+
"ultrawork",
|
|
2063
|
+
"ultraqa",
|
|
2064
|
+
]);
|
|
1978
2065
|
function isActiveDeepInterviewPhase(state) {
|
|
1979
2066
|
if (!state || state.active !== true)
|
|
1980
2067
|
return false;
|
|
@@ -1986,7 +2073,31 @@ function isActiveDeepInterviewPhase(state) {
|
|
|
1986
2073
|
return false;
|
|
1987
2074
|
return true;
|
|
1988
2075
|
}
|
|
1989
|
-
function
|
|
2076
|
+
function isActiveRalplanPhase(state) {
|
|
2077
|
+
if (!state || state.active !== true)
|
|
2078
|
+
return false;
|
|
2079
|
+
const mode = safeString(state.mode).trim();
|
|
2080
|
+
if (mode && mode !== "ralplan")
|
|
2081
|
+
return false;
|
|
2082
|
+
const phase = safeString(state.current_phase ?? state.currentPhase).trim().toLowerCase();
|
|
2083
|
+
if (phase && (TERMINAL_MODE_PHASES.has(phase) || phase === "completing"))
|
|
2084
|
+
return false;
|
|
2085
|
+
return true;
|
|
2086
|
+
}
|
|
2087
|
+
function isActiveAutopilotRalplanPhase(state) {
|
|
2088
|
+
if (!state || state.active !== true)
|
|
2089
|
+
return false;
|
|
2090
|
+
const mode = safeString(state.mode).trim();
|
|
2091
|
+
if (mode && mode !== "autopilot")
|
|
2092
|
+
return false;
|
|
2093
|
+
const phase = safeString(state.current_phase ?? state.currentPhase).trim().toLowerCase();
|
|
2094
|
+
return phase === "ralplan";
|
|
2095
|
+
}
|
|
2096
|
+
function hasExplicitExecutionHandoffSkill(state, sessionId, threadId) {
|
|
2097
|
+
return listActiveSkills(state ?? {}).some((entry) => (RALPLAN_EXECUTION_HANDOFF_SKILLS.has(entry.skill)
|
|
2098
|
+
&& matchesSkillStopContext(entry, state ?? {}, sessionId, threadId)));
|
|
2099
|
+
}
|
|
2100
|
+
function isAllowedPlanningArtifactPath(cwd, rawPath, allowedPrefixes) {
|
|
1990
2101
|
const trimmed = rawPath.trim().replace(/^['"]|['"]$/g, "");
|
|
1991
2102
|
if (!trimmed || trimmed.includes("\0"))
|
|
1992
2103
|
return false;
|
|
@@ -2000,7 +2111,13 @@ function isAllowedDeepInterviewArtifactPath(cwd, rawPath) {
|
|
|
2000
2111
|
}
|
|
2001
2112
|
if (!relativePath || relativePath.startsWith("..") || relativePath.startsWith("/"))
|
|
2002
2113
|
return false;
|
|
2003
|
-
return
|
|
2114
|
+
return allowedPrefixes.some((prefix) => (relativePath === prefix || relativePath.startsWith(`${prefix}/`)));
|
|
2115
|
+
}
|
|
2116
|
+
function isAllowedDeepInterviewArtifactPath(cwd, rawPath) {
|
|
2117
|
+
return isAllowedPlanningArtifactPath(cwd, rawPath, DEEP_INTERVIEW_ALLOWED_WRITE_PREFIXES);
|
|
2118
|
+
}
|
|
2119
|
+
function isAllowedRalplanArtifactPath(cwd, rawPath) {
|
|
2120
|
+
return isAllowedPlanningArtifactPath(cwd, rawPath, RALPLAN_ALLOWED_WRITE_PREFIXES);
|
|
2004
2121
|
}
|
|
2005
2122
|
function readPreToolUseCommand(payload) {
|
|
2006
2123
|
const toolInput = safeObject(payload.tool_input);
|
|
@@ -2064,6 +2181,76 @@ async function readActiveDeepInterviewStateForPreToolUse(cwd, stateDir, sessionI
|
|
|
2064
2181
|
&& matchesSkillStopContext(entry, canonicalState, sessionId, threadId)));
|
|
2065
2182
|
return hasActiveDeepInterviewSkill ? modeState : null;
|
|
2066
2183
|
}
|
|
2184
|
+
async function readActiveRalplanStateForPreToolUse(cwd, stateDir, sessionId, threadId) {
|
|
2185
|
+
const modeState = sessionId
|
|
2186
|
+
? await readStopSessionPinnedState("ralplan-state.json", cwd, sessionId, stateDir)
|
|
2187
|
+
: await readJsonIfExists(join(stateDir, "ralplan-state.json"));
|
|
2188
|
+
const canonicalState = sessionId
|
|
2189
|
+
? await readVisibleSkillActiveStateForStateDir(stateDir, sessionId)
|
|
2190
|
+
: await readSkillActiveState(join(stateDir, SKILL_ACTIVE_STATE_FILE));
|
|
2191
|
+
if (isActiveRalplanPhase(modeState) && modeState && modeStateMatchesSkillStopContext(modeState, cwd, sessionId)) {
|
|
2192
|
+
if (hasExplicitExecutionHandoffSkill(canonicalState, sessionId, threadId))
|
|
2193
|
+
return null;
|
|
2194
|
+
if (!canonicalState)
|
|
2195
|
+
return modeState;
|
|
2196
|
+
const hasActiveRalplanSkill = listActiveSkills(canonicalState).some((entry) => (entry.skill === "ralplan"
|
|
2197
|
+
&& matchesSkillStopContext(entry, canonicalState, sessionId, threadId)));
|
|
2198
|
+
if (hasActiveRalplanSkill)
|
|
2199
|
+
return modeState;
|
|
2200
|
+
}
|
|
2201
|
+
const autopilotState = sessionId
|
|
2202
|
+
? await readStopSessionPinnedState("autopilot-state.json", cwd, sessionId, stateDir)
|
|
2203
|
+
: await readJsonIfExists(join(stateDir, "autopilot-state.json"));
|
|
2204
|
+
if (!isActiveAutopilotRalplanPhase(autopilotState) || !autopilotState)
|
|
2205
|
+
return null;
|
|
2206
|
+
if (!modeStateMatchesSkillStopContext(autopilotState, cwd, sessionId))
|
|
2207
|
+
return null;
|
|
2208
|
+
const terminalAutopilotRunState = await readCanonicalTerminalRunStateForStop(cwd, sessionId, "autopilot");
|
|
2209
|
+
if (terminalAutopilotRunState)
|
|
2210
|
+
return null;
|
|
2211
|
+
if (!canonicalState)
|
|
2212
|
+
return autopilotState;
|
|
2213
|
+
const hasActiveAutopilotSkill = listActiveSkills(canonicalState).some((entry) => (entry.skill === "autopilot"
|
|
2214
|
+
&& matchesSkillStopContext(entry, canonicalState, sessionId, threadId)));
|
|
2215
|
+
return hasActiveAutopilotSkill ? autopilotState : null;
|
|
2216
|
+
}
|
|
2217
|
+
function isAllowedRalplanBashWrite(cwd, command) {
|
|
2218
|
+
if (!commandHasDeepInterviewWriteIntent(command))
|
|
2219
|
+
return true;
|
|
2220
|
+
if (/\bomx\s+(?:state\s+(?:write|read|clear)|question)\b/.test(command))
|
|
2221
|
+
return true;
|
|
2222
|
+
const targets = extractDeepInterviewCommandWriteTargets(command);
|
|
2223
|
+
return targets.length > 0 && targets.every((target) => isAllowedRalplanArtifactPath(cwd, target));
|
|
2224
|
+
}
|
|
2225
|
+
async function buildRalplanPreToolUseBoundaryOutput(payload, cwd, stateDir) {
|
|
2226
|
+
const sessionId = readPayloadSessionId(payload);
|
|
2227
|
+
const threadId = readPayloadThreadId(payload);
|
|
2228
|
+
const activeState = await readActiveRalplanStateForPreToolUse(cwd, stateDir, sessionId, threadId);
|
|
2229
|
+
if (!activeState)
|
|
2230
|
+
return null;
|
|
2231
|
+
const toolName = safeString(payload.tool_name).trim();
|
|
2232
|
+
const command = readPreToolUseCommand(payload);
|
|
2233
|
+
const pathCandidates = readPreToolUsePathCandidates(payload);
|
|
2234
|
+
let blocked = false;
|
|
2235
|
+
if (toolName === "Bash") {
|
|
2236
|
+
blocked = !isAllowedRalplanBashWrite(cwd, command);
|
|
2237
|
+
}
|
|
2238
|
+
else if (PLANNING_MODE_IMPLEMENTATION_TOOL_NAMES.has(toolName)) {
|
|
2239
|
+
blocked = pathCandidates.length === 0
|
|
2240
|
+
|| !pathCandidates.every((candidate) => isAllowedRalplanArtifactPath(cwd, candidate));
|
|
2241
|
+
}
|
|
2242
|
+
if (!blocked)
|
|
2243
|
+
return null;
|
|
2244
|
+
const phase = formatPhase(activeState.current_phase ?? activeState.currentPhase, "planning");
|
|
2245
|
+
return {
|
|
2246
|
+
decision: "block",
|
|
2247
|
+
reason: `Ralplan is active (phase: ${phase}); implementation/write tools are blocked until an explicit execution handoff workflow is activated.`,
|
|
2248
|
+
hookSpecificOutput: {
|
|
2249
|
+
hookEventName: "PreToolUse",
|
|
2250
|
+
additionalContext: "Ralplan is consensus-planning mode. Write only planning artifacts under `.omx/context/`, `.omx/plans/`, `.omx/specs/`, or required `.omx/state/` files. Do not edit implementation files or run implementation-focused writes from ralplan. To execute, first process an explicit handoff such as `$ultragoal`, `$team`, or `$ralph`, which must emit terminal ralplan state before implementation begins.",
|
|
2251
|
+
},
|
|
2252
|
+
};
|
|
2253
|
+
}
|
|
2067
2254
|
async function buildDeepInterviewPreToolUseBoundaryOutput(payload, cwd, stateDir) {
|
|
2068
2255
|
const sessionId = readPayloadSessionId(payload);
|
|
2069
2256
|
const threadId = readPayloadThreadId(payload);
|
|
@@ -2304,12 +2491,12 @@ function buildRalplanContinuationStatus(blocker, activeSubagentCount) {
|
|
|
2304
2491
|
};
|
|
2305
2492
|
}
|
|
2306
2493
|
const completeHint = blocker.planningComplete
|
|
2307
|
-
? " The planning artifacts are present; if consensus is approved, emit
|
|
2494
|
+
? " The planning artifacts are present; if consensus is approved, emit terminal ralplan complete/approved handoff state and stop planning. Implementation must wait for an explicit $ultragoal, $team, or $ralph handoff."
|
|
2308
2495
|
: "";
|
|
2309
2496
|
return {
|
|
2310
|
-
reason: `Status: continue_from_artifact — ralplan is still active (phase: ${phase}) and has not emitted a terminal complete/paused/waiting status. Continue from the current ralplan artifact, resolve any review ambiguity conservatively or ask the user if needed, and proceed to the next planning/review step before stopping.${artifact}${completeHint}`,
|
|
2497
|
+
reason: `Status: continue_from_artifact — ralplan is still active (phase: ${phase}) and has not emitted a terminal complete/paused/waiting status. Continue from the current ralplan artifact, resolve any review ambiguity conservatively or ask the user if needed, and proceed to the next planning/review step before stopping; do not begin implementation from ralplan.${artifact}${completeHint}`,
|
|
2311
2498
|
stopReasonSuffix: "continue_artifact",
|
|
2312
|
-
systemMessage: `OMX ralplan status: continue_from_artifact at phase ${phase}; continue from the current ralplan artifact and finish by stating whether ralplan is complete, paused for review, waiting for input, or still continuing.`,
|
|
2499
|
+
systemMessage: `OMX ralplan status: continue_from_artifact at phase ${phase}; continue from the current ralplan artifact and finish by stating whether ralplan is complete, paused for review, waiting for input, or still continuing; do not begin implementation from ralplan.`,
|
|
2313
2500
|
};
|
|
2314
2501
|
}
|
|
2315
2502
|
async function readStopAutoNudgePhase(cwd, stateDir, sessionId, threadId) {
|
|
@@ -2878,6 +3065,14 @@ async function buildStopHookOutput(payload, cwd, stateDir, options = {}) {
|
|
|
2878
3065
|
export async function dispatchCodexNativeHook(payload, options = {}) {
|
|
2879
3066
|
const hookEventName = readHookEventName(payload);
|
|
2880
3067
|
const cwd = options.cwd ?? (safeString(payload.cwd).trim() || process.cwd());
|
|
3068
|
+
if (hookEventName === "Stop" && !hasNativeStopRuntimeSurface(cwd)) {
|
|
3069
|
+
return {
|
|
3070
|
+
hookEventName,
|
|
3071
|
+
omxEventName: mapCodexHookEventToOmxEvent(hookEventName),
|
|
3072
|
+
skillState: null,
|
|
3073
|
+
outputJson: null,
|
|
3074
|
+
};
|
|
3075
|
+
}
|
|
2881
3076
|
// Native hooks must use the same authoritative runtime state root as HUD/MCP
|
|
2882
3077
|
// when boxed/team roots are active; do not bypass it with cwd/.omx/state.
|
|
2883
3078
|
const stateDir = getBaseStateDir(cwd);
|
|
@@ -2938,14 +3133,16 @@ export async function dispatchCodexNativeHook(payload, options = {}) {
|
|
|
2938
3133
|
const sessionIdForState = canonicalSessionId || nativeSessionId;
|
|
2939
3134
|
let outputJson = null;
|
|
2940
3135
|
const isSubagentPromptSubmit = hookEventName === "UserPromptSubmit"
|
|
2941
|
-
? await isNativeSubagentHook(cwd, canonicalSessionId, nativeSessionId, threadId)
|
|
3136
|
+
? await isNativeSubagentHook(cwd, canonicalSessionId, nativeSessionId, threadId, safeString(currentSessionState?.native_session_id).trim())
|
|
2942
3137
|
: false;
|
|
2943
3138
|
const isSubagentStop = hookEventName === "Stop"
|
|
2944
3139
|
? (await Promise.all([...new Set([
|
|
2945
3140
|
canonicalSessionId,
|
|
2946
3141
|
safeString(currentSessionState?.session_id).trim(),
|
|
2947
3142
|
].filter(Boolean))]
|
|
2948
|
-
.map((candidateSessionId) => isNativeSubagentHook(cwd, candidateSessionId, nativeSessionId, threadId)
|
|
3143
|
+
.map((candidateSessionId) => isNativeSubagentHook(cwd, candidateSessionId, nativeSessionId, threadId, candidateSessionId === safeString(currentSessionState?.session_id).trim()
|
|
3144
|
+
? safeString(currentSessionState?.native_session_id).trim()
|
|
3145
|
+
: "")))).some(Boolean)
|
|
2949
3146
|
: false;
|
|
2950
3147
|
const suppressNoisySubagentLifecycleDispatch = (isSubagentSessionStart || isSubagentStop)
|
|
2951
3148
|
&& shouldSuppressSubagentLifecycleHookDispatch();
|
|
@@ -3044,7 +3241,8 @@ export async function dispatchCodexNativeHook(payload, options = {}) {
|
|
|
3044
3241
|
&& await isConfirmedTeamWorkerPromptSubmitPane(cwd).catch(() => false);
|
|
3045
3242
|
if (!skipHudReconcileForTeamWorkerPane) {
|
|
3046
3243
|
const reconcileHudForPromptSubmitFn = options.reconcileHudForPromptSubmitFn ?? reconcileHudForPromptSubmit;
|
|
3047
|
-
|
|
3244
|
+
const hudSessionId = resolveHudReconcileSessionId(currentSessionState, canonicalSessionId, sessionIdForState);
|
|
3245
|
+
await reconcileHudForPromptSubmitFn(cwd, { sessionId: hudSessionId }).catch(() => { });
|
|
3048
3246
|
}
|
|
3049
3247
|
}
|
|
3050
3248
|
if (omxEventName && !skipCanonicalSessionStartContext && !suppressNoisySubagentLifecycleDispatch) {
|
|
@@ -3101,6 +3299,7 @@ export async function dispatchCodexNativeHook(payload, options = {}) {
|
|
|
3101
3299
|
}
|
|
3102
3300
|
else if (hookEventName === "PreToolUse") {
|
|
3103
3301
|
outputJson = await buildDeepInterviewPreToolUseBoundaryOutput(payload, cwd, stateDir)
|
|
3302
|
+
?? await buildRalplanPreToolUseBoundaryOutput(payload, cwd, stateDir)
|
|
3104
3303
|
?? buildNativePreToolUseOutput(payload);
|
|
3105
3304
|
}
|
|
3106
3305
|
else if (hookEventName === "PostToolUse") {
|
|
@@ -3122,6 +3321,28 @@ export async function dispatchCodexNativeHook(payload, options = {}) {
|
|
|
3122
3321
|
outputJson,
|
|
3123
3322
|
};
|
|
3124
3323
|
}
|
|
3324
|
+
function hasNativeStopRuntimeSurface(cwd) {
|
|
3325
|
+
if (existsSync(join(cwd, ".omx")))
|
|
3326
|
+
return true;
|
|
3327
|
+
if (findGitLayout(cwd))
|
|
3328
|
+
return true;
|
|
3329
|
+
const omxRoot = safeString(process.env.OMX_ROOT).trim();
|
|
3330
|
+
if (omxRoot && existsSync(join(omxRoot, ".omx")))
|
|
3331
|
+
return true;
|
|
3332
|
+
const stateRoot = safeString(process.env.OMX_STATE_ROOT).trim();
|
|
3333
|
+
if (stateRoot && existsSync(stateRoot))
|
|
3334
|
+
return true;
|
|
3335
|
+
return [
|
|
3336
|
+
process.env.OMX_SESSION_ID,
|
|
3337
|
+
process.env.OMX_TEAM_INTERNAL_WORKER,
|
|
3338
|
+
process.env.OMX_TEAM_WORKER,
|
|
3339
|
+
process.env.OMX_TEAM_STATE_ROOT,
|
|
3340
|
+
process.env.OMX_TEAM_LEADER_CWD,
|
|
3341
|
+
process.env.OMX_NOTIFY_HOOK_TRUSTED_MANAGED_CWD,
|
|
3342
|
+
process.env.OMX_TMUX_HUD_OWNER,
|
|
3343
|
+
process.env.OMX_TMUX_HUD_LEADER_PANE,
|
|
3344
|
+
].some((value) => safeString(value).trim() !== "");
|
|
3345
|
+
}
|
|
3125
3346
|
export function isCodexNativeHookMainModule(moduleUrl, argv1) {
|
|
3126
3347
|
if (!argv1)
|
|
3127
3348
|
return false;
|
|
@@ -3129,30 +3350,129 @@ export function isCodexNativeHookMainModule(moduleUrl, argv1) {
|
|
|
3129
3350
|
}
|
|
3130
3351
|
async function readStdinJson() {
|
|
3131
3352
|
const chunks = [];
|
|
3353
|
+
let totalBytes = 0;
|
|
3354
|
+
let oversized = false;
|
|
3132
3355
|
for await (const chunk of process.stdin) {
|
|
3133
|
-
|
|
3356
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
|
|
3357
|
+
totalBytes += buffer.byteLength;
|
|
3358
|
+
if (totalBytes > MAX_NATIVE_STDIN_JSON_BYTES) {
|
|
3359
|
+
const remaining = Math.max(0, MAX_NATIVE_STDIN_JSON_BYTES - (totalBytes - buffer.byteLength));
|
|
3360
|
+
if (remaining > 0)
|
|
3361
|
+
chunks.push(Buffer.from(buffer.subarray(0, remaining)));
|
|
3362
|
+
oversized = true;
|
|
3363
|
+
process.stdin.destroy();
|
|
3364
|
+
break;
|
|
3365
|
+
}
|
|
3366
|
+
chunks.push(buffer);
|
|
3134
3367
|
}
|
|
3135
3368
|
const raw = Buffer.concat(chunks).toString("utf-8").trim();
|
|
3369
|
+
const rawHookEventName = extractRawCodexHookEventName(raw);
|
|
3370
|
+
if (oversized) {
|
|
3371
|
+
return {
|
|
3372
|
+
payload: {},
|
|
3373
|
+
parseError: null,
|
|
3374
|
+
rawInput: raw,
|
|
3375
|
+
oversized: true,
|
|
3376
|
+
rawHookEventName,
|
|
3377
|
+
};
|
|
3378
|
+
}
|
|
3136
3379
|
if (!raw) {
|
|
3137
|
-
return { payload: {}, parseError: null };
|
|
3380
|
+
return { payload: {}, parseError: null, rawInput: raw, oversized: false, rawHookEventName };
|
|
3138
3381
|
}
|
|
3139
3382
|
try {
|
|
3140
3383
|
return {
|
|
3141
3384
|
payload: safeObject(JSON.parse(raw)),
|
|
3142
3385
|
parseError: null,
|
|
3386
|
+
rawInput: raw,
|
|
3387
|
+
oversized: false,
|
|
3388
|
+
rawHookEventName,
|
|
3143
3389
|
};
|
|
3144
3390
|
}
|
|
3145
3391
|
catch (error) {
|
|
3146
3392
|
return {
|
|
3147
3393
|
payload: {},
|
|
3148
3394
|
parseError: error instanceof Error ? error : new Error(String(error)),
|
|
3395
|
+
rawInput: raw,
|
|
3396
|
+
oversized: false,
|
|
3397
|
+
rawHookEventName,
|
|
3149
3398
|
};
|
|
3150
3399
|
}
|
|
3151
3400
|
}
|
|
3401
|
+
function inferHookEventNameFromMalformedInput(raw) {
|
|
3402
|
+
const match = raw.match(/(?:\"|['"])?hook[_-]?event[_-]?name(?:\"|['"])?\s*:\s*(?:\"|['"])?(SessionStart|PreToolUse|PostToolUse|UserPromptSubmit|PreCompact|PostCompact|Stop)\b/i);
|
|
3403
|
+
const value = match?.[1];
|
|
3404
|
+
if (!value)
|
|
3405
|
+
return null;
|
|
3406
|
+
return readHookEventName({ hook_event_name: value });
|
|
3407
|
+
}
|
|
3408
|
+
function buildMalformedStdinHookOutput(parseError, rawInput) {
|
|
3409
|
+
const reason = "OMX native hook received malformed JSON input. Preserve runtime state, inspect the emitting hook payload yourself, and retry with valid JSON.";
|
|
3410
|
+
const systemMessage = `${reason} stdin JSON parsing failed inside codex-native-hook: ${parseError.message}.`;
|
|
3411
|
+
if (inferHookEventNameFromMalformedInput(rawInput) === "Stop") {
|
|
3412
|
+
return {
|
|
3413
|
+
decision: "block",
|
|
3414
|
+
reason,
|
|
3415
|
+
stopReason: "native_hook_stdin_parse_error",
|
|
3416
|
+
systemMessage,
|
|
3417
|
+
};
|
|
3418
|
+
}
|
|
3419
|
+
return {
|
|
3420
|
+
continue: false,
|
|
3421
|
+
stopReason: "native_hook_stdin_parse_error",
|
|
3422
|
+
systemMessage,
|
|
3423
|
+
};
|
|
3424
|
+
}
|
|
3425
|
+
async function buildOversizedStopActiveWorkflowOutput(cwd) {
|
|
3426
|
+
const currentSession = await readUsableSessionState(cwd);
|
|
3427
|
+
const currentSessionId = safeString(currentSession?.session_id).trim()
|
|
3428
|
+
|| safeString(process.env.OMX_SESSION_ID || process.env.CODEX_SESSION_ID).trim();
|
|
3429
|
+
if (!currentSessionId)
|
|
3430
|
+
return null;
|
|
3431
|
+
if (await readCanonicalTerminalRunStateForStop(cwd, currentSessionId, "autopilot"))
|
|
3432
|
+
return null;
|
|
3433
|
+
const autopilotState = await readModeStateForActiveDecision("autopilot", currentSessionId, cwd);
|
|
3434
|
+
if (!autopilotState || !shouldContinueRun(autopilotState))
|
|
3435
|
+
return null;
|
|
3436
|
+
const phase = formatPhase(autopilotState.current_phase);
|
|
3437
|
+
const reason = `OMX native Stop received oversized stdin before parsing while the current session has active OMX autopilot state (phase: ${phase}); continue once with a compact response or reduce hook payload size so normal Stop gates can run.`;
|
|
3438
|
+
return {
|
|
3439
|
+
decision: "block",
|
|
3440
|
+
reason,
|
|
3441
|
+
stopReason: "native_stop_stdin_oversized_active_workflow",
|
|
3442
|
+
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.",
|
|
3443
|
+
};
|
|
3444
|
+
}
|
|
3445
|
+
async function buildOversizedStdinHookOutput(rawHookEventName, cwd) {
|
|
3446
|
+
if (rawHookEventName === "Stop") {
|
|
3447
|
+
return await buildOversizedStopActiveWorkflowOutput(cwd) ?? {};
|
|
3448
|
+
}
|
|
3449
|
+
const systemMessage = `OMX native hook rejected oversized stdin JSON before parsing; maxBytes=${MAX_NATIVE_STDIN_JSON_BYTES}.`;
|
|
3450
|
+
return {
|
|
3451
|
+
continue: false,
|
|
3452
|
+
stopReason: "native_hook_stdin_oversized",
|
|
3453
|
+
systemMessage,
|
|
3454
|
+
};
|
|
3455
|
+
}
|
|
3152
3456
|
function writeNativeHookJsonStdout(output) {
|
|
3153
3457
|
process.stdout.write(`${JSON.stringify(output)}\n`);
|
|
3154
3458
|
}
|
|
3155
|
-
|
|
3459
|
+
function redactMalformedHookPreview(rawInput) {
|
|
3460
|
+
const withoutControls = rawInput.replace(/[\u0000-\u001f\u007f-\u009f]/g, "");
|
|
3461
|
+
const withoutAuthSecrets = redactAuthSecrets(withoutControls);
|
|
3462
|
+
return withoutAuthSecrets
|
|
3463
|
+
.replace(/(["']?(?:prompt|user_prompt|input|text)["']?\s*:\s*)(["'])(?:\\.|(?!\2)[^\\])*\2/gi, "$1$2[REDACTED]$2")
|
|
3464
|
+
.replace(/(["']?(?:prompt|user_prompt|input|text)["']?\s*:\s*)(["'])(?:\\.|[^\\])*$/gi, "$1$2[REDACTED]$2")
|
|
3465
|
+
.replace(/(["']?(?:prompt|user_prompt|input|text)["']?\s*:\s*)(?!["'])[^,}]*/gi, "$1[REDACTED]");
|
|
3466
|
+
}
|
|
3467
|
+
function buildRawInputLogFields(rawInput) {
|
|
3468
|
+
if (!rawInput)
|
|
3469
|
+
return {};
|
|
3470
|
+
return {
|
|
3471
|
+
raw_input_length: Buffer.byteLength(rawInput, "utf-8"),
|
|
3472
|
+
raw_input_prefix: redactMalformedHookPreview(rawInput).slice(0, 240),
|
|
3473
|
+
};
|
|
3474
|
+
}
|
|
3475
|
+
async function logNativeHookCliError(cwd, type, error, payload = {}, details = {}) {
|
|
3156
3476
|
const logsDir = join(cwd || process.cwd(), ".omx", "logs");
|
|
3157
3477
|
await mkdir(logsDir, { recursive: true }).catch(() => { });
|
|
3158
3478
|
const logPath = join(logsDir, `native-hook-${new Date().toISOString().split("T")[0]}.jsonl`);
|
|
@@ -3164,6 +3484,7 @@ async function logNativeHookCliError(cwd, type, error, payload = {}) {
|
|
|
3164
3484
|
thread_id: readPayloadThreadId(payload) || undefined,
|
|
3165
3485
|
turn_id: readPayloadTurnId(payload) || undefined,
|
|
3166
3486
|
error: error instanceof Error ? error.message : String(error),
|
|
3487
|
+
...details,
|
|
3167
3488
|
}) + "\n").catch(() => { });
|
|
3168
3489
|
}
|
|
3169
3490
|
function isStopDispatchFailureTestTrigger(payload) {
|
|
@@ -3186,17 +3507,14 @@ function buildStopDispatchFailureOutput(error) {
|
|
|
3186
3507
|
};
|
|
3187
3508
|
}
|
|
3188
3509
|
export async function runCodexNativeHookCli() {
|
|
3189
|
-
const { payload, parseError } = await readStdinJson();
|
|
3510
|
+
const { payload, parseError, rawInput, oversized, rawHookEventName } = await readStdinJson();
|
|
3511
|
+
if (oversized) {
|
|
3512
|
+
writeNativeHookJsonStdout(await buildOversizedStdinHookOutput(rawHookEventName, process.cwd()));
|
|
3513
|
+
return;
|
|
3514
|
+
}
|
|
3190
3515
|
if (parseError) {
|
|
3191
|
-
await logNativeHookCliError(process.cwd(), "native_hook_stdin_parse_error", parseError);
|
|
3192
|
-
writeNativeHookJsonStdout(
|
|
3193
|
-
decision: "block",
|
|
3194
|
-
reason: "OMX native hook received malformed JSON input. Preserve runtime state, inspect the emitting hook payload yourself, and retry with valid JSON.",
|
|
3195
|
-
hookSpecificOutput: {
|
|
3196
|
-
hookEventName: "Unknown",
|
|
3197
|
-
additionalContext: `stdin JSON parsing failed inside codex-native-hook: ${parseError.message}. Emit valid JSON from the native hook caller before retrying.`,
|
|
3198
|
-
},
|
|
3199
|
-
});
|
|
3516
|
+
await logNativeHookCliError(process.cwd(), "native_hook_stdin_parse_error", parseError, {}, buildRawInputLogFields(rawInput));
|
|
3517
|
+
writeNativeHookJsonStdout(buildMalformedStdinHookOutput(parseError, rawInput));
|
|
3200
3518
|
return;
|
|
3201
3519
|
}
|
|
3202
3520
|
try {
|