@8monkey/pi-context-history 0.2.0 → 0.2.1

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
@@ -1,20 +1,10 @@
1
1
  # pi-context-history
2
2
 
3
- A [Pi](https://github.com/earendil-works/pi-coding-agent) extension that manages conversation context end-to-end. It trims old history, strips stale tool chatter from earlier turns, keeps a rolling session summary, folds that summary back into the system prompt, and lets you append messages to the history by hand — so long-running conversations stay lean, focused, and continuous.
3
+ A [Pi](https://github.com/earendil-works/pi-coding-agent) extension that keeps long-running conversations lean and focused.
4
4
 
5
- Each feature is independent and can be turned off individually, so you can run the whole pipeline or just the parts you want.
5
+ In long sessions, the context window fills with old messages and tool output the model no longer needs. This extension removes what's stale and keeps a rolling summary so earlier context isn't lost.
6
6
 
7
- ## Features
8
-
9
- | Feature | Hook | What it does |
10
- |---|---|---|
11
- | **Trim history** | `context` | Drops messages older than a configurable age from the context. |
12
- | **Strip tool history** | `context` | Removes tool calls and results from prior turns, keeping the current turn's tool interactions intact. |
13
- | **Generate summary** | `session_start` | Regenerates a stale rolling summary in the background when a session resumes; also available on demand via `/summarize-session`. |
14
- | **Inject summary** | `before_agent_start` | Folds the rolling summary into the system prompt so the model continues with prior context. |
15
- | **Append message** | command | Adds a user or assistant message to the end of the history via `/add-user-message` and `/add-assistant-message`. |
16
-
17
- The summary features are a producer/consumer pair: **Generate summary** writes `~/.pi/agent/summary.md`, and **Inject summary** reads it.
7
+ Everything is on by default and works once installed. Each feature can be disabled independently.
18
8
 
19
9
  ## Install
20
10
 
@@ -22,55 +12,55 @@ The summary features are a producer/consumer pair: **Generate summary** writes `
22
12
  pi install npm:@8monkey/pi-context-history
23
13
  ```
24
14
 
25
- ## How it works
26
-
27
- - **Trim history.** On each `context` event the message list is filtered to those whose `timestamp` is within `PI_HISTORY_DAYS`. Messages exactly at the cutoff are kept.
28
- - **Strip tool history.** Tool calls and results from earlier turns are removed; the current turn is left untouched. At the start of each agent loop the extension marks where the current turn begins (scanning back past the latest tool exchange) and holds that mark steady across the loop, so a running turn is never stripped mid-flight. Before the mark, tool results are dropped and assistant messages lose their tool-call blocks (if nothing else remains, the message goes too).
29
- - **Generate summary.** When you resume a session whose summary has gone stale — older than `PI_SUMMARY_STALENESS_DAYS`, with a first message older than the window too — the extension rebuilds `~/.pi/agent/summary.md` by shelling out to `pi -p` (extensions, context files, and skills disabled). New and empty sessions are skipped. To override the built-in prompt, drop your own at `prompts/session-summary.md` (project `.pi/` wins over `~/.pi/`); it must contain the `{conversation_history}` placeholder.
30
- - **Inject summary.** Before each agent run, when `~/.pi/agent/summary.md` exists, its contents are wrapped in a `<summary date="…">` block (the date is the file's modified time) followed by an `<additional_context>` note telling the model to maintain continuity and match the existing language and tone.
31
- - **Append message.** `/add-user-message <text>` and `/add-assistant-message <text>` write a message with the matching role to the end of the session history. The entry is persisted immediately but enters the live context on the next rebuild (resume or branch), since Pi has no public way to inject into a running turn. Appending an assistant message requires a model to be selected, as the message records its identity.
15
+ ## Features
32
16
 
33
- Zero runtime dependencies. Pi loads the TypeScript directly, so there's no build step. Runs under Node or Bun.
17
+ - **Trim history.** Drops messages older than `PI_HISTORY_DAYS` (60 by default) on each context rebuild.
18
+ - **Strip tool history.** Removes tool calls and results from turns before the current one, leaving the in-progress turn untouched.
19
+ - **Compact.** Keeps a rolling summary of your sessions in `~/.pi/agent/compact.md` and folds it into the system prompt, so the model continues with prior context. The summary regenerates during session start when you resume a session after it has gone stale. Run `/compact-session` to regenerate on demand.
20
+ - **Append message.** Adds a user or assistant message to the end of the history with `/add-user-message` and `/add-assistant-message`.
34
21
 
35
22
  ## Commands
36
23
 
37
- | Command | Description |
24
+ | Command | What it does |
38
25
  |---|---|
39
- | `/add-assistant-message <text>` | Append an assistant message to the end of the history. Persisted immediately; enters context on the next rebuild. Available only when the **Append message** feature is enabled. |
40
- | `/add-user-message <text>` | Append a user message to the end of the history. Persisted immediately; enters context on the next rebuild. Available only when the **Append message** feature is enabled. |
41
- | `/summarize-session` | Regenerate the current session's summary on demand, ignoring the staleness window. Writes `~/.pi/agent/summary.md` and reports success, failure, or an empty session. Available only when the **Generate summary** feature is enabled. |
26
+ | `/compact-session` | Write a fresh summary of the current session now, ignoring the staleness window. |
27
+ | `/add-user-message <text>` | Add a message to the conversation as if you had typed it. |
28
+ | `/add-assistant-message <text>` | Add a message as if the assistant had said it. Requires a selected model. |
29
+
30
+ Appended messages are persisted immediately but only enter the live context on the next rebuild (resume or branch).
42
31
 
43
32
  ## Configuration
44
33
 
45
- All configuration is via environment variables.
34
+ All configuration is via environment variables, and all of it is optional.
46
35
 
47
36
  ### Feature toggles
48
37
 
49
- Every feature is **on by default** and switched off by setting its flag to `false` or `0`:
38
+ Each feature is on by default. Set its variable to `false` to disable it.
50
39
 
51
- | Variable | Default | Feature |
52
- |---|---|---|
53
- | `PI_TRIM_HISTORY` | on | Trim history |
54
- | `PI_STRIP_TOOL_HISTORY` | on | Strip tool history |
55
- | `PI_GENERATE_SUMMARY` | on | Generate summary |
56
- | `PI_INJECT_SUMMARY` | on | Inject summary |
57
- | `PI_APPEND_MESSAGE` | on | Append message |
40
+ | Variable | Feature |
41
+ |---|---|
42
+ | `PI_TRIM_HISTORY` | Trim history |
43
+ | `PI_STRIP_TOOL_HISTORY` | Strip tool history |
44
+ | `PI_COMPACT` | Compact (generation and injection) |
45
+ | `PI_APPEND_MESSAGE` | The append-message commands |
58
46
 
59
47
  ### Settings
60
48
 
61
49
  | Variable | Default | Description |
62
50
  |---|---|---|
63
- | `PI_HISTORY_DAYS` | `60` | Maximum age, in days, of messages kept in the context. |
64
- | `PI_SUMMARY_STALENESS_DAYS` | `3` | How many days old a summary (and the session's first message) must be before automatic regeneration kicks in on session start. |
51
+ | `PI_HISTORY_DAYS` | `60` | Maximum age, in days, of messages kept in context. |
52
+ | `PI_COMPACT_STALENESS_DAYS` | `3` | How old the summary may get before it's regenerated on resume. |
65
53
 
66
- | File | Default | Description |
67
- |---|---|---|
68
- | `prompts/session-summary.md` | built-in default | Optional override for the summary prompt. Read from project `.pi/` first, then `~/.pi/`. Must contain the `{conversation_history}` placeholder. |
54
+ ### Compact prompt
55
+
56
+ Override the built-in prompt with a file at `prompts/compact.md`, read from the project `.pi/` first, then `~/.pi/`. It must contain the `{conversation_history}` placeholder.
69
57
 
70
58
  ## Development
71
59
 
60
+ No runtime dependencies. Pi loads the TypeScript directly, so there's no build step, and it runs under Node or Bun.
61
+
72
62
  ```bash
73
- node --test # run tests
63
+ node --test # run tests
74
64
  npm run typecheck
75
65
  ```
76
66
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@8monkey/pi-context-history",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Pi extension that manages conversation context: trims old history, strips stale tool calls, and keeps a rolling summary fed back into the system prompt. Each feature toggles independently.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1,5 +1,5 @@
1
1
  import { execFileSync } from "node:child_process";
2
- import { DEFAULT_SUMMARY_PROMPT } from "./default-prompt.ts";
2
+ import { DEFAULT_COMPACT_PROMPT } from "./default-prompt.ts";
3
3
 
4
4
  const DAY_MS = 86_400_000;
5
5
 
@@ -43,10 +43,10 @@ export function isStale(summaryMtime: Date | null, firstUserDate: Date | null, n
43
43
  }
44
44
 
45
45
  export function resolvePromptTemplate(fileContents: string | null) {
46
- return fileContents ?? DEFAULT_SUMMARY_PROMPT;
46
+ return fileContents ?? DEFAULT_COMPACT_PROMPT;
47
47
  }
48
48
 
49
- export function runPiSummary(template: string, history: string) {
49
+ export function runPiCompact(template: string, history: string) {
50
50
  return execFileSync(
51
51
  "pi",
52
52
  [
package/src/config.ts CHANGED
@@ -5,4 +5,4 @@ export function featureEnabled(flag: string) {
5
5
  }
6
6
 
7
7
  export const HISTORY_DAYS = Number(process.env["PI_HISTORY_DAYS"]) || 60;
8
- export const SUMMARY_STALENESS_DAYS = Number(process.env["PI_SUMMARY_STALENESS_DAYS"]) || 3;
8
+ export const COMPACT_STALENESS_DAYS = Number(process.env["PI_COMPACT_STALENESS_DAYS"]) || 3;
@@ -1,4 +1,4 @@
1
- export const DEFAULT_SUMMARY_PROMPT = `You are summarizing a conversation so it can be continued later. Produce a concise, structured summary capturing the main topics, decisions, open questions, and any important context. Be factual and neutral.
1
+ export const DEFAULT_COMPACT_PROMPT = `You are summarizing a conversation so it can be continued later. Produce a concise, structured summary capturing the main topics, decisions, open questions, and any important context. Be factual and neutral.
2
2
 
3
3
  Conversation:
4
4
  {conversation_history}`;
package/src/index.ts CHANGED
@@ -3,20 +3,21 @@ import { homedir } from "node:os";
3
3
  import { join } from "node:path";
4
4
  import type { ExtensionAPI, SessionManager } from "@earendil-works/pi-coding-agent";
5
5
  import { buildContextPrompt } from "./build-prompt.ts";
6
- import { featureEnabled, HISTORY_DAYS, SUMMARY_STALENESS_DAYS } from "./config.ts";
7
- import { filterByAge } from "./filter.ts";
8
- import { piFileMtime, readPiFile } from "./pi-file.ts";
9
- import { findBoundary, stripBeforeBoundary, type Message } from "./strip.ts";
10
6
  import {
11
7
  buildTranscript,
12
8
  isStale,
13
9
  resolvePromptTemplate,
14
- runPiSummary,
10
+ runPiCompact,
15
11
  type TranscriptEntry,
16
- } from "./summary.ts";
12
+ } from "./compact.ts";
13
+ import { COMPACT_STALENESS_DAYS, featureEnabled, HISTORY_DAYS } from "./config.ts";
14
+ import { filterByAge } from "./filter.ts";
15
+ import { piFileMtime, readPiFile } from "./pi-file.ts";
16
+ import { findBoundary, stripBeforeBoundary, type Message } from "./strip.ts";
17
17
 
18
18
  const HISTORY_MS = HISTORY_DAYS * 86_400_000;
19
- const SUMMARY_PATH = join(homedir(), ".pi", "agent", "summary.md");
19
+ const COMPACT_RELATIVE_PATH = "agent/compact.md";
20
+ const COMPACT_PATH = join(homedir(), ".pi", COMPACT_RELATIVE_PATH);
20
21
  const ZERO_USAGE = {
21
22
  input: 0,
22
23
  output: 0,
@@ -63,18 +64,18 @@ function firstUserDate(entries: TranscriptEntry[]) {
63
64
  function generate(entries: TranscriptEntry[]): string | "empty" {
64
65
  const history = buildTranscript(entries);
65
66
  if (!history) return "empty";
66
- const template = resolvePromptTemplate(readPiFile("prompts/session-summary.md"));
67
- const summary = runPiSummary(template, history);
67
+ const template = resolvePromptTemplate(readPiFile("prompts/compact.md"));
68
+ const summary = runPiCompact(template, history);
68
69
  if (!summary) return "empty";
69
- writeFileSync(SUMMARY_PATH, summary);
70
+ writeFileSync(COMPACT_PATH, summary);
70
71
  return summary;
71
72
  }
72
73
 
73
- function registerGenerateSummary(pi: ExtensionAPI) {
74
+ function registerCompact(pi: ExtensionAPI) {
74
75
  pi.on("session_start", (event, ctx) => {
75
76
  if (event.reason === "new") return;
76
77
  const entries = ctx.sessionManager.getEntries() as TranscriptEntry[];
77
- if (!isStale(piFileMtime("agent/summary.md"), firstUserDate(entries), Date.now(), SUMMARY_STALENESS_DAYS)) {
78
+ if (!isStale(piFileMtime(COMPACT_RELATIVE_PATH), firstUserDate(entries), Date.now(), COMPACT_STALENESS_DAYS)) {
78
79
  return;
79
80
  }
80
81
  try {
@@ -82,8 +83,8 @@ function registerGenerateSummary(pi: ExtensionAPI) {
82
83
  } catch {}
83
84
  });
84
85
 
85
- pi.registerCommand("summarize-session", {
86
- description: "Regenerate the current session's summary now (ignores staleness)",
86
+ pi.registerCommand("compact-session", {
87
+ description: "Regenerate the session summary in ~/.pi/agent/compact.md now (ignores staleness)",
87
88
  handler: async (_args, ctx) => {
88
89
  const entries = ctx.sessionManager.getEntries() as TranscriptEntry[];
89
90
  let result: string | "empty";
@@ -97,16 +98,14 @@ function registerGenerateSummary(pi: ExtensionAPI) {
97
98
  if (result === "empty") {
98
99
  ctx.ui.notify("Nothing to summarize in this session.", "info");
99
100
  } else {
100
- ctx.ui.notify("Session summary written to ~/.pi/agent/summary.md.", "info");
101
+ ctx.ui.notify("Session summary written to ~/.pi/agent/compact.md.", "info");
101
102
  }
102
103
  },
103
104
  });
104
- }
105
105
 
106
- function registerInjectSummary(pi: ExtensionAPI) {
107
106
  pi.on("before_agent_start", (event) => {
108
- const summary = readPiFile("agent/summary.md");
109
- const mtime = summary ? piFileMtime("agent/summary.md") : null;
107
+ const summary = readPiFile(COMPACT_RELATIVE_PATH);
108
+ const mtime = summary ? piFileMtime(COMPACT_RELATIVE_PATH) : null;
110
109
  const summaryDate = mtime ? (mtime.toISOString().split("T")[0] ?? "unknown") : "unknown";
111
110
 
112
111
  const systemPrompt = buildContextPrompt(event.systemPrompt, summary, summaryDate);
@@ -163,7 +162,6 @@ function registerAppendMessage(pi: ExtensionAPI) {
163
162
  export default function (pi: ExtensionAPI) {
164
163
  if (featureEnabled("PI_TRIM_HISTORY")) registerTrimHistory(pi);
165
164
  if (featureEnabled("PI_STRIP_TOOL_HISTORY")) registerStripToolHistory(pi);
166
- if (featureEnabled("PI_GENERATE_SUMMARY")) registerGenerateSummary(pi);
167
- if (featureEnabled("PI_INJECT_SUMMARY")) registerInjectSummary(pi);
165
+ if (featureEnabled("PI_COMPACT")) registerCompact(pi);
168
166
  if (featureEnabled("PI_APPEND_MESSAGE")) registerAppendMessage(pi);
169
167
  }