oh-my-codex 0.19.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +6 -6
- package/Cargo.toml +1 -1
- package/README.md +8 -1
- package/crates/omx-api/src/lib.rs +3 -3
- package/crates/omx-api/tests/cli.rs +4 -4
- package/crates/omx-explore/src/main.rs +4 -4
- package/crates/omx-sparkshell/src/codex_bridge.rs +2 -2
- package/dist/agents/__tests__/definitions.test.js +3 -3
- package/dist/agents/__tests__/definitions.test.js.map +1 -1
- package/dist/agents/__tests__/native-config.test.js +71 -42
- package/dist/agents/__tests__/native-config.test.js.map +1 -1
- package/dist/agents/definitions.d.ts +1 -1
- package/dist/agents/definitions.d.ts.map +1 -1
- package/dist/agents/definitions.js +3 -3
- package/dist/agents/definitions.js.map +1 -1
- package/dist/agents/native-config.d.ts +2 -2
- package/dist/agents/native-config.d.ts.map +1 -1
- package/dist/agents/native-config.js +7 -5
- package/dist/agents/native-config.js.map +1 -1
- package/dist/autopilot/__tests__/planner-routing.test.js +19 -12
- package/dist/autopilot/__tests__/planner-routing.test.js.map +1 -1
- package/dist/autopilot/__tests__/ralplan-gate.test.js +151 -0
- package/dist/autopilot/__tests__/ralplan-gate.test.js.map +1 -1
- package/dist/autopilot/planner-routing.d.ts.map +1 -1
- package/dist/autopilot/planner-routing.js +4 -2
- package/dist/autopilot/planner-routing.js.map +1 -1
- package/dist/autopilot/ralplan-gate.d.ts +1 -0
- package/dist/autopilot/ralplan-gate.d.ts.map +1 -1
- package/dist/autopilot/ralplan-gate.js +38 -2
- package/dist/autopilot/ralplan-gate.js.map +1 -1
- package/dist/autoresearch/runtime.d.ts +2 -0
- package/dist/autoresearch/runtime.d.ts.map +1 -1
- package/dist/autoresearch/runtime.js +8 -0
- package/dist/autoresearch/runtime.js.map +1 -1
- package/dist/capabilities/__tests__/lockfile.test.d.ts +2 -0
- package/dist/capabilities/__tests__/lockfile.test.d.ts.map +1 -0
- package/dist/capabilities/__tests__/lockfile.test.js +115 -0
- package/dist/capabilities/__tests__/lockfile.test.js.map +1 -0
- package/dist/capabilities/lockfile.d.ts +102 -0
- package/dist/capabilities/lockfile.d.ts.map +1 -0
- package/dist/capabilities/lockfile.js +334 -0
- package/dist/capabilities/lockfile.js.map +1 -0
- package/dist/cli/__tests__/agents.test.js +5 -5
- package/dist/cli/__tests__/agents.test.js.map +1 -1
- package/dist/cli/__tests__/capabilities.test.d.ts +2 -0
- package/dist/cli/__tests__/capabilities.test.d.ts.map +1 -0
- package/dist/cli/__tests__/capabilities.test.js +135 -0
- package/dist/cli/__tests__/capabilities.test.js.map +1 -0
- package/dist/cli/__tests__/codex-plugin-layout.test.js +237 -6
- package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-context-window-warning.test.js +9 -9
- package/dist/cli/__tests__/doctor-context-window-warning.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-invalid-config.test.js +1 -1
- package/dist/cli/__tests__/doctor-spark-routing.test.js +22 -6
- package/dist/cli/__tests__/doctor-spark-routing.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +53 -18
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/mission.test.d.ts +2 -0
- package/dist/cli/__tests__/mission.test.d.ts.map +1 -0
- package/dist/cli/__tests__/mission.test.js +239 -0
- package/dist/cli/__tests__/mission.test.js.map +1 -0
- package/dist/cli/__tests__/plugin-marketplace-idempotent.test.js +3 -3
- package/dist/cli/__tests__/plugin-marketplace-idempotent.test.js.map +1 -1
- package/dist/cli/__tests__/resume.test.js +95 -6
- package/dist/cli/__tests__/resume.test.js.map +1 -1
- package/dist/cli/__tests__/session-scoped-runtime.test.js +67 -0
- package/dist/cli/__tests__/session-scoped-runtime.test.js.map +1 -1
- package/dist/cli/__tests__/setup-install-mode.test.js +46 -18
- package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
- package/dist/cli/__tests__/setup-prompts-overwrite.test.js +3 -3
- package/dist/cli/__tests__/setup-refresh.test.js +29 -7
- package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
- package/dist/cli/__tests__/ultragoal.test.js +1 -1
- package/dist/cli/__tests__/uninstall.test.js +4 -4
- package/dist/cli/__tests__/uninstall.test.js.map +1 -1
- package/dist/cli/agents.js +1 -1
- package/dist/cli/agents.js.map +1 -1
- package/dist/cli/capabilities.d.ts +3 -0
- package/dist/cli/capabilities.d.ts.map +1 -0
- package/dist/cli/capabilities.js +118 -0
- package/dist/cli/capabilities.js.map +1 -0
- package/dist/cli/doctor.d.ts +1 -1
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +9 -4
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.d.ts +7 -4
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +107 -23
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/mission.d.ts +36 -0
- package/dist/cli/mission.d.ts.map +1 -0
- package/dist/cli/mission.js +443 -0
- package/dist/cli/mission.js.map +1 -0
- package/dist/cli/plugin-marketplace.d.ts +1 -0
- package/dist/cli/plugin-marketplace.d.ts.map +1 -1
- package/dist/cli/plugin-marketplace.js +31 -0
- package/dist/cli/plugin-marketplace.js.map +1 -1
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +7 -4
- package/dist/cli/setup.js.map +1 -1
- package/dist/config/__tests__/generator-idempotent.test.js +23 -23
- package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
- package/dist/config/__tests__/generator-notify.test.js +9 -9
- package/dist/config/__tests__/generator-notify.test.js.map +1 -1
- package/dist/config/__tests__/models.test.js +51 -26
- package/dist/config/__tests__/models.test.js.map +1 -1
- package/dist/config/generator.d.ts +1 -1
- package/dist/config/generator.d.ts.map +1 -1
- package/dist/config/models.d.ts +16 -6
- package/dist/config/models.d.ts.map +1 -1
- package/dist/config/models.js +19 -8
- package/dist/config/models.js.map +1 -1
- package/dist/hooks/__tests__/keyword-detector.test.js +12 -4
- package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +1 -1
- package/dist/hooks/__tests__/prometheus-strict-contract.test.js +2 -2
- package/dist/hooks/__tests__/prometheus-strict-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/tmux-hook-engine.test.js +2 -2
- package/dist/hud/__tests__/state.test.js +31 -6
- package/dist/hud/__tests__/state.test.js.map +1 -1
- package/dist/hud/state.d.ts.map +1 -1
- package/dist/hud/state.js +7 -3
- package/dist/hud/state.js.map +1 -1
- package/dist/leader/__tests__/contract.test.js +108 -1
- package/dist/leader/__tests__/contract.test.js.map +1 -1
- package/dist/leader/contract.d.ts +26 -0
- package/dist/leader/contract.d.ts.map +1 -1
- package/dist/leader/contract.js +170 -0
- package/dist/leader/contract.js.map +1 -1
- package/dist/mcp/__tests__/state-paths.test.js +32 -1
- package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
- package/dist/mcp/state-paths.d.ts +4 -0
- package/dist/mcp/state-paths.d.ts.map +1 -1
- package/dist/mcp/state-paths.js +6 -15
- package/dist/mcp/state-paths.js.map +1 -1
- package/dist/modes/base.d.ts +1 -1
- package/dist/modes/base.d.ts.map +1 -1
- package/dist/pipeline/__tests__/stages.test.js +27 -1
- package/dist/pipeline/__tests__/stages.test.js.map +1 -1
- package/dist/pipeline/stages/ralplan.d.ts +2 -1
- package/dist/pipeline/stages/ralplan.d.ts.map +1 -1
- package/dist/pipeline/stages/ralplan.js +1 -0
- package/dist/pipeline/stages/ralplan.js.map +1 -1
- package/dist/pipeline/stages/ultragoal.d.ts +5 -1
- package/dist/pipeline/stages/ultragoal.d.ts.map +1 -1
- package/dist/pipeline/stages/ultragoal.js +14 -4
- package/dist/pipeline/stages/ultragoal.js.map +1 -1
- package/dist/ralplan/__tests__/consensus-gate.test.js +193 -0
- package/dist/ralplan/__tests__/consensus-gate.test.js.map +1 -1
- package/dist/ralplan/__tests__/runtime.test.js +132 -0
- package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
- package/dist/ralplan/consensus-gate.js +24 -3
- package/dist/ralplan/consensus-gate.js.map +1 -1
- package/dist/ralplan/runtime.d.ts +17 -0
- package/dist/ralplan/runtime.d.ts.map +1 -1
- package/dist/ralplan/runtime.js +97 -1
- package/dist/ralplan/runtime.js.map +1 -1
- package/dist/scripts/__tests__/codex-native-hook.test.js +762 -20
- 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 +306 -22
- package/dist/scripts/codex-native-hook.js.map +1 -1
- package/dist/scripts/generate-catalog-docs.js +1 -0
- package/dist/scripts/generate-catalog-docs.js.map +1 -1
- package/dist/state/__tests__/operations.test.js +737 -0
- package/dist/state/__tests__/operations.test.js.map +1 -1
- package/dist/state/__tests__/skill-active.test.js +59 -0
- package/dist/state/__tests__/skill-active.test.js.map +1 -1
- package/dist/state/__tests__/workflow-transition.test.js +14 -0
- package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
- package/dist/state/operations.d.ts.map +1 -1
- package/dist/state/operations.js +80 -17
- package/dist/state/operations.js.map +1 -1
- package/dist/state/skill-active.js +3 -3
- package/dist/state/skill-active.js.map +1 -1
- package/dist/state/terminal-normalization.d.ts +8 -0
- package/dist/state/terminal-normalization.d.ts.map +1 -0
- package/dist/state/terminal-normalization.js +127 -0
- package/dist/state/terminal-normalization.js.map +1 -0
- package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
- package/dist/state/workflow-transition-reconcile.js +3 -1
- package/dist/state/workflow-transition-reconcile.js.map +1 -1
- package/dist/team/__tests__/delegation-policy.test.js +2 -2
- package/dist/team/__tests__/delegation-policy.test.js.map +1 -1
- package/dist/team/__tests__/model-contract.test.js +25 -25
- package/dist/team/__tests__/model-contract.test.js.map +1 -1
- package/dist/team/__tests__/runtime.test.js +28 -28
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/scaling.test.js +7 -7
- package/dist/team/__tests__/scaling.test.js.map +1 -1
- package/dist/team/__tests__/tmux-claude-workers-demo.test.js +5 -5
- package/dist/team/__tests__/tmux-claude-workers-demo.test.js.map +1 -1
- package/dist/team/__tests__/tmux-session.test.js +12 -12
- package/dist/team/__tests__/tmux-session.test.js.map +1 -1
- package/dist/team/__tests__/worker-bootstrap.test.js +57 -9
- package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
- package/dist/team/__tests__/worker-runtime-identity.test.js +3 -3
- package/dist/team/__tests__/worker-runtime-identity.test.js.map +1 -1
- package/dist/team/model-contract.d.ts +1 -1
- package/dist/team/model-contract.d.ts.map +1 -1
- package/dist/team/runtime.d.ts.map +1 -1
- package/dist/team/runtime.js +11 -0
- package/dist/team/runtime.js.map +1 -1
- package/dist/team/worker-bootstrap.d.ts +2 -0
- package/dist/team/worker-bootstrap.d.ts.map +1 -1
- package/dist/team/worker-bootstrap.js +6 -1
- package/dist/team/worker-bootstrap.js.map +1 -1
- package/dist/ultragoal/__tests__/artifacts.test.js +82 -5
- package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
- package/dist/ultragoal/artifacts.d.ts +5 -1
- package/dist/ultragoal/artifacts.d.ts.map +1 -1
- package/dist/ultragoal/artifacts.js +111 -14
- package/dist/ultragoal/artifacts.js.map +1 -1
- package/dist/utils/__tests__/agents-model-table.test.js +7 -7
- package/dist/utils/__tests__/agents-model-table.test.js.map +1 -1
- package/dist/utils/__tests__/worktree-tool-context.test.d.ts +2 -0
- package/dist/utils/__tests__/worktree-tool-context.test.d.ts.map +1 -0
- package/dist/utils/__tests__/worktree-tool-context.test.js +118 -0
- package/dist/utils/__tests__/worktree-tool-context.test.js.map +1 -0
- package/dist/utils/worktree-tool-context.d.ts +25 -0
- package/dist/utils/worktree-tool-context.d.ts.map +1 -0
- package/dist/utils/worktree-tool-context.js +116 -0
- package/dist/utils/worktree-tool-context.js.map +1 -0
- package/package.json +1 -1
- package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/oh-my-codex/hooks/codex-native-hook.mjs +96 -10
- package/plugins/oh-my-codex/skills/ai-slop-cleaner/SKILL.md +1 -1
- package/plugins/oh-my-codex/skills/code-review/SKILL.md +1 -1
- package/plugins/oh-my-codex/skills/omx-setup/SKILL.md +5 -5
- package/plugins/oh-my-codex/skills/prometheus-strict/SKILL.md +3 -3
- package/plugins/oh-my-codex/skills/ralplan/SKILL.md +1 -1
- package/plugins/oh-my-codex/skills/team/SKILL.md +1 -1
- package/plugins/oh-my-codex/skills/ultrawork/references/agent-tiers.md +2 -2
- package/prompts/prometheus-strict-metis.md +1 -1
- package/skills/ai-slop-cleaner/SKILL.md +1 -1
- package/skills/code-review/SKILL.md +1 -1
- package/skills/ecomode/references/agent-tiers.md +1 -1
- package/skills/omx-setup/SKILL.md +5 -5
- package/skills/prometheus-strict/SKILL.md +3 -3
- package/skills/ralplan/SKILL.md +1 -1
- package/skills/team/SKILL.md +1 -1
- package/skills/ultrawork/references/agent-tiers.md +2 -2
- package/src/scripts/__tests__/codex-native-hook.test.ts +824 -7
- package/src/scripts/codex-native-hook.ts +347 -20
- package/src/scripts/generate-catalog-docs.ts +1 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { describe, it } from "node:test";
|
|
8
|
+
const testDir = join(fileURLToPath(import.meta.url), "..");
|
|
9
|
+
const repoRoot = join(testDir, "..", "..", "..");
|
|
10
|
+
const omxBin = join(repoRoot, "dist", "cli", "omx.js");
|
|
11
|
+
async function withTempDir(run) {
|
|
12
|
+
const dir = await mkdtemp(join(tmpdir(), "omx-capabilities-cli-"));
|
|
13
|
+
try {
|
|
14
|
+
return await run(dir);
|
|
15
|
+
}
|
|
16
|
+
finally {
|
|
17
|
+
await rm(dir, { recursive: true, force: true });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function runOmx(cwd, args) {
|
|
21
|
+
return spawnSync(process.execPath, [omxBin, ...args], { cwd, encoding: "utf8" });
|
|
22
|
+
}
|
|
23
|
+
async function writeObservations(dir, observations) {
|
|
24
|
+
const path = join(dir, "observations.json");
|
|
25
|
+
await writeFile(path, JSON.stringify({ version: 1, kind: "omx_capability_observations", observations }, null, 2), "utf8");
|
|
26
|
+
return path;
|
|
27
|
+
}
|
|
28
|
+
const SUCCESS_OBSERVATIONS = [
|
|
29
|
+
{
|
|
30
|
+
fixture_id: "project-memory-write-required-arg",
|
|
31
|
+
tool_calls: [{ name: "project_memory_write", arguments: { memory: { note: "capability preflight fixture" } } }],
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
fixture_id: "project-memory-read-known-tool",
|
|
35
|
+
tool_calls: [{ name: "project_memory_read", arguments: {} }],
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
fixture_id: "tool-restraint-no-call",
|
|
39
|
+
tool_calls: [],
|
|
40
|
+
},
|
|
41
|
+
];
|
|
42
|
+
function hasFailure(stdout, code) {
|
|
43
|
+
const parsed = JSON.parse(stdout);
|
|
44
|
+
return parsed.failures?.some((failure) => failure.code === code) ?? false;
|
|
45
|
+
}
|
|
46
|
+
describe("omx capabilities cli", () => {
|
|
47
|
+
it("routes local help", async () => {
|
|
48
|
+
await withTempDir(async (dir) => {
|
|
49
|
+
const result = runOmx(dir, ["capabilities", "--help"]);
|
|
50
|
+
assert.equal(result.status, 0);
|
|
51
|
+
assert.match(result.stdout, /omx capabilities lock/);
|
|
52
|
+
assert.doesNotMatch(result.stdout, /Launch Codex CLI/);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
it("locks and checks with JSON output", async () => {
|
|
56
|
+
await withTempDir(async (dir) => {
|
|
57
|
+
const lock = runOmx(dir, ["capabilities", "lock", "--json"]);
|
|
58
|
+
assert.equal(lock.status, 0, lock.stderr);
|
|
59
|
+
const lockJson = JSON.parse(lock.stdout);
|
|
60
|
+
assert.equal(lockJson.ok, true);
|
|
61
|
+
assert.match(lockJson.lockfile, /omx-capabilities\.lock\.json$/);
|
|
62
|
+
const check = runOmx(dir, ["capabilities", "check", "--json"]);
|
|
63
|
+
assert.equal(check.status, 0, check.stderr);
|
|
64
|
+
const checkJson = JSON.parse(check.stdout);
|
|
65
|
+
assert.equal(checkJson.ok, true);
|
|
66
|
+
assert.equal(checkJson.observations_checked, false);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
it("checks successful fixture observations", async () => {
|
|
70
|
+
await withTempDir(async (dir) => {
|
|
71
|
+
const lock = runOmx(dir, ["capabilities", "lock", "--json"]);
|
|
72
|
+
assert.equal(lock.status, 0, lock.stderr);
|
|
73
|
+
const observations = await writeObservations(dir, SUCCESS_OBSERVATIONS);
|
|
74
|
+
const check = runOmx(dir, ["capabilities", "check", "--observations", observations, "--require-observations", "--json"]);
|
|
75
|
+
assert.equal(check.status, 0, check.stderr);
|
|
76
|
+
const checkJson = JSON.parse(check.stdout);
|
|
77
|
+
assert.equal(checkJson.ok, true);
|
|
78
|
+
assert.equal(checkJson.observations_checked, true);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
it("exits non-zero for required missing observations", async () => {
|
|
82
|
+
await withTempDir(async (dir) => {
|
|
83
|
+
const lock = runOmx(dir, ["capabilities", "lock", "--json"]);
|
|
84
|
+
assert.equal(lock.status, 0, lock.stderr);
|
|
85
|
+
const check = runOmx(dir, ["capabilities", "check", "--require-observations", "--json"]);
|
|
86
|
+
assert.notEqual(check.status, 0);
|
|
87
|
+
const checkJson = JSON.parse(check.stdout);
|
|
88
|
+
assert.equal(checkJson.ok, false);
|
|
89
|
+
assert.ok(checkJson.failures.some((failure) => failure.code === "observations_missing"));
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
it("reports missing required tool arguments", async () => {
|
|
93
|
+
await withTempDir(async (dir) => {
|
|
94
|
+
const lock = runOmx(dir, ["capabilities", "lock", "--json"]);
|
|
95
|
+
assert.equal(lock.status, 0, lock.stderr);
|
|
96
|
+
const observations = await writeObservations(dir, [
|
|
97
|
+
{ fixture_id: "project-memory-write-required-arg", tool_calls: [{ name: "project_memory_write", arguments: {} }] },
|
|
98
|
+
SUCCESS_OBSERVATIONS[1],
|
|
99
|
+
SUCCESS_OBSERVATIONS[2],
|
|
100
|
+
]);
|
|
101
|
+
const check = runOmx(dir, ["capabilities", "check", "--observations", observations, "--json"]);
|
|
102
|
+
assert.notEqual(check.status, 0);
|
|
103
|
+
assert.equal(hasFailure(check.stdout, "missing_required_arg"), true);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
it("reports hallucinated tools", async () => {
|
|
107
|
+
await withTempDir(async (dir) => {
|
|
108
|
+
const lock = runOmx(dir, ["capabilities", "lock", "--json"]);
|
|
109
|
+
assert.equal(lock.status, 0, lock.stderr);
|
|
110
|
+
const observations = await writeObservations(dir, [
|
|
111
|
+
SUCCESS_OBSERVATIONS[0],
|
|
112
|
+
{ fixture_id: "project-memory-read-known-tool", tool_calls: [{ name: "imaginary_tool", arguments: {} }] },
|
|
113
|
+
SUCCESS_OBSERVATIONS[2],
|
|
114
|
+
]);
|
|
115
|
+
const check = runOmx(dir, ["capabilities", "check", "--observations", observations, "--json"]);
|
|
116
|
+
assert.notEqual(check.status, 0);
|
|
117
|
+
assert.equal(hasFailure(check.stdout, "hallucinated_tool"), true);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
it("reports tool-restraint violations", async () => {
|
|
121
|
+
await withTempDir(async (dir) => {
|
|
122
|
+
const lock = runOmx(dir, ["capabilities", "lock", "--json"]);
|
|
123
|
+
assert.equal(lock.status, 0, lock.stderr);
|
|
124
|
+
const observations = await writeObservations(dir, [
|
|
125
|
+
SUCCESS_OBSERVATIONS[0],
|
|
126
|
+
SUCCESS_OBSERVATIONS[1],
|
|
127
|
+
{ fixture_id: "tool-restraint-no-call", tool_calls: [{ name: "project_memory_read", arguments: {} }] },
|
|
128
|
+
]);
|
|
129
|
+
const check = runOmx(dir, ["capabilities", "check", "--observations", observations, "--json"]);
|
|
130
|
+
assert.notEqual(check.status, 0);
|
|
131
|
+
assert.equal(hasFailure(check.stdout, "unexpected_tool_call"), true);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
//# sourceMappingURL=capabilities.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capabilities.test.js","sourceRoot":"","sources":["../../../src/cli/__tests__/capabilities.test.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,WAAW,CAAC;AAEzC,MAAM,OAAO,GAAG,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC;AAC3D,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;AACjD,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;AAEvD,KAAK,UAAU,WAAW,CAAI,GAAgC;IAC5D,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,uBAAuB,CAAC,CAAC,CAAC;IACnE,IAAI,CAAC;QACH,OAAO,MAAM,GAAG,CAAC,GAAG,CAAC,CAAC;IACxB,CAAC;YAAS,CAAC;QACT,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAClD,CAAC;AACH,CAAC;AAED,SAAS,MAAM,CAAC,GAAW,EAAE,IAAc;IACzC,OAAO,SAAS,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;AACnF,CAAC;AAED,KAAK,UAAU,iBAAiB,CAAC,GAAW,EAAE,YAAuB;IACnE,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,mBAAmB,CAAC,CAAC;IAC5C,MAAM,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,6BAA6B,EAAE,YAAY,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IAC1H,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,oBAAoB,GAAG;IAC3B;QACE,UAAU,EAAE,mCAAmC;QAC/C,UAAU,EAAE,CAAC,EAAE,IAAI,EAAE,sBAAsB,EAAE,SAAS,EAAE,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,8BAA8B,EAAE,EAAE,EAAE,CAAC;KAChH;IACD;QACE,UAAU,EAAE,gCAAgC;QAC5C,UAAU,EAAE,CAAC,EAAE,IAAI,EAAE,qBAAqB,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC;KAC7D;IACD;QACE,UAAU,EAAE,wBAAwB;QACpC,UAAU,EAAE,EAAE;KACf;CACF,CAAC;AAEF,SAAS,UAAU,CAAC,MAAc,EAAE,IAAY;IAC9C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAA4C,CAAC;IAC7E,OAAO,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,KAAK,CAAC;AAC5E,CAAC;AAED,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;IACpC,EAAE,CAAC,mBAAmB,EAAE,KAAK,IAAI,EAAE;QACjC,MAAM,WAAW,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;YAC9B,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,EAAE,CAAC,cAAc,EAAE,QAAQ,CAAC,CAAC,CAAC;YAEvD,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;YAC/B,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAC;YACrD,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;QACzD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mCAAmC,EAAE,KAAK,IAAI,EAAE;QACjD,MAAM,WAAW,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;YAC9B,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;YAC7D,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;YAC1C,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACzC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;YAChC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,QAAQ,EAAE,+BAA+B,CAAC,CAAC;YAEjE,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,EAAE,CAAC,cAAc,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;YAC/D,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;YAC5C,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YAC3C,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;YACjC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,oBAAoB,EAAE,KAAK,CAAC,CAAC;QACtD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wCAAwC,EAAE,KAAK,IAAI,EAAE;QACtD,MAAM,WAAW,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;YAC9B,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;YAC7D,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;YAC1C,MAAM,YAAY,GAAG,MAAM,iBAAiB,CAAC,GAAG,EAAE,oBAAoB,CAAC,CAAC;YAExE,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,EAAE,CAAC,cAAc,EAAE,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,wBAAwB,EAAE,QAAQ,CAAC,CAAC,CAAC;YAEzH,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;YAC5C,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YAC3C,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;YACjC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,oBAAoB,EAAE,IAAI,CAAC,CAAC;QACrD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE,KAAK,IAAI,EAAE;QAChE,MAAM,WAAW,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;YAC9B,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;YAC7D,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;YAE1C,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,EAAE,CAAC,cAAc,EAAE,OAAO,EAAE,wBAAwB,EAAE,QAAQ,CAAC,CAAC,CAAC;YAEzF,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;YACjC,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YAC3C,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;YAClC,MAAM,CAAC,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAyB,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,sBAAsB,CAAC,CAAC,CAAC;QAC7G,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yCAAyC,EAAE,KAAK,IAAI,EAAE;QACvD,MAAM,WAAW,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;YAC9B,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;YAC7D,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;YAC1C,MAAM,YAAY,GAAG,MAAM,iBAAiB,CAAC,GAAG,EAAE;gBAChD,EAAE,UAAU,EAAE,mCAAmC,EAAE,UAAU,EAAE,CAAC,EAAE,IAAI,EAAE,sBAAsB,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC,EAAE;gBAClH,oBAAoB,CAAC,CAAC,CAAC;gBACvB,oBAAoB,CAAC,CAAC,CAAC;aACxB,CAAC,CAAC;YAEH,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,EAAE,CAAC,cAAc,EAAE,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,QAAQ,CAAC,CAAC,CAAC;YAE/F,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;YACjC,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,EAAE,sBAAsB,CAAC,EAAE,IAAI,CAAC,CAAC;QACvE,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4BAA4B,EAAE,KAAK,IAAI,EAAE;QAC1C,MAAM,WAAW,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;YAC9B,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;YAC7D,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;YAC1C,MAAM,YAAY,GAAG,MAAM,iBAAiB,CAAC,GAAG,EAAE;gBAChD,oBAAoB,CAAC,CAAC,CAAC;gBACvB,EAAE,UAAU,EAAE,gCAAgC,EAAE,UAAU,EAAE,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC,EAAE;gBACzG,oBAAoB,CAAC,CAAC,CAAC;aACxB,CAAC,CAAC;YAEH,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,EAAE,CAAC,cAAc,EAAE,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,QAAQ,CAAC,CAAC,CAAC;YAE/F,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;YACjC,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,EAAE,mBAAmB,CAAC,EAAE,IAAI,CAAC,CAAC;QACpE,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mCAAmC,EAAE,KAAK,IAAI,EAAE;QACjD,MAAM,WAAW,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;YAC9B,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;YAC7D,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;YAC1C,MAAM,YAAY,GAAG,MAAM,iBAAiB,CAAC,GAAG,EAAE;gBAChD,oBAAoB,CAAC,CAAC,CAAC;gBACvB,oBAAoB,CAAC,CAAC,CAAC;gBACvB,EAAE,UAAU,EAAE,wBAAwB,EAAE,UAAU,EAAE,CAAC,EAAE,IAAI,EAAE,qBAAqB,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC,EAAE;aACvG,CAAC,CAAC;YAEH,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,EAAE,CAAC,cAAc,EAAE,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,QAAQ,CAAC,CAAC,CAAC;YAE/F,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;YACjC,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,EAAE,sBAAsB,CAAC,EAAE,IAAI,CAAC,CAAC;QACvE,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -148,6 +148,8 @@ async function assertPluginHookLaunchesPostCompactFromCache() {
|
|
|
148
148
|
OMX_ROOT: join(cacheRoot, '.omx-root'),
|
|
149
149
|
OMX_SESSION_ID: 'omx-plugin-hook-postcompact-smoke',
|
|
150
150
|
OMX_SOURCE_CWD: cacheRoot,
|
|
151
|
+
OMX_ENTRY_PATH: omxBin,
|
|
152
|
+
OMX_CODEX_LAUNCH_ID: 'omx-plugin-hook-postcompact-smoke-launch',
|
|
151
153
|
OMX_STARTUP_CWD: cacheRoot,
|
|
152
154
|
},
|
|
153
155
|
});
|
|
@@ -197,6 +199,8 @@ async function assertPluginHookDelegatesPostCompactToPinnedCommand() {
|
|
|
197
199
|
OMX_ROOT: join(cacheRoot, '.omx-root'),
|
|
198
200
|
OMX_SESSION_ID: 'omx-plugin-hook-postcompact-delegate',
|
|
199
201
|
OMX_SOURCE_CWD: cacheRoot,
|
|
202
|
+
OMX_ENTRY_PATH: omxBin,
|
|
203
|
+
OMX_CODEX_LAUNCH_ID: 'omx-plugin-hook-postcompact-delegate-launch',
|
|
200
204
|
OMX_STARTUP_CWD: cacheRoot,
|
|
201
205
|
},
|
|
202
206
|
});
|
|
@@ -254,10 +258,25 @@ async function withPluginCacheCopy(run) {
|
|
|
254
258
|
}
|
|
255
259
|
function pluginHookEnv(overrides = {}) {
|
|
256
260
|
const env = { ...process.env };
|
|
257
|
-
for (const key of [
|
|
261
|
+
for (const key of [
|
|
262
|
+
'OMX_TEAM_STATE_ROOT',
|
|
263
|
+
'OMX_ROOT',
|
|
264
|
+
'OMX_STATE_ROOT',
|
|
265
|
+
'OMX_SESSION_ID',
|
|
266
|
+
'CODEX_SESSION_ID',
|
|
267
|
+
'OMX_ENTRY_PATH',
|
|
268
|
+
'OMX_CODEX_LAUNCH_ID',
|
|
269
|
+
'OMX_STARTUP_CWD',
|
|
270
|
+
]) {
|
|
258
271
|
delete env[key];
|
|
259
272
|
}
|
|
260
|
-
return {
|
|
273
|
+
return {
|
|
274
|
+
...env,
|
|
275
|
+
OMX_ENTRY_PATH: omxBin,
|
|
276
|
+
OMX_CODEX_LAUNCH_ID: 'omx-plugin-layout-launch',
|
|
277
|
+
OMX_STARTUP_CWD: root,
|
|
278
|
+
...overrides,
|
|
279
|
+
};
|
|
261
280
|
}
|
|
262
281
|
function runPluginNativeHook(cachePluginRoot, input, env = {}) {
|
|
263
282
|
return spawnSync(process.execPath, [join(cachePluginRoot, 'hooks', 'codex-native-hook.mjs')], {
|
|
@@ -336,6 +355,97 @@ describe('official Codex plugin layout', () => {
|
|
|
336
355
|
assert.match(launcher, /return \{ \.\.\.options, shell: true, windowsHide: true \};/);
|
|
337
356
|
assert.doesNotMatch(launcher, /shell:\s*process\.platform === 'win32'/);
|
|
338
357
|
});
|
|
358
|
+
it('no-ops plugin hooks when Codex was not launched through omx', async () => {
|
|
359
|
+
await withPluginCacheCopy(async (cachePluginRoot, cacheRoot) => {
|
|
360
|
+
const calledPath = join(cacheRoot, 'called.txt');
|
|
361
|
+
const commandPath = join(cacheRoot, process.platform === 'win32' ? 'record-called.cmd' : 'record-called.sh');
|
|
362
|
+
if (process.platform === 'win32') {
|
|
363
|
+
await writeFile(commandPath, `@echo off\r\necho called > "${calledPath}"\r\necho {}\r\n`, 'utf-8');
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
await writeFile(commandPath, `#!/bin/sh\necho called > "${calledPath}"\nprintf '{}\\n'\n`, 'utf-8');
|
|
367
|
+
await chmod(commandPath, 0o755);
|
|
368
|
+
}
|
|
369
|
+
const userPrompt = runPluginNativeHook(cachePluginRoot, JSON.stringify({ hook_event_name: 'UserPromptSubmit', prompt: '$ralplan smoke' }), {
|
|
370
|
+
OMX_ENTRY_PATH: '',
|
|
371
|
+
OMX_CODEX_LAUNCH_ID: '',
|
|
372
|
+
OMX_NATIVE_HOOK_COMMAND: commandPath,
|
|
373
|
+
});
|
|
374
|
+
assert.equal(userPrompt.status, 0, userPrompt.stderr || userPrompt.stdout);
|
|
375
|
+
assert.equal(userPrompt.stdout, '');
|
|
376
|
+
await assert.rejects(readFile(calledPath, 'utf-8'), { code: 'ENOENT' });
|
|
377
|
+
const stop = runPluginNativeHook(cachePluginRoot, JSON.stringify({ hook_event_name: 'Stop', session_id: 'plain-codex-stop' }), {
|
|
378
|
+
OMX_ENTRY_PATH: '',
|
|
379
|
+
OMX_CODEX_LAUNCH_ID: '',
|
|
380
|
+
OMX_NATIVE_HOOK_COMMAND: commandPath,
|
|
381
|
+
});
|
|
382
|
+
assert.equal(stop.status, 0, stop.stderr || stop.stdout);
|
|
383
|
+
assert.deepEqual(parseSingleJsonStdout(stop.stdout), {});
|
|
384
|
+
await assert.rejects(readFile(calledPath, 'utf-8'), { code: 'ENOENT' });
|
|
385
|
+
const oversizedUserPrompt = runPluginNativeHook(cachePluginRoot, JSON.stringify({
|
|
386
|
+
hook_event_name: 'UserPromptSubmit',
|
|
387
|
+
prompt: 'x'.repeat(2 * 1024 * 1024),
|
|
388
|
+
}), {
|
|
389
|
+
OMX_ENTRY_PATH: '',
|
|
390
|
+
OMX_CODEX_LAUNCH_ID: '',
|
|
391
|
+
OMX_NATIVE_HOOK_COMMAND: commandPath,
|
|
392
|
+
});
|
|
393
|
+
assert.equal(oversizedUserPrompt.error, undefined, oversizedUserPrompt.error?.message ?? '');
|
|
394
|
+
assert.equal(oversizedUserPrompt.status, 0, oversizedUserPrompt.stderr || oversizedUserPrompt.stdout);
|
|
395
|
+
assert.equal(oversizedUserPrompt.stdout, '');
|
|
396
|
+
await assert.rejects(readFile(calledPath, 'utf-8'), { code: 'ENOENT' });
|
|
397
|
+
const oversizedStop = runPluginNativeHook(cachePluginRoot, JSON.stringify({
|
|
398
|
+
hook_event_name: 'Stop',
|
|
399
|
+
session_id: 'plain-codex-oversized-stop',
|
|
400
|
+
padding: 'x'.repeat(2 * 1024 * 1024),
|
|
401
|
+
}), {
|
|
402
|
+
OMX_ENTRY_PATH: '',
|
|
403
|
+
OMX_CODEX_LAUNCH_ID: '',
|
|
404
|
+
OMX_NATIVE_HOOK_COMMAND: commandPath,
|
|
405
|
+
});
|
|
406
|
+
assert.equal(oversizedStop.error, undefined, oversizedStop.error?.message ?? '');
|
|
407
|
+
assert.equal(oversizedStop.status, 0, oversizedStop.stderr || oversizedStop.stdout);
|
|
408
|
+
assert.deepEqual(parseSingleJsonStdout(oversizedStop.stdout), {});
|
|
409
|
+
await assert.rejects(readFile(calledPath, 'utf-8'), { code: 'ENOENT' });
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
it('no-ops plugin hooks for nested plain Codex sessions that inherit omx launch env', async () => {
|
|
413
|
+
await withPluginCacheCopy(async (cachePluginRoot, cacheRoot) => {
|
|
414
|
+
const calledPath = join(cacheRoot, 'called.txt');
|
|
415
|
+
const commandPath = join(cacheRoot, process.platform === 'win32' ? 'record-inherited-called.cmd' : 'record-inherited-called.sh');
|
|
416
|
+
if (process.platform === 'win32') {
|
|
417
|
+
await writeFile(commandPath, `@echo off\r\necho %* > "${calledPath}"\r\necho {}\r\n`, 'utf-8');
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
420
|
+
await writeFile(commandPath, `#!/bin/sh\necho "$@" > "${calledPath}"\nprintf '{}\\n'\n`, 'utf-8');
|
|
421
|
+
await chmod(commandPath, 0o755);
|
|
422
|
+
}
|
|
423
|
+
const inheritedEnv = {
|
|
424
|
+
OMX_ROOT: join(cacheRoot, '.omx-root'),
|
|
425
|
+
OMX_ENTRY_PATH: omxBin,
|
|
426
|
+
OMX_CODEX_LAUNCH_ID: 'inherited-launch-token',
|
|
427
|
+
OMX_NATIVE_HOOK_COMMAND: commandPath,
|
|
428
|
+
};
|
|
429
|
+
const owner = runPluginNativeHook(cachePluginRoot, JSON.stringify({
|
|
430
|
+
hook_event_name: 'UserPromptSubmit',
|
|
431
|
+
session_id: 'owner-codex-session',
|
|
432
|
+
session_pid: 111,
|
|
433
|
+
prompt: '$ralplan smoke',
|
|
434
|
+
}), inheritedEnv);
|
|
435
|
+
assert.equal(owner.status, 0, owner.stderr || owner.stdout);
|
|
436
|
+
assert.equal((await readFile(calledPath, 'utf-8')).trim(), 'codex-native-hook');
|
|
437
|
+
await rm(calledPath, { force: true });
|
|
438
|
+
const nestedPlainCodex = runPluginNativeHook(cachePluginRoot, JSON.stringify({
|
|
439
|
+
hook_event_name: 'UserPromptSubmit',
|
|
440
|
+
session_id: 'nested-plain-codex-session',
|
|
441
|
+
session_pid: 222,
|
|
442
|
+
prompt: '$ralplan nested',
|
|
443
|
+
}), inheritedEnv);
|
|
444
|
+
assert.equal(nestedPlainCodex.status, 0, nestedPlainCodex.stderr || nestedPlainCodex.stdout);
|
|
445
|
+
assert.equal(nestedPlainCodex.stdout, '');
|
|
446
|
+
await assert.rejects(readFile(calledPath, 'utf-8'), { code: 'ENOENT' });
|
|
447
|
+
});
|
|
448
|
+
});
|
|
339
449
|
it('emits Stop JSON when the plugin hook pinned launcher is invalid', async () => {
|
|
340
450
|
await withPluginCacheCopy(async (cachePluginRoot) => {
|
|
341
451
|
await writeFile(join(cachePluginRoot, 'hooks', 'omx-command.json'), '{"command":', 'utf-8');
|
|
@@ -428,6 +538,125 @@ exit 7
|
|
|
428
538
|
assert.doesNotMatch(result.stdout, /plugin_stop_hook_launcher/);
|
|
429
539
|
});
|
|
430
540
|
});
|
|
541
|
+
it('preserves valid pretty-printed Stop child JSON', async () => {
|
|
542
|
+
await withPluginCacheCopy(async (cachePluginRoot, cacheRoot) => {
|
|
543
|
+
const commandPath = join(cacheRoot, process.platform === 'win32' ? 'pretty-valid-json.cmd' : 'pretty-valid-json.sh');
|
|
544
|
+
if (process.platform === 'win32') {
|
|
545
|
+
await writeFile(commandPath, '@echo off\r\necho {\r\necho "decision": "block",\r\necho "stopReason": "pretty_child_json"\r\necho }\r\nexit /b 0\r\n', 'utf-8');
|
|
546
|
+
}
|
|
547
|
+
else {
|
|
548
|
+
await writeFile(commandPath, `#!/bin/sh
|
|
549
|
+
printf '{\n'
|
|
550
|
+
printf ' "decision": "block",\n'
|
|
551
|
+
printf ' "stopReason": "pretty_child_json"\n'
|
|
552
|
+
printf '}\n'
|
|
553
|
+
`, 'utf-8');
|
|
554
|
+
await chmod(commandPath, 0o755);
|
|
555
|
+
}
|
|
556
|
+
const result = runPluginNativeHook(cachePluginRoot, JSON.stringify({ hook_event_name: 'Stop', session_id: 'sess-plugin-pretty-valid-json-stop' }), { OMX_NATIVE_HOOK_COMMAND: commandPath });
|
|
557
|
+
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
558
|
+
const output = parseSingleJsonStdout(result.stdout);
|
|
559
|
+
assert.equal(output.decision, 'block');
|
|
560
|
+
assert.equal(output.stopReason, 'pretty_child_json');
|
|
561
|
+
assert.doesNotMatch(result.stdout, /plugin_stop_hook_launcher_invalid_stdout/);
|
|
562
|
+
});
|
|
563
|
+
});
|
|
564
|
+
it('preserves valid Stop child JSON when stdout includes prior launcher noise', async () => {
|
|
565
|
+
await withPluginCacheCopy(async (cachePluginRoot, cacheRoot) => {
|
|
566
|
+
const commandPath = join(cacheRoot, process.platform === 'win32' ? 'noisy-valid-json.cmd' : 'noisy-valid-json.sh');
|
|
567
|
+
if (process.platform === 'win32') {
|
|
568
|
+
await writeFile(commandPath, '@echo off\r\necho runtime notice before json\r\necho {"decision":"block","stopReason":"autopilot_ultragoal"}\r\nexit /b 0\r\n', 'utf-8');
|
|
569
|
+
}
|
|
570
|
+
else {
|
|
571
|
+
await writeFile(commandPath, `#!/bin/sh
|
|
572
|
+
printf 'runtime notice before json\n'
|
|
573
|
+
printf '{"decision":"block","stopReason":"autopilot_ultragoal"}\n'
|
|
574
|
+
`, 'utf-8');
|
|
575
|
+
await chmod(commandPath, 0o755);
|
|
576
|
+
}
|
|
577
|
+
const result = runPluginNativeHook(cachePluginRoot, JSON.stringify({
|
|
578
|
+
hook_event_name: 'Stop',
|
|
579
|
+
session_id: 'omx-1783508412223-c32f1l',
|
|
580
|
+
cwd: cacheRoot,
|
|
581
|
+
}), { OMX_NATIVE_HOOK_COMMAND: commandPath });
|
|
582
|
+
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
583
|
+
assert.doesNotMatch(result.stdout, /runtime notice before json/);
|
|
584
|
+
const output = parseSingleJsonStdout(result.stdout);
|
|
585
|
+
assert.equal(output.decision, 'block');
|
|
586
|
+
assert.equal(output.stopReason, 'autopilot_ultragoal');
|
|
587
|
+
assert.doesNotMatch(result.stdout, /plugin_stop_hook_launcher_invalid_stdout/);
|
|
588
|
+
});
|
|
589
|
+
});
|
|
590
|
+
it('rejects Stop child JSON when later stdout noise follows it', async () => {
|
|
591
|
+
await withPluginCacheCopy(async (cachePluginRoot, cacheRoot) => {
|
|
592
|
+
const commandPath = join(cacheRoot, process.platform === 'win32' ? 'valid-json-trailing-noise.cmd' : 'valid-json-trailing-noise.sh');
|
|
593
|
+
if (process.platform === 'win32') {
|
|
594
|
+
await writeFile(commandPath, '@echo off\r\necho {"decision":"block","stopReason":"child_valid_json"}\r\necho trailing runtime noise\r\nexit /b 0\r\n', 'utf-8');
|
|
595
|
+
}
|
|
596
|
+
else {
|
|
597
|
+
await writeFile(commandPath, `#!/bin/sh
|
|
598
|
+
printf '{"decision":"block","stopReason":"child_valid_json"}\n'
|
|
599
|
+
printf 'trailing runtime noise\n'
|
|
600
|
+
`, 'utf-8');
|
|
601
|
+
await chmod(commandPath, 0o755);
|
|
602
|
+
}
|
|
603
|
+
const result = runPluginNativeHook(cachePluginRoot, JSON.stringify({ hook_event_name: 'Stop', session_id: 'sess-plugin-trailing-noise-stop' }), { OMX_NATIVE_HOOK_COMMAND: commandPath });
|
|
604
|
+
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
605
|
+
assert.doesNotMatch(result.stdout, /trailing runtime noise/);
|
|
606
|
+
const output = parseSingleJsonStdout(result.stdout);
|
|
607
|
+
assert.equal(output.decision, 'block');
|
|
608
|
+
assert.equal(output.stopReason, 'plugin_stop_hook_launcher_invalid_stdout');
|
|
609
|
+
});
|
|
610
|
+
});
|
|
611
|
+
it('rejects Stop child JSON when an earlier JSON decision is followed by noise and final JSON', async () => {
|
|
612
|
+
await withPluginCacheCopy(async (cachePluginRoot, cacheRoot) => {
|
|
613
|
+
const commandPath = join(cacheRoot, process.platform === 'win32' ? 'json-noise-json.cmd' : 'json-noise-json.sh');
|
|
614
|
+
if (process.platform === 'win32') {
|
|
615
|
+
await writeFile(commandPath, '@echo off\r\necho {"decision":"block","stopReason":"first_json"}\r\necho trailing runtime noise\r\necho {"decision":"block","stopReason":"second_json"}\r\nexit /b 0\r\n', 'utf-8');
|
|
616
|
+
}
|
|
617
|
+
else {
|
|
618
|
+
await writeFile(commandPath, `#!/bin/sh
|
|
619
|
+
printf '{"decision":"block","stopReason":"first_json"}\n'
|
|
620
|
+
printf 'trailing runtime noise\n'
|
|
621
|
+
printf '{"decision":"block","stopReason":"second_json"}\n'
|
|
622
|
+
`, 'utf-8');
|
|
623
|
+
await chmod(commandPath, 0o755);
|
|
624
|
+
}
|
|
625
|
+
const result = runPluginNativeHook(cachePluginRoot, JSON.stringify({ hook_event_name: 'Stop', session_id: 'sess-plugin-json-noise-json-stop' }), { OMX_NATIVE_HOOK_COMMAND: commandPath });
|
|
626
|
+
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
627
|
+
assert.doesNotMatch(result.stdout, /first_json/);
|
|
628
|
+
assert.doesNotMatch(result.stdout, /second_json/);
|
|
629
|
+
assert.doesNotMatch(result.stdout, /trailing runtime noise/);
|
|
630
|
+
const output = parseSingleJsonStdout(result.stdout);
|
|
631
|
+
assert.equal(output.decision, 'block');
|
|
632
|
+
assert.equal(output.stopReason, 'plugin_stop_hook_launcher_invalid_stdout');
|
|
633
|
+
});
|
|
634
|
+
});
|
|
635
|
+
it('rejects pretty Stop child JSON followed by final JSON log noise', async () => {
|
|
636
|
+
await withPluginCacheCopy(async (cachePluginRoot, cacheRoot) => {
|
|
637
|
+
const commandPath = join(cacheRoot, process.platform === 'win32' ? 'pretty-json-final-log.cmd' : 'pretty-json-final-log.sh');
|
|
638
|
+
if (process.platform === 'win32') {
|
|
639
|
+
await writeFile(commandPath, '@echo off\r\necho {\r\necho "decision": "block",\r\necho "stopReason": "real_block"\r\necho }\r\necho {"level":"info"}\r\nexit /b 0\r\n', 'utf-8');
|
|
640
|
+
}
|
|
641
|
+
else {
|
|
642
|
+
await writeFile(commandPath, `#!/bin/sh
|
|
643
|
+
printf '{\n'
|
|
644
|
+
printf ' "decision": "block",\n'
|
|
645
|
+
printf ' "stopReason": "real_block"\n'
|
|
646
|
+
printf '}\n'
|
|
647
|
+
printf '{"level":"info"}\n'
|
|
648
|
+
`, 'utf-8');
|
|
649
|
+
await chmod(commandPath, 0o755);
|
|
650
|
+
}
|
|
651
|
+
const result = runPluginNativeHook(cachePluginRoot, JSON.stringify({ hook_event_name: 'Stop', session_id: 'sess-plugin-pretty-json-final-log-stop' }), { OMX_NATIVE_HOOK_COMMAND: commandPath });
|
|
652
|
+
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
653
|
+
assert.doesNotMatch(result.stdout, /real_block/);
|
|
654
|
+
assert.doesNotMatch(result.stdout, /level/);
|
|
655
|
+
const output = parseSingleJsonStdout(result.stdout);
|
|
656
|
+
assert.equal(output.decision, 'block');
|
|
657
|
+
assert.equal(output.stopReason, 'plugin_stop_hook_launcher_invalid_stdout');
|
|
658
|
+
});
|
|
659
|
+
});
|
|
431
660
|
it('replaces oversized Stop child stdout with fallback Stop JSON', async () => {
|
|
432
661
|
await withPluginCacheCopy(async (cachePluginRoot, cacheRoot) => {
|
|
433
662
|
const commandPath = join(cacheRoot, process.platform === 'win32' ? 'oversized-stop-stdout.cmd' : 'oversized-stop-stdout.sh');
|
|
@@ -451,7 +680,7 @@ head -c 1100000 /dev/zero | tr '\0' x
|
|
|
451
680
|
it('emits Stop JSON for malformed Stop-looking stdin before invalid launcher failure', async () => {
|
|
452
681
|
await withPluginCacheCopy(async (cachePluginRoot) => {
|
|
453
682
|
await writeFile(join(cachePluginRoot, 'hooks', 'omx-command.json'), '{"command":', 'utf-8');
|
|
454
|
-
const result = runPluginNativeHook(cachePluginRoot, '{"hook_event_name":"Stop",');
|
|
683
|
+
const result = runPluginNativeHook(cachePluginRoot, '{"hook_event_name":"Stop","session_id":"sess-plugin-malformed-stop",');
|
|
455
684
|
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
456
685
|
const output = parseSingleJsonStdout(result.stdout);
|
|
457
686
|
assert.equal(output.decision, 'block');
|
|
@@ -461,7 +690,7 @@ head -c 1100000 /dev/zero | tr '\0' x
|
|
|
461
690
|
it('emits Stop JSON for the core-supported name alias before invalid launcher failure', async () => {
|
|
462
691
|
await withPluginCacheCopy(async (cachePluginRoot) => {
|
|
463
692
|
await writeFile(join(cachePluginRoot, 'hooks', 'omx-command.json'), '{"command":', 'utf-8');
|
|
464
|
-
const result = runPluginNativeHook(cachePluginRoot, '{"name":"Stop",');
|
|
693
|
+
const result = runPluginNativeHook(cachePluginRoot, '{"name":"Stop","session_id":"sess-plugin-malformed-name-stop",');
|
|
465
694
|
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
466
695
|
const output = parseSingleJsonStdout(result.stdout);
|
|
467
696
|
assert.equal(output.decision, 'block');
|
|
@@ -494,6 +723,7 @@ head -c 1100000 /dev/zero | tr '\0' x
|
|
|
494
723
|
await writeFile(join(cachePluginRoot, 'hooks', 'omx-command.json'), '{"command":', 'utf-8');
|
|
495
724
|
const result = runPluginNativeHook(cachePluginRoot, JSON.stringify({
|
|
496
725
|
hook_event_name: 'UserPromptSubmit',
|
|
726
|
+
session_id: 'sess-plugin-invalid-launcher-user-prompt',
|
|
497
727
|
prompt: 'hello',
|
|
498
728
|
}));
|
|
499
729
|
assert.equal(result.status, 1);
|
|
@@ -531,6 +761,7 @@ head -c 1100000 /dev/zero | tr '\0' x
|
|
|
531
761
|
await writeFile(join(cachePluginRoot, 'hooks', 'omx-command.json'), '{"command":', 'utf-8');
|
|
532
762
|
const result = runPluginNativeHook(cachePluginRoot, JSON.stringify({
|
|
533
763
|
hook_event_name: 'PreToolUse',
|
|
764
|
+
session_id: 'sess-plugin-nested-stop-text',
|
|
534
765
|
tool_input: { name: 'Stop' },
|
|
535
766
|
}));
|
|
536
767
|
assert.equal(result.status, 1);
|
|
@@ -541,7 +772,7 @@ head -c 1100000 /dev/zero | tr '\0' x
|
|
|
541
772
|
it('does not classify malformed non-Stop plugin JSON with nested Stop text as Stop', async () => {
|
|
542
773
|
await withPluginCacheCopy(async (cachePluginRoot) => {
|
|
543
774
|
await writeFile(join(cachePluginRoot, 'hooks', 'omx-command.json'), '{"command":', 'utf-8');
|
|
544
|
-
const result = runPluginNativeHook(cachePluginRoot, '{"hook_event_name":"PreToolUse","tool_input":{"name":"Stop"},');
|
|
775
|
+
const result = runPluginNativeHook(cachePluginRoot, '{"hook_event_name":"PreToolUse","session_id":"sess-plugin-malformed-non-stop","tool_input":{"name":"Stop"},');
|
|
545
776
|
assert.equal(result.status, 1);
|
|
546
777
|
assert.equal(result.stdout, '');
|
|
547
778
|
assert.match(result.stderr, /invalid plugin hook launcher/);
|
|
@@ -681,7 +912,7 @@ head -c 1100000 /dev/zero | tr '\0' x
|
|
|
681
912
|
});
|
|
682
913
|
it('fails oversized non-Stop plugin stdin without Stop JSON', async () => {
|
|
683
914
|
await withPluginCacheCopy(async (cachePluginRoot) => {
|
|
684
|
-
const result = runPluginNativeHook(cachePluginRoot, 'x'.repeat(1024 * 1024 + 1));
|
|
915
|
+
const result = runPluginNativeHook(cachePluginRoot, `{"hook_event_name":"UserPromptSubmit","session_id":"sess-plugin-oversized-non-stop","padding":"${'x'.repeat(1024 * 1024 + 1)}`);
|
|
685
916
|
assert.equal(result.status, 1);
|
|
686
917
|
assert.equal(result.stdout, '');
|
|
687
918
|
assert.match(result.stderr, /plugin hook stdin exceeded/);
|