oh-my-codex 0.13.2 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +5 -5
- package/Cargo.toml +1 -1
- package/dist/autoresearch/__tests__/skill-validation.test.d.ts +2 -0
- package/dist/autoresearch/__tests__/skill-validation.test.d.ts.map +1 -0
- package/dist/autoresearch/__tests__/skill-validation.test.js +91 -0
- package/dist/autoresearch/__tests__/skill-validation.test.js.map +1 -0
- package/dist/autoresearch/skill-validation.d.ts +13 -0
- package/dist/autoresearch/skill-validation.d.ts.map +1 -0
- package/dist/autoresearch/skill-validation.js +165 -0
- package/dist/autoresearch/skill-validation.js.map +1 -0
- package/dist/catalog/__tests__/schema.test.js +6 -0
- package/dist/catalog/__tests__/schema.test.js.map +1 -1
- package/dist/cli/__tests__/autoresearch-guided.test.js +236 -273
- package/dist/cli/__tests__/autoresearch-guided.test.js.map +1 -1
- package/dist/cli/__tests__/autoresearch.test.js +64 -653
- package/dist/cli/__tests__/autoresearch.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +7 -0
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/nested-help-routing.test.js +2 -1
- package/dist/cli/__tests__/nested-help-routing.test.js.map +1 -1
- package/dist/cli/__tests__/question.test.d.ts +2 -0
- package/dist/cli/__tests__/question.test.d.ts.map +1 -0
- package/dist/cli/__tests__/question.test.js +113 -0
- package/dist/cli/__tests__/question.test.js.map +1 -0
- package/dist/cli/__tests__/session-search-help.test.js +1 -1
- package/dist/cli/__tests__/session-search-help.test.js.map +1 -1
- package/dist/cli/__tests__/setup-skills-overwrite.test.js +2 -0
- package/dist/cli/__tests__/setup-skills-overwrite.test.js.map +1 -1
- package/dist/cli/autoresearch-guided.d.ts +24 -7
- package/dist/cli/autoresearch-guided.d.ts.map +1 -1
- package/dist/cli/autoresearch-guided.js +189 -130
- package/dist/cli/autoresearch-guided.js.map +1 -1
- package/dist/cli/autoresearch.d.ts +3 -2
- package/dist/cli/autoresearch.d.ts.map +1 -1
- package/dist/cli/autoresearch.js +29 -305
- package/dist/cli/autoresearch.js.map +1 -1
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +43 -0
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +8 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/question.d.ts +3 -0
- package/dist/cli/question.d.ts.map +1 -0
- package/dist/cli/question.js +182 -0
- package/dist/cli/question.js.map +1 -0
- package/dist/hooks/__tests__/analyze-routing-contract.test.js +22 -13
- package/dist/hooks/__tests__/analyze-routing-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/anti-slop-workflow.test.js +3 -3
- package/dist/hooks/__tests__/anti-slop-workflow.test.js.map +1 -1
- package/dist/hooks/__tests__/debugger-log-recency-contract.test.js +2 -2
- package/dist/hooks/__tests__/debugger-log-recency-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/deep-interview-contract.test.js +22 -5
- package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/explore-sparkshell-guidance-contract.test.js +2 -2
- package/dist/hooks/__tests__/explore-sparkshell-guidance-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/keyword-detector.test.js +308 -17
- package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js +570 -2
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +717 -16
- package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js +25 -0
- package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js +894 -1
- package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js +34 -0
- package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js +132 -0
- package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
- package/dist/hooks/__tests__/prompt-guidance-contract.test.js +22 -4
- package/dist/hooks/__tests__/prompt-guidance-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/prompt-guidance-fragments.test.js +4 -2
- package/dist/hooks/__tests__/prompt-guidance-fragments.test.js.map +1 -1
- package/dist/hooks/__tests__/prompt-guidance-test-helpers.d.ts +1 -0
- package/dist/hooks/__tests__/prompt-guidance-test-helpers.d.ts.map +1 -1
- package/dist/hooks/__tests__/prompt-guidance-test-helpers.js +4 -1
- package/dist/hooks/__tests__/prompt-guidance-test-helpers.js.map +1 -1
- package/dist/hooks/__tests__/prompt-guidance-wave-two.test.js +28 -0
- package/dist/hooks/__tests__/prompt-guidance-wave-two.test.js.map +1 -1
- package/dist/hooks/__tests__/prompt-orchestration-boundary.test.js +5 -4
- package/dist/hooks/__tests__/prompt-orchestration-boundary.test.js.map +1 -1
- package/dist/hooks/__tests__/prompt-team-routing.test.js +2 -2
- package/dist/hooks/__tests__/prompt-team-routing.test.js.map +1 -1
- package/dist/hooks/__tests__/triage-config.test.d.ts +2 -0
- package/dist/hooks/__tests__/triage-config.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/triage-config.test.js +211 -0
- package/dist/hooks/__tests__/triage-config.test.js.map +1 -0
- package/dist/hooks/__tests__/triage-heuristic.test.d.ts +2 -0
- package/dist/hooks/__tests__/triage-heuristic.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/triage-heuristic.test.js +230 -0
- package/dist/hooks/__tests__/triage-heuristic.test.js.map +1 -0
- package/dist/hooks/__tests__/triage-state.test.d.ts +2 -0
- package/dist/hooks/__tests__/triage-state.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/triage-state.test.js +426 -0
- package/dist/hooks/__tests__/triage-state.test.js.map +1 -0
- package/dist/hooks/keyword-detector.d.ts +26 -7
- package/dist/hooks/keyword-detector.d.ts.map +1 -1
- package/dist/hooks/keyword-detector.js +97 -26
- 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 +16 -9
- 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 +28 -1
- package/dist/hooks/prompt-guidance-contract.js.map +1 -1
- package/dist/hooks/triage-config.d.ts +33 -0
- package/dist/hooks/triage-config.d.ts.map +1 -0
- package/dist/hooks/triage-config.js +87 -0
- package/dist/hooks/triage-config.js.map +1 -0
- package/dist/hooks/triage-heuristic.d.ts +20 -0
- package/dist/hooks/triage-heuristic.d.ts.map +1 -0
- package/dist/hooks/triage-heuristic.js +210 -0
- package/dist/hooks/triage-heuristic.js.map +1 -0
- package/dist/hooks/triage-state.d.ts +63 -0
- package/dist/hooks/triage-state.d.ts.map +1 -0
- package/dist/hooks/triage-state.js +138 -0
- package/dist/hooks/triage-state.js.map +1 -0
- package/dist/hud/__tests__/reconcile.test.js +20 -0
- package/dist/hud/__tests__/reconcile.test.js.map +1 -1
- package/dist/hud/reconcile.d.ts +1 -0
- package/dist/hud/reconcile.d.ts.map +1 -1
- package/dist/hud/reconcile.js +2 -1
- package/dist/hud/reconcile.js.map +1 -1
- package/dist/mcp/__tests__/state-server.test.js +1 -0
- package/dist/mcp/__tests__/state-server.test.js.map +1 -1
- package/dist/mcp/state-server.d.ts +8 -0
- package/dist/mcp/state-server.d.ts.map +1 -1
- package/dist/mcp/state-server.js +4 -0
- package/dist/mcp/state-server.js.map +1 -1
- package/dist/modes/__tests__/base-ralph-contract.test.js +15 -0
- package/dist/modes/__tests__/base-ralph-contract.test.js.map +1 -1
- package/dist/modes/base.d.ts +1 -0
- package/dist/modes/base.d.ts.map +1 -1
- package/dist/modes/base.js +22 -6
- package/dist/modes/base.js.map +1 -1
- package/dist/notifications/__tests__/index.test.js +78 -0
- package/dist/notifications/__tests__/index.test.js.map +1 -1
- package/dist/notifications/index.d.ts.map +1 -1
- package/dist/notifications/index.js +39 -22
- package/dist/notifications/index.js.map +1 -1
- package/dist/openclaw/index.d.ts +5 -3
- package/dist/openclaw/index.d.ts.map +1 -1
- package/dist/openclaw/index.js +5 -3
- package/dist/openclaw/index.js.map +1 -1
- package/dist/question/__tests__/client.test.d.ts +2 -0
- package/dist/question/__tests__/client.test.d.ts.map +1 -0
- package/dist/question/__tests__/client.test.js +70 -0
- package/dist/question/__tests__/client.test.js.map +1 -0
- package/dist/question/__tests__/deep-interview.test.d.ts +2 -0
- package/dist/question/__tests__/deep-interview.test.d.ts.map +1 -0
- package/dist/question/__tests__/deep-interview.test.js +108 -0
- package/dist/question/__tests__/deep-interview.test.js.map +1 -0
- package/dist/question/__tests__/policy.test.d.ts +2 -0
- package/dist/question/__tests__/policy.test.d.ts.map +1 -0
- package/dist/question/__tests__/policy.test.js +107 -0
- package/dist/question/__tests__/policy.test.js.map +1 -0
- package/dist/question/__tests__/renderer.test.d.ts +2 -0
- package/dist/question/__tests__/renderer.test.d.ts.map +1 -0
- package/dist/question/__tests__/renderer.test.js +88 -0
- package/dist/question/__tests__/renderer.test.js.map +1 -0
- package/dist/question/__tests__/state.test.d.ts +2 -0
- package/dist/question/__tests__/state.test.d.ts.map +1 -0
- package/dist/question/__tests__/state.test.js +55 -0
- package/dist/question/__tests__/state.test.js.map +1 -0
- package/dist/question/__tests__/types.test.d.ts +2 -0
- package/dist/question/__tests__/types.test.d.ts.map +1 -0
- package/dist/question/__tests__/types.test.js +44 -0
- package/dist/question/__tests__/types.test.js.map +1 -0
- package/dist/question/__tests__/ui.test.d.ts +2 -0
- package/dist/question/__tests__/ui.test.d.ts.map +1 -0
- package/dist/question/__tests__/ui.test.js +169 -0
- package/dist/question/__tests__/ui.test.js.map +1 -0
- package/dist/question/client.d.ts +54 -0
- package/dist/question/client.d.ts.map +1 -0
- package/dist/question/client.js +77 -0
- package/dist/question/client.js.map +1 -0
- package/dist/question/deep-interview.d.ts +27 -0
- package/dist/question/deep-interview.d.ts.map +1 -0
- package/dist/question/deep-interview.js +101 -0
- package/dist/question/deep-interview.js.map +1 -0
- package/dist/question/policy.d.ts +18 -0
- package/dist/question/policy.d.ts.map +1 -0
- package/dist/question/policy.js +77 -0
- package/dist/question/policy.js.map +1 -0
- package/dist/question/renderer.d.ts +18 -0
- package/dist/question/renderer.d.ts.map +1 -0
- package/dist/question/renderer.js +128 -0
- package/dist/question/renderer.js.map +1 -0
- package/dist/question/state.d.ts +19 -0
- package/dist/question/state.d.ts.map +1 -0
- package/dist/question/state.js +108 -0
- package/dist/question/state.js.map +1 -0
- package/dist/question/types.d.ts +66 -0
- package/dist/question/types.d.ts.map +1 -0
- package/dist/question/types.js +82 -0
- package/dist/question/types.js.map +1 -0
- package/dist/question/ui.d.ts +38 -0
- package/dist/question/ui.d.ts.map +1 -0
- package/dist/question/ui.js +321 -0
- package/dist/question/ui.js.map +1 -0
- package/dist/ralph/contract.d.ts +1 -1
- package/dist/ralph/contract.d.ts.map +1 -1
- package/dist/ralph/contract.js +4 -1
- package/dist/ralph/contract.js.map +1 -1
- package/dist/ralplan/runtime.js +1 -1
- package/dist/ralplan/runtime.js.map +1 -1
- package/dist/runtime/__tests__/run-loop.test.d.ts +2 -0
- package/dist/runtime/__tests__/run-loop.test.d.ts.map +1 -0
- package/dist/runtime/__tests__/run-loop.test.js +35 -0
- package/dist/runtime/__tests__/run-loop.test.js.map +1 -0
- package/dist/runtime/__tests__/run-outcome.test.d.ts +2 -0
- package/dist/runtime/__tests__/run-outcome.test.d.ts.map +1 -0
- package/dist/runtime/__tests__/run-outcome.test.js +64 -0
- package/dist/runtime/__tests__/run-outcome.test.js.map +1 -0
- package/dist/runtime/run-loop.d.ts +41 -0
- package/dist/runtime/run-loop.d.ts.map +1 -0
- package/dist/runtime/run-loop.js +46 -0
- package/dist/runtime/run-loop.js.map +1 -0
- package/dist/runtime/run-outcome.d.ts +28 -0
- package/dist/runtime/run-outcome.d.ts.map +1 -0
- package/dist/runtime/run-outcome.js +136 -0
- package/dist/runtime/run-outcome.js.map +1 -0
- package/dist/runtime/run-state.d.ts +36 -0
- package/dist/runtime/run-state.d.ts.map +1 -0
- package/dist/runtime/run-state.js +110 -0
- package/dist/runtime/run-state.js.map +1 -0
- package/dist/scripts/__tests__/codex-native-hook.test.js +1128 -85
- package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
- package/dist/scripts/codex-native-hook.d.ts +2 -0
- package/dist/scripts/codex-native-hook.d.ts.map +1 -1
- package/dist/scripts/codex-native-hook.js +199 -11
- package/dist/scripts/codex-native-hook.js.map +1 -1
- package/dist/scripts/notify-fallback-watcher.js +81 -2
- package/dist/scripts/notify-fallback-watcher.js.map +1 -1
- package/dist/scripts/notify-hook/auto-nudge.d.ts +27 -0
- package/dist/scripts/notify-hook/auto-nudge.d.ts.map +1 -1
- package/dist/scripts/notify-hook/auto-nudge.js +83 -20
- package/dist/scripts/notify-hook/auto-nudge.js.map +1 -1
- package/dist/scripts/notify-hook/managed-tmux.d.ts.map +1 -1
- package/dist/scripts/notify-hook/managed-tmux.js +64 -38
- package/dist/scripts/notify-hook/managed-tmux.js.map +1 -1
- package/dist/scripts/notify-hook/ralph-session-resume.js +1 -1
- package/dist/scripts/notify-hook/ralph-session-resume.js.map +1 -1
- package/dist/scripts/notify-hook.js +15 -5
- package/dist/scripts/notify-hook.js.map +1 -1
- package/dist/scripts/sync-prompt-guidance-fragments.js +5 -0
- package/dist/scripts/sync-prompt-guidance-fragments.js.map +1 -1
- package/dist/state/__tests__/operations-ralph-phase.test.js +21 -0
- package/dist/state/__tests__/operations-ralph-phase.test.js.map +1 -1
- package/dist/state/__tests__/workflow-transition.test.js +11 -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 +15 -0
- package/dist/state/operations.js.map +1 -1
- package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
- package/dist/state/workflow-transition-reconcile.js +14 -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 +3 -1
- package/dist/state/workflow-transition.js.map +1 -1
- package/dist/team/__tests__/followup-planner.test.js +15 -0
- package/dist/team/__tests__/followup-planner.test.js.map +1 -1
- package/dist/team/__tests__/role-router.test.js +41 -0
- package/dist/team/__tests__/role-router.test.js.map +1 -1
- package/dist/team/followup-planner.d.ts.map +1 -1
- package/dist/team/followup-planner.js +31 -9
- package/dist/team/followup-planner.js.map +1 -1
- package/dist/team/role-router.d.ts.map +1 -1
- package/dist/team/role-router.js +73 -0
- package/dist/team/role-router.js.map +1 -1
- package/package.json +3 -2
- package/prompts/dependency-expert.md +3 -0
- package/prompts/executor.md +5 -0
- package/prompts/explore.md +2 -0
- package/prompts/planner.md +5 -0
- package/prompts/product-analyst.md +8 -8
- package/prompts/researcher.md +78 -30
- package/prompts/verifier.md +4 -0
- package/skills/autoresearch/SKILL.md +68 -0
- package/skills/deep-interview/SKILL.md +10 -9
- package/skills/help/SKILL.md +3 -1
- package/skills/ralplan/SKILL.md +1 -0
- package/skills/team/SKILL.md +1 -0
- package/skills/ultrawork/SKILL.md +1 -0
- package/src/scripts/__tests__/codex-native-hook.test.ts +1495 -188
- package/src/scripts/codex-native-hook.ts +235 -19
- package/src/scripts/notify-fallback-watcher.ts +92 -2
- package/src/scripts/notify-hook/auto-nudge.ts +89 -20
- package/src/scripts/notify-hook/managed-tmux.ts +70 -31
- package/src/scripts/notify-hook/ralph-session-resume.ts +1 -1
- package/src/scripts/notify-hook.ts +23 -5
- package/src/scripts/sync-prompt-guidance-fragments.ts +4 -0
- package/templates/AGENTS.md +48 -37
- package/templates/catalog-manifest.json +7 -0
|
@@ -44,6 +44,18 @@ import type { HookEventEnvelope } from "../hooks/extensibility/types.js";
|
|
|
44
44
|
import { dispatchHookEvent } from "../hooks/extensibility/dispatcher.js";
|
|
45
45
|
import { reconcileHudForPromptSubmit } from "../hud/reconcile.js";
|
|
46
46
|
import { onSessionStart as buildWikiSessionStartContext } from "../wiki/lifecycle.js";
|
|
47
|
+
import { readAutoresearchCompletionStatus, readAutoresearchModeState } from "../autoresearch/skill-validation.js";
|
|
48
|
+
import { shouldContinueRun } from "../runtime/run-loop.js";
|
|
49
|
+
import { triagePrompt } from "../hooks/triage-heuristic.js";
|
|
50
|
+
import { readTriageConfig } from "../hooks/triage-config.js";
|
|
51
|
+
import {
|
|
52
|
+
readTriageState,
|
|
53
|
+
writeTriageState,
|
|
54
|
+
shouldSuppressFollowup,
|
|
55
|
+
promptSignature,
|
|
56
|
+
type TriageStateFile,
|
|
57
|
+
} from "../hooks/triage-state.js";
|
|
58
|
+
import { isPendingDeepInterviewQuestionEnforcement } from "../question/deep-interview.js";
|
|
47
59
|
|
|
48
60
|
type CodexHookEventName =
|
|
49
61
|
| "SessionStart"
|
|
@@ -57,6 +69,7 @@ type CodexHookPayload = Record<string, unknown>;
|
|
|
57
69
|
interface NativeHookDispatchOptions {
|
|
58
70
|
cwd?: string;
|
|
59
71
|
sessionOwnerPid?: number;
|
|
72
|
+
reconcileHudForPromptSubmitFn?: typeof reconcileHudForPromptSubmit;
|
|
60
73
|
}
|
|
61
74
|
|
|
62
75
|
export interface NativeHookDispatchResult {
|
|
@@ -66,7 +79,6 @@ export interface NativeHookDispatchResult {
|
|
|
66
79
|
outputJson: Record<string, unknown> | null;
|
|
67
80
|
}
|
|
68
81
|
|
|
69
|
-
const TERMINAL_RALPH_PHASES = new Set(["complete", "failed", "cancelled"]);
|
|
70
82
|
const TERMINAL_MODE_PHASES = new Set(["complete", "failed", "cancelled"]);
|
|
71
83
|
const SKILL_STOP_BLOCKERS = new Set(["ralplan"]);
|
|
72
84
|
const TEAM_TERMINAL_TASK_STATUSES = new Set(["completed", "failed"]);
|
|
@@ -79,6 +91,18 @@ const STABLE_FINAL_RECOMMENDATION_PATTERNS = [
|
|
|
79
91
|
] as const;
|
|
80
92
|
const RELEASE_READINESS_FINALIZE_SYSTEM_MESSAGE =
|
|
81
93
|
"OMX release-readiness detected a stable final recommendation with no active worker tasks; emit one concise final decision summary and finalize.";
|
|
94
|
+
const EXECUTION_HANDOFF_PATTERNS = [
|
|
95
|
+
/^(?:好|好的|行|可以|那就|那现在)?[,,\s]*(?:开始|继续|直接)\s*(?:执行|优化|实现|修改|修复)(?=$|\s|[,,。.!!??])/u,
|
|
96
|
+
/(?:按照|按|基于)(?:这个|上述|当前)?\s*(?:plan|计划|方案).{0,16}(?:开始|继续|直接)?\s*(?:执行|优化|实现|修改|修复)/u,
|
|
97
|
+
/(?:不用|别|不要).{0,6}讨论/u,
|
|
98
|
+
/\b(?:start|begin|go ahead(?: and)?|proceed(?: now)?)\s+(?:to\s+)?(?:implement|execute|apply|fix)\b/i,
|
|
99
|
+
/\b(?:according to|based on)\s+(?:the|this|that)\s+plan\b.{0,20}\b(?:start|begin|proceed(?: now)?|go ahead(?: and)?)\b/i,
|
|
100
|
+
] as const;
|
|
101
|
+
const SHORT_FOLLOWUP_PRIORITY_PATTERNS = [
|
|
102
|
+
/^(?:继续|接着|然后|那就|那现在|还有(?:一个)?问题|这些优化都做了么|这些都做了么|现在呢|本轮|当前轮|这一轮)/u,
|
|
103
|
+
/(?:按照|按|基于)(?:这个|上述|当前)?(?:plan|计划|方案)/u,
|
|
104
|
+
/\b(?:follow up|latest request|this turn|current turn|newest request)\b/i,
|
|
105
|
+
] as const;
|
|
82
106
|
|
|
83
107
|
function safeString(value: unknown): string {
|
|
84
108
|
return typeof value === "string" ? value : "";
|
|
@@ -97,6 +121,34 @@ function safePositiveInteger(value: unknown): number | null {
|
|
|
97
121
|
return null;
|
|
98
122
|
}
|
|
99
123
|
|
|
124
|
+
function normalizePromptSignalText(text: string): string {
|
|
125
|
+
return text.trim().replace(/\s+/g, " ");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function looksLikeExecutionHandoffPrompt(prompt: string): boolean {
|
|
129
|
+
const normalized = normalizePromptSignalText(prompt);
|
|
130
|
+
if (!normalized) return false;
|
|
131
|
+
return EXECUTION_HANDOFF_PATTERNS.some((pattern) => pattern.test(normalized));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function looksLikeShortFollowupPrompt(prompt: string): boolean {
|
|
135
|
+
const normalized = normalizePromptSignalText(prompt);
|
|
136
|
+
if (!normalized) return false;
|
|
137
|
+
if (looksLikeExecutionHandoffPrompt(normalized)) return true;
|
|
138
|
+
if (normalized.length > 240) return false;
|
|
139
|
+
return SHORT_FOLLOWUP_PRIORITY_PATTERNS.some((pattern) => pattern.test(normalized));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function buildPromptPriorityMessage(prompt: string): string | null {
|
|
143
|
+
if (looksLikeExecutionHandoffPrompt(prompt)) {
|
|
144
|
+
return "Newest user input is an execution handoff for the current task. Treat it as authorization to act now against the latest approved plan/request. Do not restate the prior plan unless the user explicitly asks for a recap or status update.";
|
|
145
|
+
}
|
|
146
|
+
if (looksLikeShortFollowupPrompt(prompt)) {
|
|
147
|
+
return "Newest user input is a same-thread follow-up. Answer that latest follow-up directly and prefer it over older unresolved prompts when choosing what to do next.";
|
|
148
|
+
}
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
|
|
100
152
|
function readHookEventName(payload: CodexHookPayload): CodexHookEventName | null {
|
|
101
153
|
const raw = safeString(
|
|
102
154
|
payload.hook_event_name
|
|
@@ -196,6 +248,18 @@ function formatPhase(value: unknown, fallback = "active"): string {
|
|
|
196
248
|
return phase || fallback;
|
|
197
249
|
}
|
|
198
250
|
|
|
251
|
+
async function readActiveAutoresearchState(
|
|
252
|
+
cwd: string,
|
|
253
|
+
sessionId?: string,
|
|
254
|
+
): Promise<Record<string, unknown> | null> {
|
|
255
|
+
const normalizedSessionId = sessionId?.trim() || undefined;
|
|
256
|
+
if (!normalizedSessionId) return null;
|
|
257
|
+
const state = await readAutoresearchModeState(cwd, normalizedSessionId);
|
|
258
|
+
if (state?.active !== true) return null;
|
|
259
|
+
if (!isNonTerminalPhase(state.current_phase ?? state.currentPhase ?? 'executing')) return null;
|
|
260
|
+
return state;
|
|
261
|
+
}
|
|
262
|
+
|
|
199
263
|
async function readActiveRalphState(
|
|
200
264
|
stateDir: string,
|
|
201
265
|
preferredSessionId?: string,
|
|
@@ -211,12 +275,7 @@ async function readActiveRalphState(
|
|
|
211
275
|
const sessionScoped = await readJsonIfExists(
|
|
212
276
|
join(stateDir, "sessions", sessionId, "ralph-state.json"),
|
|
213
277
|
);
|
|
214
|
-
if (
|
|
215
|
-
sessionScoped?.active === true
|
|
216
|
-
&& !TERMINAL_RALPH_PHASES.has(
|
|
217
|
-
safeString(sessionScoped.current_phase).trim().toLowerCase(),
|
|
218
|
-
)
|
|
219
|
-
) {
|
|
278
|
+
if (sessionScoped?.active === true && shouldContinueRun(sessionScoped)) {
|
|
220
279
|
return sessionScoped;
|
|
221
280
|
}
|
|
222
281
|
}
|
|
@@ -224,7 +283,7 @@ async function readActiveRalphState(
|
|
|
224
283
|
if (sessionCandidates.length > 0) return null;
|
|
225
284
|
|
|
226
285
|
const direct = await readJsonIfExists(join(stateDir, "ralph-state.json"));
|
|
227
|
-
if (direct?.active === true &&
|
|
286
|
+
if (direct?.active === true && shouldContinueRun(direct)) {
|
|
228
287
|
return direct;
|
|
229
288
|
}
|
|
230
289
|
|
|
@@ -234,12 +293,7 @@ async function readActiveRalphState(
|
|
|
234
293
|
for (const entry of entries) {
|
|
235
294
|
if (!entry.isDirectory()) continue;
|
|
236
295
|
const candidate = await readJsonIfExists(join(sessionsRoot, entry.name, "ralph-state.json"));
|
|
237
|
-
if (
|
|
238
|
-
candidate?.active === true
|
|
239
|
-
&& !TERMINAL_RALPH_PHASES.has(
|
|
240
|
-
safeString(candidate.current_phase).trim().toLowerCase(),
|
|
241
|
-
)
|
|
242
|
-
) {
|
|
296
|
+
if (candidate?.active === true && shouldContinueRun(candidate)) {
|
|
243
297
|
return candidate;
|
|
244
298
|
}
|
|
245
299
|
}
|
|
@@ -471,9 +525,10 @@ async function buildSessionStartContext(
|
|
|
471
525
|
|
|
472
526
|
function buildAdditionalContextMessage(prompt: string, skillState?: SkillActiveState | null): string | null {
|
|
473
527
|
if (!prompt) return null;
|
|
528
|
+
const promptPriorityMessage = buildPromptPriorityMessage(prompt);
|
|
474
529
|
const matches = detectKeywords(prompt);
|
|
475
530
|
const match = detectPrimaryKeyword(prompt);
|
|
476
|
-
if (!match) return
|
|
531
|
+
if (!match) return promptPriorityMessage;
|
|
477
532
|
const detectedKeywordMessage = matches.length > 1
|
|
478
533
|
? `OMX native UserPromptSubmit detected workflow keywords ${matches.map((entry) => `"${entry.keyword}" -> ${entry.skill}`).join(", ")}.`
|
|
479
534
|
: `OMX native UserPromptSubmit detected workflow keyword "${match.keyword}" -> ${match.skill}.`;
|
|
@@ -487,6 +542,9 @@ function buildAdditionalContextMessage(prompt: string, skillState?: SkillActiveS
|
|
|
487
542
|
const ralphPromptActivationNote = skillState?.initialized_mode === "ralph"
|
|
488
543
|
? "Prompt-side `$ralph` activation seeds Ralph workflow state only; it does not invoke `omx ralph`. Use `omx ralph --prd ...` only when you explicitly want the PRD-gated CLI startup path."
|
|
489
544
|
: null;
|
|
545
|
+
const deepInterviewPromptActivationNote = skillState?.initialized_mode === "deep-interview"
|
|
546
|
+
? "Deep-interview must ask each interview round via `omx question`; do not fall back to `request_user_input` or plain-text questioning. Stop remains blocked while a deep-interview question obligation is pending."
|
|
547
|
+
: null;
|
|
490
548
|
const combinedTransitionMessage = (() => {
|
|
491
549
|
if (!skillState?.transition_message) return null;
|
|
492
550
|
if (matches.length <= 1 || activeSkills.length <= 1) return skillState.transition_message;
|
|
@@ -499,6 +557,7 @@ function buildAdditionalContextMessage(prompt: string, skillState?: SkillActiveS
|
|
|
499
557
|
return [
|
|
500
558
|
`OMX native UserPromptSubmit denied workflow keyword "${match.keyword}" -> ${match.skill}.`,
|
|
501
559
|
skillState.transition_error,
|
|
560
|
+
promptPriorityMessage,
|
|
502
561
|
'Follow AGENTS.md routing and preserve workflow transition and planning-safety rules.',
|
|
503
562
|
].join(' ');
|
|
504
563
|
}
|
|
@@ -511,6 +570,7 @@ function buildAdditionalContextMessage(prompt: string, skillState?: SkillActiveS
|
|
|
511
570
|
deferredSkills.length > 0
|
|
512
571
|
? `planning preserved over simultaneous execution follow-up; deferred skills: ${deferredSkills.join(", ")}.`
|
|
513
572
|
: null,
|
|
573
|
+
promptPriorityMessage,
|
|
514
574
|
skillState.initialized_mode && skillState.initialized_state_path
|
|
515
575
|
? `skill: ${skillState.initialized_mode} activated and initial state initialized at ${skillState.initialized_state_path}; write subsequent updates via omx_state MCP.`
|
|
516
576
|
: null,
|
|
@@ -532,7 +592,9 @@ function buildAdditionalContextMessage(prompt: string, skillState?: SkillActiveS
|
|
|
532
592
|
deferredSkills.length > 0
|
|
533
593
|
? `planning preserved over simultaneous execution follow-up; deferred skills: ${deferredSkills.join(", ")}.`
|
|
534
594
|
: null,
|
|
595
|
+
promptPriorityMessage,
|
|
535
596
|
initializedStateMessage,
|
|
597
|
+
deepInterviewPromptActivationNote,
|
|
536
598
|
"Use the durable OMX team runtime via `omx team ...` for coordinated execution; do not replace it with in-process fanout.",
|
|
537
599
|
"If you need runtime syntax, run `omx team --help` yourself.",
|
|
538
600
|
"Follow AGENTS.md routing and preserve workflow transition and planning-safety rules.",
|
|
@@ -546,13 +608,15 @@ function buildAdditionalContextMessage(prompt: string, skillState?: SkillActiveS
|
|
|
546
608
|
deferredSkills.length > 0
|
|
547
609
|
? `planning preserved over simultaneous execution follow-up; deferred skills: ${deferredSkills.join(", ")}.`
|
|
548
610
|
: null,
|
|
611
|
+
promptPriorityMessage,
|
|
549
612
|
`skill: ${skillState.initialized_mode} activated and initial state initialized at ${skillState.initialized_state_path}; write subsequent updates via omx_state MCP.`,
|
|
613
|
+
deepInterviewPromptActivationNote,
|
|
550
614
|
ralphPromptActivationNote,
|
|
551
615
|
"Follow AGENTS.md routing and preserve workflow transition and planning-safety rules.",
|
|
552
616
|
].join(" ");
|
|
553
617
|
}
|
|
554
618
|
|
|
555
|
-
return
|
|
619
|
+
return [detectedKeywordMessage, promptPriorityMessage, "Follow AGENTS.md routing and preserve workflow transition and planning-safety rules."].filter(Boolean).join(" ");
|
|
556
620
|
}
|
|
557
621
|
|
|
558
622
|
function parseTeamWorkerEnv(rawValue: string): { teamName: string; workerName: string } | null {
|
|
@@ -678,7 +742,7 @@ async function buildModeBasedStopOutput(
|
|
|
678
742
|
const state = sessionId
|
|
679
743
|
? await readModeStateForSession(mode, sessionId, cwd)
|
|
680
744
|
: await readModeState(mode, cwd);
|
|
681
|
-
if (state
|
|
745
|
+
if (!state || !shouldContinueRun(state)) return null;
|
|
682
746
|
const phase = formatPhase(state.current_phase);
|
|
683
747
|
return {
|
|
684
748
|
decision: "block",
|
|
@@ -927,6 +991,51 @@ async function readStopAutoNudgePhase(
|
|
|
927
991
|
return modePhase === "intent-first" ? "planning" : "";
|
|
928
992
|
}
|
|
929
993
|
|
|
994
|
+
async function buildDeepInterviewQuestionStopOutput(
|
|
995
|
+
cwd: string,
|
|
996
|
+
sessionId: string,
|
|
997
|
+
threadId: string,
|
|
998
|
+
): Promise<{ output: Record<string, unknown>; obligationId: string } | null> {
|
|
999
|
+
const modeState = await readStopSessionPinnedState("deep-interview-state.json", cwd, sessionId);
|
|
1000
|
+
if (!modeState || modeState.active !== true) return null;
|
|
1001
|
+
|
|
1002
|
+
const phase = formatPhase(modeState.current_phase, "planning");
|
|
1003
|
+
if (TERMINAL_MODE_PHASES.has(phase.toLowerCase()) || phase === "completing") {
|
|
1004
|
+
return null;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
const canonicalState = await readVisibleSkillActiveState(cwd, sessionId);
|
|
1008
|
+
if (canonicalState) {
|
|
1009
|
+
const blocker = listActiveSkills(canonicalState).find((entry) => (
|
|
1010
|
+
entry.skill === "deep-interview"
|
|
1011
|
+
&& matchesSkillStopContext(entry, canonicalState, sessionId, threadId)
|
|
1012
|
+
));
|
|
1013
|
+
if (!blocker) return null;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
const questionEnforcement = safeObject(modeState.question_enforcement);
|
|
1017
|
+
if (!isPendingDeepInterviewQuestionEnforcement(questionEnforcement)) {
|
|
1018
|
+
return null;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
const obligationId = safeString(questionEnforcement.obligation_id).trim();
|
|
1022
|
+
if (!obligationId) return null;
|
|
1023
|
+
|
|
1024
|
+
const systemMessage =
|
|
1025
|
+
`OMX deep-interview is still active (phase: ${phase}) and requires a structured question via omx question before stopping.`;
|
|
1026
|
+
|
|
1027
|
+
return {
|
|
1028
|
+
obligationId,
|
|
1029
|
+
output: {
|
|
1030
|
+
decision: "block",
|
|
1031
|
+
reason:
|
|
1032
|
+
`Deep interview is still active (phase: ${phase}) and has a pending structured question obligation; use \`omx question\` before stopping.`,
|
|
1033
|
+
stopReason: "deep_interview_question_required",
|
|
1034
|
+
systemMessage,
|
|
1035
|
+
},
|
|
1036
|
+
};
|
|
1037
|
+
}
|
|
1038
|
+
|
|
930
1039
|
function resolveRepeatableStopSessionId(
|
|
931
1040
|
payload: CodexHookPayload,
|
|
932
1041
|
canonicalSessionId?: string,
|
|
@@ -1283,6 +1392,28 @@ async function buildStopHookOutput(
|
|
|
1283
1392
|
const threadId = readPayloadThreadId(payload);
|
|
1284
1393
|
const ralphState = await readActiveRalphState(stateDir, canonicalSessionId);
|
|
1285
1394
|
if (!ralphState) {
|
|
1395
|
+
const autoresearchState = await readActiveAutoresearchState(cwd, canonicalSessionId);
|
|
1396
|
+
if (autoresearchState) {
|
|
1397
|
+
const completion = await readAutoresearchCompletionStatus(cwd, canonicalSessionId!.trim());
|
|
1398
|
+
if (!completion.complete) {
|
|
1399
|
+
const currentPhase = safeString(autoresearchState.current_phase ?? autoresearchState.currentPhase).trim() || 'executing';
|
|
1400
|
+
const systemMessage = `OMX autoresearch is still active (phase: ${currentPhase}); continue until validator evidence is complete before stopping.`;
|
|
1401
|
+
return await maybeReturnRepeatableStopOutput(
|
|
1402
|
+
payload,
|
|
1403
|
+
stateDir,
|
|
1404
|
+
buildRepeatableStopSignature(payload, 'autoresearch-stop', `${currentPhase}|${completion.reason}`, canonicalSessionId),
|
|
1405
|
+
{
|
|
1406
|
+
decision: 'block',
|
|
1407
|
+
reason: systemMessage,
|
|
1408
|
+
stopReason: `autoresearch_${currentPhase}`,
|
|
1409
|
+
systemMessage,
|
|
1410
|
+
},
|
|
1411
|
+
canonicalSessionId,
|
|
1412
|
+
{ allowRepeatDuringStopHook: true },
|
|
1413
|
+
);
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1286
1417
|
const teamWorkerOutput = await buildTeamWorkerStopOutput(cwd);
|
|
1287
1418
|
if (hasTeamWorkerContext() && teamWorkerOutput) return teamWorkerOutput;
|
|
1288
1419
|
|
|
@@ -1343,6 +1474,22 @@ async function buildStopHookOutput(
|
|
|
1343
1474
|
}
|
|
1344
1475
|
|
|
1345
1476
|
if (canonicalSessionId) {
|
|
1477
|
+
const deepInterviewQuestionOutput = await buildDeepInterviewQuestionStopOutput(
|
|
1478
|
+
cwd,
|
|
1479
|
+
canonicalSessionId,
|
|
1480
|
+
threadId,
|
|
1481
|
+
);
|
|
1482
|
+
if (deepInterviewQuestionOutput) {
|
|
1483
|
+
return await returnPersistentStopBlock(
|
|
1484
|
+
payload,
|
|
1485
|
+
stateDir,
|
|
1486
|
+
"deep-interview-question-stop",
|
|
1487
|
+
deepInterviewQuestionOutput.obligationId,
|
|
1488
|
+
deepInterviewQuestionOutput.output,
|
|
1489
|
+
canonicalSessionId,
|
|
1490
|
+
);
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1346
1493
|
const canonicalTeam = await findCanonicalActiveTeamForSession(cwd, canonicalSessionId);
|
|
1347
1494
|
if (canonicalTeam) {
|
|
1348
1495
|
const canonicalTeamOutput = buildTeamStopOutputForPhase(
|
|
@@ -1435,6 +1582,7 @@ export async function dispatchCodexNativeHook(
|
|
|
1435
1582
|
|
|
1436
1583
|
const omxEventName = mapCodexHookEventToOmxEvent(hookEventName);
|
|
1437
1584
|
let skillState: SkillActiveState | null = null;
|
|
1585
|
+
let triageAdditionalContext: string | null = null;
|
|
1438
1586
|
|
|
1439
1587
|
const nativeSessionId = safeString(payload.session_id ?? payload.sessionId).trim();
|
|
1440
1588
|
const threadId = safeString(payload.thread_id ?? payload.threadId).trim();
|
|
@@ -1464,7 +1612,75 @@ export async function dispatchCodexNativeHook(
|
|
|
1464
1612
|
turnId,
|
|
1465
1613
|
});
|
|
1466
1614
|
}
|
|
1467
|
-
|
|
1615
|
+
// --- Triage classifier (advisory-only, non-keyword prompts) ---
|
|
1616
|
+
if (prompt && skillState === null) {
|
|
1617
|
+
try {
|
|
1618
|
+
if (readTriageConfig().enabled) {
|
|
1619
|
+
const normalized = prompt.trim().toLowerCase();
|
|
1620
|
+
const previous = readTriageState({ cwd, sessionId: sessionIdForState || null });
|
|
1621
|
+
const suppress = shouldSuppressFollowup({
|
|
1622
|
+
previous,
|
|
1623
|
+
currentPrompt: normalized,
|
|
1624
|
+
currentHasKeyword: false,
|
|
1625
|
+
});
|
|
1626
|
+
if (!suppress) {
|
|
1627
|
+
const decision = triagePrompt(prompt);
|
|
1628
|
+
const nowIso = new Date().toISOString();
|
|
1629
|
+
const effectiveTurnId = turnId || nowIso;
|
|
1630
|
+
if (decision.lane === "HEAVY") {
|
|
1631
|
+
triageAdditionalContext =
|
|
1632
|
+
"OMX native UserPromptSubmit triage detected a multi-step goal with no workflow keyword. This is advisory prompt-routing context only; it did not activate autopilot or initialize workflow state. Prefer the existing autopilot-style workflow if AGENTS.md/runtime conditions allow it, unless newer user context narrows or opts out.";
|
|
1633
|
+
const newState: TriageStateFile = {
|
|
1634
|
+
version: 1,
|
|
1635
|
+
last_triage: {
|
|
1636
|
+
lane: "HEAVY",
|
|
1637
|
+
destination: "autopilot",
|
|
1638
|
+
reason: decision.reason,
|
|
1639
|
+
prompt_signature: promptSignature(normalized),
|
|
1640
|
+
turn_id: effectiveTurnId,
|
|
1641
|
+
created_at: nowIso,
|
|
1642
|
+
},
|
|
1643
|
+
suppress_followup: true,
|
|
1644
|
+
};
|
|
1645
|
+
writeTriageState({ cwd, sessionId: sessionIdForState || null, state: newState });
|
|
1646
|
+
} else if (decision.lane === "LIGHT") {
|
|
1647
|
+
if (decision.destination === "explore") {
|
|
1648
|
+
triageAdditionalContext =
|
|
1649
|
+
"OMX native UserPromptSubmit triage detected a read-only/question-shaped request with no workflow keyword. This is advisory prompt-routing context only. Prefer the explore role surface rather than escalating to autopilot.";
|
|
1650
|
+
} else if (decision.destination === "executor") {
|
|
1651
|
+
triageAdditionalContext =
|
|
1652
|
+
"OMX native UserPromptSubmit triage detected a narrow edit-shaped request with no workflow keyword. This is advisory prompt-routing context only. Prefer the executor role surface rather than autopilot.";
|
|
1653
|
+
} else if (decision.destination === "designer") {
|
|
1654
|
+
triageAdditionalContext =
|
|
1655
|
+
"OMX native UserPromptSubmit triage detected a visual/style request with no workflow keyword. This is advisory prompt-routing context only. Prefer the designer role surface.";
|
|
1656
|
+
}
|
|
1657
|
+
if (triageAdditionalContext !== null) {
|
|
1658
|
+
const dest = decision.destination as "explore" | "executor" | "designer";
|
|
1659
|
+
const newState: TriageStateFile = {
|
|
1660
|
+
version: 1,
|
|
1661
|
+
last_triage: {
|
|
1662
|
+
lane: "LIGHT",
|
|
1663
|
+
destination: dest,
|
|
1664
|
+
reason: decision.reason,
|
|
1665
|
+
prompt_signature: promptSignature(normalized),
|
|
1666
|
+
turn_id: effectiveTurnId,
|
|
1667
|
+
created_at: nowIso,
|
|
1668
|
+
},
|
|
1669
|
+
suppress_followup: true,
|
|
1670
|
+
};
|
|
1671
|
+
writeTriageState({ cwd, sessionId: sessionIdForState || null, state: newState });
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
// lane === "PASS": no context, no state write
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
} catch {
|
|
1678
|
+
// Swallow all triage errors; never break the hook
|
|
1679
|
+
triageAdditionalContext = null;
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
const reconcileHudForPromptSubmitFn = options.reconcileHudForPromptSubmitFn ?? reconcileHudForPromptSubmit;
|
|
1683
|
+
await reconcileHudForPromptSubmitFn(cwd, { sessionId: canonicalSessionId || sessionIdForState || undefined }).catch(() => {});
|
|
1468
1684
|
}
|
|
1469
1685
|
|
|
1470
1686
|
if (omxEventName) {
|
|
@@ -1493,7 +1709,7 @@ export async function dispatchCodexNativeHook(
|
|
|
1493
1709
|
if (hookEventName === "SessionStart" || hookEventName === "UserPromptSubmit") {
|
|
1494
1710
|
const additionalContext = hookEventName === "SessionStart"
|
|
1495
1711
|
? await buildSessionStartContext(cwd, canonicalSessionId || nativeSessionId)
|
|
1496
|
-
: buildAdditionalContextMessage(readPromptText(payload), skillState);
|
|
1712
|
+
: (buildAdditionalContextMessage(readPromptText(payload), skillState) ?? triageAdditionalContext);
|
|
1497
1713
|
if (additionalContext) {
|
|
1498
1714
|
outputJson = {
|
|
1499
1715
|
hookSpecificOutput: {
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
maybeNudgeTeamLeader,
|
|
26
26
|
resolveLeaderStalenessThresholdMs,
|
|
27
27
|
} from './notify-hook/team-leader-nudge.js';
|
|
28
|
+
import { resolveManagedPaneFromAnchor, resolveManagedSessionPane } from './notify-hook/managed-tmux.js';
|
|
28
29
|
import { DEFAULT_MARKER } from './tmux-hook-engine.js';
|
|
29
30
|
import { isTerminalPhase } from './notify-hook/utils.js';
|
|
30
31
|
import { isSessionStale, isSessionStateAuthoritativeForCwd, readSessionState } from '../hooks/session.js';
|
|
@@ -36,6 +37,7 @@ import { listNotifyCanonicalActiveTeams } from './notify-hook/active-team.js';
|
|
|
36
37
|
import { sameFilePath } from '../utils/paths.js';
|
|
37
38
|
import { validateSessionId } from '../mcp/state-paths.js';
|
|
38
39
|
import { TEAM_NAME_SAFE_PATTERN } from '../team/contracts.js';
|
|
40
|
+
import { shouldContinueRun } from '../runtime/run-loop.js';
|
|
39
41
|
|
|
40
42
|
function argValue(name: string, fallback = ''): string {
|
|
41
43
|
const idx = process.argv.indexOf(name);
|
|
@@ -145,7 +147,7 @@ const watcherOwnerToken = `${process.pid}-${startedAt}-${Math.random().toString(
|
|
|
145
147
|
const RALPH_CONTINUE_TEXT = 'Ralph loop active continue';
|
|
146
148
|
const RALPH_CONTINUE_CADENCE_MS = 60_000;
|
|
147
149
|
const RALPH_STEER_LOCK_STALE_MS = 30_000;
|
|
148
|
-
const RALPH_TERMINAL_PHASES = new Set(['complete', 'failed', 'cancelled']);
|
|
150
|
+
const RALPH_TERMINAL_PHASES = new Set(['blocked_on_user', 'complete', 'failed', 'cancelled']);
|
|
149
151
|
const RALPH_STARTING_PHASE_TIMEOUT_MS = RALPH_CONTINUE_CADENCE_MS * 2;
|
|
150
152
|
const QUIET_ONCE_EVENT_TYPES = new Set(['watcher_start', 'watcher_once_complete']);
|
|
151
153
|
|
|
@@ -474,6 +476,7 @@ function normalizeRalphContinueSteerState(raw: Record<string, unknown> | null |
|
|
|
474
476
|
function hasRalphTerminalState(raw: Record<string, unknown> | null | undefined): boolean {
|
|
475
477
|
if (!raw || typeof raw !== 'object') return true;
|
|
476
478
|
if (raw.active !== true) return true;
|
|
479
|
+
if (!shouldContinueRun(raw)) return true;
|
|
477
480
|
const phase = safeString(raw.current_phase).trim().toLowerCase();
|
|
478
481
|
if (phase && RALPH_TERMINAL_PHASES.has(phase)) return true;
|
|
479
482
|
if (isStaleRalphStartingPhase(raw)) return true;
|
|
@@ -1029,6 +1032,90 @@ async function writePidFileRecord(): Promise<void> {
|
|
|
1029
1032
|
await writeFile(pidFilePath, JSON.stringify(nextRecord, null, 2)).catch(() => {});
|
|
1030
1033
|
}
|
|
1031
1034
|
|
|
1035
|
+
async function buildWatcherManagedPayload(): Promise<Record<string, string> | null> {
|
|
1036
|
+
const session = await readSessionState(cwd).catch(() => null);
|
|
1037
|
+
const sessionId = safeString(session?.session_id).trim();
|
|
1038
|
+
if (!sessionId || !session || isSessionStale(session)) return null;
|
|
1039
|
+
return { session_id: sessionId };
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
async function persistReboundRalphPaneState(
|
|
1043
|
+
statePath: string,
|
|
1044
|
+
state: Record<string, unknown> | null,
|
|
1045
|
+
paneId: string,
|
|
1046
|
+
nowIso: string,
|
|
1047
|
+
): Promise<Record<string, unknown>> {
|
|
1048
|
+
const latestState = await readFile(statePath, 'utf-8')
|
|
1049
|
+
.then((content) => JSON.parse(content) as Record<string, unknown>)
|
|
1050
|
+
.catch(() => null);
|
|
1051
|
+
const nextState = {
|
|
1052
|
+
...((latestState && typeof latestState === 'object') ? latestState : (state || {})),
|
|
1053
|
+
tmux_pane_id: paneId,
|
|
1054
|
+
tmux_pane_set_at: nowIso,
|
|
1055
|
+
};
|
|
1056
|
+
const tmpPath = `${statePath}.tmp.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}`;
|
|
1057
|
+
await writeFile(tmpPath, JSON.stringify(nextState, null, 2));
|
|
1058
|
+
try {
|
|
1059
|
+
await rename(tmpPath, statePath);
|
|
1060
|
+
} catch (error) {
|
|
1061
|
+
await unlink(tmpPath).catch(() => {});
|
|
1062
|
+
throw error;
|
|
1063
|
+
}
|
|
1064
|
+
return nextState;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
async function resolveRalphContinuePaneTarget(
|
|
1068
|
+
activeRalph: ActiveModeResult,
|
|
1069
|
+
nowIso: string,
|
|
1070
|
+
): Promise<{ paneId: string; state: Record<string, unknown> | null; reboundFrom: string }> {
|
|
1071
|
+
const currentState = activeRalph.state && typeof activeRalph.state === 'object'
|
|
1072
|
+
? activeRalph.state as Record<string, unknown>
|
|
1073
|
+
: null;
|
|
1074
|
+
const anchorPaneId = safeString(currentState?.tmux_pane_id).trim();
|
|
1075
|
+
if (!anchorPaneId) {
|
|
1076
|
+
return {
|
|
1077
|
+
paneId: '',
|
|
1078
|
+
state: currentState,
|
|
1079
|
+
reboundFrom: '',
|
|
1080
|
+
};
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
const managedPayload = await buildWatcherManagedPayload();
|
|
1084
|
+
if (!managedPayload) {
|
|
1085
|
+
return {
|
|
1086
|
+
paneId: anchorPaneId,
|
|
1087
|
+
state: currentState,
|
|
1088
|
+
reboundFrom: '',
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
let resolvedPaneId = await resolveManagedPaneFromAnchor(anchorPaneId, cwd, managedPayload, { allowTeamWorker: false });
|
|
1093
|
+
if (!resolvedPaneId) {
|
|
1094
|
+
resolvedPaneId = await resolveManagedSessionPane(cwd, managedPayload);
|
|
1095
|
+
}
|
|
1096
|
+
if (!resolvedPaneId) {
|
|
1097
|
+
return {
|
|
1098
|
+
paneId: '',
|
|
1099
|
+
state: currentState,
|
|
1100
|
+
reboundFrom: '',
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
1103
|
+
if (resolvedPaneId === anchorPaneId) {
|
|
1104
|
+
return {
|
|
1105
|
+
paneId: resolvedPaneId,
|
|
1106
|
+
state: currentState,
|
|
1107
|
+
reboundFrom: '',
|
|
1108
|
+
};
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
const updatedState = await persistReboundRalphPaneState(activeRalph.path, currentState, resolvedPaneId, nowIso);
|
|
1112
|
+
return {
|
|
1113
|
+
paneId: resolvedPaneId,
|
|
1114
|
+
state: updatedState,
|
|
1115
|
+
reboundFrom: anchorPaneId,
|
|
1116
|
+
};
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1032
1119
|
async function runRalphContinueSteerTick(): Promise<void> {
|
|
1033
1120
|
const now = Date.now();
|
|
1034
1121
|
const nowIso = new Date(now).toISOString();
|
|
@@ -1088,7 +1175,9 @@ async function runRalphContinueSteerTick(): Promise<void> {
|
|
|
1088
1175
|
return { sent: false, skipped: true };
|
|
1089
1176
|
}
|
|
1090
1177
|
|
|
1091
|
-
const
|
|
1178
|
+
const resolvedPane = await resolveRalphContinuePaneTarget(activeRalph, nowIso);
|
|
1179
|
+
activeRalph.state = resolvedPane.state;
|
|
1180
|
+
const paneId = resolvedPane.paneId;
|
|
1092
1181
|
if (!paneId) {
|
|
1093
1182
|
lastRalphContinueSteer.last_reason = 'pane_missing';
|
|
1094
1183
|
lastRalphContinueSteer.pane_id = '';
|
|
@@ -1113,6 +1202,7 @@ async function runRalphContinueSteerTick(): Promise<void> {
|
|
|
1113
1202
|
type: 'ralph_continue_steer',
|
|
1114
1203
|
reason: 'sent',
|
|
1115
1204
|
pane_id: paneId,
|
|
1205
|
+
rebound_from: resolvedPane.reboundFrom || null,
|
|
1116
1206
|
state_path: activeRalph.path,
|
|
1117
1207
|
current_phase: safeString(activeRalph.state?.current_phase) || null,
|
|
1118
1208
|
cadence_ms: RALPH_CONTINUE_CADENCE_MS,
|