oh-my-codex 0.18.7 → 0.18.8
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 +5 -5
- package/crates/omx-sparkshell/tests/execution.rs +1 -1
- package/dist/agents/__tests__/native-config.test.js +42 -1
- package/dist/agents/__tests__/native-config.test.js.map +1 -1
- package/dist/agents/definitions.d.ts +8 -0
- package/dist/agents/definitions.d.ts.map +1 -1
- package/dist/agents/definitions.js +1 -0
- package/dist/agents/definitions.js.map +1 -1
- package/dist/agents/native-config.d.ts +5 -1
- package/dist/agents/native-config.d.ts.map +1 -1
- package/dist/agents/native-config.js +17 -2
- package/dist/agents/native-config.js.map +1 -1
- package/dist/cli/__tests__/codex-plugin-layout.test.js +512 -1
- package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-warning-copy.test.js +39 -0
- package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +61 -5
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/package-bin-contract.test.js +8 -4
- package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
- package/dist/cli/__tests__/ralph-goal-mode-contract.test.js +13 -0
- package/dist/cli/__tests__/ralph-goal-mode-contract.test.js.map +1 -1
- package/dist/cli/__tests__/ralph.test.js +14 -0
- package/dist/cli/__tests__/ralph.test.js.map +1 -1
- package/dist/cli/__tests__/setup-install-mode.test.js +89 -0
- package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
- package/dist/cli/__tests__/setup-refresh.test.js +65 -0
- package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
- package/dist/cli/__tests__/state.test.js +21 -0
- package/dist/cli/__tests__/state.test.js.map +1 -1
- package/dist/cli/__tests__/team.test.js +2 -2
- package/dist/cli/__tests__/update.test.js +110 -2
- package/dist/cli/__tests__/update.test.js.map +1 -1
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +8 -1
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.d.ts +11 -2
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +108 -15
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/plugin-marketplace.d.ts +14 -2
- package/dist/cli/plugin-marketplace.d.ts.map +1 -1
- package/dist/cli/plugin-marketplace.js +62 -15
- package/dist/cli/plugin-marketplace.js.map +1 -1
- package/dist/cli/ralph.d.ts.map +1 -1
- package/dist/cli/ralph.js +3 -1
- package/dist/cli/ralph.js.map +1 -1
- package/dist/cli/setup-preferences.d.ts +2 -0
- package/dist/cli/setup-preferences.d.ts.map +1 -1
- package/dist/cli/setup-preferences.js +4 -0
- package/dist/cli/setup-preferences.js.map +1 -1
- package/dist/cli/setup.d.ts +3 -0
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +166 -27
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/state.d.ts.map +1 -1
- package/dist/cli/state.js +8 -1
- package/dist/cli/state.js.map +1 -1
- package/dist/cli/tmux-hook.d.ts.map +1 -1
- package/dist/cli/tmux-hook.js +16 -0
- package/dist/cli/tmux-hook.js.map +1 -1
- package/dist/cli/update.d.ts +2 -0
- package/dist/cli/update.d.ts.map +1 -1
- package/dist/cli/update.js +47 -3
- package/dist/cli/update.js.map +1 -1
- package/dist/config/__tests__/generator-notify.test.js +1 -0
- package/dist/config/__tests__/generator-notify.test.js.map +1 -1
- package/dist/config/generator.d.ts +2 -2
- package/dist/config/generator.d.ts.map +1 -1
- package/dist/config/generator.js +2 -2
- package/dist/config/generator.js.map +1 -1
- package/dist/config/team-mode.d.ts +12 -0
- package/dist/config/team-mode.d.ts.map +1 -0
- package/dist/config/team-mode.js +91 -0
- package/dist/config/team-mode.js.map +1 -0
- package/dist/hooks/__tests__/agents-overlay.test.js +88 -0
- package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
- package/dist/hooks/__tests__/code-review-skill-contract.test.js +8 -0
- package/dist/hooks/__tests__/code-review-skill-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/keyword-detector.test.js +423 -3
- package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js +1 -1
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +189 -0
- package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +35 -2
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js +3 -3
- package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
- package/dist/hooks/__tests__/skill-guidance-contract.test.js +21 -0
- package/dist/hooks/__tests__/skill-guidance-contract.test.js.map +1 -1
- package/dist/hooks/agents-overlay.d.ts.map +1 -1
- package/dist/hooks/agents-overlay.js +36 -50
- package/dist/hooks/agents-overlay.js.map +1 -1
- package/dist/hooks/extensibility/__tests__/plugin-runner.test.js +31 -0
- package/dist/hooks/extensibility/__tests__/plugin-runner.test.js.map +1 -1
- package/dist/hooks/extensibility/plugin-runner.js +17 -21
- package/dist/hooks/extensibility/plugin-runner.js.map +1 -1
- package/dist/hooks/keyword-detector.d.ts.map +1 -1
- package/dist/hooks/keyword-detector.js +258 -12
- package/dist/hooks/keyword-detector.js.map +1 -1
- package/dist/hooks/prompt-guidance-contract.d.ts.map +1 -1
- package/dist/hooks/prompt-guidance-contract.js +6 -0
- package/dist/hooks/prompt-guidance-contract.js.map +1 -1
- package/dist/hooks/session.d.ts +1 -0
- package/dist/hooks/session.d.ts.map +1 -1
- package/dist/hooks/session.js.map +1 -1
- package/dist/hud/__tests__/authority.test.js +435 -32
- package/dist/hud/__tests__/authority.test.js.map +1 -1
- package/dist/hud/__tests__/hud-tmux-injection.test.js +2 -1
- package/dist/hud/__tests__/hud-tmux-injection.test.js.map +1 -1
- package/dist/hud/__tests__/index.test.js +42 -0
- package/dist/hud/__tests__/index.test.js.map +1 -1
- package/dist/hud/__tests__/reconcile.test.js +521 -15
- package/dist/hud/__tests__/reconcile.test.js.map +1 -1
- package/dist/hud/__tests__/render.test.js +61 -0
- package/dist/hud/__tests__/render.test.js.map +1 -1
- package/dist/hud/__tests__/state.test.js +132 -4
- package/dist/hud/__tests__/state.test.js.map +1 -1
- package/dist/hud/__tests__/tmux.test.js +180 -21
- package/dist/hud/__tests__/tmux.test.js.map +1 -1
- package/dist/hud/authority.d.ts +5 -0
- package/dist/hud/authority.d.ts.map +1 -1
- package/dist/hud/authority.js +324 -28
- package/dist/hud/authority.js.map +1 -1
- package/dist/hud/index.d.ts +3 -2
- package/dist/hud/index.d.ts.map +1 -1
- package/dist/hud/index.js +42 -19
- package/dist/hud/index.js.map +1 -1
- package/dist/hud/reconcile.d.ts +3 -3
- package/dist/hud/reconcile.d.ts.map +1 -1
- package/dist/hud/reconcile.js +128 -19
- package/dist/hud/reconcile.js.map +1 -1
- package/dist/hud/render.d.ts.map +1 -1
- package/dist/hud/render.js +35 -0
- package/dist/hud/render.js.map +1 -1
- package/dist/hud/state.d.ts.map +1 -1
- package/dist/hud/state.js +61 -62
- package/dist/hud/state.js.map +1 -1
- package/dist/hud/tmux.d.ts +24 -6
- package/dist/hud/tmux.d.ts.map +1 -1
- package/dist/hud/tmux.js +136 -38
- package/dist/hud/tmux.js.map +1 -1
- package/dist/hud/types.d.ts +11 -0
- package/dist/hud/types.d.ts.map +1 -1
- package/dist/hud/types.js.map +1 -1
- package/dist/mcp/__tests__/state-paths.test.js +71 -1
- package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
- package/dist/mcp/state-paths.d.ts +32 -0
- package/dist/mcp/state-paths.d.ts.map +1 -1
- package/dist/mcp/state-paths.js +113 -17
- package/dist/mcp/state-paths.js.map +1 -1
- package/dist/mcp/state-server.d.ts +4 -4
- package/dist/scripts/__tests__/codex-native-hook.test.js +593 -11
- package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
- package/dist/scripts/__tests__/notify-state-io.test.js +72 -1
- package/dist/scripts/__tests__/notify-state-io.test.js.map +1 -1
- package/dist/scripts/__tests__/notify-tmux-injection.test.d.ts +2 -0
- package/dist/scripts/__tests__/notify-tmux-injection.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/notify-tmux-injection.test.js +57 -0
- package/dist/scripts/__tests__/notify-tmux-injection.test.js.map +1 -0
- package/dist/scripts/__tests__/run-test-files.test.js +74 -0
- package/dist/scripts/__tests__/run-test-files.test.js.map +1 -1
- package/dist/scripts/__tests__/verify-native-agents.test.js +65 -0
- 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 +88 -31
- package/dist/scripts/codex-native-hook.js.map +1 -1
- package/dist/scripts/eval/eval-parity-smoke.js +1 -1
- package/dist/scripts/eval/eval-parity-smoke.js.map +1 -1
- package/dist/scripts/notify-hook/auto-nudge.d.ts.map +1 -1
- package/dist/scripts/notify-hook/auto-nudge.js +3 -1
- package/dist/scripts/notify-hook/auto-nudge.js.map +1 -1
- package/dist/scripts/notify-hook/ralph-session-resume.d.ts.map +1 -1
- package/dist/scripts/notify-hook/ralph-session-resume.js +3 -10
- package/dist/scripts/notify-hook/ralph-session-resume.js.map +1 -1
- package/dist/scripts/notify-hook/state-io.d.ts.map +1 -1
- package/dist/scripts/notify-hook/state-io.js +62 -38
- package/dist/scripts/notify-hook/state-io.js.map +1 -1
- package/dist/scripts/notify-hook/team-leader-nudge.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-leader-nudge.js +7 -0
- package/dist/scripts/notify-hook/team-leader-nudge.js.map +1 -1
- package/dist/scripts/notify-hook/tmux-injection.d.ts +7 -0
- package/dist/scripts/notify-hook/tmux-injection.d.ts.map +1 -1
- package/dist/scripts/notify-hook/tmux-injection.js +24 -18
- package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
- package/dist/scripts/notify-hook.js +75 -11
- package/dist/scripts/notify-hook.js.map +1 -1
- package/dist/scripts/run-test-files.js +193 -22
- package/dist/scripts/run-test-files.js.map +1 -1
- package/dist/scripts/sync-plugin-mirror.d.ts.map +1 -1
- package/dist/scripts/sync-plugin-mirror.js +61 -3
- package/dist/scripts/sync-plugin-mirror.js.map +1 -1
- package/dist/scripts/verify-native-agents.d.ts.map +1 -1
- package/dist/scripts/verify-native-agents.js +58 -1
- package/dist/scripts/verify-native-agents.js.map +1 -1
- package/dist/state/__tests__/operations.test.js +113 -0
- package/dist/state/__tests__/operations.test.js.map +1 -1
- package/dist/state/__tests__/skill-active.test.js +3 -16
- package/dist/state/__tests__/skill-active.test.js.map +1 -1
- package/dist/state/__tests__/workflow-transition.test.js +25 -0
- package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
- package/dist/state/operations.d.ts.map +1 -1
- package/dist/state/operations.js +57 -2
- package/dist/state/operations.js.map +1 -1
- package/dist/state/skill-active.d.ts.map +1 -1
- package/dist/state/skill-active.js +7 -39
- package/dist/state/skill-active.js.map +1 -1
- package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
- package/dist/state/workflow-transition-reconcile.js +10 -14
- package/dist/state/workflow-transition-reconcile.js.map +1 -1
- package/dist/team/__tests__/runtime.test.js +1 -1
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/scaling.test.js +9 -4
- package/dist/team/__tests__/scaling.test.js.map +1 -1
- package/dist/team/__tests__/tmux-session.test.js +195 -2
- package/dist/team/__tests__/tmux-session.test.js.map +1 -1
- package/dist/team/__tests__/worker-runtime-identity.test.js +4 -2
- package/dist/team/__tests__/worker-runtime-identity.test.js.map +1 -1
- package/dist/team/scaling.d.ts.map +1 -1
- package/dist/team/scaling.js +3 -2
- package/dist/team/scaling.js.map +1 -1
- package/dist/team/tmux-session.d.ts +2 -0
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +142 -12
- package/dist/team/tmux-session.js.map +1 -1
- package/dist/verification/__tests__/ci-rust-gates.test.js +81 -1
- package/dist/verification/__tests__/ci-rust-gates.test.js.map +1 -1
- package/package.json +8 -8
- package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/oh-my-codex/hooks/codex-native-hook.mjs +334 -21
- package/plugins/oh-my-codex/hooks/hooks.json +1 -2
- package/plugins/oh-my-codex/skills/autopilot/SKILL.md +3 -1
- package/plugins/oh-my-codex/skills/code-review/SKILL.md +7 -7
- package/plugins/oh-my-codex/skills/ralph/SKILL.md +22 -22
- package/plugins/oh-my-codex/skills/ultraqa/SKILL.md +9 -0
- package/skills/autopilot/SKILL.md +3 -1
- package/skills/code-review/SKILL.md +7 -7
- package/skills/ralph/SKILL.md +22 -22
- package/skills/ultraqa/SKILL.md +9 -0
- package/src/scripts/__tests__/codex-native-hook.test.ts +686 -13
- package/src/scripts/__tests__/notify-state-io.test.ts +95 -0
- package/src/scripts/__tests__/notify-tmux-injection.test.ts +82 -0
- package/src/scripts/__tests__/run-test-files.test.ts +102 -0
- package/src/scripts/__tests__/verify-native-agents.test.ts +75 -0
- package/src/scripts/codex-native-hook.ts +105 -28
- package/src/scripts/demo-team-e2e.sh +10 -7
- package/src/scripts/eval/eval-parity-smoke.ts +1 -1
- package/src/scripts/notify-hook/auto-nudge.ts +3 -1
- package/src/scripts/notify-hook/ralph-session-resume.ts +2 -8
- package/src/scripts/notify-hook/state-io.ts +75 -37
- package/src/scripts/notify-hook/team-leader-nudge.ts +7 -0
- package/src/scripts/notify-hook/tmux-injection.ts +35 -19
- package/src/scripts/notify-hook.ts +91 -4
- package/src/scripts/run-test-files.ts +192 -22
- package/src/scripts/sync-plugin-mirror.ts +98 -9
- package/src/scripts/verify-native-agents.ts +65 -1
|
@@ -48,6 +48,7 @@ import {
|
|
|
48
48
|
type SkillActiveState,
|
|
49
49
|
} from "../hooks/keyword-detector.js";
|
|
50
50
|
import { buildDeepInterviewConfigInstruction } from "../hooks/deep-interview-config-instruction.js";
|
|
51
|
+
import { readTeamModeConfig } from "../config/team-mode.js";
|
|
51
52
|
import {
|
|
52
53
|
detectNativeStopStallPattern,
|
|
53
54
|
loadAutoNudgeConfig,
|
|
@@ -189,6 +190,25 @@ function resolveHudReconcileSessionId(
|
|
|
189
190
|
return canonicalSessionId || sessionIdForState || undefined;
|
|
190
191
|
}
|
|
191
192
|
|
|
193
|
+
function resolveHudReconcileSessionIds(
|
|
194
|
+
currentSessionState: SessionState | null,
|
|
195
|
+
canonicalSessionId: string | null,
|
|
196
|
+
sessionIdForState: string | null,
|
|
197
|
+
nativeSessionId: string | null,
|
|
198
|
+
): string[] {
|
|
199
|
+
const ownerOmxSessionId = safeString(currentSessionState?.owner_omx_session_id).trim();
|
|
200
|
+
return uniqueNonEmpty([
|
|
201
|
+
resolveHudReconcileSessionId(currentSessionState, canonicalSessionId, sessionIdForState),
|
|
202
|
+
canonicalSessionId ?? undefined,
|
|
203
|
+
sessionIdForState ?? undefined,
|
|
204
|
+
nativeSessionId ?? undefined,
|
|
205
|
+
safeString(currentSessionState?.session_id),
|
|
206
|
+
safeString(currentSessionState?.native_session_id),
|
|
207
|
+
OMX_OWNER_SESSION_ID_PATTERN.test(ownerOmxSessionId) ? ownerOmxSessionId : undefined,
|
|
208
|
+
safeString(currentSessionState?.owner_codex_session_id),
|
|
209
|
+
]);
|
|
210
|
+
}
|
|
211
|
+
|
|
192
212
|
function safeContextSnippet(value: unknown, maxLength = 300): string {
|
|
193
213
|
const text = safeString(value).replace(/\s+/g, " ").trim();
|
|
194
214
|
if (text.length <= maxLength) return text;
|
|
@@ -1730,6 +1750,7 @@ function buildNativeOutsideTmuxTeamPromptBlockState(
|
|
|
1730
1750
|
threadId?: string,
|
|
1731
1751
|
turnId?: string,
|
|
1732
1752
|
): SkillActiveState | null {
|
|
1753
|
+
if (!readTeamModeConfig(cwd).enabled) return null;
|
|
1733
1754
|
const match = detectPrimaryKeyword(prompt);
|
|
1734
1755
|
if (match?.skill !== "team") return null;
|
|
1735
1756
|
|
|
@@ -1765,11 +1786,14 @@ function buildSkillStateCliInstruction(mode: string, statePath: string): string
|
|
|
1765
1786
|
|
|
1766
1787
|
function buildAutopilotPromptActivationNote(
|
|
1767
1788
|
skillState?: SkillActiveState | null,
|
|
1768
|
-
options: { markedQuestionAnswer?: boolean } = {},
|
|
1789
|
+
options: { markedQuestionAnswer?: boolean; cwd?: string } = {},
|
|
1769
1790
|
): string | null {
|
|
1770
1791
|
if (skillState?.initialized_mode !== "autopilot") return null;
|
|
1792
|
+
const teamHandoff = readTeamModeConfig(options.cwd).enabled
|
|
1793
|
+
? " (+ $team if needed)"
|
|
1794
|
+
: "";
|
|
1771
1795
|
return [
|
|
1772
|
-
|
|
1796
|
+
`Autopilot protocol: the durable default chain is $deep-interview -> $ralplan -> $ultragoal${teamHandoff} -> $code-review -> $ultraqa (deep-interview -> ralplan -> ultragoal -> code-review -> ultraqa).`,
|
|
1773
1797
|
"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
1798
|
"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
1799
|
options.markedQuestionAnswer
|
|
@@ -1782,6 +1806,12 @@ function buildAutopilotPromptActivationNote(
|
|
|
1782
1806
|
].filter(Boolean).join(" ");
|
|
1783
1807
|
}
|
|
1784
1808
|
|
|
1809
|
+
function formatExecutionHandoffList(cwd: string): string {
|
|
1810
|
+
return readTeamModeConfig(cwd).enabled
|
|
1811
|
+
? "`$ultragoal`, `$team`, or `$ralph`"
|
|
1812
|
+
: "`$ultragoal` or `$ralph`";
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1785
1815
|
function buildAdditionalContextMessage(
|
|
1786
1816
|
prompt: string,
|
|
1787
1817
|
skillState?: SkillActiveState | null,
|
|
@@ -1790,8 +1820,9 @@ function buildAdditionalContextMessage(
|
|
|
1790
1820
|
): string | null {
|
|
1791
1821
|
if (!prompt) return null;
|
|
1792
1822
|
const promptPriorityMessage = buildPromptPriorityMessage(prompt);
|
|
1793
|
-
const
|
|
1794
|
-
const
|
|
1823
|
+
const teamMode = readTeamModeConfig(cwd);
|
|
1824
|
+
const matches = detectKeywords(prompt).filter((entry) => teamMode.enabled || entry.skill !== "team");
|
|
1825
|
+
const match = matches[0] ?? null;
|
|
1795
1826
|
if (!match) {
|
|
1796
1827
|
const continuedSkill = safeString(skillState?.skill).trim();
|
|
1797
1828
|
if (!continuedSkill) return promptPriorityMessage;
|
|
@@ -1800,7 +1831,7 @@ function buildAdditionalContextMessage(
|
|
|
1800
1831
|
: null;
|
|
1801
1832
|
const deepInterviewConfigPromptActivationNote = buildDeepInterviewConfigInstruction(cwd, skillState);
|
|
1802
1833
|
const markedQuestionAnswer = /^\s*\[omx question answered\]/i.test(prompt);
|
|
1803
|
-
const autopilotPromptActivationNote = buildAutopilotPromptActivationNote(skillState, { markedQuestionAnswer });
|
|
1834
|
+
const autopilotPromptActivationNote = buildAutopilotPromptActivationNote(skillState, { markedQuestionAnswer, cwd });
|
|
1804
1835
|
return [
|
|
1805
1836
|
`OMX native UserPromptSubmit continued active workflow skill "${continuedSkill}".`,
|
|
1806
1837
|
promptPriorityMessage,
|
|
@@ -1826,7 +1857,7 @@ function buildAdditionalContextMessage(
|
|
|
1826
1857
|
? buildDeepInterviewQuestionBridgeInstruction(cwd, payload)
|
|
1827
1858
|
: null;
|
|
1828
1859
|
const deepInterviewConfigPromptActivationNote = buildDeepInterviewConfigInstruction(cwd, skillState);
|
|
1829
|
-
const autopilotPromptActivationNote = buildAutopilotPromptActivationNote(skillState, { markedQuestionAnswer: true });
|
|
1860
|
+
const autopilotPromptActivationNote = buildAutopilotPromptActivationNote(skillState, { markedQuestionAnswer: true, cwd });
|
|
1830
1861
|
return [
|
|
1831
1862
|
`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
1863
|
promptPriorityMessage,
|
|
@@ -1859,7 +1890,7 @@ function buildAdditionalContextMessage(
|
|
|
1859
1890
|
const ultragoalPromptActivationNote = match.skill === "ultragoal"
|
|
1860
1891
|
? "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."
|
|
1861
1892
|
: null;
|
|
1862
|
-
const autopilotPromptActivationNote = buildAutopilotPromptActivationNote(skillState);
|
|
1893
|
+
const autopilotPromptActivationNote = buildAutopilotPromptActivationNote(skillState, { cwd });
|
|
1863
1894
|
const combinedTransitionMessage = (() => {
|
|
1864
1895
|
if (!skillState?.transition_message) return null;
|
|
1865
1896
|
if (matches.length <= 1 || activeSkills.length <= 1) return skillState.transition_message;
|
|
@@ -2464,8 +2495,11 @@ function readPayloadTurnId(payload: CodexHookPayload): string {
|
|
|
2464
2495
|
async function resolveInternalSessionIdForPayload(
|
|
2465
2496
|
cwd: string,
|
|
2466
2497
|
payloadSessionId: string,
|
|
2498
|
+
stateDir?: string,
|
|
2467
2499
|
): Promise<string> {
|
|
2468
|
-
const currentSession =
|
|
2500
|
+
const currentSession = stateDir
|
|
2501
|
+
? await readUsableSessionStateFromStateDir(cwd, stateDir)
|
|
2502
|
+
: await readUsableSessionState(cwd);
|
|
2469
2503
|
const canonicalSessionId = safeString(currentSession?.session_id).trim();
|
|
2470
2504
|
if (!canonicalSessionId) return payloadSessionId;
|
|
2471
2505
|
|
|
@@ -2476,6 +2510,22 @@ async function resolveInternalSessionIdForPayload(
|
|
|
2476
2510
|
return payloadSessionId;
|
|
2477
2511
|
}
|
|
2478
2512
|
|
|
2513
|
+
async function readUsableSessionStateFromStateDir(
|
|
2514
|
+
cwd: string,
|
|
2515
|
+
stateDir: string,
|
|
2516
|
+
): Promise<SessionState | null> {
|
|
2517
|
+
const sessionPath = join(stateDir, "session.json");
|
|
2518
|
+
if (!existsSync(sessionPath)) return null;
|
|
2519
|
+
|
|
2520
|
+
try {
|
|
2521
|
+
const content = await readFile(sessionPath, "utf-8");
|
|
2522
|
+
const state = JSON.parse(content) as SessionState;
|
|
2523
|
+
return isSessionStateUsable(state, cwd) ? state : null;
|
|
2524
|
+
} catch {
|
|
2525
|
+
return null;
|
|
2526
|
+
}
|
|
2527
|
+
}
|
|
2528
|
+
|
|
2479
2529
|
async function readStopSessionPinnedState(
|
|
2480
2530
|
fileName: string,
|
|
2481
2531
|
cwd: string,
|
|
@@ -2514,7 +2564,8 @@ const PLANNING_MODE_IMPLEMENTATION_TOOL_NAMES = new Set([
|
|
|
2514
2564
|
const DEEP_INTERVIEW_IMPLEMENTATION_TOOL_NAMES = PLANNING_MODE_IMPLEMENTATION_TOOL_NAMES;
|
|
2515
2565
|
|
|
2516
2566
|
const RALPLAN_EXECUTION_HANDOFF_SKILLS = new Set([
|
|
2517
|
-
|
|
2567
|
+
// Autopilot is intentionally excluded: it supervises planning phases such as
|
|
2568
|
+
// ralplan/replan and is not by itself an execution authorization.
|
|
2518
2569
|
"autoresearch",
|
|
2519
2570
|
"ralph",
|
|
2520
2571
|
"team",
|
|
@@ -2546,7 +2597,7 @@ function isActiveAutopilotRalplanPhase(state: Record<string, unknown> | null): b
|
|
|
2546
2597
|
const mode = safeString(state.mode).trim();
|
|
2547
2598
|
if (mode && mode !== "autopilot") return false;
|
|
2548
2599
|
const phase = safeString(state.current_phase ?? state.currentPhase).trim().toLowerCase();
|
|
2549
|
-
return phase === "ralplan";
|
|
2600
|
+
return phase === "ralplan" || phase === "replan" || phase === "autopilot:replan";
|
|
2550
2601
|
}
|
|
2551
2602
|
|
|
2552
2603
|
function hasExplicitExecutionHandoffSkill(
|
|
@@ -2649,7 +2700,7 @@ async function readActiveDeepInterviewStateForPreToolUse(
|
|
|
2649
2700
|
const canonicalState = sessionId
|
|
2650
2701
|
? await readVisibleSkillActiveStateForStateDir(stateDir, sessionId)
|
|
2651
2702
|
: await readSkillActiveState(join(stateDir, SKILL_ACTIVE_STATE_FILE));
|
|
2652
|
-
if (!canonicalState) return
|
|
2703
|
+
if (!canonicalState) return null;
|
|
2653
2704
|
const hasActiveDeepInterviewSkill = listActiveSkills(canonicalState).some((entry) => (
|
|
2654
2705
|
entry.skill === "deep-interview"
|
|
2655
2706
|
&& matchesSkillStopContext(entry, canonicalState, sessionId, threadId)
|
|
@@ -2671,7 +2722,7 @@ async function readActiveRalplanStateForPreToolUse(
|
|
|
2671
2722
|
: await readSkillActiveState(join(stateDir, SKILL_ACTIVE_STATE_FILE));
|
|
2672
2723
|
if (isActiveRalplanPhase(modeState) && modeState && modeStateMatchesSkillStopContext(modeState, cwd, sessionId)) {
|
|
2673
2724
|
if (hasExplicitExecutionHandoffSkill(canonicalState, sessionId, threadId)) return null;
|
|
2674
|
-
if (!canonicalState) return
|
|
2725
|
+
if (!canonicalState) return null;
|
|
2675
2726
|
const hasActiveRalplanSkill = listActiveSkills(canonicalState).some((entry) => (
|
|
2676
2727
|
entry.skill === "ralplan"
|
|
2677
2728
|
&& matchesSkillStopContext(entry, canonicalState, sessionId, threadId)
|
|
@@ -2686,7 +2737,7 @@ async function readActiveRalplanStateForPreToolUse(
|
|
|
2686
2737
|
if (!modeStateMatchesSkillStopContext(autopilotState, cwd, sessionId)) return null;
|
|
2687
2738
|
const terminalAutopilotRunState = await readCanonicalTerminalRunStateForStop(cwd, sessionId, "autopilot");
|
|
2688
2739
|
if (terminalAutopilotRunState) return null;
|
|
2689
|
-
if (!canonicalState) return
|
|
2740
|
+
if (!canonicalState) return null;
|
|
2690
2741
|
const hasActiveAutopilotSkill = listActiveSkills(canonicalState).some((entry) => (
|
|
2691
2742
|
entry.skill === "autopilot"
|
|
2692
2743
|
&& matchesSkillStopContext(entry, canonicalState, sessionId, threadId)
|
|
@@ -2705,8 +2756,9 @@ async function buildRalplanPreToolUseBoundaryOutput(
|
|
|
2705
2756
|
payload: CodexHookPayload,
|
|
2706
2757
|
cwd: string,
|
|
2707
2758
|
stateDir: string,
|
|
2759
|
+
resolvedSessionId?: string,
|
|
2708
2760
|
): Promise<Record<string, unknown> | null> {
|
|
2709
|
-
const sessionId = readPayloadSessionId(payload);
|
|
2761
|
+
const sessionId = safeString(resolvedSessionId ?? readPayloadSessionId(payload)).trim();
|
|
2710
2762
|
const threadId = readPayloadThreadId(payload);
|
|
2711
2763
|
const activeState = await readActiveRalplanStateForPreToolUse(cwd, stateDir, sessionId, threadId);
|
|
2712
2764
|
if (!activeState) return null;
|
|
@@ -2726,13 +2778,21 @@ async function buildRalplanPreToolUseBoundaryOutput(
|
|
|
2726
2778
|
if (!blocked) return null;
|
|
2727
2779
|
|
|
2728
2780
|
const phase = formatPhase(activeState.current_phase ?? activeState.currentPhase, "planning");
|
|
2781
|
+
const activeMode = safeString(activeState.mode).trim().toLowerCase();
|
|
2782
|
+
const planningModeLabel = activeMode === "autopilot" ? "Autopilot planning" : "Ralplan";
|
|
2783
|
+
const planningModeDescription = activeMode === "autopilot"
|
|
2784
|
+
? "Autopilot is supervising a planning phase"
|
|
2785
|
+
: "Ralplan is consensus-planning mode";
|
|
2729
2786
|
return {
|
|
2730
2787
|
decision: "block",
|
|
2731
|
-
reason:
|
|
2788
|
+
reason: `${planningModeLabel} is active (phase: ${phase}); implementation/write tools are blocked until an explicit execution handoff workflow is activated.`,
|
|
2732
2789
|
hookSpecificOutput: {
|
|
2733
2790
|
hookEventName: "PreToolUse",
|
|
2734
2791
|
additionalContext:
|
|
2735
|
-
|
|
2792
|
+
`${planningModeDescription}. `
|
|
2793
|
+
+ "Write only planning artifacts under `.omx/context/`, `.omx/plans/`, `.omx/specs/`, or required `.omx/state/` files. "
|
|
2794
|
+
+ "Do not edit implementation files or run implementation-focused writes from planning phases. "
|
|
2795
|
+
+ `To execute, first process an explicit handoff such as ${formatExecutionHandoffList(cwd)}, which must emit terminal planning state before implementation begins.`,
|
|
2736
2796
|
},
|
|
2737
2797
|
};
|
|
2738
2798
|
}
|
|
@@ -2741,8 +2801,9 @@ async function buildDeepInterviewPreToolUseBoundaryOutput(
|
|
|
2741
2801
|
payload: CodexHookPayload,
|
|
2742
2802
|
cwd: string,
|
|
2743
2803
|
stateDir: string,
|
|
2804
|
+
resolvedSessionId?: string,
|
|
2744
2805
|
): Promise<Record<string, unknown> | null> {
|
|
2745
|
-
const sessionId = readPayloadSessionId(payload);
|
|
2806
|
+
const sessionId = safeString(resolvedSessionId ?? readPayloadSessionId(payload)).trim();
|
|
2746
2807
|
const threadId = readPayloadThreadId(payload);
|
|
2747
2808
|
const activeState = await readActiveDeepInterviewStateForPreToolUse(cwd, stateDir, sessionId, threadId);
|
|
2748
2809
|
if (!activeState) return null;
|
|
@@ -2768,7 +2829,7 @@ async function buildDeepInterviewPreToolUseBoundaryOutput(
|
|
|
2768
2829
|
hookSpecificOutput: {
|
|
2769
2830
|
hookEventName: "PreToolUse",
|
|
2770
2831
|
additionalContext:
|
|
2771
|
-
|
|
2832
|
+
`Deep-interview is requirements/spec mode. Treat detailed user answers as interview/spec material, not implicit implementation authorization. You may write only deep-interview artifacts under \`.omx/context/\`, \`.omx/interviews/\`, \`.omx/specs/\`, or required \`.omx/state/\` files. To implement, first ask for or process an explicit transition such as \`$ralplan\`, \`$autopilot\`, ${formatExecutionHandoffList(cwd)}.`,
|
|
2772
2833
|
},
|
|
2773
2834
|
};
|
|
2774
2835
|
}
|
|
@@ -3027,6 +3088,7 @@ async function reconcileStaleRootSkillActiveStateForStop(
|
|
|
3027
3088
|
function buildRalplanContinuationStatus(
|
|
3028
3089
|
blocker: { phase: string; latestPlanPath?: string; planningComplete?: boolean; runOutcome?: string },
|
|
3029
3090
|
activeSubagentCount: number,
|
|
3091
|
+
cwd: string,
|
|
3030
3092
|
): { reason: string; systemMessage: string; stopReasonSuffix: string } {
|
|
3031
3093
|
const phase = blocker.phase || "planning";
|
|
3032
3094
|
const artifact = blocker.latestPlanPath
|
|
@@ -3062,7 +3124,7 @@ function buildRalplanContinuationStatus(
|
|
|
3062
3124
|
}
|
|
3063
3125
|
|
|
3064
3126
|
const completeHint = blocker.planningComplete
|
|
3065
|
-
?
|
|
3127
|
+
? ` 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 ${formatExecutionHandoffList(cwd).replaceAll("`", "")} handoff.`
|
|
3066
3128
|
: "";
|
|
3067
3129
|
|
|
3068
3130
|
return {
|
|
@@ -3282,6 +3344,11 @@ async function maybeBuildOrdinaryStopNoProgressOutput(
|
|
|
3282
3344
|
stateDir: string,
|
|
3283
3345
|
canonicalSessionId?: string,
|
|
3284
3346
|
): Promise<Record<string, unknown> | null> {
|
|
3347
|
+
const lastAssistantMessage = safeString(
|
|
3348
|
+
payload.last_assistant_message ?? payload.lastAssistantMessage,
|
|
3349
|
+
).trim();
|
|
3350
|
+
if (!lastAssistantMessage) return null;
|
|
3351
|
+
|
|
3285
3352
|
const statePath = join(stateDir, NATIVE_STOP_STATE_FILE);
|
|
3286
3353
|
const state = await readJsonIfExists(statePath) ?? {};
|
|
3287
3354
|
const sessions = safeObject(state.sessions);
|
|
@@ -3313,9 +3380,6 @@ async function maybeBuildOrdinaryStopNoProgressOutput(
|
|
|
3313
3380
|
await mkdir(stateDir, { recursive: true });
|
|
3314
3381
|
await writeFile(statePath, JSON.stringify({ ...state, sessions }, null, 2));
|
|
3315
3382
|
|
|
3316
|
-
const stopHookActive = payload.stop_hook_active === true || payload.stopHookActive === true;
|
|
3317
|
-
if (!stopHookActive) return null;
|
|
3318
|
-
|
|
3319
3383
|
const maxRepeats = parseBoundedPositiveInteger(
|
|
3320
3384
|
process.env.OMX_NATIVE_STOP_NO_PROGRESS_MAX_REPEATS,
|
|
3321
3385
|
ORDINARY_STOP_NO_PROGRESS_DEFAULT_MAX_REPEATS,
|
|
@@ -3520,7 +3584,7 @@ async function buildSkillStopOutput(
|
|
|
3520
3584
|
const activeSubagentCount = subagentSummary?.activeSubagentThreadIds.length ?? 0;
|
|
3521
3585
|
|
|
3522
3586
|
if (blocker.skill === "ralplan") {
|
|
3523
|
-
const status = buildRalplanContinuationStatus(blocker, activeSubagentCount);
|
|
3587
|
+
const status = buildRalplanContinuationStatus(blocker, activeSubagentCount, cwd);
|
|
3524
3588
|
return {
|
|
3525
3589
|
decision: "block",
|
|
3526
3590
|
reason: status.reason,
|
|
@@ -3978,7 +4042,9 @@ export async function dispatchCodexNativeHook(
|
|
|
3978
4042
|
// Native hooks must use the same authoritative runtime state root as HUD/MCP
|
|
3979
4043
|
// when boxed/team roots are active; do not bypass it with cwd/.omx/state.
|
|
3980
4044
|
const stateDir = getBaseStateDir(cwd);
|
|
3981
|
-
|
|
4045
|
+
if (hookEventName !== "Stop") {
|
|
4046
|
+
await mkdir(stateDir, { recursive: true });
|
|
4047
|
+
}
|
|
3982
4048
|
|
|
3983
4049
|
const omxEventName = mapCodexHookEventToOmxEvent(hookEventName);
|
|
3984
4050
|
let skillState: SkillActiveState | null = null;
|
|
@@ -4179,16 +4245,23 @@ export async function dispatchCodexNativeHook(
|
|
|
4179
4245
|
triageAdditionalContext = null;
|
|
4180
4246
|
}
|
|
4181
4247
|
}
|
|
4248
|
+
const skipHudReconcileForDoctorSmoke = process.env.OMX_NATIVE_HOOK_DOCTOR_SMOKE === "1";
|
|
4182
4249
|
const skipHudReconcileForTeamWorkerPane = !isSubagentPromptSubmit
|
|
4183
4250
|
&& await isConfirmedTeamWorkerPromptSubmitPane(cwd).catch(() => false);
|
|
4184
|
-
if (!skipHudReconcileForTeamWorkerPane) {
|
|
4251
|
+
if (!skipHudReconcileForDoctorSmoke && !skipHudReconcileForTeamWorkerPane) {
|
|
4185
4252
|
const reconcileHudForPromptSubmitFn = options.reconcileHudForPromptSubmitFn ?? reconcileHudForPromptSubmit;
|
|
4186
4253
|
const hudSessionId = resolveHudReconcileSessionId(
|
|
4187
4254
|
currentSessionState,
|
|
4188
4255
|
canonicalSessionId,
|
|
4189
4256
|
sessionIdForState,
|
|
4190
4257
|
);
|
|
4191
|
-
|
|
4258
|
+
const hudSessionIds = resolveHudReconcileSessionIds(
|
|
4259
|
+
currentSessionState,
|
|
4260
|
+
canonicalSessionId,
|
|
4261
|
+
sessionIdForState,
|
|
4262
|
+
nativeSessionId,
|
|
4263
|
+
);
|
|
4264
|
+
await reconcileHudForPromptSubmitFn(cwd, { sessionId: hudSessionId, sessionIds: hudSessionIds }).catch(() => {});
|
|
4192
4265
|
}
|
|
4193
4266
|
}
|
|
4194
4267
|
|
|
@@ -4248,8 +4321,12 @@ export async function dispatchCodexNativeHook(
|
|
|
4248
4321
|
};
|
|
4249
4322
|
}
|
|
4250
4323
|
} else if (hookEventName === "PreToolUse") {
|
|
4251
|
-
|
|
4252
|
-
|
|
4324
|
+
const payloadSessionId = readPayloadSessionId(payload);
|
|
4325
|
+
const preToolUseSessionId = payloadSessionId
|
|
4326
|
+
? await resolveInternalSessionIdForPayload(cwd, payloadSessionId, stateDir)
|
|
4327
|
+
: "";
|
|
4328
|
+
outputJson = await buildDeepInterviewPreToolUseBoundaryOutput(payload, cwd, stateDir, preToolUseSessionId)
|
|
4329
|
+
?? await buildRalplanPreToolUseBoundaryOutput(payload, cwd, stateDir, preToolUseSessionId)
|
|
4253
4330
|
?? buildNativePreToolUseOutput(payload);
|
|
4254
4331
|
} else if (hookEventName === "PostToolUse") {
|
|
4255
4332
|
if (detectMcpTransportFailure(payload)) {
|
|
@@ -89,10 +89,8 @@ OMX_TEAM_WORKER_LAUNCH_ARGS="${OMX_TEAM_WORKER_LAUNCH_ARGS:--c model_reasoning_e
|
|
|
89
89
|
TEAM_STARTED=0
|
|
90
90
|
cleanup() {
|
|
91
91
|
if ((TEAM_STARTED == 1)); then
|
|
92
|
-
echo "[cleanup]
|
|
93
|
-
omx team
|
|
94
|
-
echo "[cleanup] cleaning state for team: $TEAM_NAME"
|
|
95
|
-
omx team api cleanup --input "{\"team_name\":\"$TEAM_NAME\"}" --json >/dev/null 2>&1 || true
|
|
92
|
+
echo "[cleanup] force-cleaning demo team: $TEAM_NAME"
|
|
93
|
+
omx team api cleanup --input "{\"team_name\":\"$TEAM_NAME\",\"force\":true,\"confirm_issues\":true}" --json >/dev/null 2>&1 || true
|
|
96
94
|
fi
|
|
97
95
|
}
|
|
98
96
|
trap cleanup EXIT
|
|
@@ -106,7 +104,13 @@ echo "OMX_TEAM_WORKER_CLI_MAP=$OMX_TEAM_WORKER_CLI_MAP"
|
|
|
106
104
|
echo "OMX_TEAM_WORKER_LAUNCH_ARGS=$OMX_TEAM_WORKER_LAUNCH_ARGS"
|
|
107
105
|
|
|
108
106
|
echo "[1/8] start team (${WORKER_COUNT} mixed workers)"
|
|
109
|
-
omx team "${WORKER_COUNT}:executor" "$TEAM_TASK"
|
|
107
|
+
START_OUTPUT="$(omx team "${WORKER_COUNT}:executor" "$TEAM_TASK")"
|
|
108
|
+
echo "$START_OUTPUT"
|
|
109
|
+
ACTUAL_TEAM_NAME="$(echo "$START_OUTPUT" | sed -nE 's/^Team started: ([^[:space:]]+)$/\1/p' | head -n 1)"
|
|
110
|
+
if [[ -n "$ACTUAL_TEAM_NAME" && "$ACTUAL_TEAM_NAME" != "$TEAM_NAME" ]]; then
|
|
111
|
+
echo "TEAM_NAME_RESOLVED=$ACTUAL_TEAM_NAME"
|
|
112
|
+
TEAM_NAME="$ACTUAL_TEAM_NAME"
|
|
113
|
+
fi
|
|
110
114
|
TEAM_STARTED=1
|
|
111
115
|
|
|
112
116
|
echo "[2/8] status"
|
|
@@ -174,8 +178,7 @@ SUMMARY_JSON="$(omx team api get-summary --input "$SUMMARY_INPUT" --json)"
|
|
|
174
178
|
echo "$SUMMARY_JSON" | jq -e '.schema_version == "1.0" and .operation == "get-summary" and .ok == true' >/dev/null
|
|
175
179
|
|
|
176
180
|
echo "[8/8] shutdown + cleanup"
|
|
177
|
-
omx team
|
|
178
|
-
omx team api cleanup --input "{\"team_name\":\"$TEAM_NAME\"}" --json >/dev/null
|
|
181
|
+
omx team api cleanup --input "{\"team_name\":\"$TEAM_NAME\",\"force\":true,\"confirm_issues\":true}" --json >/dev/null
|
|
179
182
|
TEAM_STARTED=0
|
|
180
183
|
|
|
181
184
|
echo "E2E demo complete."
|
|
@@ -9,7 +9,7 @@ if (build.status !== 0) {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
const test = spawnSync('node', [
|
|
12
|
-
'
|
|
12
|
+
'dist/scripts/run-test-files.js',
|
|
13
13
|
'dist/autoresearch/__tests__/contracts.test.js',
|
|
14
14
|
'dist/autoresearch/__tests__/runtime.test.js',
|
|
15
15
|
'dist/cli/__tests__/autoresearch.test.js',
|
|
@@ -269,7 +269,9 @@ export async function syncSkillStateFromTurn(stateDir, payload) {
|
|
|
269
269
|
|
|
270
270
|
|
|
271
271
|
export async function isDeepInterviewStateActive(stateDir, sessionId) {
|
|
272
|
-
const modeState =
|
|
272
|
+
const modeState = typeof sessionId === 'string' && sessionId.trim()
|
|
273
|
+
? await readScopedJsonIfExists(stateDir, 'deep-interview-state.json', sessionId, null)
|
|
274
|
+
: await readJsonIfExists(join(stateDir, 'deep-interview-state.json'), null);
|
|
273
275
|
return Boolean(modeState && modeState.active === true);
|
|
274
276
|
}
|
|
275
277
|
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { existsSync } from 'fs';
|
|
2
2
|
import { mkdir, readFile, readdir, rename, rm, stat, writeFile } from 'fs/promises';
|
|
3
|
-
import { dirname, join
|
|
3
|
+
import { dirname, join } from 'path';
|
|
4
4
|
import { captureTmuxPaneFromEnv } from '../../state/mode-state-context.js';
|
|
5
|
-
import { isSessionStateUsable } from '../../hooks/session.js';
|
|
6
5
|
import { resolveCodexPane } from '../tmux-hook-engine.js';
|
|
7
6
|
import { safeString } from './utils.js';
|
|
8
7
|
|
|
@@ -222,15 +221,10 @@ function readSessionIdFromEnvironment(env: NodeJS.ProcessEnv = process.env): str
|
|
|
222
221
|
|
|
223
222
|
async function readCurrentOmxSessionId(stateDir: string, env: NodeJS.ProcessEnv = process.env): Promise<string> {
|
|
224
223
|
const envSessionId = readSessionIdFromEnvironment(env);
|
|
225
|
-
if (envSessionId)
|
|
226
|
-
const envScopedDir = join(stateDir, 'sessions', envSessionId);
|
|
227
|
-
if (existsSync(envScopedDir)) return envSessionId;
|
|
228
|
-
}
|
|
224
|
+
if (envSessionId) return envSessionId;
|
|
229
225
|
|
|
230
|
-
const cwd = resolve(stateDir, '..', '..');
|
|
231
226
|
const session = await readJson(join(stateDir, 'session.json'));
|
|
232
227
|
if (!session || typeof session !== 'object') return '';
|
|
233
|
-
if (!isSessionStateUsable(session as any, cwd)) return '';
|
|
234
228
|
const sessionId = safeString(session?.session_id).trim();
|
|
235
229
|
return SESSION_ID_PATTERN.test(sessionId) ? sessionId : '';
|
|
236
230
|
}
|
|
@@ -3,12 +3,10 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { mkdir, readFile, readdir, writeFile } from 'fs/promises';
|
|
6
|
-
import { dirname, join
|
|
7
|
-
import {
|
|
8
|
-
import { isSessionStateUsable } from '../../hooks/session.js';
|
|
6
|
+
import { dirname, join } from 'path';
|
|
7
|
+
import { validateSessionId } from '../../mcp/state-paths.js';
|
|
9
8
|
import { asNumber, safeString } from './utils.js';
|
|
10
9
|
|
|
11
|
-
const SESSION_ID_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;
|
|
12
10
|
|
|
13
11
|
export { readdir };
|
|
14
12
|
|
|
@@ -25,46 +23,90 @@ function isSafeStateFileName(fileName: string): boolean {
|
|
|
25
23
|
&& !fileName.includes('\\');
|
|
26
24
|
}
|
|
27
25
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
26
|
+
interface SessionMetadata {
|
|
27
|
+
sessionId?: string;
|
|
28
|
+
nativeAliases: string[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function readSessionMetadataFromBaseStateDir(baseStateDir: string): Promise<SessionMetadata> {
|
|
32
|
+
const session = await readJsonIfExists(join(baseStateDir, 'session.json'), null);
|
|
33
|
+
let sessionId: string | undefined;
|
|
34
|
+
try {
|
|
35
|
+
sessionId = validateSessionId(session?.session_id);
|
|
36
|
+
} catch {
|
|
37
|
+
sessionId = undefined;
|
|
34
38
|
}
|
|
35
|
-
|
|
39
|
+
const nativeAliases = [
|
|
40
|
+
session?.native_session_id,
|
|
41
|
+
session?.codex_session_id,
|
|
42
|
+
session?.previous_native_session_id,
|
|
43
|
+
]
|
|
44
|
+
.map((value) => safeString(value).trim())
|
|
45
|
+
.filter(Boolean);
|
|
46
|
+
return { sessionId, nativeAliases: [...new Set(nativeAliases)] };
|
|
36
47
|
}
|
|
37
48
|
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
const
|
|
42
|
-
if (
|
|
49
|
+
function readSessionIdFromEnvironment(): string | undefined {
|
|
50
|
+
for (const candidate of [process.env.OMX_SESSION_ID, process.env.CODEX_SESSION_ID, process.env.SESSION_ID]) {
|
|
51
|
+
if (typeof candidate !== 'string') continue;
|
|
52
|
+
const trimmed = candidate.trim();
|
|
53
|
+
if (!trimmed) continue;
|
|
54
|
+
try {
|
|
55
|
+
const sessionId = validateSessionId(trimmed);
|
|
56
|
+
if (sessionId) return sessionId;
|
|
57
|
+
} catch {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
43
60
|
}
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
44
63
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
return SESSION_ID_PATTERN.test(sessionId) ? sessionId : undefined;
|
|
64
|
+
function resolveCanonicalSessionId(candidate: string | undefined, metadata: SessionMetadata): string | undefined {
|
|
65
|
+
if (!candidate) return undefined;
|
|
66
|
+
return metadata.sessionId && metadata.nativeAliases.includes(candidate)
|
|
67
|
+
? metadata.sessionId
|
|
68
|
+
: candidate;
|
|
51
69
|
}
|
|
52
70
|
|
|
53
|
-
|
|
71
|
+
async function resolveBaseScopedStateDir(
|
|
54
72
|
baseStateDir: string,
|
|
55
73
|
explicitSessionId?: string,
|
|
56
74
|
): Promise<string> {
|
|
57
|
-
const normalizedExplicit =
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
75
|
+
const normalizedExplicit = typeof explicitSessionId === 'string' && explicitSessionId.trim()
|
|
76
|
+
? explicitSessionId.trim()
|
|
77
|
+
: undefined;
|
|
78
|
+
const validatedExplicit = validateSessionId(normalizedExplicit);
|
|
79
|
+
const metadata = await readSessionMetadataFromBaseStateDir(baseStateDir);
|
|
80
|
+
const sessionId = resolveCanonicalSessionId(validatedExplicit, metadata)
|
|
81
|
+
?? resolveCanonicalSessionId(readSessionIdFromEnvironment(), metadata)
|
|
82
|
+
?? metadata.sessionId;
|
|
83
|
+
return sessionId ? join(baseStateDir, 'sessions', sessionId) : baseStateDir;
|
|
84
|
+
}
|
|
61
85
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
86
|
+
async function resolveBaseScopedStateDirs(
|
|
87
|
+
baseStateDir: string,
|
|
88
|
+
explicitSessionId?: string,
|
|
89
|
+
options: { includeRootFallback?: boolean } = {},
|
|
90
|
+
): Promise<string[]> {
|
|
91
|
+
const scopedDir = await resolveBaseScopedStateDir(baseStateDir, explicitSessionId);
|
|
92
|
+
return options.includeRootFallback === true && scopedDir !== baseStateDir
|
|
93
|
+
? [scopedDir, baseStateDir]
|
|
94
|
+
: [scopedDir];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
export async function readCurrentSessionId(baseStateDir: string): Promise<string | undefined> {
|
|
101
|
+
const metadata = await readSessionMetadataFromBaseStateDir(baseStateDir);
|
|
102
|
+
return resolveCanonicalSessionId(readSessionIdFromEnvironment(), metadata) ?? metadata.sessionId;
|
|
103
|
+
}
|
|
66
104
|
|
|
67
|
-
|
|
105
|
+
export async function resolveScopedStateDir(
|
|
106
|
+
baseStateDir: string,
|
|
107
|
+
explicitSessionId?: string,
|
|
108
|
+
): Promise<string> {
|
|
109
|
+
return resolveBaseScopedStateDir(baseStateDir, explicitSessionId);
|
|
68
110
|
}
|
|
69
111
|
|
|
70
112
|
export async function getScopedStateDirsForCurrentSession(
|
|
@@ -72,11 +114,7 @@ export async function getScopedStateDirsForCurrentSession(
|
|
|
72
114
|
explicitSessionId?: string,
|
|
73
115
|
options: { includeRootFallback?: boolean } = {},
|
|
74
116
|
): Promise<string[]> {
|
|
75
|
-
|
|
76
|
-
if (scopedDir === baseStateDir || options.includeRootFallback !== true) {
|
|
77
|
-
return [scopedDir];
|
|
78
|
-
}
|
|
79
|
-
return [scopedDir, baseStateDir];
|
|
117
|
+
return resolveBaseScopedStateDirs(baseStateDir, explicitSessionId, options);
|
|
80
118
|
}
|
|
81
119
|
|
|
82
120
|
export async function getScopedStatePath(
|
|
@@ -25,6 +25,7 @@ import { writeTeamLeaderAttention } from '../../team/state.js';
|
|
|
25
25
|
import { readLatestTeamProgressEvidenceMs } from '../../team/progress-evidence.js';
|
|
26
26
|
import { validateSessionId } from '../../mcp/state-paths.js';
|
|
27
27
|
import { TEAM_NAME_SAFE_PATTERN } from '../../team/contracts.js';
|
|
28
|
+
import { isDeepInterviewStateActive } from './auto-nudge.js';
|
|
28
29
|
const LEADER_PANE_MISSING_NO_INJECTION_REASON = 'leader_pane_missing_no_injection';
|
|
29
30
|
const LEADER_PANE_SHELL_NO_INJECTION_REASON = 'leader_pane_shell_no_injection';
|
|
30
31
|
const LEADER_PANE_SAME_CLASSIFIED_STATE_SUPPRESSED_REASON = 'pane_already_shows_same_classified_state';
|
|
@@ -571,6 +572,12 @@ export async function maybeNudgeTeamLeader({
|
|
|
571
572
|
|
|
572
573
|
const candidateTeamNames = new Set();
|
|
573
574
|
const currentSessionId = await resolveCurrentSessionId(stateDir);
|
|
575
|
+
const deepInterviewActive = currentSessionId
|
|
576
|
+
? await isDeepInterviewStateActive(stateDir, currentSessionId).catch(() => false)
|
|
577
|
+
: await isDeepInterviewStateActive(stateDir, undefined).catch(() => false);
|
|
578
|
+
if (deepInterviewActive) {
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
574
581
|
try {
|
|
575
582
|
const scopedDirs = await getScopedStateDirsForCurrentSession(stateDir);
|
|
576
583
|
const candidateStateDirs = [...new Set([...scopedDirs, stateDir])];
|