@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/defaults/index.js
CHANGED
|
@@ -9828,6 +9828,24 @@ import {
|
|
|
9828
9828
|
} from "@wrongstack/persistence";
|
|
9829
9829
|
|
|
9830
9830
|
// src/security/secret-scrubber.ts
|
|
9831
|
+
var JSON_CREDENTIAL_KEY_ANCHORS = [
|
|
9832
|
+
'Key"',
|
|
9833
|
+
'key"',
|
|
9834
|
+
'KEY"',
|
|
9835
|
+
'token"',
|
|
9836
|
+
'Token"',
|
|
9837
|
+
'TOKEN"',
|
|
9838
|
+
'secret"',
|
|
9839
|
+
'Secret"',
|
|
9840
|
+
'SECRET"',
|
|
9841
|
+
'password"',
|
|
9842
|
+
'Password"',
|
|
9843
|
+
'PASSWORD"',
|
|
9844
|
+
'authorization"',
|
|
9845
|
+
'Authorization"',
|
|
9846
|
+
'bearer"',
|
|
9847
|
+
'Bearer"'
|
|
9848
|
+
];
|
|
9831
9849
|
var PATTERNS = [
|
|
9832
9850
|
// Anchored at the start where possible so partial matches inside larger
|
|
9833
9851
|
// strings don't trigger false positives.
|
|
@@ -9930,6 +9948,30 @@ var PATTERNS = [
|
|
|
9930
9948
|
regex: /(^|\s)([A-Z_]{4,}(?:KEY|TOKEN|SECRET|PASSWORD|PWD))\s*[:=]\s*['"]?([A-Za-z0-9_/+=-]{20,512})['"]?(?=\s|$)/g,
|
|
9931
9949
|
anchor: ["KEY", "TOKEN", "SECRET", "PASSWORD", "PWD"]
|
|
9932
9950
|
},
|
|
9951
|
+
{
|
|
9952
|
+
type: "json_credential_key",
|
|
9953
|
+
// The JSON counterpart to `high_entropy_env`, and the pattern that the
|
|
9954
|
+
// now-deleted `JSON_KEY_ANCHORS` list was written for. Without it those
|
|
9955
|
+
// anchors only widened the cheap pre-scan — `hasCredentialAnchors` said
|
|
9956
|
+
// "this text may hold a secret", every pattern then declined to match, and
|
|
9957
|
+
// the value went out verbatim. `high_entropy_env` cannot cover these: it
|
|
9958
|
+
// requires an UPPERCASE unquoted key (`API_KEY=…`), so `{"apiKey":"…"}`
|
|
9959
|
+
// never matched.
|
|
9960
|
+
//
|
|
9961
|
+
// Tool results are routinely serialised as JSON, and a credential with no
|
|
9962
|
+
// recognisable prefix (Azure, self-hosted gateways, Anthropic/Codex OAuth)
|
|
9963
|
+
// has no other pattern that can catch it — this is the only thing standing
|
|
9964
|
+
// between such a value and the session JSONL, chronicle, HQ broadcast and
|
|
9965
|
+
// the model's own context.
|
|
9966
|
+
//
|
|
9967
|
+
// The key may carry a prefix (`"anthropicApiKey"`), but the credential word
|
|
9968
|
+
// must END the key: `"tokenCount"` and `"maxTokens"` do not match, because
|
|
9969
|
+
// the closing quote has to follow the word immediately.
|
|
9970
|
+
// Value floor of 8 chars keeps enum-ish values (`"authorization":"none"`)
|
|
9971
|
+
// out. Capture groups: 1=key + punctuation, 2=value, 3=closing quote.
|
|
9972
|
+
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,
|
|
9973
|
+
anchor: JSON_CREDENTIAL_KEY_ANCHORS
|
|
9974
|
+
},
|
|
9933
9975
|
// ── Ported from packages/plugins credential-patterns.ts (WS-034) ─────────
|
|
9934
9976
|
// The plugin runtime carried 37 patterns while this scrubber — the one that
|
|
9935
9977
|
// guards session JSONL, chronicle, HQ broadcast, WebUI events and the auth
|
|
@@ -10012,9 +10054,12 @@ var PATTERNS = [
|
|
|
10012
10054
|
anchor: "GOCSPX-"
|
|
10013
10055
|
}
|
|
10014
10056
|
];
|
|
10015
|
-
var SIMPLE_PATTERNS = PATTERNS.filter(
|
|
10057
|
+
var SIMPLE_PATTERNS = PATTERNS.filter(
|
|
10058
|
+
(p) => p.type !== "high_entropy_env" && p.type !== "json_credential_key"
|
|
10059
|
+
);
|
|
10016
10060
|
var COMBINED_REGEX = new RegExp(SIMPLE_PATTERNS.map((p) => `(${p.regex.source})`).join("|"), "g");
|
|
10017
10061
|
var HIGH_ENTROPY_REGEX = PATTERNS.find((p) => p.type === "high_entropy_env").regex;
|
|
10062
|
+
var JSON_CREDENTIAL_REGEX = PATTERNS.find((p) => p.type === "json_credential_key").regex;
|
|
10018
10063
|
var COMBINED_REPLACEMENTS = SIMPLE_PATTERNS.map((p) => `[REDACTED:${p.type}]`);
|
|
10019
10064
|
var SCRUB_CHUNK_BYTES = 64 * 1024;
|
|
10020
10065
|
var SCRUB_OVERLAP_BYTES = 1024;
|
|
@@ -10025,20 +10070,7 @@ var PATTERN_ANCHORS = [
|
|
|
10025
10070
|
)
|
|
10026
10071
|
)
|
|
10027
10072
|
];
|
|
10028
|
-
var
|
|
10029
|
-
'"apiKey"',
|
|
10030
|
-
'"api_key"',
|
|
10031
|
-
'"token"',
|
|
10032
|
-
'"secret"',
|
|
10033
|
-
'"password"',
|
|
10034
|
-
'"authorization"',
|
|
10035
|
-
'"bearer"',
|
|
10036
|
-
'"private_key"',
|
|
10037
|
-
'"access_token"',
|
|
10038
|
-
'"refresh_token"',
|
|
10039
|
-
'"client_secret"'
|
|
10040
|
-
];
|
|
10041
|
-
var ALL_ANCHORS = [...PATTERN_ANCHORS, ...JSON_KEY_ANCHORS];
|
|
10073
|
+
var ALL_ANCHORS = PATTERN_ANCHORS;
|
|
10042
10074
|
function hasCredentialAnchors(text) {
|
|
10043
10075
|
for (const anchor of ALL_ANCHORS) {
|
|
10044
10076
|
if (text.includes(anchor)) return true;
|
|
@@ -10087,6 +10119,9 @@ var DefaultSecretScrubber = class {
|
|
|
10087
10119
|
out = out.replace(HIGH_ENTROPY_REGEX, (_match, lead, key, _value) => {
|
|
10088
10120
|
return `${lead}${key}=[REDACTED:high_entropy_env]`;
|
|
10089
10121
|
});
|
|
10122
|
+
out = out.replace(JSON_CREDENTIAL_REGEX, (_match, keyPrefix, _value, closingQuote) => {
|
|
10123
|
+
return `${keyPrefix}[REDACTED:json_credential_key]${closingQuote}`;
|
|
10124
|
+
});
|
|
10090
10125
|
return out;
|
|
10091
10126
|
}
|
|
10092
10127
|
/**
|
|
@@ -11017,6 +11052,7 @@ function isEmptyMessage(msg) {
|
|
|
11017
11052
|
}
|
|
11018
11053
|
|
|
11019
11054
|
// src/core/context.ts
|
|
11055
|
+
import { realpathSync } from "node:fs";
|
|
11020
11056
|
import * as path13 from "node:path";
|
|
11021
11057
|
|
|
11022
11058
|
// src/utils/tool-wire-compact.ts
|
|
@@ -11446,12 +11482,15 @@ var ConversationState = class {
|
|
|
11446
11482
|
* cap determines the starting index for the sum but does not gate it.
|
|
11447
11483
|
*/
|
|
11448
11484
|
overflowCount(arr) {
|
|
11449
|
-
|
|
11450
|
-
|
|
11485
|
+
const contextClass = this.ctx.constructor;
|
|
11486
|
+
const maxMessages = contextClass.MAX_MESSAGES;
|
|
11487
|
+
const maxMessageTokens = contextClass.MAX_MESSAGE_TOKENS;
|
|
11488
|
+
let drop = maxMessages > 0 ? Math.max(0, arr.length - maxMessages) : 0;
|
|
11489
|
+
if (maxMessageTokens <= 0) return this.protocolSafeDropCount(arr, drop);
|
|
11451
11490
|
let total = 0;
|
|
11452
11491
|
for (let i = drop; i < arr.length; i++) total += arr[i]?._estTokens ?? 0;
|
|
11453
|
-
if (total <=
|
|
11454
|
-
while (drop < arr.length - 1 && total >
|
|
11492
|
+
if (total <= maxMessageTokens) return this.protocolSafeDropCount(arr, drop);
|
|
11493
|
+
while (drop < arr.length - 1 && total > maxMessageTokens) {
|
|
11455
11494
|
total -= arr[drop]?._estTokens ?? 0;
|
|
11456
11495
|
drop++;
|
|
11457
11496
|
}
|
|
@@ -12274,6 +12313,19 @@ var Context = class _Context {
|
|
|
12274
12313
|
if (rel.startsWith("..") || path13.isAbsolute(rel)) {
|
|
12275
12314
|
throw new Error(`Working directory "${resolved}" is outside project root "${root}"`);
|
|
12276
12315
|
}
|
|
12316
|
+
let realTarget = resolved;
|
|
12317
|
+
let realRoot = root;
|
|
12318
|
+
try {
|
|
12319
|
+
realTarget = realpathSync.native(resolved);
|
|
12320
|
+
realRoot = realpathSync.native(root);
|
|
12321
|
+
} catch {
|
|
12322
|
+
}
|
|
12323
|
+
const realRel = path13.relative(realRoot, realTarget);
|
|
12324
|
+
if (realRel.startsWith("..") || path13.isAbsolute(realRel)) {
|
|
12325
|
+
throw new Error(
|
|
12326
|
+
`Working directory "${resolved}" resolves to "${realTarget}", outside project root "${realRoot}"`
|
|
12327
|
+
);
|
|
12328
|
+
}
|
|
12277
12329
|
}
|
|
12278
12330
|
const old = this.workingDir;
|
|
12279
12331
|
this.workingDir = resolved;
|
|
@@ -14005,6 +14057,44 @@ function userInputTitle(content) {
|
|
|
14005
14057
|
return sessionContentPreview(content, 60);
|
|
14006
14058
|
}
|
|
14007
14059
|
|
|
14060
|
+
// src/storage/session-writer-scrubber.ts
|
|
14061
|
+
function scrubSessionWriterEvent(event, secretScrubber) {
|
|
14062
|
+
const persistMessage = (message) => {
|
|
14063
|
+
const { _estTokens: _ignored, ...persisted } = message;
|
|
14064
|
+
return {
|
|
14065
|
+
...persisted,
|
|
14066
|
+
content: typeof persisted.content === "string" ? secretScrubber?.scrub(persisted.content) ?? persisted.content : secretScrubber?.scrubObject(persisted.content) ?? persisted.content
|
|
14067
|
+
};
|
|
14068
|
+
};
|
|
14069
|
+
if (event.type === "context_snapshot" || event.type === "messages_replaced") {
|
|
14070
|
+
return { ...event, messages: event.messages.map(persistMessage) };
|
|
14071
|
+
}
|
|
14072
|
+
if (event.type === "message_appended" || event.type === "message_updated") {
|
|
14073
|
+
return { ...event, message: persistMessage(event.message) };
|
|
14074
|
+
}
|
|
14075
|
+
if (!secretScrubber) return event;
|
|
14076
|
+
if (event.type === "user_input") {
|
|
14077
|
+
return {
|
|
14078
|
+
...event,
|
|
14079
|
+
content: typeof event.content === "string" ? secretScrubber.scrub(event.content) : secretScrubber.scrubObject(event.content)
|
|
14080
|
+
};
|
|
14081
|
+
}
|
|
14082
|
+
if (event.type === "llm_response") {
|
|
14083
|
+
return { ...event, content: secretScrubber.scrubObject(event.content) };
|
|
14084
|
+
}
|
|
14085
|
+
if (event.type === "file_snapshot") {
|
|
14086
|
+
return {
|
|
14087
|
+
...event,
|
|
14088
|
+
files: event.files.map((f) => ({
|
|
14089
|
+
...f,
|
|
14090
|
+
before: f.before !== null ? secretScrubber.scrub(f.before) : null,
|
|
14091
|
+
after: f.after !== null ? secretScrubber.scrub(f.after) : null
|
|
14092
|
+
}))
|
|
14093
|
+
};
|
|
14094
|
+
}
|
|
14095
|
+
return event;
|
|
14096
|
+
}
|
|
14097
|
+
|
|
14008
14098
|
// src/storage/session-writer-truncate.ts
|
|
14009
14099
|
import * as fsp6 from "node:fs/promises";
|
|
14010
14100
|
var CHUNK_SIZE = 65536;
|
|
@@ -14149,45 +14239,11 @@ async function rewriteSessionToCheckpoint(filePath, checkpointByteOffset) {
|
|
|
14149
14239
|
}
|
|
14150
14240
|
}
|
|
14151
14241
|
|
|
14152
|
-
// src/storage/session-writer-scrubber.ts
|
|
14153
|
-
function scrubSessionWriterEvent(event, secretScrubber) {
|
|
14154
|
-
const persistMessage = (message) => {
|
|
14155
|
-
const { _estTokens: _ignored, ...persisted } = message;
|
|
14156
|
-
return {
|
|
14157
|
-
...persisted,
|
|
14158
|
-
content: typeof persisted.content === "string" ? secretScrubber?.scrub(persisted.content) ?? persisted.content : secretScrubber?.scrubObject(persisted.content) ?? persisted.content
|
|
14159
|
-
};
|
|
14160
|
-
};
|
|
14161
|
-
if (event.type === "context_snapshot" || event.type === "messages_replaced") {
|
|
14162
|
-
return { ...event, messages: event.messages.map(persistMessage) };
|
|
14163
|
-
}
|
|
14164
|
-
if (event.type === "message_appended" || event.type === "message_updated") {
|
|
14165
|
-
return { ...event, message: persistMessage(event.message) };
|
|
14166
|
-
}
|
|
14167
|
-
if (!secretScrubber) return event;
|
|
14168
|
-
if (event.type === "user_input") {
|
|
14169
|
-
return {
|
|
14170
|
-
...event,
|
|
14171
|
-
content: typeof event.content === "string" ? secretScrubber.scrub(event.content) : secretScrubber.scrubObject(event.content)
|
|
14172
|
-
};
|
|
14173
|
-
}
|
|
14174
|
-
if (event.type === "llm_response") {
|
|
14175
|
-
return { ...event, content: secretScrubber.scrubObject(event.content) };
|
|
14176
|
-
}
|
|
14177
|
-
if (event.type === "file_snapshot") {
|
|
14178
|
-
return {
|
|
14179
|
-
...event,
|
|
14180
|
-
files: event.files.map((f) => ({
|
|
14181
|
-
...f,
|
|
14182
|
-
before: f.before !== null ? secretScrubber.scrub(f.before) : null,
|
|
14183
|
-
after: f.after !== null ? secretScrubber.scrub(f.after) : null
|
|
14184
|
-
}))
|
|
14185
|
-
};
|
|
14186
|
-
}
|
|
14187
|
-
return event;
|
|
14188
|
-
}
|
|
14189
|
-
|
|
14190
14242
|
// src/storage/file-session-writer.ts
|
|
14243
|
+
function isClosedHandleError(err) {
|
|
14244
|
+
const code = err?.code;
|
|
14245
|
+
return code === "EBADF" || code === "ERR_CLOSED_RESOURCE" || code === "ERR_INVALID_HANDLE";
|
|
14246
|
+
}
|
|
14191
14247
|
var FileSessionWriter = class _FileSessionWriter {
|
|
14192
14248
|
constructor(id, handle, startedAt, meta, events, opts = {}, traceId) {
|
|
14193
14249
|
this.id = id;
|
|
@@ -14359,8 +14415,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
14359
14415
|
try {
|
|
14360
14416
|
return await this.handle.appendFile(data, "utf8");
|
|
14361
14417
|
} catch (err) {
|
|
14362
|
-
|
|
14363
|
-
if (nodeErr?.code === "EBADF") {
|
|
14418
|
+
if (isClosedHandleError(err)) {
|
|
14364
14419
|
this.handle = await fsp7.open(this.filePath, "a", 384);
|
|
14365
14420
|
return await this.handle.appendFile(data, "utf8");
|
|
14366
14421
|
}
|
|
@@ -14400,8 +14455,8 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
14400
14455
|
bufferSynchronousEvent(event) {
|
|
14401
14456
|
if (this.closed) return;
|
|
14402
14457
|
void this.ensureInit();
|
|
14403
|
-
this.
|
|
14404
|
-
|
|
14458
|
+
const appendEvent = scrubSessionWriterEvent(event, this.secretScrubber);
|
|
14459
|
+
this.observeForSummary(appendEvent);
|
|
14405
14460
|
try {
|
|
14406
14461
|
this._onAppend?.(appendEvent);
|
|
14407
14462
|
} catch {
|
|
@@ -14554,8 +14609,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
14554
14609
|
try {
|
|
14555
14610
|
await this.handle.datasync();
|
|
14556
14611
|
} catch (err) {
|
|
14557
|
-
|
|
14558
|
-
if (nodeErr?.code === "EBADF") {
|
|
14612
|
+
if (isClosedHandleError(err)) {
|
|
14559
14613
|
this.handle = await fsp7.open(this.filePath, "a", 384);
|
|
14560
14614
|
return;
|
|
14561
14615
|
}
|
|
@@ -14792,6 +14846,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
14792
14846
|
return this.closePromise;
|
|
14793
14847
|
}
|
|
14794
14848
|
async doClose() {
|
|
14849
|
+
await this.ensureInit();
|
|
14795
14850
|
if (this.pendingFileSnapshots.length > 0) {
|
|
14796
14851
|
await this.writeFileSnapshot(this.activePromptIndex ?? 0, [...this.pendingFileSnapshots]);
|
|
14797
14852
|
this.pendingFileSnapshots = [];
|
|
@@ -14807,8 +14862,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
14807
14862
|
try {
|
|
14808
14863
|
await this.handle.datasync();
|
|
14809
14864
|
} catch (err) {
|
|
14810
|
-
|
|
14811
|
-
if (nodeErr?.code !== "EBADF") throw err;
|
|
14865
|
+
if (!isClosedHandleError(err)) throw err;
|
|
14812
14866
|
}
|
|
14813
14867
|
const endedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
14814
14868
|
const observedActivityMs = Date.parse(this.lastActivityAt);
|
|
@@ -16524,9 +16578,40 @@ async function readOrBuildShardManifestEntry(opts) {
|
|
|
16524
16578
|
return entry;
|
|
16525
16579
|
}
|
|
16526
16580
|
|
|
16527
|
-
// src/storage/session-store/
|
|
16581
|
+
// src/storage/session-store/strict-empty-check.ts
|
|
16528
16582
|
import { createReadStream as createReadStream3 } from "node:fs";
|
|
16529
16583
|
import { createInterface as createInterface3 } from "node:readline";
|
|
16584
|
+
var EMPTY_SESSION_EVENT_TYPES = /* @__PURE__ */ new Set(["session_start", "session_resumed", "session_end"]);
|
|
16585
|
+
async function isStrictlyEmptySessionFile(file) {
|
|
16586
|
+
const input = createReadStream3(file, { encoding: "utf8" });
|
|
16587
|
+
const lines = createInterface3({ input, crlfDelay: Infinity });
|
|
16588
|
+
let sawSessionStart = false;
|
|
16589
|
+
try {
|
|
16590
|
+
for await (const line of lines) {
|
|
16591
|
+
if (!line.trim()) continue;
|
|
16592
|
+
let event;
|
|
16593
|
+
try {
|
|
16594
|
+
event = JSON.parse(line);
|
|
16595
|
+
} catch {
|
|
16596
|
+
return false;
|
|
16597
|
+
}
|
|
16598
|
+
if (event === null || typeof event !== "object" || Array.isArray(event)) return false;
|
|
16599
|
+
const type = event.type;
|
|
16600
|
+
if (typeof type !== "string" || !EMPTY_SESSION_EVENT_TYPES.has(type)) return false;
|
|
16601
|
+
if (type === "session_start") sawSessionStart = true;
|
|
16602
|
+
}
|
|
16603
|
+
} catch {
|
|
16604
|
+
return false;
|
|
16605
|
+
} finally {
|
|
16606
|
+
lines.close();
|
|
16607
|
+
input.destroy();
|
|
16608
|
+
}
|
|
16609
|
+
return sawSessionStart;
|
|
16610
|
+
}
|
|
16611
|
+
|
|
16612
|
+
// src/storage/session-store/summary-builder.ts
|
|
16613
|
+
import { createReadStream as createReadStream4 } from "node:fs";
|
|
16614
|
+
import { createInterface as createInterface4 } from "node:readline";
|
|
16530
16615
|
async function summarizeSessionFile(opts) {
|
|
16531
16616
|
return summarizeSessionEventSequence({
|
|
16532
16617
|
id: opts.id,
|
|
@@ -16643,8 +16728,8 @@ async function summarizeSessionEventSequence(opts) {
|
|
|
16643
16728
|
}
|
|
16644
16729
|
}
|
|
16645
16730
|
async function* iterateSessionEvents(file, secretScrubber) {
|
|
16646
|
-
const stream =
|
|
16647
|
-
const lines =
|
|
16731
|
+
const stream = createReadStream4(file, { encoding: "utf8" });
|
|
16732
|
+
const lines = createInterface4({ input: stream, crlfDelay: Infinity });
|
|
16648
16733
|
try {
|
|
16649
16734
|
for await (const line of lines) {
|
|
16650
16735
|
if (!line.trim()) continue;
|
|
@@ -17540,6 +17625,10 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
17540
17625
|
await deleteSessionArtifacts({ rootDir: this.dir, id, jsonlPath });
|
|
17541
17626
|
await this.writeTombstone(id);
|
|
17542
17627
|
}
|
|
17628
|
+
async isEmpty(id) {
|
|
17629
|
+
const canonicalId = await this.resolveId(id);
|
|
17630
|
+
return isStrictlyEmptySessionFile(this.sessionPath(canonicalId, ".jsonl"));
|
|
17631
|
+
}
|
|
17543
17632
|
async delete(id) {
|
|
17544
17633
|
if (this.catalogClient) {
|
|
17545
17634
|
const canonical = await this.resolveId(id);
|
|
@@ -21469,6 +21558,12 @@ function findExchangeStart(messages, userIndex) {
|
|
|
21469
21558
|
|
|
21470
21559
|
// src/execution/auto-compaction-middleware.ts
|
|
21471
21560
|
var LEVEL_RANK = { warn: 0, soft: 1, hard: 2 };
|
|
21561
|
+
function pressureLevelFor(load, thresholds) {
|
|
21562
|
+
if (load >= thresholds.hard) return "hard";
|
|
21563
|
+
if (load >= thresholds.soft) return "soft";
|
|
21564
|
+
if (load >= thresholds.warn) return "warn";
|
|
21565
|
+
return null;
|
|
21566
|
+
}
|
|
21472
21567
|
var MAX_DIGEST_LOG_CHARS = 4e3;
|
|
21473
21568
|
function truncateDigest(digest) {
|
|
21474
21569
|
if (digest.length <= MAX_DIGEST_LOG_CHARS) return digest;
|
|
@@ -21514,8 +21609,19 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
21514
21609
|
* 1 / 2.5 = 0.4.
|
|
21515
21610
|
*/
|
|
21516
21611
|
static GUARD_GATE_LOAD = 0.4;
|
|
21612
|
+
/**
|
|
21613
|
+
* How much the context must grow between two history-rewriting hygiene
|
|
21614
|
+
* passes, as a fraction of the available input window and as an absolute
|
|
21615
|
+
* floor. Every pass rewrites already-transmitted messages, which forces the
|
|
21616
|
+
* provider to re-cache the whole prompt; spacing the passes out is what lets
|
|
21617
|
+
* the conversation prefix stay cached for the turns in between.
|
|
21618
|
+
*/
|
|
21619
|
+
static HYGIENE_GROWTH_RATIO = 0.15;
|
|
21620
|
+
static HYGIENE_MIN_GROWTH_TOKENS = 2e4;
|
|
21517
21621
|
/** Tracks the most recent no-op attempt so we can avoid re-firing per turn. */
|
|
21518
21622
|
lastNoopAttempt = null;
|
|
21623
|
+
/** Context size at the last hygiene pass; anchors the growth interval. */
|
|
21624
|
+
lastHygieneTokens = null;
|
|
21519
21625
|
/**
|
|
21520
21626
|
* Cached token estimate from the last handler() invocation. When the
|
|
21521
21627
|
* message count and tool count haven't changed since the last estimate
|
|
@@ -21575,55 +21681,9 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
21575
21681
|
handler() {
|
|
21576
21682
|
return async (ctx, next) => {
|
|
21577
21683
|
if (!this._enabled) return next(ctx);
|
|
21578
|
-
|
|
21579
|
-
maxRetainedTokens: this.resolveToolResultRetention(ctx)
|
|
21580
|
-
});
|
|
21581
|
-
const receiptHygiene = collapseAcknowledgedToolReceipts(rawHygiene.messages, {
|
|
21582
|
-
maxPairs: this.resolveToolReceiptRetention(ctx)
|
|
21583
|
-
});
|
|
21584
|
-
if (rawHygiene.changed || receiptHygiene.changed) {
|
|
21585
|
-
ctx.state.replaceMessages(receiptHygiene.messages);
|
|
21586
|
-
ctx.clearFileTracking();
|
|
21587
|
-
this.invalidateTokenCaches(ctx);
|
|
21588
|
-
}
|
|
21589
|
-
const msgCount = ctx.messages.length;
|
|
21590
|
-
const toolCount = (ctx.tools ?? []).length;
|
|
21591
|
-
const revision = ctx.state?.revision ?? -1;
|
|
21592
|
-
let tokens;
|
|
21593
|
-
const anchorAt = typeof ctx.meta?.["realAnchorMsgCount"] === "number" ? ctx.meta["realAnchorMsgCount"] : void 0;
|
|
21594
|
-
const anchored = realAnchoredInputTokens(ctx.messages, ctx.lastRealInputTokens, anchorAt);
|
|
21595
|
-
if (anchored !== null) {
|
|
21596
|
-
tokens = anchored;
|
|
21597
|
-
} else if (this._estimator) {
|
|
21598
|
-
tokens = this._estimator(ctx);
|
|
21599
|
-
} else if (msgCount === this._cachedMsgCount && toolCount === this._cachedToolCount && revision === this._cachedRevision && ctx.systemPrompt === this._cachedSystemRef && ctx.tools === this._cachedToolsRef && this._cachedTokens >= 0) {
|
|
21600
|
-
tokens = this._cachedTokens;
|
|
21601
|
-
} else if (this.tryStashedTokens(ctx, msgCount, toolCount, revision) !== null) {
|
|
21602
|
-
const stashed = this.tryStashedTokens(ctx, msgCount, toolCount, revision);
|
|
21603
|
-
const cal = getCalibrationState(`${ctx.provider?.id ?? "unknown"}/${ctx.model}`);
|
|
21604
|
-
tokens = cal.calibrated ? Math.round(stashed * Math.min(1.5, Math.max(0.5, cal.ratio))) : stashed;
|
|
21605
|
-
this._cachedTokens = tokens;
|
|
21606
|
-
this._cachedMsgCount = msgCount;
|
|
21607
|
-
this._cachedToolCount = toolCount;
|
|
21608
|
-
this._cachedRevision = revision;
|
|
21609
|
-
this._cachedSystemRef = ctx.systemPrompt;
|
|
21610
|
-
this._cachedToolsRef = ctx.tools;
|
|
21611
|
-
} else {
|
|
21612
|
-
tokens = estimateRequestTokensCalibrated(
|
|
21613
|
-
ctx.messages,
|
|
21614
|
-
ctx.systemPrompt,
|
|
21615
|
-
ctx.tools ?? [],
|
|
21616
|
-
`${ctx.provider?.id ?? "unknown"}/${ctx.model}`
|
|
21617
|
-
).total;
|
|
21618
|
-
this._cachedTokens = tokens;
|
|
21619
|
-
this._cachedMsgCount = msgCount;
|
|
21620
|
-
this._cachedToolCount = toolCount;
|
|
21621
|
-
this._cachedRevision = revision;
|
|
21622
|
-
this._cachedSystemRef = ctx.systemPrompt;
|
|
21623
|
-
this._cachedToolsRef = ctx.tools;
|
|
21624
|
-
}
|
|
21684
|
+
let tokens = this.estimateContextTokens(ctx);
|
|
21625
21685
|
const runtimeMaxContext = effectiveMaxContext(ctx, this._maxContext);
|
|
21626
|
-
|
|
21686
|
+
let budget = computeContextWindowBudget(ctx, tokens, runtimeMaxContext);
|
|
21627
21687
|
const calibratedLoad = budget.load;
|
|
21628
21688
|
const policy = this.policyProvider?.(ctx);
|
|
21629
21689
|
const thresholds = policy?.thresholds ?? {
|
|
@@ -21637,22 +21697,27 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
21637
21697
|
});
|
|
21638
21698
|
const aggressiveOn = policy?.aggressiveOn ?? this.aggressiveOn;
|
|
21639
21699
|
const targetLoad = normalizeTargetLoad(policy?.targetLoad, adaptiveThresholds);
|
|
21640
|
-
let load = calibratedLoad;
|
|
21641
|
-
|
|
21642
|
-
const guardTotal = estimateRequestTokensUpperBound(
|
|
21643
|
-
ctx.messages,
|
|
21644
|
-
ctx.systemPrompt,
|
|
21645
|
-
ctx.tools ?? [],
|
|
21646
|
-
`${ctx.provider?.id ?? "unknown"}/${ctx.model}`
|
|
21647
|
-
).total;
|
|
21648
|
-
const guardLoad = guardTotal / budget.availableInputTokens;
|
|
21649
|
-
if (guardLoad > load) load = guardLoad;
|
|
21650
|
-
}
|
|
21651
|
-
const level = load >= adaptiveThresholds.hard ? "hard" : load >= adaptiveThresholds.soft ? "soft" : load >= adaptiveThresholds.warn ? "warn" : null;
|
|
21700
|
+
let load = this.applySendGuard(ctx, calibratedLoad, budget.availableInputTokens);
|
|
21701
|
+
let level = pressureLevelFor(load, adaptiveThresholds);
|
|
21652
21702
|
if (!level) {
|
|
21653
21703
|
this.lastNoopAttempt = null;
|
|
21654
21704
|
return next(ctx);
|
|
21655
21705
|
}
|
|
21706
|
+
if (this.shouldRunHygiene(level, tokens, budget.availableInputTokens)) {
|
|
21707
|
+
const changed = this.runHistoryHygiene(ctx);
|
|
21708
|
+
tokens = changed ? this.estimateContextTokens(ctx) : tokens;
|
|
21709
|
+
this.lastHygieneTokens = tokens;
|
|
21710
|
+
if (changed) {
|
|
21711
|
+
budget = computeContextWindowBudget(ctx, tokens, runtimeMaxContext);
|
|
21712
|
+
load = this.applySendGuard(ctx, budget.load, budget.availableInputTokens);
|
|
21713
|
+
const relevelled = pressureLevelFor(load, adaptiveThresholds);
|
|
21714
|
+
if (!relevelled) {
|
|
21715
|
+
this.lastNoopAttempt = null;
|
|
21716
|
+
return next(ctx);
|
|
21717
|
+
}
|
|
21718
|
+
level = relevelled;
|
|
21719
|
+
}
|
|
21720
|
+
}
|
|
21656
21721
|
if (this.shouldSkipNoopRetry(level, tokens)) {
|
|
21657
21722
|
return next(ctx);
|
|
21658
21723
|
}
|
|
@@ -21669,6 +21734,123 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
21669
21734
|
return next(ctx);
|
|
21670
21735
|
};
|
|
21671
21736
|
}
|
|
21737
|
+
/**
|
|
21738
|
+
* Full-request token total for the current context.
|
|
21739
|
+
*
|
|
21740
|
+
* Reuses the last estimate when the context hasn't grown since the previous
|
|
21741
|
+
* check — common in autonomous idle loops. The cached value is invalidated
|
|
21742
|
+
* whenever messages or tools change.
|
|
21743
|
+
*
|
|
21744
|
+
* IMPORTANT: the cache is only valid for the deterministic
|
|
21745
|
+
* `estimateRequestTokensCalibrated` path (messages+system+tools → fixed
|
|
21746
|
+
* output). When a custom `_estimator` is provided (e.g. in tests with a
|
|
21747
|
+
* mutable closure, or a dynamic policy provider), always call it fresh — the
|
|
21748
|
+
* estimator owns its own semantics and the middleware cannot safely cache its
|
|
21749
|
+
* result across calls.
|
|
21750
|
+
*/
|
|
21751
|
+
estimateContextTokens(ctx) {
|
|
21752
|
+
const msgCount = ctx.messages.length;
|
|
21753
|
+
const toolCount = (ctx.tools ?? []).length;
|
|
21754
|
+
const revision = ctx.state?.revision ?? -1;
|
|
21755
|
+
const anchorAt = typeof ctx.meta?.["realAnchorMsgCount"] === "number" ? ctx.meta["realAnchorMsgCount"] : void 0;
|
|
21756
|
+
const anchored = realAnchoredInputTokens(ctx.messages, ctx.lastRealInputTokens, anchorAt);
|
|
21757
|
+
if (anchored !== null) return anchored;
|
|
21758
|
+
if (this._estimator) return this._estimator(ctx);
|
|
21759
|
+
if (msgCount === this._cachedMsgCount && toolCount === this._cachedToolCount && revision === this._cachedRevision && ctx.systemPrompt === this._cachedSystemRef && ctx.tools === this._cachedToolsRef && this._cachedTokens >= 0) {
|
|
21760
|
+
return this._cachedTokens;
|
|
21761
|
+
}
|
|
21762
|
+
const stashed = this.tryStashedTokens(ctx, msgCount, toolCount, revision);
|
|
21763
|
+
let tokens;
|
|
21764
|
+
if (stashed !== null) {
|
|
21765
|
+
const cal = getCalibrationState(`${ctx.provider?.id ?? "unknown"}/${ctx.model}`);
|
|
21766
|
+
tokens = cal.calibrated ? Math.round(stashed * Math.min(1.5, Math.max(0.5, cal.ratio))) : stashed;
|
|
21767
|
+
} else {
|
|
21768
|
+
tokens = estimateRequestTokensCalibrated(
|
|
21769
|
+
ctx.messages,
|
|
21770
|
+
ctx.systemPrompt,
|
|
21771
|
+
ctx.tools ?? [],
|
|
21772
|
+
`${ctx.provider?.id ?? "unknown"}/${ctx.model}`
|
|
21773
|
+
).total;
|
|
21774
|
+
}
|
|
21775
|
+
this._cachedTokens = tokens;
|
|
21776
|
+
this._cachedMsgCount = msgCount;
|
|
21777
|
+
this._cachedToolCount = toolCount;
|
|
21778
|
+
this._cachedRevision = revision;
|
|
21779
|
+
this._cachedSystemRef = ctx.systemPrompt;
|
|
21780
|
+
this._cachedToolsRef = ctx.tools;
|
|
21781
|
+
return tokens;
|
|
21782
|
+
}
|
|
21783
|
+
/**
|
|
21784
|
+
* Never-undercount send guard.
|
|
21785
|
+
*
|
|
21786
|
+
* The calibrated estimate can under-count dense content (CJK, base64,
|
|
21787
|
+
* minified) by >1.5×, which would let an over-limit request slip past the
|
|
21788
|
+
* thresholds and reach the provider. Once the calibrated load is high enough
|
|
21789
|
+
* that even the max density factor (2.5×) *could* overflow (load ≥ 1/2.5 =
|
|
21790
|
+
* 0.4), re-check with the upper-bound estimator and escalate to whichever
|
|
21791
|
+
* load is larger. Below 0.4 an overflow is arithmetically impossible, so the
|
|
21792
|
+
* extra scan is skipped.
|
|
21793
|
+
*/
|
|
21794
|
+
applySendGuard(ctx, calibratedLoad, availableInputTokens) {
|
|
21795
|
+
if (calibratedLoad < _AutoCompactionMiddleware.GUARD_GATE_LOAD) return calibratedLoad;
|
|
21796
|
+
const guardTotal = estimateRequestTokensUpperBound(
|
|
21797
|
+
ctx.messages,
|
|
21798
|
+
ctx.systemPrompt,
|
|
21799
|
+
ctx.tools ?? [],
|
|
21800
|
+
`${ctx.provider?.id ?? "unknown"}/${ctx.model}`
|
|
21801
|
+
).total;
|
|
21802
|
+
const guardLoad = guardTotal / availableInputTokens;
|
|
21803
|
+
return guardLoad > calibratedLoad ? guardLoad : calibratedLoad;
|
|
21804
|
+
}
|
|
21805
|
+
/**
|
|
21806
|
+
* Rewrite acknowledged tool protocol in place.
|
|
21807
|
+
*
|
|
21808
|
+
* Tool results are protocol inputs for the immediately following model
|
|
21809
|
+
* response, not unlimited durable prompt memory. Once a later assistant
|
|
21810
|
+
* message proves the provider consumed them, keep a mode-sized raw window and
|
|
21811
|
+
* replace the rest with semantic head/tail receipts, then fold fully-aged
|
|
21812
|
+
* pairs into the history digest.
|
|
21813
|
+
*
|
|
21814
|
+
* Every one of those edits touches a message the provider has already seen,
|
|
21815
|
+
* so each call costs a full prompt re-cache — it belongs behind
|
|
21816
|
+
* {@link shouldRunHygiene}, never on the per-turn path. Content staleness is
|
|
21817
|
+
* not a reason to bypass that gate: the edit/replace tools guard against
|
|
21818
|
+
* acting on an out-of-date read with their own mtime + sha-256 check.
|
|
21819
|
+
*
|
|
21820
|
+
* @returns whether the conversation was rewritten.
|
|
21821
|
+
*/
|
|
21822
|
+
runHistoryHygiene(ctx) {
|
|
21823
|
+
const raw = eliseAcknowledgedToolResults(ctx.messages, {
|
|
21824
|
+
maxRetainedTokens: this.resolveToolResultRetention(ctx)
|
|
21825
|
+
});
|
|
21826
|
+
const receipts = collapseAcknowledgedToolReceipts(raw.messages, {
|
|
21827
|
+
maxPairs: this.resolveToolReceiptRetention(ctx)
|
|
21828
|
+
});
|
|
21829
|
+
if (!raw.changed && !receipts.changed) return false;
|
|
21830
|
+
ctx.state.replaceMessages(receipts.messages);
|
|
21831
|
+
ctx.clearFileTracking();
|
|
21832
|
+
this.invalidateTokenCaches(ctx);
|
|
21833
|
+
return true;
|
|
21834
|
+
}
|
|
21835
|
+
/**
|
|
21836
|
+
* Whether the history-rewriting hygiene pass may run this turn.
|
|
21837
|
+
*
|
|
21838
|
+
* Hard pressure always runs it — staying under the window outranks caching.
|
|
21839
|
+
* Otherwise it runs at most once per growth interval, so the conversation
|
|
21840
|
+
* stays append-only (and therefore cacheable by the provider) in between.
|
|
21841
|
+
*/
|
|
21842
|
+
shouldRunHygiene(level, tokens, availableInputTokens) {
|
|
21843
|
+
if (level === "hard") return true;
|
|
21844
|
+
const last = this.lastHygieneTokens;
|
|
21845
|
+
if (last === null) return true;
|
|
21846
|
+
const anchor = Math.min(last, tokens);
|
|
21847
|
+
this.lastHygieneTokens = anchor;
|
|
21848
|
+
const interval = Math.max(
|
|
21849
|
+
_AutoCompactionMiddleware.HYGIENE_MIN_GROWTH_TOKENS,
|
|
21850
|
+
Math.floor(availableInputTokens * _AutoCompactionMiddleware.HYGIENE_GROWTH_RATIO)
|
|
21851
|
+
);
|
|
21852
|
+
return tokens - anchor >= interval;
|
|
21853
|
+
}
|
|
21672
21854
|
/**
|
|
21673
21855
|
* H1: try to read a pre-computed token total from `ctx.lastRequestTokens`
|
|
21674
21856
|
* (set by the agent loop's pre-flight or its restash in emitContextPct).
|
|
@@ -26765,26 +26947,29 @@ var DefaultProviderRunner = class {
|
|
|
26765
26947
|
// src/execution/retry-policy.ts
|
|
26766
26948
|
import { randomInt } from "node:crypto";
|
|
26767
26949
|
var MAX_RETRY_AFTER_MS = 6e4;
|
|
26950
|
+
var MODEL_RETRIES = 3;
|
|
26768
26951
|
var MAX_ATTEMPTS_BY_KIND = {
|
|
26769
|
-
rate_limit:
|
|
26952
|
+
rate_limit: MODEL_RETRIES,
|
|
26953
|
+
overloaded: MODEL_RETRIES,
|
|
26954
|
+
server: MODEL_RETRIES,
|
|
26955
|
+
timeout: MODEL_RETRIES,
|
|
26956
|
+
network: MODEL_RETRIES,
|
|
26957
|
+
stream_hang: MODEL_RETRIES,
|
|
26770
26958
|
quota_exhausted: 0,
|
|
26771
|
-
stream_hang: 2,
|
|
26772
|
-
// proxy-level timeout — retrying 5x wastes ~40s before fallback kicks in
|
|
26773
|
-
overloaded: 3,
|
|
26774
|
-
server: 3,
|
|
26775
|
-
timeout: 2,
|
|
26776
|
-
network: 2,
|
|
26777
26959
|
auth: 0,
|
|
26778
26960
|
invalid_request: 0,
|
|
26779
26961
|
context_overflow: 0,
|
|
26780
26962
|
content_filter: 0,
|
|
26781
26963
|
unknown: 0
|
|
26782
26964
|
};
|
|
26965
|
+
var FAILOVER_RETRY_AFTER_MS = 15e3;
|
|
26783
26966
|
var DefaultRetryPolicy = class {
|
|
26784
26967
|
shouldRetry(err, attempt) {
|
|
26785
26968
|
const isProviderErr = err instanceof ProviderError || ProviderError.isProviderError(err);
|
|
26786
26969
|
if (isProviderErr) {
|
|
26787
26970
|
if (!err.retryable) return false;
|
|
26971
|
+
const hint = retryAfterMsFromError(err);
|
|
26972
|
+
if (hint !== void 0 && hint >= FAILOVER_RETRY_AFTER_MS) return false;
|
|
26788
26973
|
return attempt < this.maxAttempts(err);
|
|
26789
26974
|
}
|
|
26790
26975
|
const msg = err.message ?? "";
|
|
@@ -28244,7 +28429,8 @@ async function evaluateToolKanbanBoundary(tool, input, ctx, options = {}) {
|
|
|
28244
28429
|
decision: "block",
|
|
28245
28430
|
reason: "Active card is not implementation-ready: " + readiness.issues.map((issue) => issue.message).join(" | "),
|
|
28246
28431
|
boardId: board.id,
|
|
28247
|
-
taskId: task.id
|
|
28432
|
+
taskId: task.id,
|
|
28433
|
+
readinessIssues: readiness.issues
|
|
28248
28434
|
};
|
|
28249
28435
|
}
|
|
28250
28436
|
if (task.lifecycle?.currentStage !== "running" || task.assignment?.status !== "running") {
|
|
@@ -29035,7 +29221,8 @@ ${errorDetails}`,
|
|
|
29035
29221
|
type: "tool_result",
|
|
29036
29222
|
tool_use_id: use.id,
|
|
29037
29223
|
content: `Tool "${tool.name}" blocked by Kanban boundary. ${boundary.reason ?? ""}`.trim(),
|
|
29038
|
-
is_error: true
|
|
29224
|
+
is_error: true,
|
|
29225
|
+
_kanbanBoundary: boundary
|
|
29039
29226
|
};
|
|
29040
29227
|
budget = this.budgetForString(result.content, budget);
|
|
29041
29228
|
return { result, tool, durationMs: Date.now() - start };
|
|
@@ -32070,7 +32257,7 @@ var AutoApprovePermissionPolicy = class _AutoApprovePermissionPolicy {
|
|
|
32070
32257
|
};
|
|
32071
32258
|
|
|
32072
32259
|
// src/security/permission-helpers.ts
|
|
32073
|
-
import { realpathSync } from "node:fs";
|
|
32260
|
+
import { realpathSync as realpathSync2 } from "node:fs";
|
|
32074
32261
|
import * as path38 from "node:path";
|
|
32075
32262
|
function matchesTrust(patterns, subject) {
|
|
32076
32263
|
return patterns.includes(subject) || matchAny(patterns, subject);
|
|
@@ -32155,7 +32342,7 @@ function realpathOfNearestExisting(p) {
|
|
|
32155
32342
|
const tail = [];
|
|
32156
32343
|
for (; ; ) {
|
|
32157
32344
|
try {
|
|
32158
|
-
return tail.length === 0 ?
|
|
32345
|
+
return tail.length === 0 ? realpathSync2(probe) : path38.join(realpathSync2(probe), ...tail);
|
|
32159
32346
|
} catch {
|
|
32160
32347
|
const parent = path38.dirname(probe);
|
|
32161
32348
|
if (parent === probe) return p;
|
|
@@ -33918,6 +34105,10 @@ var IN_PROJECT_ALLOWED_KEYS = /* @__PURE__ */ new Set([
|
|
|
33918
34105
|
"fallbackModels",
|
|
33919
34106
|
"fallbackBridge",
|
|
33920
34107
|
"fallbackProfiles",
|
|
34108
|
+
// The profile SELECTOR. No broader than its siblings: a repo that can write
|
|
34109
|
+
// `fallbackModels` and `fallbackProfiles` already controls the chain outright,
|
|
34110
|
+
// and this one can only name a profile the user already defined.
|
|
34111
|
+
"fallbackProfile",
|
|
33921
34112
|
"favoriteModels",
|
|
33922
34113
|
"favoriteModelsOnly",
|
|
33923
34114
|
"modelAvailabilitySchedule",
|
|
@@ -33965,6 +34156,10 @@ var KNOWN_DENIED_IN_PROJECT = [
|
|
|
33965
34156
|
{
|
|
33966
34157
|
key: "git",
|
|
33967
34158
|
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)."
|
|
34159
|
+
},
|
|
34160
|
+
{
|
|
34161
|
+
key: "fallbackMaxLastResortCandidates",
|
|
34162
|
+
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."
|
|
33968
34163
|
}
|
|
33969
34164
|
];
|
|
33970
34165
|
var KNOWN_CONFIG_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -33985,11 +34180,13 @@ var KNOWN_CONFIG_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
|
33985
34180
|
"fallbackModels",
|
|
33986
34181
|
"fallbackBridge",
|
|
33987
34182
|
"fallbackProfiles",
|
|
34183
|
+
"fallbackProfile",
|
|
33988
34184
|
"favoriteModels",
|
|
33989
34185
|
"favoriteModelsOnly",
|
|
33990
34186
|
"modelAvailabilitySchedule",
|
|
33991
34187
|
"fallbackAuto",
|
|
33992
34188
|
"fallbackStickiness",
|
|
34189
|
+
"fallbackMaxLastResortCandidates",
|
|
33993
34190
|
"hooks",
|
|
33994
34191
|
"plugins",
|
|
33995
34192
|
"pluginManager",
|
|
@@ -34075,6 +34272,17 @@ var IN_PROJECT_DENIED_PATHS = [
|
|
|
34075
34272
|
// operator owns, not the checked-out repository.
|
|
34076
34273
|
path: "tools.kanbanGovernance",
|
|
34077
34274
|
reason: "Repo-committed config could disable a Kanban governance gate the operator switched on, letting product mutations run outside any managed card."
|
|
34275
|
+
},
|
|
34276
|
+
{
|
|
34277
|
+
// The bridge spawn path resolves the CLI entry by walking UP from the
|
|
34278
|
+
// project root, so a repo that ships its own `packages/cli/dist/index.js`
|
|
34279
|
+
// gets that file spawned with `process.execPath` on WebUI boot — no
|
|
34280
|
+
// prompt, no banner. Turning the feature on is therefore equivalent to
|
|
34281
|
+
// arbitrary code execution for a hostile checkout, which makes this an
|
|
34282
|
+
// operator-owned switch and never a repo-owned one.
|
|
34283
|
+
// See discover-mailbox-bridge.ts:findWorkspaceCliEntry.
|
|
34284
|
+
path: "features.mailboxBridge",
|
|
34285
|
+
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."
|
|
34078
34286
|
}
|
|
34079
34287
|
];
|
|
34080
34288
|
function deleteNestedPath(target, path45) {
|