@wrongstack/core 0.306.4 → 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 +6011 -5894
- package/dist/coordination/mail-tools.d.ts +1 -1
- 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/fallback-doctor.d.ts +72 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.js +1159 -888
- package/dist/defaults/index.js +3579 -3545
- 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 -170
- 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.js +2415 -2443
- package/dist/execution/prompt-enhancer.js +11 -3
- 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 +8 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +7813 -7292
- package/dist/infrastructure/index.js +3 -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 +2590 -2561
- 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 +627 -652
- 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 +220 -212
- package/dist/session-catalog/project-server.js +227 -219
- 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/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 +2057 -1925
- package/dist/storage/orphan-lock-cleaner.d.ts +15 -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.d.ts +1 -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.js +5070 -5071
- package/dist/types/config/root.d.ts +7 -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/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";
|
|
@@ -1233,6 +1233,10 @@ function isPidAlive(pid) {
|
|
|
1233
1233
|
}
|
|
1234
1234
|
}
|
|
1235
1235
|
|
|
1236
|
+
// src/session-catalog/store-rebuild.ts
|
|
1237
|
+
import * as fs4 from "node:fs";
|
|
1238
|
+
import * as path4 from "node:path";
|
|
1239
|
+
|
|
1236
1240
|
// src/utils/session-scoped-path.ts
|
|
1237
1241
|
var SESSION_SIDECAR_JSONL_SUFFIXES = [
|
|
1238
1242
|
".replay.jsonl",
|
|
@@ -1249,7 +1253,8 @@ function isSessionTranscriptFileName(name) {
|
|
|
1249
1253
|
return !SESSION_SIDECAR_JSONL_SUFFIXES.some((suffix) => name.endsWith(suffix));
|
|
1250
1254
|
}
|
|
1251
1255
|
|
|
1252
|
-
// src/session-catalog/store.ts
|
|
1256
|
+
// src/session-catalog/store-schema.ts
|
|
1257
|
+
import { createHash as createHash2, timingSafeEqual } from "node:crypto";
|
|
1253
1258
|
var SCHEMA_VERSION = 1;
|
|
1254
1259
|
var MAX_LEASE_MS = 12e4;
|
|
1255
1260
|
var MAX_RESERVATION_MS = 6e4;
|
|
@@ -1294,12 +1299,192 @@ function boundPresenceValue(value, depth) {
|
|
|
1294
1299
|
}
|
|
1295
1300
|
return result;
|
|
1296
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
|
|
1297
1482
|
var SessionCatalogStore = class {
|
|
1298
1483
|
constructor(projectDir) {
|
|
1299
1484
|
this.projectDir = projectDir;
|
|
1300
|
-
this.sessionsDir =
|
|
1301
|
-
|
|
1302
|
-
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");
|
|
1303
1488
|
const Database = loadDatabaseSync();
|
|
1304
1489
|
this.db = new Database(this.databasePath);
|
|
1305
1490
|
try {
|
|
@@ -1316,12 +1501,12 @@ var SessionCatalogStore = class {
|
|
|
1316
1501
|
this.db.close();
|
|
1317
1502
|
const quarantine = `${this.databasePath}.corrupt-${Date.now()}`;
|
|
1318
1503
|
try {
|
|
1319
|
-
|
|
1504
|
+
fs5.renameSync(this.databasePath, quarantine);
|
|
1320
1505
|
} catch {
|
|
1321
1506
|
}
|
|
1322
1507
|
for (const suffix of ["-wal", "-shm"]) {
|
|
1323
1508
|
try {
|
|
1324
|
-
|
|
1509
|
+
fs5.renameSync(`${this.databasePath}${suffix}`, `${quarantine}${suffix}`);
|
|
1325
1510
|
} catch {
|
|
1326
1511
|
}
|
|
1327
1512
|
}
|
|
@@ -1333,7 +1518,7 @@ var SessionCatalogStore = class {
|
|
|
1333
1518
|
const rowCount = Number(
|
|
1334
1519
|
this.db.prepare("SELECT COUNT(*) AS count FROM sessions").get().count
|
|
1335
1520
|
);
|
|
1336
|
-
if (rowCount === 0 &&
|
|
1521
|
+
if (rowCount === 0 && walkSessionFiles(this.sessionsDir, ".jsonl").some((file) => !file.endsWith("_index.jsonl"))) {
|
|
1337
1522
|
this.rebuildCatalog();
|
|
1338
1523
|
}
|
|
1339
1524
|
}
|
|
@@ -1343,66 +1528,13 @@ var SessionCatalogStore = class {
|
|
|
1343
1528
|
db;
|
|
1344
1529
|
scrubber = new DefaultSecretScrubber();
|
|
1345
1530
|
configureDatabase() {
|
|
1346
|
-
this.db
|
|
1347
|
-
"PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON; PRAGMA busy_timeout=5000; PRAGMA synchronous=NORMAL;"
|
|
1348
|
-
);
|
|
1531
|
+
configureCatalogDatabase(this.db);
|
|
1349
1532
|
}
|
|
1350
1533
|
close() {
|
|
1351
1534
|
this.db.close();
|
|
1352
1535
|
}
|
|
1353
1536
|
initialize() {
|
|
1354
|
-
this.db
|
|
1355
|
-
CREATE TABLE IF NOT EXISTS catalog_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL);
|
|
1356
|
-
CREATE TABLE IF NOT EXISTS sessions (
|
|
1357
|
-
session_id TEXT PRIMARY KEY,
|
|
1358
|
-
transcript_relative_path TEXT NOT NULL,
|
|
1359
|
-
summary_relative_path TEXT NOT NULL,
|
|
1360
|
-
summary_json TEXT NOT NULL,
|
|
1361
|
-
transcript_size INTEGER NOT NULL DEFAULT 0,
|
|
1362
|
-
transcript_mtime_ms REAL NOT NULL DEFAULT 0,
|
|
1363
|
-
summary_revision INTEGER NOT NULL DEFAULT 1,
|
|
1364
|
-
indexed_at TEXT NOT NULL,
|
|
1365
|
-
damaged INTEGER NOT NULL DEFAULT 0
|
|
1366
|
-
);
|
|
1367
|
-
CREATE TABLE IF NOT EXISTS session_leases (
|
|
1368
|
-
session_id TEXT PRIMARY KEY,
|
|
1369
|
-
lease_id TEXT NOT NULL UNIQUE,
|
|
1370
|
-
lease_secret_hash TEXT NOT NULL,
|
|
1371
|
-
owner_instance_id TEXT NOT NULL,
|
|
1372
|
-
owner_pid INTEGER NOT NULL,
|
|
1373
|
-
owner_started_at TEXT NOT NULL,
|
|
1374
|
-
entry_json TEXT NOT NULL,
|
|
1375
|
-
agent_revision INTEGER NOT NULL DEFAULT 0,
|
|
1376
|
-
status TEXT NOT NULL,
|
|
1377
|
-
last_heartbeat_at INTEGER NOT NULL,
|
|
1378
|
-
lease_expires_at INTEGER NOT NULL
|
|
1379
|
-
);
|
|
1380
|
-
CREATE TABLE IF NOT EXISTS resume_reservations (
|
|
1381
|
-
reservation_id TEXT PRIMARY KEY,
|
|
1382
|
-
target_session_id TEXT NOT NULL UNIQUE,
|
|
1383
|
-
requester_instance_id TEXT NOT NULL,
|
|
1384
|
-
current_session_id TEXT,
|
|
1385
|
-
created_at INTEGER NOT NULL,
|
|
1386
|
-
expires_at INTEGER NOT NULL
|
|
1387
|
-
);
|
|
1388
|
-
CREATE TABLE IF NOT EXISTS maintenance_leases (
|
|
1389
|
-
session_id TEXT PRIMARY KEY,
|
|
1390
|
-
operation TEXT NOT NULL,
|
|
1391
|
-
holder_id TEXT NOT NULL,
|
|
1392
|
-
lease_id TEXT NOT NULL UNIQUE,
|
|
1393
|
-
acquired_at INTEGER NOT NULL,
|
|
1394
|
-
expires_at INTEGER NOT NULL
|
|
1395
|
-
);
|
|
1396
|
-
CREATE INDEX IF NOT EXISTS idx_sessions_activity ON sessions(indexed_at DESC);
|
|
1397
|
-
CREATE INDEX IF NOT EXISTS idx_leases_expiry ON session_leases(lease_expires_at);
|
|
1398
|
-
CREATE INDEX IF NOT EXISTS idx_reservations_expiry ON resume_reservations(expires_at);
|
|
1399
|
-
CREATE INDEX IF NOT EXISTS idx_maintenance_expiry ON maintenance_leases(expires_at);
|
|
1400
|
-
`);
|
|
1401
|
-
this.db.prepare("INSERT INTO catalog_meta(key,value) VALUES (?,?) ON CONFLICT(key) DO NOTHING").run("schema_version", String(SCHEMA_VERSION));
|
|
1402
|
-
this.db.prepare("INSERT INTO catalog_meta(key,value) VALUES (?,?) ON CONFLICT(key) DO NOTHING").run("generation", "0");
|
|
1403
|
-
const row = this.db.prepare("SELECT value FROM catalog_meta WHERE key=?").get("schema_version");
|
|
1404
|
-
if (Number(row.value) !== SCHEMA_VERSION)
|
|
1405
|
-
throw new Error(`Unsupported session catalog schema ${row.value}`);
|
|
1537
|
+
initializeCatalogSchema(this.db);
|
|
1406
1538
|
}
|
|
1407
1539
|
transaction(run) {
|
|
1408
1540
|
this.db.exec("BEGIN IMMEDIATE");
|
|
@@ -1443,21 +1575,6 @@ var SessionCatalogStore = class {
|
|
|
1443
1575
|
leaseRow(sessionId) {
|
|
1444
1576
|
return this.db.prepare("SELECT * FROM session_leases WHERE session_id=?").get(sessionId);
|
|
1445
1577
|
}
|
|
1446
|
-
/**
|
|
1447
|
-
* True when `sessionId` has a live `session_leases` row owned by a
|
|
1448
|
-
* different OS process than the *caller*. Used by
|
|
1449
|
-
* `acquireMaintenance` to distinguish "another running wstack" (which
|
|
1450
|
-
* must be fenced off) from "this very TUI owning its own session"
|
|
1451
|
-
* (which is allowed to claim a non-destructive maintenance lease on
|
|
1452
|
-
* its own transcript).
|
|
1453
|
-
*
|
|
1454
|
-
* `callerPid` must be the pid of the process that asked for the lease,
|
|
1455
|
-
* NOT `process.pid`. In the built runtime this store lives inside the
|
|
1456
|
-
* detached project-catalog daemon, so `process.pid` is the daemon's and
|
|
1457
|
-
* would mark every lease — including the caller's own session — foreign,
|
|
1458
|
-
* making `/clear` and `/rewind` fail with "Session … is live". Callers
|
|
1459
|
-
* that run the store in-process may omit it.
|
|
1460
|
-
*/
|
|
1461
1578
|
foreignLiveLease(sessionId, callerPid = process.pid) {
|
|
1462
1579
|
const live = this.leaseRow(sessionId);
|
|
1463
1580
|
return Boolean(live) && live.owner_pid !== callerPid;
|
|
@@ -1547,7 +1664,7 @@ var SessionCatalogStore = class {
|
|
|
1547
1664
|
if (this.maintenanceExists(targetSessionId))
|
|
1548
1665
|
throw conflict(`Session ${targetSessionId} is under maintenance`);
|
|
1549
1666
|
const catalog = this.db.prepare("SELECT 1 AS yes FROM sessions WHERE session_id=?").get(targetSessionId);
|
|
1550
|
-
if (!catalog && !
|
|
1667
|
+
if (!catalog && !fs5.existsSync(this.containedPath(`${targetSessionId}.jsonl`)))
|
|
1551
1668
|
throw new Error(`Session not found: ${targetSessionId}`);
|
|
1552
1669
|
const reservationId = randomUUID2();
|
|
1553
1670
|
const now = Date.now();
|
|
@@ -1675,9 +1792,9 @@ var SessionCatalogStore = class {
|
|
|
1675
1792
|
}
|
|
1676
1793
|
containedPath(relative2) {
|
|
1677
1794
|
assertId(relative2.replace(/\.(jsonl|summary\.json)$/, ""), "session path");
|
|
1678
|
-
const root =
|
|
1679
|
-
const candidate =
|
|
1680
|
-
const prefix = `${root}${
|
|
1795
|
+
const root = path5.resolve(this.sessionsDir);
|
|
1796
|
+
const candidate = path5.resolve(root, relative2);
|
|
1797
|
+
const prefix = `${root}${path5.sep}`;
|
|
1681
1798
|
if (candidate !== root && !(process.platform === "win32" ? candidate.toLowerCase().startsWith(prefix.toLowerCase()) : candidate.startsWith(prefix)))
|
|
1682
1799
|
throw new TypeError("Session path escapes sessions directory");
|
|
1683
1800
|
return candidate;
|
|
@@ -1693,7 +1810,7 @@ var SessionCatalogStore = class {
|
|
|
1693
1810
|
transcriptRelativePath = normalizedTranscript;
|
|
1694
1811
|
summaryRelativePath = normalizedSummary;
|
|
1695
1812
|
const transcript = this.containedPath(transcriptRelativePath);
|
|
1696
|
-
const stat =
|
|
1813
|
+
const stat = fs5.existsSync(transcript) ? fs5.statSync(transcript) : void 0;
|
|
1697
1814
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1698
1815
|
return this.transaction(() => {
|
|
1699
1816
|
const prior = this.db.prepare("SELECT summary_revision FROM sessions WHERE session_id=?").get(summary.id);
|
|
@@ -1816,7 +1933,7 @@ var SessionCatalogStore = class {
|
|
|
1816
1933
|
if (trimmed) summary.name = this.scrubber.scrub(trimmed).slice(0, 500);
|
|
1817
1934
|
else delete summary.name;
|
|
1818
1935
|
const summaryPath = this.containedPath(current.summaryRelativePath);
|
|
1819
|
-
|
|
1936
|
+
fs5.mkdirSync(path5.dirname(summaryPath), { recursive: true, mode: 448 });
|
|
1820
1937
|
await atomicWrite(summaryPath, `${JSON.stringify(summary)}
|
|
1821
1938
|
`, { mode: 384 });
|
|
1822
1939
|
try {
|
|
@@ -1876,16 +1993,16 @@ var SessionCatalogStore = class {
|
|
|
1876
1993
|
this.containedPath(`${sessionId}.plan.json`),
|
|
1877
1994
|
this.containedPath(`${sessionId}.tasks.json`),
|
|
1878
1995
|
this.containedPath(`${sessionId}.todos.json`),
|
|
1879
|
-
|
|
1996
|
+
path5.join(path5.dirname(transcript), path5.basename(sessionId))
|
|
1880
1997
|
];
|
|
1881
|
-
const trashRoot =
|
|
1882
|
-
|
|
1998
|
+
const trashRoot = path5.join(this.sessionsDir, "_trash", lease.leaseId);
|
|
1999
|
+
fs5.mkdirSync(trashRoot, { recursive: true, mode: 448 });
|
|
1883
2000
|
const moved = [];
|
|
1884
2001
|
try {
|
|
1885
2002
|
artifacts.forEach((artifact, index) => {
|
|
1886
|
-
if (!
|
|
1887
|
-
const target =
|
|
1888
|
-
|
|
2003
|
+
if (!fs5.existsSync(artifact)) return;
|
|
2004
|
+
const target = path5.join(trashRoot, `${index}-${path5.basename(artifact)}`);
|
|
2005
|
+
fs5.renameSync(artifact, target);
|
|
1889
2006
|
moved.push({ from: artifact, to: target });
|
|
1890
2007
|
});
|
|
1891
2008
|
this.transaction(() => {
|
|
@@ -1900,17 +2017,17 @@ var SessionCatalogStore = class {
|
|
|
1900
2017
|
} catch (error) {
|
|
1901
2018
|
for (const item of moved.reverse()) {
|
|
1902
2019
|
try {
|
|
1903
|
-
|
|
1904
|
-
|
|
2020
|
+
fs5.mkdirSync(path5.dirname(item.from), { recursive: true, mode: 448 });
|
|
2021
|
+
fs5.renameSync(item.to, item.from);
|
|
1905
2022
|
} catch {
|
|
1906
2023
|
}
|
|
1907
2024
|
}
|
|
1908
2025
|
throw error;
|
|
1909
2026
|
}
|
|
1910
2027
|
try {
|
|
1911
|
-
|
|
1912
|
-
const trashParent =
|
|
1913
|
-
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);
|
|
1914
2031
|
} catch {
|
|
1915
2032
|
}
|
|
1916
2033
|
}
|
|
@@ -1931,122 +2048,13 @@ var SessionCatalogStore = class {
|
|
|
1931
2048
|
return deleted;
|
|
1932
2049
|
}
|
|
1933
2050
|
rebuildCatalog() {
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
2051
|
+
return rebuildCatalogIndex(
|
|
2052
|
+
this.db,
|
|
2053
|
+
this.sessionsDir,
|
|
2054
|
+
(rel) => this.containedPath(rel),
|
|
2055
|
+
(run) => this.transaction(run),
|
|
2056
|
+
() => this.bumpGeneration()
|
|
1937
2057
|
);
|
|
1938
|
-
const ids = /* @__PURE__ */ new Set();
|
|
1939
|
-
for (const file of [...summaries, ...transcripts]) {
|
|
1940
|
-
const relative2 = path4.relative(this.sessionsDir, file).replaceAll("\\", "/");
|
|
1941
|
-
ids.add(relative2.replace(/\.summary\.json$|\.jsonl$/, ""));
|
|
1942
|
-
}
|
|
1943
|
-
let indexed = 0;
|
|
1944
|
-
let damaged = 0;
|
|
1945
|
-
this.transaction(() => {
|
|
1946
|
-
this.db.prepare("DELETE FROM sessions").run();
|
|
1947
|
-
for (const id of ids) {
|
|
1948
|
-
try {
|
|
1949
|
-
const summaryFile = this.containedPath(`${id}.summary.json`);
|
|
1950
|
-
const summary = fs4.existsSync(summaryFile) ? parseJson(fs4.readFileSync(summaryFile, "utf8")) : this.summarizeTranscript(id);
|
|
1951
|
-
if (!summary || summary.id !== id) throw new Error("summary identity mismatch");
|
|
1952
|
-
const transcript = this.containedPath(`${id}.jsonl`);
|
|
1953
|
-
const stat = fs4.existsSync(transcript) ? fs4.statSync(transcript) : void 0;
|
|
1954
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1955
|
-
this.db.prepare(
|
|
1956
|
-
"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)"
|
|
1957
|
-
).run(
|
|
1958
|
-
id,
|
|
1959
|
-
`${id}.jsonl`,
|
|
1960
|
-
`${id}.summary.json`,
|
|
1961
|
-
JSON.stringify(summary),
|
|
1962
|
-
stat?.size ?? 0,
|
|
1963
|
-
stat?.mtimeMs ?? 0,
|
|
1964
|
-
1,
|
|
1965
|
-
now
|
|
1966
|
-
);
|
|
1967
|
-
indexed++;
|
|
1968
|
-
} catch {
|
|
1969
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1970
|
-
const fallback = {
|
|
1971
|
-
id,
|
|
1972
|
-
title: id,
|
|
1973
|
-
startedAt: now,
|
|
1974
|
-
model: "",
|
|
1975
|
-
provider: "",
|
|
1976
|
-
tokenTotal: 0
|
|
1977
|
-
};
|
|
1978
|
-
this.db.prepare(
|
|
1979
|
-
"INSERT INTO sessions(session_id,transcript_relative_path,summary_relative_path,summary_json,summary_revision,indexed_at,damaged) VALUES (?,?,?,?,1,?,1)"
|
|
1980
|
-
).run(id, `${id}.jsonl`, `${id}.summary.json`, JSON.stringify(fallback), now);
|
|
1981
|
-
damaged++;
|
|
1982
|
-
}
|
|
1983
|
-
}
|
|
1984
|
-
this.db.prepare(
|
|
1985
|
-
"INSERT INTO catalog_meta(key,value) VALUES ('last_reconciliation',?) ON CONFLICT(key) DO UPDATE SET value=excluded.value"
|
|
1986
|
-
).run((/* @__PURE__ */ new Date()).toISOString());
|
|
1987
|
-
this.bumpGeneration();
|
|
1988
|
-
});
|
|
1989
|
-
return { indexed, damaged };
|
|
1990
|
-
}
|
|
1991
|
-
walkFiles(root, suffix) {
|
|
1992
|
-
const result = [];
|
|
1993
|
-
const visit = (dir) => {
|
|
1994
|
-
for (const entry of fs4.readdirSync(dir, { withFileTypes: true })) {
|
|
1995
|
-
if (entry.isDirectory()) {
|
|
1996
|
-
if (entry.name !== "_cas" && entry.name !== "_trash") visit(path4.join(dir, entry.name));
|
|
1997
|
-
} else if (entry.isFile() && entry.name.endsWith(suffix))
|
|
1998
|
-
result.push(path4.join(dir, entry.name));
|
|
1999
|
-
}
|
|
2000
|
-
};
|
|
2001
|
-
visit(root);
|
|
2002
|
-
return result;
|
|
2003
|
-
}
|
|
2004
|
-
summarizeTranscript(id) {
|
|
2005
|
-
const file = this.containedPath(`${id}.jsonl`);
|
|
2006
|
-
const lines = fs4.readFileSync(file, "utf8").split(/\r?\n/);
|
|
2007
|
-
let start;
|
|
2008
|
-
let endedAt;
|
|
2009
|
-
let lastActivityAt;
|
|
2010
|
-
let messageCount = 0;
|
|
2011
|
-
let iterationCount = 0;
|
|
2012
|
-
let toolCallCount = 0;
|
|
2013
|
-
let compactionCount = 0;
|
|
2014
|
-
let tokenTotal = 0;
|
|
2015
|
-
for (const line of lines) {
|
|
2016
|
-
if (!line) continue;
|
|
2017
|
-
let event;
|
|
2018
|
-
try {
|
|
2019
|
-
event = parseJson(line);
|
|
2020
|
-
} catch {
|
|
2021
|
-
continue;
|
|
2022
|
-
}
|
|
2023
|
-
lastActivityAt = event.ts;
|
|
2024
|
-
if (event.type === "session_start") start = event;
|
|
2025
|
-
if (event.type === "session_end") endedAt = event.ts;
|
|
2026
|
-
if (event.type === "message_appended" && (event.message.role === "user" || event.message.role === "assistant"))
|
|
2027
|
-
messageCount++;
|
|
2028
|
-
if (event.type === "llm_response") {
|
|
2029
|
-
iterationCount++;
|
|
2030
|
-
tokenTotal += event.usage.input + event.usage.output + (event.usage.cacheRead ?? 0) + (event.usage.cacheWrite ?? 0);
|
|
2031
|
-
}
|
|
2032
|
-
if (event.type === "tool_call_end") toolCallCount++;
|
|
2033
|
-
if (event.type === "compaction") compactionCount++;
|
|
2034
|
-
}
|
|
2035
|
-
if (!start) throw new Error("missing session_start");
|
|
2036
|
-
return {
|
|
2037
|
-
id,
|
|
2038
|
-
title: id,
|
|
2039
|
-
startedAt: start.ts,
|
|
2040
|
-
...endedAt ? { endedAt } : {},
|
|
2041
|
-
model: start.model,
|
|
2042
|
-
provider: start.provider,
|
|
2043
|
-
tokenTotal,
|
|
2044
|
-
...lastActivityAt ? { lastActivityAt } : {},
|
|
2045
|
-
messageCount,
|
|
2046
|
-
iterationCount,
|
|
2047
|
-
toolCallCount,
|
|
2048
|
-
compactionCount
|
|
2049
|
-
};
|
|
2050
2058
|
}
|
|
2051
2059
|
health(base) {
|
|
2052
2060
|
this.reapExpired();
|