@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/index.js
CHANGED
|
@@ -3884,6 +3884,10 @@ var IN_PROJECT_ALLOWED_KEYS = /* @__PURE__ */ new Set([
|
|
|
3884
3884
|
"fallbackModels",
|
|
3885
3885
|
"fallbackBridge",
|
|
3886
3886
|
"fallbackProfiles",
|
|
3887
|
+
// The profile SELECTOR. No broader than its siblings: a repo that can write
|
|
3888
|
+
// `fallbackModels` and `fallbackProfiles` already controls the chain outright,
|
|
3889
|
+
// and this one can only name a profile the user already defined.
|
|
3890
|
+
"fallbackProfile",
|
|
3887
3891
|
"favoriteModels",
|
|
3888
3892
|
"favoriteModelsOnly",
|
|
3889
3893
|
"modelAvailabilitySchedule",
|
|
@@ -3931,6 +3935,10 @@ var KNOWN_DENIED_IN_PROJECT = [
|
|
|
3931
3935
|
{
|
|
3932
3936
|
key: "git",
|
|
3933
3937
|
reason: "Carries git.identity (GIT_AUTHOR_*/GIT_COMMITTER_* injection): a repo-committed config could spoof the author identity written into the victim's commit history (impersonation)."
|
|
3938
|
+
},
|
|
3939
|
+
{
|
|
3940
|
+
key: "fallbackMaxLastResortCandidates",
|
|
3941
|
+
reason: "Bounds how many of the user's OTHER configured providers may be swept in as last-resort failover. Setting it to 0 from a repo-committed config would silently strip that depth during an outage. It was already stripped in practice (absent from the allow-list) but was missing from the key registry, so this gate never checked it."
|
|
3934
3942
|
}
|
|
3935
3943
|
];
|
|
3936
3944
|
var KNOWN_CONFIG_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -3951,11 +3959,13 @@ var KNOWN_CONFIG_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
|
3951
3959
|
"fallbackModels",
|
|
3952
3960
|
"fallbackBridge",
|
|
3953
3961
|
"fallbackProfiles",
|
|
3962
|
+
"fallbackProfile",
|
|
3954
3963
|
"favoriteModels",
|
|
3955
3964
|
"favoriteModelsOnly",
|
|
3956
3965
|
"modelAvailabilitySchedule",
|
|
3957
3966
|
"fallbackAuto",
|
|
3958
3967
|
"fallbackStickiness",
|
|
3968
|
+
"fallbackMaxLastResortCandidates",
|
|
3959
3969
|
"hooks",
|
|
3960
3970
|
"plugins",
|
|
3961
3971
|
"pluginManager",
|
|
@@ -4041,6 +4051,17 @@ var IN_PROJECT_DENIED_PATHS = [
|
|
|
4041
4051
|
// operator owns, not the checked-out repository.
|
|
4042
4052
|
path: "tools.kanbanGovernance",
|
|
4043
4053
|
reason: "Repo-committed config could disable a Kanban governance gate the operator switched on, letting product mutations run outside any managed card."
|
|
4054
|
+
},
|
|
4055
|
+
{
|
|
4056
|
+
// The bridge spawn path resolves the CLI entry by walking UP from the
|
|
4057
|
+
// project root, so a repo that ships its own `packages/cli/dist/index.js`
|
|
4058
|
+
// gets that file spawned with `process.execPath` on WebUI boot — no
|
|
4059
|
+
// prompt, no banner. Turning the feature on is therefore equivalent to
|
|
4060
|
+
// arbitrary code execution for a hostile checkout, which makes this an
|
|
4061
|
+
// operator-owned switch and never a repo-owned one.
|
|
4062
|
+
// See discover-mailbox-bridge.ts:findWorkspaceCliEntry.
|
|
4063
|
+
path: "features.mailboxBridge",
|
|
4064
|
+
reason: "Enables the mailbox bridge, whose CLI-entry resolution walks up from the project root \u2014 a repo-supplied packages/cli/dist/index.js would be spawned on WebUI boot."
|
|
4044
4065
|
}
|
|
4045
4066
|
];
|
|
4046
4067
|
function deleteNestedPath(target, path124) {
|
|
@@ -8622,6 +8643,7 @@ function hasOpenTodos(todos) {
|
|
|
8622
8643
|
}
|
|
8623
8644
|
|
|
8624
8645
|
// src/core/context.ts
|
|
8646
|
+
import { realpathSync as realpathSync2 } from "node:fs";
|
|
8625
8647
|
import * as path21 from "node:path";
|
|
8626
8648
|
|
|
8627
8649
|
// src/utils/tool-wire-compact.ts
|
|
@@ -9073,12 +9095,15 @@ var ConversationState = class {
|
|
|
9073
9095
|
* cap determines the starting index for the sum but does not gate it.
|
|
9074
9096
|
*/
|
|
9075
9097
|
overflowCount(arr) {
|
|
9076
|
-
|
|
9077
|
-
|
|
9098
|
+
const contextClass = this.ctx.constructor;
|
|
9099
|
+
const maxMessages = contextClass.MAX_MESSAGES;
|
|
9100
|
+
const maxMessageTokens = contextClass.MAX_MESSAGE_TOKENS;
|
|
9101
|
+
let drop = maxMessages > 0 ? Math.max(0, arr.length - maxMessages) : 0;
|
|
9102
|
+
if (maxMessageTokens <= 0) return this.protocolSafeDropCount(arr, drop);
|
|
9078
9103
|
let total = 0;
|
|
9079
9104
|
for (let i = drop; i < arr.length; i++) total += arr[i]?._estTokens ?? 0;
|
|
9080
|
-
if (total <=
|
|
9081
|
-
while (drop < arr.length - 1 && total >
|
|
9105
|
+
if (total <= maxMessageTokens) return this.protocolSafeDropCount(arr, drop);
|
|
9106
|
+
while (drop < arr.length - 1 && total > maxMessageTokens) {
|
|
9082
9107
|
total -= arr[drop]?._estTokens ?? 0;
|
|
9083
9108
|
drop++;
|
|
9084
9109
|
}
|
|
@@ -9904,6 +9929,19 @@ var Context = class _Context {
|
|
|
9904
9929
|
if (rel.startsWith("..") || path21.isAbsolute(rel)) {
|
|
9905
9930
|
throw new Error(`Working directory "${resolved}" is outside project root "${root}"`);
|
|
9906
9931
|
}
|
|
9932
|
+
let realTarget = resolved;
|
|
9933
|
+
let realRoot = root;
|
|
9934
|
+
try {
|
|
9935
|
+
realTarget = realpathSync2.native(resolved);
|
|
9936
|
+
realRoot = realpathSync2.native(root);
|
|
9937
|
+
} catch {
|
|
9938
|
+
}
|
|
9939
|
+
const realRel = path21.relative(realRoot, realTarget);
|
|
9940
|
+
if (realRel.startsWith("..") || path21.isAbsolute(realRel)) {
|
|
9941
|
+
throw new Error(
|
|
9942
|
+
`Working directory "${resolved}" resolves to "${realTarget}", outside project root "${realRoot}"`
|
|
9943
|
+
);
|
|
9944
|
+
}
|
|
9907
9945
|
}
|
|
9908
9946
|
const old = this.workingDir;
|
|
9909
9947
|
this.workingDir = resolved;
|
|
@@ -10655,26 +10693,41 @@ function stripNextStepsFromMessage(msg) {
|
|
|
10655
10693
|
strippedNextStepsCache.set(msg, clone);
|
|
10656
10694
|
return clone;
|
|
10657
10695
|
}
|
|
10658
|
-
function
|
|
10696
|
+
function markCacheBoundary(msg) {
|
|
10697
|
+
if (typeof msg.content === "string") return void 0;
|
|
10698
|
+
const blocks = msg.content.slice();
|
|
10699
|
+
const boundary = blocks[blocks.length - 1];
|
|
10700
|
+
if (!boundary || boundary.type !== "text" && boundary.type !== "tool_result") return void 0;
|
|
10701
|
+
blocks[blocks.length - 1] = { ...boundary, cache_control: { type: "ephemeral" } };
|
|
10702
|
+
return { ...msg, content: blocks };
|
|
10703
|
+
}
|
|
10704
|
+
function composeRequestMessages(history, tail, previous) {
|
|
10659
10705
|
if (history.length === 0) return null;
|
|
10660
10706
|
const out = history.slice();
|
|
10661
10707
|
const lastIdx = out.length - 1;
|
|
10662
10708
|
const last = out[lastIdx];
|
|
10709
|
+
if (previous && previous.index < lastIdx && history[previous.index] === previous.message) {
|
|
10710
|
+
const marked2 = markCacheBoundary(previous.message);
|
|
10711
|
+
if (marked2) out[previous.index] = marked2;
|
|
10712
|
+
}
|
|
10663
10713
|
const blocks = typeof last.content === "string" ? [{ type: "text", text: last.content }] : last.content.slice();
|
|
10664
|
-
const
|
|
10665
|
-
|
|
10666
|
-
|
|
10714
|
+
const tailBlock = blocks[blocks.length - 1];
|
|
10715
|
+
const marked = tailBlock && (tailBlock.type === "text" || tailBlock.type === "tool_result");
|
|
10716
|
+
if (marked) {
|
|
10717
|
+
blocks[blocks.length - 1] = { ...tailBlock, cache_control: { type: "ephemeral" } };
|
|
10667
10718
|
}
|
|
10719
|
+
const boundary = marked ? { message: last, index: lastIdx } : void 0;
|
|
10668
10720
|
if (tail.length === 0 || last.role !== "user") {
|
|
10669
10721
|
out[lastIdx] = { ...last, content: blocks };
|
|
10670
10722
|
if (tail.length > 0) out.push({ role: "user", content: [LIVE_CONTEXT_HEADER, ...tail] });
|
|
10671
|
-
return out;
|
|
10723
|
+
return { messages: out, boundary };
|
|
10672
10724
|
}
|
|
10673
10725
|
out[lastIdx] = { ...last, content: [...blocks, LIVE_CONTEXT_HEADER, ...tail] };
|
|
10674
|
-
return out;
|
|
10726
|
+
return { messages: out, boundary };
|
|
10675
10727
|
}
|
|
10676
10728
|
function createAgentResponseHandler(a) {
|
|
10677
10729
|
const stabilizedPromptEpochs = /* @__PURE__ */ new WeakSet();
|
|
10730
|
+
let previousBoundary;
|
|
10678
10731
|
function stabilizePromptEpoch() {
|
|
10679
10732
|
const prompt = a.ctx.systemPrompt;
|
|
10680
10733
|
if (stabilizedPromptEpochs.has(prompt)) return;
|
|
@@ -10715,7 +10768,9 @@ function createAgentResponseHandler(a) {
|
|
|
10715
10768
|
...memoryEvidence
|
|
10716
10769
|
].filter((block) => block !== void 0);
|
|
10717
10770
|
const requestHistory = stripDeliveredNextSteps(a.ctx.messages);
|
|
10718
|
-
const
|
|
10771
|
+
const composed = composeRequestMessages(requestHistory, liveContextTail, previousBoundary);
|
|
10772
|
+
if (composed) previousBoundary = composed.boundary;
|
|
10773
|
+
const composedMessages = composed?.messages ?? null;
|
|
10719
10774
|
const system = composedMessages ? stableSystem : liveContextTail.length > 0 ? [...stableSystem, ...liveContextTail] : stableSystem;
|
|
10720
10775
|
await a.ctx.waitForModelTransition();
|
|
10721
10776
|
const provider = a.ctx.provider;
|
|
@@ -27608,6 +27663,24 @@ import { createInterface as createInterface4 } from "node:readline";
|
|
|
27608
27663
|
import { homedir as homedir5 } from "node:os";
|
|
27609
27664
|
|
|
27610
27665
|
// src/security/secret-scrubber.ts
|
|
27666
|
+
var JSON_CREDENTIAL_KEY_ANCHORS = [
|
|
27667
|
+
'Key"',
|
|
27668
|
+
'key"',
|
|
27669
|
+
'KEY"',
|
|
27670
|
+
'token"',
|
|
27671
|
+
'Token"',
|
|
27672
|
+
'TOKEN"',
|
|
27673
|
+
'secret"',
|
|
27674
|
+
'Secret"',
|
|
27675
|
+
'SECRET"',
|
|
27676
|
+
'password"',
|
|
27677
|
+
'Password"',
|
|
27678
|
+
'PASSWORD"',
|
|
27679
|
+
'authorization"',
|
|
27680
|
+
'Authorization"',
|
|
27681
|
+
'bearer"',
|
|
27682
|
+
'Bearer"'
|
|
27683
|
+
];
|
|
27611
27684
|
var PATTERNS = [
|
|
27612
27685
|
// Anchored at the start where possible so partial matches inside larger
|
|
27613
27686
|
// strings don't trigger false positives.
|
|
@@ -27710,6 +27783,30 @@ var PATTERNS = [
|
|
|
27710
27783
|
regex: /(^|\s)([A-Z_]{4,}(?:KEY|TOKEN|SECRET|PASSWORD|PWD))\s*[:=]\s*['"]?([A-Za-z0-9_/+=-]{20,512})['"]?(?=\s|$)/g,
|
|
27711
27784
|
anchor: ["KEY", "TOKEN", "SECRET", "PASSWORD", "PWD"]
|
|
27712
27785
|
},
|
|
27786
|
+
{
|
|
27787
|
+
type: "json_credential_key",
|
|
27788
|
+
// The JSON counterpart to `high_entropy_env`, and the pattern that the
|
|
27789
|
+
// now-deleted `JSON_KEY_ANCHORS` list was written for. Without it those
|
|
27790
|
+
// anchors only widened the cheap pre-scan — `hasCredentialAnchors` said
|
|
27791
|
+
// "this text may hold a secret", every pattern then declined to match, and
|
|
27792
|
+
// the value went out verbatim. `high_entropy_env` cannot cover these: it
|
|
27793
|
+
// requires an UPPERCASE unquoted key (`API_KEY=…`), so `{"apiKey":"…"}`
|
|
27794
|
+
// never matched.
|
|
27795
|
+
//
|
|
27796
|
+
// Tool results are routinely serialised as JSON, and a credential with no
|
|
27797
|
+
// recognisable prefix (Azure, self-hosted gateways, Anthropic/Codex OAuth)
|
|
27798
|
+
// has no other pattern that can catch it — this is the only thing standing
|
|
27799
|
+
// between such a value and the session JSONL, chronicle, HQ broadcast and
|
|
27800
|
+
// the model's own context.
|
|
27801
|
+
//
|
|
27802
|
+
// The key may carry a prefix (`"anthropicApiKey"`), but the credential word
|
|
27803
|
+
// must END the key: `"tokenCount"` and `"maxTokens"` do not match, because
|
|
27804
|
+
// the closing quote has to follow the word immediately.
|
|
27805
|
+
// Value floor of 8 chars keeps enum-ish values (`"authorization":"none"`)
|
|
27806
|
+
// out. Capture groups: 1=key + punctuation, 2=value, 3=closing quote.
|
|
27807
|
+
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,
|
|
27808
|
+
anchor: JSON_CREDENTIAL_KEY_ANCHORS
|
|
27809
|
+
},
|
|
27713
27810
|
// ── Ported from packages/plugins credential-patterns.ts (WS-034) ─────────
|
|
27714
27811
|
// The plugin runtime carried 37 patterns while this scrubber — the one that
|
|
27715
27812
|
// guards session JSONL, chronicle, HQ broadcast, WebUI events and the auth
|
|
@@ -27792,9 +27889,12 @@ var PATTERNS = [
|
|
|
27792
27889
|
anchor: "GOCSPX-"
|
|
27793
27890
|
}
|
|
27794
27891
|
];
|
|
27795
|
-
var SIMPLE_PATTERNS = PATTERNS.filter(
|
|
27892
|
+
var SIMPLE_PATTERNS = PATTERNS.filter(
|
|
27893
|
+
(p) => p.type !== "high_entropy_env" && p.type !== "json_credential_key"
|
|
27894
|
+
);
|
|
27796
27895
|
var COMBINED_REGEX = new RegExp(SIMPLE_PATTERNS.map((p) => `(${p.regex.source})`).join("|"), "g");
|
|
27797
27896
|
var HIGH_ENTROPY_REGEX = PATTERNS.find((p) => p.type === "high_entropy_env").regex;
|
|
27897
|
+
var JSON_CREDENTIAL_REGEX = PATTERNS.find((p) => p.type === "json_credential_key").regex;
|
|
27798
27898
|
var COMBINED_REPLACEMENTS = SIMPLE_PATTERNS.map((p) => `[REDACTED:${p.type}]`);
|
|
27799
27899
|
var SCRUB_CHUNK_BYTES = 64 * 1024;
|
|
27800
27900
|
var SCRUB_OVERLAP_BYTES = 1024;
|
|
@@ -27805,20 +27905,7 @@ var PATTERN_ANCHORS = [
|
|
|
27805
27905
|
)
|
|
27806
27906
|
)
|
|
27807
27907
|
];
|
|
27808
|
-
var
|
|
27809
|
-
'"apiKey"',
|
|
27810
|
-
'"api_key"',
|
|
27811
|
-
'"token"',
|
|
27812
|
-
'"secret"',
|
|
27813
|
-
'"password"',
|
|
27814
|
-
'"authorization"',
|
|
27815
|
-
'"bearer"',
|
|
27816
|
-
'"private_key"',
|
|
27817
|
-
'"access_token"',
|
|
27818
|
-
'"refresh_token"',
|
|
27819
|
-
'"client_secret"'
|
|
27820
|
-
];
|
|
27821
|
-
var ALL_ANCHORS = [...PATTERN_ANCHORS, ...JSON_KEY_ANCHORS];
|
|
27908
|
+
var ALL_ANCHORS = PATTERN_ANCHORS;
|
|
27822
27909
|
function hasCredentialAnchors(text2) {
|
|
27823
27910
|
for (const anchor of ALL_ANCHORS) {
|
|
27824
27911
|
if (text2.includes(anchor)) return true;
|
|
@@ -27867,6 +27954,9 @@ var DefaultSecretScrubber = class {
|
|
|
27867
27954
|
out = out.replace(HIGH_ENTROPY_REGEX, (_match, lead, key, _value) => {
|
|
27868
27955
|
return `${lead}${key}=[REDACTED:high_entropy_env]`;
|
|
27869
27956
|
});
|
|
27957
|
+
out = out.replace(JSON_CREDENTIAL_REGEX, (_match, keyPrefix, _value, closingQuote) => {
|
|
27958
|
+
return `${keyPrefix}[REDACTED:json_credential_key]${closingQuote}`;
|
|
27959
|
+
});
|
|
27870
27960
|
return out;
|
|
27871
27961
|
}
|
|
27872
27962
|
/**
|
|
@@ -35035,9 +35125,40 @@ async function readOrBuildShardManifestEntry(opts) {
|
|
|
35035
35125
|
return entry;
|
|
35036
35126
|
}
|
|
35037
35127
|
|
|
35038
|
-
// src/storage/session-store/
|
|
35128
|
+
// src/storage/session-store/strict-empty-check.ts
|
|
35039
35129
|
import { createReadStream as createReadStream7 } from "node:fs";
|
|
35040
35130
|
import { createInterface as createInterface7 } from "node:readline";
|
|
35131
|
+
var EMPTY_SESSION_EVENT_TYPES = /* @__PURE__ */ new Set(["session_start", "session_resumed", "session_end"]);
|
|
35132
|
+
async function isStrictlyEmptySessionFile(file) {
|
|
35133
|
+
const input = createReadStream7(file, { encoding: "utf8" });
|
|
35134
|
+
const lines = createInterface7({ input, crlfDelay: Infinity });
|
|
35135
|
+
let sawSessionStart = false;
|
|
35136
|
+
try {
|
|
35137
|
+
for await (const line of lines) {
|
|
35138
|
+
if (!line.trim()) continue;
|
|
35139
|
+
let event;
|
|
35140
|
+
try {
|
|
35141
|
+
event = JSON.parse(line);
|
|
35142
|
+
} catch {
|
|
35143
|
+
return false;
|
|
35144
|
+
}
|
|
35145
|
+
if (event === null || typeof event !== "object" || Array.isArray(event)) return false;
|
|
35146
|
+
const type = event.type;
|
|
35147
|
+
if (typeof type !== "string" || !EMPTY_SESSION_EVENT_TYPES.has(type)) return false;
|
|
35148
|
+
if (type === "session_start") sawSessionStart = true;
|
|
35149
|
+
}
|
|
35150
|
+
} catch {
|
|
35151
|
+
return false;
|
|
35152
|
+
} finally {
|
|
35153
|
+
lines.close();
|
|
35154
|
+
input.destroy();
|
|
35155
|
+
}
|
|
35156
|
+
return sawSessionStart;
|
|
35157
|
+
}
|
|
35158
|
+
|
|
35159
|
+
// src/storage/session-store/summary-builder.ts
|
|
35160
|
+
import { createReadStream as createReadStream8 } from "node:fs";
|
|
35161
|
+
import { createInterface as createInterface8 } from "node:readline";
|
|
35041
35162
|
async function summarizeSessionFile(opts) {
|
|
35042
35163
|
return summarizeSessionEventSequence({
|
|
35043
35164
|
id: opts.id,
|
|
@@ -35154,8 +35275,8 @@ async function summarizeSessionEventSequence(opts) {
|
|
|
35154
35275
|
}
|
|
35155
35276
|
}
|
|
35156
35277
|
async function* iterateSessionEvents(file, secretScrubber) {
|
|
35157
|
-
const stream =
|
|
35158
|
-
const lines =
|
|
35278
|
+
const stream = createReadStream8(file, { encoding: "utf8" });
|
|
35279
|
+
const lines = createInterface8({ input: stream, crlfDelay: Infinity });
|
|
35159
35280
|
try {
|
|
35160
35281
|
for await (const line of lines) {
|
|
35161
35282
|
if (!line.trim()) continue;
|
|
@@ -36051,6 +36172,10 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
36051
36172
|
await deleteSessionArtifacts({ rootDir: this.dir, id, jsonlPath });
|
|
36052
36173
|
await this.writeTombstone(id);
|
|
36053
36174
|
}
|
|
36175
|
+
async isEmpty(id) {
|
|
36176
|
+
const canonicalId = await this.resolveId(id);
|
|
36177
|
+
return isStrictlyEmptySessionFile(this.sessionPath(canonicalId, ".jsonl"));
|
|
36178
|
+
}
|
|
36054
36179
|
async delete(id) {
|
|
36055
36180
|
if (this.catalogClient) {
|
|
36056
36181
|
const canonical = await this.resolveId(id);
|
|
@@ -36705,6 +36830,12 @@ function normalizeModelRef(ref, defaultProvider) {
|
|
|
36705
36830
|
function hasText(value) {
|
|
36706
36831
|
return typeof value === "string" && value.trim().length > 0;
|
|
36707
36832
|
}
|
|
36833
|
+
function asRefList(value) {
|
|
36834
|
+
return Array.isArray(value) ? value : void 0;
|
|
36835
|
+
}
|
|
36836
|
+
function asProfileName(value) {
|
|
36837
|
+
return hasText(value) ? value : void 0;
|
|
36838
|
+
}
|
|
36708
36839
|
function providerHasKey(entry) {
|
|
36709
36840
|
if (!entry) return false;
|
|
36710
36841
|
if (hasText(entry.apiKey)) return true;
|
|
@@ -36715,7 +36846,7 @@ function providerHasKey(entry) {
|
|
|
36715
36846
|
}
|
|
36716
36847
|
function visibleProviderModels(config, providerId, providerModels) {
|
|
36717
36848
|
const entry = config.providers?.[providerId];
|
|
36718
|
-
return entry?.models
|
|
36849
|
+
return Array.isArray(entry?.models) ? [...entry.models] : providerModels;
|
|
36719
36850
|
}
|
|
36720
36851
|
function buildProfiles(config) {
|
|
36721
36852
|
const entries = /* @__PURE__ */ new Map();
|
|
@@ -36752,13 +36883,34 @@ var FallbackProfileManager = class {
|
|
|
36752
36883
|
listProfiles() {
|
|
36753
36884
|
return Object.freeze([...this.profiles.keys()]);
|
|
36754
36885
|
}
|
|
36886
|
+
/**
|
|
36887
|
+
* The profile the session has selected (`config.fallbackProfile`, set by
|
|
36888
|
+
* `/fallback profile use <name>`), or undefined when none is selected or the
|
|
36889
|
+
* name no longer resolves to a defined profile.
|
|
36890
|
+
*
|
|
36891
|
+
* Consulted by every resolution entry point when the caller does not name a
|
|
36892
|
+
* profile itself. Without this the leader — which passes no profile — could
|
|
36893
|
+
* never use a named profile at all: `config.fallbackProfiles` was reachable
|
|
36894
|
+
* only by copying a chain into `fallbackModels`.
|
|
36895
|
+
*/
|
|
36896
|
+
activeProfileName() {
|
|
36897
|
+
const name = asProfileName(this.config.fallbackProfile);
|
|
36898
|
+
return name && this.profiles.has(name) ? name : void 0;
|
|
36899
|
+
}
|
|
36755
36900
|
// ── Resolution ─────────────────────────────────────────────────────────
|
|
36756
36901
|
/**
|
|
36757
36902
|
* Resolve a named fallback profile to a validated, provider-filtered chain.
|
|
36758
36903
|
*
|
|
36759
|
-
* Returns an empty chain when
|
|
36760
|
-
*
|
|
36761
|
-
*
|
|
36904
|
+
* Returns an empty chain when the profile doesn't exist, or when every entry
|
|
36905
|
+
* is excluded, quarantined, or blacked out.
|
|
36906
|
+
*
|
|
36907
|
+
* Filtering is intentionally identical to {@link resolveRefs} (the explicit
|
|
36908
|
+
* `fallbackModels` path): the self-exclusion, the runtime status tracker,
|
|
36909
|
+
* and the availability calendar — nothing else. Anything a named profile
|
|
36910
|
+
* drops, an explicit chain drops too, and vice versa. Profiles used to apply
|
|
36911
|
+
* two extra filters (provider "usability" and the `providers[].models`
|
|
36912
|
+
* snapshot) that the explicit path did not, which silently rerouted roles
|
|
36913
|
+
* pinned to a profile onto a different model than the one configured.
|
|
36762
36914
|
*
|
|
36763
36915
|
* @param name - Profile name from config.fallbackProfiles.
|
|
36764
36916
|
* @param defaultProvider - Used when an entry has no explicit provider.
|
|
@@ -36779,13 +36931,9 @@ var FallbackProfileManager = class {
|
|
|
36779
36931
|
if (seen.has(key)) continue;
|
|
36780
36932
|
seen.add(key);
|
|
36781
36933
|
if (excludeKey && key === excludeKey) continue;
|
|
36782
|
-
const health = this.checkProvider(providerId);
|
|
36783
|
-
if (!health.usable) continue;
|
|
36784
36934
|
if (this.statusTracker && !this.statusTracker.isAvailable(providerId, parsed.model)) continue;
|
|
36785
36935
|
if (!evaluateModelCalendar(this.config.modelAvailabilitySchedule, providerId, parsed.model).allowed)
|
|
36786
36936
|
continue;
|
|
36787
|
-
const allowedModels = this.config.providers?.[providerId]?.models;
|
|
36788
|
-
if (allowedModels && !allowedModels.includes(parsed.model)) continue;
|
|
36789
36937
|
resolved.push({
|
|
36790
36938
|
providerId,
|
|
36791
36939
|
model: parsed.model,
|
|
@@ -36803,12 +36951,14 @@ var FallbackProfileManager = class {
|
|
|
36803
36951
|
resolveEffective(opts = {}) {
|
|
36804
36952
|
const bridge = this.resolveBridge(opts.exclude);
|
|
36805
36953
|
let selected = FREEZER_EMPTY;
|
|
36806
|
-
|
|
36807
|
-
|
|
36954
|
+
const explicitRefs = asRefList(opts.fallbackModels);
|
|
36955
|
+
const profileName = asProfileName(opts.fallbackProfile) ?? this.activeProfileName();
|
|
36956
|
+
if (explicitRefs && explicitRefs.length > 0) {
|
|
36957
|
+
const resolved = this.resolveRefs(explicitRefs, opts.exclude);
|
|
36808
36958
|
if (resolved.length > 0) selected = resolved;
|
|
36809
36959
|
}
|
|
36810
|
-
if (selected.length === 0 &&
|
|
36811
|
-
const resolved = this.resolve(
|
|
36960
|
+
if (selected.length === 0 && profileName) {
|
|
36961
|
+
const resolved = this.resolve(profileName, { exclude: opts.exclude });
|
|
36812
36962
|
if (resolved.length > 0) selected = resolved;
|
|
36813
36963
|
}
|
|
36814
36964
|
if (selected.length === 0 && opts.fallbackAuto !== false) {
|
|
@@ -36884,13 +37034,14 @@ var FallbackProfileManager = class {
|
|
|
36884
37034
|
};
|
|
36885
37035
|
const configFallbackAuto = this.config.fallbackAuto;
|
|
36886
37036
|
const effectiveFallbackAuto = configFallbackAuto !== void 0 && configFallbackAuto !== null ? configFallbackAuto : !opts.closedWorld;
|
|
36887
|
-
const explicitRefs = opts.fallbackModels ?? this.config.fallbackModels;
|
|
37037
|
+
const explicitRefs = asRefList(opts.fallbackModels) ?? asRefList(this.config.fallbackModels);
|
|
37038
|
+
const profileName = asProfileName(opts.fallbackProfile) ?? this.activeProfileName();
|
|
36888
37039
|
const explicitUsable = explicitRefs !== void 0 && explicitRefs.length > 0 && this.resolveRefs(explicitRefs, current).length > 0;
|
|
36889
|
-
const profileUsable =
|
|
37040
|
+
const profileUsable = profileName !== void 0 && this.hasProfile(profileName) && this.resolve(profileName, { exclude: current }).length > 0;
|
|
36890
37041
|
const fromExplicitSource = explicitUsable || profileUsable;
|
|
36891
|
-
const selectedChain = opts.closedWorld ? explicitRefs && explicitRefs.length > 0 ? this.resolveRefs(explicitRefs, current) :
|
|
37042
|
+
const selectedChain = opts.closedWorld ? explicitRefs && explicitRefs.length > 0 ? this.resolveRefs(explicitRefs, current) : profileName ? this.resolve(profileName, { exclude: current }) : FREEZER_EMPTY : this.resolveEffective({
|
|
36892
37043
|
fallbackModels: explicitRefs,
|
|
36893
|
-
fallbackProfile:
|
|
37044
|
+
fallbackProfile: profileName,
|
|
36894
37045
|
fallbackAuto: effectiveFallbackAuto,
|
|
36895
37046
|
exclude: current
|
|
36896
37047
|
});
|
|
@@ -36907,7 +37058,7 @@ var FallbackProfileManager = class {
|
|
|
36907
37058
|
});
|
|
36908
37059
|
}
|
|
36909
37060
|
candidates.push(...selectedChain);
|
|
36910
|
-
if (!fromExplicitSource && effectiveFallbackAuto &&
|
|
37061
|
+
if (!fromExplicitSource && effectiveFallbackAuto && profileName !== "default") {
|
|
36911
37062
|
candidates.push(...this.resolve("default", { exclude: current }));
|
|
36912
37063
|
}
|
|
36913
37064
|
if (!fromExplicitSource && effectiveFallbackAuto) {
|
|
@@ -36985,7 +37136,7 @@ var FallbackProfileManager = class {
|
|
|
36985
37136
|
const leaderModel = this.config.model;
|
|
36986
37137
|
const providers = this.config.providers ?? {};
|
|
36987
37138
|
const favoriteSet = new Set(
|
|
36988
|
-
(this.config.favoriteModels ?? []).map((ref) => {
|
|
37139
|
+
(asRefList(this.config.favoriteModels) ?? []).map((ref) => {
|
|
36989
37140
|
const p = parseModelRef(ref);
|
|
36990
37141
|
return `${p.provider ?? leaderProvider}/${p.model}`;
|
|
36991
37142
|
})
|
|
@@ -37096,9 +37247,15 @@ function effectiveFallbackChain(config) {
|
|
|
37096
37247
|
const mgr = new FallbackProfileManager(config);
|
|
37097
37248
|
return mgr.resolveEffective({
|
|
37098
37249
|
fallbackModels: config.fallbackModels,
|
|
37250
|
+
fallbackProfile: config.fallbackProfile,
|
|
37099
37251
|
fallbackAuto: config.fallbackAuto
|
|
37100
37252
|
}).map((e) => `${e.providerId}/${e.model}`);
|
|
37101
37253
|
}
|
|
37254
|
+
function runtimeFallbackChain(config) {
|
|
37255
|
+
const mgr = new FallbackProfileManager(config);
|
|
37256
|
+
const current = primaryTarget(config);
|
|
37257
|
+
return mgr.resolveCandidates(current, {}).map((e) => `${e.providerId}/${e.model}`);
|
|
37258
|
+
}
|
|
37102
37259
|
var DEFAULT_PRIMARY_COOLDOWN_MS = 6e4;
|
|
37103
37260
|
var DEFAULT_PRIMARY_COOLDOWN_MAX_MS = 10 * 6e4;
|
|
37104
37261
|
var DEFAULT_PRIMARY_RECOVERY_SUCCESSES = 2;
|
|
@@ -37138,7 +37295,11 @@ function createFallbackModelExtension(deps) {
|
|
|
37138
37295
|
let blockedPrimary;
|
|
37139
37296
|
let primaryBlockedUntil = 0;
|
|
37140
37297
|
const now = () => deps.now?.() ?? Date.now();
|
|
37141
|
-
const
|
|
37298
|
+
const liveStickiness = () => deps.getConfig().fallbackStickiness;
|
|
37299
|
+
const cooldownBase = () => Math.max(
|
|
37300
|
+
0,
|
|
37301
|
+
deps.primaryCooldownMs ?? liveStickiness()?.primaryProbeInterval ?? DEFAULT_PRIMARY_COOLDOWN_MS
|
|
37302
|
+
);
|
|
37142
37303
|
const cooldownMax = () => Math.max(cooldownBase(), deps.primaryCooldownMaxMs ?? DEFAULT_PRIMARY_COOLDOWN_MAX_MS);
|
|
37143
37304
|
const selectedPrimary = (cfg) => deps.getPrimaryTarget?.() ?? primaryTarget(cfg);
|
|
37144
37305
|
const primaryInCooldown = (cfg) => sameTarget(blockedPrimary, selectedPrimary(cfg)) && now() < primaryBlockedUntil;
|
|
@@ -37157,7 +37318,7 @@ function createFallbackModelExtension(deps) {
|
|
|
37157
37318
|
primaryBlockedUntil = now() + Math.min(cooldownMax(), base * multiplier);
|
|
37158
37319
|
};
|
|
37159
37320
|
const recoveryTarget = () => Math.max(1, deps.primaryRecoverySuccesses ?? DEFAULT_PRIMARY_RECOVERY_SUCCESSES);
|
|
37160
|
-
const stickyTarget = () => Math.max(0, deps.stickyFallbackTurns ?? 0);
|
|
37321
|
+
const stickyTarget = () => Math.max(0, deps.stickyFallbackTurns ?? liveStickiness()?.stickyFallbackTurns ?? 0);
|
|
37161
37322
|
const inStickyWindow = () => stickyTurnsElapsed < stickyTarget();
|
|
37162
37323
|
const onPrimarySuccess = (cfg) => {
|
|
37163
37324
|
if (!sameTarget(blockedPrimary, selectedPrimary(cfg))) return;
|
|
@@ -46893,10 +47054,10 @@ var AdaptiveConcurrencyController = class {
|
|
|
46893
47054
|
|
|
46894
47055
|
// src/coordination/agent-monitor.ts
|
|
46895
47056
|
init_file_permissions();
|
|
46896
|
-
import { createReadStream as
|
|
47057
|
+
import { createReadStream as createReadStream9 } from "node:fs";
|
|
46897
47058
|
import * as fs28 from "node:fs/promises";
|
|
46898
47059
|
import * as path69 from "node:path";
|
|
46899
|
-
import { createInterface as
|
|
47060
|
+
import { createInterface as createInterface9 } from "node:readline";
|
|
46900
47061
|
var AgentMonitorService = class _AgentMonitorService {
|
|
46901
47062
|
_fleetBus;
|
|
46902
47063
|
_events;
|
|
@@ -47003,8 +47164,8 @@ var AgentMonitorService = class _AgentMonitorService {
|
|
|
47003
47164
|
const accessible = await fs28.access(file).then(() => true).catch(() => false);
|
|
47004
47165
|
if (!accessible) return [];
|
|
47005
47166
|
const out = [];
|
|
47006
|
-
const input =
|
|
47007
|
-
const lines =
|
|
47167
|
+
const input = createReadStream9(file, { encoding: "utf8" });
|
|
47168
|
+
const lines = createInterface9({ input, crlfDelay: Number.POSITIVE_INFINITY });
|
|
47008
47169
|
try {
|
|
47009
47170
|
for await (const line of lines) {
|
|
47010
47171
|
const trimmed = line.trim();
|
|
@@ -56653,6 +56814,12 @@ function findExchangeStart(messages, userIndex) {
|
|
|
56653
56814
|
|
|
56654
56815
|
// src/execution/auto-compaction-middleware.ts
|
|
56655
56816
|
var LEVEL_RANK2 = { warn: 0, soft: 1, hard: 2 };
|
|
56817
|
+
function pressureLevelFor(load, thresholds) {
|
|
56818
|
+
if (load >= thresholds.hard) return "hard";
|
|
56819
|
+
if (load >= thresholds.soft) return "soft";
|
|
56820
|
+
if (load >= thresholds.warn) return "warn";
|
|
56821
|
+
return null;
|
|
56822
|
+
}
|
|
56656
56823
|
var MAX_DIGEST_LOG_CHARS = 4e3;
|
|
56657
56824
|
function truncateDigest(digest2) {
|
|
56658
56825
|
if (digest2.length <= MAX_DIGEST_LOG_CHARS) return digest2;
|
|
@@ -56698,8 +56865,19 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
56698
56865
|
* 1 / 2.5 = 0.4.
|
|
56699
56866
|
*/
|
|
56700
56867
|
static GUARD_GATE_LOAD = 0.4;
|
|
56868
|
+
/**
|
|
56869
|
+
* How much the context must grow between two history-rewriting hygiene
|
|
56870
|
+
* passes, as a fraction of the available input window and as an absolute
|
|
56871
|
+
* floor. Every pass rewrites already-transmitted messages, which forces the
|
|
56872
|
+
* provider to re-cache the whole prompt; spacing the passes out is what lets
|
|
56873
|
+
* the conversation prefix stay cached for the turns in between.
|
|
56874
|
+
*/
|
|
56875
|
+
static HYGIENE_GROWTH_RATIO = 0.15;
|
|
56876
|
+
static HYGIENE_MIN_GROWTH_TOKENS = 2e4;
|
|
56701
56877
|
/** Tracks the most recent no-op attempt so we can avoid re-firing per turn. */
|
|
56702
56878
|
lastNoopAttempt = null;
|
|
56879
|
+
/** Context size at the last hygiene pass; anchors the growth interval. */
|
|
56880
|
+
lastHygieneTokens = null;
|
|
56703
56881
|
/**
|
|
56704
56882
|
* Cached token estimate from the last handler() invocation. When the
|
|
56705
56883
|
* message count and tool count haven't changed since the last estimate
|
|
@@ -56759,55 +56937,9 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
56759
56937
|
handler() {
|
|
56760
56938
|
return async (ctx, next) => {
|
|
56761
56939
|
if (!this._enabled) return next(ctx);
|
|
56762
|
-
|
|
56763
|
-
maxRetainedTokens: this.resolveToolResultRetention(ctx)
|
|
56764
|
-
});
|
|
56765
|
-
const receiptHygiene = collapseAcknowledgedToolReceipts(rawHygiene.messages, {
|
|
56766
|
-
maxPairs: this.resolveToolReceiptRetention(ctx)
|
|
56767
|
-
});
|
|
56768
|
-
if (rawHygiene.changed || receiptHygiene.changed) {
|
|
56769
|
-
ctx.state.replaceMessages(receiptHygiene.messages);
|
|
56770
|
-
ctx.clearFileTracking();
|
|
56771
|
-
this.invalidateTokenCaches(ctx);
|
|
56772
|
-
}
|
|
56773
|
-
const msgCount = ctx.messages.length;
|
|
56774
|
-
const toolCount = (ctx.tools ?? []).length;
|
|
56775
|
-
const revision = ctx.state?.revision ?? -1;
|
|
56776
|
-
let tokens;
|
|
56777
|
-
const anchorAt = typeof ctx.meta?.["realAnchorMsgCount"] === "number" ? ctx.meta["realAnchorMsgCount"] : void 0;
|
|
56778
|
-
const anchored = realAnchoredInputTokens(ctx.messages, ctx.lastRealInputTokens, anchorAt);
|
|
56779
|
-
if (anchored !== null) {
|
|
56780
|
-
tokens = anchored;
|
|
56781
|
-
} else if (this._estimator) {
|
|
56782
|
-
tokens = this._estimator(ctx);
|
|
56783
|
-
} else if (msgCount === this._cachedMsgCount && toolCount === this._cachedToolCount && revision === this._cachedRevision && ctx.systemPrompt === this._cachedSystemRef && ctx.tools === this._cachedToolsRef && this._cachedTokens >= 0) {
|
|
56784
|
-
tokens = this._cachedTokens;
|
|
56785
|
-
} else if (this.tryStashedTokens(ctx, msgCount, toolCount, revision) !== null) {
|
|
56786
|
-
const stashed = this.tryStashedTokens(ctx, msgCount, toolCount, revision);
|
|
56787
|
-
const cal = getCalibrationState(`${ctx.provider?.id ?? "unknown"}/${ctx.model}`);
|
|
56788
|
-
tokens = cal.calibrated ? Math.round(stashed * Math.min(1.5, Math.max(0.5, cal.ratio))) : stashed;
|
|
56789
|
-
this._cachedTokens = tokens;
|
|
56790
|
-
this._cachedMsgCount = msgCount;
|
|
56791
|
-
this._cachedToolCount = toolCount;
|
|
56792
|
-
this._cachedRevision = revision;
|
|
56793
|
-
this._cachedSystemRef = ctx.systemPrompt;
|
|
56794
|
-
this._cachedToolsRef = ctx.tools;
|
|
56795
|
-
} else {
|
|
56796
|
-
tokens = estimateRequestTokensCalibrated(
|
|
56797
|
-
ctx.messages,
|
|
56798
|
-
ctx.systemPrompt,
|
|
56799
|
-
ctx.tools ?? [],
|
|
56800
|
-
`${ctx.provider?.id ?? "unknown"}/${ctx.model}`
|
|
56801
|
-
).total;
|
|
56802
|
-
this._cachedTokens = tokens;
|
|
56803
|
-
this._cachedMsgCount = msgCount;
|
|
56804
|
-
this._cachedToolCount = toolCount;
|
|
56805
|
-
this._cachedRevision = revision;
|
|
56806
|
-
this._cachedSystemRef = ctx.systemPrompt;
|
|
56807
|
-
this._cachedToolsRef = ctx.tools;
|
|
56808
|
-
}
|
|
56940
|
+
let tokens = this.estimateContextTokens(ctx);
|
|
56809
56941
|
const runtimeMaxContext = effectiveMaxContext(ctx, this._maxContext);
|
|
56810
|
-
|
|
56942
|
+
let budget = computeContextWindowBudget(ctx, tokens, runtimeMaxContext);
|
|
56811
56943
|
const calibratedLoad = budget.load;
|
|
56812
56944
|
const policy = this.policyProvider?.(ctx);
|
|
56813
56945
|
const thresholds = policy?.thresholds ?? {
|
|
@@ -56821,22 +56953,27 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
56821
56953
|
});
|
|
56822
56954
|
const aggressiveOn = policy?.aggressiveOn ?? this.aggressiveOn;
|
|
56823
56955
|
const targetLoad = normalizeTargetLoad(policy?.targetLoad, adaptiveThresholds);
|
|
56824
|
-
let load = calibratedLoad;
|
|
56825
|
-
|
|
56826
|
-
const guardTotal = estimateRequestTokensUpperBound(
|
|
56827
|
-
ctx.messages,
|
|
56828
|
-
ctx.systemPrompt,
|
|
56829
|
-
ctx.tools ?? [],
|
|
56830
|
-
`${ctx.provider?.id ?? "unknown"}/${ctx.model}`
|
|
56831
|
-
).total;
|
|
56832
|
-
const guardLoad = guardTotal / budget.availableInputTokens;
|
|
56833
|
-
if (guardLoad > load) load = guardLoad;
|
|
56834
|
-
}
|
|
56835
|
-
const level = load >= adaptiveThresholds.hard ? "hard" : load >= adaptiveThresholds.soft ? "soft" : load >= adaptiveThresholds.warn ? "warn" : null;
|
|
56956
|
+
let load = this.applySendGuard(ctx, calibratedLoad, budget.availableInputTokens);
|
|
56957
|
+
let level = pressureLevelFor(load, adaptiveThresholds);
|
|
56836
56958
|
if (!level) {
|
|
56837
56959
|
this.lastNoopAttempt = null;
|
|
56838
56960
|
return next(ctx);
|
|
56839
56961
|
}
|
|
56962
|
+
if (this.shouldRunHygiene(level, tokens, budget.availableInputTokens)) {
|
|
56963
|
+
const changed = this.runHistoryHygiene(ctx);
|
|
56964
|
+
tokens = changed ? this.estimateContextTokens(ctx) : tokens;
|
|
56965
|
+
this.lastHygieneTokens = tokens;
|
|
56966
|
+
if (changed) {
|
|
56967
|
+
budget = computeContextWindowBudget(ctx, tokens, runtimeMaxContext);
|
|
56968
|
+
load = this.applySendGuard(ctx, budget.load, budget.availableInputTokens);
|
|
56969
|
+
const relevelled = pressureLevelFor(load, adaptiveThresholds);
|
|
56970
|
+
if (!relevelled) {
|
|
56971
|
+
this.lastNoopAttempt = null;
|
|
56972
|
+
return next(ctx);
|
|
56973
|
+
}
|
|
56974
|
+
level = relevelled;
|
|
56975
|
+
}
|
|
56976
|
+
}
|
|
56840
56977
|
if (this.shouldSkipNoopRetry(level, tokens)) {
|
|
56841
56978
|
return next(ctx);
|
|
56842
56979
|
}
|
|
@@ -56853,6 +56990,123 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
56853
56990
|
return next(ctx);
|
|
56854
56991
|
};
|
|
56855
56992
|
}
|
|
56993
|
+
/**
|
|
56994
|
+
* Full-request token total for the current context.
|
|
56995
|
+
*
|
|
56996
|
+
* Reuses the last estimate when the context hasn't grown since the previous
|
|
56997
|
+
* check — common in autonomous idle loops. The cached value is invalidated
|
|
56998
|
+
* whenever messages or tools change.
|
|
56999
|
+
*
|
|
57000
|
+
* IMPORTANT: the cache is only valid for the deterministic
|
|
57001
|
+
* `estimateRequestTokensCalibrated` path (messages+system+tools → fixed
|
|
57002
|
+
* output). When a custom `_estimator` is provided (e.g. in tests with a
|
|
57003
|
+
* mutable closure, or a dynamic policy provider), always call it fresh — the
|
|
57004
|
+
* estimator owns its own semantics and the middleware cannot safely cache its
|
|
57005
|
+
* result across calls.
|
|
57006
|
+
*/
|
|
57007
|
+
estimateContextTokens(ctx) {
|
|
57008
|
+
const msgCount = ctx.messages.length;
|
|
57009
|
+
const toolCount = (ctx.tools ?? []).length;
|
|
57010
|
+
const revision = ctx.state?.revision ?? -1;
|
|
57011
|
+
const anchorAt = typeof ctx.meta?.["realAnchorMsgCount"] === "number" ? ctx.meta["realAnchorMsgCount"] : void 0;
|
|
57012
|
+
const anchored = realAnchoredInputTokens(ctx.messages, ctx.lastRealInputTokens, anchorAt);
|
|
57013
|
+
if (anchored !== null) return anchored;
|
|
57014
|
+
if (this._estimator) return this._estimator(ctx);
|
|
57015
|
+
if (msgCount === this._cachedMsgCount && toolCount === this._cachedToolCount && revision === this._cachedRevision && ctx.systemPrompt === this._cachedSystemRef && ctx.tools === this._cachedToolsRef && this._cachedTokens >= 0) {
|
|
57016
|
+
return this._cachedTokens;
|
|
57017
|
+
}
|
|
57018
|
+
const stashed = this.tryStashedTokens(ctx, msgCount, toolCount, revision);
|
|
57019
|
+
let tokens;
|
|
57020
|
+
if (stashed !== null) {
|
|
57021
|
+
const cal = getCalibrationState(`${ctx.provider?.id ?? "unknown"}/${ctx.model}`);
|
|
57022
|
+
tokens = cal.calibrated ? Math.round(stashed * Math.min(1.5, Math.max(0.5, cal.ratio))) : stashed;
|
|
57023
|
+
} else {
|
|
57024
|
+
tokens = estimateRequestTokensCalibrated(
|
|
57025
|
+
ctx.messages,
|
|
57026
|
+
ctx.systemPrompt,
|
|
57027
|
+
ctx.tools ?? [],
|
|
57028
|
+
`${ctx.provider?.id ?? "unknown"}/${ctx.model}`
|
|
57029
|
+
).total;
|
|
57030
|
+
}
|
|
57031
|
+
this._cachedTokens = tokens;
|
|
57032
|
+
this._cachedMsgCount = msgCount;
|
|
57033
|
+
this._cachedToolCount = toolCount;
|
|
57034
|
+
this._cachedRevision = revision;
|
|
57035
|
+
this._cachedSystemRef = ctx.systemPrompt;
|
|
57036
|
+
this._cachedToolsRef = ctx.tools;
|
|
57037
|
+
return tokens;
|
|
57038
|
+
}
|
|
57039
|
+
/**
|
|
57040
|
+
* Never-undercount send guard.
|
|
57041
|
+
*
|
|
57042
|
+
* The calibrated estimate can under-count dense content (CJK, base64,
|
|
57043
|
+
* minified) by >1.5×, which would let an over-limit request slip past the
|
|
57044
|
+
* thresholds and reach the provider. Once the calibrated load is high enough
|
|
57045
|
+
* that even the max density factor (2.5×) *could* overflow (load ≥ 1/2.5 =
|
|
57046
|
+
* 0.4), re-check with the upper-bound estimator and escalate to whichever
|
|
57047
|
+
* load is larger. Below 0.4 an overflow is arithmetically impossible, so the
|
|
57048
|
+
* extra scan is skipped.
|
|
57049
|
+
*/
|
|
57050
|
+
applySendGuard(ctx, calibratedLoad, availableInputTokens) {
|
|
57051
|
+
if (calibratedLoad < _AutoCompactionMiddleware.GUARD_GATE_LOAD) return calibratedLoad;
|
|
57052
|
+
const guardTotal = estimateRequestTokensUpperBound(
|
|
57053
|
+
ctx.messages,
|
|
57054
|
+
ctx.systemPrompt,
|
|
57055
|
+
ctx.tools ?? [],
|
|
57056
|
+
`${ctx.provider?.id ?? "unknown"}/${ctx.model}`
|
|
57057
|
+
).total;
|
|
57058
|
+
const guardLoad = guardTotal / availableInputTokens;
|
|
57059
|
+
return guardLoad > calibratedLoad ? guardLoad : calibratedLoad;
|
|
57060
|
+
}
|
|
57061
|
+
/**
|
|
57062
|
+
* Rewrite acknowledged tool protocol in place.
|
|
57063
|
+
*
|
|
57064
|
+
* Tool results are protocol inputs for the immediately following model
|
|
57065
|
+
* response, not unlimited durable prompt memory. Once a later assistant
|
|
57066
|
+
* message proves the provider consumed them, keep a mode-sized raw window and
|
|
57067
|
+
* replace the rest with semantic head/tail receipts, then fold fully-aged
|
|
57068
|
+
* pairs into the history digest.
|
|
57069
|
+
*
|
|
57070
|
+
* Every one of those edits touches a message the provider has already seen,
|
|
57071
|
+
* so each call costs a full prompt re-cache — it belongs behind
|
|
57072
|
+
* {@link shouldRunHygiene}, never on the per-turn path. Content staleness is
|
|
57073
|
+
* not a reason to bypass that gate: the edit/replace tools guard against
|
|
57074
|
+
* acting on an out-of-date read with their own mtime + sha-256 check.
|
|
57075
|
+
*
|
|
57076
|
+
* @returns whether the conversation was rewritten.
|
|
57077
|
+
*/
|
|
57078
|
+
runHistoryHygiene(ctx) {
|
|
57079
|
+
const raw = eliseAcknowledgedToolResults(ctx.messages, {
|
|
57080
|
+
maxRetainedTokens: this.resolveToolResultRetention(ctx)
|
|
57081
|
+
});
|
|
57082
|
+
const receipts = collapseAcknowledgedToolReceipts(raw.messages, {
|
|
57083
|
+
maxPairs: this.resolveToolReceiptRetention(ctx)
|
|
57084
|
+
});
|
|
57085
|
+
if (!raw.changed && !receipts.changed) return false;
|
|
57086
|
+
ctx.state.replaceMessages(receipts.messages);
|
|
57087
|
+
ctx.clearFileTracking();
|
|
57088
|
+
this.invalidateTokenCaches(ctx);
|
|
57089
|
+
return true;
|
|
57090
|
+
}
|
|
57091
|
+
/**
|
|
57092
|
+
* Whether the history-rewriting hygiene pass may run this turn.
|
|
57093
|
+
*
|
|
57094
|
+
* Hard pressure always runs it — staying under the window outranks caching.
|
|
57095
|
+
* Otherwise it runs at most once per growth interval, so the conversation
|
|
57096
|
+
* stays append-only (and therefore cacheable by the provider) in between.
|
|
57097
|
+
*/
|
|
57098
|
+
shouldRunHygiene(level, tokens, availableInputTokens) {
|
|
57099
|
+
if (level === "hard") return true;
|
|
57100
|
+
const last = this.lastHygieneTokens;
|
|
57101
|
+
if (last === null) return true;
|
|
57102
|
+
const anchor = Math.min(last, tokens);
|
|
57103
|
+
this.lastHygieneTokens = anchor;
|
|
57104
|
+
const interval = Math.max(
|
|
57105
|
+
_AutoCompactionMiddleware.HYGIENE_MIN_GROWTH_TOKENS,
|
|
57106
|
+
Math.floor(availableInputTokens * _AutoCompactionMiddleware.HYGIENE_GROWTH_RATIO)
|
|
57107
|
+
);
|
|
57108
|
+
return tokens - anchor >= interval;
|
|
57109
|
+
}
|
|
56856
57110
|
/**
|
|
56857
57111
|
* H1: try to read a pre-computed token total from `ctx.lastRequestTokens`
|
|
56858
57112
|
* (set by the agent loop's pre-flight or its restash in emitContextPct).
|
|
@@ -61665,26 +61919,29 @@ var DefaultProviderRunner = class {
|
|
|
61665
61919
|
// src/execution/retry-policy.ts
|
|
61666
61920
|
import { randomInt } from "node:crypto";
|
|
61667
61921
|
var MAX_RETRY_AFTER_MS = 6e4;
|
|
61922
|
+
var MODEL_RETRIES = 3;
|
|
61668
61923
|
var MAX_ATTEMPTS_BY_KIND = {
|
|
61669
|
-
rate_limit:
|
|
61924
|
+
rate_limit: MODEL_RETRIES,
|
|
61925
|
+
overloaded: MODEL_RETRIES,
|
|
61926
|
+
server: MODEL_RETRIES,
|
|
61927
|
+
timeout: MODEL_RETRIES,
|
|
61928
|
+
network: MODEL_RETRIES,
|
|
61929
|
+
stream_hang: MODEL_RETRIES,
|
|
61670
61930
|
quota_exhausted: 0,
|
|
61671
|
-
stream_hang: 2,
|
|
61672
|
-
// proxy-level timeout — retrying 5x wastes ~40s before fallback kicks in
|
|
61673
|
-
overloaded: 3,
|
|
61674
|
-
server: 3,
|
|
61675
|
-
timeout: 2,
|
|
61676
|
-
network: 2,
|
|
61677
61931
|
auth: 0,
|
|
61678
61932
|
invalid_request: 0,
|
|
61679
61933
|
context_overflow: 0,
|
|
61680
61934
|
content_filter: 0,
|
|
61681
61935
|
unknown: 0
|
|
61682
61936
|
};
|
|
61937
|
+
var FAILOVER_RETRY_AFTER_MS = 15e3;
|
|
61683
61938
|
var DefaultRetryPolicy = class {
|
|
61684
61939
|
shouldRetry(err, attempt) {
|
|
61685
61940
|
const isProviderErr = err instanceof ProviderError || ProviderError.isProviderError(err);
|
|
61686
61941
|
if (isProviderErr) {
|
|
61687
61942
|
if (!err.retryable) return false;
|
|
61943
|
+
const hint = retryAfterMsFromError(err);
|
|
61944
|
+
if (hint !== void 0 && hint >= FAILOVER_RETRY_AFTER_MS) return false;
|
|
61688
61945
|
return attempt < this.maxAttempts(err);
|
|
61689
61946
|
}
|
|
61690
61947
|
const msg = err.message ?? "";
|
|
@@ -67094,7 +67351,7 @@ var AutoApprovePermissionPolicy = class _AutoApprovePermissionPolicy {
|
|
|
67094
67351
|
};
|
|
67095
67352
|
|
|
67096
67353
|
// src/security/permission-helpers.ts
|
|
67097
|
-
import { realpathSync as
|
|
67354
|
+
import { realpathSync as realpathSync3 } from "node:fs";
|
|
67098
67355
|
import * as path86 from "node:path";
|
|
67099
67356
|
function matchesTrust(patterns, subject2) {
|
|
67100
67357
|
return patterns.includes(subject2) || matchAny(patterns, subject2);
|
|
@@ -67179,7 +67436,7 @@ function realpathOfNearestExisting(p) {
|
|
|
67179
67436
|
const tail = [];
|
|
67180
67437
|
for (; ; ) {
|
|
67181
67438
|
try {
|
|
67182
|
-
return tail.length === 0 ?
|
|
67439
|
+
return tail.length === 0 ? realpathSync3(probe2) : path86.join(realpathSync3(probe2), ...tail);
|
|
67183
67440
|
} catch {
|
|
67184
67441
|
const parent = path86.dirname(probe2);
|
|
67185
67442
|
if (parent === probe2) return p;
|
|
@@ -83348,6 +83605,156 @@ var CLOUD_SYNC_CONTRACT = {
|
|
|
83348
83605
|
"extensions.plugins": EXTENSIONS_PLUGINS_TREE
|
|
83349
83606
|
};
|
|
83350
83607
|
var CLOUD_SYNC_NAMESPACES = Object.keys(CLOUD_SYNC_CONTRACT);
|
|
83608
|
+
var INBOUND_DENIED_PATHS = [
|
|
83609
|
+
// ── Code execution ──────────────────────────────────────────────────────
|
|
83610
|
+
{
|
|
83611
|
+
namespace: "mcp.servers",
|
|
83612
|
+
path: "mcpServers.*.command",
|
|
83613
|
+
reason: "Executable spawned for a stdio MCP server."
|
|
83614
|
+
},
|
|
83615
|
+
{
|
|
83616
|
+
namespace: "mcp.servers",
|
|
83617
|
+
path: "mcpServers.*.args",
|
|
83618
|
+
reason: "Argv for that executable."
|
|
83619
|
+
},
|
|
83620
|
+
{
|
|
83621
|
+
namespace: "mcp.servers",
|
|
83622
|
+
path: "mcpServers.*.transport",
|
|
83623
|
+
reason: "Switching transport to stdio selects the spawning code path."
|
|
83624
|
+
},
|
|
83625
|
+
{
|
|
83626
|
+
namespace: "extensions.plugins",
|
|
83627
|
+
path: "plugins",
|
|
83628
|
+
reason: "Plugin list is resolved and `await import`ed."
|
|
83629
|
+
},
|
|
83630
|
+
// `extensions` is deliberately NOT denied: it is per-plugin settings read via
|
|
83631
|
+
// `ConfigStore.getExtension(name)`, not a loader list, so it grants no import.
|
|
83632
|
+
// Syncing it is the feature (`extensions.telegram.notifyChatId` and friends).
|
|
83633
|
+
// Residual risk accepted: a plugin whose own settings include a URL can have
|
|
83634
|
+
// that URL rewritten by the portal. Constrain that in the plugin's
|
|
83635
|
+
// `configSchema`, which is where the loader validates it.
|
|
83636
|
+
// ── Credential redirection / exfiltration ───────────────────────────────
|
|
83637
|
+
{
|
|
83638
|
+
namespace: "providers.catalog",
|
|
83639
|
+
path: "providers.*.baseUrl",
|
|
83640
|
+
reason: "Repoints the provider endpoint; reinjectLocalSecrets keeps the local apiKey, so the real key follows the redirect."
|
|
83641
|
+
},
|
|
83642
|
+
{
|
|
83643
|
+
namespace: "providers.catalog",
|
|
83644
|
+
path: "providers.*.envVars",
|
|
83645
|
+
reason: "Chooses which environment variable is read for the key."
|
|
83646
|
+
},
|
|
83647
|
+
{
|
|
83648
|
+
namespace: "providers.catalog",
|
|
83649
|
+
path: "providers.*.activeKey",
|
|
83650
|
+
reason: "Selects which stored key is sent."
|
|
83651
|
+
},
|
|
83652
|
+
{
|
|
83653
|
+
namespace: "mcp.servers",
|
|
83654
|
+
path: "mcpServers.*.url",
|
|
83655
|
+
reason: "Remote MCP endpoint; receives whatever the transport carries."
|
|
83656
|
+
},
|
|
83657
|
+
{
|
|
83658
|
+
namespace: "mcp.servers",
|
|
83659
|
+
path: "mcpServers.*.envVars",
|
|
83660
|
+
reason: "Names of environment variables forwarded to the server process."
|
|
83661
|
+
},
|
|
83662
|
+
// ── Operator-owned safety switches ──────────────────────────────────────
|
|
83663
|
+
{
|
|
83664
|
+
namespace: "mcp.servers",
|
|
83665
|
+
path: "mcpServers.*.permission",
|
|
83666
|
+
reason: "Approval requirement for that server\u2019s tools."
|
|
83667
|
+
},
|
|
83668
|
+
{ namespace: "core.runtime", path: "yolo", reason: "Disables every permission prompt." },
|
|
83669
|
+
{
|
|
83670
|
+
namespace: "core.runtime",
|
|
83671
|
+
path: "features.allowOutsideProjectRoot",
|
|
83672
|
+
reason: "Short-circuits project-root containment and the symlink realpath check."
|
|
83673
|
+
},
|
|
83674
|
+
{
|
|
83675
|
+
namespace: "core.runtime",
|
|
83676
|
+
path: "tools.restrictToProjectRoot",
|
|
83677
|
+
reason: "The other half of the filesystem confinement switch."
|
|
83678
|
+
},
|
|
83679
|
+
{
|
|
83680
|
+
namespace: "core.runtime",
|
|
83681
|
+
path: "features.developerMode",
|
|
83682
|
+
reason: "Loosens guardrails; an operator opt-in, not a synced preference."
|
|
83683
|
+
},
|
|
83684
|
+
{
|
|
83685
|
+
namespace: "core.runtime",
|
|
83686
|
+
path: "tools.disabledTools",
|
|
83687
|
+
reason: "Could re-enable a tool the operator deliberately switched off."
|
|
83688
|
+
},
|
|
83689
|
+
{
|
|
83690
|
+
namespace: "ui.preferences",
|
|
83691
|
+
path: "autonomy.defaultMode",
|
|
83692
|
+
reason: "Autonomy is user-owned, never remote-owned."
|
|
83693
|
+
},
|
|
83694
|
+
{
|
|
83695
|
+
namespace: "ui.preferences",
|
|
83696
|
+
path: "autonomy.yolo",
|
|
83697
|
+
reason: "Alias for the denied top-level `yolo`, and it wins over the user setting."
|
|
83698
|
+
},
|
|
83699
|
+
{
|
|
83700
|
+
namespace: "ui.preferences",
|
|
83701
|
+
path: "launch.autonomy",
|
|
83702
|
+
reason: "Launch-time autonomy mode; same user-owned boundary."
|
|
83703
|
+
},
|
|
83704
|
+
{
|
|
83705
|
+
namespace: "models.routing",
|
|
83706
|
+
path: "brain.mode",
|
|
83707
|
+
reason: "Selects the policy/LLM/human decision ladder."
|
|
83708
|
+
},
|
|
83709
|
+
{
|
|
83710
|
+
namespace: "models.routing",
|
|
83711
|
+
path: "brain.maxAutoRisk",
|
|
83712
|
+
reason: "The risk ceiling below which actions proceed without asking."
|
|
83713
|
+
}
|
|
83714
|
+
];
|
|
83715
|
+
function contractHasPath(tree, segments) {
|
|
83716
|
+
if (segments.length === 0) return true;
|
|
83717
|
+
if (tree === true) return false;
|
|
83718
|
+
const [head, ...rest] = segments;
|
|
83719
|
+
if (head === void 0 || !Object.hasOwn(tree, head)) return false;
|
|
83720
|
+
const child = tree[head];
|
|
83721
|
+
return child === void 0 ? false : contractHasPath(child, rest);
|
|
83722
|
+
}
|
|
83723
|
+
function pruneContractPath(tree, segments) {
|
|
83724
|
+
if (tree === true || segments.length === 0) return tree;
|
|
83725
|
+
const [head, ...rest] = segments;
|
|
83726
|
+
if (head === void 0 || !Object.hasOwn(tree, head)) return tree;
|
|
83727
|
+
const next = { ...tree };
|
|
83728
|
+
if (rest.length === 0) {
|
|
83729
|
+
delete next[head];
|
|
83730
|
+
return next;
|
|
83731
|
+
}
|
|
83732
|
+
const child = next[head];
|
|
83733
|
+
if (child === void 0) return tree;
|
|
83734
|
+
next[head] = pruneContractPath(child, rest);
|
|
83735
|
+
return next;
|
|
83736
|
+
}
|
|
83737
|
+
function assertInboundDenyListResolves() {
|
|
83738
|
+
const unresolved = INBOUND_DENIED_PATHS.filter((entry) => {
|
|
83739
|
+
const tree = CLOUD_SYNC_CONTRACT[entry.namespace];
|
|
83740
|
+
return tree === void 0 || !contractHasPath(tree, entry.path.split("."));
|
|
83741
|
+
});
|
|
83742
|
+
if (unresolved.length > 0) {
|
|
83743
|
+
throw new Error(
|
|
83744
|
+
"INBOUND_DENIED_PATHS entr(ies) no longer resolve against CLOUD_SYNC_CONTRACT \u2014 a rename would silently re-open them: " + unresolved.map((entry) => `${entry.namespace}:${entry.path}`).join(", ")
|
|
83745
|
+
);
|
|
83746
|
+
}
|
|
83747
|
+
}
|
|
83748
|
+
var INBOUND_CONTRACT = (() => {
|
|
83749
|
+
assertInboundDenyListResolves();
|
|
83750
|
+
const out = { ...CLOUD_SYNC_CONTRACT };
|
|
83751
|
+
for (const entry of INBOUND_DENIED_PATHS) {
|
|
83752
|
+
const tree = out[entry.namespace];
|
|
83753
|
+
if (tree === void 0) continue;
|
|
83754
|
+
out[entry.namespace] = pruneContractPath(tree, entry.path.split("."));
|
|
83755
|
+
}
|
|
83756
|
+
return out;
|
|
83757
|
+
})();
|
|
83351
83758
|
var NAMESPACE_SCHEMA_VERSIONS = {
|
|
83352
83759
|
"core.runtime": 1,
|
|
83353
83760
|
"ui.preferences": 1,
|
|
@@ -83449,7 +83856,7 @@ function mergeAtContract(local, incoming, tree) {
|
|
|
83449
83856
|
return base;
|
|
83450
83857
|
}
|
|
83451
83858
|
function applyNamespacePayload(config, namespace, payload) {
|
|
83452
|
-
const tree =
|
|
83859
|
+
const tree = INBOUND_CONTRACT[namespace];
|
|
83453
83860
|
if (!tree || tree === true) return config;
|
|
83454
83861
|
const next = { ...config };
|
|
83455
83862
|
for (const [key, incoming] of Object.entries(payload)) {
|
|
@@ -91573,6 +91980,15 @@ async function readProviderSnapshot(configPath, vault, warn) {
|
|
|
91573
91980
|
snapshot.fallbackBridge = decrypted.fallbackBridge.trim();
|
|
91574
91981
|
}
|
|
91575
91982
|
if (decrypted.fallbackProfiles) snapshot.fallbackProfiles = decrypted.fallbackProfiles;
|
|
91983
|
+
if (typeof decrypted.fallbackProfile === "string" && decrypted.fallbackProfile.trim()) {
|
|
91984
|
+
snapshot.fallbackProfile = decrypted.fallbackProfile.trim();
|
|
91985
|
+
}
|
|
91986
|
+
if (decrypted.fallbackStickiness && typeof decrypted.fallbackStickiness === "object") {
|
|
91987
|
+
snapshot.fallbackStickiness = decrypted.fallbackStickiness;
|
|
91988
|
+
}
|
|
91989
|
+
if (typeof decrypted.fallbackMaxLastResortCandidates === "number" && Number.isFinite(decrypted.fallbackMaxLastResortCandidates)) {
|
|
91990
|
+
snapshot.fallbackMaxLastResortCandidates = decrypted.fallbackMaxLastResortCandidates;
|
|
91991
|
+
}
|
|
91576
91992
|
if (Array.isArray(decrypted.favoriteModels)) snapshot.favoriteModels = decrypted.favoriteModels;
|
|
91577
91993
|
if (typeof decrypted.favoriteModelsOnly === "boolean")
|
|
91578
91994
|
snapshot.favoriteModelsOnly = decrypted.favoriteModelsOnly;
|
|
@@ -91591,10 +92007,13 @@ function serializeSnapshot(s) {
|
|
|
91591
92007
|
fallbackModels: s.fallbackModels ?? null,
|
|
91592
92008
|
fallbackBridge: s.fallbackBridge ?? null,
|
|
91593
92009
|
fallbackProfiles: s.fallbackProfiles ?? null,
|
|
92010
|
+
fallbackProfile: s.fallbackProfile ?? null,
|
|
91594
92011
|
favoriteModels: s.favoriteModels ?? null,
|
|
91595
92012
|
favoriteModelsOnly: s.favoriteModelsOnly ?? null,
|
|
91596
92013
|
modelMatrix: s.modelMatrix ?? null,
|
|
91597
92014
|
fallbackAuto: s.fallbackAuto ?? null,
|
|
92015
|
+
fallbackStickiness: s.fallbackStickiness ?? null,
|
|
92016
|
+
fallbackMaxLastResortCandidates: s.fallbackMaxLastResortCandidates ?? null,
|
|
91598
92017
|
modelAvailabilitySchedule: s.modelAvailabilitySchedule ?? null
|
|
91599
92018
|
});
|
|
91600
92019
|
}
|
|
@@ -92080,10 +92499,10 @@ var ReplayLogStore = class _ReplayLogStore {
|
|
|
92080
92499
|
};
|
|
92081
92500
|
|
|
92082
92501
|
// src/storage/session-recovery.ts
|
|
92083
|
-
import { createReadStream as
|
|
92502
|
+
import { createReadStream as createReadStream10 } from "node:fs";
|
|
92084
92503
|
import * as fs67 from "node:fs/promises";
|
|
92085
92504
|
import * as path122 from "node:path";
|
|
92086
|
-
import { createInterface as
|
|
92505
|
+
import { createInterface as createInterface10 } from "node:readline";
|
|
92087
92506
|
var SessionRecovery = class _SessionRecovery {
|
|
92088
92507
|
constructor(dir) {
|
|
92089
92508
|
this.dir = dir;
|
|
@@ -92160,8 +92579,8 @@ var SessionRecovery = class _SessionRecovery {
|
|
|
92160
92579
|
let lastCheckpoint = null;
|
|
92161
92580
|
let latestBoundary = null;
|
|
92162
92581
|
let sawEvent = false;
|
|
92163
|
-
const stream =
|
|
92164
|
-
const lines =
|
|
92582
|
+
const stream = createReadStream10(fp, { encoding: "utf8" });
|
|
92583
|
+
const lines = createInterface10({ input: stream, crlfDelay: Infinity });
|
|
92165
92584
|
try {
|
|
92166
92585
|
for await (const line of lines) {
|
|
92167
92586
|
if (!line.trim()) continue;
|
|
@@ -92320,10 +92739,10 @@ async function applyRewindToConversation(opts) {
|
|
|
92320
92739
|
init_errors();
|
|
92321
92740
|
init_atomic_write();
|
|
92322
92741
|
init_error();
|
|
92323
|
-
import { createReadStream as
|
|
92742
|
+
import { createReadStream as createReadStream11 } from "node:fs";
|
|
92324
92743
|
import * as fsp44 from "node:fs/promises";
|
|
92325
92744
|
import * as path123 from "node:path";
|
|
92326
|
-
import { createInterface as
|
|
92745
|
+
import { createInterface as createInterface11 } from "node:readline";
|
|
92327
92746
|
var DefaultSessionRewinder = class {
|
|
92328
92747
|
constructor(sessionsDir, projectRoot) {
|
|
92329
92748
|
this.sessionsDir = sessionsDir;
|
|
@@ -92335,8 +92754,8 @@ var DefaultSessionRewinder = class {
|
|
|
92335
92754
|
return sessionScopedPath(this.sessionsDir, sessionId, ".jsonl");
|
|
92336
92755
|
}
|
|
92337
92756
|
async *readEvents(file) {
|
|
92338
|
-
const stream =
|
|
92339
|
-
const lines =
|
|
92757
|
+
const stream = createReadStream11(file, { encoding: "utf8" });
|
|
92758
|
+
const lines = createInterface11({ input: stream, crlfDelay: Infinity });
|
|
92340
92759
|
try {
|
|
92341
92760
|
for await (const line of lines) {
|
|
92342
92761
|
if (!line.trim()) continue;
|
|
@@ -93968,6 +94387,30 @@ var PROVIDER_MANAGE_SCHEMA = {
|
|
|
93968
94387
|
required: ["action"],
|
|
93969
94388
|
additionalProperties: false
|
|
93970
94389
|
};
|
|
94390
|
+
var CREDENTIAL_SELECTOR_FIELDS = ["apiKey", "apiKeys", "activeKey", "envVars"];
|
|
94391
|
+
function envVarsClaimedByOtherProviders(providers, exceptProvider) {
|
|
94392
|
+
const claimed = /* @__PURE__ */ new Map();
|
|
94393
|
+
for (const [id, entry] of Object.entries(providers)) {
|
|
94394
|
+
if (id === exceptProvider) continue;
|
|
94395
|
+
const names = entry?.["envVars"];
|
|
94396
|
+
if (!Array.isArray(names)) continue;
|
|
94397
|
+
for (const name of names) {
|
|
94398
|
+
if (typeof name === "string" && !claimed.has(name)) claimed.set(name, id);
|
|
94399
|
+
}
|
|
94400
|
+
}
|
|
94401
|
+
return claimed;
|
|
94402
|
+
}
|
|
94403
|
+
function rejectBorrowedEnvVars(providers, provider, requested) {
|
|
94404
|
+
if (!requested) return null;
|
|
94405
|
+
const claimed = envVarsClaimedByOtherProviders(providers, provider);
|
|
94406
|
+
for (const name of requested) {
|
|
94407
|
+
const owner = typeof name === "string" ? claimed.get(name) : void 0;
|
|
94408
|
+
if (owner !== void 0) {
|
|
94409
|
+
return `Environment variable "${String(name)}" already supplies the key for provider "${owner}". Reading another provider's credential from "${provider}" is not allowed; use provider_key_set to give "${provider}" its own key.`;
|
|
94410
|
+
}
|
|
94411
|
+
}
|
|
94412
|
+
return null;
|
|
94413
|
+
}
|
|
93971
94414
|
function validateProviderBaseUrl(raw) {
|
|
93972
94415
|
let url;
|
|
93973
94416
|
try {
|
|
@@ -94038,6 +94481,8 @@ ${msg}`,
|
|
|
94038
94481
|
const invalid2 = validateProviderBaseUrl(input.baseUrl);
|
|
94039
94482
|
if (invalid2) return { status: "error", message: invalid2 };
|
|
94040
94483
|
}
|
|
94484
|
+
const borrowed = rejectBorrowedEnvVars(providers, input.provider, input.envVars);
|
|
94485
|
+
if (borrowed) return { status: "error", message: borrowed };
|
|
94041
94486
|
const entry = { type: input.type };
|
|
94042
94487
|
if (input.models) entry.models = input.models;
|
|
94043
94488
|
if (input.baseUrl) entry.baseUrl = input.baseUrl;
|
|
@@ -94071,14 +94516,22 @@ ${msg}`,
|
|
|
94071
94516
|
if (input.autoDiscoverModels !== void 0) entry.autoDiscoverModels = input.autoDiscoverModels;
|
|
94072
94517
|
if (input.apiKey !== void 0) entry.apiKey = input.apiKey || void 0;
|
|
94073
94518
|
const endpointChanged = input.baseUrl !== void 0 && (entry.baseUrl ?? void 0) !== (previous.baseUrl ?? void 0);
|
|
94074
|
-
const
|
|
94075
|
-
|
|
94519
|
+
const explicitlySupplied = /* @__PURE__ */ new Set([
|
|
94520
|
+
...input.apiKey !== void 0 ? ["apiKey"] : [],
|
|
94521
|
+
...input.envVars !== void 0 ? ["envVars"] : []
|
|
94522
|
+
]);
|
|
94523
|
+
const droppedFields = endpointChanged ? CREDENTIAL_SELECTOR_FIELDS.filter(
|
|
94524
|
+
(field) => !explicitlySupplied.has(field) && previous[field] !== void 0
|
|
94525
|
+
) : [];
|
|
94526
|
+
for (const field of droppedFields) entry[field] = void 0;
|
|
94527
|
+
const borrowed = rejectBorrowedEnvVars(providers, input.provider, input.envVars);
|
|
94528
|
+
if (borrowed) return { status: "error", message: borrowed };
|
|
94076
94529
|
providers[input.provider] = entry;
|
|
94077
94530
|
await opts.updateConfig((cfg) => {
|
|
94078
94531
|
cfg.providers = providers;
|
|
94079
94532
|
});
|
|
94080
94533
|
const updated = Object.keys({ ...entry }).filter((k) => k !== "apiKey").join(", ");
|
|
94081
|
-
const keyNote =
|
|
94534
|
+
const keyNote = droppedFields.length > 0 ? ` \u2014 cleared ${droppedFields.join(", ")} because the base URL changed; set the key again with provider_key_set` : "";
|
|
94082
94535
|
return { status: "ok", message: `\u2713 Updated ${input.provider}: ${updated}${keyNote}` };
|
|
94083
94536
|
}
|
|
94084
94537
|
if (input.action === "remove") {
|
|
@@ -97019,6 +97472,7 @@ export {
|
|
|
97019
97472
|
runShellHook,
|
|
97020
97473
|
runWithNetworkTelemetry,
|
|
97021
97474
|
runWithProcessTelemetry,
|
|
97475
|
+
runtimeFallbackChain,
|
|
97022
97476
|
safeEmit,
|
|
97023
97477
|
safeParse,
|
|
97024
97478
|
safeProfileName,
|