@oh-my-pi/pi-coding-agent 12.7.6 → 12.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.
- package/CHANGELOG.md +37 -37
- package/README.md +9 -1052
- package/package.json +7 -7
- package/src/cli/args.ts +1 -0
- package/src/cli/update-cli.ts +49 -35
- package/src/cli/web-search-cli.ts +3 -2
- package/src/commands/web-search.ts +1 -0
- package/src/config/model-registry.ts +6 -0
- package/src/config/settings-schema.ts +25 -3
- package/src/config/settings.ts +1 -0
- package/src/extensibility/extensions/wrapper.ts +20 -13
- package/src/extensibility/slash-commands.ts +12 -91
- package/src/lsp/client.ts +24 -27
- package/src/lsp/index.ts +92 -42
- package/src/mcp/config-writer.ts +33 -0
- package/src/mcp/config.ts +6 -1
- package/src/mcp/types.ts +1 -0
- package/src/modes/components/custom-editor.ts +8 -5
- package/src/modes/components/settings-defs.ts +2 -1
- package/src/modes/controllers/command-controller.ts +12 -6
- package/src/modes/controllers/input-controller.ts +21 -186
- package/src/modes/controllers/mcp-command-controller.ts +60 -3
- package/src/modes/interactive-mode.ts +2 -2
- package/src/modes/types.ts +1 -1
- package/src/sdk.ts +23 -1
- package/src/secrets/index.ts +116 -0
- package/src/secrets/obfuscator.ts +269 -0
- package/src/secrets/regex.ts +21 -0
- package/src/session/agent-session.ts +143 -21
- package/src/session/compaction/branch-summarization.ts +2 -2
- package/src/session/compaction/compaction.ts +10 -3
- package/src/session/compaction/utils.ts +25 -1
- package/src/slash-commands/builtin-registry.ts +419 -0
- package/src/web/scrapers/github.ts +50 -12
- package/src/web/search/index.ts +5 -5
- package/src/web/search/provider.ts +13 -2
- package/src/web/search/providers/brave.ts +165 -0
- package/src/web/search/types.ts +1 -1
- package/docs/compaction.md +0 -436
- package/docs/config-usage.md +0 -176
- package/docs/custom-tools.md +0 -585
- package/docs/environment-variables.md +0 -257
- package/docs/extension-loading.md +0 -106
- package/docs/extensions.md +0 -1342
- package/docs/fs-scan-cache-architecture.md +0 -50
- package/docs/hooks.md +0 -906
- package/docs/models.md +0 -234
- package/docs/python-repl.md +0 -110
- package/docs/rpc.md +0 -1173
- package/docs/sdk.md +0 -1039
- package/docs/session-tree-plan.md +0 -84
- package/docs/session.md +0 -368
- package/docs/skills.md +0 -254
- package/docs/theme.md +0 -696
- package/docs/tree.md +0 -206
- package/docs/tui.md +0 -487
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Brave Web Search Provider
|
|
3
|
+
*
|
|
4
|
+
* Calls Brave's web search REST API and maps results into the unified
|
|
5
|
+
* SearchResponse shape used by the web search tool.
|
|
6
|
+
*/
|
|
7
|
+
import { getEnvApiKey } from "@oh-my-pi/pi-ai";
|
|
8
|
+
import type { SearchResponse, SearchSource } from "../../../web/search/types";
|
|
9
|
+
import { SearchProviderError } from "../../../web/search/types";
|
|
10
|
+
import type { SearchParams } from "./base";
|
|
11
|
+
import { SearchProvider } from "./base";
|
|
12
|
+
|
|
13
|
+
const BRAVE_SEARCH_URL = "https://api.search.brave.com/res/v1/web/search";
|
|
14
|
+
const DEFAULT_NUM_RESULTS = 10;
|
|
15
|
+
const MAX_NUM_RESULTS = 20;
|
|
16
|
+
|
|
17
|
+
const RECENCY_MAP: Record<"day" | "week" | "month" | "year", "pd" | "pw" | "pm" | "py"> = {
|
|
18
|
+
day: "pd",
|
|
19
|
+
week: "pw",
|
|
20
|
+
month: "pm",
|
|
21
|
+
year: "py",
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export interface BraveSearchParams {
|
|
25
|
+
query: string;
|
|
26
|
+
num_results?: number;
|
|
27
|
+
recency?: "day" | "week" | "month" | "year";
|
|
28
|
+
signal?: AbortSignal;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface BraveSearchResult {
|
|
32
|
+
title?: string | null;
|
|
33
|
+
url?: string | null;
|
|
34
|
+
description?: string | null;
|
|
35
|
+
age?: string | null;
|
|
36
|
+
extra_snippets?: string[] | null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface BraveSearchResponse {
|
|
40
|
+
web?: {
|
|
41
|
+
results?: BraveSearchResult[];
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Find BRAVE_API_KEY from environment or .env files. */
|
|
46
|
+
export function findApiKey(): string | null {
|
|
47
|
+
return getEnvApiKey("brave") ?? null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function clampNumResults(value: number | undefined): number {
|
|
51
|
+
if (!value || Number.isNaN(value)) return DEFAULT_NUM_RESULTS;
|
|
52
|
+
return Math.min(MAX_NUM_RESULTS, Math.max(1, value));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function dateToAgeSeconds(dateStr: string | null | undefined): number | undefined {
|
|
56
|
+
if (!dateStr) return undefined;
|
|
57
|
+
try {
|
|
58
|
+
const date = new Date(dateStr);
|
|
59
|
+
if (Number.isNaN(date.getTime())) return undefined;
|
|
60
|
+
return Math.floor((Date.now() - date.getTime()) / 1000);
|
|
61
|
+
} catch {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function buildSnippet(result: BraveSearchResult): string | undefined {
|
|
67
|
+
const snippets: string[] = [];
|
|
68
|
+
|
|
69
|
+
if (result.description?.trim()) {
|
|
70
|
+
snippets.push(result.description.trim());
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (Array.isArray(result.extra_snippets)) {
|
|
74
|
+
for (const snippet of result.extra_snippets) {
|
|
75
|
+
if (!snippet?.trim()) continue;
|
|
76
|
+
if (snippets.includes(snippet.trim())) continue;
|
|
77
|
+
snippets.push(snippet.trim());
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return snippets.length > 0 ? snippets.join("\n") : undefined;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function callBraveSearch(
|
|
85
|
+
apiKey: string,
|
|
86
|
+
params: BraveSearchParams,
|
|
87
|
+
): Promise<{ response: BraveSearchResponse; requestId?: string }> {
|
|
88
|
+
const numResults = clampNumResults(params.num_results);
|
|
89
|
+
const url = new URL(BRAVE_SEARCH_URL);
|
|
90
|
+
url.searchParams.set("q", params.query);
|
|
91
|
+
url.searchParams.set("count", String(numResults));
|
|
92
|
+
url.searchParams.set("extra_snippets", "true");
|
|
93
|
+
if (params.recency) {
|
|
94
|
+
url.searchParams.set("freshness", RECENCY_MAP[params.recency]);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const response = await fetch(url, {
|
|
98
|
+
headers: {
|
|
99
|
+
Accept: "application/json",
|
|
100
|
+
"X-Subscription-Token": apiKey,
|
|
101
|
+
},
|
|
102
|
+
signal: params.signal,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
if (!response.ok) {
|
|
106
|
+
const errorText = await response.text();
|
|
107
|
+
throw new SearchProviderError("brave", `Brave API error (${response.status}): ${errorText}`, response.status);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const data = (await response.json()) as BraveSearchResponse;
|
|
111
|
+
const requestId = response.headers.get("x-request-id") ?? response.headers.get("request-id") ?? undefined;
|
|
112
|
+
return { response: data, requestId };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Execute Brave web search. */
|
|
116
|
+
export async function searchBrave(params: BraveSearchParams): Promise<SearchResponse> {
|
|
117
|
+
const numResults = clampNumResults(params.num_results);
|
|
118
|
+
const apiKey = findApiKey();
|
|
119
|
+
if (!apiKey) {
|
|
120
|
+
throw new Error("BRAVE_API_KEY not found. Set it in environment or .env file.");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const { response, requestId } = await callBraveSearch(apiKey, params);
|
|
124
|
+
const sources: SearchSource[] = [];
|
|
125
|
+
|
|
126
|
+
for (const result of response.web?.results ?? []) {
|
|
127
|
+
if (!result.url) continue;
|
|
128
|
+
sources.push({
|
|
129
|
+
title: result.title ?? result.url,
|
|
130
|
+
url: result.url,
|
|
131
|
+
snippet: buildSnippet(result),
|
|
132
|
+
publishedDate: result.age ?? undefined,
|
|
133
|
+
ageSeconds: dateToAgeSeconds(result.age),
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
provider: "brave",
|
|
139
|
+
sources: sources.slice(0, numResults),
|
|
140
|
+
requestId,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Search provider for Brave web search. */
|
|
145
|
+
export class BraveProvider extends SearchProvider {
|
|
146
|
+
readonly id = "brave";
|
|
147
|
+
readonly label = "Brave";
|
|
148
|
+
|
|
149
|
+
isAvailable() {
|
|
150
|
+
try {
|
|
151
|
+
return !!findApiKey();
|
|
152
|
+
} catch {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
search(params: SearchParams): Promise<SearchResponse> {
|
|
158
|
+
return searchBrave({
|
|
159
|
+
query: params.query,
|
|
160
|
+
num_results: params.numSearchResults ?? params.limit,
|
|
161
|
+
recency: params.recency,
|
|
162
|
+
signal: params.signal,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
package/src/web/search/types.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
/** Supported web search providers */
|
|
8
|
-
export type SearchProviderId = "exa" | "jina" | "zai" | "anthropic" | "perplexity" | "gemini" | "codex";
|
|
8
|
+
export type SearchProviderId = "exa" | "brave" | "jina" | "zai" | "anthropic" | "perplexity" | "gemini" | "codex";
|
|
9
9
|
|
|
10
10
|
/** Source returned by search (all providers) */
|
|
11
11
|
export interface SearchSource {
|
package/docs/compaction.md
DELETED
|
@@ -1,436 +0,0 @@
|
|
|
1
|
-
# Compaction & Branch Summarization
|
|
2
|
-
|
|
3
|
-
LLMs have limited context windows. OMP uses compaction to summarize older context while keeping recent work intact, and branch summarization to capture work when moving between branches in the session tree.
|
|
4
|
-
|
|
5
|
-
**Source files:**
|
|
6
|
-
|
|
7
|
-
- [`src/session/compaction/compaction.ts`](../src/session/compaction/compaction.ts) - Auto-compaction logic
|
|
8
|
-
- [`src/session/compaction/branch-summarization.ts`](../src/session/compaction/branch-summarization.ts) - Branch summarization
|
|
9
|
-
- [`src/session/compaction/utils.ts`](../src/session/compaction/utils.ts) - Shared utilities (file tracking, serialization)
|
|
10
|
-
- [`src/session/compaction/pruning.ts`](../src/session/compaction/pruning.ts) - Tool output pruning
|
|
11
|
-
- [`src/session/session-manager.ts`](../src/session/session-manager.ts) - Entry types (`CompactionEntry`, `BranchSummaryEntry`)
|
|
12
|
-
- [`src/extensibility/hooks/types.ts`](../src/extensibility/hooks/types.ts) - Hook event types
|
|
13
|
-
- [`src/prompts/compaction/*`](../src/prompts/compaction) - Summarization prompts
|
|
14
|
-
- [`src/prompts/system/*`](../src/prompts/system) - Summarization system prompt + file op tags
|
|
15
|
-
|
|
16
|
-
## Overview
|
|
17
|
-
|
|
18
|
-
OMP has two summarization mechanisms:
|
|
19
|
-
|
|
20
|
-
| Mechanism | Trigger | Purpose |
|
|
21
|
-
| -------------------- | ------------------------------------------------------ | ----------------------------------------- |
|
|
22
|
-
| Compaction | Context overflow/threshold, or `/compact` | Summarize old messages to free up context |
|
|
23
|
-
| Branch summarization | `/tree` navigation (when branch summaries are enabled) | Preserve context when switching branches |
|
|
24
|
-
|
|
25
|
-
Compaction and branch summaries are stored as session entries and injected into LLM context as user messages via `compaction-summary-context.md` and `branch-summary-context.md`.
|
|
26
|
-
|
|
27
|
-
## Compaction
|
|
28
|
-
|
|
29
|
-
### When It Triggers
|
|
30
|
-
|
|
31
|
-
Auto-compaction runs after a turn completes:
|
|
32
|
-
|
|
33
|
-
- **Overflow recovery**: If the current model returns a context overflow error, OMP compacts and retries automatically.
|
|
34
|
-
- **Threshold**: If `contextTokens > contextWindow - reserveTokens`, OMP compacts without retry.
|
|
35
|
-
- Tool output pruning runs first and can reduce `contextTokens`.
|
|
36
|
-
|
|
37
|
-
Manual compaction is available via `/compact [instructions]`.
|
|
38
|
-
|
|
39
|
-
Auto-compaction is controlled by `compaction.enabled`. After threshold compaction, OMP sends a synthetic "Continue if you have next steps." prompt unless `compaction.autoContinue` is set to `false`.
|
|
40
|
-
|
|
41
|
-
### How It Works
|
|
42
|
-
|
|
43
|
-
1. **Prepare**: `prepareCompaction()` finds the latest compaction boundary and chooses a cut point that keeps approximately `keepRecentTokens` (adjusted using usage data).
|
|
44
|
-
2. **Extract**: Collect messages to summarize, plus a turn prefix if the cut point splits a turn.
|
|
45
|
-
3. **Track files**: Gather file ops from `read`/`write`/`edit` tool calls and previous compaction details.
|
|
46
|
-
4. **Summarize**:
|
|
47
|
-
- Main summary uses `compaction-summary.md` or `compaction-update-summary.md` if there is a previous summary.
|
|
48
|
-
- Split turns add a turn-prefix summary from `compaction-turn-prefix.md` and merge with:
|
|
49
|
-
|
|
50
|
-
```
|
|
51
|
-
<history summary>
|
|
52
|
-
|
|
53
|
-
---
|
|
54
|
-
|
|
55
|
-
**Turn Context (split turn):**
|
|
56
|
-
|
|
57
|
-
<turn prefix summary>
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
- Optional custom instructions are appended to the prompt.
|
|
61
|
-
- If `compaction.remoteEndpoint` is set, OMP POSTs `{ systemPrompt, prompt }` to the endpoint and expects `{ summary, shortSummary? }`.
|
|
62
|
-
5. **Finalize**: Generate a short PR-style summary from recent messages, append file-operation tags, persist `CompactionEntry`, and reload session context.
|
|
63
|
-
|
|
64
|
-
Compaction rewrites the session like this:
|
|
65
|
-
|
|
66
|
-
```
|
|
67
|
-
Before compaction:
|
|
68
|
-
|
|
69
|
-
entry: 0 1 2 3 4 5 6 7 8 9
|
|
70
|
-
┌─────┬─────┬─────┬─────┬──────┬─────┬─────┬──────┬──────┬─────┐
|
|
71
|
-
│ hdr │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool│
|
|
72
|
-
└─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴─────┘
|
|
73
|
-
└────────┬───────┘ └──────────────┬──────────────┘
|
|
74
|
-
messagesToSummarize kept messages
|
|
75
|
-
↑
|
|
76
|
-
firstKeptEntryId (entry 4)
|
|
77
|
-
|
|
78
|
-
After compaction (new entry appended):
|
|
79
|
-
|
|
80
|
-
entry: 0 1 2 3 4 5 6 7 8 9 10
|
|
81
|
-
┌─────┬─────┬─────┬─────┬──────┬─────┬─────┬──────┬──────┬─────┬─────┐
|
|
82
|
-
│ hdr │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool│ cmp │
|
|
83
|
-
└─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴─────┴─────┘
|
|
84
|
-
└──────────┬──────┘ └──────────────────────┬───────────────────┘
|
|
85
|
-
not sent to LLM sent to LLM
|
|
86
|
-
↑
|
|
87
|
-
starts from firstKeptEntryId
|
|
88
|
-
|
|
89
|
-
What the LLM sees:
|
|
90
|
-
|
|
91
|
-
┌────────┬─────────┬─────┬─────┬──────┬──────┬─────┬──────┐
|
|
92
|
-
│ system │ summary │ usr │ ass │ tool │ tool │ ass │ tool │
|
|
93
|
-
└────────┴─────────┴─────┴─────┴──────┴──────┴─────┴──────┘
|
|
94
|
-
↑ ↑ └─────────────────┬────────────────┘
|
|
95
|
-
prompt from cmp messages from firstKeptEntryId
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
Compaction summaries are injected into the LLM context using `compaction-summary-context.md`.
|
|
99
|
-
|
|
100
|
-
### Split Turns
|
|
101
|
-
|
|
102
|
-
A "turn" starts with a user message and includes all assistant responses and tool calls until the next user message. `bashExecution` messages and `custom_message`/`branch_summary` entries are treated like user messages for turn boundaries.
|
|
103
|
-
|
|
104
|
-
If a single turn exceeds `keepRecentTokens`, compaction cuts mid-turn at a non-user message (usually an assistant message). OMP produces two summaries (history + turn prefix) and merges them as shown above.
|
|
105
|
-
|
|
106
|
-
### Cut Point Rules
|
|
107
|
-
|
|
108
|
-
Valid cut points are:
|
|
109
|
-
|
|
110
|
-
- User, assistant, bashExecution, hookMessage, branchSummary, or compactionSummary messages
|
|
111
|
-
- `custom_message` and `branch_summary` entries (treated as user-role messages)
|
|
112
|
-
|
|
113
|
-
Never cut at tool results; they must stay with their tool call. Non-message entries (model changes, labels, etc.) are pulled into the kept region before the cut point until a message or compaction boundary is reached.
|
|
114
|
-
|
|
115
|
-
### CompactionEntry Structure
|
|
116
|
-
|
|
117
|
-
Defined in [`src/session/session-manager.ts`](../src/session/session-manager.ts):
|
|
118
|
-
|
|
119
|
-
```typescript
|
|
120
|
-
interface CompactionEntry<T = unknown> {
|
|
121
|
-
type: "compaction";
|
|
122
|
-
id: string;
|
|
123
|
-
parentId: string | null;
|
|
124
|
-
timestamp: string;
|
|
125
|
-
summary: string;
|
|
126
|
-
shortSummary?: string;
|
|
127
|
-
firstKeptEntryId: string;
|
|
128
|
-
tokensBefore: number;
|
|
129
|
-
details?: T;
|
|
130
|
-
preserveData?: Record<string, unknown>;
|
|
131
|
-
fromExtension?: boolean;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Default compaction details:
|
|
135
|
-
interface CompactionDetails {
|
|
136
|
-
readFiles: string[];
|
|
137
|
-
modifiedFiles: string[];
|
|
138
|
-
}
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
`shortSummary` is used in the UI tree. `preserveData` stores hook-provided state across compactions. Entries created by hooks set `fromExtension` and are excluded from default file tracking.
|
|
142
|
-
|
|
143
|
-
## Branch Summarization
|
|
144
|
-
|
|
145
|
-
### When It Triggers
|
|
146
|
-
|
|
147
|
-
When you use `/tree` to navigate to a different branch, the UI prompts to summarize the branch you're leaving if `branchSummary.enabled` is true. You can optionally supply custom instructions.
|
|
148
|
-
|
|
149
|
-
Hooks fire regardless of user choice; a summary is only generated when `preparation.userWantsSummary` is true.
|
|
150
|
-
|
|
151
|
-
### How It Works
|
|
152
|
-
|
|
153
|
-
1. **Find common ancestor**: Deepest node shared by old and new positions.
|
|
154
|
-
2. **Collect entries**: Walk from old leaf back to the common ancestor (including compactions and prior branch summaries).
|
|
155
|
-
3. **Budget**: Keep newest messages first under the token budget (`contextWindow - branchSummary.reserveTokens`).
|
|
156
|
-
4. **Summarize**: Generate summary with `branch-summary.md`, prepend `branch-summary-preamble.md`, append file-op tags, and store `BranchSummaryEntry`.
|
|
157
|
-
|
|
158
|
-
```
|
|
159
|
-
Tree before navigation:
|
|
160
|
-
|
|
161
|
-
┌─ B ─ C ─ D (old leaf, being abandoned)
|
|
162
|
-
A ───┤
|
|
163
|
-
└─ E ─ F (target)
|
|
164
|
-
|
|
165
|
-
Common ancestor: A
|
|
166
|
-
Entries to summarize: B, C, D
|
|
167
|
-
|
|
168
|
-
After navigation with summary:
|
|
169
|
-
|
|
170
|
-
┌─ B ─ C ─ D ─ [summary of B,C,D]
|
|
171
|
-
A ───┤
|
|
172
|
-
└─ E ─ F (new leaf)
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
Branch summaries are injected into context using `branch-summary-context.md`.
|
|
176
|
-
|
|
177
|
-
### BranchSummaryEntry Structure
|
|
178
|
-
|
|
179
|
-
Defined in [`src/session/session-manager.ts`](../src/session/session-manager.ts):
|
|
180
|
-
|
|
181
|
-
```typescript
|
|
182
|
-
interface BranchSummaryEntry<T = unknown> {
|
|
183
|
-
type: "branch_summary";
|
|
184
|
-
id: string;
|
|
185
|
-
parentId: string | null;
|
|
186
|
-
timestamp: string;
|
|
187
|
-
fromId: string;
|
|
188
|
-
summary: string;
|
|
189
|
-
details?: T;
|
|
190
|
-
fromExtension?: boolean;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// Default branch summary details:
|
|
194
|
-
interface BranchSummaryDetails {
|
|
195
|
-
readFiles: string[];
|
|
196
|
-
modifiedFiles: string[];
|
|
197
|
-
}
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
## Cumulative File Tracking
|
|
201
|
-
|
|
202
|
-
Both compaction and branch summarization track files cumulatively.
|
|
203
|
-
|
|
204
|
-
- File ops are extracted from `read`, `write`, and `edit` tool calls in assistant messages.
|
|
205
|
-
- Writes and edits are treated as modified files; read-only files exclude those modified.
|
|
206
|
-
- Compaction includes file ops from previous compaction details (only when `fromExtension` is false).
|
|
207
|
-
- Branch summaries include file ops from previous branch summary details even if those entries aren't within the token budget.
|
|
208
|
-
|
|
209
|
-
File lists are appended to the summary with XML tags:
|
|
210
|
-
|
|
211
|
-
```
|
|
212
|
-
<read-files>
|
|
213
|
-
path/to/file.ts
|
|
214
|
-
</read-files>
|
|
215
|
-
|
|
216
|
-
<modified-files>
|
|
217
|
-
path/to/changed.ts
|
|
218
|
-
</modified-files>
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
## Summary Format
|
|
222
|
-
|
|
223
|
-
### Compaction Summary Format
|
|
224
|
-
|
|
225
|
-
Prompt: [`compaction-summary.md`](../src/prompts/compaction/compaction-summary.md)
|
|
226
|
-
|
|
227
|
-
```markdown
|
|
228
|
-
## Goal
|
|
229
|
-
[User goals]
|
|
230
|
-
|
|
231
|
-
## Constraints & Preferences
|
|
232
|
-
- [Constraints]
|
|
233
|
-
|
|
234
|
-
## Progress
|
|
235
|
-
|
|
236
|
-
### Done
|
|
237
|
-
- [x] [Completed tasks]
|
|
238
|
-
|
|
239
|
-
### In Progress
|
|
240
|
-
- [ ] [Current work]
|
|
241
|
-
|
|
242
|
-
### Blocked
|
|
243
|
-
- [Issues, if any]
|
|
244
|
-
|
|
245
|
-
## Key Decisions
|
|
246
|
-
- **[Decision]**: [Rationale]
|
|
247
|
-
|
|
248
|
-
## Next Steps
|
|
249
|
-
1. [What should happen next]
|
|
250
|
-
|
|
251
|
-
## Critical Context
|
|
252
|
-
- [Data needed to continue]
|
|
253
|
-
|
|
254
|
-
## Additional Notes
|
|
255
|
-
[Anything else important not covered above]
|
|
256
|
-
```
|
|
257
|
-
|
|
258
|
-
File-operation tags are appended after the summary.
|
|
259
|
-
|
|
260
|
-
### Branch Summary Format
|
|
261
|
-
|
|
262
|
-
Prompt: [`branch-summary.md`](../src/prompts/compaction/branch-summary.md)
|
|
263
|
-
|
|
264
|
-
```markdown
|
|
265
|
-
## Goal
|
|
266
|
-
|
|
267
|
-
[What user trying to accomplish in this branch?]
|
|
268
|
-
|
|
269
|
-
## Constraints & Preferences
|
|
270
|
-
- [Constraints, preferences, requirements mentioned]
|
|
271
|
-
- [(none) if none mentioned]
|
|
272
|
-
|
|
273
|
-
## Progress
|
|
274
|
-
|
|
275
|
-
### Done
|
|
276
|
-
- [x] [Completed tasks/changes]
|
|
277
|
-
|
|
278
|
-
### In Progress
|
|
279
|
-
- [ ] [Work started but not finished]
|
|
280
|
-
|
|
281
|
-
### Blocked
|
|
282
|
-
- [Issues preventing progress]
|
|
283
|
-
|
|
284
|
-
## Key Decisions
|
|
285
|
-
- **[Decision]**: [Brief rationale]
|
|
286
|
-
|
|
287
|
-
## Next Steps
|
|
288
|
-
1. [What should happen next to continue]
|
|
289
|
-
```
|
|
290
|
-
|
|
291
|
-
### Short Summary
|
|
292
|
-
|
|
293
|
-
Compaction also generates a short PR-style summary (`compaction-short-summary.md`) for UI display. It is 2–3 sentences in first person, describing changes made.
|
|
294
|
-
|
|
295
|
-
## Message Serialization
|
|
296
|
-
|
|
297
|
-
Before summarization, messages are serialized to text via [`serializeConversation()`](../src/session/compaction/utils.ts). Messages are first converted with `convertToLlm()` so custom types (bash execution, hook messages, compaction summaries) are represented as user messages.
|
|
298
|
-
|
|
299
|
-
```
|
|
300
|
-
[User]: What they said
|
|
301
|
-
[Assistant thinking]: Internal reasoning
|
|
302
|
-
[Assistant]: Response text
|
|
303
|
-
[Assistant tool calls]: read(path="foo.ts"); edit(path="bar.ts", ...)
|
|
304
|
-
[Tool result]: Output from tool (or "[Output truncated - N tokens]")
|
|
305
|
-
```
|
|
306
|
-
|
|
307
|
-
This prevents the model from treating the input as a conversation to continue.
|
|
308
|
-
|
|
309
|
-
## Custom Summarization via Hooks
|
|
310
|
-
|
|
311
|
-
Hooks can customize both compaction and branch summarization. See [`src/extensibility/hooks/types.ts`](../src/extensibility/hooks/types.ts).
|
|
312
|
-
|
|
313
|
-
### session_before_compact
|
|
314
|
-
|
|
315
|
-
Fired before auto-compaction or `/compact`. Can cancel or supply a custom summary.
|
|
316
|
-
|
|
317
|
-
```typescript
|
|
318
|
-
pi.on("session_before_compact", async (event, ctx) => {
|
|
319
|
-
const { preparation, customInstructions, signal } = event;
|
|
320
|
-
|
|
321
|
-
// Cancel:
|
|
322
|
-
return { cancel: true };
|
|
323
|
-
|
|
324
|
-
// Custom summary:
|
|
325
|
-
return {
|
|
326
|
-
compaction: {
|
|
327
|
-
summary: "Your summary...",
|
|
328
|
-
shortSummary: "Short summary...",
|
|
329
|
-
firstKeptEntryId: preparation.firstKeptEntryId,
|
|
330
|
-
tokensBefore: preparation.tokensBefore,
|
|
331
|
-
details: {
|
|
332
|
-
/* custom data */
|
|
333
|
-
},
|
|
334
|
-
},
|
|
335
|
-
};
|
|
336
|
-
});
|
|
337
|
-
```
|
|
338
|
-
|
|
339
|
-
#### Converting Messages to Text
|
|
340
|
-
|
|
341
|
-
To generate a summary with your own model, convert messages to text using `serializeConversation`:
|
|
342
|
-
|
|
343
|
-
```typescript
|
|
344
|
-
import { convertToLlm, serializeConversation } from "@oh-my-pi/pi-coding-agent";
|
|
345
|
-
|
|
346
|
-
pi.on("session_before_compact", async (event, ctx) => {
|
|
347
|
-
const { preparation } = event;
|
|
348
|
-
|
|
349
|
-
const conversationText = serializeConversation(convertToLlm(preparation.messagesToSummarize));
|
|
350
|
-
const summary = await myModel.summarize(conversationText);
|
|
351
|
-
|
|
352
|
-
return {
|
|
353
|
-
compaction: {
|
|
354
|
-
summary,
|
|
355
|
-
firstKeptEntryId: preparation.firstKeptEntryId,
|
|
356
|
-
tokensBefore: preparation.tokensBefore,
|
|
357
|
-
},
|
|
358
|
-
};
|
|
359
|
-
});
|
|
360
|
-
```
|
|
361
|
-
|
|
362
|
-
See [examples/hooks/custom-compaction.ts](../examples/hooks/custom-compaction.ts) for a complete example using a different model.
|
|
363
|
-
|
|
364
|
-
### session.compacting
|
|
365
|
-
|
|
366
|
-
Fired just before summarization to override the prompt or add extra context.
|
|
367
|
-
|
|
368
|
-
```typescript
|
|
369
|
-
pi.on("session.compacting", async (event, ctx) => {
|
|
370
|
-
return {
|
|
371
|
-
prompt: "Override the default compaction prompt...",
|
|
372
|
-
context: ["Include ticket ABC-123", "Keep recent benchmark results"],
|
|
373
|
-
preserveData: { artifactIndex: ["foo.ts"] },
|
|
374
|
-
};
|
|
375
|
-
});
|
|
376
|
-
```
|
|
377
|
-
|
|
378
|
-
`context` lines are injected as `<additional-context>` in the prompt. `preserveData` is stored on the compaction entry.
|
|
379
|
-
|
|
380
|
-
### session_before_tree
|
|
381
|
-
|
|
382
|
-
Fired before `/tree` navigation. Always fires, even if the user opts out of summarization.
|
|
383
|
-
|
|
384
|
-
```typescript
|
|
385
|
-
pi.on("session_before_tree", async (event, ctx) => {
|
|
386
|
-
const { preparation, signal } = event;
|
|
387
|
-
|
|
388
|
-
// preparation.targetId - where we're navigating to
|
|
389
|
-
// preparation.oldLeafId - current position (being abandoned)
|
|
390
|
-
// preparation.commonAncestorId - shared ancestor
|
|
391
|
-
// preparation.entriesToSummarize - entries that would be summarized
|
|
392
|
-
// preparation.userWantsSummary - whether user chose to summarize
|
|
393
|
-
|
|
394
|
-
// Cancel navigation entirely:
|
|
395
|
-
return { cancel: true };
|
|
396
|
-
|
|
397
|
-
// Provide custom summary (only used if userWantsSummary is true):
|
|
398
|
-
if (preparation.userWantsSummary) {
|
|
399
|
-
return {
|
|
400
|
-
summary: {
|
|
401
|
-
summary: "Your summary...",
|
|
402
|
-
details: {
|
|
403
|
-
/* custom data */
|
|
404
|
-
},
|
|
405
|
-
},
|
|
406
|
-
};
|
|
407
|
-
}
|
|
408
|
-
});
|
|
409
|
-
```
|
|
410
|
-
|
|
411
|
-
## Settings
|
|
412
|
-
|
|
413
|
-
Global settings are stored in `~/.omp/agent/config.yml`. Project-level overrides are loaded from `settings.json` in config directories (for example `.omp/settings.json` or `.claude/settings.json`).
|
|
414
|
-
|
|
415
|
-
```yaml
|
|
416
|
-
# ~/.omp/agent/config.yml
|
|
417
|
-
compaction:
|
|
418
|
-
enabled: true
|
|
419
|
-
reserveTokens: 16384
|
|
420
|
-
keepRecentTokens: 20000
|
|
421
|
-
autoContinue: true
|
|
422
|
-
remoteEndpoint: "https://example.com/compaction"
|
|
423
|
-
branchSummary:
|
|
424
|
-
enabled: false
|
|
425
|
-
reserveTokens: 16384
|
|
426
|
-
```
|
|
427
|
-
|
|
428
|
-
| Setting | Default | Description |
|
|
429
|
-
| ------------------------------ | ------- | ------------------------------------------------------ |
|
|
430
|
-
| `compaction.enabled` | `true` | Enable auto-compaction |
|
|
431
|
-
| `compaction.reserveTokens` | `16384` | Tokens reserved for prompts + response |
|
|
432
|
-
| `compaction.keepRecentTokens` | `20000` | Recent tokens to keep |
|
|
433
|
-
| `compaction.autoContinue` | `true` | Auto-send a continuation prompt after compaction |
|
|
434
|
-
| `compaction.remoteEndpoint` | unset | Remote summarization endpoint |
|
|
435
|
-
| `branchSummary.enabled` | `false` | Prompt to summarize when leaving a branch |
|
|
436
|
-
| `branchSummary.reserveTokens` | `16384` | Tokens reserved for branch summary prompts |
|