@warmdrift/kgauto-compiler 2.0.0-alpha.77 → 2.0.0-alpha.78
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/{chunk-46U2NVOL.mjs → chunk-FT2FN6ZP.mjs} +13 -1
- package/dist/{chunk-GB7VJQ6C.mjs → chunk-QVD2QWST.mjs} +1 -1
- package/dist/{chunk-N36LE3MK.mjs → chunk-VVRDFE6T.mjs} +16 -0
- package/dist/glassbox/index.d.mts +3 -3
- package/dist/glassbox/index.d.ts +3 -3
- package/dist/glassbox-routes/format.d.mts +2 -2
- package/dist/glassbox-routes/format.d.ts +2 -2
- package/dist/glassbox-routes/index.d.mts +4 -4
- package/dist/glassbox-routes/index.d.ts +4 -4
- package/dist/glassbox-routes/index.js +16 -0
- package/dist/glassbox-routes/index.mjs +2 -2
- package/dist/glassbox-routes/react/index.d.mts +2 -2
- package/dist/glassbox-routes/react/index.d.ts +2 -2
- package/dist/index.d.mts +151 -24
- package/dist/index.d.ts +151 -24
- package/dist/index.js +1312 -1031
- package/dist/index.mjs +1282 -1033
- package/dist/{ir-CRV1_taF.d.mts → ir-BFwWhj2s.d.mts} +51 -1
- package/dist/{ir-C02EIElQ.d.ts → ir-DZKS1tI7.d.ts} +51 -1
- package/dist/key-health.js +1 -1
- package/dist/key-health.mjs +1 -1
- package/dist/profiles.d.mts +12 -2
- package/dist/profiles.d.ts +12 -2
- package/dist/profiles.js +16 -0
- package/dist/profiles.mjs +1 -1
- package/dist/{types-C5BX9bzi.d.ts → types-B4kz3Vs0.d.ts} +1 -1
- package/dist/{types-CkRvjxrI.d.ts → types-D_fLt_Xv.d.ts} +1 -1
- package/dist/{types-Caq-hOIj.d.mts → types-DpcAMmk-.d.mts} +1 -1
- package/dist/{types-3WdGMqin.d.mts → types-hjzSWxtv.d.mts} +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -25,6 +25,7 @@ __export(index_exports, {
|
|
|
25
25
|
ALL_ARCHETYPES: () => ALL_ARCHETYPES,
|
|
26
26
|
ARCHETYPE_FAMILY_FITS: () => ARCHETYPE_FAMILY_FITS,
|
|
27
27
|
ARCHETYPE_FLOOR_DEFAULT: () => ARCHETYPE_FLOOR_DEFAULT,
|
|
28
|
+
BRAIN_READ_ENV_NAMES: () => BRAIN_READ_ENV_NAMES,
|
|
28
29
|
COACH_CFG: () => COACH_CFG,
|
|
29
30
|
CallError: () => CallError,
|
|
30
31
|
DECOMPOSITION_TEMPLATES: () => DECOMPOSITION_TEMPLATES,
|
|
@@ -43,6 +44,7 @@ __export(index_exports, {
|
|
|
43
44
|
MEASURED_GROUNDING_MIN_N: () => MEASURED_GROUNDING_MIN_N,
|
|
44
45
|
PRODUCER_OWNED_RULE_CODES: () => PRODUCER_OWNED_RULE_CODES,
|
|
45
46
|
PROVIDER_ENV_KEYS: () => PROVIDER_ENV_KEYS,
|
|
47
|
+
ROLLBACK_SUPPRESSION_WINDOW_DAYS: () => ROLLBACK_SUPPRESSION_WINDOW_DAYS,
|
|
46
48
|
RULE_DISCIPLINE_GATES_V1: () => RULE_DISCIPLINE_GATES_V1,
|
|
47
49
|
RULE_DISCIPLINE_GATES_V1_STRUCTURED: () => RULE_DISCIPLINE_GATES_V1_STRUCTURED,
|
|
48
50
|
RULE_SEQUENTIAL_TOOL_CLIFF: () => RULE_SEQUENTIAL_TOOL_CLIFF,
|
|
@@ -96,12 +98,14 @@ __export(index_exports, {
|
|
|
96
98
|
getPerAxisMetrics: () => getPerAxisMetrics,
|
|
97
99
|
getProfile: () => getProfile,
|
|
98
100
|
getReachabilityDiagnostic: () => getReachabilityDiagnostic,
|
|
101
|
+
getRecentRollback: () => getRecentRollback,
|
|
99
102
|
getRecommendedPrimary: () => getRecommendedPrimary,
|
|
100
103
|
getSequentialStarterChain: () => getSequentialStarterChain,
|
|
101
104
|
getSequentialStarterChainWithGrounding: () => getSequentialStarterChainWithGrounding,
|
|
102
105
|
getStaleExclusionFindings: () => getStaleExclusionFindings,
|
|
103
106
|
getStarterChain: () => getStarterChain,
|
|
104
107
|
getStarterChainWithGrounding: () => getStarterChainWithGrounding,
|
|
108
|
+
hasMutation: () => hasMutation,
|
|
105
109
|
hashShape: () => hashShape,
|
|
106
110
|
isArchetype: () => isArchetype,
|
|
107
111
|
isAutoPromoteEnabledFromEnv: () => isAutoPromoteEnabledFromEnv,
|
|
@@ -122,9 +126,11 @@ __export(index_exports, {
|
|
|
122
126
|
loadChainsFromBrain: () => loadChainsFromBrain,
|
|
123
127
|
loadModelsFromBrain: () => loadModelsFromBrain,
|
|
124
128
|
loadPricingFromBrain: () => loadPricingFromBrain,
|
|
129
|
+
mapMeasuredFailureRows: () => mapMeasuredFailureRows,
|
|
125
130
|
markAdvisoryResolved: () => markAdvisoryResolved,
|
|
126
131
|
markExclusionFindingHandled: () => markExclusionFindingHandled,
|
|
127
132
|
markPromoteReadyHandled: () => markPromoteReadyHandled,
|
|
133
|
+
mutationId: () => mutationId,
|
|
128
134
|
parseGoldenCaptureRate: () => parseGoldenCaptureRate,
|
|
129
135
|
parseJudgeVerdict: () => parseJudgeVerdict,
|
|
130
136
|
peekBrainDeadLetter: () => peekBrainDeadLetter,
|
|
@@ -693,6 +699,22 @@ var PROFILES_RAW = [
|
|
|
693
699
|
threshold: 16,
|
|
694
700
|
action: "drop_to_top_relevant",
|
|
695
701
|
reason: "Haiku reliability degrades above ~16 tools"
|
|
702
|
+
},
|
|
703
|
+
{
|
|
704
|
+
// alpha.78 — the declared `structuredOutput: 'grammar'` does NOT
|
|
705
|
+
// hold on long-input summarize. MEASURED (brain, playbacksam):
|
|
706
|
+
// 21 disambiguated `structured_output_parse_failed` fallover rows
|
|
707
|
+
// 2026-07-22..27, tokens_in 12,280–31,450; PB's gate counted 20/20
|
|
708
|
+
// in-window failures. Clean traffic p50 sits at ~9K tokens_in, so
|
|
709
|
+
// 12K gates the failing band without touching the working one.
|
|
710
|
+
// Short-input summarize carries no failure evidence and stays
|
|
711
|
+
// ungated — this is why it's a cliff, not an archetype-wide
|
|
712
|
+
// `structuredOutputHint: 'avoid'`.
|
|
713
|
+
metric: "input_tokens",
|
|
714
|
+
threshold: 12e3,
|
|
715
|
+
action: "quality_gate_structured",
|
|
716
|
+
whenIntent: "summarize",
|
|
717
|
+
reason: "Structured-output parse failures at 100% in-window on long-input summarize (measured on playbacksam, 2026-07-25..27; haiku only led when input size made price dominate, then failed every time)."
|
|
696
718
|
}
|
|
697
719
|
],
|
|
698
720
|
costInputPer1m: 1,
|
|
@@ -2939,11 +2961,18 @@ function passScoreTargets(ir, opts) {
|
|
|
2939
2961
|
}
|
|
2940
2962
|
}
|
|
2941
2963
|
let qualityGatePenalty = 0;
|
|
2964
|
+
let structuredCliffGate;
|
|
2942
2965
|
if (constraints.structuredOutput) {
|
|
2943
2966
|
const schemaWeak = effectiveConventions(profile).some(
|
|
2944
2967
|
(c) => c.archetype === ir.intent.archetype && c.structuredOutputHint === "avoid"
|
|
2945
2968
|
);
|
|
2946
2969
|
if (schemaWeak) qualityGatePenalty = QUALITY_GATE_PENALTY;
|
|
2970
|
+
if (!schemaWeak) {
|
|
2971
|
+
structuredCliffGate = profile.cliffs.find(
|
|
2972
|
+
(c) => c.action === "quality_gate_structured" && (!c.whenIntent || c.whenIntent === ir.intent.archetype) && c.metric === "input_tokens" && opts.estimatedInputTokens >= c.threshold
|
|
2973
|
+
);
|
|
2974
|
+
if (structuredCliffGate) qualityGatePenalty = QUALITY_GATE_PENALTY;
|
|
2975
|
+
}
|
|
2947
2976
|
}
|
|
2948
2977
|
const measuredGate = opts.measuredFailureGates?.get(modelId);
|
|
2949
2978
|
if (measuredGate) qualityGatePenalty = QUALITY_GATE_PENALTY;
|
|
@@ -3008,6 +3037,16 @@ function passScoreTargets(ir, opts) {
|
|
|
3008
3037
|
rankAfter: rank,
|
|
3009
3038
|
description: `Model ${modelId} gated below the quality floor for archetype '${ir.intent.archetype}' by MEASURED evidence from this app's own outcomes \u2014 ${measuredGate.nFail} of ${measuredGate.n} attempts failed on the quality axis in the trailing window (${pct(measuredGate.rate)}; 95% lower bound ${pct(measuredGate.lowerBound)} > 50%). Rank ${rankBefore.toFixed(2)} \u2192 ${rank.toFixed(2)} (\u2212${qualityGatePenalty.toFixed(2)}). Down-ranked out of leadership; retained as graceful fallback only. The gate is derived, not stored \u2014 it lifts on its own once the failures age out of the window.`
|
|
3010
3039
|
});
|
|
3040
|
+
} else if (structuredCliffGate) {
|
|
3041
|
+
policyMutations.push({
|
|
3042
|
+
id: `quality-gate-structured-cliff-${modelId}`,
|
|
3043
|
+
source: "quality_gate",
|
|
3044
|
+
passName: "score_targets",
|
|
3045
|
+
rankDelta: -qualityGatePenalty,
|
|
3046
|
+
rankBefore,
|
|
3047
|
+
rankAfter: rank,
|
|
3048
|
+
description: `Model ${modelId} gated below the quality floor for archetype '${ir.intent.archetype}' \u2014 declared structuredOutput + input_tokens \u2265 ${structuredCliffGate.threshold} trips a measured cliff: ${structuredCliffGate.reason} Rank ${rankBefore.toFixed(2)} \u2192 ${rank.toFixed(2)} (\u2212${qualityGatePenalty.toFixed(2)}). Down-ranked out of leadership; retained as graceful fallback only. Bundled knowledge \u2014 active on cold isolates with no brain.`
|
|
3049
|
+
});
|
|
3011
3050
|
} else {
|
|
3012
3051
|
policyMutations.push({
|
|
3013
3052
|
id: `quality-gate-structured-${modelId}`,
|
|
@@ -3691,13 +3730,8 @@ function getArchetypePerfScore(modelId, archetype) {
|
|
|
3691
3730
|
return { score, n, grounding };
|
|
3692
3731
|
}
|
|
3693
3732
|
|
|
3694
|
-
// src/
|
|
3695
|
-
function
|
|
3696
|
-
if (!x || typeof x !== "object") return false;
|
|
3697
|
-
const r = x;
|
|
3698
|
-
return typeof r.intent_archetype === "string" && typeof r.family === "string" && typeof r.candidate_model === "string" && typeof r.current_model === "string" && typeof r.detected_at === "string";
|
|
3699
|
-
}
|
|
3700
|
-
function coerceNumber(v) {
|
|
3733
|
+
// src/measured-failure-brain.ts
|
|
3734
|
+
function coerceCount(v) {
|
|
3701
3735
|
if (typeof v === "number") return Number.isFinite(v) ? v : null;
|
|
3702
3736
|
if (typeof v === "string") {
|
|
3703
3737
|
const n = Number(v);
|
|
@@ -3705,58 +3739,144 @@ function coerceNumber(v) {
|
|
|
3705
3739
|
}
|
|
3706
3740
|
return null;
|
|
3707
3741
|
}
|
|
3708
|
-
function
|
|
3742
|
+
function isRawFailureRow(x) {
|
|
3743
|
+
if (!x || typeof x !== "object") return false;
|
|
3744
|
+
const r = x;
|
|
3745
|
+
return typeof r.intent_archetype === "string" && typeof r.model === "string" && (typeof r.n === "number" || typeof r.n === "string");
|
|
3746
|
+
}
|
|
3747
|
+
function mapRows(rows) {
|
|
3709
3748
|
const out = [];
|
|
3710
3749
|
for (const row of rows) {
|
|
3711
|
-
if (!
|
|
3712
|
-
const
|
|
3713
|
-
const
|
|
3714
|
-
|
|
3715
|
-
if (sampleN === null || passRate === null || avgScore === null) continue;
|
|
3750
|
+
if (!isRawFailureRow(row)) continue;
|
|
3751
|
+
const n = coerceCount(row.n);
|
|
3752
|
+
const nFail = coerceCount(row.n_fail) ?? 0;
|
|
3753
|
+
if (n === null || n <= 0) continue;
|
|
3716
3754
|
out.push({
|
|
3717
3755
|
archetype: row.intent_archetype,
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
sampleN,
|
|
3722
|
-
judgePassRate: passRate,
|
|
3723
|
-
judgeAvgScore: avgScore,
|
|
3724
|
-
costDeltaPct: coerceNumber(row.cost_delta_pct),
|
|
3725
|
-
detectedAt: row.detected_at
|
|
3756
|
+
model: row.model,
|
|
3757
|
+
n,
|
|
3758
|
+
nFail
|
|
3726
3759
|
});
|
|
3727
3760
|
}
|
|
3728
3761
|
return out;
|
|
3729
3762
|
}
|
|
3763
|
+
var MEASURED_FAILURE_CFG = {
|
|
3764
|
+
/**
|
|
3765
|
+
* Hard minimum attempts before ANY gate may be created. Guards against
|
|
3766
|
+
* pathological tiny samples that the confidence bound alone would let
|
|
3767
|
+
* through in edge cases. At 5-for-5 the bound clears the threshold; at
|
|
3768
|
+
* 3-for-3 it does not, which is the behaviour we want (three failures is
|
|
3769
|
+
* a bad day, five in a row is a pattern).
|
|
3770
|
+
*/
|
|
3771
|
+
minSample: 5,
|
|
3772
|
+
/**
|
|
3773
|
+
* Gate when we are 95% confident the model fails MORE OFTEN THAN IT
|
|
3774
|
+
* SUCCEEDS on this surface. Deliberately unarguable rather than tuned —
|
|
3775
|
+
* a model that probably fails the majority of the time has no business
|
|
3776
|
+
* leading a surface, whatever its declared scores say.
|
|
3777
|
+
*/
|
|
3778
|
+
lowerBoundThreshold: 0.5,
|
|
3779
|
+
/** 95% one-sided-ish confidence (standard two-sided z at α=0.05). */
|
|
3780
|
+
z: 1.96,
|
|
3781
|
+
/** Must match the view's window. Documented here for the advisory text. */
|
|
3782
|
+
windowDays: 28
|
|
3783
|
+
};
|
|
3784
|
+
function wilsonLowerBound(failures, n, z = MEASURED_FAILURE_CFG.z) {
|
|
3785
|
+
if (n <= 0) return 0;
|
|
3786
|
+
const p = failures / n;
|
|
3787
|
+
const z2 = z * z;
|
|
3788
|
+
const denom = 1 + z2 / n;
|
|
3789
|
+
const centre = p + z2 / (2 * n);
|
|
3790
|
+
const margin = z * Math.sqrt(p * (1 - p) / n + z2 / (4 * n * n));
|
|
3791
|
+
const lower2 = (centre - margin) / denom;
|
|
3792
|
+
return lower2 < 0 ? 0 : lower2;
|
|
3793
|
+
}
|
|
3794
|
+
function mapMeasuredFailureRows(rows) {
|
|
3795
|
+
return mapRows(rows);
|
|
3796
|
+
}
|
|
3797
|
+
function judgeMeasuredFailure(row, cfg = MEASURED_FAILURE_CFG) {
|
|
3798
|
+
if (!row) return void 0;
|
|
3799
|
+
const normalized = "nFail" in row && typeof row.n === "number" ? row : mapRows([row])[0];
|
|
3800
|
+
if (!normalized || normalized.n < cfg.minSample) return void 0;
|
|
3801
|
+
const lowerBound = wilsonLowerBound(normalized.nFail, normalized.n, cfg.z);
|
|
3802
|
+
return {
|
|
3803
|
+
gated: lowerBound > cfg.lowerBoundThreshold,
|
|
3804
|
+
rate: normalized.nFail / normalized.n,
|
|
3805
|
+
lowerBound,
|
|
3806
|
+
n: normalized.n,
|
|
3807
|
+
nFail: normalized.nFail
|
|
3808
|
+
};
|
|
3809
|
+
}
|
|
3730
3810
|
var snapshots2 = /* @__PURE__ */ new Map();
|
|
3731
3811
|
var runtime3;
|
|
3732
3812
|
var warnedOnce2 = false;
|
|
3733
|
-
|
|
3813
|
+
var DEFAULT_MEASURED_FAILURE_ENDPOINT = "https://kgauto-dashboard.vercel.app/api/kgauto-v2/measured-failure";
|
|
3814
|
+
function isMeasuredFailureGateEnabledFromEnv(envSource) {
|
|
3815
|
+
const env = envSource ?? (typeof process !== "undefined" && process.env ? process.env : {});
|
|
3816
|
+
const raw = (env.KGAUTO_MEASURED_FAILURE_GATE ?? "").trim().toLowerCase();
|
|
3817
|
+
return !(raw === "0" || raw === "false");
|
|
3818
|
+
}
|
|
3819
|
+
function configureMeasuredFailureBrain(rt) {
|
|
3820
|
+
runtime3 = rt;
|
|
3821
|
+
snapshots2.clear();
|
|
3822
|
+
warnedOnce2 = false;
|
|
3823
|
+
}
|
|
3824
|
+
function isMeasuredFailureBrainActive() {
|
|
3734
3825
|
return runtime3 !== void 0;
|
|
3735
3826
|
}
|
|
3736
|
-
function
|
|
3827
|
+
function prefetchMeasuredFailure(appId) {
|
|
3737
3828
|
const rt = runtime3;
|
|
3738
|
-
if (!rt) return
|
|
3739
|
-
|
|
3740
|
-
if (!
|
|
3829
|
+
if (!rt || !appId) return void 0;
|
|
3830
|
+
let snap = snapshots2.get(appId);
|
|
3831
|
+
if (!snap) {
|
|
3832
|
+
snap = { data: [], expiresAt: 0, refreshing: false };
|
|
3833
|
+
snapshots2.set(appId, snap);
|
|
3834
|
+
}
|
|
3835
|
+
if (snap.expiresAt > Date.now()) return void 0;
|
|
3836
|
+
const inflight = pendingRefreshes2.get(appId);
|
|
3837
|
+
if (inflight) return inflight;
|
|
3838
|
+
if (snap.refreshing) return void 0;
|
|
3839
|
+
snap.refreshing = true;
|
|
3840
|
+
void asyncRefresh3(rt, appId);
|
|
3841
|
+
return pendingRefreshes2.get(appId);
|
|
3842
|
+
}
|
|
3843
|
+
async function awaitMeasuredFailureReady(appId, timeoutMs) {
|
|
3844
|
+
if (!runtime3 || !appId) return;
|
|
3845
|
+
const pending = prefetchMeasuredFailure(appId) ?? pendingRefreshes2.get(appId);
|
|
3846
|
+
if (!(timeoutMs > 0)) return;
|
|
3847
|
+
if (!pending) return;
|
|
3848
|
+
let timer;
|
|
3849
|
+
try {
|
|
3850
|
+
await Promise.race([
|
|
3851
|
+
pending,
|
|
3852
|
+
new Promise((resolve) => {
|
|
3853
|
+
timer = setTimeout(resolve, timeoutMs);
|
|
3854
|
+
})
|
|
3855
|
+
]);
|
|
3856
|
+
} catch {
|
|
3857
|
+
} finally {
|
|
3858
|
+
if (timer) clearTimeout(timer);
|
|
3859
|
+
}
|
|
3860
|
+
}
|
|
3861
|
+
function getMeasuredFailureVerdict(opts) {
|
|
3862
|
+
const rt = runtime3;
|
|
3863
|
+
if (!rt) return void 0;
|
|
3864
|
+
const { appId, archetype, model } = opts;
|
|
3865
|
+
if (!appId || !archetype || !model) return void 0;
|
|
3741
3866
|
let snap = snapshots2.get(appId);
|
|
3742
3867
|
if (!snap) {
|
|
3743
3868
|
snap = { data: [], expiresAt: 0, refreshing: false };
|
|
3744
3869
|
snapshots2.set(appId, snap);
|
|
3745
3870
|
}
|
|
3746
3871
|
const now = Date.now();
|
|
3747
|
-
|
|
3748
|
-
if (stale && !snap.refreshing) {
|
|
3872
|
+
if (snap.expiresAt <= now && !snap.refreshing) {
|
|
3749
3873
|
snap.refreshing = true;
|
|
3750
3874
|
void asyncRefresh3(rt, appId);
|
|
3751
3875
|
}
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
if (opts.family) {
|
|
3757
|
-
rows = rows.filter((f) => f.family === opts.family);
|
|
3758
|
-
}
|
|
3759
|
-
return rows;
|
|
3876
|
+
const row = snap.data.find(
|
|
3877
|
+
(r) => r.archetype === archetype && r.model === model
|
|
3878
|
+
);
|
|
3879
|
+
return judgeMeasuredFailure(row);
|
|
3760
3880
|
}
|
|
3761
3881
|
var pendingRefreshes2 = /* @__PURE__ */ new Map();
|
|
3762
3882
|
async function asyncRefresh3(rt, appId) {
|
|
@@ -3780,12 +3900,11 @@ async function doRefresh3(rt, appId) {
|
|
|
3780
3900
|
try {
|
|
3781
3901
|
const res = await rt.fetchImpl(url, { method: "GET" });
|
|
3782
3902
|
if (!res.ok) {
|
|
3783
|
-
throw new Error(`
|
|
3903
|
+
throw new Error(`measured-failure ${res.status}: ${res.statusText}`);
|
|
3784
3904
|
}
|
|
3785
3905
|
const body = await res.json();
|
|
3786
3906
|
if (runtime3 !== rt) return;
|
|
3787
|
-
|
|
3788
|
-
snap.data = rows;
|
|
3907
|
+
snap.data = Array.isArray(body) ? mapRows(body) : [];
|
|
3789
3908
|
snap.expiresAt = Date.now() + rt.ttlMs;
|
|
3790
3909
|
snap.refreshing = false;
|
|
3791
3910
|
} catch (err) {
|
|
@@ -3800,193 +3919,84 @@ async function doRefresh3(rt, appId) {
|
|
|
3800
3919
|
}
|
|
3801
3920
|
function defaultOnError3(err) {
|
|
3802
3921
|
console.warn(
|
|
3803
|
-
"[kgauto]
|
|
3922
|
+
"[kgauto] measured-failure fetch failed (gate inactive until next refresh):",
|
|
3804
3923
|
err
|
|
3805
3924
|
);
|
|
3806
3925
|
}
|
|
3807
|
-
function
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3926
|
+
function _testResetMeasuredFailure() {
|
|
3927
|
+
runtime3 = void 0;
|
|
3928
|
+
snapshots2.clear();
|
|
3929
|
+
pendingRefreshes2 = /* @__PURE__ */ new Map();
|
|
3930
|
+
warnedOnce2 = false;
|
|
3812
3931
|
}
|
|
3813
|
-
async function
|
|
3814
|
-
const
|
|
3815
|
-
|
|
3816
|
-
archetype,
|
|
3817
|
-
family,
|
|
3818
|
-
resolution,
|
|
3819
|
-
resolutionNote,
|
|
3820
|
-
brainEndpoint,
|
|
3821
|
-
brainJwt,
|
|
3822
|
-
brainAnonKey,
|
|
3823
|
-
fetch: injectedFetch
|
|
3824
|
-
} = opts;
|
|
3825
|
-
if (!appId) return { ok: false, reason: "app_id_required" };
|
|
3826
|
-
if (!archetype) return { ok: false, reason: "archetype_required" };
|
|
3827
|
-
if (!family) return { ok: false, reason: "family_required" };
|
|
3828
|
-
if (resolution !== "promoted" && resolution !== "declined" && resolution !== "still-evaluating") {
|
|
3829
|
-
return { ok: false, reason: "resolution_invalid" };
|
|
3830
|
-
}
|
|
3831
|
-
const doFetch = resolveFetchImpl(injectedFetch);
|
|
3832
|
-
const base = normalizeEndpoint(brainEndpoint);
|
|
3833
|
-
const url = `${base}/rest/v1/promote_ready_findings?app_id=eq.${encodeURIComponent(appId)}&intent_archetype=eq.${encodeURIComponent(archetype)}&family=eq.${encodeURIComponent(family)}&resolved_at=is.null`;
|
|
3834
|
-
const patchBody = {
|
|
3835
|
-
resolved_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3836
|
-
resolution
|
|
3837
|
-
};
|
|
3838
|
-
if (resolutionNote !== void 0) {
|
|
3839
|
-
patchBody.resolution_note = resolutionNote;
|
|
3840
|
-
}
|
|
3841
|
-
let res;
|
|
3842
|
-
try {
|
|
3843
|
-
res = await doFetch(url, {
|
|
3844
|
-
method: "PATCH",
|
|
3845
|
-
headers: {
|
|
3846
|
-
Authorization: `Bearer ${brainJwt}`,
|
|
3847
|
-
apikey: brainAnonKey,
|
|
3848
|
-
"Content-Type": "application/json",
|
|
3849
|
-
Accept: "application/json",
|
|
3850
|
-
Prefer: "return=minimal"
|
|
3851
|
-
},
|
|
3852
|
-
body: JSON.stringify(patchBody)
|
|
3853
|
-
});
|
|
3854
|
-
} catch (err) {
|
|
3855
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
3856
|
-
return { ok: false, reason: `network_error:${msg}` };
|
|
3857
|
-
}
|
|
3858
|
-
if (res.status === 401 || res.status === 403) {
|
|
3859
|
-
return { ok: false, reason: "brain_auth_misconfig" };
|
|
3860
|
-
}
|
|
3861
|
-
if (res.status >= 500) {
|
|
3862
|
-
return { ok: false, reason: "brain_unavailable" };
|
|
3863
|
-
}
|
|
3864
|
-
if (!res.ok) {
|
|
3865
|
-
return { ok: false, reason: `patch_failed:${res.status}` };
|
|
3866
|
-
}
|
|
3867
|
-
return { ok: true };
|
|
3932
|
+
async function _testWaitForMeasuredFailureRefresh() {
|
|
3933
|
+
const pending = Array.from(pendingRefreshes2.values());
|
|
3934
|
+
if (pending.length > 0) await Promise.all(pending);
|
|
3868
3935
|
}
|
|
3869
3936
|
|
|
3870
|
-
// src/
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
function shouldFirePromoteReady(finding, resolvedPrimary) {
|
|
3876
|
-
if (finding.currentModel !== resolvedPrimary) return false;
|
|
3877
|
-
if (finding.judgePassRate < PROMOTE_READY_THRESHOLDS.minPassRate) return false;
|
|
3878
|
-
if (finding.judgeAvgScore < PROMOTE_READY_THRESHOLDS.minAvgScore) return false;
|
|
3879
|
-
return true;
|
|
3937
|
+
// src/promotions-brain.ts
|
|
3938
|
+
function isRawPromotionRow(x) {
|
|
3939
|
+
if (!x || typeof x !== "object") return false;
|
|
3940
|
+
const r = x;
|
|
3941
|
+
return (typeof r.id === "number" || typeof r.id === "string") && typeof r.intent_archetype === "string" && typeof r.promoted_model === "string" && typeof r.incumbent_model === "string";
|
|
3880
3942
|
}
|
|
3881
|
-
function
|
|
3882
|
-
if (
|
|
3883
|
-
if (
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
if (/^gemini-.*-pro/.test(modelId)) return "gemini-pro";
|
|
3888
|
-
if (/^deepseek-.*-pro/.test(modelId)) return "deepseek-reasoner";
|
|
3889
|
-
if (modelId.startsWith("deepseek-")) return "deepseek-chat";
|
|
3890
|
-
if (modelId.startsWith("gpt-")) return "openai-gpt";
|
|
3943
|
+
function coerceId(v) {
|
|
3944
|
+
if (typeof v === "number") return Number.isFinite(v) ? v : null;
|
|
3945
|
+
if (typeof v === "string") {
|
|
3946
|
+
const n = Number(v);
|
|
3947
|
+
return Number.isFinite(n) ? n : null;
|
|
3948
|
+
}
|
|
3891
3949
|
return null;
|
|
3892
3950
|
}
|
|
3893
|
-
function
|
|
3894
|
-
if (!isPromoteReadyBrainActive()) return [];
|
|
3895
|
-
if (!ctx.appId) return [];
|
|
3896
|
-
if (!ctx.resolvedPrimary) return [];
|
|
3897
|
-
const family = deriveFamilyLocal(ctx.resolvedPrimary);
|
|
3898
|
-
if (!family) return [];
|
|
3899
|
-
const findings = loadPromoteReadyFindings({
|
|
3900
|
-
appId: ctx.appId,
|
|
3901
|
-
archetype: ctx.archetype,
|
|
3902
|
-
family
|
|
3903
|
-
});
|
|
3904
|
-
if (findings.length === 0) return [];
|
|
3905
|
-
const qualifying = findings.filter(
|
|
3906
|
-
(f) => shouldFirePromoteReady(f, ctx.resolvedPrimary)
|
|
3907
|
-
);
|
|
3908
|
-
if (qualifying.length === 0) return [];
|
|
3909
|
-
qualifying.sort((a, b) => {
|
|
3910
|
-
if (a.judgeAvgScore !== b.judgeAvgScore) {
|
|
3911
|
-
return b.judgeAvgScore - a.judgeAvgScore;
|
|
3912
|
-
}
|
|
3913
|
-
return b.judgePassRate - a.judgePassRate;
|
|
3914
|
-
});
|
|
3915
|
-
const top = qualifying[0];
|
|
3916
|
-
const pctPass = Math.round(top.judgePassRate * 100);
|
|
3917
|
-
const score = top.judgeAvgScore.toFixed(2);
|
|
3918
|
-
let costClause = "";
|
|
3919
|
-
if (top.costDeltaPct !== null) {
|
|
3920
|
-
const sign = top.costDeltaPct < 0 ? "cheaper" : "more expensive";
|
|
3921
|
-
const magnitude = Math.abs(top.costDeltaPct * 100).toFixed(1);
|
|
3922
|
-
costClause = `, cost ${magnitude}% ${sign}`;
|
|
3923
|
-
}
|
|
3924
|
-
const message = `Probe found ${top.candidateModel} produces equivalent-or-better outputs vs ${top.currentModel} on ${top.sampleN} recent ${top.archetype} prompts (pass rate ${pctPass}%, avg score ${score}/5${costClause}). Consider promoting via markPromoteReadyHandled.`;
|
|
3925
|
-
return [
|
|
3926
|
-
{
|
|
3927
|
-
level: "info",
|
|
3928
|
-
code: "promote-ready",
|
|
3929
|
-
message,
|
|
3930
|
-
suggestion: `Migrate ${top.archetype} traffic from ${top.currentModel} to ${top.candidateModel}, then call markPromoteReadyHandled({ appId, archetype: '${top.archetype}', family: '${top.family}', resolution: 'promoted' }) to silence this advisory.`,
|
|
3931
|
-
// alpha.36 architectural field — not a no-ai-needed case.
|
|
3932
|
-
recommendedArchitecture: void 0,
|
|
3933
|
-
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
3934
|
-
}
|
|
3935
|
-
];
|
|
3936
|
-
}
|
|
3937
|
-
|
|
3938
|
-
// src/advisor-rules/consumer-on-stale-model.ts
|
|
3939
|
-
function isStaleStatus(v) {
|
|
3940
|
-
return v === "legacy" || v === "deprecated";
|
|
3941
|
-
}
|
|
3942
|
-
function asString(v) {
|
|
3943
|
-
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
3944
|
-
}
|
|
3945
|
-
function mapRowsToFindings3(rows) {
|
|
3951
|
+
function mapRowsToPromotions(rows) {
|
|
3946
3952
|
const out = [];
|
|
3947
|
-
for (const
|
|
3948
|
-
if (!
|
|
3949
|
-
const
|
|
3950
|
-
|
|
3951
|
-
const
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
};
|
|
3969
|
-
const suggestion = asString(r.suggestion);
|
|
3970
|
-
if (suggestion) row.suggestion = suggestion;
|
|
3971
|
-
if (typeof r.observation_count === "number" && Number.isFinite(r.observation_count)) {
|
|
3972
|
-
row.observationCount = r.observation_count;
|
|
3973
|
-
}
|
|
3974
|
-
out.push(row);
|
|
3953
|
+
for (const row of rows) {
|
|
3954
|
+
if (!isRawPromotionRow(row)) continue;
|
|
3955
|
+
const id = coerceId(row.id);
|
|
3956
|
+
if (id === null) continue;
|
|
3957
|
+
const mode = row.mode === "strategy" ? "strategy" : row.mode === "downswap" || row.mode === void 0 ? "downswap" : null;
|
|
3958
|
+
if (mode === null) continue;
|
|
3959
|
+
out.push({
|
|
3960
|
+
id,
|
|
3961
|
+
archetype: row.intent_archetype,
|
|
3962
|
+
mode,
|
|
3963
|
+
strategy: typeof row.strategy === "string" ? row.strategy : null,
|
|
3964
|
+
promotedModel: row.promoted_model,
|
|
3965
|
+
incumbentModel: row.incumbent_model,
|
|
3966
|
+
evalRunId: coerceId(row.eval_run_id ?? null),
|
|
3967
|
+
suppressQualityGate: row.suppress_quality_gate === true,
|
|
3968
|
+
promotedAt: typeof row.promoted_at === "string" ? row.promoted_at : "",
|
|
3969
|
+
// Pre-.78 endpoints serve no status column and only active rows —
|
|
3970
|
+
// defaulting to 'active' is exact, not optimistic.
|
|
3971
|
+
status: row.status === "rolled_back" ? "rolled_back" : "active",
|
|
3972
|
+
...typeof row.rolled_back_at === "string" ? { rolledBackAt: row.rolled_back_at } : {},
|
|
3973
|
+
...typeof row.rollback_class === "string" ? { rollbackClass: row.rollback_class } : {}
|
|
3974
|
+
});
|
|
3975
3975
|
}
|
|
3976
3976
|
return out;
|
|
3977
3977
|
}
|
|
3978
3978
|
var snapshots3 = /* @__PURE__ */ new Map();
|
|
3979
3979
|
var runtime4;
|
|
3980
3980
|
var warnedOnce3 = false;
|
|
3981
|
-
var
|
|
3982
|
-
function
|
|
3981
|
+
var DEFAULT_PROMOTIONS_ENDPOINT = "https://kgauto-dashboard.vercel.app/api/kgauto-v2/promotions";
|
|
3982
|
+
function isAutoPromoteEnabledFromEnv(envSource) {
|
|
3983
|
+
const env = envSource ?? (typeof process !== "undefined" && process.env ? process.env : {});
|
|
3984
|
+
const raw = (env.KGAUTO_AUTO_PROMOTE ?? "").trim().toLowerCase();
|
|
3985
|
+
return raw === "1" || raw === "true";
|
|
3986
|
+
}
|
|
3987
|
+
function configurePromotionsBrain(rt) {
|
|
3988
|
+
runtime4 = rt;
|
|
3989
|
+
snapshots3.clear();
|
|
3990
|
+
warnedOnce3 = false;
|
|
3991
|
+
}
|
|
3992
|
+
function isPromotionsBrainActive() {
|
|
3983
3993
|
return runtime4 !== void 0;
|
|
3984
3994
|
}
|
|
3985
|
-
function
|
|
3995
|
+
function getApplicablePromotion(opts) {
|
|
3986
3996
|
const rt = runtime4;
|
|
3987
|
-
if (!rt) return
|
|
3997
|
+
if (!rt) return void 0;
|
|
3988
3998
|
const appId = opts.appId;
|
|
3989
|
-
if (!appId) return
|
|
3999
|
+
if (!appId || !opts.archetype || !opts.mode) return void 0;
|
|
3990
4000
|
let snap = snapshots3.get(appId);
|
|
3991
4001
|
if (!snap) {
|
|
3992
4002
|
snap = { data: [], expiresAt: 0, refreshing: false };
|
|
@@ -3998,11 +4008,31 @@ function getStaleModelFindings(opts) {
|
|
|
3998
4008
|
snap.refreshing = true;
|
|
3999
4009
|
void asyncRefresh4(rt, appId);
|
|
4000
4010
|
}
|
|
4001
|
-
|
|
4002
|
-
|
|
4011
|
+
return snap.data.find(
|
|
4012
|
+
(p) => p.status === "active" && p.archetype === opts.archetype && p.mode === opts.mode
|
|
4013
|
+
);
|
|
4014
|
+
}
|
|
4015
|
+
var ROLLBACK_SUPPRESSION_WINDOW_DAYS = 28;
|
|
4016
|
+
function getRecentRollback(opts) {
|
|
4017
|
+
const rt = runtime4;
|
|
4018
|
+
if (!rt) return void 0;
|
|
4019
|
+
if (!opts.appId || !opts.archetype || !opts.model) return void 0;
|
|
4020
|
+
let snap = snapshots3.get(opts.appId);
|
|
4021
|
+
if (!snap) {
|
|
4022
|
+
snap = { data: [], expiresAt: 0, refreshing: false };
|
|
4023
|
+
snapshots3.set(opts.appId, snap);
|
|
4003
4024
|
}
|
|
4004
|
-
|
|
4025
|
+
const now = opts.nowMs ?? Date.now();
|
|
4026
|
+
if (snap.expiresAt <= now && !snap.refreshing) {
|
|
4027
|
+
snap.refreshing = true;
|
|
4028
|
+
void asyncRefresh4(rt, opts.appId);
|
|
4029
|
+
}
|
|
4030
|
+
const windowMs = (opts.windowDays ?? ROLLBACK_SUPPRESSION_WINDOW_DAYS) * 864e5;
|
|
4031
|
+
return snap.data.find(
|
|
4032
|
+
(p) => p.status === "rolled_back" && p.archetype === opts.archetype && p.promotedModel === opts.model && typeof p.rolledBackAt === "string" && now - Date.parse(p.rolledBackAt) <= windowMs
|
|
4033
|
+
);
|
|
4005
4034
|
}
|
|
4035
|
+
var pendingRefreshes3 = /* @__PURE__ */ new Map();
|
|
4006
4036
|
async function asyncRefresh4(rt, appId) {
|
|
4007
4037
|
const promise = doRefresh4(rt, appId);
|
|
4008
4038
|
pendingRefreshes3.set(appId, promise);
|
|
@@ -4015,7 +4045,7 @@ async function asyncRefresh4(rt, appId) {
|
|
|
4015
4045
|
}
|
|
4016
4046
|
}
|
|
4017
4047
|
async function doRefresh4(rt, appId) {
|
|
4018
|
-
const url = `${rt.endpoint}?app_id=${encodeURIComponent(appId)}`;
|
|
4048
|
+
const url = `${rt.endpoint}?app_id=${encodeURIComponent(appId)}&with_rollbacks=1`;
|
|
4019
4049
|
let snap = snapshots3.get(appId);
|
|
4020
4050
|
if (!snap) {
|
|
4021
4051
|
snap = { data: [], expiresAt: 0, refreshing: false };
|
|
@@ -4024,11 +4054,11 @@ async function doRefresh4(rt, appId) {
|
|
|
4024
4054
|
try {
|
|
4025
4055
|
const res = await rt.fetchImpl(url, { method: "GET" });
|
|
4026
4056
|
if (!res.ok) {
|
|
4027
|
-
throw new Error(`
|
|
4057
|
+
throw new Error(`promotions ${res.status}: ${res.statusText}`);
|
|
4028
4058
|
}
|
|
4029
4059
|
const body = await res.json();
|
|
4030
4060
|
if (runtime4 !== rt) return;
|
|
4031
|
-
const rows = Array.isArray(body) ?
|
|
4061
|
+
const rows = Array.isArray(body) ? mapRowsToPromotions(body) : [];
|
|
4032
4062
|
snap.data = rows;
|
|
4033
4063
|
snap.expiresAt = Date.now() + rt.ttlMs;
|
|
4034
4064
|
snap.refreshing = false;
|
|
@@ -4044,847 +4074,905 @@ async function doRefresh4(rt, appId) {
|
|
|
4044
4074
|
}
|
|
4045
4075
|
function defaultOnError4(err) {
|
|
4046
4076
|
console.warn(
|
|
4047
|
-
"[kgauto]
|
|
4077
|
+
"[kgauto] promotions fetch failed (promotion boost inactive until next refresh):",
|
|
4048
4078
|
err
|
|
4049
4079
|
);
|
|
4050
4080
|
}
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
if (
|
|
4060
|
-
const ranked = [...findings].sort((a, b) => {
|
|
4061
|
-
if (a.staleStatus !== b.staleStatus) {
|
|
4062
|
-
return a.staleStatus === "deprecated" ? -1 : 1;
|
|
4063
|
-
}
|
|
4064
|
-
return a.staleModel.localeCompare(b.staleModel);
|
|
4065
|
-
});
|
|
4066
|
-
const top = ranked[0];
|
|
4067
|
-
const extraCount = findings.length - 1;
|
|
4068
|
-
const extraNote = extraCount > 0 ? ` (+ ${extraCount} more stale model${extraCount === 1 ? "" : "s"} for this archetype)` : "";
|
|
4069
|
-
return [
|
|
4070
|
-
{
|
|
4071
|
-
level: "warn",
|
|
4072
|
-
code: CONSUMER_ON_STALE_MODEL_RULE_CODE,
|
|
4073
|
-
message: `${top.message}${extraNote}`,
|
|
4074
|
-
suggestion: top.suggestion ?? `Migrate ${top.staleModel} \u2192 ${top.recommendedModel} for archetype "${top.archetype}". The newer model is the current latest in the "${top.family}" family; the stale one is ${top.staleStatus}.`,
|
|
4075
|
-
recommendationType: "model-swap",
|
|
4076
|
-
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
4077
|
-
}
|
|
4078
|
-
];
|
|
4081
|
+
function _testResetPromotions() {
|
|
4082
|
+
runtime4 = void 0;
|
|
4083
|
+
snapshots3.clear();
|
|
4084
|
+
pendingRefreshes3 = /* @__PURE__ */ new Map();
|
|
4085
|
+
warnedOnce3 = false;
|
|
4086
|
+
}
|
|
4087
|
+
async function _testWaitForPromotionsRefresh() {
|
|
4088
|
+
const pending = Array.from(pendingRefreshes3.values());
|
|
4089
|
+
if (pending.length > 0) await Promise.all(pending);
|
|
4079
4090
|
}
|
|
4080
4091
|
|
|
4081
|
-
// src/
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
{
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
reason: "Critique archetype rewards epistemic humility and explicit reasoning \u2014 reasoner-family default behavior. Sonnet/Opus over-confident on critique tasks; reasoners surface uncertainty productively.",
|
|
4093
|
-
costGuidance: "comparable or cheaper at current pricing"
|
|
4094
|
-
}
|
|
4095
|
-
]);
|
|
4096
|
-
function findBetterFit(archetype, currentFamily) {
|
|
4097
|
-
for (const fit of ARCHETYPE_FAMILY_FITS) {
|
|
4098
|
-
if (fit.archetype !== archetype) continue;
|
|
4099
|
-
if (fit.betterFitFamily === currentFamily) return null;
|
|
4100
|
-
return fit;
|
|
4092
|
+
// src/promote-ready-brain.ts
|
|
4093
|
+
function isRawPromoteReadyRow(x) {
|
|
4094
|
+
if (!x || typeof x !== "object") return false;
|
|
4095
|
+
const r = x;
|
|
4096
|
+
return typeof r.intent_archetype === "string" && typeof r.family === "string" && typeof r.candidate_model === "string" && typeof r.current_model === "string" && typeof r.detected_at === "string";
|
|
4097
|
+
}
|
|
4098
|
+
function coerceNumber(v) {
|
|
4099
|
+
if (typeof v === "number") return Number.isFinite(v) ? v : null;
|
|
4100
|
+
if (typeof v === "string") {
|
|
4101
|
+
const n = Number(v);
|
|
4102
|
+
return Number.isFinite(n) ? n : null;
|
|
4101
4103
|
}
|
|
4102
4104
|
return null;
|
|
4103
4105
|
}
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
const
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
if (
|
|
4112
|
-
|
|
4106
|
+
function mapRowsToFindings2(rows) {
|
|
4107
|
+
const out = [];
|
|
4108
|
+
for (const row of rows) {
|
|
4109
|
+
if (!isRawPromoteReadyRow(row)) continue;
|
|
4110
|
+
const sampleN = coerceNumber(row.sample_n);
|
|
4111
|
+
const passRate = coerceNumber(row.judge_pass_rate);
|
|
4112
|
+
const avgScore = coerceNumber(row.judge_avg_score);
|
|
4113
|
+
if (sampleN === null || passRate === null || avgScore === null) continue;
|
|
4114
|
+
out.push({
|
|
4115
|
+
archetype: row.intent_archetype,
|
|
4116
|
+
family: row.family,
|
|
4117
|
+
candidateModel: row.candidate_model,
|
|
4118
|
+
currentModel: row.current_model,
|
|
4119
|
+
sampleN,
|
|
4120
|
+
judgePassRate: passRate,
|
|
4121
|
+
judgeAvgScore: avgScore,
|
|
4122
|
+
costDeltaPct: coerceNumber(row.cost_delta_pct),
|
|
4123
|
+
detectedAt: row.detected_at
|
|
4124
|
+
});
|
|
4113
4125
|
}
|
|
4114
|
-
return
|
|
4126
|
+
return out;
|
|
4115
4127
|
}
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
if (profile.status !== "current") continue;
|
|
4122
|
-
if (profile.active === false) continue;
|
|
4123
|
-
candidates.push(profile.id);
|
|
4124
|
-
if (candidates.length >= 3) break;
|
|
4125
|
-
}
|
|
4126
|
-
return candidates;
|
|
4128
|
+
var snapshots4 = /* @__PURE__ */ new Map();
|
|
4129
|
+
var runtime5;
|
|
4130
|
+
var warnedOnce4 = false;
|
|
4131
|
+
function isPromoteReadyBrainActive() {
|
|
4132
|
+
return runtime5 !== void 0;
|
|
4127
4133
|
}
|
|
4128
|
-
function
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
if (!
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
const
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4134
|
+
function loadPromoteReadyFindings(opts) {
|
|
4135
|
+
const rt = runtime5;
|
|
4136
|
+
if (!rt) return [];
|
|
4137
|
+
const appId = opts.appId;
|
|
4138
|
+
if (!appId) return [];
|
|
4139
|
+
let snap = snapshots4.get(appId);
|
|
4140
|
+
if (!snap) {
|
|
4141
|
+
snap = { data: [], expiresAt: 0, refreshing: false };
|
|
4142
|
+
snapshots4.set(appId, snap);
|
|
4143
|
+
}
|
|
4144
|
+
const now = Date.now();
|
|
4145
|
+
const stale = snap.expiresAt <= now;
|
|
4146
|
+
if (stale && !snap.refreshing) {
|
|
4147
|
+
snap.refreshing = true;
|
|
4148
|
+
void asyncRefresh5(rt, appId);
|
|
4149
|
+
}
|
|
4150
|
+
let rows = snap.data;
|
|
4151
|
+
if (opts.archetype) {
|
|
4152
|
+
rows = rows.filter((f) => f.archetype === opts.archetype);
|
|
4153
|
+
}
|
|
4154
|
+
if (opts.family) {
|
|
4155
|
+
rows = rows.filter((f) => f.family === opts.family);
|
|
4156
|
+
}
|
|
4157
|
+
return rows;
|
|
4158
|
+
}
|
|
4159
|
+
var pendingRefreshes4 = /* @__PURE__ */ new Map();
|
|
4160
|
+
async function asyncRefresh5(rt, appId) {
|
|
4161
|
+
const promise = doRefresh5(rt, appId);
|
|
4162
|
+
pendingRefreshes4.set(appId, promise);
|
|
4163
|
+
try {
|
|
4164
|
+
await promise;
|
|
4165
|
+
} finally {
|
|
4166
|
+
if (pendingRefreshes4.get(appId) === promise) {
|
|
4167
|
+
pendingRefreshes4.delete(appId);
|
|
4148
4168
|
}
|
|
4149
|
-
|
|
4169
|
+
}
|
|
4150
4170
|
}
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4171
|
+
async function doRefresh5(rt, appId) {
|
|
4172
|
+
const url = `${rt.endpoint}?app_id=${encodeURIComponent(appId)}`;
|
|
4173
|
+
let snap = snapshots4.get(appId);
|
|
4174
|
+
if (!snap) {
|
|
4175
|
+
snap = { data: [], expiresAt: 0, refreshing: false };
|
|
4176
|
+
snapshots4.set(appId, snap);
|
|
4177
|
+
}
|
|
4178
|
+
try {
|
|
4179
|
+
const res = await rt.fetchImpl(url, { method: "GET" });
|
|
4180
|
+
if (!res.ok) {
|
|
4181
|
+
throw new Error(`promote-ready ${res.status}: ${res.statusText}`);
|
|
4182
|
+
}
|
|
4183
|
+
const body = await res.json();
|
|
4184
|
+
if (runtime5 !== rt) return;
|
|
4185
|
+
const rows = Array.isArray(body) ? mapRowsToFindings2(body) : [];
|
|
4186
|
+
snap.data = rows;
|
|
4187
|
+
snap.expiresAt = Date.now() + rt.ttlMs;
|
|
4188
|
+
snap.refreshing = false;
|
|
4189
|
+
} catch (err) {
|
|
4190
|
+
if (runtime5 !== rt) return;
|
|
4191
|
+
snap.refreshing = false;
|
|
4192
|
+
snap.expiresAt = Date.now() + rt.ttlMs;
|
|
4193
|
+
if (!warnedOnce4) {
|
|
4194
|
+
warnedOnce4 = true;
|
|
4195
|
+
(rt.onError ?? defaultOnError5)(err);
|
|
4196
|
+
}
|
|
4197
|
+
}
|
|
4162
4198
|
}
|
|
4163
|
-
function
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4199
|
+
function defaultOnError5(err) {
|
|
4200
|
+
console.warn(
|
|
4201
|
+
"[kgauto] promote-ready fetch failed (using empty fallback):",
|
|
4202
|
+
err
|
|
4203
|
+
);
|
|
4204
|
+
}
|
|
4205
|
+
function resolveFetchImpl(injected) {
|
|
4206
|
+
return injected ?? ((...args) => globalThis.fetch(...args));
|
|
4207
|
+
}
|
|
4208
|
+
function normalizeEndpoint(endpoint) {
|
|
4209
|
+
return endpoint.replace(/\/+$/, "");
|
|
4210
|
+
}
|
|
4211
|
+
async function markPromoteReadyHandled(opts) {
|
|
4212
|
+
const {
|
|
4213
|
+
appId,
|
|
4214
|
+
archetype,
|
|
4215
|
+
family,
|
|
4216
|
+
resolution,
|
|
4217
|
+
resolutionNote,
|
|
4218
|
+
brainEndpoint,
|
|
4219
|
+
brainJwt,
|
|
4220
|
+
brainAnonKey,
|
|
4221
|
+
fetch: injectedFetch
|
|
4222
|
+
} = opts;
|
|
4223
|
+
if (!appId) return { ok: false, reason: "app_id_required" };
|
|
4224
|
+
if (!archetype) return { ok: false, reason: "archetype_required" };
|
|
4225
|
+
if (!family) return { ok: false, reason: "family_required" };
|
|
4226
|
+
if (resolution !== "promoted" && resolution !== "declined" && resolution !== "still-evaluating") {
|
|
4227
|
+
return { ok: false, reason: "resolution_invalid" };
|
|
4174
4228
|
}
|
|
4175
|
-
|
|
4176
|
-
|
|
4229
|
+
const doFetch = resolveFetchImpl(injectedFetch);
|
|
4230
|
+
const base = normalizeEndpoint(brainEndpoint);
|
|
4231
|
+
const url = `${base}/rest/v1/promote_ready_findings?app_id=eq.${encodeURIComponent(appId)}&intent_archetype=eq.${encodeURIComponent(archetype)}&family=eq.${encodeURIComponent(family)}&resolved_at=is.null`;
|
|
4232
|
+
const patchBody = {
|
|
4233
|
+
resolved_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4234
|
+
resolution
|
|
4235
|
+
};
|
|
4236
|
+
if (resolutionNote !== void 0) {
|
|
4237
|
+
patchBody.resolution_note = resolutionNote;
|
|
4177
4238
|
}
|
|
4178
|
-
|
|
4179
|
-
|
|
4239
|
+
let res;
|
|
4240
|
+
try {
|
|
4241
|
+
res = await doFetch(url, {
|
|
4242
|
+
method: "PATCH",
|
|
4243
|
+
headers: {
|
|
4244
|
+
Authorization: `Bearer ${brainJwt}`,
|
|
4245
|
+
apikey: brainAnonKey,
|
|
4246
|
+
"Content-Type": "application/json",
|
|
4247
|
+
Accept: "application/json",
|
|
4248
|
+
Prefer: "return=minimal"
|
|
4249
|
+
},
|
|
4250
|
+
body: JSON.stringify(patchBody)
|
|
4251
|
+
});
|
|
4252
|
+
} catch (err) {
|
|
4253
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4254
|
+
return { ok: false, reason: `network_error:${msg}` };
|
|
4180
4255
|
}
|
|
4181
|
-
if (
|
|
4182
|
-
|
|
4183
|
-
...advisorRulePromoteReady({
|
|
4184
|
-
appId: ir.appId,
|
|
4185
|
-
archetype: ir.intent.archetype,
|
|
4186
|
-
resolvedPrimary: profile.id
|
|
4187
|
-
})
|
|
4188
|
-
);
|
|
4189
|
-
out.push(...advisorRuleConsumerOnStaleModel(ir));
|
|
4256
|
+
if (res.status === 401 || res.status === 403) {
|
|
4257
|
+
return { ok: false, reason: "brain_auth_misconfig" };
|
|
4190
4258
|
}
|
|
4191
|
-
if (
|
|
4192
|
-
|
|
4193
|
-
...advisorRuleCrossFamilyFit({
|
|
4194
|
-
archetype: ir.intent.archetype,
|
|
4195
|
-
resolvedPrimary: profile.id
|
|
4196
|
-
})
|
|
4197
|
-
);
|
|
4259
|
+
if (res.status >= 500) {
|
|
4260
|
+
return { ok: false, reason: "brain_unavailable" };
|
|
4198
4261
|
}
|
|
4199
|
-
|
|
4200
|
-
}
|
|
4201
|
-
function translatorClearedToolCallCliff(phase2) {
|
|
4202
|
-
const rewrites = phase2?.sectionRewritesApplied;
|
|
4203
|
-
if (!rewrites || rewrites.length === 0) return false;
|
|
4204
|
-
for (const rw of rewrites) {
|
|
4205
|
-
if (rw.kind === "tool_call_contract") return true;
|
|
4262
|
+
if (!res.ok) {
|
|
4263
|
+
return { ok: false, reason: `patch_failed:${res.status}` };
|
|
4206
4264
|
}
|
|
4207
|
-
return
|
|
4265
|
+
return { ok: true };
|
|
4208
4266
|
}
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4267
|
+
|
|
4268
|
+
// src/advisor-rules/promote-ready.ts
|
|
4269
|
+
var PROMOTE_READY_THRESHOLDS = {
|
|
4270
|
+
minPassRate: 0.8,
|
|
4271
|
+
minAvgScore: 4
|
|
4272
|
+
};
|
|
4273
|
+
function shouldFirePromoteReady(finding, resolvedPrimary) {
|
|
4274
|
+
if (finding.currentModel !== resolvedPrimary) return false;
|
|
4275
|
+
if (finding.judgePassRate < PROMOTE_READY_THRESHOLDS.minPassRate) return false;
|
|
4276
|
+
if (finding.judgeAvgScore < PROMOTE_READY_THRESHOLDS.minAvgScore) return false;
|
|
4277
|
+
return true;
|
|
4278
|
+
}
|
|
4279
|
+
function deriveFamilyLocal(modelId) {
|
|
4280
|
+
if (modelId.startsWith("claude-opus-")) return "claude-opus";
|
|
4281
|
+
if (modelId.startsWith("claude-sonnet-")) return "claude-sonnet";
|
|
4282
|
+
if (modelId.startsWith("claude-haiku-")) return "claude-haiku";
|
|
4283
|
+
if (/^gemini-.*-flash-lite/.test(modelId)) return "gemini-flash-lite";
|
|
4284
|
+
if (/^gemini-.*-flash/.test(modelId)) return "gemini-flash";
|
|
4285
|
+
if (/^gemini-.*-pro/.test(modelId)) return "gemini-pro";
|
|
4286
|
+
if (/^deepseek-.*-pro/.test(modelId)) return "deepseek-reasoner";
|
|
4287
|
+
if (modelId.startsWith("deepseek-")) return "deepseek-chat";
|
|
4288
|
+
if (modelId.startsWith("gpt-")) return "openai-gpt";
|
|
4289
|
+
return null;
|
|
4290
|
+
}
|
|
4291
|
+
function advisorRulePromoteReady(ctx) {
|
|
4292
|
+
if (!isPromoteReadyBrainActive()) return [];
|
|
4293
|
+
if (!ctx.appId) return [];
|
|
4294
|
+
if (!ctx.resolvedPrimary) return [];
|
|
4295
|
+
const family = deriveFamilyLocal(ctx.resolvedPrimary);
|
|
4296
|
+
if (!family) return [];
|
|
4297
|
+
const findings = loadPromoteReadyFindings({
|
|
4298
|
+
appId: ctx.appId,
|
|
4299
|
+
archetype: ctx.archetype,
|
|
4300
|
+
family
|
|
4301
|
+
});
|
|
4302
|
+
if (findings.length === 0) return [];
|
|
4303
|
+
const qualifying = findings.filter(
|
|
4304
|
+
(f) => shouldFirePromoteReady(f, ctx.resolvedPrimary)
|
|
4305
|
+
);
|
|
4306
|
+
if (qualifying.length === 0) return [];
|
|
4307
|
+
qualifying.sort((a, b) => {
|
|
4308
|
+
if (a.judgeAvgScore !== b.judgeAvgScore) {
|
|
4309
|
+
return b.judgeAvgScore - a.judgeAvgScore;
|
|
4310
|
+
}
|
|
4311
|
+
return b.judgePassRate - a.judgePassRate;
|
|
4312
|
+
});
|
|
4313
|
+
const top = qualifying[0];
|
|
4314
|
+
const pctPass = Math.round(top.judgePassRate * 100);
|
|
4315
|
+
const score = top.judgeAvgScore.toFixed(2);
|
|
4316
|
+
let costClause = "";
|
|
4317
|
+
if (top.costDeltaPct !== null) {
|
|
4318
|
+
const sign = top.costDeltaPct < 0 ? "cheaper" : "more expensive";
|
|
4319
|
+
const magnitude = Math.abs(top.costDeltaPct * 100).toFixed(1);
|
|
4320
|
+
costClause = `, cost ${magnitude}% ${sign}`;
|
|
4321
|
+
}
|
|
4322
|
+
const message = `Probe found ${top.candidateModel} produces equivalent-or-better outputs vs ${top.currentModel} on ${top.sampleN} recent ${top.archetype} prompts (pass rate ${pctPass}%, avg score ${score}/5${costClause}). Consider promoting via markPromoteReadyHandled.`;
|
|
4215
4323
|
return [
|
|
4216
4324
|
{
|
|
4217
|
-
level: "
|
|
4218
|
-
code: "
|
|
4219
|
-
message
|
|
4220
|
-
suggestion:
|
|
4325
|
+
level: "info",
|
|
4326
|
+
code: "promote-ready",
|
|
4327
|
+
message,
|
|
4328
|
+
suggestion: `Migrate ${top.archetype} traffic from ${top.currentModel} to ${top.candidateModel}, then call markPromoteReadyHandled({ appId, archetype: '${top.archetype}', family: '${top.family}', resolution: 'promoted' }) to silence this advisory.`,
|
|
4329
|
+
// alpha.36 architectural field — not a no-ai-needed case.
|
|
4330
|
+
recommendedArchitecture: void 0,
|
|
4221
4331
|
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
4222
4332
|
}
|
|
4223
4333
|
];
|
|
4224
4334
|
}
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
if (!only || only.text.length <= 1e3) return [];
|
|
4230
|
-
return [
|
|
4231
|
-
{
|
|
4232
|
-
level: "info",
|
|
4233
|
-
code: "single-chunk-system",
|
|
4234
|
-
message: `System prompt is a single ${only.text.length}-char chunk. Splitting into NamedChunks (static role/persona vs dynamic context) gives the lowering pass a finer cache-marker boundary \u2014 only the static portion needs to be byte-stable for the cache to hit.`,
|
|
4235
|
-
suggestion: "Refactor the system builder to return an array of `PromptSection` shaped { id, text, cacheable?: boolean }. Static chunks (role, persona, tool policy) get `cacheable: true`; dynamic ones (current context, today's date) don't.",
|
|
4236
|
-
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
4237
|
-
}
|
|
4238
|
-
];
|
|
4335
|
+
|
|
4336
|
+
// src/advisor-rules/consumer-on-stale-model.ts
|
|
4337
|
+
function isStaleStatus(v) {
|
|
4338
|
+
return v === "legacy" || v === "deprecated";
|
|
4239
4339
|
}
|
|
4240
|
-
function
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
"
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
message: `${toolsKept} tools kept after the relevance pass for archetype="${ir.intent.archetype}" (consumer declared ${ir.tools.length}). This archetype is short-output and rarely needs more than 3 tools; each tool definition eats ~350 tokens of context budget.`,
|
|
4257
|
-
suggestion: "Tighten `relevanceByIntent: { [archetype]: 0..1 }` per ToolDefinition. Tools below `toolRelevanceThreshold` (default 0.2) get dropped. Without `relevanceByIntent`, every tool defaults to neutral (0.5) and stays.",
|
|
4258
|
-
docsUrl: "https://github.com/stue/kgauto/blob/main/v2/README.md#tools"
|
|
4340
|
+
function asString(v) {
|
|
4341
|
+
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
4342
|
+
}
|
|
4343
|
+
function mapRowsToFindings3(rows) {
|
|
4344
|
+
const out = [];
|
|
4345
|
+
for (const raw of rows) {
|
|
4346
|
+
if (!raw || typeof raw !== "object") continue;
|
|
4347
|
+
const r = raw;
|
|
4348
|
+
const archetype = asString(r.intent_archetype) ?? asString(r.applies_to_archetype);
|
|
4349
|
+
const staleModel = asString(r.stale_model) ?? asString(r.applies_to_model);
|
|
4350
|
+
const staleProvider = asString(r.stale_provider);
|
|
4351
|
+
const recommendedModel = asString(r.recommended_model);
|
|
4352
|
+
const family = asString(r.family);
|
|
4353
|
+
const message = asString(r.message);
|
|
4354
|
+
if (!archetype || !staleModel || !recommendedModel || !family || !message) {
|
|
4355
|
+
continue;
|
|
4259
4356
|
}
|
|
4260
|
-
|
|
4357
|
+
if (!isStaleStatus(r.stale_status)) continue;
|
|
4358
|
+
const row = {
|
|
4359
|
+
archetype,
|
|
4360
|
+
staleModel,
|
|
4361
|
+
staleProvider: staleProvider ?? "unknown",
|
|
4362
|
+
staleStatus: r.stale_status,
|
|
4363
|
+
recommendedModel,
|
|
4364
|
+
family,
|
|
4365
|
+
message
|
|
4366
|
+
};
|
|
4367
|
+
const suggestion = asString(r.suggestion);
|
|
4368
|
+
if (suggestion) row.suggestion = suggestion;
|
|
4369
|
+
if (typeof r.observation_count === "number" && Number.isFinite(r.observation_count)) {
|
|
4370
|
+
row.observationCount = r.observation_count;
|
|
4371
|
+
}
|
|
4372
|
+
out.push(row);
|
|
4373
|
+
}
|
|
4374
|
+
return out;
|
|
4261
4375
|
}
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4376
|
+
var snapshots5 = /* @__PURE__ */ new Map();
|
|
4377
|
+
var runtime6;
|
|
4378
|
+
var warnedOnce5 = false;
|
|
4379
|
+
var pendingRefreshes5 = /* @__PURE__ */ new Map();
|
|
4380
|
+
function isStaleModelFindingsBrainActive() {
|
|
4381
|
+
return runtime6 !== void 0;
|
|
4382
|
+
}
|
|
4383
|
+
function getStaleModelFindings(opts) {
|
|
4384
|
+
const rt = runtime6;
|
|
4385
|
+
if (!rt) return [];
|
|
4386
|
+
const appId = opts.appId;
|
|
4387
|
+
if (!appId) return [];
|
|
4388
|
+
let snap = snapshots5.get(appId);
|
|
4389
|
+
if (!snap) {
|
|
4390
|
+
snap = { data: [], expiresAt: 0, refreshing: false };
|
|
4391
|
+
snapshots5.set(appId, snap);
|
|
4267
4392
|
}
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4393
|
+
const now = Date.now();
|
|
4394
|
+
const stale = snap.expiresAt <= now;
|
|
4395
|
+
if (stale && !snap.refreshing) {
|
|
4396
|
+
snap.refreshing = true;
|
|
4397
|
+
void asyncRefresh6(rt, appId);
|
|
4398
|
+
}
|
|
4399
|
+
if (opts.archetype) {
|
|
4400
|
+
return snap.data.filter((f) => f.archetype === opts.archetype);
|
|
4401
|
+
}
|
|
4402
|
+
return snap.data;
|
|
4277
4403
|
}
|
|
4278
|
-
function
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
message: `\`ir.models\` has length 1 (only "${only}") and posture is not 'locked'. A single-model chain has no safety net \u2014 the first 429 / 5xx / cliff hits the user as a failure. Master plan \xA71.2 closes the reliability gap with a 2-step minimum.`,
|
|
4287
|
-
suggestion: "Use `getDefaultFallbackChain({ archetype: ir.intent.archetype, primary: '" + only + "', posture: 'preferred' })` for a user-anchored chain, or `getDefaultFallbackChain({ archetype, posture: 'open' })` for library-picked. If single-model is intentional (compliance/brand promise), set `policy.posture = 'locked'` to silence this rule.",
|
|
4288
|
-
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#single-model-array"
|
|
4404
|
+
async function asyncRefresh6(rt, appId) {
|
|
4405
|
+
const promise = doRefresh6(rt, appId);
|
|
4406
|
+
pendingRefreshes5.set(appId, promise);
|
|
4407
|
+
try {
|
|
4408
|
+
await promise;
|
|
4409
|
+
} finally {
|
|
4410
|
+
if (pendingRefreshes5.get(appId) === promise) {
|
|
4411
|
+
pendingRefreshes5.delete(appId);
|
|
4289
4412
|
}
|
|
4290
|
-
|
|
4413
|
+
}
|
|
4291
4414
|
}
|
|
4292
|
-
function
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
if (!chosenHasRoomToGrow) return [];
|
|
4299
|
-
let bestAlt = null;
|
|
4300
|
-
for (const altId of phase2.fallbackChain) {
|
|
4301
|
-
const altProfile = phase2.profileResolver(altId);
|
|
4302
|
-
if (!altProfile) continue;
|
|
4303
|
-
if (altProfile.id === profile.id) continue;
|
|
4304
|
-
const altScore = getArchetypePerfScore(altProfile.id, archetype);
|
|
4305
|
-
if (altScore.score < QUALITY_FLOOR_FOR_RECOMMENDATION) continue;
|
|
4306
|
-
if (altScore.score < chosenScore.score) continue;
|
|
4307
|
-
if (altProfile.costInputPer1m >= profile.costInputPer1m) continue;
|
|
4308
|
-
if (!bestAlt || altScore.score > bestAlt.score.score || altScore.score === bestAlt.score.score && altProfile.costInputPer1m < bestAlt.profile.costInputPer1m) {
|
|
4309
|
-
bestAlt = { id: altId, profile: altProfile, score: altScore };
|
|
4310
|
-
}
|
|
4415
|
+
async function doRefresh6(rt, appId) {
|
|
4416
|
+
const url = `${rt.endpoint}?app_id=${encodeURIComponent(appId)}`;
|
|
4417
|
+
let snap = snapshots5.get(appId);
|
|
4418
|
+
if (!snap) {
|
|
4419
|
+
snap = { data: [], expiresAt: 0, refreshing: false };
|
|
4420
|
+
snapshots5.set(appId, snap);
|
|
4311
4421
|
}
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
const altGrounding = bestAlt.score.grounding === "measured" ? `archetypePerf.${archetype}=${bestAlt.score.score}, measured, n=${bestAlt.score.n}` : `archetypePerf.${archetype}=${bestAlt.score.score}, judgment`;
|
|
4317
|
-
return [
|
|
4318
|
-
{
|
|
4319
|
-
level: "warn",
|
|
4320
|
-
code: "cost-mismatched-archetype",
|
|
4321
|
-
message: `Cost-mismatched-archetype: target=${profile.id} (${chosenGrounding}) selected for ${archetype}. Alternative ${bestAlt.id} (${altGrounding}) is cheaper ($${bestAlt.profile.costInputPer1m}/$${bestAlt.profile.costOutputPer1m} vs $${profile.costInputPer1m}/$${profile.costOutputPer1m} per 1M) at equal-or-better quality.`,
|
|
4322
|
-
suggestion: `Consider declaring \`${bestAlt.id}\` as the primary model for this archetype, or relax to posture='open' to let kgauto select among the chain. If the chosen model is required for compliance/brand reasons, set \`policy.posture = 'locked'\` to silence this rule.`,
|
|
4323
|
-
recommendationType: profile.provider === bestAlt.profile.provider ? "tier-down" : "model-swap",
|
|
4324
|
-
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
4422
|
+
try {
|
|
4423
|
+
const res = await rt.fetchImpl(url, { method: "GET" });
|
|
4424
|
+
if (!res.ok) {
|
|
4425
|
+
throw new Error(`stale-model findings ${res.status}: ${res.statusText}`);
|
|
4325
4426
|
}
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
recommendationType: "prompt-fix",
|
|
4340
|
-
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
4427
|
+
const body = await res.json();
|
|
4428
|
+
if (runtime6 !== rt) return;
|
|
4429
|
+
const rows = Array.isArray(body) ? mapRowsToFindings3(body) : [];
|
|
4430
|
+
snap.data = rows;
|
|
4431
|
+
snap.expiresAt = Date.now() + rt.ttlMs;
|
|
4432
|
+
snap.refreshing = false;
|
|
4433
|
+
} catch (err) {
|
|
4434
|
+
if (runtime6 !== rt) return;
|
|
4435
|
+
snap.refreshing = false;
|
|
4436
|
+
snap.expiresAt = Date.now() + rt.ttlMs;
|
|
4437
|
+
if (!warnedOnce5) {
|
|
4438
|
+
warnedOnce5 = true;
|
|
4439
|
+
(rt.onError ?? defaultOnError6)(err);
|
|
4341
4440
|
}
|
|
4342
|
-
|
|
4441
|
+
}
|
|
4343
4442
|
}
|
|
4344
|
-
function
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
if (
|
|
4361
|
-
|
|
4443
|
+
function defaultOnError6(err) {
|
|
4444
|
+
console.warn(
|
|
4445
|
+
"[kgauto] stale-model findings fetch failed (using empty fallback):",
|
|
4446
|
+
err
|
|
4447
|
+
);
|
|
4448
|
+
}
|
|
4449
|
+
var CONSUMER_ON_STALE_MODEL_RULE_CODE = "consumer-on-stale-model";
|
|
4450
|
+
function advisorRuleConsumerOnStaleModel(ir) {
|
|
4451
|
+
if (!isStaleModelFindingsBrainActive()) return [];
|
|
4452
|
+
if (!ir.appId) return [];
|
|
4453
|
+
const findings = getStaleModelFindings({
|
|
4454
|
+
appId: ir.appId,
|
|
4455
|
+
archetype: ir.intent.archetype
|
|
4456
|
+
});
|
|
4457
|
+
if (findings.length === 0) return [];
|
|
4458
|
+
const ranked = [...findings].sort((a, b) => {
|
|
4459
|
+
if (a.staleStatus !== b.staleStatus) {
|
|
4460
|
+
return a.staleStatus === "deprecated" ? -1 : 1;
|
|
4362
4461
|
}
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
const
|
|
4462
|
+
return a.staleModel.localeCompare(b.staleModel);
|
|
4463
|
+
});
|
|
4464
|
+
const top = ranked[0];
|
|
4465
|
+
const extraCount = findings.length - 1;
|
|
4466
|
+
const extraNote = extraCount > 0 ? ` (+ ${extraCount} more stale model${extraCount === 1 ? "" : "s"} for this archetype)` : "";
|
|
4366
4467
|
return [
|
|
4367
4468
|
{
|
|
4368
4469
|
level: "warn",
|
|
4369
|
-
code:
|
|
4370
|
-
message:
|
|
4371
|
-
suggestion: `
|
|
4372
|
-
recommendationType: "
|
|
4470
|
+
code: CONSUMER_ON_STALE_MODEL_RULE_CODE,
|
|
4471
|
+
message: `${top.message}${extraNote}`,
|
|
4472
|
+
suggestion: top.suggestion ?? `Migrate ${top.staleModel} \u2192 ${top.recommendedModel} for archetype "${top.archetype}". The newer model is the current latest in the "${top.family}" family; the stale one is ${top.staleStatus}.`,
|
|
4473
|
+
recommendationType: "model-swap",
|
|
4373
4474
|
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
4374
4475
|
}
|
|
4375
4476
|
];
|
|
4376
4477
|
}
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4478
|
+
|
|
4479
|
+
// src/archetype-fits.ts
|
|
4480
|
+
var ARCHETYPE_FAMILY_FITS = Object.freeze([
|
|
4481
|
+
{
|
|
4482
|
+
archetype: "plan",
|
|
4483
|
+
betterFitFamily: "deepseek-reasoner",
|
|
4484
|
+
reason: "Plan archetype is reasoning-shaped (multi-step chains, hypothesis-and-check, sub-goal decomposition) \u2014 exactly where reasoner-family models excel. Sonnet/Opus produce plans but at higher cost; reasoners produce equivalent-or-better plans at 7-17x lower cost at current promo pricing (deepseek-v4-pro $0.435/$0.87 per 1M promo through 2026-05-31 vs sonnet $3/$15).",
|
|
4485
|
+
costGuidance: "substantially cheaper at current pricing (deepseek-v4-pro promo: ~7-17x cheaper than sonnet)"
|
|
4486
|
+
},
|
|
4487
|
+
{
|
|
4488
|
+
archetype: "critique",
|
|
4489
|
+
betterFitFamily: "deepseek-reasoner",
|
|
4490
|
+
reason: "Critique archetype rewards epistemic humility and explicit reasoning \u2014 reasoner-family default behavior. Sonnet/Opus over-confident on critique tasks; reasoners surface uncertainty productively.",
|
|
4491
|
+
costGuidance: "comparable or cheaper at current pricing"
|
|
4492
|
+
}
|
|
4493
|
+
]);
|
|
4494
|
+
function findBetterFit(archetype, currentFamily) {
|
|
4495
|
+
for (const fit of ARCHETYPE_FAMILY_FITS) {
|
|
4496
|
+
if (fit.archetype !== archetype) continue;
|
|
4497
|
+
if (fit.betterFitFamily === currentFamily) return null;
|
|
4498
|
+
return fit;
|
|
4395
4499
|
}
|
|
4500
|
+
return null;
|
|
4501
|
+
}
|
|
4502
|
+
|
|
4503
|
+
// src/advisor-rules/cross-family-fit.ts
|
|
4504
|
+
function familyHasCurrentActiveModel(family) {
|
|
4505
|
+
for (const profile of allProfiles()) {
|
|
4506
|
+
const profileFamily = profile.family ?? deriveFamilyFromModelId(profile.id);
|
|
4507
|
+
if (profileFamily !== family) continue;
|
|
4508
|
+
if (profile.status !== "current") continue;
|
|
4509
|
+
if (profile.active === false) continue;
|
|
4510
|
+
return true;
|
|
4511
|
+
}
|
|
4512
|
+
return false;
|
|
4513
|
+
}
|
|
4514
|
+
function listCandidatesInFamily(family) {
|
|
4515
|
+
const candidates = [];
|
|
4516
|
+
for (const profile of allProfiles()) {
|
|
4517
|
+
const profileFamily = profile.family ?? deriveFamilyFromModelId(profile.id);
|
|
4518
|
+
if (profileFamily !== family) continue;
|
|
4519
|
+
if (profile.status !== "current") continue;
|
|
4520
|
+
if (profile.active === false) continue;
|
|
4521
|
+
candidates.push(profile.id);
|
|
4522
|
+
if (candidates.length >= 3) break;
|
|
4523
|
+
}
|
|
4524
|
+
return candidates;
|
|
4525
|
+
}
|
|
4526
|
+
function advisorRuleCrossFamilyFit(ctx) {
|
|
4527
|
+
if (!ctx.resolvedPrimary) return [];
|
|
4528
|
+
const currentFamily = deriveFamilyFromModelId(ctx.resolvedPrimary);
|
|
4529
|
+
if (!currentFamily) return [];
|
|
4530
|
+
const fit = findBetterFit(ctx.archetype, currentFamily);
|
|
4531
|
+
if (!fit) return [];
|
|
4532
|
+
if (!familyHasCurrentActiveModel(fit.betterFitFamily)) return [];
|
|
4533
|
+
const candidates = listCandidatesInFamily(fit.betterFitFamily);
|
|
4534
|
+
if (candidates.length === 0) return [];
|
|
4535
|
+
const candidateStr = candidates.join(", ");
|
|
4536
|
+
const message = `Your ${currentFamily} call on ${ctx.archetype} could shift to ${fit.betterFitFamily} \u2014 typically better quality + ${fit.costGuidance}. Suggested candidates: ${candidateStr}.`;
|
|
4396
4537
|
return [
|
|
4397
4538
|
{
|
|
4398
|
-
level: "
|
|
4399
|
-
code: "
|
|
4400
|
-
|
|
4401
|
-
|
|
4539
|
+
level: "info",
|
|
4540
|
+
code: "cross-family-fit-candidate",
|
|
4541
|
+
ownership: "consumer-actionable",
|
|
4542
|
+
message,
|
|
4543
|
+
suggestion: `Swap the model literal in \`ir.models\` to one of: ${candidateStr}. Or call \`getRecommendedPrimary({ family: '${fit.betterFitFamily}', archetype: '${ctx.archetype}', fallback: { id: '${candidates[0]}', reason: 'cross-family-fit-recommendation' } })\` to let kgauto resolve to the current+active family member.`,
|
|
4402
4544
|
recommendationType: "model-swap",
|
|
4403
4545
|
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
4404
4546
|
}
|
|
4405
4547
|
];
|
|
4406
4548
|
}
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4549
|
+
|
|
4550
|
+
// src/advisor.ts
|
|
4551
|
+
var QUALITY_FLOOR_FOR_RECOMMENDATION = 6;
|
|
4552
|
+
var TIER_DOWN_COST_RATIO = 0.5;
|
|
4553
|
+
var COST_MISMATCHED_CHOSEN_SCORE_CEILING = 7;
|
|
4554
|
+
var PRODUCER_OWNED_RULE_CODES = Object.freeze(
|
|
4555
|
+
/* @__PURE__ */ new Set(["model-stale-evidence", "promote-ready"])
|
|
4556
|
+
);
|
|
4557
|
+
function deriveOwnership(code, selfDeclared) {
|
|
4558
|
+
if (selfDeclared) return selfDeclared;
|
|
4559
|
+
return PRODUCER_OWNED_RULE_CODES.has(code) ? "producer-owned" : "consumer-actionable";
|
|
4560
|
+
}
|
|
4561
|
+
function runAdvisor(ir, result, profile, policy, phase2) {
|
|
4562
|
+
const out = [];
|
|
4563
|
+
out.push(...detectCachingOff(ir, profile));
|
|
4564
|
+
out.push(...detectSingleChunkSystem(ir, profile));
|
|
4565
|
+
out.push(...detectToolBloat(ir, result));
|
|
4566
|
+
out.push(...detectHistoryUncached(ir, profile));
|
|
4567
|
+
out.push(...detectSingleModelArray(ir, policy));
|
|
4568
|
+
if (policy?.posture !== "locked") {
|
|
4569
|
+
out.push(...detectCostMismatchedArchetype(ir, profile, phase2));
|
|
4570
|
+
out.push(...detectModelStaleEvidence(ir, profile));
|
|
4571
|
+
out.push(...detectTierDown(ir, profile, phase2));
|
|
4572
|
+
}
|
|
4573
|
+
if (!translatorClearedToolCallCliff(phase2)) {
|
|
4574
|
+
out.push(...detectArchetypePerfFloorBreach(ir, profile));
|
|
4575
|
+
}
|
|
4576
|
+
if (policy?.posture !== "locked") {
|
|
4577
|
+
out.push(...detectStaleExclusionCandidate(ir));
|
|
4578
|
+
}
|
|
4579
|
+
if (policy?.posture !== "locked" && ir.appId) {
|
|
4580
|
+
out.push(
|
|
4581
|
+
...advisorRulePromoteReady({
|
|
4582
|
+
appId: ir.appId,
|
|
4583
|
+
archetype: ir.intent.archetype,
|
|
4584
|
+
resolvedPrimary: profile.id
|
|
4585
|
+
})
|
|
4586
|
+
);
|
|
4587
|
+
out.push(...advisorRuleConsumerOnStaleModel(ir));
|
|
4588
|
+
}
|
|
4589
|
+
if (policy?.posture !== "locked") {
|
|
4590
|
+
out.push(
|
|
4591
|
+
...advisorRuleCrossFamilyFit({
|
|
4592
|
+
archetype: ir.intent.archetype,
|
|
4593
|
+
resolvedPrimary: profile.id
|
|
4594
|
+
})
|
|
4595
|
+
);
|
|
4596
|
+
}
|
|
4597
|
+
return out;
|
|
4598
|
+
}
|
|
4599
|
+
function translatorClearedToolCallCliff(phase2) {
|
|
4600
|
+
const rewrites = phase2?.sectionRewritesApplied;
|
|
4601
|
+
if (!rewrites || rewrites.length === 0) return false;
|
|
4602
|
+
for (const rw of rewrites) {
|
|
4603
|
+
if (rw.kind === "tool_call_contract") return true;
|
|
4604
|
+
}
|
|
4605
|
+
return false;
|
|
4606
|
+
}
|
|
4607
|
+
function detectCachingOff(ir, profile) {
|
|
4608
|
+
if (profile.provider !== "anthropic") return [];
|
|
4609
|
+
const totalChars = ir.sections.reduce((s, sec) => s + sec.text.length, 0);
|
|
4610
|
+
if (totalChars < 2e3) return [];
|
|
4611
|
+
const anyCacheable = ir.sections.some((s) => s.cacheable === true);
|
|
4612
|
+
if (anyCacheable) return [];
|
|
4424
4613
|
return [
|
|
4425
4614
|
{
|
|
4426
|
-
level: "
|
|
4427
|
-
code: "
|
|
4428
|
-
message:
|
|
4429
|
-
suggestion:
|
|
4430
|
-
recommendationType: "tier-down",
|
|
4615
|
+
level: "warn",
|
|
4616
|
+
code: "caching-off-on-claude",
|
|
4617
|
+
message: `System prompt is ${totalChars} chars on Anthropic but no PromptSection has cacheable=true. Anthropic prompt caching cuts cached-prefix input cost by ~90% on subsequent calls; without it, every turn re-pays full price for the static system context.`,
|
|
4618
|
+
suggestion: "Mark stable system sections (role, persona, tool policy) with `cacheable: true`. The lowering pass concatenates cacheable sections into a single cache-controlled block before the dynamic ones.",
|
|
4431
4619
|
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
4432
4620
|
}
|
|
4433
4621
|
];
|
|
4434
4622
|
}
|
|
4435
|
-
function
|
|
4436
|
-
if (
|
|
4437
|
-
if (
|
|
4438
|
-
|
|
4623
|
+
function detectSingleChunkSystem(ir, profile) {
|
|
4624
|
+
if (profile.provider !== "anthropic") return [];
|
|
4625
|
+
if (ir.sections.length !== 1) return [];
|
|
4626
|
+
const only = ir.sections[0];
|
|
4627
|
+
if (!only || only.text.length <= 1e3) return [];
|
|
4628
|
+
return [
|
|
4629
|
+
{
|
|
4630
|
+
level: "info",
|
|
4631
|
+
code: "single-chunk-system",
|
|
4632
|
+
message: `System prompt is a single ${only.text.length}-char chunk. Splitting into NamedChunks (static role/persona vs dynamic context) gives the lowering pass a finer cache-marker boundary \u2014 only the static portion needs to be byte-stable for the cache to hit.`,
|
|
4633
|
+
suggestion: "Refactor the system builder to return an array of `PromptSection` shaped { id, text, cacheable?: boolean }. Static chunks (role, persona, tool policy) get `cacheable: true`; dynamic ones (current context, today's date) don't. NOTE: the lowering pass HOISTS cacheable sections ahead of dynamic ones on the Anthropic wire (prefix caching requires it) \u2014 if your prompt has a protected ordering (e.g. a voice/persona block that must precede boilerplate), splitting will reorder the compiled output; declining this advisory is then correct. Also: a cacheable block under ~1024 tokens gets NO cache_control marker (provider minimum), so marking small sections is inert, not harmful.",
|
|
4634
|
+
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
4635
|
+
}
|
|
4636
|
+
];
|
|
4439
4637
|
}
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
2. One extra signal: when a finding feels conclusive, check one more adjacent signal before stating it.
|
|
4460
|
-
3. Innocent explanation first: state the most plausible benign reading before alleging the alarming one.
|
|
4461
|
-
4. Label each claim: mark it observed, inferred, or assumed.
|
|
4462
|
-
5. A surfaced gap beats a guessed answer: flag what you cannot determine rather than fabricating past it.`;
|
|
4463
|
-
var RULE_DISCIPLINE_GATES_V1_STRUCTURED = "discipline-gates-v1-structured";
|
|
4464
|
-
var DISCIPLINE_GATES_V1_STRUCTURED_WITH_TOOLS = `Work through these gates at every judgment point, explicitly:
|
|
4465
|
-
1. Evidence before reasoning: cite what you observed before concluding from it.
|
|
4466
|
-
2. One extra signal: when a finding feels conclusive, check one more adjacent signal before stating it.
|
|
4467
|
-
3. Expand, don't guess: resolve a compressed or referenced item by looking it up rather than inferring its contents.
|
|
4468
|
-
4. Innocent explanation first: state the most plausible benign reading before alleging the alarming one.`;
|
|
4469
|
-
var DISCIPLINE_GATES_V1_STRUCTURED_NO_TOOLS = `Work through these gates at every judgment point, explicitly:
|
|
4470
|
-
1. Evidence before reasoning: cite what you observed before concluding from it.
|
|
4471
|
-
2. One extra signal: when a finding feels conclusive, check one more adjacent signal before stating it.
|
|
4472
|
-
3. Innocent explanation first: state the most plausible benign reading before alleging the alarming one.`;
|
|
4473
|
-
var DISCIPLINE_ELIGIBLE_ARCHETYPES = /* @__PURE__ */ new Set([
|
|
4474
|
-
"hunt",
|
|
4475
|
-
"summarize",
|
|
4476
|
-
"plan",
|
|
4477
|
-
"critique",
|
|
4478
|
-
"judge"
|
|
4479
|
-
]);
|
|
4480
|
-
function matchRule(kind, profile, archetype, ctx) {
|
|
4481
|
-
if (kind === "discipline_contract") {
|
|
4482
|
-
if (!DISCIPLINE_ELIGIBLE_ARCHETYPES.has(archetype)) return null;
|
|
4483
|
-
if (ctx.outputMode !== "text") {
|
|
4484
|
-
return {
|
|
4485
|
-
id: RULE_DISCIPLINE_GATES_V1_STRUCTURED,
|
|
4486
|
-
preamble: ctx.hasTools ? DISCIPLINE_GATES_V1_STRUCTURED_WITH_TOOLS : DISCIPLINE_GATES_V1_STRUCTURED_NO_TOOLS
|
|
4487
|
-
};
|
|
4638
|
+
function detectToolBloat(ir, result) {
|
|
4639
|
+
const SHORT_OUTPUT = /* @__PURE__ */ new Set([
|
|
4640
|
+
"classify",
|
|
4641
|
+
"extract",
|
|
4642
|
+
"summarize",
|
|
4643
|
+
"transform",
|
|
4644
|
+
"critique"
|
|
4645
|
+
]);
|
|
4646
|
+
if (!ir.tools || ir.tools.length === 0) return [];
|
|
4647
|
+
const toolsKept = result.diagnostics.toolsKept;
|
|
4648
|
+
if (toolsKept <= 10) return [];
|
|
4649
|
+
if (!SHORT_OUTPUT.has(ir.intent.archetype)) return [];
|
|
4650
|
+
return [
|
|
4651
|
+
{
|
|
4652
|
+
level: "warn",
|
|
4653
|
+
code: "tool-bloat",
|
|
4654
|
+
message: `${toolsKept} tools kept after the relevance pass for archetype="${ir.intent.archetype}" (consumer declared ${ir.tools.length}). This archetype is short-output and rarely needs more than 3 tools; each tool definition eats ~350 tokens of context budget.`,
|
|
4655
|
+
suggestion: "Tighten `relevanceByIntent: { [archetype]: 0..1 }` per ToolDefinition. Tools below `toolRelevanceThreshold` (default 0.2) get dropped. Without `relevanceByIntent`, every tool defaults to neutral (0.5) and stays.",
|
|
4656
|
+
docsUrl: "https://github.com/stue/kgauto/blob/main/v2/README.md#tools"
|
|
4488
4657
|
}
|
|
4489
|
-
|
|
4490
|
-
id: RULE_DISCIPLINE_GATES_V1,
|
|
4491
|
-
preamble: ctx.hasTools ? DISCIPLINE_GATES_V1_WITH_TOOLS : DISCIPLINE_GATES_V1_NO_TOOLS
|
|
4492
|
-
};
|
|
4493
|
-
}
|
|
4494
|
-
if (kind === "tool_call_contract") {
|
|
4495
|
-
if (!profile.archetypePerf) return null;
|
|
4496
|
-
const archetypeScore = profile.archetypePerf[archetype];
|
|
4497
|
-
if (typeof archetypeScore !== "number" || archetypeScore >= TRANSLATOR_FLOOR) {
|
|
4498
|
-
return null;
|
|
4499
|
-
}
|
|
4500
|
-
return {
|
|
4501
|
-
id: RULE_SEQUENTIAL_TOOL_CLIFF,
|
|
4502
|
-
preamble: SEQUENTIAL_TOOL_PREAMBLE,
|
|
4503
|
-
wireOverrides: { parallelToolCalls: false }
|
|
4504
|
-
};
|
|
4505
|
-
}
|
|
4506
|
-
if (kind === "narration_contract") {
|
|
4507
|
-
if (profile.provider === "anthropic") {
|
|
4508
|
-
return {
|
|
4509
|
-
id: RULE_NARRATION_DRIFT_ANTHROPIC,
|
|
4510
|
-
preamble: NARRATION_DRIFT_ANTHROPIC_PREAMBLE
|
|
4511
|
-
};
|
|
4512
|
-
}
|
|
4513
|
-
if (profile.provider === "deepseek") {
|
|
4514
|
-
return {
|
|
4515
|
-
id: RULE_NARRATION_THINKING_LEAK_DEEPSEEK,
|
|
4516
|
-
preamble: NARRATION_THINKING_LEAK_DEEPSEEK_PREAMBLE
|
|
4517
|
-
};
|
|
4518
|
-
}
|
|
4519
|
-
return null;
|
|
4520
|
-
}
|
|
4521
|
-
return null;
|
|
4522
|
-
}
|
|
4523
|
-
function applySectionRewrites(args) {
|
|
4524
|
-
const { ir, profile, archetype } = args;
|
|
4525
|
-
if (!Array.isArray(ir.sections) || ir.sections.length === 0) {
|
|
4526
|
-
return { rewrittenIR: ir, rewrites: [] };
|
|
4527
|
-
}
|
|
4528
|
-
const outputMode = args.outputMode ?? resolveOutputMode({
|
|
4529
|
-
declared: ir.constraints?.outputMode,
|
|
4530
|
-
structuredOutput: ir.constraints?.structuredOutput,
|
|
4531
|
-
toolCount: ir.tools?.length ?? 0
|
|
4532
|
-
});
|
|
4533
|
-
const hasTools = (ir.tools?.length ?? 0) > 0;
|
|
4534
|
-
const ctx = { outputMode, hasTools };
|
|
4535
|
-
const rewrites = [];
|
|
4536
|
-
const newSections = ir.sections.map((section) => {
|
|
4537
|
-
if (!section.kind || section.kind === "arbitrary") return section;
|
|
4538
|
-
const rule = matchRule(section.kind, profile, archetype, ctx);
|
|
4539
|
-
if (!rule) return section;
|
|
4540
|
-
const originalText = section.text;
|
|
4541
|
-
const transformedText = `${rule.preamble}
|
|
4542
|
-
|
|
4543
|
-
${originalText}`;
|
|
4544
|
-
rewrites.push({
|
|
4545
|
-
sectionId: section.id,
|
|
4546
|
-
kind: section.kind,
|
|
4547
|
-
rule: rule.id,
|
|
4548
|
-
originalText,
|
|
4549
|
-
transformedText,
|
|
4550
|
-
...rule.wireOverrides ? { wireOverrides: rule.wireOverrides } : {}
|
|
4551
|
-
});
|
|
4552
|
-
return { ...section, text: transformedText };
|
|
4553
|
-
});
|
|
4554
|
-
if (rewrites.length === 0) {
|
|
4555
|
-
return { rewrittenIR: ir, rewrites: [] };
|
|
4556
|
-
}
|
|
4557
|
-
const rewrittenIR = { ...ir, sections: newSections };
|
|
4558
|
-
return { rewrittenIR, rewrites };
|
|
4559
|
-
}
|
|
4560
|
-
|
|
4561
|
-
// src/promotions-brain.ts
|
|
4562
|
-
function isRawPromotionRow(x) {
|
|
4563
|
-
if (!x || typeof x !== "object") return false;
|
|
4564
|
-
const r = x;
|
|
4565
|
-
return (typeof r.id === "number" || typeof r.id === "string") && typeof r.intent_archetype === "string" && typeof r.promoted_model === "string" && typeof r.incumbent_model === "string";
|
|
4566
|
-
}
|
|
4567
|
-
function coerceId(v) {
|
|
4568
|
-
if (typeof v === "number") return Number.isFinite(v) ? v : null;
|
|
4569
|
-
if (typeof v === "string") {
|
|
4570
|
-
const n = Number(v);
|
|
4571
|
-
return Number.isFinite(n) ? n : null;
|
|
4572
|
-
}
|
|
4573
|
-
return null;
|
|
4574
|
-
}
|
|
4575
|
-
function mapRowsToPromotions(rows) {
|
|
4576
|
-
const out = [];
|
|
4577
|
-
for (const row of rows) {
|
|
4578
|
-
if (!isRawPromotionRow(row)) continue;
|
|
4579
|
-
const id = coerceId(row.id);
|
|
4580
|
-
if (id === null) continue;
|
|
4581
|
-
const mode = row.mode === "strategy" ? "strategy" : row.mode === "downswap" || row.mode === void 0 ? "downswap" : null;
|
|
4582
|
-
if (mode === null) continue;
|
|
4583
|
-
out.push({
|
|
4584
|
-
id,
|
|
4585
|
-
archetype: row.intent_archetype,
|
|
4586
|
-
mode,
|
|
4587
|
-
strategy: typeof row.strategy === "string" ? row.strategy : null,
|
|
4588
|
-
promotedModel: row.promoted_model,
|
|
4589
|
-
incumbentModel: row.incumbent_model,
|
|
4590
|
-
evalRunId: coerceId(row.eval_run_id ?? null),
|
|
4591
|
-
suppressQualityGate: row.suppress_quality_gate === true,
|
|
4592
|
-
promotedAt: typeof row.promoted_at === "string" ? row.promoted_at : ""
|
|
4593
|
-
});
|
|
4594
|
-
}
|
|
4595
|
-
return out;
|
|
4596
|
-
}
|
|
4597
|
-
var snapshots4 = /* @__PURE__ */ new Map();
|
|
4598
|
-
var runtime5;
|
|
4599
|
-
var warnedOnce4 = false;
|
|
4600
|
-
var DEFAULT_PROMOTIONS_ENDPOINT = "https://kgauto-dashboard.vercel.app/api/kgauto-v2/promotions";
|
|
4601
|
-
function isAutoPromoteEnabledFromEnv(envSource) {
|
|
4602
|
-
const env = envSource ?? (typeof process !== "undefined" && process.env ? process.env : {});
|
|
4603
|
-
const raw = (env.KGAUTO_AUTO_PROMOTE ?? "").trim().toLowerCase();
|
|
4604
|
-
return raw === "1" || raw === "true";
|
|
4605
|
-
}
|
|
4606
|
-
function configurePromotionsBrain(rt) {
|
|
4607
|
-
runtime5 = rt;
|
|
4608
|
-
snapshots4.clear();
|
|
4609
|
-
warnedOnce4 = false;
|
|
4610
|
-
}
|
|
4611
|
-
function isPromotionsBrainActive() {
|
|
4612
|
-
return runtime5 !== void 0;
|
|
4658
|
+
];
|
|
4613
4659
|
}
|
|
4614
|
-
function
|
|
4615
|
-
|
|
4616
|
-
if (!
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
let snap = snapshots4.get(appId);
|
|
4620
|
-
if (!snap) {
|
|
4621
|
-
snap = { data: [], expiresAt: 0, refreshing: false };
|
|
4622
|
-
snapshots4.set(appId, snap);
|
|
4623
|
-
}
|
|
4624
|
-
const now = Date.now();
|
|
4625
|
-
const stale = snap.expiresAt <= now;
|
|
4626
|
-
if (stale && !snap.refreshing) {
|
|
4627
|
-
snap.refreshing = true;
|
|
4628
|
-
void asyncRefresh5(rt, appId);
|
|
4660
|
+
function detectHistoryUncached(ir, profile) {
|
|
4661
|
+
if (profile.provider !== "anthropic") return [];
|
|
4662
|
+
if (!ir.history || ir.history.length < 2) return [];
|
|
4663
|
+
if (ir.historyCachePolicy && ir.historyCachePolicy.strategy !== "none") {
|
|
4664
|
+
return [];
|
|
4629
4665
|
}
|
|
4630
|
-
return
|
|
4631
|
-
|
|
4632
|
-
|
|
4666
|
+
return [
|
|
4667
|
+
{
|
|
4668
|
+
level: "warn",
|
|
4669
|
+
code: "history-uncached-on-claude",
|
|
4670
|
+
message: `${ir.history.length} history messages on Anthropic with no historyCachePolicy. Every turn re-pays for the full conversation context; with caching, subsequent turns hit the cache at ~10% the input cost.`,
|
|
4671
|
+
suggestion: "Set `historyCachePolicy: { strategy: 'all-but-latest' }` on this IR. The lowering pass marks the message immediately preceding currentTurn with cache_control; subsequent turns whose history prefix matches byte-for-byte hit the cache.",
|
|
4672
|
+
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
4673
|
+
}
|
|
4674
|
+
];
|
|
4633
4675
|
}
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4676
|
+
function detectSingleModelArray(ir, policy) {
|
|
4677
|
+
if (ir.models.length !== 1) return [];
|
|
4678
|
+
if (policy?.posture === "locked") return [];
|
|
4679
|
+
const only = ir.models[0];
|
|
4680
|
+
return [
|
|
4681
|
+
{
|
|
4682
|
+
level: "warn",
|
|
4683
|
+
code: "single-model-array",
|
|
4684
|
+
message: `\`ir.models\` has length 1 (only "${only}") and posture is not 'locked'. A single-model chain has no safety net \u2014 the first 429 / 5xx / cliff hits the user as a failure. Master plan \xA71.2 closes the reliability gap with a 2-step minimum.`,
|
|
4685
|
+
suggestion: "Use `getDefaultFallbackChain({ archetype: ir.intent.archetype, primary: '" + only + "', posture: 'preferred' })` for a user-anchored chain, or `getDefaultFallbackChain({ archetype, posture: 'open' })` for library-picked. If single-model is intentional (compliance/brand promise), set `policy.posture = 'locked'` to silence this rule.",
|
|
4686
|
+
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#single-model-array"
|
|
4643
4687
|
}
|
|
4644
|
-
|
|
4688
|
+
];
|
|
4645
4689
|
}
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4690
|
+
function detectCostMismatchedArchetype(ir, profile, phase2) {
|
|
4691
|
+
if (!phase2 || phase2.fallbackChain.length === 0) return [];
|
|
4692
|
+
if (!phase2.profileResolver) return [];
|
|
4693
|
+
const archetype = ir.intent.archetype;
|
|
4694
|
+
const chosenScore = getArchetypePerfScore(profile.id, archetype);
|
|
4695
|
+
const chosenHasRoomToGrow = chosenScore.grounding === "judgment" || chosenScore.score < COST_MISMATCHED_CHOSEN_SCORE_CEILING;
|
|
4696
|
+
if (!chosenHasRoomToGrow) return [];
|
|
4697
|
+
let bestAlt = null;
|
|
4698
|
+
for (const altId of phase2.fallbackChain) {
|
|
4699
|
+
const altProfile = phase2.profileResolver(altId);
|
|
4700
|
+
if (!altProfile) continue;
|
|
4701
|
+
if (altProfile.id === profile.id) continue;
|
|
4702
|
+
const altScore = getArchetypePerfScore(altProfile.id, archetype);
|
|
4703
|
+
if (altScore.score < QUALITY_FLOOR_FOR_RECOMMENDATION) continue;
|
|
4704
|
+
if (altScore.score < chosenScore.score) continue;
|
|
4705
|
+
if (altProfile.costInputPer1m >= profile.costInputPer1m) continue;
|
|
4706
|
+
if (getMeasuredFailureVerdict({
|
|
4707
|
+
appId: ir.appId,
|
|
4708
|
+
archetype,
|
|
4709
|
+
model: altProfile.id
|
|
4710
|
+
})?.gated === true) {
|
|
4711
|
+
continue;
|
|
4657
4712
|
}
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4713
|
+
if (ir.constraints?.structuredOutput && effectiveConventions(altProfile).some(
|
|
4714
|
+
(c) => c.archetype === archetype && c.structuredOutputHint === "avoid"
|
|
4715
|
+
)) {
|
|
4716
|
+
continue;
|
|
4717
|
+
}
|
|
4718
|
+
if (getRecentRollback({
|
|
4719
|
+
appId: ir.appId,
|
|
4720
|
+
archetype,
|
|
4721
|
+
model: altProfile.id
|
|
4722
|
+
}) !== void 0) {
|
|
4723
|
+
continue;
|
|
4724
|
+
}
|
|
4725
|
+
if (!bestAlt || altScore.score > bestAlt.score.score || altScore.score === bestAlt.score.score && altProfile.costInputPer1m < bestAlt.profile.costInputPer1m) {
|
|
4726
|
+
bestAlt = { id: altId, profile: altProfile, score: altScore };
|
|
4671
4727
|
}
|
|
4672
4728
|
}
|
|
4729
|
+
if (!bestAlt) return [];
|
|
4730
|
+
const tierDownWouldFire = bestAlt.score.grounding === "measured" && bestAlt.profile.costInputPer1m <= profile.costInputPer1m * TIER_DOWN_COST_RATIO;
|
|
4731
|
+
if (tierDownWouldFire) return [];
|
|
4732
|
+
const chosenGrounding = chosenScore.grounding === "judgment" ? `archetypePerf.${archetype}=judgment` : `archetypePerf.${archetype}=${chosenScore.score}`;
|
|
4733
|
+
const altGrounding = bestAlt.score.grounding === "measured" ? `archetypePerf.${archetype}=${bestAlt.score.score}, measured, n=${bestAlt.score.n}` : `archetypePerf.${archetype}=${bestAlt.score.score}, judgment`;
|
|
4734
|
+
return [
|
|
4735
|
+
{
|
|
4736
|
+
level: "warn",
|
|
4737
|
+
code: "cost-mismatched-archetype",
|
|
4738
|
+
message: `Cost-mismatched-archetype: target=${profile.id} (${chosenGrounding}) selected for ${archetype}. Alternative ${bestAlt.id} (${altGrounding}) is cheaper ($${bestAlt.profile.costInputPer1m}/$${bestAlt.profile.costOutputPer1m} vs $${profile.costInputPer1m}/$${profile.costOutputPer1m} per 1M) at equal-or-better quality.`,
|
|
4739
|
+
suggestion: `Consider declaring \`${bestAlt.id}\` as the primary model for this archetype, or relax to posture='open' to let kgauto select among the chain. If the chosen model is required for compliance/brand reasons, set \`policy.posture = 'locked'\` to silence this rule.`,
|
|
4740
|
+
recommendationType: profile.provider === bestAlt.profile.provider ? "tier-down" : "model-swap",
|
|
4741
|
+
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
4742
|
+
}
|
|
4743
|
+
];
|
|
4673
4744
|
}
|
|
4674
|
-
function
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
);
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
}
|
|
4690
|
-
|
|
4691
|
-
// src/measured-failure-brain.ts
|
|
4692
|
-
function coerceCount(v) {
|
|
4693
|
-
if (typeof v === "number") return Number.isFinite(v) ? v : null;
|
|
4694
|
-
if (typeof v === "string") {
|
|
4695
|
-
const n = Number(v);
|
|
4696
|
-
return Number.isFinite(n) ? n : null;
|
|
4697
|
-
}
|
|
4698
|
-
return null;
|
|
4699
|
-
}
|
|
4700
|
-
function isRawFailureRow(x) {
|
|
4701
|
-
if (!x || typeof x !== "object") return false;
|
|
4702
|
-
const r = x;
|
|
4703
|
-
return typeof r.intent_archetype === "string" && typeof r.model === "string" && (typeof r.n === "number" || typeof r.n === "string");
|
|
4704
|
-
}
|
|
4705
|
-
function mapRows(rows) {
|
|
4706
|
-
const out = [];
|
|
4707
|
-
for (const row of rows) {
|
|
4708
|
-
if (!isRawFailureRow(row)) continue;
|
|
4709
|
-
const n = coerceCount(row.n);
|
|
4710
|
-
const nFail = coerceCount(row.n_fail) ?? 0;
|
|
4711
|
-
if (n === null || n <= 0) continue;
|
|
4712
|
-
out.push({
|
|
4713
|
-
archetype: row.intent_archetype,
|
|
4714
|
-
model: row.model,
|
|
4715
|
-
n,
|
|
4716
|
-
nFail
|
|
4717
|
-
});
|
|
4718
|
-
}
|
|
4719
|
-
return out;
|
|
4720
|
-
}
|
|
4721
|
-
var MEASURED_FAILURE_CFG = {
|
|
4722
|
-
/**
|
|
4723
|
-
* Hard minimum attempts before ANY gate may be created. Guards against
|
|
4724
|
-
* pathological tiny samples that the confidence bound alone would let
|
|
4725
|
-
* through in edge cases. At 5-for-5 the bound clears the threshold; at
|
|
4726
|
-
* 3-for-3 it does not, which is the behaviour we want (three failures is
|
|
4727
|
-
* a bad day, five in a row is a pattern).
|
|
4728
|
-
*/
|
|
4729
|
-
minSample: 5,
|
|
4730
|
-
/**
|
|
4731
|
-
* Gate when we are 95% confident the model fails MORE OFTEN THAN IT
|
|
4732
|
-
* SUCCEEDS on this surface. Deliberately unarguable rather than tuned —
|
|
4733
|
-
* a model that probably fails the majority of the time has no business
|
|
4734
|
-
* leading a surface, whatever its declared scores say.
|
|
4735
|
-
*/
|
|
4736
|
-
lowerBoundThreshold: 0.5,
|
|
4737
|
-
/** 95% one-sided-ish confidence (standard two-sided z at α=0.05). */
|
|
4738
|
-
z: 1.96,
|
|
4739
|
-
/** Must match the view's window. Documented here for the advisory text. */
|
|
4740
|
-
windowDays: 28
|
|
4741
|
-
};
|
|
4742
|
-
function wilsonLowerBound(failures, n, z = MEASURED_FAILURE_CFG.z) {
|
|
4743
|
-
if (n <= 0) return 0;
|
|
4744
|
-
const p = failures / n;
|
|
4745
|
-
const z2 = z * z;
|
|
4746
|
-
const denom = 1 + z2 / n;
|
|
4747
|
-
const centre = p + z2 / (2 * n);
|
|
4748
|
-
const margin = z * Math.sqrt(p * (1 - p) / n + z2 / (4 * n * n));
|
|
4749
|
-
const lower2 = (centre - margin) / denom;
|
|
4750
|
-
return lower2 < 0 ? 0 : lower2;
|
|
4751
|
-
}
|
|
4752
|
-
function judgeMeasuredFailure(row, cfg = MEASURED_FAILURE_CFG) {
|
|
4753
|
-
if (!row || row.n < cfg.minSample) return void 0;
|
|
4754
|
-
const lowerBound = wilsonLowerBound(row.nFail, row.n, cfg.z);
|
|
4755
|
-
return {
|
|
4756
|
-
gated: lowerBound > cfg.lowerBoundThreshold,
|
|
4757
|
-
rate: row.nFail / row.n,
|
|
4758
|
-
lowerBound,
|
|
4759
|
-
n: row.n,
|
|
4760
|
-
nFail: row.nFail
|
|
4761
|
-
};
|
|
4762
|
-
}
|
|
4763
|
-
var snapshots5 = /* @__PURE__ */ new Map();
|
|
4764
|
-
var runtime6;
|
|
4765
|
-
var warnedOnce5 = false;
|
|
4766
|
-
var DEFAULT_MEASURED_FAILURE_ENDPOINT = "https://kgauto-dashboard.vercel.app/api/kgauto-v2/measured-failure";
|
|
4767
|
-
function isMeasuredFailureGateEnabledFromEnv(envSource) {
|
|
4768
|
-
const env = envSource ?? (typeof process !== "undefined" && process.env ? process.env : {});
|
|
4769
|
-
const raw = (env.KGAUTO_MEASURED_FAILURE_GATE ?? "").trim().toLowerCase();
|
|
4770
|
-
return !(raw === "0" || raw === "false");
|
|
4771
|
-
}
|
|
4772
|
-
function configureMeasuredFailureBrain(rt) {
|
|
4773
|
-
runtime6 = rt;
|
|
4774
|
-
snapshots5.clear();
|
|
4775
|
-
warnedOnce5 = false;
|
|
4776
|
-
}
|
|
4777
|
-
function isMeasuredFailureBrainActive() {
|
|
4778
|
-
return runtime6 !== void 0;
|
|
4745
|
+
function detectModelStaleEvidence(ir, profile) {
|
|
4746
|
+
if (!isBrainQueryActiveFor("kgauto_archetype_perf")) return [];
|
|
4747
|
+
const archetype = ir.intent.archetype;
|
|
4748
|
+
const chosen = getArchetypePerfScore(profile.id, archetype);
|
|
4749
|
+
if (chosen.grounding !== "judgment") return [];
|
|
4750
|
+
return [
|
|
4751
|
+
{
|
|
4752
|
+
level: "info",
|
|
4753
|
+
code: "model-stale-evidence",
|
|
4754
|
+
message: `Model-stale-evidence: target=${profile.id} archetype=${archetype} is judgment-grounded (n=${chosen.n}, cross-app 90d window) despite brain-query mode being active. Fewer than 10 outcomes back this (model, archetype) tuple across ALL consumers \u2014 routing decisions remain pre-measured for this slot.`,
|
|
4755
|
+
suggestion: "Verify that `record()` is being called on every call() outcome. Counts are cross-app (migration 050 view): once ANY consumers accumulate n>=10 rows on this tuple, the score promotes from judgment to measured automatically within the 5-min SWR window. (Before alpha.78 this promotion was advertised but had no implementing mechanism \u2014 n was never populated; if this advisory has been firing for weeks at n=0 despite real traffic, bump to >=alpha.78 and it will clear on its own.)",
|
|
4756
|
+
recommendationType: "prompt-fix",
|
|
4757
|
+
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
4758
|
+
}
|
|
4759
|
+
];
|
|
4779
4760
|
}
|
|
4780
|
-
function
|
|
4781
|
-
|
|
4782
|
-
if (!
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4761
|
+
function detectTierDown(ir, profile, phase2) {
|
|
4762
|
+
if (!phase2 || phase2.fallbackChain.length === 0) return [];
|
|
4763
|
+
if (!phase2.profileResolver) return [];
|
|
4764
|
+
const archetype = ir.intent.archetype;
|
|
4765
|
+
const chosenScore = getArchetypePerfScore(profile.id, archetype);
|
|
4766
|
+
const chosenCost = profile.costInputPer1m;
|
|
4767
|
+
let bestAlt = null;
|
|
4768
|
+
for (const altId of phase2.fallbackChain) {
|
|
4769
|
+
const altProfile = phase2.profileResolver(altId);
|
|
4770
|
+
if (!altProfile) continue;
|
|
4771
|
+
if (altProfile.id === profile.id) continue;
|
|
4772
|
+
const altScore = getArchetypePerfScore(altProfile.id, archetype);
|
|
4773
|
+
if (altScore.grounding !== "measured") continue;
|
|
4774
|
+
if (altScore.score < QUALITY_FLOOR_FOR_RECOMMENDATION) continue;
|
|
4775
|
+
if (altScore.score < chosenScore.score) continue;
|
|
4776
|
+
if (altProfile.costInputPer1m > chosenCost * TIER_DOWN_COST_RATIO) continue;
|
|
4777
|
+
if (!bestAlt || altProfile.costInputPer1m < bestAlt.profile.costInputPer1m || altProfile.costInputPer1m === bestAlt.profile.costInputPer1m && altScore.score > bestAlt.score.score) {
|
|
4778
|
+
bestAlt = { id: altId, profile: altProfile, score: altScore };
|
|
4779
|
+
}
|
|
4787
4780
|
}
|
|
4788
|
-
if (
|
|
4789
|
-
const
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4781
|
+
if (!bestAlt) return [];
|
|
4782
|
+
const chosenDesc = chosenScore.grounding === "measured" ? `archetypePerf.${archetype}=${chosenScore.score} (measured, n=${chosenScore.n})` : `archetypePerf.${archetype}=${chosenScore.score} (${chosenScore.grounding})`;
|
|
4783
|
+
return [
|
|
4784
|
+
{
|
|
4785
|
+
level: "warn",
|
|
4786
|
+
code: "tier-down",
|
|
4787
|
+
message: `Tier-down: target=${profile.id} (${chosenDesc}) selected for ${archetype}. Brain shows ${bestAlt.id} delivers equal-or-better quality (archetypePerf.${archetype}=${bestAlt.score.score}, measured, n=${bestAlt.score.n}) at $${bestAlt.profile.costInputPer1m}/$${bestAlt.profile.costOutputPer1m} per 1M vs $${profile.costInputPer1m}/$${profile.costOutputPer1m} \u2014 a measured tier-down opportunity.`,
|
|
4788
|
+
suggestion: `Move \`${bestAlt.id}\` to primary for this archetype. The brain has n=${bestAlt.score.n} measured outcomes backing the recommendation; this is data, not opinion. If posture='locked' is required (compliance/brand promise), set it explicitly to silence this rule.`,
|
|
4789
|
+
recommendationType: "tier-down",
|
|
4790
|
+
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
4791
|
+
}
|
|
4792
|
+
];
|
|
4795
4793
|
}
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4794
|
+
function detectArchetypePerfFloorBreach(ir, profile) {
|
|
4795
|
+
const compat = getModelCompatibility(profile.id, {
|
|
4796
|
+
archetype: ir.intent.archetype,
|
|
4797
|
+
toolOrchestration: ir.constraints?.toolOrchestration
|
|
4798
|
+
});
|
|
4799
|
+
if (compat.status === "compatible") return [];
|
|
4800
|
+
if (compat.status === "requires-adapter") {
|
|
4801
|
+
return [
|
|
4802
|
+
{
|
|
4803
|
+
level: "warn",
|
|
4804
|
+
code: "archetype-perf-floor-breach",
|
|
4805
|
+
message: `${profile.id} sits below the archetype floor for ${ir.intent.archetype} (score ${compat.archetypePerf}/10, floor ${6}). A known adapter would lift it: ${compat.adapter.parameter}=${compat.adapter.value}. ${compat.adapter.consequence}`,
|
|
4806
|
+
suggestion: `Pass \`ir.constraints.${compat.adapter.parameter} = '${compat.adapter.value}'\` for this call, OR pick a model whose archetypePerf for ${ir.intent.archetype} already clears the floor (call \`getModelCompatibility(modelId, { archetype: '${ir.intent.archetype}' })\` to check). Estimated post-adapter score: ${compat.archetypePerfWithAdapter}/10.`,
|
|
4807
|
+
recommendationType: "prompt-fix",
|
|
4808
|
+
suggestedAdaptation: compat.adapter,
|
|
4809
|
+
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
4810
|
+
}
|
|
4811
|
+
];
|
|
4812
4812
|
}
|
|
4813
|
+
return [
|
|
4814
|
+
{
|
|
4815
|
+
level: "critical",
|
|
4816
|
+
code: "archetype-perf-floor-breach",
|
|
4817
|
+
message: `${profile.id} sits below the archetype floor for ${ir.intent.archetype} (score ${compat.archetypePerf}/10, floor ${6}) and no known adapter would lift it. ${compat.reason}`,
|
|
4818
|
+
suggestion: `Swap to a model whose archetypePerf for ${ir.intent.archetype} clears the floor. Use \`getModelCompatibility(candidateId, { archetype: '${ir.intent.archetype}' })\` to vet candidates, or \`getDefaultFallbackChain({ archetype: '${ir.intent.archetype}', posture: 'open' })\` for a library-picked chain that respects the floor by construction.`,
|
|
4819
|
+
recommendationType: "model-swap",
|
|
4820
|
+
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
4821
|
+
}
|
|
4822
|
+
];
|
|
4813
4823
|
}
|
|
4814
|
-
function
|
|
4815
|
-
|
|
4816
|
-
if (!
|
|
4817
|
-
const
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
const
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4824
|
+
function detectStaleExclusionCandidate(ir) {
|
|
4825
|
+
if (!isExclusionFindingsBrainActive()) return [];
|
|
4826
|
+
if (!ir.appId) return [];
|
|
4827
|
+
const findings = getStaleExclusionFindings({
|
|
4828
|
+
appId: ir.appId,
|
|
4829
|
+
archetype: ir.intent.archetype
|
|
4830
|
+
});
|
|
4831
|
+
if (findings.length === 0) return [];
|
|
4832
|
+
const ranked = [...findings].sort((a, b) => {
|
|
4833
|
+
const sa = a.estimatedSavingsUsd30d ?? -Infinity;
|
|
4834
|
+
const sb = b.estimatedSavingsUsd30d ?? -Infinity;
|
|
4835
|
+
if (sa !== sb) return sb - sa;
|
|
4836
|
+
return confidenceRank(b.confidence) - confidenceRank(a.confidence);
|
|
4837
|
+
});
|
|
4838
|
+
const top = ranked[0];
|
|
4839
|
+
const extraCount = findings.length - 1;
|
|
4840
|
+
const extraNote = extraCount > 0 ? ` (+ ${extraCount} more excluded model${extraCount === 1 ? "" : "s"} for this archetype)` : "";
|
|
4841
|
+
return [
|
|
4842
|
+
{
|
|
4843
|
+
level: "info",
|
|
4844
|
+
code: "stale-exclusion-candidate",
|
|
4845
|
+
message: `${top.message}${extraNote}`,
|
|
4846
|
+
suggestion: top.suggestion,
|
|
4847
|
+
recommendationType: "tier-down",
|
|
4848
|
+
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
4849
|
+
}
|
|
4850
|
+
];
|
|
4833
4851
|
}
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
|
|
4852
|
+
function confidenceRank(c) {
|
|
4853
|
+
if (c === "high") return 3;
|
|
4854
|
+
if (c === "medium") return 2;
|
|
4855
|
+
return 1;
|
|
4856
|
+
}
|
|
4857
|
+
|
|
4858
|
+
// src/translator.ts
|
|
4859
|
+
var TRANSLATOR_FLOOR = ARCHETYPE_FLOOR_DEFAULT;
|
|
4860
|
+
var RULE_SEQUENTIAL_TOOL_CLIFF = "sequential-tool-cliff-below-floor";
|
|
4861
|
+
var RULE_NARRATION_DRIFT_ANTHROPIC = "narration-drift-anthropic";
|
|
4862
|
+
var RULE_NARRATION_THINKING_LEAK_DEEPSEEK = "narration-thinking-leak-deepseek";
|
|
4863
|
+
var SEQUENTIAL_TOOL_PREAMBLE = "IMPORTANT: Use one tool call per response. Wait for the tool result before deciding the next tool. Do NOT batch tool calls in parallel.";
|
|
4864
|
+
var NARRATION_DRIFT_ANTHROPIC_PREAMBLE = "Output ONLY the requested content. Do not narrate your thought process. Each line \u2264 12 words.";
|
|
4865
|
+
var NARRATION_THINKING_LEAK_DEEPSEEK_PREAMBLE = "Reasoning is internal. Output ONLY the requested content; do not emit <thinking> blocks or internal monologue as user-facing text.";
|
|
4866
|
+
var RULE_DISCIPLINE_GATES_V1 = "discipline-gates-v1";
|
|
4867
|
+
var DISCIPLINE_GATES_V1_WITH_TOOLS = `Work through these gates at every judgment point, explicitly:
|
|
4868
|
+
1. Evidence before reasoning: cite what you observed before concluding from it.
|
|
4869
|
+
2. One extra signal: when a finding feels conclusive, check one more adjacent signal before stating it.
|
|
4870
|
+
3. Expand, don't guess: resolve a compressed or referenced item by looking it up rather than inferring its contents.
|
|
4871
|
+
4. Innocent explanation first: state the most plausible benign reading before alleging the alarming one.
|
|
4872
|
+
5. Label each claim: mark it observed, inferred, or assumed.
|
|
4873
|
+
6. A surfaced gap beats a guessed answer: flag what you cannot determine rather than fabricating past it.`;
|
|
4874
|
+
var DISCIPLINE_GATES_V1_NO_TOOLS = `Work through these gates at every judgment point, explicitly:
|
|
4875
|
+
1. Evidence before reasoning: cite what you observed before concluding from it.
|
|
4876
|
+
2. One extra signal: when a finding feels conclusive, check one more adjacent signal before stating it.
|
|
4877
|
+
3. Innocent explanation first: state the most plausible benign reading before alleging the alarming one.
|
|
4878
|
+
4. Label each claim: mark it observed, inferred, or assumed.
|
|
4879
|
+
5. A surfaced gap beats a guessed answer: flag what you cannot determine rather than fabricating past it.`;
|
|
4880
|
+
var RULE_DISCIPLINE_GATES_V1_STRUCTURED = "discipline-gates-v1-structured";
|
|
4881
|
+
var DISCIPLINE_GATES_V1_STRUCTURED_WITH_TOOLS = `Work through these gates at every judgment point, explicitly:
|
|
4882
|
+
1. Evidence before reasoning: cite what you observed before concluding from it.
|
|
4883
|
+
2. One extra signal: when a finding feels conclusive, check one more adjacent signal before stating it.
|
|
4884
|
+
3. Expand, don't guess: resolve a compressed or referenced item by looking it up rather than inferring its contents.
|
|
4885
|
+
4. Innocent explanation first: state the most plausible benign reading before alleging the alarming one.`;
|
|
4886
|
+
var DISCIPLINE_GATES_V1_STRUCTURED_NO_TOOLS = `Work through these gates at every judgment point, explicitly:
|
|
4887
|
+
1. Evidence before reasoning: cite what you observed before concluding from it.
|
|
4888
|
+
2. One extra signal: when a finding feels conclusive, check one more adjacent signal before stating it.
|
|
4889
|
+
3. Innocent explanation first: state the most plausible benign reading before alleging the alarming one.`;
|
|
4890
|
+
var DISCIPLINE_ELIGIBLE_ARCHETYPES = /* @__PURE__ */ new Set([
|
|
4891
|
+
"hunt",
|
|
4892
|
+
"summarize",
|
|
4893
|
+
"plan",
|
|
4894
|
+
"critique",
|
|
4895
|
+
"judge"
|
|
4896
|
+
]);
|
|
4897
|
+
function matchRule(kind, profile, archetype, ctx) {
|
|
4898
|
+
if (kind === "discipline_contract") {
|
|
4899
|
+
if (!DISCIPLINE_ELIGIBLE_ARCHETYPES.has(archetype)) return null;
|
|
4900
|
+
if (ctx.outputMode !== "text") {
|
|
4901
|
+
return {
|
|
4902
|
+
id: RULE_DISCIPLINE_GATES_V1_STRUCTURED,
|
|
4903
|
+
preamble: ctx.hasTools ? DISCIPLINE_GATES_V1_STRUCTURED_WITH_TOOLS : DISCIPLINE_GATES_V1_STRUCTURED_NO_TOOLS
|
|
4904
|
+
};
|
|
4843
4905
|
}
|
|
4906
|
+
return {
|
|
4907
|
+
id: RULE_DISCIPLINE_GATES_V1,
|
|
4908
|
+
preamble: ctx.hasTools ? DISCIPLINE_GATES_V1_WITH_TOOLS : DISCIPLINE_GATES_V1_NO_TOOLS
|
|
4909
|
+
};
|
|
4844
4910
|
}
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4911
|
+
if (kind === "tool_call_contract") {
|
|
4912
|
+
if (!profile.archetypePerf) return null;
|
|
4913
|
+
const archetypeScore = profile.archetypePerf[archetype];
|
|
4914
|
+
if (typeof archetypeScore !== "number" || archetypeScore >= TRANSLATOR_FLOOR) {
|
|
4915
|
+
return null;
|
|
4916
|
+
}
|
|
4917
|
+
return {
|
|
4918
|
+
id: RULE_SEQUENTIAL_TOOL_CLIFF,
|
|
4919
|
+
preamble: SEQUENTIAL_TOOL_PREAMBLE,
|
|
4920
|
+
wireOverrides: { parallelToolCalls: false }
|
|
4921
|
+
};
|
|
4852
4922
|
}
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4923
|
+
if (kind === "narration_contract") {
|
|
4924
|
+
if (profile.provider === "anthropic") {
|
|
4925
|
+
return {
|
|
4926
|
+
id: RULE_NARRATION_DRIFT_ANTHROPIC,
|
|
4927
|
+
preamble: NARRATION_DRIFT_ANTHROPIC_PREAMBLE
|
|
4928
|
+
};
|
|
4857
4929
|
}
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
} catch (err) {
|
|
4864
|
-
if (runtime6 !== rt) return;
|
|
4865
|
-
snap.refreshing = false;
|
|
4866
|
-
snap.expiresAt = Date.now() + rt.ttlMs;
|
|
4867
|
-
if (!warnedOnce5) {
|
|
4868
|
-
warnedOnce5 = true;
|
|
4869
|
-
(rt.onError ?? defaultOnError6)(err);
|
|
4930
|
+
if (profile.provider === "deepseek") {
|
|
4931
|
+
return {
|
|
4932
|
+
id: RULE_NARRATION_THINKING_LEAK_DEEPSEEK,
|
|
4933
|
+
preamble: NARRATION_THINKING_LEAK_DEEPSEEK_PREAMBLE
|
|
4934
|
+
};
|
|
4870
4935
|
}
|
|
4936
|
+
return null;
|
|
4871
4937
|
}
|
|
4938
|
+
return null;
|
|
4872
4939
|
}
|
|
4873
|
-
function
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
}
|
|
4885
|
-
|
|
4886
|
-
const
|
|
4887
|
-
|
|
4940
|
+
function applySectionRewrites(args) {
|
|
4941
|
+
const { ir, profile, archetype } = args;
|
|
4942
|
+
if (!Array.isArray(ir.sections) || ir.sections.length === 0) {
|
|
4943
|
+
return { rewrittenIR: ir, rewrites: [] };
|
|
4944
|
+
}
|
|
4945
|
+
const outputMode = args.outputMode ?? resolveOutputMode({
|
|
4946
|
+
declared: ir.constraints?.outputMode,
|
|
4947
|
+
structuredOutput: ir.constraints?.structuredOutput,
|
|
4948
|
+
toolCount: ir.tools?.length ?? 0
|
|
4949
|
+
});
|
|
4950
|
+
const hasTools = (ir.tools?.length ?? 0) > 0;
|
|
4951
|
+
const ctx = { outputMode, hasTools };
|
|
4952
|
+
const rewrites = [];
|
|
4953
|
+
const newSections = ir.sections.map((section) => {
|
|
4954
|
+
if (!section.kind || section.kind === "arbitrary") return section;
|
|
4955
|
+
const rule = matchRule(section.kind, profile, archetype, ctx);
|
|
4956
|
+
if (!rule) return section;
|
|
4957
|
+
const originalText = section.text;
|
|
4958
|
+
const transformedText = `${rule.preamble}
|
|
4959
|
+
|
|
4960
|
+
${originalText}`;
|
|
4961
|
+
rewrites.push({
|
|
4962
|
+
sectionId: section.id,
|
|
4963
|
+
kind: section.kind,
|
|
4964
|
+
rule: rule.id,
|
|
4965
|
+
originalText,
|
|
4966
|
+
transformedText,
|
|
4967
|
+
...rule.wireOverrides ? { wireOverrides: rule.wireOverrides } : {}
|
|
4968
|
+
});
|
|
4969
|
+
return { ...section, text: transformedText };
|
|
4970
|
+
});
|
|
4971
|
+
if (rewrites.length === 0) {
|
|
4972
|
+
return { rewrittenIR: ir, rewrites: [] };
|
|
4973
|
+
}
|
|
4974
|
+
const rewrittenIR = { ...ir, sections: newSections };
|
|
4975
|
+
return { rewrittenIR, rewrites };
|
|
4888
4976
|
}
|
|
4889
4977
|
|
|
4890
4978
|
// src/compile.ts
|
|
@@ -6014,6 +6102,17 @@ function parseJsonLoose(raw) {
|
|
|
6014
6102
|
var FAILED = /* @__PURE__ */ Symbol("parse-failed");
|
|
6015
6103
|
|
|
6016
6104
|
// src/ir.ts
|
|
6105
|
+
function mutationId(m) {
|
|
6106
|
+
return typeof m === "string" ? m : m.id;
|
|
6107
|
+
}
|
|
6108
|
+
function hasMutation(list, idOrPrefix) {
|
|
6109
|
+
if (!Array.isArray(list)) return false;
|
|
6110
|
+
const prefix = idOrPrefix.endsWith("*") ? idOrPrefix.slice(0, -1) : void 0;
|
|
6111
|
+
return list.some((m) => {
|
|
6112
|
+
const id = mutationId(m);
|
|
6113
|
+
return prefix !== void 0 ? id.startsWith(prefix) : id === idOrPrefix;
|
|
6114
|
+
});
|
|
6115
|
+
}
|
|
6017
6116
|
var CallError = class extends Error {
|
|
6018
6117
|
attempts;
|
|
6019
6118
|
lastErrorCode;
|
|
@@ -6136,6 +6235,17 @@ function getReachabilityDiagnostic(opts = {}) {
|
|
|
6136
6235
|
}
|
|
6137
6236
|
return out;
|
|
6138
6237
|
}
|
|
6238
|
+
var BRAIN_READ_ENV_NAMES = {
|
|
6239
|
+
endpoint: "KGAUTO_V2_BRAIN_SUPABASE_URL",
|
|
6240
|
+
jwt: "KGAUTO_V2_BRAIN_JWT",
|
|
6241
|
+
anonKey: "KGAUTO_V2_BRAIN_ANON_KEY",
|
|
6242
|
+
/** Accepted fallbacks, in resolution order after the canonical name. */
|
|
6243
|
+
fallbacks: {
|
|
6244
|
+
endpoint: ["KGAUTO_V2_BRAIN_URL"],
|
|
6245
|
+
jwt: ["GLASSBOX_BRAIN_JWT"],
|
|
6246
|
+
anonKey: []
|
|
6247
|
+
}
|
|
6248
|
+
};
|
|
6139
6249
|
function readBrainReadEnv(envSource = defaultEnv()) {
|
|
6140
6250
|
const endpoint = readKeyValue(envSource.KGAUTO_V2_BRAIN_SUPABASE_URL) ?? readKeyValue(envSource.KGAUTO_V2_BRAIN_URL);
|
|
6141
6251
|
const jwt = readKeyValue(envSource.KGAUTO_V2_BRAIN_JWT) ?? readKeyValue(envSource.GLASSBOX_BRAIN_JWT);
|
|
@@ -6152,12 +6262,46 @@ function isSameModelRetryEnabledFromEnv(envSource = defaultEnv()) {
|
|
|
6152
6262
|
}
|
|
6153
6263
|
|
|
6154
6264
|
// src/streaming.ts
|
|
6265
|
+
function classifyThrownFetchError(err) {
|
|
6266
|
+
const name = err?.name;
|
|
6267
|
+
if (name === "TimeoutError") {
|
|
6268
|
+
return { errorType: "retryable", errorCode: "timeout", message: String(err) };
|
|
6269
|
+
}
|
|
6270
|
+
if (name === "AbortError") {
|
|
6271
|
+
return { errorType: "terminal", errorCode: "aborted", message: "aborted by caller signal" };
|
|
6272
|
+
}
|
|
6273
|
+
return { errorType: "retryable", errorCode: "network_error", message: String(err) };
|
|
6274
|
+
}
|
|
6275
|
+
function createStallGuard(external, stallTimeoutMs) {
|
|
6276
|
+
const ctl = new AbortController();
|
|
6277
|
+
if (external) {
|
|
6278
|
+
if (external.aborted) ctl.abort(external.reason);
|
|
6279
|
+
else external.addEventListener("abort", () => ctl.abort(external.reason), { once: true });
|
|
6280
|
+
}
|
|
6281
|
+
let timer;
|
|
6282
|
+
const arm = () => {
|
|
6283
|
+
if (!stallTimeoutMs || stallTimeoutMs <= 0) return;
|
|
6284
|
+
if (timer) clearTimeout(timer);
|
|
6285
|
+
timer = setTimeout(() => {
|
|
6286
|
+
ctl.abort(
|
|
6287
|
+
new DOMException(`stream stalled: no bytes for ${stallTimeoutMs}ms`, "TimeoutError")
|
|
6288
|
+
);
|
|
6289
|
+
}, stallTimeoutMs);
|
|
6290
|
+
};
|
|
6291
|
+
const clear = () => {
|
|
6292
|
+
if (timer) clearTimeout(timer);
|
|
6293
|
+
timer = void 0;
|
|
6294
|
+
};
|
|
6295
|
+
return { signal: ctl.signal, arm, clear };
|
|
6296
|
+
}
|
|
6155
6297
|
var ANTHROPIC_URL = "https://api.anthropic.com/v1/messages";
|
|
6156
6298
|
async function streamAnthropic(request, apiKey, opts) {
|
|
6157
6299
|
const { provider: _provider, ...body } = request;
|
|
6158
6300
|
const fetchFn = opts.fetchImpl ?? fetch;
|
|
6301
|
+
const guard = createStallGuard(opts.signal, opts.stallTimeoutMs);
|
|
6159
6302
|
let res;
|
|
6160
6303
|
try {
|
|
6304
|
+
guard.arm();
|
|
6161
6305
|
res = await fetchFn(ANTHROPIC_URL, {
|
|
6162
6306
|
method: "POST",
|
|
6163
6307
|
headers: {
|
|
@@ -6165,12 +6309,16 @@ async function streamAnthropic(request, apiKey, opts) {
|
|
|
6165
6309
|
"anthropic-version": "2023-06-01",
|
|
6166
6310
|
"content-type": "application/json"
|
|
6167
6311
|
},
|
|
6168
|
-
body: JSON.stringify({ ...body, stream: true })
|
|
6312
|
+
body: JSON.stringify({ ...body, stream: true }),
|
|
6313
|
+
signal: guard.signal
|
|
6169
6314
|
});
|
|
6170
6315
|
} catch (err) {
|
|
6171
|
-
|
|
6316
|
+
guard.clear();
|
|
6317
|
+
const c = classifyThrownFetchError(err);
|
|
6318
|
+
return { ok: false, status: 0, errorType: c.errorType, errorCode: c.errorCode, message: c.message, raw: null };
|
|
6172
6319
|
}
|
|
6173
6320
|
if (!res.ok) {
|
|
6321
|
+
guard.clear();
|
|
6174
6322
|
const errBody = await res.json().catch(() => ({}));
|
|
6175
6323
|
return classifyHttpError(res.status, errBody);
|
|
6176
6324
|
}
|
|
@@ -6233,9 +6381,13 @@ async function streamAnthropic(request, apiKey, opts) {
|
|
|
6233
6381
|
if (typeof p.usage?.output_tokens === "number") outputTokens = p.usage.output_tokens;
|
|
6234
6382
|
return;
|
|
6235
6383
|
}
|
|
6236
|
-
});
|
|
6384
|
+
}, guard.arm, guard.signal);
|
|
6237
6385
|
} catch (err) {
|
|
6238
|
-
|
|
6386
|
+
const c = classifyThrownFetchError(err);
|
|
6387
|
+
const code = c.errorCode === "network_error" ? "stream_interrupted" : c.errorCode;
|
|
6388
|
+
return { ok: false, status: 0, errorType: c.errorType, errorCode: code, message: c.message, raw: null };
|
|
6389
|
+
} finally {
|
|
6390
|
+
guard.clear();
|
|
6239
6391
|
}
|
|
6240
6392
|
const toolCalls = Array.from(toolBlocks.values()).map((b) => ({
|
|
6241
6393
|
id: b.id,
|
|
@@ -6262,6 +6414,7 @@ async function streamAnthropic(request, apiKey, opts) {
|
|
|
6262
6414
|
async function streamOpenAILike(url, request, apiKey, providerLabel, opts) {
|
|
6263
6415
|
const { provider: _provider, ...body } = request;
|
|
6264
6416
|
const fetchFn = opts.fetchImpl ?? fetch;
|
|
6417
|
+
const guard = createStallGuard(opts.signal, opts.stallTimeoutMs);
|
|
6265
6418
|
const reqBody = {
|
|
6266
6419
|
...body,
|
|
6267
6420
|
stream: true,
|
|
@@ -6272,18 +6425,23 @@ async function streamOpenAILike(url, request, apiKey, providerLabel, opts) {
|
|
|
6272
6425
|
};
|
|
6273
6426
|
let res;
|
|
6274
6427
|
try {
|
|
6428
|
+
guard.arm();
|
|
6275
6429
|
res = await fetchFn(url, {
|
|
6276
6430
|
method: "POST",
|
|
6277
6431
|
headers: {
|
|
6278
6432
|
authorization: `Bearer ${apiKey}`,
|
|
6279
6433
|
"content-type": "application/json"
|
|
6280
6434
|
},
|
|
6281
|
-
body: JSON.stringify(reqBody)
|
|
6435
|
+
body: JSON.stringify(reqBody),
|
|
6436
|
+
signal: guard.signal
|
|
6282
6437
|
});
|
|
6283
6438
|
} catch (err) {
|
|
6284
|
-
|
|
6439
|
+
guard.clear();
|
|
6440
|
+
const c = classifyThrownFetchError(err);
|
|
6441
|
+
return { ok: false, status: 0, errorType: c.errorType, errorCode: c.errorCode, message: c.message, raw: null };
|
|
6285
6442
|
}
|
|
6286
6443
|
if (!res.ok) {
|
|
6444
|
+
guard.clear();
|
|
6287
6445
|
const errBody = await res.json().catch(() => ({}));
|
|
6288
6446
|
return classifyHttpError(res.status, errBody);
|
|
6289
6447
|
}
|
|
@@ -6336,9 +6494,13 @@ async function streamOpenAILike(url, request, apiKey, providerLabel, opts) {
|
|
|
6336
6494
|
const details = usage.prompt_tokens_details;
|
|
6337
6495
|
if (typeof details?.cached_tokens === "number") cachedTokens = details.cached_tokens;
|
|
6338
6496
|
}
|
|
6339
|
-
});
|
|
6497
|
+
}, guard.arm, guard.signal);
|
|
6340
6498
|
} catch (err) {
|
|
6341
|
-
|
|
6499
|
+
const c = classifyThrownFetchError(err);
|
|
6500
|
+
const code = c.errorCode === "network_error" ? "stream_interrupted" : c.errorCode;
|
|
6501
|
+
return { ok: false, status: 0, errorType: c.errorType, errorCode: code, message: c.message, raw: null };
|
|
6502
|
+
} finally {
|
|
6503
|
+
guard.clear();
|
|
6342
6504
|
}
|
|
6343
6505
|
const toolCalls = Array.from(toolBuffers.values()).filter((b) => b.name.length > 0).map((b) => ({
|
|
6344
6506
|
id: b.id,
|
|
@@ -6361,15 +6523,28 @@ async function streamOpenAILike(url, request, apiKey, providerLabel, opts) {
|
|
|
6361
6523
|
};
|
|
6362
6524
|
return { ok: true, status: res.status, response };
|
|
6363
6525
|
}
|
|
6364
|
-
async function parseSSEStream(response, handler) {
|
|
6526
|
+
async function parseSSEStream(response, handler, onRead, signal) {
|
|
6365
6527
|
const body = response.body;
|
|
6366
6528
|
if (!body) throw new Error("Response has no body for SSE parse");
|
|
6367
6529
|
const reader = body.getReader();
|
|
6368
6530
|
const decoder = new TextDecoder("utf-8");
|
|
6369
6531
|
let buffer = "";
|
|
6532
|
+
const abortRace = signal ? new Promise((_resolve, reject) => {
|
|
6533
|
+
if (signal.aborted) return reject(signal.reason);
|
|
6534
|
+
signal.addEventListener("abort", () => reject(signal.reason), { once: true });
|
|
6535
|
+
}) : void 0;
|
|
6370
6536
|
for (; ; ) {
|
|
6371
|
-
|
|
6537
|
+
let readResult;
|
|
6538
|
+
try {
|
|
6539
|
+
readResult = abortRace ? await Promise.race([reader.read(), abortRace]) : await reader.read();
|
|
6540
|
+
} catch (err) {
|
|
6541
|
+
void reader.cancel().catch(() => {
|
|
6542
|
+
});
|
|
6543
|
+
throw err;
|
|
6544
|
+
}
|
|
6545
|
+
const { value, done } = readResult;
|
|
6372
6546
|
if (done) break;
|
|
6547
|
+
onRead?.();
|
|
6373
6548
|
buffer += decoder.decode(value, { stream: true });
|
|
6374
6549
|
let sep;
|
|
6375
6550
|
while (sep = buffer.indexOf("\n\n"), sep !== -1) {
|
|
@@ -6480,9 +6655,6 @@ function extractErrorMessage(body) {
|
|
|
6480
6655
|
if (typeof b.message === "string") return b.message;
|
|
6481
6656
|
return void 0;
|
|
6482
6657
|
}
|
|
6483
|
-
function retryableError(status, code, message, raw) {
|
|
6484
|
-
return { ok: false, status, errorType: "retryable", errorCode: code, message, raw };
|
|
6485
|
-
}
|
|
6486
6658
|
|
|
6487
6659
|
// src/execute.ts
|
|
6488
6660
|
var ANTHROPIC_URL2 = "https://api.anthropic.com/v1/messages";
|
|
@@ -6527,7 +6699,9 @@ async function executeAnthropic(request, opts) {
|
|
|
6527
6699
|
if (opts.onChunk) {
|
|
6528
6700
|
return streamAnthropic(request, apiKey, {
|
|
6529
6701
|
onChunk: opts.onChunk,
|
|
6530
|
-
fetchImpl: opts.fetchImpl
|
|
6702
|
+
fetchImpl: opts.fetchImpl,
|
|
6703
|
+
signal: opts.signal,
|
|
6704
|
+
stallTimeoutMs: opts.stallTimeoutMs
|
|
6531
6705
|
});
|
|
6532
6706
|
}
|
|
6533
6707
|
const { provider: _provider, ...body } = request;
|
|
@@ -6542,11 +6716,13 @@ async function executeAnthropic(request, opts) {
|
|
|
6542
6716
|
"anthropic-version": "2023-06-01",
|
|
6543
6717
|
"content-type": "application/json"
|
|
6544
6718
|
},
|
|
6545
|
-
body: JSON.stringify(body)
|
|
6719
|
+
body: JSON.stringify(body),
|
|
6720
|
+
signal: opts.signal
|
|
6546
6721
|
});
|
|
6547
6722
|
json = await res.json().catch(() => ({}));
|
|
6548
6723
|
} catch (err) {
|
|
6549
|
-
|
|
6724
|
+
const c = classifyThrownFetchError(err);
|
|
6725
|
+
return { ok: false, status: 0, errorType: c.errorType, errorCode: c.errorCode, message: c.message, raw: null };
|
|
6550
6726
|
}
|
|
6551
6727
|
if (!res.ok) return classifyHttpError2(res.status, json);
|
|
6552
6728
|
return { ok: true, status: res.status, response: normalizeAnthropic(json) };
|
|
@@ -6578,11 +6754,13 @@ async function executeGoogle(request, opts) {
|
|
|
6578
6754
|
res = await fetchFn(url, {
|
|
6579
6755
|
method: "POST",
|
|
6580
6756
|
headers: { "content-type": "application/json" },
|
|
6581
|
-
body: JSON.stringify(body)
|
|
6757
|
+
body: JSON.stringify(body),
|
|
6758
|
+
signal: opts.signal
|
|
6582
6759
|
});
|
|
6583
6760
|
json = await res.json().catch(() => ({}));
|
|
6584
6761
|
} catch (err) {
|
|
6585
|
-
|
|
6762
|
+
const c = classifyThrownFetchError(err);
|
|
6763
|
+
return { ok: false, status: 0, errorType: c.errorType, errorCode: c.errorCode, message: c.message, raw: null };
|
|
6586
6764
|
}
|
|
6587
6765
|
if (!res.ok) return classifyHttpError2(res.status, json);
|
|
6588
6766
|
return { ok: true, status: res.status, response: normalizeGoogle(json) };
|
|
@@ -6614,7 +6792,9 @@ async function executeOpenAI(request, opts) {
|
|
|
6614
6792
|
if (opts.onChunk) {
|
|
6615
6793
|
return streamOpenAILike(OPENAI_URL, request, apiKey, "openai", {
|
|
6616
6794
|
onChunk: opts.onChunk,
|
|
6617
|
-
fetchImpl: opts.fetchImpl
|
|
6795
|
+
fetchImpl: opts.fetchImpl,
|
|
6796
|
+
signal: opts.signal,
|
|
6797
|
+
stallTimeoutMs: opts.stallTimeoutMs
|
|
6618
6798
|
});
|
|
6619
6799
|
}
|
|
6620
6800
|
const { provider: _provider, ...body } = request;
|
|
@@ -6625,11 +6805,13 @@ async function executeOpenAI(request, opts) {
|
|
|
6625
6805
|
res = await fetchFn(OPENAI_URL, {
|
|
6626
6806
|
method: "POST",
|
|
6627
6807
|
headers: { authorization: `Bearer ${apiKey}`, "content-type": "application/json" },
|
|
6628
|
-
body: JSON.stringify(body)
|
|
6808
|
+
body: JSON.stringify(body),
|
|
6809
|
+
signal: opts.signal
|
|
6629
6810
|
});
|
|
6630
6811
|
json = await res.json().catch(() => ({}));
|
|
6631
6812
|
} catch (err) {
|
|
6632
|
-
|
|
6813
|
+
const c = classifyThrownFetchError(err);
|
|
6814
|
+
return { ok: false, status: 0, errorType: c.errorType, errorCode: c.errorCode, message: c.message, raw: null };
|
|
6633
6815
|
}
|
|
6634
6816
|
if (!res.ok) return classifyHttpError2(res.status, json);
|
|
6635
6817
|
return { ok: true, status: res.status, response: normalizeOpenAILike(json) };
|
|
@@ -6642,7 +6824,9 @@ async function executeDeepSeek(request, opts) {
|
|
|
6642
6824
|
if (opts.onChunk) {
|
|
6643
6825
|
return streamOpenAILike(DEEPSEEK_URL, request, apiKey, "deepseek", {
|
|
6644
6826
|
onChunk: opts.onChunk,
|
|
6645
|
-
fetchImpl: opts.fetchImpl
|
|
6827
|
+
fetchImpl: opts.fetchImpl,
|
|
6828
|
+
signal: opts.signal,
|
|
6829
|
+
stallTimeoutMs: opts.stallTimeoutMs
|
|
6646
6830
|
});
|
|
6647
6831
|
}
|
|
6648
6832
|
const { provider: _provider, ...body } = request;
|
|
@@ -6653,11 +6837,13 @@ async function executeDeepSeek(request, opts) {
|
|
|
6653
6837
|
res = await fetchFn(DEEPSEEK_URL, {
|
|
6654
6838
|
method: "POST",
|
|
6655
6839
|
headers: { authorization: `Bearer ${apiKey}`, "content-type": "application/json" },
|
|
6656
|
-
body: JSON.stringify(body)
|
|
6840
|
+
body: JSON.stringify(body),
|
|
6841
|
+
signal: opts.signal
|
|
6657
6842
|
});
|
|
6658
6843
|
json = await res.json().catch(() => ({}));
|
|
6659
6844
|
} catch (err) {
|
|
6660
|
-
|
|
6845
|
+
const c = classifyThrownFetchError(err);
|
|
6846
|
+
return { ok: false, status: 0, errorType: c.errorType, errorCode: c.errorCode, message: c.message, raw: null };
|
|
6661
6847
|
}
|
|
6662
6848
|
if (!res.ok) return classifyHttpError2(res.status, json);
|
|
6663
6849
|
return { ok: true, status: res.status, response: normalizeOpenAILike(json) };
|
|
@@ -6670,7 +6856,9 @@ async function executeOpenAICompatible(request, opts, spec) {
|
|
|
6670
6856
|
if (opts.onChunk) {
|
|
6671
6857
|
return streamOpenAILike(spec.url, request, apiKey, spec.provider, {
|
|
6672
6858
|
onChunk: opts.onChunk,
|
|
6673
|
-
fetchImpl: opts.fetchImpl
|
|
6859
|
+
fetchImpl: opts.fetchImpl,
|
|
6860
|
+
signal: opts.signal,
|
|
6861
|
+
stallTimeoutMs: opts.stallTimeoutMs
|
|
6674
6862
|
});
|
|
6675
6863
|
}
|
|
6676
6864
|
const { provider: _provider, ...body } = request;
|
|
@@ -6681,11 +6869,13 @@ async function executeOpenAICompatible(request, opts, spec) {
|
|
|
6681
6869
|
res = await fetchFn(spec.url, {
|
|
6682
6870
|
method: "POST",
|
|
6683
6871
|
headers: { authorization: `Bearer ${apiKey}`, "content-type": "application/json" },
|
|
6684
|
-
body: JSON.stringify(body)
|
|
6872
|
+
body: JSON.stringify(body),
|
|
6873
|
+
signal: opts.signal
|
|
6685
6874
|
});
|
|
6686
6875
|
json = await res.json().catch(() => ({}));
|
|
6687
6876
|
} catch (err) {
|
|
6688
|
-
|
|
6877
|
+
const c = classifyThrownFetchError(err);
|
|
6878
|
+
return { ok: false, status: 0, errorType: c.errorType, errorCode: c.errorCode, message: c.message, raw: null };
|
|
6689
6879
|
}
|
|
6690
6880
|
if (!res.ok) return classifyHttpError2(res.status, json);
|
|
6691
6881
|
return { ok: true, status: res.status, response: normalizeOpenAILike(json) };
|
|
@@ -6755,9 +6945,6 @@ function extractErrorMessage2(body) {
|
|
|
6755
6945
|
function terminalError(status, code, message) {
|
|
6756
6946
|
return { ok: false, status, errorType: "terminal", errorCode: code, message, raw: null };
|
|
6757
6947
|
}
|
|
6758
|
-
function retryableError2(status, code, message, raw) {
|
|
6759
|
-
return { ok: false, status, errorType: "retryable", errorCode: code, message, raw };
|
|
6760
|
-
}
|
|
6761
6948
|
function tryParseJson2(s) {
|
|
6762
6949
|
if (typeof s !== "string" || s.length === 0) return void 0;
|
|
6763
6950
|
try {
|
|
@@ -7566,6 +7753,15 @@ async function call(ir, opts = {}) {
|
|
|
7566
7753
|
let retriedSameModel = false;
|
|
7567
7754
|
for (let i = 0; i < targetsToTry.length; i++) {
|
|
7568
7755
|
const targetModel = targetsToTry[i];
|
|
7756
|
+
if (opts.abortSignal?.aborted) {
|
|
7757
|
+
attempts.push({
|
|
7758
|
+
model: targetModel,
|
|
7759
|
+
status: "terminal",
|
|
7760
|
+
errorCode: "aborted",
|
|
7761
|
+
message: "Skipped \u2014 caller abortSignal fired before this attempt started"
|
|
7762
|
+
});
|
|
7763
|
+
break;
|
|
7764
|
+
}
|
|
7569
7765
|
const targetProfile = tryGetProfile(targetModel);
|
|
7570
7766
|
const providerFailReason = targetProfile ? failedProviders.get(targetProfile.provider) : void 0;
|
|
7571
7767
|
if (targetProfile && providerFailReason && !opts.noFallback) {
|
|
@@ -7602,16 +7798,29 @@ async function call(ir, opts = {}) {
|
|
|
7602
7798
|
);
|
|
7603
7799
|
const targetSupportsStreaming = targetProfile?.streaming === true;
|
|
7604
7800
|
const streamingOnChunk = opts.onChunk && !opts.noStream && targetSupportsStreaming ? opts.onChunk : void 0;
|
|
7605
|
-
const
|
|
7801
|
+
const mkExecOpts = () => ({
|
|
7606
7802
|
apiKeys: opts.apiKeys,
|
|
7607
7803
|
fetchImpl: opts.fetchImpl,
|
|
7608
7804
|
providerOverrides: opts.providerOverrides,
|
|
7609
|
-
onChunk: streamingOnChunk
|
|
7610
|
-
|
|
7611
|
-
|
|
7805
|
+
onChunk: streamingOnChunk,
|
|
7806
|
+
signal: composeAttemptSignal(
|
|
7807
|
+
streamingOnChunk ? void 0 : opts.attemptTimeoutMs,
|
|
7808
|
+
opts.abortSignal
|
|
7809
|
+
),
|
|
7810
|
+
stallTimeoutMs: streamingOnChunk ? opts.attemptTimeoutMs : void 0
|
|
7811
|
+
});
|
|
7812
|
+
const exec = await execute(activeCompile.request, mkExecOpts());
|
|
7612
7813
|
let validated = exec.ok ? validateStructuredContract(exec, ir) : exec;
|
|
7613
7814
|
let servedByRetry = false;
|
|
7614
|
-
|
|
7815
|
+
let retrySuppressionNote;
|
|
7816
|
+
if (!validated.ok && isStructuredContractViolation(validated.errorCode) && sameModelRetryEnabled && !retriedSameModel && getMeasuredFailureVerdict({
|
|
7817
|
+
appId: ir.appId,
|
|
7818
|
+
archetype: ir.intent.archetype,
|
|
7819
|
+
model: targetModel
|
|
7820
|
+
})?.gated === true) {
|
|
7821
|
+
retrySuppressionNote = " [sameModelRetry suppressed: this model carries an active measured-failure gate for this archetype \u2014 retrying it would near-certainly bill a second doomed inference; walking the chain instead]";
|
|
7822
|
+
}
|
|
7823
|
+
if (!validated.ok && isStructuredContractViolation(validated.errorCode) && sameModelRetryEnabled && !retriedSameModel && !retrySuppressionNote) {
|
|
7615
7824
|
retriedSameModel = true;
|
|
7616
7825
|
attempts.push({
|
|
7617
7826
|
model: targetModel,
|
|
@@ -7623,7 +7832,7 @@ async function call(ir, opts = {}) {
|
|
|
7623
7832
|
safeEmit(
|
|
7624
7833
|
() => emitExecuteAttempt(traceId, ir.appId, { model: targetModel, attemptIndex: i })
|
|
7625
7834
|
);
|
|
7626
|
-
const retryExec = await execute(retryRequest,
|
|
7835
|
+
const retryExec = await execute(retryRequest, mkExecOpts());
|
|
7627
7836
|
validated = retryExec.ok ? validateStructuredContract(retryExec, ir) : retryExec;
|
|
7628
7837
|
servedByRetry = true;
|
|
7629
7838
|
}
|
|
@@ -7750,7 +7959,7 @@ async function call(ir, opts = {}) {
|
|
|
7750
7959
|
model: targetModel,
|
|
7751
7960
|
status: validated.errorType,
|
|
7752
7961
|
errorCode: validated.errorCode,
|
|
7753
|
-
message: validated.message,
|
|
7962
|
+
message: retrySuppressionNote ? validated.message + retrySuppressionNote : validated.message,
|
|
7754
7963
|
...servedByRetry ? { sameModelRetry: true } : {}
|
|
7755
7964
|
});
|
|
7756
7965
|
lastErr = validated;
|
|
@@ -7789,6 +7998,25 @@ async function call(ir, opts = {}) {
|
|
|
7789
7998
|
lastErr?.errorCode
|
|
7790
7999
|
);
|
|
7791
8000
|
}
|
|
8001
|
+
function composeAttemptSignal(attemptTimeoutMs, callerSignal) {
|
|
8002
|
+
const signals = [];
|
|
8003
|
+
if (callerSignal) signals.push(callerSignal);
|
|
8004
|
+
if (typeof attemptTimeoutMs === "number" && attemptTimeoutMs > 0) {
|
|
8005
|
+
signals.push(AbortSignal.timeout(attemptTimeoutMs));
|
|
8006
|
+
}
|
|
8007
|
+
if (signals.length === 0) return void 0;
|
|
8008
|
+
if (signals.length === 1) return signals[0];
|
|
8009
|
+
if (typeof AbortSignal.any === "function") return AbortSignal.any(signals);
|
|
8010
|
+
const ctl = new AbortController();
|
|
8011
|
+
for (const s of signals) {
|
|
8012
|
+
if (s.aborted) {
|
|
8013
|
+
ctl.abort(s.reason);
|
|
8014
|
+
break;
|
|
8015
|
+
}
|
|
8016
|
+
s.addEventListener("abort", () => ctl.abort(s.reason), { once: true });
|
|
8017
|
+
}
|
|
8018
|
+
return ctl.signal;
|
|
8019
|
+
}
|
|
7792
8020
|
function compileAndRegister(ir, opts) {
|
|
7793
8021
|
const result = compile(ir, {
|
|
7794
8022
|
policy: opts.policy,
|
|
@@ -8843,6 +9071,12 @@ async function runGoldenEval(opts) {
|
|
|
8843
9071
|
);
|
|
8844
9072
|
}
|
|
8845
9073
|
}
|
|
9074
|
+
if (wins + ties + losses === 0) {
|
|
9075
|
+
notes.push(
|
|
9076
|
+
"zero cases judged \u2014 run row persisted, evidence advisory deliberately NOT written (a verdict on n=0 is not evidence)"
|
|
9077
|
+
);
|
|
9078
|
+
return result;
|
|
9079
|
+
}
|
|
8846
9080
|
const latestRes = await fetchFn(
|
|
8847
9081
|
rest(
|
|
8848
9082
|
`compile_outcomes?app_id=eq.${encodeURIComponent(opts.appId)}&select=id&order=id.desc&limit=1`
|
|
@@ -9078,7 +9312,7 @@ function createBrainForwardRoutes(config) {
|
|
|
9078
9312
|
}
|
|
9079
9313
|
|
|
9080
9314
|
// src/version.ts
|
|
9081
|
-
var LIBRARY_VERSION = "2.0.0-alpha.
|
|
9315
|
+
var LIBRARY_VERSION = "2.0.0-alpha.78";
|
|
9082
9316
|
|
|
9083
9317
|
// src/key-health.ts
|
|
9084
9318
|
var JSON_HEADERS2 = { "Content-Type": "application/json" };
|
|
@@ -9435,19 +9669,32 @@ function resolveFetch(injected) {
|
|
|
9435
9669
|
function normalizeEndpoint2(endpoint) {
|
|
9436
9670
|
return endpoint.replace(/\/+$/, "");
|
|
9437
9671
|
}
|
|
9672
|
+
function resolveBrainReadTrio(opts) {
|
|
9673
|
+
const env = readBrainReadEnv();
|
|
9674
|
+
const brainEndpoint = opts.brainEndpoint ?? env.endpoint;
|
|
9675
|
+
const brainJwt = opts.brainJwt ?? env.jwt;
|
|
9676
|
+
const brainAnonKey = opts.brainAnonKey ?? env.anonKey;
|
|
9677
|
+
if (brainEndpoint && brainJwt && brainAnonKey) {
|
|
9678
|
+
return { ok: true, brainEndpoint, brainJwt, brainAnonKey };
|
|
9679
|
+
}
|
|
9680
|
+
const missingEnv = [];
|
|
9681
|
+
if (!brainEndpoint) missingEnv.push("KGAUTO_V2_BRAIN_SUPABASE_URL");
|
|
9682
|
+
if (!brainJwt) missingEnv.push("KGAUTO_V2_BRAIN_JWT");
|
|
9683
|
+
if (!brainAnonKey) missingEnv.push("KGAUTO_V2_BRAIN_ANON_KEY");
|
|
9684
|
+
return { ok: false, missingEnv };
|
|
9685
|
+
}
|
|
9438
9686
|
async function getActionableAdvisories(opts) {
|
|
9439
|
-
const {
|
|
9440
|
-
appId,
|
|
9441
|
-
severity,
|
|
9442
|
-
status,
|
|
9443
|
-
brainEndpoint,
|
|
9444
|
-
brainJwt,
|
|
9445
|
-
brainAnonKey,
|
|
9446
|
-
fetch: injectedFetch
|
|
9447
|
-
} = opts;
|
|
9687
|
+
const { appId, severity, status, fetch: injectedFetch } = opts;
|
|
9448
9688
|
if (!appId) {
|
|
9449
9689
|
throw new Error("getActionableAdvisories: appId is required");
|
|
9450
9690
|
}
|
|
9691
|
+
const trio = resolveBrainReadTrio(opts);
|
|
9692
|
+
if (!trio.ok) {
|
|
9693
|
+
throw new Error(
|
|
9694
|
+
`getActionableAdvisories: brain-read-not-configured \u2014 missing ${trio.missingEnv.join(", ")} (pass brainEndpoint/brainJwt/brainAnonKey explicitly or set the canonical env vars)`
|
|
9695
|
+
);
|
|
9696
|
+
}
|
|
9697
|
+
const { brainEndpoint, brainJwt, brainAnonKey } = trio;
|
|
9451
9698
|
const doFetch = resolveFetch(injectedFetch);
|
|
9452
9699
|
const base = normalizeEndpoint2(brainEndpoint);
|
|
9453
9700
|
const qs = new URLSearchParams();
|
|
@@ -9500,17 +9747,15 @@ async function getActionableAdvisories(opts) {
|
|
|
9500
9747
|
return out;
|
|
9501
9748
|
}
|
|
9502
9749
|
async function markAdvisoryResolved(opts) {
|
|
9503
|
-
const {
|
|
9504
|
-
id,
|
|
9505
|
-
resolutionNote,
|
|
9506
|
-
brainEndpoint,
|
|
9507
|
-
brainJwt,
|
|
9508
|
-
brainAnonKey,
|
|
9509
|
-
fetch: injectedFetch
|
|
9510
|
-
} = opts;
|
|
9750
|
+
const { id, resolutionNote, fetch: injectedFetch } = opts;
|
|
9511
9751
|
if (!id) {
|
|
9512
9752
|
return { ok: false, reason: "id_required" };
|
|
9513
9753
|
}
|
|
9754
|
+
const trio = resolveBrainReadTrio(opts);
|
|
9755
|
+
if (!trio.ok) {
|
|
9756
|
+
return { ok: false, reason: `brain_read_not_configured:${trio.missingEnv.join(",")}` };
|
|
9757
|
+
}
|
|
9758
|
+
const { brainEndpoint, brainJwt, brainAnonKey } = trio;
|
|
9514
9759
|
const doFetch = resolveFetch(injectedFetch);
|
|
9515
9760
|
const base = normalizeEndpoint2(brainEndpoint);
|
|
9516
9761
|
const lookupUrl = `${base}/rest/v1/actionable_advisories_v?id=eq.${encodeURIComponent(id)}&select=app_id,rule`;
|
|
@@ -9595,7 +9840,7 @@ async function markAdvisoryResolved(opts) {
|
|
|
9595
9840
|
}
|
|
9596
9841
|
}
|
|
9597
9842
|
if (outcomeIds.length === 0) {
|
|
9598
|
-
return { ok: true };
|
|
9843
|
+
return { ok: true, firingsResolved: 0, status: "unknown" };
|
|
9599
9844
|
}
|
|
9600
9845
|
const inList = outcomeIds.join(",");
|
|
9601
9846
|
const patchUrl = `${base}/rest/v1/compile_outcome_advisories?outcome_id=in.(${inList})&code=eq.${encodeURIComponent(code)}&resolved_at=is.null`;
|
|
@@ -9615,8 +9860,10 @@ async function markAdvisoryResolved(opts) {
|
|
|
9615
9860
|
apikey: brainAnonKey,
|
|
9616
9861
|
"Content-Type": "application/json",
|
|
9617
9862
|
Accept: "application/json",
|
|
9618
|
-
//
|
|
9619
|
-
|
|
9863
|
+
// alpha.78 — count what actually changed. An RLS-filtered PATCH
|
|
9864
|
+
// returns 2xx with zero rows; return=representation makes that
|
|
9865
|
+
// visible instead of success-shaped.
|
|
9866
|
+
Prefer: "return=representation"
|
|
9620
9867
|
},
|
|
9621
9868
|
body: JSON.stringify(patchBody)
|
|
9622
9869
|
});
|
|
@@ -9633,7 +9880,33 @@ async function markAdvisoryResolved(opts) {
|
|
|
9633
9880
|
if (!patchRes.ok) {
|
|
9634
9881
|
return { ok: false, reason: `patch_failed:${patchRes.status}` };
|
|
9635
9882
|
}
|
|
9636
|
-
|
|
9883
|
+
let patchedRows = [];
|
|
9884
|
+
try {
|
|
9885
|
+
patchedRows = await patchRes.json();
|
|
9886
|
+
} catch {
|
|
9887
|
+
}
|
|
9888
|
+
const firingsResolved = Array.isArray(patchedRows) ? patchedRows.length : 0;
|
|
9889
|
+
let status = "unknown";
|
|
9890
|
+
try {
|
|
9891
|
+
const statusRes = await doFetch(
|
|
9892
|
+
`${base}/rest/v1/actionable_advisories_v?id=eq.${encodeURIComponent(id)}&select=status`,
|
|
9893
|
+
{
|
|
9894
|
+
method: "GET",
|
|
9895
|
+
headers: {
|
|
9896
|
+
Authorization: `Bearer ${brainJwt}`,
|
|
9897
|
+
apikey: brainAnonKey,
|
|
9898
|
+
Accept: "application/json"
|
|
9899
|
+
}
|
|
9900
|
+
}
|
|
9901
|
+
);
|
|
9902
|
+
if (statusRes.ok) {
|
|
9903
|
+
const rows = await statusRes.json();
|
|
9904
|
+
const s = Array.isArray(rows) ? rows[0]?.status : void 0;
|
|
9905
|
+
if (s === "open" || s === "resolved") status = s;
|
|
9906
|
+
}
|
|
9907
|
+
} catch {
|
|
9908
|
+
}
|
|
9909
|
+
return { ok: true, firingsResolved, status };
|
|
9637
9910
|
}
|
|
9638
9911
|
async function markExclusionFindingHandled(opts) {
|
|
9639
9912
|
const {
|
|
@@ -9642,12 +9915,14 @@ async function markExclusionFindingHandled(opts) {
|
|
|
9642
9915
|
excludedModel,
|
|
9643
9916
|
resolution,
|
|
9644
9917
|
resolutionNote,
|
|
9645
|
-
brainEndpoint,
|
|
9646
|
-
brainJwt,
|
|
9647
|
-
brainAnonKey,
|
|
9648
9918
|
fetch: injectedFetch
|
|
9649
9919
|
} = opts;
|
|
9650
9920
|
if (!appId) return { ok: false, reason: "app_id_required" };
|
|
9921
|
+
const trio = resolveBrainReadTrio(opts);
|
|
9922
|
+
if (!trio.ok) {
|
|
9923
|
+
return { ok: false, reason: `brain_read_not_configured:${trio.missingEnv.join(",")}` };
|
|
9924
|
+
}
|
|
9925
|
+
const { brainEndpoint, brainJwt, brainAnonKey } = trio;
|
|
9651
9926
|
if (!archetype) return { ok: false, reason: "archetype_required" };
|
|
9652
9927
|
if (!excludedModel) {
|
|
9653
9928
|
return { ok: false, reason: "excluded_model_required" };
|
|
@@ -9914,6 +10189,7 @@ function compile2(ir, opts) {
|
|
|
9914
10189
|
ALL_ARCHETYPES,
|
|
9915
10190
|
ARCHETYPE_FAMILY_FITS,
|
|
9916
10191
|
ARCHETYPE_FLOOR_DEFAULT,
|
|
10192
|
+
BRAIN_READ_ENV_NAMES,
|
|
9917
10193
|
COACH_CFG,
|
|
9918
10194
|
CallError,
|
|
9919
10195
|
DECOMPOSITION_TEMPLATES,
|
|
@@ -9932,6 +10208,7 @@ function compile2(ir, opts) {
|
|
|
9932
10208
|
MEASURED_GROUNDING_MIN_N,
|
|
9933
10209
|
PRODUCER_OWNED_RULE_CODES,
|
|
9934
10210
|
PROVIDER_ENV_KEYS,
|
|
10211
|
+
ROLLBACK_SUPPRESSION_WINDOW_DAYS,
|
|
9935
10212
|
RULE_DISCIPLINE_GATES_V1,
|
|
9936
10213
|
RULE_DISCIPLINE_GATES_V1_STRUCTURED,
|
|
9937
10214
|
RULE_SEQUENTIAL_TOOL_CLIFF,
|
|
@@ -9985,12 +10262,14 @@ function compile2(ir, opts) {
|
|
|
9985
10262
|
getPerAxisMetrics,
|
|
9986
10263
|
getProfile,
|
|
9987
10264
|
getReachabilityDiagnostic,
|
|
10265
|
+
getRecentRollback,
|
|
9988
10266
|
getRecommendedPrimary,
|
|
9989
10267
|
getSequentialStarterChain,
|
|
9990
10268
|
getSequentialStarterChainWithGrounding,
|
|
9991
10269
|
getStaleExclusionFindings,
|
|
9992
10270
|
getStarterChain,
|
|
9993
10271
|
getStarterChainWithGrounding,
|
|
10272
|
+
hasMutation,
|
|
9994
10273
|
hashShape,
|
|
9995
10274
|
isArchetype,
|
|
9996
10275
|
isAutoPromoteEnabledFromEnv,
|
|
@@ -10011,9 +10290,11 @@ function compile2(ir, opts) {
|
|
|
10011
10290
|
loadChainsFromBrain,
|
|
10012
10291
|
loadModelsFromBrain,
|
|
10013
10292
|
loadPricingFromBrain,
|
|
10293
|
+
mapMeasuredFailureRows,
|
|
10014
10294
|
markAdvisoryResolved,
|
|
10015
10295
|
markExclusionFindingHandled,
|
|
10016
10296
|
markPromoteReadyHandled,
|
|
10297
|
+
mutationId,
|
|
10017
10298
|
parseGoldenCaptureRate,
|
|
10018
10299
|
parseJudgeVerdict,
|
|
10019
10300
|
peekBrainDeadLetter,
|