oh-my-opencode 4.5.12 → 4.7.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 (189) 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/bin/version-mismatch.js +47 -0
  28. package/bin/version-mismatch.test.ts +120 -0
  29. package/dist/cli/cleanup-command.d.ts +4 -0
  30. package/dist/cli/cleanup.d.ts +11 -0
  31. package/dist/cli/cli-program.d.ts +2 -1
  32. package/dist/cli/codex-ulw-loop.d.ts +12 -0
  33. package/dist/cli/doctor/checks/tui-plugin-config.d.ts +2 -0
  34. package/dist/cli/index.js +2189 -529
  35. package/dist/cli/install-codex/codex-cache.d.ts +1 -0
  36. package/dist/cli/install-codex/codex-cleanup-config.d.ts +6 -0
  37. package/dist/cli/install-codex/codex-cleanup.d.ts +21 -0
  38. package/dist/cli/install-codex/codex-config-permissions.d.ts +1 -0
  39. package/dist/cli/install-codex/codex-config-reasoning.d.ts +2 -0
  40. package/dist/cli/install-codex/codex-config-toml.d.ts +2 -1
  41. package/dist/cli/install-codex/codex-installation-detection.d.ts +36 -0
  42. package/dist/cli/install-codex/codex-model-catalog.d.ts +13 -0
  43. package/dist/cli/install-codex/codex-package-layout.d.ts +1 -0
  44. package/dist/cli/install-codex/codex-project-local-cleanup-best-effort.d.ts +7 -0
  45. package/dist/cli/install-codex/codex-project-local-cleanup.d.ts +35 -0
  46. package/dist/cli/install-codex/git-bash.d.ts +35 -0
  47. package/dist/cli/install-codex/index.d.ts +4 -0
  48. package/dist/cli/install-codex/toml-section-editor.d.ts +2 -0
  49. package/dist/cli/install-codex/types.d.ts +20 -0
  50. package/dist/cli/run/event-state.d.ts +1 -0
  51. package/dist/cli/run/poll-for-completion.d.ts +1 -0
  52. package/dist/cli/run/prompt-start.d.ts +7 -0
  53. package/dist/cli/star-request.d.ts +9 -0
  54. package/dist/config/schema/hooks.d.ts +0 -1
  55. package/dist/create-hooks.d.ts +0 -1
  56. package/dist/features/background-agent/concurrency.d.ts +1 -0
  57. package/dist/features/background-agent/process-cleanup.d.ts +6 -0
  58. package/dist/features/builtin-skills/skills/debugging.d.ts +2 -0
  59. package/dist/features/builtin-skills/skills/index.d.ts +1 -0
  60. package/dist/features/claude-code-session-state/state.d.ts +1 -0
  61. package/dist/features/opencode-skill-loader/index.d.ts +1 -0
  62. package/dist/features/opencode-skill-loader/opencode-config-skills-reader.d.ts +5 -0
  63. package/dist/features/tmux-subagent/attachable-session-status.d.ts +1 -1
  64. package/dist/features/tmux-subagent/session-status-parser.d.ts +1 -0
  65. package/dist/hooks/comment-checker/cli.d.ts +1 -0
  66. package/dist/hooks/index.d.ts +0 -1
  67. package/dist/hooks/tasks-todowrite-disabler/constants.d.ts +1 -1
  68. package/dist/index.js +1077 -563
  69. package/dist/plugin/hooks/create-core-hooks.d.ts +0 -1
  70. package/dist/plugin/hooks/create-session-hooks.d.ts +1 -2
  71. package/dist/plugin/messages-transform.d.ts +8 -1
  72. package/dist/plugin/user-abort-interrupted-recovery-guard.d.ts +6 -0
  73. package/dist/shared/command-executor/execute-hook-command.d.ts +2 -0
  74. package/dist/shared/prompt-async-gate/recent-dispatches.d.ts +14 -0
  75. package/dist/shared/prompt-async-gate/semantic-dedupe.d.ts +7 -0
  76. package/dist/shared/prompt-async-gate/session-idle-dispatch.d.ts +1 -0
  77. package/dist/shared/prompt-async-gate/timing.d.ts +1 -0
  78. package/dist/shared/prompt-async-gate/types.d.ts +2 -0
  79. package/dist/shared/prompt-async-gate.d.ts +1 -1
  80. package/dist/tools/skill/description-formatter.d.ts +5 -1
  81. package/dist/tools/skill/types.d.ts +1 -0
  82. package/package.json +22 -18
  83. package/packages/ast-grep-mcp/dist/cli.js +53 -9
  84. package/packages/git-bash-mcp/dist/cli.js +367 -0
  85. package/packages/lsp-tools-mcp/dist/lsp/process.js +1 -1
  86. package/packages/omo-codex/plugin/.mcp.json +11 -0
  87. package/packages/omo-codex/plugin/components/comment-checker/README.md +1 -1
  88. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +29 -0
  89. package/packages/omo-codex/plugin/components/git-bash/package.json +23 -0
  90. package/packages/omo-codex/plugin/components/git-bash/src/cli.ts +33 -0
  91. package/packages/omo-codex/plugin/components/git-bash/src/codex-hook.ts +180 -0
  92. package/packages/omo-codex/plugin/components/git-bash/src/index.ts +10 -0
  93. package/packages/omo-codex/plugin/components/git-bash/test/codex-hook.test.ts +195 -0
  94. package/packages/omo-codex/plugin/components/git-bash/tsconfig.build.json +13 -0
  95. package/packages/omo-codex/plugin/components/git-bash/tsconfig.json +25 -0
  96. package/packages/omo-codex/plugin/components/lsp/README.md +1 -1
  97. package/packages/omo-codex/plugin/components/lsp/src/cli.ts +5 -5
  98. package/packages/omo-codex/plugin/components/lsp/src/codex-hook-cli.ts +33 -0
  99. package/packages/omo-codex/plugin/components/lsp/src/codex-hook.ts +19 -27
  100. package/packages/omo-codex/plugin/components/lsp/test/codex-hook-cli.test.ts +28 -0
  101. package/packages/omo-codex/plugin/components/lsp/test/codex-hook-errors.test.ts +55 -0
  102. package/packages/omo-codex/plugin/components/lsp/test/package-smoke.test.ts +7 -5
  103. package/packages/omo-codex/plugin/components/rules/README.md +1 -1
  104. package/packages/omo-codex/plugin/components/rules/bundled-rules/hephaestus.md +6 -4
  105. package/packages/omo-codex/plugin/components/rules/bundled-rules/windows-git-bash.md +10 -0
  106. package/packages/omo-codex/plugin/components/rules/src/post-compact-budget.ts +0 -2
  107. package/packages/omo-codex/plugin/components/rules/test/package-smoke.test.ts +3 -1
  108. package/packages/omo-codex/plugin/components/rules/test/windows-git-bash-bundled-rule.test.ts +97 -0
  109. package/packages/omo-codex/plugin/components/start-work-continuation/directive.md +6 -5
  110. package/packages/omo-codex/plugin/components/start-work-continuation/test/codex-hook.test.ts +22 -0
  111. package/packages/omo-codex/plugin/components/ultrawork/CHANGELOG.md +1 -1
  112. package/packages/omo-codex/plugin/components/ultrawork/README.md +3 -3
  113. package/packages/omo-codex/plugin/components/ultrawork/agents/codex-ultrawork-reviewer.toml +4 -1
  114. package/packages/omo-codex/plugin/components/ultrawork/agents/librarian.toml +8 -7
  115. package/packages/omo-codex/plugin/components/ultrawork/agents/plan.toml +9 -8
  116. package/packages/omo-codex/plugin/components/ultrawork/directive.md +32 -6
  117. package/packages/omo-codex/plugin/components/ultrawork/test/codex-hook.test.ts +27 -4
  118. package/packages/omo-codex/plugin/components/ultrawork/test/package-smoke.test.ts +25 -0
  119. package/packages/omo-codex/plugin/components/ulw-loop/README.md +1 -1
  120. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/SKILL.md +28 -205
  121. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +231 -0
  122. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts +12 -1
  123. package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint.test.ts +19 -1
  124. package/packages/omo-codex/plugin/components/ulw-loop/test/package-smoke.test.ts +102 -5
  125. package/packages/omo-codex/plugin/hooks/hooks.json +35 -2
  126. package/packages/omo-codex/plugin/model-catalog.json +49 -0
  127. package/packages/omo-codex/plugin/package-lock.json +19 -0
  128. package/packages/omo-codex/plugin/package.json +3 -1
  129. package/packages/omo-codex/plugin/scripts/auto-update.mjs +159 -0
  130. package/packages/omo-codex/plugin/scripts/build-bundled-mcp-runtimes.mjs +16 -1
  131. package/packages/omo-codex/plugin/scripts/build-components.mjs +2 -1
  132. package/packages/omo-codex/plugin/scripts/migrate-codex-config.mjs +269 -0
  133. package/packages/omo-codex/plugin/scripts/sync-hook-status-messages.mjs +89 -0
  134. package/packages/omo-codex/plugin/scripts/sync-skills.mjs +6 -6
  135. package/packages/omo-codex/plugin/skills/init-deep/SKILL.md +6 -6
  136. package/packages/omo-codex/plugin/skills/lcx-report-bug/SKILL.md +127 -0
  137. package/packages/omo-codex/plugin/skills/lcx-report-bug/agents/openai.yaml +9 -0
  138. package/packages/omo-codex/plugin/skills/refactor/SKILL.md +6 -6
  139. package/packages/omo-codex/plugin/skills/remove-ai-slops/SKILL.md +6 -6
  140. package/packages/omo-codex/plugin/skills/review-work/SKILL.md +33 -8
  141. package/packages/omo-codex/plugin/skills/start-work/SKILL.md +25 -5
  142. package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +28 -205
  143. package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +231 -0
  144. package/packages/omo-codex/plugin/skills/ulw-plan/SKILL.md +17 -17
  145. package/packages/omo-codex/plugin/test/aggregate.test.mjs +188 -20
  146. package/packages/omo-codex/plugin/test/auto-update.test.mjs +129 -0
  147. package/packages/omo-codex/plugin/test/hook-status-message.test.mjs +58 -11
  148. package/packages/omo-codex/plugin/test/install-time-build-runtime.test.mjs +34 -0
  149. package/packages/omo-codex/plugin/test/mcp-research-servers.test.mjs +21 -0
  150. package/packages/omo-codex/plugin/test/migrate-codex-config.test.mjs +146 -0
  151. package/packages/omo-codex/plugin/test/node-install-surface.test.mjs +48 -0
  152. package/packages/omo-codex/plugin/test/subagent-guidance.test.mjs +76 -0
  153. package/packages/omo-codex/plugin/test/sync-hook-status-messages.test.mjs +67 -0
  154. package/packages/omo-codex/plugin/test/sync-skills.test.mjs +54 -2
  155. package/packages/omo-codex/scripts/install/cache.mjs +5 -3
  156. package/packages/omo-codex/scripts/install/cli-args.mjs +112 -0
  157. package/packages/omo-codex/scripts/install/config.mjs +23 -1
  158. package/packages/omo-codex/scripts/install/delegated-command.mjs +25 -0
  159. package/packages/omo-codex/scripts/install/git-bash.mjs +99 -0
  160. package/packages/omo-codex/scripts/install/git-bash.test.mjs +174 -0
  161. package/packages/omo-codex/scripts/install/legacy-bins.mjs +1 -0
  162. package/packages/omo-codex/scripts/install/mcp-runtime-cache.mjs +5 -1
  163. package/packages/omo-codex/scripts/install/model-catalog.mjs +66 -0
  164. package/packages/omo-codex/scripts/install/multi-agent-v2-config.mjs +7 -1
  165. package/packages/omo-codex/scripts/install/permissions.d.mts +1 -0
  166. package/packages/omo-codex/scripts/install/permissions.mjs +26 -0
  167. package/packages/omo-codex/scripts/install/project-local-cleanup.mjs +229 -0
  168. package/packages/omo-codex/scripts/install/reasoning-config.mjs +72 -0
  169. package/packages/omo-codex/scripts/install/source-package-build.mjs +20 -0
  170. package/packages/omo-codex/scripts/install/toml-editor.mjs +19 -2
  171. package/packages/omo-codex/scripts/install-bin-links.test.mjs +23 -0
  172. package/packages/omo-codex/scripts/install-cli-args.test.mjs +146 -0
  173. package/packages/omo-codex/scripts/install-config-autonomous.test.mjs +48 -0
  174. package/packages/omo-codex/scripts/install-config-reasoning.test.mjs +141 -0
  175. package/packages/omo-codex/scripts/install-config.test.mjs +205 -0
  176. package/packages/omo-codex/scripts/install-local-entrypoint.test.mjs +157 -0
  177. package/packages/omo-codex/scripts/install-local-git-bash-preflight.test.mjs +145 -0
  178. package/packages/omo-codex/scripts/install-local.mjs +91 -8
  179. package/packages/omo-codex/scripts/install-local.test.mjs +15 -0
  180. package/packages/omo-codex/scripts/install-mcp-runtime.test.mjs +60 -0
  181. package/packages/omo-codex/scripts/install-packaged-local.test.mjs +67 -0
  182. package/packages/omo-codex/scripts/install-project-local-cleanup.test.mjs +277 -0
  183. package/packages/shared-skills/skills/lcx-report-bug/SKILL.md +127 -0
  184. package/packages/shared-skills/skills/lcx-report-bug/agents/openai.yaml +9 -0
  185. package/packages/shared-skills/skills/review-work/SKILL.md +33 -8
  186. package/packages/shared-skills/skills/start-work/SKILL.md +25 -5
  187. package/packages/shared-skills/skills/ulw-plan/SKILL.md +11 -11
  188. package/postinstall.mjs +36 -3
  189. package/dist/hooks/context-window-monitor.d.ts +0 -19
@@ -0,0 +1,141 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdir, 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 worker model and reasoning defaults", 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 = "gpt-5\.5"/);
26
+ assert.match(content, /model_context_window = 400000/);
27
+ assert.match(content, /model_reasoning_effort = "high"/);
28
+ assert.match(content, /plan_mode_reasoning_effort = "xhigh"/);
29
+ });
30
+
31
+ test("#given existing model and reasoning config #when script installer updates config #then replaces stale defaults without duplicate keys", async () => {
32
+ // given
33
+ const root = await mkdtemp(join(tmpdir(), "omo-codex-script-config-reasoning-existing-"));
34
+ const configPath = join(root, "config.toml");
35
+ await writeFile(
36
+ configPath,
37
+ [
38
+ 'model = "gpt-5.2"',
39
+ "model_context_window = 272000",
40
+ 'model_reasoning_effort = "low"',
41
+ 'plan_mode_reasoning_effort = "medium"',
42
+ "",
43
+ "[features]",
44
+ "plugins = false",
45
+ "",
46
+ ].join("\n"),
47
+ );
48
+
49
+ // when
50
+ await updateCodexConfig({
51
+ configPath,
52
+ repoRoot: "/repo/packages/omo-codex",
53
+ marketplaceName: "debug",
54
+ marketplaceSource: { sourceType: "local", source: "/repo/packages/omo-codex" },
55
+ pluginNames: ["omo"],
56
+ });
57
+
58
+ // then
59
+ const content = await readFile(configPath, "utf8");
60
+ assert.equal(content.match(/^model\s*=/gm)?.length, 1);
61
+ assert.equal(content.match(/^model_context_window\s*=/gm)?.length, 1);
62
+ assert.equal(content.match(/^model_reasoning_effort\s*=/gm)?.length, 1);
63
+ assert.equal(content.match(/^plan_mode_reasoning_effort\s*=/gm)?.length, 1);
64
+ assert.match(content, /model = "gpt-5\.5"/);
65
+ assert.match(content, /model_context_window = 400000/);
66
+ assert.match(content, /model_reasoning_effort = "high"/);
67
+ assert.match(content, /plan_mode_reasoning_effort = "xhigh"/);
68
+ assert.doesNotMatch(content, /model = "gpt-5\.2"/);
69
+ assert.doesNotMatch(content, /model_context_window = 272000/);
70
+ assert.doesNotMatch(content, /model_reasoning_effort = "low"/);
71
+ assert.doesNotMatch(content, /plan_mode_reasoning_effort = "medium"/);
72
+ });
73
+
74
+ test("#given user-customized model config #when script installer updates config #then preserves user reasoning values", async () => {
75
+ // given
76
+ const root = await mkdtemp(join(tmpdir(), "omo-codex-script-config-reasoning-custom-"));
77
+ const configPath = join(root, "config.toml");
78
+ await writeFile(
79
+ configPath,
80
+ [
81
+ 'model = "my-private-model"',
82
+ "model_context_window = 123456",
83
+ 'model_reasoning_effort = "medium"',
84
+ 'plan_mode_reasoning_effort = "medium"',
85
+ "",
86
+ ].join("\n"),
87
+ );
88
+
89
+ // when
90
+ await updateCodexConfig({
91
+ configPath,
92
+ repoRoot: "/repo/packages/omo-codex",
93
+ marketplaceName: "debug",
94
+ marketplaceSource: { sourceType: "local", source: "/repo/packages/omo-codex" },
95
+ pluginNames: ["omo"],
96
+ });
97
+
98
+ // then
99
+ const content = await readFile(configPath, "utf8");
100
+ assert.match(content, /model = "my-private-model"/);
101
+ assert.match(content, /model_context_window = 123456/);
102
+ assert.match(content, /model_reasoning_effort = "medium"/);
103
+ assert.match(content, /plan_mode_reasoning_effort = "medium"/);
104
+ });
105
+
106
+ test("#given bundled model catalog #when script installer updates config #then reads defaults from catalog", async () => {
107
+ // given
108
+ const root = await mkdtemp(join(tmpdir(), "omo-codex-script-config-reasoning-catalog-"));
109
+ const repoRoot = join(root, "omo-codex");
110
+ const configPath = join(root, "config.toml");
111
+ await mkdir(join(repoRoot, "plugin"), { recursive: true });
112
+ await writeFile(
113
+ join(repoRoot, "plugin", "model-catalog.json"),
114
+ JSON.stringify({
115
+ version: "test.catalog",
116
+ current: {
117
+ model: "catalog-default",
118
+ model_context_window: 123456,
119
+ model_reasoning_effort: "medium",
120
+ plan_mode_reasoning_effort: "high",
121
+ },
122
+ managedProfiles: [],
123
+ }),
124
+ );
125
+
126
+ // when
127
+ await updateCodexConfig({
128
+ configPath,
129
+ repoRoot,
130
+ marketplaceName: "debug",
131
+ marketplaceSource: { sourceType: "local", source: repoRoot },
132
+ pluginNames: ["omo"],
133
+ });
134
+
135
+ // then
136
+ const content = await readFile(configPath, "utf8");
137
+ assert.match(content, /model = "catalog-default"/);
138
+ assert.match(content, /model_context_window = 123456/);
139
+ assert.match(content, /model_reasoning_effort = "medium"/);
140
+ assert.match(content, /plan_mode_reasoning_effort = "high"/);
141
+ });
@@ -27,6 +27,60 @@ 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 leaves Context7 to the plugin MCP manifest", 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.doesNotMatch(config, /\[mcp_servers\.context7\]/);
47
+ assert.doesNotMatch(config, /@upstash\/context7-mcp/);
48
+ assert.doesNotMatch(config, /YOUR_API_KEY/);
49
+ });
50
+
51
+ test("#given existing Context7 MCP config #when script installer updates config #then leaves user setup untouched", async () => {
52
+ // given
53
+ const root = await mkdtemp(join(tmpdir(), "omo-codex-script-config-context7-existing-"));
54
+ const configPath = join(root, "config.toml");
55
+ await writeFile(
56
+ configPath,
57
+ [
58
+ "[mcp_servers.context7]",
59
+ 'command = "node"',
60
+ 'args = ["/opt/context7/server.js"]',
61
+ 'startup_timeout_sec = 40',
62
+ "",
63
+ ].join("\n"),
64
+ );
65
+
66
+ // when
67
+ await updateCodexConfig({
68
+ configPath,
69
+ repoRoot: "/repo/packages/omo-codex",
70
+ marketplaceName: "debug",
71
+ marketplaceSource: { sourceType: "local", source: "/repo/packages/omo-codex" },
72
+ pluginNames: ["omo"],
73
+ });
74
+
75
+ // then
76
+ const config = await readFile(configPath, "utf8");
77
+ assert.match(config, /\[mcp_servers\.context7\]/);
78
+ assert.match(config, /command = "node"/);
79
+ assert.match(config, /args = \["\/opt\/context7\/server\.js"\]/);
80
+ assert.match(config, /startup_timeout_sec = 40/);
81
+ assert.doesNotMatch(config, /YOUR_API_KEY/);
82
+ });
83
+
30
84
  test("#given sisyphuslabs config without explicit source #when script installer updates config #then uses local marketplace", async () => {
31
85
  // given
32
86
  const root = await mkdtemp(join(tmpdir(), "omo-codex-script-config-sisyphuslabs-"));
@@ -116,3 +170,154 @@ test("#given legacy boolean MultiAgentV2 flag and table #when script installer u
116
170
  assert.match(config, /usage_hint_enabled = false/);
117
171
  assert.match(config, /max_concurrent_threads_per_session = 10000/);
118
172
  });
173
+
174
+ test("#given legacy agents max_threads #when script installer updates config #then removes the conflicting legacy thread cap", async () => {
175
+ // given
176
+ const root = await mkdtemp(join(tmpdir(), "omo-codex-script-config-multi-agent-legacy-threads-"));
177
+ const configPath = join(root, "config.toml");
178
+ await writeFile(
179
+ configPath,
180
+ [
181
+ "[agents]",
182
+ "max_threads = 16",
183
+ "max_depth = 4",
184
+ "job_max_runtime_seconds = 3600",
185
+ "",
186
+ ].join("\n"),
187
+ );
188
+
189
+ // when
190
+ await updateCodexConfig({
191
+ configPath,
192
+ repoRoot: "/repo/packages/omo-codex",
193
+ marketplaceName: "debug",
194
+ marketplaceSource: { sourceType: "local", source: "/repo/packages/omo-codex" },
195
+ pluginNames: ["omo"],
196
+ });
197
+
198
+ // then
199
+ const config = await readFile(configPath, "utf8");
200
+ assert.match(config, /\[features\.multi_agent_v2\]/);
201
+ assert.match(config, /enabled = true/);
202
+ assert.match(config, /max_concurrent_threads_per_session = 10000/);
203
+ assert.match(config, /\[agents\]/);
204
+ assert.doesNotMatch(config, /^max_threads\s*=/m);
205
+ assert.match(config, /max_depth = 4/);
206
+ assert.match(config, /job_max_runtime_seconds = 3600/);
207
+ });
208
+
209
+ test("#given managed agent role sections #when script installer updates config #then preserves role config while removing only root agents max_threads", async () => {
210
+ // given
211
+ const root = await mkdtemp(join(tmpdir(), "omo-codex-script-config-multi-agent-role-section-"));
212
+ const configPath = join(root, "config.toml");
213
+ await writeFile(
214
+ configPath,
215
+ [
216
+ "[agents]",
217
+ "max_threads = 16",
218
+ "",
219
+ "[agents.explorer]",
220
+ 'description = "read-only explorer"',
221
+ 'config_file = "./agents/explorer.toml"',
222
+ "",
223
+ ].join("\n"),
224
+ );
225
+
226
+ // when
227
+ await updateCodexConfig({
228
+ configPath,
229
+ repoRoot: "/repo/packages/omo-codex",
230
+ marketplaceName: "debug",
231
+ marketplaceSource: { sourceType: "local", source: "/repo/packages/omo-codex" },
232
+ pluginNames: ["omo"],
233
+ agentConfigs: [{ name: "explorer", configFile: "./agents/explorer.toml" }],
234
+ });
235
+
236
+ // then
237
+ const config = await readFile(configPath, "utf8");
238
+ assert.doesNotMatch(config, /^max_threads\s*=/m);
239
+ assert.match(config, /\[agents\.explorer\]/);
240
+ assert.match(config, /description = "read-only explorer"/);
241
+ assert.match(config, /config_file = "\.\/agents\/explorer\.toml"/);
242
+ });
243
+
244
+ test("#given existing trust and lsp blocks #when updating config #then existing blocks are preserved", async () => {
245
+ // given
246
+ const root = await mkdtemp(join(tmpdir(), "omo-codex-config-baseline-"));
247
+ const configPath = join(root, "config.toml");
248
+ await writeFile(
249
+ configPath,
250
+ [
251
+ '[plugins."omo@sisyphuslabs"]',
252
+ "enabled = true",
253
+ "",
254
+ '[plugins."omo@sisyphuslabs".mcp_servers.lsp]',
255
+ "enabled = true",
256
+ "",
257
+ '[hooks.state."omo@sisyphuslabs:hooks/hooks.json:post_tool_use:0:0"]',
258
+ 'trusted_hash = "sha256:keep"',
259
+ "",
260
+ ].join("\n"),
261
+ );
262
+
263
+ // when
264
+ await updateCodexConfig({
265
+ configPath,
266
+ repoRoot: "/repo/packages/omo-codex",
267
+ marketplaceName: "sisyphuslabs",
268
+ marketplaceSource: { sourceType: "local", source: "/repo/packages/omo-codex/cache/sisyphuslabs" },
269
+ pluginNames: ["omo"],
270
+ trustedHookStates: [{ key: "omo@sisyphuslabs:hooks/hooks.json:post_tool_use:0:0", trustedHash: "sha256:keep" }],
271
+ });
272
+
273
+ // then
274
+ const content = await readFile(configPath, "utf8");
275
+ assert.match(content, /\[plugins\."omo@sisyphuslabs"\]/);
276
+ assert.match(content, /\[plugins\."omo@sisyphuslabs"\.mcp_servers\.lsp\]/);
277
+ assert.match(content, /\[hooks\.state\."omo@sisyphuslabs:hooks\/hooks\.json:post_tool_use:0:0"\]/);
278
+ assert.match(content, /trusted_hash = "sha256:keep"/);
279
+ });
280
+
281
+ test("#given windows platform #when updating config #then enables git_bash plugin mcp policy", async () => {
282
+ // given
283
+ const root = await mkdtemp(join(tmpdir(), "omo-codex-config-git-bash-win32-"));
284
+ const configPath = join(root, "config.toml");
285
+
286
+ // when
287
+ await updateCodexConfig({
288
+ configPath,
289
+ repoRoot: "/repo/packages/omo-codex",
290
+ marketplaceName: "sisyphuslabs",
291
+ marketplaceSource: { sourceType: "local", source: "/repo/packages/omo-codex/cache/sisyphuslabs" },
292
+ pluginNames: ["omo"],
293
+ platform: "win32",
294
+ });
295
+
296
+ // then
297
+ const content = await readFile(configPath, "utf8");
298
+ assert.match(content, /\[plugins\."omo@sisyphuslabs"\.mcp_servers\.git_bash\]/);
299
+ assert.match(content, /\[plugins\."omo@sisyphuslabs"\.mcp_servers\.git_bash\][\s\S]*?enabled = true/);
300
+ });
301
+
302
+ test("#given non-windows platforms #when updating config #then disables git_bash plugin mcp policy", async () => {
303
+ for (const platform of ["linux", "darwin"]) {
304
+ // given
305
+ const root = await mkdtemp(join(tmpdir(), `omo-codex-config-git-bash-${platform}-`));
306
+ const configPath = join(root, "config.toml");
307
+
308
+ // when
309
+ await updateCodexConfig({
310
+ configPath,
311
+ repoRoot: "/repo/packages/omo-codex",
312
+ marketplaceName: "sisyphuslabs",
313
+ marketplaceSource: { sourceType: "local", source: "/repo/packages/omo-codex/cache/sisyphuslabs" },
314
+ pluginNames: ["omo"],
315
+ platform,
316
+ });
317
+
318
+ // then
319
+ const content = await readFile(configPath, "utf8");
320
+ assert.match(content, /\[plugins\."omo@sisyphuslabs"\.mcp_servers\.git_bash\]/);
321
+ assert.match(content, /\[plugins\."omo@sisyphuslabs"\.mcp_servers\.git_bash\][\s\S]*?enabled = false/);
322
+ }
323
+ });
@@ -0,0 +1,157 @@
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 autonomous 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"],
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 install opt-out #when running the Node installer entrypoint #then preserves existing Codex permission settings", () => {
76
+ // given
77
+ const scriptPath = fileURLToPath(new URL("./install-local.mjs", import.meta.url));
78
+
79
+ // when
80
+ const output = execFileSync(
81
+ process.execPath,
82
+ [scriptPath, "--dry-run", "install", "--no-tui", "--no-codex-autonomous"],
83
+ { encoding: "utf8" },
84
+ ).trim();
85
+
86
+ // then
87
+ assert.equal(output, "npx --yes --package oh-my-openagent omo install --platform=codex --no-tui --no-codex-autonomous");
88
+ });
89
+
90
+ test("#given dry-run doctor #when running the Node installer entrypoint #then prints delegated doctor command", () => {
91
+ // given
92
+ const scriptPath = fileURLToPath(new URL("./install-local.mjs", import.meta.url));
93
+
94
+ // when
95
+ const output = execFileSync(process.execPath, [scriptPath, "--dry-run", "doctor"], {
96
+ encoding: "utf8",
97
+ }).trim();
98
+
99
+ // then
100
+ assert.equal(output, "npx --yes --package oh-my-openagent omo doctor");
101
+ });
102
+
103
+ test("#given dry-run cleanup #when running the Node installer entrypoint #then prints delegated codex cleanup command", () => {
104
+ // given
105
+ const scriptPath = fileURLToPath(new URL("./install-local.mjs", import.meta.url));
106
+
107
+ // when
108
+ const output = execFileSync(
109
+ process.execPath,
110
+ [scriptPath, "--dry-run", "cleanup", "--project", "/tmp/lazycodex-qa"],
111
+ { encoding: "utf8" },
112
+ ).trim();
113
+
114
+ // then
115
+ assert.equal(output, "npx --yes --package oh-my-openagent omo cleanup --platform=codex --project /tmp/lazycodex-qa");
116
+ });
117
+
118
+ test("#given dry-run ulw-loop #when running the Node installer entrypoint #then prints delegated ulw-loop command", () => {
119
+ // given
120
+ const scriptPath = fileURLToPath(new URL("./install-local.mjs", import.meta.url));
121
+
122
+ // when
123
+ const output = execFileSync(process.execPath, [scriptPath, "--dry-run", "ulw-loop", "help"], {
124
+ encoding: "utf8",
125
+ }).trim();
126
+
127
+ // then
128
+ assert.equal(output, "npx --yes --package oh-my-openagent omo ulw-loop help");
129
+ });
130
+
131
+ test("#given the invoking argv path disappears #when importing the Node installer module #then the entrypoint guard does not throw", () => {
132
+ // given
133
+ const scriptPath = fileURLToPath(new URL("./install-local.mjs", import.meta.url));
134
+ const tempDir = mkdtempSync(join(tmpdir(), "lazycodex-import-"));
135
+ const missingArgvPath = join(tempDir, "missing-entrypoint.mjs");
136
+ const probePath = join(tempDir, "probe.mjs");
137
+
138
+ try {
139
+ writeFileSync(
140
+ probePath,
141
+ [
142
+ `process.argv[1] = ${JSON.stringify(missingArgvPath)};`,
143
+ `await import(${JSON.stringify(pathToFileURL(scriptPath).href)});`,
144
+ ].join("\n"),
145
+ );
146
+
147
+ // when
148
+ const output = execFileSync(process.execPath, [probePath], {
149
+ encoding: "utf8",
150
+ }).trim();
151
+
152
+ // then
153
+ assert.equal(output, "");
154
+ } finally {
155
+ rmSync(tempDir, { recursive: true, force: true });
156
+ }
157
+ });
@@ -0,0 +1,145 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdir, mkdtemp, readFile, stat, 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 { installMarketplaceLocally } from "./install-local.mjs";
8
+
9
+ const windowsGitBashPath = "C:\\Program Files\\Git\\bin\\bash.exe";
10
+ const lspCliPath = join(process.cwd(), "packages", "lsp-tools-mcp", "dist", "cli.js");
11
+
12
+ async function withBundledLspRuntimeForTest(run) {
13
+ try {
14
+ await stat(lspCliPath);
15
+ } catch (error) {
16
+ if (!(error instanceof Error)) throw error;
17
+ await mkdir(join(process.cwd(), "packages", "lsp-tools-mcp", "dist"), { recursive: true });
18
+ await writeFile(lspCliPath, "#!/usr/bin/env node\n");
19
+ }
20
+
21
+ return run();
22
+ }
23
+
24
+ test("#given Windows without Git Bash and auto install skip env #when installing local marketplace #then rejects before marketplace or config mutation", async () => {
25
+ const repoRoot = await mkdtemp(join(tmpdir(), "omo-codex-script-git-bash-missing-repo-"));
26
+ const codexHome = await mkdtemp(join(tmpdir(), "omo-codex-script-git-bash-missing-home-"));
27
+ const commands = [];
28
+
29
+ await assert.rejects(
30
+ installMarketplaceLocally({
31
+ repoRoot,
32
+ codexHome,
33
+ platform: "win32",
34
+ env: { OMO_CODEX_SKIP_GIT_BASH_AUTO_INSTALL: "1" },
35
+ gitBashResolver: () => ({
36
+ found: false,
37
+ checkedPaths: [windowsGitBashPath],
38
+ installHint: [
39
+ "Git Bash is required.",
40
+ "winget install --id Git.Git -e --source winget",
41
+ "OMO_CODEX_GIT_BASH_PATH=C:\\path\\to\\bash.exe",
42
+ "rerun `npx lazycodex-ai install`",
43
+ ].join("\n"),
44
+ }),
45
+ runCommand: async (command, args, options) => {
46
+ commands.push([command, ...args, options.cwd].join(" "));
47
+ },
48
+ log: () => {},
49
+ }),
50
+ /winget install --id Git\.Git -e --source winget/,
51
+ );
52
+ assert.deepEqual(commands, []);
53
+ await assert.rejects(stat(join(codexHome, "config.toml")), /ENOENT/);
54
+ });
55
+
56
+ test("#given Windows without Git Bash #when winget succeeds and resolver recovers #then install continues", async () => {
57
+ const runCalls = [];
58
+ const resolutions = [
59
+ { found: false, checkedPaths: [windowsGitBashPath], installHint: "install hint before winget" },
60
+ { found: true, path: windowsGitBashPath, source: "program-files" },
61
+ ];
62
+ let resolveCallCount = 0;
63
+
64
+ const result = await withBundledLspRuntimeForTest(async () => installMarketplaceLocally({
65
+ repoRoot: process.cwd(),
66
+ codexHome: await mkdtemp(join(tmpdir(), "omo-codex-script-git-bash-auto-home-")),
67
+ binDir: await mkdtemp(join(tmpdir(), "omo-codex-script-git-bash-auto-bin-")),
68
+ platform: "win32",
69
+ gitBashResolver: () => resolutions[resolveCallCount++] ?? resolutions[resolutions.length - 1],
70
+ runCommand: async (command, args, options) => {
71
+ runCalls.push([command, ...args, options.cwd].join(" "));
72
+ },
73
+ log: () => {},
74
+ }));
75
+
76
+ assert.equal(resolveCallCount, 2);
77
+ assert.match(runCalls.join("\n"), /^winget install --id Git\.Git -e --source winget /m);
78
+ assert.equal(result.gitBashPath, windowsGitBashPath);
79
+ });
80
+
81
+ test("#given non-Windows install #when running installer #then winget is never called", async () => {
82
+ const runCalls = [];
83
+ const result = await withBundledLspRuntimeForTest(async () => installMarketplaceLocally({
84
+ repoRoot: process.cwd(),
85
+ codexHome: await mkdtemp(join(tmpdir(), "omo-codex-script-git-bash-no-winget-home-")),
86
+ binDir: await mkdtemp(join(tmpdir(), "omo-codex-script-git-bash-no-winget-bin-")),
87
+ platform: "linux",
88
+ runCommand: async (command, args, options) => {
89
+ runCalls.push([command, ...args, options.cwd].join(" "));
90
+ },
91
+ log: () => {},
92
+ }));
93
+
94
+ assert.equal(result.gitBashPath, null);
95
+ assert.equal(runCalls.some((command) => command.startsWith("winget ")), false);
96
+ });
97
+
98
+ test("#given Windows env override resolves Git Bash #when installing local marketplace #then install continues", async () => {
99
+ const result = await withBundledLspRuntimeForTest(async () => installMarketplaceLocally({
100
+ repoRoot: process.cwd(),
101
+ codexHome: await mkdtemp(join(tmpdir(), "omo-codex-script-git-bash-home-")),
102
+ binDir: await mkdtemp(join(tmpdir(), "omo-codex-script-git-bash-bin-")),
103
+ platform: "win32",
104
+ gitBashResolver: () => ({ found: true, path: windowsGitBashPath, source: "env" }),
105
+ runCommand: async () => {},
106
+ log: () => {},
107
+ }));
108
+
109
+ assert.equal(result.gitBashPath, windowsGitBashPath);
110
+ assert.equal(result.installed.length, 1);
111
+ });
112
+
113
+ test("#given Windows env override in installer options #when no custom resolver is provided #then default resolver uses it", async () => {
114
+ const codexHome = await mkdtemp(join(tmpdir(), "omo-codex-script-git-bash-env-home-"));
115
+ const gitBashPath = join(await mkdtemp(join(tmpdir(), "omo-codex-script-git-bash-env-")), "bash.exe");
116
+ await writeFile(gitBashPath, "");
117
+
118
+ const result = await withBundledLspRuntimeForTest(async () => installMarketplaceLocally({
119
+ repoRoot: process.cwd(),
120
+ codexHome,
121
+ binDir: await mkdtemp(join(tmpdir(), "omo-codex-script-git-bash-env-bin-")),
122
+ platform: "win32",
123
+ env: { OMO_CODEX_GIT_BASH_PATH: gitBashPath },
124
+ runCommand: async () => {},
125
+ log: () => {},
126
+ }));
127
+
128
+ assert.equal(result.gitBashPath, gitBashPath);
129
+ });
130
+
131
+ test("#given non-Windows local install #when resolver would fail #then installer keeps existing behavior", async () => {
132
+ const codexHome = await mkdtemp(join(tmpdir(), "omo-codex-script-git-bash-linux-home-"));
133
+ const result = await withBundledLspRuntimeForTest(async () => installMarketplaceLocally({
134
+ repoRoot: process.cwd(),
135
+ codexHome,
136
+ binDir: await mkdtemp(join(tmpdir(), "omo-codex-script-git-bash-linux-bin-")),
137
+ platform: "linux",
138
+ gitBashResolver: () => ({ found: false, checkedPaths: [windowsGitBashPath], installHint: "should not be used" }),
139
+ runCommand: async () => {},
140
+ log: () => {},
141
+ }));
142
+
143
+ assert.equal(result.gitBashPath, null);
144
+ assert.match(await readFile(join(codexHome, "config.toml"), "utf8"), /\[marketplaces\.sisyphuslabs\]/);
145
+ });