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,6 +4,7 @@ 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 {
|
|
8
9
|
SKILL_ACTIVE_STATE_FILE,
|
|
9
10
|
extractSessionIdFromInitializedStatePath,
|
|
@@ -14,6 +15,7 @@ import {
|
|
|
14
15
|
type SkillActiveStateLike,
|
|
15
16
|
} from "../state/skill-active.js";
|
|
16
17
|
import {
|
|
18
|
+
isTrustedSubagentThread,
|
|
17
19
|
readSubagentSessionSummary,
|
|
18
20
|
readSubagentTrackingState,
|
|
19
21
|
recordSubagentTurnForSession,
|
|
@@ -108,6 +110,10 @@ import {
|
|
|
108
110
|
isFinalHandoffDocumentRefreshCandidate,
|
|
109
111
|
} from "../document-refresh/enforcer.js";
|
|
110
112
|
import { buildExecFollowupStopOutput } from "../exec/followup.js";
|
|
113
|
+
import {
|
|
114
|
+
MAX_NATIVE_STDIN_JSON_BYTES,
|
|
115
|
+
extractRawCodexHookEventName,
|
|
116
|
+
} from "./hook-payload-guard.js";
|
|
111
117
|
|
|
112
118
|
type CodexHookEventName =
|
|
113
119
|
| "SessionStart"
|
|
@@ -142,6 +148,7 @@ const ORDINARY_STOP_NO_PROGRESS_DEFAULT_MAX_REPEATS = 8;
|
|
|
142
148
|
const RALPH_ORPHANED_STARTING_STALE_MS = 15 * 60_000;
|
|
143
149
|
const ORDINARY_STOP_NO_PROGRESS_DEFAULT_IDLE_MS = 10 * 60_000;
|
|
144
150
|
const ORDINARY_STOP_NO_PROGRESS_MAX_MESSAGE_LENGTH = 240;
|
|
151
|
+
const OMX_OWNER_SESSION_ID_PATTERN = /^omx-[A-Za-z0-9_-]{1,60}$/;
|
|
145
152
|
const STABLE_FINAL_RECOMMENDATION_PATTERNS = [
|
|
146
153
|
/^\s*(?:launch|release|ship)-?ready\s*:\s*(?:yes|no)\b[^\n\r]*/im,
|
|
147
154
|
/^\s*ready to release\s*:\s*(?:yes|no)\b[^\n\r]*/im,
|
|
@@ -172,6 +179,16 @@ function safeObject(value: unknown): Record<string, unknown> {
|
|
|
172
179
|
return value && typeof value === "object" ? value as Record<string, unknown> : {};
|
|
173
180
|
}
|
|
174
181
|
|
|
182
|
+
function resolveHudReconcileSessionId(
|
|
183
|
+
currentSessionState: SessionState | null,
|
|
184
|
+
canonicalSessionId: string | null,
|
|
185
|
+
sessionIdForState: string | null,
|
|
186
|
+
): string | undefined {
|
|
187
|
+
const ownerOmxSessionId = safeString(currentSessionState?.owner_omx_session_id).trim();
|
|
188
|
+
if (OMX_OWNER_SESSION_ID_PATTERN.test(ownerOmxSessionId)) return ownerOmxSessionId;
|
|
189
|
+
return canonicalSessionId || sessionIdForState || undefined;
|
|
190
|
+
}
|
|
191
|
+
|
|
175
192
|
function safeContextSnippet(value: unknown, maxLength = 300): string {
|
|
176
193
|
const text = safeString(value).replace(/\s+/g, " ").trim();
|
|
177
194
|
if (text.length <= maxLength) return text;
|
|
@@ -248,18 +265,25 @@ async function recordNativeSubagentSessionStart(
|
|
|
248
265
|
metadata: NativeSubagentSessionStartMetadata,
|
|
249
266
|
transcriptPath: string,
|
|
250
267
|
): Promise<void> {
|
|
268
|
+
const parentThreadId = metadata.parentThreadId.trim();
|
|
269
|
+
const childThreadId = childSessionId.trim();
|
|
251
270
|
const trackingSessionIds = [...new Set([
|
|
252
271
|
canonicalSessionId.trim(),
|
|
253
|
-
|
|
272
|
+
parentThreadId,
|
|
254
273
|
].filter(Boolean))];
|
|
255
274
|
for (const sessionId of trackingSessionIds) {
|
|
275
|
+
if (parentThreadId && parentThreadId !== childThreadId) {
|
|
276
|
+
await recordSubagentTurnForSession(cwd, {
|
|
277
|
+
sessionId,
|
|
278
|
+
threadId: parentThreadId,
|
|
279
|
+
kind: 'leader',
|
|
280
|
+
}).catch(() => {});
|
|
281
|
+
}
|
|
256
282
|
await recordSubagentTurnForSession(cwd, {
|
|
257
283
|
sessionId,
|
|
258
|
-
threadId:
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
sessionId,
|
|
262
|
-
threadId: childSessionId,
|
|
284
|
+
threadId: childThreadId,
|
|
285
|
+
kind: 'subagent',
|
|
286
|
+
...(parentThreadId && parentThreadId !== childThreadId ? { leaderThreadId: parentThreadId } : {}),
|
|
263
287
|
mode: metadata.agentRole,
|
|
264
288
|
}).catch(() => {});
|
|
265
289
|
}
|
|
@@ -301,17 +325,54 @@ async function isNativeSubagentHook(
|
|
|
301
325
|
canonicalSessionId: string,
|
|
302
326
|
nativeSessionId: string,
|
|
303
327
|
threadId: string,
|
|
328
|
+
canonicalLeaderNativeSessionId = "",
|
|
304
329
|
): Promise<boolean> {
|
|
305
|
-
const
|
|
330
|
+
const nativeId = nativeSessionId.trim();
|
|
331
|
+
const promptThreadId = threadId.trim();
|
|
332
|
+
const candidateIds = [nativeId, promptThreadId]
|
|
306
333
|
.map((value) => value.trim())
|
|
307
334
|
.filter(Boolean);
|
|
308
335
|
if (candidateIds.length === 0) return false;
|
|
309
336
|
|
|
310
337
|
const sessionId = canonicalSessionId.trim();
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
338
|
+
const currentLeaderNativeSessionId = canonicalLeaderNativeSessionId.trim();
|
|
339
|
+
const summary = sessionId
|
|
340
|
+
? await readSubagentSessionSummary(cwd, sessionId).catch(() => null)
|
|
341
|
+
: null;
|
|
342
|
+
const currentLeaderIds = new Set([
|
|
343
|
+
currentLeaderNativeSessionId,
|
|
344
|
+
summary?.leaderThreadId?.trim(),
|
|
345
|
+
].filter(Boolean));
|
|
346
|
+
if (
|
|
347
|
+
summary
|
|
348
|
+
&& candidateIds.some((id) => !currentLeaderIds.has(id) && summary.allSubagentThreadIds.includes(id))
|
|
349
|
+
) {
|
|
350
|
+
return true;
|
|
351
|
+
}
|
|
352
|
+
// Native UserPromptSubmit can carry a per-turn thread_id that differs from
|
|
353
|
+
// the long-lived native session id. Treat the current canonical native
|
|
354
|
+
// session as the leader before consulting stale/global tracker state.
|
|
355
|
+
if (
|
|
356
|
+
sessionId
|
|
357
|
+
&& currentLeaderNativeSessionId
|
|
358
|
+
&& (
|
|
359
|
+
nativeId === currentLeaderNativeSessionId
|
|
360
|
+
|| (!nativeId && promptThreadId === currentLeaderNativeSessionId)
|
|
361
|
+
)
|
|
362
|
+
) {
|
|
363
|
+
return false;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (summary) {
|
|
367
|
+
const leaderThreadId = summary.leaderThreadId?.trim();
|
|
368
|
+
if (
|
|
369
|
+
leaderThreadId
|
|
370
|
+
&& (
|
|
371
|
+
nativeId === leaderThreadId
|
|
372
|
+
|| (!nativeId && promptThreadId === leaderThreadId)
|
|
373
|
+
)
|
|
374
|
+
) {
|
|
375
|
+
return false;
|
|
315
376
|
}
|
|
316
377
|
}
|
|
317
378
|
|
|
@@ -326,7 +387,7 @@ async function isNativeSubagentHook(
|
|
|
326
387
|
if (!trackingState) return false;
|
|
327
388
|
|
|
328
389
|
return Object.values(trackingState.sessions).some((session) => (
|
|
329
|
-
candidateIds.some((id) => session
|
|
390
|
+
candidateIds.some((id) => isTrustedSubagentThread(session, id))
|
|
330
391
|
));
|
|
331
392
|
}
|
|
332
393
|
|
|
@@ -1702,15 +1763,23 @@ function buildSkillStateCliInstruction(mode: string, statePath: string): string
|
|
|
1702
1763
|
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.`;
|
|
1703
1764
|
}
|
|
1704
1765
|
|
|
1705
|
-
function buildAutopilotPromptActivationNote(
|
|
1766
|
+
function buildAutopilotPromptActivationNote(
|
|
1767
|
+
skillState?: SkillActiveState | null,
|
|
1768
|
+
options: { markedQuestionAnswer?: boolean } = {},
|
|
1769
|
+
): string | null {
|
|
1706
1770
|
if (skillState?.initialized_mode !== "autopilot") return null;
|
|
1707
1771
|
return [
|
|
1708
1772
|
"Autopilot protocol: the durable default chain is $deep-interview -> $ralplan -> $ultragoal (+ $team if needed) -> $code-review -> $ultraqa (deep-interview -> ralplan -> ultragoal -> code-review -> ultraqa).",
|
|
1709
1773
|
"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.",
|
|
1774
|
+
"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.",
|
|
1775
|
+
options.markedQuestionAnswer
|
|
1776
|
+
? "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."
|
|
1777
|
+
: null,
|
|
1778
|
+
"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.",
|
|
1710
1779
|
"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.",
|
|
1711
1780
|
"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.",
|
|
1712
1781
|
"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.",
|
|
1713
|
-
].join(" ");
|
|
1782
|
+
].filter(Boolean).join(" ");
|
|
1714
1783
|
}
|
|
1715
1784
|
|
|
1716
1785
|
function buildAdditionalContextMessage(
|
|
@@ -1730,6 +1799,8 @@ function buildAdditionalContextMessage(
|
|
|
1730
1799
|
? buildDeepInterviewQuestionBridgeInstruction(cwd, payload)
|
|
1731
1800
|
: null;
|
|
1732
1801
|
const deepInterviewConfigPromptActivationNote = buildDeepInterviewConfigInstruction(cwd, skillState);
|
|
1802
|
+
const markedQuestionAnswer = /^\s*\[omx question answered\]/i.test(prompt);
|
|
1803
|
+
const autopilotPromptActivationNote = buildAutopilotPromptActivationNote(skillState, { markedQuestionAnswer });
|
|
1733
1804
|
return [
|
|
1734
1805
|
`OMX native UserPromptSubmit continued active workflow skill "${continuedSkill}".`,
|
|
1735
1806
|
promptPriorityMessage,
|
|
@@ -1738,12 +1809,36 @@ function buildAdditionalContextMessage(
|
|
|
1738
1809
|
: null,
|
|
1739
1810
|
deepInterviewPromptActivationNote,
|
|
1740
1811
|
deepInterviewConfigPromptActivationNote,
|
|
1812
|
+
autopilotPromptActivationNote,
|
|
1741
1813
|
"Follow AGENTS.md routing and preserve workflow transition and planning-safety rules.",
|
|
1742
1814
|
].filter(Boolean).join(" ");
|
|
1743
1815
|
}
|
|
1744
1816
|
const detectedKeywordMessage = matches.length > 1
|
|
1745
1817
|
? `OMX native UserPromptSubmit detected workflow keywords ${matches.map((entry) => `"${entry.keyword}" -> ${entry.skill}`).join(", ")}.`
|
|
1746
1818
|
: `OMX native UserPromptSubmit detected workflow keyword "${match.keyword}" -> ${match.skill}.`;
|
|
1819
|
+
const continuedSkill = safeString(skillState?.skill).trim();
|
|
1820
|
+
if (
|
|
1821
|
+
continuedSkill
|
|
1822
|
+
&& continuedSkill !== match.skill
|
|
1823
|
+
&& /^\s*\[omx question answered\]/i.test(prompt)
|
|
1824
|
+
) {
|
|
1825
|
+
const deepInterviewPromptActivationNote = skillState?.initialized_mode === "deep-interview"
|
|
1826
|
+
? buildDeepInterviewQuestionBridgeInstruction(cwd, payload)
|
|
1827
|
+
: null;
|
|
1828
|
+
const deepInterviewConfigPromptActivationNote = buildDeepInterviewConfigInstruction(cwd, skillState);
|
|
1829
|
+
const autopilotPromptActivationNote = buildAutopilotPromptActivationNote(skillState, { markedQuestionAnswer: true });
|
|
1830
|
+
return [
|
|
1831
|
+
`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.`,
|
|
1832
|
+
promptPriorityMessage,
|
|
1833
|
+
skillState?.initialized_mode && skillState.initialized_state_path
|
|
1834
|
+
? buildSkillStateCliInstruction(skillState.initialized_mode, skillState.initialized_state_path)
|
|
1835
|
+
: null,
|
|
1836
|
+
deepInterviewPromptActivationNote,
|
|
1837
|
+
deepInterviewConfigPromptActivationNote,
|
|
1838
|
+
autopilotPromptActivationNote,
|
|
1839
|
+
"Follow AGENTS.md routing and preserve workflow transition and planning-safety rules.",
|
|
1840
|
+
].filter(Boolean).join(" ");
|
|
1841
|
+
}
|
|
1747
1842
|
const activeSkills = Array.isArray(skillState?.active_skills)
|
|
1748
1843
|
? skillState.active_skills.map((entry) => entry.skill)
|
|
1749
1844
|
: [];
|
|
@@ -2400,14 +2495,34 @@ const DEEP_INTERVIEW_ALLOWED_WRITE_PREFIXES = [
|
|
|
2400
2495
|
".omx/state",
|
|
2401
2496
|
] as const;
|
|
2402
2497
|
|
|
2403
|
-
const
|
|
2498
|
+
const RALPLAN_ALLOWED_WRITE_PREFIXES = [
|
|
2499
|
+
".omx/context",
|
|
2500
|
+
".omx/plans",
|
|
2501
|
+
".omx/specs",
|
|
2502
|
+
".omx/state",
|
|
2503
|
+
] as const;
|
|
2504
|
+
|
|
2505
|
+
const PLANNING_MODE_IMPLEMENTATION_TOOL_NAMES = new Set([
|
|
2404
2506
|
"Write",
|
|
2405
2507
|
"Edit",
|
|
2406
2508
|
"MultiEdit",
|
|
2509
|
+
"NotebookEdit",
|
|
2407
2510
|
"apply_patch",
|
|
2408
2511
|
"ApplyPatch",
|
|
2409
2512
|
]);
|
|
2410
2513
|
|
|
2514
|
+
const DEEP_INTERVIEW_IMPLEMENTATION_TOOL_NAMES = PLANNING_MODE_IMPLEMENTATION_TOOL_NAMES;
|
|
2515
|
+
|
|
2516
|
+
const RALPLAN_EXECUTION_HANDOFF_SKILLS = new Set([
|
|
2517
|
+
"autopilot",
|
|
2518
|
+
"autoresearch",
|
|
2519
|
+
"ralph",
|
|
2520
|
+
"team",
|
|
2521
|
+
"ultragoal",
|
|
2522
|
+
"ultrawork",
|
|
2523
|
+
"ultraqa",
|
|
2524
|
+
]);
|
|
2525
|
+
|
|
2411
2526
|
function isActiveDeepInterviewPhase(state: Record<string, unknown> | null): boolean {
|
|
2412
2527
|
if (!state || state.active !== true) return false;
|
|
2413
2528
|
const mode = safeString(state.mode).trim();
|
|
@@ -2417,7 +2532,39 @@ function isActiveDeepInterviewPhase(state: Record<string, unknown> | null): bool
|
|
|
2417
2532
|
return true;
|
|
2418
2533
|
}
|
|
2419
2534
|
|
|
2420
|
-
function
|
|
2535
|
+
function isActiveRalplanPhase(state: Record<string, unknown> | null): boolean {
|
|
2536
|
+
if (!state || state.active !== true) return false;
|
|
2537
|
+
const mode = safeString(state.mode).trim();
|
|
2538
|
+
if (mode && mode !== "ralplan") return false;
|
|
2539
|
+
const phase = safeString(state.current_phase ?? state.currentPhase).trim().toLowerCase();
|
|
2540
|
+
if (phase && (TERMINAL_MODE_PHASES.has(phase) || phase === "completing")) return false;
|
|
2541
|
+
return true;
|
|
2542
|
+
}
|
|
2543
|
+
|
|
2544
|
+
function isActiveAutopilotRalplanPhase(state: Record<string, unknown> | null): boolean {
|
|
2545
|
+
if (!state || state.active !== true) return false;
|
|
2546
|
+
const mode = safeString(state.mode).trim();
|
|
2547
|
+
if (mode && mode !== "autopilot") return false;
|
|
2548
|
+
const phase = safeString(state.current_phase ?? state.currentPhase).trim().toLowerCase();
|
|
2549
|
+
return phase === "ralplan";
|
|
2550
|
+
}
|
|
2551
|
+
|
|
2552
|
+
function hasExplicitExecutionHandoffSkill(
|
|
2553
|
+
state: SkillActiveStateLike | null,
|
|
2554
|
+
sessionId: string,
|
|
2555
|
+
threadId: string,
|
|
2556
|
+
): boolean {
|
|
2557
|
+
return listActiveSkills(state ?? {}).some((entry) => (
|
|
2558
|
+
RALPLAN_EXECUTION_HANDOFF_SKILLS.has(entry.skill)
|
|
2559
|
+
&& matchesSkillStopContext(entry, state ?? {}, sessionId, threadId)
|
|
2560
|
+
));
|
|
2561
|
+
}
|
|
2562
|
+
|
|
2563
|
+
function isAllowedPlanningArtifactPath(
|
|
2564
|
+
cwd: string,
|
|
2565
|
+
rawPath: string,
|
|
2566
|
+
allowedPrefixes: readonly string[],
|
|
2567
|
+
): boolean {
|
|
2421
2568
|
const trimmed = rawPath.trim().replace(/^['"]|['"]$/g, "");
|
|
2422
2569
|
if (!trimmed || trimmed.includes("\0")) return false;
|
|
2423
2570
|
let relativePath: string;
|
|
@@ -2428,11 +2575,19 @@ function isAllowedDeepInterviewArtifactPath(cwd: string, rawPath: string): boole
|
|
|
2428
2575
|
return false;
|
|
2429
2576
|
}
|
|
2430
2577
|
if (!relativePath || relativePath.startsWith("..") || relativePath.startsWith("/")) return false;
|
|
2431
|
-
return
|
|
2578
|
+
return allowedPrefixes.some((prefix) => (
|
|
2432
2579
|
relativePath === prefix || relativePath.startsWith(`${prefix}/`)
|
|
2433
2580
|
));
|
|
2434
2581
|
}
|
|
2435
2582
|
|
|
2583
|
+
function isAllowedDeepInterviewArtifactPath(cwd: string, rawPath: string): boolean {
|
|
2584
|
+
return isAllowedPlanningArtifactPath(cwd, rawPath, DEEP_INTERVIEW_ALLOWED_WRITE_PREFIXES);
|
|
2585
|
+
}
|
|
2586
|
+
|
|
2587
|
+
function isAllowedRalplanArtifactPath(cwd: string, rawPath: string): boolean {
|
|
2588
|
+
return isAllowedPlanningArtifactPath(cwd, rawPath, RALPLAN_ALLOWED_WRITE_PREFIXES);
|
|
2589
|
+
}
|
|
2590
|
+
|
|
2436
2591
|
function readPreToolUseCommand(payload: CodexHookPayload): string {
|
|
2437
2592
|
const toolInput = safeObject(payload.tool_input);
|
|
2438
2593
|
return safeString(toolInput.command).trim();
|
|
@@ -2502,6 +2657,86 @@ async function readActiveDeepInterviewStateForPreToolUse(
|
|
|
2502
2657
|
return hasActiveDeepInterviewSkill ? modeState : null;
|
|
2503
2658
|
}
|
|
2504
2659
|
|
|
2660
|
+
async function readActiveRalplanStateForPreToolUse(
|
|
2661
|
+
cwd: string,
|
|
2662
|
+
stateDir: string,
|
|
2663
|
+
sessionId: string,
|
|
2664
|
+
threadId: string,
|
|
2665
|
+
): Promise<Record<string, unknown> | null> {
|
|
2666
|
+
const modeState = sessionId
|
|
2667
|
+
? await readStopSessionPinnedState("ralplan-state.json", cwd, sessionId, stateDir)
|
|
2668
|
+
: await readJsonIfExists(join(stateDir, "ralplan-state.json"));
|
|
2669
|
+
const canonicalState = sessionId
|
|
2670
|
+
? await readVisibleSkillActiveStateForStateDir(stateDir, sessionId)
|
|
2671
|
+
: await readSkillActiveState(join(stateDir, SKILL_ACTIVE_STATE_FILE));
|
|
2672
|
+
if (isActiveRalplanPhase(modeState) && modeState && modeStateMatchesSkillStopContext(modeState, cwd, sessionId)) {
|
|
2673
|
+
if (hasExplicitExecutionHandoffSkill(canonicalState, sessionId, threadId)) return null;
|
|
2674
|
+
if (!canonicalState) return modeState;
|
|
2675
|
+
const hasActiveRalplanSkill = listActiveSkills(canonicalState).some((entry) => (
|
|
2676
|
+
entry.skill === "ralplan"
|
|
2677
|
+
&& matchesSkillStopContext(entry, canonicalState, sessionId, threadId)
|
|
2678
|
+
));
|
|
2679
|
+
if (hasActiveRalplanSkill) return modeState;
|
|
2680
|
+
}
|
|
2681
|
+
|
|
2682
|
+
const autopilotState = sessionId
|
|
2683
|
+
? await readStopSessionPinnedState("autopilot-state.json", cwd, sessionId, stateDir)
|
|
2684
|
+
: await readJsonIfExists(join(stateDir, "autopilot-state.json"));
|
|
2685
|
+
if (!isActiveAutopilotRalplanPhase(autopilotState) || !autopilotState) return null;
|
|
2686
|
+
if (!modeStateMatchesSkillStopContext(autopilotState, cwd, sessionId)) return null;
|
|
2687
|
+
const terminalAutopilotRunState = await readCanonicalTerminalRunStateForStop(cwd, sessionId, "autopilot");
|
|
2688
|
+
if (terminalAutopilotRunState) return null;
|
|
2689
|
+
if (!canonicalState) return autopilotState;
|
|
2690
|
+
const hasActiveAutopilotSkill = listActiveSkills(canonicalState).some((entry) => (
|
|
2691
|
+
entry.skill === "autopilot"
|
|
2692
|
+
&& matchesSkillStopContext(entry, canonicalState, sessionId, threadId)
|
|
2693
|
+
));
|
|
2694
|
+
return hasActiveAutopilotSkill ? autopilotState : null;
|
|
2695
|
+
}
|
|
2696
|
+
|
|
2697
|
+
function isAllowedRalplanBashWrite(cwd: string, command: string): boolean {
|
|
2698
|
+
if (!commandHasDeepInterviewWriteIntent(command)) return true;
|
|
2699
|
+
if (/\bomx\s+(?:state\s+(?:write|read|clear)|question)\b/.test(command)) return true;
|
|
2700
|
+
const targets = extractDeepInterviewCommandWriteTargets(command);
|
|
2701
|
+
return targets.length > 0 && targets.every((target) => isAllowedRalplanArtifactPath(cwd, target));
|
|
2702
|
+
}
|
|
2703
|
+
|
|
2704
|
+
async function buildRalplanPreToolUseBoundaryOutput(
|
|
2705
|
+
payload: CodexHookPayload,
|
|
2706
|
+
cwd: string,
|
|
2707
|
+
stateDir: string,
|
|
2708
|
+
): Promise<Record<string, unknown> | null> {
|
|
2709
|
+
const sessionId = readPayloadSessionId(payload);
|
|
2710
|
+
const threadId = readPayloadThreadId(payload);
|
|
2711
|
+
const activeState = await readActiveRalplanStateForPreToolUse(cwd, stateDir, sessionId, threadId);
|
|
2712
|
+
if (!activeState) return null;
|
|
2713
|
+
|
|
2714
|
+
const toolName = safeString(payload.tool_name).trim();
|
|
2715
|
+
const command = readPreToolUseCommand(payload);
|
|
2716
|
+
const pathCandidates = readPreToolUsePathCandidates(payload);
|
|
2717
|
+
let blocked = false;
|
|
2718
|
+
|
|
2719
|
+
if (toolName === "Bash") {
|
|
2720
|
+
blocked = !isAllowedRalplanBashWrite(cwd, command);
|
|
2721
|
+
} else if (PLANNING_MODE_IMPLEMENTATION_TOOL_NAMES.has(toolName)) {
|
|
2722
|
+
blocked = pathCandidates.length === 0
|
|
2723
|
+
|| !pathCandidates.every((candidate) => isAllowedRalplanArtifactPath(cwd, candidate));
|
|
2724
|
+
}
|
|
2725
|
+
|
|
2726
|
+
if (!blocked) return null;
|
|
2727
|
+
|
|
2728
|
+
const phase = formatPhase(activeState.current_phase ?? activeState.currentPhase, "planning");
|
|
2729
|
+
return {
|
|
2730
|
+
decision: "block",
|
|
2731
|
+
reason: `Ralplan is active (phase: ${phase}); implementation/write tools are blocked until an explicit execution handoff workflow is activated.`,
|
|
2732
|
+
hookSpecificOutput: {
|
|
2733
|
+
hookEventName: "PreToolUse",
|
|
2734
|
+
additionalContext:
|
|
2735
|
+
"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.",
|
|
2736
|
+
},
|
|
2737
|
+
};
|
|
2738
|
+
}
|
|
2739
|
+
|
|
2505
2740
|
async function buildDeepInterviewPreToolUseBoundaryOutput(
|
|
2506
2741
|
payload: CodexHookPayload,
|
|
2507
2742
|
cwd: string,
|
|
@@ -2827,15 +3062,15 @@ function buildRalplanContinuationStatus(
|
|
|
2827
3062
|
}
|
|
2828
3063
|
|
|
2829
3064
|
const completeHint = blocker.planningComplete
|
|
2830
|
-
? " The planning artifacts are present; if consensus is approved, emit
|
|
3065
|
+
? " 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."
|
|
2831
3066
|
: "";
|
|
2832
3067
|
|
|
2833
3068
|
return {
|
|
2834
3069
|
reason:
|
|
2835
|
-
`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}`,
|
|
3070
|
+
`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}`,
|
|
2836
3071
|
stopReasonSuffix: "continue_artifact",
|
|
2837
3072
|
systemMessage:
|
|
2838
|
-
`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.`,
|
|
3073
|
+
`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.`,
|
|
2839
3074
|
};
|
|
2840
3075
|
}
|
|
2841
3076
|
|
|
@@ -3732,6 +3967,14 @@ export async function dispatchCodexNativeHook(
|
|
|
3732
3967
|
): Promise<NativeHookDispatchResult> {
|
|
3733
3968
|
const hookEventName = readHookEventName(payload);
|
|
3734
3969
|
const cwd = options.cwd ?? (safeString(payload.cwd).trim() || process.cwd());
|
|
3970
|
+
if (hookEventName === "Stop" && !hasNativeStopRuntimeSurface(cwd)) {
|
|
3971
|
+
return {
|
|
3972
|
+
hookEventName,
|
|
3973
|
+
omxEventName: mapCodexHookEventToOmxEvent(hookEventName),
|
|
3974
|
+
skillState: null,
|
|
3975
|
+
outputJson: null,
|
|
3976
|
+
};
|
|
3977
|
+
}
|
|
3735
3978
|
// Native hooks must use the same authoritative runtime state root as HUD/MCP
|
|
3736
3979
|
// when boxed/team roots are active; do not bypass it with cwd/.omx/state.
|
|
3737
3980
|
const stateDir = getBaseStateDir(cwd);
|
|
@@ -3814,7 +4057,13 @@ export async function dispatchCodexNativeHook(
|
|
|
3814
4057
|
const sessionIdForState = canonicalSessionId || nativeSessionId;
|
|
3815
4058
|
let outputJson: Record<string, unknown> | null = null;
|
|
3816
4059
|
const isSubagentPromptSubmit = hookEventName === "UserPromptSubmit"
|
|
3817
|
-
? await isNativeSubagentHook(
|
|
4060
|
+
? await isNativeSubagentHook(
|
|
4061
|
+
cwd,
|
|
4062
|
+
canonicalSessionId,
|
|
4063
|
+
nativeSessionId,
|
|
4064
|
+
threadId,
|
|
4065
|
+
safeString(currentSessionState?.native_session_id).trim(),
|
|
4066
|
+
)
|
|
3818
4067
|
: false;
|
|
3819
4068
|
const isSubagentStop = hookEventName === "Stop"
|
|
3820
4069
|
? (await Promise.all(
|
|
@@ -3822,7 +4071,15 @@ export async function dispatchCodexNativeHook(
|
|
|
3822
4071
|
canonicalSessionId,
|
|
3823
4072
|
safeString(currentSessionState?.session_id).trim(),
|
|
3824
4073
|
].filter(Boolean))]
|
|
3825
|
-
.map((candidateSessionId) => isNativeSubagentHook(
|
|
4074
|
+
.map((candidateSessionId) => isNativeSubagentHook(
|
|
4075
|
+
cwd,
|
|
4076
|
+
candidateSessionId,
|
|
4077
|
+
nativeSessionId,
|
|
4078
|
+
threadId,
|
|
4079
|
+
candidateSessionId === safeString(currentSessionState?.session_id).trim()
|
|
4080
|
+
? safeString(currentSessionState?.native_session_id).trim()
|
|
4081
|
+
: "",
|
|
4082
|
+
)),
|
|
3826
4083
|
)).some(Boolean)
|
|
3827
4084
|
: false;
|
|
3828
4085
|
const suppressNoisySubagentLifecycleDispatch =
|
|
@@ -3926,7 +4183,12 @@ export async function dispatchCodexNativeHook(
|
|
|
3926
4183
|
&& await isConfirmedTeamWorkerPromptSubmitPane(cwd).catch(() => false);
|
|
3927
4184
|
if (!skipHudReconcileForTeamWorkerPane) {
|
|
3928
4185
|
const reconcileHudForPromptSubmitFn = options.reconcileHudForPromptSubmitFn ?? reconcileHudForPromptSubmit;
|
|
3929
|
-
|
|
4186
|
+
const hudSessionId = resolveHudReconcileSessionId(
|
|
4187
|
+
currentSessionState,
|
|
4188
|
+
canonicalSessionId,
|
|
4189
|
+
sessionIdForState,
|
|
4190
|
+
);
|
|
4191
|
+
await reconcileHudForPromptSubmitFn(cwd, { sessionId: hudSessionId }).catch(() => {});
|
|
3930
4192
|
}
|
|
3931
4193
|
}
|
|
3932
4194
|
|
|
@@ -3987,6 +4249,7 @@ export async function dispatchCodexNativeHook(
|
|
|
3987
4249
|
}
|
|
3988
4250
|
} else if (hookEventName === "PreToolUse") {
|
|
3989
4251
|
outputJson = await buildDeepInterviewPreToolUseBoundaryOutput(payload, cwd, stateDir)
|
|
4252
|
+
?? await buildRalplanPreToolUseBoundaryOutput(payload, cwd, stateDir)
|
|
3990
4253
|
?? buildNativePreToolUseOutput(payload);
|
|
3991
4254
|
} else if (hookEventName === "PostToolUse") {
|
|
3992
4255
|
if (detectMcpTransportFailure(payload)) {
|
|
@@ -4008,9 +4271,31 @@ export async function dispatchCodexNativeHook(
|
|
|
4008
4271
|
};
|
|
4009
4272
|
}
|
|
4010
4273
|
|
|
4274
|
+
function hasNativeStopRuntimeSurface(cwd: string): boolean {
|
|
4275
|
+
if (existsSync(join(cwd, ".omx"))) return true;
|
|
4276
|
+
if (findGitLayout(cwd)) return true;
|
|
4277
|
+
const omxRoot = safeString(process.env.OMX_ROOT).trim();
|
|
4278
|
+
if (omxRoot && existsSync(join(omxRoot, ".omx"))) return true;
|
|
4279
|
+
const stateRoot = safeString(process.env.OMX_STATE_ROOT).trim();
|
|
4280
|
+
if (stateRoot && existsSync(stateRoot)) return true;
|
|
4281
|
+
return [
|
|
4282
|
+
process.env.OMX_SESSION_ID,
|
|
4283
|
+
process.env.OMX_TEAM_INTERNAL_WORKER,
|
|
4284
|
+
process.env.OMX_TEAM_WORKER,
|
|
4285
|
+
process.env.OMX_TEAM_STATE_ROOT,
|
|
4286
|
+
process.env.OMX_TEAM_LEADER_CWD,
|
|
4287
|
+
process.env.OMX_NOTIFY_HOOK_TRUSTED_MANAGED_CWD,
|
|
4288
|
+
process.env.OMX_TMUX_HUD_OWNER,
|
|
4289
|
+
process.env.OMX_TMUX_HUD_LEADER_PANE,
|
|
4290
|
+
].some((value) => safeString(value).trim() !== "");
|
|
4291
|
+
}
|
|
4292
|
+
|
|
4011
4293
|
interface NativeHookCliReadResult {
|
|
4012
4294
|
payload: CodexHookPayload;
|
|
4013
4295
|
parseError: Error | null;
|
|
4296
|
+
rawInput: string;
|
|
4297
|
+
oversized: boolean;
|
|
4298
|
+
rawHookEventName: CodexHookEventName | null;
|
|
4014
4299
|
}
|
|
4015
4300
|
|
|
4016
4301
|
export function isCodexNativeHookMainModule(
|
|
@@ -4023,36 +4308,156 @@ export function isCodexNativeHookMainModule(
|
|
|
4023
4308
|
|
|
4024
4309
|
async function readStdinJson(): Promise<NativeHookCliReadResult> {
|
|
4025
4310
|
const chunks: Buffer[] = [];
|
|
4311
|
+
let totalBytes = 0;
|
|
4312
|
+
let oversized = false;
|
|
4026
4313
|
for await (const chunk of process.stdin) {
|
|
4027
|
-
|
|
4314
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
|
|
4315
|
+
totalBytes += buffer.byteLength;
|
|
4316
|
+
if (totalBytes > MAX_NATIVE_STDIN_JSON_BYTES) {
|
|
4317
|
+
const remaining = Math.max(0, MAX_NATIVE_STDIN_JSON_BYTES - (totalBytes - buffer.byteLength));
|
|
4318
|
+
if (remaining > 0) chunks.push(Buffer.from(buffer.subarray(0, remaining)));
|
|
4319
|
+
oversized = true;
|
|
4320
|
+
process.stdin.destroy();
|
|
4321
|
+
break;
|
|
4322
|
+
}
|
|
4323
|
+
chunks.push(buffer);
|
|
4028
4324
|
}
|
|
4029
4325
|
const raw = Buffer.concat(chunks).toString("utf-8").trim();
|
|
4326
|
+
const rawHookEventName = extractRawCodexHookEventName(raw);
|
|
4327
|
+
if (oversized) {
|
|
4328
|
+
return {
|
|
4329
|
+
payload: {},
|
|
4330
|
+
parseError: null,
|
|
4331
|
+
rawInput: raw,
|
|
4332
|
+
oversized: true,
|
|
4333
|
+
rawHookEventName,
|
|
4334
|
+
};
|
|
4335
|
+
}
|
|
4030
4336
|
if (!raw) {
|
|
4031
|
-
return { payload: {}, parseError: null };
|
|
4337
|
+
return { payload: {}, parseError: null, rawInput: raw, oversized: false, rawHookEventName };
|
|
4032
4338
|
}
|
|
4033
4339
|
|
|
4034
4340
|
try {
|
|
4035
4341
|
return {
|
|
4036
4342
|
payload: safeObject(JSON.parse(raw)),
|
|
4037
4343
|
parseError: null,
|
|
4344
|
+
rawInput: raw,
|
|
4345
|
+
oversized: false,
|
|
4346
|
+
rawHookEventName,
|
|
4038
4347
|
};
|
|
4039
4348
|
} catch (error) {
|
|
4040
4349
|
return {
|
|
4041
4350
|
payload: {},
|
|
4042
4351
|
parseError: error instanceof Error ? error : new Error(String(error)),
|
|
4352
|
+
rawInput: raw,
|
|
4353
|
+
oversized: false,
|
|
4354
|
+
rawHookEventName,
|
|
4043
4355
|
};
|
|
4044
4356
|
}
|
|
4045
4357
|
}
|
|
4046
4358
|
|
|
4359
|
+
function inferHookEventNameFromMalformedInput(raw: string): CodexHookEventName | null {
|
|
4360
|
+
const match = raw.match(/(?:\"|['"])?hook[_-]?event[_-]?name(?:\"|['"])?\s*:\s*(?:\"|['"])?(SessionStart|PreToolUse|PostToolUse|UserPromptSubmit|PreCompact|PostCompact|Stop)\b/i);
|
|
4361
|
+
const value = match?.[1];
|
|
4362
|
+
if (!value) return null;
|
|
4363
|
+
return readHookEventName({ hook_event_name: value });
|
|
4364
|
+
}
|
|
4365
|
+
|
|
4366
|
+
function buildMalformedStdinHookOutput(parseError: Error, rawInput: string): Record<string, unknown> {
|
|
4367
|
+
const reason =
|
|
4368
|
+
"OMX native hook received malformed JSON input. Preserve runtime state, inspect the emitting hook payload yourself, and retry with valid JSON.";
|
|
4369
|
+
const systemMessage =
|
|
4370
|
+
`${reason} stdin JSON parsing failed inside codex-native-hook: ${parseError.message}.`;
|
|
4371
|
+
if (inferHookEventNameFromMalformedInput(rawInput) === "Stop") {
|
|
4372
|
+
return {
|
|
4373
|
+
decision: "block",
|
|
4374
|
+
reason,
|
|
4375
|
+
stopReason: "native_hook_stdin_parse_error",
|
|
4376
|
+
systemMessage,
|
|
4377
|
+
};
|
|
4378
|
+
}
|
|
4379
|
+
return {
|
|
4380
|
+
continue: false,
|
|
4381
|
+
stopReason: "native_hook_stdin_parse_error",
|
|
4382
|
+
systemMessage,
|
|
4383
|
+
};
|
|
4384
|
+
}
|
|
4385
|
+
|
|
4386
|
+
async function buildOversizedStopActiveWorkflowOutput(cwd: string): Promise<Record<string, unknown> | null> {
|
|
4387
|
+
const currentSession = await readUsableSessionState(cwd);
|
|
4388
|
+
const currentSessionId = safeString(currentSession?.session_id).trim()
|
|
4389
|
+
|| safeString(process.env.OMX_SESSION_ID || process.env.CODEX_SESSION_ID).trim();
|
|
4390
|
+
if (!currentSessionId) return null;
|
|
4391
|
+
|
|
4392
|
+
if (await readCanonicalTerminalRunStateForStop(cwd, currentSessionId, "autopilot")) return null;
|
|
4393
|
+
|
|
4394
|
+
const autopilotState = await readModeStateForActiveDecision("autopilot", currentSessionId, cwd);
|
|
4395
|
+
if (!autopilotState || !shouldContinueRun(autopilotState)) return null;
|
|
4396
|
+
|
|
4397
|
+
const phase = formatPhase(autopilotState.current_phase);
|
|
4398
|
+
const reason =
|
|
4399
|
+
`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.`;
|
|
4400
|
+
return {
|
|
4401
|
+
decision: "block",
|
|
4402
|
+
reason,
|
|
4403
|
+
stopReason: "native_stop_stdin_oversized_active_workflow",
|
|
4404
|
+
systemMessage:
|
|
4405
|
+
"OMX native Stop rejected oversized stdin before parsing; active current-session workflow state is present, so Stop is blocked instead of silently allowing termination.",
|
|
4406
|
+
};
|
|
4407
|
+
}
|
|
4408
|
+
|
|
4409
|
+
async function buildOversizedStdinHookOutput(
|
|
4410
|
+
rawHookEventName: CodexHookEventName | null,
|
|
4411
|
+
cwd: string,
|
|
4412
|
+
): Promise<Record<string, unknown>> {
|
|
4413
|
+
if (rawHookEventName === "Stop") {
|
|
4414
|
+
return await buildOversizedStopActiveWorkflowOutput(cwd) ?? {};
|
|
4415
|
+
}
|
|
4416
|
+
const systemMessage =
|
|
4417
|
+
`OMX native hook rejected oversized stdin JSON before parsing; maxBytes=${MAX_NATIVE_STDIN_JSON_BYTES}.`;
|
|
4418
|
+
return {
|
|
4419
|
+
continue: false,
|
|
4420
|
+
stopReason: "native_hook_stdin_oversized",
|
|
4421
|
+
systemMessage,
|
|
4422
|
+
};
|
|
4423
|
+
}
|
|
4424
|
+
|
|
4047
4425
|
function writeNativeHookJsonStdout(output: Record<string, unknown>): void {
|
|
4048
4426
|
process.stdout.write(`${JSON.stringify(output)}\n`);
|
|
4049
4427
|
}
|
|
4050
4428
|
|
|
4429
|
+
function redactMalformedHookPreview(rawInput: string): string {
|
|
4430
|
+
const withoutControls = rawInput.replace(/[\u0000-\u001f\u007f-\u009f]/g, "");
|
|
4431
|
+
const withoutAuthSecrets = redactAuthSecrets(withoutControls);
|
|
4432
|
+
return withoutAuthSecrets
|
|
4433
|
+
.replace(
|
|
4434
|
+
/(["']?(?:prompt|user_prompt|input|text)["']?\s*:\s*)(["'])(?:\\.|(?!\2)[^\\])*\2/gi,
|
|
4435
|
+
"$1$2[REDACTED]$2",
|
|
4436
|
+
)
|
|
4437
|
+
.replace(
|
|
4438
|
+
/(["']?(?:prompt|user_prompt|input|text)["']?\s*:\s*)(["'])(?:\\.|[^\\])*$/gi,
|
|
4439
|
+
"$1$2[REDACTED]$2",
|
|
4440
|
+
)
|
|
4441
|
+
.replace(
|
|
4442
|
+
/(["']?(?:prompt|user_prompt|input|text)["']?\s*:\s*)(?!["'])[^,}]*/gi,
|
|
4443
|
+
"$1[REDACTED]",
|
|
4444
|
+
);
|
|
4445
|
+
}
|
|
4446
|
+
|
|
4447
|
+
function buildRawInputLogFields(rawInput: string): Record<string, unknown> {
|
|
4448
|
+
if (!rawInput) return {};
|
|
4449
|
+
return {
|
|
4450
|
+
raw_input_length: Buffer.byteLength(rawInput, "utf-8"),
|
|
4451
|
+
raw_input_prefix: redactMalformedHookPreview(rawInput).slice(0, 240),
|
|
4452
|
+
};
|
|
4453
|
+
}
|
|
4454
|
+
|
|
4051
4455
|
async function logNativeHookCliError(
|
|
4052
4456
|
cwd: string,
|
|
4053
4457
|
type: string,
|
|
4054
4458
|
error: unknown,
|
|
4055
4459
|
payload: CodexHookPayload = {},
|
|
4460
|
+
details: Record<string, unknown> = {},
|
|
4056
4461
|
): Promise<void> {
|
|
4057
4462
|
const logsDir = join(cwd || process.cwd(), ".omx", "logs");
|
|
4058
4463
|
await mkdir(logsDir, { recursive: true }).catch(() => {});
|
|
@@ -4067,6 +4472,7 @@ async function logNativeHookCliError(
|
|
|
4067
4472
|
thread_id: readPayloadThreadId(payload) || undefined,
|
|
4068
4473
|
turn_id: readPayloadTurnId(payload) || undefined,
|
|
4069
4474
|
error: error instanceof Error ? error.message : String(error),
|
|
4475
|
+
...details,
|
|
4070
4476
|
}) + "\n",
|
|
4071
4477
|
).catch(() => {});
|
|
4072
4478
|
}
|
|
@@ -4095,18 +4501,20 @@ function buildStopDispatchFailureOutput(error: unknown): Record<string, unknown>
|
|
|
4095
4501
|
}
|
|
4096
4502
|
|
|
4097
4503
|
export async function runCodexNativeHookCli(): Promise<void> {
|
|
4098
|
-
const { payload, parseError } = await readStdinJson();
|
|
4504
|
+
const { payload, parseError, rawInput, oversized, rawHookEventName } = await readStdinJson();
|
|
4505
|
+
if (oversized) {
|
|
4506
|
+
writeNativeHookJsonStdout(await buildOversizedStdinHookOutput(rawHookEventName, process.cwd()));
|
|
4507
|
+
return;
|
|
4508
|
+
}
|
|
4099
4509
|
if (parseError) {
|
|
4100
|
-
await logNativeHookCliError(
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
},
|
|
4109
|
-
});
|
|
4510
|
+
await logNativeHookCliError(
|
|
4511
|
+
process.cwd(),
|
|
4512
|
+
"native_hook_stdin_parse_error",
|
|
4513
|
+
parseError,
|
|
4514
|
+
{},
|
|
4515
|
+
buildRawInputLogFields(rawInput),
|
|
4516
|
+
);
|
|
4517
|
+
writeNativeHookJsonStdout(buildMalformedStdinHookOutput(parseError, rawInput));
|
|
4110
4518
|
return;
|
|
4111
4519
|
}
|
|
4112
4520
|
|