open-multi-agent-kit 0.78.6 → 0.78.8

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 (170) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +38 -0
  3. package/MATURITY.md +2 -2
  4. package/README.md +19 -4
  5. package/dist/brand/night-city.theme.json +80 -0
  6. package/dist/brand/palette.d.ts +73 -177
  7. package/dist/brand/palette.js +115 -39
  8. package/dist/brand/rust-forge.theme.json +216 -0
  9. package/dist/brand/theme-compiled.d.ts +61 -0
  10. package/dist/brand/theme-compiled.js +241 -0
  11. package/dist/brand/theme.js +50 -37
  12. package/dist/cli/release-promotion-gate.d.ts +8 -1
  13. package/dist/cli/release-promotion-gate.js +20 -9
  14. package/dist/cli/root.d.ts +1 -0
  15. package/dist/cli/root.js +2 -1
  16. package/dist/cli/theme/index.d.ts +11 -2
  17. package/dist/cli/theme/index.js +6 -1
  18. package/dist/cli/theme/oklab-quantize.d.ts +29 -0
  19. package/dist/cli/theme/oklab-quantize.js +105 -0
  20. package/dist/cli/theme/render-table.d.ts +59 -0
  21. package/dist/cli/theme/render-table.js +112 -0
  22. package/dist/cli/theme/status-frame.d.ts +10 -0
  23. package/dist/cli/theme/status-frame.js +22 -0
  24. package/dist/cli/theme/terminal-capability.d.ts +6 -1
  25. package/dist/cli/theme/terminal-capability.js +20 -4
  26. package/dist/cli/theme/theme-doc.d.ts +22 -0
  27. package/dist/cli/theme/theme-doc.js +97 -0
  28. package/dist/cli/theme/theme-registry.js +39 -18
  29. package/dist/cli/theme/tier-explain.d.ts +21 -0
  30. package/dist/cli/theme/tier-explain.js +60 -0
  31. package/dist/cli/ui/green-rain-renderer.js +3 -2
  32. package/dist/cli/ui/neon-grid-renderer.js +4 -3
  33. package/dist/cli/ui/rust-forge-renderer.js +17 -3
  34. package/dist/cli/ui/system24-renderer.d.ts +18 -1
  35. package/dist/cli/ui/system24-renderer.js +39 -16
  36. package/dist/cli/v2/chat-repl.js +3 -1
  37. package/dist/cli/v2/cli-v2-skeleton.d.ts +6 -0
  38. package/dist/cli/v2/cli-v2-skeleton.js +121 -9
  39. package/dist/cli/v2/interactive-prompt.d.ts +1 -0
  40. package/dist/cli/v2/interactive-prompt.js +6 -2
  41. package/dist/commands/chat/core.js +2 -2
  42. package/dist/commands/chat/runtime.js +2 -0
  43. package/dist/commands/chat/utils.js +12 -4
  44. package/dist/commands/cockpit/render.js +102 -64
  45. package/dist/commands/cockpit/utils.d.ts +2 -0
  46. package/dist/commands/design.js +18 -16
  47. package/dist/commands/doctor/checks.d.ts +0 -1
  48. package/dist/commands/doctor/checks.js +1 -36
  49. package/dist/commands/doctor/report.js +0 -11
  50. package/dist/commands/doctor/utils.js +2 -2
  51. package/dist/commands/init/content.d.ts +1 -1
  52. package/dist/commands/init/content.js +3 -11
  53. package/dist/commands/init.js +2 -2
  54. package/dist/commands/mcp/config.d.ts +32 -0
  55. package/dist/commands/mcp/config.js +264 -0
  56. package/dist/commands/mcp/doctor-fix.d.ts +12 -0
  57. package/dist/commands/mcp/doctor-fix.js +261 -0
  58. package/dist/commands/mcp/doctor.d.ts +55 -0
  59. package/dist/commands/mcp/doctor.js +401 -0
  60. package/dist/commands/mcp/list.d.ts +1 -0
  61. package/dist/commands/mcp/list.js +56 -0
  62. package/dist/commands/mcp/shared.d.ts +47 -0
  63. package/dist/commands/mcp/shared.js +192 -0
  64. package/dist/commands/mcp/test.d.ts +5 -0
  65. package/dist/commands/mcp/test.js +457 -0
  66. package/dist/commands/mcp.d.ts +8 -104
  67. package/dist/commands/mcp.js +5 -1603
  68. package/dist/contracts/proof.d.ts +1 -1
  69. package/dist/goal/control-loop.d.ts +15 -0
  70. package/dist/goal/control-loop.js +39 -5
  71. package/dist/hud/render.d.ts +2 -0
  72. package/dist/hud/render.js +7 -1
  73. package/dist/mcp/secret-scanner.d.ts +6 -0
  74. package/dist/mcp/secret-scanner.js +44 -11
  75. package/dist/memory/graph-delta-log.d.ts +137 -0
  76. package/dist/memory/graph-delta-log.js +650 -0
  77. package/dist/memory/graph-viewer.js +13 -32
  78. package/dist/memory/local-graph-memory-store.d.ts +36 -3
  79. package/dist/memory/local-graph-memory-store.js +255 -10
  80. package/dist/orchestration/log-streamer.js +13 -8
  81. package/dist/orchestration/routing.d.ts +4 -0
  82. package/dist/orchestration/routing.js +28 -3
  83. package/dist/orchestration/state-persister.js +8 -1
  84. package/dist/providers/anthropic-messages-runner.d.ts +17 -0
  85. package/dist/providers/anthropic-messages-runner.js +197 -0
  86. package/dist/providers/model-registry.d.ts +1 -1
  87. package/dist/providers/model-registry.js +37 -3
  88. package/dist/providers/provider-runtime.js +27 -11
  89. package/dist/providers/thinking-levels.js +4 -1
  90. package/dist/providers/types.d.ts +3 -3
  91. package/dist/runtime/contracts/weakness-remediation.d.ts +9 -1
  92. package/dist/runtime/contracts/weakness-remediation.js +9 -1
  93. package/dist/runtime/router-v2-scoring.d.ts +8 -3
  94. package/dist/runtime/router-v2-scoring.js +27 -22
  95. package/dist/runtime/runtime-router.d.ts +5 -0
  96. package/dist/runtime/runtime-router.js +40 -8
  97. package/dist/runtime/sandbox-profile.d.ts +29 -0
  98. package/dist/runtime/sandbox-profile.js +101 -0
  99. package/dist/runtime/slash-commands.js +300 -2
  100. package/dist/runtime/tool-dispatch-contracts.d.ts +15 -0
  101. package/dist/runtime/tool-dispatch-contracts.js +12 -0
  102. package/dist/runtime/weights-config.d.ts +90 -0
  103. package/dist/runtime/weights-config.js +249 -0
  104. package/dist/schema/envelope.schema.d.ts +18 -18
  105. package/dist/schema/evidence.schema.d.ts +2 -2
  106. package/dist/schema/proof-bundle.schema.d.ts +6 -6
  107. package/dist/schema/proof-bundle.schema.js +0 -1
  108. package/dist/schema/provider.schema.d.ts +2 -2
  109. package/dist/schema/run-manifest.schema.d.ts +16 -16
  110. package/dist/theme/ansi.js +9 -2
  111. package/dist/theme/extended-palette.d.ts +85 -0
  112. package/dist/theme/extended-palette.js +108 -0
  113. package/dist/theme/layout.js +29 -8
  114. package/dist/theme/metrics.js +5 -5
  115. package/dist/theme/parallel.js +25 -15
  116. package/dist/ui/omk-sigil.js +5 -14
  117. package/dist/ui/omk-working-sweep.js +5 -15
  118. package/dist/util/chat-agent-mode.d.ts +11 -0
  119. package/dist/util/chat-agent-mode.js +41 -8
  120. package/dist/util/chat-cockpit.d.ts +9 -0
  121. package/dist/util/chat-cockpit.js +77 -20
  122. package/dist/util/fs/core.d.ts +5 -0
  123. package/dist/util/fs/core.js +35 -0
  124. package/dist/util/fs/internal.d.ts +14 -0
  125. package/dist/util/fs/internal.js +169 -0
  126. package/dist/util/fs/kimi-sync.d.ts +41 -0
  127. package/dist/util/fs/kimi-sync.js +495 -0
  128. package/dist/util/fs/logo.d.ts +2 -0
  129. package/dist/util/fs/logo.js +78 -0
  130. package/dist/util/fs/manifest.d.ts +6 -0
  131. package/dist/util/fs/manifest.js +40 -0
  132. package/dist/util/fs/mcp-diagnose.d.ts +15 -0
  133. package/dist/util/fs/mcp-diagnose.js +204 -0
  134. package/dist/util/fs/mcp-runtime-config.d.ts +23 -0
  135. package/dist/util/fs/mcp-runtime-config.js +412 -0
  136. package/dist/util/fs/paths.d.ts +13 -0
  137. package/dist/util/fs/paths.js +82 -0
  138. package/dist/util/fs/preflight.d.ts +27 -0
  139. package/dist/util/fs/preflight.js +271 -0
  140. package/dist/util/fs.d.ts +8 -128
  141. package/dist/util/fs.js +8 -1757
  142. package/dist/util/terminal-layout.js +5 -1
  143. package/dist/util/version.js +2 -1
  144. package/docs/2026-06-11/critical-issues.md +20 -0
  145. package/docs/2026-06-11/improvements.md +14 -0
  146. package/docs/2026-06-11/init-checklist.md +25 -0
  147. package/docs/2026-06-11/plan.md +20 -0
  148. package/docs/ARCHITECTURE_ANALYSIS_CODEGRAPH.md +223 -0
  149. package/docs/adr/0001-no-native-rust-lane.md +52 -0
  150. package/docs/codex-oauth-setup.md +14 -0
  151. package/docs/decisions/2026-06-10-git-history-bloat.md +61 -0
  152. package/docs/decisions/ADR-theme-dark-only-assets.md +43 -0
  153. package/docs/getting-started.md +1 -1
  154. package/docs/headroom-omk-final-summary.md +188 -0
  155. package/docs/headroom-omk-integration.md +394 -0
  156. package/docs/headroom-omk-setup-guide.md +422 -0
  157. package/docs/headroom-omk-usage-examples.md +371 -0
  158. package/docs/provider-maturity.md +1 -1
  159. package/docs/versioning.md +3 -3
  160. package/package.json +10 -10
  161. package/readmeasset/.npmignore +2 -0
  162. package/readmeasset/ASSET_INDEX.md +7 -5
  163. package/readmeasset/ASSET_PROVENANCE.md +21 -5
  164. package/readmeasset/omk-control-surfaces.svg +55 -0
  165. package/readmeasset/omk-core-loop.svg +1 -0
  166. package/readmeasset/omk-logo-mark.svg +1 -0
  167. package/readmeasset/omk-release-assertions.svg +51 -0
  168. package/dist/native/linux-x64/omk-safety +0 -0
  169. package/dist/util/native-safety.d.ts +0 -28
  170. package/dist/util/native-safety.js +0 -118
@@ -0,0 +1,264 @@
1
+ import { mkdir, writeFile } from "fs/promises";
2
+ import { join } from "path";
3
+ import { getProjectRoot, pathExists, getUserHome, STALE_PACKAGE_NAMES, } from "../../util/fs.js";
4
+ import { style, header, status, label } from "../../util/theme.js";
5
+ import { maskSensitiveText } from "../../util/secret-mask.js";
6
+ import { formatArgsForDisplay, isHttpUrl, isSecretEnvName, loadConfig, RAILWAY_REMOTE_MCP_URL, sanitizeMcpServerForProject, sanitizeMcpUrlForDisplay, } from "./shared.js";
7
+ export async function mcpRemoveCommand(serverName, options = {}) {
8
+ const root = getProjectRoot();
9
+ const localPath = join(root, ".kimi", "mcp.json");
10
+ const omkPath = join(root, ".omk", "mcp.json");
11
+ const globalPath = join(getUserHome(), ".kimi", "mcp.json");
12
+ const sources = options.global
13
+ ? [{ path: globalPath, label: "global" }]
14
+ : [
15
+ { path: localPath, label: "project-local" },
16
+ { path: omkPath, label: "omk-project" },
17
+ ];
18
+ let removed = false;
19
+ for (const src of sources) {
20
+ const cfg = await loadConfig(src.path);
21
+ if (!cfg.parsed || !cfg.config.mcpServers || !(serverName in cfg.config.mcpServers)) {
22
+ continue;
23
+ }
24
+ const next = { ...cfg.config.mcpServers };
25
+ delete next[serverName];
26
+ await writeFile(src.path, JSON.stringify({ mcpServers: next }, null, 2) + "\n", "utf-8");
27
+ console.log(status.ok(`Removed "${serverName}" from ${src.label} MCP config: ${src.path}`));
28
+ removed = true;
29
+ break;
30
+ }
31
+ if (!removed) {
32
+ console.log(status.error(`Server "${serverName}" not found in ${options.global ? "global" : "project-local"} MCP configs.`));
33
+ console.log(style.gray(`Checked: ${sources.map((s) => s.path).join(", ")}`));
34
+ if (!options.global) {
35
+ console.log(style.gray(`To remove from global config, run \`omk mcp remove ${serverName} --global\`, or edit ~/.kimi/mcp.json manually.`));
36
+ }
37
+ else {
38
+ console.log(style.gray(`To remove from project-local configs, run \`omk mcp remove ${serverName}\`.`));
39
+ }
40
+ process.exit(1);
41
+ }
42
+ }
43
+ export async function mcpAddCommand(serverName) {
44
+ const root = getProjectRoot();
45
+ const globalPath = join(getUserHome(), ".kimi", "mcp.json");
46
+ const projectMcpPath = join(root, ".kimi", "mcp.json");
47
+ const globalSource = await loadConfig(globalPath);
48
+ if (!globalSource.parsed || !globalSource.config.mcpServers || !(serverName in globalSource.config.mcpServers)) {
49
+ console.log(status.error(`Server "${serverName}" not found in global MCP config: ${globalPath}`));
50
+ process.exit(1);
51
+ }
52
+ const projectMcpSource = await loadConfig(projectMcpPath);
53
+ const projectMcpServers = projectMcpSource.parsed && projectMcpSource.config.mcpServers
54
+ ? { ...projectMcpSource.config.mcpServers }
55
+ : {};
56
+ if (serverName in projectMcpServers) {
57
+ console.log(status.error(`Server "${serverName}" already exists in ${projectMcpPath}`));
58
+ process.exit(1);
59
+ }
60
+ projectMcpServers[serverName] = sanitizeMcpServerForProject(globalSource.config.mcpServers[serverName]);
61
+ await mkdir(join(root, ".kimi"), { recursive: true });
62
+ await writeFile(projectMcpPath, JSON.stringify({ mcpServers: projectMcpServers }, null, 2) + "\n", "utf-8");
63
+ console.log(header("MCP Add"));
64
+ console.log(status.ok(`Added "${serverName}" to ${projectMcpPath}`));
65
+ }
66
+ export async function mcpInstallCommand(name, command, args, options = {}) {
67
+ const root = getProjectRoot();
68
+ const projectMcpPath = join(root, ".kimi", "mcp.json");
69
+ const projectMcpSource = await loadConfig(projectMcpPath);
70
+ const projectMcpServers = projectMcpSource.parsed && projectMcpSource.config.mcpServers
71
+ ? { ...projectMcpSource.config.mcpServers }
72
+ : {};
73
+ if (name in projectMcpServers) {
74
+ console.log(status.error(`Server "${name}" already exists in ${projectMcpPath}`));
75
+ process.exit(1);
76
+ }
77
+ const server = sanitizeMcpServerForProject(createInstallServer(name, command, args, options));
78
+ projectMcpServers[name] = server;
79
+ await mkdir(join(root, ".kimi"), { recursive: true });
80
+ await writeFile(projectMcpPath, JSON.stringify({ mcpServers: projectMcpServers }, null, 2) + "\n", "utf-8");
81
+ console.log(header("MCP Install"));
82
+ console.log(status.ok(`Installed "${name}" into ${projectMcpPath}`));
83
+ if (server.url) {
84
+ console.log(label("URL", sanitizeMcpUrlForDisplay(server.url)));
85
+ }
86
+ else {
87
+ console.log(label("Command", maskSensitiveText(server.command ?? command)));
88
+ if (args.length > 0)
89
+ console.log(label("Args", formatArgsForDisplay(server.args ?? args)));
90
+ }
91
+ }
92
+ function createInstallServer(name, command, args, options = {}) {
93
+ if (isRailwayInstallPreset(name, command, args, options)) {
94
+ return { url: RAILWAY_REMOTE_MCP_URL };
95
+ }
96
+ if (isHttpUrl(command) && args.length === 0) {
97
+ return { url: sanitizeMcpUrlForDisplay(command) };
98
+ }
99
+ const server = { command, args: [...args] };
100
+ if (options.env) {
101
+ server.env = {};
102
+ if (Array.isArray(options.env)) {
103
+ for (const pair of options.env) {
104
+ const idx = pair.indexOf("=");
105
+ if (idx > 0) {
106
+ const key = pair.slice(0, idx);
107
+ server.env[key] = sanitizeInstallEnvValue(key, pair.slice(idx + 1));
108
+ }
109
+ }
110
+ }
111
+ else {
112
+ for (const [key, value] of Object.entries(options.env)) {
113
+ server.env[key] = sanitizeInstallEnvValue(key, value);
114
+ }
115
+ }
116
+ }
117
+ if (Number.isFinite(options.startupTimeoutSec) && options.startupTimeoutSec && options.startupTimeoutSec > 0) {
118
+ server.startup_timeout_sec = Math.trunc(options.startupTimeoutSec);
119
+ }
120
+ return server;
121
+ }
122
+ function sanitizeInstallEnvValue(key, value) {
123
+ if (!isSecretEnvName(key))
124
+ return value;
125
+ const trimmed = value.trim();
126
+ if (/^\$\{[A-Za-z_][A-Za-z0-9_]*\}$/.test(trimmed))
127
+ return trimmed;
128
+ return `\${${key}}`;
129
+ }
130
+ function isRailwayInstallPreset(name, command, args, options) {
131
+ return name.toLowerCase() === "railway"
132
+ && command === "railway"
133
+ && args.length === 0
134
+ && (!options.env || options.env.length === 0);
135
+ }
136
+ export async function mcpBulkInstallCommand(entries) {
137
+ const root = getProjectRoot();
138
+ const projectMcpPath = join(root, ".kimi", "mcp.json");
139
+ const projectMcpSource = await loadConfig(projectMcpPath);
140
+ const projectMcpServers = projectMcpSource.parsed && projectMcpSource.config.mcpServers
141
+ ? { ...projectMcpSource.config.mcpServers }
142
+ : {};
143
+ const results = { installed: [], failed: [], skipped: [] };
144
+ await Promise.all(entries.map(async (entry) => {
145
+ try {
146
+ if (entry.name in projectMcpServers) {
147
+ results.skipped.push(entry.name);
148
+ return;
149
+ }
150
+ const server = sanitizeMcpServerForProject(createInstallServer(entry.name, entry.command, entry.args, {
151
+ env: entry.env,
152
+ startupTimeoutSec: entry.startupTimeoutSec,
153
+ }));
154
+ projectMcpServers[entry.name] = server;
155
+ results.installed.push(entry.name);
156
+ }
157
+ catch (err) {
158
+ const message = err instanceof Error ? err.message : String(err);
159
+ results.failed.push({ name: entry.name, error: message });
160
+ }
161
+ }));
162
+ if (results.installed.length > 0 || results.skipped.length > 0) {
163
+ await mkdir(join(root, ".kimi"), { recursive: true });
164
+ await writeFile(projectMcpPath, JSON.stringify({ mcpServers: projectMcpServers }, null, 2) + "\n", "utf-8");
165
+ }
166
+ return results;
167
+ }
168
+ export async function mcpSyncGlobalCommand(options) {
169
+ const root = getProjectRoot();
170
+ const globalPath = join(getUserHome(), ".kimi", "mcp.json");
171
+ const targetPath = options.omk ? join(root, ".omk", "mcp.json") : join(root, ".kimi", "mcp.json");
172
+ const globalSource = await loadConfig(globalPath);
173
+ if (!globalSource.parsed || !globalSource.config.mcpServers) {
174
+ console.log(status.error(`No global MCP config found at ${globalPath}`));
175
+ process.exit(1);
176
+ }
177
+ const targetSource = await loadConfig(targetPath);
178
+ const targetServers = targetSource.parsed && targetSource.config.mcpServers ? targetSource.config.mcpServers : {};
179
+ let imported = 0;
180
+ let skipped = 0;
181
+ const merged = options.overwrite ? {} : { ...targetServers };
182
+ for (const [name, server] of Object.entries(globalSource.config.mcpServers)) {
183
+ if (name === "omk-project") {
184
+ skipped++;
185
+ continue;
186
+ }
187
+ if (!options.overwrite && name in merged) {
188
+ skipped++;
189
+ continue;
190
+ }
191
+ merged[name] = sanitizeMcpServerForProject(server);
192
+ imported++;
193
+ }
194
+ const output = { mcpServers: merged };
195
+ await writeFile(targetPath, JSON.stringify(output, null, 2) + "\n", "utf-8");
196
+ console.log(header("MCP Sync Global"));
197
+ console.log(status.ok(`Imported ${imported} global MCP server(s)`));
198
+ if (skipped > 0) {
199
+ console.log(style.gray(`Skipped ${skipped} (omk-project or existing local)`));
200
+ }
201
+ console.log(label("Written to", targetPath));
202
+ }
203
+ export async function mcpMigrateCommand(options = {}) {
204
+ const root = getProjectRoot();
205
+ const globalPath = join(getUserHome(), ".kimi", "mcp.json");
206
+ const projectPath = join(root, ".kimi", "mcp.json");
207
+ const omkPath = join(root, ".omk", "mcp.json");
208
+ const configs = [];
209
+ if (options.global) {
210
+ configs.push({ path: globalPath, label: "global" });
211
+ }
212
+ else {
213
+ configs.push({ path: projectPath, label: "project" });
214
+ if (await pathExists(omkPath)) {
215
+ configs.push({ path: omkPath, label: "project-omk" });
216
+ }
217
+ }
218
+ let totalFixed = 0;
219
+ const fixedServers = [];
220
+ for (const { path: configPath } of configs) {
221
+ const source = await loadConfig(configPath);
222
+ if (!source.parsed || !source.config.mcpServers)
223
+ continue;
224
+ const servers = source.config.mcpServers;
225
+ let changed = false;
226
+ for (const [name, server] of Object.entries(servers)) {
227
+ const args = server.args ?? [];
228
+ for (let i = 0; i < args.length; i++) {
229
+ const arg = args[i];
230
+ if (typeof arg !== "string")
231
+ continue;
232
+ for (const [stale, current] of Object.entries(STALE_PACKAGE_NAMES)) {
233
+ if (arg.includes(stale)) {
234
+ const newArg = arg.replace(stale, current);
235
+ if (!options.dryRun) {
236
+ args[i] = newArg;
237
+ changed = true;
238
+ }
239
+ totalFixed++;
240
+ fixedServers.push({ name, file: configPath, from: stale, to: current });
241
+ }
242
+ }
243
+ }
244
+ }
245
+ if (changed) {
246
+ await writeFile(configPath, JSON.stringify(source.config, null, 2) + "\n", "utf-8");
247
+ }
248
+ }
249
+ console.log(header("MCP Migrate"));
250
+ if (totalFixed === 0) {
251
+ console.log(status.ok("No stale package names found"));
252
+ }
253
+ else {
254
+ if (options.dryRun) {
255
+ console.log(style.skin(`${totalFixed} stale package reference(s) would be fixed:`));
256
+ }
257
+ else {
258
+ console.log(status.ok(`Fixed ${totalFixed} stale package reference(s):`));
259
+ }
260
+ for (const entry of fixedServers) {
261
+ console.log(` ${label(entry.name, `${entry.from} → ${entry.to}`)} (${entry.file})`);
262
+ }
263
+ }
264
+ }
@@ -0,0 +1,12 @@
1
+ export interface McpDoctorFixReport {
2
+ changed: boolean;
3
+ dryRun: boolean;
4
+ global: boolean;
5
+ actions: string[];
6
+ skipped: string[];
7
+ backups: string[];
8
+ }
9
+ export declare function repairMcpDoctorIssues(options?: {
10
+ dryRun?: boolean;
11
+ global?: boolean;
12
+ }): Promise<McpDoctorFixReport>;
@@ -0,0 +1,261 @@
1
+ import { chmod, mkdir, readFile, writeFile } from "fs/promises";
2
+ import { dirname, join } from "path";
3
+ import { collectMcpConfigs, diagnoseRuntimeMcpServer, getProjectRoot, pathExists, getUserHome, STALE_PACKAGE_NAMES, } from "../../util/fs.js";
4
+ import { getOmkResourceSettings } from "../../util/resource-profile.js";
5
+ import { maskSensitiveText } from "../../util/secret-mask.js";
6
+ import { collectServers, isHttpUrl, isNpmLauncherCommand, resolveAllConfigs, sanitizeMcpServerForProject, } from "./shared.js";
7
+ const AUTO_DISABLE_RUNTIME_BLOCKERS = new Set([
8
+ "missing-command",
9
+ "command-path-not-found",
10
+ "arg-path-not-found",
11
+ "shell-builtin-command",
12
+ "windows-set-inline",
13
+ "inline-script-path-not-found",
14
+ "stale-home-reference",
15
+ "stdio-http-transport",
16
+ "invalid-server",
17
+ ]);
18
+ async function ensureMcpConfigFile(filePath, config, actions, options = {}) {
19
+ if (await pathExists(filePath))
20
+ return;
21
+ if (options.dryRun) {
22
+ actions.push(`would create MCP config ${filePath}`);
23
+ return;
24
+ }
25
+ await mkdir(dirname(filePath), { recursive: true });
26
+ await writeFile(filePath, JSON.stringify(config, null, 2) + "\n", "utf-8");
27
+ actions.push(`created MCP config ${filePath}`);
28
+ }
29
+ function hasConfiguredMcpServers(config) {
30
+ return Object.keys(config?.mcpServers ?? {}).length > 0;
31
+ }
32
+ async function readParsedMcpConfig(filePath) {
33
+ if (!(await pathExists(filePath)))
34
+ return null;
35
+ try {
36
+ const parsed = JSON.parse(await readFile(filePath, "utf-8"));
37
+ return parsed && typeof parsed === "object" ? parsed : null;
38
+ }
39
+ catch {
40
+ return null;
41
+ }
42
+ }
43
+ async function migrateLegacyOmkMcpBeforeEnsure(omkMcpPath, projectMcpPath, actions, options = {}) {
44
+ const legacyConfig = await readParsedMcpConfig(omkMcpPath);
45
+ if (!hasConfiguredMcpServers(legacyConfig))
46
+ return;
47
+ const projectConfig = await readParsedMcpConfig(projectMcpPath);
48
+ if (projectConfig && hasConfiguredMcpServers(projectConfig))
49
+ return;
50
+ if (options.dryRun) {
51
+ actions.push(`would migrate legacy MCP servers from ${omkMcpPath} to ${projectMcpPath}`);
52
+ return;
53
+ }
54
+ const migratedConfig = {
55
+ ...(projectConfig ?? {}),
56
+ _comment: "Project-local MCP config migrated from legacy .omk/mcp.json; review secrets before sharing.",
57
+ mcpServers: { ...(legacyConfig?.mcpServers ?? {}) },
58
+ };
59
+ if (projectConfig) {
60
+ await createSanitizedMcpBackup(projectMcpPath, projectConfig, options.backups ?? [], actions);
61
+ }
62
+ await mkdir(dirname(projectMcpPath), { recursive: true });
63
+ await writeFile(projectMcpPath, JSON.stringify(migratedConfig, null, 2) + "\n", "utf-8");
64
+ actions.push(`migrated legacy MCP servers from ${omkMcpPath} to ${projectMcpPath}`);
65
+ }
66
+ function cloneMcpConfig(config) {
67
+ return JSON.parse(JSON.stringify(config));
68
+ }
69
+ function timestampForBackup() {
70
+ return new Date().toISOString().replace(/[:.]/g, "-");
71
+ }
72
+ async function createSanitizedMcpBackup(filePath, config, backups, actions) {
73
+ if (!(await pathExists(filePath)))
74
+ return;
75
+ const backupPath = `${filePath}.omk-backup-${timestampForBackup()}-${backups.length + 1}.json`;
76
+ const dir = dirname(filePath);
77
+ await mkdir(dir, { recursive: true, mode: 0o700 });
78
+ await chmod(dir, 0o700).catch(() => undefined);
79
+ await writeFile(backupPath, JSON.stringify(sanitizeMcpConfigForBackup(config), null, 2) + "\n", { encoding: "utf-8", mode: 0o600 });
80
+ await chmod(backupPath, 0o600).catch(() => undefined);
81
+ backups.push(backupPath);
82
+ actions.push(`created sanitized MCP backup ${backupPath}`);
83
+ }
84
+ function sanitizeMcpConfigForBackup(config) {
85
+ const cleaned = cloneMcpConfig(config);
86
+ if (cleaned.mcpServers && typeof cleaned.mcpServers === "object") {
87
+ cleaned.mcpServers = Object.fromEntries(Object.entries(cleaned.mcpServers).map(([name, server]) => [name, sanitizeMcpServerForProject(server)]));
88
+ }
89
+ const disabled = cleaned._omkDisabledMcpServers;
90
+ if (disabled && typeof disabled === "object" && !Array.isArray(disabled)) {
91
+ cleaned._omkDisabledMcpServers = Object.fromEntries(Object.entries(disabled).map(([name, server]) => [
92
+ name,
93
+ server && typeof server === "object" && !Array.isArray(server)
94
+ ? sanitizeMcpServerForProject(server)
95
+ : server,
96
+ ]));
97
+ }
98
+ return cleaned;
99
+ }
100
+ function repairMcpServerConfig(name, server) {
101
+ const actions = [];
102
+ if (server.command && isHttpUrl(server.command) && (!server.args || server.args.length === 0)) {
103
+ server.url = server.command;
104
+ delete server.command;
105
+ delete server.args;
106
+ actions.push(`converted "${name}" command URL to remote url transport`);
107
+ }
108
+ if (!server.url && server.args?.includes("@modelcontextprotocol/server-pdf") && !server.args.includes("--stdio")) {
109
+ server.args = [...server.args, "--stdio"];
110
+ actions.push(`added --stdio to "${name}" PDF MCP server`);
111
+ }
112
+ if (!server.url && server.args) {
113
+ let replaced = false;
114
+ server.args = server.args.map((arg) => {
115
+ const replacement = STALE_PACKAGE_NAMES[arg];
116
+ if (!replacement)
117
+ return arg;
118
+ replaced = true;
119
+ return replacement;
120
+ });
121
+ if (replaced)
122
+ actions.push(`replaced stale MCP package argument for "${name}"`);
123
+ }
124
+ if (!server.url && isNpmLauncherCommand(server.command) && !server.startup_timeout_sec) {
125
+ server.startup_timeout_sec = 15;
126
+ actions.push(`set 15s startup timeout for npm-based MCP "${name}"`);
127
+ }
128
+ return actions;
129
+ }
130
+ function disabledMcpServers(config) {
131
+ const current = config._omkDisabledMcpServers;
132
+ if (current && typeof current === "object" && !Array.isArray(current)) {
133
+ return current;
134
+ }
135
+ const next = {};
136
+ config._omkDisabledMcpServers = next;
137
+ return next;
138
+ }
139
+ function moveMcpServerToDisabled(source, name, server, reason) {
140
+ const disabled = disabledMcpServers(source.config);
141
+ disabled[name] = {
142
+ ...server,
143
+ _omkDisabledAt: new Date().toISOString(),
144
+ _omkDisabledReason: reason,
145
+ };
146
+ delete source.config.mcpServers?.[name];
147
+ source.changed = true;
148
+ }
149
+ export async function repairMcpDoctorIssues(options = {}) {
150
+ const root = getProjectRoot();
151
+ const projectOmkDir = join(root, ".omk");
152
+ const projectKimiDir = join(root, ".kimi");
153
+ const omkMcpPath = join(projectOmkDir, "mcp.json");
154
+ const projectMcpPath = join(projectKimiDir, "mcp.json");
155
+ const dryRun = Boolean(options.dryRun);
156
+ const includeGlobal = Boolean(options.global);
157
+ const actions = [];
158
+ const skipped = [];
159
+ const backups = [];
160
+ if (!dryRun) {
161
+ await Promise.all([
162
+ mkdir(projectOmkDir, { recursive: true }),
163
+ mkdir(projectKimiDir, { recursive: true }),
164
+ ]);
165
+ }
166
+ await migrateLegacyOmkMcpBeforeEnsure(omkMcpPath, projectMcpPath, actions, { dryRun, backups });
167
+ await ensureMcpConfigFile(omkMcpPath, { mcpServers: {} }, actions, { dryRun });
168
+ await ensureMcpConfigFile(projectMcpPath, {
169
+ _comment: "Project-local MCP config. omk-project is virtual runtime MCP injected; global MCP servers remain in ~/.kimi/mcp.json and must be imported explicitly only after secret review.",
170
+ mcpServers: {},
171
+ }, actions, { dryRun });
172
+ const sources = await resolveAllConfigs();
173
+ const resources = await getOmkResourceSettings();
174
+ const activePathOrder = await collectMcpConfigs(resources.mcpScope);
175
+ const activePaths = new Set(activePathOrder);
176
+ const activePreferredPath = activePathOrder.at(-1);
177
+ const globalMcpPath = join(getUserHome(), ".kimi", "mcp.json");
178
+ const mutableSources = new Map();
179
+ for (const source of sources) {
180
+ if (!source.exists)
181
+ continue;
182
+ if (!source.parsed) {
183
+ skipped.push(`invalid JSON left unchanged: ${source.path}`);
184
+ continue;
185
+ }
186
+ const clonedConfig = cloneMcpConfig(source.config);
187
+ mutableSources.set(source.path, {
188
+ path: source.path,
189
+ config: clonedConfig,
190
+ originalConfig: cloneMcpConfig(source.config),
191
+ changed: false,
192
+ });
193
+ }
194
+ const writableConfigPaths = new Set([omkMcpPath, projectMcpPath]);
195
+ if (includeGlobal)
196
+ writableConfigPaths.add(globalMcpPath);
197
+ const servers = collectServers(sources);
198
+ for (const [name, info] of servers) {
199
+ const activeSources = info.sources.filter((sourcePath) => activePaths.has(sourcePath));
200
+ if (activeSources.length <= 1)
201
+ continue;
202
+ const projectActiveSources = activeSources.filter((sourcePath) => sourcePath !== globalMcpPath);
203
+ if (activeSources.includes(globalMcpPath) && projectActiveSources.length > 0) {
204
+ skipped.push(`duplicate MCP "${name}" spans global/project; kept project override: ${activeSources.join(", ")}`);
205
+ continue;
206
+ }
207
+ const removableSources = activeSources.includes(globalMcpPath)
208
+ ? activeSources.filter((sourcePath) => sourcePath !== globalMcpPath)
209
+ : activeSources.filter((sourcePath) => sourcePath !== activePreferredPath);
210
+ for (const sourcePath of removableSources) {
211
+ if (!writableConfigPaths.has(sourcePath)) {
212
+ skipped.push(`duplicate MCP "${name}" not modified outside requested repair scope: ${sourcePath}`);
213
+ continue;
214
+ }
215
+ const source = mutableSources.get(sourcePath);
216
+ if (!source?.config.mcpServers?.[name])
217
+ continue;
218
+ delete source.config.mcpServers[name];
219
+ source.changed = true;
220
+ actions.push(`removed duplicate MCP "${name}" from ${sourcePath}`);
221
+ }
222
+ if (removableSources.length === 0) {
223
+ skipped.push(`duplicate MCP "${name}" requires manual choice: ${activeSources.join(", ")}`);
224
+ }
225
+ }
226
+ for (const source of mutableSources.values()) {
227
+ if (!writableConfigPaths.has(source.path))
228
+ continue;
229
+ const serversByName = source.config.mcpServers ?? {};
230
+ for (const [name, server] of Object.entries(serversByName)) {
231
+ const serverActions = repairMcpServerConfig(name, server);
232
+ if (serverActions.length > 0) {
233
+ source.changed = true;
234
+ actions.push(...serverActions.map((action) => `${source.path}: ${action}`));
235
+ }
236
+ const diagnostics = await diagnoseRuntimeMcpServer(name, server);
237
+ const blockers = diagnostics.filter((diagnostic) => AUTO_DISABLE_RUNTIME_BLOCKERS.has(diagnostic.kind));
238
+ if (blockers.length > 0) {
239
+ const reason = blockers.map((diagnostic) => diagnostic.message).join("; ");
240
+ moveMcpServerToDisabled(source, name, server, reason);
241
+ actions.push(`${source.path}: disabled MCP "${name}" due to runtime startup blocker: ${reason}`);
242
+ }
243
+ }
244
+ }
245
+ for (const source of mutableSources.values()) {
246
+ if (!source.changed)
247
+ continue;
248
+ if (dryRun)
249
+ continue;
250
+ await createSanitizedMcpBackup(source.path, source.originalConfig, backups, actions);
251
+ await writeFile(source.path, JSON.stringify(source.config, null, 2) + "\n", "utf-8");
252
+ }
253
+ return {
254
+ changed: !dryRun && actions.length > 0,
255
+ dryRun,
256
+ global: includeGlobal,
257
+ actions: actions.map(maskSensitiveText),
258
+ skipped: skipped.map(maskSensitiveText),
259
+ backups,
260
+ };
261
+ }
@@ -0,0 +1,55 @@
1
+ import { type McpDoctorFixReport } from "./doctor-fix.js";
2
+ export interface McpDoctorOptions {
3
+ json?: boolean;
4
+ fix?: boolean;
5
+ dryRun?: boolean;
6
+ global?: boolean;
7
+ }
8
+ type McpDoctorSeverity = "ok" | "info" | "warn" | "error";
9
+ export interface McpDoctorCheck {
10
+ severity: McpDoctorSeverity;
11
+ kind: string;
12
+ message: string;
13
+ }
14
+ export interface McpDoctorSourceReport {
15
+ path: string;
16
+ exists: boolean;
17
+ active: boolean;
18
+ parsed: boolean;
19
+ status: "ok" | "empty" | "error";
20
+ serverCount: number;
21
+ error?: string;
22
+ }
23
+ export interface McpDoctorServerReport {
24
+ name: string;
25
+ status: "ok" | "warn" | "error";
26
+ active: boolean;
27
+ sources: string[];
28
+ activeSources: string[];
29
+ transport: "remote" | "stdio" | "invalid";
30
+ url?: string;
31
+ command?: string;
32
+ resolvedCommand?: string;
33
+ timeoutSec?: number;
34
+ checks: McpDoctorCheck[];
35
+ }
36
+ export interface McpDoctorReport {
37
+ ok: boolean;
38
+ command: "mcp doctor";
39
+ checkedAt: string;
40
+ activeScope: string;
41
+ issueCount: number;
42
+ errors: string[];
43
+ warnings: string[];
44
+ fixes?: McpDoctorFixReport;
45
+ sources: McpDoctorSourceReport[];
46
+ servers: McpDoctorServerReport[];
47
+ data: {
48
+ activeScope: string;
49
+ sourceCount: number;
50
+ serverCount: number;
51
+ };
52
+ }
53
+ export declare function mcpDoctorCommand(options?: McpDoctorOptions): Promise<void>;
54
+ export declare function buildMcpDoctorReport(): Promise<McpDoctorReport>;
55
+ export {};