oh-my-opencode 4.14.0 → 4.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/codex-qa/references/isolation.md +1 -1
- package/README.md +1 -1
- package/dist/cli/doctor/framework/types.d.ts +2 -0
- package/dist/cli/index.js +149 -1426
- package/dist/cli-node/index.js +149 -1426
- package/dist/index.js +4255 -714
- package/dist/plugin/native-skills.d.ts +9 -0
- package/dist/plugin/system-transform.d.ts +1 -1
- package/dist/skills/lcx-contribute-bug-fix/SKILL.md +44 -13
- package/dist/skills/lcx-contribute-bug-fix/agents/openai.yaml +1 -1
- package/dist/skills/lcx-doctor/SKILL.md +48 -13
- package/dist/skills/lcx-doctor/agents/openai.yaml +1 -1
- package/dist/skills/lcx-report-bug/SKILL.md +44 -13
- package/dist/skills/lcx-report-bug/agents/openai.yaml +1 -1
- package/dist/skills/visual-qa/SKILL.md +5 -5
- package/dist/skills/visual-qa/references/agent-browser-setup.md +5 -4
- package/dist/skills/visual-qa/scripts/cli.test.ts +7 -2
- package/dist/skills/visual-qa/scripts/skill-prompt-contract.test.ts +10 -2
- package/dist/skills/visual-qa/scripts/visual-qa.mjs +530 -0
- package/dist/tools/delegate-task/skill-resolver.d.ts +1 -0
- package/dist/tools/delegate-task/types.d.ts +2 -0
- package/dist/tools/skill/types.d.ts +1 -0
- package/package.json +26 -13
- package/packages/git-bash-mcp/dist/cli.js +2 -0
- package/packages/lsp-daemon/dist/cli.js +15 -4
- package/packages/lsp-daemon/dist/ensure-daemon.d.ts +1 -0
- package/packages/lsp-daemon/dist/ensure-daemon.js +9 -3
- package/packages/lsp-daemon/dist/index.js +15 -4
- package/packages/lsp-daemon/dist/paths.d.ts +1 -0
- package/packages/lsp-daemon/dist/paths.js +6 -1
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +4 -7
- package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/src/setup.ts +1 -1
- package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +123 -10
- package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +123 -10
- package/packages/omo-codex/plugin/components/codegraph/package.json +1 -1
- package/packages/omo-codex/plugin/components/codegraph/src/mcp-bridge.ts +126 -9
- package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge.test.ts +141 -0
- package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
- package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
- package/packages/omo-codex/plugin/components/lsp/dist/cli.js +90 -25
- package/packages/omo-codex/plugin/components/lsp/dist/codex-hook.js +2 -0
- package/packages/omo-codex/plugin/components/lsp/dist/daemon-cli-path.d.ts +2 -0
- package/packages/omo-codex/plugin/components/lsp/dist/daemon-cli-path.js +56 -0
- package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
- package/packages/omo-codex/plugin/components/lsp/src/cli.ts +2 -5
- package/packages/omo-codex/plugin/components/lsp/src/codex-hook.ts +2 -0
- package/packages/omo-codex/plugin/components/lsp/src/daemon-cli-path.ts +65 -0
- package/packages/omo-codex/plugin/components/lsp/test/package-smoke.test.ts +6 -1
- package/packages/omo-codex/plugin/components/rules/dist/cli.js +8 -106
- package/packages/omo-codex/plugin/components/rules/package.json +1 -1
- package/packages/omo-codex/plugin/components/rules/src/static-injection.ts +4 -17
- package/packages/omo-codex/plugin/components/start-work-continuation/package.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/skills/teammode/SKILL.md +17 -5
- package/packages/omo-codex/plugin/components/teammode/skills/teammode/scripts/team-state.mjs +63 -1
- package/packages/omo-codex/plugin/components/teammode/skills/teammode/scripts/team.mjs +80 -50
- package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/agents/explorer.toml +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/directive.md +4 -12
- package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/test/package-smoke.test.ts +7 -33
- package/packages/omo-codex/plugin/components/ulw-loop/directive.md +4 -12
- package/packages/omo-codex/plugin/components/ulw-loop/package.json +4 -2
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/SKILL.md +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +4 -4
- package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/sample-quality-gate.json +7 -7
- package/packages/omo-codex/plugin/components/ulw-loop/test/package-smoke.test.ts +3 -1
- package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-doc.test.ts +2 -2
- package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate.test.ts +7 -6
- package/packages/omo-codex/plugin/package-lock.json +16 -56
- package/packages/omo-codex/plugin/package.json +1 -1
- package/packages/omo-codex/plugin/skills/lcx-contribute-bug-fix/SKILL.md +44 -13
- package/packages/omo-codex/plugin/skills/lcx-contribute-bug-fix/agents/openai.yaml +1 -1
- package/packages/omo-codex/plugin/skills/lcx-doctor/SKILL.md +48 -13
- package/packages/omo-codex/plugin/skills/lcx-doctor/agents/openai.yaml +1 -1
- package/packages/omo-codex/plugin/skills/lcx-report-bug/SKILL.md +44 -13
- package/packages/omo-codex/plugin/skills/lcx-report-bug/agents/openai.yaml +1 -1
- package/packages/omo-codex/plugin/skills/teammode/SKILL.md +17 -5
- package/packages/omo-codex/plugin/skills/teammode/scripts/team-state.mjs +63 -1
- package/packages/omo-codex/plugin/skills/teammode/scripts/team.mjs +80 -50
- package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +1 -1
- package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +4 -4
- package/packages/omo-codex/plugin/skills/visual-qa/SKILL.md +5 -5
- package/packages/omo-codex/plugin/skills/visual-qa/references/agent-browser-setup.md +5 -4
- package/packages/omo-codex/plugin/skills/visual-qa/scripts/visual-qa.mjs +530 -0
- package/packages/omo-codex/plugin/test/bootstrap-binlinks.test.mjs +38 -2
- package/packages/omo-codex/plugin/test/component-bin-names.test.mjs +12 -11
- package/packages/omo-codex/plugin/test/component-bundled-cli.test.mjs +99 -1
- package/packages/omo-codex/plugin/test/lcx-bug-skills.test.mjs +62 -8
- package/packages/omo-codex/plugin/test/node-install-surface.test.mjs +1 -0
- package/packages/omo-codex/plugin/test/teammode-archive-ambiguity.test.mjs +102 -0
- package/packages/omo-codex/plugin/test/teammode-safety-fixture.mjs +41 -1
- package/packages/omo-codex/plugin/test/teammode-safety.test.mjs +93 -0
- package/packages/omo-codex/scripts/install-bin-links.test.mjs +6 -0
- package/packages/omo-codex/scripts/install-cache-copy.test.mjs +28 -0
- package/packages/omo-codex/scripts/install-config-autonomous-features.test.mjs +43 -3
- package/packages/omo-codex/scripts/install-delegated-command.test.mjs +93 -0
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +103 -28
- package/packages/omo-codex/scripts/install-local-entrypoint.test.mjs +21 -2
- package/packages/omo-codex/scripts/install-local.test.mjs +0 -1
- package/packages/shared-skills/skills/lcx-contribute-bug-fix/SKILL.md +44 -13
- package/packages/shared-skills/skills/lcx-contribute-bug-fix/agents/openai.yaml +1 -1
- package/packages/shared-skills/skills/lcx-doctor/SKILL.md +48 -13
- package/packages/shared-skills/skills/lcx-doctor/agents/openai.yaml +1 -1
- package/packages/shared-skills/skills/lcx-report-bug/SKILL.md +44 -13
- package/packages/shared-skills/skills/lcx-report-bug/agents/openai.yaml +1 -1
- package/packages/shared-skills/skills/visual-qa/SKILL.md +5 -5
- package/packages/shared-skills/skills/visual-qa/references/agent-browser-setup.md +5 -4
- package/packages/shared-skills/skills/visual-qa/scripts/cli.test.ts +7 -2
- package/packages/shared-skills/skills/visual-qa/scripts/skill-prompt-contract.test.ts +10 -2
- package/packages/shared-skills/skills/visual-qa/scripts/visual-qa.mjs +530 -0
- package/dist/cli/sparkshell-appserver-websocket.d.ts +0 -4
- package/dist/cli/sparkshell-appserver.d.ts +0 -16
- package/dist/cli/sparkshell-condense.d.ts +0 -10
- package/dist/cli/sparkshell-parse.d.ts +0 -24
- package/dist/cli/sparkshell-session-context.d.ts +0 -20
- package/dist/cli/sparkshell-spark.d.ts +0 -23
- package/dist/cli/sparkshell.d.ts +0 -33
- package/dist/shared/sparkshell-awareness.d.ts +0 -5
- package/packages/omo-codex/plugin/components/rules/src/sparkshell-awareness.ts +0 -106
- package/packages/omo-codex/plugin/components/rules/test/sparkshell-awareness.test.ts +0 -355
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
-
import { chmodSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { readFile } from "node:fs/promises";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { join } from "node:path";
|
|
@@ -125,6 +125,54 @@ test("#given representative component hook payloads #when executed through dist
|
|
|
125
125
|
}
|
|
126
126
|
});
|
|
127
127
|
|
|
128
|
+
test("#given bundled LSP hook CLI in installed layout #when diagnostics run #then it spawns sibling daemon target", () => {
|
|
129
|
+
const tempRoot = mkdtempSync(join(tmpdir(), "omo-codex-lsp-installed-"));
|
|
130
|
+
try {
|
|
131
|
+
const lspDist = join(tempRoot, "components", "lsp", "dist");
|
|
132
|
+
const daemonDist = join(tempRoot, "components", "lsp-daemon", "dist");
|
|
133
|
+
const daemonDir = join(tempRoot, "daemon");
|
|
134
|
+
const invocationLog = join(tempRoot, "fake-daemon-invocations.jsonl");
|
|
135
|
+
mkdirSync(lspDist, { recursive: true });
|
|
136
|
+
mkdirSync(daemonDist, { recursive: true });
|
|
137
|
+
mkdirSync(join(tempRoot, "src"), { recursive: true });
|
|
138
|
+
writeFileSync(join(tempRoot, "package.json"), JSON.stringify({ type: "module" }));
|
|
139
|
+
writeFileSync(join(lspDist, "cli.js"), readFileSync(componentCliPath("lsp"), "utf8"));
|
|
140
|
+
writeFileSync(join(daemonDist, "package.json"), JSON.stringify({ type: "module", version: "0.1.0" }));
|
|
141
|
+
writeFakeLspDaemonCli(join(daemonDist, "cli.js"));
|
|
142
|
+
const editedFile = join(tempRoot, "src", "broken.c");
|
|
143
|
+
writeFileSync(editedFile, "int main(void) { return missing_symbol; }\n");
|
|
144
|
+
|
|
145
|
+
const result = spawnSync(process.execPath, [join(lspDist, "cli.js"), "hook", "post-tool-use"], {
|
|
146
|
+
cwd: tempRoot,
|
|
147
|
+
encoding: "utf8",
|
|
148
|
+
env: hookEnv(tempRoot, {
|
|
149
|
+
CODEX_LSP_DAEMON_DIR: daemonDir,
|
|
150
|
+
FAKE_LSP_DAEMON_LOG: invocationLog,
|
|
151
|
+
}),
|
|
152
|
+
input: JSON.stringify({
|
|
153
|
+
session_id: "bundled-lsp-hook",
|
|
154
|
+
tool_name: "Edit",
|
|
155
|
+
tool_input: { file_path: editedFile },
|
|
156
|
+
tool_response: { ok: true },
|
|
157
|
+
}),
|
|
158
|
+
timeout: HOOK_CLI_TEST_TIMEOUT_MS,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const daemonInvocations = existsSync(invocationLog) ? readFileSync(invocationLog, "utf8") : "";
|
|
162
|
+
const failureContext = `stdout: ${result.stdout}\nstderr: ${result.stderr}\ndaemon log: ${daemonInvocations}`;
|
|
163
|
+
assert.equal(result.status, 0, failureContext);
|
|
164
|
+
assert.equal(result.stderr, "", failureContext);
|
|
165
|
+
assert.notEqual(result.stdout, "", failureContext);
|
|
166
|
+
const parsed = JSON.parse(result.stdout);
|
|
167
|
+
assert.equal(parsed.decision, "block");
|
|
168
|
+
assert.match(parsed.reason, /error\[fake\] \(1\) at 1:1: Missing fake symbol\./);
|
|
169
|
+
assert.deepEqual(daemonInvocations.trim().split("\n").map(JSON.parse), [["daemon"]]);
|
|
170
|
+
assert.equal(existsSync(join(daemonDir, "v0.1.0", "daemon.log")), true);
|
|
171
|
+
} finally {
|
|
172
|
+
rmSync(tempRoot, { recursive: true, force: true });
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
|
|
128
176
|
test("#given malformed comment-checker stdin #when executed through dist CLI contract #then it exits successfully without output", () => {
|
|
129
177
|
// given
|
|
130
178
|
const tempRoot = mkdtempSync(join(tmpdir(), "omo-codex-cli-malformed-"));
|
|
@@ -276,6 +324,56 @@ function smokeImportComponent(component, event) {
|
|
|
276
324
|
}
|
|
277
325
|
}
|
|
278
326
|
|
|
327
|
+
function writeFakeLspDaemonCli(path) {
|
|
328
|
+
writeFileSync(
|
|
329
|
+
path,
|
|
330
|
+
[
|
|
331
|
+
"#!/usr/bin/env node",
|
|
332
|
+
'import { createHash } from "node:crypto";',
|
|
333
|
+
'import { appendFileSync, mkdirSync, readdirSync, unlinkSync } from "node:fs";',
|
|
334
|
+
'import { tmpdir } from "node:os";',
|
|
335
|
+
'import { dirname, join } from "node:path";',
|
|
336
|
+
'import { createServer } from "node:net";',
|
|
337
|
+
"",
|
|
338
|
+
"appendFileSync(process.env.FAKE_LSP_DAEMON_LOG, `${JSON.stringify(process.argv.slice(2))}\\n`);",
|
|
339
|
+
"const baseDir = process.env.CODEX_LSP_DAEMON_DIR;",
|
|
340
|
+
'const versionDirName = readdirSync(baseDir).find((entry) => entry.startsWith("v")) ?? "v0";',
|
|
341
|
+
"const version = versionDirName.slice(1);",
|
|
342
|
+
"const dir = join(baseDir, versionDirName);",
|
|
343
|
+
'const naturalSocket = join(dir, "daemon.sock");',
|
|
344
|
+
'const digest = createHash("sha256").update(dir).digest("hex").slice(0, 16);',
|
|
345
|
+
"const socketPath = process.platform === \"win32\"",
|
|
346
|
+
"\t? `\\\\\\\\.\\\\pipe\\\\omo-lsp-${version}-${digest}`",
|
|
347
|
+
"\t: naturalSocket.length < 100 ? naturalSocket : join(tmpdir(), `omo-lsp-${version}-${digest}.sock`);",
|
|
348
|
+
"if (process.platform !== \"win32\") {",
|
|
349
|
+
"\tmkdirSync(dirname(socketPath), { recursive: true });",
|
|
350
|
+
"\ttry { unlinkSync(socketPath); } catch {}",
|
|
351
|
+
"}",
|
|
352
|
+
"const server = createServer((socket) => {",
|
|
353
|
+
'\tlet raw = "";',
|
|
354
|
+
'\tsocket.setEncoding("utf8");',
|
|
355
|
+
'\tsocket.on("data", (chunk) => {',
|
|
356
|
+
"\t\traw += chunk;",
|
|
357
|
+
'\t\tif (!raw.includes("\\n")) return;',
|
|
358
|
+
"\t\tconst request = JSON.parse(raw.trim());",
|
|
359
|
+
"\t\tconst response = {",
|
|
360
|
+
'\t\t\tjsonrpc: "2.0",',
|
|
361
|
+
"\t\t\tid: request.id,",
|
|
362
|
+
"\t\t\tresult: {",
|
|
363
|
+
'\t\t\t\tcontent: [{ type: "text", text: "error[fake] (1) at 1:1: Missing fake symbol." }],',
|
|
364
|
+
"\t\t\t},",
|
|
365
|
+
"\t\t};",
|
|
366
|
+
'\t\tsocket.end(`${JSON.stringify(response)}\\n`);',
|
|
367
|
+
"\t\tserver.close(() => process.exit(0));",
|
|
368
|
+
"\t});",
|
|
369
|
+
"});",
|
|
370
|
+
"server.listen(socketPath);",
|
|
371
|
+
"setTimeout(() => process.exit(1), 10000).unref();",
|
|
372
|
+
"",
|
|
373
|
+
].join("\n"),
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
|
|
279
377
|
function hookEnv(tempRoot, extraEnv = {}) {
|
|
280
378
|
return {
|
|
281
379
|
...process.env,
|
|
@@ -4,11 +4,16 @@ import { dirname, join } from "node:path";
|
|
|
4
4
|
import test from "node:test";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
|
|
7
|
-
const
|
|
7
|
+
const pluginRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
8
|
+
const sharedSkillsRoot = join(pluginRoot, "..", "..", "shared-skills");
|
|
9
|
+
|
|
10
|
+
async function readSkill(name) {
|
|
11
|
+
return readFile(join(sharedSkillsRoot, "skills", name, "SKILL.md"), "utf8");
|
|
12
|
+
}
|
|
8
13
|
|
|
9
14
|
test("#given synced lcx-report-bug skill #when inspected #then it files LazyCodex bug issues with generated labels", async () => {
|
|
10
15
|
// given
|
|
11
|
-
const skillRoot = join(
|
|
16
|
+
const skillRoot = join(sharedSkillsRoot, "skills", "lcx-report-bug");
|
|
12
17
|
|
|
13
18
|
// when
|
|
14
19
|
const skill = await readFile(join(skillRoot, "SKILL.md"), "utf8");
|
|
@@ -20,14 +25,14 @@ test("#given synced lcx-report-bug skill #when inspected #then it files LazyCode
|
|
|
20
25
|
assert.match(skill, /gh pr create --repo openai\/codex/);
|
|
21
26
|
assert.doesNotMatch(skill, /gh pr create --repo "\$TARGET_REPO"/);
|
|
22
27
|
assert.doesNotMatch(skill, /gh pr create --repo code-yeongyu\/lazycodex/);
|
|
23
|
-
assert.match(interfaceMetadata, /display_name: "
|
|
28
|
+
assert.match(interfaceMetadata, /display_name: "lcx-report-bug \(omo\)"/);
|
|
24
29
|
assert.match(interfaceMetadata, /- "lazycodex bug"/);
|
|
25
30
|
assert.match(interfaceMetadata, /- "openai codex bug"/);
|
|
26
31
|
});
|
|
27
32
|
|
|
28
33
|
test("#given synced lcx-contribute-bug-fix skill #when inspected #then it delivers LazyCodex fixes as issues and upstream fixes as fork PRs", async () => {
|
|
29
34
|
// given
|
|
30
|
-
const skillRoot = join(
|
|
35
|
+
const skillRoot = join(sharedSkillsRoot, "skills", "lcx-contribute-bug-fix");
|
|
31
36
|
|
|
32
37
|
// when
|
|
33
38
|
const skill = await readFile(join(skillRoot, "SKILL.md"), "utf8");
|
|
@@ -40,14 +45,14 @@ test("#given synced lcx-contribute-bug-fix skill #when inspected #then it delive
|
|
|
40
45
|
assert.match(skill, /gh pr create --repo openai\/codex/);
|
|
41
46
|
assert.doesNotMatch(skill, /gh pr create --repo "\$TARGET_REPO"/);
|
|
42
47
|
assert.doesNotMatch(skill, /gh pr create --repo code-yeongyu\/lazycodex/);
|
|
43
|
-
assert.match(interfaceMetadata, /display_name: "
|
|
48
|
+
assert.match(interfaceMetadata, /display_name: "lcx-contribute-bug-fix \(omo\)"/);
|
|
44
49
|
assert.match(interfaceMetadata, /- "contribute a bug fix"/);
|
|
45
50
|
assert.match(interfaceMetadata, /- "fix bug pr"/);
|
|
46
51
|
});
|
|
47
52
|
|
|
48
|
-
test("#given synced lcx-doctor skill #when inspected #then it diagnoses installs against latest
|
|
53
|
+
test("#given synced lcx-doctor skill #when inspected #then it diagnoses installs against latest temp-root sources without mutating them", async () => {
|
|
49
54
|
// given
|
|
50
|
-
const skillRoot = join(
|
|
55
|
+
const skillRoot = join(sharedSkillsRoot, "skills", "lcx-doctor");
|
|
51
56
|
|
|
52
57
|
// when
|
|
53
58
|
const skill = await readFile(join(skillRoot, "SKILL.md"), "utf8");
|
|
@@ -55,7 +60,56 @@ test("#given synced lcx-doctor skill #when inspected #then it diagnoses installs
|
|
|
55
60
|
|
|
56
61
|
// then
|
|
57
62
|
assert.match(skill, /^---\r?\nname: lcx-doctor\r?\n/m);
|
|
58
|
-
assert.match(interfaceMetadata, /display_name: "\(
|
|
63
|
+
assert.match(interfaceMetadata, /display_name: "lcx-doctor \(omo\)"/);
|
|
59
64
|
assert.match(interfaceMetadata, /- "lazycodex doctor"/);
|
|
60
65
|
assert.match(interfaceMetadata, /- "lazycodex health check"/);
|
|
61
66
|
});
|
|
67
|
+
|
|
68
|
+
test("#given lcx source-sync skills #when inspected #then source caches are validated before reuse", async () => {
|
|
69
|
+
// given
|
|
70
|
+
const skillNames = ["lcx-doctor", "lcx-report-bug", "lcx-contribute-bug-fix"];
|
|
71
|
+
|
|
72
|
+
for (const skillName of skillNames) {
|
|
73
|
+
// when
|
|
74
|
+
const skill = await readSkill(skillName);
|
|
75
|
+
|
|
76
|
+
// then
|
|
77
|
+
assert.match(skill, /LAZYCODEX_SOURCE_ROOT="\$\{LAZYCODEX_SOURCE_ROOT:-\$\{TMPDIR:-\/tmp\}\/lazycodex-sources\}"/);
|
|
78
|
+
assert.match(skill, /git -C "\$DEST" rev-parse --is-inside-work-tree/);
|
|
79
|
+
assert.match(skill, /git -C "\$DEST" config --get remote\.origin\.url/);
|
|
80
|
+
assert.match(skill, /DEST\.corrupt\.\$\(date \+%Y%m%d%H%M%S\)/);
|
|
81
|
+
assert.doesNotMatch(skill, /if \[ ! -d "\$DEST\/\.git" \]; then/);
|
|
82
|
+
assert.doesNotMatch(skill, /sync_latest_source code-yeongyu\/lazycodex \/tmp\/lazycodex-source/);
|
|
83
|
+
assert.doesNotMatch(skill, /sync_latest_source openai\/codex \/tmp\/openai-codex-source/);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("#given lcx-doctor skill #when inspected #then aggregate hook validation follows the current manifest", async () => {
|
|
88
|
+
// given
|
|
89
|
+
const skill = await readSkill("lcx-doctor");
|
|
90
|
+
|
|
91
|
+
// then
|
|
92
|
+
assert.match(skill, /\.codex-plugin\/plugin\.json/);
|
|
93
|
+
assert.match(skill, /manifest declares a `hooks` array/);
|
|
94
|
+
assert.match(skill, /validate every direct hook path declared by the manifest/);
|
|
95
|
+
assert.match(skill, /`hooks\/hooks\.json` only when the manifest declares it/);
|
|
96
|
+
assert.match(skill, /do not require retired paths such as `components\/workflow-selector`/);
|
|
97
|
+
assert.match(skill, /`hooks\/user-prompt-submit-selecting-lazycodex-workflow\.json` unless the current manifest declares them/);
|
|
98
|
+
assert.doesNotMatch(skill, /Plugin payload present and non-empty: `hooks\/hooks\.json`/);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("#given lcx-doctor skill #when inspected #then runtime probes and materialized payloads distinguish expected rewrites from missing files", async () => {
|
|
102
|
+
// given
|
|
103
|
+
const skill = await readSkill("lcx-doctor");
|
|
104
|
+
|
|
105
|
+
// then
|
|
106
|
+
assert.match(skill, /manifest-declared runtime payload/);
|
|
107
|
+
assert.match(skill, /`dist\/cli\/index\.js` and `dist\/cli-node\/index\.js`/);
|
|
108
|
+
assert.match(skill, /install-time materialization rewrites as expected/);
|
|
109
|
+
assert.match(skill, /absolute `\.mcp\.json` runtime paths/);
|
|
110
|
+
assert.match(skill, /Missing or zero-byte rewritten targets are FAIL/);
|
|
111
|
+
assert.match(skill, /Use the configured Codex default model/);
|
|
112
|
+
assert.match(skill, /unless the user explicitly passed a model override/);
|
|
113
|
+
assert.match(skill, /never force a guessed\/rejected model such as `gpt-5\.5-codex-mini`/);
|
|
114
|
+
assert.doesNotMatch(skill, /--model gpt-5\.5-codex-mini/);
|
|
115
|
+
});
|
|
@@ -25,5 +25,6 @@ test("#given Codex Light install docs #when inspected #then lazycodex is npm-fir
|
|
|
25
25
|
for (const [path, text] of docs) {
|
|
26
26
|
assert.match(text, /\bnpx lazycodex-ai install\b/, `${path} should document the Node/npm install command`);
|
|
27
27
|
assert.doesNotMatch(text, /\bbunx lazycodex-ai\b/, `${path} should not require Bun for lazycodex`);
|
|
28
|
+
assert.doesNotMatch(text, /npx\s+--package\s+oh-my-openagent\s+omo\s+install/, `${path} should not recommend the Windows-broken --package install command`);
|
|
28
29
|
}
|
|
29
30
|
});
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import test from "node:test";
|
|
5
|
+
|
|
6
|
+
import { root } from "./aggregate-plugin-fixture.mjs";
|
|
7
|
+
import { cleanupTeamRoot, createTeamRoot, readTeamJson, runTeam } from "./teammode-safety-fixture.mjs";
|
|
8
|
+
|
|
9
|
+
const teammodeSkillPath = join("components", "teammode", "skills", "teammode", "SKILL.md");
|
|
10
|
+
|
|
11
|
+
test("#given Codex App thread ids can be ambiguous across hosts #when archive guidance is read #then it separates app-thread archival from team-state archival", () => {
|
|
12
|
+
const body = readFileSync(join(root, teammodeSkillPath), "utf8");
|
|
13
|
+
|
|
14
|
+
assert.match(
|
|
15
|
+
body,
|
|
16
|
+
/Ambiguous Codex thread id|ambiguous across hosts/i,
|
|
17
|
+
"archive guidance must name the Codex App multi-host ambiguity failure instead of only saying the archive tool is unavailable",
|
|
18
|
+
);
|
|
19
|
+
assert.match(
|
|
20
|
+
body,
|
|
21
|
+
/app-thread archival blocker/i,
|
|
22
|
+
"archive guidance must classify ambiguous app-thread ids as app-thread archival blockers",
|
|
23
|
+
);
|
|
24
|
+
assert.match(
|
|
25
|
+
body,
|
|
26
|
+
/record .*team log/i,
|
|
27
|
+
"archive guidance must require a durable team-log record when app-thread archival cannot be proven",
|
|
28
|
+
);
|
|
29
|
+
assert.match(
|
|
30
|
+
body,
|
|
31
|
+
/continue .*team-state archive/i,
|
|
32
|
+
"archive guidance must let the team-state archive proceed after recording the app-thread archival blocker",
|
|
33
|
+
);
|
|
34
|
+
assert.match(
|
|
35
|
+
body,
|
|
36
|
+
/archive --note/i,
|
|
37
|
+
"archive guidance must route ambiguous app-thread archive blockers into archive --note evidence",
|
|
38
|
+
);
|
|
39
|
+
assert.match(
|
|
40
|
+
body,
|
|
41
|
+
/Do not delete/i,
|
|
42
|
+
"archive guidance must keep evidence available until ambiguous app-thread archival is resolved",
|
|
43
|
+
);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("#given app-thread archival is blocked by an ambiguous id #when archive runs with a note #then team state is archived and keeps the blocker in the log", () => {
|
|
47
|
+
const tempRoot = createTeamRoot("omo-codex-teammode-archive-ambiguity-");
|
|
48
|
+
try {
|
|
49
|
+
runTeam(tempRoot, "init", "--name", "ArchiveQA", "--session-name", "cleanup", "--session", "archive-ambiguity");
|
|
50
|
+
runTeam(
|
|
51
|
+
tempRoot,
|
|
52
|
+
"add-member",
|
|
53
|
+
"--team",
|
|
54
|
+
"archive-ambiguity",
|
|
55
|
+
"--id",
|
|
56
|
+
"A",
|
|
57
|
+
"--name",
|
|
58
|
+
"local",
|
|
59
|
+
"--focus",
|
|
60
|
+
"local host member",
|
|
61
|
+
"--lens",
|
|
62
|
+
"area",
|
|
63
|
+
"--deliverable",
|
|
64
|
+
"local notes",
|
|
65
|
+
);
|
|
66
|
+
runTeam(
|
|
67
|
+
tempRoot,
|
|
68
|
+
"add-member",
|
|
69
|
+
"--team",
|
|
70
|
+
"archive-ambiguity",
|
|
71
|
+
"--id",
|
|
72
|
+
"B",
|
|
73
|
+
"--name",
|
|
74
|
+
"remote",
|
|
75
|
+
"--focus",
|
|
76
|
+
"remote host member",
|
|
77
|
+
"--lens",
|
|
78
|
+
"perspective",
|
|
79
|
+
"--deliverable",
|
|
80
|
+
"remote notes",
|
|
81
|
+
);
|
|
82
|
+
runTeam(tempRoot, "bind-thread", "--team", "archive-ambiguity", "--id", "A", "--thread", "duplicate-thread-id");
|
|
83
|
+
runTeam(tempRoot, "bind-thread", "--team", "archive-ambiguity", "--id", "B", "--thread", "remote-thread-id");
|
|
84
|
+
|
|
85
|
+
const blocker = "app-thread archive blocker: Ambiguous Codex thread id duplicate-thread-id; matching hosts: local, remote-ssh-discovered:m5";
|
|
86
|
+
runTeam(tempRoot, "archive", "--team", "archive-ambiguity", "--note", blocker);
|
|
87
|
+
const team = readTeamJson(tempRoot, "archive-ambiguity");
|
|
88
|
+
|
|
89
|
+
assert.equal(team.status, "archived");
|
|
90
|
+
assert.deepEqual(
|
|
91
|
+
team.members.map((member) => member.status),
|
|
92
|
+
["archived", "archived"],
|
|
93
|
+
);
|
|
94
|
+
assert.equal(
|
|
95
|
+
team.log.some((entry) => entry.event === "archive" && entry.detail === blocker),
|
|
96
|
+
true,
|
|
97
|
+
"team log must preserve the app-thread archival blocker after durable team-state archival",
|
|
98
|
+
);
|
|
99
|
+
} finally {
|
|
100
|
+
cleanupTeamRoot(tempRoot);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
-
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
3
3
|
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { join } from "node:path";
|
|
@@ -42,6 +42,46 @@ export function runTeamRaw(cwd, ...args) {
|
|
|
42
42
|
});
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
export function runTeamRawWithEnv(cwd, env, ...args) {
|
|
46
|
+
return spawnSync(process.execPath, [teamScript, ...args], {
|
|
47
|
+
cwd,
|
|
48
|
+
encoding: "utf8",
|
|
49
|
+
env: { ...process.env, ...env },
|
|
50
|
+
timeout: 10_000,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function runTeamAsync(cwd, ...args) {
|
|
55
|
+
return new Promise((resolve, reject) => {
|
|
56
|
+
const child = spawn(process.execPath, [teamScript, ...args], {
|
|
57
|
+
cwd,
|
|
58
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
59
|
+
});
|
|
60
|
+
let stdout = "";
|
|
61
|
+
let stderr = "";
|
|
62
|
+
const timer = setTimeout(() => {
|
|
63
|
+
child.kill("SIGKILL");
|
|
64
|
+
reject(new Error(`team.mjs ${args.join(" ")} timed out`));
|
|
65
|
+
}, 10_000);
|
|
66
|
+
child.stdout.setEncoding("utf8");
|
|
67
|
+
child.stderr.setEncoding("utf8");
|
|
68
|
+
child.stdout.on("data", (chunk) => {
|
|
69
|
+
stdout += chunk;
|
|
70
|
+
});
|
|
71
|
+
child.stderr.on("data", (chunk) => {
|
|
72
|
+
stderr += chunk;
|
|
73
|
+
});
|
|
74
|
+
child.on("error", (error) => {
|
|
75
|
+
clearTimeout(timer);
|
|
76
|
+
reject(error);
|
|
77
|
+
});
|
|
78
|
+
child.on("close", (status, signal) => {
|
|
79
|
+
clearTimeout(timer);
|
|
80
|
+
resolve({ status, signal, stdout, stderr });
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
45
85
|
export function symlinkOrSkip(t, target, path, type) {
|
|
46
86
|
try {
|
|
47
87
|
symlinkSync(target, path, type);
|
|
@@ -10,7 +10,9 @@ import {
|
|
|
10
10
|
createTeamRoot,
|
|
11
11
|
readTeamJson,
|
|
12
12
|
runTeam,
|
|
13
|
+
runTeamAsync,
|
|
13
14
|
runTeamRaw,
|
|
15
|
+
runTeamRawWithEnv,
|
|
14
16
|
symlinkOrSkip,
|
|
15
17
|
teamDir,
|
|
16
18
|
teamJsonPath,
|
|
@@ -82,6 +84,97 @@ test("#given member A exists #when add-member receives A with trailing space #th
|
|
|
82
84
|
}
|
|
83
85
|
});
|
|
84
86
|
|
|
87
|
+
test("#given two add-member commands run concurrently #then both successful mutations are preserved", async () => {
|
|
88
|
+
const tempRoot = createTeamRoot("omo-codex-teammode-concurrent-add-");
|
|
89
|
+
try {
|
|
90
|
+
runTeam(tempRoot, "init", "--name", "Concurrent", "--session-name", "Members", "--session", "safe-concurrent-add");
|
|
91
|
+
|
|
92
|
+
const [alpha, beta] = await Promise.all([
|
|
93
|
+
runTeamAsync(
|
|
94
|
+
tempRoot,
|
|
95
|
+
"add-member",
|
|
96
|
+
"--team",
|
|
97
|
+
"safe-concurrent-add",
|
|
98
|
+
"--id",
|
|
99
|
+
"A",
|
|
100
|
+
"--name",
|
|
101
|
+
"alpha",
|
|
102
|
+
"--focus",
|
|
103
|
+
"alpha",
|
|
104
|
+
"--lens",
|
|
105
|
+
"area",
|
|
106
|
+
"--deliverable",
|
|
107
|
+
"first",
|
|
108
|
+
),
|
|
109
|
+
runTeamAsync(
|
|
110
|
+
tempRoot,
|
|
111
|
+
"add-member",
|
|
112
|
+
"--team",
|
|
113
|
+
"safe-concurrent-add",
|
|
114
|
+
"--id",
|
|
115
|
+
"B",
|
|
116
|
+
"--name",
|
|
117
|
+
"beta",
|
|
118
|
+
"--focus",
|
|
119
|
+
"beta",
|
|
120
|
+
"--lens",
|
|
121
|
+
"perspective",
|
|
122
|
+
"--deliverable",
|
|
123
|
+
"second",
|
|
124
|
+
),
|
|
125
|
+
]);
|
|
126
|
+
const team = readTeamJson(tempRoot, "safe-concurrent-add");
|
|
127
|
+
|
|
128
|
+
assert.equal(alpha.status, 0, alpha.stderr);
|
|
129
|
+
assert.equal(beta.status, 0, beta.stderr);
|
|
130
|
+
assert.deepEqual(
|
|
131
|
+
team.members.map((member) => member.id).sort(),
|
|
132
|
+
["A", "B"],
|
|
133
|
+
);
|
|
134
|
+
assert.equal(team.log.filter((entry) => entry.event === "add-member").length, 2);
|
|
135
|
+
} finally {
|
|
136
|
+
cleanupTeamRoot(tempRoot);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test("#given another command holds the team lock #when add-member times out #then it fails without mutating team state", () => {
|
|
141
|
+
const tempRoot = createTeamRoot("omo-codex-teammode-lock-timeout-");
|
|
142
|
+
try {
|
|
143
|
+
runTeam(tempRoot, "init", "--name", "Locked", "--session-name", "Members", "--session", "safe-lock-timeout");
|
|
144
|
+
const lockDir = join(teamDir(tempRoot, "safe-lock-timeout"), ".team.lock");
|
|
145
|
+
mkdirSync(lockDir);
|
|
146
|
+
writeFileSync(
|
|
147
|
+
join(lockDir, "owner.json"),
|
|
148
|
+
`${JSON.stringify({ command: "held-test", pid: 12345, createdAt: "2026-06-30T00:00:00.000Z" }, null, 2)}\n`,
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
const result = runTeamRawWithEnv(
|
|
152
|
+
tempRoot,
|
|
153
|
+
{ OMO_TEAMMODE_LOCK_TIMEOUT_MS: "25", OMO_TEAMMODE_LOCK_RETRY_MS: "5" },
|
|
154
|
+
"add-member",
|
|
155
|
+
"--team",
|
|
156
|
+
"safe-lock-timeout",
|
|
157
|
+
"--id",
|
|
158
|
+
"A",
|
|
159
|
+
"--name",
|
|
160
|
+
"alpha",
|
|
161
|
+
"--focus",
|
|
162
|
+
"alpha",
|
|
163
|
+
"--lens",
|
|
164
|
+
"area",
|
|
165
|
+
"--deliverable",
|
|
166
|
+
"first",
|
|
167
|
+
);
|
|
168
|
+
const team = readTeamJson(tempRoot, "safe-lock-timeout");
|
|
169
|
+
|
|
170
|
+
assert.notEqual(result.status, 0);
|
|
171
|
+
assert.match(result.stderr, /team state is locked by held-test, pid 12345/);
|
|
172
|
+
assert.deepEqual(team.members, []);
|
|
173
|
+
} finally {
|
|
174
|
+
cleanupTeamRoot(tempRoot);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
85
178
|
test("#given member focus already exists #when add-member receives same focus with different spacing and case #then state is not partially mutated", () => {
|
|
86
179
|
const tempRoot = createTeamRoot("omo-codex-teammode-duplicate-focus-");
|
|
87
180
|
try {
|
|
@@ -247,6 +247,8 @@ test("#given nested component declares reserved omo bin #when linking bins #then
|
|
|
247
247
|
bin: {
|
|
248
248
|
omo: "./dist/cli.js",
|
|
249
249
|
"omo-ulw-loop": "./dist/cli.js",
|
|
250
|
+
ulw: "./dist/cli.js",
|
|
251
|
+
"ulw-loop": "./dist/cli.js",
|
|
250
252
|
},
|
|
251
253
|
});
|
|
252
254
|
await writeFile(join(componentRoot, "dist", "cli.js"), "#!/usr/bin/env node\n");
|
|
@@ -255,9 +257,13 @@ test("#given nested component declares reserved omo bin #when linking bins #then
|
|
|
255
257
|
|
|
256
258
|
assert.deepEqual(linked, [
|
|
257
259
|
{ name: "omo-ulw-loop", path: join(binDir, "omo-ulw-loop"), target: join(componentRoot, "dist", "cli.js") },
|
|
260
|
+
{ name: "ulw", path: join(binDir, "ulw"), target: join(componentRoot, "dist", "cli.js") },
|
|
261
|
+
{ name: "ulw-loop", path: join(binDir, "ulw-loop"), target: join(componentRoot, "dist", "cli.js") },
|
|
258
262
|
]);
|
|
259
263
|
await assert.rejects(readlink(join(binDir, "omo")));
|
|
260
264
|
assert.equal(await readlink(join(binDir, "omo-ulw-loop")), join(componentRoot, "dist", "cli.js"));
|
|
265
|
+
assert.equal(await readlink(join(binDir, "ulw")), join(componentRoot, "dist", "cli.js"));
|
|
266
|
+
assert.equal(await readlink(join(binDir, "ulw-loop")), join(componentRoot, "dist", "cli.js"));
|
|
261
267
|
});
|
|
262
268
|
|
|
263
269
|
test("#given stale managed ulw-loop omo symlink #when linking bins #then removes it without touching user-owned omo", async () => {
|
|
@@ -138,6 +138,34 @@ test("#given npm creates workspace bin shims in the cache #when caching plugin #
|
|
|
138
138
|
assert.equal(await readFile(join(installed.path, "node_modules", ".bin", "other-tool.cmd"), "utf8"), "@echo off\r\necho preserved\r\n");
|
|
139
139
|
});
|
|
140
140
|
|
|
141
|
+
test("#given packaged root CLI runtimes #when caching omo plugin #then root dist is materialized into the plugin cache", async () => {
|
|
142
|
+
// given
|
|
143
|
+
const root = await makeTempDir();
|
|
144
|
+
const repoRoot = join(root, "repo");
|
|
145
|
+
const codexHome = join(root, "codex-home");
|
|
146
|
+
const sourceRoot = join(repoRoot, "packages", "omo-codex", "plugin");
|
|
147
|
+
await mkdir(sourceRoot, { recursive: true });
|
|
148
|
+
await mkdir(join(repoRoot, "dist", "cli"), { recursive: true });
|
|
149
|
+
await mkdir(join(repoRoot, "dist", "cli-node"), { recursive: true });
|
|
150
|
+
await writeFile(join(sourceRoot, "package.json"), JSON.stringify({ name: "@scope/omo", version: "0.1.0" }));
|
|
151
|
+
await writeFile(join(repoRoot, "dist", "cli", "index.js"), "console.log('omo')\n");
|
|
152
|
+
await writeFile(join(repoRoot, "dist", "cli-node", "index.js"), "console.log('omo node')\n");
|
|
153
|
+
|
|
154
|
+
// when
|
|
155
|
+
const installed = await installCachedPlugin({
|
|
156
|
+
codexHome,
|
|
157
|
+
marketplaceName: "sisyphuslabs",
|
|
158
|
+
name: "omo",
|
|
159
|
+
sourcePath: sourceRoot,
|
|
160
|
+
version: "0.1.0",
|
|
161
|
+
runCommand: async () => {},
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// then
|
|
165
|
+
assert.equal(await readFile(join(installed.path, "dist", "cli", "index.js"), "utf8"), "console.log('omo')\n");
|
|
166
|
+
assert.equal(await readFile(join(installed.path, "dist", "cli-node", "index.js"), "utf8"), "console.log('omo node')\n");
|
|
167
|
+
});
|
|
168
|
+
|
|
141
169
|
test("#given existing cache #when npm install fails #then previous active cache is preserved", async () => {
|
|
142
170
|
// given
|
|
143
171
|
const root = await makeTempDir();
|
|
@@ -6,7 +6,7 @@ import test from "node:test";
|
|
|
6
6
|
|
|
7
7
|
import { updateCodexConfig } from "./install-dist/install-local.mjs";
|
|
8
8
|
|
|
9
|
-
const ALWAYS_ON_FEATURES = ["plugins", "plugin_hooks", "multi_agent"
|
|
9
|
+
const ALWAYS_ON_FEATURES = ["plugins", "plugin_hooks", "multi_agent"];
|
|
10
10
|
const AUTONOMOUS_PERMISSION_FEATURES = ["unified_exec", "goals"];
|
|
11
11
|
|
|
12
12
|
test("#given autonomous permissions requested #when script installer updates config #then enables Codex autonomy feature flags", async () => {
|
|
@@ -20,7 +20,6 @@ test("#given autonomous permissions requested #when script installer updates con
|
|
|
20
20
|
"",
|
|
21
21
|
"[features]",
|
|
22
22
|
"multi_agent = false",
|
|
23
|
-
"child_agents_md = false",
|
|
24
23
|
"unified_exec = false",
|
|
25
24
|
"goals = false",
|
|
26
25
|
"",
|
|
@@ -59,7 +58,6 @@ test("#given autonomous permissions disabled #when script installer updates conf
|
|
|
59
58
|
"",
|
|
60
59
|
"[features]",
|
|
61
60
|
"multi_agent = false",
|
|
62
|
-
"child_agents_md = false",
|
|
63
61
|
"unified_exec = false",
|
|
64
62
|
"goals = false",
|
|
65
63
|
"",
|
|
@@ -86,3 +84,45 @@ test("#given autonomous permissions disabled #when script installer updates conf
|
|
|
86
84
|
assert.match(content, new RegExp(`${featureName} = false`));
|
|
87
85
|
}
|
|
88
86
|
});
|
|
87
|
+
|
|
88
|
+
test("#given existing child_agents_md setting #when script installer updates config #then preserves it without stamping unsupported values", async () => {
|
|
89
|
+
// given
|
|
90
|
+
const root = await mkdtemp(join(tmpdir(), "omo-codex-script-config-child-agents-preserve-"));
|
|
91
|
+
const configPath = join(root, "config.toml");
|
|
92
|
+
await writeFile(configPath, ["[features]", "child_agents_md = false", ""].join("\n"));
|
|
93
|
+
|
|
94
|
+
// when
|
|
95
|
+
await updateCodexConfig({
|
|
96
|
+
configPath,
|
|
97
|
+
repoRoot: "/repo/packages/omo-codex",
|
|
98
|
+
marketplaceName: "debug",
|
|
99
|
+
marketplaceSource: { sourceType: "local", source: "/repo/packages/omo-codex" },
|
|
100
|
+
pluginNames: ["omo"],
|
|
101
|
+
autonomousPermissions: true,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// then
|
|
105
|
+
const content = await readFile(configPath, "utf8");
|
|
106
|
+
assert.match(content, /child_agents_md = false/);
|
|
107
|
+
assert.doesNotMatch(content, /child_agents_md = true/);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("#given config without child_agents_md #when script installer updates config #then does not add unsupported feature key", async () => {
|
|
111
|
+
// given
|
|
112
|
+
const root = await mkdtemp(join(tmpdir(), "omo-codex-script-config-child-agents-absent-"));
|
|
113
|
+
const configPath = join(root, "config.toml");
|
|
114
|
+
|
|
115
|
+
// when
|
|
116
|
+
await updateCodexConfig({
|
|
117
|
+
configPath,
|
|
118
|
+
repoRoot: "/repo/packages/omo-codex",
|
|
119
|
+
marketplaceName: "debug",
|
|
120
|
+
marketplaceSource: { sourceType: "local", source: "/repo/packages/omo-codex" },
|
|
121
|
+
pluginNames: ["omo"],
|
|
122
|
+
autonomousPermissions: true,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// then
|
|
126
|
+
const content = await readFile(configPath, "utf8");
|
|
127
|
+
assert.doesNotMatch(content, /child_agents_md/);
|
|
128
|
+
});
|