@fiale-plus/pi-rogue-bundle 0.1.17 → 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/node_modules/@fiale-plus/pi-core/src/context-broker.ts +6 -0
- package/node_modules/@fiale-plus/pi-rogue-context-broker/src/extension.test.ts +100 -0
- package/node_modules/@fiale-plus/pi-rogue-context-broker/src/extension.ts +38 -2
- package/node_modules/@fiale-plus/pi-rogue-context-broker/src/file.ts +28 -2
- package/node_modules/@fiale-plus/pi-rogue-context-broker/src/index.test.ts +14 -0
- package/node_modules/@fiale-plus/pi-rogue-context-broker/src/index.ts +13 -0
- package/node_modules/@fiale-plus/pi-rogue-context-broker/src/sqlite.test.ts +20 -0
- package/node_modules/@fiale-plus/pi-rogue-context-broker/src/sqlite.ts +25 -1
- package/package.json +1 -1
|
@@ -91,11 +91,17 @@ export interface ContextBrokerOptions {
|
|
|
91
91
|
briefBytes?: number;
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
export interface ContextPurgeOptions {
|
|
95
|
+
sessionId?: string;
|
|
96
|
+
keepPinned?: boolean;
|
|
97
|
+
}
|
|
98
|
+
|
|
94
99
|
export interface BoundedContextBroker {
|
|
95
100
|
publish(input: ContextArtifactInput): ContextArtifact;
|
|
96
101
|
lookup(query?: ContextLookupQuery): ContextArtifact[];
|
|
97
102
|
pin(idOrHandle: string, pinned?: boolean): ContextArtifact | null;
|
|
98
103
|
prune(now?: number): ContextBrokerStatus;
|
|
104
|
+
purge(options?: ContextPurgeOptions): ContextBrokerStatus;
|
|
99
105
|
status(): ContextBrokerStatus;
|
|
100
106
|
renderBrief(query?: ContextLookupQuery & { budgetBytes?: number }): string;
|
|
101
107
|
}
|
|
@@ -120,6 +120,42 @@ describe("context broker beta enablement", () => {
|
|
|
120
120
|
expect(notifications.at(-1)?.message).toContain("README.md");
|
|
121
121
|
});
|
|
122
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
|
+
|
|
123
159
|
it("is safe on malformed session branches", async () => {
|
|
124
160
|
const { pi, handlers } = createPiMock();
|
|
125
161
|
registerContextBrokerBeta(pi);
|
|
@@ -225,6 +261,70 @@ describe("context broker beta enablement", () => {
|
|
|
225
261
|
expect(result.content[0].text).toContain("exact evidence payload");
|
|
226
262
|
});
|
|
227
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
|
+
|
|
228
328
|
it("context_lookup refuses empty unfocused payload-dumping calls", async () => {
|
|
229
329
|
const { pi, handlers, tools } = createPiMock();
|
|
230
330
|
registerContextBrokerBeta(pi, { lookupBytes: 500 });
|
|
@@ -155,6 +155,14 @@ function brokerPlaceholder(artifact: ContextArtifact): string {
|
|
|
155
155
|
].join("\n");
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
+
function contextLookupHistoryPlaceholder(): string {
|
|
159
|
+
return [
|
|
160
|
+
"Context lookup result omitted from prompt.",
|
|
161
|
+
"Prior context_lookup evidence is terminal and is not re-brokered.",
|
|
162
|
+
"Run context_lookup again with a focused handle/filter only if exact evidence is still needed.",
|
|
163
|
+
].join("\n");
|
|
164
|
+
}
|
|
165
|
+
|
|
158
166
|
function summarizeTool(event: { toolName: string; input?: any; isError?: boolean }, bytes: number): string {
|
|
159
167
|
const command = event.toolName === "bash" ? event.input?.command : undefined;
|
|
160
168
|
const path = event.input?.path;
|
|
@@ -162,6 +170,12 @@ function summarizeTool(event: { toolName: string; input?: any; isError?: boolean
|
|
|
162
170
|
return `${event.isError ? "failed" : "completed"} ${event.toolName}${target}; payload=${bytes} bytes`;
|
|
163
171
|
}
|
|
164
172
|
|
|
173
|
+
const NON_BROKERED_TOOL_NAMES = new Set(["context_lookup"]);
|
|
174
|
+
|
|
175
|
+
function shouldBrokerToolName(toolName: string): boolean {
|
|
176
|
+
return !NON_BROKERED_TOOL_NAMES.has(toolName);
|
|
177
|
+
}
|
|
178
|
+
|
|
165
179
|
function ttlFromNowFor(createdAt: number | undefined): number | undefined {
|
|
166
180
|
if (typeof createdAt !== "number" || !Number.isFinite(createdAt)) return undefined;
|
|
167
181
|
return Math.max(DEFAULT_TTL_MS, Date.now() - createdAt + DEFAULT_TTL_MS);
|
|
@@ -212,6 +226,8 @@ export function registerContextBrokerBeta(pi: ExtensionAPI, options: ContextBrok
|
|
|
212
226
|
createdAt?: number;
|
|
213
227
|
ttlMs?: number;
|
|
214
228
|
}): ContextArtifact | null {
|
|
229
|
+
if (!shouldBrokerToolName(event.toolName)) return null;
|
|
230
|
+
|
|
215
231
|
if (event.sourceId) {
|
|
216
232
|
const existingHandle = sourceHandles.get(event.sourceId);
|
|
217
233
|
if (existingHandle) {
|
|
@@ -295,6 +311,7 @@ export function registerContextBrokerBeta(pi: ExtensionAPI, options: ContextBrok
|
|
|
295
311
|
isError: Boolean(entry.message.isError),
|
|
296
312
|
sourceId,
|
|
297
313
|
createdAt,
|
|
314
|
+
ttlMs: ttlFromNowFor(createdAt),
|
|
298
315
|
}) && !alreadySeen) added += 1;
|
|
299
316
|
}
|
|
300
317
|
|
|
@@ -316,6 +333,7 @@ export function registerContextBrokerBeta(pi: ExtensionAPI, options: ContextBrok
|
|
|
316
333
|
isError: typeof entry.message.exitCode === "number" ? entry.message.exitCode !== 0 : Boolean(entry.message.cancelled),
|
|
317
334
|
sourceId,
|
|
318
335
|
createdAt,
|
|
336
|
+
ttlMs: ttlFromNowFor(createdAt),
|
|
319
337
|
}) && !alreadySeen) added += 1;
|
|
320
338
|
}
|
|
321
339
|
} catch {
|
|
@@ -379,6 +397,16 @@ export function registerContextBrokerBeta(pi: ExtensionAPI, options: ContextBrok
|
|
|
379
397
|
);
|
|
380
398
|
});
|
|
381
399
|
|
|
400
|
+
pi.on("session_compact", async (_event, ctx) => {
|
|
401
|
+
activeSessionId = sessionIdFor(ctx);
|
|
402
|
+
const before = broker.status();
|
|
403
|
+
const after = broker.purge({ sessionId: activeSessionId, keepPinned: true });
|
|
404
|
+
seenSourceIds.clear();
|
|
405
|
+
sourceHandles.clear();
|
|
406
|
+
const removed = before.records - after.records;
|
|
407
|
+
if (removed > 0) ctx.ui.notify(`Context broker compact cleanup purged ${removed} unpinned artifact${removed === 1 ? "" : "s"}; pinned artifacts retained.`, "info");
|
|
408
|
+
});
|
|
409
|
+
|
|
382
410
|
pi.on("tool_result", async (event: ToolResultEvent, ctx) => {
|
|
383
411
|
activeSessionId = sessionIdFor(ctx);
|
|
384
412
|
publishToolArtifact({ ...event, sourceId: event.toolCallId });
|
|
@@ -387,13 +415,17 @@ export function registerContextBrokerBeta(pi: ExtensionAPI, options: ContextBrok
|
|
|
387
415
|
pi.on("context", async (event, ctx) => {
|
|
388
416
|
activeSessionId = sessionIdFor(ctx);
|
|
389
417
|
const toolInputs = collectToolInputs(event.messages);
|
|
390
|
-
const drafts = event.messages.map((message: any): { original: any; artifact?: ContextArtifact; rewrite?: (artifact: ContextArtifact) => any } => {
|
|
418
|
+
const drafts = event.messages.map((message: any): { original: any; replacement?: any; artifact?: ContextArtifact; rewrite?: (artifact: ContextArtifact) => any } => {
|
|
391
419
|
if (message?.role === "toolResult") {
|
|
392
420
|
const raw = contentText(message.content);
|
|
393
421
|
if (Buffer.byteLength(raw, "utf8") <= rewriteThresholdBytes) return { original: message };
|
|
394
422
|
const toolInput = typeof message.toolCallId === "string" ? toolInputs.get(message.toolCallId) : undefined;
|
|
423
|
+
const toolName = String(message.toolName ?? toolInput?.toolName ?? "tool");
|
|
424
|
+
if (!shouldBrokerToolName(toolName)) {
|
|
425
|
+
return { original: message, replacement: { ...message, content: [{ type: "text", text: contextLookupHistoryPlaceholder() }] } };
|
|
426
|
+
}
|
|
395
427
|
const artifact = publishToolArtifact({
|
|
396
|
-
toolName
|
|
428
|
+
toolName,
|
|
397
429
|
input: message.input ?? toolInput?.input,
|
|
398
430
|
content: message.content,
|
|
399
431
|
details: message.details,
|
|
@@ -436,6 +468,10 @@ export function registerContextBrokerBeta(pi: ExtensionAPI, options: ContextBrok
|
|
|
436
468
|
|
|
437
469
|
let changed = false;
|
|
438
470
|
const messages = drafts.map((draft) => {
|
|
471
|
+
if (draft.replacement) {
|
|
472
|
+
changed = true;
|
|
473
|
+
return draft.replacement;
|
|
474
|
+
}
|
|
439
475
|
if (!draft.artifact || !draft.rewrite) return draft.original;
|
|
440
476
|
const live = broker.lookup({ handle: draft.artifact.handle })[0];
|
|
441
477
|
if (!live) {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { safeName } from "@fiale-plus/pi-core";
|
|
5
|
-
import type { BoundedContextBroker, ContextArtifact, ContextArtifactInput, ContextArtifactTier, ContextBrokerOptions, ContextBrokerStatus, ContextLookupQuery } from "@fiale-plus/pi-core";
|
|
5
|
+
import type { BoundedContextBroker, ContextArtifact, ContextArtifactInput, ContextArtifactTier, ContextBrokerOptions, ContextBrokerStatus, ContextLookupQuery, ContextPurgeOptions } from "@fiale-plus/pi-core";
|
|
6
6
|
import { createInMemoryContextBroker } from "./index.js";
|
|
7
7
|
|
|
8
8
|
export interface FileContextBrokerOptions extends ContextBrokerOptions {
|
|
@@ -113,6 +113,16 @@ function persistArtifactSnapshot(dir: string, artifact: ContextArtifact): void {
|
|
|
113
113
|
});
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
function removeUnreferencedBlobs(dir: string, keptSha256: Set<string>): void {
|
|
117
|
+
const blobsDir = join(dir, "blobs");
|
|
118
|
+
if (!existsSync(blobsDir)) return;
|
|
119
|
+
for (const entry of readdirSync(blobsDir)) {
|
|
120
|
+
if (!entry.endsWith(".txt")) continue;
|
|
121
|
+
const sha256 = entry.slice(0, -4);
|
|
122
|
+
if (!keptSha256.has(sha256)) unlinkSync(join(blobsDir, entry));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
116
126
|
export function createFileContextBroker(options: FileContextBrokerOptions = {}): BoundedContextBroker {
|
|
117
127
|
const dir = options.dir ?? process.env.PI_CONTEXT_BROKER_STORE_DIR ?? defaultStoreDir();
|
|
118
128
|
ensureDir(join(dir, "blobs"));
|
|
@@ -155,6 +165,22 @@ export function createFileContextBroker(options: FileContextBrokerOptions = {}):
|
|
|
155
165
|
return artifact;
|
|
156
166
|
},
|
|
157
167
|
prune(now?: number): ContextBrokerStatus { return broker.prune(now); },
|
|
168
|
+
purge(options?: ContextPurgeOptions): ContextBrokerStatus {
|
|
169
|
+
const status = broker.purge(options);
|
|
170
|
+
const remaining = broker.lookup({ limit: Number.MAX_SAFE_INTEGER });
|
|
171
|
+
persistedSources.clear();
|
|
172
|
+
handleAliases.clear();
|
|
173
|
+
writeFileSync(metadataFile(dir), "", "utf8");
|
|
174
|
+
const keptSha256 = new Set<string>();
|
|
175
|
+
for (const artifact of remaining) {
|
|
176
|
+
keptSha256.add(artifact.sha256);
|
|
177
|
+
for (const parentId of artifact.parentIds) persistedSources.set(parentId, artifact.handle);
|
|
178
|
+
handleAliases.set(artifact.handle, artifact.handle);
|
|
179
|
+
persistArtifactSnapshot(dir, artifact);
|
|
180
|
+
}
|
|
181
|
+
removeUnreferencedBlobs(dir, keptSha256);
|
|
182
|
+
return status;
|
|
183
|
+
},
|
|
158
184
|
status(): ContextBrokerStatus { return broker.status(); },
|
|
159
185
|
renderBrief(query?: ContextLookupQuery & { budgetBytes?: number }): string { return broker.renderBrief(query); },
|
|
160
186
|
};
|
|
@@ -273,4 +273,18 @@ describe("createInMemoryContextBroker", () => {
|
|
|
273
273
|
expect(Buffer.byteLength(brief, "utf8")).toBeLessThanOrEqual(170);
|
|
274
274
|
expect(brief).toContain("Context Broker");
|
|
275
275
|
});
|
|
276
|
+
|
|
277
|
+
it("purges unpinned session artifacts while retaining pinned evidence", () => {
|
|
278
|
+
const broker = createInMemoryContextBroker();
|
|
279
|
+
const unpinned = broker.publish({ sessionId: "s", kind: "tool_output", payload: "scratch", summary: "scratch" });
|
|
280
|
+
const pinned = broker.publish({ sessionId: "s", kind: "tool_output", payload: "keep", summary: "keep", pinned: true });
|
|
281
|
+
const other = broker.publish({ sessionId: "other", kind: "tool_output", payload: "other", summary: "other" });
|
|
282
|
+
|
|
283
|
+
const status = broker.purge({ sessionId: "s", keepPinned: true });
|
|
284
|
+
|
|
285
|
+
expect(status.records).toBe(2);
|
|
286
|
+
expect(broker.lookup({ handle: unpinned.handle })).toEqual([]);
|
|
287
|
+
expect(broker.lookup({ handle: pinned.handle })[0]?.payload).toBe("keep");
|
|
288
|
+
expect(broker.lookup({ handle: other.handle })[0]?.payload).toBe("other");
|
|
289
|
+
});
|
|
276
290
|
});
|
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
ContextBrokerOptions,
|
|
10
10
|
ContextBrokerStatus,
|
|
11
11
|
ContextLookupQuery,
|
|
12
|
+
ContextPurgeOptions,
|
|
12
13
|
} from "@fiale-plus/pi-core";
|
|
13
14
|
|
|
14
15
|
export type {
|
|
@@ -20,6 +21,7 @@ export type {
|
|
|
20
21
|
ContextBrokerOptions,
|
|
21
22
|
ContextBrokerStatus,
|
|
22
23
|
ContextLookupQuery,
|
|
24
|
+
ContextPurgeOptions,
|
|
23
25
|
} from "@fiale-plus/pi-core";
|
|
24
26
|
|
|
25
27
|
const DEFAULT_MAX_RECORDS = 256;
|
|
@@ -222,6 +224,16 @@ export function createInMemoryContextBroker(options: ContextBrokerOptions = {}):
|
|
|
222
224
|
return currentStatus();
|
|
223
225
|
}
|
|
224
226
|
|
|
227
|
+
function purge(options: ContextPurgeOptions = {}): ContextBrokerStatus {
|
|
228
|
+
dropExpired();
|
|
229
|
+
const keepPinned = options.keepPinned ?? true;
|
|
230
|
+
artifacts = artifacts.filter((artifact) => {
|
|
231
|
+
if (options.sessionId && artifact.sessionId !== options.sessionId) return true;
|
|
232
|
+
return keepPinned && artifact.pinned;
|
|
233
|
+
});
|
|
234
|
+
return currentStatus();
|
|
235
|
+
}
|
|
236
|
+
|
|
225
237
|
function publish(input: ContextArtifactInput): ContextArtifact {
|
|
226
238
|
const now = input.createdAt ?? Date.now();
|
|
227
239
|
const payload = payloadText(input.payload);
|
|
@@ -318,6 +330,7 @@ export function createInMemoryContextBroker(options: ContextBrokerOptions = {}):
|
|
|
318
330
|
lookup,
|
|
319
331
|
pin,
|
|
320
332
|
prune,
|
|
333
|
+
purge,
|
|
321
334
|
status,
|
|
322
335
|
renderBrief,
|
|
323
336
|
};
|
|
@@ -75,4 +75,24 @@ describe("createSqliteContextBroker", () => {
|
|
|
75
75
|
rmSync(dir, { recursive: true, force: true });
|
|
76
76
|
}
|
|
77
77
|
});
|
|
78
|
+
|
|
79
|
+
it("purges unpinned durable artifacts for a session", () => {
|
|
80
|
+
const dir = mkdtempSync(join(tmpdir(), "ctx-sqlite-test-"));
|
|
81
|
+
try {
|
|
82
|
+
const path = join(dir, "artifacts.sqlite");
|
|
83
|
+
let broker = createSqliteContextBroker({ path, defaultTtlMs: 0 });
|
|
84
|
+
const scratch = broker.publish({ sessionId: "s", kind: "tool_output", payload: "scratch" });
|
|
85
|
+
const pinned = broker.publish({ sessionId: "s", kind: "tool_output", payload: "keep", pinned: true });
|
|
86
|
+
const other = broker.publish({ sessionId: "other", kind: "tool_output", payload: "other" });
|
|
87
|
+
|
|
88
|
+
broker.purge({ sessionId: "s", keepPinned: true });
|
|
89
|
+
broker = createSqliteContextBroker({ path, defaultTtlMs: 0 });
|
|
90
|
+
|
|
91
|
+
expect(broker.lookup({ handle: scratch.handle })).toEqual([]);
|
|
92
|
+
expect(broker.lookup({ handle: pinned.handle })[0]?.payload).toBe("keep");
|
|
93
|
+
expect(broker.lookup({ handle: other.handle })[0]?.payload).toBe("other");
|
|
94
|
+
} finally {
|
|
95
|
+
rmSync(dir, { recursive: true, force: true });
|
|
96
|
+
}
|
|
97
|
+
});
|
|
78
98
|
});
|
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
ContextBrokerOptions,
|
|
14
14
|
ContextBrokerStatus,
|
|
15
15
|
ContextLookupQuery,
|
|
16
|
+
ContextPurgeOptions,
|
|
16
17
|
} from "@fiale-plus/pi-core";
|
|
17
18
|
|
|
18
19
|
export interface SqliteContextBrokerOptions extends ContextBrokerOptions {
|
|
@@ -321,6 +322,29 @@ export function createSqliteContextBroker(options: SqliteContextBrokerOptions =
|
|
|
321
322
|
return currentStatus();
|
|
322
323
|
}
|
|
323
324
|
|
|
325
|
+
function purge(options: ContextPurgeOptions = {}): ContextBrokerStatus {
|
|
326
|
+
dropExpired();
|
|
327
|
+
const keepPinned = options.keepPinned ?? true;
|
|
328
|
+
const clauses: string[] = [];
|
|
329
|
+
const params: Array<string | number> = [];
|
|
330
|
+
if (options.sessionId) {
|
|
331
|
+
clauses.push("sessionId = ?");
|
|
332
|
+
params.push(options.sessionId);
|
|
333
|
+
}
|
|
334
|
+
if (keepPinned) clauses.push("pinned = 0");
|
|
335
|
+
const where = clauses.length ? `WHERE ${clauses.join(" AND ")}` : "";
|
|
336
|
+
const rows = db.prepare(`SELECT id FROM artifacts ${where}`).all(...params);
|
|
337
|
+
db.exec("BEGIN IMMEDIATE");
|
|
338
|
+
try {
|
|
339
|
+
for (const row of rows) deleteArtifact(String(row.id));
|
|
340
|
+
db.exec("COMMIT");
|
|
341
|
+
} catch (error) {
|
|
342
|
+
db.exec("ROLLBACK");
|
|
343
|
+
throw error;
|
|
344
|
+
}
|
|
345
|
+
return currentStatus();
|
|
346
|
+
}
|
|
347
|
+
|
|
324
348
|
function publish(input: ContextArtifactInput): ContextArtifact {
|
|
325
349
|
dropExpired();
|
|
326
350
|
const source = stableSource(input);
|
|
@@ -492,7 +516,7 @@ export function createSqliteContextBroker(options: SqliteContextBrokerOptions =
|
|
|
492
516
|
return truncateUtf8(lines.join("\n"), budget);
|
|
493
517
|
}
|
|
494
518
|
|
|
495
|
-
return { publish, lookup, pin, prune, status, renderBrief };
|
|
519
|
+
return { publish, lookup, pin, prune, purge, status, renderBrief };
|
|
496
520
|
}
|
|
497
521
|
|
|
498
522
|
export function contextBrokerSqlitePathForSession(baseDir: string, sessionId: string): string {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fiale-plus/pi-rogue-bundle",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.18",
|
|
4
4
|
"description": "Public Pi-Rogue bundle for advisor, orchestration, and beta context broker. Single consolidated artefact (leaf releases paused; private packages are bundled here).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|