@wrongstack/core 0.299.0 → 0.301.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.
Files changed (165) hide show
  1. package/dist/chronicle/index.d.ts +2 -0
  2. package/dist/chronicle/index.js +297 -10
  3. package/dist/chronicle/project-server.js +253 -15
  4. package/dist/chronicle/sqlite-compaction.d.ts +20 -0
  5. package/dist/chronicle/sqlite-journal.d.ts +57 -0
  6. package/dist/coordination/agents/capability-manifest.d.ts +7 -0
  7. package/dist/coordination/agents/index.d.ts +3 -2
  8. package/dist/coordination/agents/index.js +818 -570
  9. package/dist/coordination/agents/project-agent-identity.d.ts +6 -6
  10. package/dist/coordination/agents/role-skills.d.ts +1 -1
  11. package/dist/coordination/agents/types.d.ts +6 -0
  12. package/dist/coordination/director.d.ts +10 -2
  13. package/dist/coordination/fleet-manager.d.ts +48 -3
  14. package/dist/coordination/ifleet-manager.d.ts +2 -0
  15. package/dist/coordination/index.js +6880 -6440
  16. package/dist/coordination/mail-tools.d.ts +3 -3
  17. package/dist/coordination/mailbox-project-server.js +3 -4
  18. package/dist/coordination/mailbox-types.d.ts +6 -0
  19. package/dist/coordination/multi-agent-coordinator.d.ts +1 -0
  20. package/dist/core/agent-response.d.ts +5 -1
  21. package/dist/core/agent-tools.d.ts +3 -0
  22. package/dist/core/context.d.ts +20 -0
  23. package/dist/core/fallback-model.d.ts +48 -0
  24. package/dist/core/index.d.ts +11 -9
  25. package/dist/core/index.js +1119 -310
  26. package/dist/core/instruction-template.d.ts +83 -0
  27. package/dist/core/next-steps-slot.d.ts +88 -0
  28. package/dist/core/system-prompt-blocks.d.ts +10 -1
  29. package/dist/core/system-prompt-builder.d.ts +40 -4
  30. package/dist/core/system-prompt-memory-skills.d.ts +1 -0
  31. package/dist/core/system-prompt-skill-bodies.d.ts +3 -3
  32. package/dist/defaults/index.js +9805 -8767
  33. package/dist/design/index.js +11 -3
  34. package/dist/execution/auto-compaction-middleware.d.ts +17 -0
  35. package/dist/execution/autonomy-brain.d.ts +15 -2
  36. package/dist/execution/brain-runtime.d.ts +3 -1
  37. package/dist/execution/compaction-core.d.ts +41 -2
  38. package/dist/execution/council-brain.d.ts +47 -1
  39. package/dist/execution/council-orchestrator.d.ts +25 -6
  40. package/dist/execution/council-prompts.d.ts +12 -1
  41. package/dist/execution/index.d.ts +11 -10
  42. package/dist/execution/index.js +10507 -9189
  43. package/dist/execution/tool-executor.d.ts +4 -1
  44. package/dist/execution/topic-shift-advisor.d.ts +53 -0
  45. package/dist/extension/index.js +8 -2
  46. package/dist/extension/registry.d.ts +8 -1
  47. package/dist/fleet-notifier.d.ts +9 -2
  48. package/dist/goal/index.js +11 -3
  49. package/dist/hooks/index.js +50 -13
  50. package/dist/hooks/runner.d.ts +12 -1
  51. package/dist/hq/index.js +59 -21
  52. package/dist/hq/protocol/fleet.d.ts +20 -0
  53. package/dist/hq/protocol.js +10 -0
  54. package/dist/hq/publisher.d.ts +21 -3
  55. package/dist/index.d.ts +22 -20
  56. package/dist/index.js +12211 -8572
  57. package/dist/infrastructure/index.js +108 -21
  58. package/dist/kernel/events/brain-events.d.ts +9 -0
  59. package/dist/kernel/events/provider-events.d.ts +42 -1
  60. package/dist/kernel/events/session-events.d.ts +13 -0
  61. package/dist/kernel/events/tool-events.d.ts +3 -3
  62. package/dist/models/index.d.ts +1 -0
  63. package/dist/models/index.js +18 -1
  64. package/dist/models/provider-credentials.d.ts +54 -0
  65. package/dist/plugin/api.d.ts +6 -0
  66. package/dist/plugin/config.d.ts +55 -0
  67. package/dist/plugin/index.d.ts +2 -1
  68. package/dist/plugin/index.js +2179 -544
  69. package/dist/plugins/auto-review-plugin.d.ts +3 -0
  70. package/dist/plugins/cloud-config-sync-plugin.d.ts +22 -0
  71. package/dist/plugins/review-claim-registry.d.ts +23 -8
  72. package/dist/plugins/review-types.d.ts +2 -0
  73. package/dist/registry/index.js +109 -74
  74. package/dist/registry/tool-registry.d.ts +15 -0
  75. package/dist/security/capabilities.d.ts +2 -0
  76. package/dist/security/index.d.ts +2 -2
  77. package/dist/security/index.js +166 -44
  78. package/dist/security/permission-helpers.d.ts +23 -6
  79. package/dist/security/permission-policy.d.ts +16 -0
  80. package/dist/security/readonly-permission-policy.d.ts +20 -0
  81. package/dist/security/totp.d.ts +14 -0
  82. package/dist/session-registry-atomic-file.d.ts +32 -5
  83. package/dist/skills/frontmatter.d.ts +6 -0
  84. package/dist/skills/index.js +22 -5
  85. package/dist/storage/cloud-config-sync/sanitize.d.ts +54 -0
  86. package/dist/storage/cloud-config-sync.d.ts +87 -0
  87. package/dist/storage/director-state.d.ts +7 -0
  88. package/dist/storage/index.d.ts +1 -0
  89. package/dist/storage/index.js +887 -74
  90. package/dist/tools/fallback-system-config-view-tool.d.ts +1 -1
  91. package/dist/tools/index.d.ts +1 -1
  92. package/dist/tools/index.js +898 -335
  93. package/dist/tools/one-shot-llm-tool.d.ts +1 -0
  94. package/dist/tools/plugin-manager.d.ts +27 -0
  95. package/dist/types/config/mcp-features.d.ts +18 -11
  96. package/dist/types/config/root.d.ts +8 -1
  97. package/dist/types/config/runtime.d.ts +20 -6
  98. package/dist/types/config/skills-fleet-brain.d.ts +12 -4
  99. package/dist/types/config/tools.d.ts +16 -0
  100. package/dist/types/context-window.d.ts +1 -0
  101. package/dist/types/council.d.ts +11 -0
  102. package/dist/types/hooks.d.ts +14 -0
  103. package/dist/types/index.d.ts +3 -3
  104. package/dist/types/index.js +1 -0
  105. package/dist/types/multi-agent.d.ts +12 -0
  106. package/dist/types/one-shot-llm.d.ts +25 -0
  107. package/dist/types/plugin.d.ts +28 -0
  108. package/dist/types/provider.d.ts +16 -0
  109. package/dist/types/runtime-capability-manifest.d.ts +168 -0
  110. package/dist/types/skill.d.ts +5 -0
  111. package/dist/types/system-prompt.d.ts +3 -1
  112. package/dist/types/tool-executor.d.ts +8 -1
  113. package/dist/utils/context-breakdown.d.ts +8 -2
  114. package/dist/utils/index.d.ts +1 -1
  115. package/dist/utils/index.js +141 -31
  116. package/dist/utils/regex-guard.d.ts +16 -2
  117. package/dist/utils/sage-output-block.d.ts +7 -10
  118. package/dist/utils/wstack-paths.d.ts +11 -3
  119. package/dist/worktree/index.js +4 -4
  120. package/instructions/agents/browser.md +1 -4
  121. package/instructions/agents/e2e.md +17 -15
  122. package/instructions/agents/ios.md +3 -3
  123. package/instructions/agents/search.md +1 -1
  124. package/instructions/autonomy/goal-preamble.md +4 -4
  125. package/instructions/coordination/director-preamble.md +2 -3
  126. package/instructions/coordination/subagent-baseline.md +3 -1
  127. package/instructions/leader-after-task.md +12 -0
  128. package/instructions/llm/chimera-review.md +1 -1
  129. package/instructions/modes/audit-lite.md +1 -1
  130. package/instructions/sections/tool/common-patterns.md +18 -2
  131. package/instructions/sections/tool/mailbox-compact.md +1 -1
  132. package/instructions/sections/tool/mailbox-full.md +1 -1
  133. package/instructions/system-lite.md +91 -3
  134. package/instructions/system-pro.md +296 -102
  135. package/instructions/system.md +248 -92
  136. package/package.json +3 -3
  137. package/skills/api-design/SKILL.md +3 -0
  138. package/skills/audit-log/SKILL.md +2 -0
  139. package/skills/auto-review/SKILL.md +6 -1
  140. package/skills/bug-hunter/SKILL.md +4 -1
  141. package/skills/chimera/SKILL.md +3 -0
  142. package/skills/data-governance/SKILL.md +3 -0
  143. package/skills/design-system/SKILL.md +5 -2
  144. package/skills/docker-deploy/SKILL.md +2 -0
  145. package/skills/git-flow/SKILL.md +2 -0
  146. package/skills/mailbox-bridge/SKILL.md +3 -0
  147. package/skills/mnemosyne/SKILL.md +2 -0
  148. package/skills/multi-agent/SKILL.md +4 -1
  149. package/skills/node-modern/SKILL.md +4 -1
  150. package/skills/observability/SKILL.md +3 -0
  151. package/skills/output-standards/SKILL.md +2 -0
  152. package/skills/plugin-author/SKILL.md +4 -1
  153. package/skills/prompt-engineering/SKILL.md +3 -1
  154. package/skills/react-modern/SKILL.md +6 -3
  155. package/skills/refactor-planner/SKILL.md +2 -0
  156. package/skills/research-web/SKILL.md +3 -0
  157. package/skills/sdd/SKILL.md +3 -1
  158. package/skills/security-scanner/SKILL.md +3 -0
  159. package/skills/skill-creator/SKILL.md +2 -0
  160. package/skills/tech-stack/SKILL.md +3 -0
  161. package/skills/testing/SKILL.md +4 -1
  162. package/skills/typescript-strict/SKILL.md +4 -1
  163. package/skills/wrongstack-kanban/SKILL.md +6 -3
  164. package/skills/wrongstack-mailbox/SKILL.md +4 -1
  165. package/skills/wrongstack-mailbox-mcp/SKILL.md +10 -8
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/chronicle/project-server.ts
4
4
  import { randomBytes } from "node:crypto";
5
- import * as fs9 from "node:fs";
5
+ import * as fs10 from "node:fs";
6
6
  import * as fsp4 from "node:fs/promises";
7
7
  import * as net from "node:net";
8
8
  import * as path15 from "node:path";
@@ -3696,6 +3696,7 @@ function ensureChronicleProjectServerSocketDirectory(endpoint2) {
3696
3696
 
3697
3697
  // src/chronicle/sqlite-journal.ts
3698
3698
  import { randomUUID as randomUUID3 } from "node:crypto";
3699
+ import * as fs9 from "node:fs";
3699
3700
  import { createRequire as createRequire2 } from "node:module";
3700
3701
  import * as path14 from "node:path";
3701
3702
 
@@ -3941,6 +3942,25 @@ var CHRONICLE_SQLITE_FILE = "chronicle.sqlite";
3941
3942
  var LEGACY_JSONL_MIGRATION_KEY = "legacy-jsonl-v1";
3942
3943
  var LEGACY_JSONL_QUARANTINE_KEY = "legacy-jsonl-v1:quarantine";
3943
3944
  var SCHEMA_VERSION2 = 1;
3945
+ var SQLITE_FIXED_OVERHEAD_BYTES = 16 * 1024 * 1024;
3946
+ var MIN_SQLITE_PAGE_BUDGET_BYTES = 64 * 1024;
3947
+ var MAX_ROLLBACK_JOURNAL_RESERVE_BYTES = 64 * 1024 * 1024;
3948
+ var ChronicleStorageQuotaError = class extends Error {
3949
+ currentBytes;
3950
+ batchBytes;
3951
+ maxBytes;
3952
+ path;
3953
+ constructor(details) {
3954
+ super(
3955
+ `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)`
3956
+ );
3957
+ this.name = "ChronicleStorageQuotaError";
3958
+ this.currentBytes = details.currentBytes;
3959
+ this.batchBytes = details.batchBytes;
3960
+ this.maxBytes = details.maxBytes;
3961
+ this.path = details.path;
3962
+ }
3963
+ };
3944
3964
  var Ctor2;
3945
3965
  function loadDatabaseSync2() {
3946
3966
  if (Ctor2) return Ctor2;
@@ -3963,6 +3983,14 @@ var ChronicleSqliteJournal = class {
3963
3983
  now;
3964
3984
  monotonicNow;
3965
3985
  idFactory;
3986
+ retentionDays;
3987
+ maxEvents;
3988
+ maxBytes;
3989
+ retentionCheckIntervalMs;
3990
+ nextRetentionCheckAt = 0;
3991
+ retainedEventCount = 0;
3992
+ /** Resolved lazily by `pageSizeBytes()`; constant for an open database. */
3993
+ cachedPageSize;
3966
3994
  /**
3967
3995
  * Cached chain head per day. Cleared wholesale on any write failure so the
3968
3996
  * next append rebuilds from the database rather than trusting a counter that
@@ -3984,10 +4012,36 @@ var ChronicleSqliteJournal = class {
3984
4012
  this.now = options.now ?? (() => /* @__PURE__ */ new Date());
3985
4013
  this.monotonicNow = options.monotonicNow ?? (() => process.hrtime.bigint());
3986
4014
  this.idFactory = options.idFactory ?? (() => randomUUID3());
4015
+ if (options.retentionDays !== void 0 && (!Number.isFinite(options.retentionDays) || options.retentionDays <= 0)) {
4016
+ throw new RangeError("retentionDays must be a positive finite number");
4017
+ }
4018
+ if (options.retentionCheckIntervalMs !== void 0 && (!Number.isFinite(options.retentionCheckIntervalMs) || options.retentionCheckIntervalMs <= 0)) {
4019
+ throw new RangeError("retentionCheckIntervalMs must be a positive finite number");
4020
+ }
4021
+ if (options.maxEvents !== void 0 && (!Number.isInteger(options.maxEvents) || options.maxEvents < 1)) {
4022
+ throw new RangeError("maxEvents must be a positive integer");
4023
+ }
4024
+ if (options.maxBytes !== void 0 && (!Number.isSafeInteger(options.maxBytes) || options.maxBytes < 1)) {
4025
+ throw new RangeError("maxBytes must be a positive safe integer");
4026
+ }
4027
+ const minimumQuotaBytes = SQLITE_FIXED_OVERHEAD_BYTES + 2 * MIN_SQLITE_PAGE_BUDGET_BYTES;
4028
+ if (options.maxBytes !== void 0 && options.maxBytes < minimumQuotaBytes) {
4029
+ throw new RangeError(`maxBytes must be at least ${minimumQuotaBytes}`);
4030
+ }
4031
+ this.retentionDays = options.retentionDays;
4032
+ this.maxEvents = options.maxEvents;
4033
+ this.maxBytes = options.maxBytes;
4034
+ this.retentionCheckIntervalMs = options.retentionCheckIntervalMs ?? 60 * 60 * 1e3;
3987
4035
  const Database = loadDatabaseSync2();
3988
4036
  this.db = new Database(this.dbPath);
3989
- this.db.exec("PRAGMA journal_mode = WAL");
4037
+ this.db.exec(`PRAGMA journal_mode = ${this.maxBytes === void 0 ? "WAL" : "DELETE"}`);
3990
4038
  this.ensureSchema();
4039
+ this.configureByteQuota();
4040
+ if (this.maxEvents !== void 0) {
4041
+ const row = this.db.prepare("SELECT COUNT(*) AS count FROM events").get();
4042
+ this.retainedEventCount = row.count;
4043
+ this.enforceEventLimitAtStartup();
4044
+ }
3991
4045
  }
3992
4046
  close() {
3993
4047
  this.db.close();
@@ -4046,8 +4100,10 @@ var ChronicleSqliteJournal = class {
4046
4100
  events.push(event);
4047
4101
  previous = { sequence: event.sequence, hash: event.hash };
4048
4102
  }
4103
+ let retainedCountAfterCommit = this.retainedEventCount;
4049
4104
  try {
4050
4105
  this.db.exec("BEGIN IMMEDIATE");
4106
+ this.assertWithinByteQuota(events);
4051
4107
  const insert = this.db.prepare(
4052
4108
  `INSERT INTO events (
4053
4109
  day, sequence, event_id, hash, previous_hash, occurred_at, event_type, outcome,
@@ -4079,6 +4135,8 @@ var ChronicleSqliteJournal = class {
4079
4135
  JSON.stringify(event)
4080
4136
  );
4081
4137
  }
4138
+ retainedCountAfterCommit = this.enforceEventLimitWithinTransaction();
4139
+ this.assertActualAllocationWithinQuota();
4082
4140
  this.db.exec("COMMIT");
4083
4141
  } catch (error) {
4084
4142
  try {
@@ -4088,11 +4146,13 @@ var ChronicleSqliteJournal = class {
4088
4146
  this.anchors.clear();
4089
4147
  this.counters.failedEvents += inputs.length;
4090
4148
  this.lastBatchDurationMs = performance.now() - started;
4091
- throw error;
4149
+ throw this.normalizeQuotaError(error);
4092
4150
  }
4093
4151
  this.anchors.set(day, previous);
4094
4152
  this.counters.persistedEvents += events.length;
4153
+ this.retainedEventCount = retainedCountAfterCommit;
4095
4154
  this.lastBatchDurationMs = performance.now() - started;
4155
+ await this.enforceRetentionIfDue();
4096
4156
  return events;
4097
4157
  }
4098
4158
  async readAll() {
@@ -4153,6 +4213,145 @@ var ChronicleSqliteJournal = class {
4153
4213
  }
4154
4214
  return { ok: true, entries, lastSequence, lastHash };
4155
4215
  }
4216
+ async enforceRetentionIfDue() {
4217
+ if (this.retentionDays === void 0) return;
4218
+ const now = this.now();
4219
+ if (now.getTime() < this.nextRetentionCheckAt) return;
4220
+ this.nextRetentionCheckAt = now.getTime() + this.retentionCheckIntervalMs;
4221
+ try {
4222
+ await this.purge({ retentionDays: this.retentionDays });
4223
+ } catch {
4224
+ }
4225
+ }
4226
+ configureByteQuota() {
4227
+ if (this.maxBytes === void 0) {
4228
+ this.db.exec("PRAGMA max_page_count = 2147483646");
4229
+ return;
4230
+ }
4231
+ const halfSplit = Math.floor((this.maxBytes - SQLITE_FIXED_OVERHEAD_BYTES) / 2);
4232
+ const journalReserve = Math.min(MAX_ROLLBACK_JOURNAL_RESERVE_BYTES, halfSplit);
4233
+ const mainBudget = this.maxBytes - SQLITE_FIXED_OVERHEAD_BYTES - journalReserve;
4234
+ if (mainBudget < MIN_SQLITE_PAGE_BUDGET_BYTES) {
4235
+ throw new Error(
4236
+ `maxBytes must be at least ${SQLITE_FIXED_OVERHEAD_BYTES + 2 * MIN_SQLITE_PAGE_BUDGET_BYTES}`
4237
+ );
4238
+ }
4239
+ const maxPages = Math.max(1, Math.floor(mainBudget / this.pageSizeBytes()));
4240
+ this.db.exec(`PRAGMA max_page_count = ${maxPages}`);
4241
+ }
4242
+ /**
4243
+ * Bytes the journal actually occupies: live pages in the main database plus
4244
+ * whatever the rollback/WAL sidecars currently hold.
4245
+ *
4246
+ * The quota MUST be measured this way rather than by `statSync` on the main
4247
+ * database file. SQLite never returns freed pages to the filesystem — it
4248
+ * parks them on the freelist and reuses them — so a database that once grew
4249
+ * large keeps that size forever, even after retention and `maxEvents` have
4250
+ * evicted almost everything. Measuring the file instead wedged the journal
4251
+ * permanently: a 3.9 GB file whose live data was 243 MB failed a 512 MB
4252
+ * quota on EVERY append, and the only escape (`chronicle compact`) refuses
4253
+ * to run while the daemon is up — and the daemon respawns on demand. Live
4254
+ * pages shrink when retention deletes rows, so the quota can recover on its
4255
+ * own; allocated size cannot.
4256
+ *
4257
+ * The sidecars keep their on-disk size: they are transient and genuinely
4258
+ * bounded by `configureByteQuota`'s half-of-budget split.
4259
+ */
4260
+ aggregateLiveBytes() {
4261
+ let total = 0;
4262
+ for (const file of [`${this.dbPath}-journal`, `${this.dbPath}-wal`, `${this.dbPath}-shm`]) {
4263
+ try {
4264
+ total += fs9.statSync(file).size;
4265
+ } catch (error) {
4266
+ if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) throw error;
4267
+ }
4268
+ }
4269
+ return total + this.mainDatabaseLiveBytes();
4270
+ }
4271
+ /** Live (non-freelist) pages of the main database, in bytes. */
4272
+ mainDatabaseLiveBytes() {
4273
+ const pageCount = Number(
4274
+ this.db.prepare("PRAGMA page_count").get().page_count
4275
+ );
4276
+ const freelist = Number(
4277
+ this.db.prepare("PRAGMA freelist_count").get().freelist_count
4278
+ );
4279
+ return Math.max(0, pageCount - freelist) * this.pageSizeBytes();
4280
+ }
4281
+ /** Page size never changes for an open database; resolve it once. */
4282
+ pageSizeBytes() {
4283
+ if (this.cachedPageSize === void 0) {
4284
+ this.cachedPageSize = Number(
4285
+ this.db.prepare("PRAGMA page_size").get().page_size
4286
+ );
4287
+ }
4288
+ return this.cachedPageSize;
4289
+ }
4290
+ normalizeQuotaError(error) {
4291
+ if (this.maxBytes === void 0 || !(error instanceof Error)) return error;
4292
+ const sqliteError = error;
4293
+ const quotaExhausted = sqliteError.code === "SQLITE_FULL" || sqliteError.code === 13 || sqliteError.errcode === 13 || /database or disk is full/i.test(sqliteError.message);
4294
+ if (!quotaExhausted) return error;
4295
+ return new ChronicleStorageQuotaError({
4296
+ currentBytes: this.aggregateLiveBytes(),
4297
+ batchBytes: 0,
4298
+ maxBytes: this.maxBytes,
4299
+ path: this.dbPath
4300
+ });
4301
+ }
4302
+ assertActualAllocationWithinQuota() {
4303
+ if (this.maxBytes === void 0) return;
4304
+ const currentBytes = this.aggregateLiveBytes();
4305
+ if (currentBytes <= this.maxBytes) return;
4306
+ throw new ChronicleStorageQuotaError({
4307
+ currentBytes,
4308
+ batchBytes: 0,
4309
+ maxBytes: this.maxBytes,
4310
+ path: this.dbPath
4311
+ });
4312
+ }
4313
+ assertWithinByteQuota(events) {
4314
+ if (this.maxBytes === void 0) return;
4315
+ const currentBytes = this.aggregateLiveBytes();
4316
+ const batchBytes = Buffer.byteLength(JSON.stringify(events), "utf8");
4317
+ if (currentBytes + batchBytes <= this.maxBytes) return;
4318
+ throw new ChronicleStorageQuotaError({
4319
+ currentBytes,
4320
+ batchBytes,
4321
+ maxBytes: this.maxBytes,
4322
+ path: this.dbPath
4323
+ });
4324
+ }
4325
+ enforceEventLimitWithinTransaction() {
4326
+ const row = this.db.prepare("SELECT COUNT(*) AS count FROM events").get();
4327
+ if (this.maxEvents === void 0 || row.count <= this.maxEvents) return row.count;
4328
+ const excess = row.count - this.maxEvents;
4329
+ const boundary = this.db.prepare("SELECT day, sequence, hash FROM events ORDER BY day, sequence LIMIT 1 OFFSET ?").get(excess - 1);
4330
+ if (!boundary) return row.count;
4331
+ this.db.prepare(
4332
+ `INSERT INTO chain_checkpoint (day, sequence, hash) VALUES (?, ?, ?)
4333
+ ON CONFLICT(day) DO UPDATE SET sequence = excluded.sequence, hash = excluded.hash`
4334
+ ).run(boundary.day, boundary.sequence, boundary.hash);
4335
+ this.db.prepare("DELETE FROM events WHERE day < ? OR (day = ? AND sequence <= ?)").run(boundary.day, boundary.day, boundary.sequence);
4336
+ this.db.prepare("DELETE FROM chain_checkpoint WHERE day < ?").run(boundary.day);
4337
+ this.anchors.clear();
4338
+ return this.maxEvents;
4339
+ }
4340
+ enforceEventLimitAtStartup() {
4341
+ if (this.maxEvents === void 0 || this.retainedEventCount <= this.maxEvents) return;
4342
+ try {
4343
+ this.db.exec("BEGIN IMMEDIATE");
4344
+ const retainedCount = this.enforceEventLimitWithinTransaction();
4345
+ this.db.exec("COMMIT");
4346
+ this.retainedEventCount = retainedCount;
4347
+ } catch (error) {
4348
+ try {
4349
+ this.db.exec("ROLLBACK");
4350
+ } catch {
4351
+ }
4352
+ throw error;
4353
+ }
4354
+ }
4156
4355
  /**
4157
4356
  * Drop events older than the retention window.
4158
4357
  *
@@ -4198,6 +4397,7 @@ var ChronicleSqliteJournal = class {
4198
4397
  };
4199
4398
  }
4200
4399
  this.anchors.clear();
4400
+ this.retainedEventCount = Math.max(0, this.retainedEventCount - count);
4201
4401
  return { ...empty, deletedCount: count };
4202
4402
  }
4203
4403
  /**
@@ -4229,7 +4429,9 @@ var ChronicleSqliteJournal = class {
4229
4429
  ON CONFLICT(day) DO UPDATE SET sequence = excluded.sequence, hash = excluded.hash`
4230
4430
  );
4231
4431
  this.db.exec("BEGIN IMMEDIATE");
4432
+ let retainedCountAfterCommit = this.retainedEventCount;
4232
4433
  try {
4434
+ this.assertWithinByteQuota([]);
4233
4435
  await load({
4234
4436
  insert: (day, event) => {
4235
4437
  const row = projectEvent(event);
@@ -4259,16 +4461,19 @@ var ChronicleSqliteJournal = class {
4259
4461
  checkpoint.run(day, sequence, hash);
4260
4462
  }
4261
4463
  });
4464
+ retainedCountAfterCommit = this.enforceEventLimitWithinTransaction();
4465
+ this.assertActualAllocationWithinQuota();
4262
4466
  this.db.exec("COMMIT");
4263
4467
  } catch (error) {
4264
4468
  try {
4265
4469
  this.db.exec("ROLLBACK");
4266
4470
  } catch {
4267
4471
  }
4268
- throw error;
4472
+ throw this.normalizeQuotaError(error);
4269
4473
  } finally {
4270
4474
  this.anchors.clear();
4271
4475
  }
4476
+ this.retainedEventCount = retainedCountAfterCommit;
4272
4477
  }
4273
4478
  /**
4274
4479
  * A read engine over this journal's own connection.
@@ -4451,7 +4656,7 @@ function parseArgs(argv) {
4451
4656
  projectId: values.get("--project-id"),
4452
4657
  projectDir: path15.resolve(values.get("--project-dir")),
4453
4658
  workspaceId: values.get("--workspace-id"),
4454
- retentionDays: Number.isFinite(retentionInput) && retentionInput >= 0 ? retentionInput : 30
4659
+ retentionDays: Number.isFinite(retentionInput) && retentionInput > 0 ? retentionInput : 30
4455
4660
  };
4456
4661
  }
4457
4662
  useDaemonPerfDefaults();
@@ -4524,7 +4729,16 @@ var quarantinedFamilies = [];
4524
4729
  function store() {
4525
4730
  sqliteStore ??= (async () => {
4526
4731
  await fsp4.mkdir(chronicleDirectory, { recursive: true });
4527
- const journal = new ChronicleSqliteJournal({ directory: chronicleDirectory });
4732
+ const journal = new ChronicleSqliteJournal({
4733
+ directory: chronicleDirectory,
4734
+ retentionDays: parsed.retentionDays,
4735
+ // Bound burst growth independently of age retention. Prefix eviction keeps
4736
+ // at most this many rows while preserving chain verification via checkpoints.
4737
+ maxEvents: 1e5,
4738
+ // Formal aggregate allocation ceiling. Quota-enabled journals reserve
4739
+ // rollback-journal headroom and constrain the main database with max_page_count.
4740
+ maxBytes: 512 * 1024 * 1024
4741
+ });
4528
4742
  try {
4529
4743
  const result = await importLegacyChronicleJournal(journal, chronicleDirectory);
4530
4744
  quarantinedFamilies = result.quarantined;
@@ -4734,18 +4948,32 @@ async function dispatch(op, rawArgs) {
4734
4948
  }
4735
4949
  }
4736
4950
  }
4737
- function send(state, message) {
4738
- if (state.socket.destroyed) return;
4951
+ function encodeResponse(state, message) {
4952
+ if (state.socket.destroyed) return void 0;
4739
4953
  const encoded = encodeChronicleProjectServerMessage(message);
4740
4954
  if (encoded.length > CHRONICLE_PROJECT_SERVER_MAX_FRAME_CHARS) {
4741
4955
  state.socket.destroy(new Error("Chronicle project server response exceeded frame limit"));
4742
- return;
4956
+ return void 0;
4743
4957
  }
4744
4958
  if (state.socket.writableLength > MAX_CLIENT_WRITE_BUFFER_BYTES) {
4745
4959
  state.socket.destroy(new Error("Chronicle client fell too far behind on reads"));
4746
- return;
4960
+ return void 0;
4747
4961
  }
4748
- state.socket.write(encoded);
4962
+ return encoded;
4963
+ }
4964
+ function send(state, message) {
4965
+ const encoded = encodeResponse(state, message);
4966
+ if (encoded !== void 0) state.socket.write(encoded);
4967
+ }
4968
+ async function sendAcknowledgement(state, message) {
4969
+ const encoded = encodeResponse(state, message);
4970
+ if (encoded === void 0) throw new Error("Chronicle client disconnected before acknowledgement");
4971
+ await new Promise((resolve12, reject) => {
4972
+ state.socket.write(encoded, (error) => {
4973
+ if (error) reject(error);
4974
+ else resolve12();
4975
+ });
4976
+ });
4749
4977
  }
4750
4978
  async function handleMessage(state, message) {
4751
4979
  if (message.authToken !== authToken) {
@@ -4759,7 +4987,12 @@ async function handleMessage(state, message) {
4759
4987
  return;
4760
4988
  }
4761
4989
  if (message.type === "shutdown") {
4762
- send(state, { type: "response", id: message.id, ok: true, result: { stopped: true } });
4990
+ await sendAcknowledgement(state, {
4991
+ type: "response",
4992
+ id: message.id,
4993
+ ok: true,
4994
+ result: { stopped: true }
4995
+ });
4763
4996
  await stop(message.reason ?? "client request");
4764
4997
  return;
4765
4998
  }
@@ -4829,9 +5062,14 @@ async function stop(_reason) {
4829
5062
  if (idleTimer) clearTimeout(idleTimer);
4830
5063
  idleTimer = void 0;
4831
5064
  await removeOwnedMetadata();
4832
- await new Promise((resolve12) => server.close(() => resolve12()));
4833
5065
  for (const state of clients) state.socket.destroy();
4834
5066
  clients.clear();
5067
+ await new Promise((resolve12, reject) => {
5068
+ server.close((error) => {
5069
+ if (error) reject(error);
5070
+ else resolve12();
5071
+ });
5072
+ });
4835
5073
  await watcher?.close().catch(() => {
4836
5074
  });
4837
5075
  watcher = void 0;
@@ -4882,7 +5120,7 @@ server.on("error", (error) => {
4882
5120
  probe.once("error", () => {
4883
5121
  probe.destroy();
4884
5122
  try {
4885
- fs9.rmSync(endpoint, { force: true });
5123
+ fs10.rmSync(endpoint, { force: true });
4886
5124
  } catch {
4887
5125
  }
4888
5126
  probingExistingEndpoint = false;
@@ -4895,7 +5133,7 @@ server.on("error", (error) => {
4895
5133
  server.on("listening", () => {
4896
5134
  if (process.platform !== "win32") {
4897
5135
  try {
4898
- fs9.chmodSync(endpoint, 384);
5136
+ fs10.chmodSync(endpoint, 384);
4899
5137
  } catch {
4900
5138
  }
4901
5139
  }
@@ -0,0 +1,20 @@
1
+ export interface ChronicleSqliteCompactionOptions {
2
+ dbPath: string;
3
+ endpointMetadataPath?: string;
4
+ }
5
+ export interface ChronicleSqliteCompactionResult {
6
+ beforeBytes: number;
7
+ afterBytes: number;
8
+ reclaimedBytes: number;
9
+ }
10
+ export declare class ChronicleCompactionBusyError extends Error {
11
+ constructor(pid: number);
12
+ }
13
+ /** Aggregate allocated bytes for the SQLite database and its WAL/SHM sidecars. */
14
+ export declare function chronicleSqliteAllocatedBytes(dbPath: string): number;
15
+ /**
16
+ * Reclaim free SQLite pages. The caller must keep the Chronicle project server
17
+ * stopped for the entire operation; this function refuses known live servers.
18
+ */
19
+ export declare function compactChronicleSqlite(options: ChronicleSqliteCompactionOptions): ChronicleSqliteCompactionResult;
20
+ //# sourceMappingURL=sqlite-compaction.d.ts.map
@@ -6,12 +6,31 @@ export declare const CHRONICLE_SQLITE_FILE = "chronicle.sqlite";
6
6
  export declare const LEGACY_JSONL_MIGRATION_KEY = "legacy-jsonl-v1";
7
7
  /** Day families the import refused to move, recorded so they are never retried silently. */
8
8
  export declare const LEGACY_JSONL_QUARANTINE_KEY = "legacy-jsonl-v1:quarantine";
9
+ export declare class ChronicleStorageQuotaError extends Error {
10
+ readonly currentBytes: number;
11
+ readonly batchBytes: number;
12
+ readonly maxBytes: number;
13
+ readonly path: string;
14
+ constructor(details: {
15
+ currentBytes: number;
16
+ batchBytes: number;
17
+ maxBytes: number;
18
+ path: string;
19
+ });
20
+ }
9
21
  export interface ChronicleSqliteJournalOptions {
10
22
  /** Directory holding the journal, i.e. `<projectDir>/chronicle`. */
11
23
  directory: string;
12
24
  now?: (() => Date) | undefined;
13
25
  monotonicNow?: (() => bigint) | undefined;
14
26
  idFactory?: (() => string) | undefined;
27
+ /** Optional age bound, enforced during periodic maintenance. */
28
+ retentionDays?: number | undefined;
29
+ /** Optional row ceiling; oldest rows are checkpointed and evicted after each append. */
30
+ maxEvents?: number | undefined;
31
+ /** Aggregate SQLite allocation ceiling across the database, WAL, and SHM files. */
32
+ maxBytes?: number | undefined;
33
+ retentionCheckIntervalMs?: number | undefined;
15
34
  }
16
35
  export interface ChronicleSqlitePurgeOptions {
17
36
  retentionDays: number;
@@ -43,6 +62,14 @@ export declare class ChronicleSqliteJournal {
43
62
  private readonly now;
44
63
  private readonly monotonicNow;
45
64
  private readonly idFactory;
65
+ private readonly retentionDays;
66
+ private readonly maxEvents;
67
+ private readonly maxBytes;
68
+ private readonly retentionCheckIntervalMs;
69
+ private nextRetentionCheckAt;
70
+ private retainedEventCount;
71
+ /** Resolved lazily by `pageSizeBytes()`; constant for an open database. */
72
+ private cachedPageSize;
46
73
  /**
47
74
  * Cached chain head per day. Cleared wholesale on any write failure so the
48
75
  * next append rebuilds from the database rather than trusting a counter that
@@ -83,6 +110,36 @@ export declare class ChronicleSqliteJournal {
83
110
  * an in-place edit that left the links intact.
84
111
  */
85
112
  verify(): Promise<ChronicleVerifyResult>;
113
+ private enforceRetentionIfDue;
114
+ private configureByteQuota;
115
+ /**
116
+ * Bytes the journal actually occupies: live pages in the main database plus
117
+ * whatever the rollback/WAL sidecars currently hold.
118
+ *
119
+ * The quota MUST be measured this way rather than by `statSync` on the main
120
+ * database file. SQLite never returns freed pages to the filesystem — it
121
+ * parks them on the freelist and reuses them — so a database that once grew
122
+ * large keeps that size forever, even after retention and `maxEvents` have
123
+ * evicted almost everything. Measuring the file instead wedged the journal
124
+ * permanently: a 3.9 GB file whose live data was 243 MB failed a 512 MB
125
+ * quota on EVERY append, and the only escape (`chronicle compact`) refuses
126
+ * to run while the daemon is up — and the daemon respawns on demand. Live
127
+ * pages shrink when retention deletes rows, so the quota can recover on its
128
+ * own; allocated size cannot.
129
+ *
130
+ * The sidecars keep their on-disk size: they are transient and genuinely
131
+ * bounded by `configureByteQuota`'s half-of-budget split.
132
+ */
133
+ private aggregateLiveBytes;
134
+ /** Live (non-freelist) pages of the main database, in bytes. */
135
+ private mainDatabaseLiveBytes;
136
+ /** Page size never changes for an open database; resolve it once. */
137
+ private pageSizeBytes;
138
+ private normalizeQuotaError;
139
+ private assertActualAllocationWithinQuota;
140
+ private assertWithinByteQuota;
141
+ private enforceEventLimitWithinTransaction;
142
+ private enforceEventLimitAtStartup;
86
143
  /**
87
144
  * Drop events older than the retention window.
88
145
  *
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Compatibility export for callers that historically loaded the runtime
3
+ * capability contract from the agent catalog. The contract itself lives in
4
+ * `types/` so low-level prompt composition does not depend on coordination.
5
+ */
6
+ export * from '../../types/runtime-capability-manifest.js';
7
+ //# sourceMappingURL=capability-manifest.d.ts.map
@@ -24,13 +24,14 @@ import { META_AGENTS } from './phase9-meta.js';
24
24
  import { WAVE4_AGENTS } from './phase9-wave4-platform-meta.js';
25
25
  import { ROLE_SKILL_SETS, SHADOW_AGENT_SKILLS } from './role-skills.js';
26
26
  import type { AgentDefinition } from './types.js';
27
- export * from './role-skills.js';
28
- export * from './types.js';
27
+ export * from './capability-manifest.js';
29
28
  export * from './phase3-wave1-platform.js';
30
29
  export * from './phase3-wave2-meta.js';
31
30
  export * from './phase8-wave3-products.js';
32
31
  export * from './phase9-wave4-platform-meta.js';
33
32
  export * from './project-agent-identity.js';
33
+ export * from './role-skills.js';
34
+ export * from './types.js';
34
35
  export { BUILD_AGENTS, DELIVERY_AGENTS, DISCOVERY_AGENTS, DOMAIN_AGENTS, KNOWLEDGE_AGENTS, META_AGENTS, PLANNING_AGENTS, REVIEW_AGENTS, VERIFY_AGENTS, WAVE1_AGENTS, WAVE2_AGENTS, WAVE3_AGENTS, WAVE4_AGENTS, };
35
36
  /** Every catalog agent, in phase order, enriched with its curated skills. */
36
37
  export declare const ALL_AGENT_DEFINITIONS: AgentDefinition[];