@fiale-plus/pi-rogue-bundle 0.1.11 → 0.1.13
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.
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
completeWithModelFallback,
|
|
7
7
|
contentText,
|
|
8
8
|
normalizeAdvisorConfig,
|
|
9
|
+
sanitizeAdvisorText,
|
|
9
10
|
shouldRunCheckin,
|
|
10
11
|
type AdvisorConfig,
|
|
11
12
|
} from "./extension.js";
|
|
@@ -86,6 +87,17 @@ describe("advisor message extraction", () => {
|
|
|
86
87
|
expect(contentText([{ type: "toolResult", content: [{ type: "text", text: "ok" }] }])).toBe("ok");
|
|
87
88
|
expect(contentText({ arbitrary: "shape" })).toBe("");
|
|
88
89
|
});
|
|
90
|
+
|
|
91
|
+
it("redacts transient clipboard image paths from advisor-facing text", () => {
|
|
92
|
+
const text = "see /var/folders/fm/rwczdnws5j58x7kbyn3vcx_h0000gn/T/clipboard-2026-06-04-012248-DEE3A154.png please";
|
|
93
|
+
expect(sanitizeAdvisorText(text)).toBe("see [clipboard image] please");
|
|
94
|
+
expect(contentText({ content: [{ type: "text", text }] })).toBe("see [clipboard image] please");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("does not redact ordinary repo or temp file paths", () => {
|
|
98
|
+
const text = "inspect /Users/pavel/repos/fiale-plus/pi-rogue/packages/advisor/src/extension.ts and /tmp/benchmark-results.json";
|
|
99
|
+
expect(sanitizeAdvisorText(text)).toBe(text);
|
|
100
|
+
});
|
|
89
101
|
});
|
|
90
102
|
|
|
91
103
|
describe("mid-hour check-ins", () => {
|
|
@@ -234,16 +234,22 @@ function hash(...parts: string[]): string {
|
|
|
234
234
|
|
|
235
235
|
function brief(s: SessionState): string {
|
|
236
236
|
const lines: string[] = [];
|
|
237
|
-
if (s.lastTask) lines.push(`Task: ${truncate(s.lastTask, 200)}`);
|
|
237
|
+
if (s.lastTask) lines.push(`Task: ${truncate(sanitizeAdvisorText(s.lastTask), 200)}`);
|
|
238
238
|
if (s.turns) lines.push(`Turns: ${s.turns}`);
|
|
239
239
|
if (s.notes.length) { lines.push("Notes:"); s.notes.slice(-4).forEach(n => lines.push(`- ${truncate(n, 200)}`)); }
|
|
240
|
-
if (s.files.length) lines.push(`Files: ${s.files.slice(-4).join(", ")}`);
|
|
241
|
-
if (s.errors.length) lines.push(`Errors: ${s.errors.slice(-2).join(" | ")}`);
|
|
240
|
+
if (s.files.length) lines.push(`Files: ${sanitizeAdvisorText(s.files.slice(-4).join(", "))}`);
|
|
241
|
+
if (s.errors.length) lines.push(`Errors: ${sanitizeAdvisorText(s.errors.slice(-2).join(" | "))}`);
|
|
242
242
|
return lines.join("\n").slice(0, 1200);
|
|
243
243
|
}
|
|
244
244
|
|
|
245
|
+
const CLIPBOARD_IMAGE_PATH_RE = /(?:\/(?:private\/)?var\/folders\/[^\s"'`<>]+\/T|\/(?:tmp|var\/tmp))\/clipboard-\d{4}-\d{2}-\d{2}-[A-Za-z0-9-]+\.(?:png|jpe?g|gif|webp)\b/g;
|
|
246
|
+
|
|
247
|
+
export function sanitizeAdvisorText(text: unknown): string {
|
|
248
|
+
return String(text ?? "").replace(CLIPBOARD_IMAGE_PATH_RE, "[clipboard image]");
|
|
249
|
+
}
|
|
250
|
+
|
|
245
251
|
function squish(t: unknown, max = 200): string {
|
|
246
|
-
const s =
|
|
252
|
+
const s = sanitizeAdvisorText(t).replace(/\s+/g, " ").trim();
|
|
247
253
|
return s.length <= max ? s : s.slice(0, max - 1).trimEnd() + "…";
|
|
248
254
|
}
|
|
249
255
|
|
|
@@ -386,35 +392,64 @@ function normalizeAdvisorActions(actions: unknown): string[] {
|
|
|
386
392
|
return raw.map((action) => squish(action, 200)).filter(Boolean).slice(0, 2);
|
|
387
393
|
}
|
|
388
394
|
|
|
395
|
+
function comparableAdvisorText(text: string): string {
|
|
396
|
+
return sanitizeAdvisorText(text).toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function isRedundantAdvisorSummary(reason: string, summary: string): boolean {
|
|
400
|
+
const r = comparableAdvisorText(reason);
|
|
401
|
+
const s = comparableAdvisorText(summary);
|
|
402
|
+
if (!s) return true;
|
|
403
|
+
if (!r) return false;
|
|
404
|
+
if (r === s) return true;
|
|
405
|
+
if (Math.min(r.length, s.length) >= 60 && (r.includes(s) || s.includes(r))) return true;
|
|
406
|
+
|
|
407
|
+
const rTokens = new Set(r.split(" ").filter((token) => token.length > 2));
|
|
408
|
+
const sTokens = new Set(s.split(" ").filter((token) => token.length > 2));
|
|
409
|
+
if (rTokens.size < 8 || sTokens.size < 8) return false;
|
|
410
|
+
const overlap = [...sTokens].filter((token) => rTokens.has(token)).length;
|
|
411
|
+
return overlap / Math.max(rTokens.size, sTokens.size) >= 0.86;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function distinctAdvisorSummary(reason: string, summary: string): string {
|
|
415
|
+
const cleanSummary = sanitizeAdvisorText(summary).trim();
|
|
416
|
+
return isRedundantAdvisorSummary(reason, cleanSummary) ? "" : cleanSummary;
|
|
417
|
+
}
|
|
418
|
+
|
|
389
419
|
function advisorHandoffText(decision: "continue" | "review" | "defer", reason: string, summary: string, actions: unknown = []): string {
|
|
390
420
|
const limitedActions = normalizeAdvisorActions(actions);
|
|
421
|
+
const cleanReason = sanitizeAdvisorText(reason);
|
|
422
|
+
const cleanSummary = distinctAdvisorSummary(cleanReason, summary);
|
|
391
423
|
return [
|
|
392
424
|
`Advisor verdict: ${decision}.`,
|
|
393
|
-
|
|
394
|
-
|
|
425
|
+
cleanReason ? `Reason: ${cleanReason}` : "",
|
|
426
|
+
cleanSummary ? `Summary: ${cleanSummary}` : "",
|
|
395
427
|
limitedActions.length ? `Actions: ${limitedActions.join("; ")}` : "",
|
|
396
428
|
].filter(Boolean).join("\n");
|
|
397
429
|
}
|
|
398
430
|
|
|
399
431
|
function sendAdvisorHint(pi: ExtensionAPI, decision: "continue" | "review" | "defer", reason: string, summary: string, actions: unknown = []) {
|
|
432
|
+
const cleanReason = sanitizeAdvisorText(reason);
|
|
433
|
+
const cleanSummary = distinctAdvisorSummary(cleanReason, summary);
|
|
400
434
|
const limitedActions = normalizeAdvisorActions(actions);
|
|
401
435
|
pi.sendMessage(
|
|
402
436
|
{
|
|
403
437
|
customType: "advisor:llm",
|
|
404
|
-
content: advisorHandoffText(decision,
|
|
438
|
+
content: advisorHandoffText(decision, cleanReason, cleanSummary, limitedActions),
|
|
405
439
|
display: true,
|
|
406
|
-
details: { decision, reason, summary, actions: limitedActions },
|
|
440
|
+
details: { kind: "handoff", decision, reason: cleanReason, summary: cleanSummary, actions: limitedActions },
|
|
407
441
|
},
|
|
408
442
|
{ deliverAs: "followUp" },
|
|
409
443
|
);
|
|
410
444
|
}
|
|
411
445
|
|
|
412
446
|
function sendAdvisorAnswer(pi: ExtensionAPI, text: string) {
|
|
447
|
+
const cleanText = sanitizeAdvisorText(text);
|
|
413
448
|
pi.sendMessage({
|
|
414
449
|
customType: "advisor:llm",
|
|
415
|
-
content:
|
|
450
|
+
content: cleanText,
|
|
416
451
|
display: true,
|
|
417
|
-
details: { kind: "answer", summary:
|
|
452
|
+
details: { kind: "answer", summary: cleanText },
|
|
418
453
|
});
|
|
419
454
|
}
|
|
420
455
|
|
|
@@ -425,7 +460,7 @@ function renderAdvisorHint(message: any, options: { expanded?: boolean }, theme:
|
|
|
425
460
|
const source = theme.bold(theme.fg(sourceColor, `[${customType}]`));
|
|
426
461
|
|
|
427
462
|
if (details.kind === "answer") {
|
|
428
|
-
const body = contentText(message?.content) || details.summary || "No advisor response.";
|
|
463
|
+
const body = sanitizeAdvisorText(contentText(message?.content) || details.summary || "No advisor response.");
|
|
429
464
|
const box = new Box(1, 1, (s: string) => theme.bg("customMessageBg", s));
|
|
430
465
|
box.addChild(new Text(`${theme.bold(theme.fg("success", "↗"))} ${source} ${theme.bold(theme.fg("success", "answer"))}`, 0, 0));
|
|
431
466
|
box.addChild(new Text(theme.fg("dim", body), 0, 0));
|
|
@@ -438,19 +473,30 @@ function renderAdvisorHint(message: any, options: { expanded?: boolean }, theme:
|
|
|
438
473
|
const glyph = decision === "review" ? "↗" : decision === "defer" ? "…" : "·";
|
|
439
474
|
const reason = squish(details.reason || contentText(message?.content) || "no extra detail", 180);
|
|
440
475
|
const actions = normalizeAdvisorActions(details.actions);
|
|
476
|
+
const fullHandoff = sanitizeAdvisorText(
|
|
477
|
+
(details.reason || details.summary || actions.length)
|
|
478
|
+
? advisorHandoffText(decision, details.reason || "", details.summary || "", actions)
|
|
479
|
+
: contentText(message?.content),
|
|
480
|
+
);
|
|
441
481
|
|
|
442
482
|
const box = new Box(1, 1, (s: string) => theme.bg("customMessageBg", s));
|
|
443
483
|
box.addChild(new Text(`${theme.bold(theme.fg(decisionColor, glyph))} ${source} ${verdict}`, 0, 0));
|
|
444
|
-
box.addChild(new Text(theme.fg("dim", `reason: ${reason}`), 0, 0));
|
|
445
484
|
|
|
446
|
-
if (
|
|
447
|
-
box.addChild(new Text(theme.fg("dim",
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
box.addChild(new Text(theme.fg("dim", `
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
485
|
+
if (options.expanded) {
|
|
486
|
+
box.addChild(new Text(theme.fg("dim", "full handoff:"), 0, 0));
|
|
487
|
+
box.addChild(new Text(theme.fg("dim", fullHandoff), 0, 0));
|
|
488
|
+
} else {
|
|
489
|
+
box.addChild(new Text(theme.fg("dim", `reason: ${reason}`), 0, 0));
|
|
490
|
+
const summary = distinctAdvisorSummary(details.reason || "", details.summary || "");
|
|
491
|
+
if (summary) {
|
|
492
|
+
box.addChild(new Text(theme.fg("dim", `summary: ${squish(summary, 220)}`), 0, 0));
|
|
493
|
+
}
|
|
494
|
+
if (actions.length) {
|
|
495
|
+
box.addChild(new Text(theme.fg("dim", `actions: ${actions.map((a) => squish(a, 80)).join(" • ")}`), 0, 0));
|
|
496
|
+
}
|
|
497
|
+
if (fullHandoff.split("\n").length > 3) {
|
|
498
|
+
box.addChild(new Text(theme.fg("dim", "Ctrl+O full advisor handoff"), 0, 0));
|
|
499
|
+
}
|
|
454
500
|
}
|
|
455
501
|
|
|
456
502
|
return box;
|
|
@@ -458,15 +504,15 @@ function renderAdvisorHint(message: any, options: { expanded?: boolean }, theme:
|
|
|
458
504
|
|
|
459
505
|
/** Extract readable text from message content (handles strings, blocks, and nested message payloads). */
|
|
460
506
|
export function contentText(content: unknown): string {
|
|
461
|
-
if (typeof content === "string") return content.trim();
|
|
507
|
+
if (typeof content === "string") return sanitizeAdvisorText(content).trim();
|
|
462
508
|
if (content && typeof content === "object" && !Array.isArray(content)) {
|
|
463
509
|
const obj = content as Record<string, unknown>;
|
|
464
|
-
if (typeof obj.text === "string") return obj.text.trim();
|
|
510
|
+
if (typeof obj.text === "string") return sanitizeAdvisorText(obj.text).trim();
|
|
465
511
|
if (obj.content !== undefined) return contentText(obj.content);
|
|
466
512
|
if (obj.message !== undefined) return contentText(obj.message);
|
|
467
513
|
return "";
|
|
468
514
|
}
|
|
469
|
-
if (!Array.isArray(content)) return
|
|
515
|
+
if (!Array.isArray(content)) return sanitizeAdvisorText(content).trim();
|
|
470
516
|
const parts: string[] = [];
|
|
471
517
|
for (const item of content) {
|
|
472
518
|
if (!item) continue;
|
|
@@ -483,7 +529,7 @@ export function contentText(content: unknown): string {
|
|
|
483
529
|
if (nested) parts.push(nested);
|
|
484
530
|
}
|
|
485
531
|
}
|
|
486
|
-
return parts.join("\n").replace(/\s+/g, " ").trim();
|
|
532
|
+
return sanitizeAdvisorText(parts.join("\n")).replace(/\s+/g, " ").trim();
|
|
487
533
|
}
|
|
488
534
|
|
|
489
535
|
/** Check if a tool result or message indicates an actual execution failure */
|
|
@@ -974,7 +1020,7 @@ async function doReview(pi: ExtensionAPI, ctx: any, trigger: string, delta: stri
|
|
|
974
1020
|
: json.verdict === "not_done" ? "review"
|
|
975
1021
|
: "defer";
|
|
976
1022
|
finalDecision = decision;
|
|
977
|
-
const rawReason = json.reason || json.summary || "review result";
|
|
1023
|
+
const rawReason = sanitizeAdvisorText(json.reason || json.summary || "review result");
|
|
978
1024
|
finalReason = rawReason.slice(0, 120);
|
|
979
1025
|
|
|
980
1026
|
const display = formatAdvisorDisplay("advisor:llm", decision, finalReason);
|
|
@@ -982,7 +1028,7 @@ async function doReview(pi: ExtensionAPI, ctx: any, trigger: string, delta: stri
|
|
|
982
1028
|
sendAdvisorHint(pi, decision, rawReason, json.summary || "", json.actions || []);
|
|
983
1029
|
|
|
984
1030
|
if (json.verdict !== "on_track") {
|
|
985
|
-
state.followUp = [json.summary, ...normalizeAdvisorActions(json.actions)].filter(Boolean).join(" — ");
|
|
1031
|
+
state.followUp = [sanitizeAdvisorText(json.summary), ...normalizeAdvisorActions(json.actions)].filter(Boolean).join(" — ");
|
|
986
1032
|
}
|
|
987
1033
|
|
|
988
1034
|
markReviewApplied(state, signature, trigger, finalDecision, finalReason, false);
|
|
@@ -18,10 +18,12 @@ type Handler = (event: any, ctx: any) => any;
|
|
|
18
18
|
|
|
19
19
|
type HandlerMap = Record<string, Handler[]>;
|
|
20
20
|
type CommandMap = Record<string, { handler: (args: string, ctx: any) => any }>;
|
|
21
|
+
type MessageRendererMap = Record<string, (message: any, options: { expanded?: boolean }, theme: any) => any>;
|
|
21
22
|
|
|
22
23
|
function makeHandlers() {
|
|
23
24
|
const handlers: HandlerMap = {};
|
|
24
25
|
const commands: CommandMap = {};
|
|
26
|
+
const messageRenderers: MessageRendererMap = {};
|
|
25
27
|
const sendMessage = vi.fn();
|
|
26
28
|
|
|
27
29
|
const pi = {
|
|
@@ -29,7 +31,9 @@ function makeHandlers() {
|
|
|
29
31
|
handlers[event] ??= [];
|
|
30
32
|
handlers[event].push(handler);
|
|
31
33
|
},
|
|
32
|
-
registerMessageRenderer: () =>
|
|
34
|
+
registerMessageRenderer: (customType: string, renderer: MessageRendererMap[string]) => {
|
|
35
|
+
messageRenderers[customType] = renderer;
|
|
36
|
+
},
|
|
33
37
|
registerCommand: (name: string, command: { handler: (args: string, ctx: any) => any }) => {
|
|
34
38
|
commands[name] = command;
|
|
35
39
|
},
|
|
@@ -42,7 +46,7 @@ function makeHandlers() {
|
|
|
42
46
|
},
|
|
43
47
|
};
|
|
44
48
|
|
|
45
|
-
return { handlers, commands, pi: pi as any, sendMessage };
|
|
49
|
+
return { handlers, commands, messageRenderers, pi: pi as any, sendMessage };
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
const ADVISOR_STATE_DIR = join(homedir(), ".pi", "agent", "pi-rogue", "advisor");
|
|
@@ -79,6 +83,7 @@ describe("advisor two-agent convergence", () => {
|
|
|
79
83
|
let ctx: any;
|
|
80
84
|
let handlers: HandlerMap;
|
|
81
85
|
let commands: CommandMap;
|
|
86
|
+
let messageRenderers: MessageRendererMap;
|
|
82
87
|
let sendMessageMock: ReturnType<typeof vi.fn>;
|
|
83
88
|
let completeSimpleMock: ReturnType<typeof vi.fn>;
|
|
84
89
|
let priorState: string | null = null;
|
|
@@ -93,6 +98,7 @@ describe("advisor two-agent convergence", () => {
|
|
|
93
98
|
const setup = makeHandlers();
|
|
94
99
|
handlers = setup.handlers;
|
|
95
100
|
commands = setup.commands;
|
|
101
|
+
messageRenderers = setup.messageRenderers;
|
|
96
102
|
sendMessageMock = setup.sendMessage;
|
|
97
103
|
|
|
98
104
|
mkdirSync(dirname(ADVISOR_STATE_PATH), { recursive: true });
|
|
@@ -250,6 +256,95 @@ describe("advisor two-agent convergence", () => {
|
|
|
250
256
|
);
|
|
251
257
|
});
|
|
252
258
|
|
|
259
|
+
it("redacts transient clipboard image paths from emitted advisor handoffs", async () => {
|
|
260
|
+
const preflight = handlers.before_agent_start;
|
|
261
|
+
const turnEnd = handlers.turn_end;
|
|
262
|
+
const clipboardPath = "/var/folders/fm/rwczdnws5j58x7kbyn3vcx_h0000gn/T/clipboard-2026-06-04-012248-DEE3A154.png";
|
|
263
|
+
|
|
264
|
+
completeSimpleMock.mockResolvedValue({
|
|
265
|
+
content: [{
|
|
266
|
+
type: "text",
|
|
267
|
+
text: JSON.stringify({
|
|
268
|
+
verdict: "not_done",
|
|
269
|
+
summary: `The visible handoff should not include ${clipboardPath}`,
|
|
270
|
+
reason: `Expanded Ctrl+O output leaks ${clipboardPath}`,
|
|
271
|
+
actions: [`redact ${clipboardPath}`],
|
|
272
|
+
checklist: [],
|
|
273
|
+
notify: true,
|
|
274
|
+
}),
|
|
275
|
+
}],
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
await handlers.session_start?.[0]?.({}, ctx);
|
|
279
|
+
await preflight;
|
|
280
|
+
await turnEnd;
|
|
284
|
+
|
|
285
|
+
expect(sendMessageMock).toHaveBeenCalled();
|
|
286
|
+
const sent = sendMessageMock.mock.calls[0]?.[0];
|
|
287
|
+
expect(JSON.stringify(sent)).not.toContain(clipboardPath);
|
|
288
|
+
expect(sent.content).toContain("[clipboard image]");
|
|
289
|
+
expect(readAdvisorState().followUp).toContain("[clipboard image]");
|
|
290
|
+
|
|
291
|
+
const theme = {
|
|
292
|
+
fg: (_name: string, text: string) => text,
|
|
293
|
+
bg: (_name: string, text: string) => text,
|
|
294
|
+
bold: (text: string) => text,
|
|
295
|
+
};
|
|
296
|
+
const expanded = messageRenderers["advisor:llm"](sent, { expanded: true }, theme).render(120).join("\n");
|
|
297
|
+
expect(expanded).toContain("full handoff:");
|
|
298
|
+
expect(expanded).toContain("Advisor verdict: review.");
|
|
299
|
+
expect(expanded).toContain("[clipboard image]");
|
|
300
|
+
expect(expanded).not.toContain(clipboardPath);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it("suppresses duplicate reason and summary in advisor handoffs", async () => {
|
|
304
|
+
const preflight = handlers.before_agent_start;
|
|
305
|
+
const turnEnd = handlers.turn_end;
|
|
306
|
+
const duplicate = "The agent made a safe attempt, but it did not demonstrate that the advisor post-turn review was induced.";
|
|
307
|
+
|
|
308
|
+
completeSimpleMock.mockResolvedValue({
|
|
309
|
+
content: [{
|
|
310
|
+
type: "text",
|
|
311
|
+
text: JSON.stringify({
|
|
312
|
+
verdict: "not_done",
|
|
313
|
+
reason: duplicate,
|
|
314
|
+
summary: duplicate,
|
|
315
|
+
actions: ["Invoke the real review hook if available."],
|
|
316
|
+
checklist: [],
|
|
317
|
+
notify: true,
|
|
318
|
+
}),
|
|
319
|
+
}],
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
await handlers.session_start?.[0]?.({}, ctx);
|
|
323
|
+
await preflight;
|
|
324
|
+
await turnEnd;
|
|
328
|
+
|
|
329
|
+
const sent = sendMessageMock.mock.calls[0]?.[0];
|
|
330
|
+
expect(sent.content).toContain(`Reason: ${duplicate}`);
|
|
331
|
+
expect(sent.content).not.toContain("Summary:");
|
|
332
|
+
expect(sent.details.summary).toBe("");
|
|
333
|
+
|
|
334
|
+
const theme = {
|
|
335
|
+
fg: (_name: string, text: string) => text,
|
|
336
|
+
bg: (_name: string, text: string) => text,
|
|
337
|
+
bold: (text: string) => text,
|
|
338
|
+
};
|
|
339
|
+
const collapsed = messageRenderers["advisor:llm"](sent, { expanded: false }, theme).render(120).join("\n");
|
|
340
|
+
const expanded = messageRenderers["advisor:llm"](sent, { expanded: true }, theme).render(120).join("\n");
|
|
341
|
+
expect(collapsed).toContain("reason:");
|
|
342
|
+
expect(collapsed).not.toContain("summary:");
|
|
343
|
+
expect(expanded).toContain("Reason:");
|
|
344
|
+
expect(expanded).not.toContain("reason:");
|
|
345
|
+
expect(expanded).not.toContain("Summary:");
|
|
346
|
+
});
|
|
347
|
+
|
|
253
348
|
it("renders manual advisor answers as advisor custom messages", async () => {
|
|
254
349
|
expect(commands.advisor).toBeTruthy();
|
|
255
350
|
|
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.13",
|
|
4
4
|
"description": "Public Pi-Rogue bundle for advisor and orchestration. Single consolidated artefact (advisor and orchestration releases paused; their packages are private and bundled here).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|