open-multi-agent-kit 0.78.8 → 0.79.3

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 (162) hide show
  1. package/AGENTS.md +131 -2
  2. package/CHANGELOG.md +32 -0
  3. package/CLAUDE.md +105 -3
  4. package/DESIGN.md +141 -117
  5. package/MATURITY.md +7 -12
  6. package/README.md +66 -347
  7. package/ROADMAP.md +33 -47
  8. package/dist/adapters/kimi/runner.js +2 -2
  9. package/dist/brand/theme.js +2 -2
  10. package/dist/cli/register-basic-commands.js +3 -2
  11. package/dist/cli/register-mcp-dag-cron-screenshot-commands.js +1 -1
  12. package/dist/cli/root.d.ts +1 -0
  13. package/dist/cli/root.js +2 -0
  14. package/dist/cli/runtime/command-bus.js +14 -1
  15. package/dist/cli/theme/terminal-capability.js +1 -1
  16. package/dist/cli/theme/theme-registry.js +90 -2
  17. package/dist/cli/ui/event.d.ts +1 -0
  18. package/dist/cli/ui/neon-grid-renderer.d.ts +8 -0
  19. package/dist/cli/ui/neon-grid-renderer.js +84 -14
  20. package/dist/cli/ui/plain-renderer.js +3 -1
  21. package/dist/cli/ui/system24-renderer.js +3 -1
  22. package/dist/cli/v2/chat-repl.d.ts +2 -1
  23. package/dist/cli/v2/chat-repl.js +58 -2
  24. package/dist/cli/v2/cli-v2-skeleton.js +7 -4
  25. package/dist/cli/v2/interactive-prompt.js +4 -3
  26. package/dist/commands/auth.js +2 -0
  27. package/dist/commands/chat/native-root-loop.d.ts +1 -0
  28. package/dist/commands/chat/native-root-loop.js +162 -13
  29. package/dist/commands/chat/runtime.js +9 -0
  30. package/dist/commands/chat/slash/commands/routing.js +31 -1
  31. package/dist/commands/chat/utils.js +3 -1
  32. package/dist/commands/cockpit/render.js +2 -1
  33. package/dist/commands/doctor/checks.js +2 -2
  34. package/dist/commands/init/core.js +2 -2
  35. package/dist/commands/init.js +2 -2
  36. package/dist/commands/mcp/config.js +38 -16
  37. package/dist/commands/mcp/doctor.js +7 -8
  38. package/dist/commands/mcp/list.js +4 -3
  39. package/dist/commands/mcp/shared.d.ts +4 -1
  40. package/dist/commands/mcp/shared.js +21 -3
  41. package/dist/commands/mcp/test.js +13 -12
  42. package/dist/commands/model.js +4 -0
  43. package/dist/commands/provider.js +5 -1
  44. package/dist/commands/version.d.ts +1 -0
  45. package/dist/commands/version.js +18 -0
  46. package/dist/contracts/command.d.ts +25 -0
  47. package/dist/contracts/command.js +1 -0
  48. package/dist/contracts/evidence-bundle.d.ts +52 -0
  49. package/dist/contracts/evidence-bundle.js +1 -0
  50. package/dist/contracts/index.d.ts +2 -0
  51. package/dist/contracts/index.js +2 -0
  52. package/dist/contracts/web-bridge.d.ts +1 -1
  53. package/dist/contracts/web-bridge.js +1 -1
  54. package/dist/evidence/bundle-validator.d.ts +7 -0
  55. package/dist/evidence/bundle-validator.js +103 -0
  56. package/dist/evidence/index.d.ts +5 -0
  57. package/dist/evidence/index.js +2 -0
  58. package/dist/evidence/proof-trust.js +2 -10
  59. package/dist/evidence/trust-calibration.d.ts +25 -0
  60. package/dist/evidence/trust-calibration.js +151 -0
  61. package/dist/mcp/omk-project-server.js +1 -1
  62. package/dist/metrics/provenance-ratio.d.ts +16 -0
  63. package/dist/metrics/provenance-ratio.js +33 -0
  64. package/dist/orchestration/dag.d.ts +17 -0
  65. package/dist/orchestration/ensemble-decision.d.ts +2 -0
  66. package/dist/orchestration/ensemble-decision.js +4 -0
  67. package/dist/orchestration/hedge-persona-weights.d.ts +25 -0
  68. package/dist/orchestration/hedge-persona-weights.js +43 -0
  69. package/dist/orchestration/parallel-ui.d.ts +1 -0
  70. package/dist/orchestration/parallel-ui.js +12 -3
  71. package/dist/orchestration/route-calibration.d.ts +27 -0
  72. package/dist/orchestration/route-calibration.js +123 -0
  73. package/dist/orchestration/routing/mcp-config.js +5 -1
  74. package/dist/orchestration/routing/types.d.ts +21 -0
  75. package/dist/orchestration/routing.d.ts +10 -0
  76. package/dist/orchestration/routing.js +89 -3
  77. package/dist/providers/model-registry.d.ts +2 -1
  78. package/dist/providers/model-registry.js +76 -4
  79. package/dist/providers/provider-runtime.js +9 -1
  80. package/dist/providers/thinking-levels.js +2 -0
  81. package/dist/providers/types.d.ts +1 -1
  82. package/dist/runtime/glm-api-runtime.d.ts +4 -0
  83. package/dist/runtime/glm-api-runtime.js +15 -0
  84. package/dist/runtime/index.d.ts +1 -0
  85. package/dist/runtime/index.js +1 -0
  86. package/dist/runtime/kimi-api-runtime.d.ts +6 -1
  87. package/dist/runtime/kimi-api-runtime.js +10 -4
  88. package/dist/runtime/recovery-artifact-store.d.ts +39 -0
  89. package/dist/runtime/recovery-artifact-store.js +111 -0
  90. package/dist/runtime/runtime-backed-task-runner.js +10 -0
  91. package/dist/runtime/runtime-bootstrap.js +17 -1
  92. package/dist/runtime/slash-commands.js +38 -5
  93. package/dist/schema/command.schema.d.ts +121 -0
  94. package/dist/schema/command.schema.js +25 -0
  95. package/dist/schema/envelope.schema.d.ts +2 -2
  96. package/dist/schema/evidence-bundle.schema.d.ts +196 -0
  97. package/dist/schema/evidence-bundle.schema.js +38 -0
  98. package/dist/schema/index.d.ts +2 -0
  99. package/dist/schema/index.js +2 -0
  100. package/dist/schema/proof-bundle.schema.d.ts +2 -2
  101. package/dist/theme/external-theme-adapters.d.ts +27 -0
  102. package/dist/theme/external-theme-adapters.js +52 -0
  103. package/dist/theme/library-effects.d.ts +12 -0
  104. package/dist/theme/library-effects.js +47 -0
  105. package/dist/theme/parallel.js +3 -2
  106. package/dist/tui/terminal-frame-renderer.d.ts +9 -1
  107. package/dist/tui/terminal-frame-renderer.js +77 -7
  108. package/dist/util/chat-cockpit.d.ts +3 -1
  109. package/dist/util/chat-cockpit.js +44 -5
  110. package/dist/util/command-envelope.d.ts +19 -0
  111. package/dist/util/command-envelope.js +31 -0
  112. package/dist/util/events-logger.d.ts +1 -1
  113. package/dist/util/events-logger.js +23 -16
  114. package/dist/util/fs/mcp-diagnose.js +2 -1
  115. package/dist/util/fs/mcp-runtime-config.js +14 -2
  116. package/dist/util/i18n.js +2 -2
  117. package/dist/util/screenshot-store.d.ts +7 -0
  118. package/dist/util/screenshot-store.js +30 -0
  119. package/dist/util/terminal-kit-bridge.d.ts +24 -0
  120. package/dist/util/terminal-kit-bridge.js +60 -0
  121. package/dist/util/terminal-owner.d.ts +1 -0
  122. package/dist/util/terminal-owner.js +31 -0
  123. package/dist/version.d.ts +3 -1
  124. package/dist/version.js +4 -0
  125. package/docs/2026-05-18-project-direction.md +1 -1
  126. package/docs/2026-06-14/critical-issues.md +19 -0
  127. package/docs/2026-06-14/improvements.md +15 -0
  128. package/docs/2026-06-14/init-checklist.md +25 -0
  129. package/docs/2026-06-14/plan.md +20 -0
  130. package/docs/2026-06-15/critical-issues.md +19 -0
  131. package/docs/2026-06-15/improvements.md +15 -0
  132. package/docs/2026-06-15/init-checklist.md +25 -0
  133. package/docs/2026-06-15/plan.md +20 -0
  134. package/docs/claims.md +85 -0
  135. package/docs/comparisons/omk-vs-claude-code.md +31 -0
  136. package/docs/comparisons/omk-vs-codex.md +33 -0
  137. package/docs/comparisons/omk-vs-mcp-agent.md +33 -0
  138. package/docs/comparisons/omk-vs-opencode.md +32 -0
  139. package/docs/decision-layer-calibration.md +20 -0
  140. package/docs/geo-eval-prompts.md +53 -0
  141. package/docs/getting-started.md +3 -31
  142. package/docs/headroom-omk-setup-guide.md +2 -2
  143. package/docs/integrations/adaptorch.md +46 -0
  144. package/docs/native-root-runtime-algorithms.md +2 -303
  145. package/docs/native-root-runtime-hardening.md +1 -1
  146. package/docs/provider-maturity.md +1 -1
  147. package/docs/use-cases/evidence-gated-coding-agents.md +52 -0
  148. package/docs/use-cases/no-kimi-mode.md +38 -0
  149. package/docs/use-cases/provider-routing-for-ai-coding.md +36 -0
  150. package/docs/use-cases/replayable-agent-runs.md +40 -0
  151. package/docs/use-cases/scoped-mcp-for-coding-agents.md +34 -0
  152. package/docs/versioning.md +9 -3
  153. package/docs/what-is-omk.md +64 -0
  154. package/llms.txt +28 -18
  155. package/package.json +7 -2
  156. package/readmeasset/.npmignore +3 -0
  157. package/readmeasset/ASSET_INDEX.md +3 -0
  158. package/readmeasset/ASSET_PROVENANCE.md +6 -0
  159. package/readmeasset/omk-adaptorch-ouroboros-supermemory.svg +95 -0
  160. package/readmeasset/omk-init-control-loop.svg +118 -0
  161. package/readmeasset/omk-parallel-subagents.svg +97 -0
  162. package/templates/web-bridge/chrome-extension/background.js +1 -1
@@ -0,0 +1,33 @@
1
+ export function computeProvenanceRatio(layers) {
2
+ if (layers.length === 0)
3
+ throw new Error("provenance ratio requires at least one layer");
4
+ const importanceSum = layers.reduce((sum, layer) => sum + layer.importance, 0);
5
+ if (importanceSum <= 0)
6
+ throw new Error("provenance layer importance must sum to a positive value");
7
+ const normalizedLayers = layers.map((layer) => {
8
+ const importance = layer.importance / importanceSum;
9
+ const originality = clamp01(layer.loc <= 0 ? 0 : layer.addedModifiedLines / layer.loc);
10
+ return {
11
+ ...layer,
12
+ importance,
13
+ originality: round6(originality),
14
+ weightedOriginality: round6(originality * importance),
15
+ };
16
+ });
17
+ const rawHeadline = layers.reduce((sum, layer) => {
18
+ const importance = layer.importance / importanceSum;
19
+ const originality = clamp01(layer.loc <= 0 ? 0 : layer.addedModifiedLines / layer.loc);
20
+ return sum + (originality * importance);
21
+ }, 0);
22
+ return {
23
+ headlineOriginality: round6(rawHeadline),
24
+ layers: normalizedLayers,
25
+ importanceSum: round6(normalizedLayers.reduce((sum, layer) => sum + layer.importance, 0)),
26
+ };
27
+ }
28
+ function clamp01(value) {
29
+ return Math.max(0, Math.min(1, value));
30
+ }
31
+ function round6(value) {
32
+ return Math.round(value * 1_000_000) / 1_000_000;
33
+ }
@@ -56,6 +56,23 @@ export interface DagNodeRouting {
56
56
  readOnly?: boolean;
57
57
  evidenceRequired?: boolean;
58
58
  rationale?: string;
59
+ routeTrace?: Array<{
60
+ id: string;
61
+ kind: "skill" | "mcp" | "tool" | "hook";
62
+ source: "project" | "global" | "builtin";
63
+ baseScore: number;
64
+ sourcePrior: number;
65
+ score: number;
66
+ features: {
67
+ roleMatch: number;
68
+ keywordScore: number;
69
+ evidenceFit: number;
70
+ contextFit: number;
71
+ safetyFit: number;
72
+ keywordMatches: number;
73
+ };
74
+ reason: string;
75
+ }>;
59
76
  replanHint?: {
60
77
  criterionId?: string;
61
78
  artifactRef?: string;
@@ -1,5 +1,6 @@
1
1
  import type { RunState, NextAction } from "../contracts/orchestration.js";
2
2
  import type { GoalSpec, GoalEvidence, IntentFrame } from "../contracts/goal.js";
3
+ import { type PersonaWeightState } from "./hedge-persona-weights.js";
3
4
  export interface DecisionCandidate {
4
5
  id: string;
5
6
  perspective: string;
@@ -12,6 +13,7 @@ export interface EnsembleDecisionPolicy {
12
13
  candidates?: DecisionCandidate[];
13
14
  extraVotes?: EnsembleDecisionCandidateVote[];
14
15
  intentFrame?: IntentFrame;
16
+ personaWeights?: PersonaWeightState;
15
17
  }
16
18
  export interface EnsembleDecisionResult {
17
19
  action: NextAction;
@@ -1,6 +1,7 @@
1
1
  import { evaluateMissingCriteria, suggestNextAction } from "../goal/eval-criteria.js";
2
2
  import { scoreGoal } from "../goal/scoring.js";
3
3
  import { renderPromptDigest } from "../goal/prompt-digest.js";
4
+ import { applyPersonaWeights } from "./hedge-persona-weights.js";
4
5
  const DEFAULT_CANDIDATES = [
5
6
  { id: "progress-analyst", perspective: "Evaluate success-criteria completion rate and blocker severity", weight: 1.0 },
6
7
  { id: "risk-evaluator", perspective: "Assess failure modes, retry exhaustion, and rollback risk", weight: 0.9 },
@@ -248,6 +249,9 @@ export function evaluateEnsembleDecision(goal, runState, evidence, policy = {})
248
249
  ...(policy.extraVotes ?? []).map(normalizeExtraVote).filter((vote) => Boolean(vote)),
249
250
  ];
250
251
  votes = calibrateVoteWeights(votes, runState, evidence);
252
+ if (policy.personaWeights) {
253
+ votes = applyPersonaWeights(votes, policy.personaWeights);
254
+ }
251
255
  const actionScores = new Map();
252
256
  for (const vote of votes) {
253
257
  actionScores.set(vote.action, (actionScores.get(vote.action) ?? 0) + vote.weight);
@@ -0,0 +1,25 @@
1
+ import type { NextAction } from "../contracts/orchestration.js";
2
+ export interface PersonaWeightState {
3
+ readonly weights: Record<string, number>;
4
+ readonly eta: number;
5
+ readonly floor: number;
6
+ readonly horizon: number;
7
+ }
8
+ export interface PersonaVoteOutcome {
9
+ readonly id: string;
10
+ readonly action: NextAction;
11
+ }
12
+ export interface PersonaWeightUpdateResult {
13
+ readonly state: PersonaWeightState;
14
+ readonly collapseAlert: boolean;
15
+ readonly dominantShare: number;
16
+ }
17
+ export declare function createHedgePersonaWeights(personaIds: readonly string[], options?: {
18
+ horizon?: number;
19
+ floor?: number;
20
+ }): PersonaWeightState;
21
+ export declare function updateHedgePersonaWeights(state: PersonaWeightState, votes: readonly PersonaVoteOutcome[], correctAction: NextAction): PersonaWeightUpdateResult;
22
+ export declare function applyPersonaWeights<T extends {
23
+ id: string;
24
+ weight: number;
25
+ }>(votes: readonly T[], state: PersonaWeightState): T[];
@@ -0,0 +1,43 @@
1
+ export function createHedgePersonaWeights(personaIds, options = {}) {
2
+ const uniqueIds = [...new Set(personaIds)].filter(Boolean);
3
+ if (uniqueIds.length === 0)
4
+ throw new Error("Hedge persona weights require at least one persona");
5
+ const horizon = Math.max(1, options.horizon ?? 100);
6
+ const floor = Math.max(0, options.floor ?? 0.3);
7
+ const eta = Math.sqrt((8 * Math.log(uniqueIds.length)) / horizon);
8
+ return {
9
+ weights: Object.fromEntries(uniqueIds.map((id) => [id, 1])),
10
+ eta,
11
+ floor,
12
+ horizon,
13
+ };
14
+ }
15
+ export function updateHedgePersonaWeights(state, votes, correctAction) {
16
+ const ids = Object.keys(state.weights);
17
+ const voteById = new Map(votes.map((vote) => [vote.id, vote.action]));
18
+ const decayed = {};
19
+ for (const id of ids) {
20
+ const loss = voteById.get(id) === correctAction ? 0 : 1;
21
+ decayed[id] = state.weights[id] * Math.exp(-state.eta * loss);
22
+ }
23
+ const sum = Object.values(decayed).reduce((acc, value) => acc + value, 0);
24
+ const scaled = {};
25
+ for (const id of ids) {
26
+ scaled[id] = Math.max(state.floor, (ids.length * decayed[id]) / Math.max(Number.EPSILON, sum));
27
+ }
28
+ const scaledSum = Object.values(scaled).reduce((acc, value) => acc + value, 0);
29
+ const normalized = Object.fromEntries(ids.map((id) => [id, round6((ids.length * scaled[id]) / scaledSum)]));
30
+ const total = Object.values(normalized).reduce((acc, value) => acc + value, 0);
31
+ const dominantShare = Math.max(...Object.values(normalized)) / Math.max(Number.EPSILON, total);
32
+ return {
33
+ state: { ...state, weights: normalized },
34
+ collapseAlert: dominantShare > 0.5,
35
+ dominantShare: round6(dominantShare),
36
+ };
37
+ }
38
+ export function applyPersonaWeights(votes, state) {
39
+ return votes.map((vote) => ({ ...vote, weight: state.weights[vote.id] ?? vote.weight }));
40
+ }
41
+ function round6(value) {
42
+ return Math.round(value * 1_000_000) / 1_000_000;
43
+ }
@@ -47,5 +47,6 @@ export declare class ParallelLiveRenderer {
47
47
  start(stateProvider: () => RunState | undefined): void;
48
48
  stop(): void;
49
49
  renderOnce(state: RunState): string;
50
+ private firstCockpitFrame;
50
51
  private renderNow;
51
52
  }
@@ -406,6 +406,7 @@ export class ParallelLiveRenderer {
406
406
  }
407
407
  start(stateProvider) {
408
408
  this.stopped = false;
409
+ this.firstCockpitFrame = true;
409
410
  if (this.useAlternateScreen) {
410
411
  process.stdout.write("\x1b[?1049h");
411
412
  }
@@ -431,6 +432,7 @@ export class ParallelLiveRenderer {
431
432
  this.lastFrame = frame;
432
433
  return frame;
433
434
  }
435
+ firstCockpitFrame = true;
434
436
  renderNow(stateProvider) {
435
437
  const state = stateProvider();
436
438
  if (!state)
@@ -462,9 +464,16 @@ export class ParallelLiveRenderer {
462
464
  void lockedStdoutWrite(frame);
463
465
  return;
464
466
  }
465
- // Cockpit mode
466
- const clearPrefix = this.useAlternateScreen ? "\x1b[2J\x1b[H" : "\x1b[H\x1b[J";
467
- const output = clearPrefix + frame + "\n";
467
+ // Cockpit mode: use alternate-screen clear+home only when explicitly opted in.
468
+ // On the main screen, append frames so drag/scroll do not snap to the top.
469
+ let output;
470
+ if (this.useAlternateScreen) {
471
+ output = `${this.firstCockpitFrame ? "" : "\x1b[2J\x1b[H"}${frame}\n`;
472
+ }
473
+ else {
474
+ output = `${this.firstCockpitFrame ? "" : "\n\n"}${frame}\n`;
475
+ }
476
+ this.firstCockpitFrame = false;
468
477
  void lockedStdoutWrite(output);
469
478
  }
470
479
  }
@@ -0,0 +1,27 @@
1
+ export type RouteWeightKey = "role" | "keyword" | "evidence" | "context" | "safety";
2
+ export type RouteWeights = Record<RouteWeightKey, number>;
3
+ export interface RouteWeightEvaluation {
4
+ readonly weights: RouteWeights;
5
+ readonly keywordTau: number;
6
+ readonly outcomes: readonly boolean[];
7
+ }
8
+ export interface RouteCalibrationResult {
9
+ readonly adopted: boolean;
10
+ readonly current: RouteWeightEvaluation;
11
+ readonly candidate: RouteWeightEvaluation;
12
+ readonly meanDelta: number;
13
+ readonly mcnemarP: number;
14
+ readonly oddsRatio: number;
15
+ readonly reason: string;
16
+ }
17
+ export declare const DEFAULT_ROUTE_WEIGHTS: RouteWeights;
18
+ export declare function projectRouteWeights(weights: RouteWeights): RouteWeights;
19
+ export declare function smoothKeywordScore(matchCount: number, tau: number): number;
20
+ export declare function generateRouteWeightCandidates(current?: RouteWeights, options?: {
21
+ step?: number;
22
+ keywordTaus?: readonly number[];
23
+ }): RouteWeights[];
24
+ export declare function selectBestRouteWeightEvaluation(evaluations: readonly RouteWeightEvaluation[]): RouteWeightEvaluation;
25
+ export declare function decideRouteWeightAdoption(current: RouteWeightEvaluation, candidate: RouteWeightEvaluation, options?: {
26
+ alpha?: number;
27
+ }): RouteCalibrationResult;
@@ -0,0 +1,123 @@
1
+ const DEFAULT_KEYS = ["role", "keyword", "evidence", "context", "safety"];
2
+ export const DEFAULT_ROUTE_WEIGHTS = Object.freeze({
3
+ role: 0.30,
4
+ keyword: 0.25,
5
+ evidence: 0.20,
6
+ context: 0.15,
7
+ safety: 0.10,
8
+ });
9
+ export function projectRouteWeights(weights) {
10
+ const clipped = Object.fromEntries(DEFAULT_KEYS.map((key) => [key, Math.max(0, Number(weights[key]) || 0)]));
11
+ const sum = DEFAULT_KEYS.reduce((acc, key) => acc + clipped[key], 0);
12
+ if (sum <= 0)
13
+ return { ...DEFAULT_ROUTE_WEIGHTS };
14
+ const projected = Object.fromEntries(DEFAULT_KEYS.map((key) => [key, clipped[key] / sum]));
15
+ return roundRouteWeights(projected);
16
+ }
17
+ export function smoothKeywordScore(matchCount, tau) {
18
+ const safeTau = Math.max(0.001, tau);
19
+ return round6(1 - Math.exp(-Math.max(0, matchCount) / safeTau));
20
+ }
21
+ export function generateRouteWeightCandidates(current = DEFAULT_ROUTE_WEIGHTS, options = {}) {
22
+ const step = options.step ?? 0.05;
23
+ const seeds = [projectRouteWeights(current), uniformRouteWeights()];
24
+ for (const key of DEFAULT_KEYS) {
25
+ seeds.push(projectRouteWeights({ ...current, [key]: current[key] + step }));
26
+ seeds.push(projectRouteWeights({ ...current, [key]: Math.max(0, current[key] - step) }));
27
+ }
28
+ const seen = new Set();
29
+ const result = [];
30
+ for (const seed of seeds) {
31
+ const id = DEFAULT_KEYS.map((key) => seed[key].toFixed(6)).join(",");
32
+ if (seen.has(id))
33
+ continue;
34
+ seen.add(id);
35
+ result.push(seed);
36
+ }
37
+ return result;
38
+ }
39
+ export function selectBestRouteWeightEvaluation(evaluations) {
40
+ if (evaluations.length === 0)
41
+ throw new Error("route calibration requires at least one evaluation");
42
+ return [...evaluations].sort((a, b) => meanSuccess(b.outcomes) - meanSuccess(a.outcomes))[0];
43
+ }
44
+ export function decideRouteWeightAdoption(current, candidate, options = {}) {
45
+ if (current.outcomes.length !== candidate.outcomes.length) {
46
+ throw new Error("paired route calibration requires equal-length outcome vectors");
47
+ }
48
+ if (current.outcomes.length === 0) {
49
+ throw new Error("route calibration requires at least one paired outcome");
50
+ }
51
+ const currentMean = meanSuccess(current.outcomes);
52
+ const candidateMean = meanSuccess(candidate.outcomes);
53
+ const meanDelta = round6(candidateMean - currentMean);
54
+ const { b, c } = discordantCounts(current.outcomes, candidate.outcomes);
55
+ const mcnemarP = exactMcNemarP(b, c);
56
+ const oddsRatio = c === 0 ? (b > 0 ? Number.POSITIVE_INFINITY : 1) : b / c;
57
+ const alpha = options.alpha ?? 0.05;
58
+ const adopted = mcnemarP < alpha && oddsRatio > 1;
59
+ return {
60
+ adopted,
61
+ current,
62
+ candidate,
63
+ meanDelta,
64
+ mcnemarP,
65
+ oddsRatio,
66
+ reason: adopted
67
+ ? `candidate wins paired McNemar p=${mcnemarP.toFixed(4)}, oddsRatio=${formatRatio(oddsRatio)}`
68
+ : "hand-set weights not yet beaten by paired evidence",
69
+ };
70
+ }
71
+ function uniformRouteWeights() {
72
+ return { role: 0.2, keyword: 0.2, evidence: 0.2, context: 0.2, safety: 0.2 };
73
+ }
74
+ function meanSuccess(outcomes) {
75
+ return outcomes.filter(Boolean).length / Math.max(1, outcomes.length);
76
+ }
77
+ function discordantCounts(current, candidate) {
78
+ let b = 0;
79
+ let c = 0;
80
+ for (let i = 0; i < current.length; i += 1) {
81
+ if (candidate[i] && !current[i])
82
+ b += 1;
83
+ if (!candidate[i] && current[i])
84
+ c += 1;
85
+ }
86
+ return { b, c };
87
+ }
88
+ function exactMcNemarP(b, c) {
89
+ const n = b + c;
90
+ if (n === 0)
91
+ return 1;
92
+ const observed = Math.min(b, c);
93
+ let tail = 0;
94
+ for (let k = 0; k <= observed; k += 1) {
95
+ tail += binomialProbability(n, k, 0.5);
96
+ }
97
+ return Math.min(1, 2 * tail);
98
+ }
99
+ function binomialProbability(n, k, p) {
100
+ return combination(n, k) * (p ** k) * ((1 - p) ** (n - k));
101
+ }
102
+ function combination(n, k) {
103
+ const kk = Math.min(k, n - k);
104
+ let result = 1;
105
+ for (let i = 1; i <= kk; i += 1) {
106
+ result = (result * (n - kk + i)) / i;
107
+ }
108
+ return result;
109
+ }
110
+ function roundRouteWeights(weights) {
111
+ const rounded = Object.fromEntries(DEFAULT_KEYS.map((key) => [key, round6(weights[key])]));
112
+ const sum = DEFAULT_KEYS.reduce((total, key) => total + rounded[key], 0);
113
+ const residual = round6(1 - sum);
114
+ const recipient = DEFAULT_KEYS.reduce((best, key) => (rounded[key] > rounded[best] ? key : best));
115
+ rounded[recipient] = round6(Math.max(0, rounded[recipient] + residual));
116
+ return rounded;
117
+ }
118
+ function round6(value) {
119
+ return Math.round(value * 1_000_000) / 1_000_000;
120
+ }
121
+ function formatRatio(value) {
122
+ return Number.isFinite(value) ? value.toFixed(3) : "Infinity";
123
+ }
@@ -101,7 +101,11 @@ export function loadMergedMcpConfigSync(projectRoot, scope) {
101
101
  if (scope === "none") {
102
102
  return { servers, sources, diagnostics };
103
103
  }
104
- const globalFiles = scope === "all" ? [join(getRoutingUserHome(), ".kimi", "mcp.json"), join(getRoutingUserHome(), ".omk", "mcp.json")] : [];
104
+ const globalFiles = scope === "all" ? [
105
+ join(getRoutingUserHome(), ".omk", "agent", "mcp.json"),
106
+ join(getRoutingUserHome(), ".omk", "mcp.json"),
107
+ join(getRoutingUserHome(), ".kimi", "mcp.json"),
108
+ ] : [];
105
109
  for (const path of globalFiles) {
106
110
  if (!existsSync(path))
107
111
  continue;
@@ -15,9 +15,30 @@ export interface RouteCandidate {
15
15
  contextCost: 1 | 2 | 3;
16
16
  capabilities: string[];
17
17
  }
18
+ export interface RouteScoreFeatures {
19
+ roleMatch: number;
20
+ keywordScore: number;
21
+ evidenceFit: number;
22
+ contextFit: number;
23
+ safetyFit: number;
24
+ keywordMatches: number;
25
+ }
26
+ export interface RouteScoreTrace {
27
+ id: string;
28
+ kind: RouteKind;
29
+ source: RouteSource;
30
+ baseScore: number;
31
+ sourcePrior: number;
32
+ score: number;
33
+ features: RouteScoreFeatures;
34
+ reason: string;
35
+ }
18
36
  export interface ScoredRoute {
19
37
  candidate: RouteCandidate;
20
38
  score: number;
39
+ baseScore: number;
40
+ sourcePrior: number;
41
+ features: RouteScoreFeatures;
21
42
  reason: string;
22
43
  }
23
44
  export interface RoutingDiagnostic {
@@ -1,6 +1,13 @@
1
1
  import type { DagNode, DagNodeDefinition, DagNodeRouting } from "./dag.js";
2
2
  type RouteSource = "project" | "global" | "builtin";
3
3
  export type RoutingInput = Pick<DagNodeDefinition, "id" | "name" | "role" | "inputs" | "outputs" | "cost" | "routing">;
4
+ declare const ROUTE_SCORE_WEIGHTS: {
5
+ readonly role: 0.3;
6
+ readonly keyword: 0.25;
7
+ readonly evidence: 0.2;
8
+ readonly context: 0.15;
9
+ readonly safety: 0.1;
10
+ };
4
11
  export interface RoutingInventory {
5
12
  skills: Map<string, RouteSource>;
6
13
  mcpServers: Map<string, RouteSource>;
@@ -21,6 +28,9 @@ export declare function selectTaskRouting(input: RoutingInput): DagNodeRouting;
21
28
  export declare function resetRoutingInventoryCache(): void;
22
29
  export declare function mergeDagNodeRouting(auto: DagNodeRouting, override: DagNodeRouting | undefined): DagNodeRouting;
23
30
  export declare function dagNodeRoutingEnv(node: DagNode, dag?: import("./dag.js").Dag): Record<string, string>;
31
+ export declare function computeRouteBaseScoreMinGap(weights: typeof ROUTE_SCORE_WEIGHTS): number;
32
+ export declare function routeSourcePrior(source: RouteSource): number;
33
+ export declare function assertRoutePriorTiebreakInvariant(priors: Record<RouteSource, number>, minBaseGap?: number): void;
24
34
  export declare function discoverRoutingInventory(projectRoot?: string): RoutingInventory;
25
35
  /** Exported for focused cache-equivalence tests. Returns the cached matcher for an
26
36
  * already-normalized alphanumeric keyword (or null when no regex path applies). */
@@ -4,6 +4,21 @@ import { isAbsolute, join, relative, resolve, sep } from "path";
4
4
  import { normalizeUserHomePath } from "../util/fs.js";
5
5
  import { assignSkills } from "./skill-assigner.js";
6
6
  import { OMK_RELEASE_GUARD_PRESET, OMK_TS_PRODUCT_PRESET, OMK_WORKTREE_TEAM_PRESET, } from "../runtime/core-verified-preset.js";
7
+ const ROUTE_SCORE_WEIGHTS = {
8
+ role: 0.30,
9
+ keyword: 0.25,
10
+ evidence: 0.20,
11
+ context: 0.15,
12
+ safety: 0.10,
13
+ };
14
+ const ROUTE_SOURCE_PRIOR = {
15
+ project: 0.02,
16
+ builtin: 0.01,
17
+ global: 0,
18
+ };
19
+ const ROUTE_SCORE_SCALE = 1_000_000;
20
+ const ROUTE_BASE_SCORE_MIN_GAP = computeRouteBaseScoreMinGap(ROUTE_SCORE_WEIGHTS);
21
+ assertRoutePriorTiebreakInvariant(ROUTE_SOURCE_PRIOR, ROUTE_BASE_SCORE_MIN_GAP);
7
22
  const MAX_SKILLS = 3;
8
23
  const MAX_MCP_SERVERS = 2;
9
24
  const MAX_TOOLS = 4;
@@ -363,6 +378,7 @@ export function selectTaskRouting(input) {
363
378
  const mergedRationale = autoAssignment.skills.length > 0
364
379
  ? `${renderRationale(scored.slice(0, 3), contextBudget)} | Auto: ${autoAssignment.rationale}`
365
380
  : renderRationale(scored.slice(0, 3), contextBudget);
381
+ const routeTrace = scored.slice(0, 8).map(toRouteScoreTrace);
366
382
  return {
367
383
  provider: "auto",
368
384
  providerReason: "Provider-neutral runtime router decides at node execution time",
@@ -374,6 +390,7 @@ export function selectTaskRouting(input) {
374
390
  readOnly,
375
391
  evidenceRequired,
376
392
  rationale: mergedRationale,
393
+ routeTrace,
377
394
  rejected: rejected.slice(0, 6),
378
395
  requiresMcp: input.routing?.requiresMcp ?? false,
379
396
  requiresToolCalling: input.routing?.requiresToolCalling ?? false,
@@ -467,6 +484,55 @@ export function dagNodeRoutingEnv(node, dag) {
467
484
  OMK_ROUTE_REQUIRES_TOOL_CALLING: String(routing.requiresToolCalling ?? false),
468
485
  };
469
486
  }
487
+ export function computeRouteBaseScoreMinGap(weights) {
488
+ const increments = [
489
+ weights.role,
490
+ weights.keyword / 2,
491
+ weights.keyword,
492
+ weights.evidence,
493
+ weights.context / 2,
494
+ weights.context,
495
+ weights.safety / 2,
496
+ weights.safety,
497
+ ].map((value) => Math.round(value * ROUTE_SCORE_SCALE)).filter((value) => value > 0);
498
+ const gap = increments.reduce((acc, value) => gcd(acc, value));
499
+ return gap / ROUTE_SCORE_SCALE;
500
+ }
501
+ export function routeSourcePrior(source) {
502
+ return ROUTE_SOURCE_PRIOR[source] ?? 0;
503
+ }
504
+ export function assertRoutePriorTiebreakInvariant(priors, minBaseGap = ROUTE_BASE_SCORE_MIN_GAP) {
505
+ const values = Object.values(priors);
506
+ const spread = Math.max(...values) - Math.min(...values);
507
+ if (!(spread < minBaseGap)) {
508
+ throw new Error(`Route source prior spread ${spread.toFixed(6)} must be < base-score min gap ${minBaseGap.toFixed(6)}`);
509
+ }
510
+ }
511
+ function gcd(a, b) {
512
+ let x = Math.abs(a);
513
+ let y = Math.abs(b);
514
+ while (y !== 0) {
515
+ const next = x % y;
516
+ x = y;
517
+ y = next;
518
+ }
519
+ return x;
520
+ }
521
+ function roundScore(value) {
522
+ return Math.round(value * ROUTE_SCORE_SCALE) / ROUTE_SCORE_SCALE;
523
+ }
524
+ function toRouteScoreTrace(route) {
525
+ return {
526
+ id: route.candidate.id,
527
+ kind: route.candidate.kind,
528
+ source: route.candidate.source,
529
+ baseScore: route.baseScore,
530
+ sourcePrior: route.sourcePrior,
531
+ score: route.score,
532
+ features: route.features,
533
+ reason: route.reason,
534
+ };
535
+ }
470
536
  function scoreRoute(candidate, role, text, evidenceRequired, contextBudget) {
471
537
  const roleMatch = candidate.roles.includes(role) ? 1 : 0;
472
538
  const keywordMatches = candidate.keywords.filter((keyword) => textMatchesKeyword(text, keyword)).length;
@@ -476,11 +542,27 @@ function scoreRoute(candidate, role, text, evidenceRequired, contextBudget) {
476
542
  ? candidate.contextCost === 1 ? 1 : 0
477
543
  : candidate.contextCost <= 2 ? 1 : 0.5;
478
544
  const safetyFit = candidate.writeRisk === "none" ? 1 : candidate.writeRisk === "low" ? 0.5 : 0;
479
- const localFit = candidate.source === "project" ? 0.08 : candidate.source === "builtin" ? 0.04 : 0;
480
- const score = (0.3 * roleMatch) + (0.25 * keywordScore) + (0.2 * evidenceFit) + (0.15 * smallContextFit) + (0.1 * safetyFit) + localFit;
545
+ const sourcePrior = routeSourcePrior(candidate.source);
546
+ const features = {
547
+ roleMatch,
548
+ keywordScore,
549
+ evidenceFit,
550
+ contextFit: smallContextFit,
551
+ safetyFit,
552
+ keywordMatches,
553
+ };
554
+ const baseScore = roundScore((ROUTE_SCORE_WEIGHTS.role * roleMatch)
555
+ + (ROUTE_SCORE_WEIGHTS.keyword * keywordScore)
556
+ + (ROUTE_SCORE_WEIGHTS.evidence * evidenceFit)
557
+ + (ROUTE_SCORE_WEIGHTS.context * smallContextFit)
558
+ + (ROUTE_SCORE_WEIGHTS.safety * safetyFit));
559
+ const score = roundScore(baseScore + sourcePrior);
481
560
  return {
482
561
  candidate,
483
562
  score,
563
+ baseScore,
564
+ sourcePrior,
565
+ features,
484
566
  reason: [
485
567
  roleMatch ? "role" : "",
486
568
  keywordMatches > 0 ? `${keywordMatches} keyword` : "",
@@ -806,7 +888,11 @@ function loadMergedMcpConfigSync(projectRoot, scope) {
806
888
  if (scope === "none") {
807
889
  return { servers, sources, diagnostics };
808
890
  }
809
- const globalFiles = scope === "all" ? [join(getRoutingUserHome(), ".kimi", "mcp.json"), join(getRoutingUserHome(), ".omk", "mcp.json")] : [];
891
+ const globalFiles = scope === "all" ? [
892
+ join(getRoutingUserHome(), ".omk", "agent", "mcp.json"),
893
+ join(getRoutingUserHome(), ".omk", "mcp.json"),
894
+ join(getRoutingUserHome(), ".kimi", "mcp.json"),
895
+ ] : [];
810
896
  for (const path of globalFiles) {
811
897
  if (!existsSync(path))
812
898
  continue;
@@ -1,6 +1,6 @@
1
1
  import { type DeepSeekConfigPathOptions } from "./deepseek/deepseek-config.js";
2
2
  import type { ProviderAuthMethod, ProviderId, ProviderKind, ProviderModelRef, ProviderPlanKind, ProviderPolicy, ProviderProfileType, ProviderWireApi } from "./types.js";
3
- export declare const KNOWN_PROVIDER_IDS: readonly ["mimo", "kimi", "deepseek", "qwen", "codex", "openrouter", "anthropic"];
3
+ export declare const KNOWN_PROVIDER_IDS: readonly ["mimo", "kimi", "deepseek", "qwen", "codex", "openrouter", "anthropic", "glm"];
4
4
  export declare const QWEN_DASHSCOPE_COMPAT_BASE_URL = "https://dashscope-intl.aliyuncs.com/compatible-mode/v1";
5
5
  export declare const OPENROUTER_COMPAT_BASE_URL = "https://openrouter.ai/api/v1";
6
6
  export interface ProviderRegistryEntry {
@@ -80,6 +80,7 @@ export interface UserModelAliasResolution {
80
80
  input: string;
81
81
  provider?: ProviderId;
82
82
  model: string;
83
+ thinkingLevel?: string;
83
84
  source: "user-alias" | "provider-alias" | "literal";
84
85
  }
85
86
  export declare function listUserModelAliases(options?: DeepSeekConfigPathOptions): Promise<Record<string, string>>;