@mariozechner/pi-coding-agent 0.30.2 → 0.31.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 +244 -1
- package/README.md +105 -84
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +5 -1
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/file-processor.d.ts +3 -3
- package/dist/cli/file-processor.d.ts.map +1 -1
- package/dist/cli/file-processor.js +7 -10
- package/dist/cli/file-processor.js.map +1 -1
- package/dist/config.d.ts +9 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +18 -0
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session.d.ts +73 -34
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +464 -210
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts +2 -2
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +2 -2
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/bash-executor.d.ts +2 -2
- package/dist/core/bash-executor.d.ts.map +1 -1
- package/dist/core/bash-executor.js +2 -2
- package/dist/core/bash-executor.js.map +1 -1
- package/dist/core/compaction/branch-summarization.d.ts +84 -0
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -0
- package/dist/core/compaction/branch-summarization.js +233 -0
- package/dist/core/compaction/branch-summarization.js.map +1 -0
- package/dist/core/{compaction.d.ts → compaction/compaction.d.ts} +38 -19
- package/dist/core/compaction/compaction.d.ts.map +1 -0
- package/dist/core/compaction/compaction.js +558 -0
- package/dist/core/compaction/compaction.js.map +1 -0
- package/dist/core/compaction/index.d.ts +7 -0
- package/dist/core/compaction/index.d.ts.map +1 -0
- package/dist/core/compaction/index.js +7 -0
- package/dist/core/compaction/index.js.map +1 -0
- package/dist/core/compaction/utils.d.ts +35 -0
- package/dist/core/compaction/utils.d.ts.map +1 -0
- package/dist/core/compaction/utils.js +138 -0
- package/dist/core/compaction/utils.js.map +1 -0
- package/dist/core/custom-tools/index.d.ts +2 -1
- package/dist/core/custom-tools/index.d.ts.map +1 -1
- package/dist/core/custom-tools/index.js +1 -0
- package/dist/core/custom-tools/index.js.map +1 -1
- package/dist/core/custom-tools/loader.d.ts.map +1 -1
- package/dist/core/custom-tools/loader.js +13 -80
- package/dist/core/custom-tools/loader.js.map +1 -1
- package/dist/core/custom-tools/types.d.ts +84 -59
- package/dist/core/custom-tools/types.d.ts.map +1 -1
- package/dist/core/custom-tools/types.js.map +1 -1
- package/dist/core/custom-tools/wrapper.d.ts +15 -0
- package/dist/core/custom-tools/wrapper.d.ts.map +1 -0
- package/dist/core/custom-tools/wrapper.js +23 -0
- package/dist/core/custom-tools/wrapper.js.map +1 -0
- package/dist/core/exec.d.ts +29 -0
- package/dist/core/exec.d.ts.map +1 -0
- package/dist/core/exec.js +71 -0
- package/dist/core/exec.js.map +1 -0
- package/dist/core/export-html/index.d.ts +17 -0
- package/dist/core/export-html/index.d.ts.map +1 -0
- package/dist/core/export-html/index.js +171 -0
- package/dist/core/export-html/index.js.map +1 -0
- package/dist/core/export-html/template.css +781 -0
- package/dist/core/export-html/template.html +54 -0
- package/dist/core/export-html/template.js +1185 -0
- package/dist/core/export-html/vendor/highlight.min.js +1213 -0
- package/dist/core/export-html/vendor/marked.min.js +6 -0
- package/dist/core/hooks/index.d.ts +4 -4
- package/dist/core/hooks/index.d.ts.map +1 -1
- package/dist/core/hooks/index.js +3 -3
- package/dist/core/hooks/index.js.map +1 -1
- package/dist/core/hooks/loader.d.ts +40 -5
- package/dist/core/hooks/loader.d.ts.map +1 -1
- package/dist/core/hooks/loader.js +43 -10
- package/dist/core/hooks/loader.js.map +1 -1
- package/dist/core/hooks/runner.d.ts +94 -18
- package/dist/core/hooks/runner.d.ts.map +1 -1
- package/dist/core/hooks/runner.js +199 -120
- package/dist/core/hooks/runner.js.map +1 -1
- package/dist/core/hooks/tool-wrapper.d.ts +1 -1
- package/dist/core/hooks/tool-wrapper.d.ts.map +1 -1
- package/dist/core/hooks/tool-wrapper.js +36 -19
- package/dist/core/hooks/tool-wrapper.js.map +1 -1
- package/dist/core/hooks/types.d.ts +407 -96
- package/dist/core/hooks/types.d.ts.map +1 -1
- package/dist/core/hooks/types.js.map +1 -1
- package/dist/core/index.d.ts +4 -3
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/messages.d.ts +44 -12
- package/dist/core/messages.d.ts.map +1 -1
- package/dist/core/messages.js +82 -34
- package/dist/core/messages.js.map +1 -1
- package/dist/core/model-registry.d.ts +5 -5
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +7 -7
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.d.ts +7 -7
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +45 -14
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/sdk.d.ts +7 -10
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +88 -32
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts +202 -36
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +565 -133
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +9 -3
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +13 -12
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +6 -3
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/bash.d.ts +1 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/edit-diff.d.ts +33 -0
- package/dist/core/tools/edit-diff.d.ts.map +1 -0
- package/dist/core/tools/edit-diff.js +171 -0
- package/dist/core/tools/edit-diff.js.map +1 -0
- package/dist/core/tools/edit.d.ts +7 -1
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +20 -95
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/find.d.ts +1 -1
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.d.ts +1 -1
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/index.d.ts +1 -1
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/ls.d.ts +1 -1
- package/dist/core/tools/ls.d.ts.map +1 -1
- package/dist/core/tools/ls.js.map +1 -1
- package/dist/core/tools/read.d.ts +1 -1
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/write.d.ts +1 -1
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js.map +1 -1
- package/dist/index.d.ts +8 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +22 -21
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +3 -4
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/bash-execution.d.ts +1 -1
- package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/bash-execution.js +6 -2
- package/dist/modes/interactive/components/bash-execution.js.map +1 -1
- package/dist/modes/interactive/components/bordered-loader.d.ts +12 -0
- package/dist/modes/interactive/components/bordered-loader.d.ts.map +1 -0
- package/dist/modes/interactive/components/bordered-loader.js +30 -0
- package/dist/modes/interactive/components/bordered-loader.js.map +1 -0
- package/dist/modes/interactive/components/branch-summary-message.d.ts +14 -0
- package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -0
- package/dist/modes/interactive/components/branch-summary-message.js +35 -0
- package/dist/modes/interactive/components/branch-summary-message.js.map +1 -0
- package/dist/modes/interactive/components/compaction-summary-message.d.ts +14 -0
- package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -0
- package/dist/modes/interactive/components/compaction-summary-message.js +36 -0
- package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -0
- package/dist/modes/interactive/components/dynamic-border.d.ts +5 -1
- package/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -1
- package/dist/modes/interactive/components/dynamic-border.js +5 -1
- package/dist/modes/interactive/components/dynamic-border.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts +12 -6
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +57 -25
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/hook-editor.d.ts +15 -0
- package/dist/modes/interactive/components/hook-editor.d.ts.map +1 -0
- package/dist/modes/interactive/components/hook-editor.js +95 -0
- package/dist/modes/interactive/components/hook-editor.js.map +1 -0
- package/dist/modes/interactive/components/hook-message.d.ts +18 -0
- package/dist/modes/interactive/components/hook-message.d.ts.map +1 -0
- package/dist/modes/interactive/components/hook-message.js +80 -0
- package/dist/modes/interactive/components/hook-message.js.map +1 -0
- package/dist/modes/interactive/components/model-selector.d.ts +3 -3
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +1 -1
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +15 -2
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +70 -21
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/tree-selector.d.ts +52 -0
- package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -0
- package/dist/modes/interactive/components/tree-selector.js +745 -0
- package/dist/modes/interactive/components/tree-selector.js.map +1 -0
- package/dist/modes/interactive/components/user-message-selector.d.ts +3 -3
- package/dist/modes/interactive/components/user-message-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/user-message-selector.js +1 -1
- package/dist/modes/interactive/components/user-message-selector.js.map +1 -1
- package/dist/modes/interactive/components/user-message.d.ts +1 -1
- package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/user-message.js +2 -5
- package/dist/modes/interactive/components/user-message.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +29 -12
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +589 -208
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/dark.json +13 -1
- package/dist/modes/interactive/theme/light.json +13 -1
- package/dist/modes/interactive/theme/theme-schema.json +34 -0
- package/dist/modes/interactive/theme/theme.d.ts +20 -2
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +135 -2
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/print-mode.d.ts +3 -3
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +26 -20
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +13 -10
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +11 -10
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +88 -35
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +30 -11
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/utils/shell.d.ts +4 -2
- package/dist/utils/shell.d.ts.map +1 -1
- package/dist/utils/shell.js +36 -7
- package/dist/utils/shell.js.map +1 -1
- package/dist/utils/tools-manager.d.ts +1 -1
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +2 -2
- package/dist/utils/tools-manager.js.map +1 -1
- package/docs/compaction.md +388 -0
- package/docs/custom-tools.md +146 -43
- package/docs/extension-loading.md +1004 -0
- package/docs/hooks.md +562 -596
- package/docs/rpc.md +33 -19
- package/docs/sdk.md +93 -21
- package/docs/session-tree-plan.md +441 -0
- package/docs/session.md +172 -21
- package/docs/skills.md +2 -0
- package/docs/theme.md +31 -2
- package/docs/tree.md +197 -0
- package/docs/tui.md +343 -0
- package/examples/README.md +1 -9
- package/examples/custom-tools/hello/index.ts +4 -3
- package/examples/custom-tools/question/index.ts +4 -4
- package/examples/custom-tools/subagent/index.ts +7 -6
- package/examples/custom-tools/todo/index.ts +11 -5
- package/examples/hooks/README.md +29 -71
- package/examples/hooks/auto-commit-on-exit.ts +8 -9
- package/examples/hooks/confirm-destructive.ts +29 -30
- package/examples/hooks/custom-compaction.ts +20 -21
- package/examples/hooks/dirty-repo-guard.ts +41 -40
- package/examples/hooks/file-trigger.ts +10 -5
- package/examples/hooks/git-checkpoint.ts +16 -12
- package/examples/hooks/handoff.ts +150 -0
- package/examples/hooks/permission-gate.ts +1 -1
- package/examples/hooks/protected-paths.ts +1 -1
- package/examples/hooks/qna.ts +119 -0
- package/examples/hooks/snake.ts +343 -0
- package/examples/hooks/status-line.ts +40 -0
- package/examples/sdk/01-minimal.ts +1 -1
- package/examples/sdk/02-custom-model.ts +1 -1
- package/examples/sdk/03-custom-prompt.ts +1 -1
- package/examples/sdk/04-skills.ts +1 -1
- package/examples/sdk/05-tools.ts +4 -4
- package/examples/sdk/06-hooks.ts +1 -1
- package/examples/sdk/07-context-files.ts +1 -1
- package/examples/sdk/08-slash-commands.ts +6 -1
- package/examples/sdk/09-api-keys-and-oauth.ts +1 -1
- package/examples/sdk/10-settings.ts +1 -1
- package/examples/sdk/11-sessions.ts +1 -1
- package/examples/sdk/12-full-control.ts +4 -7
- package/package.json +6 -6
- package/dist/core/compaction.d.ts.map +0 -1
- package/dist/core/compaction.js +0 -412
- package/dist/core/compaction.js.map +0 -1
- package/dist/core/export-html.d.ts +0 -23
- package/dist/core/export-html.d.ts.map +0 -1
- package/dist/core/export-html.js +0 -1185
- package/dist/core/export-html.js.map +0 -1
- package/dist/modes/interactive/components/compaction.d.ts +0 -15
- package/dist/modes/interactive/components/compaction.d.ts.map +0 -1
- package/dist/modes/interactive/components/compaction.js +0 -41
- package/dist/modes/interactive/components/compaction.js.map +0 -1
- package/docs/hooks-v2.md +0 -385
- package/docs/session-tree.md +0 -452
package/docs/hooks-v2.md
DELETED
|
@@ -1,385 +0,0 @@
|
|
|
1
|
-
# Hooks v2: Context Control + Commands
|
|
2
|
-
|
|
3
|
-
Issue: #289
|
|
4
|
-
|
|
5
|
-
## Motivation
|
|
6
|
-
|
|
7
|
-
Enable features like session stacking (`/pop`) as hooks, not core code. Core provides primitives, hooks implement features.
|
|
8
|
-
|
|
9
|
-
## Primitives
|
|
10
|
-
|
|
11
|
-
| Primitive | Purpose |
|
|
12
|
-
|-----------|---------|
|
|
13
|
-
| `ctx.saveEntry({type, ...})` | Persist custom entry to session |
|
|
14
|
-
| `pi.on("context", handler)` | Transform messages before LLM |
|
|
15
|
-
| `ctx.rebuildContext()` | Trigger context rebuild |
|
|
16
|
-
| `pi.command(name, opts)` | Register slash command |
|
|
17
|
-
|
|
18
|
-
## Extended HookEventContext
|
|
19
|
-
|
|
20
|
-
```typescript
|
|
21
|
-
interface HookEventContext {
|
|
22
|
-
// Existing
|
|
23
|
-
exec, ui, hasUI, cwd, sessionFile
|
|
24
|
-
|
|
25
|
-
// State (read-only)
|
|
26
|
-
model: Model<any> | null;
|
|
27
|
-
thinkingLevel: ThinkingLevel;
|
|
28
|
-
entries: readonly SessionEntry[];
|
|
29
|
-
|
|
30
|
-
// Utilities
|
|
31
|
-
findModel(provider: string, id: string): Model<any> | null;
|
|
32
|
-
availableModels(): Promise<Model<any>[]>;
|
|
33
|
-
resolveApiKey(model: Model<any>): Promise<string | undefined>;
|
|
34
|
-
|
|
35
|
-
// Mutation
|
|
36
|
-
saveEntry(entry: { type: string; [k: string]: unknown }): Promise<void>;
|
|
37
|
-
rebuildContext(): Promise<void>;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
interface ContextMessage {
|
|
41
|
-
message: AppMessage;
|
|
42
|
-
entryIndex: number | null; // null = synthetic
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
interface ContextEvent {
|
|
46
|
-
type: "context";
|
|
47
|
-
entries: readonly SessionEntry[];
|
|
48
|
-
messages: ContextMessage[];
|
|
49
|
-
}
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
Commands also get: `args`, `argsRaw`, `signal`, `setModel()`, `setThinkingLevel()`.
|
|
53
|
-
|
|
54
|
-
## Stacking: Design
|
|
55
|
-
|
|
56
|
-
### Entry Format
|
|
57
|
-
|
|
58
|
-
```typescript
|
|
59
|
-
interface StackPopEntry {
|
|
60
|
-
type: "stack_pop";
|
|
61
|
-
backToIndex: number;
|
|
62
|
-
summary: string;
|
|
63
|
-
prePopSummary?: string; // when crossing compaction
|
|
64
|
-
timestamp: number;
|
|
65
|
-
}
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
### Crossing Compaction
|
|
69
|
-
|
|
70
|
-
Entries are never deleted. Raw data always available.
|
|
71
|
-
|
|
72
|
-
When `backToIndex < compaction.firstKeptEntryIndex`:
|
|
73
|
-
1. Read raw entries `[0, backToIndex)` → summarize → `prePopSummary`
|
|
74
|
-
2. Read raw entries `[backToIndex, now)` → summarize → `summary`
|
|
75
|
-
|
|
76
|
-
### Context Algorithm: Later Wins
|
|
77
|
-
|
|
78
|
-
Assign sequential IDs to ranges. On overlap, highest ID wins.
|
|
79
|
-
|
|
80
|
-
```
|
|
81
|
-
Compaction at 40: range [0, 30) id=0
|
|
82
|
-
StackPop at 50, backTo=20, prePopSummary: ranges [0, 20) id=1, [20, 50) id=2
|
|
83
|
-
|
|
84
|
-
Index 0-19: id=0 and id=1 cover → id=1 wins (prePopSummary)
|
|
85
|
-
Index 20-29: id=0 and id=2 cover → id=2 wins (popSummary)
|
|
86
|
-
Index 30-49: id=2 covers → id=2 (already emitted at 20)
|
|
87
|
-
Index 50+: no coverage → include as messages
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
## Complex Scenario Trace
|
|
91
|
-
|
|
92
|
-
```
|
|
93
|
-
Initial: [msg1, msg2, msg3, msg4, msg5]
|
|
94
|
-
idx: 1, 2, 3, 4, 5
|
|
95
|
-
|
|
96
|
-
Compaction triggers:
|
|
97
|
-
[msg1-5, compaction{firstKept:4, summary:C1}]
|
|
98
|
-
idx: 1-5, 6
|
|
99
|
-
Context: [C1, msg4, msg5]
|
|
100
|
-
|
|
101
|
-
User continues:
|
|
102
|
-
[..., compaction, msg4, msg5, msg6, msg7]
|
|
103
|
-
idx: 6, 4*, 5*, 7, 8 (* kept from before)
|
|
104
|
-
|
|
105
|
-
User does /pop to msg2 (index 2):
|
|
106
|
-
- backTo=2 < firstKept=4 → crossing!
|
|
107
|
-
- prePopSummary: summarize raw [0,2) → P1
|
|
108
|
-
- summary: summarize raw [2,8) → S1
|
|
109
|
-
- save: stack_pop{backTo:2, summary:S1, prePopSummary:P1} at index 9
|
|
110
|
-
|
|
111
|
-
Ranges:
|
|
112
|
-
compaction [0,4) id=0
|
|
113
|
-
prePopSummary [0,2) id=1
|
|
114
|
-
popSummary [2,9) id=2
|
|
115
|
-
|
|
116
|
-
Context build:
|
|
117
|
-
idx 0: covered by id=0,1 → id=1 wins, emit P1
|
|
118
|
-
idx 1: covered by id=0,1 → id=1 (already emitted)
|
|
119
|
-
idx 2: covered by id=0,2 → id=2 wins, emit S1
|
|
120
|
-
idx 3-8: covered by id=0 or id=2 → id=2 (already emitted)
|
|
121
|
-
idx 9: stack_pop entry, skip
|
|
122
|
-
idx 10+: not covered, include as messages
|
|
123
|
-
|
|
124
|
-
Result: [P1, S1, msg10+]
|
|
125
|
-
|
|
126
|
-
User continues, another compaction:
|
|
127
|
-
[..., stack_pop, msg10, msg11, msg12, compaction{firstKept:11, summary:C2}]
|
|
128
|
-
idx: 9, 10, 11, 12, 13
|
|
129
|
-
|
|
130
|
-
Ranges:
|
|
131
|
-
compaction@6 [0,4) id=0
|
|
132
|
-
prePopSummary [0,2) id=1
|
|
133
|
-
popSummary [2,9) id=2
|
|
134
|
-
compaction@13 [0,11) id=3 ← this now covers previous ranges!
|
|
135
|
-
|
|
136
|
-
Context build:
|
|
137
|
-
idx 0-10: covered by multiple, id=3 wins → emit C2 at idx 0
|
|
138
|
-
idx 11+: include as messages
|
|
139
|
-
|
|
140
|
-
Result: [C2, msg11, msg12]
|
|
141
|
-
|
|
142
|
-
C2's summary text includes info from P1 and S1 (they were in context when C2 was generated).
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
The "later wins" rule naturally handles all cases.
|
|
146
|
-
|
|
147
|
-
## Core Changes
|
|
148
|
-
|
|
149
|
-
| File | Change |
|
|
150
|
-
|------|--------|
|
|
151
|
-
| `session-manager.ts` | `saveEntry()`, `buildSessionContext()` returns `ContextMessage[]` |
|
|
152
|
-
| `hooks/types.ts` | `ContextEvent`, `ContextMessage`, extended context, command types |
|
|
153
|
-
| `hooks/loader.ts` | Track commands |
|
|
154
|
-
| `hooks/runner.ts` | `setStateCallbacks()`, `emitContext()`, command methods |
|
|
155
|
-
| `agent-session.ts` | `saveEntry()`, `rebuildContext()`, state callbacks |
|
|
156
|
-
| `interactive-mode.ts` | Command handling, autocomplete |
|
|
157
|
-
|
|
158
|
-
## Stacking Hook: Complete Implementation
|
|
159
|
-
|
|
160
|
-
```typescript
|
|
161
|
-
import { complete } from "@mariozechner/pi-ai";
|
|
162
|
-
import type { HookAPI, AppMessage, SessionEntry, ContextMessage } from "@mariozechner/pi-coding-agent/hooks";
|
|
163
|
-
|
|
164
|
-
export default function(pi: HookAPI) {
|
|
165
|
-
pi.command("pop", {
|
|
166
|
-
description: "Pop to previous turn, summarizing work",
|
|
167
|
-
handler: async (ctx) => {
|
|
168
|
-
const entries = ctx.entries as SessionEntry[];
|
|
169
|
-
|
|
170
|
-
// Get user turns
|
|
171
|
-
const turns = entries
|
|
172
|
-
.map((e, i) => ({ e, i }))
|
|
173
|
-
.filter(({ e }) => e.type === "message" && (e as any).message.role === "user")
|
|
174
|
-
.map(({ e, i }) => ({ idx: i, text: preview((e as any).message) }));
|
|
175
|
-
|
|
176
|
-
if (turns.length < 2) return { status: "Need at least 2 turns" };
|
|
177
|
-
|
|
178
|
-
// Select target (skip last turn - that's current)
|
|
179
|
-
const options = turns.slice(0, -1).map(t => `[${t.idx}] ${t.text}`);
|
|
180
|
-
const selected = ctx.args[0]
|
|
181
|
-
? options.find(o => o.startsWith(`[${ctx.args[0]}]`))
|
|
182
|
-
: await ctx.ui.select("Pop to:", options);
|
|
183
|
-
|
|
184
|
-
if (!selected) return;
|
|
185
|
-
const backTo = parseInt(selected.match(/\[(\d+)\]/)![1]);
|
|
186
|
-
|
|
187
|
-
// Check compaction crossing
|
|
188
|
-
const compactions = entries.filter(e => e.type === "compaction") as any[];
|
|
189
|
-
const latestCompaction = compactions[compactions.length - 1];
|
|
190
|
-
const crossing = latestCompaction && backTo < latestCompaction.firstKeptEntryIndex;
|
|
191
|
-
|
|
192
|
-
// Generate summaries
|
|
193
|
-
let prePopSummary: string | undefined;
|
|
194
|
-
if (crossing) {
|
|
195
|
-
ctx.ui.notify("Crossing compaction, generating pre-pop summary...", "info");
|
|
196
|
-
const preMsgs = getMessages(entries.slice(0, backTo));
|
|
197
|
-
prePopSummary = await summarize(preMsgs, ctx, "context before this work");
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
const popMsgs = getMessages(entries.slice(backTo));
|
|
201
|
-
const summary = await summarize(popMsgs, ctx, "completed work");
|
|
202
|
-
|
|
203
|
-
// Save and rebuild
|
|
204
|
-
await ctx.saveEntry({
|
|
205
|
-
type: "stack_pop",
|
|
206
|
-
backToIndex: backTo,
|
|
207
|
-
summary,
|
|
208
|
-
prePopSummary,
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
await ctx.rebuildContext();
|
|
212
|
-
return { status: `Popped to turn ${backTo}` };
|
|
213
|
-
}
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
pi.on("context", (event, ctx) => {
|
|
217
|
-
const hasPops = event.entries.some(e => e.type === "stack_pop");
|
|
218
|
-
if (!hasPops) return;
|
|
219
|
-
|
|
220
|
-
// Collect ranges with IDs
|
|
221
|
-
let rangeId = 0;
|
|
222
|
-
const ranges: Array<{from: number; to: number; summary: string; id: number}> = [];
|
|
223
|
-
|
|
224
|
-
for (let i = 0; i < event.entries.length; i++) {
|
|
225
|
-
const e = event.entries[i] as any;
|
|
226
|
-
if (e.type === "compaction") {
|
|
227
|
-
ranges.push({ from: 0, to: e.firstKeptEntryIndex, summary: e.summary, id: rangeId++ });
|
|
228
|
-
}
|
|
229
|
-
if (e.type === "stack_pop") {
|
|
230
|
-
if (e.prePopSummary) {
|
|
231
|
-
ranges.push({ from: 0, to: e.backToIndex, summary: e.prePopSummary, id: rangeId++ });
|
|
232
|
-
}
|
|
233
|
-
ranges.push({ from: e.backToIndex, to: i, summary: e.summary, id: rangeId++ });
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// Build messages
|
|
238
|
-
const messages: ContextMessage[] = [];
|
|
239
|
-
const emitted = new Set<number>();
|
|
240
|
-
|
|
241
|
-
for (let i = 0; i < event.entries.length; i++) {
|
|
242
|
-
const covering = ranges.filter(r => r.from <= i && i < r.to);
|
|
243
|
-
|
|
244
|
-
if (covering.length) {
|
|
245
|
-
const winner = covering.reduce((a, b) => a.id > b.id ? a : b);
|
|
246
|
-
if (i === winner.from && !emitted.has(winner.id)) {
|
|
247
|
-
messages.push({
|
|
248
|
-
message: { role: "user", content: `[Summary]\n\n${winner.summary}`, timestamp: Date.now() } as AppMessage,
|
|
249
|
-
entryIndex: null
|
|
250
|
-
});
|
|
251
|
-
emitted.add(winner.id);
|
|
252
|
-
}
|
|
253
|
-
continue;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
const e = event.entries[i];
|
|
257
|
-
if (e.type === "message") {
|
|
258
|
-
messages.push({ message: (e as any).message, entryIndex: i });
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
return { messages };
|
|
263
|
-
});
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
function getMessages(entries: SessionEntry[]): AppMessage[] {
|
|
267
|
-
return entries.filter(e => e.type === "message").map(e => (e as any).message);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
function preview(msg: AppMessage): string {
|
|
271
|
-
const text = typeof msg.content === "string" ? msg.content
|
|
272
|
-
: (msg.content as any[]).filter(c => c.type === "text").map(c => c.text).join(" ");
|
|
273
|
-
return text.slice(0, 40) + (text.length > 40 ? "..." : "");
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
async function summarize(msgs: AppMessage[], ctx: any, purpose: string): Promise<string> {
|
|
277
|
-
const apiKey = await ctx.resolveApiKey(ctx.model);
|
|
278
|
-
const resp = await complete(ctx.model, {
|
|
279
|
-
messages: [...msgs, { role: "user", content: `Summarize as "${purpose}". Be concise.`, timestamp: Date.now() }]
|
|
280
|
-
}, { apiKey, maxTokens: 2000, signal: ctx.signal });
|
|
281
|
-
return resp.content.filter((c: any) => c.type === "text").map((c: any) => c.text).join("\n");
|
|
282
|
-
}
|
|
283
|
-
```
|
|
284
|
-
|
|
285
|
-
## Edge Cases
|
|
286
|
-
|
|
287
|
-
### Session Resumed Without Hook
|
|
288
|
-
|
|
289
|
-
User has stacking hook, does `/pop`, saves `stack_pop` entry. Later removes hook and resumes session.
|
|
290
|
-
|
|
291
|
-
**What happens:**
|
|
292
|
-
1. Core loads all entries (including `stack_pop`)
|
|
293
|
-
2. Core's `buildSessionContext()` ignores unknown types, returns compaction + message entries
|
|
294
|
-
3. `context` event fires, but no handler processes `stack_pop`
|
|
295
|
-
4. Core's messages pass through unchanged
|
|
296
|
-
|
|
297
|
-
**Result:** Messages that were "popped" return to context. The pop is effectively undone.
|
|
298
|
-
|
|
299
|
-
**Why this is OK:**
|
|
300
|
-
- Session file is intact, no data lost
|
|
301
|
-
- If compaction happened after pop, the compaction summary captured the popped state
|
|
302
|
-
- User removed the hook, so hook's behavior (hiding messages) is gone
|
|
303
|
-
- User can re-add hook to restore stacking behavior
|
|
304
|
-
|
|
305
|
-
**Mitigation:** Could warn on session load if unknown entry types found:
|
|
306
|
-
```typescript
|
|
307
|
-
// In session load
|
|
308
|
-
const unknownTypes = entries
|
|
309
|
-
.map(e => e.type)
|
|
310
|
-
.filter(t => !knownTypes.has(t));
|
|
311
|
-
if (unknownTypes.length) {
|
|
312
|
-
console.warn(`Session has entries of unknown types: ${unknownTypes.join(", ")}`);
|
|
313
|
-
}
|
|
314
|
-
```
|
|
315
|
-
|
|
316
|
-
### Hook Added to Existing Session
|
|
317
|
-
|
|
318
|
-
User has old session without stacking. Adds stacking hook, does `/pop`.
|
|
319
|
-
|
|
320
|
-
**What happens:**
|
|
321
|
-
1. Hook saves `stack_pop` entry
|
|
322
|
-
2. `context` event fires, hook processes it
|
|
323
|
-
3. Works normally
|
|
324
|
-
|
|
325
|
-
No issue. Hook processes entries it recognizes, ignores others.
|
|
326
|
-
|
|
327
|
-
### Multiple Hooks with Different Entry Types
|
|
328
|
-
|
|
329
|
-
Hook A handles `type_a` entries, Hook B handles `type_b` entries.
|
|
330
|
-
|
|
331
|
-
**What happens:**
|
|
332
|
-
1. `context` event chains through both hooks
|
|
333
|
-
2. Each hook checks for its entry types, passes through if none found
|
|
334
|
-
3. Each hook's transforms are applied in order
|
|
335
|
-
|
|
336
|
-
**Best practice:** Hooks should:
|
|
337
|
-
- Only process their own entry types
|
|
338
|
-
- Return `undefined` (pass through) if no relevant entries
|
|
339
|
-
- Use prefixed type names: `myhook_pop`, `myhook_prune`
|
|
340
|
-
|
|
341
|
-
### Conflicting Hooks
|
|
342
|
-
|
|
343
|
-
Two hooks both try to handle the same entry type (e.g., both handle `compaction`).
|
|
344
|
-
|
|
345
|
-
**What happens:**
|
|
346
|
-
- Later hook (project > global) wins in the chain
|
|
347
|
-
- Earlier hook's transform is overwritten
|
|
348
|
-
|
|
349
|
-
**Mitigation:**
|
|
350
|
-
- Core entry types (`compaction`, `message`, etc.) should not be overridden by hooks
|
|
351
|
-
- Hooks should use unique prefixed type names
|
|
352
|
-
- Document which types are "reserved"
|
|
353
|
-
|
|
354
|
-
### Session with Future Entry Types
|
|
355
|
-
|
|
356
|
-
User downgrades pi version, session has entry types from newer version.
|
|
357
|
-
|
|
358
|
-
**What happens:**
|
|
359
|
-
- Same as "hook removed" - unknown types ignored
|
|
360
|
-
- Core handles what it knows, hooks handle what they know
|
|
361
|
-
|
|
362
|
-
**Session file is forward-compatible:** Unknown entries are preserved in file, just not processed.
|
|
363
|
-
|
|
364
|
-
## Implementation Phases
|
|
365
|
-
|
|
366
|
-
| Phase | Scope | LOC |
|
|
367
|
-
|-------|-------|-----|
|
|
368
|
-
| v2.0 | `saveEntry`, `context` event, `rebuildContext`, extended context | ~150 |
|
|
369
|
-
| v2.1 | `pi.command()`, TUI integration, autocomplete | ~200 |
|
|
370
|
-
| v2.2 | Example hooks, documentation | ~300 |
|
|
371
|
-
|
|
372
|
-
## Implementation Order
|
|
373
|
-
|
|
374
|
-
1. `ContextMessage` type, update `buildSessionContext()` return type
|
|
375
|
-
2. `saveEntry()` in session-manager
|
|
376
|
-
3. `context` event in runner with chaining
|
|
377
|
-
4. State callbacks interface and wiring
|
|
378
|
-
5. `rebuildContext()` in agent-session
|
|
379
|
-
6. Manual test with simple hook
|
|
380
|
-
7. Command registration in loader
|
|
381
|
-
8. Command invocation in runner
|
|
382
|
-
9. TUI command handling + autocomplete
|
|
383
|
-
10. Stacking example hook
|
|
384
|
-
11. Pruning example hook
|
|
385
|
-
12. Update hooks.md
|