@wrongstack/core 0.270.0 → 0.272.1
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/{agent-bridge-PcHQl_UQ.d.ts → agent-bridge-DFQYEeXf.d.ts} +1 -1
- package/dist/{agent-subagent-runner-SHJW7t8q.d.ts → agent-subagent-runner-BZa_IEcd.d.ts} +7 -7
- package/dist/{brain-BYcK__Ym.d.ts → brain-etbcbRwV.d.ts} +95 -2
- package/dist/{compactor-C2RKEBtC.d.ts → compactor-72ug-ZRB.d.ts} +1 -1
- package/dist/{config-C_ae2k86.d.ts → config-rRS8yorV.d.ts} +71 -2
- package/dist/{context-Dp87Bcaq.d.ts → context-Dw55zZ_Q.d.ts} +110 -1
- package/dist/coordination/index.d.ts +181 -17
- package/dist/coordination/index.js +1018 -166
- package/dist/coordination/index.js.map +1 -1
- package/dist/defaults/index.d.ts +25 -25
- package/dist/defaults/index.js +804 -222
- package/dist/defaults/index.js.map +1 -1
- package/dist/execution/index.d.ts +23 -18
- package/dist/execution/index.js +136 -41
- package/dist/execution/index.js.map +1 -1
- package/dist/execution/prompt-enhancer.d.ts +36 -6
- package/dist/execution/prompt-enhancer.js +35 -9
- package/dist/execution/prompt-enhancer.js.map +1 -1
- package/dist/extension/index.d.ts +6 -6
- package/dist/{global-mailbox-Bvrz1P3f.d.ts → global-mailbox-DJ4EoRr0.d.ts} +145 -5
- package/dist/{goal-preamble-CA_4yiGQ.d.ts → goal-preamble-hM8BH7TK.d.ts} +9 -9
- package/dist/{goal-store-DhuJoUNG.d.ts → goal-store-CWlbT0TO.d.ts} +1 -1
- package/dist/hq/index.d.ts +95 -6
- package/dist/hq/index.js +628 -50
- package/dist/hq/index.js.map +1 -1
- package/dist/{index-whDfTANu.d.ts → index-2Lhk5v0o.d.ts} +2 -2
- package/dist/{index-W4VJCzHa.d.ts → index-DWm_PE9L.d.ts} +5 -5
- package/dist/{index-CZQ6Pwbs.d.ts → index-DqW4o62H.d.ts} +8 -8
- package/dist/index.d.ts +96 -56
- package/dist/index.js +2464 -519
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +6 -6
- package/dist/infrastructure/index.js +5 -3
- package/dist/infrastructure/index.js.map +1 -1
- package/dist/kernel/index.d.ts +9 -9
- package/dist/kernel/index.js.map +1 -1
- package/dist/{mcp-servers-DJdZiRcv.d.ts → mcp-servers-BpWHTKlE.d.ts} +3 -3
- package/dist/models/index.d.ts +5 -5
- package/dist/models/index.js +28 -5
- package/dist/models/index.js.map +1 -1
- package/dist/{models-registry-C3a-2-Yd.d.ts → models-registry-CXQFUn5t.d.ts} +1 -1
- package/dist/{multi-agent-coordinator-CJSpTe5O.d.ts → multi-agent-coordinator-jyimfo7D.d.ts} +1 -1
- package/dist/{null-fleet-bus-QVshIsDx.d.ts → null-fleet-bus-DOGQcvrY.d.ts} +6 -6
- package/dist/observability/index.d.ts +2 -2
- package/dist/{parallel-eternal-engine-D9y5Pkcc.d.ts → parallel-eternal-engine-rItJBYp9.d.ts} +9 -9
- package/dist/{path-resolver-CnQ8SIfh.d.ts → path-resolver-DrpF5MGK.d.ts} +3 -3
- package/dist/{permission-CvYQNUqZ.d.ts → permission-CC7XFYWG.d.ts} +1 -1
- package/dist/{permission-policy-D5Ss8j4B.d.ts → permission-policy-cYR4RJmw.d.ts} +2 -2
- package/dist/{pipeline-l_zzFRh3.d.ts → pipeline-Ckkn3AOA.d.ts} +2 -2
- package/dist/{plan-templates-NtPgyeJA.d.ts → plan-templates-BvHw5Znw.d.ts} +33 -9
- package/dist/{provider-model-resolve-d5poT5y0.d.ts → provider-model-resolve-nZqnCeaR.d.ts} +3 -3
- package/dist/{provider-runner-gkctlQV_.d.ts → provider-runner-zVOn1p67.d.ts} +3 -3
- package/dist/{retry-policy-CtFhfwa8.d.ts → retry-policy-BV7nzeAd.d.ts} +1 -1
- package/dist/sdd/index.d.ts +8 -8
- package/dist/sdd/index.js +2 -0
- package/dist/sdd/index.js.map +1 -1
- package/dist/{secret-vault-BLsVmTIK.d.ts → secret-vault-eMBKfheR.d.ts} +9 -1
- package/dist/security/index.d.ts +5 -5
- package/dist/security/index.js +137 -10
- package/dist/security/index.js.map +1 -1
- package/dist/{selector-CXl2_y9W.d.ts → selector-C4ORTOid.d.ts} +1 -1
- package/dist/{session-event-bridge-Ccud20CC.d.ts → session-event-bridge-CeNpUL9w.d.ts} +1 -1
- package/dist/{session-reader-ZeXQmsmE.d.ts → session-reader-BepLSnGL.d.ts} +1 -1
- package/dist/storage/index.d.ts +50 -13
- package/dist/storage/index.js +620 -220
- package/dist/storage/index.js.map +1 -1
- package/dist/tools/index.d.ts +2 -2
- package/dist/tools/index.js +9 -2
- package/dist/tools/index.js.map +1 -1
- package/dist/types/index.d.ts +19 -19
- package/dist/types/index.js +202 -41
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +17 -4
- package/dist/utils/index.js +48 -9
- package/dist/utils/index.js.map +1 -1
- package/package.json +1 -1
package/dist/storage/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createReadStream } from 'fs';
|
|
2
2
|
import * as fsp from 'fs/promises';
|
|
3
3
|
import * as path2 from 'path';
|
|
4
|
+
import { createInterface } from 'readline';
|
|
5
|
+
import { randomBytes, randomUUID, createHash } from 'crypto';
|
|
4
6
|
import * as os from 'os';
|
|
5
7
|
import { hostname } from 'os';
|
|
6
|
-
import 'fs';
|
|
7
8
|
|
|
8
9
|
// src/storage/session-store.ts
|
|
9
10
|
async function atomicWrite(targetPath, content, opts = {}) {
|
|
@@ -79,7 +80,7 @@ async function withFileLock(targetPath, fn, opts = {}) {
|
|
|
79
80
|
if (Date.now() - started >= timeoutMs) {
|
|
80
81
|
throw new Error(`Timed out waiting for file lock: ${targetPath}`);
|
|
81
82
|
}
|
|
82
|
-
await new Promise((
|
|
83
|
+
await new Promise((resolve7) => setTimeout(resolve7, 25));
|
|
83
84
|
}
|
|
84
85
|
}
|
|
85
86
|
try {
|
|
@@ -113,7 +114,7 @@ async function renameWithRetry(from, to) {
|
|
|
113
114
|
if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {
|
|
114
115
|
throw err;
|
|
115
116
|
}
|
|
116
|
-
await new Promise((
|
|
117
|
+
await new Promise((resolve7) => setTimeout(resolve7, delays[i]));
|
|
117
118
|
}
|
|
118
119
|
}
|
|
119
120
|
throw lastErr;
|
|
@@ -430,8 +431,6 @@ function resolveWstackPaths(opts) {
|
|
|
430
431
|
projectStatus: (projectHash2) => path2.join(globalRoot, "projects", projectHash2, "status.json")
|
|
431
432
|
};
|
|
432
433
|
}
|
|
433
|
-
|
|
434
|
-
// src/storage/session-store.ts
|
|
435
434
|
function sanitizeModel(model) {
|
|
436
435
|
return model.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
|
|
437
436
|
}
|
|
@@ -442,6 +441,8 @@ function generateSessionId(startedAt, model) {
|
|
|
442
441
|
const modelPart = model ? `_${sanitizeModel(model)}` : "";
|
|
443
442
|
return `${date}/${time}Z${modelPart}_${suffix}`;
|
|
444
443
|
}
|
|
444
|
+
|
|
445
|
+
// src/storage/session-store.ts
|
|
445
446
|
var DefaultSessionStore = class _DefaultSessionStore {
|
|
446
447
|
dir;
|
|
447
448
|
events;
|
|
@@ -459,6 +460,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
459
460
|
_loadCache = /* @__PURE__ */ new Map();
|
|
460
461
|
_indexCache = null;
|
|
461
462
|
static LOAD_CACHE_MAX_ENTRIES = 50;
|
|
463
|
+
static LIST_SCAN_CONCURRENCY = 32;
|
|
462
464
|
constructor(opts) {
|
|
463
465
|
this.dir = opts.dir;
|
|
464
466
|
this.events = opts.events;
|
|
@@ -475,7 +477,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
475
477
|
this._loadCache.clear();
|
|
476
478
|
}
|
|
477
479
|
}
|
|
478
|
-
//
|
|
480
|
+
// ── Storage event helpers ───────────────────────────────────────────────────
|
|
479
481
|
emitRead(sessionId, filePath, operation, outcome, durationMs, error) {
|
|
480
482
|
this.events?.emit("storage.read", {
|
|
481
483
|
sessionId,
|
|
@@ -590,7 +592,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
590
592
|
this.events,
|
|
591
593
|
{
|
|
592
594
|
resumed: true,
|
|
593
|
-
// Shard directory (sessions/<date>/)
|
|
595
|
+
// Shard directory (sessions/<date>/) — must match create() so the
|
|
594
596
|
// .summary.json sidecar lands next to the JSONL instead of the
|
|
595
597
|
// sessions root (where summaryFor() would never find it).
|
|
596
598
|
dir: path2.dirname(file),
|
|
@@ -631,19 +633,93 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
631
633
|
const raw = await fsp.readFile(file, "utf8");
|
|
632
634
|
const lines = raw.split("\n").filter((l) => l.trim());
|
|
633
635
|
const events = [];
|
|
636
|
+
let sessionStartEvent;
|
|
637
|
+
let sessionEndEvent;
|
|
638
|
+
let sessionModel;
|
|
639
|
+
let sessionProvider;
|
|
640
|
+
let sessionPendingToolUses;
|
|
641
|
+
const messages = [];
|
|
642
|
+
const openToolUses = /* @__PURE__ */ new Set();
|
|
643
|
+
let usage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
634
644
|
for (const line of lines) {
|
|
635
645
|
try {
|
|
636
646
|
const parsed = JSON.parse(line);
|
|
637
647
|
if (parsed !== null && typeof parsed === "object" && typeof parsed.type === "string" && typeof parsed.ts === "string") {
|
|
638
|
-
|
|
648
|
+
const ev = parsed;
|
|
649
|
+
events.push(ev);
|
|
650
|
+
if (ev.type === "session_start" && !sessionStartEvent) {
|
|
651
|
+
sessionStartEvent = ev;
|
|
652
|
+
sessionModel = ev.model;
|
|
653
|
+
sessionProvider = ev.provider;
|
|
654
|
+
}
|
|
655
|
+
if (ev.type === "session_end") {
|
|
656
|
+
sessionEndEvent = ev;
|
|
657
|
+
sessionPendingToolUses = ev.pendingToolUses;
|
|
658
|
+
}
|
|
659
|
+
if (ev.type === "user_input") {
|
|
660
|
+
openToolUses.clear();
|
|
661
|
+
messages.push({ role: "user", content: ev.content, ts: ev.ts });
|
|
662
|
+
} else if (ev.type === "llm_response") {
|
|
663
|
+
messages.push({ role: "assistant", content: ev.content, ts: ev.ts });
|
|
664
|
+
for (const b of ev.content) {
|
|
665
|
+
if (b.type === "tool_use") openToolUses.add(b.id);
|
|
666
|
+
}
|
|
667
|
+
usage = {
|
|
668
|
+
input: usage.input + (ev.usage.input ?? 0),
|
|
669
|
+
output: usage.output + (ev.usage.output ?? 0),
|
|
670
|
+
cacheRead: (usage.cacheRead ?? 0) + (ev.usage.cacheRead ?? 0),
|
|
671
|
+
cacheWrite: (usage.cacheWrite ?? 0) + (ev.usage.cacheWrite ?? 0)
|
|
672
|
+
};
|
|
673
|
+
} else if (ev.type === "tool_result") {
|
|
674
|
+
if (!openToolUses.has(ev.id)) {
|
|
675
|
+
this.events?.emit("session.damaged", {
|
|
676
|
+
sessionId: id,
|
|
677
|
+
detail: `Orphan tool_result "${ev.id}" has no matching tool_use`
|
|
678
|
+
});
|
|
679
|
+
continue;
|
|
680
|
+
}
|
|
681
|
+
openToolUses.delete(ev.id);
|
|
682
|
+
const resultBlock = {
|
|
683
|
+
type: "tool_result",
|
|
684
|
+
tool_use_id: ev.id,
|
|
685
|
+
content: typeof ev.content === "string" ? ev.content : JSON.stringify(ev.content),
|
|
686
|
+
is_error: ev.isError
|
|
687
|
+
};
|
|
688
|
+
const last = messages[messages.length - 1];
|
|
689
|
+
const lastIsToolResultUser = last?.role === "user" && Array.isArray(last.content) && last.content.every((b) => b.type === "tool_result");
|
|
690
|
+
if (lastIsToolResultUser && Array.isArray(last.content)) {
|
|
691
|
+
last.content.push(resultBlock);
|
|
692
|
+
} else {
|
|
693
|
+
messages.push({ role: "user", content: [resultBlock], ts: ev.ts });
|
|
694
|
+
}
|
|
695
|
+
}
|
|
639
696
|
}
|
|
640
697
|
} catch {
|
|
641
698
|
}
|
|
642
699
|
}
|
|
643
|
-
|
|
644
|
-
|
|
700
|
+
if (openToolUses.size > 0) {
|
|
701
|
+
this.events?.emit("session.damaged", {
|
|
702
|
+
sessionId: id,
|
|
703
|
+
detail: `${openToolUses.size} tool_use blocks without matching results - replay repaired`
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
const repaired = repairToolUseAdjacency(messages);
|
|
707
|
+
if (repaired.report.changed) {
|
|
708
|
+
this.events?.emit("session.damaged", {
|
|
709
|
+
sessionId: id,
|
|
710
|
+
detail: `Repaired replay adjacency: removed ${repaired.report.removedToolUses.length} tool_use, ${repaired.report.removedToolResults.length} tool_result, ${repaired.report.removedMessages} empty messages`
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
const meta = {
|
|
714
|
+
id,
|
|
715
|
+
startedAt: sessionStartEvent?.ts ?? (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
716
|
+
endedAt: sessionEndEvent?.ts,
|
|
717
|
+
model: sessionModel,
|
|
718
|
+
provider: sessionProvider,
|
|
719
|
+
pendingToolUses: sessionPendingToolUses
|
|
720
|
+
};
|
|
645
721
|
const toolCallEnds = extractToolCallEnds(events);
|
|
646
|
-
const data = { metadata: meta, events, messages, usage, toolCallEnds };
|
|
722
|
+
const data = { metadata: meta, events, messages: repaired.messages, usage, toolCallEnds };
|
|
647
723
|
if (this._loadCache.size >= _DefaultSessionStore.LOAD_CACHE_MAX_ENTRIES) {
|
|
648
724
|
const oldest = this._loadCache.keys().next().value;
|
|
649
725
|
if (oldest !== void 0) {
|
|
@@ -681,20 +757,12 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
681
757
|
});
|
|
682
758
|
return indexed.slice(0, limit);
|
|
683
759
|
}
|
|
684
|
-
|
|
685
|
-
const sessions = await Promise.all(ids.map((id) => this.summaryFor(id).catch(() => null)));
|
|
686
|
-
const out = sessions.filter((s) => s !== null);
|
|
687
|
-
out.sort((a, b) => {
|
|
688
|
-
if (a.startedAt < b.startedAt) return 1;
|
|
689
|
-
if (a.startedAt > b.startedAt) return -1;
|
|
690
|
-
return a.id.localeCompare(b.id);
|
|
691
|
-
});
|
|
692
|
-
return out.slice(0, limit);
|
|
760
|
+
return await this.listFromDirectoryScan(limit);
|
|
693
761
|
} catch {
|
|
694
762
|
return [];
|
|
695
763
|
}
|
|
696
764
|
}
|
|
697
|
-
//
|
|
765
|
+
// ── Session index (_index.jsonl) ─────────────────────────────────────────
|
|
698
766
|
//
|
|
699
767
|
// One JSON line per closed session, appended atomically on close().
|
|
700
768
|
// When a session is deleted, a tombstone {action:"delete",id:"..."} is
|
|
@@ -814,43 +882,102 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
814
882
|
this._indexCache = null;
|
|
815
883
|
return valid.length;
|
|
816
884
|
}
|
|
885
|
+
async listFromDirectoryScan(limit) {
|
|
886
|
+
const refs = await this.collectSessionFiles(this.dir);
|
|
887
|
+
const candidates = await mapWithConcurrency(
|
|
888
|
+
refs,
|
|
889
|
+
_DefaultSessionStore.LIST_SCAN_CONCURRENCY,
|
|
890
|
+
async (ref) => {
|
|
891
|
+
const manifest = await this.readSummaryManifest(ref.id);
|
|
892
|
+
if (manifest) return { summary: manifest, needsBackfill: false };
|
|
893
|
+
const summary = await this.summaryHeaderFor(ref);
|
|
894
|
+
return summary ? { summary, needsBackfill: true } : null;
|
|
895
|
+
}
|
|
896
|
+
);
|
|
897
|
+
const out = candidates.filter((s) => s !== null);
|
|
898
|
+
out.sort((a, b) => compareSessionSummaries(a.summary, b.summary));
|
|
899
|
+
const selected = out.slice(0, limit);
|
|
900
|
+
const summaries = await mapWithConcurrency(
|
|
901
|
+
selected,
|
|
902
|
+
Math.min(_DefaultSessionStore.LIST_SCAN_CONCURRENCY, Math.max(1, limit)),
|
|
903
|
+
async (candidate) => {
|
|
904
|
+
if (!candidate.needsBackfill) return candidate.summary;
|
|
905
|
+
return await this.summaryFor(candidate.summary.id).catch(() => candidate.summary);
|
|
906
|
+
}
|
|
907
|
+
);
|
|
908
|
+
return summaries.filter((s) => s !== null);
|
|
909
|
+
}
|
|
910
|
+
async collectSessionFiles(dir, prefix = "", depth = 0) {
|
|
911
|
+
let entries;
|
|
912
|
+
try {
|
|
913
|
+
entries = await fsp.readdir(dir, { withFileTypes: true });
|
|
914
|
+
} catch {
|
|
915
|
+
return [];
|
|
916
|
+
}
|
|
917
|
+
const dirEntries = [];
|
|
918
|
+
const files = [];
|
|
919
|
+
for (const entry of entries) {
|
|
920
|
+
if (entry.name.startsWith(".") && entry.name !== ".wrongstack") continue;
|
|
921
|
+
if (entry.name === "shared" || entry.name === "subagents" || entry.name === "attachments")
|
|
922
|
+
continue;
|
|
923
|
+
if (entry.isDirectory()) {
|
|
924
|
+
dirEntries.push(entry);
|
|
925
|
+
} else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
|
|
926
|
+
if (entry.name === "_index.jsonl") continue;
|
|
927
|
+
const base = entry.name.replace(/\.jsonl$/, "");
|
|
928
|
+
const id = prefix ? `${prefix}/${base}` : base;
|
|
929
|
+
files.push({ id, filePath: path2.join(dir, entry.name) });
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
const childFileArrays = await Promise.all(
|
|
933
|
+
dirEntries.map((entry) => {
|
|
934
|
+
const childPrefix = depth === 0 ? entry.name : `${prefix}/${entry.name}`;
|
|
935
|
+
return this.collectSessionFiles(path2.join(dir, entry.name), childPrefix, depth + 1);
|
|
936
|
+
})
|
|
937
|
+
);
|
|
938
|
+
return [...childFileArrays.flat(), ...files];
|
|
939
|
+
}
|
|
817
940
|
/** Recursively collect session IDs from date-shard subdirectories.
|
|
818
|
-
* IDs include the date-prefix path (e.g. "2026-06-06/17-46-57Z_
|
|
941
|
+
* IDs include the date-prefix path (e.g. "2026-06-06/17-46-57Z_…").
|
|
819
942
|
* Skips `.jsonl`/`.summary.json` root files, dot-files, and
|
|
820
943
|
* sub-directories that belong to fleet/subagent sessions. */
|
|
821
944
|
async collectSessionIds(dir, prefix = "", depth = 0) {
|
|
822
|
-
const ids = [];
|
|
823
945
|
let entries;
|
|
824
946
|
try {
|
|
825
947
|
entries = await fsp.readdir(dir, { withFileTypes: true });
|
|
826
948
|
} catch {
|
|
827
|
-
return
|
|
949
|
+
return [];
|
|
828
950
|
}
|
|
951
|
+
const dirEntries = [];
|
|
952
|
+
const fileIds = [];
|
|
829
953
|
for (const entry of entries) {
|
|
830
954
|
if (entry.name.startsWith(".") && entry.name !== ".wrongstack") continue;
|
|
831
955
|
if (entry.name === "shared" || entry.name === "subagents" || entry.name === "attachments")
|
|
832
956
|
continue;
|
|
833
957
|
if (entry.isDirectory()) {
|
|
834
|
-
|
|
835
|
-
ids.push(...await this.collectSessionIds(path2.join(dir, entry.name), childPrefix, depth + 1));
|
|
958
|
+
dirEntries.push(entry);
|
|
836
959
|
} else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
|
|
837
960
|
if (entry.name === "_index.jsonl") continue;
|
|
838
961
|
const base = entry.name.replace(/\.jsonl$/, "");
|
|
839
|
-
|
|
962
|
+
fileIds.push(prefix ? `${prefix}/${base}` : base);
|
|
840
963
|
}
|
|
841
964
|
}
|
|
842
|
-
|
|
965
|
+
const childIdArrays = await Promise.all(
|
|
966
|
+
dirEntries.map((entry) => {
|
|
967
|
+
const childPrefix = depth === 0 ? entry.name : `${prefix}/${entry.name}`;
|
|
968
|
+
return this.collectSessionIds(path2.join(dir, entry.name), childPrefix, depth + 1);
|
|
969
|
+
})
|
|
970
|
+
);
|
|
971
|
+
return [...childIdArrays.flat(), ...fileIds];
|
|
843
972
|
}
|
|
844
973
|
async summaryFor(id) {
|
|
845
974
|
const manifest = this.sessionPath(id, ".summary.json");
|
|
846
975
|
const t0 = Date.now();
|
|
847
976
|
let outcome = "success";
|
|
848
977
|
let errorMsg;
|
|
978
|
+
const fromManifest = await this.readSummaryManifest(id, t0);
|
|
979
|
+
if (fromManifest) return fromManifest;
|
|
849
980
|
try {
|
|
850
|
-
const raw = await fsp.readFile(manifest, "utf8");
|
|
851
|
-
this.emitRead(id, manifest, "summary", "success", Date.now() - t0);
|
|
852
|
-
return JSON.parse(raw);
|
|
853
|
-
} catch {
|
|
854
981
|
const full = this.sessionPath(id, ".jsonl");
|
|
855
982
|
const stat7 = await fsp.stat(full);
|
|
856
983
|
const summary = await this.summarize(id, stat7.mtime.toISOString());
|
|
@@ -866,9 +993,81 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
866
993
|
}));
|
|
867
994
|
});
|
|
868
995
|
outcome = "failure";
|
|
869
|
-
errorMsg = "summary fallback \
|
|
996
|
+
errorMsg = "summary fallback \xE2\u20AC\u201D manifest rebuilt";
|
|
870
997
|
this.emitRead(id, manifest, "summary", outcome, Date.now() - t0, errorMsg);
|
|
871
998
|
return summary;
|
|
999
|
+
} catch (err) {
|
|
1000
|
+
outcome = "failure";
|
|
1001
|
+
errorMsg = toErrorMessage(err);
|
|
1002
|
+
this.emitRead(id, manifest, "summary", outcome, Date.now() - t0, errorMsg);
|
|
1003
|
+
return {
|
|
1004
|
+
id,
|
|
1005
|
+
title: "(damaged)",
|
|
1006
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1007
|
+
model: "unknown",
|
|
1008
|
+
provider: "unknown",
|
|
1009
|
+
tokenTotal: 0
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
async readSummaryManifest(id, startTime = Date.now()) {
|
|
1014
|
+
const manifest = this.sessionPath(id, ".summary.json");
|
|
1015
|
+
try {
|
|
1016
|
+
const raw = await fsp.readFile(manifest, "utf8");
|
|
1017
|
+
this.emitRead(id, manifest, "summary", "success", Date.now() - startTime);
|
|
1018
|
+
return JSON.parse(raw);
|
|
1019
|
+
} catch {
|
|
1020
|
+
return null;
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
async summaryHeaderFor(ref) {
|
|
1024
|
+
let mtime = (/* @__PURE__ */ new Date(0)).toISOString();
|
|
1025
|
+
try {
|
|
1026
|
+
const stat7 = await fsp.stat(ref.filePath);
|
|
1027
|
+
if (!stat7.isFile()) {
|
|
1028
|
+
return {
|
|
1029
|
+
id: ref.id,
|
|
1030
|
+
title: "(damaged)",
|
|
1031
|
+
startedAt: stat7.mtime.toISOString(),
|
|
1032
|
+
model: "unknown",
|
|
1033
|
+
provider: "unknown",
|
|
1034
|
+
tokenTotal: 0
|
|
1035
|
+
};
|
|
1036
|
+
}
|
|
1037
|
+
mtime = stat7.mtime.toISOString();
|
|
1038
|
+
} catch {
|
|
1039
|
+
return null;
|
|
1040
|
+
}
|
|
1041
|
+
try {
|
|
1042
|
+
for await (const event of this.iterSessionEvents(ref.filePath)) {
|
|
1043
|
+
if (event.type === "session_start") {
|
|
1044
|
+
return {
|
|
1045
|
+
id: ref.id,
|
|
1046
|
+
title: "(empty session)",
|
|
1047
|
+
startedAt: event.ts,
|
|
1048
|
+
model: event.model ?? "unknown",
|
|
1049
|
+
provider: event.provider ?? "unknown",
|
|
1050
|
+
tokenTotal: 0
|
|
1051
|
+
};
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
return {
|
|
1055
|
+
id: ref.id,
|
|
1056
|
+
title: "(empty session)",
|
|
1057
|
+
startedAt: (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
1058
|
+
model: "unknown",
|
|
1059
|
+
provider: "unknown",
|
|
1060
|
+
tokenTotal: 0
|
|
1061
|
+
};
|
|
1062
|
+
} catch {
|
|
1063
|
+
return {
|
|
1064
|
+
id: ref.id,
|
|
1065
|
+
title: "(damaged)",
|
|
1066
|
+
startedAt: mtime,
|
|
1067
|
+
model: "unknown",
|
|
1068
|
+
provider: "unknown",
|
|
1069
|
+
tokenTotal: 0
|
|
1070
|
+
};
|
|
872
1071
|
}
|
|
873
1072
|
}
|
|
874
1073
|
/**
|
|
@@ -993,39 +1192,62 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
993
1192
|
}
|
|
994
1193
|
async summarize(id, mtime) {
|
|
995
1194
|
try {
|
|
996
|
-
const
|
|
997
|
-
|
|
998
|
-
|
|
1195
|
+
const file = this.sessionPath(id, ".jsonl");
|
|
1196
|
+
let title = "(empty session)";
|
|
1197
|
+
let startedAt = (/* @__PURE__ */ new Date(0)).toISOString();
|
|
1198
|
+
let endedAt;
|
|
1199
|
+
let model = "unknown";
|
|
1200
|
+
let provider = "unknown";
|
|
1201
|
+
let tokenIn = 0;
|
|
1202
|
+
let tokenOut = 0;
|
|
999
1203
|
let iterationCount = 0;
|
|
1000
1204
|
let toolCallCount = 0;
|
|
1001
1205
|
let toolErrorCount = 0;
|
|
1002
1206
|
let fileChangeCount = 0;
|
|
1003
1207
|
const toolBreakdown = {};
|
|
1004
1208
|
let outcome;
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1209
|
+
let lastEventType;
|
|
1210
|
+
let hasError = false;
|
|
1211
|
+
let sawStart = false;
|
|
1212
|
+
for await (const e of this.iterSessionEvents(file)) {
|
|
1213
|
+
lastEventType = e.type;
|
|
1214
|
+
if (e.type === "session_start") {
|
|
1215
|
+
if (!sawStart) {
|
|
1216
|
+
sawStart = true;
|
|
1217
|
+
startedAt = e.ts;
|
|
1218
|
+
model = e.model ?? "unknown";
|
|
1219
|
+
provider = e.provider ?? "unknown";
|
|
1220
|
+
}
|
|
1221
|
+
} else if (e.type === "session_end") {
|
|
1222
|
+
endedAt = e.ts;
|
|
1223
|
+
} else if (e.type === "user_input") {
|
|
1224
|
+
if (title === "(empty session)") title = userInputTitle(e.content);
|
|
1225
|
+
} else if (e.type === "llm_response") {
|
|
1226
|
+
tokenIn += e.usage.input ?? 0;
|
|
1227
|
+
tokenOut += e.usage.output ?? 0;
|
|
1228
|
+
} else if (e.type === "in_flight_start") iterationCount++;
|
|
1008
1229
|
else if (e.type === "tool_call_start") {
|
|
1009
1230
|
toolCallCount++;
|
|
1010
1231
|
toolBreakdown[e.name] = (toolBreakdown[e.name] ?? 0) + 1;
|
|
1011
1232
|
} else if (e.type === "tool_result" && e.isError) toolErrorCount++;
|
|
1012
1233
|
else if (e.type === "file_snapshot") fileChangeCount += e.files.length;
|
|
1234
|
+
else if (e.type === "error" || e.type === "provider_error") hasError = true;
|
|
1013
1235
|
}
|
|
1014
|
-
if (
|
|
1236
|
+
if (lastEventType === "session_end") {
|
|
1015
1237
|
outcome = "completed";
|
|
1016
|
-
} else if (
|
|
1238
|
+
} else if (lastEventType === "in_flight_start") {
|
|
1017
1239
|
outcome = "aborted";
|
|
1018
|
-
} else if (
|
|
1240
|
+
} else if (hasError) {
|
|
1019
1241
|
outcome = "error";
|
|
1020
1242
|
}
|
|
1021
1243
|
return {
|
|
1022
1244
|
id,
|
|
1023
1245
|
title,
|
|
1024
|
-
startedAt
|
|
1025
|
-
endedAt
|
|
1026
|
-
model
|
|
1027
|
-
provider
|
|
1028
|
-
tokenTotal:
|
|
1246
|
+
startedAt,
|
|
1247
|
+
endedAt,
|
|
1248
|
+
model,
|
|
1249
|
+
provider,
|
|
1250
|
+
tokenTotal: tokenIn + tokenOut,
|
|
1029
1251
|
iterationCount: iterationCount > 0 ? iterationCount : void 0,
|
|
1030
1252
|
toolCallCount: toolCallCount > 0 ? toolCallCount : void 0,
|
|
1031
1253
|
toolErrorCount: toolErrorCount > 0 ? toolErrorCount : void 0,
|
|
@@ -1044,75 +1266,24 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
1044
1266
|
};
|
|
1045
1267
|
}
|
|
1046
1268
|
}
|
|
1047
|
-
|
|
1048
|
-
const
|
|
1049
|
-
const
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
replay(events, sessionId = "unknown") {
|
|
1060
|
-
const messages = [];
|
|
1061
|
-
let usage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
1062
|
-
const openToolUses = /* @__PURE__ */ new Set();
|
|
1063
|
-
for (const e of events) {
|
|
1064
|
-
if (e.type === "user_input") {
|
|
1065
|
-
openToolUses.clear();
|
|
1066
|
-
messages.push({ role: "user", content: e.content, ts: e.ts });
|
|
1067
|
-
} else if (e.type === "llm_response") {
|
|
1068
|
-
messages.push({ role: "assistant", content: e.content, ts: e.ts });
|
|
1069
|
-
for (const b of e.content) {
|
|
1070
|
-
if (b.type === "tool_use") openToolUses.add(b.id);
|
|
1071
|
-
}
|
|
1072
|
-
usage = {
|
|
1073
|
-
input: usage.input + (e.usage.input ?? 0),
|
|
1074
|
-
output: usage.output + (e.usage.output ?? 0),
|
|
1075
|
-
cacheRead: (usage.cacheRead ?? 0) + (e.usage.cacheRead ?? 0),
|
|
1076
|
-
cacheWrite: (usage.cacheWrite ?? 0) + (e.usage.cacheWrite ?? 0)
|
|
1077
|
-
};
|
|
1078
|
-
} else if (e.type === "tool_result") {
|
|
1079
|
-
if (!openToolUses.has(e.id)) {
|
|
1080
|
-
this.events?.emit("session.damaged", {
|
|
1081
|
-
sessionId,
|
|
1082
|
-
detail: `Orphan tool_result "${e.id}" has no matching tool_use`
|
|
1083
|
-
});
|
|
1084
|
-
continue;
|
|
1085
|
-
}
|
|
1086
|
-
openToolUses.delete(e.id);
|
|
1087
|
-
const resultBlock = {
|
|
1088
|
-
type: "tool_result",
|
|
1089
|
-
tool_use_id: e.id,
|
|
1090
|
-
content: typeof e.content === "string" ? e.content : JSON.stringify(e.content),
|
|
1091
|
-
is_error: e.isError
|
|
1092
|
-
};
|
|
1093
|
-
const last = messages[messages.length - 1];
|
|
1094
|
-
const lastIsToolResultUser = last?.role === "user" && Array.isArray(last.content) && last.content.every((b) => b.type === "tool_result");
|
|
1095
|
-
if (lastIsToolResultUser && Array.isArray(last.content)) {
|
|
1096
|
-
last.content.push(resultBlock);
|
|
1097
|
-
} else {
|
|
1098
|
-
messages.push({ role: "user", content: [resultBlock], ts: e.ts });
|
|
1269
|
+
async *iterSessionEvents(file) {
|
|
1270
|
+
const stream = createReadStream(file, { encoding: "utf8" });
|
|
1271
|
+
const lines = createInterface({ input: stream, crlfDelay: Infinity });
|
|
1272
|
+
try {
|
|
1273
|
+
for await (const line of lines) {
|
|
1274
|
+
if (!line.trim()) continue;
|
|
1275
|
+
try {
|
|
1276
|
+
const parsed = JSON.parse(line);
|
|
1277
|
+
if (parsed !== null && typeof parsed === "object" && typeof parsed.type === "string" && typeof parsed.ts === "string") {
|
|
1278
|
+
yield parsed;
|
|
1279
|
+
}
|
|
1280
|
+
} catch {
|
|
1099
1281
|
}
|
|
1100
1282
|
}
|
|
1283
|
+
} finally {
|
|
1284
|
+
lines.close();
|
|
1285
|
+
stream.destroy();
|
|
1101
1286
|
}
|
|
1102
|
-
if (openToolUses.size > 0) {
|
|
1103
|
-
this.events?.emit("session.damaged", {
|
|
1104
|
-
sessionId,
|
|
1105
|
-
detail: `${openToolUses.size} tool_use blocks without matching results - replay repaired`
|
|
1106
|
-
});
|
|
1107
|
-
}
|
|
1108
|
-
const repaired = repairToolUseAdjacency(messages);
|
|
1109
|
-
if (repaired.report.changed) {
|
|
1110
|
-
this.events?.emit("session.damaged", {
|
|
1111
|
-
sessionId,
|
|
1112
|
-
detail: `Repaired replay adjacency: removed ${repaired.report.removedToolUses.length} tool_use, ${repaired.report.removedToolResults.length} tool_result, ${repaired.report.removedMessages} empty messages`
|
|
1113
|
-
});
|
|
1114
|
-
}
|
|
1115
|
-
return { messages: repaired.messages, usage };
|
|
1116
1287
|
}
|
|
1117
1288
|
};
|
|
1118
1289
|
function extractToolCallEnds(events) {
|
|
@@ -1172,7 +1343,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
1172
1343
|
/**
|
|
1173
1344
|
* Lazy session_start/session_resumed init, shared by all appenders.
|
|
1174
1345
|
* A single promise (not a boolean) so a second append racing the first
|
|
1175
|
-
* can't push its event into the buffer BEFORE the first append's event
|
|
1346
|
+
* can't push its event into the buffer BEFORE the first append's event —
|
|
1176
1347
|
* every appender awaits the same init and resumes in FIFO call order.
|
|
1177
1348
|
*/
|
|
1178
1349
|
initPromise = null;
|
|
@@ -1185,24 +1356,24 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
1185
1356
|
lastAppendWarnAt = 0;
|
|
1186
1357
|
secretScrubber;
|
|
1187
1358
|
onCloseCb;
|
|
1188
|
-
/** Implements SessionWriter.traceId
|
|
1359
|
+
/** Implements SessionWriter.traceId — propagated from ContextInit.traceId. */
|
|
1189
1360
|
traceId;
|
|
1190
|
-
//
|
|
1361
|
+
// ── Write buffer — batches events to reduce per-event disk I/O ─────────
|
|
1191
1362
|
//
|
|
1192
1363
|
// Every append() pushes the scrubbed event into an in-memory buffer instead
|
|
1193
1364
|
// of calling handle.appendFile() synchronously. The buffer flushes to disk
|
|
1194
1365
|
// when it reaches FLUSH_SIZE events OR after FLUSH_INTERVAL_MS of inactivity.
|
|
1195
1366
|
// This cuts the number of disk writes by ~95% without changing the on-disk
|
|
1196
|
-
// format
|
|
1367
|
+
// format — the JSONL is still one JSON object per line.
|
|
1197
1368
|
writeBuffer = [];
|
|
1198
1369
|
flushTimer = null;
|
|
1199
1370
|
static FLUSH_INTERVAL_MS = 500;
|
|
1200
1371
|
static FLUSH_SIZE = 50;
|
|
1201
|
-
//
|
|
1372
|
+
// ── Write serialization ─────────────────────────────────────────────────
|
|
1202
1373
|
//
|
|
1203
1374
|
// All disk writes are funneled through a FIFO promise chain. Without it,
|
|
1204
1375
|
// a timer-driven flush racing an explicit flush()/close() issues two
|
|
1205
|
-
// concurrent appendFile() calls on the shared O_APPEND handle
|
|
1376
|
+
// concurrent appendFile() calls on the shared O_APPEND handle — the kernel
|
|
1206
1377
|
// may complete them out of order (chronology breaks) or, for large
|
|
1207
1378
|
// batches, interleave partial writes (torn JSONL lines). The chain keeps
|
|
1208
1379
|
// exactly one write in flight; failures don't break the chain.
|
|
@@ -1216,7 +1387,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
1216
1387
|
);
|
|
1217
1388
|
return write;
|
|
1218
1389
|
}
|
|
1219
|
-
//
|
|
1390
|
+
// ── Enriched summary tracking ──────────────────────────────────────────
|
|
1220
1391
|
iterationCount = 0;
|
|
1221
1392
|
toolCallCount = 0;
|
|
1222
1393
|
toolErrorCount = 0;
|
|
@@ -1308,7 +1479,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
1308
1479
|
* (user_input, llm_response) call this so they survive SIGKILL/crash
|
|
1309
1480
|
* instead of sitting in the in-memory buffer for up to 500ms.
|
|
1310
1481
|
*
|
|
1311
|
-
* Idempotent
|
|
1482
|
+
* Idempotent — cancels any pending timer and writes whatever has
|
|
1312
1483
|
* accumulated in the buffer. Safe to call even when the buffer
|
|
1313
1484
|
* is empty (no-op).
|
|
1314
1485
|
*/
|
|
@@ -1331,7 +1502,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
1331
1502
|
/**
|
|
1332
1503
|
* Flush all buffered events to disk as a single appendFile call.
|
|
1333
1504
|
* Errors use the same throttled-warning pattern the old per-event
|
|
1334
|
-
* append path used
|
|
1505
|
+
* append path used — one warning every 5s with a suppressed count.
|
|
1335
1506
|
* On failure the buffer is cleared (events are best-effort, same as
|
|
1336
1507
|
* the old per-event path where a failed write was silently dropped).
|
|
1337
1508
|
*/
|
|
@@ -1514,7 +1685,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
1514
1685
|
/**
|
|
1515
1686
|
* Truncate the session file to the checkpoint with the given promptIndex,
|
|
1516
1687
|
* removing all events that follow it. Uses a single-pass byte-offset scan
|
|
1517
|
-
* so post-checkpoint content is never read or parsed
|
|
1688
|
+
* so post-checkpoint content is never read or parsed — O(1) memory instead
|
|
1518
1689
|
* of O(N) JSON.parse calls over the full file.
|
|
1519
1690
|
*/
|
|
1520
1691
|
async truncateToCheckpoint(targetPromptIndex) {
|
|
@@ -1671,7 +1842,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
1671
1842
|
await fsp.writeFile(this.filePath, record, "utf8");
|
|
1672
1843
|
}
|
|
1673
1844
|
/**
|
|
1674
|
-
* Idea #1
|
|
1845
|
+
* Idea #1 — write an in-flight marker. The agent loop should call
|
|
1675
1846
|
* this at the start of each long-running operation; a matching
|
|
1676
1847
|
* `clearInFlightMarker` follows on clean exit. A stale marker
|
|
1677
1848
|
* (no end) is what `SessionRecovery.detectStale` looks for.
|
|
@@ -1688,9 +1859,9 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
1688
1859
|
this.events?.emit("in_flight.started", { context, ts: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1689
1860
|
}
|
|
1690
1861
|
/**
|
|
1691
|
-
* Idea #1
|
|
1862
|
+
* Idea #1 — close the in-flight marker. Idempotent in spirit
|
|
1692
1863
|
* (you can call it after a successful iteration even if you
|
|
1693
|
-
* didn't open one this round)
|
|
1864
|
+
* didn't open one this round) — but the session log records
|
|
1694
1865
|
* every call so postmortem tooling can see "the agent finished
|
|
1695
1866
|
* cleanly X times, then died without finishing Y".
|
|
1696
1867
|
*/
|
|
@@ -1707,6 +1878,27 @@ function userInputTitle(content) {
|
|
|
1707
1878
|
const text = typeof content === "string" ? content : content.filter((b) => b.type === "text").map((b) => b.text).join(" ");
|
|
1708
1879
|
return (text || "(non-text input)").slice(0, 60);
|
|
1709
1880
|
}
|
|
1881
|
+
function compareSessionSummaries(a, b) {
|
|
1882
|
+
if (a.startedAt < b.startedAt) return 1;
|
|
1883
|
+
if (a.startedAt > b.startedAt) return -1;
|
|
1884
|
+
return a.id.localeCompare(b.id);
|
|
1885
|
+
}
|
|
1886
|
+
async function mapWithConcurrency(items, concurrency, fn) {
|
|
1887
|
+
if (items.length === 0) return [];
|
|
1888
|
+
const out = new Array(items.length);
|
|
1889
|
+
let next = 0;
|
|
1890
|
+
const workerCount = Math.min(Math.max(1, concurrency), items.length);
|
|
1891
|
+
const workers = Array.from({ length: workerCount }, async () => {
|
|
1892
|
+
for (; ; ) {
|
|
1893
|
+
const idx = next++;
|
|
1894
|
+
if (idx >= items.length) return;
|
|
1895
|
+
const item = items[idx];
|
|
1896
|
+
if (item !== void 0) out[idx] = await fn(item);
|
|
1897
|
+
}
|
|
1898
|
+
});
|
|
1899
|
+
await Promise.all(workers);
|
|
1900
|
+
return out;
|
|
1901
|
+
}
|
|
1710
1902
|
var QueueStore = class {
|
|
1711
1903
|
file;
|
|
1712
1904
|
// Use `| undefined` (not `?`) so exactOptionalPropertyTypes doesn't
|
|
@@ -2287,6 +2479,20 @@ var DefaultMemoryStore = class {
|
|
|
2287
2479
|
*/
|
|
2288
2480
|
persistBackup;
|
|
2289
2481
|
backupDir;
|
|
2482
|
+
/**
|
|
2483
|
+
* Per-scope tracked byte sizes — incremented on `remember()`, decremented on
|
|
2484
|
+
* `forget()`, recalculated after `consolidate()`. Eliminates the redundant
|
|
2485
|
+
* readAll() call that previously checked the file size after every write.
|
|
2486
|
+
*/
|
|
2487
|
+
_trackedByteSizes = {};
|
|
2488
|
+
/** Result cache for scoreRelevant() — keyed by scope + context hash, TTL 30s. */
|
|
2489
|
+
_scoreCache = /* @__PURE__ */ new Map();
|
|
2490
|
+
/**
|
|
2491
|
+
* Per-entry cached lowercase strings — computed once per scoreRelevant() call,
|
|
2492
|
+
* stored here so repeated scoring of the same entries avoids re-computation.
|
|
2493
|
+
* Cleared on every mutation (remember/forget/consolidate/clear).
|
|
2494
|
+
*/
|
|
2495
|
+
_cachedLower = null;
|
|
2290
2496
|
constructor(opts) {
|
|
2291
2497
|
this.files = {
|
|
2292
2498
|
"project-agents": opts.paths.inProjectAgentsFile,
|
|
@@ -2320,6 +2526,20 @@ var DefaultMemoryStore = class {
|
|
|
2320
2526
|
}
|
|
2321
2527
|
}
|
|
2322
2528
|
}
|
|
2529
|
+
/**
|
|
2530
|
+
* Recalculate the tracked byte size for a scope by re-reading the file and
|
|
2531
|
+
* summing the serialized byte length of each line. Called after consolidate()
|
|
2532
|
+
* (which modifies the file) to keep the tracker accurate.
|
|
2533
|
+
*/
|
|
2534
|
+
async _recalcTrackedByteSize(scope) {
|
|
2535
|
+
const raw = await this.backend.readAll(scope, this.files[scope]);
|
|
2536
|
+
let total = 0;
|
|
2537
|
+
for (const line of raw.split("\n")) {
|
|
2538
|
+
if (line.trim()) total += Buffer.byteLength(line, "utf8");
|
|
2539
|
+
}
|
|
2540
|
+
this._trackedByteSizes[scope] = total;
|
|
2541
|
+
return total;
|
|
2542
|
+
}
|
|
2323
2543
|
async readAll() {
|
|
2324
2544
|
const parts = [];
|
|
2325
2545
|
for (const scope of ["project-agents", "project-memory", "user-memory"]) {
|
|
@@ -2420,6 +2640,7 @@ ${body.trim()}`);
|
|
|
2420
2640
|
const t0 = Date.now();
|
|
2421
2641
|
try {
|
|
2422
2642
|
await this.backend.remember(scope, entry, filePath);
|
|
2643
|
+
this._scoreCache.clear();
|
|
2423
2644
|
const dur = Date.now() - t0;
|
|
2424
2645
|
this.events?.emit("storage.write", {
|
|
2425
2646
|
sessionId: "~memory~",
|
|
@@ -2444,16 +2665,21 @@ ${body.trim()}`);
|
|
|
2444
2665
|
});
|
|
2445
2666
|
throw err;
|
|
2446
2667
|
}
|
|
2447
|
-
|
|
2448
|
-
if (
|
|
2668
|
+
let trackedSize = this._trackedByteSizes[scope];
|
|
2669
|
+
if (trackedSize === void 0) {
|
|
2670
|
+
trackedSize = await this._recalcTrackedByteSize(scope);
|
|
2671
|
+
}
|
|
2672
|
+
if (trackedSize > MAX_BYTES_TOTAL) {
|
|
2449
2673
|
const removed = await this.backend.consolidate(scope, this.files[scope]);
|
|
2450
2674
|
if (removed > 0) {
|
|
2451
2675
|
this.events?.emit("memory.consolidated", {
|
|
2452
2676
|
scope,
|
|
2453
2677
|
removed
|
|
2454
2678
|
});
|
|
2679
|
+
await this._recalcTrackedByteSize(scope);
|
|
2455
2680
|
}
|
|
2456
2681
|
}
|
|
2682
|
+
this._trackedByteSizes[scope] = (this._trackedByteSizes[scope] ?? 0) + Buffer.byteLength(JSON.stringify(entry), "utf8");
|
|
2457
2683
|
await this.mirrorBackup(scope);
|
|
2458
2684
|
this.events?.emit("memory.remembered", {
|
|
2459
2685
|
scope,
|
|
@@ -2472,16 +2698,30 @@ ${body.trim()}`);
|
|
|
2472
2698
|
async scoreRelevant(ctx, scope = "project-memory", limit = 8) {
|
|
2473
2699
|
const all = await this.list(scope);
|
|
2474
2700
|
if (all.length === 0) return [];
|
|
2701
|
+
const ctxHash = `${scope}|${ctx.currentTask}|${(ctx.activeSkills ?? []).join(",")}|${(ctx.toolNames ?? []).join(",")}`;
|
|
2702
|
+
const now = Date.now();
|
|
2703
|
+
const TTL_MS = 3e4;
|
|
2704
|
+
const cached = this._scoreCache.get(ctxHash);
|
|
2705
|
+
if (cached && cached.expiresAt > now && cached.entries === all) {
|
|
2706
|
+
return cached.scored.slice(0, Math.min(limit, 15));
|
|
2707
|
+
}
|
|
2475
2708
|
const taskWords = ctx.currentTask.toLowerCase().split(/\s+/).filter((w) => w.length > 2);
|
|
2476
2709
|
const skillWords = (ctx.activeSkills ?? []).flatMap((s) => s.split("-"));
|
|
2477
2710
|
const toolWords = (ctx.toolNames ?? []).flatMap((t) => t.toLowerCase().split("_"));
|
|
2478
|
-
|
|
2711
|
+
this._cachedLower = /* @__PURE__ */ new WeakMap();
|
|
2479
2712
|
const scored = [];
|
|
2480
2713
|
for (const entry of all) {
|
|
2481
2714
|
let score = 0;
|
|
2482
2715
|
const reasons = [];
|
|
2483
|
-
|
|
2484
|
-
|
|
2716
|
+
let cachedLower = this._cachedLower.get(entry);
|
|
2717
|
+
if (!cachedLower) {
|
|
2718
|
+
cachedLower = {
|
|
2719
|
+
textLower: entry.text.toLowerCase(),
|
|
2720
|
+
tagsLower: (entry.tags ?? []).map((t) => t.toLowerCase())
|
|
2721
|
+
};
|
|
2722
|
+
this._cachedLower.set(entry, cachedLower);
|
|
2723
|
+
}
|
|
2724
|
+
const { textLower, tagsLower } = cachedLower;
|
|
2485
2725
|
let taskHits = 0;
|
|
2486
2726
|
for (const w of taskWords) {
|
|
2487
2727
|
if (textLower.includes(w)) {
|
|
@@ -2567,6 +2807,7 @@ ${body.trim()}`);
|
|
|
2567
2807
|
const relevant = scored.filter(
|
|
2568
2808
|
(s) => s.score >= threshold || s.priority === "critical" || s.priority === "high"
|
|
2569
2809
|
);
|
|
2810
|
+
this._scoreCache.set(ctxHash, { entries: all, scored: relevant, expiresAt: now + TTL_MS });
|
|
2570
2811
|
return relevant.slice(0, Math.min(limit, 15));
|
|
2571
2812
|
}
|
|
2572
2813
|
async forget(query, scope = "project-memory") {
|
|
@@ -2576,6 +2817,7 @@ ${body.trim()}`);
|
|
|
2576
2817
|
let removed = 0;
|
|
2577
2818
|
try {
|
|
2578
2819
|
removed = await this.backend.forget(scope, query, filePath);
|
|
2820
|
+
this._scoreCache.clear();
|
|
2579
2821
|
const dur = Date.now() - t0;
|
|
2580
2822
|
this.events?.emit("storage.write", {
|
|
2581
2823
|
sessionId: "~memory~",
|
|
@@ -2607,6 +2849,7 @@ ${body.trim()}`);
|
|
|
2607
2849
|
removed
|
|
2608
2850
|
});
|
|
2609
2851
|
await this.mirrorBackup(scope);
|
|
2852
|
+
await this._recalcTrackedByteSize(scope);
|
|
2610
2853
|
}
|
|
2611
2854
|
return removed;
|
|
2612
2855
|
});
|
|
@@ -2618,6 +2861,7 @@ ${body.trim()}`);
|
|
|
2618
2861
|
let removed = 0;
|
|
2619
2862
|
try {
|
|
2620
2863
|
removed = await this.backend.consolidate(scope, filePath);
|
|
2864
|
+
this._scoreCache.clear();
|
|
2621
2865
|
const dur = Date.now() - t0;
|
|
2622
2866
|
this.events?.emit("storage.write", {
|
|
2623
2867
|
sessionId: "~memory~",
|
|
@@ -2648,6 +2892,7 @@ ${body.trim()}`);
|
|
|
2648
2892
|
removed
|
|
2649
2893
|
});
|
|
2650
2894
|
await this.mirrorBackup(scope);
|
|
2895
|
+
await this._recalcTrackedByteSize(scope);
|
|
2651
2896
|
}
|
|
2652
2897
|
});
|
|
2653
2898
|
}
|
|
@@ -2658,6 +2903,7 @@ ${body.trim()}`);
|
|
|
2658
2903
|
const t0 = Date.now();
|
|
2659
2904
|
try {
|
|
2660
2905
|
await this.backend.clear(scope, filePath);
|
|
2906
|
+
this._scoreCache.clear();
|
|
2661
2907
|
const dur = Date.now() - t0;
|
|
2662
2908
|
this.events?.emit("storage.write", {
|
|
2663
2909
|
sessionId: "~memory~",
|
|
@@ -2684,6 +2930,7 @@ ${body.trim()}`);
|
|
|
2684
2930
|
}
|
|
2685
2931
|
this.events?.emit("memory.cleared", { scope });
|
|
2686
2932
|
await this.mirrorBackup(scope);
|
|
2933
|
+
this._trackedByteSizes[scope] = 0;
|
|
2687
2934
|
});
|
|
2688
2935
|
return;
|
|
2689
2936
|
}
|
|
@@ -2767,6 +3014,12 @@ var GraphMemoryBackend = class {
|
|
|
2767
3014
|
edges = [];
|
|
2768
3015
|
loadedScope = null;
|
|
2769
3016
|
loaded = false;
|
|
3017
|
+
/**
|
|
3018
|
+
* Promise that resolves when the current in-flight _saveGraph completes.
|
|
3019
|
+
* Tests call flush() to await this before deleting the backend or its temp dir.
|
|
3020
|
+
* Each save operation chains onto the previous one so concurrent saves are serialised.
|
|
3021
|
+
*/
|
|
3022
|
+
_saveDone = Promise.resolve();
|
|
2770
3023
|
constructor(opts) {
|
|
2771
3024
|
this.file = new FileMemoryBackend({ paths: opts.paths });
|
|
2772
3025
|
this.graphFile = opts.graphPath ?? `${opts.paths.projectDir}/memory-graph.json`;
|
|
@@ -2793,7 +3046,8 @@ var GraphMemoryBackend = class {
|
|
|
2793
3046
|
tags: entry.tags,
|
|
2794
3047
|
priority: entry.priority
|
|
2795
3048
|
});
|
|
2796
|
-
|
|
3049
|
+
const recentNodes = [...this.nodes.values()].slice(-100);
|
|
3050
|
+
for (const other of recentNodes) {
|
|
2797
3051
|
if (other.id === nodeId) continue;
|
|
2798
3052
|
const sim = wordOverlap(entry.text, other.entry.text);
|
|
2799
3053
|
const tagSim = sharedTags(entry.tags ?? [], other.tags ?? []);
|
|
@@ -2809,7 +3063,8 @@ var GraphMemoryBackend = class {
|
|
|
2809
3063
|
}
|
|
2810
3064
|
}
|
|
2811
3065
|
}
|
|
2812
|
-
|
|
3066
|
+
this._saveDone = this._saveGraph(scope);
|
|
3067
|
+
await this._saveDone;
|
|
2813
3068
|
}
|
|
2814
3069
|
async forget(scope, query, filePath) {
|
|
2815
3070
|
const removed = await this.file.forget(scope, query, filePath);
|
|
@@ -2824,7 +3079,8 @@ var GraphMemoryBackend = class {
|
|
|
2824
3079
|
}
|
|
2825
3080
|
for (const id of toRemove) this.nodes.delete(id);
|
|
2826
3081
|
this.edges = this.edges.filter((e) => !toRemove.includes(e.from) && !toRemove.includes(e.to));
|
|
2827
|
-
|
|
3082
|
+
this._saveDone = this._saveGraph(scope);
|
|
3083
|
+
await this._saveDone;
|
|
2828
3084
|
}
|
|
2829
3085
|
return removed;
|
|
2830
3086
|
}
|
|
@@ -2936,7 +3192,8 @@ var GraphMemoryBackend = class {
|
|
|
2936
3192
|
this.loadedScope = scope;
|
|
2937
3193
|
this.loaded = true;
|
|
2938
3194
|
}
|
|
2939
|
-
|
|
3195
|
+
/** Fire-and-forget graph persistence. Named _saveGraph to signal it must not be awaited. */
|
|
3196
|
+
async _saveGraph(scope) {
|
|
2940
3197
|
this.loadedScope = scope;
|
|
2941
3198
|
this.loaded = true;
|
|
2942
3199
|
try {
|
|
@@ -2944,16 +3201,21 @@ var GraphMemoryBackend = class {
|
|
|
2944
3201
|
nodes: [...this.nodes.entries()],
|
|
2945
3202
|
edges: this.edges
|
|
2946
3203
|
};
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
{ recursive: true }
|
|
2950
|
-
);
|
|
3204
|
+
const dir = this.graphFile.substring(0, this.graphFile.lastIndexOf("/"));
|
|
3205
|
+
await fsp.mkdir(dir, { recursive: true });
|
|
2951
3206
|
const tmp = `${this.graphFile}.tmp`;
|
|
2952
3207
|
await fsp.writeFile(tmp, JSON.stringify(data));
|
|
2953
3208
|
await fsp.rename(tmp, this.graphFile);
|
|
2954
3209
|
} catch {
|
|
2955
3210
|
}
|
|
2956
3211
|
}
|
|
3212
|
+
/**
|
|
3213
|
+
* Wait for all in-flight _saveGraph operations to complete.
|
|
3214
|
+
* Call this before deleting the backend or its temp directory.
|
|
3215
|
+
*/
|
|
3216
|
+
async flush() {
|
|
3217
|
+
await this._saveDone;
|
|
3218
|
+
}
|
|
2957
3219
|
};
|
|
2958
3220
|
function wordOverlap(a, b) {
|
|
2959
3221
|
const wordsA = new Set(a.toLowerCase().split(/\s+/).filter((w) => w.length > 2));
|
|
@@ -3054,84 +3316,89 @@ var SessionMemoryConsolidator = class {
|
|
|
3054
3316
|
this.minIterations = opts.minIterations ?? 2;
|
|
3055
3317
|
this.maxExistingEntries = opts.maxExistingEntries ?? 15;
|
|
3056
3318
|
}
|
|
3057
|
-
afterRun =
|
|
3319
|
+
afterRun = (ctx, result) => {
|
|
3058
3320
|
if (result.status !== "done") return;
|
|
3059
3321
|
if (!result.finalText || result.finalText.trim().length < 20) return;
|
|
3060
3322
|
if (result.iterations < this.minIterations) return;
|
|
3061
3323
|
const provider = this.provider ?? ctx.provider;
|
|
3062
3324
|
if (!provider?.complete) return;
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
existingEntries
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3325
|
+
const _finalText = result.finalText;
|
|
3326
|
+
const _iterations = result.iterations;
|
|
3327
|
+
const _model = this.model ?? ctx.model;
|
|
3328
|
+
void (async () => {
|
|
3329
|
+
try {
|
|
3330
|
+
const existingEntries = await this.memoryStore.list("project-memory", this.maxExistingEntries);
|
|
3331
|
+
const prompt = buildConsolidationPrompt(
|
|
3332
|
+
_finalText,
|
|
3333
|
+
_iterations,
|
|
3334
|
+
existingEntries
|
|
3335
|
+
);
|
|
3336
|
+
const signal = AbortSignal.timeout(15e3);
|
|
3337
|
+
const response = await provider.complete(
|
|
3338
|
+
{
|
|
3339
|
+
model: _model,
|
|
3340
|
+
system: [{ type: "text", text: prompt }],
|
|
3341
|
+
messages: [
|
|
3342
|
+
{ role: "user", content: "Review the session and return memory operations as JSON." }
|
|
3343
|
+
],
|
|
3344
|
+
maxTokens: 500
|
|
3345
|
+
},
|
|
3346
|
+
{ signal }
|
|
3347
|
+
);
|
|
3348
|
+
const text = response.content.filter((b) => b.type === "text").map((b) => b.text).join("").trim();
|
|
3349
|
+
if (!text) return;
|
|
3350
|
+
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
3351
|
+
if (!jsonMatch) return;
|
|
3352
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
3353
|
+
if (!Array.isArray(parsed.operations) || parsed.operations.length === 0) return;
|
|
3354
|
+
let added = 0;
|
|
3355
|
+
let edited = 0;
|
|
3356
|
+
let deleted = 0;
|
|
3357
|
+
for (const op of parsed.operations) {
|
|
3358
|
+
switch (op.action) {
|
|
3359
|
+
case "add": {
|
|
3360
|
+
if (op.text?.trim()) {
|
|
3361
|
+
await this.memoryStore.remember(op.text.trim(), void 0, {
|
|
3362
|
+
type: op.type,
|
|
3363
|
+
tags: op.tags,
|
|
3364
|
+
priority: op.priority
|
|
3365
|
+
});
|
|
3366
|
+
added++;
|
|
3367
|
+
}
|
|
3368
|
+
break;
|
|
3101
3369
|
}
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
}
|
|
3112
|
-
|
|
3370
|
+
case "edit": {
|
|
3371
|
+
if (op.query && op.text?.trim()) {
|
|
3372
|
+
await this.memoryStore.forget(op.query);
|
|
3373
|
+
await this.memoryStore.remember(op.text.trim(), void 0, {
|
|
3374
|
+
type: op.type,
|
|
3375
|
+
tags: op.tags,
|
|
3376
|
+
priority: op.priority
|
|
3377
|
+
});
|
|
3378
|
+
edited++;
|
|
3379
|
+
}
|
|
3380
|
+
break;
|
|
3113
3381
|
}
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3382
|
+
case "delete": {
|
|
3383
|
+
if (op.query) {
|
|
3384
|
+
const n = await this.memoryStore.forget(op.query);
|
|
3385
|
+
deleted += n;
|
|
3386
|
+
}
|
|
3387
|
+
break;
|
|
3120
3388
|
}
|
|
3121
|
-
break;
|
|
3122
3389
|
}
|
|
3123
3390
|
}
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
process.stderr.write(`[memory] Session consolidation: ${parts.join(", ")}
|
|
3391
|
+
if (added > 0 || edited > 0 || deleted > 0) {
|
|
3392
|
+
const parts = [];
|
|
3393
|
+
if (added) parts.push(`${added} added`);
|
|
3394
|
+
if (edited) parts.push(`${edited} edited`);
|
|
3395
|
+
if (deleted) parts.push(`${deleted} deleted`);
|
|
3396
|
+
process.stderr.write(`[memory] Session consolidation: ${parts.join(", ")}
|
|
3131
3397
|
`);
|
|
3398
|
+
}
|
|
3399
|
+
} catch {
|
|
3132
3400
|
}
|
|
3133
|
-
}
|
|
3134
|
-
}
|
|
3401
|
+
})();
|
|
3135
3402
|
};
|
|
3136
3403
|
};
|
|
3137
3404
|
|
|
@@ -3297,8 +3564,13 @@ function deepFreeze(obj) {
|
|
|
3297
3564
|
return Object.freeze(obj);
|
|
3298
3565
|
}
|
|
3299
3566
|
var KEY_BYTES = 32;
|
|
3567
|
+
var IV_BYTES = 12;
|
|
3568
|
+
var TAG_BYTES = 16;
|
|
3300
3569
|
var KEY_FILE_MAGIC = Buffer.from("WSKV", "ascii");
|
|
3301
3570
|
KEY_FILE_MAGIC.length + 1 + KEY_BYTES;
|
|
3571
|
+
var KEK_MAGIC = Buffer.from("WSKW", "ascii");
|
|
3572
|
+
var KEK_SALT_BYTES = 16;
|
|
3573
|
+
KEK_MAGIC.length + 1 + KEK_SALT_BYTES + IV_BYTES + TAG_BYTES + KEY_BYTES;
|
|
3302
3574
|
function decryptConfigSecrets(cfg, vault, opts) {
|
|
3303
3575
|
const warn = ((msg) => console.warn(msg));
|
|
3304
3576
|
return walk(cfg, vault, (v, key) => {
|
|
@@ -3514,6 +3786,51 @@ var defaultIndexing = {
|
|
|
3514
3786
|
watchExternal: true,
|
|
3515
3787
|
debounceMs: 400
|
|
3516
3788
|
};
|
|
3789
|
+
var IN_PROJECT_FORBIDDEN_KEYS = /* @__PURE__ */ new Set([
|
|
3790
|
+
"provider",
|
|
3791
|
+
"apiKey",
|
|
3792
|
+
"baseUrl",
|
|
3793
|
+
"providers",
|
|
3794
|
+
"mcpServers",
|
|
3795
|
+
"hooks",
|
|
3796
|
+
"plugins",
|
|
3797
|
+
"sync",
|
|
3798
|
+
"yolo",
|
|
3799
|
+
"extensions"
|
|
3800
|
+
]);
|
|
3801
|
+
function stripUnsafeInProjectFields(inProject, sourcePath, warn = (msg) => console.warn(msg)) {
|
|
3802
|
+
const stripped = [];
|
|
3803
|
+
const out = {};
|
|
3804
|
+
for (const [k, v] of Object.entries(inProject)) {
|
|
3805
|
+
if (IN_PROJECT_FORBIDDEN_KEYS.has(k)) {
|
|
3806
|
+
stripped.push(k);
|
|
3807
|
+
continue;
|
|
3808
|
+
}
|
|
3809
|
+
out[k] = v;
|
|
3810
|
+
}
|
|
3811
|
+
if (stripped.length > 0) {
|
|
3812
|
+
warn(
|
|
3813
|
+
JSON.stringify({
|
|
3814
|
+
level: "warn",
|
|
3815
|
+
event: "config.in_project_unsafe_fields_ignored",
|
|
3816
|
+
path: sourcePath,
|
|
3817
|
+
ignoredKeys: stripped,
|
|
3818
|
+
message: `Ignored ${stripped.length} unsafe field(s) from the repo-committed config "${sourcePath}": ${stripped.join(", ")}. These can only be set in your personal ~/.wrongstack/config.json, not in a project-committed file.`,
|
|
3819
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3820
|
+
})
|
|
3821
|
+
);
|
|
3822
|
+
}
|
|
3823
|
+
return out;
|
|
3824
|
+
}
|
|
3825
|
+
function samePath(a, b) {
|
|
3826
|
+
let ra = path2.resolve(a);
|
|
3827
|
+
let rb = path2.resolve(b);
|
|
3828
|
+
if (process.platform === "win32" || process.platform === "darwin") {
|
|
3829
|
+
ra = ra.toLowerCase();
|
|
3830
|
+
rb = rb.toLowerCase();
|
|
3831
|
+
}
|
|
3832
|
+
return ra === rb;
|
|
3833
|
+
}
|
|
3517
3834
|
function deepMerge2(base, patch) {
|
|
3518
3835
|
const opts = { arrayMode: "concat-primitives" };
|
|
3519
3836
|
if (envBoolOptional(process.env.WRONGSTACK_DEBUG_CONFIG)) {
|
|
@@ -3542,14 +3859,15 @@ var DefaultConfigLoader = class {
|
|
|
3542
3859
|
}
|
|
3543
3860
|
async load(opts = {}) {
|
|
3544
3861
|
let cfg = { ...BEHAVIOR_DEFAULTS };
|
|
3862
|
+
const inProjectCollides = samePath(this.paths.inProjectConfig, this.paths.globalConfig) || samePath(this.paths.inProjectConfig, this.paths.projectLocalConfig);
|
|
3545
3863
|
const [global, local, inProject] = await Promise.all([
|
|
3546
3864
|
this.readJson(this.paths.globalConfig),
|
|
3547
3865
|
this.readJson(this.paths.projectLocalConfig),
|
|
3548
|
-
this.readJson(this.paths.inProjectConfig)
|
|
3866
|
+
inProjectCollides ? Promise.resolve({}) : this.readJson(this.paths.inProjectConfig)
|
|
3549
3867
|
]);
|
|
3550
3868
|
cfg = deepMerge2(cfg, global);
|
|
3551
3869
|
cfg = deepMerge2(cfg, local);
|
|
3552
|
-
cfg = deepMerge2(cfg, inProject);
|
|
3870
|
+
cfg = deepMerge2(cfg, stripUnsafeInProjectFields(inProject, this.paths.inProjectConfig));
|
|
3553
3871
|
for (const [key, fn] of Object.entries(ENV_MAP)) {
|
|
3554
3872
|
const v = process.env[key];
|
|
3555
3873
|
if (v) fn(cfg, v);
|
|
@@ -5814,6 +6132,9 @@ var AgentStatusTracker = class {
|
|
|
5814
6132
|
leaderName;
|
|
5815
6133
|
// Live agent map: agentId → AgentEntry
|
|
5816
6134
|
agents = /* @__PURE__ */ new Map();
|
|
6135
|
+
// Last full agent list flushed (leader + subagents). Lets external consumers
|
|
6136
|
+
// read the current state synchronously without re-deriving it.
|
|
6137
|
+
lastAgents = [];
|
|
5817
6138
|
// Leader tracking
|
|
5818
6139
|
leaderStatus = "idle";
|
|
5819
6140
|
leaderCurrentTool;
|
|
@@ -5825,6 +6146,7 @@ var AgentStatusTracker = class {
|
|
|
5825
6146
|
leaderCtxPct;
|
|
5826
6147
|
leaderModel;
|
|
5827
6148
|
leaderPartialText = "";
|
|
6149
|
+
leaderStartedAt;
|
|
5828
6150
|
unsubscribers = [];
|
|
5829
6151
|
onUpdate;
|
|
5830
6152
|
sweepTimer = null;
|
|
@@ -5835,9 +6157,17 @@ var AgentStatusTracker = class {
|
|
|
5835
6157
|
this.leaderName = opts.leaderName ?? "leader";
|
|
5836
6158
|
this.onUpdate = opts.onUpdate;
|
|
5837
6159
|
}
|
|
6160
|
+
/** Current full agent list (leader + subagents) as of the last flush. */
|
|
6161
|
+
getAgents() {
|
|
6162
|
+
return this.lastAgents.length > 0 ? [...this.lastAgents] : [];
|
|
6163
|
+
}
|
|
5838
6164
|
start() {
|
|
5839
6165
|
this.unsubscribers.push(
|
|
5840
|
-
this.events.onPattern("agent.run.started", () => {
|
|
6166
|
+
this.events.onPattern("agent.run.started", (_event, payload) => {
|
|
6167
|
+
const p = payload;
|
|
6168
|
+
this.markLeaderStarted(p?.at);
|
|
6169
|
+
this.captureLeaderContext(p?.ctx);
|
|
6170
|
+
if (p?.model) this.leaderModel = p.model;
|
|
5841
6171
|
this.leaderStatus = "running";
|
|
5842
6172
|
this.leaderIterations++;
|
|
5843
6173
|
this.flush();
|
|
@@ -5845,25 +6175,36 @@ var AgentStatusTracker = class {
|
|
|
5845
6175
|
);
|
|
5846
6176
|
this.unsubscribers.push(
|
|
5847
6177
|
this.events.onPattern("iteration.started", (_e, payload) => {
|
|
5848
|
-
const
|
|
5849
|
-
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
|
|
6178
|
+
const p = payload;
|
|
6179
|
+
const ctx = p?.ctx;
|
|
6180
|
+
this.markLeaderStarted();
|
|
6181
|
+
this.leaderStatus = "running";
|
|
6182
|
+
if (typeof p?.index === "number") {
|
|
6183
|
+
this.leaderIterations = Math.max(this.leaderIterations, p.index + 1);
|
|
5853
6184
|
}
|
|
6185
|
+
if (!ctx) {
|
|
6186
|
+
this.flush();
|
|
6187
|
+
return;
|
|
6188
|
+
}
|
|
6189
|
+
this.captureLeaderContext(ctx);
|
|
5854
6190
|
this.flush();
|
|
5855
6191
|
})
|
|
5856
6192
|
);
|
|
5857
6193
|
this.unsubscribers.push(
|
|
5858
|
-
this.events.onPattern("agent.run.completed", () => {
|
|
5859
|
-
|
|
6194
|
+
this.events.onPattern("agent.run.completed", (_event, payload) => {
|
|
6195
|
+
const p = payload;
|
|
6196
|
+
this.captureLeaderContext(p?.ctx);
|
|
6197
|
+
this.leaderStatus = p?.status === "failed" ? "error" : "idle";
|
|
5860
6198
|
this.leaderCurrentTool = void 0;
|
|
5861
6199
|
this.leaderPartialText = "";
|
|
6200
|
+
if (this.leaderStatus === "idle") this.leaderStartedAt = void 0;
|
|
5862
6201
|
this.flush();
|
|
5863
6202
|
})
|
|
5864
6203
|
);
|
|
5865
6204
|
this.unsubscribers.push(
|
|
5866
|
-
this.events.onPattern("agent.run.error", () => {
|
|
6205
|
+
this.events.onPattern("agent.run.error", (_event, payload) => {
|
|
6206
|
+
const p = payload;
|
|
6207
|
+
this.captureLeaderContext(p?.ctx);
|
|
5867
6208
|
this.leaderStatus = "error";
|
|
5868
6209
|
this.leaderCurrentTool = void 0;
|
|
5869
6210
|
this.leaderPartialText = "";
|
|
@@ -5874,6 +6215,7 @@ var AgentStatusTracker = class {
|
|
|
5874
6215
|
this.events.onPattern("tool.started", (_event, payload) => {
|
|
5875
6216
|
const p = payload;
|
|
5876
6217
|
if (p?.name) {
|
|
6218
|
+
this.markLeaderStarted();
|
|
5877
6219
|
this.leaderCurrentTool = p.name;
|
|
5878
6220
|
this.leaderToolCalls++;
|
|
5879
6221
|
}
|
|
@@ -5889,12 +6231,14 @@ var AgentStatusTracker = class {
|
|
|
5889
6231
|
);
|
|
5890
6232
|
this.unsubscribers.push(
|
|
5891
6233
|
this.events.onPattern("brain.ask_human", () => {
|
|
6234
|
+
this.markLeaderStarted();
|
|
5892
6235
|
this.leaderStatus = "waiting_user";
|
|
5893
6236
|
this.flush();
|
|
5894
6237
|
})
|
|
5895
6238
|
);
|
|
5896
6239
|
this.unsubscribers.push(
|
|
5897
6240
|
this.events.onPattern("llm.stream_started", () => {
|
|
6241
|
+
this.markLeaderStarted();
|
|
5898
6242
|
this.leaderStatus = "streaming";
|
|
5899
6243
|
this.leaderPartialText = "";
|
|
5900
6244
|
this.flush();
|
|
@@ -5902,14 +6246,42 @@ var AgentStatusTracker = class {
|
|
|
5902
6246
|
);
|
|
5903
6247
|
this.unsubscribers.push(
|
|
5904
6248
|
this.events.onPattern("provider.text_delta", (_e, payload) => {
|
|
5905
|
-
const
|
|
6249
|
+
const p = payload;
|
|
6250
|
+
const text = p?.text;
|
|
5906
6251
|
if (!text) return;
|
|
6252
|
+
this.markLeaderStarted();
|
|
6253
|
+
this.captureLeaderContext(p?.ctx);
|
|
5907
6254
|
this.leaderStatus = "streaming";
|
|
5908
6255
|
const next = this.leaderPartialText + text;
|
|
5909
6256
|
this.leaderPartialText = next.length > PARTIAL_TEXT_CAP ? next.slice(next.length - PARTIAL_TEXT_CAP) : next;
|
|
5910
6257
|
this.schedulePartialFlush();
|
|
5911
6258
|
})
|
|
5912
6259
|
);
|
|
6260
|
+
this.unsubscribers.push(
|
|
6261
|
+
this.events.onPattern("provider.response", (_e, payload) => {
|
|
6262
|
+
const p = payload;
|
|
6263
|
+
this.captureLeaderContext(p?.ctx);
|
|
6264
|
+
this.flush();
|
|
6265
|
+
})
|
|
6266
|
+
);
|
|
6267
|
+
this.unsubscribers.push(
|
|
6268
|
+
this.events.onPattern("provider.fallback", (_e, payload) => {
|
|
6269
|
+
const p = payload;
|
|
6270
|
+
if (p?.to?.model) {
|
|
6271
|
+
this.leaderModel = p.to.providerId ? `${p.to.providerId}/${p.to.model}` : p.to.model;
|
|
6272
|
+
this.flush();
|
|
6273
|
+
}
|
|
6274
|
+
})
|
|
6275
|
+
);
|
|
6276
|
+
this.unsubscribers.push(
|
|
6277
|
+
this.events.onPattern("ctx.pct", (_e, payload) => {
|
|
6278
|
+
const p = payload;
|
|
6279
|
+
if (typeof p?.load === "number" && Number.isFinite(p.load)) {
|
|
6280
|
+
this.leaderCtxPct = Math.round(p.load * 100);
|
|
6281
|
+
this.flush();
|
|
6282
|
+
}
|
|
6283
|
+
})
|
|
6284
|
+
);
|
|
5913
6285
|
this.unsubscribers.push(
|
|
5914
6286
|
this.events.onPattern("token.accounted", (_e, payload) => {
|
|
5915
6287
|
const p = payload;
|
|
@@ -5923,7 +6295,8 @@ var AgentStatusTracker = class {
|
|
|
5923
6295
|
const touch = (id) => {
|
|
5924
6296
|
let entry = this.agents.get(id);
|
|
5925
6297
|
if (!entry) {
|
|
5926
|
-
|
|
6298
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6299
|
+
entry = { id, name: id, status: "idle", iterations: 0, toolCalls: 0, startedAt: now, lastActivityAt: now };
|
|
5927
6300
|
this.agents.set(id, entry);
|
|
5928
6301
|
}
|
|
5929
6302
|
entry.lastActivityAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -5936,6 +6309,7 @@ var AgentStatusTracker = class {
|
|
|
5936
6309
|
const entry = touch(p.subagentId);
|
|
5937
6310
|
entry.name = p.name?.trim() || entry.name;
|
|
5938
6311
|
if (p.model) entry.model = p.model;
|
|
6312
|
+
if (!entry.startedAt) entry.startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5939
6313
|
entry.status = "running";
|
|
5940
6314
|
this.flush();
|
|
5941
6315
|
})
|
|
@@ -5955,6 +6329,7 @@ var AgentStatusTracker = class {
|
|
|
5955
6329
|
if (!p?.subagentId) return;
|
|
5956
6330
|
const entry = touch(p.subagentId);
|
|
5957
6331
|
entry.status = "running";
|
|
6332
|
+
if (!entry.startedAt) entry.startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5958
6333
|
entry.iterations++;
|
|
5959
6334
|
this.flush();
|
|
5960
6335
|
})
|
|
@@ -5965,6 +6340,7 @@ var AgentStatusTracker = class {
|
|
|
5965
6340
|
if (!p?.subagentId) return;
|
|
5966
6341
|
const entry = touch(p.subagentId);
|
|
5967
6342
|
entry.status = "running";
|
|
6343
|
+
if (!entry.startedAt) entry.startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5968
6344
|
entry.currentTool = p.name;
|
|
5969
6345
|
entry.toolCalls++;
|
|
5970
6346
|
this.flush();
|
|
@@ -5976,6 +6352,7 @@ var AgentStatusTracker = class {
|
|
|
5976
6352
|
if (!p?.subagentId) return;
|
|
5977
6353
|
const entry = touch(p.subagentId);
|
|
5978
6354
|
entry.status = "running";
|
|
6355
|
+
if (!entry.startedAt) entry.startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5979
6356
|
if (typeof p.iteration === "number") entry.iterations = p.iteration;
|
|
5980
6357
|
if (typeof p.toolCalls === "number") entry.toolCalls = p.toolCalls;
|
|
5981
6358
|
if (typeof p.costUsd === "number") entry.costUsd = p.costUsd;
|
|
@@ -6063,6 +6440,7 @@ var AgentStatusTracker = class {
|
|
|
6063
6440
|
const leaderEntry = {
|
|
6064
6441
|
id: "leader",
|
|
6065
6442
|
name: this.leaderName,
|
|
6443
|
+
startedAt: this.leaderStartedAt,
|
|
6066
6444
|
status: this.leaderStatus,
|
|
6067
6445
|
currentTool: this.leaderCurrentTool,
|
|
6068
6446
|
iterations: this.leaderIterations,
|
|
@@ -6076,6 +6454,11 @@ var AgentStatusTracker = class {
|
|
|
6076
6454
|
lastActivityAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6077
6455
|
};
|
|
6078
6456
|
const allAgents = [leaderEntry, ...this.agents.values()];
|
|
6457
|
+
this.lastAgents = allAgents;
|
|
6458
|
+
try {
|
|
6459
|
+
this.events.emit("session.agents_updated", { agents: allAgents });
|
|
6460
|
+
} catch {
|
|
6461
|
+
}
|
|
6079
6462
|
this.registry.updateAgents(allAgents).then(() => {
|
|
6080
6463
|
try {
|
|
6081
6464
|
this.onUpdate?.();
|
|
@@ -6083,6 +6466,23 @@ var AgentStatusTracker = class {
|
|
|
6083
6466
|
}
|
|
6084
6467
|
}).catch(() => void 0);
|
|
6085
6468
|
}
|
|
6469
|
+
markLeaderStarted(startedAt) {
|
|
6470
|
+
if (this.leaderStartedAt && (this.leaderStatus === "running" || this.leaderStatus === "streaming" || this.leaderStatus === "waiting_user")) {
|
|
6471
|
+
return;
|
|
6472
|
+
}
|
|
6473
|
+
this.leaderStartedAt = startedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
6474
|
+
}
|
|
6475
|
+
captureLeaderContext(ctx) {
|
|
6476
|
+
if (typeof ctx !== "object" || ctx === null) return;
|
|
6477
|
+
const c = ctx;
|
|
6478
|
+
if (typeof c.model === "string" && c.model.length > 0) this.leaderModel = c.model;
|
|
6479
|
+
const metaLimit = c.meta?.["effectiveMaxContext"];
|
|
6480
|
+
const providerMax = c.provider?.capabilities?.maxContext;
|
|
6481
|
+
const maxContext = typeof metaLimit === "number" && metaLimit > 0 ? metaLimit : typeof providerMax === "number" && providerMax > 0 ? providerMax : void 0;
|
|
6482
|
+
if (typeof c.lastRequestTokens === "number" && c.lastRequestTokens > 0 && maxContext !== void 0) {
|
|
6483
|
+
this.leaderCtxPct = Math.round(c.lastRequestTokens / maxContext * 100);
|
|
6484
|
+
}
|
|
6485
|
+
}
|
|
6086
6486
|
};
|
|
6087
6487
|
var INSTANCES_FILE = "webui-instances.json";
|
|
6088
6488
|
var DISCOVERY_TTL_MS = 2500;
|
|
@@ -7831,6 +8231,6 @@ function resolveSessionLoggingConfig(cfg) {
|
|
|
7831
8231
|
};
|
|
7832
8232
|
}
|
|
7833
8233
|
|
|
7834
|
-
export { ALL_SYNC_CATEGORIES, AgentStatusTracker, AnnotationsStore, CORE_RECONSTRUCT_EVENTS, CloudSync, ConfigMigrationError, DEFAULT_CONFIG_MIGRATIONS, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultMemoryStore, DefaultPromptStore, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, DirectorStateCheckpoint, FileMemoryBackend, FleetNotifier, GraphMemoryBackend, MAX_JOURNAL_ENTRIES, MAX_PROGRESS_HISTORY, QueueStore, RecoveryLock, ReplayLogStore, STANDARD_AUDIT_EVENTS, SessionAnalyzer, SessionMemoryConsolidator, SessionRecovery, SessionRegistry, ToolAuditLog, addPlanItem, appendJournal, attachPlanCheckpoint, attachTodosCheckpoint, clearPlan, createSessionEventBridge, deriveTodosFromPlanItem, emptyGoal, emptyPlan, emptyTaskFile, formatGoal, formatPlan, formatPlanTemplates, getPlanTemplate, getSessionRegistry, goalFilePath, hasSessionRegistry, listPlanTemplates, loadDirectorState, loadGoal, loadPlan, loadTasks, loadTodosCheckpoint, mutatePlan, mutateTasks, parseEntries, parseProgressFromText, recordProgress, removePlanItem, resolveAuditLevel, resolveSessionLoggingConfig, runConfigMigrations, saveGoal, savePlan, saveTasks, saveTodosCheckpoint, setPlanItemStatus, setProgress, summarizeUsage };
|
|
8234
|
+
export { ALL_SYNC_CATEGORIES, AgentStatusTracker, AnnotationsStore, CORE_RECONSTRUCT_EVENTS, CloudSync, ConfigMigrationError, DEFAULT_CONFIG_MIGRATIONS, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultMemoryStore, DefaultPromptStore, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, DirectorStateCheckpoint, FileMemoryBackend, FleetNotifier, GraphMemoryBackend, MAX_JOURNAL_ENTRIES, MAX_PROGRESS_HISTORY, QueueStore, RecoveryLock, ReplayLogStore, STANDARD_AUDIT_EVENTS, SessionAnalyzer, SessionMemoryConsolidator, SessionRecovery, SessionRegistry, ToolAuditLog, addPlanItem, appendJournal, attachPlanCheckpoint, attachTodosCheckpoint, clearPlan, createSessionEventBridge, deriveTodosFromPlanItem, emptyGoal, emptyPlan, emptyTaskFile, formatGoal, formatPlan, formatPlanTemplates, generateSessionId, getPlanTemplate, getSessionRegistry, goalFilePath, hasSessionRegistry, listPlanTemplates, loadDirectorState, loadGoal, loadPlan, loadTasks, loadTodosCheckpoint, mutatePlan, mutateTasks, parseEntries, parseProgressFromText, recordProgress, removePlanItem, resolveAuditLevel, resolveSessionLoggingConfig, runConfigMigrations, sanitizeModel, saveGoal, savePlan, saveTasks, saveTodosCheckpoint, setPlanItemStatus, setProgress, summarizeUsage };
|
|
7835
8235
|
//# sourceMappingURL=index.js.map
|
|
7836
8236
|
//# sourceMappingURL=index.js.map
|