open-multi-agent-kit 0.78.5 → 0.78.7

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 (176) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +64 -0
  3. package/MATURITY.md +2 -2
  4. package/README.md +20 -5
  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 +23 -11
  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/init/content.d.ts +1 -1
  51. package/dist/commands/init/content.js +3 -11
  52. package/dist/commands/init.js +2 -2
  53. package/dist/commands/mcp/config.d.ts +32 -0
  54. package/dist/commands/mcp/config.js +264 -0
  55. package/dist/commands/mcp/doctor-fix.d.ts +12 -0
  56. package/dist/commands/mcp/doctor-fix.js +261 -0
  57. package/dist/commands/mcp/doctor.d.ts +55 -0
  58. package/dist/commands/mcp/doctor.js +401 -0
  59. package/dist/commands/mcp/list.d.ts +1 -0
  60. package/dist/commands/mcp/list.js +56 -0
  61. package/dist/commands/mcp/shared.d.ts +47 -0
  62. package/dist/commands/mcp/shared.js +192 -0
  63. package/dist/commands/mcp/test.d.ts +5 -0
  64. package/dist/commands/mcp/test.js +457 -0
  65. package/dist/commands/mcp.d.ts +8 -104
  66. package/dist/commands/mcp.js +5 -1603
  67. package/dist/contracts/proof.d.ts +1 -1
  68. package/dist/goal/control-loop.d.ts +15 -0
  69. package/dist/goal/control-loop.js +39 -5
  70. package/dist/hud/render.d.ts +2 -0
  71. package/dist/hud/render.js +7 -1
  72. package/dist/mcp/secret-scanner.d.ts +6 -0
  73. package/dist/mcp/secret-scanner.js +44 -11
  74. package/dist/memory/graph-delta-log.d.ts +137 -0
  75. package/dist/memory/graph-delta-log.js +650 -0
  76. package/dist/memory/graph-viewer.js +13 -32
  77. package/dist/memory/local-graph-memory-store.d.ts +36 -3
  78. package/dist/memory/local-graph-memory-store.js +255 -10
  79. package/dist/orchestration/log-streamer.js +13 -8
  80. package/dist/orchestration/routing.d.ts +4 -0
  81. package/dist/orchestration/routing.js +28 -3
  82. package/dist/orchestration/state-persister.js +8 -1
  83. package/dist/providers/anthropic-messages-runner.d.ts +17 -0
  84. package/dist/providers/anthropic-messages-runner.js +197 -0
  85. package/dist/providers/model-registry.d.ts +1 -1
  86. package/dist/providers/model-registry.js +37 -3
  87. package/dist/providers/provider-runtime.js +27 -11
  88. package/dist/providers/thinking-levels.js +4 -1
  89. package/dist/providers/types.d.ts +3 -3
  90. package/dist/runtime/contracts/weakness-remediation.d.ts +11 -1
  91. package/dist/runtime/contracts/weakness-remediation.js +9 -1
  92. package/dist/runtime/router-v2-scoring.d.ts +8 -3
  93. package/dist/runtime/router-v2-scoring.js +27 -22
  94. package/dist/runtime/runtime-router.d.ts +5 -0
  95. package/dist/runtime/runtime-router.js +40 -8
  96. package/dist/runtime/sandbox-profile.d.ts +29 -0
  97. package/dist/runtime/sandbox-profile.js +101 -0
  98. package/dist/runtime/slash-commands.js +300 -2
  99. package/dist/runtime/tool-dispatch-contracts.d.ts +15 -0
  100. package/dist/runtime/tool-dispatch-contracts.js +12 -0
  101. package/dist/runtime/weights-config.d.ts +90 -0
  102. package/dist/runtime/weights-config.js +249 -0
  103. package/dist/schema/envelope.schema.d.ts +18 -18
  104. package/dist/schema/evidence.schema.d.ts +2 -2
  105. package/dist/schema/proof-bundle.schema.d.ts +6 -6
  106. package/dist/schema/proof-bundle.schema.js +0 -1
  107. package/dist/schema/provider.schema.d.ts +2 -2
  108. package/dist/schema/run-manifest.schema.d.ts +16 -16
  109. package/dist/theme/ansi.js +9 -2
  110. package/dist/theme/extended-palette.d.ts +85 -0
  111. package/dist/theme/extended-palette.js +108 -0
  112. package/dist/theme/layout.js +29 -8
  113. package/dist/theme/metrics.js +5 -5
  114. package/dist/theme/parallel.js +25 -15
  115. package/dist/ui/omk-sigil.js +5 -14
  116. package/dist/ui/omk-working-sweep.js +5 -15
  117. package/dist/util/chat-agent-mode.d.ts +11 -0
  118. package/dist/util/chat-agent-mode.js +41 -8
  119. package/dist/util/chat-cockpit.d.ts +9 -0
  120. package/dist/util/chat-cockpit.js +77 -20
  121. package/dist/util/fs/core.d.ts +5 -0
  122. package/dist/util/fs/core.js +35 -0
  123. package/dist/util/fs/internal.d.ts +14 -0
  124. package/dist/util/fs/internal.js +169 -0
  125. package/dist/util/fs/kimi-sync.d.ts +41 -0
  126. package/dist/util/fs/kimi-sync.js +495 -0
  127. package/dist/util/fs/logo.d.ts +2 -0
  128. package/dist/util/fs/logo.js +78 -0
  129. package/dist/util/fs/manifest.d.ts +6 -0
  130. package/dist/util/fs/manifest.js +40 -0
  131. package/dist/util/fs/mcp-diagnose.d.ts +15 -0
  132. package/dist/util/fs/mcp-diagnose.js +204 -0
  133. package/dist/util/fs/mcp-runtime-config.d.ts +23 -0
  134. package/dist/util/fs/mcp-runtime-config.js +412 -0
  135. package/dist/util/fs/paths.d.ts +13 -0
  136. package/dist/util/fs/paths.js +82 -0
  137. package/dist/util/fs/preflight.d.ts +27 -0
  138. package/dist/util/fs/preflight.js +271 -0
  139. package/dist/util/fs.d.ts +8 -128
  140. package/dist/util/fs.js +8 -1757
  141. package/dist/util/terminal-layout.js +5 -1
  142. package/dist/util/version.js +2 -1
  143. package/docs/2026-06-10/critical-issues.md +20 -0
  144. package/docs/2026-06-10/improvements.md +14 -0
  145. package/docs/2026-06-10/init-checklist.md +25 -0
  146. package/docs/2026-06-10/plan.md +20 -0
  147. package/docs/2026-06-11/critical-issues.md +20 -0
  148. package/docs/2026-06-11/improvements.md +14 -0
  149. package/docs/2026-06-11/init-checklist.md +25 -0
  150. package/docs/2026-06-11/plan.md +20 -0
  151. package/docs/ARCHITECTURE_ANALYSIS_CODEGRAPH.md +223 -0
  152. package/docs/adr/0001-no-native-rust-lane.md +52 -0
  153. package/docs/codex-oauth-setup.md +14 -0
  154. package/docs/decisions/2026-06-10-git-history-bloat.md +61 -0
  155. package/docs/decisions/ADR-theme-dark-only-assets.md +43 -0
  156. package/docs/getting-started.md +1 -1
  157. package/docs/headroom-omk-final-summary.md +188 -0
  158. package/docs/headroom-omk-integration.md +394 -0
  159. package/docs/headroom-omk-setup-guide.md +422 -0
  160. package/docs/headroom-omk-usage-examples.md +371 -0
  161. package/docs/provider-maturity.md +1 -1
  162. package/docs/versioning.md +3 -3
  163. package/package.json +10 -10
  164. package/readmeasset/.npmignore +2 -0
  165. package/readmeasset/ASSET_INDEX.md +7 -5
  166. package/readmeasset/ASSET_PROVENANCE.md +21 -5
  167. package/readmeasset/omk-control-surfaces.svg +55 -0
  168. package/readmeasset/omk-core-loop.svg +1 -0
  169. package/readmeasset/omk-logo-mark.svg +1 -0
  170. package/readmeasset/omk-release-assertions.svg +51 -0
  171. package/dist/native/linux-x64/omk-safety +0 -0
  172. package/dist/util/native-safety.d.ts +0 -28
  173. package/dist/util/native-safety.js +0 -118
  174. package/templates/skills/agents/omk-adaptorch-orchestration-review/SKILL.md +0 -52
  175. package/templates/skills/kimi/mcp-install/SKILL.md +0 -66
  176. package/templates/skills/kimi/omk-adaptorch-orchestration-review/SKILL.md +0 -52
@@ -0,0 +1,204 @@
1
+ import { homedir } from "os";
2
+ import { join } from "path";
3
+ import { pathExists } from "./core.js";
4
+ import { getProjectRoot, getProjectRootAsync, getUserHome } from "./paths.js";
5
+ import { basenameOfRuntimeCommand, hasHttpTransportMismatch, isRecord, isShellInlineMcpArg, runtimeShellInlineScripts, } from "./internal.js";
6
+ /** Canonical OMK MCP config collection with project `.kimi/mcp.json` compatibility. */
7
+ export async function collectMcpConfigs(scope = "project") {
8
+ const configs = [];
9
+ if (scope === "none")
10
+ return configs;
11
+ const root = await getProjectRootAsync();
12
+ const home = getUserHome();
13
+ const projectOmkMcp = join(root, ".omk", "mcp.json");
14
+ const projectKimiMcp = join(root, ".kimi", "mcp.json");
15
+ const globalOmkMcp = join(home, ".omk", "mcp.json");
16
+ const globalKimiMcp = join(home, ".kimi", "mcp.json");
17
+ // Precedence is left-to-right with later files winning during runtime merge:
18
+ // global first, project second. Project `.kimi/mcp.json` is the active
19
+ // compatibility surface; `.omk/mcp.json` is used only as a project fallback
20
+ // when the `.kimi` companion does not exist.
21
+ if (scope === "all") {
22
+ for (const candidate of [globalOmkMcp, globalKimiMcp]) {
23
+ if (await pathExists(candidate))
24
+ configs.push(candidate);
25
+ }
26
+ }
27
+ if (await pathExists(projectKimiMcp)) {
28
+ configs.push(projectKimiMcp);
29
+ }
30
+ else if (await pathExists(projectOmkMcp)) {
31
+ configs.push(projectOmkMcp);
32
+ }
33
+ return [...new Set(configs)];
34
+ }
35
+ const SHELL_BUILTIN_MCP_COMMANDS = new Set([
36
+ "set",
37
+ "source",
38
+ "export",
39
+ "alias",
40
+ "cd",
41
+ "copy",
42
+ "del",
43
+ "dir",
44
+ "move",
45
+ "start",
46
+ ]);
47
+ const WINDOWS_SYSTEM32_SET_RE = /(?:^|\/)mnt\/[a-z]\/windows\/system32\/set(?:\.exe)?(?:\s|$|[;&|])/i;
48
+ const POSIX_HOME_REF_RE = /\/home\/([A-Za-z0-9._-]+)(?:\/|$)/g;
49
+ const HOST_HOME = homedir();
50
+ export const STALE_PACKAGE_NAMES = {
51
+ "@supabase/mcp-server@latest": "@supabase/mcp-server-supabase@latest",
52
+ };
53
+ function isRuntimePathLike(value) {
54
+ return value.startsWith("/")
55
+ || value.startsWith("~/")
56
+ || /^[A-Za-z]:[\\/]/.test(value)
57
+ || value.startsWith("\\\\");
58
+ }
59
+ function expandRuntimeUserPath(value) {
60
+ if (value === "~")
61
+ return getUserHome();
62
+ if (value.startsWith("~/"))
63
+ return join(getUserHome(), value.slice(2));
64
+ return value;
65
+ }
66
+ function allowedRuntimeHomes() {
67
+ return [
68
+ getUserHome(),
69
+ HOST_HOME,
70
+ homedir(),
71
+ posixHomeRoot(process.execPath),
72
+ posixHomeRoot(process.argv[1]),
73
+ posixHomeRoot(getProjectRoot()),
74
+ ]
75
+ .map((home) => home.replace(/\\/g, "/").replace(/\/+$/, ""))
76
+ .filter((home, index, homes) => home.length > 0 && homes.indexOf(home) === index);
77
+ }
78
+ function posixHomeRoot(value) {
79
+ const match = value?.replace(/\\/g, "/").match(/^\/home\/[A-Za-z0-9._-]+(?:\/|$)/);
80
+ return match ? match[0].replace(/\/$/, "") : "";
81
+ }
82
+ function containsStaleHomeReference(value) {
83
+ const normalized = value.replace(/\\/g, "/");
84
+ for (const match of normalized.matchAll(POSIX_HOME_REF_RE)) {
85
+ const referencedHome = `/home/${match[1]}`;
86
+ const allowed = allowedRuntimeHomes().some((home) => {
87
+ const normalizedHome = home.replace(/\\/g, "/").replace(/\/+$/, "");
88
+ return normalizedHome === referencedHome || normalizedHome.startsWith(`${referencedHome}/`);
89
+ });
90
+ if (!allowed)
91
+ return true;
92
+ }
93
+ return false;
94
+ }
95
+ function shouldValidateRuntimeMcpArgPath(server, arg, index) {
96
+ if (typeof arg !== "string")
97
+ return false;
98
+ if (!arg || arg.startsWith("-") || arg.startsWith("$") || /^https?:\/\//i.test(arg))
99
+ return false;
100
+ if (isShellInlineMcpArg(server, index))
101
+ return false;
102
+ if (/[ \t\r\n;"'|&<>]/.test(arg))
103
+ return false;
104
+ return isRuntimePathLike(arg);
105
+ }
106
+ function findInlineScriptPaths(script) {
107
+ const paths = new Set();
108
+ const re = /(?:^|[\s"'`=:(])((?:~|\/)[^\s"'`|&;<>:]+?\.(?:cjs|mjs|js|py))(?:$|[\s"'`),;|&<>:])/gi;
109
+ for (const match of script.matchAll(re)) {
110
+ const candidate = match[1];
111
+ if (!candidate || /[*?[\]{}$]/.test(candidate))
112
+ continue;
113
+ paths.add(candidate);
114
+ }
115
+ return [...paths];
116
+ }
117
+ export async function diagnoseRuntimeMcpServer(name, server) {
118
+ const diagnostics = [];
119
+ if (!isRecord(server)) {
120
+ return [{ name, kind: "invalid-server", message: "server definition must be an object" }];
121
+ }
122
+ if (server.enabled === false) {
123
+ return [{ name, kind: "disabled-server", message: "server is disabled" }];
124
+ }
125
+ if (typeof server.url === "string" && server.url.trim()) {
126
+ return diagnostics;
127
+ }
128
+ const command = typeof server.command === "string" ? server.command.trim() : "";
129
+ if (!command) {
130
+ return [{ name, kind: "missing-command", message: "stdio server has no command" }];
131
+ }
132
+ const commandName = basenameOfRuntimeCommand(command);
133
+ if (SHELL_BUILTIN_MCP_COMMANDS.has(commandName)) {
134
+ diagnostics.push({
135
+ name,
136
+ kind: "shell-builtin-command",
137
+ message: "shell built-in was configured as the MCP command; wrap it in a shell or move values to env",
138
+ });
139
+ }
140
+ if (hasHttpTransportMismatch(name, server)) {
141
+ diagnostics.push({
142
+ name,
143
+ kind: "stdio-http-transport",
144
+ message: "stdio MCP config starts an HTTP MCP server that writes startup logs to stdout; configure it as a remote url or use stdio transport",
145
+ });
146
+ }
147
+ if (isRuntimePathLike(command)) {
148
+ const commandPath = expandRuntimeUserPath(command);
149
+ if (containsStaleHomeReference(commandPath)) {
150
+ diagnostics.push({ name, kind: "stale-home-reference", message: "MCP config references a different user home path" });
151
+ }
152
+ if (!(await pathExists(commandPath))) {
153
+ diagnostics.push({ name, kind: "command-path-not-found", message: "configured command path does not exist" });
154
+ }
155
+ }
156
+ for (const script of runtimeShellInlineScripts(server)) {
157
+ if (containsStaleHomeReference(script)) {
158
+ diagnostics.push({ name, kind: "stale-home-reference", message: "MCP config references a different user home path" });
159
+ }
160
+ if (WINDOWS_SYSTEM32_SET_RE.test(script.replace(/\\/g, "/"))) {
161
+ diagnostics.push({
162
+ name,
163
+ kind: "windows-set-inline",
164
+ message: "Windows System32 set was embedded in a shell MCP command and cannot be launched from WSL",
165
+ });
166
+ }
167
+ for (const candidate of findInlineScriptPaths(script)) {
168
+ const expanded = expandRuntimeUserPath(candidate);
169
+ if (containsStaleHomeReference(expanded)) {
170
+ diagnostics.push({ name, kind: "stale-home-reference", message: "MCP config references a different user home path" });
171
+ }
172
+ if (!(await pathExists(expanded))) {
173
+ diagnostics.push({ name, kind: "inline-script-path-not-found", message: "inline MCP script references a missing local script" });
174
+ }
175
+ }
176
+ }
177
+ const args = Array.isArray(server.args) ? server.args : [];
178
+ for (const [index, arg] of args.entries()) {
179
+ if (!shouldValidateRuntimeMcpArgPath(server, arg, index))
180
+ continue;
181
+ const argPath = expandRuntimeUserPath(arg);
182
+ if (containsStaleHomeReference(argPath)) {
183
+ diagnostics.push({ name, kind: "stale-home-reference", message: "MCP config references a different user home path" });
184
+ }
185
+ if (!(await pathExists(argPath))) {
186
+ diagnostics.push({ name, kind: "arg-path-not-found", message: "MCP argument path does not exist" });
187
+ }
188
+ }
189
+ // Detect known renamed/broken npm package names in MCP server args
190
+ for (const arg of args) {
191
+ if (typeof arg !== "string")
192
+ continue;
193
+ for (const [stale, current] of Object.entries(STALE_PACKAGE_NAMES)) {
194
+ if (arg.includes(stale)) {
195
+ diagnostics.push({
196
+ name,
197
+ kind: "stale-package-name",
198
+ message: `MCP server package was renamed: ${stale} → ${current}. Run \`omk mcp migrate\` to auto-fix, or update the config manually.`,
199
+ });
200
+ }
201
+ }
202
+ }
203
+ return diagnostics;
204
+ }
@@ -0,0 +1,23 @@
1
+ import { type OmkRuntimeScope } from "../resource-profile.js";
2
+ import { type RuntimeMcpNormalization, type RuntimeMcpPruneDiagnostic } from "./mcp-diagnose.js";
3
+ export declare function pruneRuntimeMcpServers(servers: Record<string, unknown>): Promise<{
4
+ servers: Record<string, unknown>;
5
+ diagnostics: RuntimeMcpPruneDiagnostic[];
6
+ normalizations: RuntimeMcpNormalization[];
7
+ }>;
8
+ export declare function writeRuntimeMcpConfig(configPaths: string[], allowlist?: readonly string[]): Promise<string | null>;
9
+ /**
10
+ * Auto-generate a built-in omk-project MCP config so users never need to
11
+ * define it manually in ~/.kimi/mcp.json or .kimi/mcp.json.
12
+ * The config uses the currently-running omk CLI path and sets OMK_PROJECT_ROOT
13
+ * to the current project root.
14
+ */
15
+ export declare function writeBuiltinMcpConfig(): Promise<string | null>;
16
+ /** Kimi CLI 실행 인자에 model + MCP + Skills 주입 (전역 동기화는 별도) */
17
+ export declare function injectKimiGlobals(args: string[], options?: {
18
+ mcpScope?: OmkRuntimeScope;
19
+ skillsScope?: OmkRuntimeScope;
20
+ hooksScope?: OmkRuntimeScope;
21
+ role?: string;
22
+ mcpAllowlist?: readonly string[];
23
+ }): Promise<void>;
@@ -0,0 +1,412 @@
1
+ import { chmod, readdir, readFile, realpath, rm, stat, writeFile } from "fs/promises";
2
+ import { dirname, isAbsolute, join, relative, resolve } from "path";
3
+ import { getOmkResourceSettings } from "../resource-profile.js";
4
+ import { resolveRuntimeProfile, buildProfileArgs } from "../runtime-profile.js";
5
+ import { ensureDir, pathExists } from "./core.js";
6
+ import { getKimiDefaultModel, getKimiSkillsDir, getProjectRoot, getProjectRootAsync, getUserHome, } from "./paths.js";
7
+ import { collectMcpConfigs, diagnoseRuntimeMcpServer, } from "./mcp-diagnose.js";
8
+ import { preflightRuntimeMcpServers, resolveRuntimeMcpPreflightOptions, } from "./preflight.js";
9
+ import { formatPreflightFailureDetail, hasHttpTransportMismatch, isPackageManagerRuntimeServer, isRecord, isShellInlineMcpArg, PDF_MCP_SERVER_RE, QUIET_PACKAGE_MANAGER_ENV, registerRuntimeMcpCleanupPath, runtimeCommandText, sanitizeRuntimeMcpPreflightText, } from "./internal.js";
10
+ async function readMcpServersForRuntime(configPath) {
11
+ try {
12
+ const content = await readFile(configPath, "utf-8");
13
+ const parsed = JSON.parse(content);
14
+ const servers = parsed.mcpServers ?? parsed.mcp_servers;
15
+ if (servers && typeof servers === "object" && !Array.isArray(servers)) {
16
+ return normalizeRuntimeMcpRelativePaths(servers, configPath);
17
+ }
18
+ }
19
+ catch {
20
+ // Existing doctor/preflight paths report invalid config details. Runtime merge
21
+ // skips unreadable files so Kimi does not receive partial/broken JSON.
22
+ }
23
+ return {};
24
+ }
25
+ function normalizeRuntimeMcpRelativePaths(servers, configPath) {
26
+ const baseDir = runtimeMcpPathBase(configPath);
27
+ return Object.fromEntries(Object.entries(servers).map(([name, server]) => [name, normalizeRuntimeMcpRelativePathServer(server, baseDir)]));
28
+ }
29
+ function runtimeMcpPathBase(configPath) {
30
+ const root = resolve(getProjectRoot());
31
+ const resolvedConfig = resolve(configPath);
32
+ const relativeToRoot = relative(root, resolvedConfig);
33
+ if (relativeToRoot === "" || (!relativeToRoot.startsWith("..") && !isAbsolute(relativeToRoot))) {
34
+ return root;
35
+ }
36
+ return dirname(resolvedConfig);
37
+ }
38
+ function normalizeRuntimeMcpRelativePathServer(server, baseDir) {
39
+ if (!isRecord(server) || typeof server.url === "string")
40
+ return server;
41
+ let changed = false;
42
+ const next = { ...server };
43
+ if (typeof next.command === "string" && isRelativeRuntimeMcpPathLike(next.command)) {
44
+ next.command = resolve(baseDir, next.command);
45
+ changed = true;
46
+ }
47
+ if (Array.isArray(next.args)) {
48
+ const args = next.args.map((arg, index) => {
49
+ if (typeof arg === "string" && isShellInlineMcpArg(server, index)) {
50
+ const normalized = normalizeRuntimeMcpInlineScript(arg, baseDir);
51
+ if (normalized.changed)
52
+ changed = true;
53
+ return normalized.script;
54
+ }
55
+ if (!shouldNormalizeRuntimeMcpRelativeArg(server, arg, index))
56
+ return arg;
57
+ changed = true;
58
+ return resolve(baseDir, arg);
59
+ });
60
+ next.args = args;
61
+ }
62
+ return changed ? next : server;
63
+ }
64
+ function isRelativeRuntimeMcpPathLike(value) {
65
+ return value.startsWith("./") || value.startsWith("../") || value.startsWith(".\\") || value.startsWith("..\\");
66
+ }
67
+ function shouldNormalizeRuntimeMcpRelativeArg(server, arg, index) {
68
+ if (typeof arg !== "string" || !isRelativeRuntimeMcpPathLike(arg))
69
+ return false;
70
+ if (isShellInlineMcpArg(server, index))
71
+ return false;
72
+ if (/[ \t\r\n;"'|&<>]/.test(arg))
73
+ return false;
74
+ return true;
75
+ }
76
+ function normalizeRuntimeMcpInlineScript(script, baseDir) {
77
+ let changed = false;
78
+ const nextScript = script.replace(/(^|[\s"'`=:(])((?:\.{1,2}[\\/])[^ \t\r\n"'`|&;<>:)]+)/g, (match, prefix, relativePath) => {
79
+ if (/[$*?[\]{}]/.test(relativePath))
80
+ return match;
81
+ const absolutePath = resolve(baseDir, relativePath);
82
+ if (/[ \t\r\n"'`]/.test(absolutePath))
83
+ return match;
84
+ changed = true;
85
+ return `${prefix}${absolutePath}`;
86
+ });
87
+ return { script: nextScript, changed };
88
+ }
89
+ function emitPreflightSummary(result, removedNames = result.failed) {
90
+ if (result.failed.size === 0 || process.env.OMK_MCP_SUPPRESS_PRUNE_WARNINGS === "1")
91
+ return;
92
+ const parts = [];
93
+ for (const name of result.failed) {
94
+ const detail = result.details.get(name);
95
+ if (detail) {
96
+ parts.push(`${sanitizeRuntimeMcpPreflightText(name)} (${formatPreflightFailureDetail(detail.reason, detail.detail)})`);
97
+ }
98
+ else {
99
+ parts.push(sanitizeRuntimeMcpPreflightText(name));
100
+ }
101
+ }
102
+ const shown = parts.slice(0, 5).join(", ");
103
+ const suffix = parts.length > 5 ? `, +${parts.length - 5} more` : "";
104
+ const removedCount = removedNames.size;
105
+ const keptCount = Math.max(0, result.failed.size - removedCount);
106
+ const action = removedCount > 0
107
+ ? `Removed ${removedCount} failed server(s)`
108
+ : "No servers were removed";
109
+ const kept = keptCount > 0
110
+ ? ` Kept ${keptCount} timeout server(s) as prewarm-needed.`
111
+ : "";
112
+ console.warn(`[omk] MCP preflight found ${result.failed.size} issue(s) in npm-family servers: ${shown}${suffix}. ` +
113
+ `${action}.${kept} Run \`omk mcp check --all\` (or \`omk mcp prewarm --all\`) to check caches, ` +
114
+ "or `omk mcp doctor --fix` for durable repairs.");
115
+ }
116
+ function normalizeRuntimeMcpServer(name, server) {
117
+ if (!isRecord(server) || typeof server.url === "string" || !hasHttpTransportMismatch(name, server)) {
118
+ return { server, normalizations: [] };
119
+ }
120
+ const args = Array.isArray(server.args) ? [...server.args] : [];
121
+ let changed = false;
122
+ for (const [index, arg] of args.entries()) {
123
+ if (typeof arg !== "string" || !isShellInlineMcpArg(server, index) || !PDF_MCP_SERVER_RE.test(arg))
124
+ continue;
125
+ args[index] = `${arg.trimEnd()} --stdio`;
126
+ changed = true;
127
+ }
128
+ if (!changed && PDF_MCP_SERVER_RE.test(runtimeCommandText(server))) {
129
+ args.push("--stdio");
130
+ changed = true;
131
+ }
132
+ if (!changed)
133
+ return { server, normalizations: [] };
134
+ return {
135
+ server: { ...server, args },
136
+ normalizations: [{
137
+ name,
138
+ kind: "runtime-stdio-normalized",
139
+ message: "runtime MCP config was normalized to stdio transport before startup; run `omk mcp doctor --fix` to persist the repair",
140
+ }],
141
+ };
142
+ }
143
+ function prepareRuntimeMcpServer(server) {
144
+ if (!isRecord(server) || typeof server.url === "string" || !isPackageManagerRuntimeServer(server)) {
145
+ return server;
146
+ }
147
+ const existingEnv = isRecord(server.env) ? server.env : {};
148
+ const env = { ...QUIET_PACKAGE_MANAGER_ENV, ...existingEnv };
149
+ return { ...server, env };
150
+ }
151
+ export async function pruneRuntimeMcpServers(servers) {
152
+ const pruned = {};
153
+ const diagnostics = [];
154
+ const normalizations = [];
155
+ for (const [name, server] of Object.entries(servers)) {
156
+ const normalized = normalizeRuntimeMcpServer(name, server);
157
+ const normalizedServer = normalized.server;
158
+ const serverDiagnostics = await diagnoseRuntimeMcpServer(name, normalizedServer);
159
+ if (serverDiagnostics.length > 0) {
160
+ diagnostics.push(...serverDiagnostics);
161
+ continue;
162
+ }
163
+ normalizations.push(...normalized.normalizations);
164
+ pruned[name] = prepareRuntimeMcpServer(normalizedServer);
165
+ }
166
+ return { servers: pruned, diagnostics, normalizations };
167
+ }
168
+ function emitRuntimeMcpNormalizationNotice(normalizations) {
169
+ if (normalizations.length === 0 || process.env.OMK_MCP_SUPPRESS_PRUNE_WARNINGS === "1")
170
+ return;
171
+ const names = [...new Set(normalizations.map((diagnostic) => diagnostic.name))];
172
+ const shown = names.slice(0, 5).join(", ");
173
+ const suffix = names.length > 5 ? `, +${names.length - 5} more` : "";
174
+ console.warn(`[omk] normalized ${names.length} MCP server(s) for Kimi startup: ${shown}${suffix}. `
175
+ + "Run `omk mcp doctor --fix` to persist the repair.");
176
+ }
177
+ function emitRuntimeMcpPruneWarning(diagnostics) {
178
+ if (diagnostics.length === 0 || process.env.OMK_MCP_SUPPRESS_PRUNE_WARNINGS === "1")
179
+ return;
180
+ const names = [...new Set(diagnostics.map((diagnostic) => diagnostic.name))];
181
+ const shown = names.slice(0, 5).join(", ");
182
+ const suffix = names.length > 5 ? `, +${names.length - 5} more` : "";
183
+ console.warn(`[omk] skipped ${names.length} broken MCP server(s) before Kimi startup: ${shown}${suffix}. `
184
+ + "Run `omk mcp doctor` to repair stale global MCP config.");
185
+ }
186
+ export async function writeRuntimeMcpConfig(configPaths, allowlist) {
187
+ const uniquePaths = [...new Set(configPaths)];
188
+ const mergedServers = {};
189
+ for (const configPath of uniquePaths) {
190
+ Object.assign(mergedServers, await readMcpServersForRuntime(configPath));
191
+ }
192
+ let targetServers = mergedServers;
193
+ if (allowlist !== undefined && allowlist.length === 0) {
194
+ targetServers = {};
195
+ }
196
+ else if (allowlist && allowlist.length > 0) {
197
+ const allowed = new Set(allowlist);
198
+ const missing = allowlist.filter((name) => !mergedServers[name]);
199
+ if (missing.length > 0) {
200
+ console.warn(`[omk] MCP allowlist contains servers not found in config: ${missing.join(", ")}`);
201
+ }
202
+ targetServers = Object.fromEntries(Object.entries(mergedServers).filter(([name]) => allowed.has(name)));
203
+ }
204
+ const { servers: runtimeServers, diagnostics, normalizations } = await pruneRuntimeMcpServers(targetServers);
205
+ emitRuntimeMcpPruneWarning(diagnostics);
206
+ emitRuntimeMcpNormalizationNotice(normalizations);
207
+ const preflightOptions = resolveRuntimeMcpPreflightOptions();
208
+ const preflightMode = preflightOptions.mode;
209
+ if (preflightMode !== "off") {
210
+ const preflightResult = await preflightRuntimeMcpServers(runtimeServers, preflightOptions);
211
+ const removedByPreflight = new Set();
212
+ for (const name of Object.keys(runtimeServers)) {
213
+ if (preflightResult.failed.has(name)) {
214
+ if (preflightMode === "strict") {
215
+ const parts = [];
216
+ for (const failedName of preflightResult.failed) {
217
+ const detail = preflightResult.details.get(failedName);
218
+ parts.push(`${sanitizeRuntimeMcpPreflightText(failedName)} (${formatPreflightFailureDetail(detail?.reason, detail?.detail)})`);
219
+ }
220
+ throw new Error(`[omk] MCP preflight strict mode: server "${name}" failed probe. ` +
221
+ parts.join(", "));
222
+ }
223
+ const detail = preflightResult.details.get(name);
224
+ if (detail?.reason === "timeout") {
225
+ // Timeout-only failures are kept as prewarm-needed; do not delete.
226
+ continue;
227
+ }
228
+ delete runtimeServers[name];
229
+ removedByPreflight.add(name);
230
+ }
231
+ }
232
+ if (preflightResult.failed.size > 0) {
233
+ emitPreflightSummary(preflightResult, removedByPreflight);
234
+ }
235
+ }
236
+ if (Object.keys(runtimeServers).length === 0)
237
+ return null;
238
+ const root = await getProjectRootAsync();
239
+ const cacheDir = join(root, ".omk", "cache");
240
+ await ensureDir(cacheDir);
241
+ await chmod(cacheDir, 0o700).catch(() => undefined);
242
+ // Eagerly remove stale runtime configs left by prior crashed/killed processes
243
+ await cleanupStaleRuntimeMcpConfigs(cacheDir);
244
+ const runtimeConfigPath = join(cacheDir, `mcp-runtime-merged-${process.pid}-${Date.now()}.json`);
245
+ await writeFile(runtimeConfigPath, JSON.stringify({ mcpServers: runtimeServers }, null, 2) + "\n", { mode: 0o600 });
246
+ registerRuntimeMcpCleanupPath(runtimeConfigPath);
247
+ return runtimeConfigPath;
248
+ }
249
+ async function cleanupStaleRuntimeMcpConfigs(cacheDir) {
250
+ const now = Date.now();
251
+ try {
252
+ const entries = await readdir(cacheDir);
253
+ const stale = entries.filter((name) => name.startsWith("mcp-runtime-merged-") && name.endsWith(".json"));
254
+ for (const name of stale) {
255
+ const fullPath = join(cacheDir, name);
256
+ if (!(await shouldCleanupRuntimeMcpConfig(fullPath, name, now)))
257
+ continue;
258
+ try {
259
+ await rm(fullPath, { force: true });
260
+ }
261
+ catch {
262
+ // Best-effort cleanup
263
+ }
264
+ }
265
+ }
266
+ catch {
267
+ // Directory may not exist or be unreadable
268
+ }
269
+ }
270
+ async function shouldCleanupRuntimeMcpConfig(fullPath, fileName, now) {
271
+ const match = /^mcp-runtime-merged-(\d+)-(\d+)\.json$/.exec(fileName);
272
+ if (match) {
273
+ const ownerPid = Number.parseInt(match[1], 10);
274
+ if (Number.isFinite(ownerPid) && ownerPid > 0 && isProcessAlive(ownerPid)) {
275
+ return false;
276
+ }
277
+ return true;
278
+ }
279
+ try {
280
+ const info = await stat(fullPath);
281
+ return now - info.mtimeMs > 24 * 60 * 60 * 1000;
282
+ }
283
+ catch {
284
+ return false;
285
+ }
286
+ }
287
+ function isProcessAlive(pid) {
288
+ try {
289
+ process.kill(pid, 0);
290
+ return true;
291
+ }
292
+ catch (err) {
293
+ return err.code === "EPERM";
294
+ }
295
+ }
296
+ /**
297
+ * Auto-generate a built-in omk-project MCP config so users never need to
298
+ * define it manually in ~/.kimi/mcp.json or .kimi/mcp.json.
299
+ * The config uses the currently-running omk CLI path and sets OMK_PROJECT_ROOT
300
+ * to the current project root.
301
+ */
302
+ export async function writeBuiltinMcpConfig() {
303
+ const root = getProjectRoot();
304
+ const cacheDir = join(root, ".omk", "cache");
305
+ await ensureDir(cacheDir);
306
+ await chmod(cacheDir, 0o700).catch(() => undefined);
307
+ let omkCliPath;
308
+ try {
309
+ omkCliPath = await realpath(process.argv[1] ?? "");
310
+ }
311
+ catch {
312
+ omkCliPath = process.argv[1] ?? "omk";
313
+ }
314
+ const autoConfigPath = join(cacheDir, `mcp-auto-omk-project-${process.pid}-${Date.now()}.json`);
315
+ const config = {
316
+ mcpServers: {
317
+ "omk-project": {
318
+ command: process.argv[0] || "node",
319
+ args: [omkCliPath, "mcp", "serve", "omk-project"],
320
+ env: {
321
+ OMK_PROJECT_ROOT: root,
322
+ npm_config_loglevel: "error",
323
+ NODE_NO_WARNINGS: "1",
324
+ },
325
+ },
326
+ },
327
+ };
328
+ await writeFile(autoConfigPath, JSON.stringify(config, null, 2) + "\n", { mode: 0o600 });
329
+ registerRuntimeMcpCleanupPath(autoConfigPath);
330
+ return autoConfigPath;
331
+ }
332
+ /** Kimi CLI 실행 인자에 model + MCP + Skills 주입 (전역 동기화는 별도) */
333
+ export async function injectKimiGlobals(args, options = {}) {
334
+ const resources = await getOmkResourceSettings();
335
+ const mcpScope = options.mcpScope ?? resources.mcpScope;
336
+ const skillsScope = options.skillsScope ?? resources.skillsScope;
337
+ const hooksScope = options.hooksScope ?? resources.hooksScope;
338
+ // Resolve role-based runtime profile and inject supported flags
339
+ let injectedModel;
340
+ if (options.role) {
341
+ const profile = await resolveRuntimeProfile(options.role);
342
+ const { getKimiCapabilities } = await import("../../kimi/capability.js");
343
+ const caps = getKimiCapabilities();
344
+ const profileArgs = buildProfileArgs(profile, caps);
345
+ args.push(...profileArgs);
346
+ injectedModel = profile.model;
347
+ // maxOutputMb is not a native CLI flag; map to env hint if present
348
+ if (profile.maxOutputMb !== undefined && !process.env.OMK_MAX_OUTPUT_MB) {
349
+ // Handled downstream by resource-profile.ts / shell runners
350
+ }
351
+ }
352
+ // default_model이 있으면 주입 (agent-file 사용 시 model이 unset 될 수 있음)
353
+ // Profile model takes precedence; skip duplicate injection.
354
+ if (!injectedModel) {
355
+ const defaultModel = await getKimiDefaultModel();
356
+ if (defaultModel) {
357
+ args.push("--model", defaultModel);
358
+ }
359
+ }
360
+ if (mcpScope !== "none") {
361
+ const mcpConfigs = await collectMcpConfigs(mcpScope);
362
+ // Auto-inject built-in omk-project MCP server so it never needs user config.
363
+ // Merge runtime configs before passing them to Kimi: Kimi warns on duplicate
364
+ // server names across multiple --mcp-config-file values and then overrides
365
+ // silently. A single merged config preserves the same precedence (global first,
366
+ // project second, built-in omk-project last) without duplicate startup noise.
367
+ const builtinMcp = await writeBuiltinMcpConfig();
368
+ const allowlist = options.mcpAllowlist !== undefined
369
+ ? [...new Set([...options.mcpAllowlist, "omk-project"].map((name) => name.trim()).filter(Boolean))]
370
+ : undefined;
371
+ const runtimeMcp = await writeRuntimeMcpConfig(builtinMcp ? [...mcpConfigs, builtinMcp] : mcpConfigs, allowlist);
372
+ if (runtimeMcp) {
373
+ args.push("--mcp-config-file", runtimeMcp);
374
+ }
375
+ else if (allowlist && allowlist.length > 0) {
376
+ console.warn(`[omk] MCP allowlist resulted in zero available servers. ` +
377
+ `Allowed: ${allowlist.join(", ")}. ` +
378
+ `Check that the allowlist matches actual MCP server names in your config. ` +
379
+ `MCP config will not be passed to Kimi.`);
380
+ }
381
+ }
382
+ const globalSkillsDir = join(getUserHome(), ".kimi", "skills");
383
+ const projectSkillsDir = getKimiSkillsDir();
384
+ const [globalSkillsExists, projectSkillsExists] = await Promise.all([
385
+ skillsScope === "all" ? pathExists(globalSkillsDir) : Promise.resolve(false),
386
+ skillsScope !== "none" ? pathExists(projectSkillsDir) : Promise.resolve(false),
387
+ ]);
388
+ if (globalSkillsExists)
389
+ args.push("--skills-dir", globalSkillsDir);
390
+ if (projectSkillsExists)
391
+ args.push("--skills-dir", projectSkillsDir);
392
+ if (process.env.OMK_DEBUG === "1") {
393
+ const mcpFiles = await collectMcpConfigs(mcpScope);
394
+ const skillDirs = [];
395
+ if (globalSkillsExists)
396
+ skillDirs.push(globalSkillsDir);
397
+ if (projectSkillsExists)
398
+ skillDirs.push(projectSkillsDir);
399
+ const modelIdx = args.indexOf("--model");
400
+ const effectiveModel = modelIdx >= 0 ? args[modelIdx + 1] : null;
401
+ console.error("[OMK_DEBUG] injectKimiGlobals:", {
402
+ role: options.role ?? null,
403
+ model: effectiveModel,
404
+ mcpFiles,
405
+ skillDirs,
406
+ mcpScope,
407
+ skillsScope,
408
+ hooksScope,
409
+ mcpAllowlist: options.mcpAllowlist ?? null,
410
+ });
411
+ }
412
+ }
@@ -0,0 +1,13 @@
1
+ import { getProjectRootDiagnostics, resolveProjectRoot, resolveProjectRootAsync, displayProjectRootPath, type ProjectRootResolution, type ProjectRootSource } from "../project-root.js";
2
+ export { getProjectRootDiagnostics, resolveProjectRoot, resolveProjectRootAsync, displayProjectRootPath, type ProjectRootResolution, type ProjectRootSource, };
3
+ export declare function getProjectRoot(): string;
4
+ export declare function getProjectRootAsync(): Promise<string>;
5
+ export declare function getUserHome(env?: NodeJS.ProcessEnv): string;
6
+ export declare function normalizeUserHomePath(value: string | undefined): string | undefined;
7
+ export declare function getOmkPath(subPath?: string): string;
8
+ export { validateRunId, sanitizeRunId, validateRunArtifactPath, getRunsDir, getRunPath, getRunArtifactPath, listValidRunIds, } from "../run-store.js";
9
+ export declare function getKimiConfigPath(): string;
10
+ /** 프로젝트의 .kimi/skills 디렉토리 경로 */
11
+ export declare function getKimiSkillsDir(): string;
12
+ /** ~/.kimi/config.toml 에서 default_model 읽기 */
13
+ export declare function getKimiDefaultModel(): Promise<string | null>;