@wrongstack/core 0.300.0 → 0.302.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.d.ts +2 -0
- package/dist/chronicle/index.js +297 -10
- package/dist/chronicle/project-server.js +253 -15
- package/dist/chronicle/sqlite-compaction.d.ts +20 -0
- package/dist/chronicle/sqlite-journal.d.ts +57 -0
- package/dist/coordination/agents/capability-manifest.d.ts +7 -0
- package/dist/coordination/agents/index.d.ts +3 -2
- package/dist/coordination/agents/index.js +818 -570
- package/dist/coordination/agents/project-agent-identity.d.ts +6 -6
- package/dist/coordination/agents/role-skills.d.ts +1 -1
- package/dist/coordination/agents/types.d.ts +6 -0
- package/dist/coordination/director.d.ts +2 -2
- package/dist/coordination/index.js +6787 -6447
- package/dist/coordination/mail-tools.d.ts +3 -3
- package/dist/coordination/mailbox-project-server.js +3 -4
- package/dist/coordination/mailbox-types.d.ts +6 -0
- package/dist/core/agent-response.d.ts +5 -1
- package/dist/core/agent-tools.d.ts +3 -0
- package/dist/core/context.d.ts +20 -0
- package/dist/core/index.d.ts +8 -7
- package/dist/core/index.js +893 -284
- package/dist/core/instruction-template.d.ts +6 -3
- package/dist/core/next-steps-slot.d.ts +88 -0
- package/dist/core/system-prompt-builder.d.ts +5 -3
- package/dist/core/system-prompt-memory-skills.d.ts +1 -0
- package/dist/core/system-prompt-skill-bodies.d.ts +3 -3
- package/dist/defaults/index.js +9797 -8898
- package/dist/design/index.js +11 -3
- package/dist/execution/auto-compaction-middleware.d.ts +17 -0
- package/dist/execution/autonomy-brain.d.ts +8 -2
- package/dist/execution/brain-runtime.d.ts +3 -1
- package/dist/execution/compaction-core.d.ts +41 -2
- package/dist/execution/council-brain.d.ts +37 -2
- package/dist/execution/council-orchestrator.d.ts +2 -2
- package/dist/execution/index.d.ts +11 -10
- package/dist/execution/index.js +5179 -4078
- package/dist/execution/tool-executor.d.ts +4 -1
- package/dist/execution/topic-shift-advisor.d.ts +53 -0
- package/dist/extension/index.js +8 -2
- package/dist/extension/registry.d.ts +8 -1
- package/dist/goal/index.js +11 -3
- package/dist/hooks/index.js +42 -9
- package/dist/hooks/runner.d.ts +12 -1
- package/dist/hq/index.js +41 -17
- package/dist/hq/publisher.d.ts +21 -3
- package/dist/index.d.ts +21 -20
- package/dist/index.js +10901 -8067
- package/dist/infrastructure/index.js +108 -21
- package/dist/kernel/events/session-events.d.ts +13 -0
- package/dist/kernel/events/tool-events.d.ts +3 -3
- package/dist/models/index.d.ts +1 -0
- package/dist/models/index.js +17 -0
- package/dist/models/provider-credentials.d.ts +54 -0
- package/dist/plugin/index.d.ts +1 -0
- package/dist/plugin/index.js +2024 -502
- package/dist/plugins/auto-review-plugin.d.ts +3 -0
- package/dist/plugins/cloud-config-sync-plugin.d.ts +22 -0
- package/dist/plugins/review-claim-registry.d.ts +23 -8
- package/dist/plugins/review-types.d.ts +2 -0
- package/dist/registry/index.js +109 -74
- package/dist/registry/tool-registry.d.ts +15 -0
- package/dist/security/capabilities.d.ts +2 -0
- package/dist/security/index.d.ts +1 -1
- package/dist/security/index.js +9 -2
- package/dist/security/readonly-permission-policy.d.ts +20 -0
- package/dist/session-registry-atomic-file.d.ts +32 -5
- package/dist/session-registry-types.d.ts +17 -0
- package/dist/session-registry.d.ts +1 -1
- package/dist/skills/frontmatter.d.ts +6 -0
- package/dist/skills/index.js +22 -5
- package/dist/storage/cloud-config-sync/sanitize.d.ts +54 -0
- package/dist/storage/cloud-config-sync.d.ts +87 -0
- package/dist/storage/index.d.ts +2 -1
- package/dist/storage/index.js +854 -66
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.js +511 -234
- package/dist/tools/one-shot-llm-tool.d.ts +1 -0
- package/dist/tools/plugin-manager.d.ts +27 -0
- package/dist/types/config/mcp-features.d.ts +18 -11
- package/dist/types/config/root.d.ts +8 -1
- package/dist/types/config/runtime.d.ts +20 -6
- package/dist/types/config/skills-fleet-brain.d.ts +12 -4
- package/dist/types/config/tools.d.ts +16 -0
- package/dist/types/context-window.d.ts +1 -0
- package/dist/types/hooks.d.ts +14 -0
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.js +1 -0
- package/dist/types/multi-agent.d.ts +2 -0
- package/dist/types/one-shot-llm.d.ts +16 -0
- package/dist/types/provider.d.ts +16 -0
- package/dist/types/runtime-capability-manifest.d.ts +168 -0
- package/dist/types/skill.d.ts +5 -0
- package/dist/types/system-prompt.d.ts +3 -1
- package/dist/types/tool-executor.d.ts +8 -1
- package/dist/utils/context-breakdown.d.ts +8 -2
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.js +141 -31
- package/dist/utils/regex-guard.d.ts +16 -2
- package/dist/utils/sage-output-block.d.ts +7 -10
- package/dist/utils/wstack-paths.d.ts +11 -3
- package/instructions/agents/browser.md +1 -4
- package/instructions/agents/e2e.md +17 -15
- package/instructions/agents/ios.md +3 -3
- package/instructions/agents/search.md +1 -1
- package/instructions/autonomy/goal-preamble.md +4 -4
- package/instructions/coordination/director-preamble.md +2 -3
- package/instructions/coordination/subagent-baseline.md +3 -1
- package/instructions/leader-after-task.md +12 -0
- package/instructions/llm/chimera-review.md +1 -1
- package/instructions/modes/audit-lite.md +1 -1
- package/instructions/sections/tool/common-patterns.md +18 -2
- package/instructions/sections/tool/mailbox-compact.md +1 -1
- package/instructions/sections/tool/mailbox-full.md +1 -1
- package/instructions/system-lite.md +10 -0
- package/instructions/system-pro.md +23 -14
- package/instructions/system.md +22 -13
- package/package.json +3 -3
- package/skills/api-design/SKILL.md +3 -0
- package/skills/audit-log/SKILL.md +2 -0
- package/skills/auto-review/SKILL.md +6 -1
- package/skills/bug-hunter/SKILL.md +4 -1
- package/skills/chimera/SKILL.md +3 -0
- package/skills/data-governance/SKILL.md +3 -0
- package/skills/design-system/SKILL.md +5 -2
- package/skills/docker-deploy/SKILL.md +2 -0
- package/skills/git-flow/SKILL.md +2 -0
- package/skills/mailbox-bridge/SKILL.md +3 -0
- package/skills/mnemosyne/SKILL.md +2 -0
- package/skills/multi-agent/SKILL.md +4 -1
- package/skills/node-modern/SKILL.md +4 -1
- package/skills/observability/SKILL.md +3 -0
- package/skills/output-standards/SKILL.md +2 -0
- package/skills/plugin-author/SKILL.md +4 -1
- package/skills/prompt-engineering/SKILL.md +3 -1
- package/skills/react-modern/SKILL.md +6 -3
- package/skills/refactor-planner/SKILL.md +2 -0
- package/skills/research-web/SKILL.md +3 -0
- package/skills/sdd/SKILL.md +3 -1
- package/skills/security-scanner/SKILL.md +3 -0
- package/skills/skill-creator/SKILL.md +2 -0
- package/skills/tech-stack/SKILL.md +3 -0
- package/skills/testing/SKILL.md +4 -1
- package/skills/typescript-strict/SKILL.md +4 -1
- package/skills/wrongstack-kanban/SKILL.md +6 -3
- package/skills/wrongstack-mailbox/SKILL.md +4 -1
- package/skills/wrongstack-mailbox-mcp/SKILL.md +10 -8
|
@@ -7,6 +7,8 @@ export { ChronicleProjectServerClient, ChronicleRemoteJournal, isChronicleProjec
|
|
|
7
7
|
export { chronicleProjectServerEndpoint, chronicleProjectServerKey, chronicleProjectServerMetadataPath, } from './project-server-endpoint.js';
|
|
8
8
|
export { CHRONICLE_PROJECT_SERVER_MAX_FRAME_CHARS, CHRONICLE_PROJECT_SERVER_PROTOCOL_VERSION, type ChronicleMetricsRequest, type ChronicleMetricsResponse, type ChronicleMetricsView, type ChronicleProjectServerHealth, type ChronicleProjectServerInfo, type ChronicleServerOperationName, type ChronicleServerOperations, } from './project-server-protocol.js';
|
|
9
9
|
export { ChronicleJournal, GENESIS_HASH, type ChronicleJournalOptions, type ChronicleJournalStats, type ChroniclePurgeOptions, type ChroniclePurgeResult } from './journal.js';
|
|
10
|
+
export { ChronicleCompactionBusyError, compactChronicleSqlite, type ChronicleSqliteCompactionOptions, type ChronicleSqliteCompactionResult, } from './sqlite-compaction.js';
|
|
11
|
+
export { ChronicleStorageQuotaError } from './sqlite-journal.js';
|
|
10
12
|
export { wireProviderAttemptsToChronicle, type ChronicleProviderAdapterOptions, } from './provider-adapter.js';
|
|
11
13
|
export { wireToolsToChronicle, type ChronicleToolAdapterOptions } from './tool-adapter.js';
|
|
12
14
|
export { wireProcessesToChronicle, type ChronicleProcessAdapterOptions, } from './process-adapter.js';
|
package/dist/chronicle/index.js
CHANGED
|
@@ -557,6 +557,9 @@ function canonicalProjectRoot(absRoot) {
|
|
|
557
557
|
const commonDirFile = path4.join(gitDir, "commondir");
|
|
558
558
|
if (!fs2.statSync(commonDirFile).isFile()) return checkoutRoot;
|
|
559
559
|
const commonDir = path4.resolve(gitDir, fs2.readFileSync(commonDirFile, "utf8").trim());
|
|
560
|
+
const worktreesDir = path4.dirname(gitDir);
|
|
561
|
+
if (path4.basename(worktreesDir).toLowerCase() !== "worktrees") return checkoutRoot;
|
|
562
|
+
if (path4.resolve(worktreesDir, "..") !== commonDir) return checkoutRoot;
|
|
560
563
|
if (path4.basename(commonDir).toLowerCase() !== ".git") return checkoutRoot;
|
|
561
564
|
return path4.dirname(commonDir);
|
|
562
565
|
} catch {
|
|
@@ -579,7 +582,7 @@ function safeProfileName(name) {
|
|
|
579
582
|
const safe = (name ?? "").replace(/[/\\:]/g, "_").replace(/\.\./g, "_").trim();
|
|
580
583
|
return safe || "default";
|
|
581
584
|
}
|
|
582
|
-
function
|
|
585
|
+
function bootstrapProfileName(globalRoot) {
|
|
583
586
|
try {
|
|
584
587
|
const parsed = JSON.parse(fs2.readFileSync(path4.join(globalRoot, "config.json"), "utf8"));
|
|
585
588
|
return safeProfileName(
|
|
@@ -597,7 +600,7 @@ function wstackGlobalRoot() {
|
|
|
597
600
|
function resolveWstackPaths(opts) {
|
|
598
601
|
const globalRoot = opts.globalRoot ?? (opts.userHome ? path4.join(opts.userHome, ".wrongstack") : wstackGlobalRoot());
|
|
599
602
|
const homeDir = opts.userHome ?? os2.homedir();
|
|
600
|
-
const profileName = safeProfileName(opts.profileName ??
|
|
603
|
+
const profileName = safeProfileName(opts.profileName ?? bootstrapProfileName(globalRoot));
|
|
601
604
|
const profileDir = path4.join(globalRoot, "profiles", profileName);
|
|
602
605
|
const hash3 = projectHash(opts.projectRoot);
|
|
603
606
|
const slug = projectSlug(opts.projectRoot);
|
|
@@ -667,7 +670,12 @@ function resolveWstackPaths(opts) {
|
|
|
667
670
|
projectRequirementIntakes: path4.join(projectDir, "requirement-intakes"),
|
|
668
671
|
syncConfig: path4.join(profileDir, "sync.json"),
|
|
669
672
|
configHistoryDir: path4.join(globalRoot, "config-history"),
|
|
670
|
-
projectStatus: (
|
|
673
|
+
projectStatus: (statusProjectSlug) => {
|
|
674
|
+
if (!/^[a-z0-9](?:[a-z0-9-]{0,39})-[a-f0-9]{6}$/.test(statusProjectSlug)) {
|
|
675
|
+
throw new Error(`Invalid project slug: ${statusProjectSlug}`);
|
|
676
|
+
}
|
|
677
|
+
return path4.join(globalRoot, "projects", statusProjectSlug, "status.json");
|
|
678
|
+
}
|
|
671
679
|
};
|
|
672
680
|
}
|
|
673
681
|
|
|
@@ -3099,9 +3107,24 @@ async function importLegacyChronicleJournal(journal, directory) {
|
|
|
3099
3107
|
|
|
3100
3108
|
// src/chronicle/sqlite-journal.ts
|
|
3101
3109
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
3110
|
+
import * as fs8 from "node:fs";
|
|
3102
3111
|
import { createRequire as createRequire2 } from "node:module";
|
|
3103
3112
|
import * as path11 from "node:path";
|
|
3104
3113
|
|
|
3114
|
+
// src/utils/pid.ts
|
|
3115
|
+
function isPidAlive(pid) {
|
|
3116
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
3117
|
+
if (pid === process.pid) return true;
|
|
3118
|
+
try {
|
|
3119
|
+
process.kill(pid, 0);
|
|
3120
|
+
return true;
|
|
3121
|
+
} catch (err) {
|
|
3122
|
+
const code = err.code;
|
|
3123
|
+
if (code === "EPERM") return true;
|
|
3124
|
+
return false;
|
|
3125
|
+
}
|
|
3126
|
+
}
|
|
3127
|
+
|
|
3105
3128
|
// src/chronicle/sqlite-query.ts
|
|
3106
3129
|
var MAX_LIMIT = 1e4;
|
|
3107
3130
|
function encodeCursor2(cursor) {
|
|
@@ -3344,6 +3367,25 @@ var CHRONICLE_SQLITE_FILE = "chronicle.sqlite";
|
|
|
3344
3367
|
var LEGACY_JSONL_MIGRATION_KEY = "legacy-jsonl-v1";
|
|
3345
3368
|
var LEGACY_JSONL_QUARANTINE_KEY = "legacy-jsonl-v1:quarantine";
|
|
3346
3369
|
var SCHEMA_VERSION2 = 1;
|
|
3370
|
+
var SQLITE_FIXED_OVERHEAD_BYTES = 16 * 1024 * 1024;
|
|
3371
|
+
var MIN_SQLITE_PAGE_BUDGET_BYTES = 64 * 1024;
|
|
3372
|
+
var MAX_ROLLBACK_JOURNAL_RESERVE_BYTES = 64 * 1024 * 1024;
|
|
3373
|
+
var ChronicleStorageQuotaError = class extends Error {
|
|
3374
|
+
currentBytes;
|
|
3375
|
+
batchBytes;
|
|
3376
|
+
maxBytes;
|
|
3377
|
+
path;
|
|
3378
|
+
constructor(details) {
|
|
3379
|
+
super(
|
|
3380
|
+
`Chronicle SQLite quota exceeded at ${details.path}: ${details.currentBytes} live bytes + ${details.batchBytes} batch bytes exceeds ${details.maxBytes}; lower chronicle retentionDays/maxEvents to shed data (run chronicle compact to return the freed pages to the filesystem)`
|
|
3381
|
+
);
|
|
3382
|
+
this.name = "ChronicleStorageQuotaError";
|
|
3383
|
+
this.currentBytes = details.currentBytes;
|
|
3384
|
+
this.batchBytes = details.batchBytes;
|
|
3385
|
+
this.maxBytes = details.maxBytes;
|
|
3386
|
+
this.path = details.path;
|
|
3387
|
+
}
|
|
3388
|
+
};
|
|
3347
3389
|
var Ctor2;
|
|
3348
3390
|
function loadDatabaseSync2() {
|
|
3349
3391
|
if (Ctor2) return Ctor2;
|
|
@@ -3366,6 +3408,14 @@ var ChronicleSqliteJournal = class {
|
|
|
3366
3408
|
now;
|
|
3367
3409
|
monotonicNow;
|
|
3368
3410
|
idFactory;
|
|
3411
|
+
retentionDays;
|
|
3412
|
+
maxEvents;
|
|
3413
|
+
maxBytes;
|
|
3414
|
+
retentionCheckIntervalMs;
|
|
3415
|
+
nextRetentionCheckAt = 0;
|
|
3416
|
+
retainedEventCount = 0;
|
|
3417
|
+
/** Resolved lazily by `pageSizeBytes()`; constant for an open database. */
|
|
3418
|
+
cachedPageSize;
|
|
3369
3419
|
/**
|
|
3370
3420
|
* Cached chain head per day. Cleared wholesale on any write failure so the
|
|
3371
3421
|
* next append rebuilds from the database rather than trusting a counter that
|
|
@@ -3387,10 +3437,36 @@ var ChronicleSqliteJournal = class {
|
|
|
3387
3437
|
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
3388
3438
|
this.monotonicNow = options.monotonicNow ?? (() => process.hrtime.bigint());
|
|
3389
3439
|
this.idFactory = options.idFactory ?? (() => randomUUID3());
|
|
3440
|
+
if (options.retentionDays !== void 0 && (!Number.isFinite(options.retentionDays) || options.retentionDays <= 0)) {
|
|
3441
|
+
throw new RangeError("retentionDays must be a positive finite number");
|
|
3442
|
+
}
|
|
3443
|
+
if (options.retentionCheckIntervalMs !== void 0 && (!Number.isFinite(options.retentionCheckIntervalMs) || options.retentionCheckIntervalMs <= 0)) {
|
|
3444
|
+
throw new RangeError("retentionCheckIntervalMs must be a positive finite number");
|
|
3445
|
+
}
|
|
3446
|
+
if (options.maxEvents !== void 0 && (!Number.isInteger(options.maxEvents) || options.maxEvents < 1)) {
|
|
3447
|
+
throw new RangeError("maxEvents must be a positive integer");
|
|
3448
|
+
}
|
|
3449
|
+
if (options.maxBytes !== void 0 && (!Number.isSafeInteger(options.maxBytes) || options.maxBytes < 1)) {
|
|
3450
|
+
throw new RangeError("maxBytes must be a positive safe integer");
|
|
3451
|
+
}
|
|
3452
|
+
const minimumQuotaBytes = SQLITE_FIXED_OVERHEAD_BYTES + 2 * MIN_SQLITE_PAGE_BUDGET_BYTES;
|
|
3453
|
+
if (options.maxBytes !== void 0 && options.maxBytes < minimumQuotaBytes) {
|
|
3454
|
+
throw new RangeError(`maxBytes must be at least ${minimumQuotaBytes}`);
|
|
3455
|
+
}
|
|
3456
|
+
this.retentionDays = options.retentionDays;
|
|
3457
|
+
this.maxEvents = options.maxEvents;
|
|
3458
|
+
this.maxBytes = options.maxBytes;
|
|
3459
|
+
this.retentionCheckIntervalMs = options.retentionCheckIntervalMs ?? 60 * 60 * 1e3;
|
|
3390
3460
|
const Database = loadDatabaseSync2();
|
|
3391
3461
|
this.db = new Database(this.dbPath);
|
|
3392
|
-
this.db.exec(
|
|
3462
|
+
this.db.exec(`PRAGMA journal_mode = ${this.maxBytes === void 0 ? "WAL" : "DELETE"}`);
|
|
3393
3463
|
this.ensureSchema();
|
|
3464
|
+
this.configureByteQuota();
|
|
3465
|
+
if (this.maxEvents !== void 0) {
|
|
3466
|
+
const row = this.db.prepare("SELECT COUNT(*) AS count FROM events").get();
|
|
3467
|
+
this.retainedEventCount = row.count;
|
|
3468
|
+
this.enforceEventLimitAtStartup();
|
|
3469
|
+
}
|
|
3394
3470
|
}
|
|
3395
3471
|
close() {
|
|
3396
3472
|
this.db.close();
|
|
@@ -3449,8 +3525,10 @@ var ChronicleSqliteJournal = class {
|
|
|
3449
3525
|
events.push(event);
|
|
3450
3526
|
previous = { sequence: event.sequence, hash: event.hash };
|
|
3451
3527
|
}
|
|
3528
|
+
let retainedCountAfterCommit = this.retainedEventCount;
|
|
3452
3529
|
try {
|
|
3453
3530
|
this.db.exec("BEGIN IMMEDIATE");
|
|
3531
|
+
this.assertWithinByteQuota(events);
|
|
3454
3532
|
const insert = this.db.prepare(
|
|
3455
3533
|
`INSERT INTO events (
|
|
3456
3534
|
day, sequence, event_id, hash, previous_hash, occurred_at, event_type, outcome,
|
|
@@ -3482,6 +3560,8 @@ var ChronicleSqliteJournal = class {
|
|
|
3482
3560
|
JSON.stringify(event)
|
|
3483
3561
|
);
|
|
3484
3562
|
}
|
|
3563
|
+
retainedCountAfterCommit = this.enforceEventLimitWithinTransaction();
|
|
3564
|
+
this.assertActualAllocationWithinQuota();
|
|
3485
3565
|
this.db.exec("COMMIT");
|
|
3486
3566
|
} catch (error) {
|
|
3487
3567
|
try {
|
|
@@ -3491,11 +3571,13 @@ var ChronicleSqliteJournal = class {
|
|
|
3491
3571
|
this.anchors.clear();
|
|
3492
3572
|
this.counters.failedEvents += inputs.length;
|
|
3493
3573
|
this.lastBatchDurationMs = performance.now() - started;
|
|
3494
|
-
throw error;
|
|
3574
|
+
throw this.normalizeQuotaError(error);
|
|
3495
3575
|
}
|
|
3496
3576
|
this.anchors.set(day, previous);
|
|
3497
3577
|
this.counters.persistedEvents += events.length;
|
|
3578
|
+
this.retainedEventCount = retainedCountAfterCommit;
|
|
3498
3579
|
this.lastBatchDurationMs = performance.now() - started;
|
|
3580
|
+
await this.enforceRetentionIfDue();
|
|
3499
3581
|
return events;
|
|
3500
3582
|
}
|
|
3501
3583
|
async readAll() {
|
|
@@ -3556,6 +3638,145 @@ var ChronicleSqliteJournal = class {
|
|
|
3556
3638
|
}
|
|
3557
3639
|
return { ok: true, entries, lastSequence, lastHash };
|
|
3558
3640
|
}
|
|
3641
|
+
async enforceRetentionIfDue() {
|
|
3642
|
+
if (this.retentionDays === void 0) return;
|
|
3643
|
+
const now = this.now();
|
|
3644
|
+
if (now.getTime() < this.nextRetentionCheckAt) return;
|
|
3645
|
+
this.nextRetentionCheckAt = now.getTime() + this.retentionCheckIntervalMs;
|
|
3646
|
+
try {
|
|
3647
|
+
await this.purge({ retentionDays: this.retentionDays });
|
|
3648
|
+
} catch {
|
|
3649
|
+
}
|
|
3650
|
+
}
|
|
3651
|
+
configureByteQuota() {
|
|
3652
|
+
if (this.maxBytes === void 0) {
|
|
3653
|
+
this.db.exec("PRAGMA max_page_count = 2147483646");
|
|
3654
|
+
return;
|
|
3655
|
+
}
|
|
3656
|
+
const halfSplit = Math.floor((this.maxBytes - SQLITE_FIXED_OVERHEAD_BYTES) / 2);
|
|
3657
|
+
const journalReserve = Math.min(MAX_ROLLBACK_JOURNAL_RESERVE_BYTES, halfSplit);
|
|
3658
|
+
const mainBudget = this.maxBytes - SQLITE_FIXED_OVERHEAD_BYTES - journalReserve;
|
|
3659
|
+
if (mainBudget < MIN_SQLITE_PAGE_BUDGET_BYTES) {
|
|
3660
|
+
throw new Error(
|
|
3661
|
+
`maxBytes must be at least ${SQLITE_FIXED_OVERHEAD_BYTES + 2 * MIN_SQLITE_PAGE_BUDGET_BYTES}`
|
|
3662
|
+
);
|
|
3663
|
+
}
|
|
3664
|
+
const maxPages = Math.max(1, Math.floor(mainBudget / this.pageSizeBytes()));
|
|
3665
|
+
this.db.exec(`PRAGMA max_page_count = ${maxPages}`);
|
|
3666
|
+
}
|
|
3667
|
+
/**
|
|
3668
|
+
* Bytes the journal actually occupies: live pages in the main database plus
|
|
3669
|
+
* whatever the rollback/WAL sidecars currently hold.
|
|
3670
|
+
*
|
|
3671
|
+
* The quota MUST be measured this way rather than by `statSync` on the main
|
|
3672
|
+
* database file. SQLite never returns freed pages to the filesystem — it
|
|
3673
|
+
* parks them on the freelist and reuses them — so a database that once grew
|
|
3674
|
+
* large keeps that size forever, even after retention and `maxEvents` have
|
|
3675
|
+
* evicted almost everything. Measuring the file instead wedged the journal
|
|
3676
|
+
* permanently: a 3.9 GB file whose live data was 243 MB failed a 512 MB
|
|
3677
|
+
* quota on EVERY append, and the only escape (`chronicle compact`) refuses
|
|
3678
|
+
* to run while the daemon is up — and the daemon respawns on demand. Live
|
|
3679
|
+
* pages shrink when retention deletes rows, so the quota can recover on its
|
|
3680
|
+
* own; allocated size cannot.
|
|
3681
|
+
*
|
|
3682
|
+
* The sidecars keep their on-disk size: they are transient and genuinely
|
|
3683
|
+
* bounded by `configureByteQuota`'s half-of-budget split.
|
|
3684
|
+
*/
|
|
3685
|
+
aggregateLiveBytes() {
|
|
3686
|
+
let total = 0;
|
|
3687
|
+
for (const file of [`${this.dbPath}-journal`, `${this.dbPath}-wal`, `${this.dbPath}-shm`]) {
|
|
3688
|
+
try {
|
|
3689
|
+
total += fs8.statSync(file).size;
|
|
3690
|
+
} catch (error) {
|
|
3691
|
+
if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) throw error;
|
|
3692
|
+
}
|
|
3693
|
+
}
|
|
3694
|
+
return total + this.mainDatabaseLiveBytes();
|
|
3695
|
+
}
|
|
3696
|
+
/** Live (non-freelist) pages of the main database, in bytes. */
|
|
3697
|
+
mainDatabaseLiveBytes() {
|
|
3698
|
+
const pageCount = Number(
|
|
3699
|
+
this.db.prepare("PRAGMA page_count").get().page_count
|
|
3700
|
+
);
|
|
3701
|
+
const freelist = Number(
|
|
3702
|
+
this.db.prepare("PRAGMA freelist_count").get().freelist_count
|
|
3703
|
+
);
|
|
3704
|
+
return Math.max(0, pageCount - freelist) * this.pageSizeBytes();
|
|
3705
|
+
}
|
|
3706
|
+
/** Page size never changes for an open database; resolve it once. */
|
|
3707
|
+
pageSizeBytes() {
|
|
3708
|
+
if (this.cachedPageSize === void 0) {
|
|
3709
|
+
this.cachedPageSize = Number(
|
|
3710
|
+
this.db.prepare("PRAGMA page_size").get().page_size
|
|
3711
|
+
);
|
|
3712
|
+
}
|
|
3713
|
+
return this.cachedPageSize;
|
|
3714
|
+
}
|
|
3715
|
+
normalizeQuotaError(error) {
|
|
3716
|
+
if (this.maxBytes === void 0 || !(error instanceof Error)) return error;
|
|
3717
|
+
const sqliteError = error;
|
|
3718
|
+
const quotaExhausted = sqliteError.code === "SQLITE_FULL" || sqliteError.code === 13 || sqliteError.errcode === 13 || /database or disk is full/i.test(sqliteError.message);
|
|
3719
|
+
if (!quotaExhausted) return error;
|
|
3720
|
+
return new ChronicleStorageQuotaError({
|
|
3721
|
+
currentBytes: this.aggregateLiveBytes(),
|
|
3722
|
+
batchBytes: 0,
|
|
3723
|
+
maxBytes: this.maxBytes,
|
|
3724
|
+
path: this.dbPath
|
|
3725
|
+
});
|
|
3726
|
+
}
|
|
3727
|
+
assertActualAllocationWithinQuota() {
|
|
3728
|
+
if (this.maxBytes === void 0) return;
|
|
3729
|
+
const currentBytes = this.aggregateLiveBytes();
|
|
3730
|
+
if (currentBytes <= this.maxBytes) return;
|
|
3731
|
+
throw new ChronicleStorageQuotaError({
|
|
3732
|
+
currentBytes,
|
|
3733
|
+
batchBytes: 0,
|
|
3734
|
+
maxBytes: this.maxBytes,
|
|
3735
|
+
path: this.dbPath
|
|
3736
|
+
});
|
|
3737
|
+
}
|
|
3738
|
+
assertWithinByteQuota(events) {
|
|
3739
|
+
if (this.maxBytes === void 0) return;
|
|
3740
|
+
const currentBytes = this.aggregateLiveBytes();
|
|
3741
|
+
const batchBytes = Buffer.byteLength(JSON.stringify(events), "utf8");
|
|
3742
|
+
if (currentBytes + batchBytes <= this.maxBytes) return;
|
|
3743
|
+
throw new ChronicleStorageQuotaError({
|
|
3744
|
+
currentBytes,
|
|
3745
|
+
batchBytes,
|
|
3746
|
+
maxBytes: this.maxBytes,
|
|
3747
|
+
path: this.dbPath
|
|
3748
|
+
});
|
|
3749
|
+
}
|
|
3750
|
+
enforceEventLimitWithinTransaction() {
|
|
3751
|
+
const row = this.db.prepare("SELECT COUNT(*) AS count FROM events").get();
|
|
3752
|
+
if (this.maxEvents === void 0 || row.count <= this.maxEvents) return row.count;
|
|
3753
|
+
const excess = row.count - this.maxEvents;
|
|
3754
|
+
const boundary = this.db.prepare("SELECT day, sequence, hash FROM events ORDER BY day, sequence LIMIT 1 OFFSET ?").get(excess - 1);
|
|
3755
|
+
if (!boundary) return row.count;
|
|
3756
|
+
this.db.prepare(
|
|
3757
|
+
`INSERT INTO chain_checkpoint (day, sequence, hash) VALUES (?, ?, ?)
|
|
3758
|
+
ON CONFLICT(day) DO UPDATE SET sequence = excluded.sequence, hash = excluded.hash`
|
|
3759
|
+
).run(boundary.day, boundary.sequence, boundary.hash);
|
|
3760
|
+
this.db.prepare("DELETE FROM events WHERE day < ? OR (day = ? AND sequence <= ?)").run(boundary.day, boundary.day, boundary.sequence);
|
|
3761
|
+
this.db.prepare("DELETE FROM chain_checkpoint WHERE day < ?").run(boundary.day);
|
|
3762
|
+
this.anchors.clear();
|
|
3763
|
+
return this.maxEvents;
|
|
3764
|
+
}
|
|
3765
|
+
enforceEventLimitAtStartup() {
|
|
3766
|
+
if (this.maxEvents === void 0 || this.retainedEventCount <= this.maxEvents) return;
|
|
3767
|
+
try {
|
|
3768
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
3769
|
+
const retainedCount = this.enforceEventLimitWithinTransaction();
|
|
3770
|
+
this.db.exec("COMMIT");
|
|
3771
|
+
this.retainedEventCount = retainedCount;
|
|
3772
|
+
} catch (error) {
|
|
3773
|
+
try {
|
|
3774
|
+
this.db.exec("ROLLBACK");
|
|
3775
|
+
} catch {
|
|
3776
|
+
}
|
|
3777
|
+
throw error;
|
|
3778
|
+
}
|
|
3779
|
+
}
|
|
3559
3780
|
/**
|
|
3560
3781
|
* Drop events older than the retention window.
|
|
3561
3782
|
*
|
|
@@ -3601,6 +3822,7 @@ var ChronicleSqliteJournal = class {
|
|
|
3601
3822
|
};
|
|
3602
3823
|
}
|
|
3603
3824
|
this.anchors.clear();
|
|
3825
|
+
this.retainedEventCount = Math.max(0, this.retainedEventCount - count);
|
|
3604
3826
|
return { ...empty, deletedCount: count };
|
|
3605
3827
|
}
|
|
3606
3828
|
/**
|
|
@@ -3632,7 +3854,9 @@ var ChronicleSqliteJournal = class {
|
|
|
3632
3854
|
ON CONFLICT(day) DO UPDATE SET sequence = excluded.sequence, hash = excluded.hash`
|
|
3633
3855
|
);
|
|
3634
3856
|
this.db.exec("BEGIN IMMEDIATE");
|
|
3857
|
+
let retainedCountAfterCommit = this.retainedEventCount;
|
|
3635
3858
|
try {
|
|
3859
|
+
this.assertWithinByteQuota([]);
|
|
3636
3860
|
await load({
|
|
3637
3861
|
insert: (day, event) => {
|
|
3638
3862
|
const row = projectEvent(event);
|
|
@@ -3662,16 +3886,19 @@ var ChronicleSqliteJournal = class {
|
|
|
3662
3886
|
checkpoint.run(day, sequence, hash3);
|
|
3663
3887
|
}
|
|
3664
3888
|
});
|
|
3889
|
+
retainedCountAfterCommit = this.enforceEventLimitWithinTransaction();
|
|
3890
|
+
this.assertActualAllocationWithinQuota();
|
|
3665
3891
|
this.db.exec("COMMIT");
|
|
3666
3892
|
} catch (error) {
|
|
3667
3893
|
try {
|
|
3668
3894
|
this.db.exec("ROLLBACK");
|
|
3669
3895
|
} catch {
|
|
3670
3896
|
}
|
|
3671
|
-
throw error;
|
|
3897
|
+
throw this.normalizeQuotaError(error);
|
|
3672
3898
|
} finally {
|
|
3673
3899
|
this.anchors.clear();
|
|
3674
3900
|
}
|
|
3901
|
+
this.retainedEventCount = retainedCountAfterCommit;
|
|
3675
3902
|
}
|
|
3676
3903
|
/**
|
|
3677
3904
|
* A read engine over this journal's own connection.
|
|
@@ -3827,7 +4054,7 @@ function projectEvent(event) {
|
|
|
3827
4054
|
|
|
3828
4055
|
// src/chronicle/project-server-client.ts
|
|
3829
4056
|
import { spawn } from "node:child_process";
|
|
3830
|
-
import * as
|
|
4057
|
+
import * as fs9 from "node:fs";
|
|
3831
4058
|
import * as net from "node:net";
|
|
3832
4059
|
import * as path13 from "node:path";
|
|
3833
4060
|
import { fileURLToPath } from "node:url";
|
|
@@ -3876,14 +4103,14 @@ var DEFAULT_CALL_TIMEOUT_MS = 3e4;
|
|
|
3876
4103
|
var APPEND_CALL_TIMEOUT_MS = 6e4;
|
|
3877
4104
|
var DEFAULT_BATCH_WINDOW_MS = 5;
|
|
3878
4105
|
var DEFAULT_MAX_PENDING = 1e5;
|
|
3879
|
-
function resolveChronicleDaemonAvailability(moduleUrl = import.meta.url, exists =
|
|
4106
|
+
function resolveChronicleDaemonAvailability(moduleUrl = import.meta.url, exists = fs9.existsSync) {
|
|
3880
4107
|
if (process.env["WRONGSTACK_CHRONICLE_INLINE"] || process.env["WRONGSTACK_CHRONICLE_SERVER"] === "0") {
|
|
3881
4108
|
return { kind: "inline-requested" };
|
|
3882
4109
|
}
|
|
3883
4110
|
const url = locateChronicleProjectServer(moduleUrl, exists);
|
|
3884
4111
|
return url ? { kind: "available", url } : { kind: "missing-build" };
|
|
3885
4112
|
}
|
|
3886
|
-
function resolveChronicleProjectServerUrl(moduleUrl = import.meta.url, exists =
|
|
4113
|
+
function resolveChronicleProjectServerUrl(moduleUrl = import.meta.url, exists = fs9.existsSync) {
|
|
3887
4114
|
const availability = resolveChronicleDaemonAvailability(moduleUrl, exists);
|
|
3888
4115
|
return availability.kind === "available" ? availability.url : null;
|
|
3889
4116
|
}
|
|
@@ -4052,7 +4279,7 @@ var ChronicleProjectServerClient = class {
|
|
|
4052
4279
|
currentAuthToken() {
|
|
4053
4280
|
if (this.authToken === void 0) {
|
|
4054
4281
|
try {
|
|
4055
|
-
const raw =
|
|
4282
|
+
const raw = fs9.readFileSync(
|
|
4056
4283
|
chronicleProjectServerMetadataPath(this.options.projectDir),
|
|
4057
4284
|
"utf8"
|
|
4058
4285
|
);
|
|
@@ -4596,6 +4823,63 @@ var InlineChronicleProjectAccess = class {
|
|
|
4596
4823
|
}
|
|
4597
4824
|
};
|
|
4598
4825
|
|
|
4826
|
+
// src/chronicle/sqlite-compaction.ts
|
|
4827
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3, statSync as statSync3 } from "node:fs";
|
|
4828
|
+
import { createRequire as createRequire3 } from "node:module";
|
|
4829
|
+
var require2 = createRequire3(import.meta.url);
|
|
4830
|
+
var ChronicleCompactionBusyError = class extends Error {
|
|
4831
|
+
constructor(pid) {
|
|
4832
|
+
super(`Chronicle project server is still running (pid ${pid}); stop it before compacting`);
|
|
4833
|
+
this.name = "ChronicleCompactionBusyError";
|
|
4834
|
+
}
|
|
4835
|
+
};
|
|
4836
|
+
function chronicleSqliteAllocatedBytes(dbPath) {
|
|
4837
|
+
return [dbPath, `${dbPath}-wal`, `${dbPath}-shm`].reduce((total, file) => {
|
|
4838
|
+
try {
|
|
4839
|
+
return total + statSync3(file).size;
|
|
4840
|
+
} catch (error) {
|
|
4841
|
+
if (error.code === "ENOENT") return total;
|
|
4842
|
+
throw error;
|
|
4843
|
+
}
|
|
4844
|
+
}, 0);
|
|
4845
|
+
}
|
|
4846
|
+
function compactChronicleSqlite(options) {
|
|
4847
|
+
assertProjectServerStopped(options.endpointMetadataPath);
|
|
4848
|
+
const beforeBytes = chronicleSqliteAllocatedBytes(options.dbPath);
|
|
4849
|
+
if (!existsSync2(options.dbPath)) {
|
|
4850
|
+
return { beforeBytes, afterBytes: beforeBytes, reclaimedBytes: 0 };
|
|
4851
|
+
}
|
|
4852
|
+
const Database = withSqliteExperimentalWarningSuppressed(
|
|
4853
|
+
() => require2("node:sqlite").DatabaseSync
|
|
4854
|
+
);
|
|
4855
|
+
const db = new Database(options.dbPath);
|
|
4856
|
+
try {
|
|
4857
|
+
db.exec("PRAGMA busy_timeout = 1");
|
|
4858
|
+
db.exec("PRAGMA wal_checkpoint(TRUNCATE)");
|
|
4859
|
+
db.exec("VACUUM");
|
|
4860
|
+
db.exec("PRAGMA wal_checkpoint(TRUNCATE)");
|
|
4861
|
+
} finally {
|
|
4862
|
+
db.close();
|
|
4863
|
+
}
|
|
4864
|
+
const afterBytes = chronicleSqliteAllocatedBytes(options.dbPath);
|
|
4865
|
+
return {
|
|
4866
|
+
beforeBytes,
|
|
4867
|
+
afterBytes,
|
|
4868
|
+
reclaimedBytes: Math.max(0, beforeBytes - afterBytes)
|
|
4869
|
+
};
|
|
4870
|
+
}
|
|
4871
|
+
function assertProjectServerStopped(endpointMetadataPath) {
|
|
4872
|
+
if (!endpointMetadataPath || !existsSync2(endpointMetadataPath)) return;
|
|
4873
|
+
try {
|
|
4874
|
+
const metadata = JSON.parse(readFileSync3(endpointMetadataPath, "utf8"));
|
|
4875
|
+
if (typeof metadata.pid === "number" && isPidAlive(metadata.pid)) {
|
|
4876
|
+
throw new ChronicleCompactionBusyError(metadata.pid);
|
|
4877
|
+
}
|
|
4878
|
+
} catch (error) {
|
|
4879
|
+
if (error instanceof ChronicleCompactionBusyError) throw error;
|
|
4880
|
+
}
|
|
4881
|
+
}
|
|
4882
|
+
|
|
4599
4883
|
// src/chronicle/provider-adapter.ts
|
|
4600
4884
|
function wireProviderAttemptsToChronicle(options) {
|
|
4601
4885
|
const seenToolManifests = /* @__PURE__ */ new Set();
|
|
@@ -5753,17 +6037,20 @@ export {
|
|
|
5753
6037
|
CHRONICLE_PROJECT_SERVER_MAX_FRAME_CHARS,
|
|
5754
6038
|
CHRONICLE_PROJECT_SERVER_PROTOCOL_VERSION,
|
|
5755
6039
|
CHRONICLE_SCHEMA_VERSION,
|
|
6040
|
+
ChronicleCompactionBusyError,
|
|
5756
6041
|
ChronicleJournal,
|
|
5757
6042
|
ChronicleMetricsStore,
|
|
5758
6043
|
ChroniclePartitionRangeCache,
|
|
5759
6044
|
ChronicleProjectServerClient,
|
|
5760
6045
|
ChronicleQueryEngine,
|
|
5761
6046
|
ChronicleRemoteJournal,
|
|
6047
|
+
ChronicleStorageQuotaError,
|
|
5762
6048
|
GENESIS_HASH,
|
|
5763
6049
|
childChronicleContext,
|
|
5764
6050
|
chronicleProjectServerEndpoint,
|
|
5765
6051
|
chronicleProjectServerKey,
|
|
5766
6052
|
chronicleProjectServerMetadataPath,
|
|
6053
|
+
compactChronicleSqlite,
|
|
5767
6054
|
createChronicleContext,
|
|
5768
6055
|
createChronicleEventJournal,
|
|
5769
6056
|
createChronicleProjectAccess,
|