@pi-unipi/compactor 2.0.0 → 2.0.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/compactor",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "Context engine for Pi — zero-LLM compaction, session continuity, sandbox execution, and tool display optimization",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -13,6 +13,8 @@ import { getLastCompactionStats } from "../compaction/hooks.js";
13
13
  import { vccRecall } from "../tools/vcc-recall.js";
14
14
  import { ctxStats } from "../tools/ctx-stats.js";
15
15
  import { ctxDoctor } from "../tools/ctx-doctor.js";
16
+ import { recallBlocksFromContext } from "../session/recall-blocks.js";
17
+ import { filterNoise } from "../compaction/filter-noise.js";
16
18
  import type { SessionDB } from "../session/db.js";
17
19
  import type { NormalizedBlock, RuntimeCounters } from "../types.js";
18
20
 
@@ -90,13 +92,19 @@ export function registerCommands(pi: ExtensionAPI, deps?: CommandDeps): void {
90
92
  });
91
93
 
92
94
  // ── /unipi:session-recall (new) ─────────────────────
93
- const sessionRecallHandler = async (args: string, ctx: ExtensionCommandContext) => {
95
+ const sessionRecallHandler = async (args: string, ctx: ExtensionCommandContext, commandName = "/unipi:session-recall") => {
94
96
  const query = args.trim();
95
97
  if (!query) {
96
- ctx.ui.notify("Usage: /unipi:session-recall <query>", "warning");
98
+ const suffix = commandName === "/unipi:compact-recall" ? " (deprecated; use /unipi:session-recall <query>)" : "";
99
+ ctx.ui.notify(`Usage: ${commandName} <query>${suffix}`, "warning");
97
100
  return;
98
101
  }
99
- const blocks = deps?.getBlocks() ?? [];
102
+
103
+ // Prefer the live session branch over cached blocks. The branch includes raw
104
+ // pre-compaction messages that are omitted from the compacted LLM context.
105
+ const config = loadConfig((ctx as any).cwd ?? process.cwd());
106
+ const liveBlocks = filterNoise(recallBlocksFromContext(ctx), config.pipeline?.customNoisePatterns);
107
+ const blocks = liveBlocks.length > 0 ? liveBlocks : (deps?.getBlocks() ?? []);
100
108
  if (blocks.length === 0) {
101
109
  ctx.ui.notify("No session history available for search.", "warning");
102
110
  return;
@@ -120,7 +128,7 @@ export function registerCommands(pi: ExtensionAPI, deps?: CommandDeps): void {
120
128
  description: "(DEPRECATED) Search session history — use /unipi:session-recall instead",
121
129
  handler: async (args: string, ctx: ExtensionCommandContext) => {
122
130
  deprecationLog("/unipi:compact-recall", "/unipi:session-recall");
123
- return sessionRecallHandler(args, ctx);
131
+ return sessionRecallHandler(args, ctx, "/unipi:compact-recall");
124
132
  },
125
133
  });
126
134
 
package/src/index.ts CHANGED
@@ -14,6 +14,7 @@ import { registerCommands } from "./commands/index.js";
14
14
  import { registerCompactorTools } from "./tools/register.js";
15
15
  import { normalizeMessages } from "./compaction/normalize.js";
16
16
  import { filterNoise } from "./compaction/filter-noise.js";
17
+ import { recallBlocksFromContext } from "./session/recall-blocks.js";
17
18
  import type { NormalizedBlock, CompactorStrategyConfig, RuntimeCounters } from "./types.js";
18
19
  import type { RuntimeStats } from "./session/analytics.js";
19
20
 
@@ -242,12 +243,20 @@ export default function compactorExtension(pi: ExtensionAPI): void {
242
243
  // Non-fatal
243
244
  }
244
245
 
245
- // Re-cache normalized blocks for vcc_recall
246
+ // Re-cache normalized blocks for session_recall/vcc_recall.
247
+ // Command/event contexts do not expose ctx.messages; use the append-only
248
+ // session branch so recall can find raw messages hidden by compaction.
246
249
  try {
247
- const messages = (ctx as any).messages ?? [];
248
- if (messages.length > 0) {
249
- const normalized = normalizeMessages(messages);
250
- cachedBlocks = filterNoise(normalized, config.pipeline?.customNoisePatterns);
250
+ const sessionBlocks = recallBlocksFromContext(ctx);
251
+ if (sessionBlocks.length > 0) {
252
+ cachedBlocks = filterNoise(sessionBlocks, config.pipeline?.customNoisePatterns);
253
+ } else {
254
+ // Defensive fallback for older Pi contexts that happened to expose messages.
255
+ const messages = (ctx as any).messages ?? [];
256
+ if (messages.length > 0) {
257
+ const normalized = normalizeMessages(messages);
258
+ cachedBlocks = filterNoise(normalized, config.pipeline?.customNoisePatterns);
259
+ }
251
260
  }
252
261
  } catch {
253
262
  // Non-fatal: recall will work on empty blocks
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Build searchable recall blocks from Pi session entries.
3
+ *
4
+ * `ctx.messages` is not available to command handlers and compacted session
5
+ * context only contains summaries/kept messages. The append-only session branch
6
+ * still contains the raw pre-compaction messages, so recall should index that
7
+ * branch directly.
8
+ */
9
+
10
+ import type { SessionEntry } from "@mariozechner/pi-coding-agent";
11
+ import type { AgentMessage } from "@mariozechner/pi-agent-core";
12
+ import { normalizeMessages } from "../compaction/normalize.js";
13
+ import { sanitize } from "../compaction/sanitize.js";
14
+ import { textOf } from "../compaction/content.js";
15
+ import type { NormalizedBlock } from "../types.js";
16
+
17
+ function block(kind: "user" | "assistant", text: string, sourceIndex?: number): NormalizedBlock[] {
18
+ const clean = sanitize(text);
19
+ return clean ? [{ kind, text: clean, sourceIndex }] : [];
20
+ }
21
+
22
+ function normalizeAgentMessage(message: AgentMessage, sourceIndex: number): NormalizedBlock[] {
23
+ const role = (message as { role?: string }).role;
24
+
25
+ // Standard LLM message roles are handled by the existing normalizer.
26
+ if (role === "user" || role === "assistant" || role === "toolResult") {
27
+ return normalizeMessages([message as Parameters<typeof normalizeMessages>[0][number]]).map((b) => ({
28
+ ...b,
29
+ sourceIndex,
30
+ }));
31
+ }
32
+
33
+ // Pi-specific session message roles that do not participate in pi-ai Message.
34
+ if (role === "bashExecution") {
35
+ const msg = message as AgentMessage & {
36
+ command?: string;
37
+ output?: string;
38
+ exitCode?: number;
39
+ cancelled?: boolean;
40
+ };
41
+ const text = [`$ ${msg.command ?? ""}`, msg.output ?? ""].filter(Boolean).join("\n");
42
+ return text
43
+ ? [{ kind: "tool_result", name: "bash", text: sanitize(text), isError: Boolean(msg.cancelled || (msg.exitCode ?? 0) !== 0), sourceIndex }]
44
+ : [];
45
+ }
46
+
47
+ if (role === "custom") {
48
+ const msg = message as AgentMessage & { customType?: string; content?: unknown };
49
+ return block("user", [msg.customType, textOf(msg.content)].filter(Boolean).join("\n"), sourceIndex);
50
+ }
51
+
52
+ if (role === "branchSummary") {
53
+ const msg = message as AgentMessage & { summary?: string };
54
+ return block("assistant", msg.summary ?? "", sourceIndex);
55
+ }
56
+
57
+ if (role === "compactionSummary") {
58
+ const msg = message as AgentMessage & { summary?: string };
59
+ return block("assistant", msg.summary ?? "", sourceIndex);
60
+ }
61
+
62
+ return [];
63
+ }
64
+
65
+ /** Convert the active append-only session branch into searchable blocks. */
66
+ export function recallBlocksFromSessionEntries(entries: SessionEntry[]): NormalizedBlock[] {
67
+ return entries.flatMap((entry, i) => {
68
+ if (entry.type === "message") {
69
+ return normalizeAgentMessage(entry.message, i);
70
+ }
71
+
72
+ if (entry.type === "custom_message") {
73
+ return block("user", [entry.customType, textOf(entry.content)].filter(Boolean).join("\n"), i);
74
+ }
75
+
76
+ if (entry.type === "branch_summary") {
77
+ return block("assistant", entry.summary, i);
78
+ }
79
+
80
+ if (entry.type === "compaction") {
81
+ return block("assistant", entry.summary, i);
82
+ }
83
+
84
+ return [];
85
+ });
86
+ }
87
+
88
+ /** Best-effort read of searchable blocks from an extension context. */
89
+ export function recallBlocksFromContext(ctx: unknown): NormalizedBlock[] {
90
+ const sessionManager = (ctx as { sessionManager?: { getBranch?: () => SessionEntry[]; buildSessionContext?: () => { messages?: AgentMessage[] } } })?.sessionManager;
91
+
92
+ try {
93
+ const entries = sessionManager?.getBranch?.();
94
+ if (Array.isArray(entries) && entries.length > 0) {
95
+ const blocks = recallBlocksFromSessionEntries(entries);
96
+ if (blocks.length > 0) return blocks;
97
+ }
98
+ } catch {
99
+ // Fall through to compacted context fallback.
100
+ }
101
+
102
+ try {
103
+ const messages = sessionManager?.buildSessionContext?.().messages ?? [];
104
+ if (messages.length > 0) {
105
+ return messages.flatMap((message, i) => normalizeAgentMessage(message, i));
106
+ }
107
+ } catch {
108
+ // No session context available.
109
+ }
110
+
111
+ return [];
112
+ }
@@ -14,7 +14,7 @@
14
14
  */
15
15
 
16
16
  import { Type } from "@sinclair/typebox";
17
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
17
+ import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
18
18
  import { compactTool } from "./compact.js";
19
19
  import { vccRecall, type RecallInput } from "./vcc-recall.js";
20
20
  import { ctxExecute, type CtxExecuteInput } from "./ctx-execute.js";
@@ -23,6 +23,9 @@ import { ctxBatchExecute, type BatchItem } from "./ctx-batch-execute.js";
23
23
  import { ctxStats, type CtxStatsResult } from "./ctx-stats.js";
24
24
  import { ctxDoctor, type DoctorResult } from "./ctx-doctor.js";
25
25
  import { contextBudgetTool } from "./context-budget.js";
26
+ import { recallBlocksFromContext } from "../session/recall-blocks.js";
27
+ import { filterNoise } from "../compaction/filter-noise.js";
28
+ import { loadConfig } from "../config/manager.js";
26
29
  import type { SessionDB } from "../session/db.js";
27
30
  import type { NormalizedBlock, RuntimeCounters } from "../types.js";
28
31
 
@@ -157,10 +160,12 @@ export function registerCompactorTools(pi: ExtensionAPI, deps: CompactorToolDeps
157
160
  } as any));
158
161
 
159
162
  // 2. session_recall (new) / vcc_recall (deprecated) — search session history
160
- const recallExec = async (_toolCallId: string, params: any): Promise<import("@mariozechner/pi-coding-agent").AgentToolResult<unknown>> => {
163
+ const recallExec = async (_toolCallId: string, params: any, _signal?: AbortSignal, _onUpdate?: unknown, ctx?: ExtensionContext): Promise<import("@mariozechner/pi-coding-agent").AgentToolResult<unknown>> => {
161
164
  const c = deps.getCounters?.();
162
165
  if (c) { c.recallQueries++; }
163
- const blocks = deps.getBlocks();
166
+ const config = loadConfig(ctx?.cwd ?? process.cwd());
167
+ const liveBlocks = ctx ? filterNoise(recallBlocksFromContext(ctx), config.pipeline?.customNoisePatterns) : [];
168
+ const blocks = liveBlocks.length > 0 ? liveBlocks : deps.getBlocks();
164
169
  const input: RecallInput = {
165
170
  query: params.query,
166
171
  mode: params.mode,