oh-my-codex 0.16.4 → 0.17.1
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/catalog/__tests__/generator.test.js +2 -0
- package/dist/catalog/__tests__/generator.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-warning-copy.test.js +80 -7
- package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +17 -11
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/mcp-serve.test.js +4 -0
- package/dist/cli/__tests__/mcp-serve.test.js.map +1 -1
- package/dist/cli/__tests__/ralph-goal-mode-contract.test.js +3 -0
- package/dist/cli/__tests__/ralph-goal-mode-contract.test.js.map +1 -1
- package/dist/cli/__tests__/ralph.test.js +0 -124
- package/dist/cli/__tests__/ralph.test.js.map +1 -1
- package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js +8 -3
- package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js.map +1 -1
- package/dist/cli/__tests__/setup-install-mode.test.js +183 -4
- package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
- package/dist/cli/__tests__/setup-refresh.test.js +3 -3
- package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
- package/dist/cli/__tests__/team.test.js +166 -42
- package/dist/cli/__tests__/team.test.js.map +1 -1
- package/dist/cli/__tests__/ultragoal.test.js +22 -0
- package/dist/cli/__tests__/ultragoal.test.js.map +1 -1
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +75 -14
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.d.ts +8 -2
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +17 -7
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/mcp-serve.d.ts.map +1 -1
- package/dist/cli/mcp-serve.js +4 -0
- package/dist/cli/mcp-serve.js.map +1 -1
- package/dist/cli/plugin-marketplace.d.ts +25 -1
- package/dist/cli/plugin-marketplace.d.ts.map +1 -1
- package/dist/cli/plugin-marketplace.js +146 -3
- package/dist/cli/plugin-marketplace.js.map +1 -1
- package/dist/cli/question.d.ts +1 -1
- package/dist/cli/question.d.ts.map +1 -1
- package/dist/cli/question.js +98 -4
- package/dist/cli/question.js.map +1 -1
- package/dist/cli/ralph.d.ts.map +1 -1
- package/dist/cli/ralph.js +1 -49
- package/dist/cli/ralph.js.map +1 -1
- package/dist/cli/setup.d.ts +1 -0
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +103 -18
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/team.d.ts.map +1 -1
- package/dist/cli/team.js +21 -29
- package/dist/cli/team.js.map +1 -1
- package/dist/cli/ultragoal.d.ts.map +1 -1
- package/dist/cli/ultragoal.js +7 -1
- package/dist/cli/ultragoal.js.map +1 -1
- package/dist/config/__tests__/codex-hooks.test.js +136 -9
- package/dist/config/__tests__/codex-hooks.test.js.map +1 -1
- package/dist/config/__tests__/generator-idempotent.test.js +15 -0
- package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
- package/dist/config/codex-hooks.d.ts +13 -14
- package/dist/config/codex-hooks.d.ts.map +1 -1
- package/dist/config/codex-hooks.js +85 -7
- package/dist/config/codex-hooks.js.map +1 -1
- package/dist/config/generator.d.ts +8 -1
- package/dist/config/generator.d.ts.map +1 -1
- package/dist/config/generator.js +73 -9
- package/dist/config/generator.js.map +1 -1
- package/dist/config/omx-first-party-mcp.d.ts.map +1 -1
- package/dist/config/omx-first-party-mcp.js +7 -0
- package/dist/config/omx-first-party-mcp.js.map +1 -1
- package/dist/hooks/__tests__/agents-overlay.test.js +29 -0
- package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
- package/dist/hooks/__tests__/design-skill.test.d.ts +2 -0
- package/dist/hooks/__tests__/design-skill.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/design-skill.test.js +55 -0
- package/dist/hooks/__tests__/design-skill.test.js.map +1 -0
- package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js +265 -0
- package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
- package/dist/hooks/__tests__/session.test.js +126 -1
- package/dist/hooks/__tests__/session.test.js.map +1 -1
- package/dist/hooks/__tests__/skill-catalog-hygiene.test.js +1 -1
- package/dist/hooks/__tests__/skill-catalog-hygiene.test.js.map +1 -1
- package/dist/hooks/__tests__/skill-guidance-contract.test.js +41 -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 +6 -3
- package/dist/hooks/agents-overlay.js.map +1 -1
- package/dist/hooks/keyword-detector.d.ts.map +1 -1
- package/dist/hooks/keyword-detector.js +5 -1
- 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 +2 -0
- package/dist/hooks/keyword-registry.js.map +1 -1
- package/dist/hooks/prompt-guidance-contract.d.ts.map +1 -1
- package/dist/hooks/prompt-guidance-contract.js +47 -2
- package/dist/hooks/prompt-guidance-contract.js.map +1 -1
- package/dist/hooks/session.d.ts +11 -3
- package/dist/hooks/session.d.ts.map +1 -1
- package/dist/hooks/session.js +68 -6
- package/dist/hooks/session.js.map +1 -1
- package/dist/hud/__tests__/reconcile.test.js +63 -0
- package/dist/hud/__tests__/reconcile.test.js.map +1 -1
- package/dist/hud/__tests__/tmux.test.d.ts +2 -0
- package/dist/hud/__tests__/tmux.test.d.ts.map +1 -0
- package/dist/hud/__tests__/tmux.test.js +92 -0
- package/dist/hud/__tests__/tmux.test.js.map +1 -0
- package/dist/hud/reconcile.d.ts +2 -0
- package/dist/hud/reconcile.d.ts.map +1 -1
- package/dist/hud/reconcile.js +14 -1
- package/dist/hud/reconcile.js.map +1 -1
- package/dist/hud/tmux.d.ts +12 -0
- package/dist/hud/tmux.d.ts.map +1 -1
- package/dist/hud/tmux.js +88 -0
- package/dist/hud/tmux.js.map +1 -1
- package/dist/mcp/__tests__/bootstrap.test.js +3 -0
- package/dist/mcp/__tests__/bootstrap.test.js.map +1 -1
- package/dist/mcp/__tests__/hermes-bridge.test.d.ts +2 -0
- package/dist/mcp/__tests__/hermes-bridge.test.d.ts.map +1 -0
- package/dist/mcp/__tests__/hermes-bridge.test.js +441 -0
- package/dist/mcp/__tests__/hermes-bridge.test.js.map +1 -0
- package/dist/mcp/__tests__/state-paths.test.js +96 -13
- package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
- package/dist/mcp/bootstrap.d.ts +1 -1
- package/dist/mcp/bootstrap.d.ts.map +1 -1
- package/dist/mcp/bootstrap.js +2 -0
- package/dist/mcp/bootstrap.js.map +1 -1
- package/dist/mcp/hermes-bridge.d.ts +111 -0
- package/dist/mcp/hermes-bridge.d.ts.map +1 -0
- package/dist/mcp/hermes-bridge.js +474 -0
- package/dist/mcp/hermes-bridge.js.map +1 -0
- package/dist/mcp/hermes-server.d.ts +374 -0
- package/dist/mcp/hermes-server.d.ts.map +1 -0
- package/dist/mcp/hermes-server.js +158 -0
- package/dist/mcp/hermes-server.js.map +1 -0
- package/dist/mcp/state-paths.d.ts.map +1 -1
- package/dist/mcp/state-paths.js +41 -9
- package/dist/mcp/state-paths.js.map +1 -1
- package/dist/modes/__tests__/base-tmux-pane.test.js +31 -1
- package/dist/modes/__tests__/base-tmux-pane.test.js.map +1 -1
- package/dist/pipeline/__tests__/stages.test.js +18 -9
- package/dist/pipeline/__tests__/stages.test.js.map +1 -1
- package/dist/pipeline/stages/team-exec.d.ts.map +1 -1
- package/dist/pipeline/stages/team-exec.js +2 -7
- package/dist/pipeline/stages/team-exec.js.map +1 -1
- package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.js +111 -269
- package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.js.map +1 -1
- package/dist/planning/__tests__/approved-launch-hint-lineage-matrix.test.js +31 -72
- package/dist/planning/__tests__/approved-launch-hint-lineage-matrix.test.js.map +1 -1
- package/dist/planning/__tests__/artifacts.test.js +27 -372
- package/dist/planning/__tests__/artifacts.test.js.map +1 -1
- package/dist/planning/artifacts.d.ts +1 -14
- package/dist/planning/artifacts.d.ts.map +1 -1
- package/dist/planning/artifacts.js +11 -31
- package/dist/planning/artifacts.js.map +1 -1
- package/dist/question/__tests__/state.test.js +287 -1
- package/dist/question/__tests__/state.test.js.map +1 -1
- package/dist/question/__tests__/ui.test.js +8 -8
- package/dist/question/__tests__/ui.test.js.map +1 -1
- package/dist/question/events.d.ts +53 -0
- package/dist/question/events.d.ts.map +1 -0
- package/dist/question/events.js +201 -0
- package/dist/question/events.js.map +1 -0
- package/dist/question/state.d.ts +25 -1
- package/dist/question/state.d.ts.map +1 -1
- package/dist/question/state.js +259 -3
- package/dist/question/state.js.map +1 -1
- package/dist/question/types.d.ts +1 -0
- package/dist/question/types.d.ts.map +1 -1
- package/dist/question/types.js.map +1 -1
- package/dist/question/ui.d.ts.map +1 -1
- package/dist/question/ui.js +1 -18
- package/dist/question/ui.js.map +1 -1
- package/dist/ralph/__tests__/completion-audit.test.js +39 -0
- package/dist/ralph/__tests__/completion-audit.test.js.map +1 -1
- package/dist/scripts/__tests__/codex-native-hook.test.js +298 -3
- package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
- package/dist/scripts/__tests__/run-test-files.test.js +22 -0
- package/dist/scripts/__tests__/run-test-files.test.js.map +1 -1
- package/dist/scripts/codex-native-hook.d.ts +1 -0
- package/dist/scripts/codex-native-hook.d.ts.map +1 -1
- package/dist/scripts/codex-native-hook.js +137 -18
- package/dist/scripts/codex-native-hook.js.map +1 -1
- package/dist/scripts/codex-native-pre-post.d.ts.map +1 -1
- package/dist/scripts/codex-native-pre-post.js +12 -6
- package/dist/scripts/codex-native-pre-post.js.map +1 -1
- package/dist/scripts/notify-hook/tmux-injection.d.ts.map +1 -1
- package/dist/scripts/notify-hook/tmux-injection.js +91 -2
- package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
- package/dist/scripts/run-test-files.js +12 -1
- package/dist/scripts/run-test-files.js.map +1 -1
- package/dist/state/mode-state-context.d.ts +2 -0
- package/dist/state/mode-state-context.d.ts.map +1 -1
- package/dist/state/mode-state-context.js +21 -0
- package/dist/state/mode-state-context.js.map +1 -1
- package/dist/team/__tests__/approved-execution.test.js +25 -24
- package/dist/team/__tests__/approved-execution.test.js.map +1 -1
- package/dist/team/__tests__/runtime.test.js +173 -26
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/scaling.test.js +66 -17
- package/dist/team/__tests__/scaling.test.js.map +1 -1
- package/dist/team/__tests__/tmux-session.test.js +42 -0
- package/dist/team/__tests__/tmux-session.test.js.map +1 -1
- package/dist/team/__tests__/worker-bootstrap.test.js +205 -0
- package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
- package/dist/team/approved-execution.d.ts +13 -0
- package/dist/team/approved-execution.d.ts.map +1 -1
- package/dist/team/approved-execution.js +65 -30
- package/dist/team/approved-execution.js.map +1 -1
- package/dist/team/runtime.d.ts.map +1 -1
- package/dist/team/runtime.js +28 -24
- package/dist/team/runtime.js.map +1 -1
- package/dist/team/scaling.d.ts.map +1 -1
- package/dist/team/scaling.js +7 -8
- package/dist/team/scaling.js.map +1 -1
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +48 -2
- package/dist/team/tmux-session.js.map +1 -1
- package/dist/team/ultragoal-context.d.ts +35 -0
- package/dist/team/ultragoal-context.d.ts.map +1 -0
- package/dist/team/ultragoal-context.js +191 -0
- package/dist/team/ultragoal-context.js.map +1 -0
- package/dist/ultragoal/__tests__/artifacts.test.js +121 -0
- package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
- package/dist/ultragoal/__tests__/docs-contract.test.js +19 -0
- package/dist/ultragoal/__tests__/docs-contract.test.js.map +1 -1
- package/dist/ultragoal/artifacts.d.ts +9 -1
- package/dist/ultragoal/artifacts.d.ts.map +1 -1
- package/dist/ultragoal/artifacts.js +105 -3
- package/dist/ultragoal/artifacts.js.map +1 -1
- package/dist/utils/__tests__/paths.test.js +31 -1
- package/dist/utils/__tests__/paths.test.js.map +1 -1
- package/dist/utils/paths.d.ts +6 -0
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +18 -0
- package/dist/utils/paths.js.map +1 -1
- package/dist/wiki/lifecycle.js +3 -3
- package/dist/wiki/lifecycle.js.map +1 -1
- package/package.json +1 -1
- package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/oh-my-codex/.mcp.json +8 -0
- package/plugins/oh-my-codex/skills/design/SKILL.md +180 -0
- package/plugins/oh-my-codex/skills/plan/SKILL.md +3 -3
- package/plugins/oh-my-codex/skills/ralph/SKILL.md +2 -2
- package/plugins/oh-my-codex/skills/ralplan/SKILL.md +1 -1
- package/plugins/oh-my-codex/skills/skill/SKILL.md +2 -1
- package/plugins/oh-my-codex/skills/team/SKILL.md +6 -0
- package/plugins/oh-my-codex/skills/ultragoal/SKILL.md +11 -0
- package/plugins/oh-my-codex/skills/ultraqa/SKILL.md +161 -47
- package/plugins/oh-my-codex/skills/visual-ralph/SKILL.md +2 -2
- package/skills/design/SKILL.md +180 -0
- package/skills/frontend-ui-ux/SKILL.md +6 -2
- package/skills/plan/SKILL.md +3 -3
- package/skills/ralph/SKILL.md +2 -2
- package/skills/ralplan/SKILL.md +1 -1
- package/skills/skill/SKILL.md +2 -1
- package/skills/team/SKILL.md +6 -0
- package/skills/ultragoal/SKILL.md +11 -0
- package/skills/ultraqa/SKILL.md +161 -47
- package/skills/visual-ralph/SKILL.md +2 -2
- package/src/scripts/__tests__/codex-native-hook.test.ts +339 -2
- package/src/scripts/__tests__/run-test-files.test.ts +32 -0
- package/src/scripts/codex-native-hook.ts +166 -20
- package/src/scripts/codex-native-pre-post.ts +12 -6
- package/src/scripts/notify-hook/tmux-injection.ts +110 -3
- package/src/scripts/run-test-files.ts +13 -2
- package/templates/catalog-manifest.json +9 -2
- package/dist/planning/__tests__/context-pack-status.test.d.ts +0 -2
- package/dist/planning/__tests__/context-pack-status.test.d.ts.map +0 -1
- package/dist/planning/__tests__/context-pack-status.test.js +0 -795
- package/dist/planning/__tests__/context-pack-status.test.js.map +0 -1
- package/dist/planning/__tests__/ready-context-pack-role-refs.test.d.ts +0 -2
- package/dist/planning/__tests__/ready-context-pack-role-refs.test.d.ts.map +0 -1
- package/dist/planning/__tests__/ready-context-pack-role-refs.test.js +0 -612
- package/dist/planning/__tests__/ready-context-pack-role-refs.test.js.map +0 -1
- package/dist/planning/context-pack-status.d.ts +0 -73
- package/dist/planning/context-pack-status.d.ts.map +0 -1
- package/dist/planning/context-pack-status.js +0 -745
- package/dist/planning/context-pack-status.js.map +0 -1
|
@@ -33,7 +33,7 @@ import {
|
|
|
33
33
|
writeTeamLeaderAttention,
|
|
34
34
|
writeTeamPhase,
|
|
35
35
|
} from "../team/state.js";
|
|
36
|
-
import { omxNotepadPath,
|
|
36
|
+
import { omxNotepadPath, resolveProjectMemoryPath } from "../utils/paths.js";
|
|
37
37
|
import { findGitLayout } from "../utils/git-layout.js";
|
|
38
38
|
import { getBaseStateDir, getStateFilePath, getStatePath } from "../mcp/state-paths.js";
|
|
39
39
|
import {
|
|
@@ -127,6 +127,9 @@ const SKILL_STOP_BLOCKERS = new Set(["ralplan"]);
|
|
|
127
127
|
const TEAM_STOP_BLOCKING_TASK_STATUSES = new Set(["pending", "in_progress", "blocked"]);
|
|
128
128
|
const TEAM_WORKER_TERMINAL_RUN_STATES = new Set(["done", "complete", "completed", "failed", "stopped", "cancelled"]);
|
|
129
129
|
const NATIVE_STOP_STATE_FILE = "native-stop-state.json";
|
|
130
|
+
const ORDINARY_STOP_NO_PROGRESS_DEFAULT_MAX_REPEATS = 8;
|
|
131
|
+
const ORDINARY_STOP_NO_PROGRESS_DEFAULT_IDLE_MS = 10 * 60_000;
|
|
132
|
+
const ORDINARY_STOP_NO_PROGRESS_MAX_MESSAGE_LENGTH = 240;
|
|
130
133
|
const STABLE_FINAL_RECOMMENDATION_PATTERNS = [
|
|
131
134
|
/^\s*(?:launch|release|ship)-?ready\s*:\s*(?:yes|no)\b[^\n\r]*/im,
|
|
132
135
|
/^\s*ready to release\s*:\s*(?:yes|no)\b[^\n\r]*/im,
|
|
@@ -157,6 +160,12 @@ function safeObject(value: unknown): Record<string, unknown> {
|
|
|
157
160
|
return value && typeof value === "object" ? value as Record<string, unknown> : {};
|
|
158
161
|
}
|
|
159
162
|
|
|
163
|
+
function safeContextSnippet(value: unknown, maxLength = 300): string {
|
|
164
|
+
const text = safeString(value).replace(/\s+/g, " ").trim();
|
|
165
|
+
if (text.length <= maxLength) return text;
|
|
166
|
+
return `${text.slice(0, maxLength - 1).trimEnd()}…`;
|
|
167
|
+
}
|
|
168
|
+
|
|
160
169
|
interface NativeSubagentSessionStartMetadata {
|
|
161
170
|
parentThreadId: string;
|
|
162
171
|
agentNickname?: string;
|
|
@@ -1122,28 +1131,31 @@ async function buildSessionStartContext(
|
|
|
1122
1131
|
sections.push(["[Active OMX modes]", ...modeSummaries].join("\n"));
|
|
1123
1132
|
}
|
|
1124
1133
|
|
|
1125
|
-
const
|
|
1126
|
-
|
|
1134
|
+
const projectMemoryPath = resolveProjectMemoryPath(cwd);
|
|
1135
|
+
const projectMemory = projectMemoryPath ? await readJsonIfExists(projectMemoryPath) : null;
|
|
1136
|
+
if (projectMemory && projectMemoryPath) {
|
|
1127
1137
|
const directives = Array.isArray(projectMemory.directives) ? projectMemory.directives : [];
|
|
1128
1138
|
const notes = Array.isArray(projectMemory.notes) ? projectMemory.notes : [];
|
|
1129
|
-
const techStack =
|
|
1130
|
-
const conventions =
|
|
1131
|
-
const build =
|
|
1139
|
+
const techStack = safeContextSnippet(projectMemory.techStack);
|
|
1140
|
+
const conventions = safeContextSnippet(projectMemory.conventions);
|
|
1141
|
+
const build = safeContextSnippet(projectMemory.build);
|
|
1132
1142
|
const summary: string[] = [];
|
|
1143
|
+
const relativeMemoryPath = relative(cwd, projectMemoryPath).replace(/\\/g, "/");
|
|
1144
|
+
summary.push(`- source: ${relativeMemoryPath === "project-memory.json" ? "project-memory.json" : ".omx/project-memory.json"}`);
|
|
1133
1145
|
if (techStack) summary.push(`- stack: ${techStack}`);
|
|
1134
1146
|
if (conventions) summary.push(`- conventions: ${conventions}`);
|
|
1135
1147
|
if (build) summary.push(`- build: ${build}`);
|
|
1136
1148
|
if (directives.length > 0) {
|
|
1137
1149
|
const firstDirective = directives[0] as Record<string, unknown>;
|
|
1138
|
-
const directive =
|
|
1150
|
+
const directive = safeContextSnippet(firstDirective.directive);
|
|
1139
1151
|
if (directive) summary.push(`- directive: ${directive}`);
|
|
1140
1152
|
}
|
|
1141
1153
|
if (notes.length > 0) {
|
|
1142
1154
|
const firstNote = notes[0] as Record<string, unknown>;
|
|
1143
|
-
const note =
|
|
1155
|
+
const note = safeContextSnippet(firstNote.content);
|
|
1144
1156
|
if (note) summary.push(`- note: ${note}`);
|
|
1145
1157
|
}
|
|
1146
|
-
if (summary.length >
|
|
1158
|
+
if (summary.length > 1) {
|
|
1147
1159
|
sections.push(["[Project memory]", ...summary].join("\n"));
|
|
1148
1160
|
}
|
|
1149
1161
|
}
|
|
@@ -1695,20 +1707,33 @@ async function buildModeBasedStopOutput(
|
|
|
1695
1707
|
};
|
|
1696
1708
|
}
|
|
1697
1709
|
|
|
1698
|
-
function looksLikeGoalCompletionPrompt(text: string): boolean {
|
|
1699
|
-
return /\
|
|
1700
|
-
|| /\
|
|
1701
|
-
|| /\
|
|
1710
|
+
export function looksLikeGoalCompletionPrompt(text: string): boolean {
|
|
1711
|
+
return /\bupdate_goal\s*\(/i.test(text)
|
|
1712
|
+
|| /\bomx\s+(?:ultragoal|performance-goal|autoresearch-goal)\s+(?:checkpoint|complete)\b/i.test(text)
|
|
1713
|
+
|| /\b(?:complete|checkpoint|finish|close|mark)\b.{0,80}\b(?:goal|ultragoal|performance[-\s]goal|autoresearch[-\s]goal)\b/i.test(text)
|
|
1714
|
+
|| /\b(?:ultragoal|performance[-\s]goal|autoresearch[-\s]goal)\b.{0,80}\b(?:complete|checkpoint|finish|close|mark)\b/i.test(text)
|
|
1715
|
+
|| /(?:^|[.!?]\s+)(?:the\s+)?goal\s+(?:is\s+|now\s+|has\s+been\s+)?(?:complete|completed|finished|closed)(?:\s*(?:[.!?]|$)|\s*[:;]\s*\S|\s*[—–-]\s*\S)/i.test(text);
|
|
1702
1716
|
}
|
|
1703
1717
|
|
|
1704
|
-
async function findActiveGoalWorkflowReconciliationRequirement(cwd: string): Promise<{ workflow: string; command: string } | null> {
|
|
1718
|
+
async function findActiveGoalWorkflowReconciliationRequirement(cwd: string): Promise<{ workflow: string; command: string; remediation?: string } | null> {
|
|
1705
1719
|
const ultragoal = await readJsonIfExists(join(cwd, ".omx", "ultragoal", "goals.json"));
|
|
1720
|
+
const aggregateCompletion = safeObject(ultragoal?.aggregateCompletion);
|
|
1721
|
+
const aggregateProductComplete = safeString(aggregateCompletion.status) === "complete";
|
|
1706
1722
|
const ultragoals = Array.isArray(ultragoal?.goals) ? ultragoal.goals.map(safeObject) : [];
|
|
1707
|
-
const activeUltragoal =
|
|
1723
|
+
const activeUltragoal = aggregateProductComplete
|
|
1724
|
+
? undefined
|
|
1725
|
+
: ultragoals.find((goal) => safeString(goal.status) === "in_progress" || safeString(goal.id) === safeString(ultragoal?.activeGoalId));
|
|
1708
1726
|
if (activeUltragoal) {
|
|
1727
|
+
const goalId = safeString(activeUltragoal.id) || "<goal-id>";
|
|
1709
1728
|
return {
|
|
1710
1729
|
workflow: "ultragoal",
|
|
1711
|
-
command: `omx ultragoal checkpoint --goal-id ${
|
|
1730
|
+
command: `omx ultragoal checkpoint --goal-id ${goalId} --status complete --codex-goal-json '<get_goal JSON or path>' --evidence '<evidence>'`,
|
|
1731
|
+
remediation: [
|
|
1732
|
+
`If get_goal returns a completed task-scoped objective for the same aggregate ultragoal plan, checkpoint ${goalId} with evidence naming ${goalId} plus .omx/ultragoal/goals.json or ledger.jsonl and pass final quality-gate JSON; OMX will reconcile the completed planned scope without mutating Codex goal state.`,
|
|
1733
|
+
`If get_goal instead returns a different completed legacy objective and complete checkpointing fails, do not repeat --status complete in this thread.`,
|
|
1734
|
+
`Record the non-terminal blocker with: omx ultragoal checkpoint --goal-id ${goalId} --status blocked --codex-goal-json '<different completed get_goal JSON or path>' --evidence '<completed legacy Codex goal blocks create_goal in this thread>'.`,
|
|
1735
|
+
"Then continue this ultragoal from a fresh Codex thread in the same repo/worktree and create the intended goal there.",
|
|
1736
|
+
].join(" "),
|
|
1712
1737
|
};
|
|
1713
1738
|
}
|
|
1714
1739
|
|
|
@@ -1752,7 +1777,8 @@ async function buildGoalWorkflowReconciliationPromptWarning(cwd: string, prompt:
|
|
|
1752
1777
|
`OMX ${requirement.workflow} goal workflow requires Codex goal snapshot reconciliation before completion.`,
|
|
1753
1778
|
"Call get_goal, pass the resulting JSON or a path with --codex-goal-json, and do not rely on hooks or shell commands to mutate Codex-owned goal state.",
|
|
1754
1779
|
`Required command shape: ${requirement.command}.`,
|
|
1755
|
-
|
|
1780
|
+
requirement.remediation,
|
|
1781
|
+
].filter(Boolean).join(" ");
|
|
1756
1782
|
}
|
|
1757
1783
|
|
|
1758
1784
|
async function buildGoalWorkflowReconciliationStopOutput(
|
|
@@ -1764,7 +1790,11 @@ async function buildGoalWorkflowReconciliationStopOutput(
|
|
|
1764
1790
|
const requirement = await findActiveGoalWorkflowReconciliationRequirement(cwd);
|
|
1765
1791
|
if (!requirement) return null;
|
|
1766
1792
|
const systemMessage =
|
|
1767
|
-
|
|
1793
|
+
[
|
|
1794
|
+
`OMX ${requirement.workflow} requires get_goal snapshot reconciliation before completion; call get_goal and pass --codex-goal-json to ${requirement.command}.`,
|
|
1795
|
+
requirement.remediation,
|
|
1796
|
+
"Hooks must not mutate Codex goal state.",
|
|
1797
|
+
].filter(Boolean).join(" ");
|
|
1768
1798
|
return {
|
|
1769
1799
|
decision: "block",
|
|
1770
1800
|
reason: systemMessage,
|
|
@@ -2323,6 +2353,109 @@ function readPreviousNativeStopSignature(
|
|
|
2323
2353
|
return safeString(sessionState.last_signature).trim();
|
|
2324
2354
|
}
|
|
2325
2355
|
|
|
2356
|
+
function parseBoundedPositiveInteger(value: unknown, fallback: number): number {
|
|
2357
|
+
const parsed = Math.trunc(Number(value));
|
|
2358
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
2359
|
+
}
|
|
2360
|
+
|
|
2361
|
+
function parseBoundedNonNegativeInteger(value: unknown, fallback: number): number {
|
|
2362
|
+
const parsed = Math.trunc(Number(value));
|
|
2363
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
|
|
2364
|
+
}
|
|
2365
|
+
|
|
2366
|
+
function normalizeOrdinaryStopProgressText(value: unknown): string {
|
|
2367
|
+
return safeString(value)
|
|
2368
|
+
.replace(/\s+/g, " ")
|
|
2369
|
+
.trim()
|
|
2370
|
+
.toLowerCase();
|
|
2371
|
+
}
|
|
2372
|
+
|
|
2373
|
+
function shortenOrdinaryStopProgressText(value: string): string {
|
|
2374
|
+
const trimmed = value.replace(/\s+/g, " ").trim();
|
|
2375
|
+
if (trimmed.length <= ORDINARY_STOP_NO_PROGRESS_MAX_MESSAGE_LENGTH) return trimmed;
|
|
2376
|
+
return `${trimmed.slice(0, ORDINARY_STOP_NO_PROGRESS_MAX_MESSAGE_LENGTH - 1).trimEnd()}…`;
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
function ordinaryStopProgressFingerprint(payload: CodexHookPayload): string {
|
|
2380
|
+
const message = normalizeOrdinaryStopProgressText(
|
|
2381
|
+
payload.last_assistant_message ?? payload.lastAssistantMessage,
|
|
2382
|
+
) || "<no assistant message>";
|
|
2383
|
+
const mode = normalizeOrdinaryStopProgressText(payload.mode) || "ordinary";
|
|
2384
|
+
return `${mode}|${message}`;
|
|
2385
|
+
}
|
|
2386
|
+
|
|
2387
|
+
function readIsoTimeMs(value: unknown): number | null {
|
|
2388
|
+
const parsed = Date.parse(safeString(value));
|
|
2389
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
2390
|
+
}
|
|
2391
|
+
|
|
2392
|
+
async function maybeBuildOrdinaryStopNoProgressOutput(
|
|
2393
|
+
payload: CodexHookPayload,
|
|
2394
|
+
stateDir: string,
|
|
2395
|
+
canonicalSessionId?: string,
|
|
2396
|
+
): Promise<Record<string, unknown> | null> {
|
|
2397
|
+
const statePath = join(stateDir, NATIVE_STOP_STATE_FILE);
|
|
2398
|
+
const state = await readJsonIfExists(statePath) ?? {};
|
|
2399
|
+
const sessions = safeObject(state.sessions);
|
|
2400
|
+
const sessionKey = readNativeStopSessionKey(payload, canonicalSessionId);
|
|
2401
|
+
const sessionState = safeObject(sessions[sessionKey]);
|
|
2402
|
+
const previousGuard = safeObject(sessionState.ordinary_no_progress_guard);
|
|
2403
|
+
const fingerprint = ordinaryStopProgressFingerprint(payload);
|
|
2404
|
+
const nowIso = new Date().toISOString();
|
|
2405
|
+
const previousFingerprint = safeString(previousGuard.fingerprint).trim();
|
|
2406
|
+
const sameFingerprint = previousFingerprint === fingerprint;
|
|
2407
|
+
const firstSeenAt = sameFingerprint
|
|
2408
|
+
? safeString(previousGuard.first_seen_at).trim() || nowIso
|
|
2409
|
+
: nowIso;
|
|
2410
|
+
const repeatCount = sameFingerprint
|
|
2411
|
+
? parseBoundedPositiveInteger(previousGuard.repeat_count, 1) + 1
|
|
2412
|
+
: 1;
|
|
2413
|
+
|
|
2414
|
+
sessions[sessionKey] = {
|
|
2415
|
+
...sessionState,
|
|
2416
|
+
ordinary_no_progress_guard: {
|
|
2417
|
+
fingerprint,
|
|
2418
|
+
first_seen_at: firstSeenAt,
|
|
2419
|
+
last_seen_at: nowIso,
|
|
2420
|
+
repeat_count: repeatCount,
|
|
2421
|
+
last_turn_id: readPayloadTurnId(payload) || null,
|
|
2422
|
+
last_thread_id: readPayloadThreadId(payload) || null,
|
|
2423
|
+
},
|
|
2424
|
+
};
|
|
2425
|
+
await mkdir(stateDir, { recursive: true });
|
|
2426
|
+
await writeFile(statePath, JSON.stringify({ ...state, sessions }, null, 2));
|
|
2427
|
+
|
|
2428
|
+
const stopHookActive = payload.stop_hook_active === true || payload.stopHookActive === true;
|
|
2429
|
+
if (!stopHookActive) return null;
|
|
2430
|
+
|
|
2431
|
+
const maxRepeats = parseBoundedPositiveInteger(
|
|
2432
|
+
process.env.OMX_NATIVE_STOP_NO_PROGRESS_MAX_REPEATS,
|
|
2433
|
+
ORDINARY_STOP_NO_PROGRESS_DEFAULT_MAX_REPEATS,
|
|
2434
|
+
);
|
|
2435
|
+
const idleMs = parseBoundedNonNegativeInteger(
|
|
2436
|
+
process.env.OMX_NATIVE_STOP_NO_PROGRESS_IDLE_MS,
|
|
2437
|
+
ORDINARY_STOP_NO_PROGRESS_DEFAULT_IDLE_MS,
|
|
2438
|
+
);
|
|
2439
|
+
const firstSeenMs = readIsoTimeMs(firstSeenAt) ?? Date.now();
|
|
2440
|
+
const elapsedMs = Math.max(0, Date.now() - firstSeenMs);
|
|
2441
|
+
if (repeatCount < maxRepeats || elapsedMs < idleMs) return null;
|
|
2442
|
+
|
|
2443
|
+
const message = shortenOrdinaryStopProgressText(
|
|
2444
|
+
safeString(payload.last_assistant_message ?? payload.lastAssistantMessage) || "no assistant message recorded",
|
|
2445
|
+
);
|
|
2446
|
+
const elapsedSeconds = Math.round(elapsedMs / 1000);
|
|
2447
|
+
const diagnostic =
|
|
2448
|
+
`OMX ordinary task no-progress guard triggered after ${repeatCount} repeated Stop-hook pass(es) over ~${elapsedSeconds}s with unchanged status: "${message}". ` +
|
|
2449
|
+
"Emit a concise diagnostic summary now: state the last concrete progress/evidence, whether the task is complete, blocked, failed, or needs missing information, and stop instead of continuing a vague working loop.";
|
|
2450
|
+
|
|
2451
|
+
return {
|
|
2452
|
+
decision: "block",
|
|
2453
|
+
reason: diagnostic,
|
|
2454
|
+
stopReason: "ordinary_task_no_progress_guard",
|
|
2455
|
+
systemMessage: diagnostic,
|
|
2456
|
+
};
|
|
2457
|
+
}
|
|
2458
|
+
|
|
2326
2459
|
async function persistNativeStopSignature(
|
|
2327
2460
|
stateDir: string,
|
|
2328
2461
|
payload: CodexHookPayload,
|
|
@@ -2651,8 +2784,14 @@ async function buildStopHookOutput(
|
|
|
2651
2784
|
if (ralphCompletionAuditBlock) {
|
|
2652
2785
|
await reopenRalphCompletionAuditBlock(ralphCompletionAuditBlock);
|
|
2653
2786
|
const blockingPath = formatStopStatePath(cwd, ralphCompletionAuditBlock.path);
|
|
2654
|
-
const systemMessage =
|
|
2655
|
-
`OMX Ralph completion audit is missing required evidence (${ralphCompletionAuditBlock.reason}; state: ${blockingPath})
|
|
2787
|
+
const systemMessage = [
|
|
2788
|
+
`OMX Ralph completion audit is missing required evidence (${ralphCompletionAuditBlock.reason}; state: ${blockingPath}).`,
|
|
2789
|
+
"Continue verification and do not report complete yet.",
|
|
2790
|
+
"Record machine-readable completion evidence before stopping:",
|
|
2791
|
+
"- either set state.completion_audit = { passed: true, prompt_to_artifact_checklist: [...], verification_evidence: [...] }",
|
|
2792
|
+
"- or set completion_audit_path / completion_audit_evidence_path to a repo-relative JSON file with those same fields.",
|
|
2793
|
+
"Markdown artifacts and flat top-level checklist/evidence fields are not accepted by the Ralph Stop gate.",
|
|
2794
|
+
].join(" ");
|
|
2656
2795
|
return await returnPersistentStopBlock(
|
|
2657
2796
|
payload,
|
|
2658
2797
|
stateDir,
|
|
@@ -2843,6 +2982,13 @@ async function buildStopHookOutput(
|
|
|
2843
2982
|
{ allowRepeatDuringStopHook: true },
|
|
2844
2983
|
);
|
|
2845
2984
|
}
|
|
2985
|
+
const ordinaryNoProgressOutput = await maybeBuildOrdinaryStopNoProgressOutput(
|
|
2986
|
+
payload,
|
|
2987
|
+
stateDir,
|
|
2988
|
+
canonicalSessionId,
|
|
2989
|
+
);
|
|
2990
|
+
if (ordinaryNoProgressOutput) return ordinaryNoProgressOutput;
|
|
2991
|
+
|
|
2846
2992
|
const autoNudgeConfig = await loadAutoNudgeConfig();
|
|
2847
2993
|
const autoNudgePhase = await readStopAutoNudgePhase(cwd, stateDir, canonicalSessionId, threadId);
|
|
2848
2994
|
|
|
@@ -793,14 +793,20 @@ function buildGitCommitComplianceErrors(message: string | null): string[] {
|
|
|
793
793
|
errors.push("Add a blank line after the subject before the narrative body.");
|
|
794
794
|
}
|
|
795
795
|
|
|
796
|
+
const hasSubject = (lines[0]?.trim() ?? "") !== "";
|
|
797
|
+
const hasBlankSeparator = lines.length >= 2 && lines[1]?.trim() === "";
|
|
796
798
|
const { bodyText, trailerLines } = splitBodyAndTrailerLines(lines.slice(2).join("\n"));
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
799
|
+
const hasOmxCoauthorTrailer = trailerLines.includes(OMX_COAUTHOR_TRAILER);
|
|
800
|
+
const usesCompactLorePath = hasSubject && hasBlankSeparator && !bodyText && hasOmxCoauthorTrailer;
|
|
801
|
+
if (!usesCompactLorePath) {
|
|
802
|
+
if (!bodyText) {
|
|
803
|
+
errors.push("Add a narrative body paragraph explaining the decision context.");
|
|
804
|
+
}
|
|
805
|
+
if (!trailerLines.some((line) => LORE_TRAILER_PREFIXES.some((prefix) => line.startsWith(prefix)))) {
|
|
806
|
+
errors.push("Add at least one Lore trailer such as `Constraint:`, `Confidence:`, or `Tested:`.");
|
|
807
|
+
}
|
|
802
808
|
}
|
|
803
|
-
if (!
|
|
809
|
+
if (!hasOmxCoauthorTrailer) {
|
|
804
810
|
errors.push(`Add the required co-author trailer: \`${OMX_COAUTHOR_TRAILER}\`.`);
|
|
805
811
|
}
|
|
806
812
|
|
|
@@ -99,7 +99,7 @@ async function resolveCanonicalPaneFromPaneTarget(paneTarget: any, expectedCwd:
|
|
|
99
99
|
return finalizeResolvedPane(healedPaneId, 'healed_hud_pane_target', expectedCwd);
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
async function resolvePreferredModePane(stateDir: string, allowedModes: string[]): Promise<{ mode: string; state: any; pane: string } | null> {
|
|
102
|
+
async function resolvePreferredModePane(stateDir: string, allowedModes: string[]): Promise<{ mode: string; state: any; pane: string; stateDir: string } | null> {
|
|
103
103
|
const scopedDirs = await getScopedStateDirsForCurrentSession(stateDir).catch(() => [stateDir]);
|
|
104
104
|
const dirs = [...scopedDirs];
|
|
105
105
|
if (!dirs.map((dir) => resolvePath(dir)).includes(resolvePath(stateDir))) {
|
|
@@ -111,13 +111,84 @@ async function resolvePreferredModePane(stateDir: string, allowedModes: string[]
|
|
|
111
111
|
const parsed = await readJsonIfExists(path, null);
|
|
112
112
|
const pane = safeString(parsed?.tmux_pane_id || '').trim();
|
|
113
113
|
if (parsed?.active && pane) {
|
|
114
|
-
return { mode, state: parsed, pane };
|
|
114
|
+
return { mode, state: parsed, pane, stateDir: dir };
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
118
|
return null;
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
function modeStateMatchesInvocationOwner(modeState: any, payload: any, managedContext: any): { ok: true } | { ok: false; reason: string } {
|
|
122
|
+
const invocationSessionId = resolveInvocationSessionId(payload);
|
|
123
|
+
const canonicalSessionId = safeString(managedContext?.canonicalSessionId || managedContext?.sessionState?.session_id).trim();
|
|
124
|
+
const nativeSessionId = safeString(managedContext?.nativeSessionId || managedContext?.sessionState?.native_session_id || managedContext?.sessionState?.codex_session_id).trim();
|
|
125
|
+
const allowedSessionIds = new Set([
|
|
126
|
+
invocationSessionId,
|
|
127
|
+
canonicalSessionId,
|
|
128
|
+
nativeSessionId,
|
|
129
|
+
].filter(Boolean));
|
|
130
|
+
|
|
131
|
+
const ownerOmxSessionId = safeString(modeState?.owner_omx_session_id).trim();
|
|
132
|
+
if (ownerOmxSessionId && !allowedSessionIds.has(ownerOmxSessionId)) {
|
|
133
|
+
return { ok: false, reason: 'mode_owner_session_mismatch' };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const stateSessionId = safeString(modeState?.session_id).trim();
|
|
137
|
+
if (!ownerOmxSessionId && stateSessionId && !allowedSessionIds.has(stateSessionId)) {
|
|
138
|
+
return { ok: false, reason: 'mode_session_mismatch' };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const ownerCodexSessionId = safeString(modeState?.owner_codex_session_id || modeState?.codex_session_id).trim();
|
|
142
|
+
if (ownerCodexSessionId && !allowedSessionIds.has(ownerCodexSessionId)) {
|
|
143
|
+
return { ok: false, reason: 'mode_codex_session_mismatch' };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return { ok: true };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function validateResolvedInjectionOwnership({
|
|
150
|
+
paneTarget,
|
|
151
|
+
cwd,
|
|
152
|
+
payload,
|
|
153
|
+
modeState,
|
|
154
|
+
modePane,
|
|
155
|
+
managedCurrentPane,
|
|
156
|
+
}: any): Promise<{ ok: true } | { ok: false; reason: string; managedContext?: any }> {
|
|
157
|
+
const ownership = await verifyManagedPaneTarget(paneTarget, cwd, payload, { allowTeamWorker: false });
|
|
158
|
+
if (!ownership.ok) {
|
|
159
|
+
return { ok: false, reason: ownership.reason || 'pane_not_managed_session', managedContext: ownership.managedContext };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const modeOwner = modeStateMatchesInvocationOwner(modeState, payload, ownership.managedContext);
|
|
163
|
+
if (!modeOwner.ok) return { ...modeOwner, managedContext: ownership.managedContext };
|
|
164
|
+
|
|
165
|
+
const statePane = safeString(modePane || modeState?.tmux_pane_id).trim();
|
|
166
|
+
const currentPane = safeString(managedCurrentPane).trim();
|
|
167
|
+
if (statePane && currentPane && statePane !== currentPane) {
|
|
168
|
+
return { ok: false, reason: 'mode_pane_current_pane_mismatch', managedContext: ownership.managedContext };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const expectedWindowId = safeString(modeState?.tmux_window_id || modeState?.tmuxWindowId).trim();
|
|
172
|
+
if (!expectedWindowId) {
|
|
173
|
+
return { ok: true };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
const windowResult = await runProcess('tmux', ['display-message', '-p', '-t', paneTarget, '#{window_id}'], 2000);
|
|
178
|
+
const paneWindowId = safeString(windowResult.stdout).trim();
|
|
179
|
+
if (!paneWindowId) {
|
|
180
|
+
return { ok: false, reason: 'pane_window_unverified', managedContext: ownership.managedContext };
|
|
181
|
+
}
|
|
182
|
+
if (paneWindowId !== expectedWindowId) {
|
|
183
|
+
return { ok: false, reason: 'pane_window_mismatch', managedContext: ownership.managedContext };
|
|
184
|
+
}
|
|
185
|
+
} catch {
|
|
186
|
+
return { ok: false, reason: 'pane_window_unverified', managedContext: ownership.managedContext };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return { ok: true };
|
|
190
|
+
}
|
|
191
|
+
|
|
121
192
|
async function readVisibleAllowedModes(
|
|
122
193
|
cwd: string,
|
|
123
194
|
stateDir: string,
|
|
@@ -460,7 +531,22 @@ export async function handleTmuxInjection({
|
|
|
460
531
|
turnId,
|
|
461
532
|
timestamp: nowIso,
|
|
462
533
|
}), sourceText);
|
|
463
|
-
const
|
|
534
|
+
const managedCurrentPane = await resolveManagedCurrentPane(cwd, payload, { allowTeamWorker: false });
|
|
535
|
+
if (modePane && managedCurrentPane && modePane !== managedCurrentPane) {
|
|
536
|
+
state.last_reason = 'mode_pane_current_pane_mismatch';
|
|
537
|
+
state.last_event_at = nowIso;
|
|
538
|
+
await writeFile(hookStatePath, JSON.stringify(state, null, 2)).catch(() => {});
|
|
539
|
+
await logTmuxHookEvent(logsDir, {
|
|
540
|
+
...baseLog,
|
|
541
|
+
event: 'injection_skipped',
|
|
542
|
+
reason: 'mode_pane_current_pane_mismatch',
|
|
543
|
+
mode_pane: modePane,
|
|
544
|
+
current_pane: managedCurrentPane,
|
|
545
|
+
});
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
const preferredPaneTarget = modePane || managedCurrentPane;
|
|
464
550
|
let resolution = preferredModePane
|
|
465
551
|
? await resolvePaneTarget({ type: 'pane', value: preferredModePane.pane }, cwd, preferredModePane.pane, cwd, payload)
|
|
466
552
|
: preferredPaneTarget
|
|
@@ -484,6 +570,27 @@ export async function handleTmuxInjection({
|
|
|
484
570
|
}
|
|
485
571
|
const paneTarget = resolution.paneTarget;
|
|
486
572
|
|
|
573
|
+
const ownership = await validateResolvedInjectionOwnership({
|
|
574
|
+
paneTarget,
|
|
575
|
+
cwd,
|
|
576
|
+
payload,
|
|
577
|
+
modeState,
|
|
578
|
+
modePane,
|
|
579
|
+
managedCurrentPane,
|
|
580
|
+
});
|
|
581
|
+
if (!ownership.ok) {
|
|
582
|
+
state.last_reason = ownership.reason;
|
|
583
|
+
state.last_event_at = nowIso;
|
|
584
|
+
await writeFile(hookStatePath, JSON.stringify(state, null, 2)).catch(() => {});
|
|
585
|
+
await logTmuxHookEvent(logsDir, {
|
|
586
|
+
...baseLog,
|
|
587
|
+
event: 'injection_skipped',
|
|
588
|
+
reason: ownership.reason,
|
|
589
|
+
pane_target: paneTarget,
|
|
590
|
+
});
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
|
|
487
594
|
// Final guard phase: pane is canonical identity for quota/cooldown.
|
|
488
595
|
const guard = evaluateInjectionGuards({
|
|
489
596
|
config,
|
|
@@ -6,6 +6,12 @@ const DEFAULT_TEST_TIMEOUT_MS = 0;
|
|
|
6
6
|
const DEFAULT_RUNNER_TIMEOUT_MS = 30 * 60 * 1_000;
|
|
7
7
|
const DEFAULT_CI_TEST_CONCURRENCY = 1;
|
|
8
8
|
|
|
9
|
+
function parseBooleanEnv(value: string | undefined): boolean {
|
|
10
|
+
if (!value) return false;
|
|
11
|
+
const normalized = value.trim().toLowerCase();
|
|
12
|
+
return normalized === '1' || normalized === 'true' || normalized === 'yes' || normalized === 'on';
|
|
13
|
+
}
|
|
14
|
+
|
|
9
15
|
function collectTests(path: string, out: string[]): void {
|
|
10
16
|
let stats;
|
|
11
17
|
try {
|
|
@@ -61,6 +67,7 @@ if (files.length === 0) {
|
|
|
61
67
|
const testTimeoutMs = parseTimeoutMs(process.env.OMX_NODE_TEST_TIMEOUT_MS, DEFAULT_TEST_TIMEOUT_MS);
|
|
62
68
|
const runnerTimeoutMs = parseTimeoutMs(process.env.OMX_NODE_TEST_RUNNER_TIMEOUT_MS, DEFAULT_RUNNER_TIMEOUT_MS);
|
|
63
69
|
const testConcurrency = parseTestConcurrency(process.env);
|
|
70
|
+
const forceExit = parseBooleanEnv(process.env.OMX_NODE_TEST_FORCE_EXIT);
|
|
64
71
|
const testArgs = ['--test'];
|
|
65
72
|
if (testTimeoutMs > 0) {
|
|
66
73
|
testArgs.push(`--test-timeout=${testTimeoutMs}`);
|
|
@@ -68,14 +75,17 @@ if (testTimeoutMs > 0) {
|
|
|
68
75
|
if (testConcurrency) {
|
|
69
76
|
testArgs.push(`--test-concurrency=${testConcurrency}`);
|
|
70
77
|
}
|
|
78
|
+
if (forceExit) {
|
|
79
|
+
testArgs.push('--test-force-exit');
|
|
80
|
+
}
|
|
71
81
|
testArgs.push(...files);
|
|
72
82
|
|
|
73
83
|
console.error(
|
|
74
84
|
`[run-test-files] running ${files.length} test file(s) from ${targets.join(', ')}${
|
|
75
85
|
testTimeoutMs > 0 ? ` with per-test timeout ${testTimeoutMs}ms` : ' with per-test timeout disabled'
|
|
76
86
|
}${testConcurrency ? `, test concurrency ${testConcurrency}` : ', default test concurrency'}${
|
|
77
|
-
|
|
78
|
-
}`,
|
|
87
|
+
forceExit ? ', force exit enabled' : ', force exit disabled'
|
|
88
|
+
}${runnerTimeoutMs > 0 ? `, and runner timeout ${runnerTimeoutMs}ms` : ', and runner timeout disabled'}`,
|
|
79
89
|
);
|
|
80
90
|
|
|
81
91
|
const childEnv = { ...process.env };
|
|
@@ -101,6 +111,7 @@ console.error(
|
|
|
101
111
|
+ `Roots: ${targets.join(', ')}. Test files: ${files.length}. `
|
|
102
112
|
+ `Per-test timeout: ${testTimeoutMs > 0 ? `${testTimeoutMs}ms` : 'disabled'}. `
|
|
103
113
|
+ `Test concurrency: ${testConcurrency ?? 'default'}. `
|
|
114
|
+
+ `Force exit: ${forceExit ? 'enabled' : 'disabled'}. `
|
|
104
115
|
+ `Runner timeout: ${runnerTimeoutMs > 0 ? `${runnerTimeoutMs}ms` : 'disabled'}.`,
|
|
105
116
|
);
|
|
106
117
|
process.exit(1);
|
|
@@ -181,13 +181,20 @@
|
|
|
181
181
|
"internalRequired": false
|
|
182
182
|
},
|
|
183
183
|
{
|
|
184
|
-
"name": "
|
|
184
|
+
"name": "design",
|
|
185
185
|
"category": "shortcut",
|
|
186
|
-
"status": "
|
|
186
|
+
"status": "active",
|
|
187
187
|
"canonical": "designer",
|
|
188
188
|
"core": false,
|
|
189
189
|
"internalRequired": false
|
|
190
190
|
},
|
|
191
|
+
{
|
|
192
|
+
"name": "frontend-ui-ux",
|
|
193
|
+
"category": "shortcut",
|
|
194
|
+
"status": "deprecated",
|
|
195
|
+
"core": false,
|
|
196
|
+
"internalRequired": false
|
|
197
|
+
},
|
|
191
198
|
{
|
|
192
199
|
"name": "git-master",
|
|
193
200
|
"category": "shortcut",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"context-pack-status.test.d.ts","sourceRoot":"","sources":["../../../src/planning/__tests__/context-pack-status.test.ts"],"names":[],"mappings":""}
|