@wrongstack/core 0.306.2 → 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 +184 -86
- package/dist/coordination/mail-tools.d.ts +1 -1
- package/dist/coordination/mailbox-project-server.js +38 -16
- package/dist/coordination/sqlite-mailbox.d.ts +1 -0
- 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 +347 -139
- 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 +234 -91
- 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 +642 -184
- package/dist/infrastructure/index.js +21 -0
- package/dist/plugin/index.js +195 -19
- package/dist/security/index.js +52 -16
- package/dist/security/kanban-boundary.d.ts +3 -1
- 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 +326 -71
- 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/blocks.d.ts +9 -0
- package/dist/types/config/root.d.ts +14 -0
- package/dist/types/session.d.ts +7 -0
- package/instructions/agents/code-reviewer.md +3 -0
- package/instructions/coordination/subagent-baseline.md +10 -1
- package/instructions/system-lite.md +8 -5
- package/instructions/system-pro.md +26 -0
- package/instructions/system.md +21 -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
|
/**
|
|
@@ -32528,6 +32618,44 @@ function userInputTitle(content) {
|
|
|
32528
32618
|
return sessionContentPreview(content, 60);
|
|
32529
32619
|
}
|
|
32530
32620
|
|
|
32621
|
+
// src/storage/session-writer-scrubber.ts
|
|
32622
|
+
function scrubSessionWriterEvent(event, secretScrubber) {
|
|
32623
|
+
const persistMessage = (message) => {
|
|
32624
|
+
const { _estTokens: _ignored, ...persisted } = message;
|
|
32625
|
+
return {
|
|
32626
|
+
...persisted,
|
|
32627
|
+
content: typeof persisted.content === "string" ? secretScrubber?.scrub(persisted.content) ?? persisted.content : secretScrubber?.scrubObject(persisted.content) ?? persisted.content
|
|
32628
|
+
};
|
|
32629
|
+
};
|
|
32630
|
+
if (event.type === "context_snapshot" || event.type === "messages_replaced") {
|
|
32631
|
+
return { ...event, messages: event.messages.map(persistMessage) };
|
|
32632
|
+
}
|
|
32633
|
+
if (event.type === "message_appended" || event.type === "message_updated") {
|
|
32634
|
+
return { ...event, message: persistMessage(event.message) };
|
|
32635
|
+
}
|
|
32636
|
+
if (!secretScrubber) return event;
|
|
32637
|
+
if (event.type === "user_input") {
|
|
32638
|
+
return {
|
|
32639
|
+
...event,
|
|
32640
|
+
content: typeof event.content === "string" ? secretScrubber.scrub(event.content) : secretScrubber.scrubObject(event.content)
|
|
32641
|
+
};
|
|
32642
|
+
}
|
|
32643
|
+
if (event.type === "llm_response") {
|
|
32644
|
+
return { ...event, content: secretScrubber.scrubObject(event.content) };
|
|
32645
|
+
}
|
|
32646
|
+
if (event.type === "file_snapshot") {
|
|
32647
|
+
return {
|
|
32648
|
+
...event,
|
|
32649
|
+
files: event.files.map((f) => ({
|
|
32650
|
+
...f,
|
|
32651
|
+
before: f.before !== null ? secretScrubber.scrub(f.before) : null,
|
|
32652
|
+
after: f.after !== null ? secretScrubber.scrub(f.after) : null
|
|
32653
|
+
}))
|
|
32654
|
+
};
|
|
32655
|
+
}
|
|
32656
|
+
return event;
|
|
32657
|
+
}
|
|
32658
|
+
|
|
32531
32659
|
// src/storage/session-writer-truncate.ts
|
|
32532
32660
|
import * as fsp11 from "node:fs/promises";
|
|
32533
32661
|
var CHUNK_SIZE = 65536;
|
|
@@ -32672,45 +32800,11 @@ async function rewriteSessionToCheckpoint(filePath, checkpointByteOffset) {
|
|
|
32672
32800
|
}
|
|
32673
32801
|
}
|
|
32674
32802
|
|
|
32675
|
-
// src/storage/session-writer-scrubber.ts
|
|
32676
|
-
function scrubSessionWriterEvent(event, secretScrubber) {
|
|
32677
|
-
const persistMessage = (message) => {
|
|
32678
|
-
const { _estTokens: _ignored, ...persisted } = message;
|
|
32679
|
-
return {
|
|
32680
|
-
...persisted,
|
|
32681
|
-
content: typeof persisted.content === "string" ? secretScrubber?.scrub(persisted.content) ?? persisted.content : secretScrubber?.scrubObject(persisted.content) ?? persisted.content
|
|
32682
|
-
};
|
|
32683
|
-
};
|
|
32684
|
-
if (event.type === "context_snapshot" || event.type === "messages_replaced") {
|
|
32685
|
-
return { ...event, messages: event.messages.map(persistMessage) };
|
|
32686
|
-
}
|
|
32687
|
-
if (event.type === "message_appended" || event.type === "message_updated") {
|
|
32688
|
-
return { ...event, message: persistMessage(event.message) };
|
|
32689
|
-
}
|
|
32690
|
-
if (!secretScrubber) return event;
|
|
32691
|
-
if (event.type === "user_input") {
|
|
32692
|
-
return {
|
|
32693
|
-
...event,
|
|
32694
|
-
content: typeof event.content === "string" ? secretScrubber.scrub(event.content) : secretScrubber.scrubObject(event.content)
|
|
32695
|
-
};
|
|
32696
|
-
}
|
|
32697
|
-
if (event.type === "llm_response") {
|
|
32698
|
-
return { ...event, content: secretScrubber.scrubObject(event.content) };
|
|
32699
|
-
}
|
|
32700
|
-
if (event.type === "file_snapshot") {
|
|
32701
|
-
return {
|
|
32702
|
-
...event,
|
|
32703
|
-
files: event.files.map((f) => ({
|
|
32704
|
-
...f,
|
|
32705
|
-
before: f.before !== null ? secretScrubber.scrub(f.before) : null,
|
|
32706
|
-
after: f.after !== null ? secretScrubber.scrub(f.after) : null
|
|
32707
|
-
}))
|
|
32708
|
-
};
|
|
32709
|
-
}
|
|
32710
|
-
return event;
|
|
32711
|
-
}
|
|
32712
|
-
|
|
32713
32803
|
// src/storage/file-session-writer.ts
|
|
32804
|
+
function isClosedHandleError(err) {
|
|
32805
|
+
const code = err?.code;
|
|
32806
|
+
return code === "EBADF" || code === "ERR_CLOSED_RESOURCE" || code === "ERR_INVALID_HANDLE";
|
|
32807
|
+
}
|
|
32714
32808
|
var FileSessionWriter = class _FileSessionWriter {
|
|
32715
32809
|
constructor(id, handle, startedAt, meta, events, opts = {}, traceId) {
|
|
32716
32810
|
this.id = id;
|
|
@@ -32882,8 +32976,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
32882
32976
|
try {
|
|
32883
32977
|
return await this.handle.appendFile(data, "utf8");
|
|
32884
32978
|
} catch (err) {
|
|
32885
|
-
|
|
32886
|
-
if (nodeErr?.code === "EBADF") {
|
|
32979
|
+
if (isClosedHandleError(err)) {
|
|
32887
32980
|
this.handle = await fsp12.open(this.filePath, "a", 384);
|
|
32888
32981
|
return await this.handle.appendFile(data, "utf8");
|
|
32889
32982
|
}
|
|
@@ -32923,8 +33016,8 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
32923
33016
|
bufferSynchronousEvent(event) {
|
|
32924
33017
|
if (this.closed) return;
|
|
32925
33018
|
void this.ensureInit();
|
|
32926
|
-
this.
|
|
32927
|
-
|
|
33019
|
+
const appendEvent = scrubSessionWriterEvent(event, this.secretScrubber);
|
|
33020
|
+
this.observeForSummary(appendEvent);
|
|
32928
33021
|
try {
|
|
32929
33022
|
this._onAppend?.(appendEvent);
|
|
32930
33023
|
} catch {
|
|
@@ -33077,8 +33170,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
33077
33170
|
try {
|
|
33078
33171
|
await this.handle.datasync();
|
|
33079
33172
|
} catch (err) {
|
|
33080
|
-
|
|
33081
|
-
if (nodeErr?.code === "EBADF") {
|
|
33173
|
+
if (isClosedHandleError(err)) {
|
|
33082
33174
|
this.handle = await fsp12.open(this.filePath, "a", 384);
|
|
33083
33175
|
return;
|
|
33084
33176
|
}
|
|
@@ -33315,6 +33407,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
33315
33407
|
return this.closePromise;
|
|
33316
33408
|
}
|
|
33317
33409
|
async doClose() {
|
|
33410
|
+
await this.ensureInit();
|
|
33318
33411
|
if (this.pendingFileSnapshots.length > 0) {
|
|
33319
33412
|
await this.writeFileSnapshot(this.activePromptIndex ?? 0, [...this.pendingFileSnapshots]);
|
|
33320
33413
|
this.pendingFileSnapshots = [];
|
|
@@ -33330,8 +33423,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
33330
33423
|
try {
|
|
33331
33424
|
await this.handle.datasync();
|
|
33332
33425
|
} catch (err) {
|
|
33333
|
-
|
|
33334
|
-
if (nodeErr?.code !== "EBADF") throw err;
|
|
33426
|
+
if (!isClosedHandleError(err)) throw err;
|
|
33335
33427
|
}
|
|
33336
33428
|
const endedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
33337
33429
|
const observedActivityMs = Date.parse(this.lastActivityAt);
|
|
@@ -35033,9 +35125,40 @@ async function readOrBuildShardManifestEntry(opts) {
|
|
|
35033
35125
|
return entry;
|
|
35034
35126
|
}
|
|
35035
35127
|
|
|
35036
|
-
// src/storage/session-store/
|
|
35128
|
+
// src/storage/session-store/strict-empty-check.ts
|
|
35037
35129
|
import { createReadStream as createReadStream7 } from "node:fs";
|
|
35038
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";
|
|
35039
35162
|
async function summarizeSessionFile(opts) {
|
|
35040
35163
|
return summarizeSessionEventSequence({
|
|
35041
35164
|
id: opts.id,
|
|
@@ -35152,8 +35275,8 @@ async function summarizeSessionEventSequence(opts) {
|
|
|
35152
35275
|
}
|
|
35153
35276
|
}
|
|
35154
35277
|
async function* iterateSessionEvents(file, secretScrubber) {
|
|
35155
|
-
const stream =
|
|
35156
|
-
const lines =
|
|
35278
|
+
const stream = createReadStream8(file, { encoding: "utf8" });
|
|
35279
|
+
const lines = createInterface8({ input: stream, crlfDelay: Infinity });
|
|
35157
35280
|
try {
|
|
35158
35281
|
for await (const line of lines) {
|
|
35159
35282
|
if (!line.trim()) continue;
|
|
@@ -36049,6 +36172,10 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
36049
36172
|
await deleteSessionArtifacts({ rootDir: this.dir, id, jsonlPath });
|
|
36050
36173
|
await this.writeTombstone(id);
|
|
36051
36174
|
}
|
|
36175
|
+
async isEmpty(id) {
|
|
36176
|
+
const canonicalId = await this.resolveId(id);
|
|
36177
|
+
return isStrictlyEmptySessionFile(this.sessionPath(canonicalId, ".jsonl"));
|
|
36178
|
+
}
|
|
36052
36179
|
async delete(id) {
|
|
36053
36180
|
if (this.catalogClient) {
|
|
36054
36181
|
const canonical = await this.resolveId(id);
|
|
@@ -36703,6 +36830,12 @@ function normalizeModelRef(ref, defaultProvider) {
|
|
|
36703
36830
|
function hasText(value) {
|
|
36704
36831
|
return typeof value === "string" && value.trim().length > 0;
|
|
36705
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
|
+
}
|
|
36706
36839
|
function providerHasKey(entry) {
|
|
36707
36840
|
if (!entry) return false;
|
|
36708
36841
|
if (hasText(entry.apiKey)) return true;
|
|
@@ -36713,7 +36846,7 @@ function providerHasKey(entry) {
|
|
|
36713
36846
|
}
|
|
36714
36847
|
function visibleProviderModels(config, providerId, providerModels) {
|
|
36715
36848
|
const entry = config.providers?.[providerId];
|
|
36716
|
-
return entry?.models
|
|
36849
|
+
return Array.isArray(entry?.models) ? [...entry.models] : providerModels;
|
|
36717
36850
|
}
|
|
36718
36851
|
function buildProfiles(config) {
|
|
36719
36852
|
const entries = /* @__PURE__ */ new Map();
|
|
@@ -36750,13 +36883,34 @@ var FallbackProfileManager = class {
|
|
|
36750
36883
|
listProfiles() {
|
|
36751
36884
|
return Object.freeze([...this.profiles.keys()]);
|
|
36752
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
|
+
}
|
|
36753
36900
|
// ── Resolution ─────────────────────────────────────────────────────────
|
|
36754
36901
|
/**
|
|
36755
36902
|
* Resolve a named fallback profile to a validated, provider-filtered chain.
|
|
36756
36903
|
*
|
|
36757
|
-
* Returns an empty chain when
|
|
36758
|
-
*
|
|
36759
|
-
*
|
|
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.
|
|
36760
36914
|
*
|
|
36761
36915
|
* @param name - Profile name from config.fallbackProfiles.
|
|
36762
36916
|
* @param defaultProvider - Used when an entry has no explicit provider.
|
|
@@ -36777,13 +36931,9 @@ var FallbackProfileManager = class {
|
|
|
36777
36931
|
if (seen.has(key)) continue;
|
|
36778
36932
|
seen.add(key);
|
|
36779
36933
|
if (excludeKey && key === excludeKey) continue;
|
|
36780
|
-
const health = this.checkProvider(providerId);
|
|
36781
|
-
if (!health.usable) continue;
|
|
36782
36934
|
if (this.statusTracker && !this.statusTracker.isAvailable(providerId, parsed.model)) continue;
|
|
36783
36935
|
if (!evaluateModelCalendar(this.config.modelAvailabilitySchedule, providerId, parsed.model).allowed)
|
|
36784
36936
|
continue;
|
|
36785
|
-
const allowedModels = this.config.providers?.[providerId]?.models;
|
|
36786
|
-
if (allowedModels && !allowedModels.includes(parsed.model)) continue;
|
|
36787
36937
|
resolved.push({
|
|
36788
36938
|
providerId,
|
|
36789
36939
|
model: parsed.model,
|
|
@@ -36801,12 +36951,14 @@ var FallbackProfileManager = class {
|
|
|
36801
36951
|
resolveEffective(opts = {}) {
|
|
36802
36952
|
const bridge = this.resolveBridge(opts.exclude);
|
|
36803
36953
|
let selected = FREEZER_EMPTY;
|
|
36804
|
-
|
|
36805
|
-
|
|
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);
|
|
36806
36958
|
if (resolved.length > 0) selected = resolved;
|
|
36807
36959
|
}
|
|
36808
|
-
if (selected.length === 0 &&
|
|
36809
|
-
const resolved = this.resolve(
|
|
36960
|
+
if (selected.length === 0 && profileName) {
|
|
36961
|
+
const resolved = this.resolve(profileName, { exclude: opts.exclude });
|
|
36810
36962
|
if (resolved.length > 0) selected = resolved;
|
|
36811
36963
|
}
|
|
36812
36964
|
if (selected.length === 0 && opts.fallbackAuto !== false) {
|
|
@@ -36882,13 +37034,14 @@ var FallbackProfileManager = class {
|
|
|
36882
37034
|
};
|
|
36883
37035
|
const configFallbackAuto = this.config.fallbackAuto;
|
|
36884
37036
|
const effectiveFallbackAuto = configFallbackAuto !== void 0 && configFallbackAuto !== null ? configFallbackAuto : !opts.closedWorld;
|
|
36885
|
-
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();
|
|
36886
37039
|
const explicitUsable = explicitRefs !== void 0 && explicitRefs.length > 0 && this.resolveRefs(explicitRefs, current).length > 0;
|
|
36887
|
-
const profileUsable =
|
|
37040
|
+
const profileUsable = profileName !== void 0 && this.hasProfile(profileName) && this.resolve(profileName, { exclude: current }).length > 0;
|
|
36888
37041
|
const fromExplicitSource = explicitUsable || profileUsable;
|
|
36889
|
-
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({
|
|
36890
37043
|
fallbackModels: explicitRefs,
|
|
36891
|
-
fallbackProfile:
|
|
37044
|
+
fallbackProfile: profileName,
|
|
36892
37045
|
fallbackAuto: effectiveFallbackAuto,
|
|
36893
37046
|
exclude: current
|
|
36894
37047
|
});
|
|
@@ -36905,7 +37058,7 @@ var FallbackProfileManager = class {
|
|
|
36905
37058
|
});
|
|
36906
37059
|
}
|
|
36907
37060
|
candidates.push(...selectedChain);
|
|
36908
|
-
if (!fromExplicitSource && effectiveFallbackAuto &&
|
|
37061
|
+
if (!fromExplicitSource && effectiveFallbackAuto && profileName !== "default") {
|
|
36909
37062
|
candidates.push(...this.resolve("default", { exclude: current }));
|
|
36910
37063
|
}
|
|
36911
37064
|
if (!fromExplicitSource && effectiveFallbackAuto) {
|
|
@@ -36983,7 +37136,7 @@ var FallbackProfileManager = class {
|
|
|
36983
37136
|
const leaderModel = this.config.model;
|
|
36984
37137
|
const providers = this.config.providers ?? {};
|
|
36985
37138
|
const favoriteSet = new Set(
|
|
36986
|
-
(this.config.favoriteModels ?? []).map((ref) => {
|
|
37139
|
+
(asRefList(this.config.favoriteModels) ?? []).map((ref) => {
|
|
36987
37140
|
const p = parseModelRef(ref);
|
|
36988
37141
|
return `${p.provider ?? leaderProvider}/${p.model}`;
|
|
36989
37142
|
})
|
|
@@ -37094,9 +37247,15 @@ function effectiveFallbackChain(config) {
|
|
|
37094
37247
|
const mgr = new FallbackProfileManager(config);
|
|
37095
37248
|
return mgr.resolveEffective({
|
|
37096
37249
|
fallbackModels: config.fallbackModels,
|
|
37250
|
+
fallbackProfile: config.fallbackProfile,
|
|
37097
37251
|
fallbackAuto: config.fallbackAuto
|
|
37098
37252
|
}).map((e) => `${e.providerId}/${e.model}`);
|
|
37099
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
|
+
}
|
|
37100
37259
|
var DEFAULT_PRIMARY_COOLDOWN_MS = 6e4;
|
|
37101
37260
|
var DEFAULT_PRIMARY_COOLDOWN_MAX_MS = 10 * 6e4;
|
|
37102
37261
|
var DEFAULT_PRIMARY_RECOVERY_SUCCESSES = 2;
|
|
@@ -37136,7 +37295,11 @@ function createFallbackModelExtension(deps) {
|
|
|
37136
37295
|
let blockedPrimary;
|
|
37137
37296
|
let primaryBlockedUntil = 0;
|
|
37138
37297
|
const now = () => deps.now?.() ?? Date.now();
|
|
37139
|
-
const
|
|
37298
|
+
const liveStickiness = () => deps.getConfig().fallbackStickiness;
|
|
37299
|
+
const cooldownBase = () => Math.max(
|
|
37300
|
+
0,
|
|
37301
|
+
deps.primaryCooldownMs ?? liveStickiness()?.primaryProbeInterval ?? DEFAULT_PRIMARY_COOLDOWN_MS
|
|
37302
|
+
);
|
|
37140
37303
|
const cooldownMax = () => Math.max(cooldownBase(), deps.primaryCooldownMaxMs ?? DEFAULT_PRIMARY_COOLDOWN_MAX_MS);
|
|
37141
37304
|
const selectedPrimary = (cfg) => deps.getPrimaryTarget?.() ?? primaryTarget(cfg);
|
|
37142
37305
|
const primaryInCooldown = (cfg) => sameTarget(blockedPrimary, selectedPrimary(cfg)) && now() < primaryBlockedUntil;
|
|
@@ -37155,7 +37318,7 @@ function createFallbackModelExtension(deps) {
|
|
|
37155
37318
|
primaryBlockedUntil = now() + Math.min(cooldownMax(), base * multiplier);
|
|
37156
37319
|
};
|
|
37157
37320
|
const recoveryTarget = () => Math.max(1, deps.primaryRecoverySuccesses ?? DEFAULT_PRIMARY_RECOVERY_SUCCESSES);
|
|
37158
|
-
const stickyTarget = () => Math.max(0, deps.stickyFallbackTurns ?? 0);
|
|
37321
|
+
const stickyTarget = () => Math.max(0, deps.stickyFallbackTurns ?? liveStickiness()?.stickyFallbackTurns ?? 0);
|
|
37159
37322
|
const inStickyWindow = () => stickyTurnsElapsed < stickyTarget();
|
|
37160
37323
|
const onPrimarySuccess = (cfg) => {
|
|
37161
37324
|
if (!sameTarget(blockedPrimary, selectedPrimary(cfg))) return;
|
|
@@ -46891,10 +47054,10 @@ var AdaptiveConcurrencyController = class {
|
|
|
46891
47054
|
|
|
46892
47055
|
// src/coordination/agent-monitor.ts
|
|
46893
47056
|
init_file_permissions();
|
|
46894
|
-
import { createReadStream as
|
|
47057
|
+
import { createReadStream as createReadStream9 } from "node:fs";
|
|
46895
47058
|
import * as fs28 from "node:fs/promises";
|
|
46896
47059
|
import * as path69 from "node:path";
|
|
46897
|
-
import { createInterface as
|
|
47060
|
+
import { createInterface as createInterface9 } from "node:readline";
|
|
46898
47061
|
var AgentMonitorService = class _AgentMonitorService {
|
|
46899
47062
|
_fleetBus;
|
|
46900
47063
|
_events;
|
|
@@ -47001,8 +47164,8 @@ var AgentMonitorService = class _AgentMonitorService {
|
|
|
47001
47164
|
const accessible = await fs28.access(file).then(() => true).catch(() => false);
|
|
47002
47165
|
if (!accessible) return [];
|
|
47003
47166
|
const out = [];
|
|
47004
|
-
const input =
|
|
47005
|
-
const lines =
|
|
47167
|
+
const input = createReadStream9(file, { encoding: "utf8" });
|
|
47168
|
+
const lines = createInterface9({ input, crlfDelay: Number.POSITIVE_INFINITY });
|
|
47006
47169
|
try {
|
|
47007
47170
|
for await (const line of lines) {
|
|
47008
47171
|
const trimmed = line.trim();
|
|
@@ -56651,6 +56814,12 @@ function findExchangeStart(messages, userIndex) {
|
|
|
56651
56814
|
|
|
56652
56815
|
// src/execution/auto-compaction-middleware.ts
|
|
56653
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
|
+
}
|
|
56654
56823
|
var MAX_DIGEST_LOG_CHARS = 4e3;
|
|
56655
56824
|
function truncateDigest(digest2) {
|
|
56656
56825
|
if (digest2.length <= MAX_DIGEST_LOG_CHARS) return digest2;
|
|
@@ -56696,8 +56865,19 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
56696
56865
|
* 1 / 2.5 = 0.4.
|
|
56697
56866
|
*/
|
|
56698
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;
|
|
56699
56877
|
/** Tracks the most recent no-op attempt so we can avoid re-firing per turn. */
|
|
56700
56878
|
lastNoopAttempt = null;
|
|
56879
|
+
/** Context size at the last hygiene pass; anchors the growth interval. */
|
|
56880
|
+
lastHygieneTokens = null;
|
|
56701
56881
|
/**
|
|
56702
56882
|
* Cached token estimate from the last handler() invocation. When the
|
|
56703
56883
|
* message count and tool count haven't changed since the last estimate
|
|
@@ -56757,55 +56937,9 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
56757
56937
|
handler() {
|
|
56758
56938
|
return async (ctx, next) => {
|
|
56759
56939
|
if (!this._enabled) return next(ctx);
|
|
56760
|
-
|
|
56761
|
-
maxRetainedTokens: this.resolveToolResultRetention(ctx)
|
|
56762
|
-
});
|
|
56763
|
-
const receiptHygiene = collapseAcknowledgedToolReceipts(rawHygiene.messages, {
|
|
56764
|
-
maxPairs: this.resolveToolReceiptRetention(ctx)
|
|
56765
|
-
});
|
|
56766
|
-
if (rawHygiene.changed || receiptHygiene.changed) {
|
|
56767
|
-
ctx.state.replaceMessages(receiptHygiene.messages);
|
|
56768
|
-
ctx.clearFileTracking();
|
|
56769
|
-
this.invalidateTokenCaches(ctx);
|
|
56770
|
-
}
|
|
56771
|
-
const msgCount = ctx.messages.length;
|
|
56772
|
-
const toolCount = (ctx.tools ?? []).length;
|
|
56773
|
-
const revision = ctx.state?.revision ?? -1;
|
|
56774
|
-
let tokens;
|
|
56775
|
-
const anchorAt = typeof ctx.meta?.["realAnchorMsgCount"] === "number" ? ctx.meta["realAnchorMsgCount"] : void 0;
|
|
56776
|
-
const anchored = realAnchoredInputTokens(ctx.messages, ctx.lastRealInputTokens, anchorAt);
|
|
56777
|
-
if (anchored !== null) {
|
|
56778
|
-
tokens = anchored;
|
|
56779
|
-
} else if (this._estimator) {
|
|
56780
|
-
tokens = this._estimator(ctx);
|
|
56781
|
-
} else if (msgCount === this._cachedMsgCount && toolCount === this._cachedToolCount && revision === this._cachedRevision && ctx.systemPrompt === this._cachedSystemRef && ctx.tools === this._cachedToolsRef && this._cachedTokens >= 0) {
|
|
56782
|
-
tokens = this._cachedTokens;
|
|
56783
|
-
} else if (this.tryStashedTokens(ctx, msgCount, toolCount, revision) !== null) {
|
|
56784
|
-
const stashed = this.tryStashedTokens(ctx, msgCount, toolCount, revision);
|
|
56785
|
-
const cal = getCalibrationState(`${ctx.provider?.id ?? "unknown"}/${ctx.model}`);
|
|
56786
|
-
tokens = cal.calibrated ? Math.round(stashed * Math.min(1.5, Math.max(0.5, cal.ratio))) : stashed;
|
|
56787
|
-
this._cachedTokens = tokens;
|
|
56788
|
-
this._cachedMsgCount = msgCount;
|
|
56789
|
-
this._cachedToolCount = toolCount;
|
|
56790
|
-
this._cachedRevision = revision;
|
|
56791
|
-
this._cachedSystemRef = ctx.systemPrompt;
|
|
56792
|
-
this._cachedToolsRef = ctx.tools;
|
|
56793
|
-
} else {
|
|
56794
|
-
tokens = estimateRequestTokensCalibrated(
|
|
56795
|
-
ctx.messages,
|
|
56796
|
-
ctx.systemPrompt,
|
|
56797
|
-
ctx.tools ?? [],
|
|
56798
|
-
`${ctx.provider?.id ?? "unknown"}/${ctx.model}`
|
|
56799
|
-
).total;
|
|
56800
|
-
this._cachedTokens = tokens;
|
|
56801
|
-
this._cachedMsgCount = msgCount;
|
|
56802
|
-
this._cachedToolCount = toolCount;
|
|
56803
|
-
this._cachedRevision = revision;
|
|
56804
|
-
this._cachedSystemRef = ctx.systemPrompt;
|
|
56805
|
-
this._cachedToolsRef = ctx.tools;
|
|
56806
|
-
}
|
|
56940
|
+
let tokens = this.estimateContextTokens(ctx);
|
|
56807
56941
|
const runtimeMaxContext = effectiveMaxContext(ctx, this._maxContext);
|
|
56808
|
-
|
|
56942
|
+
let budget = computeContextWindowBudget(ctx, tokens, runtimeMaxContext);
|
|
56809
56943
|
const calibratedLoad = budget.load;
|
|
56810
56944
|
const policy = this.policyProvider?.(ctx);
|
|
56811
56945
|
const thresholds = policy?.thresholds ?? {
|
|
@@ -56819,22 +56953,27 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
56819
56953
|
});
|
|
56820
56954
|
const aggressiveOn = policy?.aggressiveOn ?? this.aggressiveOn;
|
|
56821
56955
|
const targetLoad = normalizeTargetLoad(policy?.targetLoad, adaptiveThresholds);
|
|
56822
|
-
let load = calibratedLoad;
|
|
56823
|
-
|
|
56824
|
-
const guardTotal = estimateRequestTokensUpperBound(
|
|
56825
|
-
ctx.messages,
|
|
56826
|
-
ctx.systemPrompt,
|
|
56827
|
-
ctx.tools ?? [],
|
|
56828
|
-
`${ctx.provider?.id ?? "unknown"}/${ctx.model}`
|
|
56829
|
-
).total;
|
|
56830
|
-
const guardLoad = guardTotal / budget.availableInputTokens;
|
|
56831
|
-
if (guardLoad > load) load = guardLoad;
|
|
56832
|
-
}
|
|
56833
|
-
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);
|
|
56834
56958
|
if (!level) {
|
|
56835
56959
|
this.lastNoopAttempt = null;
|
|
56836
56960
|
return next(ctx);
|
|
56837
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
|
+
}
|
|
56838
56977
|
if (this.shouldSkipNoopRetry(level, tokens)) {
|
|
56839
56978
|
return next(ctx);
|
|
56840
56979
|
}
|
|
@@ -56851,6 +56990,123 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
56851
56990
|
return next(ctx);
|
|
56852
56991
|
};
|
|
56853
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
|
+
}
|
|
56854
57110
|
/**
|
|
56855
57111
|
* H1: try to read a pre-computed token total from `ctx.lastRequestTokens`
|
|
56856
57112
|
* (set by the agent loop's pre-flight or its restash in emitContextPct).
|
|
@@ -61663,26 +61919,29 @@ var DefaultProviderRunner = class {
|
|
|
61663
61919
|
// src/execution/retry-policy.ts
|
|
61664
61920
|
import { randomInt } from "node:crypto";
|
|
61665
61921
|
var MAX_RETRY_AFTER_MS = 6e4;
|
|
61922
|
+
var MODEL_RETRIES = 3;
|
|
61666
61923
|
var MAX_ATTEMPTS_BY_KIND = {
|
|
61667
|
-
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,
|
|
61668
61930
|
quota_exhausted: 0,
|
|
61669
|
-
stream_hang: 2,
|
|
61670
|
-
// proxy-level timeout — retrying 5x wastes ~40s before fallback kicks in
|
|
61671
|
-
overloaded: 3,
|
|
61672
|
-
server: 3,
|
|
61673
|
-
timeout: 2,
|
|
61674
|
-
network: 2,
|
|
61675
61931
|
auth: 0,
|
|
61676
61932
|
invalid_request: 0,
|
|
61677
61933
|
context_overflow: 0,
|
|
61678
61934
|
content_filter: 0,
|
|
61679
61935
|
unknown: 0
|
|
61680
61936
|
};
|
|
61937
|
+
var FAILOVER_RETRY_AFTER_MS = 15e3;
|
|
61681
61938
|
var DefaultRetryPolicy = class {
|
|
61682
61939
|
shouldRetry(err, attempt) {
|
|
61683
61940
|
const isProviderErr = err instanceof ProviderError || ProviderError.isProviderError(err);
|
|
61684
61941
|
if (isProviderErr) {
|
|
61685
61942
|
if (!err.retryable) return false;
|
|
61943
|
+
const hint = retryAfterMsFromError(err);
|
|
61944
|
+
if (hint !== void 0 && hint >= FAILOVER_RETRY_AFTER_MS) return false;
|
|
61686
61945
|
return attempt < this.maxAttempts(err);
|
|
61687
61946
|
}
|
|
61688
61947
|
const msg = err.message ?? "";
|
|
@@ -63237,7 +63496,8 @@ async function evaluateToolKanbanBoundary(tool, input, ctx, options = {}) {
|
|
|
63237
63496
|
decision: "block",
|
|
63238
63497
|
reason: "Active card is not implementation-ready: " + readiness.issues.map((issue) => issue.message).join(" | "),
|
|
63239
63498
|
boardId: board.id,
|
|
63240
|
-
taskId: task.id
|
|
63499
|
+
taskId: task.id,
|
|
63500
|
+
readinessIssues: readiness.issues
|
|
63241
63501
|
};
|
|
63242
63502
|
}
|
|
63243
63503
|
if (task.lifecycle?.currentStage !== "running" || task.assignment?.status !== "running") {
|
|
@@ -64047,7 +64307,8 @@ ${errorDetails}`,
|
|
|
64047
64307
|
type: "tool_result",
|
|
64048
64308
|
tool_use_id: use.id,
|
|
64049
64309
|
content: `Tool "${tool.name}" blocked by Kanban boundary. ${boundary.reason ?? ""}`.trim(),
|
|
64050
|
-
is_error: true
|
|
64310
|
+
is_error: true,
|
|
64311
|
+
_kanbanBoundary: boundary
|
|
64051
64312
|
};
|
|
64052
64313
|
budget = this.budgetForString(result.content, budget);
|
|
64053
64314
|
return { result, tool, durationMs: Date.now() - start };
|
|
@@ -67090,7 +67351,7 @@ var AutoApprovePermissionPolicy = class _AutoApprovePermissionPolicy {
|
|
|
67090
67351
|
};
|
|
67091
67352
|
|
|
67092
67353
|
// src/security/permission-helpers.ts
|
|
67093
|
-
import { realpathSync as
|
|
67354
|
+
import { realpathSync as realpathSync3 } from "node:fs";
|
|
67094
67355
|
import * as path86 from "node:path";
|
|
67095
67356
|
function matchesTrust(patterns, subject2) {
|
|
67096
67357
|
return patterns.includes(subject2) || matchAny(patterns, subject2);
|
|
@@ -67175,7 +67436,7 @@ function realpathOfNearestExisting(p) {
|
|
|
67175
67436
|
const tail = [];
|
|
67176
67437
|
for (; ; ) {
|
|
67177
67438
|
try {
|
|
67178
|
-
return tail.length === 0 ?
|
|
67439
|
+
return tail.length === 0 ? realpathSync3(probe2) : path86.join(realpathSync3(probe2), ...tail);
|
|
67179
67440
|
} catch {
|
|
67180
67441
|
const parent = path86.dirname(probe2);
|
|
67181
67442
|
if (parent === probe2) return p;
|
|
@@ -83344,6 +83605,156 @@ var CLOUD_SYNC_CONTRACT = {
|
|
|
83344
83605
|
"extensions.plugins": EXTENSIONS_PLUGINS_TREE
|
|
83345
83606
|
};
|
|
83346
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
|
+
})();
|
|
83347
83758
|
var NAMESPACE_SCHEMA_VERSIONS = {
|
|
83348
83759
|
"core.runtime": 1,
|
|
83349
83760
|
"ui.preferences": 1,
|
|
@@ -83445,7 +83856,7 @@ function mergeAtContract(local, incoming, tree) {
|
|
|
83445
83856
|
return base;
|
|
83446
83857
|
}
|
|
83447
83858
|
function applyNamespacePayload(config, namespace, payload) {
|
|
83448
|
-
const tree =
|
|
83859
|
+
const tree = INBOUND_CONTRACT[namespace];
|
|
83449
83860
|
if (!tree || tree === true) return config;
|
|
83450
83861
|
const next = { ...config };
|
|
83451
83862
|
for (const [key, incoming] of Object.entries(payload)) {
|
|
@@ -89672,8 +90083,8 @@ var SessionRegistry = class {
|
|
|
89672
90083
|
if (id !== entry.sessionId) delete registry2[id];
|
|
89673
90084
|
continue;
|
|
89674
90085
|
}
|
|
89675
|
-
const
|
|
89676
|
-
if (
|
|
90086
|
+
const heartbeatAt = Date.parse(existing.lastHeartbeatAt);
|
|
90087
|
+
if (!Number.isFinite(heartbeatAt) || now - heartbeatAt > PID_CHECK_AFTER_MS && !pidAlive2(existing.pid)) {
|
|
89677
90088
|
delete registry2[id];
|
|
89678
90089
|
}
|
|
89679
90090
|
}
|
|
@@ -91569,6 +91980,15 @@ async function readProviderSnapshot(configPath, vault, warn) {
|
|
|
91569
91980
|
snapshot.fallbackBridge = decrypted.fallbackBridge.trim();
|
|
91570
91981
|
}
|
|
91571
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
|
+
}
|
|
91572
91992
|
if (Array.isArray(decrypted.favoriteModels)) snapshot.favoriteModels = decrypted.favoriteModels;
|
|
91573
91993
|
if (typeof decrypted.favoriteModelsOnly === "boolean")
|
|
91574
91994
|
snapshot.favoriteModelsOnly = decrypted.favoriteModelsOnly;
|
|
@@ -91587,10 +92007,13 @@ function serializeSnapshot(s) {
|
|
|
91587
92007
|
fallbackModels: s.fallbackModels ?? null,
|
|
91588
92008
|
fallbackBridge: s.fallbackBridge ?? null,
|
|
91589
92009
|
fallbackProfiles: s.fallbackProfiles ?? null,
|
|
92010
|
+
fallbackProfile: s.fallbackProfile ?? null,
|
|
91590
92011
|
favoriteModels: s.favoriteModels ?? null,
|
|
91591
92012
|
favoriteModelsOnly: s.favoriteModelsOnly ?? null,
|
|
91592
92013
|
modelMatrix: s.modelMatrix ?? null,
|
|
91593
92014
|
fallbackAuto: s.fallbackAuto ?? null,
|
|
92015
|
+
fallbackStickiness: s.fallbackStickiness ?? null,
|
|
92016
|
+
fallbackMaxLastResortCandidates: s.fallbackMaxLastResortCandidates ?? null,
|
|
91594
92017
|
modelAvailabilitySchedule: s.modelAvailabilitySchedule ?? null
|
|
91595
92018
|
});
|
|
91596
92019
|
}
|
|
@@ -92076,10 +92499,10 @@ var ReplayLogStore = class _ReplayLogStore {
|
|
|
92076
92499
|
};
|
|
92077
92500
|
|
|
92078
92501
|
// src/storage/session-recovery.ts
|
|
92079
|
-
import { createReadStream as
|
|
92502
|
+
import { createReadStream as createReadStream10 } from "node:fs";
|
|
92080
92503
|
import * as fs67 from "node:fs/promises";
|
|
92081
92504
|
import * as path122 from "node:path";
|
|
92082
|
-
import { createInterface as
|
|
92505
|
+
import { createInterface as createInterface10 } from "node:readline";
|
|
92083
92506
|
var SessionRecovery = class _SessionRecovery {
|
|
92084
92507
|
constructor(dir) {
|
|
92085
92508
|
this.dir = dir;
|
|
@@ -92156,8 +92579,8 @@ var SessionRecovery = class _SessionRecovery {
|
|
|
92156
92579
|
let lastCheckpoint = null;
|
|
92157
92580
|
let latestBoundary = null;
|
|
92158
92581
|
let sawEvent = false;
|
|
92159
|
-
const stream =
|
|
92160
|
-
const lines =
|
|
92582
|
+
const stream = createReadStream10(fp, { encoding: "utf8" });
|
|
92583
|
+
const lines = createInterface10({ input: stream, crlfDelay: Infinity });
|
|
92161
92584
|
try {
|
|
92162
92585
|
for await (const line of lines) {
|
|
92163
92586
|
if (!line.trim()) continue;
|
|
@@ -92316,10 +92739,10 @@ async function applyRewindToConversation(opts) {
|
|
|
92316
92739
|
init_errors();
|
|
92317
92740
|
init_atomic_write();
|
|
92318
92741
|
init_error();
|
|
92319
|
-
import { createReadStream as
|
|
92742
|
+
import { createReadStream as createReadStream11 } from "node:fs";
|
|
92320
92743
|
import * as fsp44 from "node:fs/promises";
|
|
92321
92744
|
import * as path123 from "node:path";
|
|
92322
|
-
import { createInterface as
|
|
92745
|
+
import { createInterface as createInterface11 } from "node:readline";
|
|
92323
92746
|
var DefaultSessionRewinder = class {
|
|
92324
92747
|
constructor(sessionsDir, projectRoot) {
|
|
92325
92748
|
this.sessionsDir = sessionsDir;
|
|
@@ -92331,8 +92754,8 @@ var DefaultSessionRewinder = class {
|
|
|
92331
92754
|
return sessionScopedPath(this.sessionsDir, sessionId, ".jsonl");
|
|
92332
92755
|
}
|
|
92333
92756
|
async *readEvents(file) {
|
|
92334
|
-
const stream =
|
|
92335
|
-
const lines =
|
|
92757
|
+
const stream = createReadStream11(file, { encoding: "utf8" });
|
|
92758
|
+
const lines = createInterface11({ input: stream, crlfDelay: Infinity });
|
|
92336
92759
|
try {
|
|
92337
92760
|
for await (const line of lines) {
|
|
92338
92761
|
if (!line.trim()) continue;
|
|
@@ -93964,6 +94387,30 @@ var PROVIDER_MANAGE_SCHEMA = {
|
|
|
93964
94387
|
required: ["action"],
|
|
93965
94388
|
additionalProperties: false
|
|
93966
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
|
+
}
|
|
93967
94414
|
function validateProviderBaseUrl(raw) {
|
|
93968
94415
|
let url;
|
|
93969
94416
|
try {
|
|
@@ -94034,6 +94481,8 @@ ${msg}`,
|
|
|
94034
94481
|
const invalid2 = validateProviderBaseUrl(input.baseUrl);
|
|
94035
94482
|
if (invalid2) return { status: "error", message: invalid2 };
|
|
94036
94483
|
}
|
|
94484
|
+
const borrowed = rejectBorrowedEnvVars(providers, input.provider, input.envVars);
|
|
94485
|
+
if (borrowed) return { status: "error", message: borrowed };
|
|
94037
94486
|
const entry = { type: input.type };
|
|
94038
94487
|
if (input.models) entry.models = input.models;
|
|
94039
94488
|
if (input.baseUrl) entry.baseUrl = input.baseUrl;
|
|
@@ -94067,14 +94516,22 @@ ${msg}`,
|
|
|
94067
94516
|
if (input.autoDiscoverModels !== void 0) entry.autoDiscoverModels = input.autoDiscoverModels;
|
|
94068
94517
|
if (input.apiKey !== void 0) entry.apiKey = input.apiKey || void 0;
|
|
94069
94518
|
const endpointChanged = input.baseUrl !== void 0 && (entry.baseUrl ?? void 0) !== (previous.baseUrl ?? void 0);
|
|
94070
|
-
const
|
|
94071
|
-
|
|
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 };
|
|
94072
94529
|
providers[input.provider] = entry;
|
|
94073
94530
|
await opts.updateConfig((cfg) => {
|
|
94074
94531
|
cfg.providers = providers;
|
|
94075
94532
|
});
|
|
94076
94533
|
const updated = Object.keys({ ...entry }).filter((k) => k !== "apiKey").join(", ");
|
|
94077
|
-
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` : "";
|
|
94078
94535
|
return { status: "ok", message: `\u2713 Updated ${input.provider}: ${updated}${keyNote}` };
|
|
94079
94536
|
}
|
|
94080
94537
|
if (input.action === "remove") {
|
|
@@ -97015,6 +97472,7 @@ export {
|
|
|
97015
97472
|
runShellHook,
|
|
97016
97473
|
runWithNetworkTelemetry,
|
|
97017
97474
|
runWithProcessTelemetry,
|
|
97475
|
+
runtimeFallbackChain,
|
|
97018
97476
|
safeEmit,
|
|
97019
97477
|
safeParse,
|
|
97020
97478
|
safeProfileName,
|