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,12 +1,16 @@
|
|
|
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 {
|
|
9
|
+
import {
|
|
10
|
+
initTeamState,
|
|
11
|
+
readTeamLeaderAttention,
|
|
12
|
+
readTeamPhase,
|
|
13
|
+
} from "../../team/state.js";
|
|
10
14
|
import {
|
|
11
15
|
dispatchCodexNativeHook,
|
|
12
16
|
mapCodexHookEventToOmxEvent,
|
|
@@ -21,6 +25,31 @@ async function writeJson(path: string, value: unknown): Promise<void> {
|
|
|
21
25
|
const TEAM_STOP_COMMIT_GUIDANCE =
|
|
22
26
|
" If system-generated worker auto-checkpoint commits exist, rewrite them into Lore-format final commits before merge/finalization.";
|
|
23
27
|
|
|
28
|
+
const TEAM_ENV_KEYS = [
|
|
29
|
+
"OMX_TEAM_WORKER",
|
|
30
|
+
"OMX_TEAM_STATE_ROOT",
|
|
31
|
+
"OMX_TEAM_LEADER_CWD",
|
|
32
|
+
] as const;
|
|
33
|
+
|
|
34
|
+
const priorTeamEnv = new Map<(typeof TEAM_ENV_KEYS)[number], string | undefined>();
|
|
35
|
+
|
|
36
|
+
beforeEach(() => {
|
|
37
|
+
priorTeamEnv.clear();
|
|
38
|
+
for (const key of TEAM_ENV_KEYS) {
|
|
39
|
+
priorTeamEnv.set(key, process.env[key]);
|
|
40
|
+
delete process.env[key];
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
afterEach(() => {
|
|
45
|
+
for (const key of TEAM_ENV_KEYS) {
|
|
46
|
+
const value = priorTeamEnv.get(key);
|
|
47
|
+
if (typeof value === "string") process.env[key] = value;
|
|
48
|
+
else delete process.env[key];
|
|
49
|
+
}
|
|
50
|
+
priorTeamEnv.clear();
|
|
51
|
+
});
|
|
52
|
+
|
|
24
53
|
describe("codex native hook config", () => {
|
|
25
54
|
it("builds the expected managed hooks.json shape", () => {
|
|
26
55
|
const config = buildManagedCodexHooksConfig("/tmp/omx");
|
|
@@ -42,6 +71,17 @@ describe("codex native hook config", () => {
|
|
|
42
71
|
/codex-native-hook\.js"?$/,
|
|
43
72
|
);
|
|
44
73
|
|
|
74
|
+
const postToolUse = config.hooks.PostToolUse[0] as {
|
|
75
|
+
matcher?: string;
|
|
76
|
+
hooks?: Array<Record<string, unknown>>;
|
|
77
|
+
};
|
|
78
|
+
assert.equal(postToolUse.matcher, undefined);
|
|
79
|
+
assert.match(
|
|
80
|
+
String(postToolUse.hooks?.[0]?.command || ""),
|
|
81
|
+
/codex-native-hook\.js"?$/,
|
|
82
|
+
);
|
|
83
|
+
assert.equal(postToolUse.hooks?.[0]?.statusMessage, "Running OMX tool review");
|
|
84
|
+
|
|
45
85
|
const stop = config.hooks.Stop[0] as {
|
|
46
86
|
hooks?: Array<Record<string, unknown>>;
|
|
47
87
|
};
|
|
@@ -210,6 +250,142 @@ describe("codex native hook dispatch", () => {
|
|
|
210
250
|
}
|
|
211
251
|
});
|
|
212
252
|
|
|
253
|
+
it("does not emit UserPromptSubmit routing context for unknown $tokens", async () => {
|
|
254
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-unknown-token-"));
|
|
255
|
+
try {
|
|
256
|
+
await mkdir(join(cwd, ".omx", "state"), { recursive: true });
|
|
257
|
+
const result = await dispatchCodexNativeHook(
|
|
258
|
+
{
|
|
259
|
+
hook_event_name: "UserPromptSubmit",
|
|
260
|
+
cwd,
|
|
261
|
+
session_id: "sess-unknown-1",
|
|
262
|
+
thread_id: "thread-unknown-1",
|
|
263
|
+
turn_id: "turn-unknown-1",
|
|
264
|
+
prompt: "$maer-thinking 다시 설명해봐",
|
|
265
|
+
},
|
|
266
|
+
{ cwd },
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
assert.equal(result.omxEventName, "keyword-detector");
|
|
270
|
+
assert.equal(result.skillState, null);
|
|
271
|
+
assert.equal(result.outputJson, null);
|
|
272
|
+
assert.equal(existsSync(join(cwd, ".omx", "state", "skill-active-state.json")), false);
|
|
273
|
+
} finally {
|
|
274
|
+
await rm(cwd, { recursive: true, force: true });
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it("nudges $team prompt-submit routing toward omx team runtime usage", async () => {
|
|
279
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-team-"));
|
|
280
|
+
try {
|
|
281
|
+
await mkdir(join(cwd, ".omx", "state"), { recursive: true });
|
|
282
|
+
const result = await dispatchCodexNativeHook(
|
|
283
|
+
{
|
|
284
|
+
hook_event_name: "UserPromptSubmit",
|
|
285
|
+
cwd,
|
|
286
|
+
session_id: "sess-team-1",
|
|
287
|
+
thread_id: "thread-team-1",
|
|
288
|
+
turn_id: "turn-team-1",
|
|
289
|
+
prompt: "$team ship this fix with verification",
|
|
290
|
+
},
|
|
291
|
+
{ cwd },
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
assert.equal(result.omxEventName, "keyword-detector");
|
|
295
|
+
assert.equal(result.skillState?.skill, "team");
|
|
296
|
+
assert.match(
|
|
297
|
+
JSON.stringify(result.outputJson),
|
|
298
|
+
/skill: team activated and initial state initialized at \.omx\/state\/team-state\.json; write subsequent updates via omx_state MCP\./,
|
|
299
|
+
);
|
|
300
|
+
assert.match(JSON.stringify(result.outputJson), /Use the durable OMX team runtime via `omx team \.\.\.`/);
|
|
301
|
+
assert.match(JSON.stringify(result.outputJson), /If you need help, run `omx team --help`\./);
|
|
302
|
+
|
|
303
|
+
const state = JSON.parse(
|
|
304
|
+
await readFile(join(cwd, ".omx", "state", "team-state.json"), "utf-8"),
|
|
305
|
+
) as { mode?: string; active?: boolean; current_phase?: string };
|
|
306
|
+
assert.equal(state.mode, "team");
|
|
307
|
+
assert.equal(state.active, true);
|
|
308
|
+
assert.equal(state.current_phase, "starting");
|
|
309
|
+
} finally {
|
|
310
|
+
await rm(cwd, { recursive: true, force: true });
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it("runs prompt-submit HUD reconciliation as a best-effort tmux-only side effect", async () => {
|
|
315
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-hud-reconcile-"));
|
|
316
|
+
const originalTmux = process.env.TMUX;
|
|
317
|
+
const originalTmuxPane = process.env.TMUX_PANE;
|
|
318
|
+
const originalPath = process.env.PATH;
|
|
319
|
+
const originalArgv = process.argv;
|
|
320
|
+
try {
|
|
321
|
+
process.env.TMUX = "1";
|
|
322
|
+
process.env.TMUX_PANE = "%1";
|
|
323
|
+
await mkdir(join(cwd, ".omx", "state"), { recursive: true });
|
|
324
|
+
await writeFile(
|
|
325
|
+
join(cwd, ".omx", "hud-config.json"),
|
|
326
|
+
JSON.stringify({ preset: "focused", git: { display: "branch" } }, null, 2),
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
const binDir = await mkdtemp(join(tmpdir(), "omx-native-hook-hud-reconcile-bin-"));
|
|
330
|
+
const tmuxLog = join(cwd, "tmux.log");
|
|
331
|
+
await writeFile(
|
|
332
|
+
join(binDir, "tmux"),
|
|
333
|
+
`#!/usr/bin/env bash
|
|
334
|
+
set -euo pipefail
|
|
335
|
+
printf '%s\\n' "$*" >> ${JSON.stringify(tmuxLog)}
|
|
336
|
+
case "$1" in
|
|
337
|
+
list-panes)
|
|
338
|
+
printf '%%1\\tcodex\\tcodex\\n'
|
|
339
|
+
;;
|
|
340
|
+
display-message)
|
|
341
|
+
printf '80\\t24\\n'
|
|
342
|
+
;;
|
|
343
|
+
split-window)
|
|
344
|
+
printf '%%9\\n'
|
|
345
|
+
;;
|
|
346
|
+
resize-pane)
|
|
347
|
+
;;
|
|
348
|
+
esac
|
|
349
|
+
`,
|
|
350
|
+
);
|
|
351
|
+
await chmod(join(binDir, "tmux"), 0o755);
|
|
352
|
+
process.env.PATH = `${binDir}:${originalPath}`;
|
|
353
|
+
process.argv = [originalArgv[0] || 'node', '/tmp/codex-host-binary'];
|
|
354
|
+
|
|
355
|
+
const result = await dispatchCodexNativeHook(
|
|
356
|
+
{
|
|
357
|
+
hook_event_name: "UserPromptSubmit",
|
|
358
|
+
cwd,
|
|
359
|
+
session_id: "sess-hud-1",
|
|
360
|
+
prompt: "$ralplan prepare plan",
|
|
361
|
+
},
|
|
362
|
+
{ cwd },
|
|
363
|
+
);
|
|
364
|
+
|
|
365
|
+
assert.equal(result.omxEventName, "keyword-detector");
|
|
366
|
+
const tmuxCalls = await readFile(tmuxLog, "utf-8");
|
|
367
|
+
assert.match(tmuxCalls, /list-panes/);
|
|
368
|
+
assert.match(tmuxCalls, /split-window/);
|
|
369
|
+
assert.match(tmuxCalls, /resize-pane -t %9 -y 3/);
|
|
370
|
+
assert.match(tmuxCalls, /dist\/cli\/omx\.js' hud --watch --preset=focused/);
|
|
371
|
+
assert.doesNotMatch(tmuxCalls, /\/tmp\/codex-host-binary' hud --watch/);
|
|
372
|
+
} finally {
|
|
373
|
+
if (originalTmux === undefined) {
|
|
374
|
+
delete process.env.TMUX;
|
|
375
|
+
} else {
|
|
376
|
+
process.env.TMUX = originalTmux;
|
|
377
|
+
}
|
|
378
|
+
if (originalTmuxPane === undefined) {
|
|
379
|
+
delete process.env.TMUX_PANE;
|
|
380
|
+
} else {
|
|
381
|
+
process.env.TMUX_PANE = originalTmuxPane;
|
|
382
|
+
}
|
|
383
|
+
process.env.PATH = originalPath;
|
|
384
|
+
process.argv = originalArgv;
|
|
385
|
+
await rm(cwd, { recursive: true, force: true });
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
|
|
213
389
|
it("returns a destructive-command caution on PreToolUse for rm -rf dist", async () => {
|
|
214
390
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-danger-"));
|
|
215
391
|
try {
|
|
@@ -288,6 +464,123 @@ describe("codex native hook dispatch", () => {
|
|
|
288
464
|
}
|
|
289
465
|
});
|
|
290
466
|
|
|
467
|
+
it("returns PostToolUse MCP transport fallback guidance for clear MCP transport death", async () => {
|
|
468
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-posttool-mcp-transport-"));
|
|
469
|
+
try {
|
|
470
|
+
const result = await dispatchCodexNativeHook(
|
|
471
|
+
{
|
|
472
|
+
hook_event_name: "PostToolUse",
|
|
473
|
+
cwd,
|
|
474
|
+
tool_name: "mcp__omx_state__state_write",
|
|
475
|
+
tool_use_id: "tool-mcp-transport",
|
|
476
|
+
tool_input: { mode: "team", active: true },
|
|
477
|
+
tool_response: "{\"error\":\"MCP transport closed\",\"details\":\"stdio pipe closed before response\"}",
|
|
478
|
+
},
|
|
479
|
+
{ cwd },
|
|
480
|
+
);
|
|
481
|
+
|
|
482
|
+
assert.equal(result.omxEventName, "post-tool-use");
|
|
483
|
+
const output = result.outputJson as {
|
|
484
|
+
decision?: string;
|
|
485
|
+
reason?: string;
|
|
486
|
+
hookSpecificOutput?: { additionalContext?: string };
|
|
487
|
+
} | null;
|
|
488
|
+
assert.equal(output?.decision, "block");
|
|
489
|
+
assert.equal(
|
|
490
|
+
output?.reason,
|
|
491
|
+
"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.",
|
|
492
|
+
);
|
|
493
|
+
const additionalContext = String(
|
|
494
|
+
output?.hookSpecificOutput?.additionalContext ?? "",
|
|
495
|
+
);
|
|
496
|
+
assert.match(
|
|
497
|
+
additionalContext,
|
|
498
|
+
/omx state state_write --input/,
|
|
499
|
+
);
|
|
500
|
+
assert.match(
|
|
501
|
+
additionalContext,
|
|
502
|
+
/plain Node stdio processes/i,
|
|
503
|
+
);
|
|
504
|
+
assert.match(
|
|
505
|
+
additionalContext,
|
|
506
|
+
/read-stall-state/,
|
|
507
|
+
);
|
|
508
|
+
assert.match(
|
|
509
|
+
additionalContext,
|
|
510
|
+
/OMX_MCP_TRANSPORT_DEBUG=1/,
|
|
511
|
+
);
|
|
512
|
+
} finally {
|
|
513
|
+
await rm(cwd, { recursive: true, force: true });
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
it("does not classify non-transport MCP failures as transport death", async () => {
|
|
518
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-posttool-mcp-nontransport-"));
|
|
519
|
+
try {
|
|
520
|
+
const result = await dispatchCodexNativeHook(
|
|
521
|
+
{
|
|
522
|
+
hook_event_name: "PostToolUse",
|
|
523
|
+
cwd,
|
|
524
|
+
tool_name: "mcp__omx_state__state_write",
|
|
525
|
+
tool_use_id: "tool-mcp-nontransport",
|
|
526
|
+
tool_input: { active: true },
|
|
527
|
+
tool_response: "{\"error\":\"validation failed\",\"details\":\"mode is required\"}",
|
|
528
|
+
},
|
|
529
|
+
{ cwd },
|
|
530
|
+
);
|
|
531
|
+
|
|
532
|
+
assert.equal(result.omxEventName, "post-tool-use");
|
|
533
|
+
assert.equal(result.outputJson, null);
|
|
534
|
+
} finally {
|
|
535
|
+
await rm(cwd, { recursive: true, force: true });
|
|
536
|
+
}
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
it("marks active team state failed on MCP transport death without deleting team state", async () => {
|
|
540
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-team-mcp-transport-"));
|
|
541
|
+
const previousCwd = process.cwd();
|
|
542
|
+
try {
|
|
543
|
+
process.chdir(cwd);
|
|
544
|
+
await initTeamState(
|
|
545
|
+
"transport-team",
|
|
546
|
+
"task",
|
|
547
|
+
"executor",
|
|
548
|
+
1,
|
|
549
|
+
cwd,
|
|
550
|
+
undefined,
|
|
551
|
+
{ ...process.env, OMX_SESSION_ID: "sess-transport" },
|
|
552
|
+
);
|
|
553
|
+
await writeJson(join(cwd, ".omx", "state", "team-state.json"), {
|
|
554
|
+
active: true,
|
|
555
|
+
team_name: "transport-team",
|
|
556
|
+
current_phase: "team-exec",
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
await dispatchCodexNativeHook(
|
|
560
|
+
{
|
|
561
|
+
hook_event_name: "PostToolUse",
|
|
562
|
+
cwd,
|
|
563
|
+
session_id: "sess-transport",
|
|
564
|
+
tool_name: "mcp__omx_state__state_write",
|
|
565
|
+
tool_use_id: "tool-mcp-transport-team",
|
|
566
|
+
tool_input: { mode: "team", active: true },
|
|
567
|
+
tool_response: "{\"error\":\"MCP transport closed\",\"details\":\"stdio pipe closed before response\"}",
|
|
568
|
+
},
|
|
569
|
+
{ cwd },
|
|
570
|
+
);
|
|
571
|
+
|
|
572
|
+
const phase = await readTeamPhase("transport-team", cwd);
|
|
573
|
+
const attention = await readTeamLeaderAttention("transport-team", cwd);
|
|
574
|
+
assert.equal(phase?.current_phase, "failed");
|
|
575
|
+
assert.equal(attention?.leader_attention_reason, "mcp_transport_dead");
|
|
576
|
+
assert.equal(attention?.leader_attention_pending, true);
|
|
577
|
+
assert.equal(existsSync(join(cwd, ".omx", "state", "team", "transport-team")), true);
|
|
578
|
+
} finally {
|
|
579
|
+
process.chdir(previousCwd);
|
|
580
|
+
await rm(cwd, { recursive: true, force: true });
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
|
|
291
584
|
it("treats stderr-only informative non-zero output as reviewable instead of a generic failure", async () => {
|
|
292
585
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-posttool-informative-stderr-"));
|
|
293
586
|
try {
|
|
@@ -348,6 +641,67 @@ describe("codex native hook dispatch", () => {
|
|
|
348
641
|
}
|
|
349
642
|
});
|
|
350
643
|
|
|
644
|
+
it("returns MCP transport-death guidance and preserves failed team state", async () => {
|
|
645
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-posttool-mcp-dead-"));
|
|
646
|
+
try {
|
|
647
|
+
await initTeamState(
|
|
648
|
+
"mcp-transport-dead-team",
|
|
649
|
+
"transport failure fallback",
|
|
650
|
+
"executor",
|
|
651
|
+
1,
|
|
652
|
+
cwd,
|
|
653
|
+
undefined,
|
|
654
|
+
{ ...process.env, OMX_SESSION_ID: "sess-mcp-dead" },
|
|
655
|
+
);
|
|
656
|
+
|
|
657
|
+
const result = await dispatchCodexNativeHook(
|
|
658
|
+
{
|
|
659
|
+
hook_event_name: "PostToolUse",
|
|
660
|
+
cwd,
|
|
661
|
+
session_id: "sess-mcp-dead",
|
|
662
|
+
tool_name: "mcp__omx_state__state_write",
|
|
663
|
+
tool_use_id: "tool-mcp-dead",
|
|
664
|
+
tool_response: JSON.stringify({
|
|
665
|
+
error: "transport closed",
|
|
666
|
+
message: "MCP server disconnected",
|
|
667
|
+
}),
|
|
668
|
+
},
|
|
669
|
+
{ cwd },
|
|
670
|
+
);
|
|
671
|
+
|
|
672
|
+
assert.equal(result.omxEventName, "post-tool-use");
|
|
673
|
+
assert.equal(result.outputJson?.decision, "block");
|
|
674
|
+
assert.match(String(result.outputJson?.reason || ""), /lost its transport\/server connection/);
|
|
675
|
+
const hookSpecificOutput = result.outputJson?.hookSpecificOutput as {
|
|
676
|
+
hookEventName?: string;
|
|
677
|
+
additionalContext?: string;
|
|
678
|
+
} | undefined;
|
|
679
|
+
assert.equal(hookSpecificOutput?.hookEventName, "PostToolUse");
|
|
680
|
+
assert.match(
|
|
681
|
+
String(hookSpecificOutput?.additionalContext || ""),
|
|
682
|
+
/Retry via CLI parity with `omx state state_write --input '\{\}' --json`\./,
|
|
683
|
+
);
|
|
684
|
+
assert.match(
|
|
685
|
+
String(hookSpecificOutput?.additionalContext || ""),
|
|
686
|
+
/omx team api read-stall-state/,
|
|
687
|
+
);
|
|
688
|
+
|
|
689
|
+
const phase = JSON.parse(
|
|
690
|
+
await readFile(join(cwd, ".omx", "state", "team", "mcp-transport-dead-team", "phase.json"), "utf-8"),
|
|
691
|
+
) as { current_phase?: string; transitions?: Array<{ reason?: string }> };
|
|
692
|
+
assert.equal(phase.current_phase, "failed");
|
|
693
|
+
assert.equal(phase.transitions?.at(-1)?.reason, "mcp_transport_dead");
|
|
694
|
+
|
|
695
|
+
const attention = JSON.parse(
|
|
696
|
+
await readFile(join(cwd, ".omx", "state", "team", "mcp-transport-dead-team", "leader-attention.json"), "utf-8"),
|
|
697
|
+
) as { leader_attention_reason?: string; attention_reasons?: string[] };
|
|
698
|
+
assert.equal(attention.leader_attention_reason, "mcp_transport_dead");
|
|
699
|
+
assert.ok(attention.attention_reasons?.includes("mcp_transport_dead"));
|
|
700
|
+
} finally {
|
|
701
|
+
await rm(cwd, { recursive: true, force: true });
|
|
702
|
+
}
|
|
703
|
+
});
|
|
704
|
+
|
|
351
705
|
it("stays silent on neutral successful PostToolUse output", async () => {
|
|
352
706
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-posttool-neutral-"));
|
|
353
707
|
try {
|
|
@@ -370,6 +724,60 @@ describe("codex native hook dispatch", () => {
|
|
|
370
724
|
}
|
|
371
725
|
});
|
|
372
726
|
|
|
727
|
+
it("returns CLI fallback guidance and preserves failed team state on clear MCP transport death", async () => {
|
|
728
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-posttool-mcp-transport-"));
|
|
729
|
+
try {
|
|
730
|
+
await initTeamState(
|
|
731
|
+
"transport-team",
|
|
732
|
+
"transport failure fallback",
|
|
733
|
+
"executor",
|
|
734
|
+
1,
|
|
735
|
+
cwd,
|
|
736
|
+
undefined,
|
|
737
|
+
{ ...process.env, OMX_SESSION_ID: "sess-stop-mcp-transport" },
|
|
738
|
+
);
|
|
739
|
+
await writeJson(join(cwd, ".omx", "state", "team-state.json"), {
|
|
740
|
+
active: true,
|
|
741
|
+
team_name: "transport-team",
|
|
742
|
+
current_phase: "team-exec",
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
const result = await dispatchCodexNativeHook(
|
|
746
|
+
{
|
|
747
|
+
hook_event_name: "PostToolUse",
|
|
748
|
+
cwd,
|
|
749
|
+
session_id: "sess-stop-mcp-transport",
|
|
750
|
+
tool_name: "mcp__omx_state__state_write",
|
|
751
|
+
tool_use_id: "tool-mcp-fail",
|
|
752
|
+
tool_input: { mode: "team", active: true },
|
|
753
|
+
tool_response: JSON.stringify({
|
|
754
|
+
error: "MCP transport closed unexpectedly",
|
|
755
|
+
exit_code: 1,
|
|
756
|
+
}),
|
|
757
|
+
},
|
|
758
|
+
{ cwd },
|
|
759
|
+
);
|
|
760
|
+
|
|
761
|
+
assert.equal(result.omxEventName, "post-tool-use");
|
|
762
|
+
assert.deepEqual(result.outputJson, {
|
|
763
|
+
decision: "block",
|
|
764
|
+
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.",
|
|
765
|
+
hookSpecificOutput: {
|
|
766
|
+
hookEventName: "PostToolUse",
|
|
767
|
+
additionalContext:
|
|
768
|
+
"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.",
|
|
769
|
+
},
|
|
770
|
+
});
|
|
771
|
+
|
|
772
|
+
const phase = await readTeamPhase("transport-team", cwd);
|
|
773
|
+
const attention = await readTeamLeaderAttention("transport-team", cwd);
|
|
774
|
+
assert.equal(phase?.current_phase, "failed");
|
|
775
|
+
assert.equal(attention?.leader_attention_reason, "mcp_transport_dead");
|
|
776
|
+
} finally {
|
|
777
|
+
await rm(cwd, { recursive: true, force: true });
|
|
778
|
+
}
|
|
779
|
+
});
|
|
780
|
+
|
|
373
781
|
it("returns Stop continuation output while Autopilot is active", async () => {
|
|
374
782
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-autopilot-"));
|
|
375
783
|
try {
|
|
@@ -857,7 +1265,7 @@ describe("codex native hook dispatch", () => {
|
|
|
857
1265
|
}
|
|
858
1266
|
});
|
|
859
1267
|
|
|
860
|
-
it("returns Stop continuation output for active ralplan skill without active subagents", async () => {
|
|
1268
|
+
it("returns Stop continuation output for active ralplan skill with matching active mode state and without active subagents", async () => {
|
|
861
1269
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-skill-"));
|
|
862
1270
|
try {
|
|
863
1271
|
const stateDir = join(cwd, ".omx", "state");
|
|
@@ -868,6 +1276,10 @@ describe("codex native hook dispatch", () => {
|
|
|
868
1276
|
skill: "ralplan",
|
|
869
1277
|
phase: "planning",
|
|
870
1278
|
});
|
|
1279
|
+
await writeJson(join(stateDir, "sessions", "sess-stop-skill", "ralplan-state.json"), {
|
|
1280
|
+
active: true,
|
|
1281
|
+
current_phase: "planning",
|
|
1282
|
+
});
|
|
871
1283
|
|
|
872
1284
|
const result = await dispatchCodexNativeHook(
|
|
873
1285
|
{
|
|
@@ -891,6 +1303,41 @@ describe("codex native hook dispatch", () => {
|
|
|
891
1303
|
}
|
|
892
1304
|
});
|
|
893
1305
|
|
|
1306
|
+
it("does not block on stale ralplan skill-active state when the matching mode state is absent", async () => {
|
|
1307
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-skill-"));
|
|
1308
|
+
try {
|
|
1309
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
1310
|
+
await mkdir(join(stateDir, "sessions", "sess-stop-stale-skill"), { recursive: true });
|
|
1311
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-stop-stale-skill" });
|
|
1312
|
+
await writeJson(join(stateDir, "sessions", "sess-stop-stale-skill", "skill-active-state.json"), {
|
|
1313
|
+
active: true,
|
|
1314
|
+
skill: "ralplan",
|
|
1315
|
+
phase: "planning",
|
|
1316
|
+
session_id: "sess-stop-stale-skill",
|
|
1317
|
+
active_skills: [{
|
|
1318
|
+
skill: "ralplan",
|
|
1319
|
+
phase: "planning",
|
|
1320
|
+
active: true,
|
|
1321
|
+
session_id: "sess-stop-stale-skill",
|
|
1322
|
+
}],
|
|
1323
|
+
});
|
|
1324
|
+
|
|
1325
|
+
const result = await dispatchCodexNativeHook(
|
|
1326
|
+
{
|
|
1327
|
+
hook_event_name: "Stop",
|
|
1328
|
+
cwd,
|
|
1329
|
+
session_id: "sess-stop-stale-skill",
|
|
1330
|
+
},
|
|
1331
|
+
{ cwd },
|
|
1332
|
+
);
|
|
1333
|
+
|
|
1334
|
+
assert.equal(result.omxEventName, "stop");
|
|
1335
|
+
assert.equal(result.outputJson, null);
|
|
1336
|
+
} finally {
|
|
1337
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1338
|
+
}
|
|
1339
|
+
});
|
|
1340
|
+
|
|
894
1341
|
it("does not block on active ralplan skill when subagents are still active", async () => {
|
|
895
1342
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-skill-subagent-"));
|
|
896
1343
|
try {
|
|
@@ -902,6 +1349,10 @@ describe("codex native hook dispatch", () => {
|
|
|
902
1349
|
skill: "ralplan",
|
|
903
1350
|
phase: "planning",
|
|
904
1351
|
});
|
|
1352
|
+
await writeJson(join(stateDir, "sessions", "sess-stop-skill-subagent", "ralplan-state.json"), {
|
|
1353
|
+
active: true,
|
|
1354
|
+
current_phase: "planning",
|
|
1355
|
+
});
|
|
905
1356
|
await writeJson(join(stateDir, "subagent-tracking.json"), {
|
|
906
1357
|
schemaVersion: 1,
|
|
907
1358
|
sessions: {
|
|
@@ -945,7 +1396,35 @@ describe("codex native hook dispatch", () => {
|
|
|
945
1396
|
}
|
|
946
1397
|
});
|
|
947
1398
|
|
|
948
|
-
it("
|
|
1399
|
+
it("does not block on stale root ralplan skill when the explicit session-scoped canonical skill state is absent", async () => {
|
|
1400
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-root-skill-"));
|
|
1401
|
+
try {
|
|
1402
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
1403
|
+
await mkdir(stateDir, { recursive: true });
|
|
1404
|
+
await writeJson(join(stateDir, "skill-active-state.json"), {
|
|
1405
|
+
active: true,
|
|
1406
|
+
skill: "ralplan",
|
|
1407
|
+
phase: "planning",
|
|
1408
|
+
});
|
|
1409
|
+
|
|
1410
|
+
const result = await dispatchCodexNativeHook(
|
|
1411
|
+
{
|
|
1412
|
+
hook_event_name: "Stop",
|
|
1413
|
+
cwd,
|
|
1414
|
+
session_id: "sess-stop-stale-root-skill",
|
|
1415
|
+
thread_id: "thread-stop-stale-root-skill",
|
|
1416
|
+
},
|
|
1417
|
+
{ cwd },
|
|
1418
|
+
);
|
|
1419
|
+
|
|
1420
|
+
assert.equal(result.omxEventName, "stop");
|
|
1421
|
+
assert.equal(result.outputJson, null);
|
|
1422
|
+
} finally {
|
|
1423
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1424
|
+
}
|
|
1425
|
+
});
|
|
1426
|
+
|
|
1427
|
+
it("returns Stop continuation output for active deep-interview skill with matching active mode state and without active subagents", async () => {
|
|
949
1428
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-deep-interview-"));
|
|
950
1429
|
try {
|
|
951
1430
|
const stateDir = join(cwd, ".omx", "state");
|
|
@@ -956,6 +1435,10 @@ describe("codex native hook dispatch", () => {
|
|
|
956
1435
|
skill: "deep-interview",
|
|
957
1436
|
phase: "planning",
|
|
958
1437
|
});
|
|
1438
|
+
await writeJson(join(stateDir, "sessions", "sess-stop-deep-interview", "deep-interview-state.json"), {
|
|
1439
|
+
active: true,
|
|
1440
|
+
current_phase: "planning",
|
|
1441
|
+
});
|
|
959
1442
|
|
|
960
1443
|
const result = await dispatchCodexNativeHook(
|
|
961
1444
|
{
|
|
@@ -1043,6 +1526,35 @@ describe("codex native hook dispatch", () => {
|
|
|
1043
1526
|
}
|
|
1044
1527
|
});
|
|
1045
1528
|
|
|
1529
|
+
it("does not block Stop from stale session-scoped Ralph state that belongs to another session", async () => {
|
|
1530
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-session-ralph-"));
|
|
1531
|
+
try {
|
|
1532
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
1533
|
+
await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
|
|
1534
|
+
await mkdir(join(stateDir, "sessions", "sess-stale"), { recursive: true });
|
|
1535
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
|
|
1536
|
+
await writeJson(join(stateDir, "sessions", "sess-stale", "ralph-state.json"), {
|
|
1537
|
+
active: true,
|
|
1538
|
+
current_phase: "starting",
|
|
1539
|
+
session_id: "sess-stale",
|
|
1540
|
+
});
|
|
1541
|
+
|
|
1542
|
+
const result = await dispatchCodexNativeHook(
|
|
1543
|
+
{
|
|
1544
|
+
hook_event_name: "Stop",
|
|
1545
|
+
cwd,
|
|
1546
|
+
session_id: "sess-current",
|
|
1547
|
+
},
|
|
1548
|
+
{ cwd },
|
|
1549
|
+
);
|
|
1550
|
+
|
|
1551
|
+
assert.equal(result.omxEventName, "stop");
|
|
1552
|
+
assert.equal(result.outputJson, null);
|
|
1553
|
+
} finally {
|
|
1554
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1555
|
+
}
|
|
1556
|
+
});
|
|
1557
|
+
|
|
1046
1558
|
it("does not re-block Ralph when Stop already continued once", async () => {
|
|
1047
1559
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ralph-once-"));
|
|
1048
1560
|
try {
|
|
@@ -1202,6 +1714,10 @@ describe("codex native hook dispatch", () => {
|
|
|
1202
1714
|
message: "Deep interview is active; auto-approval shortcuts are blocked until the interview finishes.",
|
|
1203
1715
|
},
|
|
1204
1716
|
});
|
|
1717
|
+
await writeJson(join(stateDir, "sessions", "sess-stop-auto-lock", "deep-interview-state.json"), {
|
|
1718
|
+
active: true,
|
|
1719
|
+
current_phase: "planning",
|
|
1720
|
+
});
|
|
1205
1721
|
|
|
1206
1722
|
const result = await dispatchCodexNativeHook(
|
|
1207
1723
|
{
|
|
@@ -1260,7 +1776,7 @@ describe("codex native hook dispatch", () => {
|
|
|
1260
1776
|
}
|
|
1261
1777
|
});
|
|
1262
1778
|
|
|
1263
|
-
it("suppresses native auto-nudge when deep-interview mode state is active without
|
|
1779
|
+
it("suppresses native auto-nudge when root deep-interview mode state is active without an explicit session", async () => {
|
|
1264
1780
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-auto-nudge-deep-interview-mode-"));
|
|
1265
1781
|
try {
|
|
1266
1782
|
const stateDir = join(cwd, ".omx", "state");
|
|
@@ -1275,8 +1791,6 @@ describe("codex native hook dispatch", () => {
|
|
|
1275
1791
|
{
|
|
1276
1792
|
hook_event_name: "Stop",
|
|
1277
1793
|
cwd,
|
|
1278
|
-
session_id: "sess-stop-auto-mode",
|
|
1279
|
-
thread_id: "thread-stop-auto-mode",
|
|
1280
1794
|
turn_id: "turn-stop-auto-mode-1",
|
|
1281
1795
|
last_assistant_message: "Would you like me to continue with the next step?",
|
|
1282
1796
|
},
|
|
@@ -1290,6 +1804,120 @@ describe("codex native hook dispatch", () => {
|
|
|
1290
1804
|
}
|
|
1291
1805
|
});
|
|
1292
1806
|
|
|
1807
|
+
it("does not suppress native auto-nudge from stale root deep-interview mode state when the explicit session-scoped mode state is absent", async () => {
|
|
1808
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-auto-nudge-stale-root-mode-"));
|
|
1809
|
+
try {
|
|
1810
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
1811
|
+
await mkdir(stateDir, { recursive: true });
|
|
1812
|
+
await writeJson(join(stateDir, "deep-interview-state.json"), {
|
|
1813
|
+
active: true,
|
|
1814
|
+
mode: "deep-interview",
|
|
1815
|
+
current_phase: "intent-first",
|
|
1816
|
+
});
|
|
1817
|
+
|
|
1818
|
+
const result = await dispatchCodexNativeHook(
|
|
1819
|
+
{
|
|
1820
|
+
hook_event_name: "Stop",
|
|
1821
|
+
cwd,
|
|
1822
|
+
session_id: "sess-stop-auto-stale-root-mode",
|
|
1823
|
+
thread_id: "thread-stop-auto-stale-root-mode",
|
|
1824
|
+
turn_id: "turn-stop-auto-stale-root-mode-1",
|
|
1825
|
+
last_assistant_message: "Would you like me to continue with the next step?",
|
|
1826
|
+
},
|
|
1827
|
+
{ cwd },
|
|
1828
|
+
);
|
|
1829
|
+
|
|
1830
|
+
assert.equal(result.omxEventName, "stop");
|
|
1831
|
+
assert.deepEqual(result.outputJson, {
|
|
1832
|
+
decision: "block",
|
|
1833
|
+
reason: "yes, proceed",
|
|
1834
|
+
stopReason: "auto_nudge",
|
|
1835
|
+
systemMessage:
|
|
1836
|
+
"OMX native Stop detected a stall/permission-style handoff and continued the turn automatically.",
|
|
1837
|
+
});
|
|
1838
|
+
} finally {
|
|
1839
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1840
|
+
}
|
|
1841
|
+
});
|
|
1842
|
+
|
|
1843
|
+
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 () => {
|
|
1844
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-auto-nudge-stale-root-skill-"));
|
|
1845
|
+
try {
|
|
1846
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
1847
|
+
await mkdir(stateDir, { recursive: true });
|
|
1848
|
+
await writeJson(join(stateDir, "skill-active-state.json"), {
|
|
1849
|
+
active: true,
|
|
1850
|
+
skill: "deep-interview",
|
|
1851
|
+
phase: "planning",
|
|
1852
|
+
});
|
|
1853
|
+
|
|
1854
|
+
const result = await dispatchCodexNativeHook(
|
|
1855
|
+
{
|
|
1856
|
+
hook_event_name: "Stop",
|
|
1857
|
+
cwd,
|
|
1858
|
+
session_id: "sess-stop-auto-stale-root-skill",
|
|
1859
|
+
thread_id: "thread-stop-auto-stale-root-skill",
|
|
1860
|
+
turn_id: "turn-stop-auto-stale-root-skill-1",
|
|
1861
|
+
last_assistant_message: "Would you like me to continue with the next step?",
|
|
1862
|
+
},
|
|
1863
|
+
{ cwd },
|
|
1864
|
+
);
|
|
1865
|
+
|
|
1866
|
+
assert.equal(result.omxEventName, "stop");
|
|
1867
|
+
assert.deepEqual(result.outputJson, {
|
|
1868
|
+
decision: "block",
|
|
1869
|
+
reason: "yes, proceed",
|
|
1870
|
+
stopReason: "auto_nudge",
|
|
1871
|
+
systemMessage:
|
|
1872
|
+
"OMX native Stop detected a stall/permission-style handoff and continued the turn automatically.",
|
|
1873
|
+
});
|
|
1874
|
+
} finally {
|
|
1875
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1876
|
+
}
|
|
1877
|
+
});
|
|
1878
|
+
|
|
1879
|
+
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 () => {
|
|
1880
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-auto-nudge-stale-root-lock-"));
|
|
1881
|
+
try {
|
|
1882
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
1883
|
+
await mkdir(stateDir, { recursive: true });
|
|
1884
|
+
await writeJson(join(stateDir, "skill-active-state.json"), {
|
|
1885
|
+
active: true,
|
|
1886
|
+
skill: "deep-interview",
|
|
1887
|
+
phase: "planning",
|
|
1888
|
+
input_lock: {
|
|
1889
|
+
active: true,
|
|
1890
|
+
scope: "deep-interview-auto-approval",
|
|
1891
|
+
blocked_inputs: ["yes", "proceed"],
|
|
1892
|
+
message: "Deep interview is active; auto-approval shortcuts are blocked until the interview finishes.",
|
|
1893
|
+
},
|
|
1894
|
+
});
|
|
1895
|
+
|
|
1896
|
+
const result = await dispatchCodexNativeHook(
|
|
1897
|
+
{
|
|
1898
|
+
hook_event_name: "Stop",
|
|
1899
|
+
cwd,
|
|
1900
|
+
session_id: "sess-stop-auto-stale-root-lock",
|
|
1901
|
+
thread_id: "thread-stop-auto-stale-root-lock",
|
|
1902
|
+
turn_id: "turn-stop-auto-stale-root-lock-1",
|
|
1903
|
+
last_assistant_message: "Would you like me to continue with the next step?",
|
|
1904
|
+
},
|
|
1905
|
+
{ cwd },
|
|
1906
|
+
);
|
|
1907
|
+
|
|
1908
|
+
assert.equal(result.omxEventName, "stop");
|
|
1909
|
+
assert.deepEqual(result.outputJson, {
|
|
1910
|
+
decision: "block",
|
|
1911
|
+
reason: "yes, proceed",
|
|
1912
|
+
stopReason: "auto_nudge",
|
|
1913
|
+
systemMessage:
|
|
1914
|
+
"OMX native Stop detected a stall/permission-style handoff and continued the turn automatically.",
|
|
1915
|
+
});
|
|
1916
|
+
} finally {
|
|
1917
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1918
|
+
}
|
|
1919
|
+
});
|
|
1920
|
+
|
|
1293
1921
|
it("re-fires team Stop output for a later fresh Stop reply while the team is still active", async () => {
|
|
1294
1922
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-refire-"));
|
|
1295
1923
|
try {
|