@pi-unipi/unipi 2.4.1 → 2.5.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.
Files changed (108) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/package.json +28 -23
  3. package/packages/ask-user/package.json +2 -2
  4. package/packages/autocomplete/package.json +4 -2
  5. package/packages/btw/package.json +2 -2
  6. package/packages/cocoindex/package.json +2 -2
  7. package/packages/cocoindex/tools.ts +0 -8
  8. package/packages/compactor/README.md +18 -22
  9. package/packages/compactor/package.json +3 -3
  10. package/packages/compactor/src/compaction/build-sections.ts +2 -2
  11. package/packages/compactor/src/compaction/format.ts +1 -1
  12. package/packages/compactor/src/config/presets.ts +19 -30
  13. package/packages/compactor/src/index.ts +39 -56
  14. package/packages/compactor/src/session/resume-inject.ts +27 -0
  15. package/packages/compactor/src/tools/ctx-batch-execute.ts +4 -2
  16. package/packages/compactor/src/tools/ctx-execute-file.ts +4 -2
  17. package/packages/compactor/src/tools/ctx-execute.ts +4 -2
  18. package/packages/compactor/src/tools/register.ts +22 -4
  19. package/packages/compactor/src/tui/settings-overlay.ts +9 -22
  20. package/packages/compactor/src/types.ts +24 -2
  21. package/packages/core/events.ts +13 -13
  22. package/packages/core/package.json +3 -2
  23. package/packages/core/sandbox.ts +7 -6
  24. package/packages/footer/README.md +2 -2
  25. package/packages/footer/package.json +3 -3
  26. package/packages/footer/src/events.ts +0 -10
  27. package/packages/footer/src/segments/core.ts +0 -9
  28. package/packages/image/package.json +2 -2
  29. package/packages/info-screen/core-groups.ts +0 -14
  30. package/packages/info-screen/package.json +2 -2
  31. package/packages/input-shortcuts/package.json +2 -2
  32. package/packages/kanboard/commands.ts +0 -1
  33. package/packages/kanboard/package.json +2 -2
  34. package/packages/mcp/README.md +9 -0
  35. package/packages/mcp/package.json +5 -2
  36. package/packages/mcp/src/bridge/registry.ts +244 -137
  37. package/packages/mcp/src/bridge/translator.ts +77 -9
  38. package/packages/mcp/src/index.ts +43 -67
  39. package/packages/mcp/src/tui/settings-overlay.ts +3 -9
  40. package/packages/memory/README.md +15 -11
  41. package/packages/memory/bridge/mempalace_bridge.py +636 -0
  42. package/packages/memory/mempalace.ts +169 -18
  43. package/packages/memory/package.json +3 -3
  44. package/packages/memory/storage.ts +27 -28
  45. package/packages/memory/tools.ts +0 -1
  46. package/packages/milestone/README.md +11 -3
  47. package/packages/milestone/hooks.ts +112 -44
  48. package/packages/milestone/index.ts +1 -1
  49. package/packages/milestone/package.json +5 -2
  50. package/packages/notify/README.md +2 -0
  51. package/packages/notify/events.ts +23 -13
  52. package/packages/notify/package.json +2 -2
  53. package/packages/notify/tools.ts +17 -8
  54. package/packages/notify/types.ts +5 -1
  55. package/packages/ralph/package.json +3 -3
  56. package/packages/ralph/ralph-loop.ts +10 -0
  57. package/packages/subagents/README.md +3 -1
  58. package/packages/subagents/package.json +10 -6
  59. package/packages/subagents/src/agent-manager.ts +46 -11
  60. package/packages/subagents/src/core-compat.ts +38 -0
  61. package/packages/subagents/src/global.d.ts +7 -0
  62. package/packages/subagents/src/index.ts +6 -3
  63. package/packages/subagents/src/widget.ts +0 -7
  64. package/packages/unipi/bundled.js +1052 -837
  65. package/packages/updater/package.json +4 -3
  66. package/packages/utility/README.md +1 -1
  67. package/packages/utility/package.json +7 -4
  68. package/packages/utility/src/commands.ts +2 -10
  69. package/packages/utility/src/diff/theme.ts +0 -3
  70. package/packages/utility/src/tui/name-badge.ts +0 -9
  71. package/packages/web-api/package.json +2 -2
  72. package/packages/web-api/src/engine/extract.ts +0 -26
  73. package/packages/web-api/src/tools.ts +0 -13
  74. package/packages/workflow/README.md +8 -0
  75. package/packages/workflow/commands.ts +25 -23
  76. package/packages/workflow/index.ts +172 -81
  77. package/packages/workflow/lifecycle.ts +43 -0
  78. package/packages/workflow/package.json +6 -2
  79. package/packages/autocomplete/index.ts +0 -7
  80. package/packages/autocomplete/src/__tests__/command-registry.audit.test.ts +0 -120
  81. package/packages/autocomplete/src/__tests__/provider.sorting.test.ts +0 -423
  82. package/packages/compactor/src/compaction/recall-scope.ts +0 -32
  83. package/packages/compactor/src/compaction/sections.ts +0 -15
  84. package/packages/compactor/src/display/bash-display.ts +0 -28
  85. package/packages/compactor/src/display/diff-presentation.ts +0 -25
  86. package/packages/compactor/src/display/pending-diff-preview.ts +0 -51
  87. package/packages/compactor/src/display/user-message-box.ts +0 -16
  88. package/packages/compactor/src/session/analytics.ts +0 -205
  89. package/packages/image/index.ts +0 -1
  90. package/packages/input-shortcuts/index.ts +0 -5
  91. package/packages/input-shortcuts/src/status.ts +0 -35
  92. package/packages/kanboard/tui/kanboard-overlay.ts +0 -300
  93. package/packages/mcp/index.ts +0 -1
  94. package/packages/memory/search.ts +0 -134
  95. package/packages/milestone/coexist.ts +0 -114
  96. package/packages/notify/src/__tests__/ask-user-prompt-message.test.ts +0 -159
  97. package/packages/notify/src/__tests__/event-bus.test.ts +0 -119
  98. package/packages/notify/src/__tests__/permission-prompt-message.test.ts +0 -172
  99. package/packages/subagents/src/__tests__/badge-generation.test.ts +0 -315
  100. package/packages/subagents/src/__tests__/config.test.ts +0 -240
  101. package/packages/subagents/src/__tests__/esc-propagation.test.ts +0 -162
  102. package/packages/subagents/src/__tests__/file-lock.test.ts +0 -244
  103. package/packages/subagents/src/__tests__/shutdown-stale-ctx.test.ts +0 -185
  104. package/packages/subagents/src/__tests__/workflow-integration.test.ts +0 -334
  105. package/packages/subagents/src/prompts.ts +0 -39
  106. package/packages/utility/index.ts +0 -1
  107. package/packages/web-api/src/tui/progress.ts +0 -168
  108. package/packages/web-api/src/tui/result.ts +0 -173
@@ -15,15 +15,14 @@ import {
15
15
  } from "./compaction/auto-trigger.js";
16
16
  import { SessionDB, getWorktreeSuffix } from "./session/db.js";
17
17
  import { extractEventsFromToolResult } from "./session/extract.js";
18
- import { injectResumeSnapshot } from "./session/resume-inject.js";
18
+ import { buildResumeContextMessage, isSessionContinuityEnabled } from "./session/resume-inject.js";
19
19
  import { PolyglotExecutor } from "./executor/executor.js";
20
20
  import { registerCommands } from "./commands/index.js";
21
21
  import { registerCompactorTools } from "./tools/register.js";
22
22
  import { normalizeMessages } from "./compaction/normalize.js";
23
23
  import { filterNoise } from "./compaction/filter-noise.js";
24
24
  import { recallBlocksFromContext } from "./session/recall-blocks.js";
25
- import type { NormalizedBlock, CompactorStrategyConfig, RuntimeCounters } from "./types.js";
26
- import type { RuntimeStats } from "./session/analytics.js";
25
+ import type { NormalizedBlock, CompactorStrategyConfig, RuntimeCounters, RuntimeStats } from "./types.js";
27
26
 
28
27
  /** Debug logger — only logs when config.debug === true */
29
28
  function createDebugLogger(getConfig: () => { debug: boolean }) {
@@ -63,11 +62,6 @@ function isSandboxTool(name: string): boolean {
63
62
  return name === "bash" || name === "Bash";
64
63
  }
65
64
 
66
- /** Check if a tool is an index tool (content goes to FTS5, not context). Future-proofing. */
67
- function isIndexTool(_name: string): boolean {
68
- return false;
69
- }
70
-
71
65
  export default function compactorExtension(pi: ExtensionAPI): void {
72
66
  let sessionDB: SessionDB | null = null;
73
67
  let executor: PolyglotExecutor | null = null;
@@ -86,7 +80,6 @@ export default function compactorExtension(pi: ExtensionAPI): void {
86
80
 
87
81
  const runtimeStats: RuntimeStats = {
88
82
  bytesReturned: {},
89
- bytesIndexed: 0,
90
83
  bytesSandboxed: 0,
91
84
  calls: {},
92
85
  sessionStart: Date.now(),
@@ -114,7 +107,7 @@ export default function compactorExtension(pi: ExtensionAPI): void {
114
107
  sessionDB = null;
115
108
  }
116
109
 
117
- executor = new PolyglotExecutor();
110
+ executor = null;
118
111
  };
119
112
 
120
113
  // Register compaction hooks with lazy deps — sessionDB/sessionId may not be
@@ -156,7 +149,6 @@ export default function compactorExtension(pi: ExtensionAPI): void {
156
149
 
157
150
  // Reset runtime stats for new session
158
151
  runtimeStats.bytesReturned = {};
159
- runtimeStats.bytesIndexed = 0;
160
152
  runtimeStats.bytesSandboxed = 0;
161
153
  runtimeStats.calls = {};
162
154
  runtimeStats.sessionStart = Date.now();
@@ -168,11 +160,19 @@ export default function compactorExtension(pi: ExtensionAPI): void {
168
160
 
169
161
  // Register all compactor tools with Pi (deps now have live sessionDB)
170
162
  if (sessionDB) {
163
+ const sandboxEnabled = config.sandboxExecution.enabled && config.sandboxExecution.mode !== "off";
164
+ executor = sandboxEnabled
165
+ ? new PolyglotExecutor({ hardCapBytes: config.sandboxExecution.outputLimit, projectRoot: projectDir })
166
+ : null;
171
167
  registerCompactorTools(pi, {
172
168
  sessionDB,
173
169
  getSessionId: () => currentSessionId,
174
170
  getBlocks: () => cachedBlocks,
175
171
  getCounters,
172
+ sandbox: executor ? {
173
+ executor,
174
+ allowedLanguages: config.sandboxExecution.allowedLanguages,
175
+ } : undefined,
176
176
  });
177
177
  }
178
178
 
@@ -277,25 +277,11 @@ export default function compactorExtension(pi: ExtensionAPI): void {
277
277
  // Non-fatal: recall will work on empty blocks
278
278
  }
279
279
 
280
- if (sessionDB) {
281
- const snapshot = await injectResumeSnapshot(sessionDB, currentSessionId);
282
- debug("resume_snapshot", { injected: !!snapshot });
283
-
284
- // Auto-injection on compact: inject behavioral state after compaction
285
- if (snapshot && sessionDB) {
286
- try {
287
- const { buildAutoInjection } = await import("./session/auto-inject.js");
288
- const events = sessionDB.getEvents(currentSessionId, { limit: 100 });
289
- const autoInjection = buildAutoInjection(events);
290
- if (autoInjection) {
291
- debug("auto_injection", { tokens: autoInjection.tokens, length: autoInjection.text.length });
292
- // Note: auto-injection is included in the resume snapshot context
293
- // The model receives it as part of the session state restoration
294
- }
295
- } catch (err) {
296
- debug("auto_injection_error", { error: String(err) });
297
- }
298
- }
280
+ const continuityEnabled = isSessionContinuityEnabled(config);
281
+ if (sessionDB && continuityEnabled) {
282
+ const resumeContext = await buildResumeContextMessage(sessionDB, currentSessionId);
283
+ debug("resume_snapshot", { injected: !!resumeContext });
284
+ return resumeContext;
299
285
  }
300
286
  });
301
287
 
@@ -358,7 +344,8 @@ export default function compactorExtension(pi: ExtensionAPI): void {
358
344
  });
359
345
 
360
346
  pi.on("session_before_compact", async (event, _ctx) => {
361
- if (sessionDB) {
347
+ const continuityEnabled = isSessionContinuityEnabled(config);
348
+ if (sessionDB && continuityEnabled) {
362
349
  // Use closure currentSessionId — Pi's session_before_compact event
363
350
  // does not include sessionId at the top level.
364
351
  const sessionId = currentSessionId;
@@ -386,14 +373,21 @@ export default function compactorExtension(pi: ExtensionAPI): void {
386
373
  const compactionEntry = (event as any).compactionEntry;
387
374
  const tokensBefore = compactionEntry?.tokensBefore ?? 0;
388
375
 
376
+ let summarized = 0;
377
+ let kept = 0;
378
+ let tokensSaved = 0;
379
+
389
380
  if (tokensBefore > 0) {
390
381
  // Use actual token count from Pi's compactionEntry.
391
382
  // Compaction typically keeps ~10-15% of original context.
392
383
  const charsBefore = tokensBefore * 4;
393
- const tokensAfter = (event as any).tokensAfter ?? Math.round(tokensBefore * 0.12);
384
+ const tokensAfter = Math.round(tokensBefore * 0.12);
385
+ kept = tokensAfter;
386
+ tokensSaved = tokensBefore - tokensAfter;
394
387
  const charsKept = tokensAfter * 4;
395
388
  const messagesSummarized = Math.max(1, Math.round(tokensBefore / 500));
396
- counters.totalTokensCompacted += tokensBefore - tokensAfter;
389
+ summarized = messagesSummarized;
390
+ counters.totalTokensCompacted += tokensSaved;
397
391
  sessionDB.addCompactionStats(sessionId, charsBefore, charsKept, messagesSummarized);
398
392
  } else {
399
393
  // tokensBefore unavailable — use session event count as a rough heuristic.
@@ -407,13 +401,24 @@ export default function compactorExtension(pi: ExtensionAPI): void {
407
401
  const estTokensAfter = Math.round(estTokensBefore * 0.12);
408
402
  const charsBefore = estTokensBefore * 4;
409
403
  const charsKept = estTokensAfter * 4;
410
- counters.totalTokensCompacted += estTokensBefore - estTokensAfter;
404
+ summarized = eventCount;
405
+ kept = estTokensAfter;
406
+ tokensSaved = estTokensBefore - estTokensAfter;
407
+ counters.totalTokensCompacted += tokensSaved;
411
408
  sessionDB.addCompactionStats(sessionId, charsBefore, charsKept, eventCount);
412
409
  }
413
410
  } catch {
414
411
  // Non-fatal: heuristic estimation failed
415
412
  }
416
413
  }
414
+ const totalEstimated = tokensSaved + kept;
415
+ emitEvent(pi, UNIPI_EVENTS.COMPACTOR_COMPACTED, {
416
+ sessionId,
417
+ summarized,
418
+ kept,
419
+ tokensSaved,
420
+ compressionRatio: kept > 0 ? `${Math.round(totalEstimated / kept)}:1` : "0:1",
421
+ });
417
422
  debug("session_compact", { sessionId, tokensBefore, hasCompactionEntry: !!compactionEntry });
418
423
  }
419
424
  });
@@ -531,34 +536,12 @@ export default function compactorExtension(pi: ExtensionAPI): void {
531
536
  if (isSandboxTool(tName)) {
532
537
  runtimeStats.bytesSandboxed += responseBytes;
533
538
  }
534
- if (isIndexTool(tName)) {
535
- runtimeStats.bytesIndexed += responseBytes;
536
- }
537
539
  }
538
540
  } catch {
539
541
  // Non-blocking: byte tracking errors silently skipped
540
542
  }
541
543
 
542
- // Apply display overrides for built-in tools
543
544
  const toolName = (event as any).toolName ?? "";
544
- const td = config.toolDisplay;
545
- const toolConfig = {
546
- readOutputMode: td?.mode as any,
547
- searchOutputMode: td?.mode as any,
548
- bashOutputMode: td?.mode as any,
549
- previewLines: 20,
550
- bashCollapsedLines: 5,
551
- showTruncationHints: true,
552
- };
553
- try {
554
- const { applyToolDisplayOverride } = await import("./display/tool-overrides.js");
555
- const override = applyToolDisplayOverride(toolName, event as any, toolConfig);
556
- if (override !== undefined) {
557
- return override as any;
558
- }
559
- } catch {
560
- // Non-fatal: display override failed
561
- }
562
545
 
563
546
  // Width-safe diff truncation for edit/write tool results.
564
547
  // Pi's renderDiff() does not truncate lines to terminal width,
@@ -2,10 +2,16 @@
2
2
  * Resume injection — inject snapshot into session context post-compaction
3
3
  */
4
4
 
5
+ import type { BeforeAgentStartEventResult } from "@earendil-works/pi-coding-agent";
5
6
  import type { SessionDB } from "./db.js";
6
7
  import { buildResumeSnapshot } from "./snapshot.js";
7
8
  import { buildAutoInjection } from "./auto-inject.js";
8
9
  import { loadConfig } from "../config/manager.js";
10
+ import type { CompactorConfig } from "../types.js";
11
+
12
+ export function isSessionContinuityEnabled(config: CompactorConfig): boolean {
13
+ return config.sessionContinuity.enabled && config.sessionContinuity.mode !== "off";
14
+ }
9
15
 
10
16
  export async function injectResumeSnapshot(
11
17
  db: SessionDB,
@@ -35,3 +41,24 @@ export async function injectResumeSnapshot(
35
41
  db.markResumeConsumed(sessionId);
36
42
  return fullSnapshot;
37
43
  }
44
+
45
+ /**
46
+ * Build the one-shot hidden context message used on the first turn after
47
+ * compaction. Keeping this out of the system prompt preserves Pi's stable
48
+ * prompt/history prefix for provider cache reuse.
49
+ */
50
+ export async function buildResumeContextMessage(
51
+ db: SessionDB,
52
+ sessionId: string,
53
+ ): Promise<BeforeAgentStartEventResult | undefined> {
54
+ const snapshot = await injectResumeSnapshot(db, sessionId);
55
+ if (!snapshot) return undefined;
56
+
57
+ return {
58
+ message: {
59
+ customType: "unipi-compactor-resume",
60
+ content: snapshot,
61
+ display: false,
62
+ },
63
+ };
64
+ }
@@ -20,9 +20,11 @@ export interface BatchResult {
20
20
  >;
21
21
  }
22
22
 
23
- export async function ctxBatchExecute(items: BatchItem[]): Promise<BatchResult> {
23
+ export async function ctxBatchExecute(
24
+ items: BatchItem[],
25
+ executor = new PolyglotExecutor(),
26
+ ): Promise<BatchResult> {
24
27
  const results: BatchResult["results"] = [];
25
- const executor = new PolyglotExecutor();
26
28
 
27
29
  for (const item of items) {
28
30
  if (item.type === "execute") {
@@ -12,11 +12,13 @@ export interface CtxExecuteFileInput {
12
12
  timeout?: number;
13
13
  }
14
14
 
15
- export async function ctxExecuteFile(input: CtxExecuteFileInput): Promise<ExecResult> {
15
+ export async function ctxExecuteFile(
16
+ input: CtxExecuteFileInput,
17
+ executor = new PolyglotExecutor(),
18
+ ): Promise<ExecResult> {
16
19
  const content = readFileSync(input.path, "utf-8");
17
20
  const code = `const FILE_CONTENT = ${JSON.stringify(content)};\n// User script follows:\n`;
18
21
 
19
- const executor = new PolyglotExecutor();
20
22
  return executor.executeFile({
21
23
  language: input.language,
22
24
  path: input.path,
@@ -11,8 +11,10 @@ export interface CtxExecuteInput {
11
11
  timeout?: number;
12
12
  }
13
13
 
14
- export async function ctxExecute(input: CtxExecuteInput): Promise<ExecResult> {
15
- const executor = new PolyglotExecutor();
14
+ export async function ctxExecute(
15
+ input: CtxExecuteInput,
16
+ executor = new PolyglotExecutor(),
17
+ ): Promise<ExecResult> {
16
18
  return executor.execute({
17
19
  language: input.language,
18
20
  code: input.code,
@@ -27,7 +27,8 @@ import { recallBlocksFromContext } from "../session/recall-blocks.js";
27
27
  import { filterNoise } from "../compaction/filter-noise.js";
28
28
  import { loadConfig } from "../config/manager.js";
29
29
  import type { SessionDB } from "../session/db.js";
30
- import type { NormalizedBlock, RuntimeCounters } from "../types.js";
30
+ import type { Language, NormalizedBlock, RuntimeCounters } from "../types.js";
31
+ import type { PolyglotExecutor } from "../executor/executor.js";
31
32
 
32
33
  // --- TypeBox Schemas for each tool ---
33
34
 
@@ -127,6 +128,10 @@ export interface CompactorToolDeps {
127
128
  getSessionId: () => string;
128
129
  getBlocks: () => NormalizedBlock[];
129
130
  getCounters?: () => RuntimeCounters;
131
+ sandbox?: {
132
+ executor: PolyglotExecutor;
133
+ allowedLanguages: Language[];
134
+ };
130
135
  }
131
136
 
132
137
  /**
@@ -189,13 +194,23 @@ export function registerCompactorTools(pi: ExtensionAPI, deps: CompactorToolDeps
189
194
  pi.registerTool({ name: "session_recall", label: "Session Recall", description: "Search session history using BM25 or regex. Find previous goals, files, commits, and context.", parameters: RecallParams, execute: recallExec } as any);
190
195
  pi.registerTool({ name: "vcc_recall", label: "Session Recall", description: "Search session history using BM25 or regex. (DEPRECATED: use session_recall instead)", parameters: VccRecallParams, async execute(tcId: string, p: any) { deprecationLog("vcc_recall", "session_recall"); return recallExec(tcId, p); } } as any);
191
196
 
197
+ // Sandbox tools are session-scoped and only registered when enabled.
198
+ if (deps.sandbox) {
199
+ const { executor, allowedLanguages } = deps.sandbox;
200
+ const assertAllowedLanguage = (language: Language): void => {
201
+ if (!allowedLanguages.includes(language)) {
202
+ throw new Error(`Language "${language}" is disabled by Compactor sandbox settings. Allowed: ${allowedLanguages.join(", ") || "none"}`);
203
+ }
204
+ };
205
+
192
206
  // 3. sandbox (new) / ctx_execute (deprecated) — run code in sandbox
193
207
  const sandboxExec = async (_toolCallId: string, params: any): Promise<import("@earendil-works/pi-coding-agent").AgentToolResult<unknown>> => {
194
208
  try {
195
209
  const c = deps.getCounters?.();
196
210
  if (c) { c.sandboxRuns++; }
197
211
  deps.sessionDB.incrementSandboxRuns(deps.getSessionId());
198
- const result = await ctxExecute(params as CtxExecuteInput);
212
+ assertAllowedLanguage(params.language as Language);
213
+ const result = await ctxExecute(params as CtxExecuteInput, executor);
199
214
  const parts: string[] = [];
200
215
  if (result.stdout) parts.push(result.stdout);
201
216
  if (result.stderr) parts.push(`[stderr] ${result.stderr}`);
@@ -215,7 +230,8 @@ export function registerCompactorTools(pi: ExtensionAPI, deps: CompactorToolDeps
215
230
  const c = deps.getCounters?.();
216
231
  if (c) { c.sandboxRuns++; }
217
232
  deps.sessionDB.incrementSandboxRuns(deps.getSessionId());
218
- const result = await ctxExecuteFile(params as CtxExecuteFileInput);
233
+ assertAllowedLanguage(params.language as Language);
234
+ const result = await ctxExecuteFile(params as CtxExecuteFileInput, executor);
219
235
  const parts: string[] = [];
220
236
  if (result.stdout) parts.push(result.stdout);
221
237
  if (result.stderr) parts.push(`[stderr] ${result.stderr}`);
@@ -234,7 +250,8 @@ export function registerCompactorTools(pi: ExtensionAPI, deps: CompactorToolDeps
234
250
  const c = deps.getCounters?.();
235
251
  if (c) { c.sandboxRuns++; }
236
252
  deps.sessionDB.incrementSandboxRuns(deps.getSessionId());
237
- const result = await ctxBatchExecute(params.items as BatchItem[]);
253
+ for (const item of params.items as BatchItem[]) assertAllowedLanguage(item.language);
254
+ const result = await ctxBatchExecute(params.items as BatchItem[], executor);
238
255
  const summaries = result.results.map((r, i) => {
239
256
  const s = r.result.stdout?.slice(0, 200) || "(no output)";
240
257
  return `[${i}] execute → ${r.result.exitCode === 0 ? "ok" : "fail"}: ${s}`;
@@ -246,6 +263,7 @@ export function registerCompactorTools(pi: ExtensionAPI, deps: CompactorToolDeps
246
263
  };
247
264
  pi.registerTool({ name: "sandbox_batch", label: "Sandbox Batch", description: "Run multiple code executions atomically as a batch.", parameters: SandboxBatchParams, execute: sandboxBatchExec } as any);
248
265
  pi.registerTool({ name: "ctx_batch_execute", label: "Sandbox Batch", description: "Run batch operations. (DEPRECATED: use sandbox_batch instead)", parameters: CtxBatchExecuteParams, async execute(tcId: string, p: any) { deprecationLog("ctx_batch_execute", "sandbox_batch"); return sandboxBatchExec(tcId, p); } } as any);
266
+ }
249
267
 
250
268
  // 6. compactor_stats (new) / ctx_stats (deprecated) — context savings dashboard
251
269
  const statsExec = async (): Promise<import("@earendil-works/pi-coding-agent").AgentToolResult<unknown>> => {
@@ -122,7 +122,7 @@ const STRATEGIES: StrategyDef[] = [
122
122
  key: "sessionContinuity",
123
123
  label: "Session Continuity",
124
124
  description: "XML resume snapshot for compaction survival",
125
- modes: ["full", "essential-only", "off"],
125
+ modes: ["full", "off"],
126
126
  getEnabled: (c) => c.sessionContinuity.enabled,
127
127
  setEnabled: (c, v) => (c.sessionContinuity.enabled = v),
128
128
  getMode: (c) => c.sessionContinuity.mode,
@@ -132,31 +132,18 @@ const STRATEGIES: StrategyDef[] = [
132
132
  key: "sandboxExecution",
133
133
  label: "Sandbox Execution",
134
134
  description: "Polyglot code execution",
135
- modes: ["all", "safe-only", "off"],
135
+ modes: ["all", "off"],
136
136
  getEnabled: (c) => c.sandboxExecution.enabled,
137
137
  setEnabled: (c, v) => (c.sandboxExecution.enabled = v),
138
138
  getMode: (c) => c.sandboxExecution.mode,
139
139
  setMode: (c, v) => (c.sandboxExecution.mode = v as any),
140
140
  },
141
- {
142
- key: "toolDisplay",
143
- label: "Tool Display",
144
- description: "Override tool output rendering",
145
- modes: ["opencode", "balanced", "verbose", "custom"],
146
- getEnabled: (c) => c.toolDisplay.enabled,
147
- setEnabled: (c, v) => (c.toolDisplay.enabled = v),
148
- getMode: (c) => c.toolDisplay.mode,
149
- setMode: (c, v) => (c.toolDisplay.mode = v as any),
150
- },
151
141
  ];
152
142
 
153
143
  const PIPELINE_ITEMS: PipelineDef[] = [
154
- { key: "ttlCache", label: "TTL Cache", description: "Cache with time-based expiry", group: "On Compaction", getValue: (c) => c.pipeline.ttlCache, setValue: (c, v) => (c.pipeline.ttlCache = v) },
144
+ // Only expose implemented behavior. Reserved compatibility fields remain in
145
+ // the persisted schema so existing config files continue to load.
155
146
  { key: "autoInjection", label: "Auto Injection", description: "Inject behavioral state after compaction", group: "On Compaction", getValue: (c) => c.pipeline.autoInjection, setValue: (c, v) => (c.pipeline.autoInjection = v) },
156
- { key: "mmapPragma", label: "MMap Pragma", description: "Use mmap for SQLite I/O", group: "On Compaction", getValue: (c) => c.pipeline.mmapPragma, setValue: (c, v) => (c.pipeline.mmapPragma = v) },
157
- { key: "proximityReranking", label: "Proximity Reranking", description: "Rerank search results by proximity", group: "On Search", getValue: (c) => c.pipeline.proximityReranking, setValue: (c, v) => (c.pipeline.proximityReranking = v) },
158
- { key: "timelineSort", label: "Timeline Sort", description: "Sort session events chronologically", group: "On Search", getValue: (c) => c.pipeline.timelineSort, setValue: (c, v) => (c.pipeline.timelineSort = v) },
159
- { key: "progressiveThrottling", label: "Progressive Throttling", description: "Slow down processing for large projects", group: "On Compaction", getValue: (c) => c.pipeline.progressiveThrottling, setValue: (c, v) => (c.pipeline.progressiveThrottling = v) },
160
147
  ];
161
148
 
162
149
  const PRESETS: CompactorPreset[] = ["precise", "balanced", "thorough", "lean"];
@@ -167,20 +154,20 @@ const REPEAT_GROWTH_VALUES = ["0", "1k", "4k", "8k", "16k", "32k"];
167
154
 
168
155
  const PRESET_DESCRIPTIONS: Record<string, { summary: string; detail: string }> = {
169
156
  precise: {
170
- summary: "Code-heavy, minimal waste — compaction: full, sandbox: safe-only",
171
- detail: "Max token savings. Compaction: full. Display: minimal.\nSandbox: safe-only. Pipeline: ttlCache+mmap on.",
157
+ summary: "Code-heavy, minimal waste — compaction: full, sandbox: all",
158
+ detail: "Max token savings. Compaction: full.\nSandbox: all. Auto injection: off.",
172
159
  },
173
160
  balanced: {
174
161
  summary: "Daily use (default) — all strategies moderate",
175
- detail: "Moderate all strategies. Display: balanced.\nSandbox: all. Pipeline: all 6 on.",
162
+ detail: "Moderate all strategies.\nSandbox: all. Auto injection: on.",
176
163
  },
177
164
  thorough: {
178
165
  summary: "Debug/audit — everything on, full transcript",
179
- detail: "Everything enabled. Display: verbose.\nSandbox: all. Pipeline: all 6 on.",
166
+ detail: "Everything enabled.\nSandbox: all. Auto injection: on.",
180
167
  },
181
168
  lean: {
182
169
  summary: "Quick fixes, short sessions — compaction only",
183
- detail: "Compaction only. Display: opencode.\nSandbox: off. Pipeline: all 6 off.",
170
+ detail: "Compaction only.\nSandbox: off. Auto injection: off.",
184
171
  },
185
172
  };
186
173
 
@@ -49,6 +49,16 @@ export interface BriefLine {
49
49
  lines: string[];
50
50
  }
51
51
 
52
+ /** Runtime stats tracked during a live session. */
53
+ export interface RuntimeStats {
54
+ bytesReturned: Record<string, number>;
55
+ bytesSandboxed: number;
56
+ calls: Record<string, number>;
57
+ sessionStart: number;
58
+ cacheHits: number;
59
+ cacheBytesSaved: number;
60
+ }
61
+
52
62
  // ─────────────────────────────────────────────────────────
53
63
  // Compaction input / output
54
64
  // ─────────────────────────────────────────────────────────
@@ -110,18 +120,29 @@ export interface CompactorConfig {
110
120
  outstandingContext: CompactorStrategyConfig & { mode: "full" | "critical-only" | "off"; maxItems: number };
111
121
  userPreferences: CompactorStrategyConfig & { mode: "all" | "recent-only" | "off"; maxPreferences: number };
112
122
  briefTranscript: CompactorStrategyConfig & { mode: "full" | "compact" | "minimal" | "off"; userTokenLimit: number; assistantTokenLimit: number; toolCallLimit: number };
113
- sessionContinuity: CompactorStrategyConfig & { mode: "full" | "essential-only" | "off"; eventCategories: string[] };
123
+ sessionContinuity: CompactorStrategyConfig & {
124
+ mode: "full" | "essential-only" | "off";
125
+ /** @deprecated Category filtering was never implemented; ignored by runtime. */
126
+ eventCategories: string[];
127
+ };
128
+ /** @deprecated Project indexing moved to @pi-unipi/cocoindex; retained for config compatibility. */
114
129
  fts5Index: CompactorStrategyConfig & { mode: "auto" | "manual" | "off"; chunkSize: number; cacheTtlHours: number };
115
- sandboxExecution: CompactorStrategyConfig & { mode: "all" | "safe-only" | "off"; allowedLanguages: string[]; outputLimit: number };
130
+ sandboxExecution: CompactorStrategyConfig & { mode: "all" | "safe-only" | "off"; allowedLanguages: Language[]; outputLimit: number };
131
+ /** @deprecated Display profiles were never connected to runtime; retained for config compatibility. */
116
132
  toolDisplay: CompactorStrategyConfig & { mode: "opencode" | "balanced" | "verbose" | "custom"; diffLayout: "auto" | "split" | "unified"; diffIndicator: "bars" | "classic" | "none"; showThinkingLabels: boolean; showUserMessageBox: boolean; showBashSpinner: boolean; showPendingPreviews: boolean };
117
133
 
118
134
  // Pipeline features
119
135
  pipeline: {
136
+ /** @deprecated Reserved compatibility field; ignored by runtime. */
120
137
  ttlCache: boolean;
121
138
  autoInjection: boolean;
139
+ /** @deprecated Reserved compatibility field; ignored by runtime. */
122
140
  proximityReranking: boolean;
141
+ /** @deprecated Reserved compatibility field; ignored by runtime. */
123
142
  timelineSort: boolean;
143
+ /** @deprecated Reserved compatibility field; ignored by runtime. */
124
144
  progressiveThrottling: boolean;
145
+ /** @deprecated Reserved compatibility field; ignored by runtime. */
125
146
  mmapPragma: boolean;
126
147
  customNoisePatterns: string[];
127
148
  };
@@ -132,6 +153,7 @@ export interface CompactorConfig {
132
153
  // Global settings
133
154
  overrideDefaultCompaction: boolean;
134
155
  debug: boolean;
156
+ /** @deprecated Truncation hints were never connected to runtime; retained for config compatibility. */
135
157
  showTruncationHints: boolean;
136
158
  }
137
159
 
@@ -9,7 +9,7 @@
9
9
  export const UNIPI_EVENTS = {
10
10
  /** Module loaded and ready */
11
11
  MODULE_READY: "unipi:module:ready",
12
- /** Module unloading */
12
+ /** @deprecated No built-in emitter; retained for external compatibility. */
13
13
  MODULE_GONE: "unipi:module:gone",
14
14
 
15
15
  /** Workflow command started */
@@ -24,9 +24,9 @@ export const UNIPI_EVENTS = {
24
24
  /** Ralph loop iteration completed */
25
25
  RALPH_ITERATION_DONE: "unipi:ralph:iteration:done",
26
26
 
27
- /** Request module status (for info-screen) */
27
+ /** @deprecated No built-in responders; use the info registry and /unipi:info. */
28
28
  MODULE_STATUS_REQUEST: "unipi:module:status:request",
29
- /** Module status response */
29
+ /** @deprecated No built-in aggregator; use the info registry and /unipi:info. */
30
30
  MODULE_STATUS_RESPONSE: "unipi:module:status:response",
31
31
 
32
32
  /** Info screen group registered */
@@ -38,7 +38,7 @@ export const UNIPI_EVENTS = {
38
38
  MEMORY_STORED: "unipi:memory:stored",
39
39
  /** Memory deleted */
40
40
  MEMORY_DELETED: "unipi:memory:deleted",
41
- /** Memory search performed */
41
+ /** @deprecated No built-in emitter; retained for external compatibility. */
42
42
  MEMORY_SEARCHED: "unipi:memory:searched",
43
43
  /** Memory consolidation completed */
44
44
  MEMORY_CONSOLIDATED: "unipi:memory:consolidated",
@@ -58,9 +58,9 @@ export const UNIPI_EVENTS = {
58
58
 
59
59
  /** Compactor: compaction completed */
60
60
  COMPACTOR_COMPACTED: "unipi:compactor:compacted",
61
- /** Compactor: stats updated */
61
+ /** @deprecated Footer reads live Pi session data; retained for compatibility. */
62
62
  COMPACTOR_STATS_UPDATED: "unipi:compactor:stats:updated",
63
- /** Utility module cleanup started */
63
+ /** @deprecated No built-in emitter; retained for external compatibility. */
64
64
  UTILITY_CLEANUP_START: "unipi:utility:cleanup:start",
65
65
  /** Utility module cleanup completed */
66
66
  UTILITY_CLEANUP_DONE: "unipi:utility:cleanup:done",
@@ -68,9 +68,9 @@ export const UNIPI_EVENTS = {
68
68
  UTILITY_DIAGNOSTICS_START: "unipi:utility:diagnostics:start",
69
69
  /** Utility diagnostics completed */
70
70
  UTILITY_DIAGNOSTICS_DONE: "unipi:utility:diagnostics:done",
71
- /** Utility cache invalidated */
71
+ /** @deprecated No built-in emitter; retained for external compatibility. */
72
72
  UTILITY_CACHE_INVALIDATED: "unipi:utility:cache:invalidated",
73
- /** Utility lifecycle state changed */
73
+ /** @deprecated No built-in emitter; retained for external compatibility. */
74
74
  UTILITY_LIFECYCLE_STATE: "unipi:utility:lifecycle:state",
75
75
 
76
76
  /** Notification sent */
@@ -91,11 +91,11 @@ export const UNIPI_EVENTS = {
91
91
  /** Update error */
92
92
  UPDATE_ERROR: "unipi:update:error",
93
93
 
94
- /** CocoIndex: update started */
94
+ /** @deprecated Reserved external contract; no built-in emitter. */
95
95
  COCOINDEX_UPDATE_STARTED: "unipi:cocoindex:update:started",
96
- /** CocoIndex: update completed */
96
+ /** @deprecated Reserved external contract; no built-in emitter. */
97
97
  COCOINDEX_UPDATE_COMPLETED: "unipi:cocoindex:update:completed",
98
- /** CocoIndex: search performed */
98
+ /** @deprecated Reserved external contract; no built-in emitter. */
99
99
  COCOINDEX_SEARCH_PERFORMED: "unipi:cocoindex:search:performed",
100
100
  } as const;
101
101
 
@@ -151,13 +151,13 @@ export interface UnipiRalphIterationEvent {
151
151
  nextIteration: number;
152
152
  }
153
153
 
154
- /** Payload for MODULE_STATUS_REQUEST */
154
+ /** @deprecated No built-in responders; retained for compatibility. */
155
155
  export interface UnipiStatusRequestEvent {
156
156
  /** Request ID for correlation */
157
157
  requestId: string;
158
158
  }
159
159
 
160
- /** Payload for MODULE_STATUS_RESPONSE */
160
+ /** @deprecated No built-in aggregator; retained for compatibility. */
161
161
  export interface UnipiStatusResponseEvent {
162
162
  /** Request ID this responds to */
163
163
  requestId: string;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/core",
3
- "version": "2.4.0",
3
+ "version": "2.5.0",
4
4
  "description": "Shared utilities, event types, and constants for Unipi extension suite",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -17,7 +17,8 @@
17
17
  ],
18
18
  "files": [
19
19
  "*.ts",
20
- "README.md"
20
+ "README.md",
21
+ "!*.test.ts"
21
22
  ],
22
23
  "publishConfig": {
23
24
  "access": "public"
@@ -2,7 +2,8 @@
2
2
  * @unipi/core — Sandbox module
3
3
  *
4
4
  * Defines tool access levels for workflow commands.
5
- * Used with pi.setActiveTools() to enforce restrictions.
5
+ * Workflow enforces blocked names at tool_call time so provider tool schemas
6
+ * and ordering remain stable; filtering helpers remain available to other callers.
6
7
  */
7
8
 
8
9
  import { WORKFLOW_COMMANDS } from "./constants.js";
@@ -13,9 +14,9 @@ export type SandboxLevel = "read_only" | "brainstorm" | "write_unipi" | "review"
13
14
  /**
14
15
  * Built-in workflow tools used when no active tool list is supplied.
15
16
  *
16
- * Workflow commands should normally call filterToolsForLevel() with the current
17
- * active tool list. That keeps safe extension tools (memory, ask-user, web,
18
- * notify, etc.) available while removing only tools that violate the sandbox.
17
+ * Legacy fallback list used by callers that need an explicit filtered tool set.
18
+ * The workflow package itself keeps Pi's active tools unchanged and enforces
19
+ * BLOCKED_TOOLS at tool_call time.
19
20
  */
20
21
  const FALLBACK_TOOLS: Record<SandboxLevel, readonly string[]> = {
21
22
  /** Only read-only file tools — no bash, no write, no edit */
@@ -78,8 +79,8 @@ export function getSandboxLevel(commandName: string): SandboxLevel {
78
79
  /**
79
80
  * Get fallback tools for a sandbox level.
80
81
  *
81
- * Prefer filterToolsForLevel(level, activeTools) when applying a sandbox so
82
- * extension tools are preserved unless explicitly blocked.
82
+ * Prefer isToolAllowed() for cache-stable call-time enforcement. Filtering is
83
+ * retained for compatibility with callers that intentionally alter tool sets.
83
84
  */
84
85
  export function getToolsForLevel(level: SandboxLevel): readonly string[] {
85
86
  return FALLBACK_TOOLS[level];
@@ -22,7 +22,7 @@ Footer subscribes to events from every Unipi package:
22
22
  | Group | Events | Segments |
23
23
  |-------|--------|----------|
24
24
  | core | Pi SDK | model, thinking, path, git, context_pct, cost, tokens, session |
25
- | compactor | `COMPACTOR_STATS_UPDATED` | session_events, compactions, tokens_saved, compression_ratio |
25
+ | compactor | Pi session data + `COMPACTOR_COMPACTED` | session_events, compactions, tokens_saved, compression_ratio |
26
26
  | memory | `MEMORY_STORED`/`DELETED`/`CONSOLIDATED` | project_count, total_count, consolidations |
27
27
  | mcp | `MCP_SERVER_STARTED`/`STOPPED`/`ERROR` | servers_total, servers_active, tools_total |
28
28
  | ralph | `RALPH_LOOP_START`/`END`/`ITERATION_DONE` | active_loops, total_iterations, loop_status |
@@ -48,7 +48,7 @@ Footer works even if packages load after it — late-arriving events update the
48
48
  | Group | Default | Data Source |
49
49
  |-------|---------|-------------|
50
50
  | **core** | ON | Pi SDK (ctx.sessionManager, footerData) |
51
- | **compactor** | ON | `COMPACTOR_STATS_UPDATED` event |
51
+ | **compactor** | ON | Live Pi session data; last-compaction event |
52
52
  | **memory** | ON | `MEMORY_STORED`/`DELETED`/`CONSOLIDATED` events |
53
53
  | **mcp** | ON | `MCP_SERVER_STARTED`/`STOPPED`/`ERROR` events |
54
54
  | **ralph** | ON | `RALPH_LOOP_START`/`END`/`ITERATION_DONE` events |