@legioncodeinc/honeycomb 0.1.6 → 0.1.8

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/daemon/index.js CHANGED
@@ -7369,7 +7369,7 @@ var require_dist = __commonJS({
7369
7369
  // dist/src/shared/constants.js
7370
7370
  var DAEMON_PORT = 3850;
7371
7371
  var DAEMON_HOST = "127.0.0.1";
7372
- var HONEYCOMB_VERSION = true ? "0.1.6" : "0.0.0-dev";
7372
+ var HONEYCOMB_VERSION = true ? "0.1.8" : "0.0.0-dev";
7373
7373
 
7374
7374
  // node_modules/zod/v4/classic/external.js
7375
7375
  var external_exports = {};
@@ -25673,6 +25673,28 @@ var SESSIONS_COLUMNS = Object.freeze([
25673
25673
  { name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" },
25674
25674
  { name: "agent_id", sql: "TEXT NOT NULL DEFAULT 'default'" },
25675
25675
  { name: "visibility", sql: "TEXT NOT NULL DEFAULT 'global'" },
25676
+ // ── PRD-060a (a-AC-3 / a-AC-6): per-turn token + cache usage ──────────────────
25677
+ // Additive columns, healed in via the SAME `ALTER TABLE ADD COLUMN` path the rest
25678
+ // of the catalog uses (the heal engine iterates THIS array; nothing else to wire).
25679
+ //
25680
+ // ZERO vs NULL (a-AC-1 / a-AC-6, the open-question ruling): these four counts are
25681
+ // NULLABLE BIGINT with NO `DEFAULT 0`. The distinction is load-bearing — a genuine
25682
+ // `cache_read_input_tokens = 0` (a real measurement: nothing read from cache) must
25683
+ // stay DISTINCT from "no usage data" (the count was never produced). A `DEFAULT 0`
25684
+ // would collapse "absent" into "measured zero", which a-AC-6 forbids, so absent is
25685
+ // encoded as SQL NULL and a measured zero as the integer 0. Nullable columns are
25686
+ // exempt from the NOT-NULL-needs-a-DEFAULT load guard (NULL is their implicit
25687
+ // default), so `ALTER TABLE ADD COLUMN … BIGINT` heals cleanly onto a populated
25688
+ // legacy table: existing rows read back NULL = "token data absent" (a-AC-4).
25689
+ { name: "input_tokens", sql: "BIGINT" },
25690
+ { name: "output_tokens", sql: "BIGINT" },
25691
+ { name: "cache_read_input_tokens", sql: "BIGINT" },
25692
+ { name: "cache_creation_input_tokens", sql: "BIGINT" },
25693
+ // The capture-source discriminant (a-AC-7): every Claude-Code row carries
25694
+ // `source_tool = 'claude-code'`, so 060b/060e can render a "Claude Code only"
25695
+ // partial state. NOT NULL DEFAULT '' (a discriminant always present; '' = unknown
25696
+ // source) — heal-safe on a populated table because the empty string backfills.
25697
+ { name: "source_tool", sql: "TEXT NOT NULL DEFAULT ''" },
25676
25698
  { name: "creation_date", sql: "TEXT NOT NULL DEFAULT ''" },
25677
25699
  { name: "last_update_date", sql: "TEXT NOT NULL DEFAULT ''" }
25678
25700
  ]);
@@ -25939,6 +25961,52 @@ var ROUTER_HISTORY_COLUMNS = Object.freeze([
25939
25961
  { name: "workspace_id", sql: "TEXT NOT NULL DEFAULT ''" },
25940
25962
  { name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" }
25941
25963
  ]);
25964
+ var ROI_METRICS_COLUMNS = Object.freeze([
25965
+ { name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
25966
+ { name: "session_id", sql: "TEXT NOT NULL DEFAULT ''" },
25967
+ { name: "org_id", sql: "TEXT NOT NULL DEFAULT ''" },
25968
+ { name: "workspace_id", sql: "TEXT NOT NULL DEFAULT ''" },
25969
+ { name: "agent_id", sql: "TEXT NOT NULL DEFAULT 'default'" },
25970
+ { name: "project_id", sql: "TEXT NOT NULL DEFAULT ''" },
25971
+ { name: "team_id", sql: "TEXT NOT NULL DEFAULT ''" },
25972
+ // GATED: '' until a verified backend-token claim populates it (f-AC-6/f-AC-7).
25973
+ { name: "user_id", sql: "TEXT NOT NULL DEFAULT ''" },
25974
+ { name: "input_tokens", sql: "BIGINT NOT NULL DEFAULT 0" },
25975
+ { name: "output_tokens", sql: "BIGINT NOT NULL DEFAULT 0" },
25976
+ { name: "cache_read_tokens", sql: "BIGINT NOT NULL DEFAULT 0" },
25977
+ { name: "cache_creation_tokens", sql: "BIGINT NOT NULL DEFAULT 0" },
25978
+ // MEASURED / MODELED / GROSS / INFRA money — BIGINT integer cents, never FLOAT (f-AC-4).
25979
+ { name: "measured_cache_savings_cents", sql: "BIGINT NOT NULL DEFAULT 0" },
25980
+ { name: "modeled_savings_cents", sql: "BIGINT NOT NULL DEFAULT 0" },
25981
+ { name: "modeled_assumption_ref", sql: "TEXT NOT NULL DEFAULT ''" },
25982
+ { name: "gross_cost_cents", sql: "BIGINT NOT NULL DEFAULT 0" },
25983
+ { name: "infra_cost_cents", sql: "BIGINT NOT NULL DEFAULT 0" },
25984
+ // cost_basis ∈ {measured, allocated, none}; allocation_method '' unless allocated (f-AC-5).
25985
+ { name: "cost_basis", sql: "TEXT NOT NULL DEFAULT 'none'" },
25986
+ { name: "allocation_method", sql: "TEXT NOT NULL DEFAULT ''" },
25987
+ { name: "price_ref", sql: "TEXT NOT NULL DEFAULT ''" },
25988
+ { name: "period_start", sql: "TEXT NOT NULL DEFAULT ''" },
25989
+ { name: "period_end", sql: "TEXT NOT NULL DEFAULT ''" },
25990
+ { name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" }
25991
+ ]);
25992
+ var ROI_COST_BASES = Object.freeze(["measured", "allocated", "none"]);
25993
+ var TEAMS_COLUMNS = Object.freeze([
25994
+ { name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
25995
+ { name: "team_id", sql: "TEXT NOT NULL DEFAULT ''" },
25996
+ { name: "team_name", sql: "TEXT NOT NULL DEFAULT ''" },
25997
+ // 'agent' rows work today; 'user' rows inert until user_id verified.
25998
+ { name: "member_type", sql: "TEXT NOT NULL DEFAULT 'agent'" },
25999
+ { name: "member_id", sql: "TEXT NOT NULL DEFAULT ''" },
26000
+ { name: "role", sql: "TEXT NOT NULL DEFAULT 'member'" },
26001
+ { name: "active", sql: "BIGINT NOT NULL DEFAULT 1" },
26002
+ { name: "org_id", sql: "TEXT NOT NULL DEFAULT ''" },
26003
+ { name: "workspace_id", sql: "TEXT NOT NULL DEFAULT ''" },
26004
+ { name: "version", sql: "BIGINT NOT NULL DEFAULT 0" },
26005
+ { name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
26006
+ { name: "updated_at", sql: "TEXT NOT NULL DEFAULT ''" }
26007
+ ]);
26008
+ var TEAM_MEMBER_TYPES = Object.freeze(["agent", "user"]);
26009
+ var TEAM_ACTIVE = 1;
25942
26010
  var TENANCY_TABLES = defineGroup([
25943
26011
  {
25944
26012
  name: "agents",
@@ -25977,6 +26045,25 @@ var TENANCY_TABLES = defineGroup([
25977
26045
  pattern: "append-only",
25978
26046
  embeddingColumns: [],
25979
26047
  scope: "tenant"
26048
+ },
26049
+ {
26050
+ // PRD-060f (f-AC-1/f-AC-2): the shared spend ledger. APPEND-ONLY — one
26051
+ // immutable row per session, a re-price APPENDs a new row (new price_ref),
26052
+ // NEVER an in-place UPDATE; the canonical row is MAX(created_at) per session.
26053
+ name: "roi_metrics",
26054
+ columns: ROI_METRICS_COLUMNS,
26055
+ pattern: "append-only",
26056
+ embeddingColumns: [],
26057
+ scope: "tenant"
26058
+ },
26059
+ {
26060
+ // PRD-060f (f-AC-8): the roster. VERSION-BUMPED — one row per (team, member),
26061
+ // an edit APPENDs version N+1, read ORDER BY version DESC (same as api_keys).
26062
+ name: "teams",
26063
+ columns: TEAMS_COLUMNS,
26064
+ pattern: "version-bumped",
26065
+ embeddingColumns: [],
26066
+ scope: "tenant"
25980
26067
  }
25981
26068
  ]);
25982
26069
  function buildApiKeyLookupByIdSql(id) {
@@ -26904,6 +26991,36 @@ function appendOnlyInsert(client, target, scope, row) {
26904
26991
  const sql = buildInsert(target.table, row);
26905
26992
  return withHeal(client, target, scope, () => client.query(sql, scope));
26906
26993
  }
26994
+ function buildInsertMany(table, rows) {
26995
+ if (rows.length === 0) {
26996
+ throw new Error("buildInsertMany: at least one row is required");
26997
+ }
26998
+ const tbl = sqlIdent(table);
26999
+ const first = rows[0];
27000
+ const colNames = first.map(([name]) => name);
27001
+ for (const row of rows)
27002
+ assertSameColumns(colNames, row);
27003
+ const cols = colNames.map((name) => sqlIdent(name)).join(", ");
27004
+ const vals = rows.map((row) => `(${row.map(([, v]) => renderValue(v)).join(", ")})`).join(", ");
27005
+ return `INSERT INTO "${tbl}" (${cols}) VALUES ${vals}`;
27006
+ }
27007
+ function assertSameColumns(expected, row) {
27008
+ if (row.length !== expected.length) {
27009
+ throw new Error(`buildInsertMany: row column count ${row.length} != expected ${expected.length}`);
27010
+ }
27011
+ for (let i = 0; i < expected.length; i++) {
27012
+ const actual = row[i][0];
27013
+ if (actual !== expected[i]) {
27014
+ throw new Error(`buildInsertMany: row column "${actual}" != expected "${expected[i]}" at index ${i}`);
27015
+ }
27016
+ }
27017
+ }
27018
+ function appendOnlyInsertMany(client, target, scope, rows, opts) {
27019
+ if (rows.length === 0)
27020
+ return Promise.resolve(ok([], 0));
27021
+ const sql = buildInsertMany(target.table, rows);
27022
+ return withHeal(client, target, scope, () => client.query(sql, scope, opts));
27023
+ }
26907
27024
  async function readLatestVersion(client, target, scope, keyColumn, keyValue, selectColumns = "*") {
26908
27025
  const tbl = sqlIdent(target.table);
26909
27026
  const key = sqlIdent(keyColumn);
@@ -26988,6 +27105,8 @@ function resolveConfig(config2, ownerFallback) {
26988
27105
  var RESOLVE_POLLS = 8;
26989
27106
  var DISCOVER_POLLS = 8;
26990
27107
  var LEASE_CANDIDATE_TRIES = 8;
27108
+ var SOURCE_LEASE = "poll-lease";
27109
+ var SOURCE_REAPER = "poll-reaper";
26991
27110
  function defaultClock2() {
26992
27111
  return {
26993
27112
  now: () => Date.now(),
@@ -27164,7 +27283,7 @@ var DeepLakeJobQueueService = class {
27164
27283
  ]);
27165
27284
  if (!wrote)
27166
27285
  return null;
27167
- const current = await this.resolveCurrent(candidate.id);
27286
+ const current = await this.resolveCurrent(candidate.id, SOURCE_LEASE);
27168
27287
  if (current !== null && current.leaseOwner === owner && current.status === JOB_LEASED) {
27169
27288
  return {
27170
27289
  id: candidate.id,
@@ -27189,7 +27308,7 @@ var DeepLakeJobQueueService = class {
27189
27308
  */
27190
27309
  async selectLeasable(exclude, kinds) {
27191
27310
  const nowIso10 = this.nowIso();
27192
- const states = await this.discoverIds();
27311
+ const states = await this.discoverIds(SOURCE_LEASE);
27193
27312
  const leasable = states.filter((s) => !exclude.has(s.id) && (kinds === void 0 || kinds.includes(s.type)) && (s.status === JOB_QUEUED || s.status === JOB_FAILED) && s.nextRunAt !== "" && s.nextRunAt <= nowIso10);
27194
27313
  if (leasable.length === 0)
27195
27314
  return null;
@@ -27204,12 +27323,12 @@ var DeepLakeJobQueueService = class {
27204
27323
  * growing. Then resolves each id's current state via {@link resolveCurrent}. The
27205
27324
  * `__ensure__` sentinel row (see {@link ensureTable}) is filtered out.
27206
27325
  */
27207
- async discoverIds() {
27326
+ async discoverIds(source) {
27208
27327
  const ids = /* @__PURE__ */ new Set();
27209
27328
  let lastSize = -1;
27210
27329
  for (let poll = 0; poll < DISCOVER_POLLS; poll++) {
27211
27330
  const sql = `SELECT DISTINCT ${sqlIdent("id")} FROM "${this.tbl()}"`;
27212
- const res = await this.storage.query(sql, this.scope);
27331
+ const res = await this.storage.query(sql, this.scope, source !== void 0 ? { source } : {});
27213
27332
  if (isOk(res)) {
27214
27333
  for (const row of res.rows) {
27215
27334
  const id = rowText(row, "id");
@@ -27223,7 +27342,7 @@ var DeepLakeJobQueueService = class {
27223
27342
  }
27224
27343
  const states = [];
27225
27344
  for (const id of ids) {
27226
- const state = await this.resolveCurrent(id);
27345
+ const state = await this.resolveCurrent(id, source);
27227
27346
  if (state !== null)
27228
27347
  states.push(state);
27229
27348
  }
@@ -27239,11 +27358,11 @@ var DeepLakeJobQueueService = class {
27239
27358
  * when the id has no row at all. Short-circuits once a max-version row is seen
27240
27359
  * twice (the fake is decisive on the first read).
27241
27360
  */
27242
- async resolveCurrent(id) {
27361
+ async resolveCurrent(id, source) {
27243
27362
  let best = null;
27244
27363
  let seenBestTwice = false;
27245
27364
  for (let poll = 0; poll < RESOLVE_POLLS; poll++) {
27246
- const row = await this.latestById(id);
27365
+ const row = await this.latestById(id, source);
27247
27366
  if (row !== null) {
27248
27367
  const state = toJobState(row);
27249
27368
  if (best === null || state.version > best.version) {
@@ -27259,10 +27378,10 @@ var DeepLakeJobQueueService = class {
27259
27378
  return best;
27260
27379
  }
27261
27380
  /** One highest-version by-id read of the full job row, or `null` when absent. */
27262
- async latestById(id) {
27381
+ async latestById(id, source) {
27263
27382
  const cols = STATE_COLUMNS.map((c) => sqlIdent(c)).join(", ");
27264
27383
  const sql = `SELECT ${cols} FROM "${this.tbl()}" WHERE ${sqlIdent("id")} = ${sLiteral(id)} ORDER BY ${sqlIdent("version")} DESC LIMIT 1`;
27265
- const res = await this.storage.query(sql, this.scope);
27384
+ const res = await this.storage.query(sql, this.scope, source !== void 0 ? { source } : {});
27266
27385
  if (isOk(res) && res.rows.length > 0)
27267
27386
  return res.rows[0];
27268
27387
  return null;
@@ -27362,7 +27481,7 @@ var DeepLakeJobQueueService = class {
27362
27481
  */
27363
27482
  async reapExpiredLeases() {
27364
27483
  const nowIso10 = this.nowIso();
27365
- const states = await this.discoverIds();
27484
+ const states = await this.discoverIds(SOURCE_REAPER);
27366
27485
  const expired = states.filter((s) => s.status === JOB_LEASED && s.leaseExpiresAt !== "" && s.leaseExpiresAt <= nowIso10);
27367
27486
  if (expired.length === 0)
27368
27487
  return 0;
@@ -28464,7 +28583,7 @@ function buildAllowedProperties(input) {
28464
28583
  }
28465
28584
  var systemTelemetryClock = () => (/* @__PURE__ */ new Date()).toISOString();
28466
28585
  var DEFAULT_EMIT_TIMEOUT_MS = 2e3;
28467
- var HONEYCOMB_VERSION2 = true ? "0.1.6" : "0.0.0-dev";
28586
+ var HONEYCOMB_VERSION2 = true ? "0.1.8" : "0.0.0-dev";
28468
28587
  async function emitTelemetry(event, opts, deps = {}) {
28469
28588
  const env = deps.env ?? process.env;
28470
28589
  const key = deps.posthogKey ?? POSTHOG_KEY;
@@ -31039,6 +31158,17 @@ function createSummaryJobWorker(deps) {
31039
31158
 
31040
31159
  // dist/src/daemon/runtime/capture/event-contract.js
31041
31160
  var nonEmpty = external_exports.string().trim().min(1);
31161
+ var tokenCount = external_exports.number().int().nonnegative();
31162
+ var TurnUsageSchema = external_exports.object({
31163
+ /** `input_tokens` — prompt tokens billed for this turn. */
31164
+ input: tokenCount.optional(),
31165
+ /** `output_tokens` — completion tokens billed for this turn. */
31166
+ output: tokenCount.optional(),
31167
+ /** `cache_read_input_tokens` — tokens served from the prompt cache (a real 0 ≠ absent). */
31168
+ cacheRead: tokenCount.optional(),
31169
+ /** `cache_creation_input_tokens` — tokens written into the prompt cache. */
31170
+ cacheCreation: tokenCount.optional()
31171
+ }).optional().transform((u) => u !== void 0 && (u.input !== void 0 || u.output !== void 0 || u.cacheRead !== void 0 || u.cacheCreation !== void 0) ? u : void 0);
31042
31172
  var UserMessageEventSchema = external_exports.object({
31043
31173
  kind: external_exports.literal("user_message"),
31044
31174
  text: external_exports.string()
@@ -31051,7 +31181,9 @@ var ToolCallEventSchema = external_exports.object({
31051
31181
  });
31052
31182
  var AssistantMessageEventSchema = external_exports.object({
31053
31183
  kind: external_exports.literal("assistant_message"),
31054
- text: external_exports.string()
31184
+ text: external_exports.string(),
31185
+ /** PRD-060a: optional per-turn token + cache counts; absent when unavailable. */
31186
+ usage: TurnUsageSchema
31055
31187
  });
31056
31188
  var CaptureEventSchema = external_exports.discriminatedUnion("kind", [
31057
31189
  UserMessageEventSchema,
@@ -31094,15 +31226,204 @@ function parseCaptureRequest(body) {
31094
31226
  return { ok: false, error: issues };
31095
31227
  }
31096
31228
 
31229
+ // dist/src/daemon/runtime/capture/capture-buffer.js
31230
+ var realBufferClock = {
31231
+ now: () => Date.now(),
31232
+ setTimer: (fn, ms) => {
31233
+ const t = setTimeout(fn, ms);
31234
+ if (typeof t === "object" && t !== null && "unref" in t && typeof t.unref === "function")
31235
+ t.unref();
31236
+ return t;
31237
+ },
31238
+ clearTimer: (handle) => clearTimeout(handle)
31239
+ };
31240
+ var DEFAULT_MAX_EVENTS = 25;
31241
+ var DEFAULT_WINDOW_MS = 1e3;
31242
+ var CaptureBuffer = class {
31243
+ maxEvents;
31244
+ windowMs;
31245
+ clock;
31246
+ flushFn;
31247
+ /** The current window's buffered items. */
31248
+ items = [];
31249
+ /** The pending time-flush timer, or null when the window is empty. */
31250
+ timer = null;
31251
+ /** The in-flight flush, so a second trigger awaits it rather than racing it. */
31252
+ inFlight = Promise.resolve();
31253
+ /** Set on `close()` so a late `add` after shutdown is rejected, not silently buffered-then-lost. */
31254
+ closed = false;
31255
+ constructor(flushFn, config2 = {}, clock = realBufferClock) {
31256
+ this.flushFn = flushFn;
31257
+ this.maxEvents = Math.max(1, config2.maxEvents ?? DEFAULT_MAX_EVENTS);
31258
+ this.windowMs = Math.max(1, config2.windowMs ?? DEFAULT_WINDOW_MS);
31259
+ this.clock = clock;
31260
+ }
31261
+ /** Number of items currently buffered (for assertions / diagnostics). */
31262
+ get size() {
31263
+ return this.items.length;
31264
+ }
31265
+ /**
31266
+ * Buffer one item. Starts the time window on the first item of an empty buffer,
31267
+ * and triggers an immediate flush when the size cap is reached. Returns a promise
31268
+ * that resolves when THIS item has been flushed (either by the size cap it just
31269
+ * tripped, or by a later time/force flush), so a caller that needs the durable
31270
+ * write before responding can await it; a caller that wants fire-and-forget
31271
+ * batching simply does not await.
31272
+ */
31273
+ add(item) {
31274
+ if (this.closed) {
31275
+ return Promise.reject(new Error("CaptureBuffer.add after close"));
31276
+ }
31277
+ this.items.push(item);
31278
+ if (this.items.length === 1)
31279
+ this.startTimer();
31280
+ if (this.items.length >= this.maxEvents)
31281
+ return this.flushNow();
31282
+ return this.inFlight;
31283
+ }
31284
+ /**
31285
+ * Force a flush of the current window NOW (window close / shutdown drain). Cancels
31286
+ * the pending timer, swaps out the buffered batch, and appends it as one multi-row
31287
+ * write. Awaits any in-flight flush first so two flushes never overlap. A no-op
31288
+ * (resolved) when the buffer is empty.
31289
+ */
31290
+ flushNow() {
31291
+ this.cancelTimer();
31292
+ if (this.items.length === 0)
31293
+ return this.inFlight;
31294
+ const batch = this.items;
31295
+ this.items = [];
31296
+ this.inFlight = this.inFlight.then(() => this.flushFn(batch));
31297
+ return this.inFlight;
31298
+ }
31299
+ /**
31300
+ * Drain + close (graceful shutdown, AC-5 / AC-62c.1.2). Flushes the remaining
31301
+ * window so nothing buffered is lost on a clean stop, then marks the buffer closed
31302
+ * so a late `add` is rejected rather than buffered into a buffer that will never
31303
+ * flush again. Idempotent.
31304
+ */
31305
+ async close() {
31306
+ if (this.closed) {
31307
+ await this.inFlight;
31308
+ return;
31309
+ }
31310
+ const drained = this.flushNow();
31311
+ this.closed = true;
31312
+ await drained;
31313
+ }
31314
+ /** Start the time-flush timer for the current window (called on the first buffered item). */
31315
+ startTimer() {
31316
+ this.cancelTimer();
31317
+ this.timer = this.clock.setTimer(() => {
31318
+ this.timer = null;
31319
+ void this.flushNow();
31320
+ }, this.windowMs);
31321
+ }
31322
+ /** Cancel + clear the pending time-flush timer, if any. */
31323
+ cancelTimer() {
31324
+ if (this.timer !== null) {
31325
+ this.clock.clearTimer(this.timer);
31326
+ this.timer = null;
31327
+ }
31328
+ }
31329
+ };
31330
+
31331
+ // dist/src/daemon/runtime/capture/capture-config.js
31332
+ var DEFAULT_CAPTURE_WINDOW_MS = 1e3;
31333
+ var DEFAULT_CAPTURE_MAX_EVENTS = 25;
31334
+ var DEFAULT_CAPTURE_ENVELOPE_BUDGET_BYTES = 16384;
31335
+ var BoolFlag = external_exports.preprocess((raw2) => {
31336
+ if (typeof raw2 === "boolean")
31337
+ return raw2;
31338
+ return raw2 === "true" || raw2 === "1";
31339
+ }, external_exports.boolean());
31340
+ function ClampedInt(def, min = 0) {
31341
+ return external_exports.preprocess((raw2) => {
31342
+ const n = typeof raw2 === "number" ? raw2 : Number(raw2);
31343
+ if (!Number.isFinite(n))
31344
+ return def;
31345
+ return Math.max(min, Math.trunc(n));
31346
+ }, external_exports.number().int());
31347
+ }
31348
+ var CaptureConfigSchema = external_exports.object({
31349
+ /** Master switch for write batching; DEFAULT-ON (L-X1). Off ⇒ one INSERT per event. */
31350
+ batch: BoolFlag.default(true),
31351
+ /** Time-flush window in ms (AC-5). */
31352
+ windowMs: ClampedInt(DEFAULT_CAPTURE_WINDOW_MS, 1).default(DEFAULT_CAPTURE_WINDOW_MS),
31353
+ /** Size-flush cap in events (AC-5). */
31354
+ maxEvents: ClampedInt(DEFAULT_CAPTURE_MAX_EVENTS, 1).default(DEFAULT_CAPTURE_MAX_EVENTS),
31355
+ /** Per-field tool-I/O byte budget; `0` disables trimming (full envelope, pre-062c). */
31356
+ envelopeBudgetBytes: ClampedInt(DEFAULT_CAPTURE_ENVELOPE_BUDGET_BYTES, 0).default(DEFAULT_CAPTURE_ENVELOPE_BUDGET_BYTES)
31357
+ });
31358
+ var CAPTURE_ENV_KEYS = {
31359
+ batch: "HONEYCOMB_CAPTURE_BATCH",
31360
+ windowMs: "HONEYCOMB_CAPTURE_WINDOW_MS",
31361
+ maxEvents: "HONEYCOMB_CAPTURE_MAX_EVENTS",
31362
+ envelopeBudgetBytes: "HONEYCOMB_CAPTURE_ENVELOPE_BUDGET_BYTES"
31363
+ };
31364
+ function resolveCaptureConfig(env = process.env) {
31365
+ const parsed = CaptureConfigSchema.safeParse({
31366
+ batch: env[CAPTURE_ENV_KEYS.batch],
31367
+ windowMs: env[CAPTURE_ENV_KEYS.windowMs],
31368
+ maxEvents: env[CAPTURE_ENV_KEYS.maxEvents],
31369
+ envelopeBudgetBytes: env[CAPTURE_ENV_KEYS.envelopeBudgetBytes]
31370
+ });
31371
+ return parsed.success ? parsed.data : CaptureConfigSchema.parse({});
31372
+ }
31373
+
31374
+ // dist/src/daemon/runtime/capture/budgeted-stringify.js
31375
+ var DEFAULT_ENVELOPE_BUDGET_BYTES = 16384;
31376
+ function truncationMarker(originalBytes) {
31377
+ return `\u2026[truncated ${originalBytes} bytes]`;
31378
+ }
31379
+ function byteLength(value) {
31380
+ return Buffer.byteLength(value, "utf8");
31381
+ }
31382
+ function capToolField(value, budgetBytes) {
31383
+ if (value === void 0)
31384
+ return void 0;
31385
+ let serialized;
31386
+ try {
31387
+ serialized = JSON.stringify(value);
31388
+ } catch {
31389
+ return void 0;
31390
+ }
31391
+ if (serialized === void 0)
31392
+ return void 0;
31393
+ if (byteLength(serialized) <= budgetBytes)
31394
+ return value;
31395
+ return truncationMarker(byteLength(serialized));
31396
+ }
31397
+ function trimEvent(event, budgetBytes) {
31398
+ if (event.kind !== "tool_call")
31399
+ return event;
31400
+ const cappedInput = capToolField(event.input, budgetBytes);
31401
+ const cappedResponse = capToolField(event.response, budgetBytes);
31402
+ if (cappedInput === event.input && cappedResponse === event.response)
31403
+ return event;
31404
+ return {
31405
+ kind: "tool_call",
31406
+ tool: event.tool,
31407
+ ...cappedInput !== void 0 ? { input: cappedInput } : {},
31408
+ ...cappedResponse !== void 0 ? { response: cappedResponse } : {}
31409
+ };
31410
+ }
31411
+ function budgetedStringify(event, metadata, budgetBytes = DEFAULT_ENVELOPE_BUDGET_BYTES) {
31412
+ const trimmedEvent = trimEvent(event, budgetBytes);
31413
+ return JSON.stringify({ event: trimmedEvent, metadata });
31414
+ }
31415
+
31097
31416
  // dist/src/daemon/runtime/capture/capture-handler.js
31098
31417
  var HOOKS_GROUP = "/api/hooks";
31418
+ var CAPTURE_WRITE_SOURCE = "capture-write";
31099
31419
  var CAPTURE_PATH = "/capture";
31100
31420
  var CONVERSATION_PATH = "/conversation";
31101
31421
  function createCaptureHandler(deps) {
31102
31422
  const counters = deps.counters ?? new TurnCounters(deps.counterConfig);
31103
31423
  const embed = deps.embed ?? noopEmbedAttachment;
31104
31424
  const now = deps.now ?? (() => Date.now());
31105
- const handler = new CaptureRouteHandler(deps, counters, embed, now);
31425
+ const config2 = deps.captureConfig ?? resolveCaptureConfig();
31426
+ const handler = new CaptureRouteHandler(deps, counters, embed, now, config2);
31106
31427
  return {
31107
31428
  register(daemon) {
31108
31429
  const group = daemon.group(HOOKS_GROUP);
@@ -31112,7 +31433,8 @@ function createCaptureHandler(deps) {
31112
31433
  group.post(CAPTURE_PATH, (c) => handler.handleCapture(c));
31113
31434
  group.get(CONVERSATION_PATH, (c) => handler.handleConversation(c));
31114
31435
  },
31115
- counters
31436
+ counters,
31437
+ flush: () => handler.flush()
31116
31438
  };
31117
31439
  }
31118
31440
  var CaptureRouteHandler = class {
@@ -31120,11 +31442,19 @@ var CaptureRouteHandler = class {
31120
31442
  counters;
31121
31443
  embed;
31122
31444
  now;
31123
- constructor(deps, counters, embed, now) {
31445
+ config;
31446
+ /**
31447
+ * PRD-062c (L-C1): the capture write buffer. NULL when batching is off — the handler
31448
+ * then does one append-only INSERT per event (the pre-062c path). Lazily created on the
31449
+ * first buffered write so a flag-off handler allocates nothing.
31450
+ */
31451
+ buffer = null;
31452
+ constructor(deps, counters, embed, now, config2) {
31124
31453
  this.deps = deps;
31125
31454
  this.counters = counters;
31126
31455
  this.embed = embed;
31127
31456
  this.now = now;
31457
+ this.config = config2;
31128
31458
  }
31129
31459
  /**
31130
31460
  * POST /api/hooks/capture (FR-1..5, FR-7..9 / a-AC-1..5).
@@ -31153,10 +31483,16 @@ var CaptureRouteHandler = class {
31153
31483
  const nowIso10 = new Date(this.now()).toISOString();
31154
31484
  const projectId = this.resolveCaptureProjectId(metadata);
31155
31485
  const row = this.buildRow(id, event, metadata, nowIso10, projectId);
31156
- const result = await appendOnlyInsert(this.deps.storage, this.deps.sessionsTarget, scope, row);
31157
- if (!isOk(result)) {
31158
- this.deps.logger?.event("capture.insert.failed", { id, kind: result.kind });
31159
- return c.json({ error: "capture_failed", reason: "could not write the session row" }, 502);
31486
+ if (this.config.batch) {
31487
+ this.bufferRow(id, row, scope);
31488
+ } else {
31489
+ const result = await appendOnlyInsertMany(this.deps.storage, this.deps.sessionsTarget, scope, [row], {
31490
+ source: CAPTURE_WRITE_SOURCE
31491
+ });
31492
+ if (!isOk(result)) {
31493
+ this.deps.logger?.event("capture.insert.failed", { id, kind: result.kind });
31494
+ return c.json({ error: "capture_failed", reason: "could not write the session row" }, 502);
31495
+ }
31160
31496
  }
31161
31497
  const cues = this.bumpCounters(metadata);
31162
31498
  await this.enqueueCues(cues);
@@ -31189,6 +31525,67 @@ var CaptureRouteHandler = class {
31189
31525
  }
31190
31526
  return c.json({ path: path4.trim(), rows: result.rows });
31191
31527
  }
31528
+ /**
31529
+ * Buffer one built row for batched flushing (PRD-062c L-C1). Lazily creates the
31530
+ * buffer on first use (a flag-off handler never allocates one). The per-item flush
31531
+ * promise the buffer returns is observed fire-and-forget: a flush failure is logged
31532
+ * here (never swallowed) but does NOT fail the captured turn — the row is committed
31533
+ * to the in-memory window, which the buffer GUARANTEES to flush (window / size /
31534
+ * shutdown). This is the documented trade: worst-case loss is one window on a hard
31535
+ * crash (see {@link CaptureBuffer}); a graceful stop drains the buffer.
31536
+ */
31537
+ bufferRow(id, row, scope) {
31538
+ const buffer = this.ensureBuffer();
31539
+ void buffer.add({ row, scope }).catch((err) => {
31540
+ this.deps.logger?.event("capture.flush.failed", {
31541
+ id,
31542
+ reason: err instanceof Error ? err.message : String(err)
31543
+ });
31544
+ });
31545
+ }
31546
+ /** Lazily build the capture write buffer from the resolved config + the flush callback. */
31547
+ ensureBuffer() {
31548
+ if (this.buffer === null) {
31549
+ const cfg = { maxEvents: this.config.maxEvents, windowMs: this.config.windowMs };
31550
+ this.buffer = this.deps.bufferClock !== void 0 ? new CaptureBuffer((batch) => this.flushBatch(batch), cfg, this.deps.bufferClock) : new CaptureBuffer((batch) => this.flushBatch(batch), cfg);
31551
+ }
31552
+ return this.buffer;
31553
+ }
31554
+ /**
31555
+ * Flush a buffered batch as multi-row append(s) (PRD-062c L-C1 / AC-5). Rows that
31556
+ * share a tenancy scope are grouped and written with ONE `appendOnlyInsertMany`, so N
31557
+ * within-window same-scope events become ONE DeepLake write (AC-62c.1.1). Different
31558
+ * scopes (the rare cross-tenant interleave within a window) each get their own append —
31559
+ * a multi-row INSERT cannot span partitions. Every append threads the `capture-write`
31560
+ * 062a meter source. A failed append rejects so the awaiter ({@link bufferRow}) logs it.
31561
+ */
31562
+ async flushBatch(batch) {
31563
+ for (const [scope, rows] of groupRowsByScope(batch)) {
31564
+ const result = await appendOnlyInsertMany(this.deps.storage, this.deps.sessionsTarget, scope, rows, {
31565
+ source: CAPTURE_WRITE_SOURCE
31566
+ });
31567
+ if (!isOk(result)) {
31568
+ this.deps.logger?.event("capture.batch_insert.failed", { count: rows.length, kind: result.kind });
31569
+ throw new Error(`capture batch append failed: ${result.kind}`);
31570
+ }
31571
+ }
31572
+ }
31573
+ /**
31574
+ * Force-flush + close the buffer on shutdown (PRD-062c AC-5 / AC-62c.1.2). Drains the
31575
+ * remaining window so nothing buffered is lost on a clean stop. A no-op when batching is
31576
+ * off (no buffer). Never throws — a flush failure on shutdown is logged, not surfaced.
31577
+ */
31578
+ async flush() {
31579
+ if (this.buffer === null)
31580
+ return;
31581
+ try {
31582
+ await this.buffer.close();
31583
+ } catch (err) {
31584
+ this.deps.logger?.event("capture.flush.failed", {
31585
+ reason: err instanceof Error ? err.message : String(err)
31586
+ });
31587
+ }
31588
+ }
31192
31589
  /**
31193
31590
  * Build the single `sessions` row's ordered column values (FR-4 / FR-5).
31194
31591
  *
@@ -31205,8 +31602,8 @@ var CaptureRouteHandler = class {
31205
31602
  * org/workspace scope the partition.
31206
31603
  */
31207
31604
  buildRow(id, event, meta3, nowIso10, projectId) {
31208
- const message = JSON.stringify({ event, metadata: meta3 });
31209
- return [
31605
+ const message = this.config.envelopeBudgetBytes > 0 ? budgetedStringify(event, meta3, this.config.envelopeBudgetBytes) : JSON.stringify({ event, metadata: meta3 });
31606
+ const row = [
31210
31607
  ["id", val.str(id)],
31211
31608
  ["path", val.str(meta3.path)],
31212
31609
  ["filename", val.str(meta3.hookEventName)],
@@ -31220,9 +31617,17 @@ var CaptureRouteHandler = class {
31220
31617
  ["project_id", val.str(projectId)],
31221
31618
  ["plugin_version", val.str(meta3.pluginVersion)],
31222
31619
  ["agent_id", val.str(meta3.agentId)],
31620
+ // PRD-060a (a-AC-7): the capture-source discriminant. `metadata.agent` is the
31621
+ // canonical harness token the shim stamps (`claude-code` for the reference
31622
+ // shim), so every Claude-Code-captured row carries `source_tool='claude-code'`.
31623
+ ["source_tool", val.str(meta3.agent)],
31223
31624
  ["creation_date", val.str(nowIso10)],
31224
31625
  ["last_update_date", val.str(nowIso10)]
31225
31626
  ];
31627
+ for (const [col, value] of usageColumns(event)) {
31628
+ row.push([col, val.num(value)]);
31629
+ }
31630
+ return row;
31226
31631
  }
31227
31632
  /**
31228
31633
  * Resolve the capture row's `project_id` from the session cwd (PRD-049b 49b-AC-1 / 49b-AC-3).
@@ -31365,6 +31770,36 @@ function embedTextFor(event) {
31365
31770
  return [event.tool, serialize(event.input), serialize(event.response)].filter((s) => s.length > 0).join("\n");
31366
31771
  }
31367
31772
  }
31773
+ function usageColumns(event) {
31774
+ if (event.kind !== "assistant_message" || event.usage === void 0)
31775
+ return [];
31776
+ const u = event.usage;
31777
+ const cols = [];
31778
+ if (u.input !== void 0)
31779
+ cols.push(["input_tokens", u.input]);
31780
+ if (u.output !== void 0)
31781
+ cols.push(["output_tokens", u.output]);
31782
+ if (u.cacheRead !== void 0)
31783
+ cols.push(["cache_read_input_tokens", u.cacheRead]);
31784
+ if (u.cacheCreation !== void 0)
31785
+ cols.push(["cache_creation_input_tokens", u.cacheCreation]);
31786
+ return cols;
31787
+ }
31788
+ function groupRowsByScope(batch) {
31789
+ const byKey = /* @__PURE__ */ new Map();
31790
+ for (const item of batch) {
31791
+ const key = JSON.stringify([item.scope.org, item.scope.workspace ?? ""]);
31792
+ const bucket = byKey.get(key);
31793
+ if (bucket === void 0)
31794
+ byKey.set(key, { scope: item.scope, rows: [item.row] });
31795
+ else
31796
+ bucket.rows.push(item.row);
31797
+ }
31798
+ const out = /* @__PURE__ */ new Map();
31799
+ for (const { scope, rows } of byKey.values())
31800
+ out.set(scope, rows);
31801
+ return out;
31802
+ }
31368
31803
  function serialize(value) {
31369
31804
  if (value === void 0 || value === null)
31370
31805
  return "";
@@ -31389,7 +31824,9 @@ function attachHooksHandlers(daemon, options) {
31389
31824
  ...options.enqueuePipelineEntry !== void 0 ? { enqueuePipelineEntry: options.enqueuePipelineEntry } : {},
31390
31825
  ...options.firstRunGate !== void 0 ? { firstRunGate: options.firstRunGate } : {},
31391
31826
  ...options.projectsDir !== void 0 ? { projectsDir: options.projectsDir } : {},
31392
- ...options.logger !== void 0 ? { logger: options.logger } : {}
31827
+ ...options.logger !== void 0 ? { logger: options.logger } : {},
31828
+ ...options.captureConfig !== void 0 ? { captureConfig: options.captureConfig } : {},
31829
+ ...options.bufferClock !== void 0 ? { bufferClock: options.bufferClock } : {}
31393
31830
  });
31394
31831
  handler.register(daemon);
31395
31832
  const group = daemon.group(HOOKS_GROUP);
@@ -32426,9 +32863,9 @@ async function loadGrammar(language, tsx) {
32426
32863
  const base = GRAMMAR_WASM[language];
32427
32864
  const wasm = language === "typescript" && tsx ? "tree-sitter-tsx" : base;
32428
32865
  const key = wasm;
32429
- const cached2 = grammarCache.get(key);
32430
- if (cached2)
32431
- return cached2;
32866
+ const cached3 = grammarCache.get(key);
32867
+ if (cached3)
32868
+ return cached3;
32432
32869
  const promise2 = (async () => {
32433
32870
  const Parser = await parserModule();
32434
32871
  return Parser.Language.load(`${grammarDir()}${wasm}.wasm`);
@@ -34945,6 +35382,37 @@ function mountSkillPropagationApi(daemon, options) {
34945
35382
  }
34946
35383
  }
34947
35384
 
35385
+ // dist/src/dashboard/contracts.js
35386
+ var EMPTY_DASHBOARD_DATA = Object.freeze({
35387
+ kpis: { memoryCount: 0, sessionCount: 0, turnCount: 0, estimatedSavings: 0, teamSkillCount: 0 },
35388
+ sessions: { sessions: [] },
35389
+ settings: { orgId: "", orgName: "", workspace: "", settings: {} },
35390
+ graph: { built: false, nodes: [], edges: [] },
35391
+ rules: { rules: [] },
35392
+ skillSync: { skills: [] }
35393
+ });
35394
+ var EMPTY_ROI_VIEW = Object.freeze({
35395
+ savings: {
35396
+ status: "absent",
35397
+ measuredCents: 0,
35398
+ modeledCents: 0,
35399
+ assumption: { kind: "", assumptionText: "", signedOff: false },
35400
+ blendedCentsPerMtok: null
35401
+ },
35402
+ infra: { status: "absent", cents: 0, costBasis: "none" },
35403
+ pollination: { status: "absent", cents: 0, lines: [] },
35404
+ net: { status: "absent", computed: false, netCents: 0, modeled: true, costBasis: "none" },
35405
+ rollups: [],
35406
+ perUserAvailable: false,
35407
+ scopedAcrossDevices: false,
35408
+ ratesAsOf: ""
35409
+ });
35410
+ var EMPTY_ROI_TREND = Object.freeze({
35411
+ status: "absent",
35412
+ series: [],
35413
+ startedAt: ""
35414
+ });
35415
+
34948
35416
  // dist/src/daemon/runtime/dashboard/installed-assets.js
34949
35417
  import { homedir as homedir11 } from "node:os";
34950
35418
  import { join as join16 } from "node:path";
@@ -35103,6 +35571,548 @@ function sanitizeName(name) {
35103
35571
  return name.replace(/[^A-Za-z0-9._-]/g, "_");
35104
35572
  }
35105
35573
 
35574
+ // dist/src/daemon/runtime/dashboard/roi-rates.js
35575
+ var ANTHROPIC_CACHE_READ_MULTIPLIER = 0.1;
35576
+ var ANTHROPIC_CACHE_WRITE_MULTIPLIER = 1.25;
35577
+ function anthropicCacheRate(inputCentsPerMtok, multiplier) {
35578
+ return Math.round(inputCentsPerMtok * multiplier);
35579
+ }
35580
+ var RATES_AS_OF = "2026-06-26";
35581
+ var RATE_TABLE = Object.freeze([
35582
+ Object.freeze({
35583
+ provider: "anthropic",
35584
+ model: "claude-sonnet-4-6",
35585
+ input_cents_per_mtok: 300,
35586
+ output_cents_per_mtok: 1500,
35587
+ cache_read_cents_per_mtok: anthropicCacheRate(300, ANTHROPIC_CACHE_READ_MULTIPLIER),
35588
+ cache_write_cents_per_mtok: anthropicCacheRate(300, ANTHROPIC_CACHE_WRITE_MULTIPLIER)
35589
+ }),
35590
+ Object.freeze({
35591
+ provider: "anthropic",
35592
+ model: "claude-opus-4-8",
35593
+ input_cents_per_mtok: 1500,
35594
+ output_cents_per_mtok: 7500,
35595
+ cache_read_cents_per_mtok: anthropicCacheRate(1500, ANTHROPIC_CACHE_READ_MULTIPLIER),
35596
+ cache_write_cents_per_mtok: anthropicCacheRate(1500, ANTHROPIC_CACHE_WRITE_MULTIPLIER)
35597
+ }),
35598
+ // Finding (haiku-rate): the skillify gate runs `claude-haiku-4-5` (roi-pollination.ts
35599
+ // SKILLIFY_HAIKU_MODEL). Without its own row, `priceHaikuTokens` fell back to the Sonnet default and
35600
+ // MIS-priced Honeycomb's own-inference cost. Anthropic Haiku per-Mtok: $1 in / $5 out; the cache
35601
+ // columns are derived from input via the SAME 0.1x / 1.25x multipliers (the invariant a test asserts).
35602
+ Object.freeze({
35603
+ provider: "anthropic",
35604
+ model: "claude-haiku-4-5",
35605
+ input_cents_per_mtok: 100,
35606
+ output_cents_per_mtok: 500,
35607
+ cache_read_cents_per_mtok: anthropicCacheRate(100, ANTHROPIC_CACHE_READ_MULTIPLIER),
35608
+ cache_write_cents_per_mtok: anthropicCacheRate(100, ANTHROPIC_CACHE_WRITE_MULTIPLIER)
35609
+ })
35610
+ ]);
35611
+ var DEFAULT_RATE_PROVIDER = "anthropic";
35612
+ var DEFAULT_RATE_MODEL = "claude-sonnet-4-6";
35613
+ function rateRowFor(provider, model) {
35614
+ return RATE_TABLE.find((r) => r.provider === provider && r.model === model);
35615
+ }
35616
+ function defaultRateRow() {
35617
+ const row = rateRowFor(DEFAULT_RATE_PROVIDER, DEFAULT_RATE_MODEL);
35618
+ if (row === void 0) {
35619
+ throw new Error("roi-rates: the default rate row must exist in RATE_TABLE");
35620
+ }
35621
+ return row;
35622
+ }
35623
+ function resolveRate(provider, model) {
35624
+ if (provider === void 0 || model === void 0 || provider.length === 0 || model.length === 0) {
35625
+ return defaultRateRow();
35626
+ }
35627
+ return rateRowFor(provider, model) ?? defaultRateRow();
35628
+ }
35629
+
35630
+ // dist/src/daemon/runtime/dashboard/roi-savings.js
35631
+ function measured(value) {
35632
+ return { tag: "measured", value };
35633
+ }
35634
+ function modeled(value, assumption) {
35635
+ return { tag: "modeled", value, assumption };
35636
+ }
35637
+ function tokensAtRate(tokens, centsPerMtok) {
35638
+ return Math.round(tokens * centsPerMtok / 1e6);
35639
+ }
35640
+ function measuredCacheSavings(turns) {
35641
+ let savingsCents = 0;
35642
+ let measuredTurns = 0;
35643
+ for (const turn of turns) {
35644
+ if (turn.cache_read_input_tokens === null)
35645
+ continue;
35646
+ measuredTurns += 1;
35647
+ const rate = resolveRate(turn.provider, turn.model);
35648
+ const deltaCentsPerMtok = rate.input_cents_per_mtok - rate.cache_read_cents_per_mtok;
35649
+ savingsCents += tokensAtRate(turn.cache_read_input_tokens, deltaCentsPerMtok);
35650
+ }
35651
+ const totalTurns = turns.length;
35652
+ const status = totalTurns === 0 || measuredTurns === 0 ? "absent" : measuredTurns < totalTurns ? "partial" : "measured";
35653
+ return measured({ savingsCents, measuredTurns, totalTurns, status });
35654
+ }
35655
+ function blendedCentsPerMtok(turns) {
35656
+ let totalCents = 0;
35657
+ let totalTokens = 0;
35658
+ for (const turn of turns) {
35659
+ const rate = resolveRate(turn.provider, turn.model);
35660
+ if (turn.input_tokens !== null) {
35661
+ totalCents += tokensAtRate(turn.input_tokens, rate.input_cents_per_mtok);
35662
+ totalTokens += turn.input_tokens;
35663
+ }
35664
+ if (turn.output_tokens !== null) {
35665
+ totalCents += tokensAtRate(turn.output_tokens, rate.output_cents_per_mtok);
35666
+ totalTokens += turn.output_tokens;
35667
+ }
35668
+ if (turn.cache_read_input_tokens !== null) {
35669
+ totalCents += tokensAtRate(turn.cache_read_input_tokens, rate.cache_read_cents_per_mtok);
35670
+ totalTokens += turn.cache_read_input_tokens;
35671
+ }
35672
+ if (turn.cache_creation_input_tokens !== null) {
35673
+ totalCents += tokensAtRate(turn.cache_creation_input_tokens, rate.cache_write_cents_per_mtok);
35674
+ totalTokens += turn.cache_creation_input_tokens;
35675
+ }
35676
+ }
35677
+ if (totalTokens === 0)
35678
+ return null;
35679
+ return Math.round(totalCents / totalTokens * 1e6);
35680
+ }
35681
+ var MEMORY_INJECTION_ASSUMPTION = Object.freeze({
35682
+ kind: "turns-saved-per-session",
35683
+ turnsSavedPerSession: 2,
35684
+ avgTokensPerSavedTurn: 4e3,
35685
+ includesOutputTokens: false,
35686
+ signedOff: false,
35687
+ assumptionText: "PLACEHOLDER (pending operator sign-off): estimates that injecting memory saves ~2 turns of re-explaining per session, at ~4,000 input tokens per saved turn, priced at the session's input rate. Output-token savings are NOT yet claimed. This is a model, not a billed figure."
35688
+ });
35689
+ function modeledMemoryInjectionSavings(sessions, rate = resolveRate(void 0, void 0), assumption = MEMORY_INJECTION_ASSUMPTION) {
35690
+ const savedTokens = Math.max(0, sessions) * assumption.turnsSavedPerSession * assumption.avgTokensPerSavedTurn;
35691
+ let estimatedCents = tokensAtRate(savedTokens, rate.input_cents_per_mtok);
35692
+ if (assumption.includesOutputTokens) {
35693
+ estimatedCents += tokensAtRate(savedTokens, rate.output_cents_per_mtok);
35694
+ }
35695
+ return modeled({ estimatedCents, sessions: Math.max(0, sessions) }, assumption);
35696
+ }
35697
+
35698
+ // dist/src/daemon/runtime/dashboard/roi-billing.js
35699
+ var realSleeper3 = (ms) => new Promise((resolve6) => setTimeout(resolve6, ms));
35700
+ var systemBillingClock = {
35701
+ now() {
35702
+ return Date.now();
35703
+ }
35704
+ };
35705
+ var DEFAULT_MAX_RETRIES2 = 3;
35706
+ var DEFAULT_TIMEOUT_MS = 1e4;
35707
+ var DEFAULT_TTL_MS2 = 5 * 6e4;
35708
+ var DEEPLAKE_CLIENT_HEADER2 = "X-Deeplake-Client";
35709
+ var DEEPLAKE_ORG_HEADER2 = "X-Activeloop-Org-Id";
35710
+ var DEEPLAKE_CLIENT_VALUE2 = "honeycomb";
35711
+ var centsField = external_exports.number().int().catch(0);
35712
+ var numberField = external_exports.number().catch(0);
35713
+ var ComputeTierSchema = external_exports.object({
35714
+ tier: external_exports.string().catch(""),
35715
+ hours: numberField,
35716
+ cost_cents: centsField,
35717
+ unit_price_cents: centsField
35718
+ });
35719
+ var BillingComparisonSchema = external_exports.object({
35720
+ compute_cost_previous: centsField,
35721
+ total_cost_previous: centsField,
35722
+ delta_pct: numberField
35723
+ });
35724
+ var BillingComputeSchema = external_exports.object({
35725
+ total_cost_cents: centsField,
35726
+ total_pod_hours: numberField,
35727
+ by_tier: external_exports.array(ComputeTierSchema).catch([])
35728
+ });
35729
+ var BillingSummarySchema = external_exports.object({
35730
+ balance_cents: centsField,
35731
+ period_start: external_exports.string().catch(""),
35732
+ period_end: external_exports.string().catch(""),
35733
+ total_cost_cents: centsField,
35734
+ storage_cost_cents: centsField,
35735
+ transfer_cost_cents: centsField,
35736
+ projected_end_of_period_cents: centsField,
35737
+ compute: BillingComputeSchema,
35738
+ comparison: BillingComparisonSchema
35739
+ });
35740
+ var SESSION_TYPES = ["query", "embedding", "ingestion"];
35741
+ var SessionTypeSchema = external_exports.enum(SESSION_TYPES);
35742
+ var ComputeSessionSchema = external_exports.object({
35743
+ session_type: SessionTypeSchema,
35744
+ gpu_hours: numberField,
35745
+ gpu_units: numberField,
35746
+ price_cents_per_gpu_hour: centsField,
35747
+ // Finding (billing-zero): keep `total_cost_cents` OPTIONAL/nullish so a MISSING field (absent on the
35748
+ // wire) stays `undefined` and is distinguishable from a legitimate explicit `0` (a free session). The
35749
+ // `.catch(undefined)` keeps a present-but-malformed value from throwing while NOT collapsing it to 0
35750
+ // -- `buildSessionTypeBreakdown` derives a cost ONLY when the field is truly absent, never over an
35751
+ // explicit 0 (which would overwrite a legitimately-free session with gpu_hours x price).
35752
+ total_cost_cents: centsField.optional().nullable().catch(void 0)
35753
+ });
35754
+ var ComputeUsageSchema = external_exports.object({
35755
+ total_cost_cents: centsField,
35756
+ total_gpu_hours: numberField,
35757
+ sessions: external_exports.array(ComputeSessionSchema).catch([])
35758
+ });
35759
+ function sessionTypeTotalCents(lines) {
35760
+ return lines.reduce((sum, line) => sum + line.cost_cents, 0);
35761
+ }
35762
+ function isRetryable2(status) {
35763
+ return status === 429 || status >= 500 && status <= 599;
35764
+ }
35765
+ function billingHeaders(creds) {
35766
+ const headers = {
35767
+ Authorization: `Bearer ${creds.token}`,
35768
+ "Content-Type": "application/json",
35769
+ [DEEPLAKE_CLIENT_HEADER2]: DEEPLAKE_CLIENT_VALUE2
35770
+ };
35771
+ if (creds.orgId.length > 0)
35772
+ headers[DEEPLAKE_ORG_HEADER2] = creds.orgId;
35773
+ return headers;
35774
+ }
35775
+ function resolveBillingApiUrl(creds) {
35776
+ const base = creds.apiUrl !== void 0 && creds.apiUrl.length > 0 ? creds.apiUrl : DEFAULT_DEEPLAKE_API_URL;
35777
+ return base.replace(/\/+$/, "");
35778
+ }
35779
+ function buildSessionTypeBreakdown(usage) {
35780
+ return usage.sessions.map((s) => {
35781
+ const derived = Math.round(s.gpu_hours * s.price_cents_per_gpu_hour);
35782
+ const cost_cents = s.total_cost_cents !== void 0 && s.total_cost_cents !== null ? s.total_cost_cents : derived;
35783
+ return {
35784
+ session_type: s.session_type,
35785
+ gpu_hours: s.gpu_hours,
35786
+ price_cents_per_gpu_hour: s.price_cents_per_gpu_hour,
35787
+ cost_cents
35788
+ };
35789
+ });
35790
+ }
35791
+ function createInfraCostReadModel(options = {}) {
35792
+ const doFetch = options.fetch ?? globalThis.fetch;
35793
+ const sleep2 = options.sleep ?? realSleeper3;
35794
+ const clock = options.clock ?? systemBillingClock;
35795
+ const dir = options.dir;
35796
+ const loadCreds = options.creds ?? (() => loadDiskCredentials(dir));
35797
+ const maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES2;
35798
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
35799
+ const ttlMs = options.ttlMs ?? DEFAULT_TTL_MS2;
35800
+ let cached3;
35801
+ async function getJson(apiUrl, path4, headers) {
35802
+ for (let attempt = 0; attempt <= maxRetries; attempt += 1) {
35803
+ const controller = new AbortController();
35804
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
35805
+ try {
35806
+ const resp = await doFetch(`${apiUrl}${path4}`, { method: "GET", headers, signal: controller.signal });
35807
+ if (resp.ok) {
35808
+ return await resp.json().catch(() => null);
35809
+ }
35810
+ if (isRetryable2(resp.status) && attempt < maxRetries) {
35811
+ clearTimeout(timer);
35812
+ await sleep2(250 * 2 ** attempt);
35813
+ continue;
35814
+ }
35815
+ return null;
35816
+ } catch {
35817
+ if (attempt < maxRetries) {
35818
+ clearTimeout(timer);
35819
+ await sleep2(250 * 2 ** attempt);
35820
+ continue;
35821
+ }
35822
+ return null;
35823
+ } finally {
35824
+ clearTimeout(timer);
35825
+ }
35826
+ }
35827
+ return null;
35828
+ }
35829
+ function parseOrMissing(raw2, schema) {
35830
+ if (raw2 === null || raw2 === void 0)
35831
+ return void 0;
35832
+ const result = schema.safeParse(raw2);
35833
+ return result.success ? result.data : void 0;
35834
+ }
35835
+ async function fetchSnapshot() {
35836
+ const creds = loadCreds();
35837
+ if (creds === null || creds.token.length === 0) {
35838
+ return { status: "unauthenticated", missing: [], sessionTypes: [], fetchedAt: clock.now() };
35839
+ }
35840
+ const apiUrl = resolveBillingApiUrl(creds);
35841
+ const headers = billingHeaders(creds);
35842
+ const [summaryRaw, computeRaw] = await Promise.all([
35843
+ getJson(apiUrl, "/billing/summary", headers),
35844
+ getJson(apiUrl, "/billing/usage/compute", headers)
35845
+ ]);
35846
+ const summary = parseOrMissing(summaryRaw, BillingSummarySchema);
35847
+ const compute = parseOrMissing(computeRaw, ComputeUsageSchema);
35848
+ const missing = [];
35849
+ if (summary === void 0)
35850
+ missing.push("/billing/summary");
35851
+ if (compute === void 0)
35852
+ missing.push("/billing/usage/compute");
35853
+ const okCount = (summary !== void 0 ? 1 : 0) + (compute !== void 0 ? 1 : 0);
35854
+ const status = okCount === 0 ? "unreachable" : okCount === 2 ? "ok" : "partial";
35855
+ const sessionTypes = compute !== void 0 ? buildSessionTypeBreakdown(compute) : [];
35856
+ return {
35857
+ status,
35858
+ missing,
35859
+ ...summary !== void 0 ? { summary } : {},
35860
+ ...compute !== void 0 ? { compute } : {},
35861
+ sessionTypes,
35862
+ fetchedAt: clock.now()
35863
+ };
35864
+ }
35865
+ return {
35866
+ async read() {
35867
+ const now = clock.now();
35868
+ if (cached3 !== void 0 && now - cached3.fetchedAt < ttlMs) {
35869
+ return cached3;
35870
+ }
35871
+ const snapshot = await fetchSnapshot();
35872
+ cached3 = snapshot;
35873
+ return snapshot;
35874
+ },
35875
+ invalidate() {
35876
+ cached3 = void 0;
35877
+ }
35878
+ };
35879
+ }
35880
+
35881
+ // dist/src/daemon/runtime/dashboard/roi-pollination.js
35882
+ var SKILLIFY_PROVIDER = "anthropic";
35883
+ var SKILLIFY_HAIKU_MODEL = "claude-haiku-4-5";
35884
+ function tokensAtRate2(tokens, centsPerMtok) {
35885
+ return Math.round(tokens * centsPerMtok / 1e6);
35886
+ }
35887
+ function priceHaikuTokens(snapshot) {
35888
+ if (snapshot.perModel !== void 0 && snapshot.perModel.length > 0) {
35889
+ return snapshot.perModel.reduce((sum, b) => sum + priceBucket(b), 0);
35890
+ }
35891
+ const rate = resolveRate(SKILLIFY_PROVIDER, snapshot.model ?? SKILLIFY_HAIKU_MODEL);
35892
+ return tokensAtRate2(snapshot.inputTokens, rate.input_cents_per_mtok) + tokensAtRate2(snapshot.outputTokens, rate.output_cents_per_mtok) + tokensAtRate2(snapshot.cacheReadInputTokens, rate.cache_read_cents_per_mtok) + tokensAtRate2(snapshot.cacheCreationInputTokens, rate.cache_write_cents_per_mtok);
35893
+ }
35894
+ function priceBucket(b) {
35895
+ const rate = resolveRate(SKILLIFY_PROVIDER, b.model.length > 0 ? b.model : SKILLIFY_HAIKU_MODEL);
35896
+ return tokensAtRate2(b.inputTokens, rate.input_cents_per_mtok) + tokensAtRate2(b.outputTokens, rate.output_cents_per_mtok) + tokensAtRate2(b.cacheReadInputTokens, rate.cache_read_cents_per_mtok) + tokensAtRate2(b.cacheCreationInputTokens, rate.cache_write_cents_per_mtok);
35897
+ }
35898
+ function composeHaikuContribution(snapshot) {
35899
+ const buckets = snapshot.perModel;
35900
+ const model = buckets !== void 0 && buckets.length > 1 ? "mixed" : buckets !== void 0 && buckets.length === 1 ? buckets[0].model : snapshot.model ?? SKILLIFY_HAIKU_MODEL;
35901
+ if (snapshot.recorded === 0) {
35902
+ return {
35903
+ status: "absent",
35904
+ cents: 0,
35905
+ recorded: 0,
35906
+ inputTokens: 0,
35907
+ outputTokens: 0,
35908
+ cacheReadInputTokens: 0,
35909
+ cacheCreationInputTokens: 0,
35910
+ model
35911
+ };
35912
+ }
35913
+ return {
35914
+ status: "measured",
35915
+ cents: priceHaikuTokens(snapshot),
35916
+ recorded: snapshot.recorded,
35917
+ inputTokens: snapshot.inputTokens,
35918
+ outputTokens: snapshot.outputTokens,
35919
+ cacheReadInputTokens: snapshot.cacheReadInputTokens,
35920
+ cacheCreationInputTokens: snapshot.cacheCreationInputTokens,
35921
+ model
35922
+ };
35923
+ }
35924
+ function emptyPerType() {
35925
+ return { query: 0, embedding: 0, ingestion: 0 };
35926
+ }
35927
+ function composeDeeplakeContribution(infra) {
35928
+ const perTypeCents = emptyPerType();
35929
+ for (const line of infra.sessionTypes) {
35930
+ perTypeCents[line.session_type] += line.cost_cents;
35931
+ }
35932
+ return {
35933
+ status: infra.status,
35934
+ cents: sessionTypeTotalCents(infra.sessionTypes),
35935
+ bySessionType: infra.sessionTypes,
35936
+ perTypeCents
35937
+ };
35938
+ }
35939
+ var STATUS_SEVERITY = Object.freeze({
35940
+ unauthenticated: 0,
35941
+ // worst — no credentials, no figure.
35942
+ unreachable: 1,
35943
+ // billing could not be read.
35944
+ absent: 2,
35945
+ // Haiku has no data yet.
35946
+ partial: 3,
35947
+ // billing partially read.
35948
+ measured: 4,
35949
+ // Haiku ok (a real token figure).
35950
+ ok: 5
35951
+ // best — billing fully read.
35952
+ });
35953
+ function worstPollinationStatus(haiku, deeplake) {
35954
+ if (haiku === "measured" && deeplake === "ok")
35955
+ return "ok";
35956
+ const haikuStatus = haiku;
35957
+ const deeplakeStatus = deeplake;
35958
+ return STATUS_SEVERITY[haikuStatus] <= STATUS_SEVERITY[deeplakeStatus] ? haikuStatus : deeplakeStatus;
35959
+ }
35960
+ function composePollinationCost(usage, infra) {
35961
+ const haiku = composeHaikuContribution(usage.snapshot());
35962
+ const deeplake = composeDeeplakeContribution(infra);
35963
+ return {
35964
+ pollinationCents: haiku.cents + deeplake.cents,
35965
+ status: worstPollinationStatus(haiku.status, deeplake.status),
35966
+ haiku,
35967
+ deeplake
35968
+ };
35969
+ }
35970
+
35971
+ // dist/src/daemon/runtime/dashboard/roi-skillify-meter.js
35972
+ function emptySnapshot() {
35973
+ return {
35974
+ recorded: 0,
35975
+ inputTokens: 0,
35976
+ outputTokens: 0,
35977
+ cacheReadInputTokens: 0,
35978
+ cacheCreationInputTokens: 0
35979
+ };
35980
+ }
35981
+ function snapshotSource(snapshot) {
35982
+ return { snapshot: () => snapshot };
35983
+ }
35984
+ var emptyUsageSource = snapshotSource(emptySnapshot());
35985
+
35986
+ // dist/src/daemon/runtime/recall/scope-clause.js
35987
+ var PROJECT_ID_COLUMN = "project_id";
35988
+ var PROJECT_ID_UNSET = "";
35989
+ var CROSS_PROJECT_ADMITTED = Object.freeze(["user", "workspace"]);
35990
+ var SCOPE_READ_POLICIES = Object.freeze(["isolated", "shared", "group"]);
35991
+ function asReadPolicy(raw2) {
35992
+ return SCOPE_READ_POLICIES.includes(raw2) ? raw2 : null;
35993
+ }
35994
+ function buildProjectScopeClause(input) {
35995
+ const projectColumn = input.projectColumn ?? PROJECT_ID_COLUMN;
35996
+ const rawId = input.projectId ?? "";
35997
+ const isInbox = rawId.trim() === "" || rawId === UNSORTED_PROJECT_ID;
35998
+ const bound = input.bound ?? !isInbox;
35999
+ const col = sqlIdent(projectColumn);
36000
+ const primaryId = bound ? rawId : UNSORTED_PROJECT_ID;
36001
+ const admitted = primaryId === PROJECT_ID_UNSET ? [PROJECT_ID_UNSET] : [primaryId, PROJECT_ID_UNSET];
36002
+ const disjuncts = admitted.map((id) => `${col} = ${sLiteral(id)}`);
36003
+ const values = [...admitted];
36004
+ if (input.promotionColumn !== void 0 && input.promotionColumn !== "") {
36005
+ const promoCol = sqlIdent(input.promotionColumn);
36006
+ for (const reach of CROSS_PROJECT_ADMITTED) {
36007
+ disjuncts.push(`${promoCol} = ${sLiteral(reach)}`);
36008
+ values.push(reach);
36009
+ }
36010
+ }
36011
+ const sql = `(${disjuncts.join(" OR ")})`;
36012
+ return { sql, values, bound };
36013
+ }
36014
+ function buildProjectScopeConjunct(input) {
36015
+ return ` AND ${buildProjectScopeClause(input).sql}`;
36016
+ }
36017
+
36018
+ // dist/src/daemon/runtime/dashboard/roi-ledger.js
36019
+ var BACKEND_TOKEN_SOURCE = "backend-token";
36020
+ function resolveGatedUserId(verifiedClaim) {
36021
+ if (verifiedClaim !== void 0 && verifiedClaim.source === BACKEND_TOKEN_SOURCE) {
36022
+ return verifiedClaim.userId;
36023
+ }
36024
+ return "";
36025
+ }
36026
+ async function resolveTeamId(client, scope, agentId) {
36027
+ const tbl = sqlIdent("teams");
36028
+ const memberType = sqlIdent("member_type");
36029
+ const memberId = sqlIdent("member_id");
36030
+ const activeCol = sqlIdent("active");
36031
+ const versionCol = sqlIdent("version");
36032
+ const sql = `SELECT team_id FROM "${tbl}" AS t WHERE ${memberType} = ${sLiteral("agent")} AND ${memberId} = ${sLiteral(agentId)} AND ${activeCol} = ${TEAM_ACTIVE} AND ${versionCol} = (SELECT MAX(${versionCol}) FROM "${tbl}" AS u WHERE u.${memberType} = ${sLiteral("agent")} AND u.${memberId} = ${sLiteral(agentId)}) LIMIT 1`;
36033
+ let res;
36034
+ try {
36035
+ res = await client.query(sql, scope);
36036
+ } catch {
36037
+ return "";
36038
+ }
36039
+ if (!isOk(res) || res.rows.length === 0)
36040
+ return "";
36041
+ const raw2 = res.rows[0].team_id;
36042
+ return typeof raw2 === "string" ? raw2 : "";
36043
+ }
36044
+ function toInt(n) {
36045
+ if (n === void 0 || !Number.isFinite(n))
36046
+ return 0;
36047
+ return Math.trunc(n);
36048
+ }
36049
+ function asCostBasis(raw2) {
36050
+ return raw2 !== void 0 && ROI_COST_BASES.includes(raw2) ? raw2 : "none";
36051
+ }
36052
+ async function appendRoiMetric(client, scope, input) {
36053
+ const agentId = input.agentId.trim() === "" ? "default" : input.agentId;
36054
+ const teamId = await resolveTeamId(client, scope, agentId);
36055
+ const userId = resolveGatedUserId(input.verifiedClaim);
36056
+ const costBasis = asCostBasis(input.costBasis);
36057
+ const row = [
36058
+ ["id", val.str(input.id)],
36059
+ ["session_id", val.str(input.sessionId)],
36060
+ ["org_id", val.str(scope.org)],
36061
+ ["workspace_id", val.str(scope.workspace ?? "")],
36062
+ ["agent_id", val.str(agentId)],
36063
+ ["project_id", val.str(input.projectId ?? "")],
36064
+ ["team_id", val.str(teamId)],
36065
+ ["user_id", val.str(userId)],
36066
+ ["input_tokens", val.num(toInt(input.inputTokens))],
36067
+ ["output_tokens", val.num(toInt(input.outputTokens))],
36068
+ ["cache_read_tokens", val.num(toInt(input.cacheReadTokens))],
36069
+ ["cache_creation_tokens", val.num(toInt(input.cacheCreationTokens))],
36070
+ ["measured_cache_savings_cents", val.num(toInt(input.measuredCacheSavingsCents))],
36071
+ ["modeled_savings_cents", val.num(toInt(input.modeledSavingsCents))],
36072
+ ["modeled_assumption_ref", val.str(input.modeledAssumptionRef ?? "")],
36073
+ ["gross_cost_cents", val.num(toInt(input.grossCostCents))],
36074
+ ["infra_cost_cents", val.num(toInt(input.infraCostCents))],
36075
+ ["cost_basis", val.str(costBasis)],
36076
+ ["allocation_method", val.str(input.allocationMethod ?? "")],
36077
+ ["price_ref", val.str(input.priceRef ?? "")],
36078
+ ["period_start", val.str(input.periodStart ?? "")],
36079
+ ["period_end", val.str(input.periodEnd ?? "")],
36080
+ ["created_at", val.str(input.createdAt)]
36081
+ ];
36082
+ const result = await appendOnlyInsert(client, healTargetFor("roi_metrics"), scope, row);
36083
+ return { result, teamId, userId };
36084
+ }
36085
+ function buildRoiReadScopeSql(input, tableAlias) {
36086
+ const agentCol = tableAlias !== void 0 ? `${sqlIdent(tableAlias)}.${sqlIdent("agent_id")}` : sqlIdent("agent_id");
36087
+ const ownSql = `${agentCol} = ${sLiteral(input.agentId)}`;
36088
+ const noRows = `(${sLiteral("1")} = ${sLiteral("0")})`;
36089
+ const policy = asReadPolicy(input.readPolicy);
36090
+ if (input.agentId.trim() === "" || policy === null || policy === "isolated") {
36091
+ const ownClause = input.agentId.trim() === "" ? noRows : ownSql;
36092
+ return { sql: `(${ownClause})`, policyApplied: "isolated" };
36093
+ }
36094
+ if (policy === "shared") {
36095
+ return { sql: `(${sLiteral("1")} = ${sLiteral("1")})`, policyApplied: "shared" };
36096
+ }
36097
+ return { sql: `(${ownSql})`, policyApplied: "group" };
36098
+ }
36099
+ async function readRoiMetrics(client, scope, input) {
36100
+ const tbl = sqlIdent("roi_metrics");
36101
+ const { sql: scopeSql } = buildRoiReadScopeSql(input, "m");
36102
+ const projectClause = input.projectId !== void 0 && input.projectId.trim() !== "" ? ` AND ${sqlIdent("m")}.${sqlIdent("project_id")} = ${sLiteral(input.projectId.trim())}` : "";
36103
+ const sql = `SELECT m.* FROM "${tbl}" AS m WHERE ${scopeSql}${projectClause} AND NOT EXISTS (SELECT 1 FROM "${tbl}" AS n WHERE n.session_id = m.session_id AND (n.created_at > m.created_at OR (n.created_at = m.created_at AND n.id > m.id)))`;
36104
+ let res;
36105
+ try {
36106
+ res = await client.query(sql, scope);
36107
+ } catch (err) {
36108
+ return { status: "shared-ledger-absent", reason: err instanceof Error ? err.message : "query threw" };
36109
+ }
36110
+ if (!isOk(res)) {
36111
+ return { status: "shared-ledger-absent", reason: res.kind === "query_error" ? res.message : res.kind };
36112
+ }
36113
+ return { status: "ok", rows: res.rows };
36114
+ }
36115
+
35106
36116
  // dist/src/daemon/runtime/dashboard/api.js
35107
36117
  var DASHBOARD_GROUPS = Object.freeze({
35108
36118
  /**
@@ -35147,7 +36157,14 @@ var DASHBOARD_GROUPS = Object.freeze({
35147
36157
  * READ-ONLY filesystem walk; tenancy-independent (no org header required). PRD-036b calls
35148
36158
  * the underlying `scanInstalledAssets` IN-PROCESS, not over this HTTP surface.
35149
36159
  */
35150
- installedAssets: "/api/diagnostics"
36160
+ installedAssets: "/api/diagnostics",
36161
+ /**
36162
+ * ROI composite read-model (PRD-060e) — served off the diagnostics group at `/roi` + `/roi/trend`
36163
+ * (full paths `/api/diagnostics/roi` + `/api/diagnostics/roi/trend`). Local-mode-only loopback like
36164
+ * every other dashboard view-model; attached under the already-mounted, protected diagnostics group
36165
+ * (no new group, no `server.ts` edit). The page is a PURE function of the `RoiView` this returns.
36166
+ */
36167
+ roi: "/api/diagnostics"
35151
36168
  });
35152
36169
  function toNum2(value) {
35153
36170
  const n = typeof value === "number" ? value : Number(value ?? 0);
@@ -35331,6 +36348,176 @@ async function fetchSkillSyncView(storage, scope, scan = scanInstalledAssets) {
35331
36348
  }
35332
36349
  return { skills: [...merged.values()] };
35333
36350
  }
36351
+ var DEFAULT_ROI_READ_POLICY = "shared";
36352
+ function roiSectionStatusFor(status) {
36353
+ if (status === "measured")
36354
+ return "ok";
36355
+ return status;
36356
+ }
36357
+ function tokenCountOrNull(value) {
36358
+ if (value === null || value === void 0)
36359
+ return null;
36360
+ const n = typeof value === "number" ? value : Number(value);
36361
+ return Number.isFinite(n) ? Math.trunc(n) : null;
36362
+ }
36363
+ function rowToCapturedTurn(r) {
36364
+ const sourceTool = toStr2(r.source_tool);
36365
+ return {
36366
+ input_tokens: tokenCountOrNull(r.input_tokens),
36367
+ output_tokens: tokenCountOrNull(r.output_tokens),
36368
+ cache_read_input_tokens: tokenCountOrNull(r.cache_read_input_tokens),
36369
+ cache_creation_input_tokens: tokenCountOrNull(r.cache_creation_input_tokens),
36370
+ ...sourceTool !== "" ? { sourceTool } : {}
36371
+ };
36372
+ }
36373
+ var ROI_SESSIONS_LIMIT = 5e3;
36374
+ async function readCapturedTurns(storage, scope, projectId) {
36375
+ const tbl = sqlIdent("sessions");
36376
+ const dateCol = sqlIdent("creation_date");
36377
+ const idCol = sqlIdent("id");
36378
+ const projClause = projectId !== void 0 && projectId !== "" ? ` WHERE ${sqlIdent("project_id")} = ${sLiteral(projectId)}` : "";
36379
+ const sql = `SELECT ${sqlIdent("input_tokens")}, ${sqlIdent("output_tokens")}, ${sqlIdent("cache_read_input_tokens")}, ${sqlIdent("cache_creation_input_tokens")}, ${sqlIdent("source_tool")} FROM "${tbl}"${projClause} ORDER BY ${dateCol} DESC, ${idCol} DESC LIMIT ${ROI_SESSIONS_LIMIT}`;
36380
+ const rows = await selectRows2(storage, sql, scope);
36381
+ return rows.map(rowToCapturedTurn);
36382
+ }
36383
+ var ROLLUP_DIMENSIONS = Object.freeze([
36384
+ { dimension: "org", column: "org_id" },
36385
+ { dimension: "team", column: "team_id" },
36386
+ { dimension: "agent", column: "agent_id" },
36387
+ { dimension: "project", column: "project_id" }
36388
+ ]);
36389
+ function asCostBasisTag(raw2) {
36390
+ const s = toStr2(raw2);
36391
+ return s === "measured" || s === "allocated" || s === "none" ? s : "none";
36392
+ }
36393
+ function computeRollups(rows) {
36394
+ return ROLLUP_DIMENSIONS.map(({ dimension, column }) => {
36395
+ const groups = /* @__PURE__ */ new Map();
36396
+ const allBases = /* @__PURE__ */ new Set();
36397
+ for (const r of rows) {
36398
+ const key = toStr2(r[column]);
36399
+ const measured2 = toNum2(r.measured_cache_savings_cents);
36400
+ const modeled2 = toNum2(r.modeled_savings_cents);
36401
+ const infra = toNum2(r.infra_cost_cents);
36402
+ const gross = toNum2(r.gross_cost_cents);
36403
+ const basis = asCostBasisTag(r.cost_basis);
36404
+ allBases.add(basis);
36405
+ const g = groups.get(key) ?? { measured: 0, net: 0, infra: 0, sessions: 0, bases: /* @__PURE__ */ new Set() };
36406
+ g.measured += measured2;
36407
+ g.net += measured2 + modeled2 - (infra + gross);
36408
+ g.infra += infra;
36409
+ g.sessions += 1;
36410
+ g.bases.add(basis);
36411
+ groups.set(key, g);
36412
+ }
36413
+ const rollupRows = [...groups.entries()].map(([key, g]) => ({
36414
+ key,
36415
+ label: key,
36416
+ measuredSavingsCents: g.measured,
36417
+ netCents: g.net,
36418
+ infraCostCents: g.infra,
36419
+ // A row whose own group mixes bases is flagged `allocated` (the more-cautious tag); a single
36420
+ // basis carries through verbatim, so a measured-only row stays `measured`.
36421
+ costBasis: g.bases.size > 1 ? "allocated" : [...g.bases][0] ?? "none",
36422
+ sessions: g.sessions
36423
+ }));
36424
+ return { dimension, rows: rollupRows, mixedBasis: allBases.size > 1 };
36425
+ });
36426
+ }
36427
+ async function fetchRoiView(storage, scope, options = {}) {
36428
+ const agentId = options.agentId !== void 0 && options.agentId.trim() !== "" ? options.agentId.trim() : "";
36429
+ const readPolicy = options.readPolicy ?? DEFAULT_ROI_READ_POLICY;
36430
+ const infraModel = options.infra ?? createInfraCostReadModel();
36431
+ const usage = options.usage ?? emptyUsageSource;
36432
+ const projectId = options.projectId !== void 0 && options.projectId.trim() !== "" ? options.projectId.trim() : void 0;
36433
+ try {
36434
+ const [turns, infra, ledger] = await Promise.all([
36435
+ readCapturedTurns(storage, scope, projectId),
36436
+ infraModel.read(),
36437
+ readRoiMetrics(storage, scope, { agentId, readPolicy, ...projectId !== void 0 ? { projectId } : {} })
36438
+ ]);
36439
+ return assembleRoiView({ turns, infra, usage, ledger, readPolicy });
36440
+ } catch {
36441
+ return EMPTY_ROI_VIEW;
36442
+ }
36443
+ }
36444
+ function assembleRoiView(input) {
36445
+ const { turns, infra, usage, ledger, readPolicy } = input;
36446
+ const measured2 = measuredCacheSavings(turns);
36447
+ const modeled2 = modeledMemoryInjectionSavings(turns.length);
36448
+ const blended = blendedCentsPerMtok(turns);
36449
+ const captureStatus = measured2.value.status;
36450
+ const savingsStatus = captureStatus === "measured" ? "ok" : captureStatus;
36451
+ const savings = {
36452
+ status: savingsStatus,
36453
+ measuredCents: measured2.value.savingsCents,
36454
+ modeledCents: modeled2.value.estimatedCents,
36455
+ assumption: {
36456
+ kind: modeled2.assumption.kind,
36457
+ assumptionText: modeled2.assumption.assumptionText,
36458
+ signedOff: modeled2.assumption.signedOff
36459
+ },
36460
+ blendedCentsPerMtok: blended
36461
+ };
36462
+ const infraStatus = roiSectionStatusFor(infra.status);
36463
+ const infraCents = infra.summary !== void 0 ? infra.summary.total_cost_cents : 0;
36464
+ const infraSection = {
36465
+ status: infraStatus,
36466
+ cents: infraCents,
36467
+ // Org/workspace infra read from billing is a MEASURED fact when present; otherwise no line.
36468
+ costBasis: infra.status === "ok" || infra.status === "partial" ? "measured" : "none"
36469
+ };
36470
+ const pollination = composePollinationCost(usage, infra);
36471
+ const pollinationLines = [
36472
+ { label: "haiku-skillify", cents: pollination.haiku.cents },
36473
+ ...pollination.deeplake.bySessionType.map((s) => ({ label: `deeplake-${s.session_type}`, cents: s.cost_cents }))
36474
+ ];
36475
+ const pollinationSection = {
36476
+ status: roiSectionStatusFor(pollination.status),
36477
+ cents: pollination.pollinationCents,
36478
+ lines: pollinationLines
36479
+ };
36480
+ const savingsPresent = savingsStatus === "ok";
36481
+ const infraConfident = infraStatus === "ok";
36482
+ const pollinationConfident = pollinationSection.status === "ok";
36483
+ const netComputable = savingsPresent && infraConfident && pollinationConfident;
36484
+ let netSection;
36485
+ if (netComputable) {
36486
+ const netCents = measured2.value.savingsCents + modeled2.value.estimatedCents - (infraCents + pollination.pollinationCents);
36487
+ netSection = {
36488
+ status: "ok",
36489
+ computed: true,
36490
+ netCents,
36491
+ modeled: true,
36492
+ // the net folds a modeled term → ALWAYS `est.` (e-AC-3 net-hero inheritance).
36493
+ costBasis: infraSection.costBasis
36494
+ };
36495
+ } else {
36496
+ const reason = !savingsPresent ? savingsStatus : !infraConfident ? infraStatus : pollinationSection.status;
36497
+ netSection = { status: reason, computed: false, netCents: 0, modeled: true, costBasis: "none" };
36498
+ }
36499
+ const ledgerRows = ledger.status === "ok" ? ledger.rows : [];
36500
+ const rollups = computeRollups(ledgerRows);
36501
+ return {
36502
+ savings,
36503
+ infra: infraSection,
36504
+ pollination: pollinationSection,
36505
+ net: netSection,
36506
+ rollups,
36507
+ // PER-USER GATE (e-AC-14): there is no verified backend user-claim today, so per-user is NEVER
36508
+ // available — the page shows the "per-user requires verified login" empty state, never a $0/name.
36509
+ perUserAvailable: false,
36510
+ // ACROSS-DEVICE (e-AC-12): a `shared` read returned workspace-wide rows (across devices); an
36511
+ // `isolated` read returned only this machine's. The page captions the scope from this.
36512
+ scopedAcrossDevices: readPolicy === "shared",
36513
+ ratesAsOf: RATES_AS_OF
36514
+ };
36515
+ }
36516
+ async function fetchRoiTrendView(storage, scope, _range, _options = {}) {
36517
+ void storage;
36518
+ void scope;
36519
+ return EMPTY_ROI_TREND;
36520
+ }
35334
36521
  var NO_ORG_BODY4 = { error: "bad_request", reason: "x-honeycomb-org header is required" };
35335
36522
  function mountDashboardApi(daemon, options) {
35336
36523
  const storage = options.storage;
@@ -35398,16 +36585,44 @@ function mountDashboardApi(daemon, options) {
35398
36585
  return c.json(await inventoryCache());
35399
36586
  });
35400
36587
  }
36588
+ const roi = daemon.group(DASHBOARD_GROUPS.roi);
36589
+ if (roi !== void 0) {
36590
+ const roiOptions = (c, scope) => {
36591
+ const project = resolveRequestProject(c, scope);
36592
+ return {
36593
+ ...options.roiInfra !== void 0 ? { infra: options.roiInfra } : {},
36594
+ ...options.roiUsage !== void 0 ? { usage: options.roiUsage } : {},
36595
+ readPolicy: resolveRoiReadPolicy(c.req.query("policy")),
36596
+ ...!project.degraded ? { projectId: project.projectId } : {}
36597
+ };
36598
+ };
36599
+ roi.get("/roi", async (c) => {
36600
+ const scope = resolveScope5(c);
36601
+ if (scope === null)
36602
+ return c.json(NO_ORG_BODY4, 400);
36603
+ return c.json(await fetchRoiView(storage, scope, roiOptions(c, scope)));
36604
+ });
36605
+ roi.get("/roi/trend", async (c) => {
36606
+ const scope = resolveScope5(c);
36607
+ if (scope === null)
36608
+ return c.json(NO_ORG_BODY4, 400);
36609
+ const range = c.req.query("range") ?? "";
36610
+ return c.json(await fetchRoiTrendView(storage, scope, range, roiOptions(c, scope)));
36611
+ });
36612
+ }
36613
+ }
36614
+ function resolveRoiReadPolicy(raw2) {
36615
+ return raw2 === "isolated" || raw2 === "shared" || raw2 === "group" ? raw2 : DEFAULT_ROI_READ_POLICY;
35401
36616
  }
35402
36617
  var INSTALLED_ASSETS_TTL_MS = 5e3;
35403
36618
  function createInventoryCache() {
35404
- let cached2;
36619
+ let cached3;
35405
36620
  return async () => {
35406
36621
  const now = Date.now();
35407
- if (cached2 !== void 0 && now - cached2.at < INSTALLED_ASSETS_TTL_MS)
35408
- return cached2.value;
36622
+ if (cached3 !== void 0 && now - cached3.at < INSTALLED_ASSETS_TTL_MS)
36623
+ return cached3.value;
35409
36624
  const value = await scanInstalledAssets();
35410
- cached2 = { value, at: now };
36625
+ cached3 = { value, at: now };
35411
36626
  return value;
35412
36627
  };
35413
36628
  }
@@ -35693,8 +36908,61 @@ function nestedString(raw2, a, b) {
35693
36908
  function userMessageData(text) {
35694
36909
  return { kind: "user_message", text };
35695
36910
  }
35696
- function assistantMessageData(text) {
35697
- return { kind: "assistant_message", text };
36911
+ function assistantMessageData(text, usage) {
36912
+ const normalized = usage !== void 0 ? compactUsage(usage) : void 0;
36913
+ return {
36914
+ kind: "assistant_message",
36915
+ text,
36916
+ ...normalized !== void 0 ? { usage: normalized } : {}
36917
+ };
36918
+ }
36919
+ function compactUsage(usage) {
36920
+ const out = {};
36921
+ if (isCount(usage.input))
36922
+ out.input = usage.input;
36923
+ if (isCount(usage.output))
36924
+ out.output = usage.output;
36925
+ if (isCount(usage.cacheRead))
36926
+ out.cacheRead = usage.cacheRead;
36927
+ if (isCount(usage.cacheCreation))
36928
+ out.cacheCreation = usage.cacheCreation;
36929
+ return Object.keys(out).length > 0 ? out : void 0;
36930
+ }
36931
+ function isCount(n) {
36932
+ return typeof n === "number" && Number.isInteger(n) && n >= 0;
36933
+ }
36934
+ function extractTurnUsage(raw2) {
36935
+ const block = usageBlock(raw2);
36936
+ if (block === void 0)
36937
+ return void 0;
36938
+ const usage = {
36939
+ ...readCount(block, "input_tokens") !== void 0 ? { input: readCount(block, "input_tokens") } : {},
36940
+ ...readCount(block, "output_tokens") !== void 0 ? { output: readCount(block, "output_tokens") } : {},
36941
+ ...readCount(block, "cache_read_input_tokens") !== void 0 ? { cacheRead: readCount(block, "cache_read_input_tokens") } : {},
36942
+ ...readCount(block, "cache_creation_input_tokens") !== void 0 ? { cacheCreation: readCount(block, "cache_creation_input_tokens") } : {}
36943
+ };
36944
+ return compactUsage(usage);
36945
+ }
36946
+ function usageBlock(raw2) {
36947
+ const top = nested(raw2, "usage");
36948
+ if (top !== null && typeof top === "object")
36949
+ return top;
36950
+ const inner = nestedRecord(nested(raw2, "message"), "usage");
36951
+ return inner;
36952
+ }
36953
+ function nestedRecord(obj, key) {
36954
+ if (obj !== null && typeof obj === "object") {
36955
+ const value = obj[key];
36956
+ if (value !== null && typeof value === "object")
36957
+ return value;
36958
+ }
36959
+ return void 0;
36960
+ }
36961
+ function readCount(block, key) {
36962
+ const value = block[key];
36963
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 0)
36964
+ return void 0;
36965
+ return value;
35698
36966
  }
35699
36967
  function toolCallData(tool, input, response) {
35700
36968
  return { kind: "tool_call", tool, input, response };
@@ -35744,7 +37012,7 @@ function claudeCodeExtractData(raw2, logical) {
35744
37012
  case "tool_call":
35745
37013
  return toolCallData(pickString(raw2, "tool_name", "tool"), nested(raw2, "tool_input"), nested(raw2, "tool_response"));
35746
37014
  case "assistant_message":
35747
- return assistantMessageData(pickString(raw2, "text", "message"));
37015
+ return assistantMessageData(pickString(raw2, "text", "message"), extractTurnUsage(raw2));
35748
37016
  case "session-end":
35749
37017
  return sessionEndData(pickString(raw2, "reason") || "Stop");
35750
37018
  default:
@@ -36162,7 +37430,17 @@ var StorageConfigSchema = external_exports.object({
36162
37430
  /** Per-statement timeout, clamped non-negative. */
36163
37431
  queryTimeoutMs: QueryTimeoutMs.default(DEFAULT_QUERY_TIMEOUT_MS),
36164
37432
  /** SQL tracing gate (FR-6). Evaluated at call time, see client.ts. */
36165
- traceSql: external_exports.boolean().default(false)
37433
+ traceSql: external_exports.boolean().default(false),
37434
+ /**
37435
+ * Query-meter persistence gate (PRD-062a). RESERVED, NOT YET IMPLEMENTED. The
37436
+ * query meter's default posture is in-memory + structured-log only and adds
37437
+ * ZERO DeepLake queries; persisting per-source counts to the existing
37438
+ * `telemetry_counters` tenant group is a later, separate concern and would add
37439
+ * write cost, so it is gated behind this flag. Today the flag is parsed and
37440
+ * carried but nothing reads it for behavior — wiring persistence is out of
37441
+ * scope for this sub-PRD (it must not make the meter itself a write-cost driver).
37442
+ */
37443
+ queryMeterPersist: external_exports.boolean().default(false)
36166
37444
  });
36167
37445
  var StorageConfigError = class extends Error {
36168
37446
  issues;
@@ -36181,7 +37459,8 @@ function envCredentialProvider(env = process.env) {
36181
37459
  org: env.HONEYCOMB_DEEPLAKE_ORG,
36182
37460
  workspace: env.HONEYCOMB_DEEPLAKE_WORKSPACE,
36183
37461
  queryTimeoutMs: env.HONEYCOMB_QUERY_TIMEOUT_MS,
36184
- traceSql: env.HONEYCOMB_TRACE_SQL === "1" || env.HONEYCOMB_TRACE_SQL === "true"
37462
+ traceSql: env.HONEYCOMB_TRACE_SQL === "1" || env.HONEYCOMB_TRACE_SQL === "true",
37463
+ queryMeterPersist: env.HONEYCOMB_QUERY_METER_PERSIST === "1" || env.HONEYCOMB_QUERY_METER_PERSIST === "true"
36185
37464
  };
36186
37465
  }
36187
37466
  };
@@ -36233,7 +37512,8 @@ function defaultCredentialProvider(options = {}) {
36233
37512
  orgName: fromFile.orgName,
36234
37513
  // Tuning knobs are env-only (the file carries none).
36235
37514
  queryTimeoutMs: fromEnv.queryTimeoutMs,
36236
- traceSql: fromEnv.traceSql
37515
+ traceSql: fromEnv.traceSql,
37516
+ queryMeterPersist: fromEnv.queryMeterPersist
36237
37517
  };
36238
37518
  }
36239
37519
  };
@@ -37583,12 +38863,12 @@ function mountSetupMigrate(daemon, options = {}) {
37583
38863
  // dist/src/daemon/runtime/pollinating/config.js
37584
38864
  var DEFAULT_TOKEN_THRESHOLD = 1e5;
37585
38865
  var DEFAULT_MAX_INPUT_TOKENS = 128e3;
37586
- var BoolFlag = external_exports.preprocess((raw2) => {
38866
+ var BoolFlag2 = external_exports.preprocess((raw2) => {
37587
38867
  if (typeof raw2 === "boolean")
37588
38868
  return raw2;
37589
38869
  return raw2 === "true" || raw2 === "1";
37590
38870
  }, external_exports.boolean());
37591
- function ClampedInt(def, min = 1) {
38871
+ function ClampedInt2(def, min = 1) {
37592
38872
  return external_exports.preprocess((raw2) => {
37593
38873
  const n = typeof raw2 === "number" ? raw2 : Number(raw2);
37594
38874
  if (!Number.isFinite(n))
@@ -37598,13 +38878,13 @@ function ClampedInt(def, min = 1) {
37598
38878
  }
37599
38879
  var PollinatingConfigSchema = external_exports.object({
37600
38880
  /** Master switch; off → counter still grows but no job is queued (FR-7 / a-AC-4). */
37601
- enabled: BoolFlag.default(false),
38881
+ enabled: BoolFlag2.default(false),
37602
38882
  /** Tokens-since-last-pass that queues a pass; reset SUBTRACTS this (FR-3 / FR-5). */
37603
- tokenThreshold: ClampedInt(DEFAULT_TOKEN_THRESHOLD).default(DEFAULT_TOKEN_THRESHOLD),
38883
+ tokenThreshold: ClampedInt2(DEFAULT_TOKEN_THRESHOLD).default(DEFAULT_TOKEN_THRESHOLD),
37604
38884
  /** Input-token budget a pass's payload must fit under (D-2 / 009c). */
37605
- maxInputTokens: ClampedInt(DEFAULT_MAX_INPUT_TOKENS).default(DEFAULT_MAX_INPUT_TOKENS),
38885
+ maxInputTokens: ClampedInt2(DEFAULT_MAX_INPUT_TOKENS).default(DEFAULT_MAX_INPUT_TOKENS),
37606
38886
  /** First run with no prior pass enters compaction, not incremental (D-4 / c-AC-1). */
37607
- backfillOnFirstRun: BoolFlag.default(true)
38887
+ backfillOnFirstRun: BoolFlag2.default(true)
37608
38888
  });
37609
38889
  var PollinatingConfigError = class extends Error {
37610
38890
  issues;
@@ -38632,7 +39912,7 @@ var DEFAULT_KEEP_LATEST_N = 5;
38632
39912
  var DEFAULT_WINDOW_DAYS = 30;
38633
39913
  var DEFAULT_TIMESTAMP_COLUMN = "updated_at";
38634
39914
  var DEFAULT_VERSION_COLUMN = "version";
38635
- function ClampedInt2(def, min) {
39915
+ function ClampedInt3(def, min) {
38636
39916
  return external_exports.preprocess((raw2) => {
38637
39917
  const n = typeof raw2 === "number" ? raw2 : Number(raw2);
38638
39918
  if (!Number.isFinite(n))
@@ -38649,9 +39929,9 @@ function IdentColumn(def) {
38649
39929
  }
38650
39930
  var CompactionRetentionSchema = external_exports.object({
38651
39931
  /** Keep the most-recent N versions below the highest (D-1). Clamp `>= 1`. */
38652
- keepLatestN: ClampedInt2(DEFAULT_KEEP_LATEST_N, 1).default(DEFAULT_KEEP_LATEST_N),
39932
+ keepLatestN: ClampedInt3(DEFAULT_KEEP_LATEST_N, 1).default(DEFAULT_KEEP_LATEST_N),
38653
39933
  /** Keep any version newer than now − windowDays (D-1). Clamp `>= 0` (0 = off). */
38654
- windowDays: ClampedInt2(DEFAULT_WINDOW_DAYS, 0).default(DEFAULT_WINDOW_DAYS),
39934
+ windowDays: ClampedInt3(DEFAULT_WINDOW_DAYS, 0).default(DEFAULT_WINDOW_DAYS),
38655
39935
  /** The timestamp column the window is measured on. Default `updated_at`. */
38656
39936
  timestampColumn: IdentColumn(DEFAULT_TIMESTAMP_COLUMN).default(DEFAULT_TIMESTAMP_COLUMN),
38657
39937
  /** The version column. Default `version` (mirrors `appendVersionBumped`). */
@@ -39055,35 +40335,6 @@ async function readBody4(c) {
39055
40335
  }
39056
40336
  }
39057
40337
 
39058
- // dist/src/daemon/runtime/recall/scope-clause.js
39059
- var PROJECT_ID_COLUMN = "project_id";
39060
- var PROJECT_ID_UNSET = "";
39061
- var CROSS_PROJECT_ADMITTED = Object.freeze(["user", "workspace"]);
39062
- var SCOPE_READ_POLICIES = Object.freeze(["isolated", "shared", "group"]);
39063
- function buildProjectScopeClause(input) {
39064
- const projectColumn = input.projectColumn ?? PROJECT_ID_COLUMN;
39065
- const rawId = input.projectId ?? "";
39066
- const isInbox = rawId.trim() === "" || rawId === UNSORTED_PROJECT_ID;
39067
- const bound = input.bound ?? !isInbox;
39068
- const col = sqlIdent(projectColumn);
39069
- const primaryId = bound ? rawId : UNSORTED_PROJECT_ID;
39070
- const admitted = primaryId === PROJECT_ID_UNSET ? [PROJECT_ID_UNSET] : [primaryId, PROJECT_ID_UNSET];
39071
- const disjuncts = admitted.map((id) => `${col} = ${sLiteral(id)}`);
39072
- const values = [...admitted];
39073
- if (input.promotionColumn !== void 0 && input.promotionColumn !== "") {
39074
- const promoCol = sqlIdent(input.promotionColumn);
39075
- for (const reach of CROSS_PROJECT_ADMITTED) {
39076
- disjuncts.push(`${promoCol} = ${sLiteral(reach)}`);
39077
- values.push(reach);
39078
- }
39079
- }
39080
- const sql = `(${disjuncts.join(" OR ")})`;
39081
- return { sql, values, bound };
39082
- }
39083
- function buildProjectScopeConjunct(input) {
39084
- return ` AND ${buildProjectScopeClause(input).sql}`;
39085
- }
39086
-
39087
40338
  // dist/src/daemon/runtime/memories/reads.js
39088
40339
  var DEFAULT_LIST_LIMIT = 50;
39089
40340
  var MAX_LIST_LIMIT = 500;
@@ -40382,12 +41633,12 @@ var DEFAULT_REHEARSAL_BOOST = 1.1;
40382
41633
  var DEFAULT_REHEARSAL_WINDOW_MS = 7 * 24 * 60 * 60 * 1e3;
40383
41634
  var DEFAULT_MIN_INJECTION_SCORE = 0.6;
40384
41635
  var RERANKER_STRATEGIES = Object.freeze(["embedding-cosine", "llm", "none"]);
40385
- var BoolFlag2 = external_exports.preprocess((raw2) => {
41636
+ var BoolFlag3 = external_exports.preprocess((raw2) => {
40386
41637
  if (typeof raw2 === "boolean")
40387
41638
  return raw2;
40388
41639
  return raw2 === "true" || raw2 === "1";
40389
41640
  }, external_exports.boolean());
40390
- function ClampedInt3(def, min = 0) {
41641
+ function ClampedInt4(def, min = 0) {
40391
41642
  return external_exports.preprocess((raw2) => {
40392
41643
  const n = typeof raw2 === "number" ? raw2 : Number(raw2);
40393
41644
  if (!Number.isFinite(n))
@@ -40423,29 +41674,29 @@ function OptionalClampedHalfLifeDays(min) {
40423
41674
  }
40424
41675
  var TraversalConfigSchema = external_exports.object({
40425
41676
  /** Max aspects per focal entity. */
40426
- aspectsPerEntity: ClampedInt3(DEFAULT_TRAVERSAL_ASPECTS_PER_ENTITY, 1).default(DEFAULT_TRAVERSAL_ASPECTS_PER_ENTITY),
41677
+ aspectsPerEntity: ClampedInt4(DEFAULT_TRAVERSAL_ASPECTS_PER_ENTITY, 1).default(DEFAULT_TRAVERSAL_ASPECTS_PER_ENTITY),
40427
41678
  /** Max attributes per aspect. */
40428
- attrsPerAspect: ClampedInt3(DEFAULT_TRAVERSAL_ATTRS_PER_ASPECT, 1).default(DEFAULT_TRAVERSAL_ATTRS_PER_ASPECT),
41679
+ attrsPerAspect: ClampedInt4(DEFAULT_TRAVERSAL_ATTRS_PER_ASPECT, 1).default(DEFAULT_TRAVERSAL_ATTRS_PER_ASPECT),
40429
41680
  /** Max branching factor per node. */
40430
- branching: ClampedInt3(DEFAULT_TRAVERSAL_BRANCHING, 1).default(DEFAULT_TRAVERSAL_BRANCHING),
41681
+ branching: ClampedInt4(DEFAULT_TRAVERSAL_BRANCHING, 1).default(DEFAULT_TRAVERSAL_BRANCHING),
40431
41682
  /** Hard cap on total IDs the walk may collect. */
40432
- totalIds: ClampedInt3(DEFAULT_TRAVERSAL_TOTAL_IDS, 1).default(DEFAULT_TRAVERSAL_TOTAL_IDS),
41683
+ totalIds: ClampedInt4(DEFAULT_TRAVERSAL_TOTAL_IDS, 1).default(DEFAULT_TRAVERSAL_TOTAL_IDS),
40433
41684
  /** Minimum edge strength×confidence to follow an edge. */
40434
41685
  minEdgeWeight: ClampedFloat(DEFAULT_TRAVERSAL_MIN_EDGE_WEIGHT).default(DEFAULT_TRAVERSAL_MIN_EDGE_WEIGHT),
40435
41686
  /** Hard traversal timeout in ms. */
40436
- timeoutMs: ClampedInt3(DEFAULT_TRAVERSAL_TIMEOUT_MS, 1).default(DEFAULT_TRAVERSAL_TIMEOUT_MS)
41687
+ timeoutMs: ClampedInt4(DEFAULT_TRAVERSAL_TIMEOUT_MS, 1).default(DEFAULT_TRAVERSAL_TIMEOUT_MS)
40437
41688
  });
40438
41689
  var RerankerConfigSchema = external_exports.object({
40439
41690
  /** The reranker strategy (D-4). */
40440
41691
  strategy: external_exports.enum(RERANKER_STRATEGIES).default(DEFAULT_RERANKER),
40441
41692
  /** Reranker timeout in ms; on timeout keep the original order (d-AC-2 / b-AC-2). */
40442
- timeoutMs: ClampedInt3(DEFAULT_RERANKER_TIMEOUT_MS, 1).default(DEFAULT_RERANKER_TIMEOUT_MS),
41693
+ timeoutMs: ClampedInt4(DEFAULT_RERANKER_TIMEOUT_MS, 1).default(DEFAULT_RERANKER_TIMEOUT_MS),
40443
41694
  /** Rerank window N: how many fused top-N candidates to re-score (PRD-047b). */
40444
- window: ClampedInt3(DEFAULT_RERANKER_WINDOW, 1).default(DEFAULT_RERANKER_WINDOW)
41695
+ window: ClampedInt4(DEFAULT_RERANKER_WINDOW, 1).default(DEFAULT_RERANKER_WINDOW)
40445
41696
  });
40446
41697
  var DedupConfigSchema = external_exports.object({
40447
41698
  /** Whether semantic near-duplicate dedup runs; ON by default (PRD-047c / c-AC-3). */
40448
- enabled: BoolFlag2.default(DEFAULT_DEDUP_ENABLED),
41699
+ enabled: BoolFlag3.default(DEFAULT_DEDUP_ENABLED),
40449
41700
  /** The cosine-similarity collapse threshold in `[0,1]` (PRD-047c / c-AC-1). */
40450
41701
  similarityThreshold: ClampedFloat(DEFAULT_DEDUP_SIMILARITY_THRESHOLD).default(DEFAULT_DEDUP_SIMILARITY_THRESHOLD)
40451
41702
  });
@@ -40484,21 +41735,21 @@ var DampeningConfigSchema = external_exports.object({
40484
41735
  /** Bounded rehearsal boost for a recently-accessed memory. Ceiling 4. */
40485
41736
  rehearsalBoost: ClampedFloat(DEFAULT_REHEARSAL_BOOST, 4).default(DEFAULT_REHEARSAL_BOOST),
40486
41737
  /** "Recent" window for the rehearsal boost in ms (7d). */
40487
- rehearsalWindowMs: ClampedInt3(DEFAULT_REHEARSAL_WINDOW_MS, 1).default(DEFAULT_REHEARSAL_WINDOW_MS)
41738
+ rehearsalWindowMs: ClampedInt4(DEFAULT_REHEARSAL_WINDOW_MS, 1).default(DEFAULT_REHEARSAL_WINDOW_MS)
40488
41739
  });
40489
41740
  var RecallConfigSchema = external_exports.object({
40490
41741
  /** Over-fetch multiplier for scoped vector recalls (D-1 / a-AC-2 / FR-5). */
40491
- overFetchMultiplier: ClampedInt3(DEFAULT_OVER_FETCH_MULTIPLIER, 1).default(DEFAULT_OVER_FETCH_MULTIPLIER),
41742
+ overFetchMultiplier: ClampedInt4(DEFAULT_OVER_FETCH_MULTIPLIER, 1).default(DEFAULT_OVER_FETCH_MULTIPLIER),
40492
41743
  /** Base per-channel candidate limit (FTS/vector base, pre over-fetch). */
40493
- channelLimit: ClampedInt3(DEFAULT_CHANNEL_LIMIT, 1).default(DEFAULT_CHANNEL_LIMIT),
41744
+ channelLimit: ClampedInt4(DEFAULT_CHANNEL_LIMIT, 1).default(DEFAULT_CHANNEL_LIMIT),
40494
41745
  /** Hint cap so a memory can't ride in on hints alone (D-2 / a-AC-4 / FR-7). */
40495
- hintCap: ClampedInt3(DEFAULT_HINT_CAP, 0).default(DEFAULT_HINT_CAP),
41746
+ hintCap: ClampedInt4(DEFAULT_HINT_CAP, 0).default(DEFAULT_HINT_CAP),
40496
41747
  /** Keyword expansion for the LEXICAL path only; OFF by default (D-2 / FR-2). */
40497
- keywordExpansion: BoolFlag2.default(false),
41748
+ keywordExpansion: BoolFlag3.default(false),
40498
41749
  /** Minimum calibrated injection score (D-6 / e-AC-1); per-agent tunable. */
40499
41750
  minInjectionScore: ClampedFloat(DEFAULT_MIN_INJECTION_SCORE).default(DEFAULT_MIN_INJECTION_SCORE),
40500
41751
  /** Master graph switch for the traversal channel/phase (007b). */
40501
- graphEnabled: BoolFlag2.default(false),
41752
+ graphEnabled: BoolFlag3.default(false),
40502
41753
  /** D-3 traversal budgets (007b). */
40503
41754
  traversal: TraversalConfigSchema.default(() => TraversalConfigSchema.parse({})),
40504
41755
  /** D-4 reranker config (007d). */
@@ -40581,6 +41832,129 @@ function resolveRecallConfig(provider = envRecallConfigProvider()) {
40581
41832
  return parsed.data;
40582
41833
  }
40583
41834
 
41835
+ // dist/src/daemon/runtime/memories/bounded-pool.js
41836
+ var Semaphore = class {
41837
+ /** The maximum permits (in-flight tasks) allowed at once. Always `>= 1`. */
41838
+ max;
41839
+ /** Permits currently held (tasks running). Never exceeds {@link max}. */
41840
+ held = 0;
41841
+ /** FIFO queue of waiters parked because all permits were held at acquire time. */
41842
+ waiters = [];
41843
+ constructor(max) {
41844
+ this.max = Number.isFinite(max) && max >= 1 ? Math.trunc(max) : 1;
41845
+ }
41846
+ /** Permits currently held (the live in-flight count). Test-observable for the cap assertion. */
41847
+ get inFlight() {
41848
+ return this.held;
41849
+ }
41850
+ /** Number of tasks parked waiting for a permit (test-observable). */
41851
+ get waiting() {
41852
+ return this.waiters.length;
41853
+ }
41854
+ /**
41855
+ * Acquire one permit. Resolves immediately when a permit is free; otherwise parks
41856
+ * FIFO and resolves when {@link release} hands a permit forward. Each acquire MUST
41857
+ * be paired with exactly one {@link release} (use {@link run} to guarantee it).
41858
+ */
41859
+ acquire() {
41860
+ if (this.held < this.max) {
41861
+ this.held += 1;
41862
+ return Promise.resolve();
41863
+ }
41864
+ return new Promise((resolve6) => {
41865
+ this.waiters.push(() => {
41866
+ this.held += 1;
41867
+ resolve6();
41868
+ });
41869
+ });
41870
+ }
41871
+ /**
41872
+ * Release one permit. If a waiter is parked, hand the permit STRAIGHT to it (the
41873
+ * held count stays balanced — the waiter's resolver re-increments), otherwise drop
41874
+ * the held count. Releasing with no permit held is a no-op (defensive — a double
41875
+ * release never drives the count negative).
41876
+ */
41877
+ release() {
41878
+ const next = this.waiters.shift();
41879
+ if (next !== void 0) {
41880
+ this.held -= 1;
41881
+ next();
41882
+ return;
41883
+ }
41884
+ if (this.held > 0)
41885
+ this.held -= 1;
41886
+ }
41887
+ /**
41888
+ * Run `fn` under one permit: acquire, run, and ALWAYS release (even when `fn`
41889
+ * throws/rejects), then re-throw the original error. The safe wrapper — a task
41890
+ * that throws never leaks a permit and wedges the pool.
41891
+ */
41892
+ async run(fn) {
41893
+ await this.acquire();
41894
+ try {
41895
+ return await fn();
41896
+ } finally {
41897
+ this.release();
41898
+ }
41899
+ }
41900
+ };
41901
+
41902
+ // dist/src/daemon/runtime/memories/amplification-config.js
41903
+ var DEFAULT_FANOUT_BATCH = true;
41904
+ var DEFAULT_RECALL_MAX_CONCURRENCY = 6;
41905
+ var MIN_RECALL_MAX_CONCURRENCY = 1;
41906
+ var OnByDefaultFlag = external_exports.preprocess((raw2) => {
41907
+ if (typeof raw2 === "boolean")
41908
+ return raw2;
41909
+ if (raw2 === void 0 || raw2 === null || raw2 === "")
41910
+ return true;
41911
+ return !(raw2 === "false" || raw2 === "0");
41912
+ }, external_exports.boolean());
41913
+ var ConcurrencyKnob = external_exports.preprocess((raw2) => {
41914
+ const n = typeof raw2 === "number" ? raw2 : Number(raw2);
41915
+ if (!Number.isFinite(n))
41916
+ return DEFAULT_RECALL_MAX_CONCURRENCY;
41917
+ return Math.max(MIN_RECALL_MAX_CONCURRENCY, Math.trunc(n));
41918
+ }, external_exports.number().int());
41919
+ var AmplificationConfigSchema = external_exports.object({
41920
+ /** `HONEYCOMB_FANOUT_BATCH` — batched fan-out enqueue; ON by default (AC-62d.1.1 / AC-9). */
41921
+ fanoutBatch: OnByDefaultFlag.default(DEFAULT_FANOUT_BATCH),
41922
+ /** `HONEYCOMB_RECALL_MAX_CONCURRENCY` — in-flight DeepLake-query ceiling; 6 by default (AC-62d.2.1). */
41923
+ recallMaxConcurrency: ConcurrencyKnob.default(DEFAULT_RECALL_MAX_CONCURRENCY)
41924
+ });
41925
+ var AmplificationConfigError = class extends Error {
41926
+ issues;
41927
+ constructor(issues) {
41928
+ super(`Invalid amplification config: ${issues.join("; ")}`);
41929
+ this.name = "AmplificationConfigError";
41930
+ this.issues = issues;
41931
+ }
41932
+ };
41933
+ function envAmplificationConfigProvider(env = process.env) {
41934
+ return {
41935
+ read() {
41936
+ return {
41937
+ fanoutBatch: env.HONEYCOMB_FANOUT_BATCH,
41938
+ recallMaxConcurrency: env.HONEYCOMB_RECALL_MAX_CONCURRENCY
41939
+ };
41940
+ }
41941
+ };
41942
+ }
41943
+ function resolveAmplificationConfig(provider = envAmplificationConfigProvider()) {
41944
+ const parsed = AmplificationConfigSchema.safeParse(provider.read());
41945
+ if (!parsed.success) {
41946
+ const issues = parsed.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`);
41947
+ throw new AmplificationConfigError(issues);
41948
+ }
41949
+ return parsed.data;
41950
+ }
41951
+ var cached2;
41952
+ function amplificationConfig() {
41953
+ if (cached2 === void 0)
41954
+ cached2 = resolveAmplificationConfig();
41955
+ return cached2;
41956
+ }
41957
+
40584
41958
  // dist/src/daemon/runtime/memories/activation.js
40585
41959
  var DEFAULT_ACTR_DECAY = 0.5;
40586
41960
  var DEFAULT_ACTR_A_MIN = 0.05;
@@ -40695,6 +42069,15 @@ function deserializeModel(blob) {
40695
42069
  }
40696
42070
 
40697
42071
  // dist/src/daemon/runtime/memories/recall.js
42072
+ var SOURCE_RECALL_ARM = "recall-arm";
42073
+ var sharedRecallPool;
42074
+ function resolveRecallPool(deps) {
42075
+ if (deps.recallPool !== void 0)
42076
+ return deps.recallPool;
42077
+ if (sharedRecallPool === void 0)
42078
+ sharedRecallPool = new Semaphore(amplificationConfig().recallMaxConcurrency);
42079
+ return sharedRecallPool;
42080
+ }
40698
42081
  var DEFAULT_RECALL_LIMIT = 20;
40699
42082
  var MAX_RECALL_LIMIT = 200;
40700
42083
  var RRF_K = 60;
@@ -40826,7 +42209,8 @@ function projectConjunctFor(request) {
40826
42209
  });
40827
42210
  }
40828
42211
  async function runArm(sql, request, deps) {
40829
- const result = await deps.storage.query(sql, request.scope);
42212
+ const pool = resolveRecallPool(deps);
42213
+ const result = await pool.run(() => deps.storage.query(sql, request.scope, { source: SOURCE_RECALL_ARM }));
40830
42214
  return isOk(result) ? result.rows : [];
40831
42215
  }
40832
42216
  var SEMANTIC_ARMS = [
@@ -40866,7 +42250,8 @@ async function runSemanticArm(spec, queryVector, request, deps, limit) {
40866
42250
  const projectClause = projectConjunctFor(request);
40867
42251
  let scored;
40868
42252
  try {
40869
- const recall = await vectorSearch(deps.storage, request.scope, {
42253
+ const pool = resolveRecallPool(deps);
42254
+ const recall = await pool.run(() => vectorSearch(deps.storage, request.scope, {
40870
42255
  table: spec.table,
40871
42256
  idColumn: spec.idColumn,
40872
42257
  embeddingColumn: spec.embeddingColumn,
@@ -40874,7 +42259,7 @@ async function runSemanticArm(spec, queryVector, request, deps, limit) {
40874
42259
  scope: {},
40875
42260
  limit,
40876
42261
  ...projectClause !== "" ? { extraClause: projectClause } : {}
40877
- });
42262
+ }));
40878
42263
  scored = recall.ids;
40879
42264
  } catch {
40880
42265
  return [];
@@ -41388,6 +42773,19 @@ async function recallMemories(request, deps) {
41388
42773
  }
41389
42774
 
41390
42775
  // dist/src/daemon/runtime/secrets/api.js
42776
+ function localDefaultScopeResolver(mode, defaultScope) {
42777
+ return {
42778
+ resolve(c) {
42779
+ const fromHeader = headerScopeResolver2.resolve(c);
42780
+ if (fromHeader !== null)
42781
+ return fromHeader;
42782
+ if (mode === "local" && defaultScope !== void 0) {
42783
+ return { org: defaultScope.org, workspace: defaultScope.workspace ?? "default" };
42784
+ }
42785
+ return null;
42786
+ }
42787
+ };
42788
+ }
41391
42789
  var headerScopeResolver2 = {
41392
42790
  resolve(c) {
41393
42791
  const org = c.req.header("x-honeycomb-org");
@@ -41911,12 +43309,12 @@ var DEFAULT_DEAD_JOB_RETENTION_MS = 30 * 24 * 60 * 60 * 1e3;
41911
43309
  var DEFAULT_HISTORY_RETENTION_MS = 90 * 24 * 60 * 60 * 1e3;
41912
43310
  var DEFAULT_TOMBSTONE_RETENTION_MS = 30 * 24 * 60 * 60 * 1e3;
41913
43311
  var EXTRACTION_PROVIDER_NONE = "none";
41914
- var BoolFlag3 = external_exports.preprocess((raw2) => {
43312
+ var BoolFlag4 = external_exports.preprocess((raw2) => {
41915
43313
  if (typeof raw2 === "boolean")
41916
43314
  return raw2;
41917
43315
  return raw2 === "true" || raw2 === "1";
41918
43316
  }, external_exports.boolean());
41919
- function ClampedInt4(def, min = 1) {
43317
+ function ClampedInt5(def, min = 1) {
41920
43318
  return external_exports.preprocess((raw2) => {
41921
43319
  const n = typeof raw2 === "number" ? raw2 : Number(raw2);
41922
43320
  if (!Number.isFinite(n))
@@ -41932,49 +43330,49 @@ var Confidence = external_exports.preprocess((raw2) => {
41932
43330
  }, external_exports.number());
41933
43331
  var AutonomousConfigSchema = external_exports.object({
41934
43332
  /** Master switch for retention/autonomous mutation; off → retention does not run (006e e-AC-4). */
41935
- enabled: BoolFlag3.default(false),
43333
+ enabled: BoolFlag4.default(false),
41936
43334
  /** Halt switch: set → no further purges even when enabled (006e e-AC-5). */
41937
- frozen: BoolFlag3.default(false),
43335
+ frozen: BoolFlag4.default(false),
41938
43336
  /** Gate for UPDATE/DELETE proposals being applied (006c c-AC-3 / D-7). */
41939
- allowUpdateDelete: BoolFlag3.default(false)
43337
+ allowUpdateDelete: BoolFlag4.default(false)
41940
43338
  });
41941
43339
  var GraphConfigSchema = external_exports.object({
41942
43340
  /** Master graph switch; off → no graph rows written (006d d-AC-4). */
41943
- enabled: BoolFlag3.default(false),
43341
+ enabled: BoolFlag4.default(false),
41944
43342
  /** Whether extraction-derived triples are persisted to the graph (006d d-AC-4). */
41945
- extractionWritesEnabled: BoolFlag3.default(false)
43343
+ extractionWritesEnabled: BoolFlag4.default(false)
41946
43344
  });
41947
43345
  var ExtractionConfigSchema = external_exports.object({
41948
43346
  /** Input char cap before the model call (a-AC-2 / FR-6). */
41949
- inputCharCap: ClampedInt4(DEFAULT_INPUT_CHAR_CAP).default(DEFAULT_INPUT_CHAR_CAP),
43347
+ inputCharCap: ClampedInt5(DEFAULT_INPUT_CHAR_CAP).default(DEFAULT_INPUT_CHAR_CAP),
41950
43348
  /** Max facts kept (a-AC-3 / FR-7). */
41951
- maxFacts: ClampedInt4(DEFAULT_MAX_FACTS).default(DEFAULT_MAX_FACTS),
43349
+ maxFacts: ClampedInt5(DEFAULT_MAX_FACTS).default(DEFAULT_MAX_FACTS),
41952
43350
  /** Max entity triples kept (a-AC-3 / FR-7). */
41953
- maxEntities: ClampedInt4(DEFAULT_MAX_ENTITIES).default(DEFAULT_MAX_ENTITIES),
43351
+ maxEntities: ClampedInt5(DEFAULT_MAX_ENTITIES).default(DEFAULT_MAX_ENTITIES),
41954
43352
  /** Per-fact content length cap (a-AC-3 / FR-7). */
41955
- maxFactChars: ClampedInt4(DEFAULT_MAX_FACT_CHARS).default(DEFAULT_MAX_FACT_CHARS)
43353
+ maxFactChars: ClampedInt5(DEFAULT_MAX_FACT_CHARS).default(DEFAULT_MAX_FACT_CHARS)
41956
43354
  });
41957
43355
  var RetentionConfigSchema = external_exports.object({
41958
43356
  /** Per-run row batch cap (e-AC-1 / e-AC-6). */
41959
- batchLimit: ClampedInt4(DEFAULT_RETENTION_BATCH_LIMIT).default(DEFAULT_RETENTION_BATCH_LIMIT),
43357
+ batchLimit: ClampedInt5(DEFAULT_RETENTION_BATCH_LIMIT).default(DEFAULT_RETENTION_BATCH_LIMIT),
41960
43358
  /** Completed-jobs window in ms. */
41961
- completedJobMs: ClampedInt4(DEFAULT_COMPLETED_JOB_RETENTION_MS).default(DEFAULT_COMPLETED_JOB_RETENTION_MS),
43359
+ completedJobMs: ClampedInt5(DEFAULT_COMPLETED_JOB_RETENTION_MS).default(DEFAULT_COMPLETED_JOB_RETENTION_MS),
41962
43360
  /** Dead-jobs window in ms. */
41963
- deadJobMs: ClampedInt4(DEFAULT_DEAD_JOB_RETENTION_MS).default(DEFAULT_DEAD_JOB_RETENTION_MS),
43361
+ deadJobMs: ClampedInt5(DEFAULT_DEAD_JOB_RETENTION_MS).default(DEFAULT_DEAD_JOB_RETENTION_MS),
41964
43362
  /** History window in ms. */
41965
- historyMs: ClampedInt4(DEFAULT_HISTORY_RETENTION_MS).default(DEFAULT_HISTORY_RETENTION_MS),
43363
+ historyMs: ClampedInt5(DEFAULT_HISTORY_RETENTION_MS).default(DEFAULT_HISTORY_RETENTION_MS),
41966
43364
  /** Tombstone window in ms. */
41967
- tombstoneMs: ClampedInt4(DEFAULT_TOMBSTONE_RETENTION_MS).default(DEFAULT_TOMBSTONE_RETENTION_MS)
43365
+ tombstoneMs: ClampedInt5(DEFAULT_TOMBSTONE_RETENTION_MS).default(DEFAULT_TOMBSTONE_RETENTION_MS)
41968
43366
  });
41969
43367
  var PipelineConfigSchema = external_exports.object({
41970
43368
  /** Master switch; off → no stage runs (a-AC-5 / FR-9). */
41971
- enabled: BoolFlag3.default(false),
43369
+ enabled: BoolFlag4.default(false),
41972
43370
  /** Router-selection token; `'none'` disables extraction (a-AC-5 / FR-9). */
41973
43371
  extractionProvider: external_exports.string().trim().min(1).default(EXTRACTION_PROVIDER_NONE),
41974
43372
  /** Shadow mode: proposals logged, no memory written (006c c-AC-4). */
41975
- shadowMode: BoolFlag3.default(false),
43373
+ shadowMode: BoolFlag4.default(false),
41976
43374
  /** Frozen: nothing written even if shadow off; supersedes shadow (006c c-AC-5). */
41977
- mutationsFrozen: BoolFlag3.default(false),
43375
+ mutationsFrozen: BoolFlag4.default(false),
41978
43376
  /** ADD confidence gate (D-1 / 006c c-AC-1). */
41979
43377
  minFactConfidenceForWrite: Confidence.default(DEFAULT_MIN_FACT_CONFIDENCE),
41980
43378
  /** Autonomous brakes (retention + UPDATE/DELETE). */
@@ -42125,6 +43523,7 @@ function normalizeProposalKeys(candidate) {
42125
43523
  // dist/src/daemon/runtime/pipeline/controlled-writes.js
42126
43524
  var silentLogger4 = { event() {
42127
43525
  } };
43526
+ var CONTROLLED_WRITE_BATCH_KEY = "proposals";
42128
43527
  var MEMORIES_VERSION_COLUMN = Object.freeze({
42129
43528
  name: "version",
42130
43529
  sql: "BIGINT NOT NULL DEFAULT 1"
@@ -42474,33 +43873,66 @@ var noopControlledWriteHandler = async (_job) => {
42474
43873
  function createControlledWriteHandler(deps) {
42475
43874
  if (deps === void 0)
42476
43875
  return noopControlledWriteHandler;
43876
+ const handlerDeps = deps;
42477
43877
  const logger = deps.logger ?? silentLogger4;
42478
43878
  return async (job) => {
42479
- const parsed = readControlledWriteInput(job.payload);
42480
- if (parsed === null) {
42481
- logger.event("controlled_write.unparseable_payload", { id: job.id });
43879
+ const batch = readBatchPayloads(job.payload);
43880
+ if (batch !== null) {
43881
+ for (const factPayload of batch) {
43882
+ const merged = { ...envelopeOf(job.payload), ...factPayload };
43883
+ const factJob = { ...job, payload: merged };
43884
+ await applyOneControlledWrite(factJob, handlerDeps, logger);
43885
+ }
42482
43886
  return;
42483
43887
  }
42484
- const input = {
42485
- ...parsed,
42486
- agentId: parsed.agentId ?? job.scope.agentId
42487
- };
42488
- const scope = { org: job.scope.org, workspace: job.scope.workspace };
42489
- const outcome = await applyControlledWrite(input, scope, deps);
42490
- if (deps.onOutcome) {
42491
- try {
42492
- await deps.onOutcome(job, outcome);
42493
- } catch (err) {
42494
- const reason = err instanceof Error ? err.message : String(err);
42495
- logger.event("controlled_write.fan_out_failed", {
42496
- id: job.id,
42497
- action: outcome.action,
42498
- memoryId: outcome.memoryId,
42499
- reason
42500
- });
42501
- }
43888
+ await applyOneControlledWrite(job, handlerDeps, logger);
43889
+ };
43890
+ }
43891
+ function envelopeOf(payload) {
43892
+ const out = {};
43893
+ for (const key of ["org", "workspace", "agent_id", "project_id"]) {
43894
+ if (typeof payload[key] === "string")
43895
+ out[key] = payload[key];
43896
+ }
43897
+ return out;
43898
+ }
43899
+ function readBatchPayloads(payload) {
43900
+ const raw2 = payload[CONTROLLED_WRITE_BATCH_KEY];
43901
+ if (!Array.isArray(raw2) || raw2.length === 0)
43902
+ return null;
43903
+ const out = [];
43904
+ for (const item of raw2) {
43905
+ if (item !== null && typeof item === "object" && !Array.isArray(item)) {
43906
+ out.push(item);
42502
43907
  }
43908
+ }
43909
+ return out.length > 0 ? out : null;
43910
+ }
43911
+ async function applyOneControlledWrite(job, deps, logger) {
43912
+ const parsed = readControlledWriteInput(job.payload);
43913
+ if (parsed === null) {
43914
+ logger.event("controlled_write.unparseable_payload", { id: job.id });
43915
+ return;
43916
+ }
43917
+ const input = {
43918
+ ...parsed,
43919
+ agentId: parsed.agentId ?? job.scope.agentId
42503
43920
  };
43921
+ const scope = { org: job.scope.org, workspace: job.scope.workspace };
43922
+ const outcome = await applyControlledWrite(input, scope, deps);
43923
+ if (deps.onOutcome) {
43924
+ try {
43925
+ await deps.onOutcome(job, outcome);
43926
+ } catch (err) {
43927
+ const reason = err instanceof Error ? err.message : String(err);
43928
+ logger.event("controlled_write.fan_out_failed", {
43929
+ id: job.id,
43930
+ action: outcome.action,
43931
+ memoryId: outcome.memoryId,
43932
+ reason
43933
+ });
43934
+ }
43935
+ }
42504
43936
  }
42505
43937
 
42506
43938
  // dist/src/daemon/runtime/memories/store.js
@@ -43609,7 +45041,7 @@ var DEFAULT_OPEN_CONFLICT_SUPPRESSION = DEFAULT_RHO;
43609
45041
  var DEFAULT_CONFLICT_AUTO_RESOLVE = false;
43610
45042
  var STALE_REF_POSTURES = Object.freeze(["observe", "execute"]);
43611
45043
  var DEFAULT_STALE_REF_POSTURE = "observe";
43612
- var BoolFlag4 = external_exports.preprocess((raw2) => {
45044
+ var BoolFlag5 = external_exports.preprocess((raw2) => {
43613
45045
  if (typeof raw2 === "boolean")
43614
45046
  return raw2;
43615
45047
  return raw2 === "true" || raw2 === "1";
@@ -43664,7 +45096,7 @@ var LifecycleConfigSchema = external_exports.object({
43664
45096
  /** Open-conflict suppression `ρ` (058b); `0` default (fully suppress, reversible), clamped into `[0,1]`. */
43665
45097
  openConflictSuppression: ClampedFloat2(DEFAULT_OPEN_CONFLICT_SUPPRESSION, 1).default(DEFAULT_OPEN_CONFLICT_SUPPRESSION),
43666
45098
  /** Conflict auto-resolve (058b); OFF by default (detect + queue only, human-in-the-loop). */
43667
- conflictAutoResolve: BoolFlag4.default(DEFAULT_CONFLICT_AUTO_RESOLVE),
45099
+ conflictAutoResolve: BoolFlag5.default(DEFAULT_CONFLICT_AUTO_RESOLVE),
43668
45100
  /** Stale-ref posture (058c); `observe` by default (`s = 0`, inert). */
43669
45101
  staleRefPosture: PostureFlag.default(DEFAULT_STALE_REF_POSTURE)
43670
45102
  });
@@ -46179,8 +47611,8 @@ function realDiscordProvider(cfg, transport) {
46179
47611
  }
46180
47612
  }
46181
47613
  async function* indexDesktopCache() {
46182
- const cached2 = await transport.readDesktopCache();
46183
- for (const m of cached2) {
47614
+ const cached3 = await transport.readDesktopCache();
47615
+ for (const m of cached3) {
46184
47616
  yield messageArtifact(cfg, m);
46185
47617
  }
46186
47618
  }
@@ -48850,12 +50282,34 @@ var RouterModelClient = class {
48850
50282
  var ANTHROPIC_MESSAGES_URL = "https://api.anthropic.com/v1/messages";
48851
50283
  var ANTHROPIC_VERSION = "2023-06-01";
48852
50284
  var DEFAULT_MAX_TOKENS = 4096;
50285
+ var noopUsageSink = {
50286
+ record() {
50287
+ }
50288
+ };
48853
50289
  var AnthropicContentBlockSchema = external_exports.object({
48854
50290
  type: external_exports.string(),
48855
50291
  text: external_exports.string().optional()
48856
50292
  });
50293
+ var AnthropicUsageSchema = external_exports.object({
50294
+ input_tokens: external_exports.number().int().nonnegative().catch(0).default(0),
50295
+ output_tokens: external_exports.number().int().nonnegative().catch(0).default(0),
50296
+ cache_read_input_tokens: external_exports.number().int().nonnegative().catch(0).default(0),
50297
+ cache_creation_input_tokens: external_exports.number().int().nonnegative().catch(0).default(0)
50298
+ });
50299
+ var ZERO_USAGE = {
50300
+ input_tokens: 0,
50301
+ output_tokens: 0,
50302
+ cache_read_input_tokens: 0,
50303
+ cache_creation_input_tokens: 0
50304
+ };
48857
50305
  var AnthropicMessagesResponseSchema = external_exports.object({
48858
- content: external_exports.array(AnthropicContentBlockSchema).default([])
50306
+ content: external_exports.array(AnthropicContentBlockSchema).default([]),
50307
+ // Finding (usage-failsoft): `.default(ZERO_USAGE)` only covers `usage === undefined`. A response with
50308
+ // `usage: null` or a malformed `usage` object would FAIL the object parse and -- because this field is
50309
+ // part of the whole-response schema -- bubble up to a 502 that DROPS an otherwise-valid completion.
50310
+ // `.catch(ZERO_USAGE)` makes ANY invalid value (null, a string, a malformed object) fall back to
50311
+ // zero-usage so a valid completion is NEVER dropped over its (side-channel) usage block.
50312
+ usage: AnthropicUsageSchema.default(ZERO_USAGE).catch(ZERO_USAGE)
48859
50313
  });
48860
50314
  function toAnthropicBody(call, defaultMaxTokens) {
48861
50315
  const systemParts = [];
@@ -48883,6 +50337,7 @@ function createAnthropicTransport(deps = {}) {
48883
50337
  const doFetch = deps.fetch ?? globalThis.fetch;
48884
50338
  const url2 = deps.baseUrl ?? ANTHROPIC_MESSAGES_URL;
48885
50339
  const defaultMaxTokens = deps.defaultMaxTokens ?? DEFAULT_MAX_TOKENS;
50340
+ const usageSink = deps.usageSink ?? noopUsageSink;
48886
50341
  async function post(call) {
48887
50342
  const body = toAnthropicBody(call, defaultMaxTokens);
48888
50343
  let res;
@@ -48908,16 +50363,33 @@ function createAnthropicTransport(deps = {}) {
48908
50363
  if (!parsed.success) {
48909
50364
  throw new ProviderError(502, "anthropic transport: malformed provider response");
48910
50365
  }
48911
- return joinContentText(parsed.data.content);
50366
+ const u = parsed.data.usage;
50367
+ const usage = {
50368
+ model: call.target.model,
50369
+ workload: call.request.workload,
50370
+ inputTokens: u.input_tokens,
50371
+ outputTokens: u.output_tokens,
50372
+ cacheReadInputTokens: u.cache_read_input_tokens,
50373
+ cacheCreationInputTokens: u.cache_creation_input_tokens
50374
+ };
50375
+ return { output: joinContentText(parsed.data.content), usage };
50376
+ }
50377
+ function reportUsage(usage) {
50378
+ try {
50379
+ usageSink.record(usage);
50380
+ } catch {
50381
+ }
48912
50382
  }
48913
50383
  return {
48914
50384
  async execute(call) {
48915
- const output = await post(call);
50385
+ const { output, usage } = await post(call);
50386
+ reportUsage(usage);
48916
50387
  return { output };
48917
50388
  },
48918
50389
  stream(call) {
48919
50390
  async function* gen() {
48920
- const output = await post(call);
50391
+ const { output, usage } = await post(call);
50392
+ reportUsage(usage);
48921
50393
  yield { delta: output };
48922
50394
  }
48923
50395
  return gen();
@@ -48962,7 +50434,7 @@ async function buildInferenceModelClient(deps) {
48962
50434
  }
48963
50435
  const effectiveConfig = deps.providerModelOverride !== void 0 ? applyProviderModelOverride(config2, deps.providerModelOverride) : config2;
48964
50436
  const secrets = createSecretResolver(deps.secretsStore, deps.scope);
48965
- const transport = createAnthropicTransport();
50437
+ const transport = createAnthropicTransport(deps.usageSink !== void 0 ? { usageSink: deps.usageSink } : {});
48966
50438
  const router = createInferenceRouter({
48967
50439
  config: effectiveConfig,
48968
50440
  transport,
@@ -48972,6 +50444,216 @@ async function buildInferenceModelClient(deps) {
48972
50444
  return new RouterModelClient(router);
48973
50445
  }
48974
50446
 
50447
+ // dist/src/daemon/runtime/services/poll-backoff.js
50448
+ var DEFAULT_POLL_BACKOFF_FLOOR_MS = 1e3;
50449
+ var DEFAULT_POLL_BACKOFF_CEILING_MS = 3e4;
50450
+ var DEFAULT_POLL_BACKOFF_JITTER = 0.1;
50451
+ var BoolFlag6 = external_exports.preprocess((raw2) => {
50452
+ if (typeof raw2 === "boolean")
50453
+ return raw2;
50454
+ return raw2 === "true" || raw2 === "1";
50455
+ }, external_exports.boolean());
50456
+ function ClampedInt6(def, min = 1) {
50457
+ return external_exports.preprocess((raw2) => {
50458
+ const n = typeof raw2 === "number" ? raw2 : Number(raw2);
50459
+ if (!Number.isFinite(n))
50460
+ return def;
50461
+ return Math.max(min, Math.trunc(n));
50462
+ }, external_exports.number().int());
50463
+ }
50464
+ function ClampedFraction(def) {
50465
+ return external_exports.preprocess((raw2) => {
50466
+ const n = typeof raw2 === "number" ? raw2 : Number(raw2);
50467
+ if (!Number.isFinite(n))
50468
+ return def;
50469
+ return Math.min(1, Math.max(0, n));
50470
+ }, external_exports.number());
50471
+ }
50472
+ var PollBackoffConfigSchema = external_exports.object({
50473
+ /** Master switch; off → flat `floorMs`, the exact pre-PRD cadence (AC-9). */
50474
+ enabled: BoolFlag6.default(false),
50475
+ /** Fast floor the backoff starts at and resets to on any lease (AC-3). */
50476
+ floorMs: ClampedInt6(DEFAULT_POLL_BACKOFF_FLOOR_MS).default(DEFAULT_POLL_BACKOFF_FLOOR_MS),
50477
+ /** Ceiling the backoff doubles toward while the queue stays empty (AC-2). */
50478
+ ceilingMs: ClampedInt6(DEFAULT_POLL_BACKOFF_CEILING_MS).default(DEFAULT_POLL_BACKOFF_CEILING_MS),
50479
+ /** +/- jitter fraction of the current step, anti-thundering-herd. */
50480
+ jitter: ClampedFraction(DEFAULT_POLL_BACKOFF_JITTER).default(DEFAULT_POLL_BACKOFF_JITTER)
50481
+ });
50482
+ var PollBackoffConfigError = class extends Error {
50483
+ issues;
50484
+ constructor(issues) {
50485
+ super(`Invalid poll-backoff config: ${issues.join("; ")}`);
50486
+ this.name = "PollBackoffConfigError";
50487
+ this.issues = issues;
50488
+ }
50489
+ };
50490
+ function envPollBackoffConfigProvider(env = process.env) {
50491
+ return {
50492
+ read() {
50493
+ const raw2 = env.HONEYCOMB_POLL_BACKOFF_ENABLED;
50494
+ const enabled = raw2 === void 0 ? true : raw2;
50495
+ return {
50496
+ enabled,
50497
+ floorMs: env.HONEYCOMB_POLL_BACKOFF_FLOOR_MS,
50498
+ ceilingMs: env.HONEYCOMB_POLL_BACKOFF_CEILING_MS,
50499
+ jitter: env.HONEYCOMB_POLL_BACKOFF_JITTER
50500
+ };
50501
+ }
50502
+ };
50503
+ }
50504
+ function resolvePollBackoffConfig(provider = envPollBackoffConfigProvider()) {
50505
+ const parsed = PollBackoffConfigSchema.safeParse(provider.read());
50506
+ if (!parsed.success) {
50507
+ const issues = parsed.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`);
50508
+ throw new PollBackoffConfigError(issues);
50509
+ }
50510
+ const cfg = parsed.data;
50511
+ return cfg.ceilingMs < cfg.floorMs ? { ...cfg, ceilingMs: cfg.floorMs } : cfg;
50512
+ }
50513
+ var defaultJitterSource = () => Math.random() * 2 - 1;
50514
+ var PollBackoff = class {
50515
+ floorMs;
50516
+ ceilingMs;
50517
+ jitter;
50518
+ jitterSource;
50519
+ /** The current un-jittered step. Starts at the floor; doubles toward the ceiling. */
50520
+ stepMs;
50521
+ /**
50522
+ * @param config the resolved bounds + jitter fraction. The state machine is only
50523
+ * ever CONSTRUCTED when backoff is active; a loop with `config.enabled === false`
50524
+ * does not build one (it keeps its flat legacy interval), so this class need not
50525
+ * re-check the flag.
50526
+ * @param jitterSource injectable `[-1, 1]` source — a test pins it to 0 to assert
50527
+ * the exact geometric schedule; production uses the uniform random default.
50528
+ */
50529
+ constructor(config2, jitterSource = defaultJitterSource) {
50530
+ this.floorMs = config2.floorMs;
50531
+ this.ceilingMs = Math.max(config2.floorMs, config2.ceilingMs);
50532
+ this.jitter = config2.jitter;
50533
+ this.jitterSource = jitterSource;
50534
+ this.stepMs = this.floorMs;
50535
+ }
50536
+ /**
50537
+ * Step the delay toward the ceiling after an EMPTY lease (no job this tick):
50538
+ * double the current step, capped at the ceiling. Idempotent at the ceiling (a
50539
+ * fully-idle daemon stays there until a job arrives).
50540
+ */
50541
+ onEmptyLease() {
50542
+ this.stepMs = Math.min(this.stepMs * 2, this.ceilingMs);
50543
+ }
50544
+ /**
50545
+ * Reset the delay to the floor after a SUCCESSFUL lease (AC-3): the first real
50546
+ * job snaps the cadence back to fast so an active session is unchanged.
50547
+ */
50548
+ onLease() {
50549
+ this.stepMs = this.floorMs;
50550
+ }
50551
+ /** The current un-jittered step (for assertions on the geometric schedule). */
50552
+ currentStepMs() {
50553
+ return this.stepMs;
50554
+ }
50555
+ /**
50556
+ * The ms to wait before the next tick: the current step plus a bounded jitter.
50557
+ * The jitter is a fraction of the step (`jitter * step`), so it scales with the
50558
+ * step; the result is clamped to `[floorMs, ceilingMs]` so jitter never pushes
50559
+ * the cadence below the fast floor (which would defeat the cost cut) or above the
50560
+ * ceiling (which would blow the worst-case pickup-latency budget).
50561
+ */
50562
+ nextDelayMs() {
50563
+ const offset = this.jitterSource() * this.jitter * this.stepMs;
50564
+ const delayed = this.stepMs + offset;
50565
+ return Math.min(this.ceilingMs, Math.max(this.floorMs, Math.round(delayed)));
50566
+ }
50567
+ };
50568
+
50569
+ // dist/src/daemon/runtime/services/poll-loop.js
50570
+ var AdaptivePollLoop = class {
50571
+ tick;
50572
+ backoffEnabled;
50573
+ flatIntervalMs;
50574
+ timers;
50575
+ backoff;
50576
+ handle;
50577
+ stopped = false;
50578
+ /** Guards against overlapping ticks on the poll loop (the workers' `running` flag). */
50579
+ running = false;
50580
+ constructor(deps) {
50581
+ this.tick = deps.tick;
50582
+ this.backoffEnabled = deps.backoff.enabled;
50583
+ this.flatIntervalMs = deps.flatIntervalMs;
50584
+ this.timers = deps.timers;
50585
+ this.backoff = deps.backoff.enabled ? new PollBackoff(deps.backoff) : null;
50586
+ }
50587
+ start() {
50588
+ this.stopped = false;
50589
+ if (this.backoff === null) {
50590
+ this.handle = this.timers.setTimer(() => {
50591
+ this.fireGuarded(null);
50592
+ }, this.flatIntervalMs);
50593
+ return;
50594
+ }
50595
+ this.scheduleNext(this.backoff.nextDelayMs());
50596
+ }
50597
+ /** Arm the next one-shot tick (adaptive path only). */
50598
+ scheduleNext(ms) {
50599
+ if (this.stopped)
50600
+ return;
50601
+ this.handle = this.timers.setTimer(() => {
50602
+ this.fireGuarded(this.backoff);
50603
+ }, ms);
50604
+ }
50605
+ /**
50606
+ * One guarded tick. Skips if a previous run is still in flight (never overlap).
50607
+ * On the adaptive path, feeds the lease outcome to the state machine and re-arms
50608
+ * the next tick; on the flat path, the repeating interval re-fires on its own.
50609
+ */
50610
+ fireGuarded(backoff) {
50611
+ if (this.running)
50612
+ return;
50613
+ this.running = true;
50614
+ void this.tick().then((processed) => {
50615
+ if (backoff === null)
50616
+ return;
50617
+ if (processed)
50618
+ backoff.onLease();
50619
+ else
50620
+ backoff.onEmptyLease();
50621
+ }).finally(() => {
50622
+ this.running = false;
50623
+ if (backoff !== null)
50624
+ this.scheduleNext(backoff.nextDelayMs());
50625
+ });
50626
+ }
50627
+ stop() {
50628
+ this.stopped = true;
50629
+ if (this.handle !== void 0) {
50630
+ this.timers.clearTimer(this.handle);
50631
+ this.handle = void 0;
50632
+ }
50633
+ }
50634
+ };
50635
+ function createPollLoop(deps) {
50636
+ return new AdaptivePollLoop(deps);
50637
+ }
50638
+ function buildWorkerPollLoop(options) {
50639
+ const setTimer = options.setTimer ?? ((cb, ms) => {
50640
+ const t = setInterval(cb, ms);
50641
+ if (typeof t === "object" && t !== null && "unref" in t && typeof t.unref === "function")
50642
+ t.unref();
50643
+ return t;
50644
+ });
50645
+ const clearTimer = options.clearTimer ?? ((handle) => {
50646
+ if (handle !== void 0)
50647
+ clearInterval(handle);
50648
+ });
50649
+ return createPollLoop({
50650
+ tick: options.tick,
50651
+ backoff: options.backoff ?? PollBackoffConfigSchema.parse({}),
50652
+ flatIntervalMs: options.flatIntervalMs,
50653
+ timers: { setTimer, clearTimer }
50654
+ });
50655
+ }
50656
+
48975
50657
  // dist/src/daemon/runtime/pipeline/stage-worker.js
48976
50658
  var PIPELINE_JOB_KINDS = Object.freeze([
48977
50659
  "memory_extraction",
@@ -48999,33 +50681,42 @@ var PipelineStageWorker = class {
48999
50681
  queue;
49000
50682
  handlers;
49001
50683
  logger;
50684
+ /** Public for the lease coordinator's union (the kinds this participant owns). */
49002
50685
  leaseKinds;
49003
- pollIntervalMs;
49004
- setTimer;
49005
- clearTimer;
49006
- handle;
49007
- /** Guards against overlapping `runOnce` invocations on the poll loop. */
49008
- running = false;
50686
+ loop;
49009
50687
  constructor(deps) {
49010
50688
  this.queue = deps.queue;
49011
50689
  this.handlers = deps.handlers;
49012
50690
  this.logger = deps.logger;
49013
50691
  this.leaseKinds = deps.leaseKinds ?? PIPELINE_JOB_KINDS;
49014
- this.pollIntervalMs = deps.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS2;
49015
- this.setTimer = deps.setTimer ?? ((cb, ms) => setInterval(cb, ms));
49016
- this.clearTimer = deps.clearTimer ?? ((handle) => {
49017
- if (handle !== void 0)
49018
- clearInterval(handle);
50692
+ this.loop = buildWorkerPollLoop({
50693
+ tick: () => this.runOnce(),
50694
+ flatIntervalMs: deps.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS2,
50695
+ backoff: deps.backoff,
50696
+ setTimer: deps.setTimer,
50697
+ clearTimer: deps.clearTimer
49019
50698
  });
49020
50699
  }
49021
50700
  async runOnce() {
49022
50701
  const leased = await this.queue.lease(this.leaseKinds);
49023
50702
  if (leased === null)
49024
50703
  return false;
50704
+ await this.processLeased(leased);
50705
+ return true;
50706
+ }
50707
+ /**
50708
+ * PRD-062b (AC-4): process ONE already-leased pipeline job — route it by kind, run
50709
+ * the handler, and complete/fail it. Split out of {@link runOnce} so the single
50710
+ * combined lease coordinator can dispatch a job IT leased (over the union of kinds)
50711
+ * to this participant without a second lease. The standalone `runOnce` leases then
50712
+ * calls this; both paths share the identical route+run+complete/fail body, so kind
50713
+ * isolation and the no-swallowed-error contract hold whether consolidation is on or off.
50714
+ */
50715
+ async processLeased(leased) {
49025
50716
  if (!isPipelineJobKind(leased.kind)) {
49026
50717
  this.logger?.event("stage.unknown_kind", { id: leased.id, kind: leased.kind });
49027
50718
  await this.queue.fail(leased.id, `unknown pipeline job kind: ${leased.kind}`);
49028
- return true;
50719
+ return;
49029
50720
  }
49030
50721
  const job = toStageJob(leased, leased.kind);
49031
50722
  const handler = this.handlers[leased.kind];
@@ -49038,23 +50729,12 @@ var PipelineStageWorker = class {
49038
50729
  this.logger?.event("stage.failed", { id: job.id, kind: job.kind, attempt: job.attempt, reason });
49039
50730
  await this.queue.fail(job.id, reason);
49040
50731
  }
49041
- return true;
49042
50732
  }
49043
50733
  start() {
49044
- this.handle = this.setTimer(() => {
49045
- if (this.running)
49046
- return;
49047
- this.running = true;
49048
- void this.runOnce().finally(() => {
49049
- this.running = false;
49050
- });
49051
- }, this.pollIntervalMs);
50734
+ this.loop.start();
49052
50735
  }
49053
50736
  stop() {
49054
- if (this.handle !== void 0) {
49055
- this.clearTimer(this.handle);
49056
- this.handle = void 0;
49057
- }
50737
+ this.loop.stop();
49058
50738
  }
49059
50739
  };
49060
50740
  function createStageWorker(deps) {
@@ -49894,34 +51574,45 @@ function extractionFanOut(queue) {
49894
51574
  });
49895
51575
  };
49896
51576
  }
49897
- function decisionFanOut(queue) {
51577
+ function buildControlledWritePayload(decision, entities) {
51578
+ const proposal = {
51579
+ action: decision.proposal.action,
51580
+ confidence: decision.proposal.confidence,
51581
+ reason: decision.proposal.reason
51582
+ };
51583
+ if (decision.proposal.targetId !== void 0 && decision.proposal.targetId !== "") {
51584
+ proposal.target_id = decision.proposal.targetId;
51585
+ }
51586
+ const targetId = decision.proposal.targetId;
51587
+ const candidates = decision.candidates.filter((c) => c.id !== "" && c.id !== targetId && typeof c.content === "string" && c.content !== "").map((c) => ({ id: c.id, content: c.content }));
51588
+ return {
51589
+ proposal,
51590
+ content: decision.fact.content,
51591
+ normalized_content: decision.fact.content,
51592
+ fact_confidence: decision.fact.confidence,
51593
+ fact_type: decision.fact.type,
51594
+ entities: serializeEntities(entities),
51595
+ ...candidates.length > 0 ? { candidates } : {}
51596
+ };
51597
+ }
51598
+ function decisionFanOut(queue, config2 = amplificationConfig()) {
49898
51599
  return async (job, decisions) => {
49899
51600
  const entities = readForwardedEntities(job.payload);
49900
- for (const decision of decisions) {
49901
- if (decision.proposal.action === "none")
49902
- continue;
49903
- const proposal = {
49904
- action: decision.proposal.action,
49905
- confidence: decision.proposal.confidence,
49906
- reason: decision.proposal.reason
49907
- };
49908
- if (decision.proposal.targetId !== void 0 && decision.proposal.targetId !== "") {
49909
- proposal.target_id = decision.proposal.targetId;
49910
- }
49911
- const targetId = decision.proposal.targetId;
49912
- const candidates = decision.candidates.filter((c) => c.id !== "" && c.id !== targetId && typeof c.content === "string" && c.content !== "").map((c) => ({ id: c.id, content: c.content }));
51601
+ const envelope = scopeEnvelope(job.scope);
51602
+ const facts = decisions.filter((d) => d.proposal.action !== "none").map((d) => buildControlledWritePayload(d, entities));
51603
+ if (facts.length === 0)
51604
+ return;
51605
+ if (config2.fanoutBatch) {
49913
51606
  await queue.enqueue({
49914
51607
  kind: "memory_controlled_write",
49915
- payload: {
49916
- ...scopeEnvelope(job.scope),
49917
- proposal,
49918
- content: decision.fact.content,
49919
- normalized_content: decision.fact.content,
49920
- fact_confidence: decision.fact.confidence,
49921
- fact_type: decision.fact.type,
49922
- entities: serializeEntities(entities),
49923
- ...candidates.length > 0 ? { candidates } : {}
49924
- }
51608
+ payload: { ...envelope, [CONTROLLED_WRITE_BATCH_KEY]: facts }
51609
+ });
51610
+ return;
51611
+ }
51612
+ for (const fact of facts) {
51613
+ await queue.enqueue({
51614
+ kind: "memory_controlled_write",
51615
+ payload: { ...envelope, ...fact }
49925
51616
  });
49926
51617
  }
49927
51618
  };
@@ -50463,6 +52154,8 @@ function stateUpdaterFromTrigger(trigger) {
50463
52154
  };
50464
52155
  }
50465
52156
  var PollinatingJobWorkerImpl = class {
52157
+ /** Public for the lease coordinator's union — the single `pollinating` kind. */
52158
+ leaseKinds = LEASE_KINDS2;
50466
52159
  queue;
50467
52160
  storage;
50468
52161
  scope;
@@ -50472,12 +52165,7 @@ var PollinatingJobWorkerImpl = class {
50472
52165
  stateUpdater;
50473
52166
  logger;
50474
52167
  clock;
50475
- pollIntervalMs;
50476
- setTimer;
50477
- clearTimer;
50478
- handle;
50479
- /** Guards against overlapping `runOnce` invocations on the poll loop. */
50480
- running = false;
52168
+ loop;
50481
52169
  constructor(deps) {
50482
52170
  this.queue = deps.queue;
50483
52171
  this.storage = deps.storage;
@@ -50488,22 +52176,36 @@ var PollinatingJobWorkerImpl = class {
50488
52176
  this.stateUpdater = deps.stateUpdater ?? stateUpdaterFromTrigger(deps.trigger);
50489
52177
  this.logger = deps.logger;
50490
52178
  this.clock = deps.clock ?? { now: () => Date.now() };
50491
- this.pollIntervalMs = deps.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS3;
50492
- this.setTimer = deps.setTimer ?? ((cb, ms) => setInterval(cb, ms));
50493
- this.clearTimer = deps.clearTimer ?? ((handle) => {
50494
- if (handle !== void 0)
50495
- clearInterval(handle);
52179
+ this.loop = buildWorkerPollLoop({
52180
+ tick: () => this.runOnce(),
52181
+ flatIntervalMs: deps.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS3,
52182
+ backoff: deps.backoff,
52183
+ setTimer: deps.setTimer,
52184
+ clearTimer: deps.clearTimer
50496
52185
  });
50497
52186
  }
50498
52187
  async runOnce() {
50499
- const leased = await this.queue.lease(LEASE_KINDS2);
52188
+ const leased = await this.queue.lease(this.leaseKinds);
50500
52189
  if (leased === null)
50501
52190
  return false;
52191
+ await this.processLeased(leased);
52192
+ return true;
52193
+ }
52194
+ /**
52195
+ * PRD-062b (AC-4): process ONE already-leased `pollinating` job — parse it, select
52196
+ * the strategy, run the pass, and complete/fail it. Split out of {@link runOnce} so
52197
+ * the single combined lease coordinator can dispatch a job IT leased (over the
52198
+ * union of kinds) to this participant without a second lease. The standalone
52199
+ * `runOnce` leases then calls this; both share the identical parse+run+complete/fail
52200
+ * body, so kind isolation and the no-swallowed-error contract hold whether
52201
+ * consolidation is on or off.
52202
+ */
52203
+ async processLeased(leased) {
50502
52204
  const job = parsePollinatingJobPayload(leased.payload);
50503
52205
  if (job === null) {
50504
52206
  this.logger?.event("pollinating.worker.bad_payload", { id: leased.id });
50505
52207
  await this.queue.fail(leased.id, "malformed pollinating job payload");
50506
- return true;
52208
+ return;
50507
52209
  }
50508
52210
  try {
50509
52211
  const strategy = await this.selectStrategy(job);
@@ -50527,7 +52229,6 @@ var PollinatingJobWorkerImpl = class {
50527
52229
  this.logger?.event("pollinating.worker.failed", { id: leased.id, attempt: leased.attempt, reason });
50528
52230
  await this.queue.fail(leased.id, reason);
50529
52231
  }
50530
- return true;
50531
52232
  }
50532
52233
  /**
50533
52234
  * Select the payload strategy for a job by mode (D-4). Compaction when the payload
@@ -50555,26 +52256,105 @@ var PollinatingJobWorkerImpl = class {
50555
52256
  return shouldEnterCompaction(this.config, state.lastPassAt);
50556
52257
  }
50557
52258
  start() {
50558
- this.handle = this.setTimer(() => {
50559
- if (this.running)
50560
- return;
50561
- this.running = true;
50562
- void this.runOnce().finally(() => {
50563
- this.running = false;
50564
- });
50565
- }, this.pollIntervalMs);
52259
+ this.loop.start();
50566
52260
  }
50567
52261
  stop() {
50568
- if (this.handle !== void 0) {
50569
- this.clearTimer(this.handle);
50570
- this.handle = void 0;
50571
- }
52262
+ this.loop.stop();
50572
52263
  }
50573
52264
  };
50574
52265
  function createPollinatingWorker(deps) {
50575
52266
  return new PollinatingJobWorkerImpl(deps);
50576
52267
  }
50577
52268
 
52269
+ // dist/src/daemon/runtime/services/lease-coordinator.js
52270
+ var CombinedLeaseCoordinator = class {
52271
+ queue;
52272
+ participants;
52273
+ logger;
52274
+ unionKinds;
52275
+ /** `kind` → the participant that owns it (built once; one owner per kind). */
52276
+ routes;
52277
+ loop;
52278
+ constructor(deps) {
52279
+ this.queue = deps.queue;
52280
+ this.participants = deps.participants;
52281
+ this.logger = deps.logger;
52282
+ this.routes = /* @__PURE__ */ new Map();
52283
+ const union2 = [];
52284
+ for (const participant of deps.participants) {
52285
+ for (const kind of participant.leaseKinds) {
52286
+ if (this.routes.has(kind)) {
52287
+ this.logger?.event("lease.coordinator.duplicate_kind", { kind });
52288
+ continue;
52289
+ }
52290
+ this.routes.set(kind, participant);
52291
+ union2.push(kind);
52292
+ }
52293
+ }
52294
+ this.unionKinds = union2;
52295
+ const timers = deps.timers ?? {
52296
+ setTimer: (cb, ms) => {
52297
+ const t = setInterval(cb, ms);
52298
+ if (typeof t === "object" && t !== null && "unref" in t && typeof t.unref === "function")
52299
+ t.unref();
52300
+ return t;
52301
+ },
52302
+ clearTimer: (handle) => {
52303
+ if (handle !== void 0)
52304
+ clearInterval(handle);
52305
+ }
52306
+ };
52307
+ this.loop = createPollLoop({
52308
+ tick: () => this.runOnce(),
52309
+ backoff: deps.backoff,
52310
+ flatIntervalMs: deps.flatIntervalMs,
52311
+ timers
52312
+ });
52313
+ }
52314
+ async runOnce() {
52315
+ const leased = await this.queue.lease(this.unionKinds);
52316
+ if (leased === null)
52317
+ return false;
52318
+ const participant = this.routes.get(leased.kind);
52319
+ if (participant === void 0) {
52320
+ this.logger?.event("lease.coordinator.unknown_kind", { id: leased.id, kind: leased.kind });
52321
+ await this.queue.fail(leased.id, `no participant owns leased kind: ${leased.kind}`);
52322
+ return true;
52323
+ }
52324
+ await participant.processLeased(leased);
52325
+ return true;
52326
+ }
52327
+ start() {
52328
+ this.loop.start();
52329
+ }
52330
+ stop() {
52331
+ this.loop.stop();
52332
+ }
52333
+ };
52334
+ function createLeaseCoordinator(deps) {
52335
+ return new CombinedLeaseCoordinator(deps);
52336
+ }
52337
+ var BoolFlag7 = external_exports.preprocess((raw2) => {
52338
+ if (typeof raw2 === "boolean")
52339
+ return raw2;
52340
+ return raw2 === "true" || raw2 === "1";
52341
+ }, external_exports.boolean());
52342
+ var PollConsolidateConfigSchema = external_exports.object({
52343
+ /** Master switch; off → two independent lease passes, the pre-PRD path (AC-9). */
52344
+ enabled: BoolFlag7.default(false)
52345
+ });
52346
+ function envPollConsolidateConfigProvider(env = process.env) {
52347
+ return {
52348
+ read() {
52349
+ const raw2 = env.HONEYCOMB_POLL_CONSOLIDATE;
52350
+ return { enabled: raw2 === void 0 ? true : raw2 };
52351
+ }
52352
+ };
52353
+ }
52354
+ function resolvePollConsolidateConfig(provider = envPollConsolidateConfigProvider()) {
52355
+ return PollConsolidateConfigSchema.parse(provider.read());
52356
+ }
52357
+
50578
52358
  // dist/src/daemon/runtime/skillify/worker.js
50579
52359
  import { homedir as homedir24 } from "node:os";
50580
52360
  import { join as join31 } from "node:path";
@@ -50668,6 +52448,95 @@ function sanitizeSegment5(projectKey) {
50668
52448
  return cleaned === "" ? "default" : cleaned;
50669
52449
  }
50670
52450
 
52451
+ // dist/src/daemon/runtime/dashboard/roi-session-writer.js
52452
+ import { randomUUID as randomUUID7 } from "node:crypto";
52453
+ var SESSION_TURNS_LIMIT = 2e3;
52454
+ function tokenCountOrNull2(value) {
52455
+ if (value === null || value === void 0)
52456
+ return null;
52457
+ const n = typeof value === "number" ? value : Number(value);
52458
+ return Number.isFinite(n) ? Math.trunc(n) : null;
52459
+ }
52460
+ function rowToCapturedTurn2(r) {
52461
+ const sourceTool = typeof r.source_tool === "string" ? r.source_tool : "";
52462
+ return {
52463
+ input_tokens: tokenCountOrNull2(r.input_tokens),
52464
+ output_tokens: tokenCountOrNull2(r.output_tokens),
52465
+ cache_read_input_tokens: tokenCountOrNull2(r.cache_read_input_tokens),
52466
+ cache_creation_input_tokens: tokenCountOrNull2(r.cache_creation_input_tokens),
52467
+ ...sourceTool !== "" ? { sourceTool } : {}
52468
+ };
52469
+ }
52470
+ async function readSessionTurns(storage, scope, path4) {
52471
+ const tbl = sqlIdent("sessions");
52472
+ const pathCol = sqlIdent("path");
52473
+ const sql = `SELECT ${sqlIdent("input_tokens")}, ${sqlIdent("output_tokens")}, ${sqlIdent("cache_read_input_tokens")}, ${sqlIdent("cache_creation_input_tokens")}, ${sqlIdent("source_tool")} FROM "${tbl}" WHERE ${pathCol} = ${sLiteral(path4)} LIMIT ${SESSION_TURNS_LIMIT}`;
52474
+ let result;
52475
+ try {
52476
+ result = await storage.query(sql, scope);
52477
+ } catch {
52478
+ return [];
52479
+ }
52480
+ return isOk(result) ? result.rows.map(rowToCapturedTurn2) : [];
52481
+ }
52482
+ function createRoiSessionWriter(deps) {
52483
+ const clock = deps.clock ?? { now: () => Date.now() };
52484
+ return {
52485
+ async writeForSession(input) {
52486
+ try {
52487
+ if (input.path.trim() === "") {
52488
+ deps.logger?.event("roi.write.skipped", { reason: "blank path", sessionId: input.sessionId });
52489
+ return;
52490
+ }
52491
+ const turns = await readSessionTurns(deps.storage, deps.scope, input.path);
52492
+ const measured2 = measuredCacheSavings(turns);
52493
+ const modeled2 = modeledMemoryInjectionSavings(1);
52494
+ let inputTokens = 0;
52495
+ let outputTokens = 0;
52496
+ let cacheReadTokens = 0;
52497
+ let cacheCreationTokens = 0;
52498
+ for (const t of turns) {
52499
+ inputTokens += t.input_tokens ?? 0;
52500
+ outputTokens += t.output_tokens ?? 0;
52501
+ cacheReadTokens += t.cache_read_input_tokens ?? 0;
52502
+ cacheCreationTokens += t.cache_creation_input_tokens ?? 0;
52503
+ }
52504
+ const createdAt = new Date(clock.now()).toISOString();
52505
+ const { teamId, userId } = await appendRoiMetric(deps.storage, deps.scope, {
52506
+ id: randomUUID7(),
52507
+ sessionId: input.sessionId,
52508
+ agentId: input.agentId,
52509
+ ...input.projectId !== void 0 ? { projectId: input.projectId } : {},
52510
+ inputTokens,
52511
+ outputTokens,
52512
+ cacheReadTokens,
52513
+ cacheCreationTokens,
52514
+ measuredCacheSavingsCents: measured2.value.savingsCents,
52515
+ modeledSavingsCents: modeled2.value.estimatedCents,
52516
+ modeledAssumptionRef: modeled2.assumption.kind,
52517
+ grossCostCents: 0,
52518
+ infraCostCents: 0,
52519
+ costBasis: "none",
52520
+ allocationMethod: "",
52521
+ createdAt
52522
+ });
52523
+ deps.logger?.event("roi.write.appended", {
52524
+ sessionId: input.sessionId,
52525
+ measuredCents: measured2.value.savingsCents,
52526
+ teamId,
52527
+ // user_id is gated to '' today — surface that it stayed gated (never a leaked identity).
52528
+ userGated: userId === ""
52529
+ });
52530
+ } catch (err) {
52531
+ deps.logger?.event("roi.write.failed", {
52532
+ sessionId: input.sessionId,
52533
+ reason: err instanceof Error ? err.message : String(err)
52534
+ });
52535
+ }
52536
+ }
52537
+ };
52538
+ }
52539
+
50671
52540
  // dist/src/daemon/runtime/skillify/worker.js
50672
52541
  var SKILLIFY_JOB_KIND = "skillify";
50673
52542
  var DEFAULT_POLL_INTERVAL_MS4 = 1e3;
@@ -50710,6 +52579,8 @@ var SkillifyJobWorkerImpl = class {
50710
52579
  gateOverride;
50711
52580
  fetcherOverride;
50712
52581
  storeOverride;
52582
+ /** PRD-060e/060f — the per-session ROI writer fired once at completion (fail-soft). */
52583
+ roiWriter;
50713
52584
  handle;
50714
52585
  /** Guards against overlapping `runOnce` invocations on the poll loop. */
50715
52586
  running = false;
@@ -50735,6 +52606,7 @@ var SkillifyJobWorkerImpl = class {
50735
52606
  this.gateOverride = deps.gateOverride;
50736
52607
  this.fetcherOverride = deps.fetcherOverride;
50737
52608
  this.storeOverride = deps.storeOverride;
52609
+ this.roiWriter = deps.roiWriter ?? createRoiSessionWriter({ storage: this.storage, scope: this.scope });
50738
52610
  }
50739
52611
  async runOnce() {
50740
52612
  let leased;
@@ -50775,6 +52647,19 @@ var SkillifyJobWorkerImpl = class {
50775
52647
  const outcome = await writeSkill(result.outcome.verdict, { store, install, author: this.author, projectId }, result.outcome.minedSessionIds, "global");
50776
52648
  const sessionDates = result.outcome.pairs.map((p) => p.sessionDate);
50777
52649
  this.watermarkStore.advance(projectKey, sessionDates);
52650
+ try {
52651
+ await this.roiWriter.writeForSession({
52652
+ sessionId: payload.sessionId,
52653
+ path: payload.path,
52654
+ agentId: this.author,
52655
+ projectId
52656
+ });
52657
+ } catch (roiErr) {
52658
+ this.logger?.event("skillify.worker.roi_write_failed", {
52659
+ id: job.id,
52660
+ reason: roiErr instanceof Error ? roiErr.message : String(roiErr)
52661
+ });
52662
+ }
50778
52663
  await this.queue.complete(job.id);
50779
52664
  this.logger?.event("skillify.worker.completed", {
50780
52665
  id: job.id,
@@ -50841,6 +52726,86 @@ function createSkillifyJobWorker(deps) {
50841
52726
  // dist/src/daemon/storage/client.js
50842
52727
  import { setTimeout as delay3 } from "node:timers/promises";
50843
52728
 
52729
+ // dist/src/daemon/storage/query-meter.js
52730
+ var QUERY_SOURCES = [
52731
+ "poll-lease",
52732
+ "poll-reaper",
52733
+ "capture-write",
52734
+ "fan-out-enqueue",
52735
+ "controlled-write",
52736
+ "recall-arm",
52737
+ "embedding",
52738
+ "other"
52739
+ ];
52740
+ var DEFAULT_QUERY_SOURCE = "other";
52741
+ var QueryMeter = class {
52742
+ /** `source` → {reads, writes}. Lazily populated on first hit per source. */
52743
+ counts = /* @__PURE__ */ new Map();
52744
+ /**
52745
+ * Record ONE metered operation against a `source`.
52746
+ *
52747
+ * @param source the attribution label. Callers that have not threaded a label
52748
+ * pass nothing and the operation is counted under {@link DEFAULT_QUERY_SOURCE}
52749
+ * (`"other"`), so it is visibly "unlabeled" rather than dropped.
52750
+ * @param isWrite `true` for a write statement (INSERT/UPDATE/DELETE/DDL),
52751
+ * `false` for a read (SELECT / read-only WITH). The storage client classifies
52752
+ * this from the statement shape so the split is consistent with the retry
52753
+ * layer's read/write tag, not guessed per call site.
52754
+ */
52755
+ record(source = DEFAULT_QUERY_SOURCE, isWrite = false) {
52756
+ const entry = this.counts.get(source) ?? { reads: 0, writes: 0 };
52757
+ if (isWrite)
52758
+ entry.writes += 1;
52759
+ else
52760
+ entry.reads += 1;
52761
+ this.counts.set(source, entry);
52762
+ }
52763
+ /**
52764
+ * Take an immutable snapshot of the current per-source counts and rollup
52765
+ * totals. Sources that have never been hit are omitted from `perSource` (a
52766
+ * zero-traffic source contributes nothing), but the entries that ARE present
52767
+ * are emitted in the canonical {@link QUERY_SOURCES} order for stable output.
52768
+ */
52769
+ snapshot() {
52770
+ const perSource = [];
52771
+ let totalReads = 0;
52772
+ let totalWrites = 0;
52773
+ for (const source of QUERY_SOURCES) {
52774
+ const entry = this.counts.get(source);
52775
+ if (entry === void 0)
52776
+ continue;
52777
+ perSource.push({ source, reads: entry.reads, writes: entry.writes });
52778
+ totalReads += entry.reads;
52779
+ totalWrites += entry.writes;
52780
+ }
52781
+ return { perSource, totalReads, totalWrites };
52782
+ }
52783
+ /**
52784
+ * Reset every counter to zero. Used by the idle-baseline harness to start a
52785
+ * clean measurement window, and available if a future caller flushes the meter
52786
+ * per period.
52787
+ */
52788
+ reset() {
52789
+ this.counts.clear();
52790
+ }
52791
+ /**
52792
+ * Render the current snapshot as a single structured log line for the periodic
52793
+ * diagnostic surface (AC-62a.1.3). The shape is `key=value` pairs so it greps
52794
+ * cleanly and parses without a schema:
52795
+ *
52796
+ * [query-meter] total_reads=42 total_writes=7 poll-lease=r:30/w:0 capture-write=r:0/w:5 other=r:12/w:2
52797
+ *
52798
+ * A meter with no traffic yet renders the header with zero totals and no
52799
+ * per-source segments, so an idle window is still an explicit, loggable fact.
52800
+ */
52801
+ formatLogLine() {
52802
+ const snap = this.snapshot();
52803
+ const segments = snap.perSource.map((e) => `${e.source}=r:${e.reads}/w:${e.writes}`);
52804
+ const header = `[query-meter] total_reads=${snap.totalReads} total_writes=${snap.totalWrites}`;
52805
+ return segments.length === 0 ? header : `${header} ${segments.join(" ")}`;
52806
+ }
52807
+ };
52808
+
50844
52809
  // dist/src/daemon/storage/transport.js
50845
52810
  var TransportError = class extends Error {
50846
52811
  kind;
@@ -50853,8 +52818,8 @@ var TransportError = class extends Error {
50853
52818
  this.status = status;
50854
52819
  }
50855
52820
  };
50856
- var DEEPLAKE_CLIENT_HEADER2 = "X-Deeplake-Client";
50857
- var DEEPLAKE_ORG_HEADER2 = "X-Activeloop-Org-Id";
52821
+ var DEEPLAKE_CLIENT_HEADER3 = "X-Deeplake-Client";
52822
+ var DEEPLAKE_ORG_HEADER3 = "X-Activeloop-Org-Id";
50858
52823
  var HttpDeepLakeTransport = class {
50859
52824
  endpoint;
50860
52825
  token;
@@ -50870,8 +52835,8 @@ var HttpDeepLakeTransport = class {
50870
52835
  headers: {
50871
52836
  Authorization: `Bearer ${this.token}`,
50872
52837
  "Content-Type": "application/json",
50873
- [DEEPLAKE_ORG_HEADER2]: req.org,
50874
- [DEEPLAKE_CLIENT_HEADER2]: "honeycomb"
52838
+ [DEEPLAKE_ORG_HEADER3]: req.org,
52839
+ [DEEPLAKE_CLIENT_HEADER3]: "honeycomb"
50875
52840
  },
50876
52841
  signal: req.signal,
50877
52842
  body: JSON.stringify({ query: req.sql })
@@ -51015,15 +52980,39 @@ var StorageClient = class {
51015
52980
  * the real timer; a test injects a no-op so the bounded backoff costs zero
51016
52981
  * wall-clock time and the retry count stays deterministic.
51017
52982
  */
51018
- constructor(transport, config2, sleep2 = realSleep2) {
52983
+ /** Per-source DeepLake query meter (PRD-062a). Always present; default mode is in-memory + log only. */
52984
+ meter;
52985
+ /**
52986
+ * @param sleep injectable backoff clock for the read-retry layer. Defaults to
52987
+ * the real timer; a test injects a no-op so the bounded backoff costs zero
52988
+ * wall-clock time and the retry count stays deterministic.
52989
+ * @param meter injectable query meter (PRD-062a). Defaults to a fresh in-memory
52990
+ * {@link QueryMeter}; the daemon may inject a shared one so diagnostics and a
52991
+ * later persistence path observe the SAME counts. The meter is a pure observer:
52992
+ * supplying it never changes any query's behavior or result.
52993
+ */
52994
+ constructor(transport, config2, sleep2 = realSleep2, meter = new QueryMeter()) {
51019
52995
  this.transport = transport;
51020
52996
  this.config = config2;
51021
52997
  this.sleep = sleep2;
52998
+ this.meter = meter;
51022
52999
  }
51023
53000
  /** The endpoint the client is bound to (for diagnostics; no secrets). */
51024
53001
  get endpoint() {
51025
53002
  return this.config.endpoint;
51026
53003
  }
53004
+ /**
53005
+ * Snapshot the per-source query counts (PRD-062a, AC-62a.1.3). The diagnostic
53006
+ * surface and the idle-baseline harness read the meter through here without
53007
+ * touching the live counter, so a snapshot is stable even as traffic continues.
53008
+ */
53009
+ meterSnapshot() {
53010
+ return this.meter.snapshot();
53011
+ }
53012
+ /** Render the current per-source counts as one structured log line (PRD-062a). */
53013
+ meterLogLine() {
53014
+ return this.meter.formatLogLine();
53015
+ }
51027
53016
  /**
51028
53017
  * Liveness check (a-AC-1): "connects" against the fake transport means a
51029
53018
  * trivial statement succeeds. Returns a typed result so a caller branches on
@@ -51056,7 +53045,9 @@ var StorageClient = class {
51056
53045
  * from the per-statement timeout: each attempt gets its own fresh timeout/abort.
51057
53046
  */
51058
53047
  async query(sql, scope, opts = {}) {
51059
- if (statementRetryability(sql) === "unsafe-write")
53048
+ const retryability = statementRetryability(sql);
53049
+ this.meter.record(opts.source, retryability !== "read");
53050
+ if (retryability === "unsafe-write")
51060
53051
  return this.attemptOnce(sql, scope, opts);
51061
53052
  let last;
51062
53053
  for (let attempt = 1; attempt <= RETRY_ATTEMPTS; attempt++) {
@@ -51141,7 +53132,7 @@ function createStorageClient(options = {}) {
51141
53132
  const provider = options.provider ?? defaultCredentialProvider();
51142
53133
  const config2 = resolveStorageConfig(provider);
51143
53134
  const transport = options.transport ?? new HttpDeepLakeTransport(config2.endpoint, config2.token);
51144
- return new StorageClient(transport, config2, options.sleep);
53135
+ return new StorageClient(transport, config2, options.sleep, options.meter);
51145
53136
  }
51146
53137
  function createLazyStorageClient(options = {}) {
51147
53138
  let built = null;
@@ -51291,7 +53282,7 @@ function authForMode(mode, storage, scope) {
51291
53282
  return { authenticator: composeAuthenticator(storage, scope), policy: defaultDenyPolicy };
51292
53283
  }
51293
53284
  function assembleSeams(daemon, storage, defaultScope, orgName, embed, healthDetail, workspaceDir, installedHarnesses, logStore, seams = defaultSeamFns, vault) {
51294
- seams.attachHooks(daemon, {
53285
+ const captureHandler = seams.attachHooks(daemon, {
51295
53286
  storage,
51296
53287
  queue: daemon.services.queue,
51297
53288
  embed,
@@ -51347,7 +53338,7 @@ function assembleSeams(daemon, storage, defaultScope, orgName, embed, healthDeta
51347
53338
  seams.mountMemoriesPrime(daemon, { storage, defaultScope });
51348
53339
  }
51349
53340
  seams.mountVfs(daemon, { storage, defaultScope });
51350
- seams.mountProductData(daemon, resolveProductDataDeps(storage, defaultScope, daemon.services.queue, embed.client));
53341
+ seams.mountProductData(daemon, resolveProductDataDeps(storage, defaultScope, daemon.services.queue, embed.client, daemon.config.mode));
51351
53342
  seams.mountPollinate(daemon, { storage, defaultScope, enqueuer: daemon.services.queue });
51352
53343
  try {
51353
53344
  seams.mountProjectsSync(daemon, { storage, defaultScope });
@@ -51416,11 +53407,16 @@ function assembleSeams(daemon, storage, defaultScope, orgName, embed, healthDeta
51416
53407
  `);
51417
53408
  }
51418
53409
  }
53410
+ return captureHandler;
51419
53411
  }
51420
- function resolveProductDataDeps(storage, defaultScope, queue, embed) {
53412
+ function resolveProductDataDeps(storage, defaultScope, queue, embed, mode) {
51421
53413
  const baseDir = process.env.HONEYCOMB_WORKSPACE ?? process.cwd();
51422
53414
  const secrets = {
51423
- store: new SecretsStore({ baseDir, machineKey: createMachineKeyProvider() })
53415
+ store: new SecretsStore({ baseDir, machineKey: createMachineKeyProvider() }),
53416
+ // PRD-022 local-mode default: the dashboard's `GET /api/secrets` (names-only) carries no
53417
+ // `x-honeycomb-org` header, so resolve the daemon's single local tenant instead of 400ing.
53418
+ // Team/hybrid stay fail-closed (a missing org still 400s) — cross-tenant access is rejected.
53419
+ scope: localDefaultScopeResolver(mode, defaultScope)
51424
53420
  };
51425
53421
  let sources;
51426
53422
  try {
@@ -51502,7 +53498,7 @@ function coerceSettingBool(value) {
51502
53498
  return value === "true" || value === "1";
51503
53499
  return false;
51504
53500
  }
51505
- async function buildGatedPollinatingWorker(options, storage, scope, queue, vault) {
53501
+ async function buildGatedPollinatingWorker(options, storage, scope, queue, vault, backoff) {
51506
53502
  let config2;
51507
53503
  try {
51508
53504
  config2 = resolvePollinatingConfig(options.pollinatingConfigProvider);
@@ -51529,7 +53525,7 @@ async function buildGatedPollinatingWorker(options, storage, scope, queue, vault
51529
53525
  ...providerModelOverride !== void 0 ? { providerModelOverride } : {}
51530
53526
  });
51531
53527
  const trigger = createPollinatingTrigger({ storage, scope, config: config2, enqueuer: queue });
51532
- return createPollinatingWorker({ queue, storage, scope, config: config2, model, trigger });
53528
+ return createPollinatingWorker({ queue, storage, scope, config: config2, model, trigger, backoff });
51533
53529
  }
51534
53530
  function buildSummaryWorker(storage, scope, queue, embed) {
51535
53531
  return createSummaryJobWorker({ queue, storage, scope, embed: embed.client });
@@ -51550,7 +53546,7 @@ function makePipelineEntryEnqueuer(queue) {
51550
53546
  });
51551
53547
  };
51552
53548
  }
51553
- async function buildPipelineWorker(options, storage, scope, queue, embed) {
53549
+ async function buildPipelineWorker(options, storage, scope, queue, embed, backoff) {
51554
53550
  let config2;
51555
53551
  try {
51556
53552
  config2 = resolvePipelineConfig();
@@ -51580,7 +53576,7 @@ async function buildPipelineWorker(options, storage, scope, queue, embed) {
51580
53576
  graphPersist: { storage, scope: queryScope, config: config2 },
51581
53577
  retention: { storage, scope: queryScope, config: config2 }
51582
53578
  });
51583
- return createStageWorker({ queue, handlers });
53579
+ return createStageWorker({ queue, handlers, backoff });
51584
53580
  }
51585
53581
  function buildSkillifyWorker(storage, scope, queue) {
51586
53582
  return createSkillifyJobWorker({
@@ -51635,10 +53631,10 @@ function assembleDaemon(options = {}) {
51635
53631
  const embed = options.embed ?? createEmbedAttachment({ storage });
51636
53632
  const installedHarnesses = options.installedHarnesses ?? (options.harnessTargets !== void 0 && options.harnessTargets.length > 0 ? new Set(options.harnessTargets.map((t) => t.name)) : options.storage === void 0 ? detectInstalledHarnesses() : /* @__PURE__ */ new Set());
51637
53633
  const vault = options.vault ?? (options.storage === void 0 ? buildVaultStore() : void 0);
51638
- assembleSeams(daemon, storage, scope, daemonOrgName, embed, healthDetail, options.workspaceDir ?? process.cwd(), installedHarnesses, logStore, options.seams ?? defaultSeamFns, vault);
53634
+ const captureHandler = assembleSeams(daemon, storage, scope, daemonOrgName, embed, healthDetail, options.workspaceDir ?? process.cwd(), installedHarnesses, logStore, options.seams ?? defaultSeamFns, vault);
51639
53635
  if (vault !== void 0 && vault instanceof VaultStore) {
51640
53636
  try {
51641
- mountSettingsApi(daemon, { store: vault });
53637
+ mountSettingsApi(daemon, { store: vault, scope: localDefaultScopeResolver(daemon.config.mode, scope) });
51642
53638
  } catch (err) {
51643
53639
  const reason = err instanceof Error ? err.message : String(err);
51644
53640
  process.stderr.write(`honeycomb: settings API mount failed (non-fatal): ${reason}
@@ -51705,6 +53701,7 @@ function assembleDaemon(options = {}) {
51705
53701
  let pollinatingWorker = null;
51706
53702
  let summaryWorker = null;
51707
53703
  let pipelineWorker = null;
53704
+ let leaseCoordinator = null;
51708
53705
  let skillifyWorker = null;
51709
53706
  return {
51710
53707
  daemon,
@@ -51724,6 +53721,18 @@ function assembleDaemon(options = {}) {
51724
53721
  probeTimer.unref();
51725
53722
  }
51726
53723
  await daemon.startServices();
53724
+ let pollBackoff;
53725
+ try {
53726
+ pollBackoff = resolvePollBackoffConfig();
53727
+ } catch {
53728
+ pollBackoff = resolvePollBackoffConfig({ read: () => ({}) });
53729
+ }
53730
+ let consolidatePoll = false;
53731
+ try {
53732
+ consolidatePoll = resolvePollConsolidateConfig().enabled;
53733
+ } catch {
53734
+ consolidatePoll = false;
53735
+ }
51727
53736
  try {
51728
53737
  summaryWorker = buildSummaryWorker(storage, scope, daemon.services.queue, embed);
51729
53738
  summaryWorker.start();
@@ -51734,8 +53743,9 @@ function assembleDaemon(options = {}) {
51734
53743
  summaryWorker = null;
51735
53744
  }
51736
53745
  try {
51737
- pipelineWorker = await buildPipelineWorker(options, storage, scope, daemon.services.queue, embed);
51738
- pipelineWorker.start();
53746
+ pipelineWorker = await buildPipelineWorker(options, storage, scope, daemon.services.queue, embed, pollBackoff);
53747
+ if (!consolidatePoll)
53748
+ pipelineWorker.start();
51739
53749
  } catch (err) {
51740
53750
  const reason = err instanceof Error ? err.message : String(err);
51741
53751
  process.stderr.write(`honeycomb: memory-pipeline worker start failed (non-fatal): ${reason}
@@ -51765,16 +53775,41 @@ function assembleDaemon(options = {}) {
51765
53775
  }
51766
53776
  }
51767
53777
  try {
51768
- pollinatingWorker = await buildGatedPollinatingWorker(options, storage, scope, daemon.services.queue, vault);
51769
- pollinatingWorker?.start();
53778
+ pollinatingWorker = await buildGatedPollinatingWorker(options, storage, scope, daemon.services.queue, vault, pollBackoff);
53779
+ const pollinatingInjected = options.pollinatingWorker !== void 0;
53780
+ if (consolidatePoll && !pollinatingInjected) {
53781
+ const participants = [pipelineWorker, pollinatingWorker].filter((p) => p !== null);
53782
+ if (participants.length > 0) {
53783
+ leaseCoordinator = createLeaseCoordinator({
53784
+ queue: daemon.services.queue,
53785
+ participants,
53786
+ backoff: pollBackoff,
53787
+ flatIntervalMs: 1e3
53788
+ });
53789
+ leaseCoordinator.start();
53790
+ }
53791
+ } else {
53792
+ pollinatingWorker?.start();
53793
+ if (consolidatePoll && pollinatingInjected && pipelineWorker !== null) {
53794
+ pipelineWorker.start();
53795
+ }
53796
+ }
51770
53797
  } catch (err) {
51771
53798
  const reason = err instanceof Error ? err.message : String(err);
51772
53799
  process.stderr.write(`honeycomb: pollinating worker start failed (non-fatal): ${reason}
51773
53800
  `);
51774
53801
  pollinatingWorker = null;
53802
+ if (consolidatePoll && leaseCoordinator === null && pipelineWorker !== null) {
53803
+ pipelineWorker.start();
53804
+ }
51775
53805
  }
51776
53806
  },
51777
53807
  async shutdown() {
53808
+ await captureHandler.flush?.();
53809
+ if (leaseCoordinator !== null) {
53810
+ leaseCoordinator.stop();
53811
+ leaseCoordinator = null;
53812
+ }
51778
53813
  if (pollinatingWorker !== null) {
51779
53814
  pollinatingWorker.stop();
51780
53815
  pollinatingWorker = null;
@@ -51897,9 +53932,9 @@ export {
51897
53932
  CompactionConfigError,
51898
53933
  CompactionRefusedError,
51899
53934
  CompactionRetentionSchema,
51900
- DEEPLAKE_CLIENT_HEADER2 as DEEPLAKE_CLIENT_HEADER,
53935
+ DEEPLAKE_CLIENT_HEADER3 as DEEPLAKE_CLIENT_HEADER,
51901
53936
  DEFAULT_MAX_POLLS as DEEPLAKE_DEFAULT_MAX_POLLS,
51902
- DEEPLAKE_ORG_HEADER2 as DEEPLAKE_ORG_HEADER,
53937
+ DEEPLAKE_ORG_HEADER3 as DEEPLAKE_ORG_HEADER,
51903
53938
  DEFAULT_CONVERGE_BACKOFF_BASE_MS,
51904
53939
  DEFAULT_CONVERGE_BACKOFF_CAP_MS,
51905
53940
  DEFAULT_CONVERGE_BUDGET,
@@ -51911,6 +53946,7 @@ export {
51911
53946
  DEFAULT_MAX_POLLS2 as DEFAULT_MAX_POLLS,
51912
53947
  DEFAULT_MAX_RETRIES,
51913
53948
  DEFAULT_OVERFETCH_MULTIPLIER,
53949
+ DEFAULT_QUERY_SOURCE,
51914
53950
  DEFAULT_QUERY_TIMEOUT_MS,
51915
53951
  DEFAULT_TIMESTAMP_COLUMN,
51916
53952
  DEFAULT_VERSION_COLUMN,
@@ -51935,6 +53971,8 @@ export {
51935
53971
  LEGACY_CREDENTIALS_DIR_NAME,
51936
53972
  LOCK_FILE_NAME,
51937
53973
  PID_FILE_NAME,
53974
+ QUERY_SOURCES,
53975
+ QueryMeter,
51938
53976
  ROLES,
51939
53977
  RuntimeConfigError,
51940
53978
  RuntimeConfigSchema,