dsh-context 0.3.0 → 0.4.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 (4) hide show
  1. package/README.md +9 -17
  2. package/lib/client.js +532 -498
  3. package/lib/index.js +239 -279
  4. package/package.json +10 -3
package/lib/index.js CHANGED
@@ -1,276 +1,252 @@
1
- /**
2
- * dsh-context — Host half (installed package entry).
3
- *
4
- * A plain Cordis plugin module (ESM, zero dependencies) loaded by the harness
5
- * as the `dsh-context` loader row. It replays a session's durable event log
6
- * into a per-request context-composition timeline and serves it to the
7
- * Client half over a generic Connection RPC channel (`/dsh-context`).
8
- *
9
- * Performance: live sessions are folded straight from the in-memory log
10
- * (`sessions.get(id).events` — no clone, no parse) and the fold is
11
- * INCREMENTAL: per-session state advances only over newly appended events.
12
- * Cold (persisted, not live) sessions fall back to `sessionQuery` and are
13
- * served from cache once folded, since their logs never grow.
14
- *
15
- * Token figures use the same fixed-density heuristic as the harness's own
16
- * token-meter (4 chars ≈ 1 token, +4 per content block, +4 role framing).
17
- * Labels are sent structured (kind/form/name/count) so the Client localizes.
18
- */
19
-
20
- export const name = 'dsh-context'
21
-
22
- /** Required services: the generic Connection RPC registry (host half). */
23
- export const inject = ['connection']
24
-
25
- // ---- harness token-meter heuristic (mirrors dsh-token-meter/estimate.ts) ----
26
- var CHARS_PER_TOKEN = 4
27
- var BLOCK_OVERHEAD = 4
28
- var ROLE_OVERHEAD = 4
29
-
1
+ const name = "dsh-context";
2
+ const inject = ["connection"];
3
+ const CHARS_PER_TOKEN = 4;
4
+ const BLOCK_OVERHEAD = 4;
5
+ const ROLE_OVERHEAD = 4;
30
6
  function estimateBlocks(blocks) {
31
- var tokens = 0
32
- if (!Array.isArray(blocks)) return 0
33
- for (var i = 0; i < blocks.length; i++) {
34
- var block = blocks[i]
35
- if (block === null || typeof block !== 'object') continue
7
+ let tokens = 0;
8
+ if (!Array.isArray(blocks)) return 0;
9
+ for (const block of blocks) {
10
+ if (block === null || typeof block !== "object") continue;
36
11
  switch (block.type) {
37
- case 'text':
38
- case 'reasoning':
39
- tokens += Math.ceil(String(block.text || '').length / CHARS_PER_TOKEN) + BLOCK_OVERHEAD
40
- break
41
- case 'tool-call':
42
- tokens += Math.ceil(String(block.name || '').length / CHARS_PER_TOKEN)
43
- + Math.ceil(String(block.arguments || '').length / CHARS_PER_TOKEN) + BLOCK_OVERHEAD
44
- break
45
- case 'tool-result':
46
- tokens += estimateBlocks(block.content) + BLOCK_OVERHEAD
47
- break
12
+ case "text":
13
+ case "reasoning":
14
+ tokens += Math.ceil(String(block.text || "").length / CHARS_PER_TOKEN) + BLOCK_OVERHEAD;
15
+ break;
16
+ case "tool-call":
17
+ tokens += Math.ceil(String(block.name || "").length / CHARS_PER_TOKEN) + Math.ceil(String(block.arguments || "").length / CHARS_PER_TOKEN) + BLOCK_OVERHEAD;
18
+ break;
19
+ case "tool-result":
20
+ tokens += estimateBlocks(block.content) + BLOCK_OVERHEAD;
21
+ break;
48
22
  default:
49
- tokens += BLOCK_OVERHEAD + Math.ceil(JSON.stringify(block).length / CHARS_PER_TOKEN)
23
+ tokens += BLOCK_OVERHEAD + Math.ceil(JSON.stringify(block).length / CHARS_PER_TOKEN);
50
24
  }
51
25
  }
52
- return tokens
26
+ return tokens;
53
27
  }
54
-
55
28
  function estimateMessage(message) {
56
- return estimateBlocks(message && message.content) + ROLE_OVERHEAD
29
+ return estimateBlocks(message?.content) + ROLE_OVERHEAD;
57
30
  }
58
-
59
31
  function estimateSystem(text) {
60
- if (typeof text !== 'string' || text.length === 0) return 0
61
- return Math.ceil(text.length / CHARS_PER_TOKEN) + ROLE_OVERHEAD
32
+ if (typeof text !== "string" || text.length === 0) return 0;
33
+ return Math.ceil(text.length / CHARS_PER_TOKEN) + ROLE_OVERHEAD;
62
34
  }
63
-
64
35
  function estimateToolSchema(tool) {
65
- return Math.ceil(JSON.stringify(tool).length / CHARS_PER_TOKEN) + BLOCK_OVERHEAD
36
+ return Math.ceil(JSON.stringify(tool).length / CHARS_PER_TOKEN) + BLOCK_OVERHEAD;
66
37
  }
67
-
68
- // ---- content extraction -----------------------------------------------------
69
-
70
38
  function firstText(blocks) {
71
- if (!Array.isArray(blocks)) return ''
72
- for (var i = 0; i < blocks.length; i++) {
73
- var b = blocks[i]
74
- if (b && b.type === 'text' && typeof b.text === 'string' && b.text.trim() !== '') {
75
- return b.text.replace(/\s+/g, ' ').trim().slice(0, 80)
39
+ if (!Array.isArray(blocks)) return "";
40
+ for (const b of blocks) {
41
+ if (b && b.type === "text" && typeof b.text === "string" && b.text.trim() !== "") {
42
+ return b.text.replace(/\s+/g, " ").trim().slice(0, 80);
76
43
  }
77
44
  }
78
- return ''
45
+ return "";
79
46
  }
80
-
81
47
  function toolCallNames(blocks) {
82
- var names = []
83
- if (!Array.isArray(blocks)) return names
84
- for (var i = 0; i < blocks.length; i++) {
85
- var b = blocks[i]
86
- if (b && b.type === 'tool-call' && typeof b.name === 'string') names.push(b.name)
48
+ const names = [];
49
+ if (!Array.isArray(blocks)) return names;
50
+ for (const b of blocks) {
51
+ if (b && b.type === "tool-call" && typeof b.name === "string") names.push(b.name);
87
52
  }
88
- return names
53
+ return names;
89
54
  }
90
-
91
55
  function isInjection(source) {
92
- // plugin context (AGENTS.md, snapshots, notices, …) and user-explicit skill
93
- // invocations both ride user-role messages with a declared form.
94
- return source !== null && typeof source === 'object'
95
- && (source.kind === 'plugin' || source.kind === 'skill-invocation' || typeof source.form === 'string')
56
+ return source !== null && typeof source === "object" && (source.kind === "plugin" || source.kind === "skill-invocation" || typeof source.form === "string");
96
57
  }
97
-
98
- // ---- the incremental fold -----------------------------------------------------
99
-
100
58
  function createFold() {
101
59
  return {
102
- n: 0, // number of log events already folded
103
- surface: [], // { seq, cat, tokens, form?, text?, tool?, err?, skill?, calls? }
60
+ n: 0,
61
+ // number of log events already folded
62
+ surface: [],
63
+ // { seq, cat, tokens, form?, text?, tool?, err?, skill?, calls? }
104
64
  sums: { user: 0, inject: 0, assistant: 0, tool: 0 },
105
65
  systemTokens: 0,
106
66
  toolsTokens: 0,
107
- toolList: [], // { name, tokens }
108
- model: undefined,
109
- provider: undefined,
110
- lastModel: undefined,
111
- contextWindow: undefined,
112
- requests: [], // one entry per answered model call
113
- events: [], // notable context events (structured; the Client labels them)
114
- callNames: {}, // callId -> tool name
115
- }
67
+ toolList: [],
68
+ // { name, tokens }
69
+ model: void 0,
70
+ provider: void 0,
71
+ lastModel: void 0,
72
+ contextWindow: void 0,
73
+ requests: [],
74
+ // one entry per answered model call
75
+ events: [],
76
+ // notable context events (structured; the Client labels them)
77
+ callNames: {}
78
+ // callId -> tool name
79
+ };
116
80
  }
117
-
118
81
  function categoryOf(type, message) {
119
- if (type === 'assistant/message') return 'assistant'
120
- if (type === 'tool/result') return 'tool'
121
- if (isInjection(message && message.source)) return 'inject'
122
- return 'user'
82
+ if (type === "assistant/message") return "assistant";
83
+ if (type === "tool/result") return "tool";
84
+ if (isInjection(message?.source)) return "inject";
85
+ return "user";
123
86
  }
124
-
125
87
  function applySurface(st, ev, type, data, message) {
126
- var cat = categoryOf(type, message)
127
- var node = { seq: ev.seq, cat: cat, tokens: estimateMessage(message) }
128
- var source = message && message.source
129
- var form = source && source.form
130
- if (typeof form === 'string') node.form = form
131
- if (type === 'assistant/message') {
132
- var text = firstText(message && message.content)
133
- if (text !== '') node.text = text
88
+ const cat = categoryOf(type, message);
89
+ const node = { seq: ev.seq, cat, tokens: estimateMessage(message) };
90
+ const source = message?.source;
91
+ const form = source?.form;
92
+ if (typeof form === "string") node.form = form;
93
+ if (type === "assistant/message") {
94
+ const text = firstText(message?.content);
95
+ if (text !== "") node.text = text;
134
96
  else {
135
- var names = toolCallNames(message && message.content)
136
- if (names.length > 0) node.calls = names.slice(0, 3)
97
+ const names = toolCallNames(message?.content);
98
+ if (names.length > 0) node.calls = names.slice(0, 3);
137
99
  }
138
- } else if (type === 'tool/result') {
139
- var block = message && message.content && message.content[0]
140
- var tname = block && block.callId !== undefined ? st.callNames[block.callId] : undefined
141
- if (tname) node.tool = tname
142
- if (data && data.error) node.err = true
143
- } else if (source && source.kind === 'skill-invocation') {
144
- node.skill = typeof source.name === 'string' ? source.name : '?'
145
- } else if (source && source.kind === 'plugin') {
146
- if (source.form === 'notice' && typeof source.summary === 'string') node.text = source.summary
147
- else if (source.form === 'snapshot' && Array.isArray(source.sections)) {
148
- node.text = source.sections.map(function (s) { return s && s.name }).filter(Boolean).join(', ').slice(0, 80)
100
+ } else if (type === "tool/result") {
101
+ const block = message?.content?.[0];
102
+ const tname = block && block.callId !== void 0 ? st.callNames[block.callId] : void 0;
103
+ if (tname) node.tool = tname;
104
+ if (data?.error) node.err = true;
105
+ } else if (source?.kind === "skill-invocation") {
106
+ node.skill = typeof source.name === "string" ? source.name : "?";
107
+ } else if (source?.kind === "plugin") {
108
+ if (source.form === "notice" && typeof source.summary === "string") node.text = source.summary;
109
+ else if (source.form === "snapshot" && Array.isArray(source.sections)) {
110
+ node.text = source.sections.map((s) => s?.name).filter(Boolean).join(", ").slice(0, 80);
149
111
  } else {
150
- var ptext = firstText(message && message.content)
151
- if (ptext !== '') node.text = ptext
112
+ const ptext = firstText(message?.content);
113
+ if (ptext !== "") node.text = ptext;
152
114
  }
153
115
  } else {
154
- var utext = firstText(message && message.content)
155
- if (utext !== '') node.text = utext
116
+ const utext = firstText(message?.content);
117
+ if (utext !== "") node.text = utext;
156
118
  }
157
-
158
- var op = ev.surfaceOp
159
- if (op !== null && typeof op === 'object' && op.op === 'replace') {
160
- var si = -1
161
- var ei = -1
162
- for (var i = 0; i < st.surface.length; i++) {
163
- if (si < 0 && st.surface[i].seq === op.start) si = i
164
- if (st.surface[i].seq === op.end) { ei = i; break }
119
+ const op = ev.surfaceOp;
120
+ if (op !== null && typeof op === "object" && op.op === "replace") {
121
+ let si = -1;
122
+ let ei = -1;
123
+ for (let i = 0; i < st.surface.length; i++) {
124
+ if (si < 0 && st.surface[i].seq === op.start) si = i;
125
+ if (st.surface[i].seq === op.end) {
126
+ ei = i;
127
+ break;
128
+ }
165
129
  }
166
130
  if (si >= 0 && ei >= si) {
167
- var removed = st.surface.splice(si, ei - si + 1, node)
168
- for (var r = 0; r < removed.length; r++) st.sums[removed[r].cat] -= removed[r].tokens
169
- st.sums[cat] += node.tokens
170
- return node
131
+ const removed = st.surface.splice(si, ei - si + 1, node);
132
+ for (const r of removed) st.sums[r.cat] -= r.tokens;
133
+ st.sums[cat] += node.tokens;
134
+ return node;
171
135
  }
172
136
  }
173
- st.surface.push(node)
174
- st.sums[cat] += node.tokens
175
- return node
137
+ st.surface.push(node);
138
+ st.sums[cat] += node.tokens;
139
+ return node;
176
140
  }
177
-
178
141
  function foldInto(st, events) {
179
- for (var e = st.n; e < events.length; e++) {
180
- var ev = events[e]
181
- if (ev === null || typeof ev !== 'object') continue
182
- var data = ev.data
142
+ for (let e = st.n; e < events.length; e++) {
143
+ const ev = events[e];
144
+ if (ev === null || typeof ev !== "object") continue;
145
+ const data = ev.data;
183
146
  switch (ev.type) {
184
- case 'request/header': {
185
- var header = data && data.header ? data.header : {}
186
- var tools = Array.isArray(header.tools) ? header.tools : []
187
- st.toolList = tools.map(function (t) {
188
- return { name: typeof t.name === 'string' ? t.name : '?', tokens: estimateToolSchema(t) }
189
- })
190
- st.toolsTokens = st.toolList.reduce(function (a, t) { return a + t.tokens }, 0)
191
- if (tools.length > 0) st.toolsTokens += BLOCK_OVERHEAD
192
- st.systemTokens = estimateSystem(header.system)
193
- if (header.config && typeof header.config.model === 'string') st.model = header.config.model
194
- if (header.config && typeof header.config.provider === 'string') st.provider = header.config.provider
195
- if (data && data.reason === 'change' && st.model && st.lastModel && st.model !== st.lastModel) {
196
- st.events.push({ seq: ev.seq, time: ev.time, kind: 'model', from: st.lastModel, to: st.model })
147
+ case "request/header": {
148
+ const header = data?.header ?? {};
149
+ const tools = Array.isArray(header.tools) ? header.tools : [];
150
+ st.toolList = tools.map((t) => ({
151
+ name: typeof t.name === "string" ? t.name : "?",
152
+ tokens: estimateToolSchema(t)
153
+ }));
154
+ st.toolsTokens = st.toolList.reduce((a, t) => a + t.tokens, 0);
155
+ if (tools.length > 0) st.toolsTokens += BLOCK_OVERHEAD;
156
+ st.systemTokens = estimateSystem(header.system);
157
+ if (header.config && typeof header.config.model === "string") st.model = header.config.model;
158
+ if (header.config && typeof header.config.provider === "string") st.provider = header.config.provider;
159
+ if (data?.reason === "change" && st.model && st.lastModel && st.model !== st.lastModel) {
160
+ st.events.push({ seq: ev.seq, time: ev.time, kind: "model", from: st.lastModel, to: st.model });
197
161
  }
198
- if (st.model) st.lastModel = st.model
199
- break
162
+ if (st.model) st.lastModel = st.model;
163
+ break;
200
164
  }
201
- case 'request/context':
202
- if (data && typeof data.contextWindow === 'number') st.contextWindow = data.contextWindow
203
- if (data && typeof data.model === 'string') st.model = data.model
204
- if (data && typeof data.provider === 'string') st.provider = data.provider
205
- break
206
- case 'tool/call':
207
- if (data && data.callId !== undefined && typeof data.name === 'string') st.callNames[data.callId] = data.name
208
- break
209
- case 'user/message': {
210
- var node = applySurface(st, ev, ev.type, data, data)
211
- var source = data && data.source
165
+ case "request/context":
166
+ if (data && typeof data.contextWindow === "number") st.contextWindow = data.contextWindow;
167
+ if (data && typeof data.model === "string") st.model = data.model;
168
+ if (data && typeof data.provider === "string") st.provider = data.provider;
169
+ break;
170
+ case "tool/call":
171
+ if (data && data.callId !== void 0 && typeof data.name === "string") st.callNames[String(data.callId)] = data.name;
172
+ break;
173
+ case "user/message": {
174
+ const msg = data;
175
+ const node = applySurface(st, ev, ev.type, data, msg);
176
+ const source = msg?.source;
212
177
  if (isInjection(source)) {
213
- var rec = { seq: ev.seq, time: ev.time, kind: 'inject', form: source.form || 'context', tokens: node.tokens }
214
- if (source.kind === 'skill-invocation') {
215
- rec.sub = 'skill'
216
- rec.name = typeof source.name === 'string' ? source.name : '?'
217
- } else if (typeof source.plugin === 'string' && source.plugin !== '') {
218
- rec.name = source.plugin
178
+ const rec = {
179
+ seq: ev.seq,
180
+ time: ev.time,
181
+ kind: "inject",
182
+ form: source.form || "context",
183
+ tokens: node.tokens
184
+ };
185
+ if (source.kind === "skill-invocation") {
186
+ rec.sub = "skill";
187
+ rec.name = typeof source.name === "string" ? source.name : "?";
188
+ } else if (typeof source.plugin === "string" && source.plugin !== "") {
189
+ rec.name = source.plugin;
219
190
  }
220
- st.events.push(rec)
191
+ st.events.push(rec);
221
192
  }
222
- break
193
+ break;
223
194
  }
224
- case 'tool/result':
225
- applySurface(st, ev, ev.type, data, data && data.message)
226
- break
227
- case 'assistant/message': {
228
- // Snapshot the request exactly as dispatched: current surface + header,
229
- // before this response joins the surface.
230
- var usage = data && data.usage
231
- var record = {
232
- turn: data && data.turn, step: data && data.step, time: ev.time, seq: ev.seq,
195
+ case "tool/result":
196
+ applySurface(st, ev, ev.type, data, data);
197
+ break;
198
+ case "assistant/message": {
199
+ const usage = data?.usage;
200
+ const total = st.systemTokens + st.toolsTokens + st.sums.user + st.sums.inject + st.sums.assistant + st.sums.tool;
201
+ const record = {
202
+ turn: data && typeof data.turn === "number" ? data.turn : void 0,
203
+ step: data && typeof data.step === "number" ? data.step : void 0,
204
+ time: ev.time,
205
+ seq: ev.seq,
233
206
  system: st.systemTokens,
234
207
  tools: st.toolsTokens,
235
208
  user: st.sums.user,
236
209
  inject: st.sums.inject,
237
210
  assistant: st.sums.assistant,
238
211
  tool: st.sums.tool,
212
+ total
213
+ };
214
+ if (usage && typeof usage.inputTokens === "number") {
215
+ record.prompt = usage.inputTokens + (usage.cacheReadTokens || 0) + (usage.cacheWriteTokens || 0);
216
+ if (typeof usage.outputTokens === "number") record.output = usage.outputTokens;
239
217
  }
240
- record.total = record.system + record.tools + record.user + record.inject + record.assistant + record.tool
241
- if (usage && typeof usage.inputTokens === 'number') {
242
- record.prompt = usage.inputTokens + (usage.cacheReadTokens || 0) + (usage.cacheWriteTokens || 0)
243
- if (typeof usage.outputTokens === 'number') record.output = usage.outputTokens
244
- }
245
- st.requests.push(record)
246
- applySurface(st, ev, ev.type, data, data && data.message)
247
- break
218
+ st.requests.push(record);
219
+ applySurface(st, ev, ev.type, data, data);
220
+ break;
248
221
  }
249
- case 'compaction/summary':
222
+ case "compaction/summary":
250
223
  st.events.push({
251
- seq: ev.seq, time: ev.time, kind: 'compaction',
252
- tokens: data && typeof data.shadowedTokenCount === 'number' ? data.shadowedTokenCount : 0,
253
- count: data && Array.isArray(data.shadowedSeqs) ? data.shadowedSeqs.length : 0,
254
- })
255
- break
256
- case 'compaction/prune':
224
+ seq: ev.seq,
225
+ time: ev.time,
226
+ kind: "compaction",
227
+ tokens: data && typeof data.shadowedTokenCount === "number" ? data.shadowedTokenCount : 0,
228
+ count: data && Array.isArray(data.shadowedSeqs) ? data.shadowedSeqs.length : 0
229
+ });
230
+ break;
231
+ case "compaction/prune":
257
232
  st.events.push({
258
- seq: ev.seq, time: ev.time, kind: 'prune',
259
- tokens: data && typeof data.shadowedTokenCount === 'number' ? data.shadowedTokenCount : 0,
260
- })
261
- break
233
+ seq: ev.seq,
234
+ time: ev.time,
235
+ kind: "prune",
236
+ tokens: data && typeof data.shadowedTokenCount === "number" ? data.shadowedTokenCount : 0
237
+ });
238
+ break;
262
239
  default:
263
- break
240
+ break;
264
241
  }
265
242
  }
266
- st.n = events.length
267
- if (st.requests.length > 160) st.requests = st.requests.slice(-160)
268
- if (st.events.length > 150) st.events = st.events.slice(-150)
243
+ st.n = events.length;
244
+ if (st.requests.length > 160) st.requests = st.requests.slice(-160);
245
+ if (st.events.length > 150) st.events = st.events.slice(-150);
269
246
  }
270
-
271
247
  function buildResult(st) {
272
- var surfaceTotal = st.sums.user + st.sums.inject + st.sums.assistant + st.sums.tool
273
- var result = {
248
+ const surfaceTotal = st.sums.user + st.sums.inject + st.sums.assistant + st.sums.tool;
249
+ const result = {
274
250
  ok: true,
275
251
  model: st.model,
276
252
  provider: st.provider,
@@ -282,96 +258,80 @@ function buildResult(st) {
282
258
  inject: st.sums.inject,
283
259
  assistant: st.sums.assistant,
284
260
  tool: st.sums.tool,
285
- total: surfaceTotal + st.systemTokens + st.toolsTokens,
261
+ total: surfaceTotal + st.systemTokens + st.toolsTokens
286
262
  },
287
263
  toolList: st.toolList,
288
264
  requests: st.requests,
289
265
  events: st.events,
290
- }
291
- // Bound the payload: the newest surface nodes carry the most signal.
292
- var MAX_NODES = 200
293
- result.droppedNodes = Math.max(0, st.surface.length - MAX_NODES)
294
- result.nodes = st.surface.slice(-MAX_NODES)
295
- return result
266
+ nodes: [],
267
+ droppedNodes: 0
268
+ };
269
+ const MAX_NODES = 200;
270
+ result.droppedNodes = Math.max(0, st.surface.length - MAX_NODES);
271
+ result.nodes = st.surface.slice(-MAX_NODES);
272
+ return result;
296
273
  }
297
-
298
- // ---- RPC endpoint: /dsh-context snapshot -------------------------------------
299
- //
300
- // The generic Connection RPC channel replaces the dynamic-runner
301
- // `harness.handle` seat: installed packages register a channel on the host
302
- // half and call it from the browser half through `ctx.connection.rpc.call`.
303
- // Responses use the harness RpcResult envelope ({ok:true,value} | {ok:false,error}).
304
-
305
274
  async function computeSnapshot(ctx, states, sessionId) {
306
- var st = states.get(sessionId)
307
- if (st === undefined) {
308
- st = { fold: createFold(), count: -1, result: null }
309
- states.set(sessionId, st)
275
+ let st = states.get(sessionId);
276
+ if (st === void 0) {
277
+ st = { fold: createFold(), count: -1, result: null };
278
+ states.set(sessionId, st);
310
279
  }
311
-
312
- // Resolve the log sources lazily per call: `sessions` / `sessionQuery` may
313
- // be provided after this plugin applies, and a replaced service must not
314
- // leave us holding a stale instance.
315
- var sessions = ctx.get('sessions')
316
- var sessionQuery = ctx.get('sessionQuery')
317
-
318
- // Live sessions fold from the in-memory log — no clone, no disk parse.
319
- var live = sessions !== undefined ? sessions.get(sessionId) : undefined
320
- var events
321
- if (live !== undefined) {
322
- events = live.events
280
+ const sessions = ctx.get("sessions");
281
+ const sessionQuery = ctx.get("sessionQuery");
282
+ const live = sessions !== void 0 ? sessions.get(sessionId) : void 0;
283
+ let events;
284
+ if (live !== void 0) {
285
+ events = live.events;
323
286
  } else {
324
- if (sessionQuery === undefined) throw new Error('session is not live and sessionQuery is unavailable')
287
+ if (sessionQuery === void 0) throw new Error("session is not live and sessionQuery is unavailable");
325
288
  if (st.result !== null && st.count >= 0) {
326
- // Cold logs never grow: probe the lightweight record count only.
327
- var records = await sessionQuery.listEvents(sessionId)
328
- if (records.length === st.count) return st.result
289
+ const records = await sessionQuery.listEvents(sessionId);
290
+ if (records.length === st.count) return st.result;
329
291
  }
330
- var snapshot = await sessionQuery.readSession(sessionId)
331
- events = snapshot && Array.isArray(snapshot.events) ? snapshot.events : []
292
+ const snapshot = await sessionQuery.readSession(sessionId);
293
+ events = snapshot && Array.isArray(snapshot.events) ? snapshot.events : [];
332
294
  }
333
-
334
- if (events.length === st.count && st.result !== null) return st.result
335
- if (events.length < st.fold.n) st.fold = createFold() // defensive: log replaced
336
- foldInto(st.fold, events)
337
- st.count = events.length
338
- st.result = buildResult(st.fold)
339
- return st.result
295
+ if (events.length === st.count && st.result !== null) return st.result;
296
+ if (events.length < st.fold.n) st.fold = createFold();
297
+ foldInto(st.fold, events);
298
+ st.count = events.length;
299
+ st.result = buildResult(st.fold);
300
+ return st.result;
340
301
  }
341
-
342
- export function apply(ctx) {
343
- // sessionId -> { fold state + last built result + the count it reflects }.
344
- var states = new Map()
345
-
346
- ctx.effect(function () {
302
+ function apply(ctx) {
303
+ const states = /* @__PURE__ */ new Map();
304
+ ctx.effect(() => {
347
305
  return ctx.connection.rpc.handle(
348
- '/dsh-context',
349
- async function (endpoint, payload) {
306
+ "/dsh-context",
307
+ async (endpoint, payload) => {
350
308
  try {
351
- if (endpoint !== 'snapshot') {
352
- return {
353
- ok: false,
354
- error: { code: 'internal', message: 'unknown endpoint: ' + endpoint, details: {} },
355
- }
309
+ if (endpoint !== "snapshot") {
310
+ return { ok: false, error: { code: "internal", message: `unknown endpoint: ${endpoint}`, details: {} } };
356
311
  }
357
- var sessionId = payload !== null && typeof payload === 'object' ? payload.sessionId : undefined
358
- if (typeof sessionId !== 'string' || sessionId === '') {
359
- return { ok: false, error: { code: 'internal', message: 'missing sessionId', details: {} } }
312
+ const sessionId = payload !== null && typeof payload === "object" ? payload.sessionId : void 0;
313
+ if (typeof sessionId !== "string" || sessionId === "") {
314
+ return { ok: false, error: { code: "internal", message: "missing sessionId", details: {} } };
360
315
  }
361
- var value = await computeSnapshot(ctx, states, sessionId)
362
- return { ok: true, value: value }
316
+ const value = await computeSnapshot(ctx, states, sessionId);
317
+ return { ok: true, value };
363
318
  } catch (err) {
364
319
  return {
365
320
  ok: false,
366
321
  error: {
367
- code: 'internal',
368
- message: String(err && err.message ? err.message : err),
369
- details: {},
370
- },
371
- }
322
+ code: "internal",
323
+ message: err instanceof Error ? err.message : String(err),
324
+ details: {}
325
+ }
326
+ };
372
327
  }
373
328
  },
374
- { authority: 'trusted-host' },
375
- )
376
- }, 'dsh-context: rpc channel')
329
+ { authority: "trusted-host" }
330
+ );
331
+ }, "dsh-context: rpc channel");
377
332
  }
333
+ export {
334
+ apply,
335
+ inject,
336
+ name
337
+ };