@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
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { randomBytes as randomBytes2, randomUUID as randomUUID2 } from "node:crypto";
|
|
5
5
|
import * as fsp from "node:fs/promises";
|
|
6
6
|
import * as net from "node:net";
|
|
7
|
-
import * as
|
|
7
|
+
import * as path4 from "node:path";
|
|
8
8
|
import { bindProjectEndpoint } from "@wrongstack/persistence";
|
|
9
9
|
|
|
10
10
|
// src/security/file-permissions.ts
|
|
@@ -179,9 +179,9 @@ function sessionCatalogProjectServerMetadataPath(projectDir) {
|
|
|
179
179
|
}
|
|
180
180
|
|
|
181
181
|
// src/session-catalog/store.ts
|
|
182
|
-
import {
|
|
183
|
-
import * as
|
|
184
|
-
import * as
|
|
182
|
+
import { randomBytes, randomUUID } from "node:crypto";
|
|
183
|
+
import * as fs2 from "node:fs";
|
|
184
|
+
import * as path3 from "node:path";
|
|
185
185
|
|
|
186
186
|
// src/coordination/sqlite-mailbox-schema.ts
|
|
187
187
|
import { createRequire } from "node:module";
|
|
@@ -221,6 +221,24 @@ function loadDatabaseSync() {
|
|
|
221
221
|
}
|
|
222
222
|
|
|
223
223
|
// src/security/secret-scrubber.ts
|
|
224
|
+
var JSON_CREDENTIAL_KEY_ANCHORS = [
|
|
225
|
+
'Key"',
|
|
226
|
+
'key"',
|
|
227
|
+
'KEY"',
|
|
228
|
+
'token"',
|
|
229
|
+
'Token"',
|
|
230
|
+
'TOKEN"',
|
|
231
|
+
'secret"',
|
|
232
|
+
'Secret"',
|
|
233
|
+
'SECRET"',
|
|
234
|
+
'password"',
|
|
235
|
+
'Password"',
|
|
236
|
+
'PASSWORD"',
|
|
237
|
+
'authorization"',
|
|
238
|
+
'Authorization"',
|
|
239
|
+
'bearer"',
|
|
240
|
+
'Bearer"'
|
|
241
|
+
];
|
|
224
242
|
var PATTERNS = [
|
|
225
243
|
// Anchored at the start where possible so partial matches inside larger
|
|
226
244
|
// strings don't trigger false positives.
|
|
@@ -323,6 +341,30 @@ var PATTERNS = [
|
|
|
323
341
|
regex: /(^|\s)([A-Z_]{4,}(?:KEY|TOKEN|SECRET|PASSWORD|PWD))\s*[:=]\s*['"]?([A-Za-z0-9_/+=-]{20,512})['"]?(?=\s|$)/g,
|
|
324
342
|
anchor: ["KEY", "TOKEN", "SECRET", "PASSWORD", "PWD"]
|
|
325
343
|
},
|
|
344
|
+
{
|
|
345
|
+
type: "json_credential_key",
|
|
346
|
+
// The JSON counterpart to `high_entropy_env`, and the pattern that the
|
|
347
|
+
// now-deleted `JSON_KEY_ANCHORS` list was written for. Without it those
|
|
348
|
+
// anchors only widened the cheap pre-scan — `hasCredentialAnchors` said
|
|
349
|
+
// "this text may hold a secret", every pattern then declined to match, and
|
|
350
|
+
// the value went out verbatim. `high_entropy_env` cannot cover these: it
|
|
351
|
+
// requires an UPPERCASE unquoted key (`API_KEY=…`), so `{"apiKey":"…"}`
|
|
352
|
+
// never matched.
|
|
353
|
+
//
|
|
354
|
+
// Tool results are routinely serialised as JSON, and a credential with no
|
|
355
|
+
// recognisable prefix (Azure, self-hosted gateways, Anthropic/Codex OAuth)
|
|
356
|
+
// has no other pattern that can catch it — this is the only thing standing
|
|
357
|
+
// between such a value and the session JSONL, chronicle, HQ broadcast and
|
|
358
|
+
// the model's own context.
|
|
359
|
+
//
|
|
360
|
+
// The key may carry a prefix (`"anthropicApiKey"`), but the credential word
|
|
361
|
+
// must END the key: `"tokenCount"` and `"maxTokens"` do not match, because
|
|
362
|
+
// the closing quote has to follow the word immediately.
|
|
363
|
+
// Value floor of 8 chars keeps enum-ish values (`"authorization":"none"`)
|
|
364
|
+
// out. Capture groups: 1=key + punctuation, 2=value, 3=closing quote.
|
|
365
|
+
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,
|
|
366
|
+
anchor: JSON_CREDENTIAL_KEY_ANCHORS
|
|
367
|
+
},
|
|
326
368
|
// ── Ported from packages/plugins credential-patterns.ts (WS-034) ─────────
|
|
327
369
|
// The plugin runtime carried 37 patterns while this scrubber — the one that
|
|
328
370
|
// guards session JSONL, chronicle, HQ broadcast, WebUI events and the auth
|
|
@@ -405,9 +447,12 @@ var PATTERNS = [
|
|
|
405
447
|
anchor: "GOCSPX-"
|
|
406
448
|
}
|
|
407
449
|
];
|
|
408
|
-
var SIMPLE_PATTERNS = PATTERNS.filter(
|
|
450
|
+
var SIMPLE_PATTERNS = PATTERNS.filter(
|
|
451
|
+
(p) => p.type !== "high_entropy_env" && p.type !== "json_credential_key"
|
|
452
|
+
);
|
|
409
453
|
var COMBINED_REGEX = new RegExp(SIMPLE_PATTERNS.map((p) => `(${p.regex.source})`).join("|"), "g");
|
|
410
454
|
var HIGH_ENTROPY_REGEX = PATTERNS.find((p) => p.type === "high_entropy_env").regex;
|
|
455
|
+
var JSON_CREDENTIAL_REGEX = PATTERNS.find((p) => p.type === "json_credential_key").regex;
|
|
411
456
|
var COMBINED_REPLACEMENTS = SIMPLE_PATTERNS.map((p) => `[REDACTED:${p.type}]`);
|
|
412
457
|
var SCRUB_CHUNK_BYTES = 64 * 1024;
|
|
413
458
|
var SCRUB_OVERLAP_BYTES = 1024;
|
|
@@ -418,20 +463,7 @@ var PATTERN_ANCHORS = [
|
|
|
418
463
|
)
|
|
419
464
|
)
|
|
420
465
|
];
|
|
421
|
-
var
|
|
422
|
-
'"apiKey"',
|
|
423
|
-
'"api_key"',
|
|
424
|
-
'"token"',
|
|
425
|
-
'"secret"',
|
|
426
|
-
'"password"',
|
|
427
|
-
'"authorization"',
|
|
428
|
-
'"bearer"',
|
|
429
|
-
'"private_key"',
|
|
430
|
-
'"access_token"',
|
|
431
|
-
'"refresh_token"',
|
|
432
|
-
'"client_secret"'
|
|
433
|
-
];
|
|
434
|
-
var ALL_ANCHORS = [...PATTERN_ANCHORS, ...JSON_KEY_ANCHORS];
|
|
466
|
+
var ALL_ANCHORS = PATTERN_ANCHORS;
|
|
435
467
|
function hasCredentialAnchors(text) {
|
|
436
468
|
for (const anchor of ALL_ANCHORS) {
|
|
437
469
|
if (text.includes(anchor)) return true;
|
|
@@ -480,6 +512,9 @@ var DefaultSecretScrubber = class {
|
|
|
480
512
|
out = out.replace(HIGH_ENTROPY_REGEX, (_match, lead, key, _value) => {
|
|
481
513
|
return `${lead}${key}=[REDACTED:high_entropy_env]`;
|
|
482
514
|
});
|
|
515
|
+
out = out.replace(JSON_CREDENTIAL_REGEX, (_match, keyPrefix, _value, closingQuote) => {
|
|
516
|
+
return `${keyPrefix}[REDACTED:json_credential_key]${closingQuote}`;
|
|
517
|
+
});
|
|
483
518
|
return out;
|
|
484
519
|
}
|
|
485
520
|
/**
|
|
@@ -521,6 +556,10 @@ function isPidAlive(pid) {
|
|
|
521
556
|
}
|
|
522
557
|
}
|
|
523
558
|
|
|
559
|
+
// src/session-catalog/store-rebuild.ts
|
|
560
|
+
import * as fs from "node:fs";
|
|
561
|
+
import * as path2 from "node:path";
|
|
562
|
+
|
|
524
563
|
// src/utils/session-scoped-path.ts
|
|
525
564
|
var SESSION_SIDECAR_JSONL_SUFFIXES = [
|
|
526
565
|
".replay.jsonl",
|
|
@@ -537,7 +576,8 @@ function isSessionTranscriptFileName(name) {
|
|
|
537
576
|
return !SESSION_SIDECAR_JSONL_SUFFIXES.some((suffix) => name.endsWith(suffix));
|
|
538
577
|
}
|
|
539
578
|
|
|
540
|
-
// src/session-catalog/store.ts
|
|
579
|
+
// src/session-catalog/store-schema.ts
|
|
580
|
+
import { createHash as createHash2, timingSafeEqual } from "node:crypto";
|
|
541
581
|
var SCHEMA_VERSION = 1;
|
|
542
582
|
var MAX_LEASE_MS = 12e4;
|
|
543
583
|
var MAX_RESERVATION_MS = 6e4;
|
|
@@ -582,12 +622,192 @@ function boundPresenceValue(value, depth) {
|
|
|
582
622
|
}
|
|
583
623
|
return result;
|
|
584
624
|
}
|
|
625
|
+
function configureCatalogDatabase(db) {
|
|
626
|
+
db.exec(
|
|
627
|
+
"PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON; PRAGMA busy_timeout=5000; PRAGMA synchronous=NORMAL;"
|
|
628
|
+
);
|
|
629
|
+
}
|
|
630
|
+
function initializeCatalogSchema(db) {
|
|
631
|
+
db.exec(`
|
|
632
|
+
CREATE TABLE IF NOT EXISTS catalog_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL);
|
|
633
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
634
|
+
session_id TEXT PRIMARY KEY,
|
|
635
|
+
transcript_relative_path TEXT NOT NULL,
|
|
636
|
+
summary_relative_path TEXT NOT NULL,
|
|
637
|
+
summary_json TEXT NOT NULL,
|
|
638
|
+
transcript_size INTEGER NOT NULL DEFAULT 0,
|
|
639
|
+
transcript_mtime_ms REAL NOT NULL DEFAULT 0,
|
|
640
|
+
summary_revision INTEGER NOT NULL DEFAULT 1,
|
|
641
|
+
indexed_at TEXT NOT NULL,
|
|
642
|
+
damaged INTEGER NOT NULL DEFAULT 0
|
|
643
|
+
);
|
|
644
|
+
CREATE TABLE IF NOT EXISTS session_leases (
|
|
645
|
+
session_id TEXT PRIMARY KEY,
|
|
646
|
+
lease_id TEXT NOT NULL UNIQUE,
|
|
647
|
+
lease_secret_hash TEXT NOT NULL,
|
|
648
|
+
owner_instance_id TEXT NOT NULL,
|
|
649
|
+
owner_pid INTEGER NOT NULL,
|
|
650
|
+
owner_started_at TEXT NOT NULL,
|
|
651
|
+
entry_json TEXT NOT NULL,
|
|
652
|
+
agent_revision INTEGER NOT NULL DEFAULT 0,
|
|
653
|
+
status TEXT NOT NULL,
|
|
654
|
+
last_heartbeat_at INTEGER NOT NULL,
|
|
655
|
+
lease_expires_at INTEGER NOT NULL
|
|
656
|
+
);
|
|
657
|
+
CREATE TABLE IF NOT EXISTS resume_reservations (
|
|
658
|
+
reservation_id TEXT PRIMARY KEY,
|
|
659
|
+
target_session_id TEXT NOT NULL UNIQUE,
|
|
660
|
+
requester_instance_id TEXT NOT NULL,
|
|
661
|
+
current_session_id TEXT,
|
|
662
|
+
created_at INTEGER NOT NULL,
|
|
663
|
+
expires_at INTEGER NOT NULL
|
|
664
|
+
);
|
|
665
|
+
CREATE TABLE IF NOT EXISTS maintenance_leases (
|
|
666
|
+
session_id TEXT PRIMARY KEY,
|
|
667
|
+
operation TEXT NOT NULL,
|
|
668
|
+
holder_id TEXT NOT NULL,
|
|
669
|
+
lease_id TEXT NOT NULL UNIQUE,
|
|
670
|
+
acquired_at INTEGER NOT NULL,
|
|
671
|
+
expires_at INTEGER NOT NULL
|
|
672
|
+
);
|
|
673
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_activity ON sessions(indexed_at DESC);
|
|
674
|
+
CREATE INDEX IF NOT EXISTS idx_leases_expiry ON session_leases(lease_expires_at);
|
|
675
|
+
CREATE INDEX IF NOT EXISTS idx_reservations_expiry ON resume_reservations(expires_at);
|
|
676
|
+
CREATE INDEX IF NOT EXISTS idx_maintenance_expiry ON maintenance_leases(expires_at);
|
|
677
|
+
`);
|
|
678
|
+
db.prepare("INSERT INTO catalog_meta(key,value) VALUES (?,?) ON CONFLICT(key) DO NOTHING").run("schema_version", String(SCHEMA_VERSION));
|
|
679
|
+
db.prepare("INSERT INTO catalog_meta(key,value) VALUES (?,?) ON CONFLICT(key) DO NOTHING").run("generation", "0");
|
|
680
|
+
const row = db.prepare("SELECT value FROM catalog_meta WHERE key=?").get("schema_version");
|
|
681
|
+
if (Number(row.value) !== SCHEMA_VERSION)
|
|
682
|
+
throw new Error(`Unsupported session catalog schema ${row.value}`);
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// src/session-catalog/store-rebuild.ts
|
|
686
|
+
function walkSessionFiles(root, suffix) {
|
|
687
|
+
const result = [];
|
|
688
|
+
const visit = (dir) => {
|
|
689
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
690
|
+
if (entry.isDirectory()) {
|
|
691
|
+
if (entry.name !== "_cas" && entry.name !== "_trash") visit(path2.join(dir, entry.name));
|
|
692
|
+
} else if (entry.isFile() && entry.name.endsWith(suffix))
|
|
693
|
+
result.push(path2.join(dir, entry.name));
|
|
694
|
+
}
|
|
695
|
+
};
|
|
696
|
+
visit(root);
|
|
697
|
+
return result;
|
|
698
|
+
}
|
|
699
|
+
function summarizeTranscriptFile(transcriptFilePath, id) {
|
|
700
|
+
const lines = fs.readFileSync(transcriptFilePath, "utf8").split(/\r?\n/);
|
|
701
|
+
let start;
|
|
702
|
+
let endedAt;
|
|
703
|
+
let lastActivityAt;
|
|
704
|
+
let messageCount = 0;
|
|
705
|
+
let iterationCount = 0;
|
|
706
|
+
let toolCallCount = 0;
|
|
707
|
+
let compactionCount = 0;
|
|
708
|
+
let tokenTotal = 0;
|
|
709
|
+
for (const line of lines) {
|
|
710
|
+
if (!line) continue;
|
|
711
|
+
let event;
|
|
712
|
+
try {
|
|
713
|
+
event = parseJson(line);
|
|
714
|
+
} catch {
|
|
715
|
+
continue;
|
|
716
|
+
}
|
|
717
|
+
lastActivityAt = event.ts;
|
|
718
|
+
if (event.type === "session_start") start = event;
|
|
719
|
+
if (event.type === "session_end") endedAt = event.ts;
|
|
720
|
+
if (event.type === "message_appended" && (event.message.role === "user" || event.message.role === "assistant"))
|
|
721
|
+
messageCount++;
|
|
722
|
+
if (event.type === "llm_response") {
|
|
723
|
+
iterationCount++;
|
|
724
|
+
tokenTotal += event.usage.input + event.usage.output + (event.usage.cacheRead ?? 0) + (event.usage.cacheWrite ?? 0);
|
|
725
|
+
}
|
|
726
|
+
if (event.type === "tool_call_end") toolCallCount++;
|
|
727
|
+
if (event.type === "compaction") compactionCount++;
|
|
728
|
+
}
|
|
729
|
+
if (!start) throw new Error("missing session_start");
|
|
730
|
+
return {
|
|
731
|
+
id,
|
|
732
|
+
title: id,
|
|
733
|
+
startedAt: start.ts,
|
|
734
|
+
...endedAt ? { endedAt } : {},
|
|
735
|
+
model: start.model,
|
|
736
|
+
provider: start.provider,
|
|
737
|
+
tokenTotal,
|
|
738
|
+
...lastActivityAt ? { lastActivityAt } : {},
|
|
739
|
+
messageCount,
|
|
740
|
+
iterationCount,
|
|
741
|
+
toolCallCount,
|
|
742
|
+
compactionCount
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
function rebuildCatalogIndex(db, sessionsDir, containedPathFn, transactionFn, bumpGenerationFn) {
|
|
746
|
+
const summaries = walkSessionFiles(sessionsDir, ".summary.json");
|
|
747
|
+
const transcripts = walkSessionFiles(sessionsDir, ".jsonl").filter(
|
|
748
|
+
(file) => isSessionTranscriptFileName(path2.basename(file))
|
|
749
|
+
);
|
|
750
|
+
const ids = /* @__PURE__ */ new Set();
|
|
751
|
+
for (const file of [...summaries, ...transcripts]) {
|
|
752
|
+
const relative3 = path2.relative(sessionsDir, file).replaceAll("\\", "/");
|
|
753
|
+
ids.add(relative3.replace(/\.summary\.json$|\.jsonl$/, ""));
|
|
754
|
+
}
|
|
755
|
+
let indexed = 0;
|
|
756
|
+
let damaged = 0;
|
|
757
|
+
transactionFn(() => {
|
|
758
|
+
db.prepare("DELETE FROM sessions").run();
|
|
759
|
+
for (const id of ids) {
|
|
760
|
+
try {
|
|
761
|
+
const summaryFile = containedPathFn(`${id}.summary.json`);
|
|
762
|
+
const summary = fs.existsSync(summaryFile) ? parseJson(fs.readFileSync(summaryFile, "utf8")) : summarizeTranscriptFile(containedPathFn(`${id}.jsonl`), id);
|
|
763
|
+
if (!summary || summary.id !== id) throw new Error("summary identity mismatch");
|
|
764
|
+
const transcript = containedPathFn(`${id}.jsonl`);
|
|
765
|
+
const stat = fs.existsSync(transcript) ? fs.statSync(transcript) : void 0;
|
|
766
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
767
|
+
db.prepare(
|
|
768
|
+
"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)"
|
|
769
|
+
).run(
|
|
770
|
+
id,
|
|
771
|
+
`${id}.jsonl`,
|
|
772
|
+
`${id}.summary.json`,
|
|
773
|
+
JSON.stringify(summary),
|
|
774
|
+
stat?.size ?? 0,
|
|
775
|
+
stat?.mtimeMs ?? 0,
|
|
776
|
+
1,
|
|
777
|
+
now
|
|
778
|
+
);
|
|
779
|
+
indexed++;
|
|
780
|
+
} catch {
|
|
781
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
782
|
+
const fallback = {
|
|
783
|
+
id,
|
|
784
|
+
title: id,
|
|
785
|
+
startedAt: now,
|
|
786
|
+
model: "",
|
|
787
|
+
provider: "",
|
|
788
|
+
tokenTotal: 0
|
|
789
|
+
};
|
|
790
|
+
db.prepare(
|
|
791
|
+
"INSERT INTO sessions(session_id,transcript_relative_path,summary_relative_path,summary_json,summary_revision,indexed_at,damaged) VALUES (?,?,?,?,1,?,1)"
|
|
792
|
+
).run(id, `${id}.jsonl`, `${id}.summary.json`, JSON.stringify(fallback), now);
|
|
793
|
+
damaged++;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
db.prepare(
|
|
797
|
+
"INSERT INTO catalog_meta(key,value) VALUES ('last_reconciliation',?) ON CONFLICT(key) DO UPDATE SET value=excluded.value"
|
|
798
|
+
).run((/* @__PURE__ */ new Date()).toISOString());
|
|
799
|
+
bumpGenerationFn();
|
|
800
|
+
});
|
|
801
|
+
return { indexed, damaged };
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// src/session-catalog/store.ts
|
|
585
805
|
var SessionCatalogStore = class {
|
|
586
806
|
constructor(projectDir) {
|
|
587
807
|
this.projectDir = projectDir;
|
|
588
|
-
this.sessionsDir =
|
|
589
|
-
|
|
590
|
-
this.databasePath =
|
|
808
|
+
this.sessionsDir = path3.join(projectDir, "sessions");
|
|
809
|
+
fs2.mkdirSync(this.sessionsDir, { recursive: true, mode: 448 });
|
|
810
|
+
this.databasePath = path3.join(this.sessionsDir, "catalog.sqlite");
|
|
591
811
|
const Database = loadDatabaseSync();
|
|
592
812
|
this.db = new Database(this.databasePath);
|
|
593
813
|
try {
|
|
@@ -604,12 +824,12 @@ var SessionCatalogStore = class {
|
|
|
604
824
|
this.db.close();
|
|
605
825
|
const quarantine = `${this.databasePath}.corrupt-${Date.now()}`;
|
|
606
826
|
try {
|
|
607
|
-
|
|
827
|
+
fs2.renameSync(this.databasePath, quarantine);
|
|
608
828
|
} catch {
|
|
609
829
|
}
|
|
610
830
|
for (const suffix of ["-wal", "-shm"]) {
|
|
611
831
|
try {
|
|
612
|
-
|
|
832
|
+
fs2.renameSync(`${this.databasePath}${suffix}`, `${quarantine}${suffix}`);
|
|
613
833
|
} catch {
|
|
614
834
|
}
|
|
615
835
|
}
|
|
@@ -621,7 +841,7 @@ var SessionCatalogStore = class {
|
|
|
621
841
|
const rowCount = Number(
|
|
622
842
|
this.db.prepare("SELECT COUNT(*) AS count FROM sessions").get().count
|
|
623
843
|
);
|
|
624
|
-
if (rowCount === 0 &&
|
|
844
|
+
if (rowCount === 0 && walkSessionFiles(this.sessionsDir, ".jsonl").some((file) => !file.endsWith("_index.jsonl"))) {
|
|
625
845
|
this.rebuildCatalog();
|
|
626
846
|
}
|
|
627
847
|
}
|
|
@@ -631,66 +851,13 @@ var SessionCatalogStore = class {
|
|
|
631
851
|
db;
|
|
632
852
|
scrubber = new DefaultSecretScrubber();
|
|
633
853
|
configureDatabase() {
|
|
634
|
-
this.db
|
|
635
|
-
"PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON; PRAGMA busy_timeout=5000; PRAGMA synchronous=NORMAL;"
|
|
636
|
-
);
|
|
854
|
+
configureCatalogDatabase(this.db);
|
|
637
855
|
}
|
|
638
856
|
close() {
|
|
639
857
|
this.db.close();
|
|
640
858
|
}
|
|
641
859
|
initialize() {
|
|
642
|
-
this.db
|
|
643
|
-
CREATE TABLE IF NOT EXISTS catalog_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL);
|
|
644
|
-
CREATE TABLE IF NOT EXISTS sessions (
|
|
645
|
-
session_id TEXT PRIMARY KEY,
|
|
646
|
-
transcript_relative_path TEXT NOT NULL,
|
|
647
|
-
summary_relative_path TEXT NOT NULL,
|
|
648
|
-
summary_json TEXT NOT NULL,
|
|
649
|
-
transcript_size INTEGER NOT NULL DEFAULT 0,
|
|
650
|
-
transcript_mtime_ms REAL NOT NULL DEFAULT 0,
|
|
651
|
-
summary_revision INTEGER NOT NULL DEFAULT 1,
|
|
652
|
-
indexed_at TEXT NOT NULL,
|
|
653
|
-
damaged INTEGER NOT NULL DEFAULT 0
|
|
654
|
-
);
|
|
655
|
-
CREATE TABLE IF NOT EXISTS session_leases (
|
|
656
|
-
session_id TEXT PRIMARY KEY,
|
|
657
|
-
lease_id TEXT NOT NULL UNIQUE,
|
|
658
|
-
lease_secret_hash TEXT NOT NULL,
|
|
659
|
-
owner_instance_id TEXT NOT NULL,
|
|
660
|
-
owner_pid INTEGER NOT NULL,
|
|
661
|
-
owner_started_at TEXT NOT NULL,
|
|
662
|
-
entry_json TEXT NOT NULL,
|
|
663
|
-
agent_revision INTEGER NOT NULL DEFAULT 0,
|
|
664
|
-
status TEXT NOT NULL,
|
|
665
|
-
last_heartbeat_at INTEGER NOT NULL,
|
|
666
|
-
lease_expires_at INTEGER NOT NULL
|
|
667
|
-
);
|
|
668
|
-
CREATE TABLE IF NOT EXISTS resume_reservations (
|
|
669
|
-
reservation_id TEXT PRIMARY KEY,
|
|
670
|
-
target_session_id TEXT NOT NULL UNIQUE,
|
|
671
|
-
requester_instance_id TEXT NOT NULL,
|
|
672
|
-
current_session_id TEXT,
|
|
673
|
-
created_at INTEGER NOT NULL,
|
|
674
|
-
expires_at INTEGER NOT NULL
|
|
675
|
-
);
|
|
676
|
-
CREATE TABLE IF NOT EXISTS maintenance_leases (
|
|
677
|
-
session_id TEXT PRIMARY KEY,
|
|
678
|
-
operation TEXT NOT NULL,
|
|
679
|
-
holder_id TEXT NOT NULL,
|
|
680
|
-
lease_id TEXT NOT NULL UNIQUE,
|
|
681
|
-
acquired_at INTEGER NOT NULL,
|
|
682
|
-
expires_at INTEGER NOT NULL
|
|
683
|
-
);
|
|
684
|
-
CREATE INDEX IF NOT EXISTS idx_sessions_activity ON sessions(indexed_at DESC);
|
|
685
|
-
CREATE INDEX IF NOT EXISTS idx_leases_expiry ON session_leases(lease_expires_at);
|
|
686
|
-
CREATE INDEX IF NOT EXISTS idx_reservations_expiry ON resume_reservations(expires_at);
|
|
687
|
-
CREATE INDEX IF NOT EXISTS idx_maintenance_expiry ON maintenance_leases(expires_at);
|
|
688
|
-
`);
|
|
689
|
-
this.db.prepare("INSERT INTO catalog_meta(key,value) VALUES (?,?) ON CONFLICT(key) DO NOTHING").run("schema_version", String(SCHEMA_VERSION));
|
|
690
|
-
this.db.prepare("INSERT INTO catalog_meta(key,value) VALUES (?,?) ON CONFLICT(key) DO NOTHING").run("generation", "0");
|
|
691
|
-
const row = this.db.prepare("SELECT value FROM catalog_meta WHERE key=?").get("schema_version");
|
|
692
|
-
if (Number(row.value) !== SCHEMA_VERSION)
|
|
693
|
-
throw new Error(`Unsupported session catalog schema ${row.value}`);
|
|
860
|
+
initializeCatalogSchema(this.db);
|
|
694
861
|
}
|
|
695
862
|
transaction(run) {
|
|
696
863
|
this.db.exec("BEGIN IMMEDIATE");
|
|
@@ -731,21 +898,6 @@ var SessionCatalogStore = class {
|
|
|
731
898
|
leaseRow(sessionId) {
|
|
732
899
|
return this.db.prepare("SELECT * FROM session_leases WHERE session_id=?").get(sessionId);
|
|
733
900
|
}
|
|
734
|
-
/**
|
|
735
|
-
* True when `sessionId` has a live `session_leases` row owned by a
|
|
736
|
-
* different OS process than the *caller*. Used by
|
|
737
|
-
* `acquireMaintenance` to distinguish "another running wstack" (which
|
|
738
|
-
* must be fenced off) from "this very TUI owning its own session"
|
|
739
|
-
* (which is allowed to claim a non-destructive maintenance lease on
|
|
740
|
-
* its own transcript).
|
|
741
|
-
*
|
|
742
|
-
* `callerPid` must be the pid of the process that asked for the lease,
|
|
743
|
-
* NOT `process.pid`. In the built runtime this store lives inside the
|
|
744
|
-
* detached project-catalog daemon, so `process.pid` is the daemon's and
|
|
745
|
-
* would mark every lease — including the caller's own session — foreign,
|
|
746
|
-
* making `/clear` and `/rewind` fail with "Session … is live". Callers
|
|
747
|
-
* that run the store in-process may omit it.
|
|
748
|
-
*/
|
|
749
901
|
foreignLiveLease(sessionId, callerPid = process.pid) {
|
|
750
902
|
const live = this.leaseRow(sessionId);
|
|
751
903
|
return Boolean(live) && live.owner_pid !== callerPid;
|
|
@@ -835,7 +987,7 @@ var SessionCatalogStore = class {
|
|
|
835
987
|
if (this.maintenanceExists(targetSessionId))
|
|
836
988
|
throw conflict(`Session ${targetSessionId} is under maintenance`);
|
|
837
989
|
const catalog = this.db.prepare("SELECT 1 AS yes FROM sessions WHERE session_id=?").get(targetSessionId);
|
|
838
|
-
if (!catalog && !
|
|
990
|
+
if (!catalog && !fs2.existsSync(this.containedPath(`${targetSessionId}.jsonl`)))
|
|
839
991
|
throw new Error(`Session not found: ${targetSessionId}`);
|
|
840
992
|
const reservationId = randomUUID();
|
|
841
993
|
const now = Date.now();
|
|
@@ -963,9 +1115,9 @@ var SessionCatalogStore = class {
|
|
|
963
1115
|
}
|
|
964
1116
|
containedPath(relative3) {
|
|
965
1117
|
assertId(relative3.replace(/\.(jsonl|summary\.json)$/, ""), "session path");
|
|
966
|
-
const root =
|
|
967
|
-
const candidate =
|
|
968
|
-
const prefix = `${root}${
|
|
1118
|
+
const root = path3.resolve(this.sessionsDir);
|
|
1119
|
+
const candidate = path3.resolve(root, relative3);
|
|
1120
|
+
const prefix = `${root}${path3.sep}`;
|
|
969
1121
|
if (candidate !== root && !(process.platform === "win32" ? candidate.toLowerCase().startsWith(prefix.toLowerCase()) : candidate.startsWith(prefix)))
|
|
970
1122
|
throw new TypeError("Session path escapes sessions directory");
|
|
971
1123
|
return candidate;
|
|
@@ -981,7 +1133,7 @@ var SessionCatalogStore = class {
|
|
|
981
1133
|
transcriptRelativePath = normalizedTranscript;
|
|
982
1134
|
summaryRelativePath = normalizedSummary;
|
|
983
1135
|
const transcript = this.containedPath(transcriptRelativePath);
|
|
984
|
-
const stat =
|
|
1136
|
+
const stat = fs2.existsSync(transcript) ? fs2.statSync(transcript) : void 0;
|
|
985
1137
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
986
1138
|
return this.transaction(() => {
|
|
987
1139
|
const prior = this.db.prepare("SELECT summary_revision FROM sessions WHERE session_id=?").get(summary.id);
|
|
@@ -1104,7 +1256,7 @@ var SessionCatalogStore = class {
|
|
|
1104
1256
|
if (trimmed) summary.name = this.scrubber.scrub(trimmed).slice(0, 500);
|
|
1105
1257
|
else delete summary.name;
|
|
1106
1258
|
const summaryPath = this.containedPath(current.summaryRelativePath);
|
|
1107
|
-
|
|
1259
|
+
fs2.mkdirSync(path3.dirname(summaryPath), { recursive: true, mode: 448 });
|
|
1108
1260
|
await atomicWrite(summaryPath, `${JSON.stringify(summary)}
|
|
1109
1261
|
`, { mode: 384 });
|
|
1110
1262
|
try {
|
|
@@ -1164,16 +1316,16 @@ var SessionCatalogStore = class {
|
|
|
1164
1316
|
this.containedPath(`${sessionId}.plan.json`),
|
|
1165
1317
|
this.containedPath(`${sessionId}.tasks.json`),
|
|
1166
1318
|
this.containedPath(`${sessionId}.todos.json`),
|
|
1167
|
-
|
|
1319
|
+
path3.join(path3.dirname(transcript), path3.basename(sessionId))
|
|
1168
1320
|
];
|
|
1169
|
-
const trashRoot =
|
|
1170
|
-
|
|
1321
|
+
const trashRoot = path3.join(this.sessionsDir, "_trash", lease.leaseId);
|
|
1322
|
+
fs2.mkdirSync(trashRoot, { recursive: true, mode: 448 });
|
|
1171
1323
|
const moved = [];
|
|
1172
1324
|
try {
|
|
1173
1325
|
artifacts.forEach((artifact, index) => {
|
|
1174
|
-
if (!
|
|
1175
|
-
const target =
|
|
1176
|
-
|
|
1326
|
+
if (!fs2.existsSync(artifact)) return;
|
|
1327
|
+
const target = path3.join(trashRoot, `${index}-${path3.basename(artifact)}`);
|
|
1328
|
+
fs2.renameSync(artifact, target);
|
|
1177
1329
|
moved.push({ from: artifact, to: target });
|
|
1178
1330
|
});
|
|
1179
1331
|
this.transaction(() => {
|
|
@@ -1188,17 +1340,17 @@ var SessionCatalogStore = class {
|
|
|
1188
1340
|
} catch (error) {
|
|
1189
1341
|
for (const item of moved.reverse()) {
|
|
1190
1342
|
try {
|
|
1191
|
-
|
|
1192
|
-
|
|
1343
|
+
fs2.mkdirSync(path3.dirname(item.from), { recursive: true, mode: 448 });
|
|
1344
|
+
fs2.renameSync(item.to, item.from);
|
|
1193
1345
|
} catch {
|
|
1194
1346
|
}
|
|
1195
1347
|
}
|
|
1196
1348
|
throw error;
|
|
1197
1349
|
}
|
|
1198
1350
|
try {
|
|
1199
|
-
|
|
1200
|
-
const trashParent =
|
|
1201
|
-
if (
|
|
1351
|
+
fs2.rmSync(trashRoot, { recursive: true, force: true });
|
|
1352
|
+
const trashParent = path3.dirname(trashRoot);
|
|
1353
|
+
if (fs2.readdirSync(trashParent).length === 0) fs2.rmdirSync(trashParent);
|
|
1202
1354
|
} catch {
|
|
1203
1355
|
}
|
|
1204
1356
|
}
|
|
@@ -1219,122 +1371,13 @@ var SessionCatalogStore = class {
|
|
|
1219
1371
|
return deleted;
|
|
1220
1372
|
}
|
|
1221
1373
|
rebuildCatalog() {
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1374
|
+
return rebuildCatalogIndex(
|
|
1375
|
+
this.db,
|
|
1376
|
+
this.sessionsDir,
|
|
1377
|
+
(rel) => this.containedPath(rel),
|
|
1378
|
+
(run) => this.transaction(run),
|
|
1379
|
+
() => this.bumpGeneration()
|
|
1225
1380
|
);
|
|
1226
|
-
const ids = /* @__PURE__ */ new Set();
|
|
1227
|
-
for (const file of [...summaries, ...transcripts]) {
|
|
1228
|
-
const relative3 = path2.relative(this.sessionsDir, file).replaceAll("\\", "/");
|
|
1229
|
-
ids.add(relative3.replace(/\.summary\.json$|\.jsonl$/, ""));
|
|
1230
|
-
}
|
|
1231
|
-
let indexed = 0;
|
|
1232
|
-
let damaged = 0;
|
|
1233
|
-
this.transaction(() => {
|
|
1234
|
-
this.db.prepare("DELETE FROM sessions").run();
|
|
1235
|
-
for (const id of ids) {
|
|
1236
|
-
try {
|
|
1237
|
-
const summaryFile = this.containedPath(`${id}.summary.json`);
|
|
1238
|
-
const summary = fs.existsSync(summaryFile) ? parseJson(fs.readFileSync(summaryFile, "utf8")) : this.summarizeTranscript(id);
|
|
1239
|
-
if (!summary || summary.id !== id) throw new Error("summary identity mismatch");
|
|
1240
|
-
const transcript = this.containedPath(`${id}.jsonl`);
|
|
1241
|
-
const stat = fs.existsSync(transcript) ? fs.statSync(transcript) : void 0;
|
|
1242
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1243
|
-
this.db.prepare(
|
|
1244
|
-
"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)"
|
|
1245
|
-
).run(
|
|
1246
|
-
id,
|
|
1247
|
-
`${id}.jsonl`,
|
|
1248
|
-
`${id}.summary.json`,
|
|
1249
|
-
JSON.stringify(summary),
|
|
1250
|
-
stat?.size ?? 0,
|
|
1251
|
-
stat?.mtimeMs ?? 0,
|
|
1252
|
-
1,
|
|
1253
|
-
now
|
|
1254
|
-
);
|
|
1255
|
-
indexed++;
|
|
1256
|
-
} catch {
|
|
1257
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1258
|
-
const fallback = {
|
|
1259
|
-
id,
|
|
1260
|
-
title: id,
|
|
1261
|
-
startedAt: now,
|
|
1262
|
-
model: "",
|
|
1263
|
-
provider: "",
|
|
1264
|
-
tokenTotal: 0
|
|
1265
|
-
};
|
|
1266
|
-
this.db.prepare(
|
|
1267
|
-
"INSERT INTO sessions(session_id,transcript_relative_path,summary_relative_path,summary_json,summary_revision,indexed_at,damaged) VALUES (?,?,?,?,1,?,1)"
|
|
1268
|
-
).run(id, `${id}.jsonl`, `${id}.summary.json`, JSON.stringify(fallback), now);
|
|
1269
|
-
damaged++;
|
|
1270
|
-
}
|
|
1271
|
-
}
|
|
1272
|
-
this.db.prepare(
|
|
1273
|
-
"INSERT INTO catalog_meta(key,value) VALUES ('last_reconciliation',?) ON CONFLICT(key) DO UPDATE SET value=excluded.value"
|
|
1274
|
-
).run((/* @__PURE__ */ new Date()).toISOString());
|
|
1275
|
-
this.bumpGeneration();
|
|
1276
|
-
});
|
|
1277
|
-
return { indexed, damaged };
|
|
1278
|
-
}
|
|
1279
|
-
walkFiles(root, suffix) {
|
|
1280
|
-
const result = [];
|
|
1281
|
-
const visit = (dir) => {
|
|
1282
|
-
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
1283
|
-
if (entry.isDirectory()) {
|
|
1284
|
-
if (entry.name !== "_cas" && entry.name !== "_trash") visit(path2.join(dir, entry.name));
|
|
1285
|
-
} else if (entry.isFile() && entry.name.endsWith(suffix))
|
|
1286
|
-
result.push(path2.join(dir, entry.name));
|
|
1287
|
-
}
|
|
1288
|
-
};
|
|
1289
|
-
visit(root);
|
|
1290
|
-
return result;
|
|
1291
|
-
}
|
|
1292
|
-
summarizeTranscript(id) {
|
|
1293
|
-
const file = this.containedPath(`${id}.jsonl`);
|
|
1294
|
-
const lines = fs.readFileSync(file, "utf8").split(/\r?\n/);
|
|
1295
|
-
let start;
|
|
1296
|
-
let endedAt;
|
|
1297
|
-
let lastActivityAt;
|
|
1298
|
-
let messageCount = 0;
|
|
1299
|
-
let iterationCount = 0;
|
|
1300
|
-
let toolCallCount = 0;
|
|
1301
|
-
let compactionCount = 0;
|
|
1302
|
-
let tokenTotal = 0;
|
|
1303
|
-
for (const line of lines) {
|
|
1304
|
-
if (!line) continue;
|
|
1305
|
-
let event;
|
|
1306
|
-
try {
|
|
1307
|
-
event = parseJson(line);
|
|
1308
|
-
} catch {
|
|
1309
|
-
continue;
|
|
1310
|
-
}
|
|
1311
|
-
lastActivityAt = event.ts;
|
|
1312
|
-
if (event.type === "session_start") start = event;
|
|
1313
|
-
if (event.type === "session_end") endedAt = event.ts;
|
|
1314
|
-
if (event.type === "message_appended" && (event.message.role === "user" || event.message.role === "assistant"))
|
|
1315
|
-
messageCount++;
|
|
1316
|
-
if (event.type === "llm_response") {
|
|
1317
|
-
iterationCount++;
|
|
1318
|
-
tokenTotal += event.usage.input + event.usage.output + (event.usage.cacheRead ?? 0) + (event.usage.cacheWrite ?? 0);
|
|
1319
|
-
}
|
|
1320
|
-
if (event.type === "tool_call_end") toolCallCount++;
|
|
1321
|
-
if (event.type === "compaction") compactionCount++;
|
|
1322
|
-
}
|
|
1323
|
-
if (!start) throw new Error("missing session_start");
|
|
1324
|
-
return {
|
|
1325
|
-
id,
|
|
1326
|
-
title: id,
|
|
1327
|
-
startedAt: start.ts,
|
|
1328
|
-
...endedAt ? { endedAt } : {},
|
|
1329
|
-
model: start.model,
|
|
1330
|
-
provider: start.provider,
|
|
1331
|
-
tokenTotal,
|
|
1332
|
-
...lastActivityAt ? { lastActivityAt } : {},
|
|
1333
|
-
messageCount,
|
|
1334
|
-
iterationCount,
|
|
1335
|
-
toolCallCount,
|
|
1336
|
-
compactionCount
|
|
1337
|
-
};
|
|
1338
1381
|
}
|
|
1339
1382
|
health(base) {
|
|
1340
1383
|
this.reapExpired();
|
|
@@ -1366,13 +1409,13 @@ function parseArgs(argv) {
|
|
|
1366
1409
|
const projectRoot = values.get("--project-root");
|
|
1367
1410
|
if (!projectDir) throw new Error("Session Catalog project server requires --project-dir");
|
|
1368
1411
|
if (!projectRoot) throw new Error("Session Catalog project server requires --project-root");
|
|
1369
|
-
return { projectDir:
|
|
1412
|
+
return { projectDir: path4.resolve(projectDir), projectRoot: path4.resolve(projectRoot) };
|
|
1370
1413
|
}
|
|
1371
1414
|
useDaemonPerfDefaults();
|
|
1372
1415
|
var parsed = parseArgs(process.argv.slice(2));
|
|
1373
1416
|
var endpoint = sessionCatalogProjectServerEndpoint(parsed.projectDir);
|
|
1374
1417
|
var metadataPath = sessionCatalogProjectServerMetadataPath(parsed.projectDir);
|
|
1375
|
-
var databasePath =
|
|
1418
|
+
var databasePath = path4.join(parsed.projectDir, "sessions", "catalog.sqlite");
|
|
1376
1419
|
var startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1377
1420
|
var instanceId = randomUUID2();
|
|
1378
1421
|
var authToken = randomBytes2(32).toString("hex");
|
|
@@ -1389,7 +1432,7 @@ var serverInfo = {
|
|
|
1389
1432
|
instanceId,
|
|
1390
1433
|
startedAt
|
|
1391
1434
|
};
|
|
1392
|
-
process.title = `wrongstack-session-catalog:${
|
|
1435
|
+
process.title = `wrongstack-session-catalog:${path4.basename(parsed.projectRoot)}`;
|
|
1393
1436
|
var store;
|
|
1394
1437
|
var activeRequests = 0;
|
|
1395
1438
|
var stopping = false;
|
|
@@ -1493,13 +1536,13 @@ function validateOperationArgs(op, args) {
|
|
|
1493
1536
|
if (record["entry"] !== void 0) exact(record["entry"], `${op}.entry`, entryKeys);
|
|
1494
1537
|
if (record["entry"] && typeof record["entry"] === "object") {
|
|
1495
1538
|
const entry = record["entry"];
|
|
1496
|
-
if (typeof entry["projectRoot"] !== "string" || (process.platform === "win32" ?
|
|
1539
|
+
if (typeof entry["projectRoot"] !== "string" || (process.platform === "win32" ? path4.resolve(entry["projectRoot"]).toLowerCase() !== parsed.projectRoot.toLowerCase() : path4.resolve(entry["projectRoot"]) !== parsed.projectRoot)) {
|
|
1497
1540
|
throw new TypeError(`${op}.entry project identity does not match this daemon`);
|
|
1498
1541
|
}
|
|
1499
1542
|
if (typeof entry["workingDir"] !== "string")
|
|
1500
1543
|
throw new TypeError(`${op}.entry workingDir is required`);
|
|
1501
|
-
const relative3 =
|
|
1502
|
-
if (relative3 === ".." || relative3.startsWith(`..${
|
|
1544
|
+
const relative3 = path4.relative(parsed.projectRoot, path4.resolve(entry["workingDir"]));
|
|
1545
|
+
if (relative3 === ".." || relative3.startsWith(`..${path4.sep}`) || path4.isAbsolute(relative3)) {
|
|
1503
1546
|
throw new TypeError(`${op}.entry workingDir is outside the project root`);
|
|
1504
1547
|
}
|
|
1505
1548
|
}
|