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.
- package/README.md +9 -17
- package/lib/client.js +532 -498
- package/lib/index.js +239 -279
- package/package.json +10 -3
package/lib/index.js
CHANGED
|
@@ -1,276 +1,252 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
|
|
32
|
-
if (!Array.isArray(blocks)) return 0
|
|
33
|
-
for (
|
|
34
|
-
|
|
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
|
|
38
|
-
case
|
|
39
|
-
tokens += Math.ceil(String(block.text ||
|
|
40
|
-
break
|
|
41
|
-
case
|
|
42
|
-
tokens += Math.ceil(String(block.name ||
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
|
29
|
+
return estimateBlocks(message?.content) + ROLE_OVERHEAD;
|
|
57
30
|
}
|
|
58
|
-
|
|
59
31
|
function estimateSystem(text) {
|
|
60
|
-
if (typeof text !==
|
|
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 (
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
83
|
-
if (!Array.isArray(blocks)) return names
|
|
84
|
-
for (
|
|
85
|
-
|
|
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
|
-
|
|
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,
|
|
103
|
-
|
|
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: [],
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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 ===
|
|
120
|
-
if (type ===
|
|
121
|
-
if (isInjection(message
|
|
122
|
-
return
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if (typeof form ===
|
|
131
|
-
if (type ===
|
|
132
|
-
|
|
133
|
-
if (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
|
-
|
|
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 ===
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
if (tname) node.tool = tname
|
|
142
|
-
if (data
|
|
143
|
-
} else if (source
|
|
144
|
-
node.skill = typeof source.name ===
|
|
145
|
-
} else if (source
|
|
146
|
-
if (source.form ===
|
|
147
|
-
else if (source.form ===
|
|
148
|
-
node.text = source.sections.map(
|
|
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
|
-
|
|
151
|
-
if (ptext !==
|
|
112
|
+
const ptext = firstText(message?.content);
|
|
113
|
+
if (ptext !== "") node.text = ptext;
|
|
152
114
|
}
|
|
153
115
|
} else {
|
|
154
|
-
|
|
155
|
-
if (utext !==
|
|
116
|
+
const utext = firstText(message?.content);
|
|
117
|
+
if (utext !== "") node.text = utext;
|
|
156
118
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
if (
|
|
164
|
-
|
|
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
|
-
|
|
168
|
-
for (
|
|
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 (
|
|
180
|
-
|
|
181
|
-
if (ev === null || typeof ev !==
|
|
182
|
-
|
|
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
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
st.toolList = tools.map(
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
st.
|
|
193
|
-
|
|
194
|
-
if (header.config && typeof header.config.
|
|
195
|
-
if (
|
|
196
|
-
|
|
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
|
|
202
|
-
if (data && typeof data.contextWindow ===
|
|
203
|
-
if (data && typeof data.model ===
|
|
204
|
-
if (data && typeof data.provider ===
|
|
205
|
-
break
|
|
206
|
-
case
|
|
207
|
-
if (data && data.callId !==
|
|
208
|
-
break
|
|
209
|
-
case
|
|
210
|
-
|
|
211
|
-
|
|
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
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
|
225
|
-
applySurface(st, ev, ev.type, data, data
|
|
226
|
-
break
|
|
227
|
-
case
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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
|
|
222
|
+
case "compaction/summary":
|
|
250
223
|
st.events.push({
|
|
251
|
-
seq: ev.seq,
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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,
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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
|
-
|
|
273
|
-
|
|
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
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
result.
|
|
295
|
-
|
|
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
|
-
|
|
307
|
-
if (st ===
|
|
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
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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 ===
|
|
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
|
-
|
|
327
|
-
|
|
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
|
-
|
|
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
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
st.
|
|
338
|
-
st.result
|
|
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
|
-
|
|
343
|
-
|
|
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
|
-
|
|
349
|
-
async
|
|
306
|
+
"/dsh-context",
|
|
307
|
+
async (endpoint, payload) => {
|
|
350
308
|
try {
|
|
351
|
-
if (endpoint !==
|
|
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
|
-
|
|
358
|
-
if (typeof sessionId !==
|
|
359
|
-
return { ok: false, error: { code:
|
|
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
|
-
|
|
362
|
-
return { ok: true, 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:
|
|
368
|
-
message:
|
|
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:
|
|
375
|
-
)
|
|
376
|
-
},
|
|
329
|
+
{ authority: "trusted-host" }
|
|
330
|
+
);
|
|
331
|
+
}, "dsh-context: rpc channel");
|
|
377
332
|
}
|
|
333
|
+
export {
|
|
334
|
+
apply,
|
|
335
|
+
inject,
|
|
336
|
+
name
|
|
337
|
+
};
|