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.
- package/AGENTS.md +131 -2
- package/CHANGELOG.md +32 -0
- package/CLAUDE.md +105 -3
- package/DESIGN.md +141 -117
- package/MATURITY.md +7 -12
- package/README.md +66 -347
- package/ROADMAP.md +33 -47
- package/dist/adapters/kimi/runner.js +2 -2
- package/dist/brand/theme.js +2 -2
- package/dist/cli/register-basic-commands.js +3 -2
- package/dist/cli/register-mcp-dag-cron-screenshot-commands.js +1 -1
- package/dist/cli/root.d.ts +1 -0
- package/dist/cli/root.js +2 -0
- package/dist/cli/runtime/command-bus.js +14 -1
- package/dist/cli/theme/terminal-capability.js +1 -1
- package/dist/cli/theme/theme-registry.js +90 -2
- package/dist/cli/ui/event.d.ts +1 -0
- package/dist/cli/ui/neon-grid-renderer.d.ts +8 -0
- package/dist/cli/ui/neon-grid-renderer.js +84 -14
- package/dist/cli/ui/plain-renderer.js +3 -1
- package/dist/cli/ui/system24-renderer.js +3 -1
- package/dist/cli/v2/chat-repl.d.ts +2 -1
- package/dist/cli/v2/chat-repl.js +58 -2
- package/dist/cli/v2/cli-v2-skeleton.js +7 -4
- package/dist/cli/v2/interactive-prompt.js +4 -3
- package/dist/commands/auth.js +2 -0
- package/dist/commands/chat/native-root-loop.d.ts +1 -0
- package/dist/commands/chat/native-root-loop.js +162 -13
- package/dist/commands/chat/runtime.js +9 -0
- package/dist/commands/chat/slash/commands/routing.js +31 -1
- package/dist/commands/chat/utils.js +3 -1
- package/dist/commands/cockpit/render.js +2 -1
- package/dist/commands/doctor/checks.js +2 -2
- package/dist/commands/init/core.js +2 -2
- package/dist/commands/init.js +2 -2
- package/dist/commands/mcp/config.js +38 -16
- package/dist/commands/mcp/doctor.js +7 -8
- package/dist/commands/mcp/list.js +4 -3
- package/dist/commands/mcp/shared.d.ts +4 -1
- package/dist/commands/mcp/shared.js +21 -3
- package/dist/commands/mcp/test.js +13 -12
- package/dist/commands/model.js +4 -0
- package/dist/commands/provider.js +5 -1
- package/dist/commands/version.d.ts +1 -0
- package/dist/commands/version.js +18 -0
- package/dist/contracts/command.d.ts +25 -0
- package/dist/contracts/command.js +1 -0
- package/dist/contracts/evidence-bundle.d.ts +52 -0
- package/dist/contracts/evidence-bundle.js +1 -0
- package/dist/contracts/index.d.ts +2 -0
- package/dist/contracts/index.js +2 -0
- package/dist/contracts/web-bridge.d.ts +1 -1
- package/dist/contracts/web-bridge.js +1 -1
- package/dist/evidence/bundle-validator.d.ts +7 -0
- package/dist/evidence/bundle-validator.js +103 -0
- package/dist/evidence/index.d.ts +5 -0
- package/dist/evidence/index.js +2 -0
- package/dist/evidence/proof-trust.js +2 -10
- package/dist/evidence/trust-calibration.d.ts +25 -0
- package/dist/evidence/trust-calibration.js +151 -0
- package/dist/mcp/omk-project-server.js +1 -1
- package/dist/metrics/provenance-ratio.d.ts +16 -0
- package/dist/metrics/provenance-ratio.js +33 -0
- package/dist/orchestration/dag.d.ts +17 -0
- package/dist/orchestration/ensemble-decision.d.ts +2 -0
- package/dist/orchestration/ensemble-decision.js +4 -0
- package/dist/orchestration/hedge-persona-weights.d.ts +25 -0
- package/dist/orchestration/hedge-persona-weights.js +43 -0
- package/dist/orchestration/parallel-ui.d.ts +1 -0
- package/dist/orchestration/parallel-ui.js +12 -3
- package/dist/orchestration/route-calibration.d.ts +27 -0
- package/dist/orchestration/route-calibration.js +123 -0
- package/dist/orchestration/routing/mcp-config.js +5 -1
- package/dist/orchestration/routing/types.d.ts +21 -0
- package/dist/orchestration/routing.d.ts +10 -0
- package/dist/orchestration/routing.js +89 -3
- package/dist/providers/model-registry.d.ts +2 -1
- package/dist/providers/model-registry.js +76 -4
- package/dist/providers/provider-runtime.js +9 -1
- package/dist/providers/thinking-levels.js +2 -0
- package/dist/providers/types.d.ts +1 -1
- package/dist/runtime/glm-api-runtime.d.ts +4 -0
- package/dist/runtime/glm-api-runtime.js +15 -0
- package/dist/runtime/index.d.ts +1 -0
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/kimi-api-runtime.d.ts +6 -1
- package/dist/runtime/kimi-api-runtime.js +10 -4
- package/dist/runtime/recovery-artifact-store.d.ts +39 -0
- package/dist/runtime/recovery-artifact-store.js +111 -0
- package/dist/runtime/runtime-backed-task-runner.js +10 -0
- package/dist/runtime/runtime-bootstrap.js +17 -1
- package/dist/runtime/slash-commands.js +38 -5
- package/dist/schema/command.schema.d.ts +121 -0
- package/dist/schema/command.schema.js +25 -0
- package/dist/schema/envelope.schema.d.ts +2 -2
- package/dist/schema/evidence-bundle.schema.d.ts +196 -0
- package/dist/schema/evidence-bundle.schema.js +38 -0
- package/dist/schema/index.d.ts +2 -0
- package/dist/schema/index.js +2 -0
- package/dist/schema/proof-bundle.schema.d.ts +2 -2
- package/dist/theme/external-theme-adapters.d.ts +27 -0
- package/dist/theme/external-theme-adapters.js +52 -0
- package/dist/theme/library-effects.d.ts +12 -0
- package/dist/theme/library-effects.js +47 -0
- package/dist/theme/parallel.js +3 -2
- package/dist/tui/terminal-frame-renderer.d.ts +9 -1
- package/dist/tui/terminal-frame-renderer.js +77 -7
- package/dist/util/chat-cockpit.d.ts +3 -1
- package/dist/util/chat-cockpit.js +44 -5
- package/dist/util/command-envelope.d.ts +19 -0
- package/dist/util/command-envelope.js +31 -0
- package/dist/util/events-logger.d.ts +1 -1
- package/dist/util/events-logger.js +23 -16
- package/dist/util/fs/mcp-diagnose.js +2 -1
- package/dist/util/fs/mcp-runtime-config.js +14 -2
- package/dist/util/i18n.js +2 -2
- package/dist/util/screenshot-store.d.ts +7 -0
- package/dist/util/screenshot-store.js +30 -0
- package/dist/util/terminal-kit-bridge.d.ts +24 -0
- package/dist/util/terminal-kit-bridge.js +60 -0
- package/dist/util/terminal-owner.d.ts +1 -0
- package/dist/util/terminal-owner.js +31 -0
- package/dist/version.d.ts +3 -1
- package/dist/version.js +4 -0
- package/docs/2026-05-18-project-direction.md +1 -1
- package/docs/2026-06-14/critical-issues.md +19 -0
- package/docs/2026-06-14/improvements.md +15 -0
- package/docs/2026-06-14/init-checklist.md +25 -0
- package/docs/2026-06-14/plan.md +20 -0
- package/docs/2026-06-15/critical-issues.md +19 -0
- package/docs/2026-06-15/improvements.md +15 -0
- package/docs/2026-06-15/init-checklist.md +25 -0
- package/docs/2026-06-15/plan.md +20 -0
- package/docs/claims.md +85 -0
- package/docs/comparisons/omk-vs-claude-code.md +31 -0
- package/docs/comparisons/omk-vs-codex.md +33 -0
- package/docs/comparisons/omk-vs-mcp-agent.md +33 -0
- package/docs/comparisons/omk-vs-opencode.md +32 -0
- package/docs/decision-layer-calibration.md +20 -0
- package/docs/geo-eval-prompts.md +53 -0
- package/docs/getting-started.md +3 -31
- package/docs/headroom-omk-setup-guide.md +2 -2
- package/docs/integrations/adaptorch.md +46 -0
- package/docs/native-root-runtime-algorithms.md +2 -303
- package/docs/native-root-runtime-hardening.md +1 -1
- package/docs/provider-maturity.md +1 -1
- package/docs/use-cases/evidence-gated-coding-agents.md +52 -0
- package/docs/use-cases/no-kimi-mode.md +38 -0
- package/docs/use-cases/provider-routing-for-ai-coding.md +36 -0
- package/docs/use-cases/replayable-agent-runs.md +40 -0
- package/docs/use-cases/scoped-mcp-for-coding-agents.md +34 -0
- package/docs/versioning.md +9 -3
- package/docs/what-is-omk.md +64 -0
- package/llms.txt +28 -18
- package/package.json +7 -2
- package/readmeasset/.npmignore +3 -0
- package/readmeasset/ASSET_INDEX.md +3 -0
- package/readmeasset/ASSET_PROVENANCE.md +6 -0
- package/readmeasset/omk-adaptorch-ouroboros-supermemory.svg +95 -0
- package/readmeasset/omk-init-control-loop.svg +118 -0
- package/readmeasset/omk-parallel-subagents.svg +97 -0
- 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
|
+
}
|
|
@@ -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
|
-
|
|
467
|
-
|
|
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" ? [
|
|
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
|
|
480
|
-
const
|
|
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" ? [
|
|
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>>;
|