oh-my-codex 0.12.2 → 0.12.4
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/README.md +2 -0
- package/dist/cli/__tests__/index.test.js +73 -12
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/launch-fallback.test.js +8 -27
- package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
- package/dist/cli/__tests__/mcp-parity.test.d.ts +2 -0
- package/dist/cli/__tests__/mcp-parity.test.d.ts.map +1 -0
- package/dist/cli/__tests__/mcp-parity.test.js +111 -0
- package/dist/cli/__tests__/mcp-parity.test.js.map +1 -0
- package/dist/cli/__tests__/nested-help-routing.test.js +13 -0
- package/dist/cli/__tests__/nested-help-routing.test.js.map +1 -1
- package/dist/cli/__tests__/package-bin-contract.test.js +6 -1
- package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
- package/dist/cli/__tests__/setup-hooks-shared-ownership.test.d.ts +2 -0
- package/dist/cli/__tests__/setup-hooks-shared-ownership.test.d.ts.map +1 -0
- package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js +189 -0
- package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js.map +1 -0
- package/dist/cli/__tests__/setup-scope.test.js +48 -0
- package/dist/cli/__tests__/setup-scope.test.js.map +1 -1
- package/dist/cli/__tests__/state.test.d.ts +2 -0
- package/dist/cli/__tests__/state.test.d.ts.map +1 -0
- package/dist/cli/__tests__/state.test.js +46 -0
- package/dist/cli/__tests__/state.test.js.map +1 -0
- package/dist/cli/__tests__/team.test.js +238 -2
- package/dist/cli/__tests__/team.test.js.map +1 -1
- package/dist/cli/__tests__/uninstall.test.js +37 -2
- package/dist/cli/__tests__/uninstall.test.js.map +1 -1
- package/dist/cli/index.d.ts +6 -13
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +47 -60
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/mcp-parity.d.ts +22 -0
- package/dist/cli/mcp-parity.d.ts.map +1 -0
- package/dist/cli/mcp-parity.js +227 -0
- package/dist/cli/mcp-parity.js.map +1 -0
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +5 -2
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/state.d.ts +8 -0
- package/dist/cli/state.d.ts.map +1 -0
- package/dist/cli/state.js +71 -0
- package/dist/cli/state.js.map +1 -0
- package/dist/cli/team.d.ts.map +1 -1
- package/dist/cli/team.js +6 -5
- package/dist/cli/team.js.map +1 -1
- package/dist/cli/uninstall.d.ts.map +1 -1
- package/dist/cli/uninstall.js +18 -4
- package/dist/cli/uninstall.js.map +1 -1
- package/dist/config/__tests__/codex-hooks.test.d.ts +2 -0
- package/dist/config/__tests__/codex-hooks.test.d.ts.map +1 -0
- package/dist/config/__tests__/codex-hooks.test.js +53 -0
- package/dist/config/__tests__/codex-hooks.test.js.map +1 -0
- package/dist/config/codex-hooks.d.ts +16 -7
- package/dist/config/codex-hooks.d.ts.map +1 -1
- package/dist/config/codex-hooks.js +134 -2
- package/dist/config/codex-hooks.js.map +1 -1
- package/dist/hooks/__tests__/keyword-detector.test.js +62 -0
- package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
- package/dist/hooks/keyword-detector.d.ts.map +1 -1
- package/dist/hooks/keyword-detector.js +20 -8
- package/dist/hooks/keyword-detector.js.map +1 -1
- package/dist/hud/__tests__/reconcile.test.d.ts +2 -0
- package/dist/hud/__tests__/reconcile.test.d.ts.map +1 -0
- package/dist/hud/__tests__/reconcile.test.js +83 -0
- package/dist/hud/__tests__/reconcile.test.js.map +1 -0
- package/dist/hud/__tests__/render.test.js +43 -0
- package/dist/hud/__tests__/render.test.js.map +1 -1
- package/dist/hud/constants.d.ts +2 -1
- package/dist/hud/constants.d.ts.map +1 -1
- package/dist/hud/constants.js +2 -1
- package/dist/hud/constants.js.map +1 -1
- package/dist/hud/index.d.ts +4 -1
- package/dist/hud/index.d.ts.map +1 -1
- package/dist/hud/index.js +11 -5
- package/dist/hud/index.js.map +1 -1
- package/dist/hud/reconcile.d.ts +23 -0
- package/dist/hud/reconcile.d.ts.map +1 -0
- package/dist/hud/reconcile.js +71 -0
- package/dist/hud/reconcile.js.map +1 -0
- package/dist/hud/render.d.ts +6 -1
- package/dist/hud/render.d.ts.map +1 -1
- package/dist/hud/render.js +77 -3
- package/dist/hud/render.js.map +1 -1
- package/dist/hud/tmux.d.ts +26 -0
- package/dist/hud/tmux.d.ts.map +1 -0
- package/dist/hud/tmux.js +126 -0
- package/dist/hud/tmux.js.map +1 -0
- package/dist/mcp/bootstrap.d.ts.map +1 -1
- package/dist/mcp/bootstrap.js +16 -6
- package/dist/mcp/bootstrap.js.map +1 -1
- package/dist/mcp/code-intel-server.d.ts +298 -0
- package/dist/mcp/code-intel-server.d.ts.map +1 -1
- package/dist/mcp/code-intel-server.js +9 -5
- package/dist/mcp/code-intel-server.js.map +1 -1
- package/dist/mcp/memory-server.d.ts +195 -1
- package/dist/mcp/memory-server.d.ts.map +1 -1
- package/dist/mcp/memory-server.js +9 -5
- package/dist/mcp/memory-server.js.map +1 -1
- package/dist/mcp/trace-server.d.ts +51 -0
- package/dist/mcp/trace-server.d.ts.map +1 -1
- package/dist/mcp/trace-server.js +9 -5
- package/dist/mcp/trace-server.js.map +1 -1
- package/dist/scripts/__tests__/codex-native-hook.test.js +481 -8
- package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
- package/dist/scripts/codex-native-hook.d.ts.map +1 -1
- package/dist/scripts/codex-native-hook.js +171 -52
- package/dist/scripts/codex-native-hook.js.map +1 -1
- package/dist/scripts/codex-native-pre-post.d.ts +5 -0
- package/dist/scripts/codex-native-pre-post.d.ts.map +1 -1
- package/dist/scripts/codex-native-pre-post.js +86 -0
- package/dist/scripts/codex-native-pre-post.js.map +1 -1
- package/dist/scripts/notify-hook/operational-events.d.ts.map +1 -1
- package/dist/scripts/notify-hook/operational-events.js +7 -2
- package/dist/scripts/notify-hook/operational-events.js.map +1 -1
- package/dist/state/__tests__/operations-ralph-phase.test.d.ts +2 -0
- package/dist/state/__tests__/operations-ralph-phase.test.d.ts.map +1 -0
- package/dist/state/__tests__/operations-ralph-phase.test.js +82 -0
- package/dist/state/__tests__/operations-ralph-phase.test.js.map +1 -0
- package/dist/state/__tests__/operations.test.d.ts +2 -0
- package/dist/state/__tests__/operations.test.d.ts.map +1 -0
- package/dist/state/__tests__/operations.test.js +200 -0
- package/dist/state/__tests__/operations.test.js.map +1 -0
- package/dist/state/__tests__/path-traversal.test.d.ts +2 -0
- package/dist/state/__tests__/path-traversal.test.d.ts.map +1 -0
- package/dist/state/__tests__/path-traversal.test.js +49 -0
- package/dist/state/__tests__/path-traversal.test.js.map +1 -0
- package/dist/state/operations.d.ts +11 -0
- package/dist/state/operations.d.ts.map +1 -0
- package/dist/state/operations.js +233 -0
- package/dist/state/operations.js.map +1 -0
- package/dist/team/__tests__/api-interop.test.js +24 -2
- package/dist/team/__tests__/api-interop.test.js.map +1 -1
- package/dist/team/__tests__/delivery-e2e-smoke.test.js +9 -1
- package/dist/team/__tests__/delivery-e2e-smoke.test.js.map +1 -1
- package/dist/team/__tests__/runtime-cli.test.js +45 -0
- package/dist/team/__tests__/runtime-cli.test.js.map +1 -1
- package/dist/team/__tests__/runtime.test.js +227 -66
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/tmux-session.test.js +33 -0
- package/dist/team/__tests__/tmux-session.test.js.map +1 -1
- package/dist/team/api-interop.d.ts.map +1 -1
- package/dist/team/api-interop.js +2 -1
- package/dist/team/api-interop.js.map +1 -1
- package/dist/team/runtime-cli.d.ts.map +1 -1
- package/dist/team/runtime-cli.js +21 -2
- package/dist/team/runtime-cli.js.map +1 -1
- package/dist/team/runtime.d.ts +8 -0
- package/dist/team/runtime.d.ts.map +1 -1
- package/dist/team/runtime.js +203 -85
- package/dist/team/runtime.js.map +1 -1
- package/dist/team/state/dispatch.d.ts.map +1 -1
- package/dist/team/state/dispatch.js +9 -0
- package/dist/team/state/dispatch.js.map +1 -1
- package/dist/team/tmux-session.js +3 -3
- package/dist/team/tmux-session.js.map +1 -1
- package/dist/team/worktree.d.ts +2 -0
- package/dist/team/worktree.d.ts.map +1 -1
- package/dist/team/worktree.js +7 -1
- package/dist/team/worktree.js.map +1 -1
- package/dist/utils/__tests__/paths.test.js +76 -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 +14 -0
- package/dist/utils/paths.js.map +1 -1
- package/dist/verification/__tests__/ci-rust-gates.test.js +59 -11
- package/dist/verification/__tests__/ci-rust-gates.test.js.map +1 -1
- package/dist/verification/__tests__/ralph-persistence-gate.test.js +1 -4
- package/dist/verification/__tests__/ralph-persistence-gate.test.js.map +1 -1
- package/package.json +6 -1
- package/src/scripts/__tests__/codex-native-hook.test.ts +636 -8
- package/src/scripts/codex-native-hook.ts +249 -60
- package/src/scripts/codex-native-pre-post.ts +104 -0
- package/src/scripts/notify-hook/operational-events.ts +6 -2
|
@@ -1,18 +1,41 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import { execFileSync } from "node:child_process";
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
|
-
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
4
|
+
import { chmod, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
6
|
import { dirname, join } from "node:path";
|
|
7
|
-
import { describe, it } from "node:test";
|
|
7
|
+
import { afterEach, beforeEach, describe, it } from "node:test";
|
|
8
8
|
import { buildManagedCodexHooksConfig } from "../../config/codex-hooks.js";
|
|
9
|
-
import { initTeamState } from "../../team/state.js";
|
|
9
|
+
import { initTeamState, readTeamLeaderAttention, readTeamPhase, } from "../../team/state.js";
|
|
10
10
|
import { dispatchCodexNativeHook, mapCodexHookEventToOmxEvent, resolveSessionOwnerPidFromAncestry, } from "../codex-native-hook.js";
|
|
11
11
|
async function writeJson(path, value) {
|
|
12
12
|
await mkdir(dirname(path), { recursive: true }).catch(() => { });
|
|
13
13
|
await writeFile(path, JSON.stringify(value, null, 2));
|
|
14
14
|
}
|
|
15
15
|
const TEAM_STOP_COMMIT_GUIDANCE = " If system-generated worker auto-checkpoint commits exist, rewrite them into Lore-format final commits before merge/finalization.";
|
|
16
|
+
const TEAM_ENV_KEYS = [
|
|
17
|
+
"OMX_TEAM_WORKER",
|
|
18
|
+
"OMX_TEAM_STATE_ROOT",
|
|
19
|
+
"OMX_TEAM_LEADER_CWD",
|
|
20
|
+
];
|
|
21
|
+
const priorTeamEnv = new Map();
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
priorTeamEnv.clear();
|
|
24
|
+
for (const key of TEAM_ENV_KEYS) {
|
|
25
|
+
priorTeamEnv.set(key, process.env[key]);
|
|
26
|
+
delete process.env[key];
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
afterEach(() => {
|
|
30
|
+
for (const key of TEAM_ENV_KEYS) {
|
|
31
|
+
const value = priorTeamEnv.get(key);
|
|
32
|
+
if (typeof value === "string")
|
|
33
|
+
process.env[key] = value;
|
|
34
|
+
else
|
|
35
|
+
delete process.env[key];
|
|
36
|
+
}
|
|
37
|
+
priorTeamEnv.clear();
|
|
38
|
+
});
|
|
16
39
|
describe("codex native hook config", () => {
|
|
17
40
|
it("builds the expected managed hooks.json shape", () => {
|
|
18
41
|
const config = buildManagedCodexHooksConfig("/tmp/omx");
|
|
@@ -26,6 +49,10 @@ describe("codex native hook config", () => {
|
|
|
26
49
|
const preToolUse = config.hooks.PreToolUse[0];
|
|
27
50
|
assert.equal(preToolUse.matcher, "Bash");
|
|
28
51
|
assert.match(String(preToolUse.hooks?.[0]?.command || ""), /codex-native-hook\.js"?$/);
|
|
52
|
+
const postToolUse = config.hooks.PostToolUse[0];
|
|
53
|
+
assert.equal(postToolUse.matcher, undefined);
|
|
54
|
+
assert.match(String(postToolUse.hooks?.[0]?.command || ""), /codex-native-hook\.js"?$/);
|
|
55
|
+
assert.equal(postToolUse.hooks?.[0]?.statusMessage, "Running OMX tool review");
|
|
29
56
|
const stop = config.hooks.Stop[0];
|
|
30
57
|
assert.equal(stop.hooks?.[0]?.timeout, 30);
|
|
31
58
|
});
|
|
@@ -158,6 +185,118 @@ describe("codex native hook dispatch", () => {
|
|
|
158
185
|
await rm(cwd, { recursive: true, force: true });
|
|
159
186
|
}
|
|
160
187
|
});
|
|
188
|
+
it("does not emit UserPromptSubmit routing context for unknown $tokens", async () => {
|
|
189
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-unknown-token-"));
|
|
190
|
+
try {
|
|
191
|
+
await mkdir(join(cwd, ".omx", "state"), { recursive: true });
|
|
192
|
+
const result = await dispatchCodexNativeHook({
|
|
193
|
+
hook_event_name: "UserPromptSubmit",
|
|
194
|
+
cwd,
|
|
195
|
+
session_id: "sess-unknown-1",
|
|
196
|
+
thread_id: "thread-unknown-1",
|
|
197
|
+
turn_id: "turn-unknown-1",
|
|
198
|
+
prompt: "$maer-thinking 다시 설명해봐",
|
|
199
|
+
}, { cwd });
|
|
200
|
+
assert.equal(result.omxEventName, "keyword-detector");
|
|
201
|
+
assert.equal(result.skillState, null);
|
|
202
|
+
assert.equal(result.outputJson, null);
|
|
203
|
+
assert.equal(existsSync(join(cwd, ".omx", "state", "skill-active-state.json")), false);
|
|
204
|
+
}
|
|
205
|
+
finally {
|
|
206
|
+
await rm(cwd, { recursive: true, force: true });
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
it("nudges $team prompt-submit routing toward omx team runtime usage", async () => {
|
|
210
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-team-"));
|
|
211
|
+
try {
|
|
212
|
+
await mkdir(join(cwd, ".omx", "state"), { recursive: true });
|
|
213
|
+
const result = await dispatchCodexNativeHook({
|
|
214
|
+
hook_event_name: "UserPromptSubmit",
|
|
215
|
+
cwd,
|
|
216
|
+
session_id: "sess-team-1",
|
|
217
|
+
thread_id: "thread-team-1",
|
|
218
|
+
turn_id: "turn-team-1",
|
|
219
|
+
prompt: "$team ship this fix with verification",
|
|
220
|
+
}, { cwd });
|
|
221
|
+
assert.equal(result.omxEventName, "keyword-detector");
|
|
222
|
+
assert.equal(result.skillState?.skill, "team");
|
|
223
|
+
assert.match(JSON.stringify(result.outputJson), /skill: team activated and initial state initialized at \.omx\/state\/team-state\.json; write subsequent updates via omx_state MCP\./);
|
|
224
|
+
assert.match(JSON.stringify(result.outputJson), /Use the durable OMX team runtime via `omx team \.\.\.`/);
|
|
225
|
+
assert.match(JSON.stringify(result.outputJson), /If you need help, run `omx team --help`\./);
|
|
226
|
+
const state = JSON.parse(await readFile(join(cwd, ".omx", "state", "team-state.json"), "utf-8"));
|
|
227
|
+
assert.equal(state.mode, "team");
|
|
228
|
+
assert.equal(state.active, true);
|
|
229
|
+
assert.equal(state.current_phase, "starting");
|
|
230
|
+
}
|
|
231
|
+
finally {
|
|
232
|
+
await rm(cwd, { recursive: true, force: true });
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
it("runs prompt-submit HUD reconciliation as a best-effort tmux-only side effect", async () => {
|
|
236
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-hud-reconcile-"));
|
|
237
|
+
const originalTmux = process.env.TMUX;
|
|
238
|
+
const originalTmuxPane = process.env.TMUX_PANE;
|
|
239
|
+
const originalPath = process.env.PATH;
|
|
240
|
+
const originalArgv = process.argv;
|
|
241
|
+
try {
|
|
242
|
+
process.env.TMUX = "1";
|
|
243
|
+
process.env.TMUX_PANE = "%1";
|
|
244
|
+
await mkdir(join(cwd, ".omx", "state"), { recursive: true });
|
|
245
|
+
await writeFile(join(cwd, ".omx", "hud-config.json"), JSON.stringify({ preset: "focused", git: { display: "branch" } }, null, 2));
|
|
246
|
+
const binDir = await mkdtemp(join(tmpdir(), "omx-native-hook-hud-reconcile-bin-"));
|
|
247
|
+
const tmuxLog = join(cwd, "tmux.log");
|
|
248
|
+
await writeFile(join(binDir, "tmux"), `#!/usr/bin/env bash
|
|
249
|
+
set -euo pipefail
|
|
250
|
+
printf '%s\\n' "$*" >> ${JSON.stringify(tmuxLog)}
|
|
251
|
+
case "$1" in
|
|
252
|
+
list-panes)
|
|
253
|
+
printf '%%1\\tcodex\\tcodex\\n'
|
|
254
|
+
;;
|
|
255
|
+
display-message)
|
|
256
|
+
printf '80\\t24\\n'
|
|
257
|
+
;;
|
|
258
|
+
split-window)
|
|
259
|
+
printf '%%9\\n'
|
|
260
|
+
;;
|
|
261
|
+
resize-pane)
|
|
262
|
+
;;
|
|
263
|
+
esac
|
|
264
|
+
`);
|
|
265
|
+
await chmod(join(binDir, "tmux"), 0o755);
|
|
266
|
+
process.env.PATH = `${binDir}:${originalPath}`;
|
|
267
|
+
process.argv = [originalArgv[0] || 'node', '/tmp/codex-host-binary'];
|
|
268
|
+
const result = await dispatchCodexNativeHook({
|
|
269
|
+
hook_event_name: "UserPromptSubmit",
|
|
270
|
+
cwd,
|
|
271
|
+
session_id: "sess-hud-1",
|
|
272
|
+
prompt: "$ralplan prepare plan",
|
|
273
|
+
}, { cwd });
|
|
274
|
+
assert.equal(result.omxEventName, "keyword-detector");
|
|
275
|
+
const tmuxCalls = await readFile(tmuxLog, "utf-8");
|
|
276
|
+
assert.match(tmuxCalls, /list-panes/);
|
|
277
|
+
assert.match(tmuxCalls, /split-window/);
|
|
278
|
+
assert.match(tmuxCalls, /resize-pane -t %9 -y 3/);
|
|
279
|
+
assert.match(tmuxCalls, /dist\/cli\/omx\.js' hud --watch --preset=focused/);
|
|
280
|
+
assert.doesNotMatch(tmuxCalls, /\/tmp\/codex-host-binary' hud --watch/);
|
|
281
|
+
}
|
|
282
|
+
finally {
|
|
283
|
+
if (originalTmux === undefined) {
|
|
284
|
+
delete process.env.TMUX;
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
process.env.TMUX = originalTmux;
|
|
288
|
+
}
|
|
289
|
+
if (originalTmuxPane === undefined) {
|
|
290
|
+
delete process.env.TMUX_PANE;
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
process.env.TMUX_PANE = originalTmuxPane;
|
|
294
|
+
}
|
|
295
|
+
process.env.PATH = originalPath;
|
|
296
|
+
process.argv = originalArgv;
|
|
297
|
+
await rm(cwd, { recursive: true, force: true });
|
|
298
|
+
}
|
|
299
|
+
});
|
|
161
300
|
it("returns a destructive-command caution on PreToolUse for rm -rf dist", async () => {
|
|
162
301
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-danger-"));
|
|
163
302
|
try {
|
|
@@ -222,6 +361,81 @@ describe("codex native hook dispatch", () => {
|
|
|
222
361
|
await rm(cwd, { recursive: true, force: true });
|
|
223
362
|
}
|
|
224
363
|
});
|
|
364
|
+
it("returns PostToolUse MCP transport fallback guidance for clear MCP transport death", async () => {
|
|
365
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-posttool-mcp-transport-"));
|
|
366
|
+
try {
|
|
367
|
+
const result = await dispatchCodexNativeHook({
|
|
368
|
+
hook_event_name: "PostToolUse",
|
|
369
|
+
cwd,
|
|
370
|
+
tool_name: "mcp__omx_state__state_write",
|
|
371
|
+
tool_use_id: "tool-mcp-transport",
|
|
372
|
+
tool_input: { mode: "team", active: true },
|
|
373
|
+
tool_response: "{\"error\":\"MCP transport closed\",\"details\":\"stdio pipe closed before response\"}",
|
|
374
|
+
}, { cwd });
|
|
375
|
+
assert.equal(result.omxEventName, "post-tool-use");
|
|
376
|
+
const output = result.outputJson;
|
|
377
|
+
assert.equal(output?.decision, "block");
|
|
378
|
+
assert.equal(output?.reason, "The MCP tool appears to have lost its transport/server connection. Preserve state, debug the transport failure, and use OMX CLI/file-backed fallbacks instead of retrying blindly.");
|
|
379
|
+
const additionalContext = String(output?.hookSpecificOutput?.additionalContext ?? "");
|
|
380
|
+
assert.match(additionalContext, /omx state state_write --input/);
|
|
381
|
+
assert.match(additionalContext, /plain Node stdio processes/i);
|
|
382
|
+
assert.match(additionalContext, /read-stall-state/);
|
|
383
|
+
assert.match(additionalContext, /OMX_MCP_TRANSPORT_DEBUG=1/);
|
|
384
|
+
}
|
|
385
|
+
finally {
|
|
386
|
+
await rm(cwd, { recursive: true, force: true });
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
it("does not classify non-transport MCP failures as transport death", async () => {
|
|
390
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-posttool-mcp-nontransport-"));
|
|
391
|
+
try {
|
|
392
|
+
const result = await dispatchCodexNativeHook({
|
|
393
|
+
hook_event_name: "PostToolUse",
|
|
394
|
+
cwd,
|
|
395
|
+
tool_name: "mcp__omx_state__state_write",
|
|
396
|
+
tool_use_id: "tool-mcp-nontransport",
|
|
397
|
+
tool_input: { active: true },
|
|
398
|
+
tool_response: "{\"error\":\"validation failed\",\"details\":\"mode is required\"}",
|
|
399
|
+
}, { cwd });
|
|
400
|
+
assert.equal(result.omxEventName, "post-tool-use");
|
|
401
|
+
assert.equal(result.outputJson, null);
|
|
402
|
+
}
|
|
403
|
+
finally {
|
|
404
|
+
await rm(cwd, { recursive: true, force: true });
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
it("marks active team state failed on MCP transport death without deleting team state", async () => {
|
|
408
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-team-mcp-transport-"));
|
|
409
|
+
const previousCwd = process.cwd();
|
|
410
|
+
try {
|
|
411
|
+
process.chdir(cwd);
|
|
412
|
+
await initTeamState("transport-team", "task", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: "sess-transport" });
|
|
413
|
+
await writeJson(join(cwd, ".omx", "state", "team-state.json"), {
|
|
414
|
+
active: true,
|
|
415
|
+
team_name: "transport-team",
|
|
416
|
+
current_phase: "team-exec",
|
|
417
|
+
});
|
|
418
|
+
await dispatchCodexNativeHook({
|
|
419
|
+
hook_event_name: "PostToolUse",
|
|
420
|
+
cwd,
|
|
421
|
+
session_id: "sess-transport",
|
|
422
|
+
tool_name: "mcp__omx_state__state_write",
|
|
423
|
+
tool_use_id: "tool-mcp-transport-team",
|
|
424
|
+
tool_input: { mode: "team", active: true },
|
|
425
|
+
tool_response: "{\"error\":\"MCP transport closed\",\"details\":\"stdio pipe closed before response\"}",
|
|
426
|
+
}, { cwd });
|
|
427
|
+
const phase = await readTeamPhase("transport-team", cwd);
|
|
428
|
+
const attention = await readTeamLeaderAttention("transport-team", cwd);
|
|
429
|
+
assert.equal(phase?.current_phase, "failed");
|
|
430
|
+
assert.equal(attention?.leader_attention_reason, "mcp_transport_dead");
|
|
431
|
+
assert.equal(attention?.leader_attention_pending, true);
|
|
432
|
+
assert.equal(existsSync(join(cwd, ".omx", "state", "team", "transport-team")), true);
|
|
433
|
+
}
|
|
434
|
+
finally {
|
|
435
|
+
process.chdir(previousCwd);
|
|
436
|
+
await rm(cwd, { recursive: true, force: true });
|
|
437
|
+
}
|
|
438
|
+
});
|
|
225
439
|
it("treats stderr-only informative non-zero output as reviewable instead of a generic failure", async () => {
|
|
226
440
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-posttool-informative-stderr-"));
|
|
227
441
|
try {
|
|
@@ -272,6 +486,39 @@ describe("codex native hook dispatch", () => {
|
|
|
272
486
|
await rm(cwd, { recursive: true, force: true });
|
|
273
487
|
}
|
|
274
488
|
});
|
|
489
|
+
it("returns MCP transport-death guidance and preserves failed team state", async () => {
|
|
490
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-posttool-mcp-dead-"));
|
|
491
|
+
try {
|
|
492
|
+
await initTeamState("mcp-transport-dead-team", "transport failure fallback", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: "sess-mcp-dead" });
|
|
493
|
+
const result = await dispatchCodexNativeHook({
|
|
494
|
+
hook_event_name: "PostToolUse",
|
|
495
|
+
cwd,
|
|
496
|
+
session_id: "sess-mcp-dead",
|
|
497
|
+
tool_name: "mcp__omx_state__state_write",
|
|
498
|
+
tool_use_id: "tool-mcp-dead",
|
|
499
|
+
tool_response: JSON.stringify({
|
|
500
|
+
error: "transport closed",
|
|
501
|
+
message: "MCP server disconnected",
|
|
502
|
+
}),
|
|
503
|
+
}, { cwd });
|
|
504
|
+
assert.equal(result.omxEventName, "post-tool-use");
|
|
505
|
+
assert.equal(result.outputJson?.decision, "block");
|
|
506
|
+
assert.match(String(result.outputJson?.reason || ""), /lost its transport\/server connection/);
|
|
507
|
+
const hookSpecificOutput = result.outputJson?.hookSpecificOutput;
|
|
508
|
+
assert.equal(hookSpecificOutput?.hookEventName, "PostToolUse");
|
|
509
|
+
assert.match(String(hookSpecificOutput?.additionalContext || ""), /Retry via CLI parity with `omx state state_write --input '\{\}' --json`\./);
|
|
510
|
+
assert.match(String(hookSpecificOutput?.additionalContext || ""), /omx team api read-stall-state/);
|
|
511
|
+
const phase = JSON.parse(await readFile(join(cwd, ".omx", "state", "team", "mcp-transport-dead-team", "phase.json"), "utf-8"));
|
|
512
|
+
assert.equal(phase.current_phase, "failed");
|
|
513
|
+
assert.equal(phase.transitions?.at(-1)?.reason, "mcp_transport_dead");
|
|
514
|
+
const attention = JSON.parse(await readFile(join(cwd, ".omx", "state", "team", "mcp-transport-dead-team", "leader-attention.json"), "utf-8"));
|
|
515
|
+
assert.equal(attention.leader_attention_reason, "mcp_transport_dead");
|
|
516
|
+
assert.ok(attention.attention_reasons?.includes("mcp_transport_dead"));
|
|
517
|
+
}
|
|
518
|
+
finally {
|
|
519
|
+
await rm(cwd, { recursive: true, force: true });
|
|
520
|
+
}
|
|
521
|
+
});
|
|
275
522
|
it("stays silent on neutral successful PostToolUse output", async () => {
|
|
276
523
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-posttool-neutral-"));
|
|
277
524
|
try {
|
|
@@ -290,6 +537,45 @@ describe("codex native hook dispatch", () => {
|
|
|
290
537
|
await rm(cwd, { recursive: true, force: true });
|
|
291
538
|
}
|
|
292
539
|
});
|
|
540
|
+
it("returns CLI fallback guidance and preserves failed team state on clear MCP transport death", async () => {
|
|
541
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-posttool-mcp-transport-"));
|
|
542
|
+
try {
|
|
543
|
+
await initTeamState("transport-team", "transport failure fallback", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: "sess-stop-mcp-transport" });
|
|
544
|
+
await writeJson(join(cwd, ".omx", "state", "team-state.json"), {
|
|
545
|
+
active: true,
|
|
546
|
+
team_name: "transport-team",
|
|
547
|
+
current_phase: "team-exec",
|
|
548
|
+
});
|
|
549
|
+
const result = await dispatchCodexNativeHook({
|
|
550
|
+
hook_event_name: "PostToolUse",
|
|
551
|
+
cwd,
|
|
552
|
+
session_id: "sess-stop-mcp-transport",
|
|
553
|
+
tool_name: "mcp__omx_state__state_write",
|
|
554
|
+
tool_use_id: "tool-mcp-fail",
|
|
555
|
+
tool_input: { mode: "team", active: true },
|
|
556
|
+
tool_response: JSON.stringify({
|
|
557
|
+
error: "MCP transport closed unexpectedly",
|
|
558
|
+
exit_code: 1,
|
|
559
|
+
}),
|
|
560
|
+
}, { cwd });
|
|
561
|
+
assert.equal(result.omxEventName, "post-tool-use");
|
|
562
|
+
assert.deepEqual(result.outputJson, {
|
|
563
|
+
decision: "block",
|
|
564
|
+
reason: "The MCP tool appears to have lost its transport/server connection. Preserve state, debug the transport failure, and use OMX CLI/file-backed fallbacks instead of retrying blindly.",
|
|
565
|
+
hookSpecificOutput: {
|
|
566
|
+
hookEventName: "PostToolUse",
|
|
567
|
+
additionalContext: "Clear MCP transport-death signal detected. Preserve current team/runtime state. Retry via CLI parity with `omx state state_write --input '{\"mode\":\"team\",\"active\":true}' --json`. OMX MCP servers are plain Node stdio processes, so they still shut down when stdin/transport closes. If this happened during team runtime, inspect first with `omx team status <team>` or `omx team api read-stall-state --input '{\"team_name\":\"<team>\"}' --json`, and only force cleanup after capturing needed state. For root-cause debugging, rerun with `OMX_MCP_TRANSPORT_DEBUG=1` to log why the stdio transport closed.",
|
|
568
|
+
},
|
|
569
|
+
});
|
|
570
|
+
const phase = await readTeamPhase("transport-team", cwd);
|
|
571
|
+
const attention = await readTeamLeaderAttention("transport-team", cwd);
|
|
572
|
+
assert.equal(phase?.current_phase, "failed");
|
|
573
|
+
assert.equal(attention?.leader_attention_reason, "mcp_transport_dead");
|
|
574
|
+
}
|
|
575
|
+
finally {
|
|
576
|
+
await rm(cwd, { recursive: true, force: true });
|
|
577
|
+
}
|
|
578
|
+
});
|
|
293
579
|
it("returns Stop continuation output while Autopilot is active", async () => {
|
|
294
580
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-autopilot-"));
|
|
295
581
|
try {
|
|
@@ -650,7 +936,7 @@ describe("codex native hook dispatch", () => {
|
|
|
650
936
|
await rm(cwd, { recursive: true, force: true });
|
|
651
937
|
}
|
|
652
938
|
});
|
|
653
|
-
it("returns Stop continuation output for active ralplan skill without active subagents", async () => {
|
|
939
|
+
it("returns Stop continuation output for active ralplan skill with matching active mode state and without active subagents", async () => {
|
|
654
940
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-skill-"));
|
|
655
941
|
try {
|
|
656
942
|
const stateDir = join(cwd, ".omx", "state");
|
|
@@ -661,6 +947,10 @@ describe("codex native hook dispatch", () => {
|
|
|
661
947
|
skill: "ralplan",
|
|
662
948
|
phase: "planning",
|
|
663
949
|
});
|
|
950
|
+
await writeJson(join(stateDir, "sessions", "sess-stop-skill", "ralplan-state.json"), {
|
|
951
|
+
active: true,
|
|
952
|
+
current_phase: "planning",
|
|
953
|
+
});
|
|
664
954
|
const result = await dispatchCodexNativeHook({
|
|
665
955
|
hook_event_name: "Stop",
|
|
666
956
|
cwd,
|
|
@@ -678,6 +968,36 @@ describe("codex native hook dispatch", () => {
|
|
|
678
968
|
await rm(cwd, { recursive: true, force: true });
|
|
679
969
|
}
|
|
680
970
|
});
|
|
971
|
+
it("does not block on stale ralplan skill-active state when the matching mode state is absent", async () => {
|
|
972
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-skill-"));
|
|
973
|
+
try {
|
|
974
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
975
|
+
await mkdir(join(stateDir, "sessions", "sess-stop-stale-skill"), { recursive: true });
|
|
976
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-stop-stale-skill" });
|
|
977
|
+
await writeJson(join(stateDir, "sessions", "sess-stop-stale-skill", "skill-active-state.json"), {
|
|
978
|
+
active: true,
|
|
979
|
+
skill: "ralplan",
|
|
980
|
+
phase: "planning",
|
|
981
|
+
session_id: "sess-stop-stale-skill",
|
|
982
|
+
active_skills: [{
|
|
983
|
+
skill: "ralplan",
|
|
984
|
+
phase: "planning",
|
|
985
|
+
active: true,
|
|
986
|
+
session_id: "sess-stop-stale-skill",
|
|
987
|
+
}],
|
|
988
|
+
});
|
|
989
|
+
const result = await dispatchCodexNativeHook({
|
|
990
|
+
hook_event_name: "Stop",
|
|
991
|
+
cwd,
|
|
992
|
+
session_id: "sess-stop-stale-skill",
|
|
993
|
+
}, { cwd });
|
|
994
|
+
assert.equal(result.omxEventName, "stop");
|
|
995
|
+
assert.equal(result.outputJson, null);
|
|
996
|
+
}
|
|
997
|
+
finally {
|
|
998
|
+
await rm(cwd, { recursive: true, force: true });
|
|
999
|
+
}
|
|
1000
|
+
});
|
|
681
1001
|
it("does not block on active ralplan skill when subagents are still active", async () => {
|
|
682
1002
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-skill-subagent-"));
|
|
683
1003
|
try {
|
|
@@ -689,6 +1009,10 @@ describe("codex native hook dispatch", () => {
|
|
|
689
1009
|
skill: "ralplan",
|
|
690
1010
|
phase: "planning",
|
|
691
1011
|
});
|
|
1012
|
+
await writeJson(join(stateDir, "sessions", "sess-stop-skill-subagent", "ralplan-state.json"), {
|
|
1013
|
+
active: true,
|
|
1014
|
+
current_phase: "planning",
|
|
1015
|
+
});
|
|
692
1016
|
await writeJson(join(stateDir, "subagent-tracking.json"), {
|
|
693
1017
|
schemaVersion: 1,
|
|
694
1018
|
sessions: {
|
|
@@ -727,7 +1051,30 @@ describe("codex native hook dispatch", () => {
|
|
|
727
1051
|
await rm(cwd, { recursive: true, force: true });
|
|
728
1052
|
}
|
|
729
1053
|
});
|
|
730
|
-
it("
|
|
1054
|
+
it("does not block on stale root ralplan skill when the explicit session-scoped canonical skill state is absent", async () => {
|
|
1055
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-root-skill-"));
|
|
1056
|
+
try {
|
|
1057
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
1058
|
+
await mkdir(stateDir, { recursive: true });
|
|
1059
|
+
await writeJson(join(stateDir, "skill-active-state.json"), {
|
|
1060
|
+
active: true,
|
|
1061
|
+
skill: "ralplan",
|
|
1062
|
+
phase: "planning",
|
|
1063
|
+
});
|
|
1064
|
+
const result = await dispatchCodexNativeHook({
|
|
1065
|
+
hook_event_name: "Stop",
|
|
1066
|
+
cwd,
|
|
1067
|
+
session_id: "sess-stop-stale-root-skill",
|
|
1068
|
+
thread_id: "thread-stop-stale-root-skill",
|
|
1069
|
+
}, { cwd });
|
|
1070
|
+
assert.equal(result.omxEventName, "stop");
|
|
1071
|
+
assert.equal(result.outputJson, null);
|
|
1072
|
+
}
|
|
1073
|
+
finally {
|
|
1074
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1075
|
+
}
|
|
1076
|
+
});
|
|
1077
|
+
it("returns Stop continuation output for active deep-interview skill with matching active mode state and without active subagents", async () => {
|
|
731
1078
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-deep-interview-"));
|
|
732
1079
|
try {
|
|
733
1080
|
const stateDir = join(cwd, ".omx", "state");
|
|
@@ -738,6 +1085,10 @@ describe("codex native hook dispatch", () => {
|
|
|
738
1085
|
skill: "deep-interview",
|
|
739
1086
|
phase: "planning",
|
|
740
1087
|
});
|
|
1088
|
+
await writeJson(join(stateDir, "sessions", "sess-stop-deep-interview", "deep-interview-state.json"), {
|
|
1089
|
+
active: true,
|
|
1090
|
+
current_phase: "planning",
|
|
1091
|
+
});
|
|
741
1092
|
const result = await dispatchCodexNativeHook({
|
|
742
1093
|
hook_event_name: "Stop",
|
|
743
1094
|
cwd,
|
|
@@ -804,6 +1155,30 @@ describe("codex native hook dispatch", () => {
|
|
|
804
1155
|
await rm(cwd, { recursive: true, force: true });
|
|
805
1156
|
}
|
|
806
1157
|
});
|
|
1158
|
+
it("does not block Stop from stale session-scoped Ralph state that belongs to another session", async () => {
|
|
1159
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-session-ralph-"));
|
|
1160
|
+
try {
|
|
1161
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
1162
|
+
await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
|
|
1163
|
+
await mkdir(join(stateDir, "sessions", "sess-stale"), { recursive: true });
|
|
1164
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
|
|
1165
|
+
await writeJson(join(stateDir, "sessions", "sess-stale", "ralph-state.json"), {
|
|
1166
|
+
active: true,
|
|
1167
|
+
current_phase: "starting",
|
|
1168
|
+
session_id: "sess-stale",
|
|
1169
|
+
});
|
|
1170
|
+
const result = await dispatchCodexNativeHook({
|
|
1171
|
+
hook_event_name: "Stop",
|
|
1172
|
+
cwd,
|
|
1173
|
+
session_id: "sess-current",
|
|
1174
|
+
}, { cwd });
|
|
1175
|
+
assert.equal(result.omxEventName, "stop");
|
|
1176
|
+
assert.equal(result.outputJson, null);
|
|
1177
|
+
}
|
|
1178
|
+
finally {
|
|
1179
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1180
|
+
}
|
|
1181
|
+
});
|
|
807
1182
|
it("does not re-block Ralph when Stop already continued once", async () => {
|
|
808
1183
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ralph-once-"));
|
|
809
1184
|
try {
|
|
@@ -930,6 +1305,10 @@ describe("codex native hook dispatch", () => {
|
|
|
930
1305
|
message: "Deep interview is active; auto-approval shortcuts are blocked until the interview finishes.",
|
|
931
1306
|
},
|
|
932
1307
|
});
|
|
1308
|
+
await writeJson(join(stateDir, "sessions", "sess-stop-auto-lock", "deep-interview-state.json"), {
|
|
1309
|
+
active: true,
|
|
1310
|
+
current_phase: "planning",
|
|
1311
|
+
});
|
|
933
1312
|
const result = await dispatchCodexNativeHook({
|
|
934
1313
|
hook_event_name: "Stop",
|
|
935
1314
|
cwd,
|
|
@@ -977,7 +1356,7 @@ describe("codex native hook dispatch", () => {
|
|
|
977
1356
|
await rm(cwd, { recursive: true, force: true });
|
|
978
1357
|
}
|
|
979
1358
|
});
|
|
980
|
-
it("suppresses native auto-nudge when deep-interview mode state is active without
|
|
1359
|
+
it("suppresses native auto-nudge when root deep-interview mode state is active without an explicit session", async () => {
|
|
981
1360
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-auto-nudge-deep-interview-mode-"));
|
|
982
1361
|
try {
|
|
983
1362
|
const stateDir = join(cwd, ".omx", "state");
|
|
@@ -990,8 +1369,6 @@ describe("codex native hook dispatch", () => {
|
|
|
990
1369
|
const result = await dispatchCodexNativeHook({
|
|
991
1370
|
hook_event_name: "Stop",
|
|
992
1371
|
cwd,
|
|
993
|
-
session_id: "sess-stop-auto-mode",
|
|
994
|
-
thread_id: "thread-stop-auto-mode",
|
|
995
1372
|
turn_id: "turn-stop-auto-mode-1",
|
|
996
1373
|
last_assistant_message: "Would you like me to continue with the next step?",
|
|
997
1374
|
}, { cwd });
|
|
@@ -1002,6 +1379,102 @@ describe("codex native hook dispatch", () => {
|
|
|
1002
1379
|
await rm(cwd, { recursive: true, force: true });
|
|
1003
1380
|
}
|
|
1004
1381
|
});
|
|
1382
|
+
it("does not suppress native auto-nudge from stale root deep-interview mode state when the explicit session-scoped mode state is absent", async () => {
|
|
1383
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-auto-nudge-stale-root-mode-"));
|
|
1384
|
+
try {
|
|
1385
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
1386
|
+
await mkdir(stateDir, { recursive: true });
|
|
1387
|
+
await writeJson(join(stateDir, "deep-interview-state.json"), {
|
|
1388
|
+
active: true,
|
|
1389
|
+
mode: "deep-interview",
|
|
1390
|
+
current_phase: "intent-first",
|
|
1391
|
+
});
|
|
1392
|
+
const result = await dispatchCodexNativeHook({
|
|
1393
|
+
hook_event_name: "Stop",
|
|
1394
|
+
cwd,
|
|
1395
|
+
session_id: "sess-stop-auto-stale-root-mode",
|
|
1396
|
+
thread_id: "thread-stop-auto-stale-root-mode",
|
|
1397
|
+
turn_id: "turn-stop-auto-stale-root-mode-1",
|
|
1398
|
+
last_assistant_message: "Would you like me to continue with the next step?",
|
|
1399
|
+
}, { cwd });
|
|
1400
|
+
assert.equal(result.omxEventName, "stop");
|
|
1401
|
+
assert.deepEqual(result.outputJson, {
|
|
1402
|
+
decision: "block",
|
|
1403
|
+
reason: "yes, proceed",
|
|
1404
|
+
stopReason: "auto_nudge",
|
|
1405
|
+
systemMessage: "OMX native Stop detected a stall/permission-style handoff and continued the turn automatically.",
|
|
1406
|
+
});
|
|
1407
|
+
}
|
|
1408
|
+
finally {
|
|
1409
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1410
|
+
}
|
|
1411
|
+
});
|
|
1412
|
+
it("does not suppress native auto-nudge from stale root deep-interview skill state when the explicit session-scoped canonical skill state is absent", async () => {
|
|
1413
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-auto-nudge-stale-root-skill-"));
|
|
1414
|
+
try {
|
|
1415
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
1416
|
+
await mkdir(stateDir, { recursive: true });
|
|
1417
|
+
await writeJson(join(stateDir, "skill-active-state.json"), {
|
|
1418
|
+
active: true,
|
|
1419
|
+
skill: "deep-interview",
|
|
1420
|
+
phase: "planning",
|
|
1421
|
+
});
|
|
1422
|
+
const result = await dispatchCodexNativeHook({
|
|
1423
|
+
hook_event_name: "Stop",
|
|
1424
|
+
cwd,
|
|
1425
|
+
session_id: "sess-stop-auto-stale-root-skill",
|
|
1426
|
+
thread_id: "thread-stop-auto-stale-root-skill",
|
|
1427
|
+
turn_id: "turn-stop-auto-stale-root-skill-1",
|
|
1428
|
+
last_assistant_message: "Would you like me to continue with the next step?",
|
|
1429
|
+
}, { cwd });
|
|
1430
|
+
assert.equal(result.omxEventName, "stop");
|
|
1431
|
+
assert.deepEqual(result.outputJson, {
|
|
1432
|
+
decision: "block",
|
|
1433
|
+
reason: "yes, proceed",
|
|
1434
|
+
stopReason: "auto_nudge",
|
|
1435
|
+
systemMessage: "OMX native Stop detected a stall/permission-style handoff and continued the turn automatically.",
|
|
1436
|
+
});
|
|
1437
|
+
}
|
|
1438
|
+
finally {
|
|
1439
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1440
|
+
}
|
|
1441
|
+
});
|
|
1442
|
+
it("does not suppress native auto-nudge from stale root deep-interview input lock when the explicit session-scoped canonical skill state is absent", async () => {
|
|
1443
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-auto-nudge-stale-root-lock-"));
|
|
1444
|
+
try {
|
|
1445
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
1446
|
+
await mkdir(stateDir, { recursive: true });
|
|
1447
|
+
await writeJson(join(stateDir, "skill-active-state.json"), {
|
|
1448
|
+
active: true,
|
|
1449
|
+
skill: "deep-interview",
|
|
1450
|
+
phase: "planning",
|
|
1451
|
+
input_lock: {
|
|
1452
|
+
active: true,
|
|
1453
|
+
scope: "deep-interview-auto-approval",
|
|
1454
|
+
blocked_inputs: ["yes", "proceed"],
|
|
1455
|
+
message: "Deep interview is active; auto-approval shortcuts are blocked until the interview finishes.",
|
|
1456
|
+
},
|
|
1457
|
+
});
|
|
1458
|
+
const result = await dispatchCodexNativeHook({
|
|
1459
|
+
hook_event_name: "Stop",
|
|
1460
|
+
cwd,
|
|
1461
|
+
session_id: "sess-stop-auto-stale-root-lock",
|
|
1462
|
+
thread_id: "thread-stop-auto-stale-root-lock",
|
|
1463
|
+
turn_id: "turn-stop-auto-stale-root-lock-1",
|
|
1464
|
+
last_assistant_message: "Would you like me to continue with the next step?",
|
|
1465
|
+
}, { cwd });
|
|
1466
|
+
assert.equal(result.omxEventName, "stop");
|
|
1467
|
+
assert.deepEqual(result.outputJson, {
|
|
1468
|
+
decision: "block",
|
|
1469
|
+
reason: "yes, proceed",
|
|
1470
|
+
stopReason: "auto_nudge",
|
|
1471
|
+
systemMessage: "OMX native Stop detected a stall/permission-style handoff and continued the turn automatically.",
|
|
1472
|
+
});
|
|
1473
|
+
}
|
|
1474
|
+
finally {
|
|
1475
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1476
|
+
}
|
|
1477
|
+
});
|
|
1005
1478
|
it("re-fires team Stop output for a later fresh Stop reply while the team is still active", async () => {
|
|
1006
1479
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-refire-"));
|
|
1007
1480
|
try {
|