@legioncodeinc/honeycomb 0.1.7 → 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.7" : "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) {
@@ -28496,7 +28583,7 @@ function buildAllowedProperties(input) {
28496
28583
  }
28497
28584
  var systemTelemetryClock = () => (/* @__PURE__ */ new Date()).toISOString();
28498
28585
  var DEFAULT_EMIT_TIMEOUT_MS = 2e3;
28499
- var HONEYCOMB_VERSION2 = true ? "0.1.7" : "0.0.0-dev";
28586
+ var HONEYCOMB_VERSION2 = true ? "0.1.8" : "0.0.0-dev";
28500
28587
  async function emitTelemetry(event, opts, deps = {}) {
28501
28588
  const env = deps.env ?? process.env;
28502
28589
  const key = deps.posthogKey ?? POSTHOG_KEY;
@@ -31071,6 +31158,17 @@ function createSummaryJobWorker(deps) {
31071
31158
 
31072
31159
  // dist/src/daemon/runtime/capture/event-contract.js
31073
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);
31074
31172
  var UserMessageEventSchema = external_exports.object({
31075
31173
  kind: external_exports.literal("user_message"),
31076
31174
  text: external_exports.string()
@@ -31083,7 +31181,9 @@ var ToolCallEventSchema = external_exports.object({
31083
31181
  });
31084
31182
  var AssistantMessageEventSchema = external_exports.object({
31085
31183
  kind: external_exports.literal("assistant_message"),
31086
- text: external_exports.string()
31184
+ text: external_exports.string(),
31185
+ /** PRD-060a: optional per-turn token + cache counts; absent when unavailable. */
31186
+ usage: TurnUsageSchema
31087
31187
  });
31088
31188
  var CaptureEventSchema = external_exports.discriminatedUnion("kind", [
31089
31189
  UserMessageEventSchema,
@@ -31503,7 +31603,7 @@ var CaptureRouteHandler = class {
31503
31603
  */
31504
31604
  buildRow(id, event, meta3, nowIso10, projectId) {
31505
31605
  const message = this.config.envelopeBudgetBytes > 0 ? budgetedStringify(event, meta3, this.config.envelopeBudgetBytes) : JSON.stringify({ event, metadata: meta3 });
31506
- return [
31606
+ const row = [
31507
31607
  ["id", val.str(id)],
31508
31608
  ["path", val.str(meta3.path)],
31509
31609
  ["filename", val.str(meta3.hookEventName)],
@@ -31517,9 +31617,17 @@ var CaptureRouteHandler = class {
31517
31617
  ["project_id", val.str(projectId)],
31518
31618
  ["plugin_version", val.str(meta3.pluginVersion)],
31519
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)],
31520
31624
  ["creation_date", val.str(nowIso10)],
31521
31625
  ["last_update_date", val.str(nowIso10)]
31522
31626
  ];
31627
+ for (const [col, value] of usageColumns(event)) {
31628
+ row.push([col, val.num(value)]);
31629
+ }
31630
+ return row;
31523
31631
  }
31524
31632
  /**
31525
31633
  * Resolve the capture row's `project_id` from the session cwd (PRD-049b 49b-AC-1 / 49b-AC-3).
@@ -31662,6 +31770,21 @@ function embedTextFor(event) {
31662
31770
  return [event.tool, serialize(event.input), serialize(event.response)].filter((s) => s.length > 0).join("\n");
31663
31771
  }
31664
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
+ }
31665
31788
  function groupRowsByScope(batch) {
31666
31789
  const byKey = /* @__PURE__ */ new Map();
31667
31790
  for (const item of batch) {
@@ -35259,6 +35382,37 @@ function mountSkillPropagationApi(daemon, options) {
35259
35382
  }
35260
35383
  }
35261
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
+
35262
35416
  // dist/src/daemon/runtime/dashboard/installed-assets.js
35263
35417
  import { homedir as homedir11 } from "node:os";
35264
35418
  import { join as join16 } from "node:path";
@@ -35417,6 +35571,548 @@ function sanitizeName(name) {
35417
35571
  return name.replace(/[^A-Za-z0-9._-]/g, "_");
35418
35572
  }
35419
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
+
35420
36116
  // dist/src/daemon/runtime/dashboard/api.js
35421
36117
  var DASHBOARD_GROUPS = Object.freeze({
35422
36118
  /**
@@ -35461,7 +36157,14 @@ var DASHBOARD_GROUPS = Object.freeze({
35461
36157
  * READ-ONLY filesystem walk; tenancy-independent (no org header required). PRD-036b calls
35462
36158
  * the underlying `scanInstalledAssets` IN-PROCESS, not over this HTTP surface.
35463
36159
  */
35464
- 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"
35465
36168
  });
35466
36169
  function toNum2(value) {
35467
36170
  const n = typeof value === "number" ? value : Number(value ?? 0);
@@ -35645,6 +36348,176 @@ async function fetchSkillSyncView(storage, scope, scan = scanInstalledAssets) {
35645
36348
  }
35646
36349
  return { skills: [...merged.values()] };
35647
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
+ }
35648
36521
  var NO_ORG_BODY4 = { error: "bad_request", reason: "x-honeycomb-org header is required" };
35649
36522
  function mountDashboardApi(daemon, options) {
35650
36523
  const storage = options.storage;
@@ -35712,6 +36585,34 @@ function mountDashboardApi(daemon, options) {
35712
36585
  return c.json(await inventoryCache());
35713
36586
  });
35714
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;
35715
36616
  }
35716
36617
  var INSTALLED_ASSETS_TTL_MS = 5e3;
35717
36618
  function createInventoryCache() {
@@ -36007,8 +36908,61 @@ function nestedString(raw2, a, b) {
36007
36908
  function userMessageData(text) {
36008
36909
  return { kind: "user_message", text };
36009
36910
  }
36010
- function assistantMessageData(text) {
36011
- 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;
36012
36966
  }
36013
36967
  function toolCallData(tool, input, response) {
36014
36968
  return { kind: "tool_call", tool, input, response };
@@ -36058,7 +37012,7 @@ function claudeCodeExtractData(raw2, logical) {
36058
37012
  case "tool_call":
36059
37013
  return toolCallData(pickString(raw2, "tool_name", "tool"), nested(raw2, "tool_input"), nested(raw2, "tool_response"));
36060
37014
  case "assistant_message":
36061
- return assistantMessageData(pickString(raw2, "text", "message"));
37015
+ return assistantMessageData(pickString(raw2, "text", "message"), extractTurnUsage(raw2));
36062
37016
  case "session-end":
36063
37017
  return sessionEndData(pickString(raw2, "reason") || "Stop");
36064
37018
  default:
@@ -39381,35 +40335,6 @@ async function readBody4(c) {
39381
40335
  }
39382
40336
  }
39383
40337
 
39384
- // dist/src/daemon/runtime/recall/scope-clause.js
39385
- var PROJECT_ID_COLUMN = "project_id";
39386
- var PROJECT_ID_UNSET = "";
39387
- var CROSS_PROJECT_ADMITTED = Object.freeze(["user", "workspace"]);
39388
- var SCOPE_READ_POLICIES = Object.freeze(["isolated", "shared", "group"]);
39389
- function buildProjectScopeClause(input) {
39390
- const projectColumn = input.projectColumn ?? PROJECT_ID_COLUMN;
39391
- const rawId = input.projectId ?? "";
39392
- const isInbox = rawId.trim() === "" || rawId === UNSORTED_PROJECT_ID;
39393
- const bound = input.bound ?? !isInbox;
39394
- const col = sqlIdent(projectColumn);
39395
- const primaryId = bound ? rawId : UNSORTED_PROJECT_ID;
39396
- const admitted = primaryId === PROJECT_ID_UNSET ? [PROJECT_ID_UNSET] : [primaryId, PROJECT_ID_UNSET];
39397
- const disjuncts = admitted.map((id) => `${col} = ${sLiteral(id)}`);
39398
- const values = [...admitted];
39399
- if (input.promotionColumn !== void 0 && input.promotionColumn !== "") {
39400
- const promoCol = sqlIdent(input.promotionColumn);
39401
- for (const reach of CROSS_PROJECT_ADMITTED) {
39402
- disjuncts.push(`${promoCol} = ${sLiteral(reach)}`);
39403
- values.push(reach);
39404
- }
39405
- }
39406
- const sql = `(${disjuncts.join(" OR ")})`;
39407
- return { sql, values, bound };
39408
- }
39409
- function buildProjectScopeConjunct(input) {
39410
- return ` AND ${buildProjectScopeClause(input).sql}`;
39411
- }
39412
-
39413
40338
  // dist/src/daemon/runtime/memories/reads.js
39414
40339
  var DEFAULT_LIST_LIMIT = 50;
39415
40340
  var MAX_LIST_LIMIT = 500;
@@ -41848,6 +42773,19 @@ async function recallMemories(request, deps) {
41848
42773
  }
41849
42774
 
41850
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
+ }
41851
42789
  var headerScopeResolver2 = {
41852
42790
  resolve(c) {
41853
42791
  const org = c.req.header("x-honeycomb-org");
@@ -49344,12 +50282,34 @@ var RouterModelClient = class {
49344
50282
  var ANTHROPIC_MESSAGES_URL = "https://api.anthropic.com/v1/messages";
49345
50283
  var ANTHROPIC_VERSION = "2023-06-01";
49346
50284
  var DEFAULT_MAX_TOKENS = 4096;
50285
+ var noopUsageSink = {
50286
+ record() {
50287
+ }
50288
+ };
49347
50289
  var AnthropicContentBlockSchema = external_exports.object({
49348
50290
  type: external_exports.string(),
49349
50291
  text: external_exports.string().optional()
49350
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
+ };
49351
50305
  var AnthropicMessagesResponseSchema = external_exports.object({
49352
- 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)
49353
50313
  });
49354
50314
  function toAnthropicBody(call, defaultMaxTokens) {
49355
50315
  const systemParts = [];
@@ -49377,6 +50337,7 @@ function createAnthropicTransport(deps = {}) {
49377
50337
  const doFetch = deps.fetch ?? globalThis.fetch;
49378
50338
  const url2 = deps.baseUrl ?? ANTHROPIC_MESSAGES_URL;
49379
50339
  const defaultMaxTokens = deps.defaultMaxTokens ?? DEFAULT_MAX_TOKENS;
50340
+ const usageSink = deps.usageSink ?? noopUsageSink;
49380
50341
  async function post(call) {
49381
50342
  const body = toAnthropicBody(call, defaultMaxTokens);
49382
50343
  let res;
@@ -49402,16 +50363,33 @@ function createAnthropicTransport(deps = {}) {
49402
50363
  if (!parsed.success) {
49403
50364
  throw new ProviderError(502, "anthropic transport: malformed provider response");
49404
50365
  }
49405
- 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
+ }
49406
50382
  }
49407
50383
  return {
49408
50384
  async execute(call) {
49409
- const output = await post(call);
50385
+ const { output, usage } = await post(call);
50386
+ reportUsage(usage);
49410
50387
  return { output };
49411
50388
  },
49412
50389
  stream(call) {
49413
50390
  async function* gen() {
49414
- const output = await post(call);
50391
+ const { output, usage } = await post(call);
50392
+ reportUsage(usage);
49415
50393
  yield { delta: output };
49416
50394
  }
49417
50395
  return gen();
@@ -49456,7 +50434,7 @@ async function buildInferenceModelClient(deps) {
49456
50434
  }
49457
50435
  const effectiveConfig = deps.providerModelOverride !== void 0 ? applyProviderModelOverride(config2, deps.providerModelOverride) : config2;
49458
50436
  const secrets = createSecretResolver(deps.secretsStore, deps.scope);
49459
- const transport = createAnthropicTransport();
50437
+ const transport = createAnthropicTransport(deps.usageSink !== void 0 ? { usageSink: deps.usageSink } : {});
49460
50438
  const router = createInferenceRouter({
49461
50439
  config: effectiveConfig,
49462
50440
  transport,
@@ -51470,6 +52448,95 @@ function sanitizeSegment5(projectKey) {
51470
52448
  return cleaned === "" ? "default" : cleaned;
51471
52449
  }
51472
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
+
51473
52540
  // dist/src/daemon/runtime/skillify/worker.js
51474
52541
  var SKILLIFY_JOB_KIND = "skillify";
51475
52542
  var DEFAULT_POLL_INTERVAL_MS4 = 1e3;
@@ -51512,6 +52579,8 @@ var SkillifyJobWorkerImpl = class {
51512
52579
  gateOverride;
51513
52580
  fetcherOverride;
51514
52581
  storeOverride;
52582
+ /** PRD-060e/060f — the per-session ROI writer fired once at completion (fail-soft). */
52583
+ roiWriter;
51515
52584
  handle;
51516
52585
  /** Guards against overlapping `runOnce` invocations on the poll loop. */
51517
52586
  running = false;
@@ -51537,6 +52606,7 @@ var SkillifyJobWorkerImpl = class {
51537
52606
  this.gateOverride = deps.gateOverride;
51538
52607
  this.fetcherOverride = deps.fetcherOverride;
51539
52608
  this.storeOverride = deps.storeOverride;
52609
+ this.roiWriter = deps.roiWriter ?? createRoiSessionWriter({ storage: this.storage, scope: this.scope });
51540
52610
  }
51541
52611
  async runOnce() {
51542
52612
  let leased;
@@ -51577,6 +52647,19 @@ var SkillifyJobWorkerImpl = class {
51577
52647
  const outcome = await writeSkill(result.outcome.verdict, { store, install, author: this.author, projectId }, result.outcome.minedSessionIds, "global");
51578
52648
  const sessionDates = result.outcome.pairs.map((p) => p.sessionDate);
51579
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
+ }
51580
52663
  await this.queue.complete(job.id);
51581
52664
  this.logger?.event("skillify.worker.completed", {
51582
52665
  id: job.id,
@@ -51735,8 +52818,8 @@ var TransportError = class extends Error {
51735
52818
  this.status = status;
51736
52819
  }
51737
52820
  };
51738
- var DEEPLAKE_CLIENT_HEADER2 = "X-Deeplake-Client";
51739
- 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";
51740
52823
  var HttpDeepLakeTransport = class {
51741
52824
  endpoint;
51742
52825
  token;
@@ -51752,8 +52835,8 @@ var HttpDeepLakeTransport = class {
51752
52835
  headers: {
51753
52836
  Authorization: `Bearer ${this.token}`,
51754
52837
  "Content-Type": "application/json",
51755
- [DEEPLAKE_ORG_HEADER2]: req.org,
51756
- [DEEPLAKE_CLIENT_HEADER2]: "honeycomb"
52838
+ [DEEPLAKE_ORG_HEADER3]: req.org,
52839
+ [DEEPLAKE_CLIENT_HEADER3]: "honeycomb"
51757
52840
  },
51758
52841
  signal: req.signal,
51759
52842
  body: JSON.stringify({ query: req.sql })
@@ -52255,7 +53338,7 @@ function assembleSeams(daemon, storage, defaultScope, orgName, embed, healthDeta
52255
53338
  seams.mountMemoriesPrime(daemon, { storage, defaultScope });
52256
53339
  }
52257
53340
  seams.mountVfs(daemon, { storage, defaultScope });
52258
- 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));
52259
53342
  seams.mountPollinate(daemon, { storage, defaultScope, enqueuer: daemon.services.queue });
52260
53343
  try {
52261
53344
  seams.mountProjectsSync(daemon, { storage, defaultScope });
@@ -52326,10 +53409,14 @@ function assembleSeams(daemon, storage, defaultScope, orgName, embed, healthDeta
52326
53409
  }
52327
53410
  return captureHandler;
52328
53411
  }
52329
- function resolveProductDataDeps(storage, defaultScope, queue, embed) {
53412
+ function resolveProductDataDeps(storage, defaultScope, queue, embed, mode) {
52330
53413
  const baseDir = process.env.HONEYCOMB_WORKSPACE ?? process.cwd();
52331
53414
  const secrets = {
52332
- 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)
52333
53420
  };
52334
53421
  let sources;
52335
53422
  try {
@@ -52547,7 +53634,7 @@ function assembleDaemon(options = {}) {
52547
53634
  const captureHandler = assembleSeams(daemon, storage, scope, daemonOrgName, embed, healthDetail, options.workspaceDir ?? process.cwd(), installedHarnesses, logStore, options.seams ?? defaultSeamFns, vault);
52548
53635
  if (vault !== void 0 && vault instanceof VaultStore) {
52549
53636
  try {
52550
- mountSettingsApi(daemon, { store: vault });
53637
+ mountSettingsApi(daemon, { store: vault, scope: localDefaultScopeResolver(daemon.config.mode, scope) });
52551
53638
  } catch (err) {
52552
53639
  const reason = err instanceof Error ? err.message : String(err);
52553
53640
  process.stderr.write(`honeycomb: settings API mount failed (non-fatal): ${reason}
@@ -52845,9 +53932,9 @@ export {
52845
53932
  CompactionConfigError,
52846
53933
  CompactionRefusedError,
52847
53934
  CompactionRetentionSchema,
52848
- DEEPLAKE_CLIENT_HEADER2 as DEEPLAKE_CLIENT_HEADER,
53935
+ DEEPLAKE_CLIENT_HEADER3 as DEEPLAKE_CLIENT_HEADER,
52849
53936
  DEFAULT_MAX_POLLS as DEEPLAKE_DEFAULT_MAX_POLLS,
52850
- DEEPLAKE_ORG_HEADER2 as DEEPLAKE_ORG_HEADER,
53937
+ DEEPLAKE_ORG_HEADER3 as DEEPLAKE_ORG_HEADER,
52851
53938
  DEFAULT_CONVERGE_BACKOFF_BASE_MS,
52852
53939
  DEFAULT_CONVERGE_BACKOFF_CAP_MS,
52853
53940
  DEFAULT_CONVERGE_BUDGET,