@wrongstack/core 0.289.0 → 0.291.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/boot.d.ts.map +1 -1
- package/dist/chronicle/domain-adapter.d.ts.map +1 -1
- package/dist/chronicle/index.d.ts +1 -1
- package/dist/chronicle/index.d.ts.map +1 -1
- package/dist/chronicle/index.js +964 -245
- package/dist/chronicle/index.js.map +3 -3
- package/dist/chronicle/journal.d.ts +35 -3
- package/dist/chronicle/journal.d.ts.map +1 -1
- package/dist/chronicle/query.d.ts +8 -5
- package/dist/chronicle/query.d.ts.map +1 -1
- package/dist/chronicle/tool-adapter.d.ts.map +1 -1
- package/dist/coordination/agent-subagent-runner.d.ts +6 -0
- package/dist/coordination/agent-subagent-runner.d.ts.map +1 -1
- package/dist/coordination/agents/phase6-domain.d.ts.map +1 -1
- package/dist/coordination/brain.d.ts +2 -1
- package/dist/coordination/brain.d.ts.map +1 -1
- package/dist/coordination/collab-debug.d.ts +0 -9
- package/dist/coordination/collab-debug.d.ts.map +1 -1
- package/dist/coordination/coordinator/error-classifier.d.ts.map +1 -1
- package/dist/coordination/dep-watcher.d.ts +2 -1
- package/dist/coordination/dep-watcher.d.ts.map +1 -1
- package/dist/coordination/director/director-collab.d.ts +1 -1
- package/dist/coordination/director/director-collab.d.ts.map +1 -1
- package/dist/coordination/director-tools.d.ts.map +1 -1
- package/dist/coordination/director.d.ts +19 -1
- package/dist/coordination/director.d.ts.map +1 -1
- package/dist/coordination/fleet-event-validation.d.ts +0 -7
- package/dist/coordination/fleet-event-validation.d.ts.map +1 -1
- package/dist/coordination/fleet-spawn.d.ts.map +1 -1
- package/dist/coordination/fleet-supervisor.d.ts +1 -1
- package/dist/coordination/fleet-supervisor.d.ts.map +1 -1
- package/dist/coordination/fleet.d.ts +0 -44
- package/dist/coordination/fleet.d.ts.map +1 -1
- package/dist/coordination/index.d.ts +2 -2
- package/dist/coordination/index.d.ts.map +1 -1
- package/dist/coordination/index.js +733 -111
- package/dist/coordination/index.js.map +4 -4
- package/dist/coordination/knowledge-graph.d.ts +2 -1
- package/dist/coordination/knowledge-graph.d.ts.map +1 -1
- package/dist/coordination/large-answer-store.d.ts +0 -6
- package/dist/coordination/large-answer-store.d.ts.map +1 -1
- package/dist/coordination/mail-tools.d.ts.map +1 -1
- package/dist/coordination/mailbox-http-router.d.ts.map +1 -1
- package/dist/coordination/mailbox-message-codec.d.ts +35 -0
- package/dist/coordination/mailbox-message-codec.d.ts.map +1 -1
- package/dist/coordination/mailbox-tool.d.ts +1 -1
- package/dist/coordination/mailbox-tool.d.ts.map +1 -1
- package/dist/coordination/mailbox-types.d.ts +151 -0
- package/dist/coordination/mailbox-types.d.ts.map +1 -1
- package/dist/coordination/provider-status-tracker.d.ts +27 -2
- package/dist/coordination/provider-status-tracker.d.ts.map +1 -1
- package/dist/coordination/subagent-budget.d.ts +2 -1
- package/dist/coordination/subagent-budget.d.ts.map +1 -1
- package/dist/core/agent-response.d.ts +2 -1
- package/dist/core/agent-response.d.ts.map +1 -1
- package/dist/core/agent-tools.d.ts.map +1 -1
- package/dist/core/context.d.ts +51 -0
- package/dist/core/context.d.ts.map +1 -1
- package/dist/core/fallback-model.d.ts +1 -1
- package/dist/core/fallback-model.d.ts.map +1 -1
- package/dist/core/fallback-profile-manager.d.ts +1 -1
- package/dist/core/fallback-profile-manager.d.ts.map +1 -1
- package/dist/core/fleet-pulse.d.ts +2 -1
- package/dist/core/fleet-pulse.d.ts.map +1 -1
- package/dist/core/iteration-limit.d.ts +2 -1
- package/dist/core/iteration-limit.d.ts.map +1 -1
- package/dist/core/mailbox-loop.d.ts +11 -1
- package/dist/core/mailbox-loop.d.ts.map +1 -1
- package/dist/core/model-availability-calendar.d.ts +27 -0
- package/dist/core/model-availability-calendar.d.ts.map +1 -0
- package/dist/core/system-prompt-builder.d.ts +2 -1
- package/dist/core/system-prompt-builder.d.ts.map +1 -1
- package/dist/defaults/index.js +1459 -560
- package/dist/defaults/index.js.map +4 -4
- package/dist/execution/auto-compaction-middleware.d.ts +3 -2
- package/dist/execution/auto-compaction-middleware.d.ts.map +1 -1
- package/dist/execution/enhance-recovery.d.ts +8 -0
- package/dist/execution/enhance-recovery.d.ts.map +1 -1
- package/dist/execution/error-handler.d.ts +2 -2
- package/dist/execution/error-handler.d.ts.map +1 -1
- package/dist/execution/index.js +643 -134
- package/dist/execution/index.js.map +4 -4
- package/dist/execution/intelligent-compactor.d.ts +0 -16
- package/dist/execution/intelligent-compactor.d.ts.map +1 -1
- package/dist/execution/one-shot-llm.d.ts.map +1 -1
- package/dist/execution/retry-policy.d.ts +0 -13
- package/dist/execution/retry-policy.d.ts.map +1 -1
- package/dist/execution/skill-loader.d.ts.map +1 -1
- package/dist/execution/tool-executor.d.ts.map +1 -1
- package/dist/hq/auth-store.d.ts.map +1 -1
- package/dist/hq/index.js +39 -1
- package/dist/hq/index.js.map +3 -3
- package/dist/index.d.ts +32 -30
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22263 -19999
- package/dist/index.js.map +4 -4
- package/dist/infrastructure/index.js +59 -5
- package/dist/infrastructure/index.js.map +2 -2
- package/dist/kernel/events/agent-events.d.ts +2 -0
- package/dist/kernel/events/agent-events.d.ts.map +1 -1
- package/dist/kernel/events/file-events.d.ts +46 -0
- package/dist/kernel/events/file-events.d.ts.map +1 -1
- package/dist/kernel/events/memory-events.d.ts +9 -0
- package/dist/kernel/events/memory-events.d.ts.map +1 -1
- package/dist/kernel/events/provider-events.d.ts +2 -1
- package/dist/kernel/events/provider-events.d.ts.map +1 -1
- package/dist/kernel/events/tool-events.d.ts +25 -0
- package/dist/kernel/events/tool-events.d.ts.map +1 -1
- package/dist/mailbox-attach.d.ts.map +1 -1
- package/dist/plugins/auto-review-plugin.d.ts +10 -0
- package/dist/plugins/auto-review-plugin.d.ts.map +1 -1
- package/dist/plugins/chimera-plugin.d.ts +5 -0
- package/dist/plugins/chimera-plugin.d.ts.map +1 -1
- package/dist/security/index.d.ts +1 -0
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +317 -9
- package/dist/security/index.js.map +4 -4
- package/dist/security/kanban-boundary.d.ts +10 -0
- package/dist/security/kanban-boundary.d.ts.map +1 -0
- package/dist/security/permission-policy.d.ts +8 -1
- package/dist/security/permission-policy.d.ts.map +1 -1
- package/dist/storage/config-loader.d.ts +15 -0
- package/dist/storage/config-loader.d.ts.map +1 -1
- package/dist/storage/index.d.ts +2 -2
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +627 -428
- package/dist/storage/index.js.map +4 -4
- package/dist/storage/memory-consolidator.d.ts +49 -4
- package/dist/storage/memory-consolidator.d.ts.map +1 -1
- package/dist/storage/memory-graph-backend.d.ts +11 -0
- package/dist/storage/memory-graph-backend.d.ts.map +1 -1
- package/dist/storage/provider-config-watcher.d.ts +17 -0
- package/dist/storage/provider-config-watcher.d.ts.map +1 -1
- package/dist/storage/session-event-bridge.d.ts +2 -2
- package/dist/storage/session-event-bridge.d.ts.map +1 -1
- package/dist/tools/fallback-manage-tools.d.ts.map +1 -1
- package/dist/tools/index.js +166 -26
- package/dist/tools/index.js.map +3 -3
- package/dist/types/config.d.ts +12 -4
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/file-event-record.d.ts +68 -0
- package/dist/types/file-event-record.d.ts.map +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +13 -8
- package/dist/types/index.js.map +3 -3
- package/dist/types/permission.d.ts +38 -0
- package/dist/types/permission.d.ts.map +1 -1
- package/dist/types/provider.d.ts +1 -1
- package/dist/types/provider.d.ts.map +1 -1
- package/dist/types/session.d.ts +27 -0
- package/dist/types/session.d.ts.map +1 -1
- package/dist/types/tool-executor.d.ts +2 -0
- package/dist/types/tool-executor.d.ts.map +1 -1
- package/dist/utils/config-backup.d.ts +20 -0
- package/dist/utils/config-backup.d.ts.map +1 -0
- package/dist/utils/index.d.ts +12 -11
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +436 -290
- package/dist/utils/index.js.map +4 -4
- package/dist/utils/message-invariants.d.ts +12 -0
- package/dist/utils/message-invariants.d.ts.map +1 -1
- package/dist/utils/safe-json.d.ts +1 -1
- package/dist/utils/safe-json.d.ts.map +1 -1
- package/dist/utils/term.d.ts +6 -0
- package/dist/utils/term.d.ts.map +1 -1
- package/dist/utils/tool-wire-compact.d.ts +9 -0
- package/dist/utils/tool-wire-compact.d.ts.map +1 -1
- package/dist/utils/wstack-paths.d.ts +21 -1
- package/dist/utils/wstack-paths.d.ts.map +1 -1
- package/instructions/agents/ios.md +113 -0
- package/instructions/llm/memory-consolidator.md +4 -12
- package/instructions/system.md +4 -1
- package/package.json +3 -3
- package/skills/auto-review/SKILL.md +176 -0
- package/skills/mnemosyne/SKILL.md +156 -0
- package/skills/mnemosyne/instructions/agent-prompt.md +200 -0
package/dist/chronicle/index.js
CHANGED
|
@@ -235,10 +235,10 @@ async function scanProject(root, excluded, maxHashBytes, onError) {
|
|
|
235
235
|
}
|
|
236
236
|
async function fingerprint(filePath, maxHashBytes) {
|
|
237
237
|
try {
|
|
238
|
-
const
|
|
239
|
-
if (!
|
|
240
|
-
const base = { size:
|
|
241
|
-
if (
|
|
238
|
+
const stat5 = await fsp.stat(filePath);
|
|
239
|
+
if (!stat5.isFile()) return void 0;
|
|
240
|
+
const base = { size: stat5.size, mtimeMs: stat5.mtimeMs };
|
|
241
|
+
if (stat5.size <= maxHashBytes) {
|
|
242
242
|
base.hash = createHash2("sha256").update(await fsp.readFile(filePath)).digest("hex");
|
|
243
243
|
}
|
|
244
244
|
return base;
|
|
@@ -271,6 +271,7 @@ import * as fs3 from "node:fs/promises";
|
|
|
271
271
|
import * as path4 from "node:path";
|
|
272
272
|
|
|
273
273
|
// src/utils/atomic-write.ts
|
|
274
|
+
import { randomBytes } from "node:crypto";
|
|
274
275
|
import * as fs2 from "node:fs/promises";
|
|
275
276
|
import { watch as watchDir } from "node:fs";
|
|
276
277
|
import * as path3 from "node:path";
|
|
@@ -378,6 +379,50 @@ var FsError = class extends WrongStackError {
|
|
|
378
379
|
};
|
|
379
380
|
|
|
380
381
|
// src/utils/atomic-write.ts
|
|
382
|
+
async function atomicWrite(targetPath, content, opts = {}) {
|
|
383
|
+
const dir = path3.dirname(targetPath);
|
|
384
|
+
await fs2.mkdir(dir, { recursive: true });
|
|
385
|
+
const tmp = path3.join(dir, `.${path3.basename(targetPath)}.${randomBytes(6).toString("hex")}.tmp`);
|
|
386
|
+
try {
|
|
387
|
+
if (typeof content === "string") {
|
|
388
|
+
await fs2.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
|
|
389
|
+
} else {
|
|
390
|
+
await fs2.writeFile(tmp, content, { flag: "wx" });
|
|
391
|
+
}
|
|
392
|
+
try {
|
|
393
|
+
const fh = await fs2.open(tmp, "r+");
|
|
394
|
+
try {
|
|
395
|
+
await fh.sync();
|
|
396
|
+
} finally {
|
|
397
|
+
await fh.close();
|
|
398
|
+
}
|
|
399
|
+
} catch {
|
|
400
|
+
}
|
|
401
|
+
let mode;
|
|
402
|
+
try {
|
|
403
|
+
const stat5 = await fs2.stat(targetPath);
|
|
404
|
+
mode = stat5.mode & 511;
|
|
405
|
+
} catch {
|
|
406
|
+
mode = opts.mode;
|
|
407
|
+
}
|
|
408
|
+
if (mode !== void 0) {
|
|
409
|
+
await fs2.chmod(tmp, mode);
|
|
410
|
+
}
|
|
411
|
+
await renameWithRetry(tmp, targetPath);
|
|
412
|
+
if (mode !== void 0 && process.platform === "win32") {
|
|
413
|
+
try {
|
|
414
|
+
await fs2.chmod(targetPath, mode);
|
|
415
|
+
} catch {
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
} catch (err) {
|
|
419
|
+
try {
|
|
420
|
+
await fs2.unlink(tmp);
|
|
421
|
+
} catch {
|
|
422
|
+
}
|
|
423
|
+
throw err;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
381
426
|
async function ensureDir(dir) {
|
|
382
427
|
await fs2.mkdir(dir, { recursive: true });
|
|
383
428
|
}
|
|
@@ -409,8 +454,8 @@ async function withFileLock(targetPath, fn, opts = {}) {
|
|
|
409
454
|
}
|
|
410
455
|
if (code !== "EEXIST" && code !== "EPERM") throw err;
|
|
411
456
|
try {
|
|
412
|
-
const
|
|
413
|
-
if (Date.now() -
|
|
457
|
+
const stat5 = await fs2.stat(lockPath);
|
|
458
|
+
if (Date.now() - stat5.mtimeMs > staleMs) {
|
|
414
459
|
await fs2.unlink(lockPath);
|
|
415
460
|
continue;
|
|
416
461
|
}
|
|
@@ -486,50 +531,78 @@ async function waitForLockRelease(lockPath, remainingMs) {
|
|
|
486
531
|
);
|
|
487
532
|
});
|
|
488
533
|
}
|
|
534
|
+
var TRANSIENT_RENAME_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES", "ENOTEMPTY"]);
|
|
535
|
+
async function renameWithRetry(from, to) {
|
|
536
|
+
if (process.platform !== "win32") {
|
|
537
|
+
await fs2.rename(from, to);
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
const delays = [10, 25, 60, 120, 250];
|
|
541
|
+
let lastErr;
|
|
542
|
+
for (let i = 0; i <= delays.length; i++) {
|
|
543
|
+
try {
|
|
544
|
+
await fs2.rename(from, to);
|
|
545
|
+
return;
|
|
546
|
+
} catch (err) {
|
|
547
|
+
lastErr = err;
|
|
548
|
+
const code = err?.code;
|
|
549
|
+
if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {
|
|
550
|
+
throw err;
|
|
551
|
+
}
|
|
552
|
+
await new Promise((resolve5) => setTimeout(resolve5, delays[i]));
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
throw lastErr;
|
|
556
|
+
}
|
|
489
557
|
|
|
490
558
|
// src/chronicle/types.ts
|
|
491
559
|
var CHRONICLE_SCHEMA_VERSION = 1;
|
|
492
560
|
|
|
493
561
|
// src/chronicle/journal.ts
|
|
494
562
|
var GENESIS_HASH = "0".repeat(64);
|
|
563
|
+
var DEFAULT_MAX_PARTITION_BYTES = 100 * 1024 * 1024;
|
|
564
|
+
var DEFAULT_ROTATION_WINDOW_MS = 60 * 60 * 1e3;
|
|
565
|
+
var RETENTION_CHECKPOINT_VERSION = 1;
|
|
495
566
|
var ChronicleJournal = class {
|
|
496
|
-
|
|
567
|
+
basePath;
|
|
497
568
|
now;
|
|
498
569
|
monotonicNow;
|
|
499
570
|
idFactory;
|
|
500
571
|
maxPending;
|
|
501
572
|
batchWindowMs;
|
|
573
|
+
maxPartitionSizeBytes;
|
|
574
|
+
rotationWindowMs;
|
|
575
|
+
retentionDays;
|
|
576
|
+
autoPurgeIntervalMs;
|
|
502
577
|
pending = [];
|
|
503
578
|
drainPromise;
|
|
504
579
|
drainScheduled = false;
|
|
505
580
|
drainTimer;
|
|
506
|
-
counters = {
|
|
507
|
-
acceptedEvents: 0,
|
|
508
|
-
persistedEvents: 0,
|
|
509
|
-
rejectedEvents: 0,
|
|
510
|
-
failedEvents: 0,
|
|
511
|
-
batches: 0,
|
|
512
|
-
maxObservedPending: 0,
|
|
513
|
-
largestBatch: 0
|
|
514
|
-
};
|
|
581
|
+
counters = { acceptedEvents: 0, persistedEvents: 0, rejectedEvents: 0, failedEvents: 0, batches: 0, maxObservedPending: 0, largestBatch: 0, partitionRolls: 0 };
|
|
515
582
|
lastBatchDurationMs;
|
|
583
|
+
partitionIndex = 0;
|
|
584
|
+
partitionStartedAt;
|
|
585
|
+
lastSequence = 0;
|
|
586
|
+
lastHash = GENESIS_HASH;
|
|
587
|
+
lastAutoPurgeAt = 0;
|
|
516
588
|
constructor(options) {
|
|
517
|
-
this.
|
|
589
|
+
this.basePath = path4.resolve(options.filePath);
|
|
518
590
|
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
519
591
|
this.monotonicNow = options.monotonicNow ?? (() => process.hrtime.bigint());
|
|
520
592
|
this.idFactory = options.idFactory ?? randomUUID2;
|
|
521
593
|
this.maxPending = Math.max(1, options.maxPending ?? 1e5);
|
|
522
594
|
this.batchWindowMs = Math.max(0, options.batchWindowMs ?? 5);
|
|
595
|
+
this.maxPartitionSizeBytes = options.maxPartitionSizeBytes ?? DEFAULT_MAX_PARTITION_BYTES;
|
|
596
|
+
this.rotationWindowMs = options.rotationWindowMs ?? DEFAULT_ROTATION_WINDOW_MS;
|
|
597
|
+
this.retentionDays = options.retentionDays && Number.isFinite(options.retentionDays) && options.retentionDays > 0 ? options.retentionDays : 0;
|
|
598
|
+
this.autoPurgeIntervalMs = Math.max(0, options.autoPurgeIntervalMs ?? 36e5);
|
|
599
|
+
this.partitionStartedAt = Date.now();
|
|
523
600
|
}
|
|
524
601
|
get path() {
|
|
525
|
-
return this.
|
|
602
|
+
return this.partitionIndex === 0 ? this.basePath : rotatedPath(this.basePath, this.partitionIndex);
|
|
526
603
|
}
|
|
527
604
|
stats() {
|
|
528
|
-
return {
|
|
529
|
-
...this.counters,
|
|
530
|
-
pendingEvents: this.pending.length,
|
|
531
|
-
...this.lastBatchDurationMs !== void 0 ? { lastBatchDurationMs: this.lastBatchDurationMs } : {}
|
|
532
|
-
};
|
|
605
|
+
return { ...this.counters, pendingEvents: this.pending.length, ...this.lastBatchDurationMs !== void 0 ? { lastBatchDurationMs: this.lastBatchDurationMs } : {} };
|
|
533
606
|
}
|
|
534
607
|
append(input) {
|
|
535
608
|
if (this.pending.length >= this.maxPending) {
|
|
@@ -546,9 +619,11 @@ var ChronicleJournal = class {
|
|
|
546
619
|
}
|
|
547
620
|
async readAll() {
|
|
548
621
|
await this.flush();
|
|
549
|
-
|
|
622
|
+
const files = await collectPartitions(this.basePath);
|
|
623
|
+
const entries = [];
|
|
624
|
+
for (const file of files) entries.push(...await readEntriesStrict(file));
|
|
625
|
+
return entries;
|
|
550
626
|
}
|
|
551
|
-
/** Wait until every append accepted by this process has settled. */
|
|
552
627
|
async flush() {
|
|
553
628
|
if (this.drainTimer) {
|
|
554
629
|
clearTimeout(this.drainTimer);
|
|
@@ -562,33 +637,108 @@ var ChronicleJournal = class {
|
|
|
562
637
|
}
|
|
563
638
|
async verify() {
|
|
564
639
|
await this.flush();
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
640
|
+
const files = await collectPartitions(this.basePath);
|
|
641
|
+
const checkpointResult = await readRetentionCheckpoint(this.basePath);
|
|
642
|
+
if (checkpointResult.error) return { ok: false, entries: 0, brokenAt: 0, reason: checkpointResult.error };
|
|
643
|
+
return verifyPartitionFiles(files, checkpointResult.checkpoint);
|
|
644
|
+
}
|
|
645
|
+
async purge(options) {
|
|
646
|
+
await this.flush();
|
|
647
|
+
if (!Number.isFinite(options.retentionDays) || options.retentionDays <= 0) {
|
|
648
|
+
throw new TypeError("Chronicle retentionDays must be a positive finite number");
|
|
570
649
|
}
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
650
|
+
await this.refreshStateFromDisk();
|
|
651
|
+
const cutoff = Date.now() - options.retentionDays * 864e5;
|
|
652
|
+
const activePath = path4.resolve(this.path);
|
|
653
|
+
const errors = [];
|
|
654
|
+
let dc = 0, db = 0, sc = 0;
|
|
655
|
+
const suppliedFiles = options.files === void 0 ? await collectJournalPartitions(this.basePath) : [...options.files];
|
|
656
|
+
const eligible = /* @__PURE__ */ new Set();
|
|
657
|
+
for (const suppliedPath of new Set(suppliedFiles)) {
|
|
658
|
+
const file = path4.resolve(suppliedPath);
|
|
659
|
+
if (!isJournalPartition(file, path4.dirname(this.basePath), this.basePath)) {
|
|
660
|
+
errors.push({ file: suppliedPath, reason: "not a Chronicle journal partition in this journal directory" });
|
|
661
|
+
sc++;
|
|
662
|
+
continue;
|
|
576
663
|
}
|
|
577
|
-
if (
|
|
578
|
-
|
|
664
|
+
if (file === activePath) {
|
|
665
|
+
sc++;
|
|
666
|
+
continue;
|
|
579
667
|
}
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
668
|
+
let mtimeMs;
|
|
669
|
+
try {
|
|
670
|
+
const fileStat = await fs3.lstat(file);
|
|
671
|
+
if (!fileStat.isFile()) {
|
|
672
|
+
sc++;
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
mtimeMs = fileStat.mtimeMs;
|
|
676
|
+
} catch (error) {
|
|
677
|
+
if (isNotFound(error)) continue;
|
|
678
|
+
errors.push({ file, reason: errorMessage(error) });
|
|
679
|
+
sc++;
|
|
680
|
+
continue;
|
|
681
|
+
}
|
|
682
|
+
if (mtimeMs > cutoff) {
|
|
683
|
+
sc++;
|
|
684
|
+
continue;
|
|
583
685
|
}
|
|
584
|
-
|
|
686
|
+
eligible.add(file);
|
|
687
|
+
}
|
|
688
|
+
const candidates = [];
|
|
689
|
+
const allPartitions = await collectJournalPartitions(this.basePath);
|
|
690
|
+
for (const family of groupPartitionsByFamily(allPartitions).values()) {
|
|
691
|
+
for (const file of family) {
|
|
692
|
+
if (file === activePath || !eligible.has(file)) break;
|
|
693
|
+
candidates.push(file);
|
|
694
|
+
eligible.delete(file);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
sc += eligible.size;
|
|
698
|
+
if (!options.dryRun) {
|
|
699
|
+
for (const file of candidates) {
|
|
700
|
+
try {
|
|
701
|
+
const familyBase = partitionFamilyBase(file);
|
|
702
|
+
let deletedBytes;
|
|
703
|
+
await withFileLock(familyBase, async () => {
|
|
704
|
+
const fileStat = await fs3.lstat(file);
|
|
705
|
+
if (!fileStat.isFile() || fileStat.mtimeMs > cutoff) return;
|
|
706
|
+
const entries = await readEntriesStrict(file);
|
|
707
|
+
const checkpointResult = await readRetentionCheckpoint(familyBase);
|
|
708
|
+
if (checkpointResult.error) throw new Error(checkpointResult.error);
|
|
709
|
+
const checkpoint = checkpointResult.checkpoint;
|
|
710
|
+
const nextCheckpoint = verifyRetainedPrefix(entries, checkpoint);
|
|
711
|
+
if (!nextCheckpoint) throw new Error("partition does not extend the trusted Chronicle chain");
|
|
712
|
+
if (nextCheckpoint.sequence > (checkpoint?.sequence ?? 0)) {
|
|
713
|
+
await writeRetentionCheckpoint(familyBase, nextCheckpoint);
|
|
714
|
+
}
|
|
715
|
+
deletedBytes = fileStat.size;
|
|
716
|
+
await fs3.unlink(file);
|
|
717
|
+
});
|
|
718
|
+
if (deletedBytes === void 0) {
|
|
719
|
+
sc++;
|
|
720
|
+
break;
|
|
721
|
+
}
|
|
722
|
+
db += deletedBytes;
|
|
723
|
+
dc++;
|
|
724
|
+
} catch (error) {
|
|
725
|
+
errors.push({ file, reason: errorMessage(error) });
|
|
726
|
+
sc++;
|
|
727
|
+
break;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
return { deletedCount: dc, deletedBytes: db, skippedCount: sc, errors, ...options.dryRun ? { candidates } : {} };
|
|
732
|
+
}
|
|
733
|
+
async maybeAutoPurge() {
|
|
734
|
+
if (this.retentionDays <= 0) return;
|
|
735
|
+
const n = Date.now();
|
|
736
|
+
if (n - this.lastAutoPurgeAt < this.autoPurgeIntervalMs) return;
|
|
737
|
+
this.lastAutoPurgeAt = n;
|
|
738
|
+
try {
|
|
739
|
+
await this.purge({ retentionDays: this.retentionDays });
|
|
740
|
+
} catch {
|
|
585
741
|
}
|
|
586
|
-
return {
|
|
587
|
-
ok: true,
|
|
588
|
-
entries: entries.length,
|
|
589
|
-
lastSequence: entries.at(-1)?.sequence ?? 0,
|
|
590
|
-
lastHash: entries.at(-1)?.hash ?? GENESIS_HASH
|
|
591
|
-
};
|
|
592
742
|
}
|
|
593
743
|
scheduleDrain() {
|
|
594
744
|
if (this.drainScheduled || this.drainPromise) return;
|
|
@@ -608,45 +758,241 @@ var ChronicleJournal = class {
|
|
|
608
758
|
if (this.pending.length > 0) this.scheduleDrain();
|
|
609
759
|
});
|
|
610
760
|
}
|
|
761
|
+
async refreshStateFromDisk() {
|
|
762
|
+
const files = await collectPartitions(this.basePath);
|
|
763
|
+
const latest = files[files.length - 1] ?? this.basePath;
|
|
764
|
+
this.partitionIndex = partitionIndex(latest, this.basePath);
|
|
765
|
+
const entry = await readLastEntry(latest);
|
|
766
|
+
const checkpointResult = await readRetentionCheckpoint(this.basePath);
|
|
767
|
+
if (checkpointResult.error) throw new Error(checkpointResult.error);
|
|
768
|
+
const checkpoint = checkpointResult.checkpoint;
|
|
769
|
+
this.lastSequence = entry?.sequence ?? checkpoint?.sequence ?? 0;
|
|
770
|
+
this.lastHash = entry?.hash ?? checkpoint?.hash ?? GENESIS_HASH;
|
|
771
|
+
try {
|
|
772
|
+
this.partitionStartedAt = (await fs3.stat(latest)).birthtimeMs;
|
|
773
|
+
} catch {
|
|
774
|
+
this.partitionStartedAt = Date.now();
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
async checkRotation() {
|
|
778
|
+
if (this.partitionIndex === 0 && this.lastSequence === 0) return;
|
|
779
|
+
if (Number.isFinite(this.rotationWindowMs) && Date.now() - this.partitionStartedAt >= this.rotationWindowMs) {
|
|
780
|
+
this.rotate();
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
if (Number.isFinite(this.maxPartitionSizeBytes)) {
|
|
784
|
+
try {
|
|
785
|
+
if ((await fs3.stat(this.path)).size >= this.maxPartitionSizeBytes) this.rotate();
|
|
786
|
+
} catch {
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
rotate() {
|
|
791
|
+
this.partitionIndex++;
|
|
792
|
+
this.partitionStartedAt = Date.now();
|
|
793
|
+
this.counters.partitionRolls++;
|
|
794
|
+
}
|
|
611
795
|
async persistBatch(batch) {
|
|
612
796
|
const started = performance.now();
|
|
613
797
|
this.counters.batches++;
|
|
614
798
|
this.counters.largestBatch = Math.max(this.counters.largestBatch, batch.length);
|
|
615
799
|
try {
|
|
616
|
-
await ensureDir(path4.dirname(this.
|
|
800
|
+
await ensureDir(path4.dirname(this.basePath));
|
|
617
801
|
let recorded = [];
|
|
618
|
-
await withFileLock(this.
|
|
619
|
-
|
|
802
|
+
await withFileLock(this.basePath, async () => {
|
|
803
|
+
await this.refreshStateFromDisk();
|
|
804
|
+
await this.checkRotation();
|
|
805
|
+
const cp = this.path;
|
|
806
|
+
let prev = this.lastSequence > 0 ? { sequence: this.lastSequence, hash: this.lastHash } : void 0;
|
|
620
807
|
recorded = batch.map(({ input }) => {
|
|
621
808
|
const instant = this.now().toISOString();
|
|
622
|
-
const
|
|
623
|
-
const
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
monotonicNs: input.monotonicNs ?? this.monotonicNow().toString(),
|
|
627
|
-
schemaVersion: CHRONICLE_SCHEMA_VERSION,
|
|
628
|
-
eventId: this.idFactory(),
|
|
629
|
-
observedAt: instant,
|
|
630
|
-
persistedAt: instant,
|
|
631
|
-
sequence: (previous?.sequence ?? 0) + 1,
|
|
632
|
-
previousHash: previous?.hash ?? GENESIS_HASH
|
|
633
|
-
};
|
|
634
|
-
const event = { ...unhashed, hash: hashValue(unhashed) };
|
|
635
|
-
previous = event;
|
|
809
|
+
const ni = removeUndefined(input);
|
|
810
|
+
const uh = { ...ni, occurredAt: input.occurredAt ?? instant, monotonicNs: input.monotonicNs ?? this.monotonicNow().toString(), schemaVersion: CHRONICLE_SCHEMA_VERSION, eventId: this.idFactory(), observedAt: instant, persistedAt: instant, sequence: (prev?.sequence ?? 0) + 1, previousHash: prev?.hash ?? GENESIS_HASH };
|
|
811
|
+
const event = { ...uh, hash: hashValue(uh) };
|
|
812
|
+
prev = event;
|
|
636
813
|
return event;
|
|
637
814
|
});
|
|
638
|
-
await fs3.appendFile(
|
|
815
|
+
await fs3.appendFile(cp, recorded.map((e) => JSON.stringify(e)).join("\n") + "\n", "utf8");
|
|
816
|
+
});
|
|
817
|
+
const last = recorded[recorded.length - 1];
|
|
818
|
+
this.lastSequence = last.sequence;
|
|
819
|
+
this.lastHash = last.hash;
|
|
820
|
+
batch.forEach((item, i) => {
|
|
821
|
+
item.resolve(recorded[i]);
|
|
639
822
|
});
|
|
640
|
-
batch.forEach((item, index) => item.resolve(recorded[index]));
|
|
641
823
|
this.counters.persistedEvents += batch.length;
|
|
824
|
+
void this.maybeAutoPurge();
|
|
642
825
|
} catch (error) {
|
|
643
826
|
this.counters.failedEvents += batch.length;
|
|
644
|
-
batch.forEach((item) =>
|
|
827
|
+
batch.forEach((item) => {
|
|
828
|
+
item.reject(error);
|
|
829
|
+
});
|
|
645
830
|
} finally {
|
|
646
831
|
this.lastBatchDurationMs = performance.now() - started;
|
|
647
832
|
}
|
|
648
833
|
}
|
|
649
834
|
};
|
|
835
|
+
function rotatedPath(basePath, index) {
|
|
836
|
+
const dir = path4.dirname(basePath);
|
|
837
|
+
const ext = path4.extname(basePath);
|
|
838
|
+
const base = path4.basename(basePath, ext);
|
|
839
|
+
return path4.join(dir, `${base}.${String(index).padStart(5, "0")}${ext}`);
|
|
840
|
+
}
|
|
841
|
+
async function collectPartitions(basePath) {
|
|
842
|
+
const dir = path4.dirname(basePath);
|
|
843
|
+
const ext = path4.extname(basePath);
|
|
844
|
+
const base = path4.basename(basePath, ext);
|
|
845
|
+
const pattern = new RegExp(`^${escapeRegex(base)}(?:\\.\\d{5})?${escapeRegex(ext)}$`);
|
|
846
|
+
const result = [];
|
|
847
|
+
try {
|
|
848
|
+
const entries = await fs3.readdir(dir, { withFileTypes: true });
|
|
849
|
+
for (const entry of entries) if (entry.isFile() && pattern.test(entry.name)) result.push(path4.join(dir, entry.name));
|
|
850
|
+
} catch {
|
|
851
|
+
}
|
|
852
|
+
const baseFile = path4.join(dir, base + ext);
|
|
853
|
+
const rotated = result.filter((file) => file !== baseFile).sort((left, right) => parseIndex(left, base, ext) - parseIndex(right, base, ext));
|
|
854
|
+
return [baseFile, ...rotated];
|
|
855
|
+
}
|
|
856
|
+
async function collectJournalPartitions(basePath) {
|
|
857
|
+
const directory = path4.dirname(basePath);
|
|
858
|
+
const result = [];
|
|
859
|
+
try {
|
|
860
|
+
const entries = await fs3.readdir(directory, { withFileTypes: true });
|
|
861
|
+
for (const entry of entries) {
|
|
862
|
+
const file = path4.join(directory, entry.name);
|
|
863
|
+
if (entry.isFile() && isJournalPartition(file, directory, basePath)) result.push(file);
|
|
864
|
+
}
|
|
865
|
+
} catch {
|
|
866
|
+
}
|
|
867
|
+
return result.sort(compareJournalPartitions);
|
|
868
|
+
}
|
|
869
|
+
function isJournalPartition(filePath, directory, basePath) {
|
|
870
|
+
if (path4.dirname(path4.resolve(filePath)) !== path4.resolve(directory)) return false;
|
|
871
|
+
const fileName = path4.basename(filePath);
|
|
872
|
+
if (!basePath) return false;
|
|
873
|
+
const baseName = path4.basename(basePath);
|
|
874
|
+
const dailyFamily = /^\d{4}-\d{2}-\d{2}\.events(?:\.\d{5})?\.jsonl$/;
|
|
875
|
+
if (/^\d{4}-\d{2}-\d{2}\.events\.jsonl$/.test(baseName)) return dailyFamily.test(fileName);
|
|
876
|
+
return partitionFamilyBase(path4.resolve(filePath)) === partitionFamilyBase(path4.resolve(basePath));
|
|
877
|
+
}
|
|
878
|
+
function compareJournalPartitions(left, right) {
|
|
879
|
+
const pattern = /^(.*\.events)(?:\.(\d{5}))?\.jsonl$/;
|
|
880
|
+
const leftMatch = pattern.exec(path4.basename(left));
|
|
881
|
+
const rightMatch = pattern.exec(path4.basename(right));
|
|
882
|
+
const familyOrder = (leftMatch?.[1] ?? left).localeCompare(rightMatch?.[1] ?? right);
|
|
883
|
+
return familyOrder || Number(leftMatch?.[2] ?? 0) - Number(rightMatch?.[2] ?? 0);
|
|
884
|
+
}
|
|
885
|
+
function groupPartitionsByFamily(files) {
|
|
886
|
+
const groups = /* @__PURE__ */ new Map();
|
|
887
|
+
for (const file of files) {
|
|
888
|
+
const family = partitionFamilyBase(file);
|
|
889
|
+
const group = groups.get(family) ?? [];
|
|
890
|
+
group.push(file);
|
|
891
|
+
groups.set(family, group);
|
|
892
|
+
}
|
|
893
|
+
return groups;
|
|
894
|
+
}
|
|
895
|
+
function partitionFamilyBase(filePath) {
|
|
896
|
+
return filePath.replace(/\.\d{5}(?=\.jsonl$)/, "");
|
|
897
|
+
}
|
|
898
|
+
function retentionCheckpointPath(basePath) {
|
|
899
|
+
return `${partitionFamilyBase(basePath)}.retention.json`;
|
|
900
|
+
}
|
|
901
|
+
function verifyRetainedPrefix(entries, checkpoint) {
|
|
902
|
+
if (entries.length === 0) return void 0;
|
|
903
|
+
let sequence = checkpoint?.sequence ?? 0;
|
|
904
|
+
let hash3 = checkpoint?.hash ?? GENESIS_HASH;
|
|
905
|
+
let advanced = false;
|
|
906
|
+
for (const entry of entries) {
|
|
907
|
+
if (entry.sequence <= sequence) continue;
|
|
908
|
+
if (entry.sequence !== sequence + 1 || entry.previousHash !== hash3) return void 0;
|
|
909
|
+
const { hash: recordedHash, ...content } = entry;
|
|
910
|
+
if (hashValue(content) !== recordedHash) return void 0;
|
|
911
|
+
sequence = entry.sequence;
|
|
912
|
+
hash3 = recordedHash;
|
|
913
|
+
advanced = true;
|
|
914
|
+
}
|
|
915
|
+
if (!advanced) return checkpoint;
|
|
916
|
+
return { version: RETENTION_CHECKPOINT_VERSION, sequence, hash: hash3 };
|
|
917
|
+
}
|
|
918
|
+
async function verifyPartitionFiles(files, checkpoint) {
|
|
919
|
+
const checkpointSequence = checkpoint?.sequence ?? 0;
|
|
920
|
+
let previousHash = checkpoint?.hash ?? GENESIS_HASH;
|
|
921
|
+
let entries = 0;
|
|
922
|
+
let lastSequence = checkpointSequence;
|
|
923
|
+
let coveredPrevious;
|
|
924
|
+
for (const file of files) {
|
|
925
|
+
let chunk;
|
|
926
|
+
try {
|
|
927
|
+
chunk = await readEntriesStrict(file);
|
|
928
|
+
} catch (error) {
|
|
929
|
+
return { ok: false, entries, brokenAt: entries, reason: errorMessage(error) };
|
|
930
|
+
}
|
|
931
|
+
for (const entry of chunk) {
|
|
932
|
+
const { hash: recordedHash, ...content } = entry;
|
|
933
|
+
if (entry.sequence <= checkpointSequence) {
|
|
934
|
+
if (hashValue(content) !== recordedHash) return { ok: false, entries, brokenAt: entries, reason: "entry hash mismatch" };
|
|
935
|
+
if (coveredPrevious && entry.sequence !== coveredPrevious.sequence + 1) {
|
|
936
|
+
return { ok: false, entries, brokenAt: entries, reason: `sequence ${entry.sequence} is not ${coveredPrevious.sequence + 1}` };
|
|
937
|
+
}
|
|
938
|
+
if (coveredPrevious && entry.previousHash !== coveredPrevious.hash) {
|
|
939
|
+
return { ok: false, entries, brokenAt: entries, reason: "previous hash mismatch" };
|
|
940
|
+
}
|
|
941
|
+
if (entry.sequence === checkpointSequence && recordedHash !== checkpoint?.hash) {
|
|
942
|
+
return { ok: false, entries, brokenAt: entries, reason: "retention checkpoint hash mismatch" };
|
|
943
|
+
}
|
|
944
|
+
coveredPrevious = entry;
|
|
945
|
+
continue;
|
|
946
|
+
}
|
|
947
|
+
const index = entries++;
|
|
948
|
+
if (entry.sequence !== lastSequence + 1) return { ok: false, entries, brokenAt: index, reason: `sequence ${entry.sequence} is not ${lastSequence + 1}` };
|
|
949
|
+
if (entry.previousHash !== previousHash) return { ok: false, entries, brokenAt: index, reason: "previous hash mismatch" };
|
|
950
|
+
if (hashValue(content) !== recordedHash) return { ok: false, entries, brokenAt: index, reason: "entry hash mismatch" };
|
|
951
|
+
previousHash = recordedHash;
|
|
952
|
+
lastSequence = entry.sequence;
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
if (coveredPrevious && (coveredPrevious.sequence !== checkpointSequence || coveredPrevious.hash !== checkpoint?.hash)) {
|
|
956
|
+
return { ok: false, entries, brokenAt: entries, reason: "retention checkpoint hash mismatch" };
|
|
957
|
+
}
|
|
958
|
+
return { ok: true, entries, lastSequence, lastHash: previousHash };
|
|
959
|
+
}
|
|
960
|
+
async function readRetentionCheckpoint(basePath) {
|
|
961
|
+
const checkpointPath = retentionCheckpointPath(basePath);
|
|
962
|
+
let raw;
|
|
963
|
+
try {
|
|
964
|
+
raw = await fs3.readFile(checkpointPath, "utf8");
|
|
965
|
+
} catch (error) {
|
|
966
|
+
return isNotFound(error) ? {} : { error: `cannot read retention checkpoint: ${errorMessage(error)}` };
|
|
967
|
+
}
|
|
968
|
+
try {
|
|
969
|
+
const parsed = JSON.parse(raw);
|
|
970
|
+
if (parsed.version !== RETENTION_CHECKPOINT_VERSION || !Number.isSafeInteger(parsed.sequence) || (parsed.sequence ?? -1) < 0 || !isHash(parsed.hash)) {
|
|
971
|
+
return { error: "invalid Chronicle retention checkpoint" };
|
|
972
|
+
}
|
|
973
|
+
return { checkpoint: parsed };
|
|
974
|
+
} catch {
|
|
975
|
+
return { error: "invalid Chronicle retention checkpoint JSON" };
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
async function writeRetentionCheckpoint(basePath, checkpoint) {
|
|
979
|
+
await atomicWrite(retentionCheckpointPath(basePath), `${JSON.stringify(checkpoint)}
|
|
980
|
+
`, { mode: 384 });
|
|
981
|
+
}
|
|
982
|
+
function isHash(value) {
|
|
983
|
+
return typeof value === "string" && /^[a-f0-9]{64}$/.test(value);
|
|
984
|
+
}
|
|
985
|
+
function parseIndex(filePath, base, ext) {
|
|
986
|
+
const suffix = path4.basename(filePath).slice(base.length + 1, -ext.length);
|
|
987
|
+
return suffix ? parseInt(suffix, 10) : 0;
|
|
988
|
+
}
|
|
989
|
+
function partitionIndex(filePath, basePath) {
|
|
990
|
+
const ext = path4.extname(basePath);
|
|
991
|
+
return parseIndex(filePath, path4.basename(basePath, ext), ext);
|
|
992
|
+
}
|
|
993
|
+
function escapeRegex(text2) {
|
|
994
|
+
return text2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
995
|
+
}
|
|
650
996
|
async function readLastEntry(filePath) {
|
|
651
997
|
let handle;
|
|
652
998
|
try {
|
|
@@ -657,22 +1003,20 @@ async function readLastEntry(filePath) {
|
|
|
657
1003
|
}
|
|
658
1004
|
try {
|
|
659
1005
|
const size = (await handle.stat()).size;
|
|
660
|
-
|
|
661
|
-
let position = size;
|
|
662
|
-
let suffix = "";
|
|
1006
|
+
let position = size, suffix = "";
|
|
663
1007
|
while (position > 0) {
|
|
664
|
-
const length = Math.min(
|
|
1008
|
+
const length = Math.min(65536, position);
|
|
665
1009
|
position -= length;
|
|
666
|
-
const
|
|
667
|
-
await handle.read(
|
|
668
|
-
suffix =
|
|
1010
|
+
const buf = Buffer.allocUnsafe(length);
|
|
1011
|
+
await handle.read(buf, 0, length, position);
|
|
1012
|
+
suffix = buf.toString("utf8") + suffix;
|
|
669
1013
|
const lines = suffix.split("\n");
|
|
670
|
-
const
|
|
671
|
-
for (let
|
|
672
|
-
const
|
|
673
|
-
if (!
|
|
1014
|
+
const start = position === 0 ? 0 : 1;
|
|
1015
|
+
for (let i = lines.length - 1; i >= start; i--) {
|
|
1016
|
+
const trimmed = lines[i].trim();
|
|
1017
|
+
if (!trimmed) continue;
|
|
674
1018
|
try {
|
|
675
|
-
return JSON.parse(
|
|
1019
|
+
return JSON.parse(trimmed);
|
|
676
1020
|
} catch {
|
|
677
1021
|
}
|
|
678
1022
|
}
|
|
@@ -683,7 +1027,7 @@ async function readLastEntry(filePath) {
|
|
|
683
1027
|
await handle.close();
|
|
684
1028
|
}
|
|
685
1029
|
}
|
|
686
|
-
async function
|
|
1030
|
+
async function readEntriesStrict(filePath) {
|
|
687
1031
|
let raw;
|
|
688
1032
|
try {
|
|
689
1033
|
raw = await fs3.readFile(filePath, "utf8");
|
|
@@ -692,12 +1036,14 @@ async function readEntries(filePath, strict = false) {
|
|
|
692
1036
|
throw error;
|
|
693
1037
|
}
|
|
694
1038
|
const entries = [];
|
|
695
|
-
|
|
696
|
-
|
|
1039
|
+
const lines = raw.split("\n");
|
|
1040
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1041
|
+
const trimmed = lines[i].trim();
|
|
1042
|
+
if (!trimmed) continue;
|
|
697
1043
|
try {
|
|
698
|
-
entries.push(JSON.parse(
|
|
699
|
-
} catch
|
|
700
|
-
|
|
1044
|
+
entries.push(JSON.parse(trimmed));
|
|
1045
|
+
} catch {
|
|
1046
|
+
throw new Error(`invalid JSON at line ${i + 1} in ${path4.basename(filePath)}`);
|
|
701
1047
|
}
|
|
702
1048
|
}
|
|
703
1049
|
return entries;
|
|
@@ -708,16 +1054,14 @@ function hashValue(value) {
|
|
|
708
1054
|
function stableStringify(value) {
|
|
709
1055
|
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
710
1056
|
if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
|
|
711
|
-
const
|
|
712
|
-
return `{${Object.keys(
|
|
1057
|
+
const obj = value;
|
|
1058
|
+
return `{${Object.keys(obj).sort().map((k) => `${JSON.stringify(k)}:${stableStringify(obj[k])}`).join(",")}}`;
|
|
713
1059
|
}
|
|
714
1060
|
function removeUndefined(value) {
|
|
715
1061
|
if (Array.isArray(value)) return value.map((item) => item === void 0 ? null : removeUndefined(item));
|
|
716
1062
|
if (value === null || typeof value !== "object") return value;
|
|
717
1063
|
const result = {};
|
|
718
|
-
for (const [key, item] of Object.entries(value))
|
|
719
|
-
if (item !== void 0) result[key] = removeUndefined(item);
|
|
720
|
-
}
|
|
1064
|
+
for (const [key, item] of Object.entries(value)) if (item !== void 0) result[key] = removeUndefined(item);
|
|
721
1065
|
return result;
|
|
722
1066
|
}
|
|
723
1067
|
function isNotFound(error) {
|
|
@@ -748,7 +1092,9 @@ function wireProviderAttemptsToChronicle(options) {
|
|
|
748
1092
|
durationNs: millisecondsToNanoseconds(event.durationMs)
|
|
749
1093
|
}))
|
|
750
1094
|
];
|
|
751
|
-
return () => unsubs.forEach((unsubscribe) =>
|
|
1095
|
+
return () => unsubs.forEach((unsubscribe) => {
|
|
1096
|
+
unsubscribe();
|
|
1097
|
+
});
|
|
752
1098
|
}
|
|
753
1099
|
function persist(options, event, base) {
|
|
754
1100
|
const context = typeof options.context === "function" ? options.context() : options.context;
|
|
@@ -803,6 +1149,25 @@ function wireToolsToChronicle(options) {
|
|
|
803
1149
|
}
|
|
804
1150
|
});
|
|
805
1151
|
}),
|
|
1152
|
+
options.events.on("permission.evaluated", (event) => {
|
|
1153
|
+
persist2(options, event, {
|
|
1154
|
+
eventType: "permission.evaluated",
|
|
1155
|
+
outcome: event.effectiveDecision === "deny" ? "denied" : "success",
|
|
1156
|
+
attributes: {
|
|
1157
|
+
toolName: event.name,
|
|
1158
|
+
inputHash: event.inputHash,
|
|
1159
|
+
policyDecision: event.policyDecision,
|
|
1160
|
+
effectiveDecision: event.effectiveDecision,
|
|
1161
|
+
decisionSource: event.decisionSource,
|
|
1162
|
+
reason: event.reason ? options.scrubber.scrub(event.reason) : void 0,
|
|
1163
|
+
riskTier: event.riskTier,
|
|
1164
|
+
yoloEnabled: event.yoloEnabled,
|
|
1165
|
+
boundaryDecision: event.boundaryDecision,
|
|
1166
|
+
boundaryReason: event.boundaryReason ? options.scrubber.scrub(event.boundaryReason) : void 0,
|
|
1167
|
+
capabilityDowngraded: event.capabilityDowngraded
|
|
1168
|
+
}
|
|
1169
|
+
});
|
|
1170
|
+
}),
|
|
806
1171
|
options.events.on("tool.executed", (event) => {
|
|
807
1172
|
const output = options.scrubber.scrub(event.output ?? "");
|
|
808
1173
|
persist2(options, event, {
|
|
@@ -853,7 +1218,9 @@ function wireToolsToChronicle(options) {
|
|
|
853
1218
|
});
|
|
854
1219
|
})
|
|
855
1220
|
];
|
|
856
|
-
return () => unsubs.forEach((unsubscribe) =>
|
|
1221
|
+
return () => unsubs.forEach((unsubscribe) => {
|
|
1222
|
+
unsubscribe();
|
|
1223
|
+
});
|
|
857
1224
|
}
|
|
858
1225
|
function persist2(options, event, fields) {
|
|
859
1226
|
const context = typeof options.context === "function" ? options.context() : options.context;
|
|
@@ -958,7 +1325,9 @@ function wireProcessesToChronicle(options) {
|
|
|
958
1325
|
}
|
|
959
1326
|
}))
|
|
960
1327
|
];
|
|
961
|
-
return () => unsubs.forEach((unsubscribe) =>
|
|
1328
|
+
return () => unsubs.forEach((unsubscribe) => {
|
|
1329
|
+
unsubscribe();
|
|
1330
|
+
});
|
|
962
1331
|
}
|
|
963
1332
|
function persist3(options, event, fields) {
|
|
964
1333
|
const context = typeof options.context === "function" ? options.context() : options.context;
|
|
@@ -1086,7 +1455,9 @@ function wireDecisionsToChronicle(options) {
|
|
|
1086
1455
|
options.events.on("brain.human_answered", (e) => write("decision.human_answered", e.at, e.sessionId, e.id, { resolver: "human", optionId: e.optionId, denied: e.deny ?? false, answerHash: hash(e.text) }, e.deny ? "denied" : "success")),
|
|
1087
1456
|
options.events.on("brain.outcome", (e) => write("decision.outcome_observed", e.at, e.sessionId, e.requestId, { observedOutcome: e.outcome, detailHash: hash(e.detail) }, e.outcome))
|
|
1088
1457
|
];
|
|
1089
|
-
return () => offs.forEach((off) =>
|
|
1458
|
+
return () => offs.forEach((off) => {
|
|
1459
|
+
off();
|
|
1460
|
+
});
|
|
1090
1461
|
}
|
|
1091
1462
|
function hash(value) {
|
|
1092
1463
|
return value ? createHash6("sha256").update(value).digest("hex") : void 0;
|
|
@@ -1116,6 +1487,9 @@ var CODING_SIGNAL = [
|
|
|
1116
1487
|
];
|
|
1117
1488
|
var SENSITIVE_KEY = /(content|text|prompt|question|rationale|reason|detail|summary|description|context|input|output|error|message|secret|token|password|key)$/i;
|
|
1118
1489
|
var PRESERVE_STRING_KEY = /(^|_)(id|status|state|kind|type|source|model|provider|phase|risk|fallback|path|name|role|sha|branch|mode)$/i;
|
|
1490
|
+
var TRUNCATED_ARRAYS = /* @__PURE__ */ new Set(["recentMail", "recentTools", "recentCommands"]);
|
|
1491
|
+
var TRUNCATED_ARRAY_MAX = 5;
|
|
1492
|
+
var DEFAULT_ARRAY_MAX = 20;
|
|
1119
1493
|
function wireDomainEventsToChronicle(options) {
|
|
1120
1494
|
return options.events.onAny((eventName, payload) => {
|
|
1121
1495
|
if (SPECIALIZED.some((pattern) => pattern.test(eventName))) return;
|
|
@@ -1197,8 +1571,9 @@ function sanitize(value, key = "", depth = 0, seen = /* @__PURE__ */ new WeakSet
|
|
|
1197
1571
|
if (depth >= 5) return { hash: digest(safeString(value)), depthLimited: true };
|
|
1198
1572
|
seen.add(value);
|
|
1199
1573
|
if (Array.isArray(value)) {
|
|
1200
|
-
const
|
|
1201
|
-
|
|
1574
|
+
const cap = TRUNCATED_ARRAYS.has(key) ? TRUNCATED_ARRAY_MAX : DEFAULT_ARRAY_MAX;
|
|
1575
|
+
const items = value.slice(0, cap).map((item) => sanitize(item, key, depth + 1, seen));
|
|
1576
|
+
return value.length > cap ? { items, total: value.length, truncated: true } : items;
|
|
1202
1577
|
}
|
|
1203
1578
|
const output = {};
|
|
1204
1579
|
const entries = Object.entries(value);
|
|
@@ -1291,7 +1666,9 @@ function wireProviderStreamsToChronicle(options) {
|
|
|
1291
1666
|
];
|
|
1292
1667
|
return () => {
|
|
1293
1668
|
for (const state of [...states.values()]) flush(state.sessionId, state.agentId, "failure");
|
|
1294
|
-
offs.forEach((off) =>
|
|
1669
|
+
offs.forEach((off) => {
|
|
1670
|
+
off();
|
|
1671
|
+
});
|
|
1295
1672
|
};
|
|
1296
1673
|
}
|
|
1297
1674
|
|
|
@@ -1527,7 +1904,9 @@ function wireRollupsToChronicle(options) {
|
|
|
1527
1904
|
return () => {
|
|
1528
1905
|
clearInterval(timer);
|
|
1529
1906
|
for (const key of [...buckets.keys()]) flush(key);
|
|
1530
|
-
offs.forEach((off) =>
|
|
1907
|
+
offs.forEach((off) => {
|
|
1908
|
+
off();
|
|
1909
|
+
});
|
|
1531
1910
|
};
|
|
1532
1911
|
}
|
|
1533
1912
|
function text(value) {
|
|
@@ -1542,94 +1921,166 @@ function safeDigest(value) {
|
|
|
1542
1921
|
}
|
|
1543
1922
|
|
|
1544
1923
|
// src/chronicle/query.ts
|
|
1924
|
+
import { createHash as createHash11 } from "node:crypto";
|
|
1925
|
+
import { createReadStream } from "node:fs";
|
|
1545
1926
|
import * as fs4 from "node:fs/promises";
|
|
1546
1927
|
import * as path5 from "node:path";
|
|
1928
|
+
import { createInterface } from "node:readline";
|
|
1929
|
+
var MAX_CURSOR_SNAPSHOT_ENTRIES = 1e4;
|
|
1930
|
+
function streamLines(filePath, maxBytes) {
|
|
1931
|
+
const stream = createReadStream(filePath, {
|
|
1932
|
+
encoding: "utf8",
|
|
1933
|
+
highWaterMark: 256 * 1024,
|
|
1934
|
+
...maxBytes !== void 0 ? { end: maxBytes - 1 } : {}
|
|
1935
|
+
});
|
|
1936
|
+
const rl = createInterface({ input: stream, crlfDelay: Infinity });
|
|
1937
|
+
return rl[Symbol.asyncIterator]();
|
|
1938
|
+
}
|
|
1547
1939
|
var ChronicleQueryEngine = class _ChronicleQueryEngine {
|
|
1548
|
-
constructor(
|
|
1549
|
-
this.events = events;
|
|
1940
|
+
constructor(files, diagnostics) {
|
|
1550
1941
|
this.diagnostics = diagnostics;
|
|
1942
|
+
this.partitionFiles = files;
|
|
1551
1943
|
}
|
|
1552
|
-
events;
|
|
1553
1944
|
diagnostics;
|
|
1945
|
+
partitionFiles;
|
|
1554
1946
|
static async fromDirectory(directory) {
|
|
1555
|
-
|
|
1947
|
+
const files = await findPartitions(path5.resolve(directory));
|
|
1948
|
+
return new _ChronicleQueryEngine(files, { sourceFiles: files.length, invalidLines: 0 });
|
|
1556
1949
|
}
|
|
1557
1950
|
static async fromFiles(files) {
|
|
1558
|
-
|
|
1951
|
+
return new _ChronicleQueryEngine(files, { sourceFiles: files.length, invalidLines: 0 });
|
|
1952
|
+
}
|
|
1953
|
+
/** Stream all partitions, filter, and return the requested page + summary. */
|
|
1954
|
+
async query(query = {}) {
|
|
1955
|
+
const order = query.order ?? "desc";
|
|
1956
|
+
const limit = Math.max(1, Math.min(query.limit ?? 100, 1e4));
|
|
1957
|
+
const queryHash = hashQuery(query);
|
|
1958
|
+
const cursor = decodeCursor(query.cursor, order, queryHash);
|
|
1959
|
+
const snapshotFiles = cursor ? await resolveSnapshotFiles(this.partitionFiles, cursor.snapshot) : await captureSnapshot(this.partitionFiles);
|
|
1960
|
+
const files = order === "asc" ? snapshotFiles : snapshotFiles.slice().reverse();
|
|
1961
|
+
const summaryAcc = createSummaryAccumulator();
|
|
1962
|
+
const orderedCandidates = [];
|
|
1963
|
+
const pageOrder = (left, right) => compareEvents(left, right) * (order === "asc" ? 1 : -1);
|
|
1964
|
+
let totalCount = 0;
|
|
1965
|
+
let remainingCount = 0;
|
|
1966
|
+
let scannedEvents = 0;
|
|
1559
1967
|
let invalidLines = 0;
|
|
1560
|
-
for (const
|
|
1561
|
-
let raw;
|
|
1968
|
+
for (const snapshotFile of files) {
|
|
1562
1969
|
try {
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1970
|
+
if (snapshotFile.size === 0) continue;
|
|
1971
|
+
const lines = order === "asc" ? streamLines(snapshotFile.file, snapshotFile.size) : reverseLines(snapshotFile.file, snapshotFile.size);
|
|
1972
|
+
for await (const line of lines) {
|
|
1973
|
+
if (!line.trim()) continue;
|
|
1974
|
+
let event;
|
|
1975
|
+
try {
|
|
1976
|
+
event = JSON.parse(line);
|
|
1977
|
+
if (!isChronicleEvent(event)) {
|
|
1978
|
+
invalidLines++;
|
|
1979
|
+
continue;
|
|
1980
|
+
}
|
|
1981
|
+
} catch {
|
|
1982
|
+
invalidLines++;
|
|
1983
|
+
continue;
|
|
1984
|
+
}
|
|
1985
|
+
scannedEvents++;
|
|
1986
|
+
if (!matches(event, query)) continue;
|
|
1987
|
+
totalCount++;
|
|
1988
|
+
updateSummary(summaryAcc, event);
|
|
1989
|
+
if (cursor && compareEventToKey(event, cursor.after) * (order === "asc" ? 1 : -1) <= 0) {
|
|
1990
|
+
continue;
|
|
1991
|
+
}
|
|
1992
|
+
remainingCount++;
|
|
1993
|
+
const insertionIndex = findInsertionIndex(orderedCandidates, event, pageOrder);
|
|
1994
|
+
if (insertionIndex < limit) {
|
|
1995
|
+
orderedCandidates.splice(insertionIndex, 0, event);
|
|
1996
|
+
if (orderedCandidates.length > limit) orderedCandidates.pop();
|
|
1997
|
+
}
|
|
1575
1998
|
}
|
|
1999
|
+
} catch {
|
|
1576
2000
|
}
|
|
1577
2001
|
}
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
query(query = {}) {
|
|
1581
|
-
const order = query.order ?? "desc";
|
|
1582
|
-
const found = this.events.filter((event) => matches(event, query));
|
|
1583
|
-
found.sort((a, b) => compareEvents(a, b) * (order === "asc" ? 1 : -1));
|
|
1584
|
-
const offset = decodeCursor(query.cursor);
|
|
1585
|
-
const limit = Math.max(1, Math.min(query.limit ?? 100, 1e4));
|
|
1586
|
-
const events = found.slice(offset, offset + limit);
|
|
1587
|
-
const nextOffset = offset + events.length;
|
|
2002
|
+
const pageEvents = orderedCandidates;
|
|
2003
|
+
const lastEvent = pageEvents.at(-1);
|
|
1588
2004
|
return {
|
|
1589
|
-
events,
|
|
1590
|
-
total:
|
|
1591
|
-
summary:
|
|
1592
|
-
...
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
2005
|
+
events: pageEvents,
|
|
2006
|
+
total: totalCount,
|
|
2007
|
+
summary: finalizeSummary(summaryAcc),
|
|
2008
|
+
...lastEvent && pageEvents.length < remainingCount ? { nextCursor: encodeCursor({
|
|
2009
|
+
version: 1,
|
|
2010
|
+
order,
|
|
2011
|
+
queryHash,
|
|
2012
|
+
after: orderKey(lastEvent),
|
|
2013
|
+
snapshot: snapshotFiles.map(({ id, size }) => ({ id, size }))
|
|
2014
|
+
}) } : {},
|
|
2015
|
+
scannedEvents,
|
|
2016
|
+
sourceFiles: snapshotFiles.length,
|
|
2017
|
+
invalidLines
|
|
1596
2018
|
};
|
|
1597
2019
|
}
|
|
1598
|
-
|
|
2020
|
+
/** Stream all partitions and compute facet value counts. */
|
|
2021
|
+
async facet(field, query = {}, limit = 100) {
|
|
1599
2022
|
const counts = /* @__PURE__ */ new Map();
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
2023
|
+
let invalidLines = 0;
|
|
2024
|
+
for (const file of this.partitionFiles) {
|
|
2025
|
+
try {
|
|
2026
|
+
for await (const line of streamLines(file)) {
|
|
2027
|
+
if (!line.trim()) continue;
|
|
2028
|
+
let event;
|
|
2029
|
+
try {
|
|
2030
|
+
event = JSON.parse(line);
|
|
2031
|
+
if (!isChronicleEvent(event)) {
|
|
2032
|
+
invalidLines++;
|
|
2033
|
+
continue;
|
|
2034
|
+
}
|
|
2035
|
+
} catch {
|
|
2036
|
+
invalidLines++;
|
|
2037
|
+
continue;
|
|
2038
|
+
}
|
|
2039
|
+
if (!matches(event, query)) continue;
|
|
2040
|
+
const value = facetValue(event, field);
|
|
2041
|
+
if (value !== void 0) counts.set(value, (counts.get(value) ?? 0) + 1);
|
|
2042
|
+
}
|
|
2043
|
+
} catch {
|
|
2044
|
+
}
|
|
1604
2045
|
}
|
|
2046
|
+
this.diagnostics.invalidLines = invalidLines;
|
|
1605
2047
|
return [...counts].map(([value, count]) => ({ value, count })).sort((a, b) => b.count - a.count || a.value.localeCompare(b.value)).slice(0, Math.max(0, limit));
|
|
1606
2048
|
}
|
|
1607
2049
|
/** Expand explicit and typed correlation edges; temporal proximity alone never creates causality. */
|
|
1608
|
-
graph(seed, hops = 2, maxNodes = 1e3) {
|
|
1609
|
-
const
|
|
1610
|
-
const
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
2050
|
+
async graph(seed = {}, hops = 2, maxNodes = 1e3) {
|
|
2051
|
+
const nodeLimit = Math.max(0, Math.floor(maxNodes));
|
|
2052
|
+
const selected = /* @__PURE__ */ new Map();
|
|
2053
|
+
let seedCount = 0;
|
|
2054
|
+
for await (const event of streamEvents(this.partitionFiles)) {
|
|
2055
|
+
if (!matches(event, seed)) continue;
|
|
2056
|
+
seedCount++;
|
|
2057
|
+
if (selected.size < nodeLimit) selected.set(event.eventId, event);
|
|
1615
2058
|
}
|
|
1616
|
-
const selected = new Map(seeds.slice(0, maxNodes).map((event) => [event.eventId, event]));
|
|
1617
2059
|
let frontier = [...selected.values()];
|
|
1618
|
-
|
|
2060
|
+
const depthLimit = Math.max(0, Math.min(hops, 10));
|
|
2061
|
+
for (let depth = 0; depth < depthLimit && frontier.length > 0 && selected.size < nodeLimit; depth++) {
|
|
2062
|
+
const frontierKeys = new Set(frontier.flatMap((event) => relationKeys(event).map((relation) => relation.key)));
|
|
1619
2063
|
const next = [];
|
|
1620
|
-
for
|
|
1621
|
-
if (
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
2064
|
+
for await (const event of streamEvents(this.partitionFiles)) {
|
|
2065
|
+
if (selected.has(event.eventId)) continue;
|
|
2066
|
+
if (!relationKeys(event).some((relation) => frontierKeys.has(relation.key))) continue;
|
|
2067
|
+
selected.set(event.eventId, event);
|
|
2068
|
+
next.push(event);
|
|
2069
|
+
if (selected.size >= nodeLimit) break;
|
|
1625
2070
|
}
|
|
1626
2071
|
frontier = next;
|
|
1627
2072
|
}
|
|
1628
2073
|
const nodes = [...selected.values()].sort(compareEvents);
|
|
2074
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
2075
|
+
for (const node of nodes) for (const relation of relationKeys(node)) {
|
|
2076
|
+
const related = byKey.get(relation.key) ?? [];
|
|
2077
|
+
related.push(node);
|
|
2078
|
+
byKey.set(relation.key, related);
|
|
2079
|
+
}
|
|
1629
2080
|
const edges = [];
|
|
1630
2081
|
const seen = /* @__PURE__ */ new Set();
|
|
1631
2082
|
for (const node of nodes) for (const relation of relationKeys(node)) for (const candidate of byKey.get(relation.key) ?? []) {
|
|
1632
|
-
if (
|
|
2083
|
+
if (candidate.eventId === node.eventId) continue;
|
|
1633
2084
|
const [from, to] = compareEvents(node, candidate) <= 0 ? [node, candidate] : [candidate, node];
|
|
1634
2085
|
const id = `${from.eventId}:${to.eventId}:${relation.kind}`;
|
|
1635
2086
|
if (!seen.has(id)) {
|
|
@@ -1637,87 +2088,241 @@ var ChronicleQueryEngine = class _ChronicleQueryEngine {
|
|
|
1637
2088
|
edges.push({ from: from.eventId, to: to.eventId, kind: relation.kind, confidence: relation.confidence });
|
|
1638
2089
|
}
|
|
1639
2090
|
}
|
|
1640
|
-
return { nodes, edges, truncated:
|
|
2091
|
+
return { nodes, edges, truncated: seedCount > nodeLimit || selected.size >= nodeLimit };
|
|
1641
2092
|
}
|
|
1642
2093
|
};
|
|
1643
|
-
function
|
|
1644
|
-
const
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
2094
|
+
async function* streamEvents(files) {
|
|
2095
|
+
for (const file of files) {
|
|
2096
|
+
try {
|
|
2097
|
+
for await (const line of streamLines(file)) {
|
|
2098
|
+
if (!line.trim()) continue;
|
|
2099
|
+
try {
|
|
2100
|
+
const event = JSON.parse(line);
|
|
2101
|
+
if (isChronicleEvent(event)) yield event;
|
|
2102
|
+
} catch {
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
} catch {
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2109
|
+
async function* reverseLines(filePath, maxBytes) {
|
|
2110
|
+
const CHUNK = 64 * 1024;
|
|
2111
|
+
const NEWLINE = 10;
|
|
2112
|
+
let handle;
|
|
2113
|
+
try {
|
|
2114
|
+
handle = await fs4.open(filePath, "r");
|
|
2115
|
+
} catch {
|
|
2116
|
+
return;
|
|
2117
|
+
}
|
|
2118
|
+
try {
|
|
2119
|
+
const fileSize = (await handle.stat()).size;
|
|
2120
|
+
const size = Math.min(fileSize, maxBytes ?? fileSize);
|
|
2121
|
+
let position = size;
|
|
2122
|
+
let suffix = Buffer.alloc(0);
|
|
2123
|
+
while (position > 0) {
|
|
2124
|
+
const length = Math.min(CHUNK, position);
|
|
2125
|
+
position -= length;
|
|
2126
|
+
const buffer = Buffer.allocUnsafe(length);
|
|
2127
|
+
await handle.read(buffer, 0, length, position);
|
|
2128
|
+
const data = suffix.length === 0 ? buffer : Buffer.concat([buffer, suffix]);
|
|
2129
|
+
let lineEnd = data.length;
|
|
2130
|
+
let firstNewline = -1;
|
|
2131
|
+
for (let index = data.length - 1; index >= 0; index--) {
|
|
2132
|
+
if (data[index] !== NEWLINE) continue;
|
|
2133
|
+
const trimmed2 = data.subarray(index + 1, lineEnd).toString("utf8").trim();
|
|
2134
|
+
if (trimmed2) yield trimmed2;
|
|
2135
|
+
lineEnd = index;
|
|
2136
|
+
firstNewline = index;
|
|
2137
|
+
}
|
|
2138
|
+
suffix = firstNewline >= 0 ? Buffer.from(data.subarray(0, firstNewline)) : data;
|
|
2139
|
+
}
|
|
2140
|
+
const trimmed = suffix.toString("utf8").trim();
|
|
2141
|
+
if (trimmed) yield trimmed;
|
|
2142
|
+
} finally {
|
|
2143
|
+
await handle.close();
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
function createSummaryAccumulator() {
|
|
2147
|
+
return {
|
|
2148
|
+
logicalRequestIds: /* @__PURE__ */ new Set(),
|
|
2149
|
+
modelAttempts: 0,
|
|
2150
|
+
completedAttempts: 0,
|
|
2151
|
+
failedAttempts: 0,
|
|
2152
|
+
scheduledRetries: 0,
|
|
2153
|
+
fallbacks: 0,
|
|
2154
|
+
providers: /* @__PURE__ */ new Set(),
|
|
2155
|
+
models: /* @__PURE__ */ new Set(),
|
|
2156
|
+
inputTokens: 0,
|
|
2157
|
+
outputTokens: 0,
|
|
2158
|
+
cacheReadTokens: 0,
|
|
2159
|
+
cacheWriteTokens: 0,
|
|
2160
|
+
costByScope: /* @__PURE__ */ new Map(),
|
|
2161
|
+
providerDurations: [],
|
|
2162
|
+
toolCalls: 0,
|
|
2163
|
+
completedTools: 0,
|
|
2164
|
+
failedTools: 0,
|
|
2165
|
+
toolDurations: [],
|
|
2166
|
+
processes: 0,
|
|
2167
|
+
failedProcesses: 0,
|
|
2168
|
+
fileEvents: 0,
|
|
2169
|
+
uniqueFiles: /* @__PURE__ */ new Set(),
|
|
2170
|
+
agentEvents: 0,
|
|
2171
|
+
uniqueAgents: /* @__PURE__ */ new Set(),
|
|
2172
|
+
decisions: 0,
|
|
2173
|
+
escalations: 0,
|
|
2174
|
+
failures: 0,
|
|
2175
|
+
cancellations: 0,
|
|
2176
|
+
families: { llm: 0, agent: 0, tool: 0, file: 0, memory: 0, task: 0, decision: 0, runtime: 0 },
|
|
2177
|
+
failuresByFamily: { llm: 0, agent: 0, tool: 0, file: 0, memory: 0, task: 0, decision: 0, runtime: 0 }
|
|
1649
2178
|
};
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
const
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
2179
|
+
}
|
|
2180
|
+
function updateSummary(acc, event) {
|
|
2181
|
+
const family = signalFamily(event);
|
|
2182
|
+
acc.families[family]++;
|
|
2183
|
+
if (isTerminalFailure(event)) acc.failuresByFamily[family]++;
|
|
2184
|
+
if (event.correlation.logicalRequestId) acc.logicalRequestIds.add(event.correlation.logicalRequestId);
|
|
2185
|
+
if (event.runtime?.providerId) acc.providers.add(event.runtime.providerId);
|
|
2186
|
+
if (event.runtime?.modelId) acc.models.add(event.runtime.modelId);
|
|
2187
|
+
if (event.eventType === "provider.attempt.started") acc.modelAttempts++;
|
|
2188
|
+
else if (event.eventType === "provider.attempt.completed") {
|
|
2189
|
+
acc.completedAttempts++;
|
|
2190
|
+
acc.inputTokens += numberAt(event, "usage.input");
|
|
2191
|
+
acc.outputTokens += numberAt(event, "usage.output");
|
|
2192
|
+
acc.cacheReadTokens += numberAt(event, "usage.cacheRead");
|
|
2193
|
+
acc.cacheWriteTokens += numberAt(event, "usage.cacheWrite");
|
|
2194
|
+
const dur = durationMs(event);
|
|
2195
|
+
if (dur > 0) acc.providerDurations.push(dur);
|
|
2196
|
+
} else if (event.eventType === "provider.attempt.failed") {
|
|
2197
|
+
acc.failedAttempts++;
|
|
2198
|
+
if (event.attributes?.retryScheduled === true) acc.scheduledRetries++;
|
|
2199
|
+
const dur = durationMs(event);
|
|
2200
|
+
if (dur > 0) acc.providerDurations.push(dur);
|
|
2201
|
+
} else if (event.eventType === "provider.fallback") acc.fallbacks++;
|
|
2202
|
+
else if (event.eventType === "tool.started") acc.toolCalls++;
|
|
2203
|
+
else if (event.eventType === "tool.executed") {
|
|
2204
|
+
acc.completedTools++;
|
|
2205
|
+
const dur = durationMs(event);
|
|
2206
|
+
if (dur > 0) acc.toolDurations.push(dur);
|
|
2207
|
+
} else if (event.eventType === "tool.failed") {
|
|
2208
|
+
acc.failedTools++;
|
|
2209
|
+
const dur = durationMs(event);
|
|
2210
|
+
if (dur > 0) acc.toolDurations.push(dur);
|
|
2211
|
+
} else if (event.eventType === "process.started") acc.processes++;
|
|
2212
|
+
else if (event.eventType === "process.completed" && event.outcome === "failure") acc.failedProcesses++;
|
|
2213
|
+
else if (event.eventType === "decision.requested") acc.decisions++;
|
|
2214
|
+
else if (event.eventType === "decision.escalated") acc.escalations++;
|
|
2215
|
+
if (event.eventType === "token.accounted") {
|
|
2216
|
+
const cost = readPath(event.attributes ?? {}, "cost.total");
|
|
2217
|
+
if (typeof cost === "number" && Number.isFinite(cost)) {
|
|
2218
|
+
const key = scopeKey(event);
|
|
2219
|
+
const existing = acc.costByScope.get(key);
|
|
2220
|
+
if (!existing || compareEvents(event, existing.event) > 0) {
|
|
2221
|
+
acc.costByScope.set(key, { cost, event });
|
|
2222
|
+
}
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
if (event.resource?.kind === "file" || event.eventType.startsWith("file.")) {
|
|
2226
|
+
acc.fileEvents++;
|
|
2227
|
+
if (event.resource?.path) acc.uniqueFiles.add(event.resource.path);
|
|
2228
|
+
}
|
|
2229
|
+
if (family === "agent") acc.agentEvents++;
|
|
2230
|
+
if (event.scope.agentId) acc.uniqueAgents.add(event.scope.agentId);
|
|
2231
|
+
if (isTerminalFailure(event)) acc.failures++;
|
|
2232
|
+
if (event.outcome === "cancelled" || event.outcome === "abandoned") acc.cancellations++;
|
|
2233
|
+
}
|
|
2234
|
+
function finalizeSummary(acc) {
|
|
2235
|
+
const sortedProviderDurations = acc.providerDurations.slice().sort((a, b) => a - b);
|
|
2236
|
+
const sortedToolDurations = acc.toolDurations.slice().sort((a, b) => a - b);
|
|
2237
|
+
const totalCost = [...acc.costByScope.values()].reduce((sum, entry) => sum + entry.cost, 0);
|
|
1661
2238
|
return {
|
|
1662
|
-
logicalRequests:
|
|
1663
|
-
modelAttempts:
|
|
1664
|
-
completedAttempts: completedAttempts
|
|
1665
|
-
failedAttempts: failedAttempts
|
|
1666
|
-
scheduledRetries:
|
|
1667
|
-
fallbacks:
|
|
1668
|
-
providers:
|
|
1669
|
-
models:
|
|
1670
|
-
inputTokens:
|
|
1671
|
-
outputTokens:
|
|
1672
|
-
cacheReadTokens:
|
|
1673
|
-
cacheWriteTokens:
|
|
1674
|
-
estimatedCostUsd:
|
|
1675
|
-
providerAvgDurationMs: average(
|
|
1676
|
-
providerP95DurationMs: percentile(
|
|
1677
|
-
toolCalls:
|
|
1678
|
-
completedTools: completedTools
|
|
1679
|
-
failedTools: failedTools
|
|
1680
|
-
toolAvgDurationMs: average(
|
|
1681
|
-
processes:
|
|
1682
|
-
failedProcesses,
|
|
1683
|
-
fileEvents:
|
|
1684
|
-
uniqueFiles:
|
|
1685
|
-
agentEvents:
|
|
1686
|
-
uniqueAgents:
|
|
1687
|
-
decisions:
|
|
1688
|
-
escalations:
|
|
1689
|
-
failures:
|
|
1690
|
-
cancellations:
|
|
1691
|
-
families,
|
|
1692
|
-
failuresByFamily:
|
|
2239
|
+
logicalRequests: acc.logicalRequestIds.size,
|
|
2240
|
+
modelAttempts: acc.modelAttempts,
|
|
2241
|
+
completedAttempts: acc.completedAttempts,
|
|
2242
|
+
failedAttempts: acc.failedAttempts,
|
|
2243
|
+
scheduledRetries: acc.scheduledRetries,
|
|
2244
|
+
fallbacks: acc.fallbacks,
|
|
2245
|
+
providers: acc.providers.size,
|
|
2246
|
+
models: acc.models.size,
|
|
2247
|
+
inputTokens: acc.inputTokens,
|
|
2248
|
+
outputTokens: acc.outputTokens,
|
|
2249
|
+
cacheReadTokens: acc.cacheReadTokens,
|
|
2250
|
+
cacheWriteTokens: acc.cacheWriteTokens,
|
|
2251
|
+
estimatedCostUsd: totalCost,
|
|
2252
|
+
providerAvgDurationMs: average(sortedProviderDurations),
|
|
2253
|
+
providerP95DurationMs: percentile(sortedProviderDurations, 0.95),
|
|
2254
|
+
toolCalls: acc.toolCalls,
|
|
2255
|
+
completedTools: acc.completedTools,
|
|
2256
|
+
failedTools: acc.failedTools,
|
|
2257
|
+
toolAvgDurationMs: average(sortedToolDurations),
|
|
2258
|
+
processes: acc.processes,
|
|
2259
|
+
failedProcesses: acc.failedProcesses,
|
|
2260
|
+
fileEvents: acc.fileEvents,
|
|
2261
|
+
uniqueFiles: acc.uniqueFiles.size,
|
|
2262
|
+
agentEvents: acc.agentEvents,
|
|
2263
|
+
uniqueAgents: acc.uniqueAgents.size,
|
|
2264
|
+
decisions: acc.decisions,
|
|
2265
|
+
escalations: acc.escalations,
|
|
2266
|
+
failures: acc.failures,
|
|
2267
|
+
cancellations: acc.cancellations,
|
|
2268
|
+
families: acc.families,
|
|
2269
|
+
failuresByFamily: acc.failuresByFamily
|
|
2270
|
+
};
|
|
2271
|
+
}
|
|
2272
|
+
Object.defineProperty(ChronicleQueryEngine.prototype, "partitionFiles", {
|
|
2273
|
+
get() {
|
|
2274
|
+
throw new Error("ChronicleQueryEngine no longer loads events on construction. Use async query().");
|
|
2275
|
+
},
|
|
2276
|
+
set(_val) {
|
|
2277
|
+
Object.defineProperty(this, "partitionFiles", { value: _val, writable: false, configurable: false });
|
|
2278
|
+
}
|
|
2279
|
+
});
|
|
2280
|
+
async function findPartitions(root) {
|
|
2281
|
+
const result = [];
|
|
2282
|
+
const partitionName = /^(.*\.events)(?:\.(\d{5}))?\.jsonl$/;
|
|
2283
|
+
const visit = async (directory) => {
|
|
2284
|
+
let entries;
|
|
2285
|
+
try {
|
|
2286
|
+
entries = await fs4.readdir(directory, { withFileTypes: true });
|
|
2287
|
+
} catch {
|
|
2288
|
+
return;
|
|
2289
|
+
}
|
|
2290
|
+
for (const entry of entries) {
|
|
2291
|
+
const full = path5.join(directory, entry.name);
|
|
2292
|
+
if (entry.isDirectory()) await visit(full);
|
|
2293
|
+
else if (entry.isFile() && partitionName.test(entry.name)) result.push(full);
|
|
2294
|
+
}
|
|
1693
2295
|
};
|
|
2296
|
+
await visit(root);
|
|
2297
|
+
return result.sort((left, right) => {
|
|
2298
|
+
const leftMatch = partitionName.exec(path5.basename(left));
|
|
2299
|
+
const rightMatch = partitionName.exec(path5.basename(right));
|
|
2300
|
+
const leftGroup = path5.join(path5.dirname(left), leftMatch?.[1] ?? left);
|
|
2301
|
+
const rightGroup = path5.join(path5.dirname(right), rightMatch?.[1] ?? right);
|
|
2302
|
+
const groupOrder = leftGroup.localeCompare(rightGroup);
|
|
2303
|
+
if (groupOrder !== 0) return groupOrder;
|
|
2304
|
+
return Number(leftMatch?.[2] ?? 0) - Number(rightMatch?.[2] ?? 0);
|
|
2305
|
+
});
|
|
2306
|
+
}
|
|
2307
|
+
function numberAt(event, dotPath) {
|
|
2308
|
+
const value = readPath(event.attributes ?? {}, dotPath);
|
|
2309
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
1694
2310
|
}
|
|
1695
2311
|
function durationMs(event) {
|
|
1696
2312
|
const value = Number(event.durationNs ?? 0) / 1e6;
|
|
1697
2313
|
return Number.isFinite(value) ? value : 0;
|
|
1698
2314
|
}
|
|
1699
2315
|
function average(values) {
|
|
1700
|
-
return values.length ? values.reduce((sum,
|
|
2316
|
+
return values.length ? values.reduce((sum, v) => sum + v, 0) / values.length : 0;
|
|
1701
2317
|
}
|
|
1702
2318
|
function percentile(sorted, quantile) {
|
|
1703
2319
|
return sorted.length ? sorted[Math.min(sorted.length - 1, Math.max(0, Math.ceil(sorted.length * quantile) - 1))] : 0;
|
|
1704
2320
|
}
|
|
1705
|
-
function
|
|
1706
|
-
|
|
1707
|
-
for (const event of events) {
|
|
1708
|
-
const key = `${event.scope.projectId ?? ""}\0${event.scope.sessionId ?? ""}\0${event.scope.agentId ?? ""}`;
|
|
1709
|
-
const current = latest.get(key);
|
|
1710
|
-
if (!current || compareEvents(current, event) < 0) latest.set(key, event);
|
|
1711
|
-
}
|
|
1712
|
-
return [...latest.values()];
|
|
1713
|
-
}
|
|
1714
|
-
function familyCounts(events) {
|
|
1715
|
-
const counts = { llm: 0, agent: 0, tool: 0, file: 0, memory: 0, task: 0, decision: 0, runtime: 0 };
|
|
1716
|
-
for (const event of events) counts[signalFamily(event)]++;
|
|
1717
|
-
return counts;
|
|
2321
|
+
function scopeKey(event) {
|
|
2322
|
+
return `${event.scope.projectId ?? ""}\0${event.scope.sessionId ?? ""}\0${event.scope.agentId ?? ""}`;
|
|
1718
2323
|
}
|
|
1719
2324
|
function signalFamily(event) {
|
|
1720
|
-
if (
|
|
2325
|
+
if (/^(?:decision|brain|permission)\./.test(event.eventType)) return "decision";
|
|
1721
2326
|
if (event.resource?.kind === "file" || event.resource?.kind === "symbol" || /^(?:file|worktree)\./.test(event.eventType)) return "file";
|
|
1722
2327
|
if (/^(?:provider|token|context|ctx|compaction)\./.test(event.eventType)) return "llm";
|
|
1723
2328
|
if (/^(?:agent|subagent|delegate|fleet|concurrency)\./.test(event.eventType)) return "agent";
|
|
@@ -1747,24 +2352,6 @@ function relationKeys(event) {
|
|
|
1747
2352
|
add("parent_span", event.correlation.spanId, "explicit");
|
|
1748
2353
|
return result;
|
|
1749
2354
|
}
|
|
1750
|
-
async function findPartitions(root) {
|
|
1751
|
-
const result = [];
|
|
1752
|
-
const visit = async (directory) => {
|
|
1753
|
-
let entries;
|
|
1754
|
-
try {
|
|
1755
|
-
entries = await fs4.readdir(directory, { withFileTypes: true });
|
|
1756
|
-
} catch {
|
|
1757
|
-
return;
|
|
1758
|
-
}
|
|
1759
|
-
for (const entry of entries) {
|
|
1760
|
-
const full = path5.join(directory, entry.name);
|
|
1761
|
-
if (entry.isDirectory()) await visit(full);
|
|
1762
|
-
else if (entry.isFile() && entry.name.endsWith(".events.jsonl")) result.push(full);
|
|
1763
|
-
}
|
|
1764
|
-
};
|
|
1765
|
-
await visit(root);
|
|
1766
|
-
return result.sort();
|
|
1767
|
-
}
|
|
1768
2355
|
function matches(event, query) {
|
|
1769
2356
|
if (query.eventId && event.eventId !== query.eventId) return false;
|
|
1770
2357
|
if (query.eventTypes && !query.eventTypes.includes(event.eventType)) return false;
|
|
@@ -1791,7 +2378,8 @@ function normalize2(value) {
|
|
|
1791
2378
|
return value?.replaceAll("\\", "/").toLocaleLowerCase();
|
|
1792
2379
|
}
|
|
1793
2380
|
function lineContains(event, line) {
|
|
1794
|
-
const start = event.resource?.lineStart
|
|
2381
|
+
const start = event.resource?.lineStart;
|
|
2382
|
+
const end = event.resource?.lineEnd ?? start;
|
|
1795
2383
|
return start !== void 0 && end !== void 0 && line >= start && line <= end;
|
|
1796
2384
|
}
|
|
1797
2385
|
function objectContains(actual, expected) {
|
|
@@ -1803,19 +2391,150 @@ function readPath(value, key) {
|
|
|
1803
2391
|
function deepEqual(left, right) {
|
|
1804
2392
|
return JSON.stringify(left) === JSON.stringify(right);
|
|
1805
2393
|
}
|
|
2394
|
+
function findInsertionIndex(events, event, compare) {
|
|
2395
|
+
let low = 0;
|
|
2396
|
+
let high = events.length;
|
|
2397
|
+
while (low < high) {
|
|
2398
|
+
const middle = low + high >>> 1;
|
|
2399
|
+
if (compare(events[middle], event) <= 0) low = middle + 1;
|
|
2400
|
+
else high = middle;
|
|
2401
|
+
}
|
|
2402
|
+
return low;
|
|
2403
|
+
}
|
|
1806
2404
|
function compareEvents(a, b) {
|
|
1807
|
-
return (a
|
|
2405
|
+
return compareEventToKey(a, orderKey(b));
|
|
2406
|
+
}
|
|
2407
|
+
function compareEventToKey(event, key) {
|
|
2408
|
+
return (event.occurredAt ?? event.observedAt).localeCompare(key.occurredAt) || event.persistedAt.localeCompare(key.persistedAt) || event.sequence - key.sequence || event.eventId.localeCompare(key.eventId);
|
|
2409
|
+
}
|
|
2410
|
+
function orderKey(event) {
|
|
2411
|
+
return {
|
|
2412
|
+
occurredAt: event.occurredAt ?? event.observedAt,
|
|
2413
|
+
persistedAt: event.persistedAt,
|
|
2414
|
+
sequence: event.sequence,
|
|
2415
|
+
eventId: event.eventId
|
|
2416
|
+
};
|
|
2417
|
+
}
|
|
2418
|
+
function hashQuery(query) {
|
|
2419
|
+
const { cursor: _cursor, limit: _limit, order: _order, ...filters } = query;
|
|
2420
|
+
return createHash11("sha256").update(stableStringify2(filters), "utf8").digest("base64url");
|
|
2421
|
+
}
|
|
2422
|
+
function encodeCursor(cursor) {
|
|
2423
|
+
return Buffer.from(JSON.stringify(cursor), "utf8").toString("base64url");
|
|
2424
|
+
}
|
|
2425
|
+
function decodeCursor(encoded, order, queryHash) {
|
|
2426
|
+
if (!encoded) return void 0;
|
|
2427
|
+
if (encoded.length > 1e6) throw new Error("Invalid Chronicle cursor");
|
|
2428
|
+
try {
|
|
2429
|
+
const parsed = JSON.parse(Buffer.from(encoded, "base64url").toString("utf8"));
|
|
2430
|
+
if (!isCursor(parsed) || parsed.order !== order || parsed.queryHash !== queryHash) {
|
|
2431
|
+
throw new Error("cursor does not match the query");
|
|
2432
|
+
}
|
|
2433
|
+
return parsed;
|
|
2434
|
+
} catch (error) {
|
|
2435
|
+
throw new Error(`Invalid Chronicle cursor: ${error instanceof Error ? error.message : String(error)}`);
|
|
2436
|
+
}
|
|
1808
2437
|
}
|
|
1809
|
-
function
|
|
1810
|
-
|
|
2438
|
+
function isCursor(value) {
|
|
2439
|
+
if (!value || typeof value !== "object") return false;
|
|
2440
|
+
const cursor = value;
|
|
2441
|
+
const after = cursor.after;
|
|
2442
|
+
if (cursor.version !== 1 || cursor.order !== "asc" && cursor.order !== "desc" || typeof cursor.queryHash !== "string" || !after || typeof after.occurredAt !== "string" || typeof after.persistedAt !== "string" || !Number.isSafeInteger(after.sequence) || typeof after.eventId !== "string" || !Array.isArray(cursor.snapshot) || cursor.snapshot.length > MAX_CURSOR_SNAPSHOT_ENTRIES) return false;
|
|
2443
|
+
const snapshotIds = /* @__PURE__ */ new Set();
|
|
2444
|
+
for (const entry of cursor.snapshot) {
|
|
2445
|
+
if (!entry || typeof entry.id !== "string" || !entry.id || !Number.isSafeInteger(entry.size) || entry.size < 0 || snapshotIds.has(entry.id)) return false;
|
|
2446
|
+
snapshotIds.add(entry.id);
|
|
2447
|
+
}
|
|
2448
|
+
return true;
|
|
2449
|
+
}
|
|
2450
|
+
async function captureSnapshot(files) {
|
|
2451
|
+
if (files.length > MAX_CURSOR_SNAPSHOT_ENTRIES) {
|
|
2452
|
+
throw new Error("Chronicle snapshot contains too many partitions");
|
|
2453
|
+
}
|
|
2454
|
+
return Promise.all(files.map(async (file) => {
|
|
2455
|
+
let size = 0;
|
|
2456
|
+
try {
|
|
2457
|
+
size = (await fs4.stat(file)).size;
|
|
2458
|
+
} catch {
|
|
2459
|
+
}
|
|
2460
|
+
return { file, id: fileId(file), size };
|
|
2461
|
+
}));
|
|
1811
2462
|
}
|
|
1812
|
-
function
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
2463
|
+
async function resolveSnapshotFiles(files, snapshot) {
|
|
2464
|
+
const currentFiles = new Map(files.map((file) => [fileId(file), file]));
|
|
2465
|
+
return Promise.all(snapshot.map(async (entry) => {
|
|
2466
|
+
const file = currentFiles.get(entry.id);
|
|
2467
|
+
if (!file) throw new Error("Chronicle cursor snapshot has expired");
|
|
2468
|
+
let currentSize;
|
|
2469
|
+
try {
|
|
2470
|
+
currentSize = (await fs4.stat(file)).size;
|
|
2471
|
+
} catch {
|
|
2472
|
+
throw new Error("Chronicle cursor snapshot has expired");
|
|
2473
|
+
}
|
|
2474
|
+
if (currentSize < entry.size) throw new Error("Chronicle cursor snapshot has expired");
|
|
2475
|
+
return { file, ...entry };
|
|
2476
|
+
}));
|
|
2477
|
+
}
|
|
2478
|
+
function fileId(file) {
|
|
2479
|
+
return createHash11("sha256").update(path5.resolve(file), "utf8").digest("base64url");
|
|
2480
|
+
}
|
|
2481
|
+
function stableStringify2(value) {
|
|
2482
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
2483
|
+
if (Array.isArray(value)) return `[${value.map(stableStringify2).join(",")}]`;
|
|
2484
|
+
const object = value;
|
|
2485
|
+
return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${stableStringify2(object[key])}`).join(",")}}`;
|
|
1816
2486
|
}
|
|
1817
2487
|
function isChronicleEvent(value) {
|
|
1818
|
-
|
|
2488
|
+
if (!isRecord(value) || !isRecord(value.scope) || !isRecord(value.correlation)) return false;
|
|
2489
|
+
return value.schemaVersion === 1 && typeof value.eventId === "string" && typeof value.eventType === "string" && typeof value.occurredAt === "string" && typeof value.observedAt === "string" && typeof value.persistedAt === "string" && Number.isSafeInteger(value.sequence) && value.sequence >= 0 && typeof value.previousHash === "string" && typeof value.hash === "string" && typeof value.scope.installationId === "string" && typeof value.scope.machineId === "string" && optionalStrings(value.scope, [
|
|
2490
|
+
"projectId",
|
|
2491
|
+
"repositoryId",
|
|
2492
|
+
"workspaceId",
|
|
2493
|
+
"worktreeId",
|
|
2494
|
+
"sessionId",
|
|
2495
|
+
"turnId",
|
|
2496
|
+
"iterationId",
|
|
2497
|
+
"agentId",
|
|
2498
|
+
"goalId",
|
|
2499
|
+
"planId",
|
|
2500
|
+
"taskId",
|
|
2501
|
+
"kanbanBoardId"
|
|
2502
|
+
]) && typeof value.correlation.traceId === "string" && typeof value.correlation.spanId === "string" && optionalStrings(value.correlation, [
|
|
2503
|
+
"parentSpanId",
|
|
2504
|
+
"logicalRequestId",
|
|
2505
|
+
"attemptId",
|
|
2506
|
+
"toolCallId"
|
|
2507
|
+
]) && isRuntime(value.runtime) && isResource(value.resource) && (value.attributes === void 0 || isRecord(value.attributes)) && (value.tags === void 0 || isStringRecord(value.tags));
|
|
2508
|
+
}
|
|
2509
|
+
function isRuntime(value) {
|
|
2510
|
+
return value === void 0 || isRecord(value) && optionalStrings(value, ["providerId", "modelId", "modelRevision"]) && optionalFiniteNumbers(value, ["processId", "parentProcessId"]);
|
|
2511
|
+
}
|
|
2512
|
+
function isResource(value) {
|
|
2513
|
+
if (value === void 0) return true;
|
|
2514
|
+
if (!isRecord(value) || ![
|
|
2515
|
+
"file",
|
|
2516
|
+
"symbol",
|
|
2517
|
+
"memory",
|
|
2518
|
+
"task",
|
|
2519
|
+
"kanban",
|
|
2520
|
+
"process",
|
|
2521
|
+
"network",
|
|
2522
|
+
"artifact",
|
|
2523
|
+
"other"
|
|
2524
|
+
].includes(String(value.kind)) || typeof value.id !== "string") return false;
|
|
2525
|
+
return optionalStrings(value, ["path", "contentHashBefore", "contentHashAfter"]) && optionalFiniteNumbers(value, ["lineStart", "lineEnd"]);
|
|
2526
|
+
}
|
|
2527
|
+
function optionalStrings(value, keys) {
|
|
2528
|
+
return keys.every((key) => value[key] === void 0 || typeof value[key] === "string");
|
|
2529
|
+
}
|
|
2530
|
+
function optionalFiniteNumbers(value, keys) {
|
|
2531
|
+
return keys.every((key) => value[key] === void 0 || typeof value[key] === "number" && Number.isFinite(value[key]));
|
|
2532
|
+
}
|
|
2533
|
+
function isStringRecord(value) {
|
|
2534
|
+
return isRecord(value) && Object.values(value).every((entry) => typeof entry === "string");
|
|
2535
|
+
}
|
|
2536
|
+
function isRecord(value) {
|
|
2537
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
1819
2538
|
}
|
|
1820
2539
|
function facetValue(event, field) {
|
|
1821
2540
|
const values = {
|