nexus-agents 8.85.3 → 8.86.0
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/dist/{ast-rule-runner-6XKCMMSK.js → ast-rule-runner-USLLC3XL.js} +4 -4
- package/dist/{child-mcp-config-DMTIHAH5.js → child-mcp-config-K563PP6S.js} +3 -3
- package/dist/{chunk-KVDXUGMQ.js → chunk-2SQO4FET.js} +2 -2
- package/dist/{chunk-O6TFBXE5.js → chunk-4Q753QZM.js} +3 -3
- package/dist/{chunk-OHM625YL.js → chunk-5U4XTYAB.js} +2 -2
- package/dist/{chunk-OMIR6ISN.js → chunk-64IJKT4I.js} +469 -348
- package/dist/chunk-64IJKT4I.js.map +1 -0
- package/dist/{chunk-5JQL2KOJ.js → chunk-C4YTAB6K.js} +43 -22
- package/dist/chunk-C4YTAB6K.js.map +1 -0
- package/dist/{chunk-PFZSS67U.js → chunk-CBZAMHBA.js} +2 -2
- package/dist/{chunk-6ZCKENQU.js → chunk-DSVMQVJZ.js} +4 -4
- package/dist/{chunk-LKNT7XND.js → chunk-EVYZZL7J.js} +2 -2
- package/dist/{chunk-DSIJ5L3Y.js → chunk-FNL4J77P.js} +2 -2
- package/dist/{chunk-LJNUMK67.js → chunk-GKLTEUFU.js} +2 -2
- package/dist/{chunk-HXSR24QG.js → chunk-IXKFIO6O.js} +2 -2
- package/dist/{chunk-ABIS6ZE7.js → chunk-J6GIJFV4.js} +2 -2
- package/dist/{chunk-UWAJUJKU.js → chunk-KX7A23JM.js} +2 -2
- package/dist/{chunk-LBIFDT6N.js → chunk-KXFPJOPL.js} +2 -2
- package/dist/{chunk-UXUOJOXL.js → chunk-NNYVXSQF.js} +2 -2
- package/dist/{chunk-DMUVBBOD.js → chunk-NQA77WXP.js} +3 -3
- package/dist/{chunk-CPW3LHST.js → chunk-O24ENDHN.js} +3 -3
- package/dist/{chunk-QFL3BFIV.js → chunk-OOIZ3IQD.js} +2 -2
- package/dist/{chunk-3HU4VVD7.js → chunk-P6D7S7CY.js} +2 -2
- package/dist/{chunk-RIB56PWI.js → chunk-Q75P5CVJ.js} +2 -2
- package/dist/{chunk-T2ZQUZQW.js → chunk-QCDYJYGB.js} +7 -7
- package/dist/{chunk-GNTWB3O2.js → chunk-QW6J2UV4.js} +43 -9
- package/dist/chunk-QW6J2UV4.js.map +1 -0
- package/dist/{chunk-4UHDDCRB.js → chunk-RQLIPUQK.js} +5 -1
- package/dist/{chunk-4UHDDCRB.js.map → chunk-RQLIPUQK.js.map} +1 -1
- package/dist/{chunk-KUZV4AW5.js → chunk-S6MNVNQB.js} +2 -2
- package/dist/{chunk-TECOLJ4Z.js → chunk-SQ4M3JHW.js} +41 -39
- package/dist/chunk-SQ4M3JHW.js.map +1 -0
- package/dist/{chunk-FQ6UCX2J.js → chunk-V66MMBO4.js} +5 -5
- package/dist/{chunk-AHJUIF44.js → chunk-XARB7ITD.js} +8 -8
- package/dist/{chunk-HQQ3ES3J.js → chunk-XNNNDQLK.js} +2 -2
- package/dist/{chunk-XGAMDSC5.js → chunk-XWPZZEBX.js} +4 -4
- package/dist/{chunk-VEMKYAW4.js → chunk-YG73EH43.js} +2 -2
- package/dist/{chunk-ALJPZZ57.js → chunk-YPJRVA2H.js} +4 -4
- package/dist/{chunk-QW343BTQ.js → chunk-ZE73MT66.js} +4 -4
- package/dist/{chunk-AOSBH5H7.js → chunk-ZEOHUE2N.js} +10 -10
- package/dist/{chunk-CJ2LQLZV.js → chunk-ZSFRB2S4.js} +3 -3
- package/dist/{cli-circuit-breaker-VRTWILPB.js → cli-circuit-breaker-4ASNTBDP.js} +4 -4
- package/dist/cli.js +43 -43
- package/dist/{composite-router-LAMVSGZ3.js → composite-router-V45AK6WD.js} +3 -3
- package/dist/{consensus-vote-ZB72SHTV.js → consensus-vote-N2Z7FX3D.js} +17 -17
- package/dist/{context-retriever-VRAIKZTE.js → context-retriever-NZOSGG6S.js} +9 -9
- package/dist/{doctor-deep-ZZWIF64Z.js → doctor-deep-JRY3D7BP.js} +4 -4
- package/dist/{doctor-live-KKIWEO3K.js → doctor-live-NSRHMHMG.js} +11 -11
- package/dist/{expert-bridge-TMHBUOUK.js → expert-bridge-Q4SEPVTE.js} +5 -5
- package/dist/factory-JBK45P7G.js +15 -0
- package/dist/{factory-KAMTEOXC.js → factory-ZV4MR3VM.js} +10 -10
- package/dist/{improvement-review-BI3B3PQ2.js → improvement-review-KHTIDV4R.js} +6 -6
- package/dist/index.d.ts +52 -2
- package/dist/index.js +31 -31
- package/dist/{init-opencode-EY7TUIML.js → init-opencode-6WGN5J6J.js} +6 -6
- package/dist/issue-triage-XVDXOZRW.js +16 -0
- package/dist/{learning-persistence-7BEPQEGN.js → learning-persistence-ER7E5Q6T.js} +6 -4
- package/dist/{pr-reviewer-helpers-3A6ZFECZ.js → pr-reviewer-helpers-UMNMOUAO.js} +5 -5
- package/dist/{registry-command-PWWFOIT3.js → registry-command-QRBAKWRK.js} +3 -3
- package/dist/{repo-security-plan-Y6PXBY44.js → repo-security-plan-UM6WX5PN.js} +4 -4
- package/dist/research-helpers-synthesize-DUOVXI5V.js +14 -0
- package/dist/{routing-memory-XFJJT5AW.js → routing-memory-OKYSDPXT.js} +3 -3
- package/dist/{session-memory-SRH5ZJRX.js → session-memory-ONUSRMYV.js} +4 -4
- package/dist/setup-config-BAZRBFN3.js +11 -0
- package/dist/{setup-custom-api-RQW6VSLO.js → setup-custom-api-LSMLWGKW.js} +5 -5
- package/dist/setup-data-dir-7PDX3R3Y.js +32 -0
- package/dist/{tool-memory-72T6Z6H4.js → tool-memory-Q5UWNVN3.js} +6 -6
- package/dist/{unified-registry-J2PCYXUU.js → unified-registry-Y327PN6R.js} +11 -11
- package/dist/{weather-report-GPYYWU5C.js → weather-report-C2VIAC2Q.js} +3 -3
- package/package.json +1 -1
- package/dist/chunk-5JQL2KOJ.js.map +0 -1
- package/dist/chunk-GNTWB3O2.js.map +0 -1
- package/dist/chunk-OMIR6ISN.js.map +0 -1
- package/dist/chunk-TECOLJ4Z.js.map +0 -1
- package/dist/factory-WAI626V6.js +0 -15
- package/dist/issue-triage-VAWDVGET.js +0 -16
- package/dist/research-helpers-synthesize-L22FP57N.js +0 -14
- package/dist/setup-config-T64H4UOO.js +0 -11
- package/dist/setup-data-dir-ZD4N4CEP.js +0 -32
- /package/dist/{ast-rule-runner-6XKCMMSK.js.map → ast-rule-runner-USLLC3XL.js.map} +0 -0
- /package/dist/{child-mcp-config-DMTIHAH5.js.map → child-mcp-config-K563PP6S.js.map} +0 -0
- /package/dist/{chunk-KVDXUGMQ.js.map → chunk-2SQO4FET.js.map} +0 -0
- /package/dist/{chunk-O6TFBXE5.js.map → chunk-4Q753QZM.js.map} +0 -0
- /package/dist/{chunk-OHM625YL.js.map → chunk-5U4XTYAB.js.map} +0 -0
- /package/dist/{chunk-PFZSS67U.js.map → chunk-CBZAMHBA.js.map} +0 -0
- /package/dist/{chunk-6ZCKENQU.js.map → chunk-DSVMQVJZ.js.map} +0 -0
- /package/dist/{chunk-LKNT7XND.js.map → chunk-EVYZZL7J.js.map} +0 -0
- /package/dist/{chunk-DSIJ5L3Y.js.map → chunk-FNL4J77P.js.map} +0 -0
- /package/dist/{chunk-LJNUMK67.js.map → chunk-GKLTEUFU.js.map} +0 -0
- /package/dist/{chunk-HXSR24QG.js.map → chunk-IXKFIO6O.js.map} +0 -0
- /package/dist/{chunk-ABIS6ZE7.js.map → chunk-J6GIJFV4.js.map} +0 -0
- /package/dist/{chunk-UWAJUJKU.js.map → chunk-KX7A23JM.js.map} +0 -0
- /package/dist/{chunk-LBIFDT6N.js.map → chunk-KXFPJOPL.js.map} +0 -0
- /package/dist/{chunk-UXUOJOXL.js.map → chunk-NNYVXSQF.js.map} +0 -0
- /package/dist/{chunk-DMUVBBOD.js.map → chunk-NQA77WXP.js.map} +0 -0
- /package/dist/{chunk-CPW3LHST.js.map → chunk-O24ENDHN.js.map} +0 -0
- /package/dist/{chunk-QFL3BFIV.js.map → chunk-OOIZ3IQD.js.map} +0 -0
- /package/dist/{chunk-3HU4VVD7.js.map → chunk-P6D7S7CY.js.map} +0 -0
- /package/dist/{chunk-RIB56PWI.js.map → chunk-Q75P5CVJ.js.map} +0 -0
- /package/dist/{chunk-T2ZQUZQW.js.map → chunk-QCDYJYGB.js.map} +0 -0
- /package/dist/{chunk-KUZV4AW5.js.map → chunk-S6MNVNQB.js.map} +0 -0
- /package/dist/{chunk-FQ6UCX2J.js.map → chunk-V66MMBO4.js.map} +0 -0
- /package/dist/{chunk-AHJUIF44.js.map → chunk-XARB7ITD.js.map} +0 -0
- /package/dist/{chunk-HQQ3ES3J.js.map → chunk-XNNNDQLK.js.map} +0 -0
- /package/dist/{chunk-XGAMDSC5.js.map → chunk-XWPZZEBX.js.map} +0 -0
- /package/dist/{chunk-VEMKYAW4.js.map → chunk-YG73EH43.js.map} +0 -0
- /package/dist/{chunk-ALJPZZ57.js.map → chunk-YPJRVA2H.js.map} +0 -0
- /package/dist/{chunk-QW343BTQ.js.map → chunk-ZE73MT66.js.map} +0 -0
- /package/dist/{chunk-AOSBH5H7.js.map → chunk-ZEOHUE2N.js.map} +0 -0
- /package/dist/{chunk-CJ2LQLZV.js.map → chunk-ZSFRB2S4.js.map} +0 -0
- /package/dist/{cli-circuit-breaker-VRTWILPB.js.map → cli-circuit-breaker-4ASNTBDP.js.map} +0 -0
- /package/dist/{composite-router-LAMVSGZ3.js.map → composite-router-V45AK6WD.js.map} +0 -0
- /package/dist/{consensus-vote-ZB72SHTV.js.map → consensus-vote-N2Z7FX3D.js.map} +0 -0
- /package/dist/{context-retriever-VRAIKZTE.js.map → context-retriever-NZOSGG6S.js.map} +0 -0
- /package/dist/{doctor-deep-ZZWIF64Z.js.map → doctor-deep-JRY3D7BP.js.map} +0 -0
- /package/dist/{doctor-live-KKIWEO3K.js.map → doctor-live-NSRHMHMG.js.map} +0 -0
- /package/dist/{expert-bridge-TMHBUOUK.js.map → expert-bridge-Q4SEPVTE.js.map} +0 -0
- /package/dist/{factory-KAMTEOXC.js.map → factory-JBK45P7G.js.map} +0 -0
- /package/dist/{factory-WAI626V6.js.map → factory-ZV4MR3VM.js.map} +0 -0
- /package/dist/{improvement-review-BI3B3PQ2.js.map → improvement-review-KHTIDV4R.js.map} +0 -0
- /package/dist/{init-opencode-EY7TUIML.js.map → init-opencode-6WGN5J6J.js.map} +0 -0
- /package/dist/{issue-triage-VAWDVGET.js.map → issue-triage-XVDXOZRW.js.map} +0 -0
- /package/dist/{learning-persistence-7BEPQEGN.js.map → learning-persistence-ER7E5Q6T.js.map} +0 -0
- /package/dist/{pr-reviewer-helpers-3A6ZFECZ.js.map → pr-reviewer-helpers-UMNMOUAO.js.map} +0 -0
- /package/dist/{registry-command-PWWFOIT3.js.map → registry-command-QRBAKWRK.js.map} +0 -0
- /package/dist/{repo-security-plan-Y6PXBY44.js.map → repo-security-plan-UM6WX5PN.js.map} +0 -0
- /package/dist/{research-helpers-synthesize-L22FP57N.js.map → research-helpers-synthesize-DUOVXI5V.js.map} +0 -0
- /package/dist/{routing-memory-XFJJT5AW.js.map → routing-memory-OKYSDPXT.js.map} +0 -0
- /package/dist/{session-memory-SRH5ZJRX.js.map → session-memory-ONUSRMYV.js.map} +0 -0
- /package/dist/{setup-config-T64H4UOO.js.map → setup-config-BAZRBFN3.js.map} +0 -0
- /package/dist/{setup-custom-api-RQW6VSLO.js.map → setup-custom-api-LSMLWGKW.js.map} +0 -0
- /package/dist/{setup-data-dir-ZD4N4CEP.js.map → setup-data-dir-7PDX3R3Y.js.map} +0 -0
- /package/dist/{tool-memory-72T6Z6H4.js.map → tool-memory-Q5UWNVN3.js.map} +0 -0
- /package/dist/{unified-registry-J2PCYXUU.js.map → unified-registry-Y327PN6R.js.map} +0 -0
- /package/dist/{weather-report-GPYYWU5C.js.map → weather-report-C2VIAC2Q.js.map} +0 -0
|
@@ -2,8 +2,9 @@ import {
|
|
|
2
2
|
ensureLearningDir,
|
|
3
3
|
getDecisionCostFile,
|
|
4
4
|
getModelSelectionShadowFile,
|
|
5
|
-
isPersistenceEnabled
|
|
6
|
-
|
|
5
|
+
isPersistenceEnabled,
|
|
6
|
+
isStrategyDistillationEnabled
|
|
7
|
+
} from "./chunk-RQLIPUQK.js";
|
|
7
8
|
import {
|
|
8
9
|
getNexusDataDir,
|
|
9
10
|
nexusDataPath,
|
|
@@ -2642,7 +2643,7 @@ function setDefaultRegistry(registry) {
|
|
|
2642
2643
|
}
|
|
2643
2644
|
async function reloadDefaultRegistry() {
|
|
2644
2645
|
globalRegistry = buildDefaultRegistry();
|
|
2645
|
-
const { resetGlobalRegistry } = await import("./unified-registry-
|
|
2646
|
+
const { resetGlobalRegistry } = await import("./unified-registry-Y327PN6R.js");
|
|
2646
2647
|
resetGlobalRegistry();
|
|
2647
2648
|
return globalRegistry;
|
|
2648
2649
|
}
|
|
@@ -9655,20 +9656,40 @@ var DistilledRuleStage = class {
|
|
|
9655
9656
|
config;
|
|
9656
9657
|
logger;
|
|
9657
9658
|
rulesAppliedCount = 0;
|
|
9659
|
+
firstRunChecked = false;
|
|
9658
9660
|
constructor(distiller, config, logger15) {
|
|
9659
9661
|
this.distiller = distiller;
|
|
9660
9662
|
this.config = { ...DEFAULT_CONFIG5, ...config };
|
|
9661
9663
|
this.logger = logger15 ?? createLogger({ component: "DistilledRuleStage" });
|
|
9662
9664
|
}
|
|
9663
9665
|
canHandle(ctx) {
|
|
9666
|
+
this.checkPersistedTriggerOnce();
|
|
9664
9667
|
const candidates = getRemainingCandidates(ctx);
|
|
9665
9668
|
if (candidates.length <= 1) return false;
|
|
9666
9669
|
const activeRules = this.distiller.getRules("active");
|
|
9667
9670
|
return activeRules.length > 0;
|
|
9668
9671
|
}
|
|
9669
9672
|
route(ctx) {
|
|
9673
|
+
this.checkPersistedTriggerOnce();
|
|
9670
9674
|
return Promise.resolve(this.routeSync(ctx));
|
|
9671
9675
|
}
|
|
9676
|
+
/**
|
|
9677
|
+
* The first time this stage runs, let the distiller distill from the
|
|
9678
|
+
* persisted outcome store (#6512). Without it, rules only ever came from 50
|
|
9679
|
+
* outcomes seen inside one process, which short-lived CLI processes never
|
|
9680
|
+
* reach. A failure here is logged and routing continues without rules.
|
|
9681
|
+
*/
|
|
9682
|
+
checkPersistedTriggerOnce() {
|
|
9683
|
+
if (this.firstRunChecked) return;
|
|
9684
|
+
this.firstRunChecked = true;
|
|
9685
|
+
try {
|
|
9686
|
+
this.distiller.checkPersistedTrigger();
|
|
9687
|
+
} catch (error) {
|
|
9688
|
+
this.logger.warn("Persisted-store distill trigger failed; routing without new rules", {
|
|
9689
|
+
error: error instanceof Error ? error.message : String(error)
|
|
9690
|
+
});
|
|
9691
|
+
}
|
|
9692
|
+
}
|
|
9672
9693
|
recordOutcome(outcome) {
|
|
9673
9694
|
this.distiller.onOutcome();
|
|
9674
9695
|
this.logger.debug("Forwarded outcome to distiller", {
|
|
@@ -10196,317 +10217,8 @@ var DEFAULT_DISTILLER_CONFIG = {
|
|
|
10196
10217
|
ruleExpiryMs: 24 * 60 * 60 * 1e3
|
|
10197
10218
|
};
|
|
10198
10219
|
|
|
10199
|
-
// src/learning/
|
|
10200
|
-
|
|
10201
|
-
const raw = 1 / (1 + Math.exp(-(observations - center) / 5));
|
|
10202
|
-
return Math.max(0, Math.min(1, raw));
|
|
10203
|
-
}
|
|
10204
|
-
function clampUnit(value) {
|
|
10205
|
-
if (!Number.isFinite(value)) return 0;
|
|
10206
|
-
return Math.max(0, Math.min(1, value));
|
|
10207
|
-
}
|
|
10208
|
-
function effectFor(patternType, metric, thresholds) {
|
|
10209
|
-
switch (patternType) {
|
|
10210
|
-
case "failure-rate":
|
|
10211
|
-
return clampUnit(
|
|
10212
|
-
(metric - thresholds.failureRateThreshold) / (1 - thresholds.failureRateThreshold)
|
|
10213
|
-
);
|
|
10214
|
-
case "success-rate":
|
|
10215
|
-
return clampUnit(
|
|
10216
|
-
(metric - thresholds.successRateThreshold) / (1 - thresholds.successRateThreshold)
|
|
10217
|
-
);
|
|
10218
|
-
case "latency-spike":
|
|
10219
|
-
return clampUnit(
|
|
10220
|
-
(metric - thresholds.latencyRatioThreshold) / thresholds.latencyRatioThreshold
|
|
10221
|
-
);
|
|
10222
|
-
}
|
|
10223
|
-
}
|
|
10224
|
-
function ruleFingerprint(patternType, cli, category) {
|
|
10225
|
-
return `${patternType}:${cli}:${category}`;
|
|
10226
|
-
}
|
|
10227
|
-
function groupOutcomes(outcomes) {
|
|
10228
|
-
const map2 = /* @__PURE__ */ new Map();
|
|
10229
|
-
for (const o of outcomes) {
|
|
10230
|
-
const key = `${o.cli}:${o.category}`;
|
|
10231
|
-
const list = map2.get(key);
|
|
10232
|
-
if (list !== void 0) {
|
|
10233
|
-
list.push(o);
|
|
10234
|
-
} else {
|
|
10235
|
-
map2.set(key, [o]);
|
|
10236
|
-
}
|
|
10237
|
-
}
|
|
10238
|
-
const groups = [];
|
|
10239
|
-
for (const [key, list] of map2) {
|
|
10240
|
-
const [cli, category] = key.split(":");
|
|
10241
|
-
groups.push({ cli, category, outcomes: list });
|
|
10242
|
-
}
|
|
10243
|
-
return groups;
|
|
10244
|
-
}
|
|
10245
|
-
function percentile(sorted, p) {
|
|
10246
|
-
if (sorted.length === 0) return 0;
|
|
10247
|
-
const idx = Math.ceil(p / 100 * sorted.length) - 1;
|
|
10248
|
-
return sorted[Math.max(0, idx)] ?? 0;
|
|
10249
|
-
}
|
|
10250
|
-
function detectFailurePatterns(groups, threshold) {
|
|
10251
|
-
const patterns = [];
|
|
10252
|
-
for (const g of groups) {
|
|
10253
|
-
const failCount = g.outcomes.filter((o) => !o.success).length;
|
|
10254
|
-
const failRate = failCount / g.outcomes.length;
|
|
10255
|
-
if (failRate >= threshold) {
|
|
10256
|
-
patterns.push({
|
|
10257
|
-
cli: g.cli,
|
|
10258
|
-
category: g.category,
|
|
10259
|
-
patternType: "failure-rate",
|
|
10260
|
-
action: failRate >= 0.8 ? "avoid" : "penalize",
|
|
10261
|
-
metric: failRate,
|
|
10262
|
-
observationCount: g.outcomes.length
|
|
10263
|
-
});
|
|
10264
|
-
}
|
|
10265
|
-
}
|
|
10266
|
-
return patterns;
|
|
10267
|
-
}
|
|
10268
|
-
function detectSuccessPatterns(groups, threshold) {
|
|
10269
|
-
const patterns = [];
|
|
10270
|
-
for (const g of groups) {
|
|
10271
|
-
const successCount = g.outcomes.filter((o) => o.success).length;
|
|
10272
|
-
const successRate3 = successCount / g.outcomes.length;
|
|
10273
|
-
if (successRate3 >= threshold) {
|
|
10274
|
-
patterns.push({
|
|
10275
|
-
cli: g.cli,
|
|
10276
|
-
category: g.category,
|
|
10277
|
-
patternType: "success-rate",
|
|
10278
|
-
action: "boost",
|
|
10279
|
-
metric: successRate3,
|
|
10280
|
-
observationCount: g.outcomes.length
|
|
10281
|
-
});
|
|
10282
|
-
}
|
|
10283
|
-
}
|
|
10284
|
-
return patterns;
|
|
10285
|
-
}
|
|
10286
|
-
function detectLatencyPatterns(groups, threshold) {
|
|
10287
|
-
const patterns = [];
|
|
10288
|
-
for (const g of groups) {
|
|
10289
|
-
const durations = g.outcomes.map((o) => o.durationMs).sort((a, b) => a - b);
|
|
10290
|
-
if (durations.length < 3) continue;
|
|
10291
|
-
const median = percentile(durations, 50);
|
|
10292
|
-
const p90 = percentile(durations, 90);
|
|
10293
|
-
if (median <= 0) continue;
|
|
10294
|
-
const ratio = p90 / median;
|
|
10295
|
-
if (ratio >= threshold) {
|
|
10296
|
-
patterns.push({
|
|
10297
|
-
cli: g.cli,
|
|
10298
|
-
category: g.category,
|
|
10299
|
-
patternType: "latency-spike",
|
|
10300
|
-
action: "penalize",
|
|
10301
|
-
metric: ratio,
|
|
10302
|
-
observationCount: g.outcomes.length
|
|
10303
|
-
});
|
|
10304
|
-
}
|
|
10305
|
-
}
|
|
10306
|
-
return patterns;
|
|
10307
|
-
}
|
|
10308
|
-
var StrategyDistiller = class {
|
|
10309
|
-
/** Protected so `PersistentStrategyDistiller` hydrates legacy rules under the live thresholds. */
|
|
10310
|
-
config;
|
|
10311
|
-
outcomeStore;
|
|
10312
|
-
logger;
|
|
10313
|
-
rules = /* @__PURE__ */ new Map();
|
|
10314
|
-
outcomeCounter = 0;
|
|
10315
|
-
lastDistillAt;
|
|
10316
|
-
constructor(outcomeStore, logger15, config) {
|
|
10317
|
-
this.outcomeStore = outcomeStore;
|
|
10318
|
-
this.config = { ...DEFAULT_DISTILLER_CONFIG, ...config };
|
|
10319
|
-
this.logger = logger15 ?? createLogger({ component: "StrategyDistiller" });
|
|
10320
|
-
}
|
|
10321
|
-
/** Called for each processed outcome. Triggers distillation at threshold. */
|
|
10322
|
-
onOutcome() {
|
|
10323
|
-
this.outcomeCounter++;
|
|
10324
|
-
if (this.outcomeCounter >= this.config.triggerThreshold) {
|
|
10325
|
-
this.distill();
|
|
10326
|
-
this.outcomeCounter = 0;
|
|
10327
|
-
}
|
|
10328
|
-
}
|
|
10329
|
-
/** Run distillation on current OutcomeStore data. */
|
|
10330
|
-
distill() {
|
|
10331
|
-
const now = getTimeProvider().now();
|
|
10332
|
-
const outcomes = this.outcomeStore.query();
|
|
10333
|
-
const groups = groupOutcomes(outcomes);
|
|
10334
|
-
this.expireRules(now);
|
|
10335
|
-
const failurePatterns = detectFailurePatterns(groups, this.config.failureRateThreshold);
|
|
10336
|
-
const successPatterns = detectSuccessPatterns(groups, this.config.successRateThreshold);
|
|
10337
|
-
const latencyPatterns = detectLatencyPatterns(groups, this.config.latencyRatioThreshold);
|
|
10338
|
-
const allPatterns = [...failurePatterns, ...successPatterns, ...latencyPatterns];
|
|
10339
|
-
for (const pattern of allPatterns) {
|
|
10340
|
-
this.upsertRule(pattern, now);
|
|
10341
|
-
}
|
|
10342
|
-
this.enforceMaxRules();
|
|
10343
|
-
this.lastDistillAt = now;
|
|
10344
|
-
this.logger.debug("Distillation complete", {
|
|
10345
|
-
rulesTotal: this.rules.size,
|
|
10346
|
-
patternsFound: allPatterns.length,
|
|
10347
|
-
outcomesAnalyzed: outcomes.length
|
|
10348
|
-
});
|
|
10349
|
-
}
|
|
10350
|
-
/** Get rules filtered by status. */
|
|
10351
|
-
getRules(status) {
|
|
10352
|
-
const all = [...this.rules.values()];
|
|
10353
|
-
if (status === void 0) return all;
|
|
10354
|
-
return all.filter((r) => r.status === status);
|
|
10355
|
-
}
|
|
10356
|
-
/** Get distiller statistics. */
|
|
10357
|
-
getStats() {
|
|
10358
|
-
const countByStatus = {
|
|
10359
|
-
draft: 0,
|
|
10360
|
-
active: 0,
|
|
10361
|
-
promoted: 0,
|
|
10362
|
-
expired: 0
|
|
10363
|
-
};
|
|
10364
|
-
for (const rule of this.rules.values()) {
|
|
10365
|
-
countByStatus[rule.status]++;
|
|
10366
|
-
}
|
|
10367
|
-
return {
|
|
10368
|
-
ruleCountByStatus: countByStatus,
|
|
10369
|
-
totalRules: this.rules.size,
|
|
10370
|
-
lastDistillAt: this.lastDistillAt,
|
|
10371
|
-
outcomesSinceLastDistill: this.outcomeCounter
|
|
10372
|
-
};
|
|
10373
|
-
}
|
|
10374
|
-
/**
|
|
10375
|
-
* Promote high-confidence rules to RoutingMemory.
|
|
10376
|
-
* Rules must be active, with sufficient observations and confidence.
|
|
10377
|
-
*
|
|
10378
|
-
* @deprecated No production caller (#5004 finding 4). `DistilledRuleStage`
|
|
10379
|
-
* is the single channel by which distilled rules reach routing; this
|
|
10380
|
-
* second channel into `RoutingMemory` is kept only so the deprecation is
|
|
10381
|
-
* non-breaking. Removal is tracked in #5467. Note the gate now compares
|
|
10382
|
-
* `confidence = support × effect` against `promotionConfidence`, so a rule
|
|
10383
|
-
* that would have promoted on sample size alone may no longer clear it.
|
|
10384
|
-
*/
|
|
10385
|
-
promote(routingMemory) {
|
|
10386
|
-
let promoted = 0;
|
|
10387
|
-
for (const [id, rule] of this.rules) {
|
|
10388
|
-
if (rule.status !== "active") continue;
|
|
10389
|
-
if (rule.observationCount < this.config.minObservationsForActive) continue;
|
|
10390
|
-
if (rule.confidence < this.config.promotionConfidence) continue;
|
|
10391
|
-
const performance2 = this.ruleToPerformance(rule);
|
|
10392
|
-
routingMemory.storePreference(rule.cli, rule.category, performance2);
|
|
10393
|
-
this.rules.set(id, { ...rule, status: "promoted", updatedAt: getTimeProvider().now() });
|
|
10394
|
-
promoted++;
|
|
10395
|
-
this.logger.info("Promoted distilled rule to RoutingMemory", {
|
|
10396
|
-
ruleId: id,
|
|
10397
|
-
cli: rule.cli,
|
|
10398
|
-
category: rule.category,
|
|
10399
|
-
confidence: rule.confidence
|
|
10400
|
-
});
|
|
10401
|
-
}
|
|
10402
|
-
return promoted;
|
|
10403
|
-
}
|
|
10404
|
-
// ==========================================================================
|
|
10405
|
-
// Protected — for subclass hydration (Issue #1009)
|
|
10406
|
-
// ==========================================================================
|
|
10407
|
-
/** Load pre-existing rules (e.g., from disk). Used by PersistentStrategyDistiller. */
|
|
10408
|
-
loadRules(rules) {
|
|
10409
|
-
for (const rule of rules) {
|
|
10410
|
-
this.rules.set(rule.id, rule);
|
|
10411
|
-
}
|
|
10412
|
-
}
|
|
10413
|
-
// ==========================================================================
|
|
10414
|
-
// Private
|
|
10415
|
-
// ==========================================================================
|
|
10416
|
-
upsertRule(pattern, now) {
|
|
10417
|
-
const id = ruleFingerprint(pattern.patternType, pattern.cli, pattern.category);
|
|
10418
|
-
const existing = this.rules.get(id);
|
|
10419
|
-
if (existing === void 0 && pattern.observationCount < this.config.minObservationsForDraft) {
|
|
10420
|
-
return;
|
|
10421
|
-
}
|
|
10422
|
-
const support = sigmoidConfidence(pattern.observationCount);
|
|
10423
|
-
const effect = effectFor(pattern.patternType, pattern.metric, this.config);
|
|
10424
|
-
const confidence = support * effect;
|
|
10425
|
-
const status = this.computeStatus(pattern.observationCount, existing?.status);
|
|
10426
|
-
if (existing !== void 0) {
|
|
10427
|
-
this.rules.set(id, {
|
|
10428
|
-
...existing,
|
|
10429
|
-
action: pattern.action,
|
|
10430
|
-
confidence,
|
|
10431
|
-
support,
|
|
10432
|
-
effect,
|
|
10433
|
-
observationCount: pattern.observationCount,
|
|
10434
|
-
metric: pattern.metric,
|
|
10435
|
-
status,
|
|
10436
|
-
updatedAt: now
|
|
10437
|
-
});
|
|
10438
|
-
} else {
|
|
10439
|
-
this.rules.set(id, {
|
|
10440
|
-
id,
|
|
10441
|
-
patternType: pattern.patternType,
|
|
10442
|
-
cli: pattern.cli,
|
|
10443
|
-
category: pattern.category,
|
|
10444
|
-
action: pattern.action,
|
|
10445
|
-
confidence,
|
|
10446
|
-
support,
|
|
10447
|
-
effect,
|
|
10448
|
-
observationCount: pattern.observationCount,
|
|
10449
|
-
metric: pattern.metric,
|
|
10450
|
-
status,
|
|
10451
|
-
createdAt: now,
|
|
10452
|
-
updatedAt: now,
|
|
10453
|
-
tainted: false
|
|
10454
|
-
});
|
|
10455
|
-
}
|
|
10456
|
-
}
|
|
10457
|
-
computeStatus(observations, existing) {
|
|
10458
|
-
if (existing === "promoted") return "promoted";
|
|
10459
|
-
if (observations >= this.config.minObservationsForActive) return "active";
|
|
10460
|
-
return "draft";
|
|
10461
|
-
}
|
|
10462
|
-
expireRules(now) {
|
|
10463
|
-
for (const [id, rule] of this.rules) {
|
|
10464
|
-
if (rule.status === "promoted") continue;
|
|
10465
|
-
if (now - rule.updatedAt > this.config.ruleExpiryMs) {
|
|
10466
|
-
this.rules.set(id, { ...rule, status: "expired", updatedAt: now });
|
|
10467
|
-
}
|
|
10468
|
-
}
|
|
10469
|
-
}
|
|
10470
|
-
enforceMaxRules() {
|
|
10471
|
-
if (this.rules.size <= this.config.maxRules) return;
|
|
10472
|
-
const sorted = [...this.rules.entries()].sort((a, b) => {
|
|
10473
|
-
if (a[1].status === "expired" && b[1].status !== "expired") return -1;
|
|
10474
|
-
if (b[1].status === "expired" && a[1].status !== "expired") return 1;
|
|
10475
|
-
return a[1].confidence - b[1].confidence;
|
|
10476
|
-
});
|
|
10477
|
-
const excess = this.rules.size - this.config.maxRules;
|
|
10478
|
-
for (let i = 0; i < excess; i++) {
|
|
10479
|
-
const entry = sorted[i];
|
|
10480
|
-
if (entry !== void 0) {
|
|
10481
|
-
this.rules.delete(entry[0]);
|
|
10482
|
-
}
|
|
10483
|
-
}
|
|
10484
|
-
}
|
|
10485
|
-
/** Convert rule metrics into ModelPerformance for RoutingMemory. */
|
|
10486
|
-
ruleToPerformance(rule) {
|
|
10487
|
-
const successRate3 = rule.patternType === "success-rate" ? rule.metric : 1 - rule.metric;
|
|
10488
|
-
return {
|
|
10489
|
-
avgQuality: rule.confidence,
|
|
10490
|
-
successRate: Math.max(0, Math.min(1, successRate3)),
|
|
10491
|
-
avgLatencyMs: 0,
|
|
10492
|
-
avgTokens: 0,
|
|
10493
|
-
observations: rule.observationCount
|
|
10494
|
-
};
|
|
10495
|
-
}
|
|
10496
|
-
};
|
|
10497
|
-
function createStrategyDistiller(outcomeStore, logger15, config) {
|
|
10498
|
-
return new StrategyDistiller(outcomeStore, logger15, config);
|
|
10499
|
-
}
|
|
10500
|
-
var persistentDistillerFactory;
|
|
10501
|
-
function registerPersistentDistillerFactory(factory) {
|
|
10502
|
-
persistentDistillerFactory = factory;
|
|
10503
|
-
}
|
|
10504
|
-
function createPersistentDistillerOrFallback(outcomeStore, logger15) {
|
|
10505
|
-
if (persistentDistillerFactory !== void 0) {
|
|
10506
|
-
return persistentDistillerFactory(outcomeStore, logger15);
|
|
10507
|
-
}
|
|
10508
|
-
return new StrategyDistiller(outcomeStore, logger15);
|
|
10509
|
-
}
|
|
10220
|
+
// src/learning/distiller-eligibility.ts
|
|
10221
|
+
import { existsSync as existsSync6, readFileSync as readFileSync6 } from "node:fs";
|
|
10510
10222
|
|
|
10511
10223
|
// src/orchestration/outcomes/outcome-types.ts
|
|
10512
10224
|
import { z as z14 } from "zod";
|
|
@@ -10747,37 +10459,442 @@ function classifyText(text) {
|
|
|
10747
10459
|
if (matchesAny(text, PARSE_PATTERNS)) return "parse";
|
|
10748
10460
|
return classifyExecutionOrGeneric(text);
|
|
10749
10461
|
}
|
|
10750
|
-
var MAX_ERROR_MESSAGE_LENGTH = 500;
|
|
10751
|
-
function extractNonErrorMessage(error) {
|
|
10752
|
-
if (error === null || error === void 0) return void 0;
|
|
10753
|
-
if (typeof error === "string") {
|
|
10754
|
-
return error.slice(0, MAX_ERROR_MESSAGE_LENGTH);
|
|
10462
|
+
var MAX_ERROR_MESSAGE_LENGTH = 500;
|
|
10463
|
+
function extractNonErrorMessage(error) {
|
|
10464
|
+
if (error === null || error === void 0) return void 0;
|
|
10465
|
+
if (typeof error === "string") {
|
|
10466
|
+
return error.slice(0, MAX_ERROR_MESSAGE_LENGTH);
|
|
10467
|
+
}
|
|
10468
|
+
if (typeof error === "object") {
|
|
10469
|
+
const record = error;
|
|
10470
|
+
if (typeof record["message"] === "string") {
|
|
10471
|
+
return record["message"].slice(0, MAX_ERROR_MESSAGE_LENGTH);
|
|
10472
|
+
}
|
|
10473
|
+
try {
|
|
10474
|
+
const json = JSON.stringify(error);
|
|
10475
|
+
return json.slice(0, MAX_ERROR_MESSAGE_LENGTH);
|
|
10476
|
+
} catch {
|
|
10477
|
+
return void 0;
|
|
10478
|
+
}
|
|
10479
|
+
}
|
|
10480
|
+
return void 0;
|
|
10481
|
+
}
|
|
10482
|
+
function categorizeOutcomeError(error) {
|
|
10483
|
+
if (error instanceof Error) {
|
|
10484
|
+
const text = `${error.message.toLowerCase()} ${error.name.toLowerCase()}`;
|
|
10485
|
+
return classifyText(text);
|
|
10486
|
+
}
|
|
10487
|
+
const extracted = extractNonErrorMessage(error);
|
|
10488
|
+
if (extracted === void 0) return "execution";
|
|
10489
|
+
return classifyText(extracted.toLowerCase());
|
|
10490
|
+
}
|
|
10491
|
+
function categorizeOutcomeErrorMessage(msg) {
|
|
10492
|
+
return classifyText(msg.toLowerCase());
|
|
10493
|
+
}
|
|
10494
|
+
|
|
10495
|
+
// src/learning/distiller-eligibility.ts
|
|
10496
|
+
var ROUTABLE_CLIS = new Set(CLI_NAMES);
|
|
10497
|
+
function isDistillerEligible(outcome) {
|
|
10498
|
+
if (outcome.source !== "delegate") return false;
|
|
10499
|
+
if (outcome.cliSource !== "executed") return false;
|
|
10500
|
+
if (!(outcome.durationMs > 0)) return false;
|
|
10501
|
+
return ROUTABLE_CLIS.has(outcome.cli);
|
|
10502
|
+
}
|
|
10503
|
+
function countEligibleSince(outcomes, sinceMs) {
|
|
10504
|
+
let count = 0;
|
|
10505
|
+
for (const outcome of outcomes) {
|
|
10506
|
+
if (!isDistillerEligible(outcome)) continue;
|
|
10507
|
+
if (sinceMs !== void 0 && !(Date.parse(outcome.timestamp) > sinceMs)) continue;
|
|
10508
|
+
count++;
|
|
10509
|
+
}
|
|
10510
|
+
return count;
|
|
10511
|
+
}
|
|
10512
|
+
var eligibilityFieldsSchema;
|
|
10513
|
+
function getEligibilityFieldsSchema() {
|
|
10514
|
+
eligibilityFieldsSchema ??= TaskOutcomeSchema.pick({
|
|
10515
|
+
source: true,
|
|
10516
|
+
cli: true,
|
|
10517
|
+
cliSource: true,
|
|
10518
|
+
durationMs: true
|
|
10519
|
+
});
|
|
10520
|
+
return eligibilityFieldsSchema;
|
|
10521
|
+
}
|
|
10522
|
+
function parseLine(line) {
|
|
10523
|
+
try {
|
|
10524
|
+
const parsed = getEligibilityFieldsSchema().safeParse(JSON.parse(line));
|
|
10525
|
+
return parsed.success ? parsed.data : void 0;
|
|
10526
|
+
} catch {
|
|
10527
|
+
return void 0;
|
|
10528
|
+
}
|
|
10529
|
+
}
|
|
10530
|
+
function countEligibleOutcomesInFile(filePath) {
|
|
10531
|
+
if (!existsSync6(filePath)) return 0;
|
|
10532
|
+
let count = 0;
|
|
10533
|
+
for (const line of readFileSync6(filePath, "utf-8").split("\n")) {
|
|
10534
|
+
if (line.trim().length === 0) continue;
|
|
10535
|
+
const fields = parseLine(line);
|
|
10536
|
+
if (fields !== void 0 && isDistillerEligible(fields)) count++;
|
|
10537
|
+
}
|
|
10538
|
+
return count;
|
|
10539
|
+
}
|
|
10540
|
+
|
|
10541
|
+
// src/learning/strategy-distiller.ts
|
|
10542
|
+
function sigmoidConfidence(observations, center = 30) {
|
|
10543
|
+
const raw = 1 / (1 + Math.exp(-(observations - center) / 5));
|
|
10544
|
+
return Math.max(0, Math.min(1, raw));
|
|
10545
|
+
}
|
|
10546
|
+
function clampUnit(value) {
|
|
10547
|
+
if (!Number.isFinite(value)) return 0;
|
|
10548
|
+
return Math.max(0, Math.min(1, value));
|
|
10549
|
+
}
|
|
10550
|
+
function effectFor(patternType, metric, thresholds) {
|
|
10551
|
+
switch (patternType) {
|
|
10552
|
+
case "failure-rate":
|
|
10553
|
+
return clampUnit(
|
|
10554
|
+
(metric - thresholds.failureRateThreshold) / (1 - thresholds.failureRateThreshold)
|
|
10555
|
+
);
|
|
10556
|
+
case "success-rate":
|
|
10557
|
+
return clampUnit(
|
|
10558
|
+
(metric - thresholds.successRateThreshold) / (1 - thresholds.successRateThreshold)
|
|
10559
|
+
);
|
|
10560
|
+
case "latency-spike":
|
|
10561
|
+
return clampUnit(
|
|
10562
|
+
(metric - thresholds.latencyRatioThreshold) / thresholds.latencyRatioThreshold
|
|
10563
|
+
);
|
|
10564
|
+
}
|
|
10565
|
+
}
|
|
10566
|
+
function ruleFingerprint(patternType, cli, category) {
|
|
10567
|
+
return `${patternType}:${cli}:${category}`;
|
|
10568
|
+
}
|
|
10569
|
+
function groupOutcomes(outcomes) {
|
|
10570
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
10571
|
+
for (const o of outcomes) {
|
|
10572
|
+
const key = `${o.cli}:${o.category}`;
|
|
10573
|
+
const list = map2.get(key);
|
|
10574
|
+
if (list !== void 0) {
|
|
10575
|
+
list.push(o);
|
|
10576
|
+
} else {
|
|
10577
|
+
map2.set(key, [o]);
|
|
10578
|
+
}
|
|
10579
|
+
}
|
|
10580
|
+
const groups = [];
|
|
10581
|
+
for (const [key, list] of map2) {
|
|
10582
|
+
const [cli, category] = key.split(":");
|
|
10583
|
+
groups.push({ cli, category, outcomes: list });
|
|
10584
|
+
}
|
|
10585
|
+
return groups;
|
|
10586
|
+
}
|
|
10587
|
+
function percentile(sorted, p) {
|
|
10588
|
+
if (sorted.length === 0) return 0;
|
|
10589
|
+
const idx = Math.ceil(p / 100 * sorted.length) - 1;
|
|
10590
|
+
return sorted[Math.max(0, idx)] ?? 0;
|
|
10591
|
+
}
|
|
10592
|
+
function detectFailurePatterns(groups, threshold) {
|
|
10593
|
+
const patterns = [];
|
|
10594
|
+
for (const g of groups) {
|
|
10595
|
+
const failCount = g.outcomes.filter((o) => !o.success).length;
|
|
10596
|
+
const failRate = failCount / g.outcomes.length;
|
|
10597
|
+
if (failRate >= threshold) {
|
|
10598
|
+
patterns.push({
|
|
10599
|
+
cli: g.cli,
|
|
10600
|
+
category: g.category,
|
|
10601
|
+
patternType: "failure-rate",
|
|
10602
|
+
action: failRate >= 0.8 ? "avoid" : "penalize",
|
|
10603
|
+
metric: failRate,
|
|
10604
|
+
observationCount: g.outcomes.length
|
|
10605
|
+
});
|
|
10606
|
+
}
|
|
10607
|
+
}
|
|
10608
|
+
return patterns;
|
|
10609
|
+
}
|
|
10610
|
+
function detectSuccessPatterns(groups, threshold) {
|
|
10611
|
+
const patterns = [];
|
|
10612
|
+
for (const g of groups) {
|
|
10613
|
+
const successCount = g.outcomes.filter((o) => o.success).length;
|
|
10614
|
+
const successRate3 = successCount / g.outcomes.length;
|
|
10615
|
+
if (successRate3 >= threshold) {
|
|
10616
|
+
patterns.push({
|
|
10617
|
+
cli: g.cli,
|
|
10618
|
+
category: g.category,
|
|
10619
|
+
patternType: "success-rate",
|
|
10620
|
+
action: "boost",
|
|
10621
|
+
metric: successRate3,
|
|
10622
|
+
observationCount: g.outcomes.length
|
|
10623
|
+
});
|
|
10624
|
+
}
|
|
10625
|
+
}
|
|
10626
|
+
return patterns;
|
|
10627
|
+
}
|
|
10628
|
+
function detectLatencyPatterns(groups, threshold) {
|
|
10629
|
+
const patterns = [];
|
|
10630
|
+
for (const g of groups) {
|
|
10631
|
+
const durations = g.outcomes.map((o) => o.durationMs).sort((a, b) => a - b);
|
|
10632
|
+
if (durations.length < 3) continue;
|
|
10633
|
+
const median = percentile(durations, 50);
|
|
10634
|
+
const p90 = percentile(durations, 90);
|
|
10635
|
+
if (median <= 0) continue;
|
|
10636
|
+
const ratio = p90 / median;
|
|
10637
|
+
if (ratio >= threshold) {
|
|
10638
|
+
patterns.push({
|
|
10639
|
+
cli: g.cli,
|
|
10640
|
+
category: g.category,
|
|
10641
|
+
patternType: "latency-spike",
|
|
10642
|
+
action: "penalize",
|
|
10643
|
+
metric: ratio,
|
|
10644
|
+
observationCount: g.outcomes.length
|
|
10645
|
+
});
|
|
10646
|
+
}
|
|
10755
10647
|
}
|
|
10756
|
-
|
|
10757
|
-
|
|
10758
|
-
|
|
10759
|
-
|
|
10648
|
+
return patterns;
|
|
10649
|
+
}
|
|
10650
|
+
var StrategyDistiller = class {
|
|
10651
|
+
/** Protected so `PersistentStrategyDistiller` hydrates legacy rules under the live thresholds. */
|
|
10652
|
+
config;
|
|
10653
|
+
outcomeStore;
|
|
10654
|
+
logger;
|
|
10655
|
+
rules = /* @__PURE__ */ new Map();
|
|
10656
|
+
outcomeCounter = 0;
|
|
10657
|
+
lastDistillAt;
|
|
10658
|
+
eligibleAtLastDistill;
|
|
10659
|
+
persistedTriggerChecked = false;
|
|
10660
|
+
constructor(outcomeStore, logger15, config) {
|
|
10661
|
+
this.outcomeStore = outcomeStore;
|
|
10662
|
+
this.config = { ...DEFAULT_DISTILLER_CONFIG, ...config };
|
|
10663
|
+
this.logger = logger15 ?? createLogger({ component: "StrategyDistiller" });
|
|
10664
|
+
}
|
|
10665
|
+
/**
|
|
10666
|
+
* Called for each processed outcome. Triggers distillation at threshold.
|
|
10667
|
+
* This counter is per-process; `checkPersistedTrigger` covers the
|
|
10668
|
+
* cross-process case (#6512).
|
|
10669
|
+
*/
|
|
10670
|
+
onOutcome() {
|
|
10671
|
+
this.outcomeCounter++;
|
|
10672
|
+
if (this.outcomeCounter >= this.config.triggerThreshold) {
|
|
10673
|
+
this.distill();
|
|
10674
|
+
this.outcomeCounter = 0;
|
|
10760
10675
|
}
|
|
10761
|
-
|
|
10762
|
-
|
|
10763
|
-
|
|
10764
|
-
|
|
10765
|
-
|
|
10676
|
+
}
|
|
10677
|
+
/**
|
|
10678
|
+
* First-route trigger against the persisted store (#6512).
|
|
10679
|
+
*
|
|
10680
|
+
* The in-process counter above only fires after `triggerThreshold` outcomes
|
|
10681
|
+
* in ONE process, which short-lived CLI processes never reach. This checks
|
|
10682
|
+
* the store instead: distill when it holds at least `triggerThreshold`
|
|
10683
|
+
* eligible outcomes newer than the last distill (a hydrated snapshot's
|
|
10684
|
+
* `savedAt`; no snapshot means all of them count). With no snapshot and zero
|
|
10685
|
+
* eligible outcomes it distills anyway, so the empty state is recorded as
|
|
10686
|
+
* "0 eligible, 0 rules, at <time>" rather than left as "never".
|
|
10687
|
+
*
|
|
10688
|
+
* Before deciding, rules past `ruleExpiryMs` are expired, so a hydrated rule
|
|
10689
|
+
* that is out of date is never applied.
|
|
10690
|
+
*
|
|
10691
|
+
* Runs at most once per instance; `DistilledRuleStage` calls it when it first
|
|
10692
|
+
* runs, so constructing a distiller stays cheap. Returns whether it distilled.
|
|
10693
|
+
*/
|
|
10694
|
+
checkPersistedTrigger() {
|
|
10695
|
+
if (this.persistedTriggerChecked) return false;
|
|
10696
|
+
this.persistedTriggerChecked = true;
|
|
10697
|
+
this.expireRules(getTimeProvider().now());
|
|
10698
|
+
const newer = countEligibleSince(this.outcomeStore.query(), this.lastDistillAt);
|
|
10699
|
+
const recordEmpty = this.lastDistillAt === void 0 && newer === 0;
|
|
10700
|
+
if (newer < this.config.triggerThreshold && !recordEmpty) return false;
|
|
10701
|
+
this.logger.debug("Persisted-store distill trigger fired", {
|
|
10702
|
+
eligibleSinceLastDistill: newer,
|
|
10703
|
+
triggerThreshold: this.config.triggerThreshold
|
|
10704
|
+
});
|
|
10705
|
+
this.distill();
|
|
10706
|
+
return true;
|
|
10707
|
+
}
|
|
10708
|
+
/** Run distillation on the eligible outcomes in the OutcomeStore (#6512). */
|
|
10709
|
+
distill() {
|
|
10710
|
+
const now = getTimeProvider().now();
|
|
10711
|
+
const outcomes = this.outcomeStore.query().filter(isDistillerEligible);
|
|
10712
|
+
const groups = groupOutcomes(outcomes);
|
|
10713
|
+
this.expireRules(now);
|
|
10714
|
+
const failurePatterns = detectFailurePatterns(groups, this.config.failureRateThreshold);
|
|
10715
|
+
const successPatterns = detectSuccessPatterns(groups, this.config.successRateThreshold);
|
|
10716
|
+
const latencyPatterns = detectLatencyPatterns(groups, this.config.latencyRatioThreshold);
|
|
10717
|
+
const allPatterns = [...failurePatterns, ...successPatterns, ...latencyPatterns];
|
|
10718
|
+
for (const pattern of allPatterns) {
|
|
10719
|
+
this.upsertRule(pattern, now);
|
|
10766
10720
|
}
|
|
10721
|
+
this.enforceMaxRules();
|
|
10722
|
+
this.lastDistillAt = now;
|
|
10723
|
+
this.eligibleAtLastDistill = outcomes.length;
|
|
10724
|
+
this.logger.debug("Distillation complete", {
|
|
10725
|
+
rulesTotal: this.rules.size,
|
|
10726
|
+
patternsFound: allPatterns.length,
|
|
10727
|
+
outcomesAnalyzed: outcomes.length
|
|
10728
|
+
});
|
|
10767
10729
|
}
|
|
10768
|
-
|
|
10769
|
-
|
|
10770
|
-
|
|
10771
|
-
|
|
10772
|
-
|
|
10773
|
-
return classifyText(text);
|
|
10730
|
+
/** Get rules filtered by status. */
|
|
10731
|
+
getRules(status) {
|
|
10732
|
+
const all = [...this.rules.values()];
|
|
10733
|
+
if (status === void 0) return all;
|
|
10734
|
+
return all.filter((r) => r.status === status);
|
|
10774
10735
|
}
|
|
10775
|
-
|
|
10776
|
-
|
|
10777
|
-
|
|
10736
|
+
/** Get distiller statistics. */
|
|
10737
|
+
getStats() {
|
|
10738
|
+
const countByStatus = {
|
|
10739
|
+
draft: 0,
|
|
10740
|
+
active: 0,
|
|
10741
|
+
promoted: 0,
|
|
10742
|
+
expired: 0
|
|
10743
|
+
};
|
|
10744
|
+
for (const rule of this.rules.values()) {
|
|
10745
|
+
countByStatus[rule.status]++;
|
|
10746
|
+
}
|
|
10747
|
+
return {
|
|
10748
|
+
ruleCountByStatus: countByStatus,
|
|
10749
|
+
totalRules: this.rules.size,
|
|
10750
|
+
lastDistillAt: this.lastDistillAt,
|
|
10751
|
+
outcomesSinceLastDistill: this.outcomeCounter,
|
|
10752
|
+
eligibleOutcomesAtLastDistill: this.eligibleAtLastDistill
|
|
10753
|
+
};
|
|
10754
|
+
}
|
|
10755
|
+
/**
|
|
10756
|
+
* Promote high-confidence rules to RoutingMemory.
|
|
10757
|
+
* Rules must be active, with sufficient observations and confidence.
|
|
10758
|
+
*
|
|
10759
|
+
* @deprecated No production caller (#5004 finding 4). `DistilledRuleStage`
|
|
10760
|
+
* is the single channel by which distilled rules reach routing; this
|
|
10761
|
+
* second channel into `RoutingMemory` is kept only so the deprecation is
|
|
10762
|
+
* non-breaking. Removal is tracked in #5467. Note the gate now compares
|
|
10763
|
+
* `confidence = support × effect` against `promotionConfidence`, so a rule
|
|
10764
|
+
* that would have promoted on sample size alone may no longer clear it.
|
|
10765
|
+
*/
|
|
10766
|
+
promote(routingMemory) {
|
|
10767
|
+
let promoted = 0;
|
|
10768
|
+
for (const [id, rule] of this.rules) {
|
|
10769
|
+
if (rule.status !== "active") continue;
|
|
10770
|
+
if (rule.observationCount < this.config.minObservationsForActive) continue;
|
|
10771
|
+
if (rule.confidence < this.config.promotionConfidence) continue;
|
|
10772
|
+
const performance2 = this.ruleToPerformance(rule);
|
|
10773
|
+
routingMemory.storePreference(rule.cli, rule.category, performance2);
|
|
10774
|
+
this.rules.set(id, { ...rule, status: "promoted", updatedAt: getTimeProvider().now() });
|
|
10775
|
+
promoted++;
|
|
10776
|
+
this.logger.info("Promoted distilled rule to RoutingMemory", {
|
|
10777
|
+
ruleId: id,
|
|
10778
|
+
cli: rule.cli,
|
|
10779
|
+
category: rule.category,
|
|
10780
|
+
confidence: rule.confidence
|
|
10781
|
+
});
|
|
10782
|
+
}
|
|
10783
|
+
return promoted;
|
|
10784
|
+
}
|
|
10785
|
+
// ==========================================================================
|
|
10786
|
+
// Protected — for subclass hydration (Issue #1009)
|
|
10787
|
+
// ==========================================================================
|
|
10788
|
+
/** Load pre-existing rules (e.g., from disk). Used by PersistentStrategyDistiller. */
|
|
10789
|
+
loadRules(rules) {
|
|
10790
|
+
for (const rule of rules) {
|
|
10791
|
+
this.rules.set(rule.id, rule);
|
|
10792
|
+
}
|
|
10793
|
+
}
|
|
10794
|
+
/**
|
|
10795
|
+
* Restore last-distill state from a persisted snapshot (#6512), so the
|
|
10796
|
+
* first-route trigger counts only outcomes newer than that snapshot.
|
|
10797
|
+
*/
|
|
10798
|
+
restoreDistillState(lastDistillAt, eligibleOutcomes) {
|
|
10799
|
+
this.lastDistillAt = lastDistillAt;
|
|
10800
|
+
this.eligibleAtLastDistill = eligibleOutcomes;
|
|
10801
|
+
}
|
|
10802
|
+
// ==========================================================================
|
|
10803
|
+
// Private
|
|
10804
|
+
// ==========================================================================
|
|
10805
|
+
upsertRule(pattern, now) {
|
|
10806
|
+
const id = ruleFingerprint(pattern.patternType, pattern.cli, pattern.category);
|
|
10807
|
+
const existing = this.rules.get(id);
|
|
10808
|
+
if (existing === void 0 && pattern.observationCount < this.config.minObservationsForDraft) {
|
|
10809
|
+
return;
|
|
10810
|
+
}
|
|
10811
|
+
const support = sigmoidConfidence(pattern.observationCount);
|
|
10812
|
+
const effect = effectFor(pattern.patternType, pattern.metric, this.config);
|
|
10813
|
+
const confidence = support * effect;
|
|
10814
|
+
const status = this.computeStatus(pattern.observationCount, existing?.status);
|
|
10815
|
+
if (existing !== void 0) {
|
|
10816
|
+
this.rules.set(id, {
|
|
10817
|
+
...existing,
|
|
10818
|
+
action: pattern.action,
|
|
10819
|
+
confidence,
|
|
10820
|
+
support,
|
|
10821
|
+
effect,
|
|
10822
|
+
observationCount: pattern.observationCount,
|
|
10823
|
+
metric: pattern.metric,
|
|
10824
|
+
status,
|
|
10825
|
+
updatedAt: now
|
|
10826
|
+
});
|
|
10827
|
+
} else {
|
|
10828
|
+
this.rules.set(id, {
|
|
10829
|
+
id,
|
|
10830
|
+
patternType: pattern.patternType,
|
|
10831
|
+
cli: pattern.cli,
|
|
10832
|
+
category: pattern.category,
|
|
10833
|
+
action: pattern.action,
|
|
10834
|
+
confidence,
|
|
10835
|
+
support,
|
|
10836
|
+
effect,
|
|
10837
|
+
observationCount: pattern.observationCount,
|
|
10838
|
+
metric: pattern.metric,
|
|
10839
|
+
status,
|
|
10840
|
+
createdAt: now,
|
|
10841
|
+
updatedAt: now,
|
|
10842
|
+
tainted: false
|
|
10843
|
+
});
|
|
10844
|
+
}
|
|
10845
|
+
}
|
|
10846
|
+
computeStatus(observations, existing) {
|
|
10847
|
+
if (existing === "promoted") return "promoted";
|
|
10848
|
+
if (observations >= this.config.minObservationsForActive) return "active";
|
|
10849
|
+
return "draft";
|
|
10850
|
+
}
|
|
10851
|
+
expireRules(now) {
|
|
10852
|
+
for (const [id, rule] of this.rules) {
|
|
10853
|
+
if (rule.status === "promoted") continue;
|
|
10854
|
+
if (now - rule.updatedAt > this.config.ruleExpiryMs) {
|
|
10855
|
+
this.rules.set(id, { ...rule, status: "expired", updatedAt: now });
|
|
10856
|
+
}
|
|
10857
|
+
}
|
|
10858
|
+
}
|
|
10859
|
+
enforceMaxRules() {
|
|
10860
|
+
if (this.rules.size <= this.config.maxRules) return;
|
|
10861
|
+
const sorted = [...this.rules.entries()].sort((a, b) => {
|
|
10862
|
+
if (a[1].status === "expired" && b[1].status !== "expired") return -1;
|
|
10863
|
+
if (b[1].status === "expired" && a[1].status !== "expired") return 1;
|
|
10864
|
+
return a[1].confidence - b[1].confidence;
|
|
10865
|
+
});
|
|
10866
|
+
const excess = this.rules.size - this.config.maxRules;
|
|
10867
|
+
for (let i = 0; i < excess; i++) {
|
|
10868
|
+
const entry = sorted[i];
|
|
10869
|
+
if (entry !== void 0) {
|
|
10870
|
+
this.rules.delete(entry[0]);
|
|
10871
|
+
}
|
|
10872
|
+
}
|
|
10873
|
+
}
|
|
10874
|
+
/** Convert rule metrics into ModelPerformance for RoutingMemory. */
|
|
10875
|
+
ruleToPerformance(rule) {
|
|
10876
|
+
const successRate3 = rule.patternType === "success-rate" ? rule.metric : 1 - rule.metric;
|
|
10877
|
+
return {
|
|
10878
|
+
avgQuality: rule.confidence,
|
|
10879
|
+
successRate: Math.max(0, Math.min(1, successRate3)),
|
|
10880
|
+
avgLatencyMs: 0,
|
|
10881
|
+
avgTokens: 0,
|
|
10882
|
+
observations: rule.observationCount
|
|
10883
|
+
};
|
|
10884
|
+
}
|
|
10885
|
+
};
|
|
10886
|
+
function createStrategyDistiller(outcomeStore, logger15, config) {
|
|
10887
|
+
return new StrategyDistiller(outcomeStore, logger15, config);
|
|
10778
10888
|
}
|
|
10779
|
-
|
|
10780
|
-
|
|
10889
|
+
var persistentDistillerFactory;
|
|
10890
|
+
function registerPersistentDistillerFactory(factory) {
|
|
10891
|
+
persistentDistillerFactory = factory;
|
|
10892
|
+
}
|
|
10893
|
+
function createPersistentDistillerOrFallback(outcomeStore, logger15) {
|
|
10894
|
+
if (persistentDistillerFactory !== void 0) {
|
|
10895
|
+
return persistentDistillerFactory(outcomeStore, logger15);
|
|
10896
|
+
}
|
|
10897
|
+
return new StrategyDistiller(outcomeStore, logger15);
|
|
10781
10898
|
}
|
|
10782
10899
|
|
|
10783
10900
|
// src/orchestration/outcomes/outcome-store.ts
|
|
@@ -12874,7 +12991,7 @@ function aggregateDecisionCosts(records, windowMs) {
|
|
|
12874
12991
|
import { z as z18 } from "zod";
|
|
12875
12992
|
|
|
12876
12993
|
// src/config/jsonl-store.ts
|
|
12877
|
-
import { appendFileSync as appendFileSync3, existsSync as
|
|
12994
|
+
import { appendFileSync as appendFileSync3, existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync } from "node:fs";
|
|
12878
12995
|
import { dirname as dirname5 } from "node:path";
|
|
12879
12996
|
var DIR_MODE = 448;
|
|
12880
12997
|
var JsonlStore = class {
|
|
@@ -12937,14 +13054,14 @@ var JsonlStore = class {
|
|
|
12937
13054
|
}
|
|
12938
13055
|
}
|
|
12939
13056
|
hydrate() {
|
|
12940
|
-
if (!
|
|
13057
|
+
if (!existsSync7(this.filePath)) {
|
|
12941
13058
|
this.logger.debug("No JSONL file found, starting fresh", { path: this.filePath });
|
|
12942
13059
|
return;
|
|
12943
13060
|
}
|
|
12944
13061
|
let loaded = 0;
|
|
12945
13062
|
let skipped = 0;
|
|
12946
13063
|
try {
|
|
12947
|
-
const content =
|
|
13064
|
+
const content = readFileSync7(this.filePath, "utf-8");
|
|
12948
13065
|
const lines = content.split("\n").filter((line) => line.trim().length > 0);
|
|
12949
13066
|
for (const line of lines) {
|
|
12950
13067
|
try {
|
|
@@ -15216,7 +15333,7 @@ function resolveModelForTier(cliName, tier, opts = {}) {
|
|
|
15216
15333
|
}
|
|
15217
15334
|
|
|
15218
15335
|
// src/cli-adapters/model-selection-shadow.ts
|
|
15219
|
-
import { appendFileSync as appendFileSync4, existsSync as
|
|
15336
|
+
import { appendFileSync as appendFileSync4, existsSync as existsSync8, readFileSync as readFileSync8 } from "node:fs";
|
|
15220
15337
|
import { z as z22 } from "zod";
|
|
15221
15338
|
function isRouteModelShadowEnabled() {
|
|
15222
15339
|
return parseBoolEnv("NEXUS_ROUTE_MODEL_SHADOW", false) && isPersistenceEnabled();
|
|
@@ -15267,11 +15384,11 @@ function persistModelSelectionShadowRecord(record) {
|
|
|
15267
15384
|
}
|
|
15268
15385
|
function readModelSelectionShadowRecords() {
|
|
15269
15386
|
const file = getModelSelectionShadowFile();
|
|
15270
|
-
if (!
|
|
15387
|
+
if (!existsSync8(file)) return [];
|
|
15271
15388
|
const records = [];
|
|
15272
15389
|
try {
|
|
15273
15390
|
const cutoff = Date.now() - READ_LOOKBACK_MS;
|
|
15274
|
-
const lines =
|
|
15391
|
+
const lines = readFileSync8(file, "utf-8").split("\n").filter((l) => l.trim().length > 0);
|
|
15275
15392
|
for (const line of lines) {
|
|
15276
15393
|
let parsed;
|
|
15277
15394
|
try {
|
|
@@ -15626,6 +15743,9 @@ function generateTraceId2() {
|
|
|
15626
15743
|
// src/cli-adapters/composite-router.ts
|
|
15627
15744
|
var FAILURE_QUALITY_SCORE = 0.3;
|
|
15628
15745
|
var DEFAULT_TOKEN_ESTIMATE = 1e3;
|
|
15746
|
+
function applyDistillationSwitch(config) {
|
|
15747
|
+
return isStrategyDistillationEnabled() ? config : { ...config, enableStrategyDistillation: false };
|
|
15748
|
+
}
|
|
15629
15749
|
var CompositeRouter = class _CompositeRouter {
|
|
15630
15750
|
config;
|
|
15631
15751
|
logger;
|
|
@@ -15708,7 +15828,7 @@ var CompositeRouter = class _CompositeRouter {
|
|
|
15708
15828
|
availableModelsCache,
|
|
15709
15829
|
...baseConfig
|
|
15710
15830
|
} = config ?? {};
|
|
15711
|
-
this.config = CompositeRouterConfigSchema.parse(baseConfig);
|
|
15831
|
+
this.config = applyDistillationSwitch(CompositeRouterConfigSchema.parse(baseConfig));
|
|
15712
15832
|
this.logger = logger15 ?? createLogger({ component: "CompositeRouter" });
|
|
15713
15833
|
this.pendingRoutingOutcomes = new PendingRoutingOutcomes(this.logger);
|
|
15714
15834
|
this.adapters = adapters;
|
|
@@ -15894,7 +16014,7 @@ var CompositeRouter = class _CompositeRouter {
|
|
|
15894
16014
|
*/
|
|
15895
16015
|
async consultUnifiedContext(task) {
|
|
15896
16016
|
try {
|
|
15897
|
-
const { getContextForTask, inferTaskCategory } = await import("./context-retriever-
|
|
16017
|
+
const { getContextForTask, inferTaskCategory } = await import("./context-retriever-NZOSGG6S.js");
|
|
15898
16018
|
const ctx = await getContextForTask({
|
|
15899
16019
|
task: task.content,
|
|
15900
16020
|
category: inferTaskCategory(task.content),
|
|
@@ -17477,6 +17597,7 @@ export {
|
|
|
17477
17597
|
RoutingMemory,
|
|
17478
17598
|
createRoutingMemory,
|
|
17479
17599
|
DEFAULT_DISTILLER_CONFIG,
|
|
17600
|
+
countEligibleOutcomesInFile,
|
|
17480
17601
|
sigmoidConfidence,
|
|
17481
17602
|
effectFor,
|
|
17482
17603
|
detectFailurePatterns,
|
|
@@ -17529,4 +17650,4 @@ export {
|
|
|
17529
17650
|
AgentCapability,
|
|
17530
17651
|
OrchestratorError
|
|
17531
17652
|
};
|
|
17532
|
-
//# sourceMappingURL=chunk-
|
|
17653
|
+
//# sourceMappingURL=chunk-64IJKT4I.js.map
|