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 +2 -2
- package/package.json +1 -1
- package/src/index.ts +16 -1
- package/src/run.ts +9 -4
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.
|
|
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.
|
|
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,
|
|
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;
|