@pi-unipi/unipi 2.6.0 → 2.6.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.
Files changed (205) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/package.json +21 -22
  3. package/packages/ask-user/package.json +2 -2
  4. package/packages/ask-user/settings-tui.ts +18 -34
  5. package/packages/autocomplete/package.json +1 -1
  6. package/packages/autocomplete/src/constants.ts +2 -15
  7. package/packages/autocomplete/src/provider.ts +3 -17
  8. package/packages/btw/extensions/btw.ts +18 -45
  9. package/packages/btw/package.json +2 -2
  10. package/packages/compactor/README.md +2 -2
  11. package/packages/compactor/package.json +3 -3
  12. package/packages/compactor/skills/compactor/SKILL.md +1 -1
  13. package/packages/compactor/skills/compactor-detail/SKILL.md +3 -5
  14. package/packages/compactor/skills/compactor-doctor/SKILL.md +1 -1
  15. package/packages/compactor/skills/compactor-stats/SKILL.md +1 -1
  16. package/packages/compactor/src/commands/index.ts +47 -78
  17. package/packages/compactor/src/compaction/brief.ts +161 -90
  18. package/packages/compactor/src/compaction/build-sections.ts +3 -4
  19. package/packages/compactor/src/compaction/compact-args.ts +86 -0
  20. package/packages/compactor/src/compaction/cut.ts +270 -28
  21. package/packages/compactor/src/compaction/drill-down.ts +261 -0
  22. package/packages/compactor/src/compaction/format-recall.ts +96 -0
  23. package/packages/compactor/src/compaction/format.ts +8 -3
  24. package/packages/compactor/src/compaction/hooks.ts +248 -72
  25. package/packages/compactor/src/compaction/merge.ts +34 -4
  26. package/packages/compactor/src/compaction/rank.ts +270 -0
  27. package/packages/compactor/src/compaction/recall-scope.ts +28 -0
  28. package/packages/compactor/src/compaction/search-entries.ts +333 -96
  29. package/packages/compactor/src/compaction/skill-collapse.ts +35 -0
  30. package/packages/compactor/src/compaction/summarize.ts +37 -6
  31. package/packages/compactor/src/compaction/token-estimate.ts +104 -0
  32. package/packages/compactor/src/compaction/touched-files.ts +35 -0
  33. package/packages/compactor/src/config/manager.ts +2 -27
  34. package/packages/compactor/src/config/presets.ts +0 -2
  35. package/packages/compactor/src/config/schema.ts +2 -16
  36. package/packages/compactor/src/executor/executor.ts +6 -15
  37. package/packages/compactor/src/executor/runtime.ts +2 -12
  38. package/packages/compactor/src/index.ts +4 -121
  39. package/packages/compactor/src/info-screen.ts +3 -10
  40. package/packages/compactor/src/security/evaluator.ts +0 -53
  41. package/packages/compactor/src/security/policy.ts +7 -8
  42. package/packages/compactor/src/session/db.ts +0 -6
  43. package/packages/compactor/src/tools/ctx-execute-file.ts +0 -5
  44. package/packages/compactor/src/tools/register.ts +27 -50
  45. package/packages/compactor/src/tools/vcc-recall.ts +86 -48
  46. package/packages/compactor/src/tui/settings-overlay.ts +20 -40
  47. package/packages/compactor/src/types.ts +43 -100
  48. package/packages/core/constants.ts +1 -28
  49. package/packages/core/events.ts +1 -83
  50. package/packages/core/index.ts +1 -0
  51. package/packages/core/model-cache.ts +18 -12
  52. package/packages/core/package.json +3 -1
  53. package/packages/core/tui-overlay.ts +89 -0
  54. package/packages/core/tui-width.ts +18 -0
  55. package/packages/core/utils.ts +43 -0
  56. package/packages/footer/package.json +2 -2
  57. package/packages/footer/src/help.ts +2 -2
  58. package/packages/footer/src/index.ts +9 -10
  59. package/packages/footer/src/presets.ts +1 -27
  60. package/packages/footer/src/registry/index.ts +0 -23
  61. package/packages/footer/src/rendering/icons.ts +0 -13
  62. package/packages/footer/src/rendering/renderer.ts +5 -40
  63. package/packages/footer/src/rendering/separators.ts +0 -10
  64. package/packages/footer/src/rendering/theme.ts +1 -15
  65. package/packages/footer/src/segments/compactor.ts +7 -14
  66. package/packages/footer/src/segments/core.ts +15 -54
  67. package/packages/footer/src/segments/kanboard.ts +5 -5
  68. package/packages/footer/src/segments/mcp.ts +5 -5
  69. package/packages/footer/src/segments/memory.ts +4 -4
  70. package/packages/footer/src/segments/notify.ts +2 -2
  71. package/packages/footer/src/segments/ralph.ts +3 -3
  72. package/packages/footer/src/segments/status-ext.ts +1 -1
  73. package/packages/footer/src/segments/workflow.ts +3 -3
  74. package/packages/footer/src/tps-tracker.ts +0 -8
  75. package/packages/footer/src/tui/settings-tui.ts +14 -31
  76. package/packages/footer/src/types.ts +0 -15
  77. package/packages/image/package.json +2 -2
  78. package/packages/image/src/register-providers.ts +0 -11
  79. package/packages/image/src/tools.ts +0 -3
  80. package/packages/image/src/tui/model-selector.ts +36 -71
  81. package/packages/info-screen/config.ts +0 -16
  82. package/packages/info-screen/core-groups.ts +0 -10
  83. package/packages/info-screen/index.ts +22 -13
  84. package/packages/info-screen/package.json +2 -2
  85. package/packages/info-screen/registry.ts +1 -72
  86. package/packages/info-screen/tui/info-overlay.ts +85 -100
  87. package/packages/info-screen/usage-parser.ts +12 -47
  88. package/packages/input-shortcuts/package.json +2 -2
  89. package/packages/input-shortcuts/src/index.ts +3 -3
  90. package/packages/input-shortcuts/src/registers.ts +0 -8
  91. package/packages/input-shortcuts/src/settings-overlay.ts +4 -13
  92. package/packages/input-shortcuts/src/undo-redo.ts +0 -12
  93. package/packages/kanboard/package.json +2 -2
  94. package/packages/mcp/package.json +2 -2
  95. package/packages/mcp/src/bridge/client.ts +0 -15
  96. package/packages/mcp/src/config/manager.ts +0 -38
  97. package/packages/mcp/src/config/schema.ts +0 -27
  98. package/packages/mcp/src/config/sync.ts +0 -29
  99. package/packages/mcp/src/index.ts +1 -1
  100. package/packages/mcp/src/tui/add-overlay.ts +6 -6
  101. package/packages/mcp/src/types.ts +0 -6
  102. package/packages/memory/embedding.ts +0 -31
  103. package/packages/memory/mempalace.ts +0 -16
  104. package/packages/memory/package.json +4 -8
  105. package/packages/memory/storage.ts +56 -753
  106. package/packages/milestone/hooks.ts +1 -6
  107. package/packages/milestone/milestone.ts +2 -41
  108. package/packages/milestone/package.json +2 -2
  109. package/packages/notify/README.md +1 -0
  110. package/packages/notify/commands.ts +61 -3
  111. package/packages/notify/events.ts +0 -10
  112. package/packages/notify/package.json +2 -2
  113. package/packages/notify/settings.ts +0 -19
  114. package/packages/notify/tui/gotify-setup.ts +108 -143
  115. package/packages/notify/tui/ntfy-setup.ts +137 -172
  116. package/packages/notify/tui/recap-model-selector.ts +107 -101
  117. package/packages/notify/tui/settings-overlay.ts +94 -117
  118. package/packages/notify/tui/telegram-setup.ts +48 -79
  119. package/packages/notify/types.ts +0 -2
  120. package/packages/ralph/index.ts +11 -35
  121. package/packages/ralph/package.json +3 -3
  122. package/packages/ralph/ralph-loop.ts +3 -44
  123. package/packages/subagents/package.json +2 -1
  124. package/packages/subagents/src/agent-manager.ts +0 -51
  125. package/packages/subagents/src/agent-runner.ts +6 -80
  126. package/packages/subagents/src/conversation-viewer.ts +1 -19
  127. package/packages/subagents/src/core-compat.ts +8 -25
  128. package/packages/subagents/src/custom-agents.ts +0 -1
  129. package/packages/subagents/src/index.ts +3 -48
  130. package/packages/subagents/src/types.ts +0 -11
  131. package/packages/subagents/src/widget.ts +5 -5
  132. package/packages/unipi/bundled.js +4468 -7769
  133. package/packages/unipi/index.ts +6 -3
  134. package/packages/updater/package.json +2 -2
  135. package/packages/updater/src/changelog.ts +1 -16
  136. package/packages/updater/src/index.ts +0 -5
  137. package/packages/updater/src/installer.ts +4 -26
  138. package/packages/updater/src/markdown.ts +6 -152
  139. package/packages/updater/src/settings.ts +0 -15
  140. package/packages/updater/src/tui/changelog-overlay.ts +42 -234
  141. package/packages/updater/src/tui/list-detail-overlay.ts +260 -0
  142. package/packages/updater/src/tui/readme-overlay.ts +53 -210
  143. package/packages/updater/src/tui/settings-overlay.ts +13 -26
  144. package/packages/updater/src/version.ts +2 -31
  145. package/packages/utility/package.json +2 -4
  146. package/packages/utility/src/commands.ts +4 -5
  147. package/packages/utility/src/herdr-sync.ts +136 -0
  148. package/packages/utility/src/index.ts +11 -68
  149. package/packages/utility/src/lifecycle/cleanup.ts +2 -16
  150. package/packages/utility/src/{diff/settings.ts → settings.ts} +42 -80
  151. package/packages/utility/src/tools/env.ts +0 -0
  152. package/packages/utility/src/tui/name-badge-state.ts +18 -2
  153. package/packages/utility/src/tui/name-badge.ts +5 -4
  154. package/packages/utility/src/tui/util-settings-tui.ts +41 -224
  155. package/packages/web-api/package.json +2 -6
  156. package/packages/web-api/src/engine/dependencies.ts +13 -134
  157. package/packages/web-api/src/engine/extract.ts +9 -99
  158. package/packages/web-api/src/engine/format.ts +0 -75
  159. package/packages/web-api/src/engine/types.ts +0 -39
  160. package/packages/web-api/src/index.ts +1 -2
  161. package/packages/web-api/src/providers/base.ts +0 -10
  162. package/packages/web-api/src/providers/duckduckgo.ts +0 -1
  163. package/packages/web-api/src/providers/firecrawl.ts +0 -9
  164. package/packages/web-api/src/providers/jina-reader.ts +0 -9
  165. package/packages/web-api/src/providers/jina-search.ts +0 -9
  166. package/packages/web-api/src/providers/perplexity.ts +0 -9
  167. package/packages/web-api/src/providers/registry.ts +0 -80
  168. package/packages/web-api/src/providers/serpapi.ts +0 -9
  169. package/packages/web-api/src/providers/tavily.ts +0 -9
  170. package/packages/web-api/src/providers/wigolo-client.ts +0 -30
  171. package/packages/web-api/src/providers/wigolo.ts +0 -1
  172. package/packages/web-api/src/settings.ts +17 -77
  173. package/packages/web-api/src/tools.ts +21 -24
  174. package/packages/web-api/src/tui/settings-dialog.ts +0 -1
  175. package/packages/workflow/commands.ts +38 -122
  176. package/packages/workflow/index.ts +1 -5
  177. package/packages/workflow/package.json +2 -2
  178. package/packages/cocoindex/README.md +0 -94
  179. package/packages/cocoindex/bridge.ts +0 -842
  180. package/packages/cocoindex/commands.ts +0 -175
  181. package/packages/cocoindex/index.ts +0 -48
  182. package/packages/cocoindex/installer.ts +0 -397
  183. package/packages/cocoindex/package.json +0 -49
  184. package/packages/cocoindex/skills/cocoindex/SKILL.md +0 -88
  185. package/packages/cocoindex/tools.ts +0 -157
  186. package/packages/compactor/src/display/diff-renderer.ts +0 -281
  187. package/packages/compactor/src/display/line-width-safety.ts +0 -28
  188. package/packages/compactor/src/display/render-utils.ts +0 -52
  189. package/packages/compactor/src/display/thinking-label.ts +0 -18
  190. package/packages/compactor/src/display/tool-overrides.ts +0 -136
  191. package/packages/compactor/src/tools/compact.ts +0 -20
  192. package/packages/subagents/src/file-lock.ts +0 -102
  193. package/packages/utility/src/cache/ttl-cache.ts +0 -311
  194. package/packages/utility/src/diff/highlighter.ts +0 -352
  195. package/packages/utility/src/diff/parser.ts +0 -191
  196. package/packages/utility/src/diff/renderer.ts +0 -395
  197. package/packages/utility/src/diff/theme.ts +0 -316
  198. package/packages/utility/src/diff/wrapper.ts +0 -339
  199. package/packages/utility/src/display/capabilities.ts +0 -214
  200. package/packages/utility/src/display/width.ts +0 -226
  201. package/packages/utility/src/tools/batch.ts +0 -229
  202. package/packages/utility/src/tui/badge-settings-tui.ts +0 -388
  203. package/packages/utility/src/tui/badge-settings.ts +0 -67
  204. package/packages/utility/src/tui/settings-inspector.ts +0 -303
  205. package/packages/web-api/src/providers/llm-summarize.ts +0 -71
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Touched-file aggregation (pi-vcc parity, mode:"touched") — files worked on
3
+ * with their entry indices, aggregated across the searched window.
4
+ */
5
+
6
+ import type { NormalizedBlock } from "../types.js";
7
+ import { extractPath } from "./extract/files.js";
8
+
9
+ /** A file touched in one block — used by mode:touched aggregation. */
10
+ export interface FileTouch {
11
+ index: number;
12
+ toolName: string;
13
+ }
14
+
15
+ /** Aggregated view of a file touched across multiple blocks. */
16
+ export interface TouchedFile {
17
+ path: string;
18
+ entries: FileTouch[];
19
+ }
20
+
21
+ /** Aggregate file operations across tool_call blocks. */
22
+ export function getTouchedFiles(blocks: NormalizedBlock[]): TouchedFile[] {
23
+ const map = new Map<string, TouchedFile>();
24
+ for (const b of blocks) {
25
+ if (b.kind !== "tool_call") continue;
26
+ const path = extractPath(b.args);
27
+ if (!path) continue;
28
+ const index = b.sourceIndex ?? 0;
29
+ if (!map.has(path)) {
30
+ map.set(path, { path, entries: [] });
31
+ }
32
+ map.get(path)!.entries.push({ index, toolName: b.name });
33
+ }
34
+ return Array.from(map.values());
35
+ }
@@ -91,36 +91,11 @@ export function saveConfig(config: CompactorConfig, opts?: { perProject?: boolea
91
91
 
92
92
  /**
93
93
  * Migrate partial config to full schema, filling missing keys from defaults.
94
+ * Uses deepMerge so nested strategy objects merge recursively.
94
95
  */
95
96
  export function migrateConfig(partial: Partial<CompactorConfig>): CompactorConfig {
96
97
  const defaults = structuredClone(DEFAULT_COMPACTOR_CONFIG);
97
-
98
- function mergeStrategy<K extends keyof CompactorConfig>(
99
- key: K,
100
- defaultVal: CompactorConfig[K],
101
- partialVal: CompactorConfig[K] | undefined,
102
- ): CompactorConfig[K] {
103
- if (!partialVal || typeof partialVal !== "object") return defaultVal;
104
- return { ...(defaultVal as any), ...(partialVal as any) };
105
- }
106
-
107
- return {
108
- sessionGoals: mergeStrategy("sessionGoals", defaults.sessionGoals, partial.sessionGoals),
109
- filesAndChanges: mergeStrategy("filesAndChanges", defaults.filesAndChanges, partial.filesAndChanges),
110
- commits: mergeStrategy("commits", defaults.commits, partial.commits),
111
- outstandingContext: mergeStrategy("outstandingContext", defaults.outstandingContext, partial.outstandingContext),
112
- userPreferences: mergeStrategy("userPreferences", defaults.userPreferences, partial.userPreferences),
113
- briefTranscript: mergeStrategy("briefTranscript", defaults.briefTranscript, partial.briefTranscript),
114
- sessionContinuity: mergeStrategy("sessionContinuity", defaults.sessionContinuity, partial.sessionContinuity),
115
- fts5Index: mergeStrategy("fts5Index", defaults.fts5Index, partial.fts5Index),
116
- sandboxExecution: mergeStrategy("sandboxExecution", defaults.sandboxExecution, partial.sandboxExecution),
117
- toolDisplay: mergeStrategy("toolDisplay", defaults.toolDisplay, partial.toolDisplay),
118
- pipeline: mergeStrategy("pipeline", defaults.pipeline, (partial as any).pipeline) as any,
119
- autoCompaction: mergeStrategy("autoCompaction", defaults.autoCompaction, partial.autoCompaction),
120
- overrideDefaultCompaction: partial.overrideDefaultCompaction ?? defaults.overrideDefaultCompaction,
121
- debug: partial.debug ?? defaults.debug,
122
- showTruncationHints: partial.showTruncationHints ?? defaults.showTruncationHints,
123
- };
98
+ return deepMerge(defaults, partial);
124
99
  }
125
100
 
126
101
  /**
@@ -34,8 +34,6 @@ const preset = (
34
34
  // Legacy compatibility data only; presets never advertise or alter it.
35
35
  fts5Index: { ...DEFAULT_COMPACTOR_CONFIG.fts5Index },
36
36
  sandboxExecution: { ...DEFAULT_COMPACTOR_CONFIG.sandboxExecution, ...(overrides.sandboxExecution ?? {}) },
37
- // Legacy compatibility data only; presets never advertise or alter it.
38
- toolDisplay: { ...DEFAULT_COMPACTOR_CONFIG.toolDisplay },
39
37
  pipeline: pipeline(overrides.pipeline),
40
38
  autoCompaction: { ...DEFAULT_COMPACTOR_CONFIG.autoCompaction, ...(overrides.autoCompaction ?? {}) },
41
39
  });
@@ -39,23 +39,8 @@ export const DEFAULT_COMPACTOR_CONFIG: CompactorConfig = {
39
39
  allowedLanguages: ["javascript", "typescript", "python", "shell"],
40
40
  outputLimit: 100 * 1024 * 1024,
41
41
  },
42
- toolDisplay: {
43
- ...strategy(true, "opencode"),
44
- mode: "opencode",
45
- diffLayout: "auto",
46
- diffIndicator: "bars",
47
- showThinkingLabels: true,
48
- showUserMessageBox: true,
49
- showBashSpinner: true,
50
- showPendingPreviews: true,
51
- },
52
42
  pipeline: {
53
- ttlCache: false,
54
43
  autoInjection: false,
55
- proximityReranking: false,
56
- timelineSort: false,
57
- progressiveThrottling: false,
58
- mmapPragma: false,
59
44
  customNoisePatterns: [],
60
45
  },
61
46
  autoCompaction: {
@@ -66,6 +51,7 @@ export const DEFAULT_COMPACTOR_CONFIG: CompactorConfig = {
66
51
  notify: true,
67
52
  },
68
53
  overrideDefaultCompaction: true,
54
+ smartKeepTail: true,
55
+ continueAfterThresholdCompact: true,
69
56
  debug: false,
70
- showTruncationHints: true,
71
57
  };
@@ -2,7 +2,7 @@
2
2
  * PolyglotExecutor — sandboxed code execution for 11 languages
3
3
  */
4
4
 
5
- import { spawn, execSync, execFileSync } from "node:child_process";
5
+ import { spawn, execSync } from "node:child_process";
6
6
  import { mkdtempSync, writeFileSync, rmSync, existsSync } from "node:fs";
7
7
  import { join, resolve } from "node:path";
8
8
  import { tmpdir } from "node:os";
@@ -11,19 +11,7 @@ import type { ExecResult } from "../types.js";
11
11
 
12
12
  const isWin = process.platform === "win32";
13
13
 
14
- const OS_TMPDIR = (() => {
15
- if (isWin) return process.env.TEMP ?? process.env.TMP ?? tmpdir();
16
- try {
17
- const result = execFileSync(
18
- process.platform === "darwin" ? "getconf" : "mktemp",
19
- process.platform === "darwin" ? ["DARWIN_USER_TEMP_DIR"] : ["-u", "-d"],
20
- { env: { ...process.env, TMPDIR: undefined as unknown as string }, encoding: "utf-8" },
21
- ).trim();
22
- const dir = process.platform === "darwin" ? result : resolve(result, "..");
23
- if (dir && dir !== process.cwd()) return dir;
24
- } catch { /* fall through */ }
25
- return "/tmp";
26
- })();
14
+ const OS_TMPDIR = isWin ? (process.env.TEMP ?? process.env.TMP ?? tmpdir()) : tmpdir();
27
15
 
28
16
  function killTree(proc: ReturnType<typeof spawn>): void {
29
17
  if (isWin && proc.pid) {
@@ -73,8 +61,11 @@ interface ExecuteOptions {
73
61
  background?: boolean;
74
62
  }
75
63
 
76
- interface ExecuteFileOptions extends ExecuteOptions {
64
+ interface ExecuteFileOptions {
65
+ language: Language;
77
66
  path: string;
67
+ timeout?: number;
68
+ background?: boolean;
78
69
  }
79
70
 
80
71
  export class PolyglotExecutor {
@@ -2,18 +2,8 @@
2
2
  * Runtime detection for sandbox executor
3
3
  */
4
4
 
5
- export type Language =
6
- | "javascript"
7
- | "typescript"
8
- | "python"
9
- | "shell"
10
- | "ruby"
11
- | "go"
12
- | "rust"
13
- | "php"
14
- | "perl"
15
- | "r"
16
- | "elixir";
5
+ import type { Language } from "../types.js";
6
+ export type { Language };
17
7
 
18
8
  export interface RuntimeMap {
19
9
  javascript: string;
@@ -3,7 +3,7 @@
3
3
  */
4
4
 
5
5
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
6
- import { MODULES, UNIPI_EVENTS, COMPACTOR_COMMANDS, COMPACTOR_TOOLS, COMPACTOR_INSTRUCTION, emitEvent } from "@pi-unipi/core";
6
+ import { MODULES, UNIPI_EVENTS, COMPACTOR_COMMANDS, COMPACTOR_TOOLS, COMPACTOR_INSTRUCTION, emitEvent, formatTokens } from "@pi-unipi/core";
7
7
  import { scaffoldConfig, loadConfig } from "./config/manager.js";
8
8
  import { registerCompactionHooks } from "./compaction/hooks.js";
9
9
  import {
@@ -22,45 +22,13 @@ 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, RuntimeStats } from "./types.js";
25
+ import type { NormalizedBlock, CompactorStrategyConfig, RuntimeCounters } from "./types.js";
26
26
 
27
- /** Debug logger — only logs when config.debug === true */
28
- function createDebugLogger(getConfig: () => { debug: boolean }) {
29
- return (_event: string, _data?: Record<string, unknown>) => {
30
- // Debug logging disabled — was writing to stdout causing TUI rendering issues.
31
- return;
32
- };
33
- }
34
27
 
35
- const formatTokenCount = (n: number): string => {
36
- if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;
37
- if (n >= 1_000) return `${(n / 1_000).toFixed(1)}k`;
38
- return String(n);
39
- };
40
28
 
41
29
  /** Measure byte size of a tool_result event's response content. */
42
- function measureResponseBytes(event: any): number {
43
- try {
44
- const content = event.content;
45
- if (typeof content === "string") return Buffer.byteLength(content, "utf-8");
46
- if (Array.isArray(content)) {
47
- return content.reduce((sum: number, block: any) => {
48
- if (typeof block?.text === "string") return sum + Buffer.byteLength(block.text, "utf-8");
49
- if (typeof block === "string") return sum + Buffer.byteLength(block, "utf-8");
50
- return sum;
51
- }, 0);
52
- }
53
- if (event.output && typeof event.output === "string") return Buffer.byteLength(event.output, "utf-8");
54
- } catch {
55
- // Non-blocking: byte measurement errors silently skipped
56
- }
57
- return 0;
58
- }
59
30
 
60
31
  /** Check if a tool is a sandbox tool (output stays in sandbox, not context). */
61
- function isSandboxTool(name: string): boolean {
62
- return name === "bash" || name === "Bash";
63
- }
64
32
 
65
33
  export default function compactorExtension(pi: ExtensionAPI): void {
66
34
  let sessionDB: SessionDB | null = null;
@@ -78,17 +46,6 @@ export default function compactorExtension(pi: ExtensionAPI): void {
78
46
  };
79
47
  const getCounters = () => counters;
80
48
 
81
- const runtimeStats: RuntimeStats = {
82
- bytesReturned: {},
83
- bytesSandboxed: 0,
84
- calls: {},
85
- sessionStart: Date.now(),
86
- cacheHits: 0,
87
- cacheBytesSaved: 0,
88
- };
89
-
90
- const debug = createDebugLogger(() => config);
91
-
92
49
  const init = async () => {
93
50
  scaffoldConfig();
94
51
  config = loadConfig();
@@ -134,8 +91,6 @@ export default function compactorExtension(pi: ExtensionAPI): void {
134
91
  const fullSessionId = `${sessionId}${suffix}`;
135
92
  currentSessionId = fullSessionId;
136
93
 
137
- debug("session_start", { sessionId: fullSessionId, projectDir });
138
-
139
94
  // Seed runtime counters from DB so they reflect prior usage
140
95
  if (sessionDB) {
141
96
  try {
@@ -148,12 +103,6 @@ export default function compactorExtension(pi: ExtensionAPI): void {
148
103
  }
149
104
 
150
105
  // Reset runtime stats for new session
151
- runtimeStats.bytesReturned = {};
152
- runtimeStats.bytesSandboxed = 0;
153
- runtimeStats.calls = {};
154
- runtimeStats.sessionStart = Date.now();
155
- runtimeStats.cacheHits = 0;
156
- runtimeStats.cacheBytesSaved = 0;
157
106
  autoCompactionState = createAutoCompactionState();
158
107
 
159
108
  sessionDB?.ensureSession(fullSessionId, projectDir);
@@ -226,8 +175,6 @@ export default function compactorExtension(pi: ExtensionAPI): void {
226
175
  tools: Object.values(COMPACTOR_TOOLS),
227
176
  });
228
177
 
229
- debug("MODULE_READY", { commands: Object.values(COMPACTOR_COMMANDS), tools: Object.values(COMPACTOR_TOOLS) });
230
-
231
178
  ctx.ui.notify("🗜️ Compactor ready", "info");
232
179
  });
233
180
 
@@ -235,7 +182,6 @@ export default function compactorExtension(pi: ExtensionAPI): void {
235
182
  const cwd = (ctx as any).cwd ?? process.cwd();
236
183
  config = loadConfig(cwd);
237
184
  currentSessionId = `${(ctx as any).sessionId ?? "default"}${getWorktreeSuffix()}`;
238
- debug("before_agent_start", { sessionId: currentSessionId, configDebug: config.debug });
239
185
 
240
186
  // Evaluate autoDetect conditions for strategies
241
187
  try {
@@ -248,7 +194,6 @@ export default function compactorExtension(pi: ExtensionAPI): void {
248
194
  if ((strat as any).autoDetect === "git") {
249
195
  const gitDir = join(cwd, ".git");
250
196
  if (!existsSync(gitDir)) {
251
- debug("autoDetect_disable", { strategy: key, reason: "no .git dir" });
252
197
  // Non-destructive: temporarily disable at runtime, don't modify config file
253
198
  strat.enabled = false;
254
199
  }
@@ -280,7 +225,6 @@ export default function compactorExtension(pi: ExtensionAPI): void {
280
225
  const continuityEnabled = isSessionContinuityEnabled(config);
281
226
  if (sessionDB && continuityEnabled) {
282
227
  const resumeContext = await buildResumeContextMessage(sessionDB, currentSessionId);
283
- debug("resume_snapshot", { injected: !!resumeContext });
284
228
  return resumeContext;
285
229
  }
286
230
  });
@@ -297,23 +241,12 @@ export default function compactorExtension(pi: ExtensionAPI): void {
297
241
  });
298
242
  autoCompactionState = decision.state;
299
243
 
300
- debug("auto_compaction_decision", {
301
- enabled: config.autoCompaction.enabled,
302
- reason: decision.reason,
303
- shouldTrigger: decision.shouldTrigger,
304
- percent: decision.usage?.percent,
305
- tokens: decision.usage?.tokens,
306
- thresholdPercent: decision.thresholdPercent,
307
- cooldownRemainingMs: decision.cooldownRemainingMs,
308
- tokenGrowth: decision.tokenGrowth,
309
- });
310
-
311
244
  if (!decision.shouldTrigger) return;
312
245
 
313
246
  const notify = config.autoCompaction.notify;
314
247
  if (notify && decision.usage) {
315
248
  ctx.ui.notify(
316
- `Auto-compacting at ${decision.usage.percent.toFixed(1)}% context (~${formatTokenCount(decision.usage.tokens)} tokens; threshold ${decision.thresholdPercent}%).`,
249
+ `Auto-compacting at ${decision.usage.percent.toFixed(1)}% context (~${formatTokens(decision.usage.tokens)} tokens; threshold ${decision.thresholdPercent}%).`,
317
250
  "info",
318
251
  );
319
252
  }
@@ -351,7 +284,6 @@ export default function compactorExtension(pi: ExtensionAPI): void {
351
284
  const sessionId = currentSessionId;
352
285
  const events = sessionDB.getEvents(sessionId, { limit: 1000 });
353
286
  const stats = sessionDB.getSessionStats(sessionId);
354
- debug("session_before_compact", { sessionId, eventCount: events.length, compactCount: stats?.compact_count ?? 0 });
355
287
  const { buildResumeSnapshot } = await import("./session/snapshot.js");
356
288
  const snapshot = buildResumeSnapshot(events, {
357
289
  compactCount: stats?.compact_count ?? 1,
@@ -419,12 +351,10 @@ export default function compactorExtension(pi: ExtensionAPI): void {
419
351
  tokensSaved,
420
352
  compressionRatio: kept > 0 ? `${Math.round(totalEstimated / kept)}:1` : "0:1",
421
353
  });
422
- debug("session_compact", { sessionId, tokensBefore, hasCompactionEntry: !!compactionEntry });
423
354
  }
424
355
  });
425
356
 
426
357
  pi.on("session_shutdown", async (_event, _ctx) => {
427
- debug("session_shutdown");
428
358
  if (sessionDB) {
429
359
  sessionDB.cleanupOldSessions(7);
430
360
  }
@@ -435,7 +365,6 @@ export default function compactorExtension(pi: ExtensionAPI): void {
435
365
  pi.on("input", async (event, _ctx) => {
436
366
  const toolName = (event as any).toolName ?? "";
437
367
  const args = (event as any).args ?? {};
438
- debug("input", { toolName, args: JSON.stringify(args).slice(0, 200) });
439
368
 
440
369
  // Existing network tool guard
441
370
  if (toolName === "bash" || toolName === "Bash") {
@@ -447,7 +376,7 @@ export default function compactorExtension(pi: ExtensionAPI): void {
447
376
 
448
377
  // Security scanner/evaluator wiring (fail-open pattern)
449
378
  try {
450
- const { evaluateCommand, evaluateFilePath, loadProjectPermissions } = await import("./security/evaluator.js");
379
+ const { evaluateCommand, evaluateFilePath } = await import("./security/evaluator.js");
451
380
  const { hasShellEscapes, scanForShellEscapes } = await import("./security/scanner.js");
452
381
  const { readsOrCreatesPolicy } = await import("./security/policy.js");
453
382
 
@@ -461,7 +390,6 @@ export default function compactorExtension(pi: ExtensionAPI): void {
461
390
  if (cmd) {
462
391
  const decision = evaluateCommand(cmd, denyPolicy);
463
392
  if (decision === "deny") {
464
- debug("security_deny", { toolName, cmd: cmd.slice(0, 100) });
465
393
  return {
466
394
  content: [{ type: "text", text: `Command blocked by security policy: ${cmd.slice(0, 80)}` }],
467
395
  isError: true,
@@ -478,7 +406,6 @@ export default function compactorExtension(pi: ExtensionAPI): void {
478
406
  if (language && language !== "shell" && code) {
479
407
  if (hasShellEscapes(code, language)) {
480
408
  const findings = scanForShellEscapes(code, language);
481
- debug("security_shell_escapes", { toolName, language, findings });
482
409
  // Fail-open: log but don't block (the hooks system is enforcement)
483
410
  }
484
411
  }
@@ -491,14 +418,12 @@ export default function compactorExtension(pi: ExtensionAPI): void {
491
418
  if (filePath) {
492
419
  const decision = evaluateFilePath(filePath, denyPolicy, cwd);
493
420
  if (decision === "deny") {
494
- debug("security_deny_file", { toolName, filePath });
495
421
  // Non-fatal: log warning but allow through (fail-open)
496
422
  }
497
423
  }
498
424
  }
499
425
  } catch (err) {
500
426
  // Fail-open: security checks are advisory, never block on errors
501
- debug("security_check_error", { error: String(err) });
502
427
  }
503
428
 
504
429
  return undefined;
@@ -511,8 +436,6 @@ export default function compactorExtension(pi: ExtensionAPI): void {
511
436
  const toolNameRaw = (event as any).toolName ?? "";
512
437
  const isError = (event as any).isError ?? false;
513
438
 
514
- debug("tool_result", { toolName: toolNameRaw, isError, sessionId });
515
-
516
439
  // Extract and store session events
517
440
  const toolEvents = extractEventsFromToolResult({
518
441
  toolName: (event as any).toolName ?? "",
@@ -523,22 +446,6 @@ export default function compactorExtension(pi: ExtensionAPI): void {
523
446
 
524
447
  for (const ev of toolEvents) {
525
448
  sessionDB.insertEvent(sessionId, ev, "PostToolUse");
526
- debug("event_stored", { category: ev.category, type: ev.type });
527
- }
528
-
529
- // Track byte consumption per tool for analytics
530
- try {
531
- const responseBytes = measureResponseBytes(event);
532
- if (responseBytes > 0) {
533
- const tName = (event as any).toolName ?? "unknown";
534
- runtimeStats.calls[tName] = (runtimeStats.calls[tName] || 0) + 1;
535
- runtimeStats.bytesReturned[tName] = (runtimeStats.bytesReturned[tName] || 0) + responseBytes;
536
- if (isSandboxTool(tName)) {
537
- runtimeStats.bytesSandboxed += responseBytes;
538
- }
539
- }
540
- } catch {
541
- // Non-blocking: byte tracking errors silently skipped
542
449
  }
543
450
 
544
451
  const toolName = (event as any).toolName ?? "";
@@ -558,36 +465,12 @@ export default function compactorExtension(pi: ExtensionAPI): void {
558
465
  );
559
466
  const clamped = clampDiffToWidth(details.diff);
560
467
  if (clamped !== details.diff) {
561
- debug("diff_width_clamped", { toolName });
562
468
  return { details: { ...details, diff: clamped } } as any;
563
469
  }
564
470
  }
565
471
  } catch (err) {
566
- debug("diff_width_clamp_error", { error: String(err) });
567
472
  }
568
473
  }
569
474
  });
570
475
 
571
- pi.on("message_update", async (event, _ctx) => {
572
- const msg = (event as any).message;
573
- if (msg?.thinking) {
574
- debug("message_update", { thinking: true, length: String(msg.thinking).length });
575
- }
576
- });
577
-
578
- pi.on("message_end", async (_event, _ctx) => {
579
- debug("message_end");
580
- });
581
-
582
- pi.on("context", async (event, _ctx) => {
583
- const { sanitizeThinkingArtifacts } = await import("./display/thinking-label.js");
584
- const ctxStr = (event as any).context;
585
- if (typeof ctxStr === "string") {
586
- const sanitized = sanitizeThinkingArtifacts(ctxStr);
587
- if (sanitized !== ctxStr) {
588
- debug("context", { sanitized: true, beforeLen: ctxStr.length, afterLen: sanitized.length });
589
- }
590
- (event as any).context = sanitized;
591
- }
592
- });
593
476
  }
@@ -1,3 +1,4 @@
1
+ import { formatTokens } from "@pi-unipi/core";
1
2
  /**
2
3
  * Info-screen integration for @pi-unipi/compactor
3
4
  *
@@ -11,7 +12,7 @@
11
12
  */
12
13
 
13
14
  import type { SessionDB } from "./session/db.js";
14
- import { getLastCompactionStats } from "./compaction/hooks.js";
15
+ import { getLastCompactionStats, formatCompactionStats } from "./compaction/hooks.js";
15
16
  import { parseUsageStatsAsync } from "@pi-unipi/info-screen/usage-parser.js";
16
17
  import type { RuntimeCounters } from "./types.js";
17
18
 
@@ -25,14 +26,6 @@ export interface CompactorInfoData {
25
26
  }
26
27
 
27
28
  /** Format token count for display (e.g., "12.4k", "1.2M"). */
28
- function formatTokens(n: number): string {
29
- if (n < 1000) return String(n);
30
- if (n < 10_000) return `${(n / 1000).toFixed(1)}k`;
31
- if (n < 1_000_000) return `${Math.round(n / 1000)}k`;
32
- if (n < 10_000_000) return `${(n / 1_000_000).toFixed(1)}M`;
33
- return `${Math.round(n / 1_000_000)}M`;
34
- }
35
-
36
29
  /** Format cost for display (e.g., "$0.34", "<$0.01"). */
37
30
  function formatCost(n: number): string {
38
31
  if (n === 0) return "$0.00";
@@ -184,7 +177,7 @@ export async function getInfoScreenData(
184
177
  compactions: {
185
178
  value: String(compactionCount),
186
179
  detail: compactStats
187
- ? `Last: ${compactStats.totalMessages} messages (~${formatTokens(compactStats.tokensBefore)} tokens) → ${compactStats.kept} messages (~${formatTokens(compactStats.tokensAfterEst)} tokens)`
180
+ ? `Last: ${formatCompactionStats(compactStats)}`
188
181
  : compactionCount > 0
189
182
  ? `${compactionCount} compaction(s) across all sessions`
190
183
  : "No compactions yet",
@@ -14,28 +14,6 @@ import { parseBashPattern, parseToolPattern, globToRegex, fileGlobToRegex } from
14
14
  * Load permission patterns from .pi/settings.json in the given directory.
15
15
  * Returns a SecurityPolicy merged with the provided policy.
16
16
  */
17
- export function loadProjectPermissions(
18
- cwd: string,
19
- basePolicy: SecurityPolicy,
20
- ): SecurityPolicy {
21
- const settingsPath = join(cwd, ".pi", "settings.json");
22
- if (!existsSync(settingsPath)) return basePolicy;
23
-
24
- try {
25
- const raw = readFileSync(settingsPath, "utf-8");
26
- const settings = JSON.parse(raw);
27
- const permissions = settings.permissions ?? settings.security ?? {};
28
-
29
- return {
30
- deny: [...basePolicy.deny, ...(permissions.deny ?? [])],
31
- ask: [...basePolicy.ask, ...(permissions.ask ?? [])],
32
- allow: [...basePolicy.allow, ...(permissions.allow ?? [])],
33
- };
34
- } catch {
35
- return basePolicy;
36
- }
37
- }
38
-
39
17
  export function evaluateCommand(
40
18
  command: string,
41
19
  policy: SecurityPolicy,
@@ -73,37 +51,6 @@ export function evaluateCommand(
73
51
  return "allow";
74
52
  }
75
53
 
76
- export function splitChainedCommands(command: string): string[] {
77
- const commands: string[] = [];
78
- let current = "";
79
- let inQuotes = false;
80
- let quoteChar = "";
81
-
82
- for (let i = 0; i < command.length; i++) {
83
- const char = command[i];
84
-
85
- if (!inQuotes && (char === '"' || char === "'" || char === "`")) {
86
- inQuotes = true;
87
- quoteChar = char;
88
- current += char;
89
- } else if (inQuotes && char === quoteChar) {
90
- inQuotes = false;
91
- quoteChar = "";
92
- current += char;
93
- } else if (!inQuotes && (char === "&" || char === "|" || char === ";")) {
94
- if (current.trim()) commands.push(current.trim());
95
- current = "";
96
- // Skip the next char if it's part of && or ||
97
- if ((char === "&" || char === "|") && command[i + 1] === char) i++;
98
- } else {
99
- current += char;
100
- }
101
- }
102
-
103
- if (current.trim()) commands.push(current.trim());
104
- return commands;
105
- }
106
-
107
54
  export function evaluateFilePath(
108
55
  filePath: string,
109
56
  policy: SecurityPolicy,
@@ -23,14 +23,13 @@ export function parseToolPattern(pattern: string): { tool: string; glob: string
23
23
  return match ? { tool: match[1], glob: match[2] } : null;
24
24
  }
25
25
 
26
- function escapeRegex(str: string): string {
27
- return str.replace(/[.*+?^${}()|[\]\\/\-]/g, "\\$&");
28
- }
29
-
30
26
  function convertGlobPart(glob: string): string {
27
+ // Replace wildcards FIRST, then escape the literal remainder — escaping first
28
+ // would turn `*` into `\*` and the wildcard replace would never fire.
31
29
  return glob
32
- .replace(/[.+?^${}()|[\]\\/\-]/g, "\\$&")
33
- .replace(/\*/g, ".*");
30
+ .split("*")
31
+ .map((part) => RegExp.escape(part))
32
+ .join(".*");
34
33
  }
35
34
 
36
35
  export function globToRegex(glob: string, caseInsensitive: boolean = false): RegExp {
@@ -39,7 +38,7 @@ export function globToRegex(glob: string, caseInsensitive: boolean = false): Reg
39
38
  if (colonIdx !== -1) {
40
39
  const command = glob.slice(0, colonIdx);
41
40
  const argsGlob = glob.slice(colonIdx + 1);
42
- const escapedCmd = escapeRegex(command);
41
+ const escapedCmd = RegExp.escape(command);
43
42
  const argsRegex = convertGlobPart(argsGlob);
44
43
  regexStr = `^${escapedCmd}(\\s${argsRegex})?$`;
45
44
  } else {
@@ -68,7 +67,7 @@ export function fileGlobToRegex(glob: string, caseInsensitive: boolean = false):
68
67
  regexStr += "[^/]";
69
68
  i++;
70
69
  } else {
71
- regexStr += escapeRegex(glob[i]);
70
+ regexStr += RegExp.escape(glob[i]);
72
71
  i++;
73
72
  }
74
73
  }
@@ -191,7 +191,6 @@ export class SessionDB {
191
191
  p("addCompactionStats", `UPDATE session_meta SET total_chars_before = total_chars_before + ?, total_chars_kept = total_chars_kept + ?, total_messages_summarized = total_messages_summarized + ? WHERE session_id = ?`);
192
192
  p("getAllTimeStats", `SELECT COALESCE(SUM(total_chars_before), 0) AS all_chars_before, COALESCE(SUM(total_chars_kept), 0) AS all_chars_kept, COALESCE(SUM(total_messages_summarized), 0) AS all_messages_summarized, COALESCE(SUM(compact_count), 0) AS all_compactions, COALESCE(SUM(sandbox_runs), 0) AS all_sandbox_runs, COALESCE(SUM(search_queries), 0) AS all_search_queries FROM session_meta`);
193
193
  p("incrementSandboxRuns", `UPDATE session_meta SET sandbox_runs = sandbox_runs + 1 WHERE session_id = ?`);
194
- p("incrementSearchQueries", `UPDATE session_meta SET search_queries = search_queries + 1 WHERE session_id = ?`);
195
194
  p("upsertResume", `INSERT INTO session_resume (session_id, snapshot, event_count) VALUES (?, ?, ?) ON CONFLICT(session_id) DO UPDATE SET snapshot = excluded.snapshot, event_count = excluded.event_count, created_at = datetime('now'), consumed = 0`);
196
195
  p("getResume", `SELECT snapshot, event_count, consumed FROM session_resume WHERE session_id = ?`);
197
196
  p("markResumeConsumed", `UPDATE session_resume SET consumed = 1 WHERE session_id = ?`);
@@ -283,11 +282,6 @@ export class SessionDB {
283
282
  this.stmt("incrementSandboxRuns").run(sessionId);
284
283
  }
285
284
 
286
- incrementSearchQueries(sessionId: string): void {
287
- if (!this.stmts) return;
288
- this.stmt("incrementSearchQueries").run(sessionId);
289
- }
290
-
291
285
  upsertResume(sessionId: string, snapshot: string, eventCount?: number): void {
292
286
  if (!this.stmts) return;
293
287
  this.stmt("upsertResume").run(sessionId, snapshot, eventCount ?? 0);
@@ -4,7 +4,6 @@
4
4
 
5
5
  import { PolyglotExecutor } from "../executor/executor.js";
6
6
  import type { Language, ExecResult } from "../types.js";
7
- import { readFileSync } from "node:fs";
8
7
 
9
8
  export interface CtxExecuteFileInput {
10
9
  language: Language;
@@ -16,13 +15,9 @@ export async function ctxExecuteFile(
16
15
  input: CtxExecuteFileInput,
17
16
  executor = new PolyglotExecutor(),
18
17
  ): Promise<ExecResult> {
19
- const content = readFileSync(input.path, "utf-8");
20
- const code = `const FILE_CONTENT = ${JSON.stringify(content)};\n// User script follows:\n`;
21
-
22
18
  return executor.executeFile({
23
19
  language: input.language,
24
20
  path: input.path,
25
- code,
26
21
  timeout: input.timeout ?? 30000,
27
22
  });
28
23
  }