@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,131 @@
1
+ import webpush from "web-push";
2
+ /** Per-send cap: a hung push service rejects instead of wedging the
3
+ * delivering guard (the tick's reentrancy flag) forever. */
4
+ const SEND_TIMEOUT_MS = 10_000;
5
+ export function createPushSender(db, options = {}) {
6
+ const log = options.log;
7
+ const subject = options.subject ?? "mailto:admin@localhost";
8
+ const send = options.send ??
9
+ ((sub, payload, opts) => webpush.sendNotification({ endpoint: sub.endpoint, keys: sub.keys }, payload, { vapidDetails: opts.vapidDetails, timeout: opts.timeout }));
10
+ // Generate-once VAPID keys. Concurrent first boots are safe: the INSERT is
11
+ // OR IGNORE and the read-back wins.
12
+ function vapid() {
13
+ const row = db
14
+ .query("SELECT public_key AS publicKey, private_key AS privateKey FROM vapid_keys WHERE id = 1")
15
+ .get();
16
+ if (row)
17
+ return row;
18
+ const generated = webpush.generateVAPIDKeys();
19
+ db.query("INSERT OR IGNORE INTO vapid_keys (id, public_key, private_key, created_at) VALUES (1, ?, ?, ?)").run(generated.publicKey, generated.privateKey, Date.now());
20
+ return vapid();
21
+ }
22
+ function rows() {
23
+ return db.query("SELECT * FROM push_subscriptions ORDER BY created_at").all().map((r) => ({
24
+ endpoint: r.endpoint,
25
+ p256dh: r.p256dh,
26
+ auth: r.auth,
27
+ label: r.label,
28
+ createdAt: r.created_at,
29
+ lastUsedAt: r.last_used_at,
30
+ }));
31
+ }
32
+ return {
33
+ publicKey() {
34
+ return vapid().publicKey;
35
+ },
36
+ subscribe(sub, label) {
37
+ db.query(`INSERT INTO push_subscriptions (endpoint, p256dh, auth, label, created_at)
38
+ VALUES (?, ?, ?, ?, ?)
39
+ ON CONFLICT(endpoint) DO UPDATE SET p256dh = excluded.p256dh, auth = excluded.auth`).run(sub.endpoint, sub.keys.p256dh, sub.keys.auth, label ?? null, Date.now());
40
+ },
41
+ unsubscribe(endpoint) {
42
+ return db.query("DELETE FROM push_subscriptions WHERE endpoint = ?").run(endpoint)
43
+ .changes > 0;
44
+ },
45
+ subscriptions: rows,
46
+ async deliverPending(notifier) {
47
+ const pending = notifier.pending();
48
+ if (pending.length === 0)
49
+ return 0;
50
+ if (rows().length === 0) {
51
+ // No devices: the inbox already has it; nothing to deliver. Leave
52
+ // the intents pending so a device subscribing later still gets them
53
+ // if they are fresh, and the boot sweep can retry.
54
+ return 0;
55
+ }
56
+ const keys = vapid();
57
+ let attempts = 0;
58
+ for (const intent of pending) {
59
+ // Re-read per intent: an endpoint pruned as dead during the previous
60
+ // intent's pass must not be attempted again in this one.
61
+ const subs = rows();
62
+ if (subs.length === 0)
63
+ break;
64
+ // Minimized payload; the tag coalesces repeats per run across devices.
65
+ const payload = JSON.stringify({
66
+ title: intent.title,
67
+ body: intent.body,
68
+ tag: `brain-activity:${intent.runId}`,
69
+ url: `/#/activity/${encodeURIComponent(intent.runId)}`,
70
+ });
71
+ attempts += subs.length;
72
+ const results = await Promise.allSettled(subs.map((sub) => send({ endpoint: sub.endpoint, keys: { p256dh: sub.p256dh, auth: sub.auth } }, payload, {
73
+ vapidDetails: { subject, publicKey: keys.publicKey, privateKey: keys.privateKey },
74
+ timeout: SEND_TIMEOUT_MS,
75
+ })));
76
+ // DB writes only after the whole settled pass — never concurrent
77
+ // with in-flight sends against the same handle.
78
+ let delivered = 0;
79
+ const touched = [];
80
+ const dead = [];
81
+ results.forEach((result, i) => {
82
+ const sub = subs[i];
83
+ if (result.status === "fulfilled") {
84
+ delivered++;
85
+ touched.push(sub.endpoint);
86
+ return;
87
+ }
88
+ const err = result.reason;
89
+ const status = err.statusCode;
90
+ if (status === 404 || status === 410) {
91
+ // The push service says this device is gone.
92
+ dead.push(sub.endpoint);
93
+ log?.emit({
94
+ severityText: "INFO",
95
+ body: "pruned dead push subscription",
96
+ attributes: { status: status },
97
+ });
98
+ }
99
+ else {
100
+ log?.emit({
101
+ severityText: "WARN",
102
+ body: "push send failed",
103
+ attributes: {
104
+ error: err instanceof Error ? err.message : String(err),
105
+ ...(status ? { status } : {}),
106
+ },
107
+ });
108
+ }
109
+ });
110
+ for (const endpoint of touched) {
111
+ db.query("UPDATE push_subscriptions SET last_used_at = ? WHERE endpoint = ?").run(Date.now(), endpoint);
112
+ }
113
+ for (const endpoint of dead) {
114
+ db.query("DELETE FROM push_subscriptions WHERE endpoint = ?").run(endpoint);
115
+ }
116
+ notifier.markDelivered(intent.id, delivered > 0 ? "sent" : "send_failed");
117
+ if (delivered === 0) {
118
+ // Every device failed — the all-devices signal (invalid VAPID after
119
+ // a restore, typically). Loud, because it is silent on every phone.
120
+ log?.emit({
121
+ severityText: "ERROR",
122
+ body: "push delivery failed on every subscription",
123
+ attributes: { subscriptions: subs.length },
124
+ });
125
+ }
126
+ }
127
+ return attempts;
128
+ },
129
+ };
130
+ }
131
+ //# sourceMappingURL=push-sender.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"push-sender.js","sourceRoot":"","sources":["../../src/activity/push-sender.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The agent-facing read over the activity record — the same data the UI
3
+ * reads, shaped for model consumption and served through the backend
4
+ * bridge's `queryActivity` seam (the MCP tool calls it).
5
+ *
6
+ * Read-only by construction: nothing here writes. Free-text fields that
7
+ * originated outside the primary agent's own context (failure reasons /
8
+ * stderr tails, subagent transcript excerpts) are DATA, not instructions —
9
+ * the tool layer labels them as such before they reach a model.
10
+ */
11
+ import type { Database } from "bun:sqlite";
12
+ import type { ActivityQuery, ActivityQueryResult } from "@schlessera/brain-ui-sdk/server";
13
+ import type { ActivityNotifier } from "./notify.js";
14
+ import { type ActivityStore } from "./store.js";
15
+ export declare function runActivityQuery(db: Database, store: ActivityStore, query: ActivityQuery, notifier?: ActivityNotifier): ActivityQueryResult;
16
+ //# sourceMappingURL=query.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../src/activity/query.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAI1F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAoC,KAAK,aAAa,EAAgB,MAAM,YAAY,CAAC;AAEhG,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,QAAQ,EACZ,KAAK,EAAE,aAAa,EACpB,KAAK,EAAE,aAAa,EACpB,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,mBAAmB,CA+IrB"}
@@ -0,0 +1,155 @@
1
+ import { isFailureOutcome } from "@schlessera/brain-ui-sdk/protocol";
2
+ import { latestActivityDigest } from "./digest.js";
3
+ import { rowToRunRollup, sumEffectiveCost } from "./store.js";
4
+ export function runActivityQuery(db, store, query, notifier) {
5
+ const limit = Math.min(Math.max(query.limit ?? 20, 1), 100);
6
+ const hoursBack = Math.min(Math.max(query.hoursBack ?? 24, 1), 24 * 90);
7
+ const since = Date.now() - hoursBack * 60 * 60 * 1000;
8
+ switch (query.scope) {
9
+ case "running": {
10
+ return {
11
+ running: store.openRootSpans().map(liveSummary),
12
+ };
13
+ }
14
+ case "recent": {
15
+ const rows = db
16
+ .query("SELECT * FROM activity_run_rollups WHERE started_at >= ? ORDER BY started_at DESC LIMIT ?")
17
+ .all(since, limit).map(rowToRunRollup);
18
+ return {
19
+ windowHours: hoursBack,
20
+ running: store.openRootSpans().map(liveSummary),
21
+ finished: rows.map((r) => ({
22
+ runId: r.runId,
23
+ origin: r.origin,
24
+ name: r.name,
25
+ jobName: r.jobName,
26
+ sessionId: r.sessionId,
27
+ startedAt: iso(r.startedAt),
28
+ outcome: r.outcome,
29
+ durationMs: r.durationMs,
30
+ costUsd: r.costUsd,
31
+ // Explicit nulls, never omitted: the model must be able to tell
32
+ // "unknown" (null) from "genuinely free" ($0) — AE3.
33
+ effectiveCostUsd: r.effectiveCostUsd,
34
+ billingMode: r.billingMode,
35
+ pricingEstimate: r.pricingEstimate,
36
+ failureReason: r.failureReason,
37
+ })),
38
+ };
39
+ }
40
+ case "run": {
41
+ if (!query.runId)
42
+ return { error: "scope 'run' needs runId" };
43
+ const snapshot = store.snapshotRun(query.runId);
44
+ if (snapshot) {
45
+ // The cost facts live on the rollup, not the spans — attach them here
46
+ // the same way the REST detail route does, so the agent sees the
47
+ // frozen triple for a finished run without waiting for detail
48
+ // pruning. Explicit nulls (AE3): a still-running run has no rollup
49
+ // yet, and unknown must never read as $0.
50
+ const rollupRow = db
51
+ .query("SELECT * FROM activity_run_rollups WHERE run_id = ?")
52
+ .get(query.runId);
53
+ const liveRollup = rollupRow ? rowToRunRollup(rollupRow) : null;
54
+ return {
55
+ runId: query.runId,
56
+ costUsd: liveRollup?.costUsd ?? null,
57
+ effectiveCostUsd: liveRollup?.effectiveCostUsd ?? null,
58
+ billingMode: liveRollup?.billingMode ?? null,
59
+ pricingEstimate: liveRollup?.pricingEstimate ?? null,
60
+ failureReason: liveRollup?.failureReason ?? null,
61
+ spans: snapshot.spans.map((s) => ({
62
+ spanId: s.spanId,
63
+ parent: s.parentSpanId,
64
+ name: s.name,
65
+ kind: s.kind,
66
+ startedAt: iso(s.startedAt),
67
+ endedAt: s.endedAt ? iso(s.endedAt) : null,
68
+ outcome: s.outcome,
69
+ reason: s.outcomeReason,
70
+ costUsd: s.usage.costUsd,
71
+ tokens: s.usage.inputTokens !== undefined || s.usage.outputTokens !== undefined
72
+ ? { in: s.usage.inputTokens, out: s.usage.outputTokens }
73
+ : undefined,
74
+ })),
75
+ };
76
+ }
77
+ const row = db
78
+ .query("SELECT * FROM activity_run_rollups WHERE run_id = ?")
79
+ .get(query.runId);
80
+ if (!row)
81
+ return { error: `unknown run ${query.runId}` };
82
+ const rollup = rowToRunRollup(row);
83
+ return {
84
+ runId: query.runId,
85
+ detailPruned: true,
86
+ rollup: {
87
+ name: rollup.name,
88
+ jobName: rollup.jobName,
89
+ outcome: rollup.outcome,
90
+ startedAt: iso(rollup.startedAt),
91
+ durationMs: rollup.durationMs,
92
+ costUsd: rollup.costUsd,
93
+ effectiveCostUsd: rollup.effectiveCostUsd,
94
+ billingMode: rollup.billingMode,
95
+ pricingEstimate: rollup.pricingEstimate,
96
+ failureReason: rollup.failureReason,
97
+ },
98
+ };
99
+ }
100
+ case "rollups": {
101
+ const rows = db
102
+ .query("SELECT * FROM activity_run_rollups WHERE started_at >= ?")
103
+ .all(since).map(rowToRunRollup);
104
+ const digest = latestActivityDigest(db);
105
+ const effective = sumEffectiveCost(rows);
106
+ return {
107
+ windowHours: hoursBack,
108
+ runs: rows.length,
109
+ failures: rows.filter((r) => isFailureOutcome(r.outcome)).length,
110
+ // Both sums are sum-of-KNOWNS; the runs excluded from the effective
111
+ // sum ride along as unpricedRuns so unknown never reads as $0 (AE3).
112
+ costUsd: round(rows.reduce((a, r) => a + (r.costUsd ?? 0), 0)),
113
+ effectiveCostUsd: round(effective.effectiveCostUsd),
114
+ unpricedRuns: effective.unpricedRuns,
115
+ inputTokens: rows.reduce((a, r) => a + (r.inputTokens ?? 0), 0),
116
+ outputTokens: rows.reduce((a, r) => a + (r.outputTokens ?? 0), 0),
117
+ ...(digest ? { lastDigestAt: iso(digest.generatedAt) } : {}),
118
+ };
119
+ }
120
+ case "inbox": {
121
+ // The unacknowledged notification inbox — the same list the UI shows.
122
+ if (!notifier)
123
+ return { error: "inbox unavailable" };
124
+ return {
125
+ intents: notifier.inbox(limit).map((i) => ({
126
+ kind: i.kind,
127
+ title: i.title,
128
+ status: i.status,
129
+ createdAt: iso(i.createdAt),
130
+ runId: i.runId,
131
+ })),
132
+ };
133
+ }
134
+ default:
135
+ return { error: `unknown scope` };
136
+ }
137
+ }
138
+ function liveSummary(span) {
139
+ return {
140
+ runId: span.runId,
141
+ origin: span.origin,
142
+ name: span.name,
143
+ jobName: span.jobName,
144
+ sessionId: span.sessionId,
145
+ startedAt: iso(span.startedAt),
146
+ elapsedMs: Date.now() - span.startedAt,
147
+ };
148
+ }
149
+ function iso(ms) {
150
+ return new Date(ms).toISOString();
151
+ }
152
+ function round(n) {
153
+ return Math.round(n * 10000) / 10000;
154
+ }
155
+ //# sourceMappingURL=query.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query.js","sourceRoot":"","sources":["../../src/activity/query.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Per-turn span derivation: the host-side half of activity recording.
3
+ *
4
+ * The host already sees everything a baseline trace needs — the frames it
5
+ * relays (tool starts/results, the terminal result), its own approval flow
6
+ * (wait time, denials), and the turn lifecycle it owns (timeout, cancel).
7
+ * A `TurnRecorder` turns that stream into spans, so EVERY backend gets a
8
+ * full activity tree with zero backend work; the bridge's activity side
9
+ * channel enriches with what only the backend sees (subagent lifecycle,
10
+ * usage, transcripts).
11
+ *
12
+ * Terminal-write precedence (the write-once race): enrichment arrives on the
13
+ * same ordered call path as frames, and the recorder buffers the result
14
+ * frame's outcome/usage rather than closing the root span on sight. The ONE
15
+ * terminal write for the root happens in `finish()` — after the backend's
16
+ * startTurn resolved, when everything that could refine the outcome has
17
+ * drained. Frame-derived outcome is the fallback, never a competing writer.
18
+ *
19
+ * Nothing here may fail the observed turn: every public method catches and
20
+ * drops its own errors.
21
+ */
22
+ import type { BackendActivityEvent, ServerMessage } from "@schlessera/brain-ui-sdk/server";
23
+ import { type BillingMode } from "@schlessera/brain-ui-sdk/protocol";
24
+ import type { Logger } from "@opentelemetry/api-logs";
25
+ import type { ActivityStore } from "./store.js";
26
+ export interface TurnRecorderDeps {
27
+ store: ActivityStore;
28
+ /** Called after any committed write so the live stream can pump. */
29
+ onWrite?: () => void;
30
+ log?: Logger;
31
+ }
32
+ export interface TurnRecorder {
33
+ /** The run id (one run per turn). */
34
+ readonly runId: string;
35
+ observeFrame(msg: ServerMessage): void;
36
+ observeActivity(event: BackendActivityEvent): void;
37
+ /** The user's approval decision arrived for a gated tool call. */
38
+ onApprovalDecision(toolUseId: string, allowed: boolean): void;
39
+ /**
40
+ * Close the turn: merge the buffered result enrichment into the root's
41
+ * single terminal write, cascade-cancel anything left open, roll up.
42
+ */
43
+ finish(disposition: "success" | "error" | "cancelled" | "timeout"): void;
44
+ }
45
+ export declare function createTurnRecorder(deps: TurnRecorderDeps, turn: {
46
+ turnId: string;
47
+ sessionId: string | null;
48
+ /** The RESOLVED inference profile this turn runs on (post pin-drop fallback). */
49
+ profileId?: string;
50
+ /** Billing classification of that profile, resolved at run start (U3). */
51
+ billingMode?: BillingMode;
52
+ }): TurnRecorder;
53
+ //# sourceMappingURL=recorder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recorder.d.ts","sourceRoot":"","sources":["../../src/activity/recorder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,KAAK,EACV,oBAAoB,EACpB,aAAa,EAEd,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAIL,KAAK,WAAW,EACjB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEtD,OAAO,KAAK,EAAE,aAAa,EAA0B,MAAM,YAAY,CAAC;AAExE,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,aAAa,CAAC;IACrB,oEAAoE;IACpE,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,YAAY;IAC3B,qCAAqC;IACrC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAAC;IACvC,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,IAAI,CAAC;IACnD,kEAAkE;IAClE,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IAC9D;;;OAGG;IACH,MAAM,CAAC,WAAW,EAAE,SAAS,GAAG,OAAO,GAAG,WAAW,GAAG,SAAS,GAAG,IAAI,CAAC;CAC1E;AAED,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,gBAAgB,EACtB,IAAI,EAAE;IACJ,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,iFAAiF;IACjF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0EAA0E;IAC1E,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B,GACA,YAAY,CAkOd"}
@@ -0,0 +1,238 @@
1
+ import { SPAN_OP_EXECUTE_TOOL, SPAN_OP_INVOKE_AGENT, SPAN_TOOL_NAME_PREFIX, } from "@schlessera/brain-ui-sdk/protocol";
2
+ export function createTurnRecorder(deps, turn) {
3
+ const { store, onWrite, log } = deps;
4
+ const runId = turn.turnId;
5
+ const rootSpanId = `${runId}:turn`;
6
+ let sessionId = turn.sessionId ?? undefined;
7
+ let rootStarted = false;
8
+ let finished = false;
9
+ /** Buffered terminal enrichment from the result frame. */
10
+ let resultOutcome = null;
11
+ let resultUsage;
12
+ let resultAttrs;
13
+ const guard = (fn) => {
14
+ try {
15
+ fn();
16
+ }
17
+ catch (err) {
18
+ log?.emit({
19
+ severityText: "WARN",
20
+ body: "activity recording failed; dropping",
21
+ attributes: { error: err instanceof Error ? err.message : String(err) },
22
+ });
23
+ }
24
+ };
25
+ function ensureRoot() {
26
+ if (rootStarted)
27
+ return;
28
+ rootStarted = true;
29
+ store.startSpan({
30
+ spanId: rootSpanId,
31
+ runId,
32
+ name: SPAN_OP_INVOKE_AGENT,
33
+ kind: "turn",
34
+ origin: "session",
35
+ sessionId,
36
+ // Profile + billing ride the ROOT span so the rollup can price the run
37
+ // without any registry or env lookup of its own (a root missing the
38
+ // billing attr falls back to env classification at rollup time).
39
+ attrs: {
40
+ "gen_ai.operation.name": SPAN_OP_INVOKE_AGENT,
41
+ ...(turn.profileId ? { "brain.profile_id": turn.profileId } : {}),
42
+ ...(turn.billingMode ? { "brain.billing_mode": turn.billingMode } : {}),
43
+ },
44
+ });
45
+ onWrite?.();
46
+ }
47
+ function usageFromTurnUsage(usage) {
48
+ const models = usage.perModel ? Object.keys(usage.perModel) : [];
49
+ return {
50
+ inputTokens: usage.inputTokens,
51
+ outputTokens: usage.outputTokens,
52
+ cacheReadTokens: usage.cacheReadTokens,
53
+ cacheCreationTokens: usage.cacheCreationTokens,
54
+ model: models.length === 1 ? models[0] : undefined,
55
+ };
56
+ }
57
+ return {
58
+ runId,
59
+ observeFrame(msg) {
60
+ guard(() => {
61
+ if (finished)
62
+ return;
63
+ switch (msg.type) {
64
+ case "session_info": {
65
+ sessionId = msg.sessionId;
66
+ ensureRoot();
67
+ // The root may have been opened before the session had a name.
68
+ store.patchSpan(rootSpanId, { attrs: { "session.id": msg.sessionId } });
69
+ onWrite?.();
70
+ break;
71
+ }
72
+ case "tool_use_start": {
73
+ ensureRoot();
74
+ const isSubagent = msg.toolName === "Agent";
75
+ store.startSpan({
76
+ spanId: msg.toolUseId,
77
+ runId,
78
+ parentSpanId: msg.parentToolUseId ?? rootSpanId,
79
+ name: `${SPAN_TOOL_NAME_PREFIX}${msg.toolName}`,
80
+ kind: isSubagent ? "subagent" : "tool",
81
+ origin: "session",
82
+ sessionId,
83
+ attrs: {
84
+ "gen_ai.operation.name": isSubagent ? SPAN_OP_INVOKE_AGENT : SPAN_OP_EXECUTE_TOOL,
85
+ "gen_ai.tool.name": msg.toolName,
86
+ },
87
+ });
88
+ onWrite?.();
89
+ break;
90
+ }
91
+ case "tool_approval_request": {
92
+ // The span exists (tool_use_start preceded it). Nothing to write:
93
+ // startedAt already marks the wait's beginning; the grant stamps
94
+ // the wait/execution boundary.
95
+ break;
96
+ }
97
+ case "tool_use_complete": {
98
+ // The complete input object in one frame — tool_input_delta
99
+ // frames stay ignored, no accumulation needed. Recorded as a
100
+ // span event so the Activity drill-in can expand the call (R14).
101
+ store.appendEvent(msg.toolUseId, "tool_input", safeStringify(msg.input), undefined, PAYLOAD_EVENT_CAP);
102
+ onWrite?.();
103
+ break;
104
+ }
105
+ case "tool_result": {
106
+ // endSpan is write-once: a span already closed (a denial landed
107
+ // the outcome first) rejects this as a no-op, and an unknown
108
+ // span id is equally a no-op — no bookkeeping set needed.
109
+ const outcome = msg.isError ? "error" : "success";
110
+ store.endSpan(msg.toolUseId, {
111
+ outcome,
112
+ reason: outcome === "error" ? clip(msg.output, 500) : undefined,
113
+ });
114
+ // Error output rides along too — a failed call's output is what
115
+ // the drill-in needs most. appendEvent no-ops on unknown spans.
116
+ store.appendEvent(msg.toolUseId, "tool_output", msg.output, undefined, PAYLOAD_EVENT_CAP);
117
+ onWrite?.();
118
+ break;
119
+ }
120
+ case "result": {
121
+ // Buffered — the single terminal write happens in finish().
122
+ resultOutcome =
123
+ msg.outcome === "success"
124
+ ? "success"
125
+ : msg.outcome === "cancelled"
126
+ ? "cancelled"
127
+ : msg.outcomeDetail === "max_turns" || msg.outcomeDetail === "max_budget"
128
+ ? "timeout"
129
+ : "error";
130
+ if (msg.usage)
131
+ resultUsage = usageFromTurnUsage(msg.usage);
132
+ if (typeof msg.costUsd === "number") {
133
+ resultUsage = { ...(resultUsage ?? {}), costUsd: msg.costUsd };
134
+ }
135
+ resultAttrs = {
136
+ ...(msg.outcomeDetail ? { "outcome.detail": msg.outcomeDetail } : {}),
137
+ ...(msg.usage?.perModel
138
+ ? { "gen_ai.usage.per_model": msg.usage.perModel }
139
+ : {}),
140
+ };
141
+ break;
142
+ }
143
+ default:
144
+ break;
145
+ }
146
+ });
147
+ },
148
+ observeActivity(event) {
149
+ guard(() => {
150
+ if (finished)
151
+ return;
152
+ switch (event.kind) {
153
+ case "subagent_started": {
154
+ store.patchSpan(event.toolUseId, {
155
+ attrs: {
156
+ "subagent.type": event.subagentType,
157
+ "subagent.description": event.description,
158
+ },
159
+ });
160
+ onWrite?.();
161
+ break;
162
+ }
163
+ case "subagent_status": {
164
+ const attrs = {
165
+ ...(event.usage ? { "subagent.total_tokens": event.usage.totalTokens } : {}),
166
+ ...(event.summary ? { "subagent.summary": event.summary } : {}),
167
+ };
168
+ if (Object.keys(attrs).length > 0) {
169
+ store.patchSpan(event.toolUseId, { attrs });
170
+ onWrite?.();
171
+ }
172
+ break;
173
+ }
174
+ case "subagent_transcript": {
175
+ store.appendEvent(event.toolUseId, `transcript_${event.role}`, event.text);
176
+ onWrite?.();
177
+ break;
178
+ }
179
+ }
180
+ });
181
+ },
182
+ onApprovalDecision(toolUseId, allowed) {
183
+ guard(() => {
184
+ if (allowed) {
185
+ // Everything before this moment was approval wait, not execution.
186
+ store.patchSpan(toolUseId, { waitUntil: Date.now() });
187
+ }
188
+ else {
189
+ // The denied outcome lands now — write-once makes the backend's
190
+ // later error tool_result a no-op on this span.
191
+ store.endSpan(toolUseId, { outcome: "denied", reason: "user declined" });
192
+ }
193
+ onWrite?.();
194
+ });
195
+ },
196
+ finish(disposition) {
197
+ guard(() => {
198
+ if (finished)
199
+ return;
200
+ finished = true;
201
+ ensureRoot();
202
+ // Merged precedence: the buffered result enrichment refines the
203
+ // host's own disposition; the host wins only where the backend said
204
+ // nothing. Cancellation/timeout are host-owned facts and always win.
205
+ const outcome = disposition === "cancelled" || disposition === "timeout"
206
+ ? disposition
207
+ : (resultOutcome ?? disposition);
208
+ store.endSpan(rootSpanId, {
209
+ outcome,
210
+ usage: resultUsage,
211
+ attrs: resultAttrs,
212
+ });
213
+ // Anything still open died with the turn.
214
+ store.cascadeClose(runId, "cancelled", `turn ${outcome}`);
215
+ store.rollupRun(runId);
216
+ onWrite?.();
217
+ });
218
+ },
219
+ };
220
+ }
221
+ function clip(text, max) {
222
+ return text.length > max ? text.slice(0, max) : text;
223
+ }
224
+ /** Cap for recorded tool input/output payload events — roomier than the
225
+ * 500-char outcome-reason clip, still far under the store's 16 KB event cap.
226
+ * Passed to appendEvent, whose clipping sets the wire `truncated` flag. */
227
+ const PAYLOAD_EVENT_CAP = 4096;
228
+ /** Tool inputs are arbitrary values: serialization must never take the turn
229
+ * down with it (BigInt members throw; a toJSON can return undefined). */
230
+ function safeStringify(value) {
231
+ try {
232
+ return JSON.stringify(value) ?? String(value);
233
+ }
234
+ catch {
235
+ return String(value);
236
+ }
237
+ }
238
+ //# sourceMappingURL=recorder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recorder.js","sourceRoot":"","sources":["../../src/activity/recorder.ts"],"names":[],"mappings":"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"}