@diegopetrucci/pi-extensions 0.1.78 → 0.1.80

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 (46) hide show
  1. package/README.md +1 -1
  2. package/extensions/agent-workflow-audit/index.ts +3 -20
  3. package/extensions/agent-workflow-audit/package.json +1 -1
  4. package/extensions/annotate-git-diff/glimpseui.d.ts +5 -65
  5. package/extensions/annotate-git-diff/package.json +1 -1
  6. package/extensions/annotate-last-message/glimpseui.d.ts +5 -65
  7. package/extensions/annotate-last-message/package.json +1 -1
  8. package/extensions/annotate-last-message/prompt.ts +2 -8
  9. package/extensions/annotate-last-message/web/index.html +0 -27
  10. package/extensions/code-reviewer/index.ts +2 -46
  11. package/extensions/code-reviewer/package.json +1 -1
  12. package/extensions/context-cap/README.md +9 -9
  13. package/extensions/context-cap/index.ts +3 -3
  14. package/extensions/context-cap/package.json +2 -2
  15. package/extensions/context-inspector/index.ts +2 -46
  16. package/extensions/context-inspector/package.json +1 -1
  17. package/extensions/contrarian/index.ts +5 -55
  18. package/extensions/contrarian/package.json +1 -1
  19. package/extensions/dynamic-context-pruning/README.md +24 -7
  20. package/extensions/dynamic-context-pruning/index.ts +21 -130
  21. package/extensions/dynamic-context-pruning/package.json +1 -1
  22. package/extensions/fast/README.md +16 -9
  23. package/extensions/fast/index.ts +9 -5
  24. package/extensions/fast/package.json +1 -1
  25. package/extensions/git-footer/index.ts +2 -16
  26. package/extensions/git-footer/package.json +1 -1
  27. package/extensions/librarian/index.ts +8 -46
  28. package/extensions/librarian/package.json +1 -1
  29. package/extensions/minimal-footer/README.md +5 -5
  30. package/extensions/minimal-footer/index.ts +3 -17
  31. package/extensions/minimal-footer/minimal-footer.example.json +1 -1
  32. package/extensions/minimal-footer/openai-usage.ts +2 -11
  33. package/extensions/minimal-footer/package.json +1 -1
  34. package/extensions/oracle/index.ts +5 -55
  35. package/extensions/oracle/package.json +1 -1
  36. package/extensions/permission-gate/index.ts +31 -63
  37. package/extensions/permission-gate/package.json +1 -1
  38. package/extensions/project-mcp-json/config.ts +2 -1
  39. package/extensions/project-mcp-json/package.json +1 -1
  40. package/extensions/quiet-tools/index.ts +15 -6
  41. package/extensions/quiet-tools/package.json +1 -1
  42. package/extensions/review/README.md +2 -0
  43. package/extensions/review/package.json +1 -1
  44. package/extensions/triage-comments/index.ts +1 -22
  45. package/extensions/triage-comments/package.json +1 -1
  46. package/package.json +4 -4
package/README.md CHANGED
@@ -31,7 +31,7 @@ A collection of [pi](https://pi.dev) agent extensions I made. (Btw, [I am also b
31
31
 
32
32
  ### Context management
33
33
 
34
- - [`context-cap`](./extensions/context-cap): Caps effective model context windows at 300k tokens by default so pi avoids the `dumb zone`; toggle temporarily with `/context-cap`.
34
+ - [`context-cap`](./extensions/context-cap): Caps effective model context windows at 350k tokens by default so pi avoids the `dumb zone`; toggle temporarily with `/context-cap`.
35
35
  - [`context-inspector`](./extensions/context-inspector): Adds `/context`, a local self-contained HTML dashboard that breaks down where the current session context is going, with category overview, top offenders, and drilldown search.
36
36
  - [`dynamic-context-pruning`](./extensions/dynamic-context-pruning): Automatically prunes duplicate tool results, stale errored inputs, and superseded file-op outputs from long sessions behind a cache-aware net-benefit gate, with a `/prune` picker and `/context-pruning` status/control commands.
37
37
 
@@ -63,10 +63,8 @@ type AuditDetails = {
63
63
  toolCalls: ToolCall[];
64
64
  startedAt: number;
65
65
  endedAt?: number;
66
- error?: string;
67
66
  initialGitStatus?: string;
68
67
  finalGitStatus?: string;
69
- reportLength?: number;
70
68
  toolCallCount?: number;
71
69
  };
72
70
 
@@ -250,14 +248,14 @@ function renderCollapsedReport(report: string, lineLimit = COLLAPSED_REPORT_LINE
250
248
  ].join("\n");
251
249
  }
252
250
 
253
- function summarizeGitStatus(status: string | undefined): { lineCount: number; dirtyCount: number } {
251
+ function summarizeGitStatus(status: string | undefined): { dirtyCount: number } {
254
252
  const lines =
255
253
  status
256
254
  ?.split("\n")
257
255
  .map((line) => line.trim())
258
256
  .filter(Boolean) ?? [];
259
257
  const dirty = lines.filter((line) => !line.startsWith("##"));
260
- return { lineCount: lines.length, dirtyCount: dirty.length };
258
+ return { dirtyCount: dirty.length };
261
259
  }
262
260
 
263
261
  function appendRunBoundary(report: string, details: AuditDetails): string {
@@ -373,12 +371,6 @@ function getReadPathCandidates(resolved: string): string[] {
373
371
  ];
374
372
  }
375
373
 
376
- function resolveToolPath(cwd: string, rawPath: unknown): string {
377
- const input = typeof rawPath === "string" && rawPath.trim() ? rawPath.trim() : ".";
378
- const normalized = input.startsWith("@") ? input.slice(1) : input;
379
- return path.isAbsolute(normalized) ? path.resolve(normalized) : path.resolve(cwd, normalized);
380
- }
381
-
382
374
  function isInside(parent: string, child: string): boolean {
383
375
  const parentResolved = path.resolve(parent);
384
376
  const childResolved = path.resolve(child);
@@ -399,11 +391,6 @@ async function assertToolPathInsideCwd(
399
391
  // where realpath fails and the lexical fallback starts with the alias prefix.
400
392
  const root = await fs.realpath(cwd).catch(() => path.resolve(cwd));
401
393
  const lexicalCwd = path.resolve(cwd);
402
- const resolved = resolveToolPath(cwd, rawPath);
403
- const realPath = await fs.realpath(resolved).catch(() => resolved);
404
- if (!isInside(root, realPath) && !isInside(lexicalCwd, realPath))
405
- return `${toolName} is limited to the audited checkout: ${realPath}`;
406
- // Pi-effective path check (monotonic: block if either resolution is outside).
407
394
  // For the read tool, also check Pi's resolveReadPathAsync fallback variants so
408
395
  // a missing file whose variant resolves outside is not silently allowed.
409
396
  try {
@@ -1157,7 +1144,7 @@ function hasInlineFlag(tokens: string[], flags: string[]): boolean {
1157
1144
  function getBlockedInterpreterReason(commandName: string, tokens: string[]): string | undefined {
1158
1145
  const lowerTokens = tokens.map((token) => token.toLowerCase());
1159
1146
  if (
1160
- ["node", "deno", "bun"].includes(commandName) &&
1147
+ ["node", "bun"].includes(commandName) &&
1161
1148
  hasInlineFlag(lowerTokens, [
1162
1149
  "-e",
1163
1150
  "--eval",
@@ -1718,17 +1705,14 @@ async function runAudit(
1718
1705
  throw new Error(outcome.reason);
1719
1706
  }
1720
1707
  details.endedAt = Date.now();
1721
- details.reportLength = lastContent.length;
1722
1708
  emit();
1723
1709
  return { report: lastContent, details };
1724
1710
  } catch (error) {
1725
1711
  const failure = classifyRunFailure(error, abortController.callerAborted);
1726
1712
  const { message } = failure;
1727
1713
  details.status = failure.status;
1728
- details.error = failure.error;
1729
1714
  details.endedAt = Date.now();
1730
1715
  lastContent = message;
1731
- details.reportLength = lastContent.length;
1732
1716
  emit();
1733
1717
  return { report: `## Agent Workflow Audit failed\n\n${message}`, details };
1734
1718
  } finally {
@@ -1866,7 +1850,6 @@ export default function agentWorkflowAuditExtension(pi: ExtensionAPI) {
1866
1850
  if (!details) return;
1867
1851
  details.finalGitStatus = await gitStatusShort(pi, ctx.cwd, ctx.signal).catch(() => undefined);
1868
1852
  report = appendRunBoundary(report, details);
1869
- details.reportLength = report.length;
1870
1853
  const parentDetails: AuditDetails = {
1871
1854
  ...details,
1872
1855
  toolCallCount: details.toolCalls.length,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-agent-workflow-audit",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
4
4
  "description": "A pi extension that runs an isolated repo workflow audit and returns only the final report to the main session.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -1,6 +1,4 @@
1
1
  declare module "glimpseui" {
2
- import { EventEmitter } from "node:events";
3
-
4
2
  export type FollowMode = "snap" | "spring";
5
3
  export type CursorAnchor =
6
4
  | "top-left"
@@ -32,67 +30,9 @@ declare module "glimpseui" {
32
30
  timeout?: number;
33
31
  }
34
32
 
35
- export interface GlimpseScreenInfo {
36
- width: number;
37
- height: number;
38
- scaleFactor: number;
39
- visibleX?: number;
40
- visibleY?: number;
41
- visibleWidth?: number;
42
- visibleHeight?: number;
43
- x?: number;
44
- y?: number;
45
- }
46
-
47
- export interface GlimpseAppearanceInfo {
48
- darkMode: boolean;
49
- accentColor: string;
50
- reduceMotion: boolean;
51
- increaseContrast: boolean;
52
- }
53
-
54
- export interface GlimpseCursorInfo {
55
- x: number;
56
- y: number;
57
- }
58
-
59
- export interface GlimpseCursorTip {
60
- x: number;
61
- y: number;
62
- }
63
-
64
- export interface GlimpseInfo {
65
- screen: GlimpseScreenInfo;
66
- screens: GlimpseScreenInfo[];
67
- appearance: GlimpseAppearanceInfo;
68
- cursor: GlimpseCursorInfo;
69
- cursorTip: GlimpseCursorTip | null;
70
- }
71
-
72
- export class GlimpseWindow extends EventEmitter {
73
- on(event: "ready", listener: (info: GlimpseInfo) => void): this;
74
- on(event: "message", listener: (data: unknown) => void): this;
75
- on(event: "info", listener: (info: GlimpseInfo) => void): this;
76
- on(event: "closed", listener: () => void): this;
77
- on(event: "error", listener: (error: Error) => void): this;
78
- once(event: "ready", listener: (info: GlimpseInfo) => void): this;
79
- once(event: "message", listener: (data: unknown) => void): this;
80
- once(event: "info", listener: (info: GlimpseInfo) => void): this;
81
- once(event: "closed", listener: () => void): this;
82
- once(event: "error", listener: (error: Error) => void): this;
83
- send(js: string): void;
84
- setHTML(html: string): void;
85
- show(options?: { title?: string }): void;
86
- close(): void;
87
- loadFile(path: string): void;
88
- get info(): GlimpseInfo | null;
89
- getInfo(): void;
90
- followCursor(enabled: boolean, anchor?: CursorAnchor, mode?: FollowMode): void;
91
- }
92
-
93
- export function open(html: string, options?: GlimpseOpenOptions): GlimpseWindow;
94
- export function prompt<T = unknown>(
95
- html: string,
96
- options?: GlimpseOpenOptions,
97
- ): Promise<T | null>;
33
+ export function getNativeHostInfo(): {
34
+ path: string;
35
+ extraArgs?: string[];
36
+ buildHint?: string;
37
+ };
98
38
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-annotate-git-diff",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
4
4
  "description": "A standalone pi extension that adds /annotate-git-diff, a native Glimpse UI for annotating git diffs and sending explicit feedback directly to the agent; close recovery keeps unsent drafts in the editor.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -1,6 +1,4 @@
1
1
  declare module "glimpseui" {
2
- import { EventEmitter } from "node:events";
3
-
4
2
  export type FollowMode = "snap" | "spring";
5
3
  export type CursorAnchor =
6
4
  | "top-left"
@@ -32,67 +30,9 @@ declare module "glimpseui" {
32
30
  timeout?: number;
33
31
  }
34
32
 
35
- export interface GlimpseScreenInfo {
36
- width: number;
37
- height: number;
38
- scaleFactor: number;
39
- visibleX?: number;
40
- visibleY?: number;
41
- visibleWidth?: number;
42
- visibleHeight?: number;
43
- x?: number;
44
- y?: number;
45
- }
46
-
47
- export interface GlimpseAppearanceInfo {
48
- darkMode: boolean;
49
- accentColor: string;
50
- reduceMotion: boolean;
51
- increaseContrast: boolean;
52
- }
53
-
54
- export interface GlimpseCursorInfo {
55
- x: number;
56
- y: number;
57
- }
58
-
59
- export interface GlimpseCursorTip {
60
- x: number;
61
- y: number;
62
- }
63
-
64
- export interface GlimpseInfo {
65
- screen: GlimpseScreenInfo;
66
- screens: GlimpseScreenInfo[];
67
- appearance: GlimpseAppearanceInfo;
68
- cursor: GlimpseCursorInfo;
69
- cursorTip: GlimpseCursorTip | null;
70
- }
71
-
72
- export class GlimpseWindow extends EventEmitter {
73
- on(event: "ready", listener: (info: GlimpseInfo) => void): this;
74
- on(event: "message", listener: (data: unknown) => void): this;
75
- on(event: "info", listener: (info: GlimpseInfo) => void): this;
76
- on(event: "closed", listener: () => void): this;
77
- on(event: "error", listener: (error: Error) => void): this;
78
- once(event: "ready", listener: (info: GlimpseInfo) => void): this;
79
- once(event: "message", listener: (data: unknown) => void): this;
80
- once(event: "info", listener: (info: GlimpseInfo) => void): this;
81
- once(event: "closed", listener: () => void): this;
82
- once(event: "error", listener: (error: Error) => void): this;
83
- send(js: string): void;
84
- setHTML(html: string): void;
85
- show(options?: { title?: string }): void;
86
- close(): void;
87
- loadFile(path: string): void;
88
- get info(): GlimpseInfo | null;
89
- getInfo(): void;
90
- followCursor(enabled: boolean, anchor?: CursorAnchor, mode?: FollowMode): void;
91
- }
92
-
93
- export function open(html: string, options?: GlimpseOpenOptions): GlimpseWindow;
94
- export function prompt<T = unknown>(
95
- html: string,
96
- options?: GlimpseOpenOptions,
97
- ): Promise<T | null>;
33
+ export function getNativeHostInfo(): {
34
+ path: string;
35
+ extraArgs?: string[];
36
+ buildHint?: string;
37
+ };
98
38
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-annotate-last-message",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
4
4
  "description": "A standalone pi extension that adds /annotate-last-message, a native Glimpse UI for annotating the latest assistant reply.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -1,6 +1,5 @@
1
1
  import type {
2
2
  AnnotateLastMessageInlineComment,
3
- AnnotateLastMessageSectionComment,
4
3
  AnnotateLastMessageSubmitPayload,
5
4
  LastAssistantMessageData,
6
5
  LastAssistantMessageLine,
@@ -34,10 +33,7 @@ function formatSectionLineRange(section: LastAssistantMessageSection): string {
34
33
  : `lines ${section.startLine}-${section.endLine}`;
35
34
  }
36
35
 
37
- function formatSectionComment(
38
- comment: AnnotateLastMessageSectionComment,
39
- section: LastAssistantMessageSection | undefined,
40
- ): string {
36
+ function formatSectionComment(section: LastAssistantMessageSection | undefined): string {
41
37
  if (section == null) {
42
38
  return "Unknown section";
43
39
  }
@@ -90,9 +86,7 @@ export function composeAnnotateLastMessagePrompt(
90
86
  if (sectionComments.length > 0) {
91
87
  lines.push("## Section comments");
92
88
  sectionComments.forEach((comment, index) => {
93
- lines.push(
94
- `${index + 1}. ${formatSectionComment(comment, sectionMap.get(comment.sectionId))}`,
95
- );
89
+ lines.push(`${index + 1}. ${formatSectionComment(sectionMap.get(comment.sectionId))}`);
96
90
  lines.push(` ${comment.body.trim()}`);
97
91
  lines.push("");
98
92
  });
@@ -314,9 +314,6 @@
314
314
  margin: 0;
315
315
  }
316
316
 
317
- .markdown-content p { margin: 0; }
318
- .markdown-content p + p { margin-top: 4px; }
319
-
320
317
  .markdown-content a {
321
318
  color: var(--mdLink);
322
319
  text-decoration: underline;
@@ -337,21 +334,6 @@
337
334
  font-family: inherit;
338
335
  }
339
336
 
340
- .markdown-content pre {
341
- background: transparent;
342
- margin: 4px 0;
343
- overflow-x: auto;
344
- border-left: 3px solid var(--mdCodeBlockBorder);
345
- padding-left: 8px;
346
- }
347
-
348
- .markdown-content pre code {
349
- display: block;
350
- background: none;
351
- color: var(--mdCodeBlock, var(--text));
352
- padding: 0;
353
- }
354
-
355
337
  .markdown-content blockquote {
356
338
  border-left: 3px solid var(--mdQuoteBorder);
357
339
  padding-left: 8px;
@@ -360,15 +342,6 @@
360
342
  font-style: italic;
361
343
  }
362
344
 
363
- .markdown-content ul,
364
- .markdown-content ol {
365
- margin: 4px 0;
366
- padding-left: 1.5em;
367
- }
368
-
369
- .markdown-content li { margin: 0; }
370
- .markdown-content li::marker { color: var(--mdListBullet); }
371
-
372
345
  .markdown-content hr {
373
346
  border: none;
374
347
  border-top: 1px solid var(--mdHr);
@@ -155,13 +155,11 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
155
155
  "gpt-6-luna ",
156
156
  "gpt-6-luna",
157
157
  "gpt-5.6-luna",
158
- "gpt-5.5",
159
158
  "claude-opus-5.5 ",
160
159
  "claude-opus-5.5",
161
160
  "claude-opus-5",
162
161
  "gpt-5.4",
163
162
  "gpt-5.3-codex",
164
- "gpt-5-mini",
165
163
  "grok-4.7 ",
166
164
  "grok-4.7",
167
165
  "grok-4.6",
@@ -187,7 +185,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
187
185
  "gpt-5.4",
188
186
  "gpt-5.3-codex",
189
187
  ],
190
- "opencode-go": ["grok-4.7 ", "grok-4.7", "grok-4.6"],
188
+ "opencode-go": ["grok-4.7 ", "grok-4.7"],
191
189
  openrouter: [
192
190
  "openai/gpt-6-astra ",
193
191
  "openai/gpt-6-astra",
@@ -258,8 +256,6 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
258
256
  "gpt-6-luna ",
259
257
  "gpt-6-luna",
260
258
  "gpt-5.6-luna",
261
- "gpt-5.5",
262
- "gpt-5.3-codex-spark",
263
259
  ],
264
260
  xai: ["grok-4.7 ", "grok-4.7", "grok-4.6", "grok-4.5"],
265
261
  "vercel-ai-gateway": [
@@ -503,12 +499,8 @@ const CodeReviewerParams = Type.Object({
503
499
  });
504
500
 
505
501
  type ToolCall = {
506
- id: string;
507
502
  name: string;
508
503
  args: unknown;
509
- startedAt: number;
510
- endedAt?: number;
511
- isError?: boolean;
512
504
  };
513
505
 
514
506
  type ReviewStatus = "running" | "done" | "error" | "aborted";
@@ -877,12 +869,6 @@ function addAssistantMessageUsage(total: Usage, message: unknown): void {
877
869
  addUsage(total, assistant.usage as Usage);
878
870
  }
879
871
 
880
- function aggregateAssistantUsage(messages: unknown[]): Usage {
881
- const total = createEmptyUsage();
882
- for (const message of messages) addAssistantMessageUsage(total, message);
883
- return total;
884
- }
885
-
886
872
  function addSessionEventUsage(total: Usage, event: unknown): void {
887
873
  const sessionEvent = event as { type?: string; message?: unknown; result?: { usage?: Usage } };
888
874
  if (sessionEvent?.type === "message_end")
@@ -1033,12 +1019,6 @@ function getReadPathCandidates(resolved: string): string[] {
1033
1019
  ];
1034
1020
  }
1035
1021
 
1036
- function resolveToolPath(cwd: string, rawPath: string | undefined): string {
1037
- const input = rawPath?.trim() || ".";
1038
- const normalized = input.startsWith("@") ? input.slice(1) : input;
1039
- return path.isAbsolute(normalized) ? path.resolve(normalized) : path.resolve(cwd, normalized);
1040
- }
1041
-
1042
1022
  async function assertToolPathInsideCwd(
1043
1023
  cwd: string,
1044
1024
  rawPath: unknown,
@@ -1046,24 +1026,11 @@ async function assertToolPathInsideCwd(
1046
1026
  ): Promise<string | undefined> {
1047
1027
  if (rawPath !== undefined && typeof rawPath !== "string")
1048
1028
  return `${toolName} path must be a string.`;
1049
- if (typeof rawPath === "string") {
1050
- const normalizedInput = rawPath.trim().startsWith("@")
1051
- ? rawPath.trim().slice(1)
1052
- : rawPath.trim();
1053
- if (/(^|[/\\])\.\.(?:[/\\]|$)/.test(normalizedInput)) {
1054
- return `${toolName} path must not traverse outside the local checkout.`;
1055
- }
1056
- }
1057
1029
  // A path is allowed if it resolves inside EITHER the realpath'd root (root) OR
1058
1030
  // the lexical cwd (lexicalCwd). This handles missing files in symlinked workspaces
1059
1031
  // where realpath fails and the lexical fallback starts with the alias prefix.
1060
1032
  const root = await fs.realpath(cwd).catch(() => path.resolve(cwd));
1061
1033
  const lexicalCwd = path.resolve(cwd);
1062
- const resolved = resolveToolPath(cwd, rawPath);
1063
- const realPath = await fs.realpath(resolved).catch(() => resolved);
1064
- if (!isInside(root, realPath) && !isInside(lexicalCwd, realPath))
1065
- return `${toolName} is limited to the local checkout: ${realPath}`;
1066
- // Pi-effective path check (monotonic: block if either resolution is outside).
1067
1034
  // For the read tool, also check Pi's resolveReadPathAsync fallback variants so
1068
1035
  // a missing file whose variant resolves outside is not silently allowed.
1069
1036
  try {
@@ -1780,7 +1747,7 @@ export const __test__ = {
1780
1747
  normalizeThinkingLevel,
1781
1748
  resolveThinkingLevel,
1782
1749
  selectCodeReviewerModel,
1783
- aggregateAssistantUsage,
1750
+ createEmptyUsage,
1784
1751
  addAssistantMessageUsage,
1785
1752
  addSessionEventUsage,
1786
1753
  inspectFinalAssistant,
@@ -1929,25 +1896,14 @@ export default function codeReviewerExtension(pi: ExtensionAPI) {
1929
1896
  break;
1930
1897
  case "tool_execution_start":
1931
1898
  details.toolCalls.push({
1932
- id: event.toolCallId,
1933
1899
  name: event.toolName,
1934
1900
  args: event.args,
1935
- startedAt: Date.now(),
1936
1901
  });
1937
1902
  if (details.toolCalls.length > MAX_TOOL_CALLS_TO_KEEP) {
1938
1903
  details.toolCalls.splice(0, details.toolCalls.length - MAX_TOOL_CALLS_TO_KEEP);
1939
1904
  }
1940
1905
  emit();
1941
1906
  break;
1942
- case "tool_execution_end": {
1943
- const call = details.toolCalls.find((item) => item.id === event.toolCallId);
1944
- if (call) {
1945
- call.endedAt = Date.now();
1946
- call.isError = event.isError;
1947
- }
1948
- emit();
1949
- break;
1950
- }
1951
1907
  case "message_update":
1952
1908
  if (event.assistantMessageEvent?.type === "text_delta") {
1953
1909
  lastContent += event.assistantMessageEvent.delta ?? "";
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-code-reviewer",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "description": "A standalone pi extension that adds a read-only code_reviewer subagent tool for isolated code reviews.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -1,6 +1,6 @@
1
1
  # context-cap
2
2
 
3
- A pi extension that treats large-context models as having an effective 300k-token context window, so pi's built-in auto-compaction starts earlier, and avoids the dumb zone.
3
+ A pi extension that treats large-context models as having an effective 350k-token context window, so pi's built-in auto-compaction starts earlier, and avoids the dumb zone.
4
4
 
5
5
  By default, pi auto-compacts when:
6
6
 
@@ -11,10 +11,10 @@ contextTokens > model.contextWindow - reserveTokens
11
11
  This extension changes the active model's in-memory `contextWindow` to:
12
12
 
13
13
  ```text
14
- min(originalContextWindow, 300000)
14
+ min(originalContextWindow, 350000)
15
15
  ```
16
16
 
17
- With pi's default `reserveTokens` of 16,384, models larger than 300k will proactively compact around 283,616 tokens.
17
+ With pi's default `reserveTokens` of 16,384, models larger than 350k will proactively compact around 333,616 tokens.
18
18
 
19
19
  ## Commands
20
20
 
@@ -65,21 +65,21 @@ Then reload pi:
65
65
 
66
66
  ### Settings guard and alternative
67
67
 
68
- - On Pi hosts exposing `getSettings()`, the extension skips the cap whenever `compaction.modelOverrides` contains the active model's key (`provider/model-id`), whatever fields that entry sets; it does not read `reserveTokens` from the override. Otherwise it uses `compaction.reserveTokens`, then the default `16,384`, and skips the cap when that value is at least `300,000` (which would make the threshold zero or negative). Hosts without `getSettings()` use the older cap behavior without this guard.
69
- - Pi's provider request path also derives an output-token budget from the effective window (`contextWindow - contextTokens - 4096`, with any provider-specific clamp applied afterward). The cap can therefore reduce response or thinking-token budgets near the limit, in addition to causing earlier compaction; Pi's overflow handling may also treat a request above 300k as overflow and retry after compaction. Use `/context-cap off` when you need the full model window temporarily.
70
- - As a session-scoped alternative, configure Pi's own `compaction.modelOverrides` instead of this extension. The exact key is `provider/model-id`; to match this extension's default 300k threshold for a model with original window `W`, let `R` be the configured `reserveTokens` value (default `16,384`) and use:
68
+ - On Pi hosts exposing `getSettings()`, the extension skips the cap whenever `compaction.modelOverrides` contains the active model's key (`provider/model-id`), whatever fields that entry sets; it does not read `reserveTokens` from the override. Otherwise it uses `compaction.reserveTokens`, then the default `16,384`, and skips the cap when that value is at least `350,000` (which would make the threshold zero or negative). Hosts without `getSettings()` use the older cap behavior without this guard.
69
+ - Pi's provider request path also derives an output-token budget from the effective window (`contextWindow - contextTokens - 4096`, with any provider-specific clamp applied afterward). The cap can therefore reduce response or thinking-token budgets near the limit, in addition to causing earlier compaction; Pi's overflow handling may also treat a request above 350k as overflow and retry after compaction. Use `/context-cap off` when you need the full model window temporarily.
70
+ - As a session-scoped alternative, configure Pi's own `compaction.modelOverrides` instead of this extension. The exact key is `provider/model-id`; to match this extension's default 350k threshold for a model with original window `W`, let `R` be the configured `reserveTokens` value (default `16,384`) and use:
71
71
 
72
72
  ```text
73
- reserveTokens = W - 300000 + R
73
+ reserveTokens = W - 350000 + R
74
74
  ```
75
75
 
76
- For example, with the default `R`, a 1,000,000-token model uses `716384`, yielding the same `283616` threshold as `300000 - 16384`:
76
+ For example, with the default `R`, a 1,000,000-token model uses `666384`, yielding the same `333616` threshold as `350000 - 16384`:
77
77
 
78
78
  ```json
79
79
  {
80
80
  "compaction": {
81
81
  "modelOverrides": {
82
- "provider/model-id": { "reserveTokens": 716384 }
82
+ "provider/model-id": { "reserveTokens": 666384 }
83
83
  }
84
84
  }
85
85
  }
@@ -1,7 +1,7 @@
1
1
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
2
2
  import type { Model, Api } from "@earendil-works/pi-ai";
3
3
 
4
- const DEFAULT_MAX_CONTEXT_WINDOW = 300_000;
4
+ const DEFAULT_MAX_CONTEXT_WINDOW = 350_000;
5
5
  const DEFAULT_RESERVE_TOKENS = 16_384;
6
6
  const PROCESS_STATE_KEY = Symbol.for("pi-extensions.context-cap.model-holds.v1");
7
7
  type AnyModel = Model<Api> | Model<any>;
@@ -224,7 +224,7 @@ function unavailableReason(result: ApplyResult | undefined): string | undefined
224
224
  return "physical model has no compatible context-window limit";
225
225
  }
226
226
  if (result?.reason === "reserve") {
227
- return `compaction reserveTokens (${formatTokens(result.reserveTokens ?? DEFAULT_MAX_CONTEXT_WINDOW)}) is at least 300k; the auto-compaction threshold would be zero or negative`;
227
+ return `compaction reserveTokens (${formatTokens(result.reserveTokens ?? DEFAULT_MAX_CONTEXT_WINDOW)}) is at least ${formatTokens(DEFAULT_MAX_CONTEXT_WINDOW)}; the auto-compaction threshold would be zero or negative`;
228
228
  }
229
229
  if (result?.reason === "modelOverride") {
230
230
  return `compaction.modelOverrides[\"${result.key}\"] is configured; leaving the model window unchanged`;
@@ -317,7 +317,7 @@ export default function contextCapExtension(pi: ExtensionAPI) {
317
317
  }
318
318
 
319
319
  pi.registerCommand("context-cap", {
320
- description: "Toggle the 300k effective context-window cap for auto-compaction",
320
+ description: `Toggle the ${formatTokens(DEFAULT_MAX_CONTEXT_WINDOW)} effective context-window cap for auto-compaction`,
321
321
  getArgumentCompletions: (prefix) => {
322
322
  const commands = ["on", "off", "toggle", "status"];
323
323
  const matches = commands.filter((command) => command.startsWith(prefix.trim()));
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-context-cap",
3
- "version": "0.1.15",
4
- "description": "A pi extension that caps effective model context windows at 300k tokens for earlier auto-compaction.",
3
+ "version": "0.1.16",
4
+ "description": "A pi extension that caps effective model context windows at 350k tokens for earlier auto-compaction.",
5
5
  "keywords": [
6
6
  "pi-package",
7
7
  "pi",