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
@@ -1,3 +1,5 @@
1
+ import * as nodePath from "node:path";
2
+ import * as fs from "node:fs";
1
3
  export function createRuntimeSandboxProfile(options) {
2
4
  const mode = options.mode ?? "read-only";
3
5
  const enforcement = options.enforcement ?? "env-only";
@@ -33,3 +35,102 @@ function defaultSandboxNotes(enforcement) {
33
35
  }
34
36
  return ["OS-level sandbox enforcement is not configured."];
35
37
  }
38
+ // ---------------------------------------------------------------------------
39
+ // writableRoots enforcement (Lane C2)
40
+ //
41
+ // Pure helpers that decide whether a target path is permitted by an explicit
42
+ // list of writable roots. SAFE DEFAULT: an empty/undefined root list means
43
+ // "unrestricted" so existing behavior (no enforcement) is preserved. These are
44
+ // IO-free; callers resolve the active sandbox profile and pass its roots.
45
+ // ---------------------------------------------------------------------------
46
+ /**
47
+ * Resolve `p` to a canonical real path, best-effort and fail-safe.
48
+ *
49
+ * The write target may not exist yet, so we cannot `realpathSync` it directly.
50
+ * Instead we walk UP to the deepest EXISTING ancestor, `realpathSync` that
51
+ * ancestor (collapsing any symlinks on the existing portion), then re-append the
52
+ * remaining non-existent segments. This catches a symlink whose real target
53
+ * escapes the writable roots even when the final file does not exist yet.
54
+ *
55
+ * Fail-safe: `realpathSync` can throw (ENOENT while walking, EACCES). This
56
+ * function NEVER throws — on total failure it falls back to {@link nodePath.resolve}
57
+ * (the prior behavior). Only {@link assertWritable} throws, and only on a genuine
58
+ * deny.
59
+ */
60
+ function resolveRealPathBestEffort(p) {
61
+ const resolved = nodePath.resolve(p);
62
+ const trailing = [];
63
+ let current = resolved;
64
+ for (;;) {
65
+ try {
66
+ const real = fs.realpathSync(current);
67
+ return trailing.length === 0 ? real : nodePath.join(real, ...trailing);
68
+ }
69
+ catch {
70
+ const parent = nodePath.dirname(current);
71
+ if (parent === current) {
72
+ // Reached the filesystem root with no resolvable ancestor: fall back to
73
+ // the plain resolved path (never throw from the resolution step).
74
+ return resolved;
75
+ }
76
+ trailing.unshift(nodePath.basename(current));
77
+ current = parent;
78
+ }
79
+ }
80
+ }
81
+ /**
82
+ * Error thrown by {@link assertWritable} when a path is outside every writable
83
+ * root. Carries only the resolved target and roots (no secret values).
84
+ */
85
+ export class SandboxWriteDeniedError extends Error {
86
+ target;
87
+ roots;
88
+ constructor(target, roots) {
89
+ super(`Write denied by sandbox writableRoots policy: "${target}" is not inside ` +
90
+ `any writable root [${roots.join(", ")}]`);
91
+ this.name = "SandboxWriteDeniedError";
92
+ this.target = target;
93
+ this.roots = roots;
94
+ }
95
+ }
96
+ /**
97
+ * Return true when `p` is writable under `roots`.
98
+ *
99
+ * Rules:
100
+ * - Empty/undefined `roots` => unrestricted (returns true). This preserves the
101
+ * current "metadata only" behavior; enforcement activates only when a
102
+ * sandbox profile explicitly sets non-empty `writableRoots`.
103
+ * - Both target and roots are resolved via {@link resolveRealPathBestEffort},
104
+ * which (a) collapses `.`/`..` segments and (b) realpath-resolves the deepest
105
+ * existing ancestor so a SYMLINK whose real target escapes every root is
106
+ * DENIED — even when the final write target does not exist yet.
107
+ * - Prefix matching is segment-boundary safe: target must equal a root or sit
108
+ * under `root + path.sep`, so root `/a/b` does NOT match sibling `/a/bc`.
109
+ */
110
+ export function isPathWritable(p, roots) {
111
+ if (!roots || roots.length === 0)
112
+ return true;
113
+ const target = resolveRealPathBestEffort(p);
114
+ for (const root of roots) {
115
+ if (!root)
116
+ continue;
117
+ const normalizedRoot = resolveRealPathBestEffort(root);
118
+ if (target === normalizedRoot)
119
+ return true;
120
+ const rootWithSep = normalizedRoot.endsWith(nodePath.sep)
121
+ ? normalizedRoot
122
+ : normalizedRoot + nodePath.sep;
123
+ if (target.startsWith(rootWithSep))
124
+ return true;
125
+ }
126
+ return false;
127
+ }
128
+ /**
129
+ * Throw {@link SandboxWriteDeniedError} when `p` is not writable under `roots`.
130
+ * No-op when `roots` is empty/undefined (safe default => unrestricted).
131
+ */
132
+ export function assertWritable(p, roots) {
133
+ if (isPathWritable(p, roots))
134
+ return;
135
+ throw new SandboxWriteDeniedError(resolveRealPathBestEffort(p), roots ?? []);
136
+ }
@@ -1,6 +1,13 @@
1
- import { KNOWN_PROVIDER_IDS, normalizeProviderId, parseProviderModelArg, readProviderRegistry } from "../providers/model-registry.js";
1
+ import { readFile } from "node:fs/promises";
2
+ import { KNOWN_PROVIDER_IDS, normalizeProviderId, parseProviderModelArg, providerDoctorStatus, readProviderRegistry, } from "../providers/model-registry.js";
2
3
  import { groupProviderModelsByProvider, renderProviderModelTable } from "../providers/model-table.js";
3
- import { formatThinkingModelVariant, nextThinkingLevel, normalizeThinkingLevel, normalizeThinkingVariant, thinkingLevelsFor } from "../providers/thinking-levels.js";
4
+ import { formatThinkingModelVariant, nextThinkingLevel, normalizeThinkingLevel, normalizeThinkingVariant, thinkingLevelsFor, } from "../providers/thinking-levels.js";
5
+ import { buildMcpDoctorReport } from "../commands/mcp/doctor.js";
6
+ import { evaluateHeadroom, isHeadroomEnabled } from "./headroom-policy.js";
7
+ import { buildOmkToolPlaneManifest } from "./tool-plane.js";
8
+ import { LocalGraphMemoryStore } from "../memory/local-graph-memory-store.js";
9
+ import { createReasoningTraceStore, redactTrace, summarizeTrace } from "./reasoning-trace.js";
10
+ import { getActiveRuntimePreset, getOmkResourceSettings } from "../util/resource-profile.js";
4
11
  function parseSlashInput(rawText) {
5
12
  const trimmed = rawText.trimStart();
6
13
  const match = trimmed.match(/^\/([a-zA-Z0-9_-]+)\s*(.*)/s);
@@ -33,6 +40,18 @@ function resultToCommandBusResult(result) {
33
40
  function isRecord(value) {
34
41
  return typeof value === "object" && value !== null && !Array.isArray(value);
35
42
  }
43
+ function numberValue(payload, key, fallback = 0) {
44
+ const value = payload[key];
45
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
46
+ }
47
+ function booleanValue(payload, key, fallback = false) {
48
+ const value = payload[key];
49
+ return typeof value === "boolean" ? value : fallback;
50
+ }
51
+ function recordArrayValue(payload, key) {
52
+ const value = payload[key];
53
+ return Array.isArray(value) ? value.filter((item) => isRecord(item)) : [];
54
+ }
36
55
  function stringValue(payload, key, fallback = "unknown") {
37
56
  const value = payload[key];
38
57
  return typeof value === "string" && value.trim() ? value : fallback;
@@ -96,6 +115,88 @@ function renderSlashResultContent(result, fallback) {
96
115
  const commands = stringListValue(payload, "commands");
97
116
  return [`\n OMK Slash Commands`, ...commands.map((command) => ` - ${command}`), ""].join("\n");
98
117
  }
118
+ case "mcp.show": {
119
+ const scope = stringValue(payload, "scope");
120
+ const configured = numberValue(payload, "configured");
121
+ const servers = recordArrayValue(payload, "servers");
122
+ const lines = [`\n MCP servers (${configured} configured, scope: ${scope})`];
123
+ for (const srv of servers) {
124
+ const name = stringValue(srv, "name");
125
+ const status = stringValue(srv, "status");
126
+ const active = booleanValue(srv, "active");
127
+ lines.push(` ${active ? "●" : "○"} ${name} ${status}`);
128
+ }
129
+ return lines.join("\n");
130
+ }
131
+ case "provider.show": {
132
+ const provider = stringValue(payload, "provider");
133
+ const model = stringValue(payload, "model");
134
+ const lines = [`\n Provider: ${provider}/${model}`];
135
+ const current = isRecord(payload.current) ? payload.current : undefined;
136
+ if (current) {
137
+ lines.push(` Available: ${booleanValue(current, "available")} Auth: ${booleanValue(current, "authPresent")}`);
138
+ }
139
+ const fallbacks = recordArrayValue(payload, "fallbacks");
140
+ if (fallbacks.length > 0) {
141
+ lines.push(" Fallbacks:");
142
+ for (const fb of fallbacks) {
143
+ lines.push(` ${stringValue(fb, "provider")} available=${booleanValue(fb, "available")} auth=${booleanValue(fb, "authPresent")}`);
144
+ }
145
+ }
146
+ return lines.join("\n");
147
+ }
148
+ case "headroom.show": {
149
+ const enabled = booleanValue(payload, "enabled");
150
+ const utilization = numberValue(payload, "utilization");
151
+ const threshold = numberValue(payload, "threshold");
152
+ const shouldCompact = booleanValue(payload, "shouldCompact");
153
+ return `\n Headroom: ${enabled ? "enabled" : "disabled"} usage=${(utilization * 100).toFixed(1)}% threshold=${(threshold * 100).toFixed(1)}% compact=${shouldCompact}`;
154
+ }
155
+ case "tools.show": {
156
+ const groups = recordArrayValue(payload, "groups");
157
+ const total = groups.reduce((sum, group) => sum + numberValue(group, "count"), 0);
158
+ const lines = [`\n Tools: ${total} total`];
159
+ for (const group of groups) {
160
+ const name = stringValue(group, "name");
161
+ const count = numberValue(group, "count");
162
+ const items = stringListValue(group, "items");
163
+ lines.push(` ${name}: ${count}${items.length > 0 ? ` [${items.join(", ")}]` : ""}`);
164
+ }
165
+ return lines.join("\n");
166
+ }
167
+ case "memory.show": {
168
+ const available = booleanValue(payload, "available", true);
169
+ if (!available) {
170
+ return `\n Memory: unavailable (${stringValue(payload, "reason")})`;
171
+ }
172
+ const backend = stringValue(payload, "backend");
173
+ const nodeCount = numberValue(payload, "nodeCount");
174
+ const edgeCount = numberValue(payload, "edgeCount");
175
+ const lastUpdated = stringValue(payload, "lastUpdated", "—");
176
+ const queryRaw = payload.query;
177
+ const query = typeof queryRaw === "string" && queryRaw.trim() ? queryRaw : undefined;
178
+ const results = recordArrayValue(payload, "results");
179
+ const lines = [`\n Memory: ${backend} nodes=${nodeCount} edges=${edgeCount} updated=${lastUpdated}`];
180
+ if (query) {
181
+ lines.push(` Query: "${query}"`);
182
+ for (const result of results) {
183
+ lines.push(` - ${stringValue(result, "path")} ${stringValue(result, "updatedAt")}`);
184
+ }
185
+ }
186
+ return lines.join("\n");
187
+ }
188
+ case "trace.show": {
189
+ const count = numberValue(payload, "count");
190
+ const traces = recordArrayValue(payload, "traces");
191
+ const lines = [`\n Reasoning traces (latest ${count}):`];
192
+ for (const trace of traces) {
193
+ const outcome = stringValue(trace, "outcome");
194
+ const icon = outcome === "success" ? "✔" : outcome === "partial" ? "◐" : "✖";
195
+ const redacted = booleanValue(trace, "redacted") ? " (redacted)" : "";
196
+ lines.push(` ${icon} ${stringValue(trace, "intent")} → ${outcome} [${stringValue(trace, "duration")}]${redacted}`);
197
+ }
198
+ return lines.join("\n");
199
+ }
99
200
  default:
100
201
  return fallback;
101
202
  }
@@ -262,6 +363,185 @@ class ThemeCommandHandler {
262
363
  };
263
364
  }
264
365
  }
366
+ class McpCommandHandler {
367
+ async execute() {
368
+ const report = await buildMcpDoctorReport();
369
+ const servers = report.servers.map((srv) => ({
370
+ name: srv.name,
371
+ status: srv.status === "ok" ? "ok" : srv.status === "warn" ? "degraded" : "down",
372
+ active: srv.active,
373
+ }));
374
+ return {
375
+ kind: "status",
376
+ command: "mcp.show",
377
+ payload: {
378
+ configured: report.data.serverCount,
379
+ scope: report.activeScope,
380
+ servers,
381
+ },
382
+ renderMode: "theme",
383
+ sideEffects: [],
384
+ };
385
+ }
386
+ }
387
+ class ProviderCommandHandler {
388
+ async execute(input) {
389
+ const currentProvider = input.state.provider ?? "unknown";
390
+ const currentModel = input.state.model ?? "unknown";
391
+ const registry = await readProviderRegistry();
392
+ const statuses = await Promise.all(registry.map(async (entry) => {
393
+ const doctor = await providerDoctorStatus(entry.id).catch(() => undefined);
394
+ return doctor ? { ...doctor, configured: entry.configured } : undefined;
395
+ }));
396
+ const statusesDefined = statuses.filter((s) => s !== undefined);
397
+ const current = statusesDefined.find((s) => s.provider === currentProvider);
398
+ const fallbacks = statusesDefined
399
+ .filter((s) => s.provider !== currentProvider && s.configured)
400
+ .sort((a, b) => Number(b.available) - Number(a.available));
401
+ return {
402
+ kind: "status",
403
+ command: "provider.show",
404
+ payload: {
405
+ provider: currentProvider,
406
+ model: currentModel,
407
+ current: current
408
+ ? {
409
+ provider: current.provider,
410
+ model: current.model,
411
+ available: current.available,
412
+ authPresent: current.apiKeySet === true,
413
+ kind: current.kind,
414
+ }
415
+ : undefined,
416
+ fallbacks: fallbacks.map((s) => ({
417
+ provider: s.provider,
418
+ model: s.model,
419
+ available: s.available,
420
+ authPresent: s.apiKeySet === true,
421
+ })),
422
+ },
423
+ renderMode: "theme",
424
+ sideEffects: [],
425
+ };
426
+ }
427
+ }
428
+ class HeadroomCommandHandler {
429
+ async execute(input) {
430
+ const registry = await readProviderRegistry();
431
+ const entry = registry.find((e) => e.id === input.state.provider);
432
+ const contextWindow = typeof entry?.contextWindow === "number" && entry.contextWindow > 0 ? entry.contextWindow : 0;
433
+ const decision = evaluateHeadroom({ usedTokens: 0, contextWindow });
434
+ return {
435
+ kind: "status",
436
+ command: "headroom.show",
437
+ payload: {
438
+ enabled: isHeadroomEnabled(),
439
+ utilization: decision.utilization,
440
+ threshold: decision.threshold,
441
+ usedTokens: decision.usedTokens,
442
+ contextWindow: decision.contextWindow,
443
+ shouldCompact: decision.shouldCompact,
444
+ },
445
+ renderMode: "theme",
446
+ sideEffects: [],
447
+ };
448
+ }
449
+ }
450
+ class ToolsCommandHandler {
451
+ async execute() {
452
+ const [settings, preset] = await Promise.all([
453
+ getOmkResourceSettings(),
454
+ getActiveRuntimePreset().catch(() => undefined),
455
+ ]);
456
+ const manifest = await buildOmkToolPlaneManifest({
457
+ mcpScope: settings.mcpScope,
458
+ skills: preset?.skills ?? [],
459
+ hooks: preset?.hooks ?? [],
460
+ tools: [],
461
+ });
462
+ const groups = [
463
+ { name: "MCP servers", count: manifest.mcpServers.length, items: [...manifest.mcpServers] },
464
+ { name: "Skills", count: manifest.skills.length, items: [...manifest.skills] },
465
+ { name: "Hooks", count: manifest.hooks.length, items: [...manifest.hooks] },
466
+ { name: "Tools", count: manifest.tools.length, items: [...manifest.tools] },
467
+ ];
468
+ return {
469
+ kind: "status",
470
+ command: "tools.show",
471
+ payload: { groups },
472
+ renderMode: "theme",
473
+ sideEffects: [],
474
+ };
475
+ }
476
+ }
477
+ class MemoryCommandHandler {
478
+ async execute(input) {
479
+ const store = await LocalGraphMemoryStore.create();
480
+ if (!store) {
481
+ return {
482
+ kind: "status",
483
+ command: "memory.show",
484
+ payload: { available: false, reason: "local graph memory not configured" },
485
+ renderMode: "theme",
486
+ sideEffects: [],
487
+ };
488
+ }
489
+ const status = store.status;
490
+ const raw = await readFile(status.localGraph.path, "utf-8").catch(() => "{}");
491
+ const parsed = JSON.parse(raw);
492
+ const nodeCount = Array.isArray(parsed.nodes) ? parsed.nodes.length : 0;
493
+ const edgeCount = Array.isArray(parsed.edges) ? parsed.edges.length : 0;
494
+ const lastUpdated = typeof parsed.updatedAt === "string" ? parsed.updatedAt : "—";
495
+ const query = input.args.join(" ").trim();
496
+ let results = [];
497
+ if (query) {
498
+ const hits = await store.search(query, 5);
499
+ results = hits.map((hit) => ({ path: hit.path, updatedAt: hit.updatedAt }));
500
+ }
501
+ return {
502
+ kind: "status",
503
+ command: "memory.show",
504
+ payload: {
505
+ available: true,
506
+ backend: status.backend,
507
+ nodeCount,
508
+ edgeCount,
509
+ lastUpdated,
510
+ query: query || undefined,
511
+ results,
512
+ },
513
+ renderMode: "theme",
514
+ sideEffects: [],
515
+ };
516
+ }
517
+ }
518
+ class TraceCommandHandler {
519
+ async execute(input) {
520
+ const store = createReasoningTraceStore();
521
+ const limit = Math.min(20, Math.max(1, Number(input.args[0]) || 5));
522
+ const traces = await store.list(limit);
523
+ const summaries = traces.map((trace) => {
524
+ const redacted = trace.privacy.level === "l0" || !trace.privacy.consentGiven;
525
+ const source = redacted ? redactTrace(trace) : trace;
526
+ const summary = summarizeTrace(source);
527
+ return {
528
+ id: trace.id,
529
+ intent: summary.intent,
530
+ outcome: summary.outcome,
531
+ duration: summary.duration,
532
+ confidence: summary.confidence,
533
+ redacted,
534
+ };
535
+ });
536
+ return {
537
+ kind: "status",
538
+ command: "trace.show",
539
+ payload: { count: summaries.length, traces: summaries },
540
+ renderMode: "theme",
541
+ sideEffects: [],
542
+ };
543
+ }
544
+ }
265
545
  class HelpCommandHandler {
266
546
  async execute() {
267
547
  return {
@@ -273,6 +553,12 @@ class HelpCommandHandler {
273
553
  "/think [next|medium|high|xhigh|max|variant <name>] - Cycle or set thinking variant",
274
554
  "/status - Show current runtime status",
275
555
  "/theme [name] - Show or set theme",
556
+ "/mcp - Show MCP server health",
557
+ "/provider - Show current provider, model, and fallback status",
558
+ "/headroom - Show context headroom status",
559
+ "/tools - Show tool plane manifest",
560
+ "/memory [query] - Show memory summary or search top-N",
561
+ "/trace - Show latest reasoning trace summaries",
276
562
  "/help - Show this help",
277
563
  ],
278
564
  },
@@ -287,6 +573,12 @@ const defaultHandlers = {
287
573
  thinking: new ThinkCommandHandler(),
288
574
  status: new StatusCommandHandler(),
289
575
  theme: new ThemeCommandHandler(),
576
+ mcp: new McpCommandHandler(),
577
+ provider: new ProviderCommandHandler(),
578
+ headroom: new HeadroomCommandHandler(),
579
+ tools: new ToolsCommandHandler(),
580
+ memory: new MemoryCommandHandler(),
581
+ trace: new TraceCommandHandler(),
290
582
  help: new HelpCommandHandler(),
291
583
  };
292
584
  export function createSlashCommandHandler(state = {}) {
@@ -317,5 +609,11 @@ export function registerSlashCommands(bus, state = {}) {
317
609
  bus.registerHandler("thinking", handler);
318
610
  bus.registerHandler("status", handler);
319
611
  bus.registerHandler("theme", handler);
612
+ bus.registerHandler("mcp", handler);
613
+ bus.registerHandler("provider", handler);
614
+ bus.registerHandler("headroom", handler);
615
+ bus.registerHandler("tools", handler);
616
+ bus.registerHandler("memory", handler);
617
+ bus.registerHandler("trace", handler);
320
618
  bus.registerHandler("help", handler);
321
619
  }
@@ -51,6 +51,21 @@ export interface ToolAuthorityWiring {
51
51
  * Runtimes without a valid proof cannot enter authority lanes.
52
52
  */
53
53
  readonly enforcementProof?: EnforcementProof;
54
+ /**
55
+ * Optional writable-root allowlist for filesystem write enforcement (Lane C2).
56
+ * Only consulted when `enforce === true` AND {@link resolveWritePath} is
57
+ * provided AND it returns a non-empty path. ABSENT or empty => NO path check
58
+ * runs and dispatch is byte-identical to the ungated path.
59
+ */
60
+ readonly writableRoots?: readonly string[];
61
+ /**
62
+ * Optional resolver mapping a tool call to its filesystem write target. Only
63
+ * consulted when `enforce === true` AND `writableRoots` is non-empty. When this
64
+ * is ABSENT (or returns undefined/empty), NO write-path check runs and dispatch
65
+ * is byte-identical to now. When it returns a non-empty path, that path is
66
+ * checked against `writableRoots` via {@link assertWritable}; a deny throws.
67
+ */
68
+ readonly resolveWritePath?: (call: OmkToolCall) => string | undefined;
54
69
  }
55
70
  /**
56
71
  * Resolve the global enforcement opt-in from the environment. Default OFF means
@@ -1,5 +1,6 @@
1
1
  import { createToolExecutionBatches } from "./tool-registry-contract.js";
2
2
  import { decideToolAuthority, decideToolAuthorityV2, mapToolNameToOp, } from "../safety/tool-authority-gate.js";
3
+ import { assertWritable } from "./sandbox-profile.js";
3
4
  const ENFORCE_PATTERN = /^(1|true|yes|on)$/i;
4
5
  /**
5
6
  * Resolve the global enforcement opt-in from the environment. Default OFF means
@@ -109,6 +110,17 @@ function buildGatedDispatch(wiring, dispatchOne) {
109
110
  if (blocked) {
110
111
  throw new ToolAuthorityBlockedError(record);
111
112
  }
113
+ // Lane C2 filesystem write enforcement. NON-BREAKING: this block is entered
114
+ // only when ALL opt-ins are present (enforce === true AND a non-empty
115
+ // writableRoots AND a resolveWritePath that yields a non-empty path). When
116
+ // any is absent the dispatch path is byte-identical to the pre-C2 behavior.
117
+ // A symlink/escape deny throws SandboxWriteDeniedError from assertWritable.
118
+ if (wiring.enforce === true && wiring.writableRoots?.length && wiring.resolveWritePath) {
119
+ const writeTarget = wiring.resolveWritePath(call);
120
+ if (writeTarget) {
121
+ assertWritable(writeTarget, wiring.writableRoots);
122
+ }
123
+ }
112
124
  return dispatchOne(call);
113
125
  };
114
126
  }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * omk.weights.v1 — single weights contract for scoring vectors.
3
+ *
4
+ * Source of truth: schemas/omk.weights.v1.json (repo dev). The npm package
5
+ * ships without schemas/, so DEFAULT_WEIGHTS embeds the same values and is
6
+ * used as the fallback. The two MUST stay deep-equal (enforced by
7
+ * test/weights-config.test.mjs).
8
+ *
9
+ * Normalization contract (behavior-preserving):
10
+ * ŵ = w / Σw, and penalties AND thresholds are scaled by the SAME factor
11
+ * 1/Σw, so the transform is a pure uniform scaling. Rankings and threshold
12
+ * verdicts are mathematically identical to the historical raw-weight
13
+ * formulas.
14
+ *
15
+ * intentCapability is intentionally NOT normalized: the vectors are
16
+ * feature-fit emphasis templates whose sub-unit sums are intentional;
17
+ * normalizing them would change cross-term mixing (NOT behavior-preserving).
18
+ */
19
+ import type { NodeIntent } from "./contracts/router-v2.js";
20
+ declare const RELEASE_GATE_WEIGHT_KEYS: readonly ["ci", "build", "types", "tests", "install", "demo", "proof", "maturity", "docs"];
21
+ export type ReleaseGateWeightKey = (typeof RELEASE_GATE_WEIGHT_KEYS)[number];
22
+ declare const RELEASE_GATE_PENALTY_KEYS: readonly ["regression"];
23
+ export type ReleaseGatePenaltyKey = (typeof RELEASE_GATE_PENALTY_KEYS)[number];
24
+ declare const RELEASE_GATE_THRESHOLD_KEYS: readonly ["preRelease", "stable"];
25
+ export type ReleaseGateThresholdKey = (typeof RELEASE_GATE_THRESHOLD_KEYS)[number];
26
+ declare const ROUTER_V2_WEIGHT_KEYS: readonly ["bayesianEvidence", "confidence", "capabilityFit", "maturity", "latency", "cost"];
27
+ export type RouterV2WeightKey = (typeof ROUTER_V2_WEIGHT_KEYS)[number];
28
+ declare const ROUTER_V2_PENALTY_KEYS: readonly ["recentFailure", "blastRadius"];
29
+ export type RouterV2PenaltyKey = (typeof ROUTER_V2_PENALTY_KEYS)[number];
30
+ declare const CAPABILITY_WEIGHT_KEYS: readonly ["read", "write", "shell", "patch", "review", "vision", "toolCalling"];
31
+ /** Capability keys allowed in intentCapability vectors. */
32
+ export type CapabilityWeightKey = (typeof CAPABILITY_WEIGHT_KEYS)[number];
33
+ /** A normalize:true vector: weights are normalized, penalties/thresholds scaled uniformly. */
34
+ export interface NormalizedVectorSpec<W extends string, P extends string, T extends string = never> {
35
+ readonly normalize: true;
36
+ readonly weights: Readonly<Record<W, number>>;
37
+ readonly penalties: Readonly<Record<P, number>>;
38
+ readonly thresholds?: Readonly<Record<T, number>>;
39
+ }
40
+ /** A normalize:false vector family kept verbatim (no scaling). */
41
+ export interface IntentCapabilitySpec {
42
+ readonly normalize: false;
43
+ readonly rationale: string;
44
+ readonly vectors: Readonly<Record<NodeIntent, Readonly<Partial<Record<CapabilityWeightKey, number>>>>>;
45
+ }
46
+ export type ReleaseGateSpec = NormalizedVectorSpec<ReleaseGateWeightKey, ReleaseGatePenaltyKey, ReleaseGateThresholdKey>;
47
+ export type RouterV2CompositeSpec = NormalizedVectorSpec<RouterV2WeightKey, RouterV2PenaltyKey>;
48
+ export interface WeightsConfigV1 {
49
+ readonly schemaVersion: "omk.weights.v1";
50
+ readonly vectors: {
51
+ readonly releaseGate: ReleaseGateSpec;
52
+ readonly routerV2Composite: RouterV2CompositeSpec;
53
+ readonly intentCapability: IntentCapabilitySpec;
54
+ };
55
+ }
56
+ /** Effective (uniformly scaled) weights/penalties/thresholds for a vector. */
57
+ export interface EffectiveVector<W extends string, P extends string, T extends string = never> {
58
+ readonly weights: Readonly<Record<W, number>>;
59
+ readonly penalties: Readonly<Record<P, number>>;
60
+ readonly thresholds: Readonly<Record<T, number>>;
61
+ /** Uniform scale factor 1/Σw applied to weights, penalties, and thresholds. */
62
+ readonly scale: number;
63
+ }
64
+ export declare const DEFAULT_WEIGHTS: WeightsConfigV1;
65
+ /**
66
+ * Load the omk.weights.v1 config: prefer schemas/omk.weights.v1.json when the
67
+ * repo file exists (dev checkout), otherwise use the embedded defaults
68
+ * (published package ships without schemas/).
69
+ */
70
+ export declare function loadWeightsConfig(): WeightsConfigV1;
71
+ /** Memoized accessor for the loaded weights config. */
72
+ export declare function getWeightsConfig(): WeightsConfigV1;
73
+ /**
74
+ * Normalize a normalize:true vector: ŵ = w/Σw. Penalties and thresholds are
75
+ * scaled by the SAME factor 1/Σw so the transform is a pure uniform scaling
76
+ * (ranking and threshold semantics preserved).
77
+ *
78
+ * @throws Error when the post-normalization invariant |Σŵ-1|>1e-6 fails.
79
+ */
80
+ export declare function normalizeVector<W extends string, P extends string, T extends string>(name: string, spec: NormalizedVectorSpec<W, P, T>): EffectiveVector<W, P, T>;
81
+ /** Effective (normalized) release-gate weights, penalties, and thresholds. */
82
+ export declare function releaseGateEffective(config?: WeightsConfigV1): EffectiveVector<ReleaseGateWeightKey, ReleaseGatePenaltyKey, ReleaseGateThresholdKey>;
83
+ /** Effective (normalized) Router V2 composite weights and penalties. */
84
+ export declare function routerV2CompositeEffective(config?: WeightsConfigV1): EffectiveVector<RouterV2WeightKey, RouterV2PenaltyKey>;
85
+ /**
86
+ * Intent → capability weight entries, verbatim (normalize:false), in the
87
+ * declared order of each vector.
88
+ */
89
+ export declare function intentCapabilityWeights(config?: WeightsConfigV1): Readonly<Record<NodeIntent, ReadonlyArray<readonly [CapabilityWeightKey, number]>>>;
90
+ export {};