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.
- package/CHANGELOG.md +37 -0
- package/MATURITY.md +4 -0
- package/README.md +70 -1
- package/dist/benchmark/contracts.d.ts +116 -0
- package/dist/benchmark/contracts.js +6 -0
- package/dist/benchmark/fixtures.d.ts +11 -0
- package/dist/benchmark/fixtures.js +121 -0
- package/dist/benchmark/harness.d.ts +13 -0
- package/dist/benchmark/harness.js +191 -0
- package/dist/benchmark/shadow-mode.d.ts +17 -0
- package/dist/benchmark/shadow-mode.js +96 -0
- package/dist/cli/register-spec-agent-goal-commands.js +45 -0
- package/dist/cli/release-promotion-gate.d.ts +14 -0
- package/dist/cli/release-promotion-gate.js +71 -0
- package/dist/cli/v2/release-commands.d.ts +29 -0
- package/dist/cli/v2/release-commands.js +95 -0
- package/dist/commands/chat/native-root-loop.js +14 -1
- package/dist/commands/chat/slash/commands/session.js +19 -1
- package/dist/commands/goal-interview.d.ts +18 -0
- package/dist/commands/goal-interview.js +396 -0
- package/dist/commands/merge.js +102 -56
- package/dist/contracts/interview.d.ts +106 -0
- package/dist/contracts/interview.js +9 -0
- package/dist/contracts/provider-health.d.ts +37 -0
- package/dist/contracts/provider-health.js +49 -1
- package/dist/evidence/evidence-trust-score.d.ts +101 -0
- package/dist/evidence/evidence-trust-score.js +408 -0
- package/dist/evidence/index.d.ts +6 -0
- package/dist/evidence/index.js +3 -0
- package/dist/evidence/proof-trust-cli.d.ts +8 -0
- package/dist/evidence/proof-trust-cli.js +27 -0
- package/dist/evidence/proof-trust.d.ts +14 -0
- package/dist/evidence/proof-trust.js +381 -0
- package/dist/evidence/regression-proof-matrix.d.ts +42 -0
- package/dist/evidence/regression-proof-matrix.js +72 -0
- package/dist/goal/intent-frame.d.ts +6 -0
- package/dist/goal/intent-frame.js +21 -9
- package/dist/goal/interview-assimilation.d.ts +13 -0
- package/dist/goal/interview-assimilation.js +383 -0
- package/dist/goal/interview-question-bank.d.ts +11 -0
- package/dist/goal/interview-question-bank.js +225 -0
- package/dist/goal/interview-scoring.d.ts +31 -0
- package/dist/goal/interview-scoring.js +187 -0
- package/dist/goal/interview-session.d.ts +25 -0
- package/dist/goal/interview-session.js +116 -0
- package/dist/input/input-envelope.d.ts +22 -0
- package/dist/input/input-envelope.js +1 -0
- package/dist/orchestration/merge-arbiter.d.ts +91 -0
- package/dist/orchestration/merge-arbiter.js +376 -0
- package/dist/providers/health.d.ts +3 -0
- package/dist/providers/health.js +46 -0
- package/dist/providers/index.d.ts +1 -0
- package/dist/providers/index.js +1 -0
- package/dist/providers/provider-health.d.ts +8 -1
- package/dist/providers/provider-health.js +39 -0
- package/dist/providers/provider-task-runner.js +31 -0
- package/dist/providers/provider.d.ts +2 -0
- package/dist/providers/router.js +87 -3
- package/dist/providers/types.d.ts +4 -0
- package/dist/runtime/advanced-control-loop.d.ts +60 -0
- package/dist/runtime/advanced-control-loop.js +136 -0
- package/dist/runtime/agent-runtime.d.ts +10 -0
- package/dist/runtime/blast-radius.d.ts +10 -0
- package/dist/runtime/blast-radius.js +14 -0
- package/dist/runtime/contracts/evidence.d.ts +87 -0
- package/dist/runtime/contracts/evidence.js +7 -0
- package/dist/runtime/contracts/router-v2.d.ts +44 -0
- package/dist/runtime/contracts/router-v2.js +4 -0
- package/dist/runtime/contracts/weakness-remediation.d.ts +67 -0
- package/dist/runtime/contracts/weakness-remediation.js +36 -0
- package/dist/runtime/kimi-api-runtime.js +59 -1
- package/dist/runtime/proof-bundle-trust.d.ts +74 -0
- package/dist/runtime/proof-bundle-trust.js +100 -0
- package/dist/runtime/provider-maturity-gate.d.ts +43 -0
- package/dist/runtime/provider-maturity-gate.js +129 -0
- package/dist/runtime/public-surface.d.ts +93 -0
- package/dist/runtime/public-surface.js +146 -0
- package/dist/runtime/router-v2-scoring.d.ts +11 -0
- package/dist/runtime/router-v2-scoring.js +151 -0
- package/dist/runtime/tool-dispatch-contracts.d.ts +24 -3
- package/dist/runtime/tool-dispatch-contracts.js +42 -2
- package/dist/runtime/weakness-remediation-index.d.ts +27 -0
- package/dist/runtime/weakness-remediation-index.js +37 -0
- package/dist/safety/enforcement-engine.d.ts +89 -0
- package/dist/safety/enforcement-engine.js +279 -0
- package/dist/safety/tool-authority-gate.d.ts +40 -0
- package/dist/safety/tool-authority-gate.js +92 -0
- package/dist/schema/evidence.schema.d.ts +2 -2
- package/dist/schema/proof-bundle.schema.d.ts +28 -28
- package/dist/util/clipboard-image.d.ts +49 -0
- package/dist/util/clipboard-image.js +263 -0
- package/docs/2026-06-09/critical-issues.md +20 -0
- package/docs/2026-06-09/improvements.md +14 -0
- package/docs/2026-06-09/init-checklist.md +25 -0
- package/docs/2026-06-09/plan.md +20 -0
- package/docs/benchmark-design.md +122 -0
- package/docs/github-organic-promotion.md +127 -0
- package/docs/native-root-runtime-algorithms.md +301 -0
- package/package.json +8 -4
- package/readmeasset/ASSET_INDEX.md +1 -0
- package/templates/skills/agents/omk-agent-reach-websearch/SKILL.md +55 -0
- 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;
|
package/dist/providers/health.js
CHANGED
|
@@ -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";
|
package/dist/providers/index.js
CHANGED
|
@@ -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;
|