oh-my-opencode 4.5.12 → 4.6.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/.agents/skills/opencode-qa/SKILL.md +194 -0
- package/.agents/skills/opencode-qa/references/cli-commands.md +188 -0
- package/.agents/skills/opencode-qa/references/db-investigation.md +197 -0
- package/.agents/skills/opencode-qa/references/events-hooks.md +110 -0
- package/.agents/skills/opencode-qa/references/sdk.md +96 -0
- package/.agents/skills/opencode-qa/references/server-api.md +200 -0
- package/.agents/skills/opencode-qa/references/testing-harness.md +218 -0
- package/.agents/skills/opencode-qa/references/tui-tmux.md +52 -0
- package/.agents/skills/opencode-qa/scripts/db-session-by-id.sh +53 -0
- package/.agents/skills/opencode-qa/scripts/db-session-by-name.sh +57 -0
- package/.agents/skills/opencode-qa/scripts/db-session-by-text.sh +158 -0
- package/.agents/skills/opencode-qa/scripts/export-roundtrip.sh +57 -0
- package/.agents/skills/opencode-qa/scripts/lib/common.sh +216 -0
- package/.agents/skills/opencode-qa/scripts/server-smoke.sh +64 -0
- package/.agents/skills/opencode-qa/scripts/sse-hook-probe.sh +106 -0
- package/.agents/skills/opencode-qa/scripts/tui-smoke.sh +89 -0
- package/README.ja.md +13 -3
- package/README.ko.md +13 -3
- package/README.md +24 -14
- package/README.ru.md +13 -3
- package/README.zh-cn.md +13 -3
- package/bin/oh-my-opencode.js +4 -3
- package/bin/oh-my-opencode.test.ts +35 -7
- package/bin/platform.d.ts +1 -1
- package/bin/platform.js +4 -4
- package/bin/platform.test.ts +31 -9
- package/dist/cli/cleanup-command.d.ts +4 -0
- package/dist/cli/cleanup.d.ts +11 -0
- package/dist/cli/cli-program.d.ts +2 -1
- package/dist/cli/index.js +1837 -450
- package/dist/cli/install-codex/codex-cache.d.ts +1 -0
- package/dist/cli/install-codex/codex-cleanup-config.d.ts +6 -0
- package/dist/cli/install-codex/codex-cleanup.d.ts +21 -0
- package/dist/cli/install-codex/codex-config-mcp.d.ts +1 -0
- package/dist/cli/install-codex/codex-config-permissions.d.ts +1 -0
- package/dist/cli/install-codex/codex-config-reasoning.d.ts +1 -0
- package/dist/cli/install-codex/codex-config-toml.d.ts +2 -1
- package/dist/cli/install-codex/codex-installation-detection.d.ts +36 -0
- package/dist/cli/install-codex/codex-package-layout.d.ts +1 -0
- package/dist/cli/install-codex/codex-project-local-cleanup-best-effort.d.ts +7 -0
- package/dist/cli/install-codex/codex-project-local-cleanup.d.ts +35 -0
- package/dist/cli/install-codex/git-bash.d.ts +35 -0
- package/dist/cli/install-codex/index.d.ts +4 -0
- package/dist/cli/install-codex/toml-section-editor.d.ts +2 -0
- package/dist/cli/install-codex/types.d.ts +20 -0
- package/dist/cli/run/event-state.d.ts +1 -0
- package/dist/cli/run/poll-for-completion.d.ts +1 -0
- package/dist/cli/run/prompt-start.d.ts +7 -0
- package/dist/cli/star-request.d.ts +9 -0
- package/dist/config/schema/hooks.d.ts +0 -1
- package/dist/create-hooks.d.ts +0 -1
- package/dist/features/builtin-skills/skills/debugging.d.ts +2 -0
- package/dist/features/builtin-skills/skills/index.d.ts +1 -0
- package/dist/hooks/index.d.ts +0 -1
- package/dist/index.js +267 -114
- package/dist/plugin/hooks/create-core-hooks.d.ts +0 -1
- package/dist/plugin/hooks/create-session-hooks.d.ts +1 -2
- package/dist/plugin/messages-transform.d.ts +8 -1
- package/dist/plugin/user-abort-interrupted-recovery-guard.d.ts +6 -0
- package/dist/shared/prompt-async-gate/recent-dispatches.d.ts +14 -0
- package/dist/shared/prompt-async-gate/semantic-dedupe.d.ts +7 -0
- package/dist/shared/prompt-async-gate/session-idle-dispatch.d.ts +1 -0
- package/dist/shared/prompt-async-gate/timing.d.ts +1 -0
- package/dist/shared/prompt-async-gate/types.d.ts +2 -0
- package/dist/shared/prompt-async-gate.d.ts +1 -1
- package/package.json +22 -17
- package/packages/git-bash-mcp/dist/cli.js +367 -0
- package/packages/omo-codex/plugin/.mcp.json +11 -0
- package/packages/omo-codex/plugin/components/comment-checker/README.md +1 -1
- package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +29 -0
- package/packages/omo-codex/plugin/components/git-bash/package.json +23 -0
- package/packages/omo-codex/plugin/components/git-bash/src/cli.ts +33 -0
- package/packages/omo-codex/plugin/components/git-bash/src/codex-hook.ts +180 -0
- package/packages/omo-codex/plugin/components/git-bash/src/index.ts +10 -0
- package/packages/omo-codex/plugin/components/git-bash/test/codex-hook.test.ts +195 -0
- package/packages/omo-codex/plugin/components/git-bash/tsconfig.build.json +13 -0
- package/packages/omo-codex/plugin/components/git-bash/tsconfig.json +25 -0
- package/packages/omo-codex/plugin/components/lsp/README.md +1 -1
- package/packages/omo-codex/plugin/components/lsp/src/cli.ts +5 -5
- package/packages/omo-codex/plugin/components/lsp/src/codex-hook-cli.ts +33 -0
- package/packages/omo-codex/plugin/components/lsp/src/codex-hook.ts +19 -27
- package/packages/omo-codex/plugin/components/lsp/test/codex-hook-cli.test.ts +28 -0
- package/packages/omo-codex/plugin/components/lsp/test/codex-hook-errors.test.ts +55 -0
- package/packages/omo-codex/plugin/components/lsp/test/package-smoke.test.ts +7 -5
- package/packages/omo-codex/plugin/components/rules/README.md +1 -1
- package/packages/omo-codex/plugin/components/rules/bundled-rules/windows-git-bash.md +10 -0
- package/packages/omo-codex/plugin/components/rules/test/package-smoke.test.ts +3 -1
- package/packages/omo-codex/plugin/components/rules/test/windows-git-bash-bundled-rule.test.ts +97 -0
- package/packages/omo-codex/plugin/components/start-work-continuation/directive.md +5 -4
- package/packages/omo-codex/plugin/components/start-work-continuation/test/codex-hook.test.ts +22 -0
- package/packages/omo-codex/plugin/components/ultrawork/README.md +2 -2
- package/packages/omo-codex/plugin/components/ultrawork/agents/codex-ultrawork-reviewer.toml +1 -0
- package/packages/omo-codex/plugin/components/ultrawork/agents/librarian.toml +8 -7
- package/packages/omo-codex/plugin/components/ultrawork/agents/plan.toml +2 -1
- package/packages/omo-codex/plugin/components/ultrawork/directive.md +31 -5
- package/packages/omo-codex/plugin/components/ultrawork/test/codex-hook.test.ts +27 -4
- package/packages/omo-codex/plugin/components/ultrawork/test/package-smoke.test.ts +25 -0
- package/packages/omo-codex/plugin/components/ulw-loop/README.md +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/SKILL.md +27 -205
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +230 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/package-smoke.test.ts +102 -5
- package/packages/omo-codex/plugin/hooks/hooks.json +24 -2
- package/packages/omo-codex/plugin/package-lock.json +19 -0
- package/packages/omo-codex/plugin/package.json +3 -1
- package/packages/omo-codex/plugin/scripts/build-bundled-mcp-runtimes.mjs +16 -1
- package/packages/omo-codex/plugin/scripts/build-components.mjs +2 -1
- package/packages/omo-codex/plugin/scripts/sync-hook-status-messages.mjs +87 -0
- package/packages/omo-codex/plugin/skills/review-work/SKILL.md +27 -2
- package/packages/omo-codex/plugin/skills/start-work/SKILL.md +20 -0
- package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +27 -205
- package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +230 -0
- package/packages/omo-codex/plugin/test/aggregate.test.mjs +23 -8
- package/packages/omo-codex/plugin/test/hook-status-message.test.mjs +56 -11
- package/packages/omo-codex/plugin/test/install-time-build-runtime.test.mjs +34 -0
- package/packages/omo-codex/plugin/test/mcp-research-servers.test.mjs +21 -0
- package/packages/omo-codex/plugin/test/node-install-surface.test.mjs +48 -0
- package/packages/omo-codex/plugin/test/subagent-guidance.test.mjs +76 -0
- package/packages/omo-codex/plugin/test/sync-hook-status-messages.test.mjs +66 -0
- package/packages/omo-codex/plugin/test/sync-skills.test.mjs +32 -2
- package/packages/omo-codex/scripts/install/cache.mjs +5 -3
- package/packages/omo-codex/scripts/install/cli-args.mjs +112 -0
- package/packages/omo-codex/scripts/install/config.mjs +36 -1
- package/packages/omo-codex/scripts/install/delegated-command.mjs +25 -0
- package/packages/omo-codex/scripts/install/git-bash.mjs +99 -0
- package/packages/omo-codex/scripts/install/git-bash.test.mjs +174 -0
- package/packages/omo-codex/scripts/install/mcp-runtime-cache.mjs +5 -1
- package/packages/omo-codex/scripts/install/multi-agent-v2-config.mjs +7 -1
- package/packages/omo-codex/scripts/install/permissions.d.mts +1 -0
- package/packages/omo-codex/scripts/install/permissions.mjs +26 -0
- package/packages/omo-codex/scripts/install/project-local-cleanup.mjs +229 -0
- package/packages/omo-codex/scripts/install/reasoning-config.mjs +14 -0
- package/packages/omo-codex/scripts/install/source-package-build.mjs +20 -0
- package/packages/omo-codex/scripts/install/toml-editor.mjs +19 -2
- package/packages/omo-codex/scripts/install-cli-args.test.mjs +146 -0
- package/packages/omo-codex/scripts/install-config-autonomous.test.mjs +48 -0
- package/packages/omo-codex/scripts/install-config-reasoning.test.mjs +62 -0
- package/packages/omo-codex/scripts/install-config.test.mjs +206 -0
- package/packages/omo-codex/scripts/install-local-entrypoint.test.mjs +129 -0
- package/packages/omo-codex/scripts/install-local-git-bash-preflight.test.mjs +145 -0
- package/packages/omo-codex/scripts/install-local.mjs +91 -8
- package/packages/omo-codex/scripts/install-local.test.mjs +15 -0
- package/packages/omo-codex/scripts/install-mcp-runtime.test.mjs +60 -0
- package/packages/omo-codex/scripts/install-packaged-local.test.mjs +67 -0
- package/packages/omo-codex/scripts/install-project-local-cleanup.test.mjs +277 -0
- package/packages/shared-skills/skills/review-work/SKILL.md +27 -2
- package/packages/shared-skills/skills/start-work/SKILL.md +20 -0
- package/dist/hooks/context-window-monitor.d.ts +0 -19
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
-
import { readdir, readFile } from "node:fs/promises";
|
|
2
|
+
import { access, readdir, readFile } from "node:fs/promises";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import test from "node:test";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
@@ -46,18 +46,48 @@ async function readJson(relativePath) {
|
|
|
46
46
|
return JSON.parse(await readFile(join(root, relativePath), "utf8"));
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
async function exists(relativePath) {
|
|
50
|
+
try {
|
|
51
|
+
await access(join(root, relativePath));
|
|
52
|
+
return true;
|
|
53
|
+
} catch (error) {
|
|
54
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") return false;
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
49
59
|
async function readComponentHookManifests() {
|
|
50
60
|
const components = await readdir(join(root, "components"), { withFileTypes: true });
|
|
51
61
|
const manifests = [];
|
|
52
62
|
for (const entry of components) {
|
|
53
63
|
if (!entry.isDirectory()) continue;
|
|
54
64
|
const source = join("components", entry.name, "hooks", "hooks.json");
|
|
65
|
+
if (!(await exists(source))) continue;
|
|
55
66
|
const packageJson = await readJson(join("components", entry.name, "package.json"));
|
|
56
67
|
manifests.push({ source, version: packageJson.version, hooks: await readJson(source) });
|
|
57
68
|
}
|
|
58
69
|
return manifests.sort((left, right) => left.source.localeCompare(right.source));
|
|
59
70
|
}
|
|
60
71
|
|
|
72
|
+
async function readComponentVersions() {
|
|
73
|
+
const components = await readdir(join(root, "components"), { withFileTypes: true });
|
|
74
|
+
const versions = new Map();
|
|
75
|
+
for (const entry of components) {
|
|
76
|
+
if (!entry.isDirectory()) continue;
|
|
77
|
+
const packageJson = await readJson(join("components", entry.name, "package.json"));
|
|
78
|
+
versions.set(entry.name, packageJson.version);
|
|
79
|
+
}
|
|
80
|
+
return versions;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function hookOwnerVersion(hook, aggregateVersion, componentVersions) {
|
|
84
|
+
const command = hook.command;
|
|
85
|
+
for (const [componentName, version] of componentVersions.entries()) {
|
|
86
|
+
if (command.includes(`/components/${componentName}/dist/cli.js`)) return version;
|
|
87
|
+
}
|
|
88
|
+
return aggregateVersion;
|
|
89
|
+
}
|
|
90
|
+
|
|
61
91
|
function collectCommandHooks(hooks, source, version) {
|
|
62
92
|
const commandHooks = [];
|
|
63
93
|
const normalizedSource = source.replaceAll("\\", "/");
|
|
@@ -68,6 +98,7 @@ function collectCommandHooks(hooks, source, version) {
|
|
|
68
98
|
commandHooks.push({
|
|
69
99
|
id: `${normalizedSource}:${eventName}:${groupIndex}:${handlerIndex}`,
|
|
70
100
|
version,
|
|
101
|
+
command: handler.command,
|
|
71
102
|
statusMessage: handler.statusMessage,
|
|
72
103
|
});
|
|
73
104
|
});
|
|
@@ -88,6 +119,18 @@ test("#given hook status label #when formatting #then prefixes LazyCodex with ve
|
|
|
88
119
|
assert.equal(message, "LazyCodex(0.1.0): Checking Comments");
|
|
89
120
|
});
|
|
90
121
|
|
|
122
|
+
test("#given hook status label with blank version #when formatting #then prefixes LazyCodex with local version", () => {
|
|
123
|
+
// given
|
|
124
|
+
const version = " ";
|
|
125
|
+
const label = "Checking Comments";
|
|
126
|
+
|
|
127
|
+
// when
|
|
128
|
+
const message = formatLazyCodexHookStatusMessage(version, label);
|
|
129
|
+
|
|
130
|
+
// then
|
|
131
|
+
assert.equal(message, "LazyCodex(local): Checking Comments");
|
|
132
|
+
});
|
|
133
|
+
|
|
91
134
|
test("#given loose legacy status label #when normalizing #then removes OMO wording and title-cases label", () => {
|
|
92
135
|
// given
|
|
93
136
|
const version = "0.1.0";
|
|
@@ -104,15 +147,15 @@ test("#given loose legacy status label #when normalizing #then removes OMO wordi
|
|
|
104
147
|
|
|
105
148
|
test("#given aggregate comment-checker hook #when status is inspected #then it uses LazyCodex comments label", async () => {
|
|
106
149
|
// given
|
|
107
|
-
const aggregateVersion = (await readJson(".codex-plugin/plugin.json")).version;
|
|
108
150
|
const aggregateHooks = await readJson("hooks/hooks.json");
|
|
151
|
+
const componentVersions = await readComponentVersions();
|
|
109
152
|
|
|
110
153
|
// when
|
|
111
|
-
const hooks = collectCommandHooks(aggregateHooks, "hooks/hooks.json",
|
|
154
|
+
const hooks = collectCommandHooks(aggregateHooks, "hooks/hooks.json", "0.1.0");
|
|
112
155
|
const commentCheckerHook = hooks.find((hook) => hook.id === "hooks/hooks.json:PostToolUse:0:0");
|
|
113
156
|
|
|
114
157
|
// then
|
|
115
|
-
assert.equal(commentCheckerHook?.statusMessage, formatLazyCodexHookStatusMessage(
|
|
158
|
+
assert.equal(commentCheckerHook?.statusMessage, formatLazyCodexHookStatusMessage(componentVersions.get("comment-checker"), "Checking Comments"));
|
|
116
159
|
assert.doesNotMatch(JSON.stringify(aggregateHooks), /checking\s+OMO\s+comments/i);
|
|
117
160
|
});
|
|
118
161
|
|
|
@@ -121,18 +164,22 @@ test("#given aggregate and component hooks #when status messages are inspected #
|
|
|
121
164
|
const aggregateVersion = (await readJson(".codex-plugin/plugin.json")).version;
|
|
122
165
|
const aggregateHooks = await readJson("hooks/hooks.json");
|
|
123
166
|
const componentManifests = await readComponentHookManifests();
|
|
167
|
+
const componentVersions = await readComponentVersions();
|
|
124
168
|
|
|
125
169
|
// when
|
|
126
170
|
const commandHooks = [
|
|
127
|
-
...collectCommandHooks(aggregateHooks, "hooks/hooks.json", aggregateVersion)
|
|
171
|
+
...collectCommandHooks(aggregateHooks, "hooks/hooks.json", aggregateVersion).map((hook) => ({
|
|
172
|
+
...hook,
|
|
173
|
+
version: hookOwnerVersion(hook, aggregateVersion, componentVersions),
|
|
174
|
+
})),
|
|
128
175
|
...componentManifests.flatMap((manifest) => collectCommandHooks(manifest.hooks, manifest.source, manifest.version)),
|
|
129
176
|
];
|
|
130
177
|
const expectedLabels = new Map([...AGGREGATE_EXPECTED_LABELS, ...COMPONENT_EXPECTED_LABELS]);
|
|
131
178
|
const mismatches = commandHooks
|
|
132
179
|
.map((hook) => {
|
|
133
|
-
const label = expectedLabels.get(hook.id);
|
|
134
|
-
const expected = label === undefined ? undefined : formatLazyCodexHookStatusMessage(hook.version, label);
|
|
135
180
|
const parsed = parseLazyCodexHookStatusMessage(hook.statusMessage);
|
|
181
|
+
const label = parsed?.label;
|
|
182
|
+
const expected = label === undefined ? undefined : formatLazyCodexHookStatusMessage(hook.version, label);
|
|
136
183
|
return { ...hook, expected, parsed };
|
|
137
184
|
})
|
|
138
185
|
.filter((hook) => hook.expected === undefined || hook.statusMessage !== hook.expected || hook.parsed === null)
|
|
@@ -140,10 +187,8 @@ test("#given aggregate and component hooks #when status messages are inspected #
|
|
|
140
187
|
|
|
141
188
|
// then
|
|
142
189
|
assert.deepEqual(mismatches, []);
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
[...expectedLabels.keys()].sort(),
|
|
146
|
-
);
|
|
190
|
+
const actualLabels = new Set(commandHooks.map((hook) => parseLazyCodexHookStatusMessage(hook.statusMessage)?.label));
|
|
191
|
+
assert.deepEqual([...expectedLabels.values()].filter((label) => !actualLabels.has(label)), []);
|
|
147
192
|
for (const hook of commandHooks) {
|
|
148
193
|
assert.doesNotMatch(hook.statusMessage, /\bOMO\b/i);
|
|
149
194
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import test from "node:test";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const root = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
8
|
+
|
|
9
|
+
test("#given aggregate build scripts #when inspected #then install-time build does not invoke Bun", async () => {
|
|
10
|
+
// given
|
|
11
|
+
const buildComponentsScript = await readFile(join(root, "scripts", "build-components.mjs"), "utf8");
|
|
12
|
+
const buildBundledMcpRuntimesScript = await readFile(join(root, "scripts", "build-bundled-mcp-runtimes.mjs"), "utf8");
|
|
13
|
+
|
|
14
|
+
// when
|
|
15
|
+
const installTimeBuildScripts = [buildComponentsScript, buildBundledMcpRuntimesScript].join("\n");
|
|
16
|
+
|
|
17
|
+
// then
|
|
18
|
+
assert.doesNotMatch(installTimeBuildScripts, /spawnSync\("bun"/);
|
|
19
|
+
assert.doesNotMatch(installTimeBuildScripts, /\bbun\s+run\b/);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("#given aggregate build scripts #when inspected #then npm subprocesses resolve on Windows", async () => {
|
|
23
|
+
// given
|
|
24
|
+
const buildComponentsScript = await readFile(join(root, "scripts", "build-components.mjs"), "utf8");
|
|
25
|
+
const buildBundledMcpRuntimesScript = await readFile(join(root, "scripts", "build-bundled-mcp-runtimes.mjs"), "utf8");
|
|
26
|
+
|
|
27
|
+
// when
|
|
28
|
+
const installTimeBuildScripts = [buildComponentsScript, buildBundledMcpRuntimesScript].join("\n");
|
|
29
|
+
|
|
30
|
+
// then
|
|
31
|
+
assert.match(installTimeBuildScripts, /process\.platform === "win32"/);
|
|
32
|
+
assert.match(installTimeBuildScripts, /shell: process\.platform === "win32"/);
|
|
33
|
+
assert.doesNotMatch(installTimeBuildScripts, /npm\.cmd/);
|
|
34
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import test from "node:test";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const root = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
8
|
+
|
|
9
|
+
test("#given aggregate MCP config #when inspected #then registers research and structural-search MCPs", async () => {
|
|
10
|
+
// given
|
|
11
|
+
const mcp = JSON.parse(await readFile(join(root, ".mcp.json"), "utf8"));
|
|
12
|
+
|
|
13
|
+
// when
|
|
14
|
+
const serverNames = Object.keys(mcp.mcpServers).sort();
|
|
15
|
+
|
|
16
|
+
// then
|
|
17
|
+
assert.deepEqual(serverNames, ["ast_grep", "context7", "git_bash", "grep_app", "lsp"]);
|
|
18
|
+
assert.equal(mcp.mcpServers.grep_app.url, "https://mcp.grep.app");
|
|
19
|
+
assert.equal(mcp.mcpServers.context7.url, "https://mcp.context7.com/mcp");
|
|
20
|
+
assert.deepEqual(mcp.mcpServers.ast_grep.args, ["../../ast-grep-mcp/dist/cli.js", "mcp"]);
|
|
21
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import test from "node:test";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const pluginRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
8
|
+
const repoRoot = join(pluginRoot, "..", "..", "..");
|
|
9
|
+
|
|
10
|
+
test("#given Codex Light install docs #when inspected #then lazycodex is npm-first and Bun-free", async () => {
|
|
11
|
+
// given
|
|
12
|
+
const files = [
|
|
13
|
+
join(repoRoot, "README.md"),
|
|
14
|
+
join(repoRoot, "docs", "guide", "installation.md"),
|
|
15
|
+
join(repoRoot, "packages", "omo-codex", "README.md"),
|
|
16
|
+
join(repoRoot, "packages", "omo-codex", "MARKETPLACE.md"),
|
|
17
|
+
join(pluginRoot, "components", "ultrawork", "README.md"),
|
|
18
|
+
join(pluginRoot, "components", "ulw-loop", "README.md"),
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
// when
|
|
22
|
+
const docs = await Promise.all(files.map(async (path) => [path, await readFile(path, "utf8")]));
|
|
23
|
+
|
|
24
|
+
// then
|
|
25
|
+
for (const [path, text] of docs) {
|
|
26
|
+
assert.match(text, /\bnpx lazycodex-ai install\b/, `${path} should document the Node/npm install command`);
|
|
27
|
+
assert.doesNotMatch(text, /\bbunx lazycodex-ai\b/, `${path} should not require Bun for lazycodex`);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("#given cleanup troubleshooting docs #when inspected #then project-local cleanup and command delegation are documented", async () => {
|
|
32
|
+
// given
|
|
33
|
+
const files = [
|
|
34
|
+
join(repoRoot, "README.md"),
|
|
35
|
+
join(repoRoot, "docs", "guide", "installation.md"),
|
|
36
|
+
join(repoRoot, "packages", "omo-codex", "README.md"),
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
// when
|
|
40
|
+
const docs = await Promise.all(files.map(async (path) => [path, await readFile(path, "utf8")]));
|
|
41
|
+
|
|
42
|
+
// then
|
|
43
|
+
for (const [path, text] of docs) {
|
|
44
|
+
assert.match(text, /\bnpx lazycodex-ai cleanup\b/, `${path} should document the LazyCodex cleanup command`);
|
|
45
|
+
assert.match(text, /project-local .*\.codex\/config\.toml/i, `${path} should mention project-local config repair`);
|
|
46
|
+
assert.match(text, /\.codex.*\.omx|\.omx.*\.codex/s, `${path} should distinguish project-local .codex and .omx artifacts`);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import test from "node:test";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const root = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
8
|
+
|
|
9
|
+
const SKILLS = [
|
|
10
|
+
"review-work",
|
|
11
|
+
"start-work",
|
|
12
|
+
"ulw-loop",
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
const AGENT_FILES = [
|
|
16
|
+
"components/ultrawork/agents/codex-ultrawork-reviewer.toml",
|
|
17
|
+
"components/ultrawork/agents/plan.toml",
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
test("#given orchestration skills #when inspected #then Codex subagent delegation is hardened", async () => {
|
|
21
|
+
// given
|
|
22
|
+
const skillPaths = SKILLS.map((skillName) => join("skills", skillName, "SKILL.md"));
|
|
23
|
+
|
|
24
|
+
// when
|
|
25
|
+
const missing = [];
|
|
26
|
+
for (const skillPath of skillPaths) {
|
|
27
|
+
const text = await readFile(join(root, skillPath), "utf8");
|
|
28
|
+
if (
|
|
29
|
+
!/TASK:/.test(text) ||
|
|
30
|
+
!/fork_turns:\s*"none"/.test(text) ||
|
|
31
|
+
!/wait_agent.*signal, not proof/s.test(text) ||
|
|
32
|
+
!/one targeted followup/.test(text) ||
|
|
33
|
+
!/respawn.*smaller/s.test(text) ||
|
|
34
|
+
!/model.*reasoning_effort.*default agent/s.test(text) ||
|
|
35
|
+
!/Plan and reviewer agents may run for a long time/.test(text) ||
|
|
36
|
+
!/short wait_agent cycles/.test(text) ||
|
|
37
|
+
!/single long blocking wait/.test(text)
|
|
38
|
+
) {
|
|
39
|
+
missing.push(skillPath);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// then
|
|
44
|
+
assert.deepEqual(missing, []);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("#given ultrawork directive #when inspected #then reviewer fallback keeps an agent role", async () => {
|
|
48
|
+
// given
|
|
49
|
+
const directivePath = "components/ultrawork/directive.md";
|
|
50
|
+
|
|
51
|
+
// when
|
|
52
|
+
const text = await readFile(join(root, directivePath), "utf8");
|
|
53
|
+
|
|
54
|
+
// then
|
|
55
|
+
assert.doesNotMatch(text, /any `gpt-5\.2`\s+xhigh reviewer/);
|
|
56
|
+
assert.match(text, /codex-ultrawork-reviewer/);
|
|
57
|
+
assert.match(text, /agent_type.*worker/s);
|
|
58
|
+
assert.match(text, /model.*reasoning_effort.*default agent/s);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("#given ultrawork agents #when inspected #then inter-agent commentary is treated as assignments", async () => {
|
|
62
|
+
// given
|
|
63
|
+
const agentPaths = AGENT_FILES;
|
|
64
|
+
|
|
65
|
+
// when
|
|
66
|
+
const missing = [];
|
|
67
|
+
for (const agentPath of agentPaths) {
|
|
68
|
+
const text = await readFile(join(root, agentPath), "utf8");
|
|
69
|
+
if (!/TASK:|active review assignment/.test(text) || !/context|commentary/.test(text)) {
|
|
70
|
+
missing.push(agentPath);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// then
|
|
75
|
+
assert.deepEqual(missing, []);
|
|
76
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdtemp, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import test from "node:test";
|
|
6
|
+
|
|
7
|
+
import { syncHookStatusMessages } from "../scripts/sync-hook-status-messages.mjs";
|
|
8
|
+
|
|
9
|
+
async function writeJson(path, value) {
|
|
10
|
+
await writeFile(path, `${JSON.stringify(value, null, "\t")}\n`);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function readJson(path) {
|
|
14
|
+
return JSON.parse(await readFile(path, "utf8"));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
test("#given a component without hooks #when hook status messages sync #then build-time version sync skips it", async () => {
|
|
18
|
+
// given
|
|
19
|
+
const root = await mkdtemp(join(tmpdir(), "omo-codex-hook-status-"));
|
|
20
|
+
await mkdir(join(root, ".codex-plugin"), { recursive: true });
|
|
21
|
+
await mkdir(join(root, "hooks"), { recursive: true });
|
|
22
|
+
await mkdir(join(root, "components", "comment-checker", "hooks"), { recursive: true });
|
|
23
|
+
await mkdir(join(root, "components", "git-bash"), { recursive: true });
|
|
24
|
+
await writeJson(join(root, ".codex-plugin", "plugin.json"), { version: "0.1.0" });
|
|
25
|
+
await writeJson(join(root, "components", "comment-checker", "package.json"), { version: "0.1.1" });
|
|
26
|
+
await writeJson(join(root, "components", "git-bash", "package.json"), { version: "0.3.0" });
|
|
27
|
+
await writeJson(join(root, "hooks", "hooks.json"), {
|
|
28
|
+
hooks: {
|
|
29
|
+
PostToolUse: [
|
|
30
|
+
{
|
|
31
|
+
hooks: [
|
|
32
|
+
{
|
|
33
|
+
type: "command",
|
|
34
|
+
command: 'node "${PLUGIN_ROOT}/components/comment-checker/dist/cli.js" hook post-tool-use',
|
|
35
|
+
statusMessage: "LazyCodex(0.1.0): Checking Comments",
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
await writeJson(join(root, "components", "comment-checker", "hooks", "hooks.json"), {
|
|
43
|
+
hooks: {
|
|
44
|
+
PostToolUse: [
|
|
45
|
+
{
|
|
46
|
+
hooks: [
|
|
47
|
+
{
|
|
48
|
+
type: "command",
|
|
49
|
+
command: 'node "${PLUGIN_ROOT}/dist/cli.js" hook post-tool-use',
|
|
50
|
+
statusMessage: "LazyCodex(0.1.0): Checking Comments",
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// when
|
|
59
|
+
await syncHookStatusMessages(root);
|
|
60
|
+
|
|
61
|
+
// then
|
|
62
|
+
const aggregateHooks = await readJson(join(root, "hooks", "hooks.json"));
|
|
63
|
+
const componentHooks = await readJson(join(root, "components", "comment-checker", "hooks", "hooks.json"));
|
|
64
|
+
assert.equal(aggregateHooks.hooks.PostToolUse[0].hooks[0].statusMessage, "LazyCodex(0.1.1): Checking Comments");
|
|
65
|
+
assert.equal(componentHooks.hooks.PostToolUse[0].hooks[0].statusMessage, "LazyCodex(0.1.1): Checking Comments");
|
|
66
|
+
});
|
|
@@ -7,6 +7,7 @@ import { sharedSkillsRootPath } from "@oh-my-opencode/shared-skills";
|
|
|
7
7
|
|
|
8
8
|
const root = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
9
9
|
const repoRoot = join(root, "..", "..", "..");
|
|
10
|
+
const CONTEXT_PRESSURE_SKILL_BUDGET_BYTES = 25_000;
|
|
10
11
|
|
|
11
12
|
const expectedSkills = [
|
|
12
13
|
"comment-checker",
|
|
@@ -154,8 +155,12 @@ test("#given synced ulw-loop skill #when Codex hint metadata is inspected #then
|
|
|
154
155
|
|
|
155
156
|
test("#given synced ulw-loop skill #when worker guidance is inspected #then context-hygiene guidance matches the source", async () => {
|
|
156
157
|
// given
|
|
157
|
-
const sourceSkill = await readFile(
|
|
158
|
+
const sourceSkill = await readFile(
|
|
159
|
+
join(root, "components", "ulw-loop", "skills", "ulw-loop", "references", "full-workflow.md"),
|
|
160
|
+
"utf8",
|
|
161
|
+
);
|
|
158
162
|
const syncedSkill = await readFile(join(root, "skills", "ulw-loop", "SKILL.md"), "utf8");
|
|
163
|
+
const syncedWorkflow = await readFile(join(root, "skills", "ulw-loop", "references", "full-workflow.md"), "utf8");
|
|
159
164
|
const requiredPatterns = [
|
|
160
165
|
["list_agents polling guard", /list_agents/],
|
|
161
166
|
["status polling warning", /polling or status tool/],
|
|
@@ -164,13 +169,38 @@ test("#given synced ulw-loop skill #when worker guidance is inspected #then cont
|
|
|
164
169
|
["wait_agent completion path", /wait_agent.*completion/],
|
|
165
170
|
["targeted followups", /targeted followups only when needed/],
|
|
166
171
|
["close_agent cleanup", /close_agent.*after integrating each result/],
|
|
172
|
+
["long-running plan/reviewer background guidance", /Plan and reviewer agents may run for a long time/],
|
|
173
|
+
["bounded plan/reviewer polling", /short wait_agent cycles/],
|
|
174
|
+
["single long wait guard", /single long blocking wait/],
|
|
167
175
|
];
|
|
168
176
|
|
|
169
177
|
// when / then
|
|
170
178
|
for (const [label, pattern] of requiredPatterns) {
|
|
171
179
|
assert.match(sourceSkill, pattern, `source skill missing ${label}`);
|
|
172
|
-
assert.match(
|
|
180
|
+
assert.match(syncedWorkflow, pattern, `synced workflow missing ${label}`);
|
|
173
181
|
}
|
|
182
|
+
assert.match(syncedSkill, /references\/full-workflow\.md/);
|
|
183
|
+
assert.match(syncedSkill, /wait_agent/);
|
|
184
|
+
assert.match(syncedSkill, /close_agent/);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test("#given context-pressure-prone skills #when bundled for Codex #then the eagerly loaded payload stays budgeted", async () => {
|
|
188
|
+
// given
|
|
189
|
+
const skillsRoot = join(root, "skills");
|
|
190
|
+
const skillNames = ["debugging", "ulw-loop"];
|
|
191
|
+
|
|
192
|
+
// when
|
|
193
|
+
let totalBytes = 0;
|
|
194
|
+
for (const skillName of skillNames) {
|
|
195
|
+
const content = await readFile(join(skillsRoot, skillName, "SKILL.md"), "utf8");
|
|
196
|
+
totalBytes += Buffer.byteLength(content, "utf8");
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// then
|
|
200
|
+
assert.ok(
|
|
201
|
+
totalBytes <= CONTEXT_PRESSURE_SKILL_BUDGET_BYTES,
|
|
202
|
+
`debugging + ulw-loop eager payload is ${totalBytes} bytes, above ${CONTEXT_PRESSURE_SKILL_BUDGET_BYTES}`,
|
|
203
|
+
);
|
|
174
204
|
});
|
|
175
205
|
|
|
176
206
|
test("#given synced aggregate Codex skills #when they contain OpenCode orchestration examples #then Codex tool compatibility guidance is injected", async () => {
|
|
@@ -6,9 +6,11 @@ import { exists, isRecord } from "./utils.mjs";
|
|
|
6
6
|
import { COMMAND_SHIM_MARKER } from "./command-shim.mjs";
|
|
7
7
|
import { removeLegacyCodexComponentBins } from "./legacy-bins.mjs";
|
|
8
8
|
|
|
9
|
-
export async function installCachedPlugin({ codexHome, marketplaceName, name, runCommand, sourcePath, version }) {
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
export async function installCachedPlugin({ buildSource = true, codexHome, marketplaceName, name, runCommand, sourcePath, version }) {
|
|
10
|
+
if (buildSource) {
|
|
11
|
+
await maybeRunNpmInstall(sourcePath, runCommand);
|
|
12
|
+
await maybeRunNpmBuild(sourcePath, runCommand);
|
|
13
|
+
}
|
|
12
14
|
|
|
13
15
|
const targetPath = join(codexHome, "plugins", "cache", marketplaceName, name, version);
|
|
14
16
|
await replaceDirectory(sourcePath, targetPath, shouldCopyPluginPath);
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
const CODEX_ONLY_ERROR = "lazycodex-ai installs the Codex Light edition only. Use the omo installer for OpenCode or both-platform installs.";
|
|
2
|
+
const PASSTHROUGH_COMMANDS = new Set(["doctor", "cleanup", "get-local-version", "boulder", "refresh-model-capabilities", "run"]);
|
|
3
|
+
|
|
4
|
+
export function parseLazyCodexInstallCliArgs(argv) {
|
|
5
|
+
const args = [...argv];
|
|
6
|
+
if (args.length === 0) return { kind: "install", autonomousPermissions: undefined, repoRoot: undefined };
|
|
7
|
+
|
|
8
|
+
let repoRoot;
|
|
9
|
+
let command;
|
|
10
|
+
let dryRun = false;
|
|
11
|
+
let noTui = false;
|
|
12
|
+
let skipAuth = false;
|
|
13
|
+
let autonomousPermissions;
|
|
14
|
+
let index = 0;
|
|
15
|
+
while (index < args.length) {
|
|
16
|
+
const arg = args[index];
|
|
17
|
+
if (arg === "--help" || arg === "-h" || arg === "help") return { kind: "help" };
|
|
18
|
+
if (arg === "--version" || arg === "-v" || arg === "version") return { kind: "version" };
|
|
19
|
+
if (arg === "--dry-run") {
|
|
20
|
+
dryRun = true;
|
|
21
|
+
index += 1;
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (arg === "--no-tui") {
|
|
25
|
+
noTui = true;
|
|
26
|
+
index += 1;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (arg === "--skip-auth") {
|
|
30
|
+
skipAuth = true;
|
|
31
|
+
index += 1;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (arg === "--codex-autonomous") {
|
|
35
|
+
autonomousPermissions = true;
|
|
36
|
+
index += 1;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (arg === "--no-codex-autonomous") {
|
|
40
|
+
autonomousPermissions = false;
|
|
41
|
+
index += 1;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (arg === "--platform") {
|
|
45
|
+
const platform = readOptionValue(args, index, "--platform");
|
|
46
|
+
if (platform !== "codex") throw new Error(CODEX_ONLY_ERROR);
|
|
47
|
+
index += 2;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (typeof arg === "string" && arg.startsWith("--platform=")) {
|
|
51
|
+
const platform = arg.slice("--platform=".length);
|
|
52
|
+
if (platform.trim().length === 0) throw new Error("--platform requires a value");
|
|
53
|
+
if (platform !== "codex") throw new Error(CODEX_ONLY_ERROR);
|
|
54
|
+
index += 1;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (arg === "--repo-root") {
|
|
58
|
+
repoRoot = readOptionValue(args, index, "--repo-root");
|
|
59
|
+
index += 2;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (typeof arg === "string" && arg.startsWith("--repo-root=")) {
|
|
63
|
+
const value = arg.slice("--repo-root=".length);
|
|
64
|
+
if (value.trim().length === 0) throw new Error("--repo-root requires a path");
|
|
65
|
+
repoRoot = value;
|
|
66
|
+
index += 1;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (arg === "install" || arg === "setup") {
|
|
70
|
+
if (command !== undefined) throw new Error(`Unsupported lazycodex-ai install option: ${String(arg)}`);
|
|
71
|
+
command = "install";
|
|
72
|
+
index += 1;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (PASSTHROUGH_COMMANDS.has(arg)) {
|
|
76
|
+
return { kind: "command", command: arg, dryRun, args: args.slice(index + 1) };
|
|
77
|
+
}
|
|
78
|
+
if (command === undefined && typeof arg === "string" && !arg.startsWith("-")) {
|
|
79
|
+
throw new Error(`Unsupported lazycodex-ai command: ${String(arg)}`);
|
|
80
|
+
}
|
|
81
|
+
throw new Error(`Unsupported lazycodex-ai install option: ${String(arg)}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!dryRun) return { kind: "install", autonomousPermissions, repoRoot };
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
kind: "command",
|
|
88
|
+
command: command ?? "install",
|
|
89
|
+
dryRun,
|
|
90
|
+
noTui,
|
|
91
|
+
skipAuth,
|
|
92
|
+
autonomousPermissions,
|
|
93
|
+
repoRoot,
|
|
94
|
+
args: [],
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function readOptionValue(args, index, option) {
|
|
99
|
+
const value = args[index + 1];
|
|
100
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
101
|
+
throw new Error(`${option} requires a value`);
|
|
102
|
+
}
|
|
103
|
+
return value;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function formatLazyCodexInstallHelp() {
|
|
107
|
+
return [
|
|
108
|
+
"Usage: lazycodex-ai install [--no-tui] [--codex-autonomous|--no-codex-autonomous] [--repo-root <path>]",
|
|
109
|
+
"",
|
|
110
|
+
"Installs the Codex Light edition into ~/.codex using Node/npm.",
|
|
111
|
+
].join("\n");
|
|
112
|
+
}
|
|
@@ -2,6 +2,8 @@ import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
3
|
|
|
4
4
|
import { ensureCodexMultiAgentV2Config } from "./multi-agent-v2-config.mjs";
|
|
5
|
+
import { ensureCodexReasoningConfig } from "./reasoning-config.mjs";
|
|
6
|
+
import { ensureAutonomousPermissions } from "./permissions.mjs";
|
|
5
7
|
import { appendBlock, findTomlSection, replaceOrInsertSetting } from "./toml-editor.mjs";
|
|
6
8
|
import { exists } from "./utils.mjs";
|
|
7
9
|
|
|
@@ -15,6 +17,14 @@ const MANAGED_CODEX_AGENT_NAMES = [
|
|
|
15
17
|
"momus",
|
|
16
18
|
"plan",
|
|
17
19
|
];
|
|
20
|
+
const CONTEXT7_MCP_SERVER_HEADER = "mcp_servers.context7";
|
|
21
|
+
const CONTEXT7_MCP_SERVER_BLOCK = [
|
|
22
|
+
`[${CONTEXT7_MCP_SERVER_HEADER}]`,
|
|
23
|
+
`command = "npx"`,
|
|
24
|
+
`args = ["-y", "@upstash/context7-mcp", "--api-key", "YOUR_API_KEY"]`,
|
|
25
|
+
`startup_timeout_sec = 20`,
|
|
26
|
+
"",
|
|
27
|
+
].join("\n");
|
|
18
28
|
|
|
19
29
|
export async function updateCodexConfig({
|
|
20
30
|
configPath,
|
|
@@ -22,8 +32,10 @@ export async function updateCodexConfig({
|
|
|
22
32
|
marketplaceName,
|
|
23
33
|
marketplaceSource = defaultMarketplaceSource(marketplaceName, repoRoot),
|
|
24
34
|
pluginNames,
|
|
35
|
+
platform = process.platform,
|
|
25
36
|
trustedHookStates = [],
|
|
26
37
|
agentConfigs = [],
|
|
38
|
+
autonomousPermissions = false,
|
|
27
39
|
}) {
|
|
28
40
|
await mkdir(dirname(configPath), { recursive: true });
|
|
29
41
|
let config = "";
|
|
@@ -39,11 +51,15 @@ export async function updateCodexConfig({
|
|
|
39
51
|
config = removeStaleManagedAgentBlocks(config, new Set(agentConfigs.map((agentConfig) => agentConfig.name)));
|
|
40
52
|
config = ensureFeatureEnabled(config, "plugins");
|
|
41
53
|
config = ensureFeatureEnabled(config, "plugin_hooks");
|
|
54
|
+
config = ensureCodexReasoningConfig(config);
|
|
42
55
|
config = ensureCodexMultiAgentV2Config(config);
|
|
56
|
+
if (autonomousPermissions === true) config = ensureAutonomousPermissions(config);
|
|
57
|
+
config = ensureContext7McpServer(config);
|
|
43
58
|
config = ensureMarketplaceBlock(config, marketplaceName, marketplaceSource);
|
|
44
59
|
for (const pluginName of pluginNames) {
|
|
45
60
|
config = ensurePluginEnabled(config, `${pluginName}@${marketplaceName}`);
|
|
46
61
|
}
|
|
62
|
+
config = ensureOmoGitBashMcpPolicy(config, { marketplaceName, pluginNames, platform });
|
|
47
63
|
for (const state of trustedHookStates) {
|
|
48
64
|
config = ensureHookTrusted(config, state.key, state.trustedHash);
|
|
49
65
|
}
|
|
@@ -129,6 +145,11 @@ function ensureMarketplaceBlock(config, marketplaceName, source) {
|
|
|
129
145
|
return appendBlock(config, block);
|
|
130
146
|
}
|
|
131
147
|
|
|
148
|
+
function ensureContext7McpServer(config) {
|
|
149
|
+
if (findTomlSection(config, CONTEXT7_MCP_SERVER_HEADER)) return config;
|
|
150
|
+
return appendBlock(config, CONTEXT7_MCP_SERVER_BLOCK);
|
|
151
|
+
}
|
|
152
|
+
|
|
132
153
|
function ensurePluginEnabled(config, pluginKey) {
|
|
133
154
|
const header = `plugins.${JSON.stringify(pluginKey)}`;
|
|
134
155
|
const section = findTomlSection(config, header);
|
|
@@ -136,6 +157,19 @@ function ensurePluginEnabled(config, pluginKey) {
|
|
|
136
157
|
return replaceOrInsertSetting(config, section, "enabled", "true");
|
|
137
158
|
}
|
|
138
159
|
|
|
160
|
+
function ensurePluginMcpEnabled(config, pluginKey, serverName, enabled) {
|
|
161
|
+
const header = `plugins.${JSON.stringify(pluginKey)}.mcp_servers.${serverName}`;
|
|
162
|
+
const section = findTomlSection(config, header);
|
|
163
|
+
const enabledValue = enabled ? "true" : "false";
|
|
164
|
+
if (!section) return appendBlock(config, `[${header}]\nenabled = ${enabledValue}\n`);
|
|
165
|
+
return replaceOrInsertSetting(config, section, "enabled", enabledValue);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function ensureOmoGitBashMcpPolicy(config, { marketplaceName, pluginNames, platform }) {
|
|
169
|
+
if (marketplaceName !== "sisyphuslabs" || !pluginNames.includes("omo")) return config;
|
|
170
|
+
return ensurePluginMcpEnabled(config, "omo@sisyphuslabs", "git_bash", platform === "win32");
|
|
171
|
+
}
|
|
172
|
+
|
|
139
173
|
function ensureHookTrusted(config, key, trustedHash) {
|
|
140
174
|
const header = `hooks.state.${JSON.stringify(key)}`;
|
|
141
175
|
const section = findTomlSection(config, header);
|
|
@@ -223,7 +257,8 @@ function parseJsonString(value) {
|
|
|
223
257
|
try {
|
|
224
258
|
const parsed = JSON.parse(value);
|
|
225
259
|
return typeof parsed === "string" ? parsed : null;
|
|
226
|
-
} catch {
|
|
260
|
+
} catch (error) {
|
|
261
|
+
if (error instanceof Error) return null;
|
|
227
262
|
return null;
|
|
228
263
|
}
|
|
229
264
|
}
|