@fiale-plus/pi-rogue-bundle 0.1.16 → 0.1.18
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 +2 -1
- package/node_modules/@fiale-plus/pi-core/src/context-broker.ts +26 -0
- package/node_modules/@fiale-plus/pi-rogue-advisor/src/extension.ts +26 -7
- package/node_modules/@fiale-plus/pi-rogue-advisor/src/loop-convergence.test.ts +17 -0
- package/node_modules/@fiale-plus/pi-rogue-context-broker/README.md +12 -6
- package/node_modules/@fiale-plus/pi-rogue-context-broker/package.json +5 -2
- package/node_modules/@fiale-plus/pi-rogue-context-broker/src/extension.test.ts +362 -3
- package/node_modules/@fiale-plus/pi-rogue-context-broker/src/extension.ts +278 -28
- package/node_modules/@fiale-plus/pi-rogue-context-broker/src/file.ts +191 -0
- package/node_modules/@fiale-plus/pi-rogue-context-broker/src/index.test.ts +74 -0
- package/node_modules/@fiale-plus/pi-rogue-context-broker/src/index.ts +112 -20
- package/node_modules/@fiale-plus/pi-rogue-context-broker/src/sqlite.test.ts +98 -0
- package/node_modules/@fiale-plus/pi-rogue-context-broker/src/sqlite.ts +524 -0
- package/package.json +4 -2
- package/src/context-broker-file.ts +1 -0
- package/src/context-broker-sqlite.ts +1 -0
- package/src/extension.test.ts +5 -0
- package/src/extension.ts +3 -3
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
1
4
|
import { afterEach, describe, expect, it } from "vitest";
|
|
2
5
|
import { registerContextBrokerBeta, shouldEnableContextBrokerBeta } from "./extension.js";
|
|
3
6
|
|
|
4
7
|
function createPiMock() {
|
|
5
8
|
const handlers = new Map<string, any[]>();
|
|
6
9
|
const commands = new Map<string, any>();
|
|
10
|
+
const tools = new Map<string, any>();
|
|
7
11
|
const pi: any = {
|
|
8
12
|
on(name: string, handler: any) {
|
|
9
13
|
handlers.set(name, [...(handlers.get(name) ?? []), handler]);
|
|
@@ -11,8 +15,11 @@ function createPiMock() {
|
|
|
11
15
|
registerCommand(name: string, options: any) {
|
|
12
16
|
commands.set(name, options);
|
|
13
17
|
},
|
|
18
|
+
registerTool(tool: any) {
|
|
19
|
+
tools.set(tool.name, tool);
|
|
20
|
+
},
|
|
14
21
|
};
|
|
15
|
-
return { pi, handlers, commands };
|
|
22
|
+
return { pi, handlers, commands, tools };
|
|
16
23
|
}
|
|
17
24
|
|
|
18
25
|
function createCtx(entries: any[] = []) {
|
|
@@ -61,12 +68,13 @@ describe("context broker beta enablement", () => {
|
|
|
61
68
|
expect(shouldEnableContextBrokerBeta()).toBe(true);
|
|
62
69
|
});
|
|
63
70
|
|
|
64
|
-
it("registers /context with command completions", () => {
|
|
65
|
-
const { pi, commands } = createPiMock();
|
|
71
|
+
it("registers /context with command completions and the context_lookup tool", () => {
|
|
72
|
+
const { pi, commands, tools } = createPiMock();
|
|
66
73
|
registerContextBrokerBeta(pi);
|
|
67
74
|
|
|
68
75
|
const command = commands.get("context");
|
|
69
76
|
expect(command).toBeTruthy();
|
|
77
|
+
expect(tools.has("context_lookup")).toBe(true);
|
|
70
78
|
expect(command.getArgumentCompletions("")?.map((item: any) => item.value.trim())).toEqual([
|
|
71
79
|
"status",
|
|
72
80
|
"brief",
|
|
@@ -112,6 +120,42 @@ describe("context broker beta enablement", () => {
|
|
|
112
120
|
expect(notifications.at(-1)?.message).toContain("README.md");
|
|
113
121
|
});
|
|
114
122
|
|
|
123
|
+
it("purges unpinned broker artifacts after session compaction", async () => {
|
|
124
|
+
const { pi, handlers, commands } = createPiMock();
|
|
125
|
+
registerContextBrokerBeta(pi, { briefBytes: 1200 });
|
|
126
|
+
const { ctx, notifications } = createCtx();
|
|
127
|
+
|
|
128
|
+
await runHandlers(handlers, "session_start", { type: "session_start" }, ctx);
|
|
129
|
+
await runHandlers(handlers, "tool_result", {
|
|
130
|
+
type: "tool_result",
|
|
131
|
+
toolCallId: "scratch-call",
|
|
132
|
+
toolName: "bash",
|
|
133
|
+
input: { command: "echo scratch" },
|
|
134
|
+
content: [{ type: "text", text: "scratch payload" }],
|
|
135
|
+
isError: false,
|
|
136
|
+
}, ctx);
|
|
137
|
+
await runHandlers(handlers, "tool_result", {
|
|
138
|
+
type: "tool_result",
|
|
139
|
+
toolCallId: "keep-call",
|
|
140
|
+
toolName: "bash",
|
|
141
|
+
input: { command: "echo keep" },
|
|
142
|
+
content: [{ type: "text", text: "keep payload" }],
|
|
143
|
+
isError: false,
|
|
144
|
+
}, ctx);
|
|
145
|
+
const keepCompletion = commands.get("context").getArgumentCompletions("pin ")?.find((item: any) => String(item.description).includes("echo keep"));
|
|
146
|
+
const keepHandle = keepCompletion?.value.replace(/^pin /, "");
|
|
147
|
+
expect(keepHandle).toBeTruthy();
|
|
148
|
+
await commands.get("context").handler(`pin ${keepHandle}`, ctx);
|
|
149
|
+
|
|
150
|
+
await runHandlers(handlers, "session_compact", { type: "session_compact", compactionEntry: { summary: "compact" }, fromExtension: false }, ctx);
|
|
151
|
+
await commands.get("context").handler("brief", ctx);
|
|
152
|
+
|
|
153
|
+
const brief = notifications.at(-1)?.message ?? "";
|
|
154
|
+
expect(brief).toContain("echo keep");
|
|
155
|
+
expect(brief).not.toContain("echo scratch");
|
|
156
|
+
expect(notifications.some((item) => item.message.includes("compact cleanup purged 1 unpinned artifact"))).toBe(true);
|
|
157
|
+
});
|
|
158
|
+
|
|
115
159
|
it("is safe on malformed session branches", async () => {
|
|
116
160
|
const { pi, handlers } = createPiMock();
|
|
117
161
|
registerContextBrokerBeta(pi);
|
|
@@ -196,6 +240,321 @@ describe("context broker beta enablement", () => {
|
|
|
196
240
|
expect(Buffer.byteLength(payload, "utf8")).toBeLessThanOrEqual(50);
|
|
197
241
|
});
|
|
198
242
|
|
|
243
|
+
it("context_lookup tool dereferences handles for exact evidence", async () => {
|
|
244
|
+
const { pi, handlers, commands, tools } = createPiMock();
|
|
245
|
+
registerContextBrokerBeta(pi, { lookupBytes: 500 });
|
|
246
|
+
const { ctx } = createCtx();
|
|
247
|
+
|
|
248
|
+
await runHandlers(handlers, "session_start", { type: "session_start" }, ctx);
|
|
249
|
+
await runHandlers(handlers, "tool_result", {
|
|
250
|
+
type: "tool_result",
|
|
251
|
+
toolCallId: "call-tool-lookup",
|
|
252
|
+
toolName: "bash",
|
|
253
|
+
input: { command: "echo evidence" },
|
|
254
|
+
content: [{ type: "text", text: "exact evidence payload" }],
|
|
255
|
+
isError: false,
|
|
256
|
+
}, ctx);
|
|
257
|
+
const handle = commands.get("context").getArgumentCompletions("lookup ")?.[0].value.replace(/^lookup /, "");
|
|
258
|
+
const result = await tools.get("context_lookup").execute("lookup-call", { handle }, undefined, undefined, ctx);
|
|
259
|
+
|
|
260
|
+
expect(result.content[0].text).toContain(handle);
|
|
261
|
+
expect(result.content[0].text).toContain("exact evidence payload");
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it("does not broker context_lookup results recursively", async () => {
|
|
265
|
+
const { pi, handlers, commands, tools } = createPiMock();
|
|
266
|
+
registerContextBrokerBeta(pi, { lookupBytes: 500, rewriteThresholdBytes: 1 });
|
|
267
|
+
const { ctx, notifications } = createCtx();
|
|
268
|
+
|
|
269
|
+
await runHandlers(handlers, "session_start", { type: "session_start" }, ctx);
|
|
270
|
+
await runHandlers(handlers, "tool_result", {
|
|
271
|
+
type: "tool_result",
|
|
272
|
+
toolCallId: "call-source",
|
|
273
|
+
toolName: "bash",
|
|
274
|
+
input: { command: "echo source" },
|
|
275
|
+
content: [{ type: "text", text: "source evidence payload" }],
|
|
276
|
+
isError: false,
|
|
277
|
+
}, ctx);
|
|
278
|
+
const handle = commands.get("context").getArgumentCompletions("lookup ")?.[0].value.replace(/^lookup /, "");
|
|
279
|
+
const lookupResult = await tools.get("context_lookup").execute("lookup-call", { handle }, undefined, undefined, ctx);
|
|
280
|
+
|
|
281
|
+
await runHandlers(handlers, "tool_result", {
|
|
282
|
+
type: "tool_result",
|
|
283
|
+
toolCallId: "lookup-call",
|
|
284
|
+
toolName: "context_lookup",
|
|
285
|
+
input: { handle },
|
|
286
|
+
content: lookupResult.content,
|
|
287
|
+
isError: false,
|
|
288
|
+
}, ctx);
|
|
289
|
+
const contextResult = await handlers.get("context")?.[0]({
|
|
290
|
+
type: "context",
|
|
291
|
+
messages: [{ role: "toolResult", toolCallId: "lookup-call", toolName: "context_lookup", content: lookupResult.content, isError: false }],
|
|
292
|
+
}, ctx);
|
|
293
|
+
await commands.get("context").handler("brief", ctx);
|
|
294
|
+
|
|
295
|
+
const rewrittenLookup = contextResult.messages[0].content[0].text;
|
|
296
|
+
const brief = notifications.at(-1)?.message ?? "";
|
|
297
|
+
expect(rewrittenLookup).toContain("Context lookup result omitted from prompt");
|
|
298
|
+
expect(rewrittenLookup).not.toContain("source evidence payload");
|
|
299
|
+
expect(rewrittenLookup).not.toContain("Context broker artifact: ctx://");
|
|
300
|
+
expect(brief).toContain("echo source");
|
|
301
|
+
expect(brief).not.toContain("completed context_lookup");
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it("still brokers normal tool output that exactly matches broker marker text", async () => {
|
|
305
|
+
const { pi, handlers, commands } = createPiMock();
|
|
306
|
+
registerContextBrokerBeta(pi);
|
|
307
|
+
const { ctx, notifications } = createCtx();
|
|
308
|
+
|
|
309
|
+
await runHandlers(handlers, "session_start", { type: "session_start" }, ctx);
|
|
310
|
+
await runHandlers(handlers, "tool_result", {
|
|
311
|
+
type: "tool_result",
|
|
312
|
+
toolCallId: "grep-call",
|
|
313
|
+
toolName: "bash",
|
|
314
|
+
input: { command: "grep ctx session.log" },
|
|
315
|
+
content: [{ type: "text", text: [
|
|
316
|
+
"Context broker artifact: ctx://session/example",
|
|
317
|
+
"Summary: copied placeholder",
|
|
318
|
+
"Payload bytes: 10",
|
|
319
|
+
"Raw payload omitted from prompt. Use /context lookup <handle> if exact evidence is needed.",
|
|
320
|
+
].join("\n") }],
|
|
321
|
+
isError: false,
|
|
322
|
+
}, ctx);
|
|
323
|
+
await commands.get("context").handler("brief", ctx);
|
|
324
|
+
|
|
325
|
+
expect(notifications.at(-1)?.message).toContain("grep ctx session.log");
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it("context_lookup refuses empty unfocused payload-dumping calls", async () => {
|
|
329
|
+
const { pi, handlers, tools } = createPiMock();
|
|
330
|
+
registerContextBrokerBeta(pi, { lookupBytes: 500 });
|
|
331
|
+
const { ctx } = createCtx();
|
|
332
|
+
|
|
333
|
+
await runHandlers(handlers, "session_start", { type: "session_start" }, ctx);
|
|
334
|
+
await runHandlers(handlers, "tool_result", {
|
|
335
|
+
type: "tool_result",
|
|
336
|
+
toolCallId: "call-empty-lookup",
|
|
337
|
+
toolName: "bash",
|
|
338
|
+
input: { command: "echo hidden" },
|
|
339
|
+
content: [{ type: "text", text: "payload must not dump" }],
|
|
340
|
+
isError: false,
|
|
341
|
+
}, ctx);
|
|
342
|
+
|
|
343
|
+
const result = await tools.get("context_lookup").execute("lookup-call", {}, undefined, undefined, ctx);
|
|
344
|
+
|
|
345
|
+
expect(result.content[0].text).toContain("requires a focused filter");
|
|
346
|
+
expect(result.content[0].text).not.toContain("payload must not dump");
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it("rewrites large historical tool results in context to live broker handles", async () => {
|
|
350
|
+
const { pi, handlers, commands } = createPiMock();
|
|
351
|
+
registerContextBrokerBeta(pi, { rewriteThresholdBytes: 40, lookupBytes: 500 });
|
|
352
|
+
const { ctx, notifications } = createCtx();
|
|
353
|
+
const raw = "RAW_TOOL_OUTPUT_" + "x".repeat(100);
|
|
354
|
+
|
|
355
|
+
await runHandlers(handlers, "session_start", { type: "session_start" }, ctx);
|
|
356
|
+
const result = await handlers.get("context")?.[0]({
|
|
357
|
+
type: "context",
|
|
358
|
+
messages: [
|
|
359
|
+
{ role: "assistant", content: [{ type: "toolCall", id: "call-large", name: "bash", arguments: { command: "printf raw" } }] },
|
|
360
|
+
{ role: "toolResult", toolCallId: "call-large", toolName: "bash", content: [{ type: "text", text: raw }], isError: false, timestamp: 1 },
|
|
361
|
+
],
|
|
362
|
+
}, ctx);
|
|
363
|
+
|
|
364
|
+
const text = result.messages[1].content[0].text;
|
|
365
|
+
const handle = text.match(/ctx:\/\/\S+/)?.[0];
|
|
366
|
+
expect(text).toContain("Context broker artifact: ctx://");
|
|
367
|
+
expect(text).toContain("Raw payload omitted from prompt");
|
|
368
|
+
expect(text).not.toContain(raw);
|
|
369
|
+
|
|
370
|
+
await commands.get("context").handler(`lookup ${handle}`, ctx);
|
|
371
|
+
expect(notifications.at(-1)?.message).toContain("RAW_TOOL_OUTPUT_");
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
it("leaves small tool results and excluded bash outputs unchanged in context", async () => {
|
|
375
|
+
const { pi, handlers } = createPiMock();
|
|
376
|
+
registerContextBrokerBeta(pi, { rewriteThresholdBytes: 40 });
|
|
377
|
+
const { ctx } = createCtx();
|
|
378
|
+
const secret = "SECRET_TOKEN=" + "z".repeat(80);
|
|
379
|
+
|
|
380
|
+
await runHandlers(handlers, "session_start", { type: "session_start" }, ctx);
|
|
381
|
+
const result = await handlers.get("context")?.[0]({
|
|
382
|
+
type: "context",
|
|
383
|
+
messages: [
|
|
384
|
+
{ role: "toolResult", toolCallId: "small", toolName: "read", content: [{ type: "text", text: "small" }], isError: false, timestamp: 1 },
|
|
385
|
+
{ role: "bashExecution", command: "echo secret", output: secret, exitCode: 0, cancelled: false, truncated: false, excludeFromContext: true, timestamp: 2 },
|
|
386
|
+
],
|
|
387
|
+
}, ctx);
|
|
388
|
+
|
|
389
|
+
expect(result).toBeUndefined();
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
it("does not collapse repeated bash rewrites for the same command and timestamp", async () => {
|
|
393
|
+
const { pi, handlers, commands } = createPiMock();
|
|
394
|
+
registerContextBrokerBeta(pi, { rewriteThresholdBytes: 20 });
|
|
395
|
+
const { ctx, notifications } = createCtx();
|
|
396
|
+
const firstRaw = "FIRST_RAW_" + "x".repeat(80);
|
|
397
|
+
const secondRaw = "SECOND_RAW_" + "y".repeat(80);
|
|
398
|
+
const sameTimestamp = Date.now();
|
|
399
|
+
|
|
400
|
+
await runHandlers(handlers, "session_start", { type: "session_start" }, ctx);
|
|
401
|
+
const result = await handlers.get("context")?.[0]({
|
|
402
|
+
type: "context",
|
|
403
|
+
messages: [
|
|
404
|
+
{ role: "bashExecution", command: "npm test", output: firstRaw, exitCode: 0, cancelled: false, truncated: false, timestamp: sameTimestamp },
|
|
405
|
+
{ role: "bashExecution", command: "npm test", output: secondRaw, exitCode: 0, cancelled: false, truncated: false, timestamp: sameTimestamp },
|
|
406
|
+
],
|
|
407
|
+
}, ctx);
|
|
408
|
+
|
|
409
|
+
const firstHandle = result.messages[0].output.match(/ctx:\/\/\S+/)?.[0];
|
|
410
|
+
const secondHandle = result.messages[1].output.match(/ctx:\/\/\S+/)?.[0];
|
|
411
|
+
expect(firstHandle).toBeTruthy();
|
|
412
|
+
expect(secondHandle).toBeTruthy();
|
|
413
|
+
expect(firstHandle).not.toBe(secondHandle);
|
|
414
|
+
|
|
415
|
+
await commands.get("context").handler(`lookup ${secondHandle}`, ctx);
|
|
416
|
+
expect(notifications.at(-1)?.message).toContain("SECOND_RAW_");
|
|
417
|
+
expect(notifications.at(-1)?.message).not.toContain("FIRST_RAW_");
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
it("does not emit dead handles when one context pass exceeds retention caps", async () => {
|
|
421
|
+
const { pi, handlers, commands } = createPiMock();
|
|
422
|
+
registerContextBrokerBeta(pi, { rewriteThresholdBytes: 1, maxRecords: 2, lookupBytes: 500 });
|
|
423
|
+
const { ctx, notifications } = createCtx();
|
|
424
|
+
|
|
425
|
+
await runHandlers(handlers, "session_start", { type: "session_start" }, ctx);
|
|
426
|
+
const result = await handlers.get("context")?.[0]({
|
|
427
|
+
type: "context",
|
|
428
|
+
messages: [0, 1, 2].map((index) => ({
|
|
429
|
+
role: "toolResult",
|
|
430
|
+
toolCallId: `call-${index}`,
|
|
431
|
+
toolName: "bash",
|
|
432
|
+
content: [{ type: "text", text: `RAW_${index}_` + "x".repeat(20) }],
|
|
433
|
+
isError: false,
|
|
434
|
+
timestamp: Date.now() + index,
|
|
435
|
+
})),
|
|
436
|
+
}, ctx);
|
|
437
|
+
|
|
438
|
+
const handles = result.messages
|
|
439
|
+
.map((message: any) => String(message.content?.[0]?.text ?? "").match(/ctx:\/\/\S+/)?.[0])
|
|
440
|
+
.filter(Boolean);
|
|
441
|
+
expect(handles.length).toBeLessThanOrEqual(2);
|
|
442
|
+
expect(result.messages.some((message: any) => String(message.content?.[0]?.text ?? "").includes("RAW_0_"))).toBe(true);
|
|
443
|
+
|
|
444
|
+
for (const handle of handles) {
|
|
445
|
+
await commands.get("context").handler(`lookup ${handle}`, ctx);
|
|
446
|
+
expect(notifications.at(-1)?.message).not.toContain("No context artifacts matched");
|
|
447
|
+
expect(notifications.at(-1)?.message).toContain("RAW_");
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
it("redacts secrets before storing and displaying payloads", async () => {
|
|
452
|
+
const { pi, handlers, commands } = createPiMock();
|
|
453
|
+
registerContextBrokerBeta(pi);
|
|
454
|
+
const { ctx, notifications } = createCtx();
|
|
455
|
+
|
|
456
|
+
await runHandlers(handlers, "session_start", { type: "session_start" }, ctx);
|
|
457
|
+
await runHandlers(handlers, "tool_result", {
|
|
458
|
+
type: "tool_result",
|
|
459
|
+
toolCallId: "secret-call",
|
|
460
|
+
toolName: "bash",
|
|
461
|
+
input: { command: "echo token=abc123456789", password: "hunter2" },
|
|
462
|
+
content: [{ type: "text", text: "OPENAI_API_KEY=sk-abcdefghijklmnop" }],
|
|
463
|
+
details: { nested: { apiKey: "object-secret-value" } },
|
|
464
|
+
isError: false,
|
|
465
|
+
}, ctx);
|
|
466
|
+
|
|
467
|
+
const lookupCompletion = commands.get("context").getArgumentCompletions("lookup ")?.[0];
|
|
468
|
+
await commands.get("context").handler(lookupCompletion.value, ctx);
|
|
469
|
+
|
|
470
|
+
expect(notifications.at(-1)?.message).not.toContain("abc123456789");
|
|
471
|
+
expect(notifications.at(-1)?.message).not.toContain("hunter2");
|
|
472
|
+
expect(notifications.at(-1)?.message).not.toContain("object-secret-value");
|
|
473
|
+
expect(notifications.at(-1)?.message).not.toContain("sk-abcdefghijklmnop");
|
|
474
|
+
expect(notifications.at(-1)?.message).toContain("[REDACTED");
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
it("re-publishes stale source handles instead of restoring raw prompt payloads", async () => {
|
|
478
|
+
const { pi, handlers, commands } = createPiMock();
|
|
479
|
+
registerContextBrokerBeta(pi, { maxRecords: 1, rewriteThresholdBytes: 20 });
|
|
480
|
+
const { ctx } = createCtx();
|
|
481
|
+
const raw = "STALE_RAW_PAYLOAD_" + "x".repeat(100);
|
|
482
|
+
|
|
483
|
+
await runHandlers(handlers, "session_start", { type: "session_start" }, ctx);
|
|
484
|
+
await runHandlers(handlers, "tool_result", {
|
|
485
|
+
type: "tool_result",
|
|
486
|
+
toolCallId: "stale-call",
|
|
487
|
+
toolName: "bash",
|
|
488
|
+
input: { command: "echo stale" },
|
|
489
|
+
content: [{ type: "text", text: raw }],
|
|
490
|
+
isError: false,
|
|
491
|
+
timestamp: 1,
|
|
492
|
+
}, ctx);
|
|
493
|
+
await runHandlers(handlers, "tool_result", {
|
|
494
|
+
type: "tool_result",
|
|
495
|
+
toolCallId: "newer-call",
|
|
496
|
+
toolName: "bash",
|
|
497
|
+
input: { command: "echo newer" },
|
|
498
|
+
content: [{ type: "text", text: "newer" }],
|
|
499
|
+
isError: false,
|
|
500
|
+
timestamp: 2,
|
|
501
|
+
}, ctx);
|
|
502
|
+
await commands.get("context").handler("prune", ctx);
|
|
503
|
+
|
|
504
|
+
const result = await handlers.get("context")?.[0]({
|
|
505
|
+
type: "context",
|
|
506
|
+
messages: [{ role: "toolResult", toolCallId: "stale-call", toolName: "bash", content: [{ type: "text", text: raw }], isError: false, timestamp: 1 }],
|
|
507
|
+
}, ctx);
|
|
508
|
+
|
|
509
|
+
expect(result.messages[0].content[0].text).toContain("Context broker artifact: ctx://");
|
|
510
|
+
expect(result.messages[0].content[0].text).not.toContain(raw);
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
it("can reload artifacts and pin state from durable blob storage", async () => {
|
|
514
|
+
const dir = mkdtempSync(join(tmpdir(), "ctx-broker-test-"));
|
|
515
|
+
try {
|
|
516
|
+
const first = createPiMock();
|
|
517
|
+
registerContextBrokerBeta(first.pi, { durable: true, storeDir: dir });
|
|
518
|
+
const { ctx } = createCtx();
|
|
519
|
+
await runHandlers(first.handlers, "session_start", { type: "session_start" }, ctx);
|
|
520
|
+
await runHandlers(first.handlers, "tool_result", {
|
|
521
|
+
type: "tool_result",
|
|
522
|
+
toolCallId: "durable-call",
|
|
523
|
+
toolName: "bash",
|
|
524
|
+
input: { command: "echo durable" },
|
|
525
|
+
content: [{ type: "text", text: "durable payload" }],
|
|
526
|
+
isError: false,
|
|
527
|
+
timestamp: 100,
|
|
528
|
+
}, ctx);
|
|
529
|
+
const handle = first.commands.get("context").getArgumentCompletions("lookup ")?.[0].value.replace(/^lookup /, "");
|
|
530
|
+
await first.commands.get("context").handler(`pin ${handle}`, ctx);
|
|
531
|
+
|
|
532
|
+
const second = createPiMock();
|
|
533
|
+
const secondRun = createCtx();
|
|
534
|
+
registerContextBrokerBeta(second.pi, { durable: true, storeDir: dir });
|
|
535
|
+
await runHandlers(second.handlers, "session_start", { type: "session_start" }, secondRun.ctx);
|
|
536
|
+
const secondHandle = second.commands.get("context").getArgumentCompletions("lookup ")?.[0].value.replace(/^lookup /, "");
|
|
537
|
+
await second.commands.get("context").handler(`lookup ${handle}`, secondRun.ctx);
|
|
538
|
+
await second.commands.get("context").handler("brief", secondRun.ctx);
|
|
539
|
+
|
|
540
|
+
const third = createPiMock();
|
|
541
|
+
const thirdRun = createCtx();
|
|
542
|
+
registerContextBrokerBeta(third.pi, { durable: true, storeDir: dir });
|
|
543
|
+
await runHandlers(third.handlers, "session_start", { type: "session_start" }, thirdRun.ctx);
|
|
544
|
+
await third.commands.get("context").handler(`lookup ${secondHandle}`, thirdRun.ctx);
|
|
545
|
+
await third.commands.get("context").handler("brief", thirdRun.ctx);
|
|
546
|
+
|
|
547
|
+
expect(secondRun.notifications.at(-2)?.message).toContain("durable payload");
|
|
548
|
+
expect(secondRun.notifications.at(-1)?.message).toContain("tier=hot");
|
|
549
|
+
expect(secondRun.notifications.at(-1)?.message).toContain("pinned");
|
|
550
|
+
expect(thirdRun.notifications.at(-2)?.message).toContain("durable payload");
|
|
551
|
+
expect(thirdRun.notifications.at(-1)?.message).toContain("tier=hot");
|
|
552
|
+
expect(thirdRun.notifications.at(-1)?.message).toContain("pinned");
|
|
553
|
+
} finally {
|
|
554
|
+
rmSync(dir, { recursive: true, force: true });
|
|
555
|
+
}
|
|
556
|
+
});
|
|
557
|
+
|
|
199
558
|
it("injects a bounded broker brief without raw payload text", async () => {
|
|
200
559
|
const { pi, handlers } = createPiMock();
|
|
201
560
|
registerContextBrokerBeta(pi, { briefBytes: 220 });
|