open-multi-agent-kit 0.78.1 → 0.78.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 (102) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/MATURITY.md +4 -0
  3. package/README.md +70 -1
  4. package/dist/benchmark/contracts.d.ts +116 -0
  5. package/dist/benchmark/contracts.js +6 -0
  6. package/dist/benchmark/fixtures.d.ts +11 -0
  7. package/dist/benchmark/fixtures.js +121 -0
  8. package/dist/benchmark/harness.d.ts +13 -0
  9. package/dist/benchmark/harness.js +191 -0
  10. package/dist/benchmark/shadow-mode.d.ts +17 -0
  11. package/dist/benchmark/shadow-mode.js +96 -0
  12. package/dist/cli/register-spec-agent-goal-commands.js +45 -0
  13. package/dist/cli/release-promotion-gate.d.ts +14 -0
  14. package/dist/cli/release-promotion-gate.js +71 -0
  15. package/dist/cli/v2/release-commands.d.ts +29 -0
  16. package/dist/cli/v2/release-commands.js +95 -0
  17. package/dist/commands/chat/native-root-loop.js +14 -1
  18. package/dist/commands/chat/slash/commands/session.js +19 -1
  19. package/dist/commands/goal-interview.d.ts +18 -0
  20. package/dist/commands/goal-interview.js +396 -0
  21. package/dist/commands/merge.js +102 -56
  22. package/dist/contracts/interview.d.ts +106 -0
  23. package/dist/contracts/interview.js +9 -0
  24. package/dist/contracts/provider-health.d.ts +37 -0
  25. package/dist/contracts/provider-health.js +49 -1
  26. package/dist/evidence/evidence-trust-score.d.ts +101 -0
  27. package/dist/evidence/evidence-trust-score.js +408 -0
  28. package/dist/evidence/index.d.ts +6 -0
  29. package/dist/evidence/index.js +3 -0
  30. package/dist/evidence/proof-trust-cli.d.ts +8 -0
  31. package/dist/evidence/proof-trust-cli.js +27 -0
  32. package/dist/evidence/proof-trust.d.ts +14 -0
  33. package/dist/evidence/proof-trust.js +381 -0
  34. package/dist/evidence/regression-proof-matrix.d.ts +42 -0
  35. package/dist/evidence/regression-proof-matrix.js +72 -0
  36. package/dist/goal/intent-frame.d.ts +6 -0
  37. package/dist/goal/intent-frame.js +21 -9
  38. package/dist/goal/interview-assimilation.d.ts +13 -0
  39. package/dist/goal/interview-assimilation.js +383 -0
  40. package/dist/goal/interview-question-bank.d.ts +11 -0
  41. package/dist/goal/interview-question-bank.js +225 -0
  42. package/dist/goal/interview-scoring.d.ts +31 -0
  43. package/dist/goal/interview-scoring.js +187 -0
  44. package/dist/goal/interview-session.d.ts +25 -0
  45. package/dist/goal/interview-session.js +116 -0
  46. package/dist/input/input-envelope.d.ts +22 -0
  47. package/dist/input/input-envelope.js +1 -0
  48. package/dist/orchestration/merge-arbiter.d.ts +91 -0
  49. package/dist/orchestration/merge-arbiter.js +376 -0
  50. package/dist/providers/health.d.ts +3 -0
  51. package/dist/providers/health.js +46 -0
  52. package/dist/providers/index.d.ts +1 -0
  53. package/dist/providers/index.js +1 -0
  54. package/dist/providers/provider-health.d.ts +8 -1
  55. package/dist/providers/provider-health.js +39 -0
  56. package/dist/providers/provider-task-runner.js +31 -0
  57. package/dist/providers/provider.d.ts +2 -0
  58. package/dist/providers/router.js +87 -3
  59. package/dist/providers/types.d.ts +4 -0
  60. package/dist/runtime/advanced-control-loop.d.ts +60 -0
  61. package/dist/runtime/advanced-control-loop.js +136 -0
  62. package/dist/runtime/agent-runtime.d.ts +10 -0
  63. package/dist/runtime/blast-radius.d.ts +10 -0
  64. package/dist/runtime/blast-radius.js +14 -0
  65. package/dist/runtime/contracts/evidence.d.ts +87 -0
  66. package/dist/runtime/contracts/evidence.js +7 -0
  67. package/dist/runtime/contracts/router-v2.d.ts +44 -0
  68. package/dist/runtime/contracts/router-v2.js +4 -0
  69. package/dist/runtime/contracts/weakness-remediation.d.ts +67 -0
  70. package/dist/runtime/contracts/weakness-remediation.js +36 -0
  71. package/dist/runtime/kimi-api-runtime.js +59 -1
  72. package/dist/runtime/proof-bundle-trust.d.ts +74 -0
  73. package/dist/runtime/proof-bundle-trust.js +100 -0
  74. package/dist/runtime/provider-maturity-gate.d.ts +43 -0
  75. package/dist/runtime/provider-maturity-gate.js +129 -0
  76. package/dist/runtime/public-surface.d.ts +93 -0
  77. package/dist/runtime/public-surface.js +146 -0
  78. package/dist/runtime/router-v2-scoring.d.ts +11 -0
  79. package/dist/runtime/router-v2-scoring.js +151 -0
  80. package/dist/runtime/tool-dispatch-contracts.d.ts +24 -3
  81. package/dist/runtime/tool-dispatch-contracts.js +42 -2
  82. package/dist/runtime/weakness-remediation-index.d.ts +27 -0
  83. package/dist/runtime/weakness-remediation-index.js +37 -0
  84. package/dist/safety/enforcement-engine.d.ts +89 -0
  85. package/dist/safety/enforcement-engine.js +279 -0
  86. package/dist/safety/tool-authority-gate.d.ts +40 -0
  87. package/dist/safety/tool-authority-gate.js +92 -0
  88. package/dist/schema/evidence.schema.d.ts +2 -2
  89. package/dist/schema/proof-bundle.schema.d.ts +28 -28
  90. package/dist/util/clipboard-image.d.ts +49 -0
  91. package/dist/util/clipboard-image.js +263 -0
  92. package/docs/2026-06-09/critical-issues.md +20 -0
  93. package/docs/2026-06-09/improvements.md +14 -0
  94. package/docs/2026-06-09/init-checklist.md +25 -0
  95. package/docs/2026-06-09/plan.md +20 -0
  96. package/docs/benchmark-design.md +122 -0
  97. package/docs/github-organic-promotion.md +127 -0
  98. package/docs/native-root-runtime-algorithms.md +301 -0
  99. package/package.json +8 -4
  100. package/readmeasset/ASSET_INDEX.md +1 -0
  101. package/templates/skills/agents/omk-agent-reach-websearch/SKILL.md +55 -0
  102. package/templates/skills/kimi/omk-agent-reach-websearch/SKILL.md +55 -0
@@ -0,0 +1,376 @@
1
+ /**
2
+ * Merge Arbiter — patch scoring + conflict detection + winner selection.
3
+ *
4
+ * Pipeline:
5
+ * CollectCandidatePatches → NormalizeDiffs → RunEvidenceSuite → ScorePatch
6
+ * → DetectConflicts → SelectWinnerOrHybrid → ProduceMergeRationale
7
+ */
8
+ import { readdir } from "fs/promises";
9
+ import { join } from "path";
10
+ import { runShell } from "../util/shell.js";
11
+ import { runQualityGate } from "../mcp/quality-gate.js";
12
+ // ─── Constants ─────────────────────────────────────────────────────────────
13
+ const DEFAULT_THRESHOLD = 0.6;
14
+ const DEFAULT_MAX_DIFF_LINES = 500;
15
+ const DEFAULT_TEST_TIMEOUT_MS = 120_000;
16
+ const DEFAULT_APPLY_CHECK_TIMEOUT_MS = 15_000;
17
+ const SCORE_WEIGHTS = {
18
+ testPass: 0.35,
19
+ evidenceTrust: 0.25,
20
+ minimality: 0.15,
21
+ lintTypecheck: 0.10,
22
+ conflictFree: 0.10,
23
+ reviewerAgreement: 0.05,
24
+ };
25
+ // ─── Pipeline: CollectCandidatePatches ─────────────────────────────────────
26
+ export async function collectCandidatePatches(worktreesDir, currentBranch, options) {
27
+ const workerNames = await readdir(worktreesDir, { withFileTypes: true }).then((e) => e.filter((d) => d.isDirectory()).map((d) => d.name));
28
+ const candidates = [];
29
+ for (const name of workerNames) {
30
+ const wtPath = join(worktreesDir, name);
31
+ const diffResult = await runShell("git", ["-C", wtPath, "diff", currentBranch], {
32
+ timeout: options?.applyCheckTimeoutMs ?? DEFAULT_APPLY_CHECK_TIMEOUT_MS,
33
+ });
34
+ if (diffResult.failed || !diffResult.stdout.trim())
35
+ continue;
36
+ const diff = diffResult.stdout;
37
+ const normalizedDiff = normalizeDiff(diff);
38
+ const fileScopes = extractFileScopes(normalizedDiff);
39
+ const diffLines = diff.split("\n").length;
40
+ candidates.push({
41
+ id: `candidate-${name}`,
42
+ name,
43
+ path: wtPath,
44
+ diff,
45
+ normalizedDiff,
46
+ fileScopes,
47
+ diffLines,
48
+ canApply: true,
49
+ conflictsWith: [],
50
+ evidence: {
51
+ testsPassed: false,
52
+ lintPassed: false,
53
+ typecheckPassed: false,
54
+ evidenceTrustScore: 0.5,
55
+ },
56
+ scores: {
57
+ testPassScore: 0,
58
+ evidenceTrustScore: 0.5,
59
+ minimalityScore: 0,
60
+ lintTypecheckScore: 0,
61
+ conflictFreeScore: 1,
62
+ reviewerAgreementScore: 0.5,
63
+ },
64
+ compositeScore: 0,
65
+ });
66
+ }
67
+ return candidates;
68
+ }
69
+ // ─── Pipeline: NormalizeDiffs ──────────────────────────────────────────────
70
+ export function normalizeDiff(diff) {
71
+ return diff
72
+ .split("\n")
73
+ .map((line) => {
74
+ // Strip index timestamps: "index 1234..5678 100644" → "index <hash>..<hash> <mode>"
75
+ if (line.startsWith("index ")) {
76
+ const parts = line.split(" ");
77
+ if (parts.length >= 3) {
78
+ return `index <hash>..<hash> ${parts[2]}`;
79
+ }
80
+ }
81
+ // Strip ---/+++ timestamps: "--- a/file\t2024-01-01 00:00:00.000000000 +0000"
82
+ if (line.startsWith("--- ") || line.startsWith("+++ ")) {
83
+ const tabIdx = line.indexOf("\t");
84
+ if (tabIdx > 0) {
85
+ return line.slice(0, tabIdx);
86
+ }
87
+ }
88
+ return line;
89
+ })
90
+ .join("\n");
91
+ }
92
+ // ─── Pipeline: ExtractFileScopes ───────────────────────────────────────────
93
+ export function extractFileScopes(diff) {
94
+ const scopes = new Set();
95
+ const lines = diff.split("\n");
96
+ for (const line of lines) {
97
+ if (line.startsWith("diff --git ")) {
98
+ const match = line.match(/^diff --git a\/(\S+) b\/(\S+)$/);
99
+ if (match) {
100
+ scopes.add(match[1]);
101
+ }
102
+ }
103
+ else if (line.startsWith("--- a/") || line.startsWith("+++ b/")) {
104
+ const prefix = line.startsWith("--- a/") ? "--- a/" : "+++ b/";
105
+ const path = line.slice(prefix.length).split("\t")[0];
106
+ if (path && path !== "/dev/null") {
107
+ scopes.add(path);
108
+ }
109
+ }
110
+ }
111
+ return [...scopes];
112
+ }
113
+ // ─── Pipeline: RunEvidenceSuite ────────────────────────────────────────────
114
+ export async function runEvidenceSuite(candidate, projectRoot, config, options) {
115
+ const testTimeout = options?.testTimeoutMs ?? DEFAULT_TEST_TIMEOUT_MS;
116
+ // 1. git apply --check
117
+ const applyCheck = await runShell("git", ["apply", "--check"], {
118
+ cwd: projectRoot,
119
+ input: candidate.diff,
120
+ timeout: options?.applyCheckTimeoutMs ?? DEFAULT_APPLY_CHECK_TIMEOUT_MS,
121
+ });
122
+ candidate.canApply = !applyCheck.failed;
123
+ // 2. Run tests in worktree
124
+ const testResult = await runShell("sh", ["-c", "npm test 2>/dev/null || pnpm test 2>/dev/null || yarn test 2>/dev/null || true"], { cwd: candidate.path, timeout: testTimeout });
125
+ candidate.evidence.testsPassed = !testResult.failed;
126
+ // 3. Run quality gate (lint + typecheck) in worktree
127
+ const qgResult = await runQualityGate(candidate.path, config);
128
+ candidate.evidence.lintPassed = qgResult.lint.status === "passed" || qgResult.lint.status === "skipped";
129
+ candidate.evidence.typecheckPassed = qgResult.typecheck.status === "passed" || qgResult.typecheck.status === "skipped";
130
+ // 4. Compute evidence trust score from suite results
131
+ let trust = 0.5;
132
+ if (candidate.evidence.testsPassed)
133
+ trust += 0.15;
134
+ if (candidate.evidence.lintPassed)
135
+ trust += 0.10;
136
+ if (candidate.evidence.typecheckPassed)
137
+ trust += 0.10;
138
+ if (candidate.canApply)
139
+ trust += 0.15;
140
+ candidate.evidence.evidenceTrustScore = Math.min(1, trust);
141
+ return candidate;
142
+ }
143
+ // ─── Pipeline: ScorePatch ──────────────────────────────────────────────────
144
+ export function scorePatch(candidate, options) {
145
+ const maxLines = options?.maxDiffLines ?? DEFAULT_MAX_DIFF_LINES;
146
+ const testPassScore = candidate.evidence.testsPassed ? 1 : 0;
147
+ const evidenceTrustScore = clamp01(candidate.evidence.evidenceTrustScore);
148
+ const minimalityScore = clamp01(1 - candidate.diffLines / maxLines);
149
+ const lintTypecheckScore = (candidate.evidence.lintPassed ? 0.5 : 0) +
150
+ (candidate.evidence.typecheckPassed ? 0.5 : 0);
151
+ const conflictFreeScore = candidate.canApply && candidate.conflictsWith.length === 0 ? 1 : 0;
152
+ const reviewerAgreementScore = candidate.evidence.reviewerScore !== undefined
153
+ ? clamp01(candidate.evidence.reviewerScore / 100)
154
+ : 0.5;
155
+ const composite = SCORE_WEIGHTS.testPass * testPassScore +
156
+ SCORE_WEIGHTS.evidenceTrust * evidenceTrustScore +
157
+ SCORE_WEIGHTS.minimality * minimalityScore +
158
+ SCORE_WEIGHTS.lintTypecheck * lintTypecheckScore +
159
+ SCORE_WEIGHTS.conflictFree * conflictFreeScore +
160
+ SCORE_WEIGHTS.reviewerAgreement * reviewerAgreementScore;
161
+ candidate.scores = {
162
+ testPassScore,
163
+ evidenceTrustScore,
164
+ minimalityScore,
165
+ lintTypecheckScore,
166
+ conflictFreeScore,
167
+ reviewerAgreementScore,
168
+ };
169
+ candidate.compositeScore = Math.round(clamp01(composite) * 1000) / 1000;
170
+ return candidate;
171
+ }
172
+ // ─── Pipeline: DetectConflicts ─────────────────────────────────────────────
173
+ export function detectConflicts(candidates) {
174
+ // Reset conflict state
175
+ for (const c of candidates) {
176
+ c.conflictsWith = [];
177
+ }
178
+ const scopeMap = new Map();
179
+ for (const c of candidates) {
180
+ for (const scope of c.fileScopes) {
181
+ const list = scopeMap.get(scope) ?? [];
182
+ list.push(c.id);
183
+ scopeMap.set(scope, list);
184
+ }
185
+ }
186
+ for (const c of candidates) {
187
+ const conflicting = new Set();
188
+ for (const scope of c.fileScopes) {
189
+ const owners = scopeMap.get(scope) ?? [];
190
+ for (const ownerId of owners) {
191
+ if (ownerId !== c.id) {
192
+ conflicting.add(ownerId);
193
+ }
194
+ }
195
+ }
196
+ c.conflictsWith = [...conflicting];
197
+ if (c.conflictsWith.length > 0) {
198
+ c.scores.conflictFreeScore = 0;
199
+ c.compositeScore = recalcComposite(c.scores);
200
+ }
201
+ }
202
+ return candidates;
203
+ }
204
+ function recalcComposite(scores) {
205
+ return Math.round(clamp01(SCORE_WEIGHTS.testPass * scores.testPassScore +
206
+ SCORE_WEIGHTS.evidenceTrust * scores.evidenceTrustScore +
207
+ SCORE_WEIGHTS.minimality * scores.minimalityScore +
208
+ SCORE_WEIGHTS.lintTypecheck * scores.lintTypecheckScore +
209
+ SCORE_WEIGHTS.conflictFree * scores.conflictFreeScore +
210
+ SCORE_WEIGHTS.reviewerAgreement * scores.reviewerAgreementScore) * 1000) / 1000;
211
+ }
212
+ // ─── Pipeline: SelectWinnerOrHybrid ────────────────────────────────────────
213
+ export function selectWinnerOrHybrid(candidates, options) {
214
+ const threshold = options?.threshold ?? DEFAULT_THRESHOLD;
215
+ if (candidates.length === 0) {
216
+ return { winner: null, requiresHumanApproval: true, reason: "No candidates available." };
217
+ }
218
+ // Sort by composite score descending
219
+ const sorted = [...candidates].sort((a, b) => b.compositeScore - a.compositeScore);
220
+ const best = sorted[0];
221
+ if (best.compositeScore < threshold) {
222
+ return {
223
+ winner: null,
224
+ requiresHumanApproval: true,
225
+ reason: `Best candidate "${best.name}" score ${best.compositeScore} below threshold ${threshold}.`,
226
+ };
227
+ }
228
+ // If top candidate has conflicts, check whether a conflict-free candidate
229
+ // meets the threshold; otherwise require human approval.
230
+ if (best.conflictsWith.length > 0) {
231
+ const cleanWinner = sorted.find((c) => c.compositeScore >= threshold && c.conflictsWith.length === 0);
232
+ if (cleanWinner) {
233
+ return { winner: cleanWinner, requiresHumanApproval: false };
234
+ }
235
+ return {
236
+ winner: null,
237
+ requiresHumanApproval: true,
238
+ reason: `Best candidate "${best.name}" has scope conflicts and no clean alternative meets threshold ${threshold}.`,
239
+ };
240
+ }
241
+ return { winner: best, requiresHumanApproval: false };
242
+ }
243
+ // ─── Pipeline: ProduceMergeRationale ───────────────────────────────────────
244
+ export function produceMergeRationale(candidates, selection, options) {
245
+ const threshold = options?.threshold ?? DEFAULT_THRESHOLD;
246
+ const winner = selection.winner;
247
+ const scoreBreakdown = {};
248
+ if (winner) {
249
+ scoreBreakdown[winner.name] = winner.compositeScore;
250
+ }
251
+ for (const c of candidates) {
252
+ if (c.id !== winner?.id) {
253
+ scoreBreakdown[c.name] = c.compositeScore;
254
+ }
255
+ }
256
+ const conflictPairs = new Set();
257
+ for (const c of candidates) {
258
+ for (const otherId of c.conflictsWith) {
259
+ const other = candidates.find((x) => x.id === otherId);
260
+ const pair = [c.name, other?.name ?? otherId].sort().join(" ↔ ");
261
+ conflictPairs.add(pair);
262
+ }
263
+ }
264
+ const uniqueConflicts = [...conflictPairs];
265
+ const summary = winner
266
+ ? `Selected "${winner.name}" with score ${winner.compositeScore} (threshold ${threshold}).`
267
+ : `No winner selected. ${selection.reason ?? ""}`;
268
+ const rationale = {
269
+ summary,
270
+ winnerId: winner?.id ?? null,
271
+ scoreBreakdown,
272
+ conflicts: uniqueConflicts,
273
+ threshold,
274
+ ...(selection.requiresHumanApproval && selection.reason
275
+ ? { humanApprovalReason: selection.reason }
276
+ : {}),
277
+ };
278
+ const trace = {
279
+ timestamp: new Date().toISOString(),
280
+ steps: candidates.map((c) => ({
281
+ step: "score",
282
+ candidateId: c.id,
283
+ detail: `${c.name}: composite=${c.compositeScore}, tests=${c.evidence.testsPassed}, lint=${c.evidence.lintPassed}, typecheck=${c.evidence.typecheckPassed}, conflicts=${c.conflictsWith.length}`,
284
+ })),
285
+ };
286
+ if (winner) {
287
+ trace.steps.push({
288
+ step: "select-winner",
289
+ candidateId: winner.id,
290
+ detail: `Winner "${winner.name}" selected with score ${winner.compositeScore}`,
291
+ });
292
+ }
293
+ else {
294
+ trace.steps.push({
295
+ step: "require-human-approval",
296
+ candidateId: "none",
297
+ detail: selection.reason ?? "Human approval required.",
298
+ });
299
+ }
300
+ return { rationale, trace };
301
+ }
302
+ // ─── Orchestrator: runMergeArbiter ─────────────────────────────────────────
303
+ export async function runMergeArbiter(worktreesDir, currentBranch, projectRoot, config, options) {
304
+ const traceSteps = [];
305
+ // 1. Collect
306
+ const collectStart = Date.now();
307
+ let candidates = await collectCandidatePatches(worktreesDir, currentBranch, options);
308
+ traceSteps.push({
309
+ step: "collect",
310
+ candidateId: "all",
311
+ detail: `Collected ${candidates.length} candidate patches`,
312
+ durationMs: Date.now() - collectStart,
313
+ });
314
+ if (candidates.length === 0) {
315
+ const selection = selectWinnerOrHybrid(candidates, options);
316
+ const { rationale, trace } = produceMergeRationale(candidates, selection, options);
317
+ return {
318
+ winner: null,
319
+ requiresHumanApproval: true,
320
+ rationale,
321
+ trace: { timestamp: new Date().toISOString(), steps: [...traceSteps, ...trace.steps] },
322
+ };
323
+ }
324
+ // 2. Normalize (already done during collect)
325
+ traceSteps.push({
326
+ step: "normalize",
327
+ candidateId: "all",
328
+ detail: "Diffs normalized (timestamps stripped, paths cleaned)",
329
+ });
330
+ // 3. Detect conflicts (file-scope overlap)
331
+ const conflictStart = Date.now();
332
+ candidates = detectConflicts(candidates);
333
+ traceSteps.push({
334
+ step: "detect-conflicts",
335
+ candidateId: "all",
336
+ detail: `File-scope overlap analyzed for ${candidates.length} candidates`,
337
+ durationMs: Date.now() - conflictStart,
338
+ });
339
+ // 4. Run evidence suite per candidate
340
+ for (const c of candidates) {
341
+ const evStart = Date.now();
342
+ await runEvidenceSuite(c, projectRoot, config, options);
343
+ traceSteps.push({
344
+ step: "evidence-suite",
345
+ candidateId: c.id,
346
+ detail: `apply=${c.canApply}, tests=${c.evidence.testsPassed}, lint=${c.evidence.lintPassed}, typecheck=${c.evidence.typecheckPassed}`,
347
+ durationMs: Date.now() - evStart,
348
+ });
349
+ }
350
+ // 5. Score each candidate
351
+ for (const c of candidates) {
352
+ scorePatch(c, options);
353
+ traceSteps.push({
354
+ step: "score",
355
+ candidateId: c.id,
356
+ detail: `composite=${c.compositeScore}`,
357
+ });
358
+ }
359
+ // 6. Select winner
360
+ const selection = selectWinnerOrHybrid(candidates, options);
361
+ // 7. Produce rationale
362
+ const { rationale, trace } = produceMergeRationale(candidates, selection, options);
363
+ return {
364
+ winner: selection.winner,
365
+ requiresHumanApproval: selection.requiresHumanApproval,
366
+ rationale,
367
+ trace: {
368
+ timestamp: new Date().toISOString(),
369
+ steps: [...traceSteps, ...trace.steps],
370
+ },
371
+ };
372
+ }
373
+ // ─── Helpers ───────────────────────────────────────────────────────────────
374
+ function clamp01(n) {
375
+ return Math.max(0, Math.min(1, n));
376
+ }
@@ -1,12 +1,15 @@
1
1
  import type { ProviderAvailability } from "./types.js";
2
+ import type { ProviderHealthVector } from "../contracts/provider-health.js";
2
3
  export declare class ProviderHealthRegistry {
3
4
  private kimi;
4
5
  private deepseek?;
5
6
  getKimi(): ProviderAvailability;
7
+ getKimiVector(): ProviderHealthVector | undefined;
6
8
  isKimiAvailable(): boolean;
7
9
  markKimiUnavailable(reason: string): void;
8
10
  markKimiAvailable(): void;
9
11
  getDeepSeek(): ProviderAvailability | undefined;
12
+ getDeepSeekVector(): ProviderHealthVector | undefined;
10
13
  isDeepSeekAvailable(): boolean;
11
14
  markDeepSeekUnavailable(reason: string): void;
12
15
  markDeepSeekAvailable(): void;
@@ -1,14 +1,53 @@
1
+ function makeAvailableVector(provider) {
2
+ return {
3
+ provider,
4
+ binary: "ready",
5
+ auth: "ready",
6
+ model: "ready",
7
+ quota: "ready",
8
+ latencyP50Ms: 0,
9
+ latencyP95Ms: 0,
10
+ supportsRead: true,
11
+ supportsWrite: true,
12
+ supportsShell: true,
13
+ supportsSandbox: true,
14
+ evidencePassRate7d: 1.0,
15
+ failureEwma: 0,
16
+ };
17
+ }
18
+ function makeUnavailableVector(provider, reason) {
19
+ const failureEwma = reason?.includes("402") || reason?.includes("quota") ? 0.8 : 0.5;
20
+ return {
21
+ provider,
22
+ binary: "missing",
23
+ auth: "missing",
24
+ model: "missing",
25
+ quota: "missing",
26
+ latencyP50Ms: 0,
27
+ latencyP95Ms: 0,
28
+ supportsRead: false,
29
+ supportsWrite: false,
30
+ supportsShell: false,
31
+ supportsSandbox: false,
32
+ evidencePassRate7d: 0.5,
33
+ failureEwma,
34
+ };
35
+ }
1
36
  export class ProviderHealthRegistry {
2
37
  kimi = {
3
38
  provider: "kimi",
4
39
  available: true,
5
40
  checkedAt: Date.now(),
6
41
  disableForRun: false,
42
+ healthVector: makeAvailableVector("kimi"),
7
43
  };
8
44
  deepseek;
9
45
  getKimi() {
10
46
  return this.kimi;
11
47
  }
48
+ getKimiVector() {
49
+ return this.kimi.healthVector;
50
+ }
12
51
  isKimiAvailable() {
13
52
  return this.kimi.available !== false && this.kimi.disableForRun !== true;
14
53
  }
@@ -19,6 +58,7 @@ export class ProviderHealthRegistry {
19
58
  checkedAt: Date.now(),
20
59
  reason,
21
60
  disableForRun: true,
61
+ healthVector: makeUnavailableVector("kimi", reason),
22
62
  };
23
63
  }
24
64
  markKimiAvailable() {
@@ -27,11 +67,15 @@ export class ProviderHealthRegistry {
27
67
  available: true,
28
68
  checkedAt: Date.now(),
29
69
  disableForRun: false,
70
+ healthVector: makeAvailableVector("kimi"),
30
71
  };
31
72
  }
32
73
  getDeepSeek() {
33
74
  return this.deepseek;
34
75
  }
76
+ getDeepSeekVector() {
77
+ return this.deepseek?.healthVector;
78
+ }
35
79
  isDeepSeekAvailable() {
36
80
  return this.deepseek?.available !== false && this.deepseek?.disableForRun !== true;
37
81
  }
@@ -42,6 +86,7 @@ export class ProviderHealthRegistry {
42
86
  checkedAt: Date.now(),
43
87
  reason,
44
88
  disableForRun: true,
89
+ healthVector: makeUnavailableVector("deepseek", reason),
45
90
  };
46
91
  }
47
92
  markDeepSeekAvailable() {
@@ -50,6 +95,7 @@ export class ProviderHealthRegistry {
50
95
  available: true,
51
96
  checkedAt: Date.now(),
52
97
  disableForRun: false,
98
+ healthVector: makeAvailableVector("deepseek"),
53
99
  };
54
100
  }
55
101
  }
@@ -14,6 +14,7 @@ export * from "./provider-stats.js";
14
14
  export * from "./openai-compatible-runner.js";
15
15
  export * from "./codex-cli-runner.js";
16
16
  export * from "./context-preflight.js";
17
+ export { toProviderHealth, toProviderHealthVector } from "./provider-health.js";
17
18
  export { type AgentRunInput, type AgentRunResult, type CostEstimate, type ProviderHealth, type ProviderRouteStrategy, type ProviderAttemptRecord, toTaskResult, } from "./provider.js";
18
19
  export type { AgentProvider as NewAgentProvider } from "./provider.js";
19
20
  export { createKimiProvider } from "./kimi-provider.js";
@@ -14,6 +14,7 @@ export * from "./provider-stats.js";
14
14
  export * from "./openai-compatible-runner.js";
15
15
  export * from "./codex-cli-runner.js";
16
16
  export * from "./context-preflight.js";
17
+ export { toProviderHealth, toProviderHealthVector } from "./provider-health.js";
17
18
  // New provider system (provider.ts) — explicit exports to avoid conflicts with types.ts
18
19
  export { toTaskResult, } from "./provider.js";
19
20
  export { createKimiProvider } from "./kimi-provider.js";
@@ -7,7 +7,7 @@
7
7
  * be unit-tested in isolation. The mapper never surfaces secret values — it
8
8
  * relies on boolean signals (e.g. `apiKeySet`) and environment-variable *names*.
9
9
  */
10
- import type { ProviderHealth } from "../contracts/provider-health.js";
10
+ import type { ProviderHealth, ProviderHealthVector } from "../contracts/provider-health.js";
11
11
  import type { ProviderDoctorStatus } from "./model-registry.js";
12
12
  /** DeepSeek `provider doctor` JSON object shape (balance preflight + config). */
13
13
  export interface DeepSeekDoctorHealthInput {
@@ -30,6 +30,13 @@ export interface ProviderHealthExtras {
30
30
  /** ISO timestamp override (mainly for deterministic tests). */
31
31
  checkedAt?: string;
32
32
  }
33
+ /**
34
+ * Maps a provider doctor payload onto a v2 {@link ProviderHealthVector}.
35
+ *
36
+ * @param status A {@link ProviderDoctorStatus} or DeepSeek doctor object.
37
+ * @param extras Optional non-sensitive context overrides.
38
+ */
39
+ export declare function toProviderHealthVector(status: ProviderHealthInput, extras?: ProviderHealthExtras): ProviderHealthVector;
33
40
  /**
34
41
  * Maps a provider doctor payload onto the shared {@link ProviderHealth} shape.
35
42
  *
@@ -150,6 +150,45 @@ function fromDeepSeekDoctor(input, extras) {
150
150
  remediation,
151
151
  };
152
152
  }
153
+ function toProviderHealthVectorFromHealth(health) {
154
+ const binary = health.runtimeOk ? "ready" : "missing";
155
+ const auth = health.authOk
156
+ ? "ready"
157
+ : health.failureKind === "auth"
158
+ ? "auth_present"
159
+ : "missing";
160
+ const model = health.modelOk ? "ready" : "missing";
161
+ const quota = health.quotaOk
162
+ ? "ready"
163
+ : health.failureKind === "quota"
164
+ ? "auth_valid"
165
+ : "missing";
166
+ return {
167
+ provider: health.provider,
168
+ binary,
169
+ auth,
170
+ model,
171
+ quota,
172
+ latencyP50Ms: 0,
173
+ latencyP95Ms: 0,
174
+ supportsRead: true,
175
+ supportsWrite: health.writeAuthority !== "none" && health.writeAuthority !== "advisory",
176
+ supportsShell: health.shellAuthority !== "none",
177
+ supportsSandbox: health.shellAuthority !== "none",
178
+ evidencePassRate7d: health.failureKind === "none" ? 1.0 : 0.5,
179
+ failureEwma: health.failureKind === "none" ? 0 : 0.5,
180
+ };
181
+ }
182
+ /**
183
+ * Maps a provider doctor payload onto a v2 {@link ProviderHealthVector}.
184
+ *
185
+ * @param status A {@link ProviderDoctorStatus} or DeepSeek doctor object.
186
+ * @param extras Optional non-sensitive context overrides.
187
+ */
188
+ export function toProviderHealthVector(status, extras) {
189
+ const health = toProviderHealth(status, extras);
190
+ return toProviderHealthVectorFromHealth(health);
191
+ }
153
192
  /**
154
193
  * Maps a provider doctor payload onto the shared {@link ProviderHealth} shape.
155
194
  *
@@ -113,6 +113,7 @@ export function createProviderTaskRunner(options) {
113
113
  const requiresToolCalling = node.routing?.requiresToolCalling === true;
114
114
  const requiresMcp = node.routing?.requiresMcp === true;
115
115
  const providerAvailability = providerAvailabilityForNode(node, options.providerPolicy ?? "auto", deepseekAvailable, providerRunners);
116
+ const providerHealthVectors = providerHealthVectorsFromRegistry(providerHealth, providerRunners);
116
117
  const routeInput = {
117
118
  role: node.role,
118
119
  taskType: env.OMK_TASK_TYPE ?? "general",
@@ -124,6 +125,7 @@ export function createProviderTaskRunner(options) {
124
125
  estimatedTokens: Number(env.OMK_ESTIMATED_TOKENS ?? 0),
125
126
  deepseekAvailable,
126
127
  providerAvailability,
128
+ providerHealthVectors,
127
129
  providerModels: options.providerModels,
128
130
  nodeId: node.id,
129
131
  providerHint: node.routing?.provider,
@@ -727,6 +729,35 @@ function providerAvailabilityForNode(node, providerPolicy, deepseekAvailable, pr
727
729
  }
728
730
  return availability;
729
731
  }
732
+ function providerHealthVectorsFromRegistry(providerHealth, providerRunners) {
733
+ const vectors = {};
734
+ const kimiVector = providerHealth.getKimiVector();
735
+ if (kimiVector)
736
+ vectors.kimi = kimiVector;
737
+ const deepseekVector = providerHealth.getDeepSeekVector();
738
+ if (deepseekVector)
739
+ vectors.deepseek = deepseekVector;
740
+ for (const provider of Object.keys(providerRunners ?? {})) {
741
+ if (vectors[provider])
742
+ continue;
743
+ vectors[provider] = {
744
+ provider,
745
+ binary: "ready",
746
+ auth: "ready",
747
+ model: "ready",
748
+ quota: "ready",
749
+ latencyP50Ms: 0,
750
+ latencyP95Ms: 0,
751
+ supportsRead: true,
752
+ supportsWrite: true,
753
+ supportsShell: false,
754
+ supportsSandbox: false,
755
+ evidencePassRate7d: 1.0,
756
+ failureEwma: 0,
757
+ };
758
+ }
759
+ return vectors;
760
+ }
730
761
  function resolveUnavailableSkippableProviderLane(options) {
731
762
  const explicitProvider = requestedProviderFromNodeOrPolicy(options.node, options.providerPolicy);
732
763
  if (explicitProvider &&
@@ -28,6 +28,8 @@ export interface ProviderHealth {
28
28
  latencyMs?: number;
29
29
  lastCheckedAt: number;
30
30
  reason?: string;
31
+ /** v2 capability vector (optional; present when profiler v2 is active). */
32
+ vector?: import("../contracts/provider-health.js").ProviderHealthVector;
31
33
  }
32
34
  export interface AgentProvider {
33
35
  readonly id: ProviderId | string;