@schlessera/brain-ui-server 0.18.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/README.md +5 -1
  2. package/data/model-prices.json +63 -0
  3. package/dist/activity/digest.d.ts +29 -0
  4. package/dist/activity/digest.d.ts.map +1 -0
  5. package/dist/activity/digest.js +89 -0
  6. package/dist/activity/digest.js.map +1 -0
  7. package/dist/activity/notify.d.ts +62 -0
  8. package/dist/activity/notify.d.ts.map +1 -0
  9. package/dist/activity/notify.js +193 -0
  10. package/dist/activity/notify.js.map +1 -0
  11. package/dist/activity/push-sender.d.ts +63 -0
  12. package/dist/activity/push-sender.d.ts.map +1 -0
  13. package/dist/activity/push-sender.js +131 -0
  14. package/dist/activity/push-sender.js.map +1 -0
  15. package/dist/activity/query.d.ts +16 -0
  16. package/dist/activity/query.d.ts.map +1 -0
  17. package/dist/activity/query.js +155 -0
  18. package/dist/activity/query.js.map +1 -0
  19. package/dist/activity/recorder.d.ts +53 -0
  20. package/dist/activity/recorder.d.ts.map +1 -0
  21. package/dist/activity/recorder.js +238 -0
  22. package/dist/activity/recorder.js.map +1 -0
  23. package/dist/activity/runtime.d.ts +37 -0
  24. package/dist/activity/runtime.d.ts.map +1 -0
  25. package/dist/activity/runtime.js +127 -0
  26. package/dist/activity/runtime.js.map +1 -0
  27. package/dist/activity/span-sink.d.ts +22 -0
  28. package/dist/activity/span-sink.d.ts.map +1 -0
  29. package/dist/activity/span-sink.js +138 -0
  30. package/dist/activity/span-sink.js.map +1 -0
  31. package/dist/activity/store.d.ts +256 -0
  32. package/dist/activity/store.d.ts.map +1 -0
  33. package/dist/activity/store.js +657 -0
  34. package/dist/activity/store.js.map +1 -0
  35. package/dist/activity/stream.d.ts +49 -0
  36. package/dist/activity/stream.d.ts.map +1 -0
  37. package/dist/activity/stream.js +353 -0
  38. package/dist/activity/stream.js.map +1 -0
  39. package/dist/agent/backend.d.ts +13 -4
  40. package/dist/agent/backend.d.ts.map +1 -1
  41. package/dist/agent/backend.js +68 -5
  42. package/dist/agent/backend.js.map +1 -1
  43. package/dist/app.d.ts.map +1 -1
  44. package/dist/app.js +33 -2
  45. package/dist/app.js.map +1 -1
  46. package/dist/config/env.d.ts +36 -0
  47. package/dist/config/env.d.ts.map +1 -1
  48. package/dist/config/env.js +66 -2
  49. package/dist/config/env.js.map +1 -1
  50. package/dist/cron/scheduler.d.ts.map +1 -1
  51. package/dist/cron/scheduler.js +20 -5
  52. package/dist/cron/scheduler.js.map +1 -1
  53. package/dist/db/client.d.ts.map +1 -1
  54. package/dist/db/client.js +32 -6
  55. package/dist/db/client.js.map +1 -1
  56. package/dist/db/settings.d.ts +15 -0
  57. package/dist/db/settings.d.ts.map +1 -1
  58. package/dist/db/settings.js +43 -0
  59. package/dist/db/settings.js.map +1 -1
  60. package/dist/index.d.ts +4 -0
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.js +7 -0
  63. package/dist/index.js.map +1 -1
  64. package/dist/pricing/model-pricing.d.ts +61 -0
  65. package/dist/pricing/model-pricing.d.ts.map +1 -0
  66. package/dist/pricing/model-pricing.js +379 -0
  67. package/dist/pricing/model-pricing.js.map +1 -0
  68. package/dist/routes/activity.d.ts +20 -0
  69. package/dist/routes/activity.d.ts.map +1 -0
  70. package/dist/routes/activity.js +308 -0
  71. package/dist/routes/activity.js.map +1 -0
  72. package/dist/routes/models.d.ts +6 -0
  73. package/dist/routes/models.d.ts.map +1 -1
  74. package/dist/routes/models.js +38 -3
  75. package/dist/routes/models.js.map +1 -1
  76. package/dist/routes/push.d.ts +6 -0
  77. package/dist/routes/push.d.ts.map +1 -0
  78. package/dist/routes/push.js +73 -0
  79. package/dist/routes/push.js.map +1 -0
  80. package/dist/routes/sessions.d.ts.map +1 -1
  81. package/dist/routes/sessions.js +20 -4
  82. package/dist/routes/sessions.js.map +1 -1
  83. package/dist/ws/bridge.d.ts +2 -1
  84. package/dist/ws/bridge.d.ts.map +1 -1
  85. package/dist/ws/bridge.js +27 -2
  86. package/dist/ws/bridge.js.map +1 -1
  87. package/dist/ws/connection.d.ts.map +1 -1
  88. package/dist/ws/connection.js +9 -1
  89. package/dist/ws/connection.js.map +1 -1
  90. package/dist/ws/dispatch.d.ts.map +1 -1
  91. package/dist/ws/dispatch.js +11 -0
  92. package/dist/ws/dispatch.js.map +1 -1
  93. package/dist/ws/host.d.ts +16 -0
  94. package/dist/ws/host.d.ts.map +1 -1
  95. package/dist/ws/host.js +2 -0
  96. package/dist/ws/host.js.map +1 -1
  97. package/dist/ws/run-session.d.ts.map +1 -1
  98. package/dist/ws/run-session.js +55 -1
  99. package/dist/ws/run-session.js.map +1 -1
  100. package/migrations/007_activity.sql +114 -0
  101. package/migrations/008_push_subscriptions.sql +24 -0
  102. package/migrations/009_activity_followups.sql +22 -0
  103. package/migrations/010_effective_cost.sql +21 -0
  104. package/package.json +6 -3
  105. package/src/activity/digest.ts +120 -0
  106. package/src/activity/notify.ts +307 -0
  107. package/src/activity/push-sender.ts +215 -0
  108. package/src/activity/query.ts +187 -0
  109. package/src/activity/recorder.ts +313 -0
  110. package/src/activity/runtime.ts +169 -0
  111. package/src/activity/span-sink.ts +152 -0
  112. package/src/activity/store.ts +1069 -0
  113. package/src/activity/stream.ts +396 -0
  114. package/src/agent/backend.ts +91 -10
  115. package/src/app.ts +35 -2
  116. package/src/config/env.ts +90 -2
  117. package/src/cron/scheduler.ts +28 -8
  118. package/src/db/client.ts +31 -6
  119. package/src/db/settings.ts +56 -0
  120. package/src/index.ts +23 -0
  121. package/src/pricing/model-pricing.ts +497 -0
  122. package/src/routes/activity.ts +371 -0
  123. package/src/routes/models.ts +57 -6
  124. package/src/routes/push.ts +90 -0
  125. package/src/routes/sessions.ts +24 -4
  126. package/src/ws/bridge.ts +29 -2
  127. package/src/ws/connection.ts +9 -1
  128. package/src/ws/dispatch.ts +13 -0
  129. package/src/ws/host.ts +18 -0
  130. package/src/ws/run-session.ts +71 -2
@@ -0,0 +1,37 @@
1
+ /**
2
+ * The activity runtime: everything the activity record needs to live inside
3
+ * one app instance — store, live stream, notifier, push sender, the boot
4
+ * orphan sweep, and the always-on lifecycle tick (stale sweep, notification
5
+ * detection, push delivery, hourly prune). `createApp` constructs one and
6
+ * wires its pieces into the host, routes, and bridge; `close()` releases the
7
+ * interval and subscriptions.
8
+ *
9
+ * Nothing here may block boot or fail the observed work: the sweep and every
10
+ * tick pass log and continue on failure.
11
+ */
12
+ import type { Database } from "bun:sqlite";
13
+ import type { Logger } from "@opentelemetry/api-logs";
14
+ import type { ActivityQuery, ActivityQueryResult } from "@schlessera/brain-ui-sdk/server";
15
+ import { type ActivityStore, type RollupPricing } from "./store.js";
16
+ import { type ActivityStream } from "./stream.js";
17
+ import { type ActivityNotifier } from "./notify.js";
18
+ import { type PushSender } from "./push-sender.js";
19
+ export interface ActivityRuntime {
20
+ store: ActivityStore;
21
+ stream: ActivityStream;
22
+ notifier: ActivityNotifier;
23
+ pushSender: PushSender;
24
+ /** The agent-facing read seam (bridge.queryActivity). */
25
+ query(query: ActivityQuery): ActivityQueryResult;
26
+ close(): void;
27
+ }
28
+ export declare function createActivityRuntime(db: Database, deps: {
29
+ log: Logger;
30
+ /**
31
+ * The app's shared model-pricing instance, for rollup-time effective
32
+ * cost. Optional so embedders without one fall back to the store's own
33
+ * env-derived default (the same instance shape, just not shared).
34
+ */
35
+ pricing?: RollupPricing;
36
+ }): ActivityRuntime;
37
+ //# sourceMappingURL=runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/activity/runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAE1F,OAAO,EAAuB,KAAK,aAAa,EAAE,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AACzF,OAAO,EAAwB,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AACxE,OAAO,EAA0B,KAAK,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC5E,OAAO,EAAoB,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAgBrE,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,aAAa,CAAC;IACrB,MAAM,EAAE,cAAc,CAAC;IACvB,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,UAAU,EAAE,UAAU,CAAC;IACvB,yDAAyD;IACzD,KAAK,CAAC,KAAK,EAAE,aAAa,GAAG,mBAAmB,CAAC;IACjD,KAAK,IAAI,IAAI,CAAC;CACf;AAED,wBAAgB,qBAAqB,CACnC,EAAE,EAAE,QAAQ,EACZ,IAAI,EAAE;IACJ,GAAG,EAAE,MAAM,CAAC;IACZ;;;;OAIG;IACH,OAAO,CAAC,EAAE,aAAa,CAAC;CACzB,GACA,eAAe,CAiHjB"}
@@ -0,0 +1,127 @@
1
+ import { createActivityStore } from "./store.js";
2
+ import { createActivityStream } from "./stream.js";
3
+ import { createActivityNotifier } from "./notify.js";
4
+ import { createPushSender } from "./push-sender.js";
5
+ import { digestRetentionFloor } from "./digest.js";
6
+ import { runActivityQuery } from "./query.js";
7
+ import { getDetailRetentionDays } from "../db/settings.js";
8
+ // Activity lifecycle cadence. The stale threshold must comfortably exceed
9
+ // the cron wrapper's heartbeat interval (~30s) so a live writer is never
10
+ // swept; the tick doubles as the always-on low-frequency sweep the
11
+ // notification layer extends.
12
+ const ACTIVITY_TICK_MS = 20_000;
13
+ const ACTIVITY_STALE_AFTER_MS = 2 * 60 * 1000;
14
+ const ACTIVITY_PRUNE_INTERVAL_MS = 60 * 60 * 1000;
15
+ const ACTIVITY_HARD_CEILING_MS = 90 * 24 * 60 * 60 * 1000;
16
+ /** Acknowledged notification intents are kept this long, then deleted. */
17
+ const INTENT_RETENTION_MS = 30 * 24 * 60 * 60 * 1000;
18
+ export function createActivityRuntime(db, deps) {
19
+ const { log } = deps;
20
+ const store = createActivityStore(db, deps.pricing ? { pricing: deps.pricing } : {});
21
+ const stream = createActivityStream(store, log);
22
+ // The notifier is created BEFORE the boot sweep: its change cursor starts
23
+ // at the current head, so the terminal changes the sweep writes are above
24
+ // it and the first tick turns restart-interrupted runs into failure
25
+ // intents. Created after, the sweep's changes would sit below the cursor
26
+ // and a turn killed by a restart would never be notified.
27
+ const notifier = createActivityNotifier({
28
+ db,
29
+ store,
30
+ isWatched: (scope) => stream.isWatched(scope),
31
+ log,
32
+ });
33
+ const pushSender = createPushSender(db, { log });
34
+ // Boot sweep: close this server's orphans from a previous life (interrupted).
35
+ try {
36
+ const orphans = store.sweepOwnOrphans();
37
+ if (orphans > 0) {
38
+ log.emit({
39
+ severityText: "WARN",
40
+ body: "closed orphaned activity spans from a previous process",
41
+ attributes: { count: orphans },
42
+ });
43
+ }
44
+ }
45
+ catch (err) {
46
+ log.emit({
47
+ severityText: "WARN",
48
+ body: "boot activity sweep failed",
49
+ attributes: { error: err instanceof Error ? err.message : String(err) },
50
+ });
51
+ }
52
+ let delivering = false;
53
+ let lastPrune = 0;
54
+ const tick = setInterval(() => {
55
+ try {
56
+ const stale = store.sweepStale(ACTIVITY_STALE_AFTER_MS);
57
+ if (stale > 0) {
58
+ log.emit({
59
+ severityText: "WARN",
60
+ body: "closed stale activity spans (writer went silent)",
61
+ attributes: { count: stale },
62
+ });
63
+ stream.pump();
64
+ }
65
+ notifier.tick();
66
+ // Async delivery, reentrancy-guarded: a slow push service must not
67
+ // stack passes; the next tick simply retries what stayed pending.
68
+ if (!delivering) {
69
+ delivering = true;
70
+ void pushSender
71
+ .deliverPending(notifier)
72
+ .catch((err) => log.emit({
73
+ severityText: "WARN",
74
+ body: "push delivery pass failed",
75
+ attributes: { error: err instanceof Error ? err.message : String(err) },
76
+ }))
77
+ .finally(() => {
78
+ delivering = false;
79
+ });
80
+ }
81
+ if (Date.now() - lastPrune > ACTIVITY_PRUNE_INTERVAL_MS) {
82
+ lastPrune = Date.now();
83
+ // Self-heal the pricing table on server traffic: without this, a
84
+ // long-lived server rolls up from whatever the boot-time refresh
85
+ // fetched. Single-flight + TTL inside ensureFresh make the hourly
86
+ // call free when fresh. The rollup path itself stays synchronous —
87
+ // never refresh from inside the store.
88
+ const pricing = deps.pricing;
89
+ if (typeof pricing?.ensureFresh === "function") {
90
+ void pricing.ensureFresh().catch(() => { });
91
+ }
92
+ store.prune({
93
+ // Full detail survives until the digest has covered it AND the
94
+ // retention window has passed; the hard ceiling bounds growth even
95
+ // if the digest job silently dies. The setting is read every pass
96
+ // so a change applies without a restart.
97
+ digestFloorAt: digestRetentionFloor(db),
98
+ detailRetentionMs: getDetailRetentionDays(db, log) * 24 * 60 * 60 * 1000,
99
+ hardCeilingMs: ACTIVITY_HARD_CEILING_MS,
100
+ });
101
+ notifier.pruneAcknowledged(INTENT_RETENTION_MS);
102
+ }
103
+ }
104
+ catch (err) {
105
+ log.emit({
106
+ severityText: "WARN",
107
+ body: "activity tick failed",
108
+ attributes: { error: err instanceof Error ? err.message : String(err) },
109
+ });
110
+ }
111
+ }, ACTIVITY_TICK_MS);
112
+ // A Bun interval would otherwise keep a closed test app's process alive.
113
+ if (typeof tick === "object" && "unref" in tick)
114
+ tick.unref();
115
+ return {
116
+ store,
117
+ stream,
118
+ notifier,
119
+ pushSender,
120
+ query: (query) => runActivityQuery(db, store, query, notifier),
121
+ close() {
122
+ clearInterval(tick);
123
+ stream.close();
124
+ },
125
+ };
126
+ }
127
+ //# sourceMappingURL=runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../src/activity/runtime.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import { type ActivityStore } from "./store.js";
2
+ export interface SpanSinkContext {
3
+ /** The run every ingested span belongs to. */
4
+ runId: string;
5
+ /** Parent for lines that name none — the cron root span. */
6
+ rootSpanId: string;
7
+ jobName?: string;
8
+ }
9
+ export interface SpanSinkResult {
10
+ /** Byte offset consumed so far — pass back on the next incremental call. */
11
+ offset: number;
12
+ ingested: number;
13
+ skipped: number;
14
+ }
15
+ /**
16
+ * Ingest new complete lines from a span-sink file. Incremental: pass the
17
+ * previous call's `offset`; a partial trailing line is left for next time.
18
+ * A missing file is an empty result, not an error — the child may simply
19
+ * never have emitted.
20
+ */
21
+ export declare function ingestSpanSink(store: ActivityStore, path: string, context: SpanSinkContext, offset?: number): SpanSinkResult;
22
+ //# sourceMappingURL=span-sink.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"span-sink.d.ts","sourceRoot":"","sources":["../../src/activity/span-sink.ts"],"names":[],"mappings":"AAsBA,OAAO,EAAiB,KAAK,aAAa,EAAoC,MAAM,YAAY,CAAC;AAEjG,MAAM,WAAW,eAAe;IAC9B,8CAA8C;IAC9C,KAAK,EAAE,MAAM,CAAC;IACd,4DAA4D;IAC5D,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,4EAA4E;IAC5E,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,aAAa,EACpB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,eAAe,EACxB,MAAM,SAAI,GACT,cAAc,CAuBhB"}
@@ -0,0 +1,138 @@
1
+ /**
2
+ * The span-sink JSONL contract: how an agent-running cron step reports its
3
+ * activity to the wrapper that records the run.
4
+ *
5
+ * The wrapper (brain-ui's cron-run.ts) exports a file path in
6
+ * `BRAIN_ACTIVITY_SPAN_SINK`; the child process appends one JSON object per
7
+ * line and never reads the file back. The wrapper ingests incrementally
8
+ * (liveness) and once at exit (completeness), attaching everything under the
9
+ * run's root span. No core call site emits this yet — the contract ships
10
+ * first, fixture-verified, and producers arrive when core grows an agent
11
+ * loop (see the plan's Deferred to Follow-Up Work).
12
+ *
13
+ * Line shapes (unknown fields ignored, malformed lines skipped and counted):
14
+ *
15
+ * {"type":"span_start","spanId":"s1","name":"execute_tool Read","ts":123,
16
+ * "parentSpanId":"s0"?, "kind":"tool"?}
17
+ * {"type":"span_end","spanId":"s1","outcome":"success","ts":124,
18
+ * "reason":"..."?, "usage":{"inputTokens":1,...}?}
19
+ * {"type":"event","spanId":"s1","eventType":"text","payload":...,"ts":125}
20
+ */
21
+ import { readFileSync } from "fs";
22
+ import { SPAN_OUTCOMES } from "./store.js";
23
+ /**
24
+ * Ingest new complete lines from a span-sink file. Incremental: pass the
25
+ * previous call's `offset`; a partial trailing line is left for next time.
26
+ * A missing file is an empty result, not an error — the child may simply
27
+ * never have emitted.
28
+ */
29
+ export function ingestSpanSink(store, path, context, offset = 0) {
30
+ let text;
31
+ try {
32
+ text = readFileSync(path, "utf-8");
33
+ }
34
+ catch {
35
+ return { offset, ingested: 0, skipped: 0 };
36
+ }
37
+ if (text.length <= offset)
38
+ return { offset, ingested: 0, skipped: 0 };
39
+ const chunk = text.slice(offset);
40
+ const lastNewline = chunk.lastIndexOf("\n");
41
+ if (lastNewline === -1)
42
+ return { offset, ingested: 0, skipped: 0 };
43
+ const complete = chunk.slice(0, lastNewline);
44
+ const consumed = offset + lastNewline + 1;
45
+ let ingested = 0;
46
+ let skipped = 0;
47
+ for (const line of complete.split("\n")) {
48
+ if (!line.trim())
49
+ continue;
50
+ if (applyLine(store, line, context))
51
+ ingested++;
52
+ else
53
+ skipped++;
54
+ }
55
+ return { offset: consumed, ingested, skipped };
56
+ }
57
+ function applyLine(store, line, context) {
58
+ let parsed;
59
+ try {
60
+ parsed = JSON.parse(line);
61
+ }
62
+ catch {
63
+ return false;
64
+ }
65
+ if (!parsed || typeof parsed !== "object")
66
+ return false;
67
+ const spanId = str(parsed.spanId);
68
+ if (!spanId)
69
+ return false;
70
+ // Sink span ids are namespaced by run so two runs' children can never
71
+ // collide in the shared store.
72
+ const scopedId = `${context.runId}:${spanId}`;
73
+ try {
74
+ switch (parsed.type) {
75
+ case "span_start": {
76
+ const name = str(parsed.name);
77
+ if (!name)
78
+ return false;
79
+ const parent = str(parsed.parentSpanId);
80
+ store.startSpan({
81
+ spanId: scopedId,
82
+ runId: context.runId,
83
+ parentSpanId: parent ? `${context.runId}:${parent}` : context.rootSpanId,
84
+ name,
85
+ kind: parsed.kind === "subagent" ? "subagent" : "tool",
86
+ origin: "cron",
87
+ jobName: context.jobName,
88
+ startedAt: num(parsed.ts),
89
+ });
90
+ return true;
91
+ }
92
+ case "span_end": {
93
+ const outcome = str(parsed.outcome);
94
+ if (!outcome || !SPAN_OUTCOMES.includes(outcome))
95
+ return false;
96
+ return store.endSpan(scopedId, {
97
+ outcome: outcome,
98
+ reason: str(parsed.reason),
99
+ endedAt: num(parsed.ts),
100
+ usage: usageOf(parsed.usage),
101
+ });
102
+ }
103
+ case "event": {
104
+ const eventType = str(parsed.eventType);
105
+ if (!eventType)
106
+ return false;
107
+ return store.appendEvent(scopedId, eventType, parsed.payload, num(parsed.ts)) !== null;
108
+ }
109
+ default:
110
+ return false;
111
+ }
112
+ }
113
+ catch {
114
+ // A duplicate span_start (replayed line) or similar must not abort the
115
+ // whole ingest.
116
+ return false;
117
+ }
118
+ }
119
+ function str(v) {
120
+ return typeof v === "string" && v.length > 0 ? v : undefined;
121
+ }
122
+ function num(v) {
123
+ return typeof v === "number" && Number.isFinite(v) ? v : undefined;
124
+ }
125
+ function usageOf(v) {
126
+ if (!v || typeof v !== "object")
127
+ return undefined;
128
+ const u = v;
129
+ return {
130
+ inputTokens: num(u.inputTokens),
131
+ outputTokens: num(u.outputTokens),
132
+ cacheReadTokens: num(u.cacheReadTokens),
133
+ cacheCreationTokens: num(u.cacheCreationTokens),
134
+ costUsd: num(u.costUsd),
135
+ model: str(u.model),
136
+ };
137
+ }
138
+ //# sourceMappingURL=span-sink.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"span-sink.js","sourceRoot":"","sources":["../../src/activity/span-sink.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,256 @@
1
+ /**
2
+ * The activity store: the canonical record of agent activity (migration 007).
3
+ *
4
+ * Every unit of agent work — a turn, a tool call, a subagent run, a cron run —
5
+ * is a span row forming a tree per run. Spans are written AT START (a
6
+ * non-terminal row exists while the work runs — that is what makes
7
+ * glance-checks, stuck-run detection, and "what is running?" queries
8
+ * possible) and updated to a write-once terminal outcome.
9
+ *
10
+ * Ordering has two primitives, both minted here inside the write transaction:
11
+ *
12
+ * - `seq` is per-run monotonic and totally orders every delta-visible write
13
+ * within a run. A snapshot carries the run's high-water seq; a client
14
+ * discards deltas at or below it. Deltas are derived from committed rows —
15
+ * persist-then-emit — so the stream can never show what the store does not
16
+ * hold.
17
+ * - `change_id` (AUTOINCREMENT rowid of activity_changes) is the GLOBAL
18
+ * cursor. It exists because per-run seq cannot discover a run the poller
19
+ * has never seen: a foreign writer's brand-new root span is visible only
20
+ * as "a change with a higher change_id than my cursor".
21
+ *
22
+ * Every write runs under an IMMEDIATE transaction: two processes write this
23
+ * database (server + cron wrapper), and a deferred transaction losing the
24
+ * upgrade race throws SQLITE_BUSY instead of waiting. `createUiDb` sets
25
+ * `busy_timeout` so immediate transactions queue rather than throw.
26
+ *
27
+ * Nothing in here may ever fail the work being observed: callers that
28
+ * instrument live turns wrap calls in try/catch and drop on error
29
+ * (observability must not break the observed) — but the STORE itself throws
30
+ * on programmer error, because a silent half-written record is worse than a
31
+ * loud one.
32
+ */
33
+ import type { Database } from "bun:sqlite";
34
+ import { type BillingMode } from "@schlessera/brain-ui-sdk/protocol";
35
+ import { type PricingRates } from "../pricing/model-pricing.js";
36
+ export declare const SPAN_OUTCOMES: readonly ["success", "error", "timeout", "cancelled", "denied", "interrupted"];
37
+ export type SpanOutcome = (typeof SPAN_OUTCOMES)[number];
38
+ export type SpanKind = "turn" | "tool" | "subagent" | "cron";
39
+ export type SpanOrigin = "session" | "cron";
40
+ /** Token usage as spans carry it — OTel GenAI attribute semantics. */
41
+ export interface SpanUsage {
42
+ inputTokens?: number;
43
+ outputTokens?: number;
44
+ cacheReadTokens?: number;
45
+ cacheCreationTokens?: number;
46
+ costUsd?: number;
47
+ model?: string;
48
+ }
49
+ export interface SpanRow {
50
+ spanId: string;
51
+ runId: string;
52
+ parentSpanId: string | null;
53
+ name: string;
54
+ kind: SpanKind;
55
+ origin: SpanOrigin;
56
+ sessionId: string | null;
57
+ jobName: string | null;
58
+ attrs: Record<string, unknown>;
59
+ startedAt: number;
60
+ waitUntil: number | null;
61
+ endedAt: number | null;
62
+ outcome: SpanOutcome | null;
63
+ outcomeReason: string | null;
64
+ usage: SpanUsage;
65
+ writer: string;
66
+ lastHeartbeatAt: number | null;
67
+ }
68
+ export interface SpanEventRow {
69
+ spanId: string;
70
+ eventIndex: number;
71
+ ts: number;
72
+ eventType: string;
73
+ payload: unknown;
74
+ truncated: boolean;
75
+ }
76
+ /** One `activity_run_rollups` row, snake→camel. */
77
+ export interface RunRollupRow {
78
+ runId: string;
79
+ origin: SpanOrigin;
80
+ name: string;
81
+ sessionId: string | null;
82
+ jobName: string | null;
83
+ startedAt: number;
84
+ endedAt: number | null;
85
+ outcome: SpanOutcome | null;
86
+ durationMs: number | null;
87
+ spanCount: number;
88
+ inputTokens: number | null;
89
+ outputTokens: number | null;
90
+ cacheReadTokens: number | null;
91
+ cacheCreationTokens: number | null;
92
+ costUsd: number | null;
93
+ /**
94
+ * What the run actually cost, frozen at first computation (migration 010):
95
+ * 0 for subscription-billed work regardless of tokens, priced usage for
96
+ * api-billed. NULL = unknown, 0 = genuinely free.
97
+ */
98
+ effectiveCostUsd: number | null;
99
+ /** Billing classification behind `effectiveCostUsd`; NULL = unknown. */
100
+ billingMode: BillingMode | null;
101
+ /** True when the effective cost was computed from estimated rates. */
102
+ pricingEstimate: boolean | null;
103
+ failureReason: string | null;
104
+ detailPruned: boolean;
105
+ }
106
+ /** The one snake→camel mapper for rollup rows — every reader shares it. */
107
+ export declare function rowToRunRollup(r: any): RunRollupRow;
108
+ /**
109
+ * Sum-of-KNOWNS over effective costs: unknown (NULL) rows contribute nothing
110
+ * to the sum and are counted instead, so no aggregate can pass an unknown off
111
+ * as $0 (AE3). One definition for every reader that folds rollup rows.
112
+ */
113
+ export declare function sumEffectiveCost(rows: Array<{
114
+ effectiveCostUsd: number | null;
115
+ }>): {
116
+ effectiveCostUsd: number;
117
+ unpricedRuns: number;
118
+ };
119
+ /** One committed write, as the delta stream sees it. */
120
+ export type ActivityChange = {
121
+ changeId: number;
122
+ runId: string;
123
+ seq: number;
124
+ kind: "span";
125
+ span: SpanRow;
126
+ } | {
127
+ changeId: number;
128
+ runId: string;
129
+ seq: number;
130
+ kind: "event";
131
+ event: SpanEventRow;
132
+ };
133
+ export interface StartSpanInput {
134
+ spanId: string;
135
+ runId: string;
136
+ parentSpanId?: string;
137
+ name: string;
138
+ kind: SpanKind;
139
+ origin: SpanOrigin;
140
+ sessionId?: string;
141
+ jobName?: string;
142
+ attrs?: Record<string, unknown>;
143
+ startedAt?: number;
144
+ }
145
+ export interface EndSpanInput {
146
+ outcome: SpanOutcome;
147
+ reason?: string;
148
+ endedAt?: number;
149
+ usage?: SpanUsage;
150
+ attrs?: Record<string, unknown>;
151
+ }
152
+ export interface RunSnapshot {
153
+ runId: string;
154
+ spans: SpanRow[];
155
+ events: SpanEventRow[];
156
+ /** Per-run high-water seq at the moment of the read, in the SAME
157
+ * transaction as the reads — the poller/subscriber baseline. */
158
+ highWaterSeq: number;
159
+ /** Global cursor at the same moment, for pinning a poller baseline. */
160
+ changeCursor: number;
161
+ }
162
+ export interface PruneOptions {
163
+ /** Spans of runs that ended before this are prunable (digest floor).
164
+ * Absolute epoch ms, not an offset. */
165
+ digestFloorAt: number;
166
+ /** Minimum age (ms since ended) a run must reach before the digest floor
167
+ * may prune its detail. 0 reproduces floor-only pruning. The hard
168
+ * ceiling ignores it. */
169
+ detailRetentionMs: number;
170
+ /** Runs older than this are pruned REGARDLESS of the digest floor. */
171
+ hardCeilingMs: number;
172
+ now?: number;
173
+ /** Max runs pruned per call — pruning is batched so writers are not starved. */
174
+ batch?: number;
175
+ }
176
+ /** A single event payload is capped so no delta can approach the WS frame cap. */
177
+ export declare const MAX_EVENT_PAYLOAD_BYTES = 16384;
178
+ export interface ActivityStore {
179
+ readonly writer: string;
180
+ startSpan(input: StartSpanInput): SpanRow;
181
+ /**
182
+ * Terminal write. Write-once: returns false (and writes nothing) if the
183
+ * span is already terminal or unknown. Merged-outcome precedence lives in
184
+ * the CALLER (the host merges reporter enrichment before its single
185
+ * terminal write); the store just enforces once-ness.
186
+ */
187
+ endSpan(spanId: string, input: EndSpanInput): boolean;
188
+ /** Non-terminal field updates (usage enrichment, wait boundary, attrs). */
189
+ patchSpan(spanId: string, patch: {
190
+ attrs?: Record<string, unknown>;
191
+ usage?: SpanUsage;
192
+ waitUntil?: number;
193
+ }): boolean;
194
+ /**
195
+ * Append one event; null (nothing written) for an unknown span. `cap`
196
+ * lowers the payload size cap for this call — the store's 16 KB invariant
197
+ * is the ceiling regardless. A capped payload is clipped and flagged
198
+ * `truncated`.
199
+ */
200
+ appendEvent(spanId: string, eventType: string, payload: unknown, ts?: number, cap?: number): SpanEventRow | null;
201
+ /** External writers touch their root span so staleness is heartbeat-age based. */
202
+ heartbeat(spanId: string, at?: number): void;
203
+ /** Close every open span of a run as `outcome` (children first), reason on all. */
204
+ cascadeClose(runId: string, outcome: SpanOutcome, reason: string): number;
205
+ /** Boot sweep: close THIS writer's leftover open spans as interrupted. */
206
+ sweepOwnOrphans(): number;
207
+ /** Close open spans whose root heartbeat (or own writer liveness) went stale. */
208
+ sweepStale(staleAfterMs: number, now?: number): number;
209
+ /** Open root spans running longer than the threshold — the watchdog signal. */
210
+ findStuck(thresholdMs: number, now?: number): SpanRow[];
211
+ changesSince(changeCursor: number, limit?: number): ActivityChange[];
212
+ /**
213
+ * Changes past the cursor whose span is a TERMINAL ROOT — the notifier's
214
+ * detection query, filtered and joined SQL-side. One row per span (the
215
+ * highest change_id it appears under).
216
+ */
217
+ terminalRootChangesSince(changeCursor: number, limit?: number): Array<{
218
+ changeId: number;
219
+ span: SpanRow;
220
+ }>;
221
+ latestChangeCursor(): number;
222
+ snapshotRun(runId: string): RunSnapshot | null;
223
+ /** The run's high-water seq — what a full snapshot carries, without the reads. */
224
+ runHighWaterSeq(runId: string): number;
225
+ getSpan(spanId: string): SpanRow | null;
226
+ openRootSpans(): SpanRow[];
227
+ /** Upsert the run's rollup row from current span state (call on terminal). */
228
+ rollupRun(runId: string): void;
229
+ prune(options: PruneOptions): {
230
+ runsPruned: number;
231
+ spansDeleted: number;
232
+ };
233
+ }
234
+ /**
235
+ * The pricing dependency rollups price through. `resolve` MUST be synchronous
236
+ * — it is called inside the rollup write transaction, which never awaits.
237
+ */
238
+ export interface RollupPricing {
239
+ resolve(modelId: string): PricingRates | null;
240
+ }
241
+ export interface CreateActivityStoreOptions {
242
+ /** Process identity stamped on every span this store writes. */
243
+ writer?: string;
244
+ /**
245
+ * Pricing seam for rollup-time effective cost. Defaults to an env-derived
246
+ * `createModelPricing` instance (the `$BRAIN_PATH` cache and the bundled
247
+ * snapshot are both synchronous reads), constructed on first rollup — so
248
+ * the cron wrapper's bare `createActivityStore(db)` prices identically to
249
+ * the server with no wiring of its own. Tests inject a fake to stay
250
+ * network- and disk-free (the default is also disabled under NODE_ENV=test,
251
+ * mirroring the discovery flag's default).
252
+ */
253
+ pricing?: RollupPricing;
254
+ }
255
+ export declare function createActivityStore(db: Database, options?: CreateActivityStoreOptions): ActivityStore;
256
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/activity/store.ts"],"names":[],"mappings":"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"}