oh-my-codex 0.18.1 → 0.18.2
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 +4 -2
- package/dist/agents/__tests__/definitions.test.js +14 -0
- package/dist/agents/__tests__/definitions.test.js.map +1 -1
- package/dist/agents/__tests__/native-config.test.js +19 -0
- package/dist/agents/__tests__/native-config.test.js.map +1 -1
- package/dist/agents/definitions.d.ts.map +1 -1
- package/dist/agents/definitions.js +30 -0
- package/dist/agents/definitions.js.map +1 -1
- package/dist/agents/native-config.d.ts +1 -0
- package/dist/agents/native-config.d.ts.map +1 -1
- package/dist/agents/native-config.js +4 -0
- package/dist/agents/native-config.js.map +1 -1
- package/dist/catalog/__tests__/generator.test.js +4 -0
- package/dist/catalog/__tests__/generator.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-warning-copy.test.js +61 -5
- package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +161 -21
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/launch-fallback.test.js +51 -3
- package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
- package/dist/cli/__tests__/question.test.js +2 -2
- package/dist/cli/__tests__/question.test.js.map +1 -1
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +178 -7
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.d.ts +7 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +143 -43
- package/dist/cli/index.js.map +1 -1
- package/dist/config/__tests__/codex-hooks.test.js +3 -3
- package/dist/config/__tests__/codex-hooks.test.js.map +1 -1
- package/dist/config/codex-hooks.d.ts +1 -0
- package/dist/config/codex-hooks.d.ts.map +1 -1
- package/dist/config/codex-hooks.js +2 -4
- package/dist/config/codex-hooks.js.map +1 -1
- package/dist/config/generator.d.ts +14 -0
- package/dist/config/generator.d.ts.map +1 -1
- package/dist/config/generator.js +100 -1
- package/dist/config/generator.js.map +1 -1
- package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.js +21 -0
- package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.js.map +1 -1
- package/dist/goal-workflows/codex-goal-snapshot.d.ts +3 -0
- package/dist/goal-workflows/codex-goal-snapshot.d.ts.map +1 -1
- package/dist/goal-workflows/codex-goal-snapshot.js +45 -2
- package/dist/goal-workflows/codex-goal-snapshot.js.map +1 -1
- package/dist/hooks/__tests__/autopilot-skill-contract.test.js +17 -0
- package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/keyword-detector.test.js +170 -15
- package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
- package/dist/hooks/__tests__/prometheus-strict-contract.test.d.ts +2 -0
- package/dist/hooks/__tests__/prometheus-strict-contract.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/prometheus-strict-contract.test.js +320 -0
- package/dist/hooks/__tests__/prometheus-strict-contract.test.js.map +1 -0
- package/dist/hooks/__tests__/prompt-guidance-wave-two.test.js +12 -0
- package/dist/hooks/__tests__/prompt-guidance-wave-two.test.js.map +1 -1
- package/dist/hooks/__tests__/research-workflow-boundaries.test.d.ts +2 -0
- package/dist/hooks/__tests__/research-workflow-boundaries.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/research-workflow-boundaries.test.js +35 -0
- package/dist/hooks/__tests__/research-workflow-boundaries.test.js.map +1 -0
- package/dist/hooks/keyword-detector.d.ts +1 -1
- package/dist/hooks/keyword-detector.d.ts.map +1 -1
- package/dist/hooks/keyword-detector.js +28 -6
- 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/prompt-guidance-contract.d.ts.map +1 -1
- package/dist/hooks/prompt-guidance-contract.js +11 -0
- package/dist/hooks/prompt-guidance-contract.js.map +1 -1
- package/dist/hud/__tests__/hud-tmux-injection.test.js +22 -0
- package/dist/hud/__tests__/hud-tmux-injection.test.js.map +1 -1
- package/dist/hud/__tests__/reconcile.test.js +121 -10
- package/dist/hud/__tests__/reconcile.test.js.map +1 -1
- package/dist/hud/__tests__/render.test.js +84 -0
- package/dist/hud/__tests__/render.test.js.map +1 -1
- package/dist/hud/__tests__/state.test.js +51 -1
- package/dist/hud/__tests__/state.test.js.map +1 -1
- package/dist/hud/__tests__/tmux.test.js +69 -23
- package/dist/hud/__tests__/tmux.test.js.map +1 -1
- package/dist/hud/index.d.ts +1 -1
- package/dist/hud/index.d.ts.map +1 -1
- package/dist/hud/index.js +8 -3
- package/dist/hud/index.js.map +1 -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.map +1 -1
- package/dist/hud/render.js +26 -0
- package/dist/hud/render.js.map +1 -1
- package/dist/hud/state.d.ts +2 -1
- package/dist/hud/state.d.ts.map +1 -1
- package/dist/hud/state.js +62 -1
- package/dist/hud/state.js.map +1 -1
- package/dist/hud/tmux.d.ts +10 -3
- package/dist/hud/tmux.d.ts.map +1 -1
- package/dist/hud/tmux.js +59 -10
- package/dist/hud/tmux.js.map +1 -1
- package/dist/hud/types.d.ts +22 -0
- package/dist/hud/types.d.ts.map +1 -1
- package/dist/hud/types.js.map +1 -1
- package/dist/pipeline/__tests__/orchestrator.test.js +63 -1
- package/dist/pipeline/__tests__/orchestrator.test.js.map +1 -1
- package/dist/pipeline/__tests__/stages.test.js +410 -4
- package/dist/pipeline/__tests__/stages.test.js.map +1 -1
- package/dist/pipeline/orchestrator.d.ts.map +1 -1
- package/dist/pipeline/orchestrator.js +29 -2
- package/dist/pipeline/orchestrator.js.map +1 -1
- package/dist/pipeline/stages/ralplan.d.ts.map +1 -1
- package/dist/pipeline/stages/ralplan.js +41 -6
- package/dist/pipeline/stages/ralplan.js.map +1 -1
- package/dist/question/__tests__/ui.test.js +43 -10
- package/dist/question/__tests__/ui.test.js.map +1 -1
- package/dist/question/ui.d.ts +12 -0
- package/dist/question/ui.d.ts.map +1 -1
- package/dist/question/ui.js +83 -46
- package/dist/question/ui.js.map +1 -1
- package/dist/ralplan/__tests__/runtime.test.js +200 -10
- package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
- package/dist/ralplan/consensus-gate.d.ts +23 -0
- package/dist/ralplan/consensus-gate.d.ts.map +1 -0
- package/dist/ralplan/consensus-gate.js +212 -0
- package/dist/ralplan/consensus-gate.js.map +1 -0
- package/dist/ralplan/runtime.d.ts +25 -0
- package/dist/ralplan/runtime.d.ts.map +1 -1
- package/dist/ralplan/runtime.js +144 -8
- package/dist/ralplan/runtime.js.map +1 -1
- package/dist/scripts/__tests__/codex-native-hook.test.js +626 -7
- package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
- package/dist/scripts/__tests__/docs-site-contract.test.d.ts +2 -0
- package/dist/scripts/__tests__/docs-site-contract.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/docs-site-contract.test.js +42 -0
- package/dist/scripts/__tests__/docs-site-contract.test.js.map +1 -0
- package/dist/scripts/__tests__/notify-dispatcher.test.js +115 -2
- package/dist/scripts/__tests__/notify-dispatcher.test.js.map +1 -1
- package/dist/scripts/__tests__/run-test-files.test.js +57 -0
- package/dist/scripts/__tests__/run-test-files.test.js.map +1 -1
- package/dist/scripts/__tests__/verify-native-agents.test.js +2 -2
- package/dist/scripts/__tests__/verify-native-agents.test.js.map +1 -1
- package/dist/scripts/codex-native-hook.d.ts.map +1 -1
- package/dist/scripts/codex-native-hook.js +214 -34
- package/dist/scripts/codex-native-hook.js.map +1 -1
- package/dist/scripts/notify-dispatcher.js +188 -4
- package/dist/scripts/notify-dispatcher.js.map +1 -1
- package/dist/scripts/run-test-files.js +13 -0
- package/dist/scripts/run-test-files.js.map +1 -1
- package/dist/state/__tests__/workflow-transition.test.js +6 -0
- package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
- package/dist/state/workflow-transition.d.ts +1 -1
- package/dist/state/workflow-transition.d.ts.map +1 -1
- package/dist/state/workflow-transition.js +7 -0
- package/dist/state/workflow-transition.js.map +1 -1
- package/dist/subagents/tracker.d.ts.map +1 -1
- package/dist/subagents/tracker.js +4 -3
- package/dist/subagents/tracker.js.map +1 -1
- package/dist/team/__tests__/runtime.test.js +36 -44
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/tmux-session.test.js +58 -18
- package/dist/team/__tests__/tmux-session.test.js.map +1 -1
- package/dist/team/runtime.d.ts.map +1 -1
- package/dist/team/runtime.js +10 -20
- package/dist/team/runtime.js.map +1 -1
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +15 -6
- package/dist/team/tmux-session.js.map +1 -1
- package/dist/ultragoal/__tests__/artifacts.test.js +50 -0
- package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
- package/dist/ultragoal/artifacts.d.ts.map +1 -1
- package/dist/ultragoal/artifacts.js +28 -2
- package/dist/ultragoal/artifacts.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 +16 -4
- package/plugins/oh-my-codex/skills/autoresearch/SKILL.md +4 -0
- package/plugins/oh-my-codex/skills/autoresearch-goal/SKILL.md +1 -1
- package/plugins/oh-my-codex/skills/best-practice-research/SKILL.md +1 -1
- package/plugins/oh-my-codex/skills/pipeline/SKILL.md +1 -1
- package/plugins/oh-my-codex/skills/plan/SKILL.md +1 -1
- package/plugins/oh-my-codex/skills/prometheus-strict/README.md +35 -0
- package/plugins/oh-my-codex/skills/prometheus-strict/SKILL.md +219 -0
- package/plugins/oh-my-codex/skills/ralplan/SKILL.md +18 -3
- package/prompts/prometheus-strict-metis.md +274 -0
- package/prompts/prometheus-strict-momus.md +82 -0
- package/prompts/prometheus-strict-oracle.md +107 -0
- package/prompts/researcher.md +22 -3
- package/skills/autopilot/SKILL.md +16 -4
- package/skills/autoresearch/SKILL.md +4 -0
- package/skills/autoresearch-goal/SKILL.md +1 -1
- package/skills/best-practice-research/SKILL.md +1 -1
- package/skills/pipeline/SKILL.md +1 -1
- package/skills/plan/SKILL.md +1 -1
- package/skills/prometheus-strict/README.md +35 -0
- package/skills/prometheus-strict/SKILL.md +219 -0
- package/skills/ralplan/SKILL.md +18 -3
- package/src/scripts/__tests__/codex-native-hook.test.ts +769 -8
- package/src/scripts/__tests__/docs-site-contract.test.ts +47 -0
- package/src/scripts/__tests__/notify-dispatcher.test.ts +132 -3
- package/src/scripts/__tests__/run-test-files.test.ts +67 -0
- package/src/scripts/__tests__/verify-native-agents.test.ts +2 -2
- package/src/scripts/codex-native-hook.ts +237 -30
- package/src/scripts/notify-dispatcher.ts +202 -4
- package/src/scripts/run-test-files.ts +13 -0
- package/templates/catalog-manifest.json +22 -0
|
@@ -3,9 +3,9 @@ import { closeSync, existsSync, openSync, readFileSync, readSync } from "fs";
|
|
|
3
3
|
import { appendFile, mkdir, readFile, readdir, stat, writeFile } from "fs/promises";
|
|
4
4
|
import { extname, join, relative, resolve } from "path";
|
|
5
5
|
import { pathToFileURL } from "url";
|
|
6
|
-
import {
|
|
7
|
-
import { extractSessionIdFromInitializedStatePath, getSkillActiveStatePathsForStateDir, listActiveSkills, readSkillActiveState, readVisibleSkillActiveStateForStateDir, } from "../state/skill-active.js";
|
|
8
|
-
import { readSubagentSessionSummary, recordSubagentTurnForSession, } from "../subagents/tracker.js";
|
|
6
|
+
import { readModeStateForActiveDecision, readModeStateForSession, updateModeState } from "../modes/base.js";
|
|
7
|
+
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
9
|
import { resolveCanonicalTeamStateRoot, resolveWorkerNotifyTeamStateRootPath } from "../team/state-root.js";
|
|
10
10
|
import { appendToLog, isSessionStateUsable, readSessionState, readUsableSessionState, reconcileNativeSessionStart, } from "../hooks/session.js";
|
|
11
11
|
import { appendTeamEvent, readTeamLeaderAttention, readTeamManifestV2, readTeamPhase, writeTeamLeaderAttention, writeTeamPhase, } from "../team/state.js";
|
|
@@ -175,18 +175,29 @@ async function nativeSubagentSessionStartBelongsToCanonicalSession(cwd, canonica
|
|
|
175
175
|
return summary.allThreadIds.includes(parentThreadId);
|
|
176
176
|
}
|
|
177
177
|
async function isNativeSubagentHook(cwd, canonicalSessionId, nativeSessionId, threadId) {
|
|
178
|
-
const sessionId = canonicalSessionId.trim();
|
|
179
|
-
if (!sessionId)
|
|
180
|
-
return false;
|
|
181
|
-
const summary = await readSubagentSessionSummary(cwd, sessionId).catch(() => null);
|
|
182
|
-
if (!summary)
|
|
183
|
-
return false;
|
|
184
178
|
const candidateIds = [nativeSessionId, threadId]
|
|
185
179
|
.map((value) => value.trim())
|
|
186
180
|
.filter(Boolean);
|
|
187
181
|
if (candidateIds.length === 0)
|
|
188
182
|
return false;
|
|
189
|
-
|
|
183
|
+
const sessionId = canonicalSessionId.trim();
|
|
184
|
+
if (sessionId) {
|
|
185
|
+
const summary = await readSubagentSessionSummary(cwd, sessionId).catch(() => null);
|
|
186
|
+
if (summary && candidateIds.some((id) => summary.allSubagentThreadIds.includes(id))) {
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
// Native Codex resume can report the child native session as the canonical
|
|
191
|
+
// session id before OMX reconciles it back to the owning session. In that
|
|
192
|
+
// window the per-session summary lookup above misses the child and a
|
|
193
|
+
// subagent UserPromptSubmit can accidentally activate workflow keywords from
|
|
194
|
+
// quoted review context. Fall back to the global tracking index so any known
|
|
195
|
+
// subagent thread is treated as subagent-scoped, regardless of the current
|
|
196
|
+
// hook payload's session-id mapping.
|
|
197
|
+
const trackingState = await readSubagentTrackingState(cwd).catch(() => null);
|
|
198
|
+
if (!trackingState)
|
|
199
|
+
return false;
|
|
200
|
+
return Object.values(trackingState.sessions).some((session) => (candidateIds.some((id) => session.threads[id]?.kind === "subagent")));
|
|
190
201
|
}
|
|
191
202
|
function shouldSuppressSubagentLifecycleHookDispatch() {
|
|
192
203
|
const config = getNotificationConfig();
|
|
@@ -1361,6 +1372,17 @@ function buildNativeOutsideTmuxTeamPromptBlockState(prompt, cwd, payload, sessio
|
|
|
1361
1372
|
function buildSkillStateCliInstruction(mode, statePath) {
|
|
1362
1373
|
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.`;
|
|
1363
1374
|
}
|
|
1375
|
+
function buildAutopilotPromptActivationNote(skillState) {
|
|
1376
|
+
if (skillState?.initialized_mode !== "autopilot")
|
|
1377
|
+
return null;
|
|
1378
|
+
return [
|
|
1379
|
+
"Autopilot protocol: the durable default chain is $deep-interview -> $ralplan -> $ultragoal (+ $team if needed) -> $code-review -> $ultraqa (deep-interview -> ralplan -> ultragoal -> code-review -> ultraqa).",
|
|
1380
|
+
"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.",
|
|
1381
|
+
"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.",
|
|
1382
|
+
"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.",
|
|
1383
|
+
"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.",
|
|
1384
|
+
].join(" ");
|
|
1385
|
+
}
|
|
1364
1386
|
function buildAdditionalContextMessage(prompt, skillState, cwd = process.cwd(), payload) {
|
|
1365
1387
|
if (!prompt)
|
|
1366
1388
|
return null;
|
|
@@ -1391,6 +1413,7 @@ function buildAdditionalContextMessage(prompt, skillState, cwd = process.cwd(),
|
|
|
1391
1413
|
const ultragoalPromptActivationNote = match.skill === "ultragoal"
|
|
1392
1414
|
? "Ultragoal protocol: use `omx ultragoal create-goals` / `complete-goals` / `checkpoint` for `.omx/ultragoal` artifacts, then use Codex goal model tools only from the active agent handoff (`get_goal`, `create_goal`, `update_goal`) and never overwrite a different active Codex goal. Ultragoal does not call `/goal clear`; for multiple sequential ultragoal runs in one Codex session/thread, manually clear the completed Codex goal in the UI before creating the next aggregate goal."
|
|
1393
1415
|
: null;
|
|
1416
|
+
const autopilotPromptActivationNote = buildAutopilotPromptActivationNote(skillState);
|
|
1394
1417
|
const combinedTransitionMessage = (() => {
|
|
1395
1418
|
if (!skillState?.transition_message)
|
|
1396
1419
|
return null;
|
|
@@ -1419,6 +1442,7 @@ function buildAdditionalContextMessage(prompt, skillState, cwd = process.cwd(),
|
|
|
1419
1442
|
: null,
|
|
1420
1443
|
promptPriorityMessage,
|
|
1421
1444
|
ultragoalPromptActivationNote,
|
|
1445
|
+
autopilotPromptActivationNote,
|
|
1422
1446
|
skillState.initialized_mode && skillState.initialized_state_path
|
|
1423
1447
|
? buildSkillStateCliInstruction(skillState.initialized_mode, skillState.initialized_state_path)
|
|
1424
1448
|
: null,
|
|
@@ -1444,6 +1468,7 @@ function buildAdditionalContextMessage(prompt, skillState, cwd = process.cwd(),
|
|
|
1444
1468
|
deepInterviewPromptActivationNote,
|
|
1445
1469
|
ultraworkPromptActivationNote,
|
|
1446
1470
|
ultragoalPromptActivationNote,
|
|
1471
|
+
autopilotPromptActivationNote,
|
|
1447
1472
|
buildTeamRuntimeInstruction(cwd, payload),
|
|
1448
1473
|
buildTeamHelpInstruction(cwd, payload),
|
|
1449
1474
|
"Follow AGENTS.md routing and preserve workflow transition and planning-safety rules.",
|
|
@@ -1461,11 +1486,12 @@ function buildAdditionalContextMessage(prompt, skillState, cwd = process.cwd(),
|
|
|
1461
1486
|
deepInterviewPromptActivationNote,
|
|
1462
1487
|
ultraworkPromptActivationNote,
|
|
1463
1488
|
ultragoalPromptActivationNote,
|
|
1489
|
+
autopilotPromptActivationNote,
|
|
1464
1490
|
ralphPromptActivationNote,
|
|
1465
1491
|
"Follow AGENTS.md routing and preserve workflow transition and planning-safety rules.",
|
|
1466
1492
|
].join(" ");
|
|
1467
1493
|
}
|
|
1468
|
-
return [detectedKeywordMessage, promptPriorityMessage, ultragoalPromptActivationNote, "Follow AGENTS.md routing and preserve workflow transition and planning-safety rules."].filter(Boolean).join(" ");
|
|
1494
|
+
return [detectedKeywordMessage, promptPriorityMessage, ultragoalPromptActivationNote, autopilotPromptActivationNote, "Follow AGENTS.md routing and preserve workflow transition and planning-safety rules."].filter(Boolean).join(" ");
|
|
1469
1495
|
}
|
|
1470
1496
|
function parseTeamWorkerEnv(rawValue) {
|
|
1471
1497
|
const match = /^([a-z0-9][a-z0-9-]{0,29})\/(worker-\d+)$/.exec(rawValue.trim());
|
|
@@ -1664,6 +1690,7 @@ async function findActiveGoalWorkflowReconciliationRequirement(cwd) {
|
|
|
1664
1690
|
`If get_goal returns a completed task-scoped objective for the same aggregate ultragoal plan, checkpoint ${goalId} with evidence naming ${goalId} plus .omx/ultragoal/goals.json or ledger.jsonl and pass final quality-gate JSON; OMX will reconcile the completed planned scope without mutating Codex goal state.`,
|
|
1665
1691
|
`If get_goal instead returns a different completed legacy objective and complete checkpointing fails, do not repeat --status complete in this thread.`,
|
|
1666
1692
|
`Record the non-terminal blocker with: omx ultragoal checkpoint --goal-id ${goalId} --status blocked --codex-goal-json '<different completed get_goal JSON or path>' --evidence '<completed legacy Codex goal blocks create_goal in this thread>'.`,
|
|
1693
|
+
`If get_goal itself is unavailable with a Codex DB/schema/context error such as "no such table: thread_goals", record an auditable safe-recovery blocker instead: omx ultragoal checkpoint --goal-id ${goalId} --status blocked --codex-goal-json '<unavailable get_goal error JSON or path>' --evidence '<get_goal unavailable due to Codex DB/schema/context error; safe recovery requires a working Codex goal context>'.`,
|
|
1667
1694
|
"Then continue only from a Codex goal context with no active/completed conflicting goal in the same repo/worktree and create the intended goal there.",
|
|
1668
1695
|
].join(" "),
|
|
1669
1696
|
};
|
|
@@ -1744,30 +1771,46 @@ async function buildGoalWorkflowReconciliationStopOutput(payload, cwd) {
|
|
|
1744
1771
|
systemMessage,
|
|
1745
1772
|
};
|
|
1746
1773
|
}
|
|
1747
|
-
|
|
1774
|
+
function teamStateMatchesThreadForStop(state, threadId, options = {}) {
|
|
1775
|
+
const normalizedThreadId = safeString(threadId).trim();
|
|
1776
|
+
if (!normalizedThreadId)
|
|
1777
|
+
return true;
|
|
1778
|
+
const ownerThreadId = safeString(state.owner_codex_thread_id ?? state.thread_id).trim();
|
|
1779
|
+
if (!ownerThreadId)
|
|
1780
|
+
return options.requireOwnerThread !== true;
|
|
1781
|
+
return ownerThreadId === normalizedThreadId;
|
|
1782
|
+
}
|
|
1783
|
+
async function readTeamModeStateForStop(cwd, stateDir, sessionId, threadId) {
|
|
1748
1784
|
const normalizedSessionId = safeString(sessionId).trim();
|
|
1749
|
-
if (!normalizedSessionId)
|
|
1750
|
-
return
|
|
1751
|
-
}
|
|
1785
|
+
if (!normalizedSessionId)
|
|
1786
|
+
return null;
|
|
1752
1787
|
const scopedState = await readStopSessionPinnedState("team-state.json", cwd, normalizedSessionId, stateDir);
|
|
1753
|
-
if (scopedState)
|
|
1754
|
-
return scopedState
|
|
1788
|
+
if (scopedState) {
|
|
1789
|
+
return teamStateMatchesThreadForStop(scopedState, threadId)
|
|
1790
|
+
? { state: scopedState, scope: "session" }
|
|
1791
|
+
: null;
|
|
1792
|
+
}
|
|
1755
1793
|
const rootState = await readJsonIfExists(join(stateDir, "team-state.json"));
|
|
1756
1794
|
if (rootState?.active !== true)
|
|
1757
1795
|
return null;
|
|
1796
|
+
const teamName = safeString(rootState.team_name).trim();
|
|
1797
|
+
if (!teamName)
|
|
1798
|
+
return null;
|
|
1758
1799
|
const ownerSessionId = safeString(rootState.session_id).trim();
|
|
1759
|
-
if (ownerSessionId
|
|
1800
|
+
if (!ownerSessionId || ownerSessionId !== normalizedSessionId)
|
|
1760
1801
|
return null;
|
|
1761
|
-
}
|
|
1762
|
-
|
|
1802
|
+
if (!teamStateMatchesThreadForStop(rootState, threadId, { requireOwnerThread: true }))
|
|
1803
|
+
return null;
|
|
1804
|
+
return { state: rootState, scope: "root" };
|
|
1763
1805
|
}
|
|
1764
|
-
async function buildTeamStopOutput(cwd, sessionId) {
|
|
1806
|
+
async function buildTeamStopOutput(cwd, sessionId, threadId) {
|
|
1765
1807
|
if (await readCanonicalTerminalRunStateForStop(cwd, sessionId, "team")) {
|
|
1766
1808
|
return null;
|
|
1767
1809
|
}
|
|
1768
|
-
const
|
|
1769
|
-
if (
|
|
1810
|
+
const teamStateForStop = await readTeamModeStateForStop(cwd, getBaseStateDir(cwd), sessionId, threadId);
|
|
1811
|
+
if (!teamStateForStop || teamStateForStop.state.active !== true)
|
|
1770
1812
|
return null;
|
|
1813
|
+
const teamState = teamStateForStop.state;
|
|
1771
1814
|
const teamName = safeString(teamState.team_name).trim();
|
|
1772
1815
|
if (teamName) {
|
|
1773
1816
|
const canonicalTeamDir = join(resolveCanonicalTeamStateRoot(cwd), "team", teamName);
|
|
@@ -1776,7 +1819,10 @@ async function buildTeamStopOutput(cwd, sessionId) {
|
|
|
1776
1819
|
}
|
|
1777
1820
|
}
|
|
1778
1821
|
const coarsePhase = teamState.current_phase;
|
|
1779
|
-
const
|
|
1822
|
+
const canonicalPhaseState = teamName ? await readTeamPhase(teamName, cwd) : null;
|
|
1823
|
+
if (teamStateForStop.scope === "root" && !canonicalPhaseState)
|
|
1824
|
+
return null;
|
|
1825
|
+
const canonicalPhase = canonicalPhaseState?.current_phase ?? coarsePhase;
|
|
1780
1826
|
if (!isNonTerminalPhase(canonicalPhase))
|
|
1781
1827
|
return null;
|
|
1782
1828
|
return buildTeamStopOutputForPhase(teamName, formatPhase(canonicalPhase));
|
|
@@ -1859,6 +1905,137 @@ async function readStopSessionPinnedState(fileName, cwd, sessionId, stateDir) {
|
|
|
1859
1905
|
: getStateFilePath(fileName, cwd, sessionId || undefined);
|
|
1860
1906
|
return readJsonIfExists(statePath);
|
|
1861
1907
|
}
|
|
1908
|
+
const DEEP_INTERVIEW_ALLOWED_WRITE_PREFIXES = [
|
|
1909
|
+
".omx/context",
|
|
1910
|
+
".omx/interviews",
|
|
1911
|
+
".omx/specs",
|
|
1912
|
+
".omx/state",
|
|
1913
|
+
];
|
|
1914
|
+
const DEEP_INTERVIEW_IMPLEMENTATION_TOOL_NAMES = new Set([
|
|
1915
|
+
"Write",
|
|
1916
|
+
"Edit",
|
|
1917
|
+
"MultiEdit",
|
|
1918
|
+
"apply_patch",
|
|
1919
|
+
"ApplyPatch",
|
|
1920
|
+
]);
|
|
1921
|
+
function isActiveDeepInterviewPhase(state) {
|
|
1922
|
+
if (!state || state.active !== true)
|
|
1923
|
+
return false;
|
|
1924
|
+
const mode = safeString(state.mode).trim();
|
|
1925
|
+
if (mode && mode !== "deep-interview")
|
|
1926
|
+
return false;
|
|
1927
|
+
const phase = safeString(state.current_phase ?? state.currentPhase).trim().toLowerCase();
|
|
1928
|
+
if (phase && (TERMINAL_MODE_PHASES.has(phase) || phase === "completing"))
|
|
1929
|
+
return false;
|
|
1930
|
+
return true;
|
|
1931
|
+
}
|
|
1932
|
+
function isAllowedDeepInterviewArtifactPath(cwd, rawPath) {
|
|
1933
|
+
const trimmed = rawPath.trim().replace(/^['"]|['"]$/g, "");
|
|
1934
|
+
if (!trimmed || trimmed.includes("\0"))
|
|
1935
|
+
return false;
|
|
1936
|
+
let relativePath;
|
|
1937
|
+
try {
|
|
1938
|
+
const absolute = resolve(cwd, trimmed);
|
|
1939
|
+
relativePath = relative(cwd, absolute).replace(/\\/g, "/");
|
|
1940
|
+
}
|
|
1941
|
+
catch {
|
|
1942
|
+
return false;
|
|
1943
|
+
}
|
|
1944
|
+
if (!relativePath || relativePath.startsWith("..") || relativePath.startsWith("/"))
|
|
1945
|
+
return false;
|
|
1946
|
+
return DEEP_INTERVIEW_ALLOWED_WRITE_PREFIXES.some((prefix) => (relativePath === prefix || relativePath.startsWith(`${prefix}/`)));
|
|
1947
|
+
}
|
|
1948
|
+
function readPreToolUseCommand(payload) {
|
|
1949
|
+
const toolInput = safeObject(payload.tool_input);
|
|
1950
|
+
return safeString(toolInput.command).trim();
|
|
1951
|
+
}
|
|
1952
|
+
function readPreToolUsePathCandidates(payload) {
|
|
1953
|
+
const input = safeObject(payload.tool_input);
|
|
1954
|
+
const candidates = [
|
|
1955
|
+
input.file_path,
|
|
1956
|
+
input.filePath,
|
|
1957
|
+
input.path,
|
|
1958
|
+
input.target_path,
|
|
1959
|
+
input.targetPath,
|
|
1960
|
+
];
|
|
1961
|
+
return candidates.map((candidate) => safeString(candidate).trim()).filter(Boolean);
|
|
1962
|
+
}
|
|
1963
|
+
function commandHasDeepInterviewWriteIntent(command) {
|
|
1964
|
+
return /\bapply_patch\b/.test(command)
|
|
1965
|
+
|| /(?:^|[;&|]\s*)(?:cat|printf|echo)\b[\s\S]{0,240}>\s*[^\s&|;]+/.test(command)
|
|
1966
|
+
|| /\btee\s+(?:-a\s+)?[^\s&|;]+/.test(command)
|
|
1967
|
+
|| /\bsed\s+(?:[^\n;&|]*\s)?-i(?:\b|['"])/.test(command)
|
|
1968
|
+
|| /\b(?:python3?|node|perl|ruby)\b[\s\S]{0,260}\b(?:writeFileSync|writeFile|write_text|open\([^)]*["']w|File\.write|Path\()/.test(command)
|
|
1969
|
+
|| /\b(?:git\s+(?:checkout|switch|restore|reset|apply|am|merge|rebase)|npm\s+(?:install|i|ci)|pnpm\s+(?:install|i)|yarn\s+(?:install|add))\b/.test(command);
|
|
1970
|
+
}
|
|
1971
|
+
function extractDeepInterviewCommandWriteTargets(command) {
|
|
1972
|
+
const targets = [];
|
|
1973
|
+
for (const match of command.matchAll(/(?:^|[^>])>{1,2}\s*(["']?)([^\s&|;<>]+)\1/g)) {
|
|
1974
|
+
const candidate = safeString(match[2]).trim();
|
|
1975
|
+
if (candidate)
|
|
1976
|
+
targets.push(candidate);
|
|
1977
|
+
}
|
|
1978
|
+
for (const match of command.matchAll(/\btee\s+(?:-a\s+)?(["']?)([^\s&|;<>]+)\1/g)) {
|
|
1979
|
+
const candidate = safeString(match[2]).trim();
|
|
1980
|
+
if (candidate)
|
|
1981
|
+
targets.push(candidate);
|
|
1982
|
+
}
|
|
1983
|
+
return targets;
|
|
1984
|
+
}
|
|
1985
|
+
function isAllowedDeepInterviewBashWrite(cwd, command) {
|
|
1986
|
+
if (!commandHasDeepInterviewWriteIntent(command))
|
|
1987
|
+
return true;
|
|
1988
|
+
if (/\bomx\s+(?:state\s+(?:write|read|clear)|question)\b/.test(command))
|
|
1989
|
+
return true;
|
|
1990
|
+
const targets = extractDeepInterviewCommandWriteTargets(command);
|
|
1991
|
+
return targets.length > 0 && targets.every((target) => isAllowedDeepInterviewArtifactPath(cwd, target));
|
|
1992
|
+
}
|
|
1993
|
+
async function readActiveDeepInterviewStateForPreToolUse(cwd, stateDir, sessionId, threadId) {
|
|
1994
|
+
const modeState = sessionId
|
|
1995
|
+
? await readStopSessionPinnedState("deep-interview-state.json", cwd, sessionId, stateDir)
|
|
1996
|
+
: await readJsonIfExists(join(stateDir, "deep-interview-state.json"));
|
|
1997
|
+
if (!isActiveDeepInterviewPhase(modeState) || !modeState)
|
|
1998
|
+
return null;
|
|
1999
|
+
if (!modeStateMatchesSkillStopContext(modeState, cwd, sessionId))
|
|
2000
|
+
return null;
|
|
2001
|
+
const canonicalState = sessionId
|
|
2002
|
+
? await readVisibleSkillActiveStateForStateDir(stateDir, sessionId)
|
|
2003
|
+
: await readSkillActiveState(join(stateDir, SKILL_ACTIVE_STATE_FILE));
|
|
2004
|
+
if (!canonicalState)
|
|
2005
|
+
return modeState;
|
|
2006
|
+
const hasActiveDeepInterviewSkill = listActiveSkills(canonicalState).some((entry) => (entry.skill === "deep-interview"
|
|
2007
|
+
&& matchesSkillStopContext(entry, canonicalState, sessionId, threadId)));
|
|
2008
|
+
return hasActiveDeepInterviewSkill ? modeState : null;
|
|
2009
|
+
}
|
|
2010
|
+
async function buildDeepInterviewPreToolUseBoundaryOutput(payload, cwd, stateDir) {
|
|
2011
|
+
const sessionId = readPayloadSessionId(payload);
|
|
2012
|
+
const threadId = readPayloadThreadId(payload);
|
|
2013
|
+
const activeState = await readActiveDeepInterviewStateForPreToolUse(cwd, stateDir, sessionId, threadId);
|
|
2014
|
+
if (!activeState)
|
|
2015
|
+
return null;
|
|
2016
|
+
const toolName = safeString(payload.tool_name).trim();
|
|
2017
|
+
const command = readPreToolUseCommand(payload);
|
|
2018
|
+
const pathCandidates = readPreToolUsePathCandidates(payload);
|
|
2019
|
+
let blocked = false;
|
|
2020
|
+
if (toolName === "Bash") {
|
|
2021
|
+
blocked = !isAllowedDeepInterviewBashWrite(cwd, command);
|
|
2022
|
+
}
|
|
2023
|
+
else if (DEEP_INTERVIEW_IMPLEMENTATION_TOOL_NAMES.has(toolName)) {
|
|
2024
|
+
blocked = pathCandidates.length === 0
|
|
2025
|
+
|| !pathCandidates.every((candidate) => isAllowedDeepInterviewArtifactPath(cwd, candidate));
|
|
2026
|
+
}
|
|
2027
|
+
if (!blocked)
|
|
2028
|
+
return null;
|
|
2029
|
+
const phase = formatPhase(activeState.current_phase ?? activeState.currentPhase, "planning");
|
|
2030
|
+
return {
|
|
2031
|
+
decision: "block",
|
|
2032
|
+
reason: `Deep-interview is active (phase: ${phase}); implementation/write tools are blocked until an explicit handoff workflow is activated.`,
|
|
2033
|
+
hookSpecificOutput: {
|
|
2034
|
+
hookEventName: "PreToolUse",
|
|
2035
|
+
additionalContext: "Deep-interview is requirements/spec mode. Treat detailed user answers as interview/spec material, not implicit implementation authorization. You may write only deep-interview artifacts under `.omx/context/`, `.omx/interviews/`, `.omx/specs/`, or required `.omx/state/` files. To implement, first ask for or process an explicit transition such as `$ralplan`, `$autopilot`, `$ralph`, `$team`, or `$ultragoal`.",
|
|
2036
|
+
},
|
|
2037
|
+
};
|
|
2038
|
+
}
|
|
1862
2039
|
function matchesSkillStopContext(entry, state, sessionId, threadId) {
|
|
1863
2040
|
const entrySessionId = safeString(entry.session_id ?? state.session_id).trim();
|
|
1864
2041
|
const entryThreadId = safeString(entry.thread_id ?? state.thread_id).trim();
|
|
@@ -2310,7 +2487,7 @@ async function maybeReturnRepeatableStopOutput(payload, stateDir, signature, out
|
|
|
2310
2487
|
async function returnPersistentStopBlock(payload, stateDir, signatureKind, signatureValue, output, canonicalSessionId, options = { allowRepeatDuringStopHook: true }) {
|
|
2311
2488
|
return await maybeReturnRepeatableStopOutput(payload, stateDir, buildRepeatableStopSignature(payload, signatureKind, signatureValue, canonicalSessionId), output, canonicalSessionId, options);
|
|
2312
2489
|
}
|
|
2313
|
-
async function findCanonicalActiveTeamForSession(cwd, sessionId) {
|
|
2490
|
+
async function findCanonicalActiveTeamForSession(cwd, sessionId, threadId) {
|
|
2314
2491
|
if (!sessionId.trim())
|
|
2315
2492
|
return null;
|
|
2316
2493
|
const teamsRoot = join(resolveCanonicalTeamStateRoot(cwd), "team");
|
|
@@ -2332,6 +2509,8 @@ async function findCanonicalActiveTeamForSession(cwd, sessionId) {
|
|
|
2332
2509
|
const ownerSessionId = (manifest.leader?.session_id ?? "").trim();
|
|
2333
2510
|
if (ownerSessionId && ownerSessionId !== sessionId.trim())
|
|
2334
2511
|
continue;
|
|
2512
|
+
if (!teamStateMatchesThreadForStop(manifest.leader, threadId))
|
|
2513
|
+
continue;
|
|
2335
2514
|
if (!isNonTerminalPhase(phaseState.current_phase))
|
|
2336
2515
|
continue;
|
|
2337
2516
|
return {
|
|
@@ -2341,18 +2520,18 @@ async function findCanonicalActiveTeamForSession(cwd, sessionId) {
|
|
|
2341
2520
|
}
|
|
2342
2521
|
return null;
|
|
2343
2522
|
}
|
|
2344
|
-
async function resolveActiveTeamNameForStop(cwd, stateDir, sessionId) {
|
|
2345
|
-
const directState = await readTeamModeStateForStop(cwd, stateDir, sessionId);
|
|
2346
|
-
const directTeamName = safeString(directState?.team_name).trim();
|
|
2347
|
-
if (directState?.active === true && directTeamName)
|
|
2523
|
+
async function resolveActiveTeamNameForStop(cwd, stateDir, sessionId, threadId) {
|
|
2524
|
+
const directState = await readTeamModeStateForStop(cwd, stateDir, sessionId, threadId);
|
|
2525
|
+
const directTeamName = safeString(directState?.state.team_name).trim();
|
|
2526
|
+
if (directState?.state.active === true && directTeamName)
|
|
2348
2527
|
return directTeamName;
|
|
2349
|
-
const canonicalTeam = await findCanonicalActiveTeamForSession(cwd, sessionId);
|
|
2528
|
+
const canonicalTeam = await findCanonicalActiveTeamForSession(cwd, sessionId, threadId);
|
|
2350
2529
|
return canonicalTeam?.teamName ?? "";
|
|
2351
2530
|
}
|
|
2352
2531
|
async function maybeBuildReleaseReadinessFinalizeStopOutput(payload, cwd, stateDir, sessionId) {
|
|
2353
2532
|
if (!sessionId)
|
|
2354
2533
|
return { matched: false, output: null };
|
|
2355
|
-
const teamName = await resolveActiveTeamNameForStop(cwd, stateDir, sessionId);
|
|
2534
|
+
const teamName = await resolveActiveTeamNameForStop(cwd, stateDir, sessionId, readPayloadThreadId(payload));
|
|
2356
2535
|
if (!teamName)
|
|
2357
2536
|
return { matched: false, output: null };
|
|
2358
2537
|
const explicitReleaseReadinessContext = hasReleaseReadinessMode(payload)
|
|
@@ -2569,7 +2748,7 @@ async function buildStopHookOutput(payload, cwd, stateDir, options = {}) {
|
|
|
2569
2748
|
const releaseReadinessFinalizeResult = await maybeBuildReleaseReadinessFinalizeStopOutput(payload, cwd, stateDir, canonicalSessionId);
|
|
2570
2749
|
if (releaseReadinessFinalizeResult.matched)
|
|
2571
2750
|
return releaseReadinessFinalizeResult.output;
|
|
2572
|
-
const teamOutput = await buildTeamStopOutput(cwd, canonicalSessionId);
|
|
2751
|
+
const teamOutput = await buildTeamStopOutput(cwd, canonicalSessionId, threadId);
|
|
2573
2752
|
if (teamOutput) {
|
|
2574
2753
|
return await returnPersistentStopBlock(payload, stateDir, "team-stop", safeString(teamOutput.stopReason), teamOutput, canonicalSessionId);
|
|
2575
2754
|
}
|
|
@@ -2580,7 +2759,7 @@ async function buildStopHookOutput(payload, cwd, stateDir, options = {}) {
|
|
|
2580
2759
|
}
|
|
2581
2760
|
const canonicalTeam = await readCanonicalTerminalRunStateForStop(cwd, canonicalSessionId, "team")
|
|
2582
2761
|
? null
|
|
2583
|
-
: await findCanonicalActiveTeamForSession(cwd, canonicalSessionId);
|
|
2762
|
+
: await findCanonicalActiveTeamForSession(cwd, canonicalSessionId, threadId);
|
|
2584
2763
|
if (canonicalTeam) {
|
|
2585
2764
|
const canonicalTeamOutput = buildTeamStopOutputForPhase(canonicalTeam.teamName, canonicalTeam.phase);
|
|
2586
2765
|
const repeatedCanonicalTeamOutput = await returnPersistentStopBlock(payload, stateDir, "team-stop", `${canonicalTeam.teamName}|${canonicalTeam.phase}`, canonicalTeamOutput, canonicalSessionId);
|
|
@@ -2857,7 +3036,8 @@ export async function dispatchCodexNativeHook(payload, options = {}) {
|
|
|
2857
3036
|
}
|
|
2858
3037
|
}
|
|
2859
3038
|
else if (hookEventName === "PreToolUse") {
|
|
2860
|
-
outputJson =
|
|
3039
|
+
outputJson = await buildDeepInterviewPreToolUseBoundaryOutput(payload, cwd, stateDir)
|
|
3040
|
+
?? buildNativePreToolUseOutput(payload);
|
|
2861
3041
|
}
|
|
2862
3042
|
else if (hookEventName === "PostToolUse") {
|
|
2863
3043
|
if (detectMcpTransportFailure(payload)) {
|