pi-posthorse 0.6.0 → 0.8.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 (4) hide show
  1. package/README.md +39 -37
  2. package/index.ts +397 -314
  3. package/package.json +6 -6
  4. package/ui.ts +8 -7
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-posthorse",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "type": "module",
5
5
  "description": "Posthorse: fresh context, same journey. No-summary context rollover for Pi, with sparse budget reminders, handoffs, notes, and history.",
6
6
  "keywords": ["pi-package", "pi-extension", "context", "posthorse", "rollover"],
@@ -13,7 +13,7 @@
13
13
  "engines": {
14
14
  "node": ">=24.12.0"
15
15
  },
16
- "packageManager": "npm@12.1.0",
16
+ "packageManager": "npm@12.2.0",
17
17
  "pi": {
18
18
  "extensions": ["./index.ts"],
19
19
  "image": "https://raw.githubusercontent.com/fitchmultz/pi-posthorse/main/diagram.png"
@@ -42,11 +42,11 @@
42
42
  }
43
43
  },
44
44
  "devDependencies": {
45
- "@earendil-works/pi-ai": "0.87.1",
46
- "@earendil-works/pi-coding-agent": "0.87.1",
47
- "@earendil-works/pi-tui": "0.87.1",
45
+ "@earendil-works/pi-ai": "1.0.0",
46
+ "@earendil-works/pi-coding-agent": "1.0.0",
47
+ "@earendil-works/pi-tui": "1.0.0",
48
48
  "@types/node": "24.19.0",
49
- "typebox": "1.3.34",
49
+ "typebox": "1.3.27",
50
50
  "typescript": "7.0.2"
51
51
  }
52
52
  }
package/ui.ts CHANGED
@@ -4,10 +4,10 @@ import { Box, MouseRegion, Text, stripTerminalSequences, truncateToWidth, type C
4
4
  /** Display-only facts and offsets into content, never a second copy of a note or history page. */
5
5
  export type PosthorseDisplay =
6
6
  | { kind: "context"; usage?: { tokens: number | null; contextWindow: number; percent: number | null }; rollover?: "enabled" | "disabled" | "unsupported"; rolloverAt?: number }
7
- | { kind: "notes-list" | "notes-search"; count: number; page?: { offset: number; end: number; total: number } }
8
- | { kind: "note-read"; offset: number; end: number; total: number }
7
+ | { kind: "notes-list" | "notes-search"; count: number; headerLength?: number; page?: { offset: number; end: number; total: number } }
8
+ | { kind: "note-read"; headerLength?: number; offset: number; end: number; total: number }
9
9
  | { kind: "note-write" | "note-append" | "new-context" }
10
- | { kind: "history-search"; entries: Array<{ headerLength: number; length: number }>; footerLength?: number }
10
+ | { kind: "history-search"; entries: Array<{ headerLength: number; length: number }>; footerLength?: number; more?: boolean; skipped?: number }
11
11
  | { kind: "history-read"; headerLength: number; offset: number; end: number; total: number; imageOffset?: number; imageEnd?: number; imageTotal?: number };
12
12
 
13
13
  type ToolName = "notes" | "history" | "get_context_remaining" | "new_context";
@@ -62,7 +62,7 @@ export function toolCards(name: ToolName): Pick<ToolDefinition, "renderCall" | "
62
62
  const args = argsOf(rawArgs);
63
63
  const action = name === "notes" || name === "history" ? ` · ${argument(args.op, "op")}` : name === "new_context" ? " · request" : "";
64
64
  const status = context.isError ? " · error" : context.isPartial ? context.executionStarted ? " · running" : " · preparing" : "";
65
- const target = args.op === "search" ? ` · “${argument(args.query, "query")}”` : name === "notes" && args.op !== "list" ? ` · ${argument(args.path, "path")}` : name === "history" && args.op === "read" ? ` · ${argument(args.id, "id")}` : "";
65
+ const target = args.op === "search" ? ` · “${argument(args.query, "query")}”` : name === "notes" && (args.op !== "list" || args.path !== undefined) ? ` · ${argument(args.path, "path")}` : name === "history" && args.op === "read" ? ` · ${argument(args.id, "id")}` : "";
66
66
  const label = `${context.isPartial ? "…" : context.expanded ? "▾" : "▸"} ${titles[name]}${action}${status}${target}`;
67
67
  return {
68
68
  invalidate() {},
@@ -103,7 +103,7 @@ export function toolCards(name: ToolName): Pick<ToolDefinition, "renderCall" | "
103
103
  case "note-read": summary = pageSummary(display); break;
104
104
  case "note-write": summary = args.content === "" ? "Cleared note" : "Saved note"; break;
105
105
  case "note-append": summary = "Appended to note"; break;
106
- case "history-search": if (Array.isArray(display.entries) && (!display.entries.length || sections)) summary = `${display.entries.length ? `${n(display.entries.length)} matches` : "No matches"} · ${args.all === true ? "all sessions" : "current branch"}${display.footerLength ? "\nMore results; continue with cursor" : ""}`; break;
106
+ case "history-search": if (Array.isArray(display.entries) && (!display.entries.length || sections)) summary = `${display.entries.length ? `${n(display.entries.length)} matches` : "No matches"} · ${args.all === true ? "all sessions" : "current branch"}${display.skipped ? ` · ${n(display.skipped)} in context skipped` : ""}${(display.more ?? display.footerLength) ? "\nMore results; continue with cursor" : ""}`; break;
107
107
  case "history-read":
108
108
  summary = pageSummary(display);
109
109
  if (display.imageTotal && display.imageEnd !== undefined) {
@@ -127,7 +127,8 @@ export function toolCards(name: ToolName): Pick<ToolDefinition, "renderCall" | "
127
127
  const submittedText = textBlock(submitted, theme);
128
128
  const attachmentText = textBlock(attachment, theme, "muted");
129
129
  // Old results have no spans. Only the preview sheds the known history prefix.
130
- const preview = sections ? sections.map(({ body }) => body).join("\n") : name === "history" && !context.isError ? body.replace(/^[^\n]*?\[window [^\]\n]+\] \[[^\]\n]+\](?: \[chars \d+-\d+ of \d+\])? /gm, "") : body;
130
+ const noteHeaderLength = !context.isError && (display?.kind === "note-read" || display?.kind === "notes-list") ? display.headerLength ?? 0 : 0;
131
+ const preview = sections ? sections.map(({ body }) => body).join("\n") : noteHeaderLength ? body.slice(noteHeaderLength) : name === "history" && !context.isError ? body.replace(/^[^\n]*?\[window [^\]\n]+\] \[[^\]\n]+\](?: \[chars \d+-\d+ of \d+\])? /gm, "") : body;
131
132
  const previewText = textBlock(preview, theme, color);
132
133
  const searchPreviews = sections && display?.kind === "history-search" ? sections.slice(0, 3).map(({ body }) => theme.fg("toolOutput", clean(body).replace(/\s+/g, " "))) : undefined;
133
134
  const summaryOnly = display?.kind === "context" || display?.kind === "new-context" || display?.kind === "note-write" || display?.kind === "note-append" || ((display?.kind === "notes-list" || display?.kind === "notes-search") && display.count === 0) || (display?.kind === "note-read" && display.total === 0) || (display?.kind === "history-search" && Array.isArray(display.entries) && display.entries.length === 0);
@@ -142,7 +143,7 @@ export function toolCards(name: ToolName): Pick<ToolDefinition, "renderCall" | "
142
143
  ? searchPreviews.map((line) => truncateToWidth(line, width))
143
144
  : previewText.render(width);
144
145
  const shown = previewRows.slice(0, Math.max(0, Math.min(3, 5 - summaryRows.length - (attachment ? 1 : 0))));
145
- const hidden = summaryOnly || previewRows.length > shown.length || Boolean(sections || submitted);
146
+ const hidden = summaryOnly || previewRows.length > shown.length || Boolean(sections || submitted || noteHeaderLength);
146
147
  return [...summaryRows, ...shown, ...(attachment ? [truncateToWidth(theme.fg("muted", attachment), width)] : []), ...(hidden ? [hint(theme, width)] : [])];
147
148
  },
148
149
  };