pi-zip 0.2.7 → 0.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -43,7 +43,7 @@ At most one line per turn in the transcript, only when the context was folded or
43
43
  ▸ pi-zip 182K → 41K ▰▰▱▱▱▱▱▱▱▱ summarized 64 requests · ready while you were away
44
44
  ```
45
45
 
46
- The second line appears once per session. Expand tool output (ctrl+o) to see why it happened now and what was folded:
46
+ The second line appears once per session. Click the notice (or expand tool output with ctrl+o) to see why it happened now and what was folded:
47
47
 
48
48
  ```
49
49
  ▸ pi-zip 74K → 43K ▰▰▰▰▰▰▱▱▱▱ folded 12 old outputs
@@ -53,7 +53,7 @@ The second line appears once per session. Expand tool output (ctrl+o) to see why
53
53
  … 10 more
54
54
  ```
55
55
 
56
- These lines are saved in the session, so they are still there after a restart, but they are never sent to the model. On narrow terminals the words go first, then the bar; the numbers always stay. A summary still shows up as Pi's own `[compaction]` block as well; the pi-zip line next to it tells you who made it.
56
+ These lines are saved in the session, so they are still there after a restart, but they are never sent to the model. On narrow terminals the words go first, then the bar; the numbers always stay. A summary still shows up as Pi's own `[compaction]` block as well; the pi-zip line next to it tells you who made it. The pi-zip numbers are real tokens, the same scale as Pi's context meter (the "after" side is what the edited request actually carried); the `Compacted from N tokens` figure in Pi's block is Pi's own estimate taken when the entry is saved, so it will not match.
57
57
 
58
58
  Everything else pi-zip shows uses the same one-line grammar, and only when something changed:
59
59
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-zip",
3
- "version": "0.2.7",
3
+ "version": "0.2.8",
4
4
  "description": "Keeps long Pi sessions cheap without losing anything: folds old tool output only when the prompt cache has already gone cold, and every fold can be recalled byte for byte. Zero config.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/index.ts CHANGED
@@ -19,20 +19,35 @@ export default function piZip(pi: ExtensionAPI) {
19
19
  if (typeof m?.visibleWidth === "function" && typeof m?.truncateToWidth === "function")
20
20
  setMeasure({ vw: m.visibleWidth, cut: (s: string, w: number) => (m.visibleWidth(s) <= w ? s : w <= 0 ? "" : m.truncateToWidth(s, w, "…")) });
21
21
  }, () => {});
22
+ // A click on a notice toggles its detail, like Pi's own compaction row; ctrl+o (Pi's global expand) still wins: a click
23
+ // only overrides the global state it was made under. Keyed by entry id because Pi rebuilds the component on every toggle.
24
+ const clicked = new Map<string, { open: boolean; under: boolean }>();
22
25
  pi.registerEntryRenderer?.(NOTICE_CUSTOM, (entry: Any, o: Any, theme: Any) => {
23
26
  const d = entry?.data;
24
27
  if (!d?.text) return undefined;
28
+ const global = !!o?.expanded;
29
+ const key = typeof entry?.id === "string" ? entry.id : "";
30
+ const open = () => {
31
+ const c = key ? clicked.get(key) : undefined;
32
+ return c && c.under === global ? c.open : global;
33
+ };
34
+ const expandable = d.v === 2 && d.kind !== "state" && d.kind !== "card" && !!(d.why || d.items?.length);
25
35
  return {
26
36
  render: (width: number) => {
27
37
  try {
28
38
  if (d.v !== 2) return [theme.fg("dim", measure.cut(d.text, width))];
29
39
  if (d.kind === "state") return renderState(d.word, d.reason ?? "", width, theme, measure);
30
40
  if (d.kind === "card" && d.card) return renderCard(d.card, width, theme, measure);
31
- return renderNotice(d as NoticeData, !!o?.expanded, width, theme, measure.vw);
41
+ return renderNotice(d as NoticeData, open(), width, theme, measure.vw);
32
42
  } catch {
33
43
  return [];
34
44
  }
35
45
  },
46
+ handleMouse: (ev: Any) => {
47
+ if (!expandable || !key || ev?.type !== "click" || ev?.button !== "left") return undefined;
48
+ clicked.set(key, { open: !open(), under: global });
49
+ return { handled: true, render: true };
50
+ },
36
51
  invalidate: () => {},
37
52
  };
38
53
  });
package/src/run.ts CHANGED
@@ -11,7 +11,7 @@ import { applyPlanToMessages, buildBlocks, calibrate, countUserTurns, G0, planCo
11
11
  import { handleFor, PH_MARK, RECALL_TOOL } from "./placeholder.ts";
12
12
  import { recalledHandlesFromBranch } from "./recall.ts";
13
13
  import { buildCut } from "./summary.ts";
14
- import { type Any, PRODUCT, textOf, tok4 } from "./util.ts";
14
+ import { type Any, PRODUCT, clamp, textOf, tok4, tokensOf } from "./util.ts";
15
15
  import { fmtLife, SPARK_GAPS, type CardData, type StateWord } from "./ui.ts";
16
16
 
17
17
  export const PLAN_CUSTOM = "pi-zip/plan";
@@ -646,15 +646,20 @@ export class Zip implements ZipControl {
646
646
  });
647
647
  if (cut) this.ledger({ type: "summary", trigger: cut.trigger, source: plan.source, count: cut.count, prefixTokens: cut.prefixTokens, summaryTokens: cut.summaryTokens, ms: cut.ms, waitedMs: Math.round(waitMs), llmOk: cut.llmOk, llmError: cut.llmError, costUsd: cut.costUsd, ...this.usageOf(cut) });
648
648
  const valve = plan.source === "valve";
649
+ // What the user sees should match Pi's own context meter. A run plan was carried by the request that just ended, so its
650
+ // usage is the REAL size after the edits: rescale the notice to it (display only; decisions stay on the plan's k). A
651
+ // valve plan only applies from the next request: keep the estimate.
652
+ const estAfter = ctxTokens - (before - after) - cutSaved - k * tokensOf(e.message);
653
+ const r = !valve && o.usageTotal > 0 && estAfter > 0 ? clamp(o.usageTotal / estAfter, 0.2, 5) : 1;
649
654
  const notices: NoticeAction[] = [];
650
- if (live.length) notices.push({ kind: "fold", count: live.length, tokensBefore: ctxTokens, tokensAfter: ctxTokens - (before - after), ms: plan.ms + (performance.now() - o.t0), pressure: valve });
655
+ if (live.length) notices.push({ kind: "fold", count: live.length, tokensBefore: r * ctxTokens, tokensAfter: r * (ctxTokens - (before - after)), ms: plan.ms + (performance.now() - o.t0), pressure: valve });
651
656
  if (cut) {
652
657
  const prepared = plan.source === "settle" && waitMs < 500; // finished while the user was away: report the real production time, not the zero wait
653
- notices.push({ kind: "summary", count: cut.count, tokensBefore: ctxTokens - (before - after), tokensAfter: ctxTokens - (before - after) - cutSaved, ms: prepared ? cut.ms : waitMs, prepared, pressure: valve });
658
+ notices.push({ kind: "summary", count: cut.count, tokensBefore: r * (ctxTokens - (before - after)), tokensAfter: r * (ctxTokens - (before - after) - cutSaved), ms: prepared ? cut.ms : waitMs, prepared, pressure: valve });
654
659
  }
655
660
  if (notices.length && !this.quiet) {
656
661
  const ITEMS = 8;
657
- const items = live.slice(0, ITEMS).map((t) => ({ label: `${t.tool}${t.args ? " " + t.args : ""}`, turn: byId.get(t.entryId)?.userTurn, tokens: Math.round(k * t.entryTokens), handle: handleFor(t.entryId) }));
662
+ const items = live.slice(0, ITEMS).map((t) => ({ label: `${t.tool}${t.args ? " " + t.args : ""}`, turn: byId.get(t.entryId)?.userTurn, tokens: Math.round(r * k * t.entryTokens), handle: handleFor(t.entryId) }));
658
663
  const why = valve ? "cache still warm, but the context passed the warm-cache limit" : this.cold ? this.coldWhy : "cache warm: the reads saved pay for the rewrite";
659
664
  const data: NoticeData = { v: 2, text: noticeText(notices), before: Math.round(notices[0].tokensBefore), after: Math.round(notices[notices.length - 1].tokensAfter), desc: noticeDesc(notices), why: why || undefined, items, more: Math.max(0, live.length - ITEMS) || undefined, first: !this.noticed && !this.branch(ctx).some((en: Any) => en?.type === "custom" && en.customType === NOTICE_CUSTOM) };
660
665
  this.noticed = true;