@wrongstack/core 0.306.3 → 0.306.4
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/coordination/index.js +136 -40
- package/dist/coordination/mail-tools.d.ts +1 -1
- package/dist/core/conversation-state.d.ts +1 -2
- package/dist/core/fallback-model.d.ts +19 -1
- package/dist/core/fallback-profile-manager.d.ts +21 -3
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.js +152 -46
- package/dist/defaults/index.js +295 -91
- package/dist/execution/auto-compaction-middleware.d.ts +64 -0
- package/dist/execution/compaction-core.d.ts +4 -0
- package/dist/execution/index.d.ts +1 -1
- package/dist/execution/index.js +230 -89
- package/dist/execution/retry-policy.d.ts +27 -0
- package/dist/hq/index.js +50 -15
- package/dist/index.d.ts +1 -1
- package/dist/index.js +588 -134
- package/dist/infrastructure/index.js +21 -0
- package/dist/plugin/index.js +195 -19
- package/dist/security/index.js +50 -15
- package/dist/session-catalog/index.js +50 -15
- package/dist/session-catalog/project-server.js +50 -15
- package/dist/storage/cloud-config-sync/sanitize.d.ts +18 -0
- package/dist/storage/cloud-config-sync.d.ts +1 -1
- package/dist/storage/index.js +276 -23
- package/dist/storage/provider-config-watcher.d.ts +9 -0
- package/dist/storage/session-store/strict-empty-check.d.ts +7 -0
- package/dist/storage/session-store.d.ts +1 -0
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.js +83 -22
- package/dist/types/config/root.d.ts +14 -0
- package/dist/types/session.d.ts +7 -0
- package/package.json +3 -3
package/dist/core/index.js
CHANGED
|
@@ -1722,6 +1722,24 @@ import { randomUUID } from "node:crypto";
|
|
|
1722
1722
|
import * as v8 from "node:v8";
|
|
1723
1723
|
|
|
1724
1724
|
// src/security/secret-scrubber.ts
|
|
1725
|
+
var JSON_CREDENTIAL_KEY_ANCHORS = [
|
|
1726
|
+
'Key"',
|
|
1727
|
+
'key"',
|
|
1728
|
+
'KEY"',
|
|
1729
|
+
'token"',
|
|
1730
|
+
'Token"',
|
|
1731
|
+
'TOKEN"',
|
|
1732
|
+
'secret"',
|
|
1733
|
+
'Secret"',
|
|
1734
|
+
'SECRET"',
|
|
1735
|
+
'password"',
|
|
1736
|
+
'Password"',
|
|
1737
|
+
'PASSWORD"',
|
|
1738
|
+
'authorization"',
|
|
1739
|
+
'Authorization"',
|
|
1740
|
+
'bearer"',
|
|
1741
|
+
'Bearer"'
|
|
1742
|
+
];
|
|
1725
1743
|
var PATTERNS = [
|
|
1726
1744
|
// Anchored at the start where possible so partial matches inside larger
|
|
1727
1745
|
// strings don't trigger false positives.
|
|
@@ -1824,6 +1842,30 @@ var PATTERNS = [
|
|
|
1824
1842
|
regex: /(^|\s)([A-Z_]{4,}(?:KEY|TOKEN|SECRET|PASSWORD|PWD))\s*[:=]\s*['"]?([A-Za-z0-9_/+=-]{20,512})['"]?(?=\s|$)/g,
|
|
1825
1843
|
anchor: ["KEY", "TOKEN", "SECRET", "PASSWORD", "PWD"]
|
|
1826
1844
|
},
|
|
1845
|
+
{
|
|
1846
|
+
type: "json_credential_key",
|
|
1847
|
+
// The JSON counterpart to `high_entropy_env`, and the pattern that the
|
|
1848
|
+
// now-deleted `JSON_KEY_ANCHORS` list was written for. Without it those
|
|
1849
|
+
// anchors only widened the cheap pre-scan — `hasCredentialAnchors` said
|
|
1850
|
+
// "this text may hold a secret", every pattern then declined to match, and
|
|
1851
|
+
// the value went out verbatim. `high_entropy_env` cannot cover these: it
|
|
1852
|
+
// requires an UPPERCASE unquoted key (`API_KEY=…`), so `{"apiKey":"…"}`
|
|
1853
|
+
// never matched.
|
|
1854
|
+
//
|
|
1855
|
+
// Tool results are routinely serialised as JSON, and a credential with no
|
|
1856
|
+
// recognisable prefix (Azure, self-hosted gateways, Anthropic/Codex OAuth)
|
|
1857
|
+
// has no other pattern that can catch it — this is the only thing standing
|
|
1858
|
+
// between such a value and the session JSONL, chronicle, HQ broadcast and
|
|
1859
|
+
// the model's own context.
|
|
1860
|
+
//
|
|
1861
|
+
// The key may carry a prefix (`"anthropicApiKey"`), but the credential word
|
|
1862
|
+
// must END the key: `"tokenCount"` and `"maxTokens"` do not match, because
|
|
1863
|
+
// the closing quote has to follow the word immediately.
|
|
1864
|
+
// Value floor of 8 chars keeps enum-ish values (`"authorization":"none"`)
|
|
1865
|
+
// out. Capture groups: 1=key + punctuation, 2=value, 3=closing quote.
|
|
1866
|
+
regex: /("[A-Za-z0-9_]*(?:apiKey|api_key|token|secret|password|authorization|bearer|private_key|access_token|refresh_token|client_secret)"\s*:\s*")([^"\\]{8,512})(")/gi,
|
|
1867
|
+
anchor: JSON_CREDENTIAL_KEY_ANCHORS
|
|
1868
|
+
},
|
|
1827
1869
|
// ── Ported from packages/plugins credential-patterns.ts (WS-034) ─────────
|
|
1828
1870
|
// The plugin runtime carried 37 patterns while this scrubber — the one that
|
|
1829
1871
|
// guards session JSONL, chronicle, HQ broadcast, WebUI events and the auth
|
|
@@ -1906,9 +1948,12 @@ var PATTERNS = [
|
|
|
1906
1948
|
anchor: "GOCSPX-"
|
|
1907
1949
|
}
|
|
1908
1950
|
];
|
|
1909
|
-
var SIMPLE_PATTERNS = PATTERNS.filter(
|
|
1951
|
+
var SIMPLE_PATTERNS = PATTERNS.filter(
|
|
1952
|
+
(p) => p.type !== "high_entropy_env" && p.type !== "json_credential_key"
|
|
1953
|
+
);
|
|
1910
1954
|
var COMBINED_REGEX = new RegExp(SIMPLE_PATTERNS.map((p) => `(${p.regex.source})`).join("|"), "g");
|
|
1911
1955
|
var HIGH_ENTROPY_REGEX = PATTERNS.find((p) => p.type === "high_entropy_env").regex;
|
|
1956
|
+
var JSON_CREDENTIAL_REGEX = PATTERNS.find((p) => p.type === "json_credential_key").regex;
|
|
1912
1957
|
var COMBINED_REPLACEMENTS = SIMPLE_PATTERNS.map((p) => `[REDACTED:${p.type}]`);
|
|
1913
1958
|
var SCRUB_CHUNK_BYTES = 64 * 1024;
|
|
1914
1959
|
var SCRUB_OVERLAP_BYTES = 1024;
|
|
@@ -1919,20 +1964,7 @@ var PATTERN_ANCHORS = [
|
|
|
1919
1964
|
)
|
|
1920
1965
|
)
|
|
1921
1966
|
];
|
|
1922
|
-
var
|
|
1923
|
-
'"apiKey"',
|
|
1924
|
-
'"api_key"',
|
|
1925
|
-
'"token"',
|
|
1926
|
-
'"secret"',
|
|
1927
|
-
'"password"',
|
|
1928
|
-
'"authorization"',
|
|
1929
|
-
'"bearer"',
|
|
1930
|
-
'"private_key"',
|
|
1931
|
-
'"access_token"',
|
|
1932
|
-
'"refresh_token"',
|
|
1933
|
-
'"client_secret"'
|
|
1934
|
-
];
|
|
1935
|
-
var ALL_ANCHORS = [...PATTERN_ANCHORS, ...JSON_KEY_ANCHORS];
|
|
1967
|
+
var ALL_ANCHORS = PATTERN_ANCHORS;
|
|
1936
1968
|
function hasCredentialAnchors(text) {
|
|
1937
1969
|
for (const anchor of ALL_ANCHORS) {
|
|
1938
1970
|
if (text.includes(anchor)) return true;
|
|
@@ -1981,6 +2013,9 @@ var DefaultSecretScrubber = class {
|
|
|
1981
2013
|
out = out.replace(HIGH_ENTROPY_REGEX, (_match, lead, key, _value) => {
|
|
1982
2014
|
return `${lead}${key}=[REDACTED:high_entropy_env]`;
|
|
1983
2015
|
});
|
|
2016
|
+
out = out.replace(JSON_CREDENTIAL_REGEX, (_match, keyPrefix, _value, closingQuote) => {
|
|
2017
|
+
return `${keyPrefix}[REDACTED:json_credential_key]${closingQuote}`;
|
|
2018
|
+
});
|
|
1984
2019
|
return out;
|
|
1985
2020
|
}
|
|
1986
2021
|
/**
|
|
@@ -4669,6 +4704,7 @@ function getCalibrationState(calibrationKey = CALIBRATION_GLOBAL_KEY) {
|
|
|
4669
4704
|
}
|
|
4670
4705
|
|
|
4671
4706
|
// src/core/context.ts
|
|
4707
|
+
import { realpathSync } from "node:fs";
|
|
4672
4708
|
import * as path10 from "node:path";
|
|
4673
4709
|
|
|
4674
4710
|
// src/core/conversation-state.ts
|
|
@@ -4744,12 +4780,15 @@ var ConversationState = class {
|
|
|
4744
4780
|
* cap determines the starting index for the sum but does not gate it.
|
|
4745
4781
|
*/
|
|
4746
4782
|
overflowCount(arr) {
|
|
4747
|
-
|
|
4748
|
-
|
|
4783
|
+
const contextClass = this.ctx.constructor;
|
|
4784
|
+
const maxMessages = contextClass.MAX_MESSAGES;
|
|
4785
|
+
const maxMessageTokens = contextClass.MAX_MESSAGE_TOKENS;
|
|
4786
|
+
let drop = maxMessages > 0 ? Math.max(0, arr.length - maxMessages) : 0;
|
|
4787
|
+
if (maxMessageTokens <= 0) return this.protocolSafeDropCount(arr, drop);
|
|
4749
4788
|
let total = 0;
|
|
4750
4789
|
for (let i = drop; i < arr.length; i++) total += arr[i]?._estTokens ?? 0;
|
|
4751
|
-
if (total <=
|
|
4752
|
-
while (drop < arr.length - 1 && total >
|
|
4790
|
+
if (total <= maxMessageTokens) return this.protocolSafeDropCount(arr, drop);
|
|
4791
|
+
while (drop < arr.length - 1 && total > maxMessageTokens) {
|
|
4753
4792
|
total -= arr[drop]?._estTokens ?? 0;
|
|
4754
4793
|
drop++;
|
|
4755
4794
|
}
|
|
@@ -5575,6 +5614,19 @@ var Context = class _Context {
|
|
|
5575
5614
|
if (rel.startsWith("..") || path10.isAbsolute(rel)) {
|
|
5576
5615
|
throw new Error(`Working directory "${resolved}" is outside project root "${root}"`);
|
|
5577
5616
|
}
|
|
5617
|
+
let realTarget = resolved;
|
|
5618
|
+
let realRoot = root;
|
|
5619
|
+
try {
|
|
5620
|
+
realTarget = realpathSync.native(resolved);
|
|
5621
|
+
realRoot = realpathSync.native(root);
|
|
5622
|
+
} catch {
|
|
5623
|
+
}
|
|
5624
|
+
const realRel = path10.relative(realRoot, realTarget);
|
|
5625
|
+
if (realRel.startsWith("..") || path10.isAbsolute(realRel)) {
|
|
5626
|
+
throw new Error(
|
|
5627
|
+
`Working directory "${resolved}" resolves to "${realTarget}", outside project root "${realRoot}"`
|
|
5628
|
+
);
|
|
5629
|
+
}
|
|
5578
5630
|
}
|
|
5579
5631
|
const old = this.workingDir;
|
|
5580
5632
|
this.workingDir = resolved;
|
|
@@ -8119,26 +8171,41 @@ function stripNextStepsFromMessage(msg) {
|
|
|
8119
8171
|
strippedNextStepsCache.set(msg, clone);
|
|
8120
8172
|
return clone;
|
|
8121
8173
|
}
|
|
8122
|
-
function
|
|
8174
|
+
function markCacheBoundary(msg) {
|
|
8175
|
+
if (typeof msg.content === "string") return void 0;
|
|
8176
|
+
const blocks = msg.content.slice();
|
|
8177
|
+
const boundary = blocks[blocks.length - 1];
|
|
8178
|
+
if (!boundary || boundary.type !== "text" && boundary.type !== "tool_result") return void 0;
|
|
8179
|
+
blocks[blocks.length - 1] = { ...boundary, cache_control: { type: "ephemeral" } };
|
|
8180
|
+
return { ...msg, content: blocks };
|
|
8181
|
+
}
|
|
8182
|
+
function composeRequestMessages(history, tail, previous) {
|
|
8123
8183
|
if (history.length === 0) return null;
|
|
8124
8184
|
const out = history.slice();
|
|
8125
8185
|
const lastIdx = out.length - 1;
|
|
8126
8186
|
const last = out[lastIdx];
|
|
8187
|
+
if (previous && previous.index < lastIdx && history[previous.index] === previous.message) {
|
|
8188
|
+
const marked2 = markCacheBoundary(previous.message);
|
|
8189
|
+
if (marked2) out[previous.index] = marked2;
|
|
8190
|
+
}
|
|
8127
8191
|
const blocks = typeof last.content === "string" ? [{ type: "text", text: last.content }] : last.content.slice();
|
|
8128
|
-
const
|
|
8129
|
-
|
|
8130
|
-
|
|
8192
|
+
const tailBlock = blocks[blocks.length - 1];
|
|
8193
|
+
const marked = tailBlock && (tailBlock.type === "text" || tailBlock.type === "tool_result");
|
|
8194
|
+
if (marked) {
|
|
8195
|
+
blocks[blocks.length - 1] = { ...tailBlock, cache_control: { type: "ephemeral" } };
|
|
8131
8196
|
}
|
|
8197
|
+
const boundary = marked ? { message: last, index: lastIdx } : void 0;
|
|
8132
8198
|
if (tail.length === 0 || last.role !== "user") {
|
|
8133
8199
|
out[lastIdx] = { ...last, content: blocks };
|
|
8134
8200
|
if (tail.length > 0) out.push({ role: "user", content: [LIVE_CONTEXT_HEADER, ...tail] });
|
|
8135
|
-
return out;
|
|
8201
|
+
return { messages: out, boundary };
|
|
8136
8202
|
}
|
|
8137
8203
|
out[lastIdx] = { ...last, content: [...blocks, LIVE_CONTEXT_HEADER, ...tail] };
|
|
8138
|
-
return out;
|
|
8204
|
+
return { messages: out, boundary };
|
|
8139
8205
|
}
|
|
8140
8206
|
function createAgentResponseHandler(a) {
|
|
8141
8207
|
const stabilizedPromptEpochs = /* @__PURE__ */ new WeakSet();
|
|
8208
|
+
let previousBoundary;
|
|
8142
8209
|
function stabilizePromptEpoch() {
|
|
8143
8210
|
const prompt = a.ctx.systemPrompt;
|
|
8144
8211
|
if (stabilizedPromptEpochs.has(prompt)) return;
|
|
@@ -8179,7 +8246,9 @@ function createAgentResponseHandler(a) {
|
|
|
8179
8246
|
...memoryEvidence
|
|
8180
8247
|
].filter((block) => block !== void 0);
|
|
8181
8248
|
const requestHistory = stripDeliveredNextSteps(a.ctx.messages);
|
|
8182
|
-
const
|
|
8249
|
+
const composed = composeRequestMessages(requestHistory, liveContextTail, previousBoundary);
|
|
8250
|
+
if (composed) previousBoundary = composed.boundary;
|
|
8251
|
+
const composedMessages = composed?.messages ?? null;
|
|
8183
8252
|
const system = composedMessages ? stableSystem : liveContextTail.length > 0 ? [...stableSystem, ...liveContextTail] : stableSystem;
|
|
8184
8253
|
await a.ctx.waitForModelTransition();
|
|
8185
8254
|
const provider = a.ctx.provider;
|
|
@@ -9326,6 +9395,12 @@ function normalizeModelRef(ref, defaultProvider) {
|
|
|
9326
9395
|
function hasText(value) {
|
|
9327
9396
|
return typeof value === "string" && value.trim().length > 0;
|
|
9328
9397
|
}
|
|
9398
|
+
function asRefList(value) {
|
|
9399
|
+
return Array.isArray(value) ? value : void 0;
|
|
9400
|
+
}
|
|
9401
|
+
function asProfileName(value) {
|
|
9402
|
+
return hasText(value) ? value : void 0;
|
|
9403
|
+
}
|
|
9329
9404
|
function providerHasKey(entry) {
|
|
9330
9405
|
if (!entry) return false;
|
|
9331
9406
|
if (hasText(entry.apiKey)) return true;
|
|
@@ -9336,7 +9411,7 @@ function providerHasKey(entry) {
|
|
|
9336
9411
|
}
|
|
9337
9412
|
function visibleProviderModels(config, providerId, providerModels) {
|
|
9338
9413
|
const entry = config.providers?.[providerId];
|
|
9339
|
-
return entry?.models
|
|
9414
|
+
return Array.isArray(entry?.models) ? [...entry.models] : providerModels;
|
|
9340
9415
|
}
|
|
9341
9416
|
function buildProfiles(config) {
|
|
9342
9417
|
const entries = /* @__PURE__ */ new Map();
|
|
@@ -9373,13 +9448,34 @@ var FallbackProfileManager = class {
|
|
|
9373
9448
|
listProfiles() {
|
|
9374
9449
|
return Object.freeze([...this.profiles.keys()]);
|
|
9375
9450
|
}
|
|
9451
|
+
/**
|
|
9452
|
+
* The profile the session has selected (`config.fallbackProfile`, set by
|
|
9453
|
+
* `/fallback profile use <name>`), or undefined when none is selected or the
|
|
9454
|
+
* name no longer resolves to a defined profile.
|
|
9455
|
+
*
|
|
9456
|
+
* Consulted by every resolution entry point when the caller does not name a
|
|
9457
|
+
* profile itself. Without this the leader — which passes no profile — could
|
|
9458
|
+
* never use a named profile at all: `config.fallbackProfiles` was reachable
|
|
9459
|
+
* only by copying a chain into `fallbackModels`.
|
|
9460
|
+
*/
|
|
9461
|
+
activeProfileName() {
|
|
9462
|
+
const name = asProfileName(this.config.fallbackProfile);
|
|
9463
|
+
return name && this.profiles.has(name) ? name : void 0;
|
|
9464
|
+
}
|
|
9376
9465
|
// ── Resolution ─────────────────────────────────────────────────────────
|
|
9377
9466
|
/**
|
|
9378
9467
|
* Resolve a named fallback profile to a validated, provider-filtered chain.
|
|
9379
9468
|
*
|
|
9380
|
-
* Returns an empty chain when
|
|
9381
|
-
*
|
|
9382
|
-
*
|
|
9469
|
+
* Returns an empty chain when the profile doesn't exist, or when every entry
|
|
9470
|
+
* is excluded, quarantined, or blacked out.
|
|
9471
|
+
*
|
|
9472
|
+
* Filtering is intentionally identical to {@link resolveRefs} (the explicit
|
|
9473
|
+
* `fallbackModels` path): the self-exclusion, the runtime status tracker,
|
|
9474
|
+
* and the availability calendar — nothing else. Anything a named profile
|
|
9475
|
+
* drops, an explicit chain drops too, and vice versa. Profiles used to apply
|
|
9476
|
+
* two extra filters (provider "usability" and the `providers[].models`
|
|
9477
|
+
* snapshot) that the explicit path did not, which silently rerouted roles
|
|
9478
|
+
* pinned to a profile onto a different model than the one configured.
|
|
9383
9479
|
*
|
|
9384
9480
|
* @param name - Profile name from config.fallbackProfiles.
|
|
9385
9481
|
* @param defaultProvider - Used when an entry has no explicit provider.
|
|
@@ -9400,13 +9496,9 @@ var FallbackProfileManager = class {
|
|
|
9400
9496
|
if (seen.has(key)) continue;
|
|
9401
9497
|
seen.add(key);
|
|
9402
9498
|
if (excludeKey && key === excludeKey) continue;
|
|
9403
|
-
const health = this.checkProvider(providerId);
|
|
9404
|
-
if (!health.usable) continue;
|
|
9405
9499
|
if (this.statusTracker && !this.statusTracker.isAvailable(providerId, parsed.model)) continue;
|
|
9406
9500
|
if (!evaluateModelCalendar(this.config.modelAvailabilitySchedule, providerId, parsed.model).allowed)
|
|
9407
9501
|
continue;
|
|
9408
|
-
const allowedModels = this.config.providers?.[providerId]?.models;
|
|
9409
|
-
if (allowedModels && !allowedModels.includes(parsed.model)) continue;
|
|
9410
9502
|
resolved.push({
|
|
9411
9503
|
providerId,
|
|
9412
9504
|
model: parsed.model,
|
|
@@ -9424,12 +9516,14 @@ var FallbackProfileManager = class {
|
|
|
9424
9516
|
resolveEffective(opts = {}) {
|
|
9425
9517
|
const bridge = this.resolveBridge(opts.exclude);
|
|
9426
9518
|
let selected = FREEZER_EMPTY;
|
|
9427
|
-
|
|
9428
|
-
|
|
9519
|
+
const explicitRefs = asRefList(opts.fallbackModels);
|
|
9520
|
+
const profileName = asProfileName(opts.fallbackProfile) ?? this.activeProfileName();
|
|
9521
|
+
if (explicitRefs && explicitRefs.length > 0) {
|
|
9522
|
+
const resolved = this.resolveRefs(explicitRefs, opts.exclude);
|
|
9429
9523
|
if (resolved.length > 0) selected = resolved;
|
|
9430
9524
|
}
|
|
9431
|
-
if (selected.length === 0 &&
|
|
9432
|
-
const resolved = this.resolve(
|
|
9525
|
+
if (selected.length === 0 && profileName) {
|
|
9526
|
+
const resolved = this.resolve(profileName, { exclude: opts.exclude });
|
|
9433
9527
|
if (resolved.length > 0) selected = resolved;
|
|
9434
9528
|
}
|
|
9435
9529
|
if (selected.length === 0 && opts.fallbackAuto !== false) {
|
|
@@ -9505,13 +9599,14 @@ var FallbackProfileManager = class {
|
|
|
9505
9599
|
};
|
|
9506
9600
|
const configFallbackAuto = this.config.fallbackAuto;
|
|
9507
9601
|
const effectiveFallbackAuto = configFallbackAuto !== void 0 && configFallbackAuto !== null ? configFallbackAuto : !opts.closedWorld;
|
|
9508
|
-
const explicitRefs = opts.fallbackModels ?? this.config.fallbackModels;
|
|
9602
|
+
const explicitRefs = asRefList(opts.fallbackModels) ?? asRefList(this.config.fallbackModels);
|
|
9603
|
+
const profileName = asProfileName(opts.fallbackProfile) ?? this.activeProfileName();
|
|
9509
9604
|
const explicitUsable = explicitRefs !== void 0 && explicitRefs.length > 0 && this.resolveRefs(explicitRefs, current).length > 0;
|
|
9510
|
-
const profileUsable =
|
|
9605
|
+
const profileUsable = profileName !== void 0 && this.hasProfile(profileName) && this.resolve(profileName, { exclude: current }).length > 0;
|
|
9511
9606
|
const fromExplicitSource = explicitUsable || profileUsable;
|
|
9512
|
-
const selectedChain = opts.closedWorld ? explicitRefs && explicitRefs.length > 0 ? this.resolveRefs(explicitRefs, current) :
|
|
9607
|
+
const selectedChain = opts.closedWorld ? explicitRefs && explicitRefs.length > 0 ? this.resolveRefs(explicitRefs, current) : profileName ? this.resolve(profileName, { exclude: current }) : FREEZER_EMPTY : this.resolveEffective({
|
|
9513
9608
|
fallbackModels: explicitRefs,
|
|
9514
|
-
fallbackProfile:
|
|
9609
|
+
fallbackProfile: profileName,
|
|
9515
9610
|
fallbackAuto: effectiveFallbackAuto,
|
|
9516
9611
|
exclude: current
|
|
9517
9612
|
});
|
|
@@ -9528,7 +9623,7 @@ var FallbackProfileManager = class {
|
|
|
9528
9623
|
});
|
|
9529
9624
|
}
|
|
9530
9625
|
candidates.push(...selectedChain);
|
|
9531
|
-
if (!fromExplicitSource && effectiveFallbackAuto &&
|
|
9626
|
+
if (!fromExplicitSource && effectiveFallbackAuto && profileName !== "default") {
|
|
9532
9627
|
candidates.push(...this.resolve("default", { exclude: current }));
|
|
9533
9628
|
}
|
|
9534
9629
|
if (!fromExplicitSource && effectiveFallbackAuto) {
|
|
@@ -9606,7 +9701,7 @@ var FallbackProfileManager = class {
|
|
|
9606
9701
|
const leaderModel = this.config.model;
|
|
9607
9702
|
const providers = this.config.providers ?? {};
|
|
9608
9703
|
const favoriteSet = new Set(
|
|
9609
|
-
(this.config.favoriteModels ?? []).map((ref) => {
|
|
9704
|
+
(asRefList(this.config.favoriteModels) ?? []).map((ref) => {
|
|
9610
9705
|
const p = parseModelRef(ref);
|
|
9611
9706
|
return `${p.provider ?? leaderProvider}/${p.model}`;
|
|
9612
9707
|
})
|
|
@@ -9717,9 +9812,15 @@ function effectiveFallbackChain(config) {
|
|
|
9717
9812
|
const mgr = new FallbackProfileManager(config);
|
|
9718
9813
|
return mgr.resolveEffective({
|
|
9719
9814
|
fallbackModels: config.fallbackModels,
|
|
9815
|
+
fallbackProfile: config.fallbackProfile,
|
|
9720
9816
|
fallbackAuto: config.fallbackAuto
|
|
9721
9817
|
}).map((e) => `${e.providerId}/${e.model}`);
|
|
9722
9818
|
}
|
|
9819
|
+
function runtimeFallbackChain(config) {
|
|
9820
|
+
const mgr = new FallbackProfileManager(config);
|
|
9821
|
+
const current = primaryTarget(config);
|
|
9822
|
+
return mgr.resolveCandidates(current, {}).map((e) => `${e.providerId}/${e.model}`);
|
|
9823
|
+
}
|
|
9723
9824
|
var DEFAULT_PRIMARY_COOLDOWN_MS = 6e4;
|
|
9724
9825
|
var DEFAULT_PRIMARY_COOLDOWN_MAX_MS = 10 * 6e4;
|
|
9725
9826
|
var DEFAULT_PRIMARY_RECOVERY_SUCCESSES = 2;
|
|
@@ -9759,7 +9860,11 @@ function createFallbackModelExtension(deps) {
|
|
|
9759
9860
|
let blockedPrimary;
|
|
9760
9861
|
let primaryBlockedUntil = 0;
|
|
9761
9862
|
const now = () => deps.now?.() ?? Date.now();
|
|
9762
|
-
const
|
|
9863
|
+
const liveStickiness = () => deps.getConfig().fallbackStickiness;
|
|
9864
|
+
const cooldownBase = () => Math.max(
|
|
9865
|
+
0,
|
|
9866
|
+
deps.primaryCooldownMs ?? liveStickiness()?.primaryProbeInterval ?? DEFAULT_PRIMARY_COOLDOWN_MS
|
|
9867
|
+
);
|
|
9763
9868
|
const cooldownMax = () => Math.max(cooldownBase(), deps.primaryCooldownMaxMs ?? DEFAULT_PRIMARY_COOLDOWN_MAX_MS);
|
|
9764
9869
|
const selectedPrimary = (cfg) => deps.getPrimaryTarget?.() ?? primaryTarget(cfg);
|
|
9765
9870
|
const primaryInCooldown = (cfg) => sameTarget(blockedPrimary, selectedPrimary(cfg)) && now() < primaryBlockedUntil;
|
|
@@ -9778,7 +9883,7 @@ function createFallbackModelExtension(deps) {
|
|
|
9778
9883
|
primaryBlockedUntil = now() + Math.min(cooldownMax(), base * multiplier);
|
|
9779
9884
|
};
|
|
9780
9885
|
const recoveryTarget = () => Math.max(1, deps.primaryRecoverySuccesses ?? DEFAULT_PRIMARY_RECOVERY_SUCCESSES);
|
|
9781
|
-
const stickyTarget = () => Math.max(0, deps.stickyFallbackTurns ?? 0);
|
|
9886
|
+
const stickyTarget = () => Math.max(0, deps.stickyFallbackTurns ?? liveStickiness()?.stickyFallbackTurns ?? 0);
|
|
9782
9887
|
const inStickyWindow = () => stickyTurnsElapsed < stickyTarget();
|
|
9783
9888
|
const onPrimarySuccess = (cfg) => {
|
|
9784
9889
|
if (!sameTarget(blockedPrimary, selectedPrimary(cfg))) return;
|
|
@@ -11816,6 +11921,7 @@ export {
|
|
|
11816
11921
|
renderNextStepsBlock,
|
|
11817
11922
|
resolveContinuation,
|
|
11818
11923
|
runProviderWithRetry,
|
|
11924
|
+
runtimeFallbackChain,
|
|
11819
11925
|
setBtwNote,
|
|
11820
11926
|
setQueuedMessagesSnapshot,
|
|
11821
11927
|
smartDefaultFallbackChain,
|