oh-my-codex 0.16.4 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +5 -5
- package/Cargo.toml +1 -1
- package/dist/catalog/__tests__/generator.test.js +2 -0
- package/dist/catalog/__tests__/generator.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-warning-copy.test.js +80 -7
- package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +17 -11
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/mcp-serve.test.js +4 -0
- package/dist/cli/__tests__/mcp-serve.test.js.map +1 -1
- package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js +8 -3
- package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js.map +1 -1
- package/dist/cli/__tests__/setup-install-mode.test.js +27 -1
- package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
- package/dist/cli/__tests__/ultragoal.test.js +22 -0
- package/dist/cli/__tests__/ultragoal.test.js.map +1 -1
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +66 -10
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.d.ts +8 -2
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +17 -7
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/mcp-serve.d.ts.map +1 -1
- package/dist/cli/mcp-serve.js +4 -0
- package/dist/cli/mcp-serve.js.map +1 -1
- package/dist/cli/plugin-marketplace.d.ts +20 -0
- package/dist/cli/plugin-marketplace.d.ts.map +1 -1
- package/dist/cli/plugin-marketplace.js +115 -1
- package/dist/cli/plugin-marketplace.js.map +1 -1
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +29 -10
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/ultragoal.d.ts.map +1 -1
- package/dist/cli/ultragoal.js +7 -1
- package/dist/cli/ultragoal.js.map +1 -1
- package/dist/config/__tests__/codex-hooks.test.js +136 -9
- package/dist/config/__tests__/codex-hooks.test.js.map +1 -1
- package/dist/config/__tests__/generator-idempotent.test.js +15 -0
- package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
- package/dist/config/codex-hooks.d.ts +13 -14
- package/dist/config/codex-hooks.d.ts.map +1 -1
- package/dist/config/codex-hooks.js +85 -7
- package/dist/config/codex-hooks.js.map +1 -1
- package/dist/config/generator.d.ts +4 -1
- package/dist/config/generator.d.ts.map +1 -1
- package/dist/config/generator.js +15 -9
- package/dist/config/generator.js.map +1 -1
- package/dist/config/omx-first-party-mcp.d.ts.map +1 -1
- package/dist/config/omx-first-party-mcp.js +7 -0
- package/dist/config/omx-first-party-mcp.js.map +1 -1
- package/dist/hooks/__tests__/design-skill.test.d.ts +2 -0
- package/dist/hooks/__tests__/design-skill.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/design-skill.test.js +55 -0
- package/dist/hooks/__tests__/design-skill.test.js.map +1 -0
- package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js +265 -0
- package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
- package/dist/hooks/__tests__/skill-catalog-hygiene.test.js +1 -1
- package/dist/hooks/__tests__/skill-catalog-hygiene.test.js.map +1 -1
- package/dist/hooks/__tests__/skill-guidance-contract.test.js +41 -0
- package/dist/hooks/__tests__/skill-guidance-contract.test.js.map +1 -1
- package/dist/hooks/keyword-detector.d.ts.map +1 -1
- package/dist/hooks/keyword-detector.js +5 -1
- package/dist/hooks/keyword-detector.js.map +1 -1
- package/dist/hooks/keyword-registry.d.ts.map +1 -1
- package/dist/hooks/keyword-registry.js +2 -0
- package/dist/hooks/keyword-registry.js.map +1 -1
- package/dist/hooks/prompt-guidance-contract.d.ts.map +1 -1
- package/dist/hooks/prompt-guidance-contract.js +47 -2
- package/dist/hooks/prompt-guidance-contract.js.map +1 -1
- package/dist/mcp/__tests__/bootstrap.test.js +3 -0
- package/dist/mcp/__tests__/bootstrap.test.js.map +1 -1
- package/dist/mcp/__tests__/hermes-bridge.test.d.ts +2 -0
- package/dist/mcp/__tests__/hermes-bridge.test.d.ts.map +1 -0
- package/dist/mcp/__tests__/hermes-bridge.test.js +374 -0
- package/dist/mcp/__tests__/hermes-bridge.test.js.map +1 -0
- package/dist/mcp/__tests__/state-paths.test.js +96 -13
- package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
- package/dist/mcp/bootstrap.d.ts +1 -1
- package/dist/mcp/bootstrap.d.ts.map +1 -1
- package/dist/mcp/bootstrap.js +2 -0
- package/dist/mcp/bootstrap.js.map +1 -1
- package/dist/mcp/hermes-bridge.d.ts +81 -0
- package/dist/mcp/hermes-bridge.d.ts.map +1 -0
- package/dist/mcp/hermes-bridge.js +400 -0
- package/dist/mcp/hermes-bridge.js.map +1 -0
- package/dist/mcp/hermes-server.d.ts +269 -0
- package/dist/mcp/hermes-server.d.ts.map +1 -0
- package/dist/mcp/hermes-server.js +121 -0
- package/dist/mcp/hermes-server.js.map +1 -0
- package/dist/mcp/state-paths.d.ts.map +1 -1
- package/dist/mcp/state-paths.js +41 -9
- package/dist/mcp/state-paths.js.map +1 -1
- package/dist/modes/__tests__/base-tmux-pane.test.js +31 -1
- package/dist/modes/__tests__/base-tmux-pane.test.js.map +1 -1
- package/dist/scripts/__tests__/codex-native-hook.test.js +187 -2
- package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
- package/dist/scripts/codex-native-hook.d.ts +1 -0
- package/dist/scripts/codex-native-hook.d.ts.map +1 -1
- package/dist/scripts/codex-native-hook.js +44 -17
- package/dist/scripts/codex-native-hook.js.map +1 -1
- package/dist/scripts/notify-hook/tmux-injection.d.ts.map +1 -1
- package/dist/scripts/notify-hook/tmux-injection.js +91 -2
- package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
- package/dist/state/mode-state-context.d.ts +2 -0
- package/dist/state/mode-state-context.d.ts.map +1 -1
- package/dist/state/mode-state-context.js +21 -0
- package/dist/state/mode-state-context.js.map +1 -1
- package/dist/ultragoal/__tests__/artifacts.test.js +121 -0
- package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
- package/dist/ultragoal/artifacts.d.ts +9 -1
- package/dist/ultragoal/artifacts.d.ts.map +1 -1
- package/dist/ultragoal/artifacts.js +105 -3
- package/dist/ultragoal/artifacts.js.map +1 -1
- package/dist/utils/__tests__/paths.test.js +31 -1
- package/dist/utils/__tests__/paths.test.js.map +1 -1
- package/dist/utils/paths.d.ts +6 -0
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +18 -0
- package/dist/utils/paths.js.map +1 -1
- package/dist/wiki/lifecycle.js +3 -3
- package/dist/wiki/lifecycle.js.map +1 -1
- package/package.json +1 -1
- package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/oh-my-codex/.mcp.json +8 -0
- package/plugins/oh-my-codex/skills/design/SKILL.md +180 -0
- package/plugins/oh-my-codex/skills/skill/SKILL.md +2 -1
- package/plugins/oh-my-codex/skills/ultraqa/SKILL.md +161 -47
- package/plugins/oh-my-codex/skills/visual-ralph/SKILL.md +2 -2
- package/skills/design/SKILL.md +180 -0
- package/skills/frontend-ui-ux/SKILL.md +6 -2
- package/skills/skill/SKILL.md +2 -1
- package/skills/ultraqa/SKILL.md +161 -47
- package/skills/visual-ralph/SKILL.md +2 -2
- package/src/scripts/__tests__/codex-native-hook.test.ts +206 -1
- package/src/scripts/codex-native-hook.ts +45 -18
- package/src/scripts/notify-hook/tmux-injection.ts +110 -3
- package/templates/catalog-manifest.json +9 -2
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdir, mkdtemp, realpath, rm, symlink, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { afterEach, beforeEach, describe, it } from "node:test";
|
|
6
|
+
import { hermesListArtifacts, hermesListSessions, hermesReadArtifact, hermesReadStatus, hermesReadTail, hermesReportStatus, hermesSendPrompt, hermesStartSession, } from "../hermes-bridge.js";
|
|
7
|
+
const originalRoots = process.env.OMX_MCP_WORKDIR_ROOTS;
|
|
8
|
+
const originalOmxRoot = process.env.OMX_ROOT;
|
|
9
|
+
const originalOmxStateRoot = process.env.OMX_STATE_ROOT;
|
|
10
|
+
const originalTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
|
|
11
|
+
const originalSessionId = process.env.OMX_SESSION_ID;
|
|
12
|
+
const originalCodexSessionId = process.env.CODEX_SESSION_ID;
|
|
13
|
+
const originalGenericSessionId = process.env.SESSION_ID;
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
delete process.env.OMX_MCP_WORKDIR_ROOTS;
|
|
16
|
+
delete process.env.OMX_ROOT;
|
|
17
|
+
delete process.env.OMX_STATE_ROOT;
|
|
18
|
+
delete process.env.OMX_TEAM_STATE_ROOT;
|
|
19
|
+
delete process.env.OMX_SESSION_ID;
|
|
20
|
+
delete process.env.CODEX_SESSION_ID;
|
|
21
|
+
delete process.env.SESSION_ID;
|
|
22
|
+
});
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
if (typeof originalRoots === "string")
|
|
25
|
+
process.env.OMX_MCP_WORKDIR_ROOTS = originalRoots;
|
|
26
|
+
else
|
|
27
|
+
delete process.env.OMX_MCP_WORKDIR_ROOTS;
|
|
28
|
+
if (typeof originalOmxRoot === "string")
|
|
29
|
+
process.env.OMX_ROOT = originalOmxRoot;
|
|
30
|
+
else
|
|
31
|
+
delete process.env.OMX_ROOT;
|
|
32
|
+
if (typeof originalOmxStateRoot === "string")
|
|
33
|
+
process.env.OMX_STATE_ROOT = originalOmxStateRoot;
|
|
34
|
+
else
|
|
35
|
+
delete process.env.OMX_STATE_ROOT;
|
|
36
|
+
if (typeof originalTeamStateRoot === "string")
|
|
37
|
+
process.env.OMX_TEAM_STATE_ROOT = originalTeamStateRoot;
|
|
38
|
+
else
|
|
39
|
+
delete process.env.OMX_TEAM_STATE_ROOT;
|
|
40
|
+
if (typeof originalSessionId === "string")
|
|
41
|
+
process.env.OMX_SESSION_ID = originalSessionId;
|
|
42
|
+
else
|
|
43
|
+
delete process.env.OMX_SESSION_ID;
|
|
44
|
+
if (typeof originalCodexSessionId === "string")
|
|
45
|
+
process.env.CODEX_SESSION_ID = originalCodexSessionId;
|
|
46
|
+
else
|
|
47
|
+
delete process.env.CODEX_SESSION_ID;
|
|
48
|
+
if (typeof originalGenericSessionId === "string")
|
|
49
|
+
process.env.SESSION_ID = originalGenericSessionId;
|
|
50
|
+
else
|
|
51
|
+
delete process.env.SESSION_ID;
|
|
52
|
+
});
|
|
53
|
+
async function tempWorkspace(name) {
|
|
54
|
+
delete process.env.OMX_ROOT;
|
|
55
|
+
delete process.env.OMX_STATE_ROOT;
|
|
56
|
+
delete process.env.OMX_TEAM_STATE_ROOT;
|
|
57
|
+
return await realpath(await mkdtemp(join(await realpath(tmpdir()), name)));
|
|
58
|
+
}
|
|
59
|
+
describe("Hermes MCP bridge core", () => {
|
|
60
|
+
it("lists session-scoped OMX state without exposing terminal internals", async () => {
|
|
61
|
+
const cwd = await tempWorkspace("omx-hermes-list-");
|
|
62
|
+
try {
|
|
63
|
+
await mkdir(join(cwd, ".omx", "state", "sessions", "sess-a"), { recursive: true });
|
|
64
|
+
await writeFile(join(cwd, ".omx", "state", "sessions", "sess-a", "ralph-state.json"), JSON.stringify({ active: true, current_phase: "executing" }));
|
|
65
|
+
const result = await hermesListSessions({ workingDirectory: cwd });
|
|
66
|
+
assert.equal(result.ok, true);
|
|
67
|
+
assert.deepEqual(result.data?.sessions, [
|
|
68
|
+
{ session_id: "sess-a", active: false, source: "session_state_dir", modes: ["ralph"] },
|
|
69
|
+
]);
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
await rm(cwd, { recursive: true, force: true });
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
it("projects status without leaking raw internal mode state", async () => {
|
|
76
|
+
const cwd = await tempWorkspace("omx-hermes-status-");
|
|
77
|
+
try {
|
|
78
|
+
await mkdir(join(cwd, ".omx", "state", "sessions", "sess-a"), { recursive: true });
|
|
79
|
+
await writeFile(join(cwd, ".omx", "state", "sessions", "sess-a", "ralph-state.json"), JSON.stringify({
|
|
80
|
+
active: true,
|
|
81
|
+
current_phase: "verifying",
|
|
82
|
+
run_outcome: "continue",
|
|
83
|
+
lifecycle_outcome: "finished",
|
|
84
|
+
updated_at: "2026-05-11T00:00:00.000Z",
|
|
85
|
+
completed_at: "2026-05-11T00:01:00.000Z",
|
|
86
|
+
private_control_room: { token: "do-not-leak" },
|
|
87
|
+
state: { prompt_to_artifact_checklist: ["internal"] },
|
|
88
|
+
}));
|
|
89
|
+
const result = await hermesReadStatus({ workingDirectory: cwd, session_id: "sess-a" });
|
|
90
|
+
assert.equal(result.ok, true);
|
|
91
|
+
assert.deepEqual(result.data?.modes, [
|
|
92
|
+
{
|
|
93
|
+
mode: "ralph",
|
|
94
|
+
scope: "session",
|
|
95
|
+
active: true,
|
|
96
|
+
phase: "verifying",
|
|
97
|
+
run_outcome: "continue",
|
|
98
|
+
lifecycle_outcome: "finished",
|
|
99
|
+
updated_at: "2026-05-11T00:00:00.000Z",
|
|
100
|
+
completed_at: "2026-05-11T00:01:00.000Z",
|
|
101
|
+
},
|
|
102
|
+
]);
|
|
103
|
+
assert.equal(JSON.stringify(result).includes("do-not-leak"), false);
|
|
104
|
+
assert.equal(JSON.stringify(result).includes("prompt_to_artifact_checklist"), false);
|
|
105
|
+
}
|
|
106
|
+
finally {
|
|
107
|
+
await rm(cwd, { recursive: true, force: true });
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
it("projects current session metadata without leaking process or tmux internals", async () => {
|
|
111
|
+
const cwd = await tempWorkspace("omx-hermes-current-status-");
|
|
112
|
+
try {
|
|
113
|
+
const result = await hermesReadStatus({ workingDirectory: cwd }, {
|
|
114
|
+
readUsableSessionState: async () => ({
|
|
115
|
+
session_id: "sess-current",
|
|
116
|
+
native_session_id: "native-current",
|
|
117
|
+
cwd,
|
|
118
|
+
started_at: "2026-05-11T00:00:00.000Z",
|
|
119
|
+
pid: 12345,
|
|
120
|
+
pid_cmdline: "codex --secret",
|
|
121
|
+
pid_start_ticks: 67890,
|
|
122
|
+
tmux_session_name: "private-tmux",
|
|
123
|
+
}),
|
|
124
|
+
});
|
|
125
|
+
assert.equal(result.ok, true);
|
|
126
|
+
assert.deepEqual(result.data?.session, {
|
|
127
|
+
session_id: "sess-current",
|
|
128
|
+
native_session_id: "native-current",
|
|
129
|
+
cwd,
|
|
130
|
+
started_at: "2026-05-11T00:00:00.000Z",
|
|
131
|
+
});
|
|
132
|
+
assert.equal(JSON.stringify(result).includes("12345"), false);
|
|
133
|
+
assert.equal(JSON.stringify(result).includes("codex --secret"), false);
|
|
134
|
+
assert.equal(JSON.stringify(result).includes("private-tmux"), false);
|
|
135
|
+
}
|
|
136
|
+
finally {
|
|
137
|
+
await rm(cwd, { recursive: true, force: true });
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
it("reads a bounded session-history tail without tmux scrollback", async () => {
|
|
141
|
+
const cwd = await tempWorkspace("omx-hermes-tail-");
|
|
142
|
+
try {
|
|
143
|
+
await mkdir(join(cwd, ".omx", "logs"), { recursive: true });
|
|
144
|
+
await writeFile(join(cwd, ".omx", "logs", "session-history.jsonl"), ["one", "two", "three"].map((message) => JSON.stringify({ message })).join("\n") + "\n");
|
|
145
|
+
const result = await hermesReadTail({ workingDirectory: cwd, lines: 2 });
|
|
146
|
+
assert.equal(result.ok, true);
|
|
147
|
+
assert.deepEqual(result.data?.tail, [JSON.stringify({ message: "two" }), JSON.stringify({ message: "three" })]);
|
|
148
|
+
assert.match(result.data?.path ?? "", /session-history\.jsonl$/);
|
|
149
|
+
}
|
|
150
|
+
finally {
|
|
151
|
+
await rm(cwd, { recursive: true, force: true });
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
it("requires explicit mutation opt-in before queuing prompts", async () => {
|
|
155
|
+
const result = await hermesSendPrompt({ session_id: "sess-a", prompt: "continue" });
|
|
156
|
+
assert.equal(result.ok, false);
|
|
157
|
+
assert.equal(result.code, "mutation_not_allowed");
|
|
158
|
+
});
|
|
159
|
+
it("queues selected prompts through the audited exec follow-up contract", async () => {
|
|
160
|
+
const result = await hermesSendPrompt({ session_id: "sess-a", prompt: "continue", actor: "hermes-test", allow_mutation: true }, {
|
|
161
|
+
injectExecFollowup: async ({ sessionId, prompt, actor }) => ({
|
|
162
|
+
queued: {
|
|
163
|
+
id: "followup-1",
|
|
164
|
+
session_id: sessionId,
|
|
165
|
+
prompt,
|
|
166
|
+
actor: actor ?? "missing",
|
|
167
|
+
created_at: "2026-05-11T00:00:00.000Z",
|
|
168
|
+
},
|
|
169
|
+
queuePath: "/tmp/queue.json",
|
|
170
|
+
}),
|
|
171
|
+
});
|
|
172
|
+
assert.equal(result.ok, true);
|
|
173
|
+
assert.deepEqual(result.data, {
|
|
174
|
+
followup_id: "followup-1",
|
|
175
|
+
session_id: "sess-a",
|
|
176
|
+
queue_path: "/tmp/queue.json",
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
it("starts sessions in tmux worktree mode and requires mutation opt-in", async () => {
|
|
180
|
+
const cwd = await tempWorkspace("omx-hermes-start-");
|
|
181
|
+
try {
|
|
182
|
+
let observed = null;
|
|
183
|
+
const result = await hermesStartSession({ workingDirectory: cwd, prompt: "$ralph fix it", worktreeName: "pkg/demo", allow_mutation: true }, {
|
|
184
|
+
resolveOmxCliEntryPath: () => "/opt/omx/dist/cli/omx.js",
|
|
185
|
+
spawnProcess: ((command, args, options) => {
|
|
186
|
+
observed = { command, args, cwd: options.cwd };
|
|
187
|
+
return { pid: 4242, unref() { } };
|
|
188
|
+
}),
|
|
189
|
+
});
|
|
190
|
+
assert.equal(result.ok, true);
|
|
191
|
+
assert.deepEqual(observed, {
|
|
192
|
+
command: "/opt/omx/dist/cli/omx.js",
|
|
193
|
+
args: ["--tmux", "--worktree=pkg/demo", "$ralph fix it"],
|
|
194
|
+
cwd,
|
|
195
|
+
});
|
|
196
|
+
assert.equal(result.data?.pid, 4242);
|
|
197
|
+
}
|
|
198
|
+
finally {
|
|
199
|
+
await rm(cwd, { recursive: true, force: true });
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
it("lists and reads only safe result artifact paths", async () => {
|
|
203
|
+
const cwd = await tempWorkspace("omx-hermes-artifacts-");
|
|
204
|
+
try {
|
|
205
|
+
await mkdir(join(cwd, ".omx", "plans"), { recursive: true });
|
|
206
|
+
await writeFile(join(cwd, ".omx", "plans", "prd-demo.md"), "hello artifact");
|
|
207
|
+
const list = await hermesListArtifacts({ workingDirectory: cwd });
|
|
208
|
+
assert.equal(list.ok, true);
|
|
209
|
+
assert.deepEqual(list.data?.artifacts, [{ path: ".omx/plans/prd-demo.md", bytes: 14 }]);
|
|
210
|
+
const read = await hermesReadArtifact({ workingDirectory: cwd, path: ".omx/plans/prd-demo.md", max_bytes: 5 });
|
|
211
|
+
assert.equal(read.ok, true);
|
|
212
|
+
assert.deepEqual(read.data, { path: ".omx/plans/prd-demo.md", content: "hello", truncated: true });
|
|
213
|
+
const rejected = await hermesReadArtifact({ workingDirectory: cwd, path: "package.json" });
|
|
214
|
+
assert.equal(rejected.ok, false);
|
|
215
|
+
assert.equal(rejected.code, "artifact_outside_safe_roots");
|
|
216
|
+
const traversal = await hermesReadArtifact({ workingDirectory: cwd, path: ".omx/plans/../../package.json" });
|
|
217
|
+
assert.equal(traversal.ok, false);
|
|
218
|
+
assert.equal(traversal.code, "artifact_outside_safe_roots");
|
|
219
|
+
}
|
|
220
|
+
finally {
|
|
221
|
+
await rm(cwd, { recursive: true, force: true });
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
it("reads large artifacts with max_bytes truncation and reports stat sizes", async () => {
|
|
225
|
+
const cwd = await tempWorkspace("omx-hermes-large-artifact-");
|
|
226
|
+
try {
|
|
227
|
+
await mkdir(join(cwd, ".omx", "plans"), { recursive: true });
|
|
228
|
+
const content = `${"a".repeat(1024 * 1024)}tail`;
|
|
229
|
+
await writeFile(join(cwd, ".omx", "plans", "large.md"), content);
|
|
230
|
+
const list = await hermesListArtifacts({ workingDirectory: cwd });
|
|
231
|
+
assert.equal(list.ok, true);
|
|
232
|
+
assert.deepEqual(list.data?.artifacts, [{ path: ".omx/plans/large.md", bytes: content.length }]);
|
|
233
|
+
const read = await hermesReadArtifact({ workingDirectory: cwd, path: ".omx/plans/large.md", max_bytes: 8 });
|
|
234
|
+
assert.equal(read.ok, true);
|
|
235
|
+
assert.deepEqual(read.data, { path: ".omx/plans/large.md", content: "aaaaaaaa", truncated: true });
|
|
236
|
+
}
|
|
237
|
+
finally {
|
|
238
|
+
await rm(cwd, { recursive: true, force: true });
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
it("reads session-history tails from a bounded suffix", async () => {
|
|
242
|
+
const cwd = await tempWorkspace("omx-hermes-large-tail-");
|
|
243
|
+
try {
|
|
244
|
+
await mkdir(join(cwd, ".omx", "logs"), { recursive: true });
|
|
245
|
+
await writeFile(join(cwd, ".omx", "logs", "session-history.jsonl"), `${"ignored\n".repeat(40_000)}one\ntwo\nthree\n`);
|
|
246
|
+
const result = await hermesReadTail({ workingDirectory: cwd, lines: 2 });
|
|
247
|
+
assert.equal(result.ok, true);
|
|
248
|
+
assert.deepEqual(result.data?.tail, ["two", "three"]);
|
|
249
|
+
}
|
|
250
|
+
finally {
|
|
251
|
+
await rm(cwd, { recursive: true, force: true });
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
it("does not list artifacts from safe-root directories symlinked outside the worktree", async () => {
|
|
255
|
+
const cwd = await tempWorkspace("omx-hermes-list-root-symlink-");
|
|
256
|
+
const outside = await tempWorkspace("omx-hermes-list-root-outside-");
|
|
257
|
+
try {
|
|
258
|
+
await mkdir(join(cwd, ".omx"), { recursive: true });
|
|
259
|
+
await mkdir(outside, { recursive: true });
|
|
260
|
+
await writeFile(join(outside, "secret.md"), "outside artifact");
|
|
261
|
+
await symlink(outside, join(cwd, ".omx", "plans"));
|
|
262
|
+
const result = await hermesListArtifacts({ workingDirectory: cwd });
|
|
263
|
+
assert.equal(result.ok, true);
|
|
264
|
+
assert.deepEqual(result.data?.artifacts, []);
|
|
265
|
+
}
|
|
266
|
+
finally {
|
|
267
|
+
await rm(cwd, { recursive: true, force: true });
|
|
268
|
+
await rm(outside, { recursive: true, force: true });
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
it("rejects session-history tails symlinked outside the worktree", async () => {
|
|
272
|
+
const cwd = await tempWorkspace("omx-hermes-tail-symlink-");
|
|
273
|
+
const outside = await tempWorkspace("omx-hermes-tail-outside-");
|
|
274
|
+
try {
|
|
275
|
+
await mkdir(join(cwd, ".omx", "logs"), { recursive: true });
|
|
276
|
+
const outsideLog = join(outside, "session-history.jsonl");
|
|
277
|
+
await writeFile(outsideLog, "secret\n");
|
|
278
|
+
await symlink(outsideLog, join(cwd, ".omx", "logs", "session-history.jsonl"));
|
|
279
|
+
const result = await hermesReadTail({ workingDirectory: cwd, lines: 1 });
|
|
280
|
+
assert.equal(result.ok, false);
|
|
281
|
+
assert.equal(result.code, "invalid_input");
|
|
282
|
+
assert.match(result.error ?? "", /outside working directory/);
|
|
283
|
+
}
|
|
284
|
+
finally {
|
|
285
|
+
await rm(cwd, { recursive: true, force: true });
|
|
286
|
+
await rm(outside, { recursive: true, force: true });
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
it("rejects safe-root artifact symlinks that resolve outside the worktree", async () => {
|
|
290
|
+
const cwd = await tempWorkspace("omx-hermes-artifact-symlink-");
|
|
291
|
+
const outside = await mkdtemp(join(tmpdir(), "omx-hermes-artifact-outside-"));
|
|
292
|
+
try {
|
|
293
|
+
await mkdir(join(cwd, ".omx", "plans"), { recursive: true });
|
|
294
|
+
const outsideFile = join(outside, "host.md");
|
|
295
|
+
await writeFile(outsideFile, "outside artifact");
|
|
296
|
+
await symlink(outsideFile, join(cwd, ".omx", "plans", "host.md"));
|
|
297
|
+
const result = await hermesReadArtifact({ workingDirectory: cwd, path: ".omx/plans/host.md" });
|
|
298
|
+
assert.equal(result.ok, false);
|
|
299
|
+
assert.equal(result.code, "artifact_outside_safe_roots");
|
|
300
|
+
}
|
|
301
|
+
finally {
|
|
302
|
+
await rm(cwd, { recursive: true, force: true });
|
|
303
|
+
await rm(outside, { recursive: true, force: true });
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
it("rejects workdir-root candidate symlinks that would expose outside artifacts", async () => {
|
|
307
|
+
const allowed = await tempWorkspace("omx-hermes-allowed-root-");
|
|
308
|
+
const outside = await tempWorkspace("omx-hermes-outside-root-");
|
|
309
|
+
try {
|
|
310
|
+
await mkdir(join(outside, ".omx", "plans"), { recursive: true });
|
|
311
|
+
await writeFile(join(outside, ".omx", "plans", "secret.md"), "outside via workdir symlink");
|
|
312
|
+
await symlink(outside, join(allowed, "link"));
|
|
313
|
+
process.env.OMX_MCP_WORKDIR_ROOTS = allowed;
|
|
314
|
+
const result = await hermesReadArtifact({
|
|
315
|
+
workingDirectory: join(allowed, "link"),
|
|
316
|
+
path: ".omx/plans/secret.md",
|
|
317
|
+
});
|
|
318
|
+
assert.equal(result.ok, false);
|
|
319
|
+
assert.equal(result.code, "invalid_input");
|
|
320
|
+
assert.match(result.error ?? "", /outside allowed roots/);
|
|
321
|
+
}
|
|
322
|
+
finally {
|
|
323
|
+
await rm(allowed, { recursive: true, force: true });
|
|
324
|
+
await rm(outside, { recursive: true, force: true });
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
it("rejects symlinked OMX_MCP_WORKDIR_ROOTS entries before reading artifacts", async () => {
|
|
328
|
+
const intended = await tempWorkspace("omx-hermes-intended-root-");
|
|
329
|
+
const outside = await tempWorkspace("omx-hermes-outside-root-");
|
|
330
|
+
try {
|
|
331
|
+
await mkdir(join(outside, ".omx", "plans"), { recursive: true });
|
|
332
|
+
await writeFile(join(outside, ".omx", "plans", "secret.md"), "outside via symlinked root");
|
|
333
|
+
const symlinkedRoot = join(intended, "allowed-link");
|
|
334
|
+
await symlink(outside, symlinkedRoot);
|
|
335
|
+
process.env.OMX_MCP_WORKDIR_ROOTS = symlinkedRoot;
|
|
336
|
+
const result = await hermesReadArtifact({
|
|
337
|
+
workingDirectory: symlinkedRoot,
|
|
338
|
+
path: ".omx/plans/secret.md",
|
|
339
|
+
});
|
|
340
|
+
assert.equal(result.ok, false);
|
|
341
|
+
assert.equal(result.code, "invalid_input");
|
|
342
|
+
assert.match(result.error ?? "", /resolves through a symlink/);
|
|
343
|
+
}
|
|
344
|
+
finally {
|
|
345
|
+
await rm(intended, { recursive: true, force: true });
|
|
346
|
+
await rm(outside, { recursive: true, force: true });
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
it("writes a bounded Hermes coordination report", async () => {
|
|
350
|
+
const cwd = await tempWorkspace("omx-hermes-report-");
|
|
351
|
+
try {
|
|
352
|
+
const result = await hermesReportStatus({
|
|
353
|
+
workingDirectory: cwd,
|
|
354
|
+
session_id: "sess-a",
|
|
355
|
+
status: "complete",
|
|
356
|
+
summary: "PR opened",
|
|
357
|
+
pr_url: "https://github.com/Yeachan-Heo/oh-my-codex/pull/1",
|
|
358
|
+
allow_mutation: true,
|
|
359
|
+
}, { now: () => new Date("2026-05-11T00:00:00.000Z") });
|
|
360
|
+
assert.equal(result.ok, true);
|
|
361
|
+
assert.match(result.data?.path ?? "", /sessions[/\\]sess-a[/\\]hermes-coordination\.json$/);
|
|
362
|
+
assert.deepEqual(result.data?.report, {
|
|
363
|
+
status: "complete",
|
|
364
|
+
updated_at: "2026-05-11T00:00:00.000Z",
|
|
365
|
+
summary: "PR opened",
|
|
366
|
+
pr_url: "https://github.com/Yeachan-Heo/oh-my-codex/pull/1",
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
finally {
|
|
370
|
+
await rm(cwd, { recursive: true, force: true });
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
//# sourceMappingURL=hermes-bridge.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hermes-bridge.test.js","sourceRoot":"","sources":["../../../src/mcp/__tests__/hermes-bridge.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,10 +1,36 @@
|
|
|
1
|
-
import { describe, it } from 'node:test';
|
|
1
|
+
import { afterEach, beforeEach, describe, it } from 'node:test';
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
|
-
import { mkdir, mkdtemp, rm, writeFile } from 'fs/promises';
|
|
3
|
+
import { mkdir, mkdtemp, realpath, rm, symlink, writeFile } from 'fs/promises';
|
|
4
4
|
import { existsSync } from 'fs';
|
|
5
5
|
import { tmpdir } from 'os';
|
|
6
6
|
import { join, resolve as resolvePath } from 'path';
|
|
7
7
|
import { getAllScopedStateDirs, getAllScopedStatePaths, getBaseStateDir, getAllSessionScopedStateDirs, getAllSessionScopedStatePaths, getReadScopedStateFilePaths, readCurrentSessionId, resolveWorkingDirectoryForState, getStateDir, getStateFilePath, getStatePath, validateStateFileName, validateStateModeSegment, validateSessionId, } from '../state-paths.js';
|
|
8
|
+
const isolatedEnvKeys = [
|
|
9
|
+
'OMX_MCP_WORKDIR_ROOTS',
|
|
10
|
+
'OMX_ROOT',
|
|
11
|
+
'OMX_STATE_ROOT',
|
|
12
|
+
'OMX_TEAM_STATE_ROOT',
|
|
13
|
+
'OMX_SESSION_ID',
|
|
14
|
+
'CODEX_SESSION_ID',
|
|
15
|
+
'SESSION_ID',
|
|
16
|
+
];
|
|
17
|
+
const originalEnv = Object.fromEntries(isolatedEnvKeys.map((key) => [key, process.env[key]]));
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
for (const key of isolatedEnvKeys)
|
|
20
|
+
delete process.env[key];
|
|
21
|
+
});
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
for (const key of isolatedEnvKeys) {
|
|
24
|
+
const value = originalEnv[key];
|
|
25
|
+
if (typeof value === 'string')
|
|
26
|
+
process.env[key] = value;
|
|
27
|
+
else
|
|
28
|
+
delete process.env[key];
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
async function mkRealTemp(prefix) {
|
|
32
|
+
return await realpath(await mkdtemp(join(await realpath(tmpdir()), prefix)));
|
|
33
|
+
}
|
|
8
34
|
describe('validateSessionId', () => {
|
|
9
35
|
it('accepts undefined and valid ids', () => {
|
|
10
36
|
assert.equal(validateSessionId(undefined), undefined);
|
|
@@ -118,8 +144,8 @@ describe('state paths', () => {
|
|
|
118
144
|
assert.throws(() => resolveWorkingDirectoryForState('bad\0path'), /NUL byte/);
|
|
119
145
|
});
|
|
120
146
|
it('enforces OMX_MCP_WORKDIR_ROOTS allowlist when configured', async () => {
|
|
121
|
-
const allowedRoot = await
|
|
122
|
-
const disallowedRoot = await
|
|
147
|
+
const allowedRoot = await mkRealTemp('omx-allowed-root-');
|
|
148
|
+
const disallowedRoot = await mkRealTemp('omx-disallowed-root-');
|
|
123
149
|
const prev = process.env.OMX_MCP_WORKDIR_ROOTS;
|
|
124
150
|
process.env.OMX_MCP_WORKDIR_ROOTS = allowedRoot;
|
|
125
151
|
try {
|
|
@@ -135,6 +161,63 @@ describe('state paths', () => {
|
|
|
135
161
|
await rm(disallowedRoot, { recursive: true, force: true });
|
|
136
162
|
}
|
|
137
163
|
});
|
|
164
|
+
it('preserves symlinked workingDirectory spelling when no allowlist is configured', async () => {
|
|
165
|
+
const realRoot = await mkRealTemp('omx-real-root-');
|
|
166
|
+
const linkParent = await mkRealTemp('omx-link-parent-');
|
|
167
|
+
const link = join(linkParent, 'workspace-link');
|
|
168
|
+
const prev = process.env.OMX_MCP_WORKDIR_ROOTS;
|
|
169
|
+
delete process.env.OMX_MCP_WORKDIR_ROOTS;
|
|
170
|
+
try {
|
|
171
|
+
await symlink(realRoot, link);
|
|
172
|
+
assert.equal(resolveWorkingDirectoryForState(link), link);
|
|
173
|
+
}
|
|
174
|
+
finally {
|
|
175
|
+
if (typeof prev === 'string')
|
|
176
|
+
process.env.OMX_MCP_WORKDIR_ROOTS = prev;
|
|
177
|
+
else
|
|
178
|
+
delete process.env.OMX_MCP_WORKDIR_ROOTS;
|
|
179
|
+
await rm(realRoot, { recursive: true, force: true });
|
|
180
|
+
await rm(linkParent, { recursive: true, force: true });
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
it('rejects symlinked workingDirectory candidates that escape OMX_MCP_WORKDIR_ROOTS', async () => {
|
|
184
|
+
const allowedRoot = await mkRealTemp('omx-allowed-root-');
|
|
185
|
+
const outsideRoot = await mkRealTemp('omx-outside-root-');
|
|
186
|
+
const prev = process.env.OMX_MCP_WORKDIR_ROOTS;
|
|
187
|
+
process.env.OMX_MCP_WORKDIR_ROOTS = allowedRoot;
|
|
188
|
+
try {
|
|
189
|
+
const link = join(allowedRoot, 'link');
|
|
190
|
+
await symlink(outsideRoot, link);
|
|
191
|
+
assert.throws(() => resolveWorkingDirectoryForState(link), /outside allowed roots \(OMX_MCP_WORKDIR_ROOTS\)/);
|
|
192
|
+
}
|
|
193
|
+
finally {
|
|
194
|
+
if (typeof prev === 'string')
|
|
195
|
+
process.env.OMX_MCP_WORKDIR_ROOTS = prev;
|
|
196
|
+
else
|
|
197
|
+
delete process.env.OMX_MCP_WORKDIR_ROOTS;
|
|
198
|
+
await rm(allowedRoot, { recursive: true, force: true });
|
|
199
|
+
await rm(outsideRoot, { recursive: true, force: true });
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
it('rejects symlinked OMX_MCP_WORKDIR_ROOTS entries instead of treating their targets as allowed roots', async () => {
|
|
203
|
+
const intendedRoot = await mkRealTemp('omx-intended-root-');
|
|
204
|
+
const outsideRoot = await mkRealTemp('omx-outside-root-');
|
|
205
|
+
const prev = process.env.OMX_MCP_WORKDIR_ROOTS;
|
|
206
|
+
const symlinkedRoot = join(intendedRoot, 'allowed-link');
|
|
207
|
+
process.env.OMX_MCP_WORKDIR_ROOTS = symlinkedRoot;
|
|
208
|
+
try {
|
|
209
|
+
await symlink(outsideRoot, symlinkedRoot);
|
|
210
|
+
assert.throws(() => resolveWorkingDirectoryForState(symlinkedRoot), /OMX_MCP_WORKDIR_ROOTS root .* resolves through a symlink/);
|
|
211
|
+
}
|
|
212
|
+
finally {
|
|
213
|
+
if (typeof prev === 'string')
|
|
214
|
+
process.env.OMX_MCP_WORKDIR_ROOTS = prev;
|
|
215
|
+
else
|
|
216
|
+
delete process.env.OMX_MCP_WORKDIR_ROOTS;
|
|
217
|
+
await rm(intendedRoot, { recursive: true, force: true });
|
|
218
|
+
await rm(outsideRoot, { recursive: true, force: true });
|
|
219
|
+
}
|
|
220
|
+
});
|
|
138
221
|
it('builds global state paths', () => {
|
|
139
222
|
const base = getBaseStateDir('/repo');
|
|
140
223
|
assert.equal(base, '/repo/.omx/state');
|
|
@@ -150,7 +233,7 @@ describe('state paths', () => {
|
|
|
150
233
|
assert.throws(() => getStatePath('../../etc/passwd', '/repo'), /must not contain "\.\."/);
|
|
151
234
|
});
|
|
152
235
|
it('enumerates global-only path', async () => {
|
|
153
|
-
const wd = await
|
|
236
|
+
const wd = await mkRealTemp('omx-state-paths-');
|
|
154
237
|
try {
|
|
155
238
|
const paths = await getAllScopedStatePaths('team', wd);
|
|
156
239
|
assert.deepEqual(paths, [getStatePath('team', wd)]);
|
|
@@ -160,7 +243,7 @@ describe('state paths', () => {
|
|
|
160
243
|
}
|
|
161
244
|
});
|
|
162
245
|
it('enumerates session-scoped paths', async () => {
|
|
163
|
-
const wd = await
|
|
246
|
+
const wd = await mkRealTemp('omx-state-paths-');
|
|
164
247
|
try {
|
|
165
248
|
const sessionsRoot = join(getBaseStateDir(wd), 'sessions');
|
|
166
249
|
await mkdir(join(sessionsRoot, 'sess1'), { recursive: true });
|
|
@@ -176,7 +259,7 @@ describe('state paths', () => {
|
|
|
176
259
|
}
|
|
177
260
|
});
|
|
178
261
|
it('enumerates state directories across all scopes', async () => {
|
|
179
|
-
const wd = await
|
|
262
|
+
const wd = await mkRealTemp('omx-state-paths-');
|
|
180
263
|
try {
|
|
181
264
|
const sessionsRoot = join(getBaseStateDir(wd), 'sessions');
|
|
182
265
|
await mkdir(join(sessionsRoot, 'sess1'), { recursive: true });
|
|
@@ -191,7 +274,7 @@ describe('state paths', () => {
|
|
|
191
274
|
}
|
|
192
275
|
});
|
|
193
276
|
it('enumerates global and session-scoped paths together', async () => {
|
|
194
|
-
const wd = await
|
|
277
|
+
const wd = await mkRealTemp('omx-state-paths-');
|
|
195
278
|
try {
|
|
196
279
|
const sessionsRoot = join(getBaseStateDir(wd), 'sessions');
|
|
197
280
|
await mkdir(join(sessionsRoot, 'sess1'), { recursive: true });
|
|
@@ -208,7 +291,7 @@ describe('state paths', () => {
|
|
|
208
291
|
}
|
|
209
292
|
});
|
|
210
293
|
it('ignores invalid session directory names', async () => {
|
|
211
|
-
const wd = await
|
|
294
|
+
const wd = await mkRealTemp('omx-state-paths-');
|
|
212
295
|
try {
|
|
213
296
|
const sessionsRoot = join(getBaseStateDir(wd), 'sessions');
|
|
214
297
|
await mkdir(join(sessionsRoot, 'valid-session'), { recursive: true });
|
|
@@ -222,7 +305,7 @@ describe('state paths', () => {
|
|
|
222
305
|
}
|
|
223
306
|
});
|
|
224
307
|
it('reads session-sensitive runtime files from the current session without root fallback when requested', async () => {
|
|
225
|
-
const wd = await
|
|
308
|
+
const wd = await mkRealTemp('omx-state-paths-');
|
|
226
309
|
try {
|
|
227
310
|
const stateDir = getBaseStateDir(wd);
|
|
228
311
|
await mkdir(join(stateDir, 'sessions', 'sess-current'), { recursive: true });
|
|
@@ -238,7 +321,7 @@ describe('state paths', () => {
|
|
|
238
321
|
}
|
|
239
322
|
});
|
|
240
323
|
it('prefers OMX_SESSION_ID over stale session.json when resolving current session id', async () => {
|
|
241
|
-
const wd = await
|
|
324
|
+
const wd = await mkRealTemp('omx-state-paths-');
|
|
242
325
|
const previousSessionId = process.env.OMX_SESSION_ID;
|
|
243
326
|
try {
|
|
244
327
|
const stateDir = getBaseStateDir(wd);
|
|
@@ -260,7 +343,7 @@ describe('state paths', () => {
|
|
|
260
343
|
}
|
|
261
344
|
});
|
|
262
345
|
it('resolves current session from authoritative team state root without OMX_SESSION_ID', async () => {
|
|
263
|
-
const wd = await
|
|
346
|
+
const wd = await mkRealTemp('omx-state-paths-team-root-session-');
|
|
264
347
|
const teamStateRoot = join(wd, 'team-state-root');
|
|
265
348
|
const previousTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
|
|
266
349
|
const previousSessionId = process.env.OMX_SESSION_ID;
|
|
@@ -292,7 +375,7 @@ describe('state paths', () => {
|
|
|
292
375
|
}
|
|
293
376
|
});
|
|
294
377
|
it('does not resolve current session from source root when a team state root is authoritative', async () => {
|
|
295
|
-
const wd = await
|
|
378
|
+
const wd = await mkRealTemp('omx-state-paths-ignore-source-session-');
|
|
296
379
|
const teamStateRoot = join(wd, 'team-state-root');
|
|
297
380
|
const previousTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
|
|
298
381
|
const previousSessionId = process.env.OMX_SESSION_ID;
|