@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 +30 -40
- package/package.json +1 -1
- package/src/{summary.ts → compact.ts} +3 -3
- package/src/config.ts +1 -1
- package/src/default-prompt.ts +1 -1
- package/src/index.ts +19 -21
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
|
|
3
|
+
A [Pi](https://github.com/earendil-works/pi-coding-agent) extension that keeps long-running conversations lean and focused.
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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 |
|
|
24
|
+
| Command | What it does |
|
|
38
25
|
|---|---|
|
|
39
|
-
| `/
|
|
40
|
-
| `/add-user-message <text>` |
|
|
41
|
-
| `/
|
|
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
|
-
|
|
38
|
+
Each feature is on by default. Set its variable to `false` to disable it.
|
|
50
39
|
|
|
51
|
-
| Variable |
|
|
52
|
-
|
|
53
|
-
| `PI_TRIM_HISTORY` |
|
|
54
|
-
| `PI_STRIP_TOOL_HISTORY` |
|
|
55
|
-
| `
|
|
56
|
-
| `
|
|
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
|
|
64
|
-
| `
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
|
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.
|
|
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 {
|
|
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 ??
|
|
46
|
+
return fileContents ?? DEFAULT_COMPACT_PROMPT;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
export function
|
|
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
|
|
8
|
+
export const COMPACT_STALENESS_DAYS = Number(process.env["PI_COMPACT_STALENESS_DAYS"]) || 3;
|
package/src/default-prompt.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const
|
|
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
|
-
|
|
10
|
+
runPiCompact,
|
|
15
11
|
type TranscriptEntry,
|
|
16
|
-
} from "./
|
|
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
|
|
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/
|
|
67
|
-
const summary =
|
|
67
|
+
const template = resolvePromptTemplate(readPiFile("prompts/compact.md"));
|
|
68
|
+
const summary = runPiCompact(template, history);
|
|
68
69
|
if (!summary) return "empty";
|
|
69
|
-
writeFileSync(
|
|
70
|
+
writeFileSync(COMPACT_PATH, summary);
|
|
70
71
|
return summary;
|
|
71
72
|
}
|
|
72
73
|
|
|
73
|
-
function
|
|
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(
|
|
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("
|
|
86
|
-
description: "Regenerate the
|
|
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/
|
|
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(
|
|
109
|
-
const mtime = summary ? piFileMtime(
|
|
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("
|
|
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
|
}
|