@wrongstack/core 0.306.3 → 0.307.0
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/chronicle/index.js +957 -953
- package/dist/chronicle/metrics-ingest.d.ts +55 -0
- package/dist/chronicle/metrics-schema.d.ts +86 -0
- package/dist/chronicle/metrics-store.d.ts +5 -120
- package/dist/chronicle/project-server.js +1083 -1084
- package/dist/chronicle/sqlite-journal-quota.d.ts +39 -0
- package/dist/chronicle/sqlite-journal-schema.d.ts +25 -0
- package/dist/chronicle/sqlite-journal.d.ts +12 -177
- package/dist/coordination/agents/index.js +1738 -1690
- package/dist/coordination/agents/project-agent-capture.d.ts +48 -0
- package/dist/coordination/agents/project-agent-identity.d.ts +3 -129
- package/dist/coordination/agents/project-agent-roster.d.ts +17 -0
- package/dist/coordination/index.js +6103 -5890
- package/dist/coordination/mailbox-http-actor-query.d.ts +16 -0
- package/dist/coordination/mailbox-http-router.d.ts +0 -48
- package/dist/coordination/mailbox-http-sse.d.ts +12 -0
- package/dist/coordination/mailbox-message-types.d.ts +71 -0
- package/dist/coordination/mailbox-predicates.d.ts +13 -0
- package/dist/coordination/mailbox-project-server.js +1 -1
- package/dist/coordination/mailbox-session-sync.d.ts +8 -0
- package/dist/coordination/mailbox-types.d.ts +6 -793
- package/dist/core/agent-loop-context.d.ts +28 -0
- package/dist/core/agent-loop-detector.d.ts +25 -0
- package/dist/core/conversation-state.d.ts +1 -2
- package/dist/core/fallback-doctor.d.ts +72 -0
- 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 +2 -1
- package/dist/core/index.js +1143 -766
- package/dist/defaults/index.js +3833 -3595
- package/dist/execution/auto-compaction-middleware.d.ts +64 -0
- package/dist/execution/autonomy-brain-llm.d.ts +55 -0
- package/dist/execution/autonomy-brain.d.ts +3 -156
- package/dist/execution/compaction-budget.d.ts +38 -0
- package/dist/execution/compaction-core.d.ts +3 -166
- package/dist/execution/compaction-elision.d.ts +58 -0
- package/dist/execution/council-orchestrator-helpers.d.ts +39 -0
- package/dist/execution/council-orchestrator.d.ts +3 -42
- package/dist/execution/council-response-parser.d.ts +56 -0
- package/dist/execution/index.d.ts +1 -1
- package/dist/execution/index.js +10016 -9903
- package/dist/execution/prompt-enhancer.js +11 -3
- package/dist/execution/retry-policy.d.ts +27 -0
- package/dist/execution/tool-executor-guard.d.ts +18 -0
- package/dist/execution/tool-executor-runner.d.ts +12 -0
- package/dist/execution/tool-executor.d.ts +0 -75
- package/dist/goal/index.js +19 -6
- package/dist/hq/index.js +58 -17
- package/dist/index.d.ts +2 -1
- package/dist/index.js +8731 -7756
- package/dist/infrastructure/index.js +24 -1
- package/dist/kernel/events/agent-events.d.ts +2 -0
- package/dist/kernel/events/provider-events.d.ts +13 -0
- package/dist/plugin/index.js +2123 -1918
- package/dist/plugins/auto-review-config.d.ts +53 -0
- package/dist/plugins/auto-review-git.d.ts +19 -0
- package/dist/plugins/auto-review-plugin.d.ts +2 -140
- package/dist/plugins/review-finding-verification.d.ts +7 -0
- package/dist/prompts/index.d.ts +1 -0
- package/dist/prompts/prompt-journal.d.ts +94 -0
- package/dist/security/index.js +677 -667
- package/dist/security/permission-explain.d.ts +19 -0
- package/dist/security/permission-policy.d.ts +0 -101
- package/dist/security/yolo-risk.d.ts +5 -4
- package/dist/session-catalog/index.js +270 -227
- package/dist/session-catalog/project-server.js +277 -234
- package/dist/session-catalog/store-rebuild.d.ts +9 -0
- package/dist/session-catalog/store-schema.d.ts +48 -0
- package/dist/session-catalog/store.d.ts +0 -17
- package/dist/storage/cloud-config-sync/sanitize.d.ts +18 -0
- package/dist/storage/cloud-config-sync.d.ts +1 -1
- package/dist/storage/director-state.d.ts +6 -0
- package/dist/storage/file-session-writer.d.ts +2 -58
- package/dist/storage/index.d.ts +2 -1
- package/dist/storage/index.js +2121 -1736
- package/dist/storage/orphan-lock-cleaner.d.ts +15 -0
- package/dist/storage/provider-config-watcher.d.ts +9 -0
- package/dist/storage/session-recovery.d.ts +9 -0
- package/dist/storage/session-store/rename-session.d.ts +16 -0
- package/dist/storage/session-store/resume-session.d.ts +26 -0
- package/dist/storage/session-store/session-store-clear.d.ts +11 -0
- package/dist/storage/session-store/session-store-index.d.ts +13 -0
- package/dist/storage/session-store/strict-empty-check.d.ts +7 -0
- package/dist/storage/session-store.d.ts +2 -129
- package/dist/storage/session-summary-tracker.d.ts +39 -0
- package/dist/storage/session-write-buffer.d.ts +43 -0
- package/dist/storage/session-writer/session-writer-flush.d.ts +4 -0
- package/dist/storage/session-writer/session-writer-summary-tracker.d.ts +28 -0
- package/dist/tools/fallback-agent-model-assign-tool.d.ts +18 -0
- package/dist/tools/fallback-leader-model-set-tool.d.ts +18 -0
- package/dist/tools/fallback-manage-tools.d.ts +8 -45
- package/dist/tools/fallback-profile-manage-tool.d.ts +16 -0
- package/dist/tools/fallback-provider-key-set-tool.d.ts +17 -0
- package/dist/tools/fallback-provider-manage-tool.d.ts +26 -0
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.js +5150 -5090
- package/dist/types/config/root.d.ts +21 -2
- package/dist/types/default-config.d.ts +1 -1
- package/dist/types/index.d.ts +54 -55
- package/dist/types/index.js +953 -932
- package/dist/types/runtime-capability-manifest.d.ts +5 -5
- package/dist/types/session-markers.d.ts +12 -0
- package/dist/types/session-markers.js +19 -0
- package/dist/types/session.d.ts +7 -0
- package/dist/worktree/worktree-manager.d.ts +1 -1
- package/instructions/coordination/subagent-baseline.md +2 -2
- package/instructions/llm/prompt-enhancer.md +2 -1
- package/instructions/sections/tool/common-patterns.md +21 -0
- package/instructions/sections/tool/delegation-compact.md +3 -1
- package/instructions/sections/tool/delegation-full.md +5 -1
- package/instructions/system-lite.md +28 -3
- package/instructions/system-pro.md +37 -8
- package/instructions/system.md +4 -2
- package/package.json +5 -5
|
@@ -735,9 +735,9 @@ function hasProjectSessionRegistry(globalRoot) {
|
|
|
735
735
|
}
|
|
736
736
|
|
|
737
737
|
// src/session-catalog/store.ts
|
|
738
|
-
import {
|
|
739
|
-
import * as
|
|
740
|
-
import * as
|
|
738
|
+
import { randomBytes, randomUUID as randomUUID2 } from "node:crypto";
|
|
739
|
+
import * as fs5 from "node:fs";
|
|
740
|
+
import * as path5 from "node:path";
|
|
741
741
|
|
|
742
742
|
// src/coordination/sqlite-mailbox-schema.ts
|
|
743
743
|
import { createRequire } from "node:module";
|
|
@@ -777,6 +777,24 @@ function loadDatabaseSync() {
|
|
|
777
777
|
}
|
|
778
778
|
|
|
779
779
|
// src/security/secret-scrubber.ts
|
|
780
|
+
var JSON_CREDENTIAL_KEY_ANCHORS = [
|
|
781
|
+
'Key"',
|
|
782
|
+
'key"',
|
|
783
|
+
'KEY"',
|
|
784
|
+
'token"',
|
|
785
|
+
'Token"',
|
|
786
|
+
'TOKEN"',
|
|
787
|
+
'secret"',
|
|
788
|
+
'Secret"',
|
|
789
|
+
'SECRET"',
|
|
790
|
+
'password"',
|
|
791
|
+
'Password"',
|
|
792
|
+
'PASSWORD"',
|
|
793
|
+
'authorization"',
|
|
794
|
+
'Authorization"',
|
|
795
|
+
'bearer"',
|
|
796
|
+
'Bearer"'
|
|
797
|
+
];
|
|
780
798
|
var PATTERNS = [
|
|
781
799
|
// Anchored at the start where possible so partial matches inside larger
|
|
782
800
|
// strings don't trigger false positives.
|
|
@@ -879,6 +897,30 @@ var PATTERNS = [
|
|
|
879
897
|
regex: /(^|\s)([A-Z_]{4,}(?:KEY|TOKEN|SECRET|PASSWORD|PWD))\s*[:=]\s*['"]?([A-Za-z0-9_/+=-]{20,512})['"]?(?=\s|$)/g,
|
|
880
898
|
anchor: ["KEY", "TOKEN", "SECRET", "PASSWORD", "PWD"]
|
|
881
899
|
},
|
|
900
|
+
{
|
|
901
|
+
type: "json_credential_key",
|
|
902
|
+
// The JSON counterpart to `high_entropy_env`, and the pattern that the
|
|
903
|
+
// now-deleted `JSON_KEY_ANCHORS` list was written for. Without it those
|
|
904
|
+
// anchors only widened the cheap pre-scan — `hasCredentialAnchors` said
|
|
905
|
+
// "this text may hold a secret", every pattern then declined to match, and
|
|
906
|
+
// the value went out verbatim. `high_entropy_env` cannot cover these: it
|
|
907
|
+
// requires an UPPERCASE unquoted key (`API_KEY=…`), so `{"apiKey":"…"}`
|
|
908
|
+
// never matched.
|
|
909
|
+
//
|
|
910
|
+
// Tool results are routinely serialised as JSON, and a credential with no
|
|
911
|
+
// recognisable prefix (Azure, self-hosted gateways, Anthropic/Codex OAuth)
|
|
912
|
+
// has no other pattern that can catch it — this is the only thing standing
|
|
913
|
+
// between such a value and the session JSONL, chronicle, HQ broadcast and
|
|
914
|
+
// the model's own context.
|
|
915
|
+
//
|
|
916
|
+
// The key may carry a prefix (`"anthropicApiKey"`), but the credential word
|
|
917
|
+
// must END the key: `"tokenCount"` and `"maxTokens"` do not match, because
|
|
918
|
+
// the closing quote has to follow the word immediately.
|
|
919
|
+
// Value floor of 8 chars keeps enum-ish values (`"authorization":"none"`)
|
|
920
|
+
// out. Capture groups: 1=key + punctuation, 2=value, 3=closing quote.
|
|
921
|
+
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,
|
|
922
|
+
anchor: JSON_CREDENTIAL_KEY_ANCHORS
|
|
923
|
+
},
|
|
882
924
|
// ── Ported from packages/plugins credential-patterns.ts (WS-034) ─────────
|
|
883
925
|
// The plugin runtime carried 37 patterns while this scrubber — the one that
|
|
884
926
|
// guards session JSONL, chronicle, HQ broadcast, WebUI events and the auth
|
|
@@ -961,9 +1003,12 @@ var PATTERNS = [
|
|
|
961
1003
|
anchor: "GOCSPX-"
|
|
962
1004
|
}
|
|
963
1005
|
];
|
|
964
|
-
var SIMPLE_PATTERNS = PATTERNS.filter(
|
|
1006
|
+
var SIMPLE_PATTERNS = PATTERNS.filter(
|
|
1007
|
+
(p) => p.type !== "high_entropy_env" && p.type !== "json_credential_key"
|
|
1008
|
+
);
|
|
965
1009
|
var COMBINED_REGEX = new RegExp(SIMPLE_PATTERNS.map((p) => `(${p.regex.source})`).join("|"), "g");
|
|
966
1010
|
var HIGH_ENTROPY_REGEX = PATTERNS.find((p) => p.type === "high_entropy_env").regex;
|
|
1011
|
+
var JSON_CREDENTIAL_REGEX = PATTERNS.find((p) => p.type === "json_credential_key").regex;
|
|
967
1012
|
var COMBINED_REPLACEMENTS = SIMPLE_PATTERNS.map((p) => `[REDACTED:${p.type}]`);
|
|
968
1013
|
var SCRUB_CHUNK_BYTES = 64 * 1024;
|
|
969
1014
|
var SCRUB_OVERLAP_BYTES = 1024;
|
|
@@ -974,20 +1019,7 @@ var PATTERN_ANCHORS = [
|
|
|
974
1019
|
)
|
|
975
1020
|
)
|
|
976
1021
|
];
|
|
977
|
-
var
|
|
978
|
-
'"apiKey"',
|
|
979
|
-
'"api_key"',
|
|
980
|
-
'"token"',
|
|
981
|
-
'"secret"',
|
|
982
|
-
'"password"',
|
|
983
|
-
'"authorization"',
|
|
984
|
-
'"bearer"',
|
|
985
|
-
'"private_key"',
|
|
986
|
-
'"access_token"',
|
|
987
|
-
'"refresh_token"',
|
|
988
|
-
'"client_secret"'
|
|
989
|
-
];
|
|
990
|
-
var ALL_ANCHORS = [...PATTERN_ANCHORS, ...JSON_KEY_ANCHORS];
|
|
1022
|
+
var ALL_ANCHORS = PATTERN_ANCHORS;
|
|
991
1023
|
function hasCredentialAnchors(text) {
|
|
992
1024
|
for (const anchor of ALL_ANCHORS) {
|
|
993
1025
|
if (text.includes(anchor)) return true;
|
|
@@ -1036,6 +1068,9 @@ var DefaultSecretScrubber = class {
|
|
|
1036
1068
|
out = out.replace(HIGH_ENTROPY_REGEX, (_match, lead, key, _value) => {
|
|
1037
1069
|
return `${lead}${key}=[REDACTED:high_entropy_env]`;
|
|
1038
1070
|
});
|
|
1071
|
+
out = out.replace(JSON_CREDENTIAL_REGEX, (_match, keyPrefix, _value, closingQuote) => {
|
|
1072
|
+
return `${keyPrefix}[REDACTED:json_credential_key]${closingQuote}`;
|
|
1073
|
+
});
|
|
1039
1074
|
return out;
|
|
1040
1075
|
}
|
|
1041
1076
|
/**
|
|
@@ -1198,6 +1233,10 @@ function isPidAlive(pid) {
|
|
|
1198
1233
|
}
|
|
1199
1234
|
}
|
|
1200
1235
|
|
|
1236
|
+
// src/session-catalog/store-rebuild.ts
|
|
1237
|
+
import * as fs4 from "node:fs";
|
|
1238
|
+
import * as path4 from "node:path";
|
|
1239
|
+
|
|
1201
1240
|
// src/utils/session-scoped-path.ts
|
|
1202
1241
|
var SESSION_SIDECAR_JSONL_SUFFIXES = [
|
|
1203
1242
|
".replay.jsonl",
|
|
@@ -1214,7 +1253,8 @@ function isSessionTranscriptFileName(name) {
|
|
|
1214
1253
|
return !SESSION_SIDECAR_JSONL_SUFFIXES.some((suffix) => name.endsWith(suffix));
|
|
1215
1254
|
}
|
|
1216
1255
|
|
|
1217
|
-
// src/session-catalog/store.ts
|
|
1256
|
+
// src/session-catalog/store-schema.ts
|
|
1257
|
+
import { createHash as createHash2, timingSafeEqual } from "node:crypto";
|
|
1218
1258
|
var SCHEMA_VERSION = 1;
|
|
1219
1259
|
var MAX_LEASE_MS = 12e4;
|
|
1220
1260
|
var MAX_RESERVATION_MS = 6e4;
|
|
@@ -1259,12 +1299,192 @@ function boundPresenceValue(value, depth) {
|
|
|
1259
1299
|
}
|
|
1260
1300
|
return result;
|
|
1261
1301
|
}
|
|
1302
|
+
function configureCatalogDatabase(db) {
|
|
1303
|
+
db.exec(
|
|
1304
|
+
"PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON; PRAGMA busy_timeout=5000; PRAGMA synchronous=NORMAL;"
|
|
1305
|
+
);
|
|
1306
|
+
}
|
|
1307
|
+
function initializeCatalogSchema(db) {
|
|
1308
|
+
db.exec(`
|
|
1309
|
+
CREATE TABLE IF NOT EXISTS catalog_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL);
|
|
1310
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
1311
|
+
session_id TEXT PRIMARY KEY,
|
|
1312
|
+
transcript_relative_path TEXT NOT NULL,
|
|
1313
|
+
summary_relative_path TEXT NOT NULL,
|
|
1314
|
+
summary_json TEXT NOT NULL,
|
|
1315
|
+
transcript_size INTEGER NOT NULL DEFAULT 0,
|
|
1316
|
+
transcript_mtime_ms REAL NOT NULL DEFAULT 0,
|
|
1317
|
+
summary_revision INTEGER NOT NULL DEFAULT 1,
|
|
1318
|
+
indexed_at TEXT NOT NULL,
|
|
1319
|
+
damaged INTEGER NOT NULL DEFAULT 0
|
|
1320
|
+
);
|
|
1321
|
+
CREATE TABLE IF NOT EXISTS session_leases (
|
|
1322
|
+
session_id TEXT PRIMARY KEY,
|
|
1323
|
+
lease_id TEXT NOT NULL UNIQUE,
|
|
1324
|
+
lease_secret_hash TEXT NOT NULL,
|
|
1325
|
+
owner_instance_id TEXT NOT NULL,
|
|
1326
|
+
owner_pid INTEGER NOT NULL,
|
|
1327
|
+
owner_started_at TEXT NOT NULL,
|
|
1328
|
+
entry_json TEXT NOT NULL,
|
|
1329
|
+
agent_revision INTEGER NOT NULL DEFAULT 0,
|
|
1330
|
+
status TEXT NOT NULL,
|
|
1331
|
+
last_heartbeat_at INTEGER NOT NULL,
|
|
1332
|
+
lease_expires_at INTEGER NOT NULL
|
|
1333
|
+
);
|
|
1334
|
+
CREATE TABLE IF NOT EXISTS resume_reservations (
|
|
1335
|
+
reservation_id TEXT PRIMARY KEY,
|
|
1336
|
+
target_session_id TEXT NOT NULL UNIQUE,
|
|
1337
|
+
requester_instance_id TEXT NOT NULL,
|
|
1338
|
+
current_session_id TEXT,
|
|
1339
|
+
created_at INTEGER NOT NULL,
|
|
1340
|
+
expires_at INTEGER NOT NULL
|
|
1341
|
+
);
|
|
1342
|
+
CREATE TABLE IF NOT EXISTS maintenance_leases (
|
|
1343
|
+
session_id TEXT PRIMARY KEY,
|
|
1344
|
+
operation TEXT NOT NULL,
|
|
1345
|
+
holder_id TEXT NOT NULL,
|
|
1346
|
+
lease_id TEXT NOT NULL UNIQUE,
|
|
1347
|
+
acquired_at INTEGER NOT NULL,
|
|
1348
|
+
expires_at INTEGER NOT NULL
|
|
1349
|
+
);
|
|
1350
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_activity ON sessions(indexed_at DESC);
|
|
1351
|
+
CREATE INDEX IF NOT EXISTS idx_leases_expiry ON session_leases(lease_expires_at);
|
|
1352
|
+
CREATE INDEX IF NOT EXISTS idx_reservations_expiry ON resume_reservations(expires_at);
|
|
1353
|
+
CREATE INDEX IF NOT EXISTS idx_maintenance_expiry ON maintenance_leases(expires_at);
|
|
1354
|
+
`);
|
|
1355
|
+
db.prepare("INSERT INTO catalog_meta(key,value) VALUES (?,?) ON CONFLICT(key) DO NOTHING").run("schema_version", String(SCHEMA_VERSION));
|
|
1356
|
+
db.prepare("INSERT INTO catalog_meta(key,value) VALUES (?,?) ON CONFLICT(key) DO NOTHING").run("generation", "0");
|
|
1357
|
+
const row = db.prepare("SELECT value FROM catalog_meta WHERE key=?").get("schema_version");
|
|
1358
|
+
if (Number(row.value) !== SCHEMA_VERSION)
|
|
1359
|
+
throw new Error(`Unsupported session catalog schema ${row.value}`);
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
// src/session-catalog/store-rebuild.ts
|
|
1363
|
+
function walkSessionFiles(root, suffix) {
|
|
1364
|
+
const result = [];
|
|
1365
|
+
const visit = (dir) => {
|
|
1366
|
+
for (const entry of fs4.readdirSync(dir, { withFileTypes: true })) {
|
|
1367
|
+
if (entry.isDirectory()) {
|
|
1368
|
+
if (entry.name !== "_cas" && entry.name !== "_trash") visit(path4.join(dir, entry.name));
|
|
1369
|
+
} else if (entry.isFile() && entry.name.endsWith(suffix))
|
|
1370
|
+
result.push(path4.join(dir, entry.name));
|
|
1371
|
+
}
|
|
1372
|
+
};
|
|
1373
|
+
visit(root);
|
|
1374
|
+
return result;
|
|
1375
|
+
}
|
|
1376
|
+
function summarizeTranscriptFile(transcriptFilePath, id) {
|
|
1377
|
+
const lines = fs4.readFileSync(transcriptFilePath, "utf8").split(/\r?\n/);
|
|
1378
|
+
let start;
|
|
1379
|
+
let endedAt;
|
|
1380
|
+
let lastActivityAt;
|
|
1381
|
+
let messageCount = 0;
|
|
1382
|
+
let iterationCount = 0;
|
|
1383
|
+
let toolCallCount = 0;
|
|
1384
|
+
let compactionCount = 0;
|
|
1385
|
+
let tokenTotal = 0;
|
|
1386
|
+
for (const line of lines) {
|
|
1387
|
+
if (!line) continue;
|
|
1388
|
+
let event;
|
|
1389
|
+
try {
|
|
1390
|
+
event = parseJson(line);
|
|
1391
|
+
} catch {
|
|
1392
|
+
continue;
|
|
1393
|
+
}
|
|
1394
|
+
lastActivityAt = event.ts;
|
|
1395
|
+
if (event.type === "session_start") start = event;
|
|
1396
|
+
if (event.type === "session_end") endedAt = event.ts;
|
|
1397
|
+
if (event.type === "message_appended" && (event.message.role === "user" || event.message.role === "assistant"))
|
|
1398
|
+
messageCount++;
|
|
1399
|
+
if (event.type === "llm_response") {
|
|
1400
|
+
iterationCount++;
|
|
1401
|
+
tokenTotal += event.usage.input + event.usage.output + (event.usage.cacheRead ?? 0) + (event.usage.cacheWrite ?? 0);
|
|
1402
|
+
}
|
|
1403
|
+
if (event.type === "tool_call_end") toolCallCount++;
|
|
1404
|
+
if (event.type === "compaction") compactionCount++;
|
|
1405
|
+
}
|
|
1406
|
+
if (!start) throw new Error("missing session_start");
|
|
1407
|
+
return {
|
|
1408
|
+
id,
|
|
1409
|
+
title: id,
|
|
1410
|
+
startedAt: start.ts,
|
|
1411
|
+
...endedAt ? { endedAt } : {},
|
|
1412
|
+
model: start.model,
|
|
1413
|
+
provider: start.provider,
|
|
1414
|
+
tokenTotal,
|
|
1415
|
+
...lastActivityAt ? { lastActivityAt } : {},
|
|
1416
|
+
messageCount,
|
|
1417
|
+
iterationCount,
|
|
1418
|
+
toolCallCount,
|
|
1419
|
+
compactionCount
|
|
1420
|
+
};
|
|
1421
|
+
}
|
|
1422
|
+
function rebuildCatalogIndex(db, sessionsDir, containedPathFn, transactionFn, bumpGenerationFn) {
|
|
1423
|
+
const summaries = walkSessionFiles(sessionsDir, ".summary.json");
|
|
1424
|
+
const transcripts = walkSessionFiles(sessionsDir, ".jsonl").filter(
|
|
1425
|
+
(file) => isSessionTranscriptFileName(path4.basename(file))
|
|
1426
|
+
);
|
|
1427
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1428
|
+
for (const file of [...summaries, ...transcripts]) {
|
|
1429
|
+
const relative2 = path4.relative(sessionsDir, file).replaceAll("\\", "/");
|
|
1430
|
+
ids.add(relative2.replace(/\.summary\.json$|\.jsonl$/, ""));
|
|
1431
|
+
}
|
|
1432
|
+
let indexed = 0;
|
|
1433
|
+
let damaged = 0;
|
|
1434
|
+
transactionFn(() => {
|
|
1435
|
+
db.prepare("DELETE FROM sessions").run();
|
|
1436
|
+
for (const id of ids) {
|
|
1437
|
+
try {
|
|
1438
|
+
const summaryFile = containedPathFn(`${id}.summary.json`);
|
|
1439
|
+
const summary = fs4.existsSync(summaryFile) ? parseJson(fs4.readFileSync(summaryFile, "utf8")) : summarizeTranscriptFile(containedPathFn(`${id}.jsonl`), id);
|
|
1440
|
+
if (!summary || summary.id !== id) throw new Error("summary identity mismatch");
|
|
1441
|
+
const transcript = containedPathFn(`${id}.jsonl`);
|
|
1442
|
+
const stat = fs4.existsSync(transcript) ? fs4.statSync(transcript) : void 0;
|
|
1443
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1444
|
+
db.prepare(
|
|
1445
|
+
"INSERT INTO sessions(session_id,transcript_relative_path,summary_relative_path,summary_json,transcript_size,transcript_mtime_ms,summary_revision,indexed_at,damaged) VALUES (?,?,?,?,?,?,?,?,0)"
|
|
1446
|
+
).run(
|
|
1447
|
+
id,
|
|
1448
|
+
`${id}.jsonl`,
|
|
1449
|
+
`${id}.summary.json`,
|
|
1450
|
+
JSON.stringify(summary),
|
|
1451
|
+
stat?.size ?? 0,
|
|
1452
|
+
stat?.mtimeMs ?? 0,
|
|
1453
|
+
1,
|
|
1454
|
+
now
|
|
1455
|
+
);
|
|
1456
|
+
indexed++;
|
|
1457
|
+
} catch {
|
|
1458
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1459
|
+
const fallback = {
|
|
1460
|
+
id,
|
|
1461
|
+
title: id,
|
|
1462
|
+
startedAt: now,
|
|
1463
|
+
model: "",
|
|
1464
|
+
provider: "",
|
|
1465
|
+
tokenTotal: 0
|
|
1466
|
+
};
|
|
1467
|
+
db.prepare(
|
|
1468
|
+
"INSERT INTO sessions(session_id,transcript_relative_path,summary_relative_path,summary_json,summary_revision,indexed_at,damaged) VALUES (?,?,?,?,1,?,1)"
|
|
1469
|
+
).run(id, `${id}.jsonl`, `${id}.summary.json`, JSON.stringify(fallback), now);
|
|
1470
|
+
damaged++;
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
db.prepare(
|
|
1474
|
+
"INSERT INTO catalog_meta(key,value) VALUES ('last_reconciliation',?) ON CONFLICT(key) DO UPDATE SET value=excluded.value"
|
|
1475
|
+
).run((/* @__PURE__ */ new Date()).toISOString());
|
|
1476
|
+
bumpGenerationFn();
|
|
1477
|
+
});
|
|
1478
|
+
return { indexed, damaged };
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
// src/session-catalog/store.ts
|
|
1262
1482
|
var SessionCatalogStore = class {
|
|
1263
1483
|
constructor(projectDir) {
|
|
1264
1484
|
this.projectDir = projectDir;
|
|
1265
|
-
this.sessionsDir =
|
|
1266
|
-
|
|
1267
|
-
this.databasePath =
|
|
1485
|
+
this.sessionsDir = path5.join(projectDir, "sessions");
|
|
1486
|
+
fs5.mkdirSync(this.sessionsDir, { recursive: true, mode: 448 });
|
|
1487
|
+
this.databasePath = path5.join(this.sessionsDir, "catalog.sqlite");
|
|
1268
1488
|
const Database = loadDatabaseSync();
|
|
1269
1489
|
this.db = new Database(this.databasePath);
|
|
1270
1490
|
try {
|
|
@@ -1281,12 +1501,12 @@ var SessionCatalogStore = class {
|
|
|
1281
1501
|
this.db.close();
|
|
1282
1502
|
const quarantine = `${this.databasePath}.corrupt-${Date.now()}`;
|
|
1283
1503
|
try {
|
|
1284
|
-
|
|
1504
|
+
fs5.renameSync(this.databasePath, quarantine);
|
|
1285
1505
|
} catch {
|
|
1286
1506
|
}
|
|
1287
1507
|
for (const suffix of ["-wal", "-shm"]) {
|
|
1288
1508
|
try {
|
|
1289
|
-
|
|
1509
|
+
fs5.renameSync(`${this.databasePath}${suffix}`, `${quarantine}${suffix}`);
|
|
1290
1510
|
} catch {
|
|
1291
1511
|
}
|
|
1292
1512
|
}
|
|
@@ -1298,7 +1518,7 @@ var SessionCatalogStore = class {
|
|
|
1298
1518
|
const rowCount = Number(
|
|
1299
1519
|
this.db.prepare("SELECT COUNT(*) AS count FROM sessions").get().count
|
|
1300
1520
|
);
|
|
1301
|
-
if (rowCount === 0 &&
|
|
1521
|
+
if (rowCount === 0 && walkSessionFiles(this.sessionsDir, ".jsonl").some((file) => !file.endsWith("_index.jsonl"))) {
|
|
1302
1522
|
this.rebuildCatalog();
|
|
1303
1523
|
}
|
|
1304
1524
|
}
|
|
@@ -1308,66 +1528,13 @@ var SessionCatalogStore = class {
|
|
|
1308
1528
|
db;
|
|
1309
1529
|
scrubber = new DefaultSecretScrubber();
|
|
1310
1530
|
configureDatabase() {
|
|
1311
|
-
this.db
|
|
1312
|
-
"PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON; PRAGMA busy_timeout=5000; PRAGMA synchronous=NORMAL;"
|
|
1313
|
-
);
|
|
1531
|
+
configureCatalogDatabase(this.db);
|
|
1314
1532
|
}
|
|
1315
1533
|
close() {
|
|
1316
1534
|
this.db.close();
|
|
1317
1535
|
}
|
|
1318
1536
|
initialize() {
|
|
1319
|
-
this.db
|
|
1320
|
-
CREATE TABLE IF NOT EXISTS catalog_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL);
|
|
1321
|
-
CREATE TABLE IF NOT EXISTS sessions (
|
|
1322
|
-
session_id TEXT PRIMARY KEY,
|
|
1323
|
-
transcript_relative_path TEXT NOT NULL,
|
|
1324
|
-
summary_relative_path TEXT NOT NULL,
|
|
1325
|
-
summary_json TEXT NOT NULL,
|
|
1326
|
-
transcript_size INTEGER NOT NULL DEFAULT 0,
|
|
1327
|
-
transcript_mtime_ms REAL NOT NULL DEFAULT 0,
|
|
1328
|
-
summary_revision INTEGER NOT NULL DEFAULT 1,
|
|
1329
|
-
indexed_at TEXT NOT NULL,
|
|
1330
|
-
damaged INTEGER NOT NULL DEFAULT 0
|
|
1331
|
-
);
|
|
1332
|
-
CREATE TABLE IF NOT EXISTS session_leases (
|
|
1333
|
-
session_id TEXT PRIMARY KEY,
|
|
1334
|
-
lease_id TEXT NOT NULL UNIQUE,
|
|
1335
|
-
lease_secret_hash TEXT NOT NULL,
|
|
1336
|
-
owner_instance_id TEXT NOT NULL,
|
|
1337
|
-
owner_pid INTEGER NOT NULL,
|
|
1338
|
-
owner_started_at TEXT NOT NULL,
|
|
1339
|
-
entry_json TEXT NOT NULL,
|
|
1340
|
-
agent_revision INTEGER NOT NULL DEFAULT 0,
|
|
1341
|
-
status TEXT NOT NULL,
|
|
1342
|
-
last_heartbeat_at INTEGER NOT NULL,
|
|
1343
|
-
lease_expires_at INTEGER NOT NULL
|
|
1344
|
-
);
|
|
1345
|
-
CREATE TABLE IF NOT EXISTS resume_reservations (
|
|
1346
|
-
reservation_id TEXT PRIMARY KEY,
|
|
1347
|
-
target_session_id TEXT NOT NULL UNIQUE,
|
|
1348
|
-
requester_instance_id TEXT NOT NULL,
|
|
1349
|
-
current_session_id TEXT,
|
|
1350
|
-
created_at INTEGER NOT NULL,
|
|
1351
|
-
expires_at INTEGER NOT NULL
|
|
1352
|
-
);
|
|
1353
|
-
CREATE TABLE IF NOT EXISTS maintenance_leases (
|
|
1354
|
-
session_id TEXT PRIMARY KEY,
|
|
1355
|
-
operation TEXT NOT NULL,
|
|
1356
|
-
holder_id TEXT NOT NULL,
|
|
1357
|
-
lease_id TEXT NOT NULL UNIQUE,
|
|
1358
|
-
acquired_at INTEGER NOT NULL,
|
|
1359
|
-
expires_at INTEGER NOT NULL
|
|
1360
|
-
);
|
|
1361
|
-
CREATE INDEX IF NOT EXISTS idx_sessions_activity ON sessions(indexed_at DESC);
|
|
1362
|
-
CREATE INDEX IF NOT EXISTS idx_leases_expiry ON session_leases(lease_expires_at);
|
|
1363
|
-
CREATE INDEX IF NOT EXISTS idx_reservations_expiry ON resume_reservations(expires_at);
|
|
1364
|
-
CREATE INDEX IF NOT EXISTS idx_maintenance_expiry ON maintenance_leases(expires_at);
|
|
1365
|
-
`);
|
|
1366
|
-
this.db.prepare("INSERT INTO catalog_meta(key,value) VALUES (?,?) ON CONFLICT(key) DO NOTHING").run("schema_version", String(SCHEMA_VERSION));
|
|
1367
|
-
this.db.prepare("INSERT INTO catalog_meta(key,value) VALUES (?,?) ON CONFLICT(key) DO NOTHING").run("generation", "0");
|
|
1368
|
-
const row = this.db.prepare("SELECT value FROM catalog_meta WHERE key=?").get("schema_version");
|
|
1369
|
-
if (Number(row.value) !== SCHEMA_VERSION)
|
|
1370
|
-
throw new Error(`Unsupported session catalog schema ${row.value}`);
|
|
1537
|
+
initializeCatalogSchema(this.db);
|
|
1371
1538
|
}
|
|
1372
1539
|
transaction(run) {
|
|
1373
1540
|
this.db.exec("BEGIN IMMEDIATE");
|
|
@@ -1408,21 +1575,6 @@ var SessionCatalogStore = class {
|
|
|
1408
1575
|
leaseRow(sessionId) {
|
|
1409
1576
|
return this.db.prepare("SELECT * FROM session_leases WHERE session_id=?").get(sessionId);
|
|
1410
1577
|
}
|
|
1411
|
-
/**
|
|
1412
|
-
* True when `sessionId` has a live `session_leases` row owned by a
|
|
1413
|
-
* different OS process than the *caller*. Used by
|
|
1414
|
-
* `acquireMaintenance` to distinguish "another running wstack" (which
|
|
1415
|
-
* must be fenced off) from "this very TUI owning its own session"
|
|
1416
|
-
* (which is allowed to claim a non-destructive maintenance lease on
|
|
1417
|
-
* its own transcript).
|
|
1418
|
-
*
|
|
1419
|
-
* `callerPid` must be the pid of the process that asked for the lease,
|
|
1420
|
-
* NOT `process.pid`. In the built runtime this store lives inside the
|
|
1421
|
-
* detached project-catalog daemon, so `process.pid` is the daemon's and
|
|
1422
|
-
* would mark every lease — including the caller's own session — foreign,
|
|
1423
|
-
* making `/clear` and `/rewind` fail with "Session … is live". Callers
|
|
1424
|
-
* that run the store in-process may omit it.
|
|
1425
|
-
*/
|
|
1426
1578
|
foreignLiveLease(sessionId, callerPid = process.pid) {
|
|
1427
1579
|
const live = this.leaseRow(sessionId);
|
|
1428
1580
|
return Boolean(live) && live.owner_pid !== callerPid;
|
|
@@ -1512,7 +1664,7 @@ var SessionCatalogStore = class {
|
|
|
1512
1664
|
if (this.maintenanceExists(targetSessionId))
|
|
1513
1665
|
throw conflict(`Session ${targetSessionId} is under maintenance`);
|
|
1514
1666
|
const catalog = this.db.prepare("SELECT 1 AS yes FROM sessions WHERE session_id=?").get(targetSessionId);
|
|
1515
|
-
if (!catalog && !
|
|
1667
|
+
if (!catalog && !fs5.existsSync(this.containedPath(`${targetSessionId}.jsonl`)))
|
|
1516
1668
|
throw new Error(`Session not found: ${targetSessionId}`);
|
|
1517
1669
|
const reservationId = randomUUID2();
|
|
1518
1670
|
const now = Date.now();
|
|
@@ -1640,9 +1792,9 @@ var SessionCatalogStore = class {
|
|
|
1640
1792
|
}
|
|
1641
1793
|
containedPath(relative2) {
|
|
1642
1794
|
assertId(relative2.replace(/\.(jsonl|summary\.json)$/, ""), "session path");
|
|
1643
|
-
const root =
|
|
1644
|
-
const candidate =
|
|
1645
|
-
const prefix = `${root}${
|
|
1795
|
+
const root = path5.resolve(this.sessionsDir);
|
|
1796
|
+
const candidate = path5.resolve(root, relative2);
|
|
1797
|
+
const prefix = `${root}${path5.sep}`;
|
|
1646
1798
|
if (candidate !== root && !(process.platform === "win32" ? candidate.toLowerCase().startsWith(prefix.toLowerCase()) : candidate.startsWith(prefix)))
|
|
1647
1799
|
throw new TypeError("Session path escapes sessions directory");
|
|
1648
1800
|
return candidate;
|
|
@@ -1658,7 +1810,7 @@ var SessionCatalogStore = class {
|
|
|
1658
1810
|
transcriptRelativePath = normalizedTranscript;
|
|
1659
1811
|
summaryRelativePath = normalizedSummary;
|
|
1660
1812
|
const transcript = this.containedPath(transcriptRelativePath);
|
|
1661
|
-
const stat =
|
|
1813
|
+
const stat = fs5.existsSync(transcript) ? fs5.statSync(transcript) : void 0;
|
|
1662
1814
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1663
1815
|
return this.transaction(() => {
|
|
1664
1816
|
const prior = this.db.prepare("SELECT summary_revision FROM sessions WHERE session_id=?").get(summary.id);
|
|
@@ -1781,7 +1933,7 @@ var SessionCatalogStore = class {
|
|
|
1781
1933
|
if (trimmed) summary.name = this.scrubber.scrub(trimmed).slice(0, 500);
|
|
1782
1934
|
else delete summary.name;
|
|
1783
1935
|
const summaryPath = this.containedPath(current.summaryRelativePath);
|
|
1784
|
-
|
|
1936
|
+
fs5.mkdirSync(path5.dirname(summaryPath), { recursive: true, mode: 448 });
|
|
1785
1937
|
await atomicWrite(summaryPath, `${JSON.stringify(summary)}
|
|
1786
1938
|
`, { mode: 384 });
|
|
1787
1939
|
try {
|
|
@@ -1841,16 +1993,16 @@ var SessionCatalogStore = class {
|
|
|
1841
1993
|
this.containedPath(`${sessionId}.plan.json`),
|
|
1842
1994
|
this.containedPath(`${sessionId}.tasks.json`),
|
|
1843
1995
|
this.containedPath(`${sessionId}.todos.json`),
|
|
1844
|
-
|
|
1996
|
+
path5.join(path5.dirname(transcript), path5.basename(sessionId))
|
|
1845
1997
|
];
|
|
1846
|
-
const trashRoot =
|
|
1847
|
-
|
|
1998
|
+
const trashRoot = path5.join(this.sessionsDir, "_trash", lease.leaseId);
|
|
1999
|
+
fs5.mkdirSync(trashRoot, { recursive: true, mode: 448 });
|
|
1848
2000
|
const moved = [];
|
|
1849
2001
|
try {
|
|
1850
2002
|
artifacts.forEach((artifact, index) => {
|
|
1851
|
-
if (!
|
|
1852
|
-
const target =
|
|
1853
|
-
|
|
2003
|
+
if (!fs5.existsSync(artifact)) return;
|
|
2004
|
+
const target = path5.join(trashRoot, `${index}-${path5.basename(artifact)}`);
|
|
2005
|
+
fs5.renameSync(artifact, target);
|
|
1854
2006
|
moved.push({ from: artifact, to: target });
|
|
1855
2007
|
});
|
|
1856
2008
|
this.transaction(() => {
|
|
@@ -1865,17 +2017,17 @@ var SessionCatalogStore = class {
|
|
|
1865
2017
|
} catch (error) {
|
|
1866
2018
|
for (const item of moved.reverse()) {
|
|
1867
2019
|
try {
|
|
1868
|
-
|
|
1869
|
-
|
|
2020
|
+
fs5.mkdirSync(path5.dirname(item.from), { recursive: true, mode: 448 });
|
|
2021
|
+
fs5.renameSync(item.to, item.from);
|
|
1870
2022
|
} catch {
|
|
1871
2023
|
}
|
|
1872
2024
|
}
|
|
1873
2025
|
throw error;
|
|
1874
2026
|
}
|
|
1875
2027
|
try {
|
|
1876
|
-
|
|
1877
|
-
const trashParent =
|
|
1878
|
-
if (
|
|
2028
|
+
fs5.rmSync(trashRoot, { recursive: true, force: true });
|
|
2029
|
+
const trashParent = path5.dirname(trashRoot);
|
|
2030
|
+
if (fs5.readdirSync(trashParent).length === 0) fs5.rmdirSync(trashParent);
|
|
1879
2031
|
} catch {
|
|
1880
2032
|
}
|
|
1881
2033
|
}
|
|
@@ -1896,122 +2048,13 @@ var SessionCatalogStore = class {
|
|
|
1896
2048
|
return deleted;
|
|
1897
2049
|
}
|
|
1898
2050
|
rebuildCatalog() {
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
2051
|
+
return rebuildCatalogIndex(
|
|
2052
|
+
this.db,
|
|
2053
|
+
this.sessionsDir,
|
|
2054
|
+
(rel) => this.containedPath(rel),
|
|
2055
|
+
(run) => this.transaction(run),
|
|
2056
|
+
() => this.bumpGeneration()
|
|
1902
2057
|
);
|
|
1903
|
-
const ids = /* @__PURE__ */ new Set();
|
|
1904
|
-
for (const file of [...summaries, ...transcripts]) {
|
|
1905
|
-
const relative2 = path4.relative(this.sessionsDir, file).replaceAll("\\", "/");
|
|
1906
|
-
ids.add(relative2.replace(/\.summary\.json$|\.jsonl$/, ""));
|
|
1907
|
-
}
|
|
1908
|
-
let indexed = 0;
|
|
1909
|
-
let damaged = 0;
|
|
1910
|
-
this.transaction(() => {
|
|
1911
|
-
this.db.prepare("DELETE FROM sessions").run();
|
|
1912
|
-
for (const id of ids) {
|
|
1913
|
-
try {
|
|
1914
|
-
const summaryFile = this.containedPath(`${id}.summary.json`);
|
|
1915
|
-
const summary = fs4.existsSync(summaryFile) ? parseJson(fs4.readFileSync(summaryFile, "utf8")) : this.summarizeTranscript(id);
|
|
1916
|
-
if (!summary || summary.id !== id) throw new Error("summary identity mismatch");
|
|
1917
|
-
const transcript = this.containedPath(`${id}.jsonl`);
|
|
1918
|
-
const stat = fs4.existsSync(transcript) ? fs4.statSync(transcript) : void 0;
|
|
1919
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1920
|
-
this.db.prepare(
|
|
1921
|
-
"INSERT INTO sessions(session_id,transcript_relative_path,summary_relative_path,summary_json,transcript_size,transcript_mtime_ms,summary_revision,indexed_at,damaged) VALUES (?,?,?,?,?,?,?,?,0)"
|
|
1922
|
-
).run(
|
|
1923
|
-
id,
|
|
1924
|
-
`${id}.jsonl`,
|
|
1925
|
-
`${id}.summary.json`,
|
|
1926
|
-
JSON.stringify(summary),
|
|
1927
|
-
stat?.size ?? 0,
|
|
1928
|
-
stat?.mtimeMs ?? 0,
|
|
1929
|
-
1,
|
|
1930
|
-
now
|
|
1931
|
-
);
|
|
1932
|
-
indexed++;
|
|
1933
|
-
} catch {
|
|
1934
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1935
|
-
const fallback = {
|
|
1936
|
-
id,
|
|
1937
|
-
title: id,
|
|
1938
|
-
startedAt: now,
|
|
1939
|
-
model: "",
|
|
1940
|
-
provider: "",
|
|
1941
|
-
tokenTotal: 0
|
|
1942
|
-
};
|
|
1943
|
-
this.db.prepare(
|
|
1944
|
-
"INSERT INTO sessions(session_id,transcript_relative_path,summary_relative_path,summary_json,summary_revision,indexed_at,damaged) VALUES (?,?,?,?,1,?,1)"
|
|
1945
|
-
).run(id, `${id}.jsonl`, `${id}.summary.json`, JSON.stringify(fallback), now);
|
|
1946
|
-
damaged++;
|
|
1947
|
-
}
|
|
1948
|
-
}
|
|
1949
|
-
this.db.prepare(
|
|
1950
|
-
"INSERT INTO catalog_meta(key,value) VALUES ('last_reconciliation',?) ON CONFLICT(key) DO UPDATE SET value=excluded.value"
|
|
1951
|
-
).run((/* @__PURE__ */ new Date()).toISOString());
|
|
1952
|
-
this.bumpGeneration();
|
|
1953
|
-
});
|
|
1954
|
-
return { indexed, damaged };
|
|
1955
|
-
}
|
|
1956
|
-
walkFiles(root, suffix) {
|
|
1957
|
-
const result = [];
|
|
1958
|
-
const visit = (dir) => {
|
|
1959
|
-
for (const entry of fs4.readdirSync(dir, { withFileTypes: true })) {
|
|
1960
|
-
if (entry.isDirectory()) {
|
|
1961
|
-
if (entry.name !== "_cas" && entry.name !== "_trash") visit(path4.join(dir, entry.name));
|
|
1962
|
-
} else if (entry.isFile() && entry.name.endsWith(suffix))
|
|
1963
|
-
result.push(path4.join(dir, entry.name));
|
|
1964
|
-
}
|
|
1965
|
-
};
|
|
1966
|
-
visit(root);
|
|
1967
|
-
return result;
|
|
1968
|
-
}
|
|
1969
|
-
summarizeTranscript(id) {
|
|
1970
|
-
const file = this.containedPath(`${id}.jsonl`);
|
|
1971
|
-
const lines = fs4.readFileSync(file, "utf8").split(/\r?\n/);
|
|
1972
|
-
let start;
|
|
1973
|
-
let endedAt;
|
|
1974
|
-
let lastActivityAt;
|
|
1975
|
-
let messageCount = 0;
|
|
1976
|
-
let iterationCount = 0;
|
|
1977
|
-
let toolCallCount = 0;
|
|
1978
|
-
let compactionCount = 0;
|
|
1979
|
-
let tokenTotal = 0;
|
|
1980
|
-
for (const line of lines) {
|
|
1981
|
-
if (!line) continue;
|
|
1982
|
-
let event;
|
|
1983
|
-
try {
|
|
1984
|
-
event = parseJson(line);
|
|
1985
|
-
} catch {
|
|
1986
|
-
continue;
|
|
1987
|
-
}
|
|
1988
|
-
lastActivityAt = event.ts;
|
|
1989
|
-
if (event.type === "session_start") start = event;
|
|
1990
|
-
if (event.type === "session_end") endedAt = event.ts;
|
|
1991
|
-
if (event.type === "message_appended" && (event.message.role === "user" || event.message.role === "assistant"))
|
|
1992
|
-
messageCount++;
|
|
1993
|
-
if (event.type === "llm_response") {
|
|
1994
|
-
iterationCount++;
|
|
1995
|
-
tokenTotal += event.usage.input + event.usage.output + (event.usage.cacheRead ?? 0) + (event.usage.cacheWrite ?? 0);
|
|
1996
|
-
}
|
|
1997
|
-
if (event.type === "tool_call_end") toolCallCount++;
|
|
1998
|
-
if (event.type === "compaction") compactionCount++;
|
|
1999
|
-
}
|
|
2000
|
-
if (!start) throw new Error("missing session_start");
|
|
2001
|
-
return {
|
|
2002
|
-
id,
|
|
2003
|
-
title: id,
|
|
2004
|
-
startedAt: start.ts,
|
|
2005
|
-
...endedAt ? { endedAt } : {},
|
|
2006
|
-
model: start.model,
|
|
2007
|
-
provider: start.provider,
|
|
2008
|
-
tokenTotal,
|
|
2009
|
-
...lastActivityAt ? { lastActivityAt } : {},
|
|
2010
|
-
messageCount,
|
|
2011
|
-
iterationCount,
|
|
2012
|
-
toolCallCount,
|
|
2013
|
-
compactionCount
|
|
2014
|
-
};
|
|
2015
2058
|
}
|
|
2016
2059
|
health(base) {
|
|
2017
2060
|
this.reapExpired();
|