@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
|
@@ -8328,6 +8328,24 @@ import { dirname as dirname5 } from "node:path";
|
|
|
8328
8328
|
import { homedir as homedir2 } from "node:os";
|
|
8329
8329
|
|
|
8330
8330
|
// src/security/secret-scrubber.ts
|
|
8331
|
+
var JSON_CREDENTIAL_KEY_ANCHORS = [
|
|
8332
|
+
'Key"',
|
|
8333
|
+
'key"',
|
|
8334
|
+
'KEY"',
|
|
8335
|
+
'token"',
|
|
8336
|
+
'Token"',
|
|
8337
|
+
'TOKEN"',
|
|
8338
|
+
'secret"',
|
|
8339
|
+
'Secret"',
|
|
8340
|
+
'SECRET"',
|
|
8341
|
+
'password"',
|
|
8342
|
+
'Password"',
|
|
8343
|
+
'PASSWORD"',
|
|
8344
|
+
'authorization"',
|
|
8345
|
+
'Authorization"',
|
|
8346
|
+
'bearer"',
|
|
8347
|
+
'Bearer"'
|
|
8348
|
+
];
|
|
8331
8349
|
var PATTERNS = [
|
|
8332
8350
|
// Anchored at the start where possible so partial matches inside larger
|
|
8333
8351
|
// strings don't trigger false positives.
|
|
@@ -8430,6 +8448,30 @@ var PATTERNS = [
|
|
|
8430
8448
|
regex: /(^|\s)([A-Z_]{4,}(?:KEY|TOKEN|SECRET|PASSWORD|PWD))\s*[:=]\s*['"]?([A-Za-z0-9_/+=-]{20,512})['"]?(?=\s|$)/g,
|
|
8431
8449
|
anchor: ["KEY", "TOKEN", "SECRET", "PASSWORD", "PWD"]
|
|
8432
8450
|
},
|
|
8451
|
+
{
|
|
8452
|
+
type: "json_credential_key",
|
|
8453
|
+
// The JSON counterpart to `high_entropy_env`, and the pattern that the
|
|
8454
|
+
// now-deleted `JSON_KEY_ANCHORS` list was written for. Without it those
|
|
8455
|
+
// anchors only widened the cheap pre-scan — `hasCredentialAnchors` said
|
|
8456
|
+
// "this text may hold a secret", every pattern then declined to match, and
|
|
8457
|
+
// the value went out verbatim. `high_entropy_env` cannot cover these: it
|
|
8458
|
+
// requires an UPPERCASE unquoted key (`API_KEY=…`), so `{"apiKey":"…"}`
|
|
8459
|
+
// never matched.
|
|
8460
|
+
//
|
|
8461
|
+
// Tool results are routinely serialised as JSON, and a credential with no
|
|
8462
|
+
// recognisable prefix (Azure, self-hosted gateways, Anthropic/Codex OAuth)
|
|
8463
|
+
// has no other pattern that can catch it — this is the only thing standing
|
|
8464
|
+
// between such a value and the session JSONL, chronicle, HQ broadcast and
|
|
8465
|
+
// the model's own context.
|
|
8466
|
+
//
|
|
8467
|
+
// The key may carry a prefix (`"anthropicApiKey"`), but the credential word
|
|
8468
|
+
// must END the key: `"tokenCount"` and `"maxTokens"` do not match, because
|
|
8469
|
+
// the closing quote has to follow the word immediately.
|
|
8470
|
+
// Value floor of 8 chars keeps enum-ish values (`"authorization":"none"`)
|
|
8471
|
+
// out. Capture groups: 1=key + punctuation, 2=value, 3=closing quote.
|
|
8472
|
+
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,
|
|
8473
|
+
anchor: JSON_CREDENTIAL_KEY_ANCHORS
|
|
8474
|
+
},
|
|
8433
8475
|
// ── Ported from packages/plugins credential-patterns.ts (WS-034) ─────────
|
|
8434
8476
|
// The plugin runtime carried 37 patterns while this scrubber — the one that
|
|
8435
8477
|
// guards session JSONL, chronicle, HQ broadcast, WebUI events and the auth
|
|
@@ -8512,9 +8554,12 @@ var PATTERNS = [
|
|
|
8512
8554
|
anchor: "GOCSPX-"
|
|
8513
8555
|
}
|
|
8514
8556
|
];
|
|
8515
|
-
var SIMPLE_PATTERNS = PATTERNS.filter(
|
|
8557
|
+
var SIMPLE_PATTERNS = PATTERNS.filter(
|
|
8558
|
+
(p) => p.type !== "high_entropy_env" && p.type !== "json_credential_key"
|
|
8559
|
+
);
|
|
8516
8560
|
var COMBINED_REGEX = new RegExp(SIMPLE_PATTERNS.map((p) => `(${p.regex.source})`).join("|"), "g");
|
|
8517
8561
|
var HIGH_ENTROPY_REGEX = PATTERNS.find((p) => p.type === "high_entropy_env").regex;
|
|
8562
|
+
var JSON_CREDENTIAL_REGEX = PATTERNS.find((p) => p.type === "json_credential_key").regex;
|
|
8518
8563
|
var COMBINED_REPLACEMENTS = SIMPLE_PATTERNS.map((p) => `[REDACTED:${p.type}]`);
|
|
8519
8564
|
var SCRUB_CHUNK_BYTES = 64 * 1024;
|
|
8520
8565
|
var SCRUB_OVERLAP_BYTES = 1024;
|
|
@@ -8525,20 +8570,7 @@ var PATTERN_ANCHORS = [
|
|
|
8525
8570
|
)
|
|
8526
8571
|
)
|
|
8527
8572
|
];
|
|
8528
|
-
var
|
|
8529
|
-
'"apiKey"',
|
|
8530
|
-
'"api_key"',
|
|
8531
|
-
'"token"',
|
|
8532
|
-
'"secret"',
|
|
8533
|
-
'"password"',
|
|
8534
|
-
'"authorization"',
|
|
8535
|
-
'"bearer"',
|
|
8536
|
-
'"private_key"',
|
|
8537
|
-
'"access_token"',
|
|
8538
|
-
'"refresh_token"',
|
|
8539
|
-
'"client_secret"'
|
|
8540
|
-
];
|
|
8541
|
-
var ALL_ANCHORS = [...PATTERN_ANCHORS, ...JSON_KEY_ANCHORS];
|
|
8573
|
+
var ALL_ANCHORS = PATTERN_ANCHORS;
|
|
8542
8574
|
function hasCredentialAnchors(text) {
|
|
8543
8575
|
for (const anchor of ALL_ANCHORS) {
|
|
8544
8576
|
if (text.includes(anchor)) return true;
|
|
@@ -8587,6 +8619,9 @@ var DefaultSecretScrubber = class {
|
|
|
8587
8619
|
out = out.replace(HIGH_ENTROPY_REGEX, (_match, lead, key, _value) => {
|
|
8588
8620
|
return `${lead}${key}=[REDACTED:high_entropy_env]`;
|
|
8589
8621
|
});
|
|
8622
|
+
out = out.replace(JSON_CREDENTIAL_REGEX, (_match, keyPrefix, _value, closingQuote) => {
|
|
8623
|
+
return `${keyPrefix}[REDACTED:json_credential_key]${closingQuote}`;
|
|
8624
|
+
});
|
|
8590
8625
|
return out;
|
|
8591
8626
|
}
|
|
8592
8627
|
/**
|
|
@@ -14584,6 +14619,44 @@ function userInputTitle(content) {
|
|
|
14584
14619
|
return sessionContentPreview(content, 60);
|
|
14585
14620
|
}
|
|
14586
14621
|
|
|
14622
|
+
// src/storage/session-writer-scrubber.ts
|
|
14623
|
+
function scrubSessionWriterEvent(event, secretScrubber) {
|
|
14624
|
+
const persistMessage = (message) => {
|
|
14625
|
+
const { _estTokens: _ignored, ...persisted } = message;
|
|
14626
|
+
return {
|
|
14627
|
+
...persisted,
|
|
14628
|
+
content: typeof persisted.content === "string" ? secretScrubber?.scrub(persisted.content) ?? persisted.content : secretScrubber?.scrubObject(persisted.content) ?? persisted.content
|
|
14629
|
+
};
|
|
14630
|
+
};
|
|
14631
|
+
if (event.type === "context_snapshot" || event.type === "messages_replaced") {
|
|
14632
|
+
return { ...event, messages: event.messages.map(persistMessage) };
|
|
14633
|
+
}
|
|
14634
|
+
if (event.type === "message_appended" || event.type === "message_updated") {
|
|
14635
|
+
return { ...event, message: persistMessage(event.message) };
|
|
14636
|
+
}
|
|
14637
|
+
if (!secretScrubber) return event;
|
|
14638
|
+
if (event.type === "user_input") {
|
|
14639
|
+
return {
|
|
14640
|
+
...event,
|
|
14641
|
+
content: typeof event.content === "string" ? secretScrubber.scrub(event.content) : secretScrubber.scrubObject(event.content)
|
|
14642
|
+
};
|
|
14643
|
+
}
|
|
14644
|
+
if (event.type === "llm_response") {
|
|
14645
|
+
return { ...event, content: secretScrubber.scrubObject(event.content) };
|
|
14646
|
+
}
|
|
14647
|
+
if (event.type === "file_snapshot") {
|
|
14648
|
+
return {
|
|
14649
|
+
...event,
|
|
14650
|
+
files: event.files.map((f) => ({
|
|
14651
|
+
...f,
|
|
14652
|
+
before: f.before !== null ? secretScrubber.scrub(f.before) : null,
|
|
14653
|
+
after: f.after !== null ? secretScrubber.scrub(f.after) : null
|
|
14654
|
+
}))
|
|
14655
|
+
};
|
|
14656
|
+
}
|
|
14657
|
+
return event;
|
|
14658
|
+
}
|
|
14659
|
+
|
|
14587
14660
|
// src/storage/session-writer-truncate.ts
|
|
14588
14661
|
import * as fsp6 from "node:fs/promises";
|
|
14589
14662
|
var CHUNK_SIZE = 65536;
|
|
@@ -14728,45 +14801,11 @@ async function rewriteSessionToCheckpoint(filePath, checkpointByteOffset) {
|
|
|
14728
14801
|
}
|
|
14729
14802
|
}
|
|
14730
14803
|
|
|
14731
|
-
// src/storage/session-writer-scrubber.ts
|
|
14732
|
-
function scrubSessionWriterEvent(event, secretScrubber) {
|
|
14733
|
-
const persistMessage = (message) => {
|
|
14734
|
-
const { _estTokens: _ignored, ...persisted } = message;
|
|
14735
|
-
return {
|
|
14736
|
-
...persisted,
|
|
14737
|
-
content: typeof persisted.content === "string" ? secretScrubber?.scrub(persisted.content) ?? persisted.content : secretScrubber?.scrubObject(persisted.content) ?? persisted.content
|
|
14738
|
-
};
|
|
14739
|
-
};
|
|
14740
|
-
if (event.type === "context_snapshot" || event.type === "messages_replaced") {
|
|
14741
|
-
return { ...event, messages: event.messages.map(persistMessage) };
|
|
14742
|
-
}
|
|
14743
|
-
if (event.type === "message_appended" || event.type === "message_updated") {
|
|
14744
|
-
return { ...event, message: persistMessage(event.message) };
|
|
14745
|
-
}
|
|
14746
|
-
if (!secretScrubber) return event;
|
|
14747
|
-
if (event.type === "user_input") {
|
|
14748
|
-
return {
|
|
14749
|
-
...event,
|
|
14750
|
-
content: typeof event.content === "string" ? secretScrubber.scrub(event.content) : secretScrubber.scrubObject(event.content)
|
|
14751
|
-
};
|
|
14752
|
-
}
|
|
14753
|
-
if (event.type === "llm_response") {
|
|
14754
|
-
return { ...event, content: secretScrubber.scrubObject(event.content) };
|
|
14755
|
-
}
|
|
14756
|
-
if (event.type === "file_snapshot") {
|
|
14757
|
-
return {
|
|
14758
|
-
...event,
|
|
14759
|
-
files: event.files.map((f) => ({
|
|
14760
|
-
...f,
|
|
14761
|
-
before: f.before !== null ? secretScrubber.scrub(f.before) : null,
|
|
14762
|
-
after: f.after !== null ? secretScrubber.scrub(f.after) : null
|
|
14763
|
-
}))
|
|
14764
|
-
};
|
|
14765
|
-
}
|
|
14766
|
-
return event;
|
|
14767
|
-
}
|
|
14768
|
-
|
|
14769
14804
|
// src/storage/file-session-writer.ts
|
|
14805
|
+
function isClosedHandleError(err) {
|
|
14806
|
+
const code = err?.code;
|
|
14807
|
+
return code === "EBADF" || code === "ERR_CLOSED_RESOURCE" || code === "ERR_INVALID_HANDLE";
|
|
14808
|
+
}
|
|
14770
14809
|
var FileSessionWriter = class _FileSessionWriter {
|
|
14771
14810
|
constructor(id, handle, startedAt, meta, events, opts = {}, traceId) {
|
|
14772
14811
|
this.id = id;
|
|
@@ -14938,8 +14977,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
14938
14977
|
try {
|
|
14939
14978
|
return await this.handle.appendFile(data, "utf8");
|
|
14940
14979
|
} catch (err) {
|
|
14941
|
-
|
|
14942
|
-
if (nodeErr?.code === "EBADF") {
|
|
14980
|
+
if (isClosedHandleError(err)) {
|
|
14943
14981
|
this.handle = await fsp7.open(this.filePath, "a", 384);
|
|
14944
14982
|
return await this.handle.appendFile(data, "utf8");
|
|
14945
14983
|
}
|
|
@@ -14979,8 +15017,8 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
14979
15017
|
bufferSynchronousEvent(event) {
|
|
14980
15018
|
if (this.closed) return;
|
|
14981
15019
|
void this.ensureInit();
|
|
14982
|
-
this.
|
|
14983
|
-
|
|
15020
|
+
const appendEvent = scrubSessionWriterEvent(event, this.secretScrubber);
|
|
15021
|
+
this.observeForSummary(appendEvent);
|
|
14984
15022
|
try {
|
|
14985
15023
|
this._onAppend?.(appendEvent);
|
|
14986
15024
|
} catch {
|
|
@@ -15133,8 +15171,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
15133
15171
|
try {
|
|
15134
15172
|
await this.handle.datasync();
|
|
15135
15173
|
} catch (err) {
|
|
15136
|
-
|
|
15137
|
-
if (nodeErr?.code === "EBADF") {
|
|
15174
|
+
if (isClosedHandleError(err)) {
|
|
15138
15175
|
this.handle = await fsp7.open(this.filePath, "a", 384);
|
|
15139
15176
|
return;
|
|
15140
15177
|
}
|
|
@@ -15371,6 +15408,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
15371
15408
|
return this.closePromise;
|
|
15372
15409
|
}
|
|
15373
15410
|
async doClose() {
|
|
15411
|
+
await this.ensureInit();
|
|
15374
15412
|
if (this.pendingFileSnapshots.length > 0) {
|
|
15375
15413
|
await this.writeFileSnapshot(this.activePromptIndex ?? 0, [...this.pendingFileSnapshots]);
|
|
15376
15414
|
this.pendingFileSnapshots = [];
|
|
@@ -15386,8 +15424,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
15386
15424
|
try {
|
|
15387
15425
|
await this.handle.datasync();
|
|
15388
15426
|
} catch (err) {
|
|
15389
|
-
|
|
15390
|
-
if (nodeErr?.code !== "EBADF") throw err;
|
|
15427
|
+
if (!isClosedHandleError(err)) throw err;
|
|
15391
15428
|
}
|
|
15392
15429
|
const endedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
15393
15430
|
const observedActivityMs = Date.parse(this.lastActivityAt);
|
|
@@ -17100,9 +17137,40 @@ async function readOrBuildShardManifestEntry(opts) {
|
|
|
17100
17137
|
return entry;
|
|
17101
17138
|
}
|
|
17102
17139
|
|
|
17103
|
-
// src/storage/session-store/
|
|
17140
|
+
// src/storage/session-store/strict-empty-check.ts
|
|
17104
17141
|
import { createReadStream as createReadStream4 } from "node:fs";
|
|
17105
17142
|
import { createInterface as createInterface4 } from "node:readline";
|
|
17143
|
+
var EMPTY_SESSION_EVENT_TYPES = /* @__PURE__ */ new Set(["session_start", "session_resumed", "session_end"]);
|
|
17144
|
+
async function isStrictlyEmptySessionFile(file) {
|
|
17145
|
+
const input = createReadStream4(file, { encoding: "utf8" });
|
|
17146
|
+
const lines = createInterface4({ input, crlfDelay: Infinity });
|
|
17147
|
+
let sawSessionStart = false;
|
|
17148
|
+
try {
|
|
17149
|
+
for await (const line of lines) {
|
|
17150
|
+
if (!line.trim()) continue;
|
|
17151
|
+
let event;
|
|
17152
|
+
try {
|
|
17153
|
+
event = JSON.parse(line);
|
|
17154
|
+
} catch {
|
|
17155
|
+
return false;
|
|
17156
|
+
}
|
|
17157
|
+
if (event === null || typeof event !== "object" || Array.isArray(event)) return false;
|
|
17158
|
+
const type = event.type;
|
|
17159
|
+
if (typeof type !== "string" || !EMPTY_SESSION_EVENT_TYPES.has(type)) return false;
|
|
17160
|
+
if (type === "session_start") sawSessionStart = true;
|
|
17161
|
+
}
|
|
17162
|
+
} catch {
|
|
17163
|
+
return false;
|
|
17164
|
+
} finally {
|
|
17165
|
+
lines.close();
|
|
17166
|
+
input.destroy();
|
|
17167
|
+
}
|
|
17168
|
+
return sawSessionStart;
|
|
17169
|
+
}
|
|
17170
|
+
|
|
17171
|
+
// src/storage/session-store/summary-builder.ts
|
|
17172
|
+
import { createReadStream as createReadStream5 } from "node:fs";
|
|
17173
|
+
import { createInterface as createInterface5 } from "node:readline";
|
|
17106
17174
|
async function summarizeSessionFile(opts) {
|
|
17107
17175
|
return summarizeSessionEventSequence({
|
|
17108
17176
|
id: opts.id,
|
|
@@ -17219,8 +17287,8 @@ async function summarizeSessionEventSequence(opts) {
|
|
|
17219
17287
|
}
|
|
17220
17288
|
}
|
|
17221
17289
|
async function* iterateSessionEvents(file, secretScrubber) {
|
|
17222
|
-
const stream =
|
|
17223
|
-
const lines =
|
|
17290
|
+
const stream = createReadStream5(file, { encoding: "utf8" });
|
|
17291
|
+
const lines = createInterface5({ input: stream, crlfDelay: Infinity });
|
|
17224
17292
|
try {
|
|
17225
17293
|
for await (const line of lines) {
|
|
17226
17294
|
if (!line.trim()) continue;
|
|
@@ -18116,6 +18184,10 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
18116
18184
|
await deleteSessionArtifacts({ rootDir: this.dir, id, jsonlPath });
|
|
18117
18185
|
await this.writeTombstone(id);
|
|
18118
18186
|
}
|
|
18187
|
+
async isEmpty(id) {
|
|
18188
|
+
const canonicalId = await this.resolveId(id);
|
|
18189
|
+
return isStrictlyEmptySessionFile(this.sessionPath(canonicalId, ".jsonl"));
|
|
18190
|
+
}
|
|
18119
18191
|
async delete(id) {
|
|
18120
18192
|
if (this.catalogClient) {
|
|
18121
18193
|
const canonical = await this.resolveId(id);
|
|
@@ -18679,6 +18751,12 @@ function parseModelRef(ref) {
|
|
|
18679
18751
|
function hasText(value) {
|
|
18680
18752
|
return typeof value === "string" && value.trim().length > 0;
|
|
18681
18753
|
}
|
|
18754
|
+
function asRefList(value) {
|
|
18755
|
+
return Array.isArray(value) ? value : void 0;
|
|
18756
|
+
}
|
|
18757
|
+
function asProfileName(value) {
|
|
18758
|
+
return hasText(value) ? value : void 0;
|
|
18759
|
+
}
|
|
18682
18760
|
function providerHasKey(entry) {
|
|
18683
18761
|
if (!entry) return false;
|
|
18684
18762
|
if (hasText(entry.apiKey)) return true;
|
|
@@ -18689,7 +18767,7 @@ function providerHasKey(entry) {
|
|
|
18689
18767
|
}
|
|
18690
18768
|
function visibleProviderModels(config, providerId, providerModels) {
|
|
18691
18769
|
const entry = config.providers?.[providerId];
|
|
18692
|
-
return entry?.models
|
|
18770
|
+
return Array.isArray(entry?.models) ? [...entry.models] : providerModels;
|
|
18693
18771
|
}
|
|
18694
18772
|
function buildProfiles(config) {
|
|
18695
18773
|
const entries = /* @__PURE__ */ new Map();
|
|
@@ -18726,13 +18804,34 @@ var FallbackProfileManager = class {
|
|
|
18726
18804
|
listProfiles() {
|
|
18727
18805
|
return Object.freeze([...this.profiles.keys()]);
|
|
18728
18806
|
}
|
|
18807
|
+
/**
|
|
18808
|
+
* The profile the session has selected (`config.fallbackProfile`, set by
|
|
18809
|
+
* `/fallback profile use <name>`), or undefined when none is selected or the
|
|
18810
|
+
* name no longer resolves to a defined profile.
|
|
18811
|
+
*
|
|
18812
|
+
* Consulted by every resolution entry point when the caller does not name a
|
|
18813
|
+
* profile itself. Without this the leader — which passes no profile — could
|
|
18814
|
+
* never use a named profile at all: `config.fallbackProfiles` was reachable
|
|
18815
|
+
* only by copying a chain into `fallbackModels`.
|
|
18816
|
+
*/
|
|
18817
|
+
activeProfileName() {
|
|
18818
|
+
const name = asProfileName(this.config.fallbackProfile);
|
|
18819
|
+
return name && this.profiles.has(name) ? name : void 0;
|
|
18820
|
+
}
|
|
18729
18821
|
// ── Resolution ─────────────────────────────────────────────────────────
|
|
18730
18822
|
/**
|
|
18731
18823
|
* Resolve a named fallback profile to a validated, provider-filtered chain.
|
|
18732
18824
|
*
|
|
18733
|
-
* Returns an empty chain when
|
|
18734
|
-
*
|
|
18735
|
-
*
|
|
18825
|
+
* Returns an empty chain when the profile doesn't exist, or when every entry
|
|
18826
|
+
* is excluded, quarantined, or blacked out.
|
|
18827
|
+
*
|
|
18828
|
+
* Filtering is intentionally identical to {@link resolveRefs} (the explicit
|
|
18829
|
+
* `fallbackModels` path): the self-exclusion, the runtime status tracker,
|
|
18830
|
+
* and the availability calendar — nothing else. Anything a named profile
|
|
18831
|
+
* drops, an explicit chain drops too, and vice versa. Profiles used to apply
|
|
18832
|
+
* two extra filters (provider "usability" and the `providers[].models`
|
|
18833
|
+
* snapshot) that the explicit path did not, which silently rerouted roles
|
|
18834
|
+
* pinned to a profile onto a different model than the one configured.
|
|
18736
18835
|
*
|
|
18737
18836
|
* @param name - Profile name from config.fallbackProfiles.
|
|
18738
18837
|
* @param defaultProvider - Used when an entry has no explicit provider.
|
|
@@ -18753,13 +18852,9 @@ var FallbackProfileManager = class {
|
|
|
18753
18852
|
if (seen.has(key)) continue;
|
|
18754
18853
|
seen.add(key);
|
|
18755
18854
|
if (excludeKey && key === excludeKey) continue;
|
|
18756
|
-
const health = this.checkProvider(providerId);
|
|
18757
|
-
if (!health.usable) continue;
|
|
18758
18855
|
if (this.statusTracker && !this.statusTracker.isAvailable(providerId, parsed.model)) continue;
|
|
18759
18856
|
if (!evaluateModelCalendar(this.config.modelAvailabilitySchedule, providerId, parsed.model).allowed)
|
|
18760
18857
|
continue;
|
|
18761
|
-
const allowedModels = this.config.providers?.[providerId]?.models;
|
|
18762
|
-
if (allowedModels && !allowedModels.includes(parsed.model)) continue;
|
|
18763
18858
|
resolved.push({
|
|
18764
18859
|
providerId,
|
|
18765
18860
|
model: parsed.model,
|
|
@@ -18777,12 +18872,14 @@ var FallbackProfileManager = class {
|
|
|
18777
18872
|
resolveEffective(opts = {}) {
|
|
18778
18873
|
const bridge = this.resolveBridge(opts.exclude);
|
|
18779
18874
|
let selected = FREEZER_EMPTY;
|
|
18780
|
-
|
|
18781
|
-
|
|
18875
|
+
const explicitRefs = asRefList(opts.fallbackModels);
|
|
18876
|
+
const profileName = asProfileName(opts.fallbackProfile) ?? this.activeProfileName();
|
|
18877
|
+
if (explicitRefs && explicitRefs.length > 0) {
|
|
18878
|
+
const resolved = this.resolveRefs(explicitRefs, opts.exclude);
|
|
18782
18879
|
if (resolved.length > 0) selected = resolved;
|
|
18783
18880
|
}
|
|
18784
|
-
if (selected.length === 0 &&
|
|
18785
|
-
const resolved = this.resolve(
|
|
18881
|
+
if (selected.length === 0 && profileName) {
|
|
18882
|
+
const resolved = this.resolve(profileName, { exclude: opts.exclude });
|
|
18786
18883
|
if (resolved.length > 0) selected = resolved;
|
|
18787
18884
|
}
|
|
18788
18885
|
if (selected.length === 0 && opts.fallbackAuto !== false) {
|
|
@@ -18858,13 +18955,14 @@ var FallbackProfileManager = class {
|
|
|
18858
18955
|
};
|
|
18859
18956
|
const configFallbackAuto = this.config.fallbackAuto;
|
|
18860
18957
|
const effectiveFallbackAuto = configFallbackAuto !== void 0 && configFallbackAuto !== null ? configFallbackAuto : !opts.closedWorld;
|
|
18861
|
-
const explicitRefs = opts.fallbackModels ?? this.config.fallbackModels;
|
|
18958
|
+
const explicitRefs = asRefList(opts.fallbackModels) ?? asRefList(this.config.fallbackModels);
|
|
18959
|
+
const profileName = asProfileName(opts.fallbackProfile) ?? this.activeProfileName();
|
|
18862
18960
|
const explicitUsable = explicitRefs !== void 0 && explicitRefs.length > 0 && this.resolveRefs(explicitRefs, current).length > 0;
|
|
18863
|
-
const profileUsable =
|
|
18961
|
+
const profileUsable = profileName !== void 0 && this.hasProfile(profileName) && this.resolve(profileName, { exclude: current }).length > 0;
|
|
18864
18962
|
const fromExplicitSource = explicitUsable || profileUsable;
|
|
18865
|
-
const selectedChain = opts.closedWorld ? explicitRefs && explicitRefs.length > 0 ? this.resolveRefs(explicitRefs, current) :
|
|
18963
|
+
const selectedChain = opts.closedWorld ? explicitRefs && explicitRefs.length > 0 ? this.resolveRefs(explicitRefs, current) : profileName ? this.resolve(profileName, { exclude: current }) : FREEZER_EMPTY : this.resolveEffective({
|
|
18866
18964
|
fallbackModels: explicitRefs,
|
|
18867
|
-
fallbackProfile:
|
|
18965
|
+
fallbackProfile: profileName,
|
|
18868
18966
|
fallbackAuto: effectiveFallbackAuto,
|
|
18869
18967
|
exclude: current
|
|
18870
18968
|
});
|
|
@@ -18881,7 +18979,7 @@ var FallbackProfileManager = class {
|
|
|
18881
18979
|
});
|
|
18882
18980
|
}
|
|
18883
18981
|
candidates.push(...selectedChain);
|
|
18884
|
-
if (!fromExplicitSource && effectiveFallbackAuto &&
|
|
18982
|
+
if (!fromExplicitSource && effectiveFallbackAuto && profileName !== "default") {
|
|
18885
18983
|
candidates.push(...this.resolve("default", { exclude: current }));
|
|
18886
18984
|
}
|
|
18887
18985
|
if (!fromExplicitSource && effectiveFallbackAuto) {
|
|
@@ -18959,7 +19057,7 @@ var FallbackProfileManager = class {
|
|
|
18959
19057
|
const leaderModel = this.config.model;
|
|
18960
19058
|
const providers = this.config.providers ?? {};
|
|
18961
19059
|
const favoriteSet = new Set(
|
|
18962
|
-
(this.config.favoriteModels ?? []).map((ref) => {
|
|
19060
|
+
(asRefList(this.config.favoriteModels) ?? []).map((ref) => {
|
|
18963
19061
|
const p = parseModelRef(ref);
|
|
18964
19062
|
return `${p.provider ?? leaderProvider}/${p.model}`;
|
|
18965
19063
|
})
|
|
@@ -27718,10 +27816,10 @@ var AdaptiveConcurrencyController = class {
|
|
|
27718
27816
|
};
|
|
27719
27817
|
|
|
27720
27818
|
// src/coordination/agent-monitor.ts
|
|
27721
|
-
import { createReadStream as
|
|
27819
|
+
import { createReadStream as createReadStream6 } from "node:fs";
|
|
27722
27820
|
import * as fs6 from "node:fs/promises";
|
|
27723
27821
|
import * as path38 from "node:path";
|
|
27724
|
-
import { createInterface as
|
|
27822
|
+
import { createInterface as createInterface6 } from "node:readline";
|
|
27725
27823
|
var AgentMonitorService = class _AgentMonitorService {
|
|
27726
27824
|
_fleetBus;
|
|
27727
27825
|
_events;
|
|
@@ -27828,8 +27926,8 @@ var AgentMonitorService = class _AgentMonitorService {
|
|
|
27828
27926
|
const accessible = await fs6.access(file).then(() => true).catch(() => false);
|
|
27829
27927
|
if (!accessible) return [];
|
|
27830
27928
|
const out = [];
|
|
27831
|
-
const input =
|
|
27832
|
-
const lines =
|
|
27929
|
+
const input = createReadStream6(file, { encoding: "utf8" });
|
|
27930
|
+
const lines = createInterface6({ input, crlfDelay: Number.POSITIVE_INFINITY });
|
|
27833
27931
|
try {
|
|
27834
27932
|
for await (const line of lines) {
|
|
27835
27933
|
const trimmed = line.trim();
|
|
@@ -75,12 +75,12 @@ export declare function makeMailSendTool(opts?: MailToolsOptions): {
|
|
|
75
75
|
required: string[];
|
|
76
76
|
};
|
|
77
77
|
execute(input: unknown, ctx: Context): Promise<{
|
|
78
|
+
summary?: never;
|
|
78
79
|
ok: boolean;
|
|
79
80
|
error: string;
|
|
80
81
|
messageId?: never;
|
|
81
82
|
from?: never;
|
|
82
83
|
to?: never;
|
|
83
|
-
summary?: never;
|
|
84
84
|
} | {
|
|
85
85
|
error?: never;
|
|
86
86
|
ok: boolean;
|
|
@@ -1972,16 +1972,18 @@ function persistReceipt(db, messageId, state) {
|
|
|
1972
1972
|
}
|
|
1973
1973
|
function materializeMessageRows(db, rows) {
|
|
1974
1974
|
if (rows.length === 0) return [];
|
|
1975
|
-
const
|
|
1976
|
-
const
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1975
|
+
const receiptRows = [];
|
|
1976
|
+
for (const ids of chunk(
|
|
1977
|
+
rows.map((row) => row.id),
|
|
1978
|
+
400
|
|
1979
|
+
)) {
|
|
1980
|
+
const receiptSql = `
|
|
1981
1981
|
SELECT message_id, actor_id, read_at, completed_at, completed_by, outcome
|
|
1982
1982
|
FROM message_receipts
|
|
1983
|
+
WHERE message_id IN (${ids.map(() => "?").join(", ")})
|
|
1983
1984
|
`;
|
|
1984
|
-
|
|
1985
|
+
receiptRows.push(...db.prepare(receiptSql).all(...ids));
|
|
1986
|
+
}
|
|
1985
1987
|
const receiptState = /* @__PURE__ */ new Map();
|
|
1986
1988
|
for (const row of receiptRows) {
|
|
1987
1989
|
const states = receiptState.get(row.message_id) ?? {};
|
|
@@ -2010,8 +2012,16 @@ function materializeMessageRows(db, rows) {
|
|
|
2010
2012
|
});
|
|
2011
2013
|
}
|
|
2012
2014
|
function deleteMessages(db, ids) {
|
|
2013
|
-
const
|
|
2014
|
-
|
|
2015
|
+
for (const chunkIds of chunk(ids, 400)) {
|
|
2016
|
+
db.prepare(`DELETE FROM messages WHERE id IN (${chunkIds.map(() => "?").join(", ")})`).run(
|
|
2017
|
+
...chunkIds
|
|
2018
|
+
);
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
function* chunk(values, size) {
|
|
2022
|
+
for (let start = 0; start < values.length; start += size) {
|
|
2023
|
+
yield values.slice(start, start + size);
|
|
2024
|
+
}
|
|
2015
2025
|
}
|
|
2016
2026
|
function persistAgent(db, agent) {
|
|
2017
2027
|
db.prepare(`
|
|
@@ -2264,11 +2274,11 @@ function createMailboxParseState(raw) {
|
|
|
2264
2274
|
ingestMailboxChunk(state, raw);
|
|
2265
2275
|
return state;
|
|
2266
2276
|
}
|
|
2267
|
-
function ingestMailboxChunk(state,
|
|
2277
|
+
function ingestMailboxChunk(state, chunk2) {
|
|
2268
2278
|
const firstNewIndex = state.messages.length;
|
|
2269
2279
|
const ackRecords = [];
|
|
2270
2280
|
const staleExisting = /* @__PURE__ */ new Set();
|
|
2271
|
-
for (const line of
|
|
2281
|
+
for (const line of chunk2.split(LINE_SEPARATOR)) {
|
|
2272
2282
|
if (line.trim().length === 0) continue;
|
|
2273
2283
|
let parsed;
|
|
2274
2284
|
try {
|
|
@@ -2580,6 +2590,7 @@ var SqliteMailbox = class {
|
|
|
2580
2590
|
lastHeartbeat = /* @__PURE__ */ new Map();
|
|
2581
2591
|
lastClientHeartbeat = /* @__PURE__ */ new Map();
|
|
2582
2592
|
autoCompactTimer = null;
|
|
2593
|
+
autoCompactInFlight;
|
|
2583
2594
|
closed = false;
|
|
2584
2595
|
stmt(sql) {
|
|
2585
2596
|
return this.db.prepare(sql);
|
|
@@ -3023,10 +3034,14 @@ var SqliteMailbox = class {
|
|
|
3023
3034
|
* heartbeat from that id simply is not throttled and writes once more.
|
|
3024
3035
|
*/
|
|
3025
3036
|
pruneHeartbeats(map, nowMs) {
|
|
3026
|
-
if (map.size <= HEARTBEAT_TRACKING_MAX_ENTRIES) return;
|
|
3027
3037
|
for (const [id, at] of map) {
|
|
3028
3038
|
if (nowMs - at > HEARTBEAT_TRACKING_TTL_MS) map.delete(id);
|
|
3029
3039
|
}
|
|
3040
|
+
while (map.size > HEARTBEAT_TRACKING_MAX_ENTRIES) {
|
|
3041
|
+
const oldest = map.keys().next().value;
|
|
3042
|
+
if (oldest === void 0) break;
|
|
3043
|
+
map.delete(oldest);
|
|
3044
|
+
}
|
|
3030
3045
|
}
|
|
3031
3046
|
async deregisterAgent(agentId) {
|
|
3032
3047
|
this.stmt("DELETE FROM agents WHERE agent_id = ?").run(agentId);
|
|
@@ -3130,7 +3145,14 @@ var SqliteMailbox = class {
|
|
|
3130
3145
|
return purgeStale(this.compactionCtx(), options);
|
|
3131
3146
|
}
|
|
3132
3147
|
async autoCompact(options) {
|
|
3133
|
-
|
|
3148
|
+
if (this.autoCompactInFlight !== void 0) return this.autoCompactInFlight;
|
|
3149
|
+
const inFlight = autoCompact(this.compactionCtx(), options);
|
|
3150
|
+
this.autoCompactInFlight = inFlight;
|
|
3151
|
+
try {
|
|
3152
|
+
return await inFlight;
|
|
3153
|
+
} finally {
|
|
3154
|
+
if (this.autoCompactInFlight === inFlight) this.autoCompactInFlight = void 0;
|
|
3155
|
+
}
|
|
3134
3156
|
}
|
|
3135
3157
|
/** Bundle of store operations the retention sweeps drive. */
|
|
3136
3158
|
compactionCtx() {
|
|
@@ -3446,9 +3468,9 @@ function handleMessage(state, message) {
|
|
|
3446
3468
|
scheduleIdleStop();
|
|
3447
3469
|
});
|
|
3448
3470
|
}
|
|
3449
|
-
function onData(state,
|
|
3471
|
+
function onData(state, chunk2) {
|
|
3450
3472
|
state.lastSeenAt = Date.now();
|
|
3451
|
-
state.buffer +=
|
|
3473
|
+
state.buffer += chunk2;
|
|
3452
3474
|
while (true) {
|
|
3453
3475
|
const newline = state.buffer.indexOf("\n");
|
|
3454
3476
|
if (newline < 0) {
|
|
@@ -3544,7 +3566,7 @@ var server = net.createServer((socket) => {
|
|
|
3544
3566
|
void metadataWritten.then(() => {
|
|
3545
3567
|
if (!socket.destroyed) send(state, { type: "hello", ...serverInfo });
|
|
3546
3568
|
});
|
|
3547
|
-
socket.on("data", (
|
|
3569
|
+
socket.on("data", (chunk2) => onData(state, chunk2));
|
|
3548
3570
|
socket.on("error", () => {
|
|
3549
3571
|
});
|
|
3550
3572
|
socket.on("close", () => {
|
|
@@ -14,6 +14,7 @@ export declare class SqliteMailbox implements Mailbox {
|
|
|
14
14
|
private readonly lastHeartbeat;
|
|
15
15
|
private readonly lastClientHeartbeat;
|
|
16
16
|
private autoCompactTimer;
|
|
17
|
+
private autoCompactInFlight;
|
|
17
18
|
private closed;
|
|
18
19
|
constructor(projectDir: string, events?: EventBus, eventEmitter?: MailboxEventEmitter);
|
|
19
20
|
private stmt;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { ContentBlock } from '../types/blocks.js';
|
|
2
2
|
import type { Message } from '../types/messages.js';
|
|
3
|
-
import type { TodoItem } from './context.js';
|
|
4
|
-
import { Context } from './context.js';
|
|
3
|
+
import type { Context, TodoItem } from './context.js';
|
|
5
4
|
/**
|
|
6
5
|
* Observable wrapper for mutable conversation state. Production code should
|
|
7
6
|
* mutate messages, todos, and meta through this API so subscribers see a
|
|
@@ -143,9 +143,27 @@ export declare function fallbackProfileChain(config: Config, profileName: string
|
|
|
143
143
|
export declare function smartDefaultFallbackChain(config: Config): string[];
|
|
144
144
|
/**
|
|
145
145
|
* The effective fallback chain for a turn: the explicit `fallbackModels` list
|
|
146
|
-
* when non-empty, otherwise the
|
|
146
|
+
* when non-empty, otherwise the selected profile, otherwise the smart default
|
|
147
|
+
* (unless `fallbackAuto` is off).
|
|
148
|
+
*
|
|
149
|
+
* NOTE: this is the SELECTED chain, not the full runtime order — it omits the
|
|
150
|
+
* bridge, the primary re-insertion, the extra `default`-profile depth and the
|
|
151
|
+
* last-resort sweep that {@link runtimeFallbackChain} adds. Use
|
|
152
|
+
* `runtimeFallbackChain` for anything shown to a user as "what will be tried".
|
|
147
153
|
*/
|
|
148
154
|
export declare function effectiveFallbackChain(config: Config): string[];
|
|
155
|
+
/**
|
|
156
|
+
* The chain the agent loop will ACTUALLY rotate through, in order, if the
|
|
157
|
+
* current primary fails right now — the same `resolveCandidates` call the
|
|
158
|
+
* fallback extension makes, including bridge, primary re-insertion, the
|
|
159
|
+
* `default`-profile depth and the last-resort sweep.
|
|
160
|
+
*
|
|
161
|
+
* `/fallback` used to render `effectiveFallbackChain` instead, so the
|
|
162
|
+
* displayed chain could be four entries while the runtime rotated through
|
|
163
|
+
* seventeen — the view was structurally unable to match the behavior it
|
|
164
|
+
* claimed to describe.
|
|
165
|
+
*/
|
|
166
|
+
export declare function runtimeFallbackChain(config: Config): string[];
|
|
149
167
|
/**
|
|
150
168
|
* Build the cross-provider fallback extension. Always returns an extension —
|
|
151
169
|
* the effective chain (`effectiveFallbackChain`) is recomputed every turn from
|
|
@@ -59,12 +59,30 @@ export declare class FallbackProfileManager {
|
|
|
59
59
|
setStatusTracker(tracker: ProviderModelStatusTracker | undefined): void;
|
|
60
60
|
hasProfile(name: string): boolean;
|
|
61
61
|
listProfiles(): readonly string[];
|
|
62
|
+
/**
|
|
63
|
+
* The profile the session has selected (`config.fallbackProfile`, set by
|
|
64
|
+
* `/fallback profile use <name>`), or undefined when none is selected or the
|
|
65
|
+
* name no longer resolves to a defined profile.
|
|
66
|
+
*
|
|
67
|
+
* Consulted by every resolution entry point when the caller does not name a
|
|
68
|
+
* profile itself. Without this the leader — which passes no profile — could
|
|
69
|
+
* never use a named profile at all: `config.fallbackProfiles` was reachable
|
|
70
|
+
* only by copying a chain into `fallbackModels`.
|
|
71
|
+
*/
|
|
72
|
+
activeProfileName(): string | undefined;
|
|
62
73
|
/**
|
|
63
74
|
* Resolve a named fallback profile to a validated, provider-filtered chain.
|
|
64
75
|
*
|
|
65
|
-
* Returns an empty chain when
|
|
66
|
-
*
|
|
67
|
-
*
|
|
76
|
+
* Returns an empty chain when the profile doesn't exist, or when every entry
|
|
77
|
+
* is excluded, quarantined, or blacked out.
|
|
78
|
+
*
|
|
79
|
+
* Filtering is intentionally identical to {@link resolveRefs} (the explicit
|
|
80
|
+
* `fallbackModels` path): the self-exclusion, the runtime status tracker,
|
|
81
|
+
* and the availability calendar — nothing else. Anything a named profile
|
|
82
|
+
* drops, an explicit chain drops too, and vice versa. Profiles used to apply
|
|
83
|
+
* two extra filters (provider "usability" and the `providers[].models`
|
|
84
|
+
* snapshot) that the explicit path did not, which silently rerouted roles
|
|
85
|
+
* pinned to a profile onto a different model than the one configured.
|
|
68
86
|
*
|
|
69
87
|
* @param name - Profile name from config.fallbackProfiles.
|
|
70
88
|
* @param defaultProvider - Used when an entry has no explicit provider.
|