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.
Files changed (147) hide show
  1. package/.agents/skills/opencode-qa/SKILL.md +194 -0
  2. package/.agents/skills/opencode-qa/references/cli-commands.md +188 -0
  3. package/.agents/skills/opencode-qa/references/db-investigation.md +197 -0
  4. package/.agents/skills/opencode-qa/references/events-hooks.md +110 -0
  5. package/.agents/skills/opencode-qa/references/sdk.md +96 -0
  6. package/.agents/skills/opencode-qa/references/server-api.md +200 -0
  7. package/.agents/skills/opencode-qa/references/testing-harness.md +218 -0
  8. package/.agents/skills/opencode-qa/references/tui-tmux.md +52 -0
  9. package/.agents/skills/opencode-qa/scripts/db-session-by-id.sh +53 -0
  10. package/.agents/skills/opencode-qa/scripts/db-session-by-name.sh +57 -0
  11. package/.agents/skills/opencode-qa/scripts/db-session-by-text.sh +158 -0
  12. package/.agents/skills/opencode-qa/scripts/export-roundtrip.sh +57 -0
  13. package/.agents/skills/opencode-qa/scripts/lib/common.sh +216 -0
  14. package/.agents/skills/opencode-qa/scripts/server-smoke.sh +64 -0
  15. package/.agents/skills/opencode-qa/scripts/sse-hook-probe.sh +106 -0
  16. package/.agents/skills/opencode-qa/scripts/tui-smoke.sh +89 -0
  17. package/README.ja.md +13 -3
  18. package/README.ko.md +13 -3
  19. package/README.md +24 -14
  20. package/README.ru.md +13 -3
  21. package/README.zh-cn.md +13 -3
  22. package/bin/oh-my-opencode.js +4 -3
  23. package/bin/oh-my-opencode.test.ts +35 -7
  24. package/bin/platform.d.ts +1 -1
  25. package/bin/platform.js +4 -4
  26. package/bin/platform.test.ts +31 -9
  27. package/dist/cli/cleanup-command.d.ts +4 -0
  28. package/dist/cli/cleanup.d.ts +11 -0
  29. package/dist/cli/cli-program.d.ts +2 -1
  30. package/dist/cli/index.js +1837 -450
  31. package/dist/cli/install-codex/codex-cache.d.ts +1 -0
  32. package/dist/cli/install-codex/codex-cleanup-config.d.ts +6 -0
  33. package/dist/cli/install-codex/codex-cleanup.d.ts +21 -0
  34. package/dist/cli/install-codex/codex-config-mcp.d.ts +1 -0
  35. package/dist/cli/install-codex/codex-config-permissions.d.ts +1 -0
  36. package/dist/cli/install-codex/codex-config-reasoning.d.ts +1 -0
  37. package/dist/cli/install-codex/codex-config-toml.d.ts +2 -1
  38. package/dist/cli/install-codex/codex-installation-detection.d.ts +36 -0
  39. package/dist/cli/install-codex/codex-package-layout.d.ts +1 -0
  40. package/dist/cli/install-codex/codex-project-local-cleanup-best-effort.d.ts +7 -0
  41. package/dist/cli/install-codex/codex-project-local-cleanup.d.ts +35 -0
  42. package/dist/cli/install-codex/git-bash.d.ts +35 -0
  43. package/dist/cli/install-codex/index.d.ts +4 -0
  44. package/dist/cli/install-codex/toml-section-editor.d.ts +2 -0
  45. package/dist/cli/install-codex/types.d.ts +20 -0
  46. package/dist/cli/run/event-state.d.ts +1 -0
  47. package/dist/cli/run/poll-for-completion.d.ts +1 -0
  48. package/dist/cli/run/prompt-start.d.ts +7 -0
  49. package/dist/cli/star-request.d.ts +9 -0
  50. package/dist/config/schema/hooks.d.ts +0 -1
  51. package/dist/create-hooks.d.ts +0 -1
  52. package/dist/features/builtin-skills/skills/debugging.d.ts +2 -0
  53. package/dist/features/builtin-skills/skills/index.d.ts +1 -0
  54. package/dist/hooks/index.d.ts +0 -1
  55. package/dist/index.js +267 -114
  56. package/dist/plugin/hooks/create-core-hooks.d.ts +0 -1
  57. package/dist/plugin/hooks/create-session-hooks.d.ts +1 -2
  58. package/dist/plugin/messages-transform.d.ts +8 -1
  59. package/dist/plugin/user-abort-interrupted-recovery-guard.d.ts +6 -0
  60. package/dist/shared/prompt-async-gate/recent-dispatches.d.ts +14 -0
  61. package/dist/shared/prompt-async-gate/semantic-dedupe.d.ts +7 -0
  62. package/dist/shared/prompt-async-gate/session-idle-dispatch.d.ts +1 -0
  63. package/dist/shared/prompt-async-gate/timing.d.ts +1 -0
  64. package/dist/shared/prompt-async-gate/types.d.ts +2 -0
  65. package/dist/shared/prompt-async-gate.d.ts +1 -1
  66. package/package.json +22 -17
  67. package/packages/git-bash-mcp/dist/cli.js +367 -0
  68. package/packages/omo-codex/plugin/.mcp.json +11 -0
  69. package/packages/omo-codex/plugin/components/comment-checker/README.md +1 -1
  70. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +29 -0
  71. package/packages/omo-codex/plugin/components/git-bash/package.json +23 -0
  72. package/packages/omo-codex/plugin/components/git-bash/src/cli.ts +33 -0
  73. package/packages/omo-codex/plugin/components/git-bash/src/codex-hook.ts +180 -0
  74. package/packages/omo-codex/plugin/components/git-bash/src/index.ts +10 -0
  75. package/packages/omo-codex/plugin/components/git-bash/test/codex-hook.test.ts +195 -0
  76. package/packages/omo-codex/plugin/components/git-bash/tsconfig.build.json +13 -0
  77. package/packages/omo-codex/plugin/components/git-bash/tsconfig.json +25 -0
  78. package/packages/omo-codex/plugin/components/lsp/README.md +1 -1
  79. package/packages/omo-codex/plugin/components/lsp/src/cli.ts +5 -5
  80. package/packages/omo-codex/plugin/components/lsp/src/codex-hook-cli.ts +33 -0
  81. package/packages/omo-codex/plugin/components/lsp/src/codex-hook.ts +19 -27
  82. package/packages/omo-codex/plugin/components/lsp/test/codex-hook-cli.test.ts +28 -0
  83. package/packages/omo-codex/plugin/components/lsp/test/codex-hook-errors.test.ts +55 -0
  84. package/packages/omo-codex/plugin/components/lsp/test/package-smoke.test.ts +7 -5
  85. package/packages/omo-codex/plugin/components/rules/README.md +1 -1
  86. package/packages/omo-codex/plugin/components/rules/bundled-rules/windows-git-bash.md +10 -0
  87. package/packages/omo-codex/plugin/components/rules/test/package-smoke.test.ts +3 -1
  88. package/packages/omo-codex/plugin/components/rules/test/windows-git-bash-bundled-rule.test.ts +97 -0
  89. package/packages/omo-codex/plugin/components/start-work-continuation/directive.md +5 -4
  90. package/packages/omo-codex/plugin/components/start-work-continuation/test/codex-hook.test.ts +22 -0
  91. package/packages/omo-codex/plugin/components/ultrawork/README.md +2 -2
  92. package/packages/omo-codex/plugin/components/ultrawork/agents/codex-ultrawork-reviewer.toml +1 -0
  93. package/packages/omo-codex/plugin/components/ultrawork/agents/librarian.toml +8 -7
  94. package/packages/omo-codex/plugin/components/ultrawork/agents/plan.toml +2 -1
  95. package/packages/omo-codex/plugin/components/ultrawork/directive.md +31 -5
  96. package/packages/omo-codex/plugin/components/ultrawork/test/codex-hook.test.ts +27 -4
  97. package/packages/omo-codex/plugin/components/ultrawork/test/package-smoke.test.ts +25 -0
  98. package/packages/omo-codex/plugin/components/ulw-loop/README.md +1 -1
  99. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/SKILL.md +27 -205
  100. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +230 -0
  101. package/packages/omo-codex/plugin/components/ulw-loop/test/package-smoke.test.ts +102 -5
  102. package/packages/omo-codex/plugin/hooks/hooks.json +24 -2
  103. package/packages/omo-codex/plugin/package-lock.json +19 -0
  104. package/packages/omo-codex/plugin/package.json +3 -1
  105. package/packages/omo-codex/plugin/scripts/build-bundled-mcp-runtimes.mjs +16 -1
  106. package/packages/omo-codex/plugin/scripts/build-components.mjs +2 -1
  107. package/packages/omo-codex/plugin/scripts/sync-hook-status-messages.mjs +87 -0
  108. package/packages/omo-codex/plugin/skills/review-work/SKILL.md +27 -2
  109. package/packages/omo-codex/plugin/skills/start-work/SKILL.md +20 -0
  110. package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +27 -205
  111. package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +230 -0
  112. package/packages/omo-codex/plugin/test/aggregate.test.mjs +23 -8
  113. package/packages/omo-codex/plugin/test/hook-status-message.test.mjs +56 -11
  114. package/packages/omo-codex/plugin/test/install-time-build-runtime.test.mjs +34 -0
  115. package/packages/omo-codex/plugin/test/mcp-research-servers.test.mjs +21 -0
  116. package/packages/omo-codex/plugin/test/node-install-surface.test.mjs +48 -0
  117. package/packages/omo-codex/plugin/test/subagent-guidance.test.mjs +76 -0
  118. package/packages/omo-codex/plugin/test/sync-hook-status-messages.test.mjs +66 -0
  119. package/packages/omo-codex/plugin/test/sync-skills.test.mjs +32 -2
  120. package/packages/omo-codex/scripts/install/cache.mjs +5 -3
  121. package/packages/omo-codex/scripts/install/cli-args.mjs +112 -0
  122. package/packages/omo-codex/scripts/install/config.mjs +36 -1
  123. package/packages/omo-codex/scripts/install/delegated-command.mjs +25 -0
  124. package/packages/omo-codex/scripts/install/git-bash.mjs +99 -0
  125. package/packages/omo-codex/scripts/install/git-bash.test.mjs +174 -0
  126. package/packages/omo-codex/scripts/install/mcp-runtime-cache.mjs +5 -1
  127. package/packages/omo-codex/scripts/install/multi-agent-v2-config.mjs +7 -1
  128. package/packages/omo-codex/scripts/install/permissions.d.mts +1 -0
  129. package/packages/omo-codex/scripts/install/permissions.mjs +26 -0
  130. package/packages/omo-codex/scripts/install/project-local-cleanup.mjs +229 -0
  131. package/packages/omo-codex/scripts/install/reasoning-config.mjs +14 -0
  132. package/packages/omo-codex/scripts/install/source-package-build.mjs +20 -0
  133. package/packages/omo-codex/scripts/install/toml-editor.mjs +19 -2
  134. package/packages/omo-codex/scripts/install-cli-args.test.mjs +146 -0
  135. package/packages/omo-codex/scripts/install-config-autonomous.test.mjs +48 -0
  136. package/packages/omo-codex/scripts/install-config-reasoning.test.mjs +62 -0
  137. package/packages/omo-codex/scripts/install-config.test.mjs +206 -0
  138. package/packages/omo-codex/scripts/install-local-entrypoint.test.mjs +129 -0
  139. package/packages/omo-codex/scripts/install-local-git-bash-preflight.test.mjs +145 -0
  140. package/packages/omo-codex/scripts/install-local.mjs +91 -8
  141. package/packages/omo-codex/scripts/install-local.test.mjs +15 -0
  142. package/packages/omo-codex/scripts/install-mcp-runtime.test.mjs +60 -0
  143. package/packages/omo-codex/scripts/install-packaged-local.test.mjs +67 -0
  144. package/packages/omo-codex/scripts/install-project-local-cleanup.test.mjs +277 -0
  145. package/packages/shared-skills/skills/review-work/SKILL.md +27 -2
  146. package/packages/shared-skills/skills/start-work/SKILL.md +20 -0
  147. package/dist/hooks/context-window-monitor.d.ts +0 -19
@@ -0,0 +1,146 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ import { parseLazyCodexInstallCliArgs } from "./install/cli-args.mjs";
5
+
6
+ test("#given lazycodex install flags #when parsing Node installer argv #then keeps Codex autonomous intent", () => {
7
+ // given
8
+ const argv = ["install", "--no-tui", "--codex-autonomous", "--platform=codex"];
9
+
10
+ // when
11
+ const parsed = parseLazyCodexInstallCliArgs(argv);
12
+
13
+ // then
14
+ assert.deepEqual(parsed, {
15
+ kind: "install",
16
+ autonomousPermissions: true,
17
+ repoRoot: undefined,
18
+ });
19
+ });
20
+
21
+ test("#given unsupported OpenCode platform override #when parsing Node installer argv #then rejects the Bun-backed path", () => {
22
+ // given
23
+ const argv = ["install", "--platform=both"];
24
+
25
+ // when
26
+ const parse = () => parseLazyCodexInstallCliArgs(argv);
27
+
28
+ // then
29
+ assert.throws(parse, /lazycodex-ai installs the Codex Light edition only/);
30
+ });
31
+
32
+ test("#given missing platform value #when parsing Node installer argv #then rejects the incomplete option", () => {
33
+ // given
34
+ const argv = ["install", "--platform"];
35
+
36
+ // when
37
+ const parse = () => parseLazyCodexInstallCliArgs(argv);
38
+
39
+ // then
40
+ assert.throws(parse, /--platform requires a value/);
41
+ });
42
+
43
+ test("#given repo root equals option #when parsing Node installer argv #then keeps the explicit path", () => {
44
+ // given
45
+ const argv = ["install", "--repo-root=/tmp/project"];
46
+
47
+ // when
48
+ const parsed = parseLazyCodexInstallCliArgs(argv);
49
+
50
+ // then
51
+ assert.deepEqual(parsed, {
52
+ kind: "install",
53
+ autonomousPermissions: undefined,
54
+ repoRoot: "/tmp/project",
55
+ });
56
+ });
57
+
58
+ test("#given unknown positional command #when parsing Node installer argv #then rejects instead of treating it as a repo root", () => {
59
+ // given
60
+ const argv = ["banana"];
61
+
62
+ // when
63
+ const parse = () => parseLazyCodexInstallCliArgs(argv);
64
+
65
+ // then
66
+ assert.throws(parse, /Unsupported lazycodex-ai command: banana/);
67
+ });
68
+
69
+ test("#given install help flag #when parsing Node installer argv #then returns help", () => {
70
+ // given
71
+ const argv = ["install", "--help"];
72
+
73
+ // when
74
+ const parsed = parseLazyCodexInstallCliArgs(argv);
75
+
76
+ // then
77
+ assert.deepEqual(parsed, { kind: "help" });
78
+ });
79
+
80
+ test("#given dry-run install with codex autonomy flags #when parsing Node installer argv #then keeps delegated install command and dry-run intent", () => {
81
+ // given
82
+ const argv = ["--dry-run", "install", "--no-tui", "--codex-autonomous"];
83
+
84
+ // when
85
+ const parsed = parseLazyCodexInstallCliArgs(argv);
86
+
87
+ // then
88
+ assert.deepEqual(parsed, {
89
+ kind: "command",
90
+ command: "install",
91
+ dryRun: true,
92
+ noTui: true,
93
+ skipAuth: false,
94
+ autonomousPermissions: true,
95
+ repoRoot: undefined,
96
+ args: [],
97
+ });
98
+ });
99
+
100
+ test("#given dry-run doctor command #when parsing Node installer argv #then returns delegated doctor command", () => {
101
+ // given
102
+ const argv = ["--dry-run", "doctor"];
103
+
104
+ // when
105
+ const parsed = parseLazyCodexInstallCliArgs(argv);
106
+
107
+ // then
108
+ assert.deepEqual(parsed, {
109
+ kind: "command",
110
+ command: "doctor",
111
+ dryRun: true,
112
+ args: [],
113
+ });
114
+ });
115
+
116
+ test("#given dry-run cleanup command #when parsing Node installer argv #then returns delegated codex cleanup command", () => {
117
+ // given
118
+ const argv = ["--dry-run", "cleanup", "--project", "/tmp/lazycodex-qa"];
119
+
120
+ // when
121
+ const parsed = parseLazyCodexInstallCliArgs(argv);
122
+
123
+ // then
124
+ assert.deepEqual(parsed, {
125
+ kind: "command",
126
+ command: "cleanup",
127
+ dryRun: true,
128
+ args: ["--project", "/tmp/lazycodex-qa"],
129
+ });
130
+ });
131
+
132
+ test("#given doctor flags #when parsing Node installer argv #then preserves pass-through arguments", () => {
133
+ // given
134
+ const argv = ["doctor", "--json"];
135
+
136
+ // when
137
+ const parsed = parseLazyCodexInstallCliArgs(argv);
138
+
139
+ // then
140
+ assert.deepEqual(parsed, {
141
+ kind: "command",
142
+ command: "doctor",
143
+ dryRun: false,
144
+ args: ["--json"],
145
+ });
146
+ });
@@ -0,0 +1,48 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdtemp, readFile, writeFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import test from "node:test";
6
+
7
+ import { updateCodexConfig } from "./install/config.mjs";
8
+
9
+ test("#given autonomous permissions requested #when script installer updates config #then enables full Codex autonomy", async () => {
10
+ // given
11
+ const root = await mkdtemp(join(tmpdir(), "omo-codex-script-config-autonomous-"));
12
+ const configPath = join(root, "config.toml");
13
+ await writeFile(
14
+ configPath,
15
+ [
16
+ 'approval_policy = "on-request"',
17
+ 'sandbox_mode = "workspace-write"',
18
+ 'network_access = "disabled"',
19
+ "",
20
+ "[notice]",
21
+ "hide_full_access_warning = false",
22
+ "",
23
+ "[windows]",
24
+ 'sandbox = "workspace-write"',
25
+ "",
26
+ ].join("\n"),
27
+ );
28
+
29
+ // when
30
+ await updateCodexConfig({
31
+ configPath,
32
+ repoRoot: "/repo/packages/omo-codex",
33
+ marketplaceName: "debug",
34
+ marketplaceSource: { sourceType: "local", source: "/repo/packages/omo-codex" },
35
+ pluginNames: ["omo"],
36
+ autonomousPermissions: true,
37
+ });
38
+
39
+ // then
40
+ const config = await readFile(configPath, "utf8");
41
+ assert.match(config, /approval_policy = "never"/);
42
+ assert.match(config, /sandbox_mode = "danger-full-access"/);
43
+ assert.match(config, /network_access = "enabled"/);
44
+ assert.match(config, /\[notice\]/);
45
+ assert.match(config, /hide_full_access_warning = true/);
46
+ assert.match(config, /hide_world_writable_warning = true/);
47
+ assert.doesNotMatch(config, /sandbox = "workspace-write"/);
48
+ });
@@ -0,0 +1,62 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdtemp, readFile, writeFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import test from "node:test";
6
+
7
+ import { updateCodexConfig } from "./install/config.mjs";
8
+
9
+ test("#given empty Codex config #when script installer updates config #then sets default and Plan-mode reasoning effort", async () => {
10
+ // given
11
+ const root = await mkdtemp(join(tmpdir(), "omo-codex-script-config-reasoning-"));
12
+ const configPath = join(root, "config.toml");
13
+
14
+ // when
15
+ await updateCodexConfig({
16
+ configPath,
17
+ repoRoot: "/repo/packages/omo-codex",
18
+ marketplaceName: "debug",
19
+ marketplaceSource: { sourceType: "local", source: "/repo/packages/omo-codex" },
20
+ pluginNames: ["omo"],
21
+ });
22
+
23
+ // then
24
+ const content = await readFile(configPath, "utf8");
25
+ assert.match(content, /model_reasoning_effort = "high"/);
26
+ assert.match(content, /plan_mode_reasoning_effort = "xhigh"/);
27
+ });
28
+
29
+ test("#given existing reasoning config #when script installer updates config #then replaces stale defaults without duplicate keys", async () => {
30
+ // given
31
+ const root = await mkdtemp(join(tmpdir(), "omo-codex-script-config-reasoning-existing-"));
32
+ const configPath = join(root, "config.toml");
33
+ await writeFile(
34
+ configPath,
35
+ [
36
+ 'model_reasoning_effort = "low"',
37
+ 'plan_mode_reasoning_effort = "medium"',
38
+ "",
39
+ "[features]",
40
+ "plugins = false",
41
+ "",
42
+ ].join("\n"),
43
+ );
44
+
45
+ // when
46
+ await updateCodexConfig({
47
+ configPath,
48
+ repoRoot: "/repo/packages/omo-codex",
49
+ marketplaceName: "debug",
50
+ marketplaceSource: { sourceType: "local", source: "/repo/packages/omo-codex" },
51
+ pluginNames: ["omo"],
52
+ });
53
+
54
+ // then
55
+ const content = await readFile(configPath, "utf8");
56
+ assert.equal(content.match(/^model_reasoning_effort\s*=/gm)?.length, 1);
57
+ assert.equal(content.match(/^plan_mode_reasoning_effort\s*=/gm)?.length, 1);
58
+ assert.match(content, /model_reasoning_effort = "high"/);
59
+ assert.match(content, /plan_mode_reasoning_effort = "xhigh"/);
60
+ assert.doesNotMatch(content, /model_reasoning_effort = "low"/);
61
+ assert.doesNotMatch(content, /plan_mode_reasoning_effort = "medium"/);
62
+ });
@@ -27,6 +27,61 @@ test("#given empty Codex config #when script installer updates config #then enab
27
27
  assert.match(config, /max_concurrent_threads_per_session = 10000/);
28
28
  });
29
29
 
30
+ test("#given empty Codex config #when script installer updates config #then installs Context7 MCP", async () => {
31
+ // given
32
+ const root = await mkdtemp(join(tmpdir(), "omo-codex-script-config-context7-"));
33
+ const configPath = join(root, "config.toml");
34
+
35
+ // when
36
+ await updateCodexConfig({
37
+ configPath,
38
+ repoRoot: "/repo/packages/omo-codex",
39
+ marketplaceName: "debug",
40
+ marketplaceSource: { sourceType: "local", source: "/repo/packages/omo-codex" },
41
+ pluginNames: ["omo"],
42
+ });
43
+
44
+ // then
45
+ const config = await readFile(configPath, "utf8");
46
+ assert.match(config, /\[mcp_servers\.context7\]/);
47
+ assert.match(config, /command = "npx"/);
48
+ assert.match(config, /args = \["-y", "@upstash\/context7-mcp", "--api-key", "YOUR_API_KEY"\]/);
49
+ assert.match(config, /startup_timeout_sec = 20/);
50
+ });
51
+
52
+ test("#given existing Context7 MCP config #when script installer updates config #then preserves user setup", async () => {
53
+ // given
54
+ const root = await mkdtemp(join(tmpdir(), "omo-codex-script-config-context7-existing-"));
55
+ const configPath = join(root, "config.toml");
56
+ await writeFile(
57
+ configPath,
58
+ [
59
+ "[mcp_servers.context7]",
60
+ 'command = "node"',
61
+ 'args = ["/opt/context7/server.js"]',
62
+ 'startup_timeout_sec = 40',
63
+ "",
64
+ ].join("\n"),
65
+ );
66
+
67
+ // when
68
+ await updateCodexConfig({
69
+ configPath,
70
+ repoRoot: "/repo/packages/omo-codex",
71
+ marketplaceName: "debug",
72
+ marketplaceSource: { sourceType: "local", source: "/repo/packages/omo-codex" },
73
+ pluginNames: ["omo"],
74
+ });
75
+
76
+ // then
77
+ const config = await readFile(configPath, "utf8");
78
+ assert.match(config, /\[mcp_servers\.context7\]/);
79
+ assert.match(config, /command = "node"/);
80
+ assert.match(config, /args = \["\/opt\/context7\/server\.js"\]/);
81
+ assert.match(config, /startup_timeout_sec = 40/);
82
+ assert.doesNotMatch(config, /YOUR_API_KEY/);
83
+ });
84
+
30
85
  test("#given sisyphuslabs config without explicit source #when script installer updates config #then uses local marketplace", async () => {
31
86
  // given
32
87
  const root = await mkdtemp(join(tmpdir(), "omo-codex-script-config-sisyphuslabs-"));
@@ -116,3 +171,154 @@ test("#given legacy boolean MultiAgentV2 flag and table #when script installer u
116
171
  assert.match(config, /usage_hint_enabled = false/);
117
172
  assert.match(config, /max_concurrent_threads_per_session = 10000/);
118
173
  });
174
+
175
+ test("#given legacy agents max_threads #when script installer updates config #then removes the conflicting legacy thread cap", async () => {
176
+ // given
177
+ const root = await mkdtemp(join(tmpdir(), "omo-codex-script-config-multi-agent-legacy-threads-"));
178
+ const configPath = join(root, "config.toml");
179
+ await writeFile(
180
+ configPath,
181
+ [
182
+ "[agents]",
183
+ "max_threads = 16",
184
+ "max_depth = 4",
185
+ "job_max_runtime_seconds = 3600",
186
+ "",
187
+ ].join("\n"),
188
+ );
189
+
190
+ // when
191
+ await updateCodexConfig({
192
+ configPath,
193
+ repoRoot: "/repo/packages/omo-codex",
194
+ marketplaceName: "debug",
195
+ marketplaceSource: { sourceType: "local", source: "/repo/packages/omo-codex" },
196
+ pluginNames: ["omo"],
197
+ });
198
+
199
+ // then
200
+ const config = await readFile(configPath, "utf8");
201
+ assert.match(config, /\[features\.multi_agent_v2\]/);
202
+ assert.match(config, /enabled = true/);
203
+ assert.match(config, /max_concurrent_threads_per_session = 10000/);
204
+ assert.match(config, /\[agents\]/);
205
+ assert.doesNotMatch(config, /^max_threads\s*=/m);
206
+ assert.match(config, /max_depth = 4/);
207
+ assert.match(config, /job_max_runtime_seconds = 3600/);
208
+ });
209
+
210
+ test("#given managed agent role sections #when script installer updates config #then preserves role config while removing only root agents max_threads", async () => {
211
+ // given
212
+ const root = await mkdtemp(join(tmpdir(), "omo-codex-script-config-multi-agent-role-section-"));
213
+ const configPath = join(root, "config.toml");
214
+ await writeFile(
215
+ configPath,
216
+ [
217
+ "[agents]",
218
+ "max_threads = 16",
219
+ "",
220
+ "[agents.explorer]",
221
+ 'description = "read-only explorer"',
222
+ 'config_file = "./agents/explorer.toml"',
223
+ "",
224
+ ].join("\n"),
225
+ );
226
+
227
+ // when
228
+ await updateCodexConfig({
229
+ configPath,
230
+ repoRoot: "/repo/packages/omo-codex",
231
+ marketplaceName: "debug",
232
+ marketplaceSource: { sourceType: "local", source: "/repo/packages/omo-codex" },
233
+ pluginNames: ["omo"],
234
+ agentConfigs: [{ name: "explorer", configFile: "./agents/explorer.toml" }],
235
+ });
236
+
237
+ // then
238
+ const config = await readFile(configPath, "utf8");
239
+ assert.doesNotMatch(config, /^max_threads\s*=/m);
240
+ assert.match(config, /\[agents\.explorer\]/);
241
+ assert.match(config, /description = "read-only explorer"/);
242
+ assert.match(config, /config_file = "\.\/agents\/explorer\.toml"/);
243
+ });
244
+
245
+ test("#given existing trust and lsp blocks #when updating config #then existing blocks are preserved", async () => {
246
+ // given
247
+ const root = await mkdtemp(join(tmpdir(), "omo-codex-config-baseline-"));
248
+ const configPath = join(root, "config.toml");
249
+ await writeFile(
250
+ configPath,
251
+ [
252
+ '[plugins."omo@sisyphuslabs"]',
253
+ "enabled = true",
254
+ "",
255
+ '[plugins."omo@sisyphuslabs".mcp_servers.lsp]',
256
+ "enabled = true",
257
+ "",
258
+ '[hooks.state."omo@sisyphuslabs:hooks/hooks.json:post_tool_use:0:0"]',
259
+ 'trusted_hash = "sha256:keep"',
260
+ "",
261
+ ].join("\n"),
262
+ );
263
+
264
+ // when
265
+ await updateCodexConfig({
266
+ configPath,
267
+ repoRoot: "/repo/packages/omo-codex",
268
+ marketplaceName: "sisyphuslabs",
269
+ marketplaceSource: { sourceType: "local", source: "/repo/packages/omo-codex/cache/sisyphuslabs" },
270
+ pluginNames: ["omo"],
271
+ trustedHookStates: [{ key: "omo@sisyphuslabs:hooks/hooks.json:post_tool_use:0:0", trustedHash: "sha256:keep" }],
272
+ });
273
+
274
+ // then
275
+ const content = await readFile(configPath, "utf8");
276
+ assert.match(content, /\[plugins\."omo@sisyphuslabs"\]/);
277
+ assert.match(content, /\[plugins\."omo@sisyphuslabs"\.mcp_servers\.lsp\]/);
278
+ assert.match(content, /\[hooks\.state\."omo@sisyphuslabs:hooks\/hooks\.json:post_tool_use:0:0"\]/);
279
+ assert.match(content, /trusted_hash = "sha256:keep"/);
280
+ });
281
+
282
+ test("#given windows platform #when updating config #then enables git_bash plugin mcp policy", async () => {
283
+ // given
284
+ const root = await mkdtemp(join(tmpdir(), "omo-codex-config-git-bash-win32-"));
285
+ const configPath = join(root, "config.toml");
286
+
287
+ // when
288
+ await updateCodexConfig({
289
+ configPath,
290
+ repoRoot: "/repo/packages/omo-codex",
291
+ marketplaceName: "sisyphuslabs",
292
+ marketplaceSource: { sourceType: "local", source: "/repo/packages/omo-codex/cache/sisyphuslabs" },
293
+ pluginNames: ["omo"],
294
+ platform: "win32",
295
+ });
296
+
297
+ // then
298
+ const content = await readFile(configPath, "utf8");
299
+ assert.match(content, /\[plugins\."omo@sisyphuslabs"\.mcp_servers\.git_bash\]/);
300
+ assert.match(content, /\[plugins\."omo@sisyphuslabs"\.mcp_servers\.git_bash\][\s\S]*?enabled = true/);
301
+ });
302
+
303
+ test("#given non-windows platforms #when updating config #then disables git_bash plugin mcp policy", async () => {
304
+ for (const platform of ["linux", "darwin"]) {
305
+ // given
306
+ const root = await mkdtemp(join(tmpdir(), `omo-codex-config-git-bash-${platform}-`));
307
+ const configPath = join(root, "config.toml");
308
+
309
+ // when
310
+ await updateCodexConfig({
311
+ configPath,
312
+ repoRoot: "/repo/packages/omo-codex",
313
+ marketplaceName: "sisyphuslabs",
314
+ marketplaceSource: { sourceType: "local", source: "/repo/packages/omo-codex/cache/sisyphuslabs" },
315
+ pluginNames: ["omo"],
316
+ platform,
317
+ });
318
+
319
+ // then
320
+ const content = await readFile(configPath, "utf8");
321
+ assert.match(content, /\[plugins\."omo@sisyphuslabs"\.mcp_servers\.git_bash\]/);
322
+ assert.match(content, /\[plugins\."omo@sisyphuslabs"\.mcp_servers\.git_bash\][\s\S]*?enabled = false/);
323
+ }
324
+ });
@@ -0,0 +1,129 @@
1
+ import assert from "node:assert/strict";
2
+ import { execFileSync } from "node:child_process";
3
+ import { mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { dirname, join } from "node:path";
6
+ import test from "node:test";
7
+ import { fileURLToPath, pathToFileURL } from "node:url";
8
+
9
+ import { resolveDefaultRepoRoot } from "./install-local.mjs";
10
+
11
+ test("#given published lazycodex bin runs outside the package #when resolving default repo root #then uses installer location", () => {
12
+ // given
13
+ const scriptsDir = dirname(fileURLToPath(import.meta.url));
14
+
15
+ // when
16
+ const repoRoot = resolveDefaultRepoRoot();
17
+
18
+ // then
19
+ assert.equal(repoRoot, join(scriptsDir, "..", "..", ".."));
20
+ });
21
+
22
+ test("#given lazycodex version flag #when running the Node installer entrypoint #then prints the package version", () => {
23
+ // given
24
+ const scriptPath = fileURLToPath(new URL("./install-local.mjs", import.meta.url));
25
+ const manifestPath = fileURLToPath(new URL("../../../package.json", import.meta.url));
26
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
27
+
28
+ // when
29
+ const output = execFileSync(process.execPath, [scriptPath, "--version"], {
30
+ encoding: "utf8",
31
+ }).trim();
32
+
33
+ // then
34
+ assert.equal(output, `lazycodex-ai ${manifest.version}`);
35
+ });
36
+
37
+ test("#given lazycodex runs through an npm bin symlink #when running the Node installer entrypoint #then it still executes main", { skip: process.platform === "win32" }, () => {
38
+ // given
39
+ const scriptPath = fileURLToPath(new URL("./install-local.mjs", import.meta.url));
40
+ const manifestPath = fileURLToPath(new URL("../../../package.json", import.meta.url));
41
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
42
+ const tempDir = mkdtempSync(join(tmpdir(), "lazycodex-bin-"));
43
+ const binPath = join(tempDir, "lazycodex-ai");
44
+
45
+ try {
46
+ symlinkSync(scriptPath, binPath);
47
+
48
+ // when
49
+ const output = execFileSync(process.execPath, [binPath, "--version"], {
50
+ encoding: "utf8",
51
+ }).trim();
52
+
53
+ // then
54
+ assert.equal(output, `lazycodex-ai ${manifest.version}`);
55
+ } finally {
56
+ rmSync(tempDir, { recursive: true, force: true });
57
+ }
58
+ });
59
+
60
+ test("#given dry-run install flags #when running the Node installer entrypoint #then prints delegated codex install command", () => {
61
+ // given
62
+ const scriptPath = fileURLToPath(new URL("./install-local.mjs", import.meta.url));
63
+
64
+ // when
65
+ const output = execFileSync(
66
+ process.execPath,
67
+ [scriptPath, "--dry-run", "install", "--no-tui", "--codex-autonomous"],
68
+ { encoding: "utf8" },
69
+ ).trim();
70
+
71
+ // then
72
+ assert.equal(output, "npx --yes --package oh-my-openagent omo install --platform=codex --no-tui --codex-autonomous");
73
+ });
74
+
75
+ test("#given dry-run doctor #when running the Node installer entrypoint #then prints delegated doctor command", () => {
76
+ // given
77
+ const scriptPath = fileURLToPath(new URL("./install-local.mjs", import.meta.url));
78
+
79
+ // when
80
+ const output = execFileSync(process.execPath, [scriptPath, "--dry-run", "doctor"], {
81
+ encoding: "utf8",
82
+ }).trim();
83
+
84
+ // then
85
+ assert.equal(output, "npx --yes --package oh-my-openagent omo doctor");
86
+ });
87
+
88
+ test("#given dry-run cleanup #when running the Node installer entrypoint #then prints delegated codex cleanup command", () => {
89
+ // given
90
+ const scriptPath = fileURLToPath(new URL("./install-local.mjs", import.meta.url));
91
+
92
+ // when
93
+ const output = execFileSync(
94
+ process.execPath,
95
+ [scriptPath, "--dry-run", "cleanup", "--project", "/tmp/lazycodex-qa"],
96
+ { encoding: "utf8" },
97
+ ).trim();
98
+
99
+ // then
100
+ assert.equal(output, "npx --yes --package oh-my-openagent omo cleanup --platform=codex --project /tmp/lazycodex-qa");
101
+ });
102
+
103
+ test("#given the invoking argv path disappears #when importing the Node installer module #then the entrypoint guard does not throw", () => {
104
+ // given
105
+ const scriptPath = fileURLToPath(new URL("./install-local.mjs", import.meta.url));
106
+ const tempDir = mkdtempSync(join(tmpdir(), "lazycodex-import-"));
107
+ const missingArgvPath = join(tempDir, "missing-entrypoint.mjs");
108
+ const probePath = join(tempDir, "probe.mjs");
109
+
110
+ try {
111
+ writeFileSync(
112
+ probePath,
113
+ [
114
+ `process.argv[1] = ${JSON.stringify(missingArgvPath)};`,
115
+ `await import(${JSON.stringify(pathToFileURL(scriptPath).href)});`,
116
+ ].join("\n"),
117
+ );
118
+
119
+ // when
120
+ const output = execFileSync(process.execPath, [probePath], {
121
+ encoding: "utf8",
122
+ }).trim();
123
+
124
+ // then
125
+ assert.equal(output, "");
126
+ } finally {
127
+ rmSync(tempDir, { recursive: true, force: true });
128
+ }
129
+ });