@astralyn/pi 0.82.6 → 0.83.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 +61 -5099
- package/README.md +12 -111
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +9 -2
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/credential-print.d.ts +23 -0
- package/dist/cli/credential-print.d.ts.map +1 -0
- package/dist/cli/credential-print.js +116 -0
- package/dist/cli/credential-print.js.map +1 -0
- package/dist/cli/session-picker.d.ts.map +1 -1
- package/dist/cli/session-picker.js +11 -13
- package/dist/cli/session-picker.js.map +1 -1
- package/dist/core/agent-session-runtime.d.ts.map +1 -1
- package/dist/core/agent-session-runtime.js +3 -0
- package/dist/core/agent-session-runtime.js.map +1 -1
- package/dist/core/agent-session.d.ts +1 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +13 -6
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +1 -0
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +7 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +11 -0
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/footer-data-provider.d.ts +10 -0
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +1 -1
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/core/model-runtime.d.ts +2 -0
- package/dist/core/model-runtime.d.ts.map +1 -1
- package/dist/core/model-runtime.js.map +1 -1
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +13 -6
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/resource-loader.d.ts +15 -0
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +70 -9
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +2 -2
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/tools/bash.d.ts +1 -5
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +16 -41
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +42 -11
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +40 -11
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +17 -8
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/ls.d.ts.map +1 -1
- package/dist/core/tools/ls.js +3 -3
- package/dist/core/tools/ls.js.map +1 -1
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +4 -4
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/render-utils.d.ts +7 -0
- package/dist/core/tools/render-utils.d.ts.map +1 -1
- package/dist/core/tools/render-utils.js +11 -0
- package/dist/core/tools/render-utils.js.map +1 -1
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +2 -3
- package/dist/core/tools/write.js.map +1 -1
- package/dist/core/trust-manager.d.ts.map +1 -1
- package/dist/core/trust-manager.js +48 -9
- package/dist/core/trust-manager.js.map +1 -1
- package/dist/extensions/deepwiki/render.d.ts.map +1 -1
- package/dist/extensions/deepwiki/render.js +34 -22
- package/dist/extensions/deepwiki/render.js.map +1 -1
- package/dist/extensions/index.d.ts.map +1 -1
- package/dist/extensions/index.js +0 -2
- package/dist/extensions/index.js.map +1 -1
- package/dist/extensions/llama/provider.d.ts.map +1 -1
- package/dist/extensions/llama/provider.js +1 -1
- package/dist/extensions/llama/provider.js.map +1 -1
- package/dist/extensions/question/dialog.d.ts.map +1 -1
- package/dist/extensions/question/dialog.js +29 -10
- package/dist/extensions/question/dialog.js.map +1 -1
- package/dist/extensions/question/index.d.ts.map +1 -1
- package/dist/extensions/question/index.js +7 -2
- package/dist/extensions/question/index.js.map +1 -1
- package/dist/extensions/question/render.d.ts +4 -8
- package/dist/extensions/question/render.d.ts.map +1 -1
- package/dist/extensions/question/render.js +202 -29
- package/dist/extensions/question/render.js.map +1 -1
- package/dist/extensions/question/results.d.ts.map +1 -1
- package/dist/extensions/question/results.js +1 -0
- package/dist/extensions/question/results.js.map +1 -1
- package/dist/extensions/question/schema.d.ts.map +1 -1
- package/dist/extensions/question/schema.js +2 -2
- package/dist/extensions/question/schema.js.map +1 -1
- package/dist/extensions/question/types.d.ts +1 -0
- package/dist/extensions/question/types.d.ts.map +1 -1
- package/dist/extensions/question/types.js +1 -1
- package/dist/extensions/question/types.js.map +1 -1
- package/dist/extensions/router/stream.d.ts +1 -0
- package/dist/extensions/router/stream.d.ts.map +1 -1
- package/dist/extensions/router/stream.js +44 -13
- package/dist/extensions/router/stream.js.map +1 -1
- package/dist/extensions/subagent/constants.d.ts +1 -0
- package/dist/extensions/subagent/constants.d.ts.map +1 -1
- package/dist/extensions/subagent/constants.js +1 -0
- package/dist/extensions/subagent/constants.js.map +1 -1
- package/dist/extensions/subagent/index.d.ts.map +1 -1
- package/dist/extensions/subagent/index.js +2 -0
- package/dist/extensions/subagent/index.js.map +1 -1
- package/dist/extensions/subagent/render.d.ts.map +1 -1
- package/dist/extensions/subagent/render.js +121 -48
- package/dist/extensions/subagent/render.js.map +1 -1
- package/dist/extensions/subagent/retry.d.ts +17 -0
- package/dist/extensions/subagent/retry.d.ts.map +1 -0
- package/dist/extensions/subagent/retry.js +41 -0
- package/dist/extensions/subagent/retry.js.map +1 -0
- package/dist/extensions/subagent/runner.d.ts.map +1 -1
- package/dist/extensions/subagent/runner.js +33 -5
- package/dist/extensions/subagent/runner.js.map +1 -1
- package/dist/extensions/subagent/sdk-runner.d.ts +6 -0
- package/dist/extensions/subagent/sdk-runner.d.ts.map +1 -1
- package/dist/extensions/subagent/sdk-runner.js +46 -24
- package/dist/extensions/subagent/sdk-runner.js.map +1 -1
- package/dist/extensions/subagent/types.d.ts +7 -0
- package/dist/extensions/subagent/types.d.ts.map +1 -1
- package/dist/extensions/subagent/types.js.map +1 -1
- package/dist/extensions/todo/index.d.ts.map +1 -1
- package/dist/extensions/todo/index.js +1 -1
- package/dist/extensions/todo/index.js.map +1 -1
- package/dist/extensions/todo/state.d.ts.map +1 -1
- package/dist/extensions/todo/state.js +10 -2
- package/dist/extensions/todo/state.js.map +1 -1
- package/dist/extensions/todo/view.d.ts +1 -1
- package/dist/extensions/todo/view.d.ts.map +1 -1
- package/dist/extensions/todo/view.js +179 -34
- package/dist/extensions/todo/view.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +43 -0
- package/dist/main.js.map +1 -1
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +2 -1
- package/dist/migrations.js.map +1 -1
- package/dist/modes/interactive/components/armin.d.ts.map +1 -1
- package/dist/modes/interactive/components/armin.js +5 -0
- package/dist/modes/interactive/components/armin.js.map +1 -1
- package/dist/modes/interactive/components/bash-execution.d.ts +1 -0
- package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/bash-execution.js +4 -2
- package/dist/modes/interactive/components/bash-execution.js.map +1 -1
- package/dist/modes/interactive/components/countdown-timer.d.ts +13 -6
- package/dist/modes/interactive/components/countdown-timer.d.ts.map +1 -1
- package/dist/modes/interactive/components/countdown-timer.js +54 -17
- package/dist/modes/interactive/components/countdown-timer.js.map +1 -1
- package/dist/modes/interactive/components/extension-selector.d.ts +1 -1
- package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-selector.js +10 -6
- package/dist/modes/interactive/components/extension-selector.js.map +1 -1
- package/dist/modes/interactive/components/keybinding-hints.d.ts +11 -1
- package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -1
- package/dist/modes/interactive/components/keybinding-hints.js +56 -0
- package/dist/modes/interactive/components/keybinding-hints.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +4 -1
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/session-selector.d.ts +3 -0
- package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector.js +52 -2
- package/dist/modes/interactive/components/session-selector.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +10 -0
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +133 -18
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/tool-group.d.ts +1 -0
- package/dist/modes/interactive/components/tool-group.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-group.js +5 -0
- package/dist/modes/interactive/components/tool-group.js.map +1 -1
- package/dist/modes/interactive/components/tree-selector.d.ts +10 -1
- package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/tree-selector.js +55 -2
- package/dist/modes/interactive/components/tree-selector.js.map +1 -1
- package/dist/modes/interactive/external-editor.d.ts.map +1 -1
- package/dist/modes/interactive/external-editor.js +40 -3
- package/dist/modes/interactive/external-editor.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +5 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +92 -24
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +3 -2
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +13 -0
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/utils/paths.d.ts +5 -0
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +7 -0
- package/dist/utils/paths.js.map +1 -1
- package/docs/bundled/README.md +13 -15
- package/docs/bundled/extensions/deepwiki.md +1 -1
- package/docs/bundled/extensions/question.md +7 -5
- package/docs/bundled/extensions/router.md +3 -4
- package/docs/bundled/extensions/statusline.md +0 -1
- package/docs/bundled/extensions/subagent.md +4 -3
- package/docs/bundled/extensions/todo.md +1 -1
- package/docs/bundled/themes.md +1 -1
- package/docs/bundled/tool-presentation.md +17 -16
- package/docs/compaction.md +25 -30
- package/docs/custom-provider.md +298 -583
- package/docs/docs.json +0 -8
- package/docs/extensions.md +132 -121
- package/docs/index.md +18 -74
- package/docs/json.md +20 -14
- package/docs/models.md +193 -151
- package/docs/providers.md +2 -1
- package/docs/quickstart.md +3 -3
- package/docs/rpc.md +166 -124
- package/docs/sdk.md +119 -178
- package/docs/security.md +1 -1
- package/docs/session-format.md +139 -119
- package/docs/skills.md +0 -1
- package/docs/themes.md +2 -2
- package/docs/tui.md +411 -549
- package/docs/usage.md +3 -11
- package/examples/README.md +6 -1
- package/examples/extensions/README.md +13 -33
- package/examples/extensions/custom-provider-anthropic/index.ts +9 -3
- package/examples/extensions/dynamic-resources/dynamic.json +1 -1
- package/examples/extensions/truncated-tool.ts +2 -2
- package/examples/sdk/12-full-control.ts +2 -0
- package/npm-shrinkwrap.json +25 -25
- package/package.json +5 -5
- package/dist/extensions/plan/constants.d.ts +0 -40
- package/dist/extensions/plan/constants.d.ts.map +0 -1
- package/dist/extensions/plan/constants.js +0 -69
- package/dist/extensions/plan/constants.js.map +0 -1
- package/dist/extensions/plan/index.d.ts +0 -3
- package/dist/extensions/plan/index.d.ts.map +0 -1
- package/dist/extensions/plan/index.js +0 -323
- package/dist/extensions/plan/index.js.map +0 -1
- package/dist/extensions/plan/schema.d.ts +0 -15
- package/dist/extensions/plan/schema.d.ts.map +0 -1
- package/dist/extensions/plan/schema.js +0 -13
- package/dist/extensions/plan/schema.js.map +0 -1
- package/dist/extensions/plan/state.d.ts +0 -29
- package/dist/extensions/plan/state.d.ts.map +0 -1
- package/dist/extensions/plan/state.js +0 -74
- package/dist/extensions/plan/state.js.map +0 -1
- package/dist/extensions/plan/storage.d.ts +0 -18
- package/dist/extensions/plan/storage.d.ts.map +0 -1
- package/dist/extensions/plan/storage.js +0 -58
- package/dist/extensions/plan/storage.js.map +0 -1
- package/dist/extensions/plan/view.d.ts +0 -36
- package/dist/extensions/plan/view.d.ts.map +0 -1
- package/dist/extensions/plan/view.js +0 -74
- package/dist/extensions/plan/view.js.map +0 -1
- package/docs/bundled/extensions/README.md +0 -19
- package/docs/bundled/extensions/plan.md +0 -33
- package/examples/extensions/plan-mode/README.md +0 -66
- package/examples/extensions/plan-mode/index.ts +0 -390
- package/examples/extensions/plan-mode/utils.ts +0 -168
package/docs/usage.md
CHANGED
|
@@ -85,15 +85,7 @@ pi --session <path|id> # Use a specific session file or session ID
|
|
|
85
85
|
pi --fork <path|id> # Fork a session into a new session file
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
- `/session` shows the current session file and ID.
|
|
91
|
-
- `/tree` navigates the in-file session tree and can summarize abandoned branches.
|
|
92
|
-
- `/fork` creates a new session from an earlier user message.
|
|
93
|
-
- `/clone` duplicates the current active branch into a new session file.
|
|
94
|
-
- `/compact` summarizes older messages to free context.
|
|
95
|
-
|
|
96
|
-
See [Sessions](sessions.md) and [Compaction](compaction.md) for details.
|
|
88
|
+
See [Sessions](sessions.md) and [Compaction](compaction.md) for session commands and behavior.
|
|
97
89
|
|
|
98
90
|
## Context Files
|
|
99
91
|
|
|
@@ -159,7 +151,7 @@ pi list # List installed packages
|
|
|
159
151
|
pi config # Enable/disable package resources
|
|
160
152
|
```
|
|
161
153
|
|
|
162
|
-
|
|
154
|
+
To uninstall pi itself, see [Quickstart](quickstart.md#uninstall). `pi config` and project package commands accept `--approve`/`--no-approve` to trust or ignore project-local settings for one command. `pi update` never prompts for project trust.
|
|
163
155
|
|
|
164
156
|
See [Pi Packages](packages.md) for package sources and security notes.
|
|
165
157
|
|
|
@@ -293,6 +285,6 @@ pi --exclude-tools ask_question
|
|
|
293
285
|
|
|
294
286
|
Pi keeps the core small and pushes workflow-specific behavior into extensions, skills, prompt templates, and packages.
|
|
295
287
|
|
|
296
|
-
|
|
288
|
+
This distribution ships selected workflows as hidden built-in extensions, including sub-agents and to-dos. It does not include built-in MCP, permission popups, or background bash. You can build or install additional workflows as extensions or packages, or use external tools such as containers and tmux.
|
|
297
289
|
|
|
298
290
|
For the full rationale, read the [blog post](https://mariozechner.at/posts/2025-11-30-pi-coding-agent/).
|
package/examples/README.md
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
# Examples
|
|
2
2
|
|
|
3
|
-
Example code for pi
|
|
3
|
+
Example code for the `@astralyn/pi` SDK and extensions.
|
|
4
|
+
|
|
5
|
+
## Standalone
|
|
6
|
+
|
|
7
|
+
### [rpc-extension-ui.ts](rpc-extension-ui.ts)
|
|
8
|
+
A lightweight RPC chat client that handles extension UI requests; pair it with [the RPC demo extension](extensions/rpc-demo.ts).
|
|
4
9
|
|
|
5
10
|
## Directories
|
|
6
11
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Extension Examples
|
|
2
2
|
|
|
3
|
-
Example extensions for pi
|
|
3
|
+
Example extensions for `@astralyn/pi`.
|
|
4
4
|
|
|
5
5
|
## Usage
|
|
6
6
|
|
|
@@ -35,6 +35,7 @@ cp permission-gate.ts ~/.pi/agent/extensions/
|
|
|
35
35
|
| `question.ts` | Demonstrates `ctx.ui.select()` for asking the user questions with custom UI |
|
|
36
36
|
| `questionnaire.ts` | Multi-question input with tab bar navigation between questions |
|
|
37
37
|
| `tool-override.ts` | Override built-in tools (e.g., add logging/access control to `read`) |
|
|
38
|
+
| `bash-spawn-hook.ts` | Wraps bash to adjust the command, cwd, and environment before execution |
|
|
38
39
|
| `dynamic-tools.ts` | Register tools after startup (`session_start`) and at runtime via command, with prompt snippets and tool-specific prompt guidelines |
|
|
39
40
|
| `kimi-deferred-tools.ts` | Search for and progressively activate tools for Kimi's deferred-tool loading protocol |
|
|
40
41
|
| `structured-output.ts` | Final structured-output tool that returns `terminate: true` so the agent can end on the tool call |
|
|
@@ -48,17 +49,20 @@ cp permission-gate.ts ~/.pi/agent/extensions/
|
|
|
48
49
|
| Extension | Description |
|
|
49
50
|
|-----------|-------------|
|
|
50
51
|
| `preset.ts` | Named presets for model, thinking level, tools, and instructions via `--preset` flag and `/preset` command |
|
|
51
|
-
| `plan-mode/` | Claude Code-style plan mode for read-only exploration with `/plan` command and step tracking |
|
|
52
52
|
| `tools.ts` | Interactive `/tools` command to enable/disable tools with session persistence |
|
|
53
53
|
| `handoff.ts` | Transfer context to a new focused session via `/handoff <goal>` |
|
|
54
|
+
| `commands.ts` | Lists available slash commands, optionally filtered by source |
|
|
54
55
|
| `qna.ts` | Extracts questions from last response into editor via `ctx.ui.setEditorText()` |
|
|
55
56
|
| `status-line.ts` | Shows turn progress in footer via `ctx.ui.setStatus()` with themed colors |
|
|
57
|
+
| `border-status-editor.ts` | Custom editor borders with working state, model, context, cwd, and Git branch |
|
|
56
58
|
| `github-issue-autocomplete.ts` | Adds `#1234` issue completions by stacking a custom autocomplete provider that preloads open issues from `gh issue list` |
|
|
57
59
|
| `widget-placement.ts` | Shows widgets above and below the editor via `ctx.ui.setWidget()` placement |
|
|
58
60
|
| `hidden-thinking-label.ts` | Customizes the collapsed thinking label via `ctx.ui.setHiddenThinkingLabel()` |
|
|
59
61
|
| `working-indicator.ts` | Customizes the streaming working indicator via `ctx.ui.setWorkingIndicator()` |
|
|
62
|
+
| `working-message-test.ts` | Tests custom working-message and indicator persistence across loader recreation |
|
|
60
63
|
| `model-status.ts` | Shows model changes in status bar via `model_select` hook |
|
|
61
64
|
| `snake.ts` | Snake game with custom UI, keyboard handling, and session persistence |
|
|
65
|
+
| `space-invaders.ts` | Space Invaders game with keyboard controls and session persistence |
|
|
62
66
|
| `tic-tac-toe.ts` | Tic-tac-toe vs the agent with `executionMode: "sequential"` tools to prevent race conditions on shared cursor state |
|
|
63
67
|
| `send-user-message.ts` | Demonstrates `pi.sendUserMessage()` for sending user messages from extensions |
|
|
64
68
|
| `timed-confirm.ts` | Demonstrates AbortSignal for auto-dismissing `ctx.ui.confirm()` and `ctx.ui.select()` dialogs |
|
|
@@ -77,6 +81,7 @@ cp permission-gate.ts ~/.pi/agent/extensions/
|
|
|
77
81
|
| `reload-runtime.ts` | Adds `/reload-runtime` and `reload_runtime` tool showing safe reload flow |
|
|
78
82
|
| `interactive-shell.ts` | Run interactive commands (vim, htop) with full terminal via `user_bash` hook |
|
|
79
83
|
| `inline-bash.ts` | Expands `!{command}` patterns in prompts via `input` event transformation |
|
|
84
|
+
| `input-transform.ts` | Transforms or handles typed input through the `input` event |
|
|
80
85
|
| `input-transform-streaming.ts` | Skips expensive input preprocessing for mid-stream steering via `streamingBehavior` |
|
|
81
86
|
|
|
82
87
|
### Git Integration
|
|
@@ -84,6 +89,7 @@ cp permission-gate.ts ~/.pi/agent/extensions/
|
|
|
84
89
|
| Extension | Description |
|
|
85
90
|
|-----------|-------------|
|
|
86
91
|
| `git-checkpoint.ts` | Creates git stash checkpoints at each turn for code restoration on fork |
|
|
92
|
+
| `git-merge-and-resolve.ts` | Fetches and merges the upstream branch after turns, then sends conflicts for resolution |
|
|
87
93
|
| `auto-commit-on-exit.ts` | Auto-commits on exit using last assistant message for commit message |
|
|
88
94
|
|
|
89
95
|
### System Prompt & Compaction
|
|
@@ -91,7 +97,9 @@ cp permission-gate.ts ~/.pi/agent/extensions/
|
|
|
91
97
|
| Extension | Description |
|
|
92
98
|
|-----------|-------------|
|
|
93
99
|
| `pirate.ts` | Demonstrates `systemPromptAppend` to dynamically modify system prompt |
|
|
100
|
+
| `prompt-customizer.ts` | Adds context-aware guidance based on active tools and skills |
|
|
94
101
|
| `claude-rules.ts` | Scans `.claude/rules/` folder and lists rules in system prompt |
|
|
102
|
+
| `system-prompt-header.ts` | Shows the effective system-prompt length in the status area |
|
|
95
103
|
| `custom-compaction.ts` | Custom compaction that summarizes entire conversation |
|
|
96
104
|
| `trigger-compact.ts` | Triggers compaction when context usage exceeds 100k tokens and adds `/trigger-compact` command |
|
|
97
105
|
|
|
@@ -128,6 +136,7 @@ cp permission-gate.ts ~/.pi/agent/extensions/
|
|
|
128
136
|
|-----------|-------------|
|
|
129
137
|
| `custom-provider-anthropic/` | Custom Anthropic provider with OAuth support and custom streaming implementation |
|
|
130
138
|
| `custom-provider-gitlab-duo/` | GitLab Duo provider using pi-ai's built-in Anthropic/OpenAI streaming via proxy |
|
|
139
|
+
| `provider-payload.ts` | Logs provider request payloads and response headers to `.pi/provider-payload.log` |
|
|
131
140
|
|
|
132
141
|
### External Dependencies
|
|
133
142
|
|
|
@@ -142,39 +151,10 @@ See [docs/extensions.md](../../docs/extensions.md) for full documentation.
|
|
|
142
151
|
|
|
143
152
|
```typescript
|
|
144
153
|
import type { ExtensionAPI } from "@astralyn/pi";
|
|
145
|
-
import { Type } from "typebox";
|
|
146
154
|
|
|
147
155
|
export default function (pi: ExtensionAPI) {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
if (event.toolName === "bash" && event.input.command?.includes("rm -rf")) {
|
|
151
|
-
const ok = await ctx.ui.confirm("Dangerous!", "Allow rm -rf?");
|
|
152
|
-
if (!ok) return { block: true, reason: "Blocked by user" };
|
|
153
|
-
}
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
// Register custom tools
|
|
157
|
-
pi.registerTool({
|
|
158
|
-
name: "greet",
|
|
159
|
-
label: "Greeting",
|
|
160
|
-
description: "Generate a greeting",
|
|
161
|
-
parameters: Type.Object({
|
|
162
|
-
name: Type.String({ description: "Name to greet" }),
|
|
163
|
-
}),
|
|
164
|
-
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
165
|
-
return {
|
|
166
|
-
content: [{ type: "text", text: `Hello, ${params.name}!` }],
|
|
167
|
-
details: {},
|
|
168
|
-
};
|
|
169
|
-
},
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
// Register commands
|
|
173
|
-
pi.registerCommand("hello", {
|
|
174
|
-
description: "Say hello",
|
|
175
|
-
handler: async (args, ctx) => {
|
|
176
|
-
ctx.ui.notify("Hello!", "info");
|
|
177
|
-
},
|
|
156
|
+
pi.on("session_start", (_event, ctx) => {
|
|
157
|
+
ctx.ui.notify("Extension loaded!", "info");
|
|
178
158
|
});
|
|
179
159
|
}
|
|
180
160
|
```
|
|
@@ -153,7 +153,7 @@ async function refreshAnthropicToken(credentials: OAuthCredentials): Promise<OAu
|
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
// =============================================================================
|
|
156
|
-
// Streaming Implementation
|
|
156
|
+
// Streaming Implementation Using Public @earendil-works/pi-ai Exports
|
|
157
157
|
// =============================================================================
|
|
158
158
|
|
|
159
159
|
// Claude Code tool names for OAuth stealth mode
|
|
@@ -353,7 +353,7 @@ function streamCustomAnthropic(
|
|
|
353
353
|
totalTokens: 0,
|
|
354
354
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
355
355
|
},
|
|
356
|
-
stopReason: "
|
|
356
|
+
stopReason: "pending",
|
|
357
357
|
timestamp: Date.now(),
|
|
358
358
|
};
|
|
359
359
|
|
|
@@ -546,8 +546,14 @@ function streamCustomAnthropic(
|
|
|
546
546
|
if (options?.signal?.aborted) {
|
|
547
547
|
throw new Error("Request was aborted");
|
|
548
548
|
}
|
|
549
|
+
if (output.stopReason === "pending") {
|
|
550
|
+
throw new Error("Anthropic stream ended without a stop reason");
|
|
551
|
+
}
|
|
552
|
+
if (output.stopReason === "error" || output.stopReason === "aborted") {
|
|
553
|
+
throw new Error(output.errorMessage || "An unknown error occurred");
|
|
554
|
+
}
|
|
549
555
|
|
|
550
|
-
stream.push({ type: "done", reason: output.stopReason
|
|
556
|
+
stream.push({ type: "done", reason: output.stopReason, message: output });
|
|
551
557
|
stream.end();
|
|
552
558
|
} catch (error) {
|
|
553
559
|
for (const block of output.content) delete (block as any).index;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$schema": "https://raw.githubusercontent.com/
|
|
2
|
+
"$schema": "https://raw.githubusercontent.com/earendil-works/pi/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
|
|
3
3
|
"name": "dynamic-resources",
|
|
4
4
|
"vars": {
|
|
5
5
|
"cyan": "#00d7ff",
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
* 3. Inform the LLM where to find the complete output
|
|
11
11
|
* 4. Custom rendering of tool calls and results
|
|
12
12
|
*
|
|
13
|
-
* The `rg` tool here wraps ripgrep with proper truncation.
|
|
14
|
-
*
|
|
13
|
+
* The `rg` tool here wraps ripgrep with proper truncation. For Pi's built-in
|
|
14
|
+
* implementation, use the published `createGrepTool` export from `@astralyn/pi`.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import { mkdtemp, writeFile } from "node:fs/promises";
|
|
@@ -41,7 +41,9 @@ const resourceLoader: ResourceLoader = {
|
|
|
41
41
|
getAgentsFiles: () => ({ agentsFiles: [] }),
|
|
42
42
|
getSystemPrompt: () => `You are a minimal assistant.
|
|
43
43
|
Available: read, bash. Be concise.`,
|
|
44
|
+
getSystemPromptSource: () => undefined,
|
|
44
45
|
getAppendSystemPrompt: () => [],
|
|
46
|
+
getAppendSystemPromptSources: () => [],
|
|
45
47
|
extendResources: () => {},
|
|
46
48
|
reload: async () => {},
|
|
47
49
|
};
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astralyn/pi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.83.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@astralyn/pi",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.83.0",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@earendil-works/pi-agent-core": "0.
|
|
13
|
-
"@earendil-works/pi-ai": "0.
|
|
14
|
-
"@earendil-works/pi-tui": "0.
|
|
12
|
+
"@earendil-works/pi-agent-core": "0.83.0",
|
|
13
|
+
"@earendil-works/pi-ai": "0.83.0",
|
|
14
|
+
"@earendil-works/pi-tui": "0.83.0",
|
|
15
15
|
"@silvia-odwyer/photon-node": "0.3.4",
|
|
16
16
|
"chalk": "5.6.2",
|
|
17
17
|
"cross-spawn": "7.0.6",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"minimatch": "10.2.5",
|
|
25
25
|
"proper-lockfile": "4.1.2",
|
|
26
26
|
"semver": "7.8.0",
|
|
27
|
-
"typebox": "1.
|
|
27
|
+
"typebox": "1.3.7",
|
|
28
28
|
"undici": "8.5.0",
|
|
29
29
|
"yaml": "2.9.0"
|
|
30
30
|
},
|
|
@@ -716,15 +716,15 @@
|
|
|
716
716
|
}
|
|
717
717
|
},
|
|
718
718
|
"node_modules/@earendil-works/pi-agent-core": {
|
|
719
|
-
"version": "0.
|
|
720
|
-
"resolved": "https://registry.npmjs.org/@earendil-works/pi-agent-core/-/pi-agent-core-0.
|
|
721
|
-
"integrity": "sha512-
|
|
719
|
+
"version": "0.83.0",
|
|
720
|
+
"resolved": "https://registry.npmjs.org/@earendil-works/pi-agent-core/-/pi-agent-core-0.83.0.tgz",
|
|
721
|
+
"integrity": "sha512-RorGp9OH5l3ElpuC5a5ZQ2eWcchZGXflXRzVGkV99y3y6tT+LLNyxoYIdVKvTKWEObwhExeQbTH0fI2tE4iX4g==",
|
|
722
722
|
"license": "MIT",
|
|
723
723
|
"dependencies": {
|
|
724
|
-
"@earendil-works/pi-ai": "^0.
|
|
724
|
+
"@earendil-works/pi-ai": "^0.83.0",
|
|
725
725
|
"diff": "8.0.4",
|
|
726
726
|
"ignore": "7.0.5",
|
|
727
|
-
"typebox": "1.
|
|
727
|
+
"typebox": "1.3.7",
|
|
728
728
|
"yaml": "2.9.0"
|
|
729
729
|
},
|
|
730
730
|
"engines": {
|
|
@@ -732,9 +732,9 @@
|
|
|
732
732
|
}
|
|
733
733
|
},
|
|
734
734
|
"node_modules/@earendil-works/pi-ai": {
|
|
735
|
-
"version": "0.
|
|
736
|
-
"resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.
|
|
737
|
-
"integrity": "sha512-
|
|
735
|
+
"version": "0.83.0",
|
|
736
|
+
"resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.83.0.tgz",
|
|
737
|
+
"integrity": "sha512-m3IZD4g3er0V8TC9+Vpgw/sjTKqcJlkcIBy/JvsgRubuuik3tAVzyugUg4rVrShIkkOT69mEd34NEqKUIsl6JQ==",
|
|
738
738
|
"license": "MIT",
|
|
739
739
|
"dependencies": {
|
|
740
740
|
"@anthropic-ai/sdk": "0.91.1",
|
|
@@ -747,7 +747,7 @@
|
|
|
747
747
|
"https-proxy-agent": "7.0.6",
|
|
748
748
|
"openai": "6.26.0",
|
|
749
749
|
"partial-json": "0.1.7",
|
|
750
|
-
"typebox": "1.
|
|
750
|
+
"typebox": "1.3.7"
|
|
751
751
|
},
|
|
752
752
|
"bin": {
|
|
753
753
|
"pi-ai": "dist/cli.js"
|
|
@@ -777,9 +777,9 @@
|
|
|
777
777
|
}
|
|
778
778
|
},
|
|
779
779
|
"node_modules/@earendil-works/pi-tui": {
|
|
780
|
-
"version": "0.
|
|
781
|
-
"resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.
|
|
782
|
-
"integrity": "sha512-
|
|
780
|
+
"version": "0.83.0",
|
|
781
|
+
"resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.83.0.tgz",
|
|
782
|
+
"integrity": "sha512-IoYrb0rORjELmEpNtoCA/U8je3KopMkRAVJRdSzvXRvgb+Huo1gNh8Q5CSZvNOiYtDxJdj2tYZZHZ4B3+IN3hA==",
|
|
783
783
|
"license": "MIT",
|
|
784
784
|
"dependencies": {
|
|
785
785
|
"get-east-asian-width": "1.6.0",
|
|
@@ -2476,15 +2476,15 @@
|
|
|
2476
2476
|
"license": "MIT"
|
|
2477
2477
|
},
|
|
2478
2478
|
"node_modules/brace-expansion": {
|
|
2479
|
-
"version": "5.0.
|
|
2480
|
-
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.
|
|
2481
|
-
"integrity": "sha512-
|
|
2479
|
+
"version": "5.0.8",
|
|
2480
|
+
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz",
|
|
2481
|
+
"integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==",
|
|
2482
2482
|
"license": "MIT",
|
|
2483
2483
|
"dependencies": {
|
|
2484
2484
|
"balanced-match": "^4.0.2"
|
|
2485
2485
|
},
|
|
2486
2486
|
"engines": {
|
|
2487
|
-
"node": "
|
|
2487
|
+
"node": "20 || >=22"
|
|
2488
2488
|
}
|
|
2489
2489
|
},
|
|
2490
2490
|
"node_modules/braces": {
|
|
@@ -4332,9 +4332,9 @@
|
|
|
4332
4332
|
}
|
|
4333
4333
|
},
|
|
4334
4334
|
"node_modules/typebox": {
|
|
4335
|
-
"version": "1.
|
|
4336
|
-
"resolved": "https://registry.npmjs.org/typebox/-/typebox-1.
|
|
4337
|
-
"integrity": "sha512-
|
|
4335
|
+
"version": "1.3.7",
|
|
4336
|
+
"resolved": "https://registry.npmjs.org/typebox/-/typebox-1.3.7.tgz",
|
|
4337
|
+
"integrity": "sha512-meKuifc33Pccx0O6PdIzYMq3Og8zvP4TIi/a+Bw3AEMZMxOD0+RHGQvpglEe6Zdy3wZ8nqn/j95h8LUZLk/6Hg==",
|
|
4338
4338
|
"license": "MIT"
|
|
4339
4339
|
},
|
|
4340
4340
|
"node_modules/typescript": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astralyn/pi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.83.0",
|
|
4
4
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"piConfig": {
|
|
@@ -48,9 +48,9 @@
|
|
|
48
48
|
"prepublishOnly": "npm run clean && npm run build"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@earendil-works/pi-agent-core": "0.
|
|
52
|
-
"@earendil-works/pi-ai": "0.
|
|
53
|
-
"@earendil-works/pi-tui": "0.
|
|
51
|
+
"@earendil-works/pi-agent-core": "0.83.0",
|
|
52
|
+
"@earendil-works/pi-ai": "0.83.0",
|
|
53
|
+
"@earendil-works/pi-tui": "0.83.0",
|
|
54
54
|
"@silvia-odwyer/photon-node": "0.3.4",
|
|
55
55
|
"chalk": "5.6.2",
|
|
56
56
|
"cross-spawn": "7.0.6",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"minimatch": "10.2.5",
|
|
64
64
|
"proper-lockfile": "4.1.2",
|
|
65
65
|
"semver": "7.8.0",
|
|
66
|
-
"typebox": "1.
|
|
66
|
+
"typebox": "1.3.7",
|
|
67
67
|
"undici": "8.5.0",
|
|
68
68
|
"yaml": "2.9.0"
|
|
69
69
|
},
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* plan/constants.ts — tool identity, command names, and prompt copy.
|
|
3
|
-
*
|
|
4
|
-
* Name/label/command live here so `index.ts` only assembles the extension,
|
|
5
|
-
* matching the todo/question constants.ts pattern. Prompt copy is the
|
|
6
|
-
* model-facing contract; keep it stable.
|
|
7
|
-
*/
|
|
8
|
-
import type { SelectOption } from "../../core/extensions/types.ts";
|
|
9
|
-
export declare const EXIT_PLAN_TOOL_NAME = "exit_plan";
|
|
10
|
-
export declare const EXIT_PLAN_TOOL_LABEL = "Exit Plan";
|
|
11
|
-
export declare const PLAN_COMMAND_NAME = "plan";
|
|
12
|
-
export declare const PLAN_FLAG_NAME = "plan";
|
|
13
|
-
export declare const PLAN_ENTRY_TYPE = "plan-mode";
|
|
14
|
-
export declare const PLAN_STATUS_KEY = "plan-mode";
|
|
15
|
-
/**
|
|
16
|
-
* Tools removed while planning. edit/write/bash grant write access directly;
|
|
17
|
-
* subagent spawns child sessions that do not inherit the restricted tool set,
|
|
18
|
-
* so leaving it active would let the model write through a child agent.
|
|
19
|
-
* Other extension tools (question, todo, deepwiki, ...) stay available.
|
|
20
|
-
*/
|
|
21
|
-
export declare const PLAN_BLOCKED_TOOLS: ReadonlySet<string>;
|
|
22
|
-
/** Max characters of plan markdown echoed into the post-compaction kickoff message. */
|
|
23
|
-
export declare const PLAN_EMBED_MAX_CHARS = 24000;
|
|
24
|
-
export declare const MENU_EXECUTE = "Start executing";
|
|
25
|
-
export declare const MENU_COMPACT_EXECUTE = "Compact context, then execute";
|
|
26
|
-
export declare const MENU_KEEP_PLANNING = "Keep planning";
|
|
27
|
-
/**
|
|
28
|
-
* Approval choices. The label is the decision, the description is the
|
|
29
|
-
* trade-off, so the three options can be told apart without reading to the
|
|
30
|
-
* end of a sentence. select() resolves to the label.
|
|
31
|
-
*/
|
|
32
|
-
export declare const EXIT_PLAN_MENU_OPTIONS: readonly SelectOption[];
|
|
33
|
-
/** Esc maps to "keep planning", so the dismiss hint must not read as "discard". */
|
|
34
|
-
export declare const EXIT_PLAN_CANCEL_HINT = "keep planning";
|
|
35
|
-
export declare const EXIT_PLAN_TOOL_DESCRIPTION = "Submit the finished plan and ask the user to approve leaving plan mode. Present the complete plan as normal response text first, then call this tool with a short title and the same plan markdown; on approval the plan is saved to disk. The user chooses between executing with full context, compacting the context before executing, or continuing to plan. Only available while plan mode is active.";
|
|
36
|
-
export declare const EXIT_PLAN_PROMPT_SNIPPET = "Submit the finished plan for user approval and leave plan mode";
|
|
37
|
-
export declare const EXIT_PLAN_PROMPT_GUIDELINES: string[];
|
|
38
|
-
/** Build the plan-mode system prompt block, listing plans already saved for this branch. */
|
|
39
|
-
export declare function buildPlanModePrompt(planFiles: string[]): string;
|
|
40
|
-
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/extensions/plan/constants.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAEnE,eAAO,MAAM,mBAAmB,cAAc,CAAC;AAC/C,eAAO,MAAM,oBAAoB,cAAc,CAAC;AAChD,eAAO,MAAM,iBAAiB,SAAS,CAAC;AACxC,eAAO,MAAM,cAAc,SAAS,CAAC;AACrC,eAAO,MAAM,eAAe,cAAc,CAAC;AAC3C,eAAO,MAAM,eAAe,cAAc,CAAC;AAE3C;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,EAAE,WAAW,CAAC,MAAM,CAAkD,CAAC;AAEtG,uFAAuF;AACvF,eAAO,MAAM,oBAAoB,QAAS,CAAC;AAE3C,eAAO,MAAM,YAAY,oBAAoB,CAAC;AAC9C,eAAO,MAAM,oBAAoB,kCAAkC,CAAC;AACpE,eAAO,MAAM,kBAAkB,kBAAkB,CAAC;AAElD;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,EAAE,SAAS,YAAY,EAIzD,CAAC;AAEF,mFAAmF;AACnF,eAAO,MAAM,qBAAqB,kBAAkB,CAAC;AAErD,eAAO,MAAM,0BAA0B,+YACsW,CAAC;AAE9Y,eAAO,MAAM,wBAAwB,mEAAmE,CAAC;AAEzG,eAAO,MAAM,2BAA2B,UAGvC,CAAC;AAiBF,4FAA4F;AAC5F,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,MAAM,CAQ/D","sourcesContent":["/**\n * plan/constants.ts — tool identity, command names, and prompt copy.\n *\n * Name/label/command live here so `index.ts` only assembles the extension,\n * matching the todo/question constants.ts pattern. Prompt copy is the\n * model-facing contract; keep it stable.\n */\n\nimport type { SelectOption } from \"../../core/extensions/types.ts\";\n\nexport const EXIT_PLAN_TOOL_NAME = \"exit_plan\";\nexport const EXIT_PLAN_TOOL_LABEL = \"Exit Plan\";\nexport const PLAN_COMMAND_NAME = \"plan\";\nexport const PLAN_FLAG_NAME = \"plan\";\nexport const PLAN_ENTRY_TYPE = \"plan-mode\";\nexport const PLAN_STATUS_KEY = \"plan-mode\";\n\n/**\n * Tools removed while planning. edit/write/bash grant write access directly;\n * subagent spawns child sessions that do not inherit the restricted tool set,\n * so leaving it active would let the model write through a child agent.\n * Other extension tools (question, todo, deepwiki, ...) stay available.\n */\nexport const PLAN_BLOCKED_TOOLS: ReadonlySet<string> = new Set([\"edit\", \"write\", \"bash\", \"subagent\"]);\n\n/** Max characters of plan markdown echoed into the post-compaction kickoff message. */\nexport const PLAN_EMBED_MAX_CHARS = 24_000;\n\nexport const MENU_EXECUTE = \"Start executing\";\nexport const MENU_COMPACT_EXECUTE = \"Compact context, then execute\";\nexport const MENU_KEEP_PLANNING = \"Keep planning\";\n\n/**\n * Approval choices. The label is the decision, the description is the\n * trade-off, so the three options can be told apart without reading to the\n * end of a sentence. select() resolves to the label.\n */\nexport const EXIT_PLAN_MENU_OPTIONS: readonly SelectOption[] = [\n\t{ label: MENU_EXECUTE, description: \"keep full context\" },\n\t{ label: MENU_COMPACT_EXECUTE, description: \"best for long tasks\" },\n\t{ label: MENU_KEEP_PLANNING, description: \"refine before running\" },\n];\n\n/** Esc maps to \"keep planning\", so the dismiss hint must not read as \"discard\". */\nexport const EXIT_PLAN_CANCEL_HINT = \"keep planning\";\n\nexport const EXIT_PLAN_TOOL_DESCRIPTION =\n\t\"Submit the finished plan and ask the user to approve leaving plan mode. Present the complete plan as normal response text first, then call this tool with a short title and the same plan markdown; on approval the plan is saved to disk. The user chooses between executing with full context, compacting the context before executing, or continuing to plan. Only available while plan mode is active.\";\n\nexport const EXIT_PLAN_PROMPT_SNIPPET = \"Submit the finished plan for user approval and leave plan mode\";\n\nexport const EXIT_PLAN_PROMPT_GUIDELINES = [\n\t\"Call `exit_plan` only after presenting the complete plan as response text; make it the only tool call in that step and stop after it returns.\",\n\t\"`exit_plan` results are authoritative for plan-mode state: follow them even when the plan-mode system prompt block is still present later in the run.\",\n];\n\nconst PLAN_MODE_PROMPT = `[PLAN MODE]\nYou are in plan mode: a read-only phase for exploring the codebase and agreeing on a plan with the user before any changes are made.\n\nRestrictions:\n- edit, write, bash, and subagent are unavailable; do not attempt changes or workarounds. Explore with read, grep, find, and ls instead.\n\nWorking style:\n- Answer questions from the code, not from the user: explore before asking.\n- When product intent, scope, or risk tolerance is unclear, interview the user with the question tool. Focus on one decision per exchange (batch only tightly coupled decisions) and always include your recommendation and the trade-offs.\n- Skip the interview for small, clear tasks. Pick sensible defaults for low-risk details and state them in the plan.\n\nDelivering the plan:\n- When the plan is ready, present it in full as normal response text (goal, steps, verification criteria) so the user can read it, then call exit_plan with a short title and the same markdown as the plan parameter.\n- The exit_plan tool result is authoritative for mode state: this block can outlive plan mode within a run, so if the result says plan mode has ended, it has ended.`;\n\n/** Build the plan-mode system prompt block, listing plans already saved for this branch. */\nexport function buildPlanModePrompt(planFiles: string[]): string {\n\tif (!planFiles.length) return PLAN_MODE_PROMPT;\n\tconst listing = planFiles.map((path) => `- ${path}`).join(\"\\n\");\n\treturn `${PLAN_MODE_PROMPT}\n\nPlans already saved for this session (read them with the read tool):\n${listing}\nWhen revising a saved plan, read the latest one first, account for work already completed, and produce a revision that states what changed and why; set the revises parameter to the file name of the plan being revised.`;\n}\n"]}
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* plan/constants.ts — tool identity, command names, and prompt copy.
|
|
3
|
-
*
|
|
4
|
-
* Name/label/command live here so `index.ts` only assembles the extension,
|
|
5
|
-
* matching the todo/question constants.ts pattern. Prompt copy is the
|
|
6
|
-
* model-facing contract; keep it stable.
|
|
7
|
-
*/
|
|
8
|
-
export const EXIT_PLAN_TOOL_NAME = "exit_plan";
|
|
9
|
-
export const EXIT_PLAN_TOOL_LABEL = "Exit Plan";
|
|
10
|
-
export const PLAN_COMMAND_NAME = "plan";
|
|
11
|
-
export const PLAN_FLAG_NAME = "plan";
|
|
12
|
-
export const PLAN_ENTRY_TYPE = "plan-mode";
|
|
13
|
-
export const PLAN_STATUS_KEY = "plan-mode";
|
|
14
|
-
/**
|
|
15
|
-
* Tools removed while planning. edit/write/bash grant write access directly;
|
|
16
|
-
* subagent spawns child sessions that do not inherit the restricted tool set,
|
|
17
|
-
* so leaving it active would let the model write through a child agent.
|
|
18
|
-
* Other extension tools (question, todo, deepwiki, ...) stay available.
|
|
19
|
-
*/
|
|
20
|
-
export const PLAN_BLOCKED_TOOLS = new Set(["edit", "write", "bash", "subagent"]);
|
|
21
|
-
/** Max characters of plan markdown echoed into the post-compaction kickoff message. */
|
|
22
|
-
export const PLAN_EMBED_MAX_CHARS = 24_000;
|
|
23
|
-
export const MENU_EXECUTE = "Start executing";
|
|
24
|
-
export const MENU_COMPACT_EXECUTE = "Compact context, then execute";
|
|
25
|
-
export const MENU_KEEP_PLANNING = "Keep planning";
|
|
26
|
-
/**
|
|
27
|
-
* Approval choices. The label is the decision, the description is the
|
|
28
|
-
* trade-off, so the three options can be told apart without reading to the
|
|
29
|
-
* end of a sentence. select() resolves to the label.
|
|
30
|
-
*/
|
|
31
|
-
export const EXIT_PLAN_MENU_OPTIONS = [
|
|
32
|
-
{ label: MENU_EXECUTE, description: "keep full context" },
|
|
33
|
-
{ label: MENU_COMPACT_EXECUTE, description: "best for long tasks" },
|
|
34
|
-
{ label: MENU_KEEP_PLANNING, description: "refine before running" },
|
|
35
|
-
];
|
|
36
|
-
/** Esc maps to "keep planning", so the dismiss hint must not read as "discard". */
|
|
37
|
-
export const EXIT_PLAN_CANCEL_HINT = "keep planning";
|
|
38
|
-
export const EXIT_PLAN_TOOL_DESCRIPTION = "Submit the finished plan and ask the user to approve leaving plan mode. Present the complete plan as normal response text first, then call this tool with a short title and the same plan markdown; on approval the plan is saved to disk. The user chooses between executing with full context, compacting the context before executing, or continuing to plan. Only available while plan mode is active.";
|
|
39
|
-
export const EXIT_PLAN_PROMPT_SNIPPET = "Submit the finished plan for user approval and leave plan mode";
|
|
40
|
-
export const EXIT_PLAN_PROMPT_GUIDELINES = [
|
|
41
|
-
"Call `exit_plan` only after presenting the complete plan as response text; make it the only tool call in that step and stop after it returns.",
|
|
42
|
-
"`exit_plan` results are authoritative for plan-mode state: follow them even when the plan-mode system prompt block is still present later in the run.",
|
|
43
|
-
];
|
|
44
|
-
const PLAN_MODE_PROMPT = `[PLAN MODE]
|
|
45
|
-
You are in plan mode: a read-only phase for exploring the codebase and agreeing on a plan with the user before any changes are made.
|
|
46
|
-
|
|
47
|
-
Restrictions:
|
|
48
|
-
- edit, write, bash, and subagent are unavailable; do not attempt changes or workarounds. Explore with read, grep, find, and ls instead.
|
|
49
|
-
|
|
50
|
-
Working style:
|
|
51
|
-
- Answer questions from the code, not from the user: explore before asking.
|
|
52
|
-
- When product intent, scope, or risk tolerance is unclear, interview the user with the question tool. Focus on one decision per exchange (batch only tightly coupled decisions) and always include your recommendation and the trade-offs.
|
|
53
|
-
- Skip the interview for small, clear tasks. Pick sensible defaults for low-risk details and state them in the plan.
|
|
54
|
-
|
|
55
|
-
Delivering the plan:
|
|
56
|
-
- When the plan is ready, present it in full as normal response text (goal, steps, verification criteria) so the user can read it, then call exit_plan with a short title and the same markdown as the plan parameter.
|
|
57
|
-
- The exit_plan tool result is authoritative for mode state: this block can outlive plan mode within a run, so if the result says plan mode has ended, it has ended.`;
|
|
58
|
-
/** Build the plan-mode system prompt block, listing plans already saved for this branch. */
|
|
59
|
-
export function buildPlanModePrompt(planFiles) {
|
|
60
|
-
if (!planFiles.length)
|
|
61
|
-
return PLAN_MODE_PROMPT;
|
|
62
|
-
const listing = planFiles.map((path) => `- ${path}`).join("\n");
|
|
63
|
-
return `${PLAN_MODE_PROMPT}
|
|
64
|
-
|
|
65
|
-
Plans already saved for this session (read them with the read tool):
|
|
66
|
-
${listing}
|
|
67
|
-
When revising a saved plan, read the latest one first, account for work already completed, and produce a revision that states what changed and why; set the revises parameter to the file name of the plan being revised.`;
|
|
68
|
-
}
|
|
69
|
-
//# sourceMappingURL=constants.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../../src/extensions/plan/constants.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,MAAM,CAAC,MAAM,mBAAmB,GAAG,WAAW,CAAC;AAC/C,MAAM,CAAC,MAAM,oBAAoB,GAAG,WAAW,CAAC;AAChD,MAAM,CAAC,MAAM,iBAAiB,GAAG,MAAM,CAAC;AACxC,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC;AACrC,MAAM,CAAC,MAAM,eAAe,GAAG,WAAW,CAAC;AAC3C,MAAM,CAAC,MAAM,eAAe,GAAG,WAAW,CAAC;AAE3C;;;;;GAKG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAwB,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC;AAEtG,uFAAuF;AACvF,MAAM,CAAC,MAAM,oBAAoB,GAAG,MAAM,CAAC;AAE3C,MAAM,CAAC,MAAM,YAAY,GAAG,iBAAiB,CAAC;AAC9C,MAAM,CAAC,MAAM,oBAAoB,GAAG,+BAA+B,CAAC;AACpE,MAAM,CAAC,MAAM,kBAAkB,GAAG,eAAe,CAAC;AAElD;;;;GAIG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAA4B;IAC9D,EAAE,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,mBAAmB,EAAE;IACzD,EAAE,KAAK,EAAE,oBAAoB,EAAE,WAAW,EAAE,qBAAqB,EAAE;IACnE,EAAE,KAAK,EAAE,kBAAkB,EAAE,WAAW,EAAE,uBAAuB,EAAE;CACnE,CAAC;AAEF,mFAAmF;AACnF,MAAM,CAAC,MAAM,qBAAqB,GAAG,eAAe,CAAC;AAErD,MAAM,CAAC,MAAM,0BAA0B,GACtC,4YAA4Y,CAAC;AAE9Y,MAAM,CAAC,MAAM,wBAAwB,GAAG,gEAAgE,CAAC;AAEzG,MAAM,CAAC,MAAM,2BAA2B,GAAG;IAC1C,+IAA+I;IAC/I,uJAAuJ;CACvJ,CAAC;AAEF,MAAM,gBAAgB,GAAG;;;;;;;;;;;;;qKAa4I,CAAC;AAEtK,4FAA4F;AAC5F,MAAM,UAAU,mBAAmB,CAAC,SAAmB,EAAU;IAChE,IAAI,CAAC,SAAS,CAAC,MAAM;QAAE,OAAO,gBAAgB,CAAC;IAC/C,MAAM,OAAO,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAChE,OAAO,GAAG,gBAAgB;;;EAGzB,OAAO;0NACiN,CAAC;AAAA,CAC1N","sourcesContent":["/**\n * plan/constants.ts — tool identity, command names, and prompt copy.\n *\n * Name/label/command live here so `index.ts` only assembles the extension,\n * matching the todo/question constants.ts pattern. Prompt copy is the\n * model-facing contract; keep it stable.\n */\n\nimport type { SelectOption } from \"../../core/extensions/types.ts\";\n\nexport const EXIT_PLAN_TOOL_NAME = \"exit_plan\";\nexport const EXIT_PLAN_TOOL_LABEL = \"Exit Plan\";\nexport const PLAN_COMMAND_NAME = \"plan\";\nexport const PLAN_FLAG_NAME = \"plan\";\nexport const PLAN_ENTRY_TYPE = \"plan-mode\";\nexport const PLAN_STATUS_KEY = \"plan-mode\";\n\n/**\n * Tools removed while planning. edit/write/bash grant write access directly;\n * subagent spawns child sessions that do not inherit the restricted tool set,\n * so leaving it active would let the model write through a child agent.\n * Other extension tools (question, todo, deepwiki, ...) stay available.\n */\nexport const PLAN_BLOCKED_TOOLS: ReadonlySet<string> = new Set([\"edit\", \"write\", \"bash\", \"subagent\"]);\n\n/** Max characters of plan markdown echoed into the post-compaction kickoff message. */\nexport const PLAN_EMBED_MAX_CHARS = 24_000;\n\nexport const MENU_EXECUTE = \"Start executing\";\nexport const MENU_COMPACT_EXECUTE = \"Compact context, then execute\";\nexport const MENU_KEEP_PLANNING = \"Keep planning\";\n\n/**\n * Approval choices. The label is the decision, the description is the\n * trade-off, so the three options can be told apart without reading to the\n * end of a sentence. select() resolves to the label.\n */\nexport const EXIT_PLAN_MENU_OPTIONS: readonly SelectOption[] = [\n\t{ label: MENU_EXECUTE, description: \"keep full context\" },\n\t{ label: MENU_COMPACT_EXECUTE, description: \"best for long tasks\" },\n\t{ label: MENU_KEEP_PLANNING, description: \"refine before running\" },\n];\n\n/** Esc maps to \"keep planning\", so the dismiss hint must not read as \"discard\". */\nexport const EXIT_PLAN_CANCEL_HINT = \"keep planning\";\n\nexport const EXIT_PLAN_TOOL_DESCRIPTION =\n\t\"Submit the finished plan and ask the user to approve leaving plan mode. Present the complete plan as normal response text first, then call this tool with a short title and the same plan markdown; on approval the plan is saved to disk. The user chooses between executing with full context, compacting the context before executing, or continuing to plan. Only available while plan mode is active.\";\n\nexport const EXIT_PLAN_PROMPT_SNIPPET = \"Submit the finished plan for user approval and leave plan mode\";\n\nexport const EXIT_PLAN_PROMPT_GUIDELINES = [\n\t\"Call `exit_plan` only after presenting the complete plan as response text; make it the only tool call in that step and stop after it returns.\",\n\t\"`exit_plan` results are authoritative for plan-mode state: follow them even when the plan-mode system prompt block is still present later in the run.\",\n];\n\nconst PLAN_MODE_PROMPT = `[PLAN MODE]\nYou are in plan mode: a read-only phase for exploring the codebase and agreeing on a plan with the user before any changes are made.\n\nRestrictions:\n- edit, write, bash, and subagent are unavailable; do not attempt changes or workarounds. Explore with read, grep, find, and ls instead.\n\nWorking style:\n- Answer questions from the code, not from the user: explore before asking.\n- When product intent, scope, or risk tolerance is unclear, interview the user with the question tool. Focus on one decision per exchange (batch only tightly coupled decisions) and always include your recommendation and the trade-offs.\n- Skip the interview for small, clear tasks. Pick sensible defaults for low-risk details and state them in the plan.\n\nDelivering the plan:\n- When the plan is ready, present it in full as normal response text (goal, steps, verification criteria) so the user can read it, then call exit_plan with a short title and the same markdown as the plan parameter.\n- The exit_plan tool result is authoritative for mode state: this block can outlive plan mode within a run, so if the result says plan mode has ended, it has ended.`;\n\n/** Build the plan-mode system prompt block, listing plans already saved for this branch. */\nexport function buildPlanModePrompt(planFiles: string[]): string {\n\tif (!planFiles.length) return PLAN_MODE_PROMPT;\n\tconst listing = planFiles.map((path) => `- ${path}`).join(\"\\n\");\n\treturn `${PLAN_MODE_PROMPT}\n\nPlans already saved for this session (read them with the read tool):\n${listing}\nWhen revising a saved plan, read the latest one first, account for work already completed, and produce a revision that states what changed and why; set the revises parameter to the file name of the plan being revised.`;\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/extensions/plan/index.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAEX,YAAY,EAGZ,MAAM,gCAAgC,CAAC;AAmDxC,MAAM,CAAC,OAAO,UAAU,IAAI,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAmTnD","sourcesContent":["/**\n * plan — Claude-Code-style plan mode: read-only exploration and interview,\n * then a user-approved exit that saves the plan and optionally compacts.\n *\n * Shape:\n * - Entry (/plan or --plan) removes write-capable tools (edit/write/bash/\n * subagent) and activates exit_plan; a tool_call guard covers requests\n * already in flight before the tool-set change lands.\n * - The plan text travels as an exit_plan parameter, so the model needs no\n * write access to produce the plan file.\n * - \"Compact, then execute\" keeps plan mode active until compaction completes\n * (tools are restored in onComplete) and terminates the run via the tool\n * result, so there is no window where the model holds full context plus\n * write tools. The kickoff message embeds the plan itself — continuation\n * never depends on the compaction summary preserving a path.\n * - State is conversation-backed via custom entries and branch replay (same\n * pattern as todo): /reload, /tree, resume, and fork all restore.\n */\nimport { Text } from \"@earendil-works/pi-tui\";\nimport type {\n\tAgentToolResult,\n\tExtensionAPI,\n\tExtensionContext,\n\tToolExecutionMode,\n} from \"../../core/extensions/types.ts\";\nimport {\n\tbuildPlanModePrompt,\n\tEXIT_PLAN_CANCEL_HINT,\n\tEXIT_PLAN_MENU_OPTIONS,\n\tEXIT_PLAN_PROMPT_GUIDELINES,\n\tEXIT_PLAN_PROMPT_SNIPPET,\n\tEXIT_PLAN_TOOL_DESCRIPTION,\n\tEXIT_PLAN_TOOL_LABEL,\n\tEXIT_PLAN_TOOL_NAME,\n\tMENU_COMPACT_EXECUTE,\n\tMENU_EXECUTE,\n\tMENU_KEEP_PLANNING,\n\tPLAN_BLOCKED_TOOLS,\n\tPLAN_COMMAND_NAME,\n\tPLAN_EMBED_MAX_CHARS,\n\tPLAN_ENTRY_TYPE,\n\tPLAN_FLAG_NAME,\n\tPLAN_STATUS_KEY,\n} from \"./constants.ts\";\nimport { type ExitPlanDetails, type ExitPlanParams, ExitPlanParamsSchema } from \"./schema.ts\";\nimport {\n\tclonePlanState,\n\tdisposePlanSession,\n\tEMPTY_PLAN_STATE,\n\tgetPlanState,\n\treplacePlanState,\n\treplayPlanFromBranch,\n\tsetActivePlanSession,\n} from \"./state.ts\";\nimport { savePlanFile } from \"./storage.ts\";\nimport { formatApprovalSubtitle, formatExitPlanCall, formatExitPlanResult } from \"./view.ts\";\n\ninterface PendingCompact {\n\ttitle: string;\n\tplan: string;\n\tplanPath: string;\n}\n\nfunction buildKickoffMessage(pending: PendingCompact): string {\n\tconst truncated = pending.plan.length > PLAN_EMBED_MAX_CHARS;\n\tconst body = truncated\n\t\t? `${pending.plan.slice(0, PLAN_EMBED_MAX_CHARS)}\\n\\n[Plan truncated — read the full plan at ${pending.planPath}]`\n\t\t: pending.plan;\n\treturn `Execute the approved plan \"${pending.title}\" (saved at ${pending.planPath}).\n\n${body}\n\nStart now. Track multi-step work with the todo tool, and report back instead of improvising if the plan turns out to need revision.`;\n}\n\nexport default function plan(pi: ExtensionAPI): void {\n\tlet menuOpen = false;\n\tlet pendingCompact: PendingCompact | undefined;\n\n\tpi.registerFlag(PLAN_FLAG_NAME, {\n\t\tdescription: \"Start in plan mode (read-only planning)\",\n\t\ttype: \"boolean\",\n\t\tdefault: false,\n\t});\n\n\t/** Remove blocked tools, activate exit_plan, and return what was actually removed. */\n\tfunction applyPlanTools(): string[] {\n\t\tconst active = pi.getActiveTools();\n\t\tconst removed = active.filter((name) => PLAN_BLOCKED_TOOLS.has(name));\n\t\tpi.setActiveTools([...new Set([...active.filter((name) => !PLAN_BLOCKED_TOOLS.has(name)), EXIT_PLAN_TOOL_NAME])]);\n\t\treturn removed;\n\t}\n\n\t/** Inverse of applyPlanTools: drop exit_plan, re-add exactly what was removed. */\n\tfunction restoreTools(removedTools: string[]): void {\n\t\tconst active = pi.getActiveTools().filter((name) => name !== EXIT_PLAN_TOOL_NAME);\n\t\tpi.setActiveTools([...new Set([...active, ...removedTools])]);\n\t}\n\n\tfunction persist(): void {\n\t\tpi.appendEntry(PLAN_ENTRY_TYPE, getPlanState());\n\t}\n\n\tfunction updateStatus(ctx: ExtensionContext): void {\n\t\tconst state = getPlanState();\n\t\tctx.ui.setStatus(PLAN_STATUS_KEY, state.planning ? ctx.ui.theme.fg(\"accent\", \"Plan Mode\") : undefined);\n\t}\n\n\tfunction enterPlanMode(ctx: ExtensionContext): void {\n\t\tconst state = getPlanState();\n\t\tif (state.planning) return;\n\t\tconst removedTools = applyPlanTools();\n\t\treplacePlanState({ planning: true, removedTools, awaitingCompact: false, planFiles: state.planFiles });\n\t\tpersist();\n\t\tupdateStatus(ctx);\n\t\tctx.ui.notify(\"Plan mode enabled: edit, write, bash, and subagent are disabled.\", \"info\");\n\t}\n\n\tfunction exitPlanMode(ctx: ExtensionContext, notifyText?: string): void {\n\t\tconst state = getPlanState();\n\t\trestoreTools(state.removedTools);\n\t\treplacePlanState({ ...state, planning: false, awaitingCompact: false, removedTools: [] });\n\t\tpersist();\n\t\tupdateStatus(ctx);\n\t\tif (notifyText) ctx.ui.notify(notifyText, \"info\");\n\t}\n\n\t/** Save the approved plan, index it in state, and name the session if unnamed. */\n\tfunction saveApprovedPlan(ctx: ExtensionContext, params: ExitPlanParams, title: string): string {\n\t\tconst planPath = savePlanFile({\n\t\t\tsessionId: ctx.sessionManager.getSessionId(),\n\t\t\ttitle,\n\t\t\tplan: params.plan,\n\t\t\tcwd: ctx.cwd,\n\t\t\trevises: params.revises,\n\t\t});\n\t\tconst state = getPlanState();\n\t\treplacePlanState({ ...state, planFiles: [...state.planFiles, planPath] });\n\t\tif (!pi.getSessionName()) pi.setSessionName(title);\n\t\treturn planPath;\n\t}\n\n\t/**\n\t * Rebuild state from the current branch and reconcile the live tool set.\n\t * An interrupted compact-and-execute approval (restart, resume, branch\n\t * switch) degrades to the keep-context exit so the approval is honored.\n\t */\n\tfunction syncFromBranch(ctx: ExtensionContext, options?: { applyFlag?: boolean }): void {\n\t\tsetActivePlanSession(ctx.sessionManager.getSessionId());\n\t\tconst before = getPlanState();\n\t\tconst replayed = replayPlanFromBranch(ctx);\n\t\tconst next = replayed ?? clonePlanState(EMPTY_PLAN_STATE);\n\t\tif (!replayed && options?.applyFlag && pi.getFlag(PLAN_FLAG_NAME) === true) {\n\t\t\tnext.planning = true;\n\t\t}\n\n\t\tlet interruptedCompact = false;\n\t\tif (next.awaitingCompact) {\n\t\t\tnext.planning = false;\n\t\t\tnext.awaitingCompact = false;\n\t\t\tinterruptedCompact = true;\n\t\t\tpendingCompact = undefined;\n\t\t}\n\n\t\tif (next.planning) {\n\t\t\t// The fresh removal set is authoritative for the eventual restore:\n\t\t\t// the live tool set may differ from what the entry recorded.\n\t\t\tnext.removedTools = applyPlanTools();\n\t\t} else {\n\t\t\tif (pi.getActiveTools().includes(EXIT_PLAN_TOOL_NAME)) {\n\t\t\t\trestoreTools(before.planning ? before.removedTools : next.removedTools);\n\t\t\t}\n\t\t\tnext.removedTools = [];\n\t\t}\n\n\t\treplacePlanState(next);\n\t\tif (interruptedCompact) {\n\t\t\tpersist();\n\t\t\tctx.ui.notify(\"Pending compaction was interrupted; plan mode exited with context kept.\", \"warning\");\n\t\t}\n\t\tupdateStatus(ctx);\n\t}\n\n\tpi.registerTool({\n\t\tname: EXIT_PLAN_TOOL_NAME,\n\t\tlabel: EXIT_PLAN_TOOL_LABEL,\n\t\tdescription: EXIT_PLAN_TOOL_DESCRIPTION,\n\t\tpromptSnippet: EXIT_PLAN_PROMPT_SNIPPET,\n\t\tpromptGuidelines: EXIT_PLAN_PROMPT_GUIDELINES,\n\t\tparameters: ExitPlanParamsSchema,\n\t\texecutionMode: \"sequential\" as ToolExecutionMode,\n\n\t\tasync execute(_toolCallId, params, signal, _onUpdate, ctx): Promise<AgentToolResult<ExitPlanDetails>> {\n\t\t\tsetActivePlanSession(ctx.sessionManager.getSessionId());\n\t\t\tconst state = getPlanState();\n\t\t\tconst title = params.title.trim() || \"plan\";\n\n\t\t\tif (!state.planning || state.awaitingCompact) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{ type: \"text\", text: \"Plan mode is not awaiting an exit; continue with your current instructions.\" },\n\t\t\t\t\t],\n\t\t\t\t\tdetails: { decision: \"cancelled\", title },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (!ctx.hasUI) {\n\t\t\t\t// json/print: no way to ask, so honor the least surprising default —\n\t\t\t\t// exit with context kept. Documented as a headless-mode semantic.\n\t\t\t\tconst planPath = saveApprovedPlan(ctx, params, title);\n\t\t\t\texitPlanMode(ctx);\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\ttext: `Plan saved to ${planPath}. No interactive UI, so plan mode exited with context kept (authoritative: write tools are restored). Begin executing the plan now.`,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tdetails: { decision: \"execute\", title, planPath },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tlet choice: string | undefined;\n\t\t\tmenuOpen = true;\n\t\t\ttry {\n\t\t\t\tchoice = await ctx.ui.select(`Approve plan \"${title}\"?`, EXIT_PLAN_MENU_OPTIONS, {\n\t\t\t\t\tsignal,\n\t\t\t\t\tsubtitle: formatApprovalSubtitle(ctx),\n\t\t\t\t\tcancelHint: EXIT_PLAN_CANCEL_HINT,\n\t\t\t\t});\n\t\t\t} finally {\n\t\t\t\tmenuOpen = false;\n\t\t\t}\n\n\t\t\tif (signal?.aborted) {\n\t\t\t\t// Throwing lets Pi mark the result as an error, matching the todo convention.\n\t\t\t\tthrow new Error(\"Plan approval was interrupted; plan mode is still active.\");\n\t\t\t}\n\n\t\t\tif (choice === MENU_EXECUTE) {\n\t\t\t\tconst planPath = saveApprovedPlan(ctx, params, title);\n\t\t\t\texitPlanMode(ctx, \"Plan approved: executing with full context.\");\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\ttext: `Plan approved and saved to ${planPath}. Plan mode has ended and write tools are restored (authoritative — overrides the plan-mode system prompt block). Begin executing the plan now; track multi-step work with the todo tool.`,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tdetails: { decision: \"execute\", title, planPath },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (choice === MENU_COMPACT_EXECUTE) {\n\t\t\t\tconst planPath = saveApprovedPlan(ctx, params, title);\n\t\t\t\t// Stay in plan mode (tools restricted) until compaction completes;\n\t\t\t\t// terminate ends the run so agent_settled can start the compaction.\n\t\t\t\treplacePlanState({ ...getPlanState(), awaitingCompact: true });\n\t\t\t\tpersist();\n\t\t\t\tpendingCompact = { title, plan: params.plan, planPath };\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\ttext: `Plan approved and saved to ${planPath}. The context will now be compacted; stop here — execution restarts automatically after compaction.`,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tdetails: { decision: \"compactAndExecute\", title, planPath },\n\t\t\t\t\tterminate: true,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t// Explicit \"Keep planning\", Esc, or timeout: the only side-effect-free\n\t\t\t// choice, so any dismissal maps here.\n\t\t\tlet feedback: string | undefined;\n\t\t\tif (choice === MENU_KEEP_PLANNING) {\n\t\t\t\tfeedback =\n\t\t\t\t\tctx.mode === \"tui\"\n\t\t\t\t\t\t? await ctx.ui.editor(\"Feedback for the plan (optional):\", \"\")\n\t\t\t\t\t\t: await ctx.ui.input(\"Feedback for the plan (optional):\");\n\t\t\t}\n\t\t\tconst trimmedFeedback = feedback?.trim();\n\t\t\treturn {\n\t\t\t\tcontent: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\ttext: trimmedFeedback\n\t\t\t\t\t\t\t? `The user wants to keep planning. Feedback:\\n${trimmedFeedback}`\n\t\t\t\t\t\t\t: \"The user wants to keep planning. Ask what should change, or keep refining the open points.\",\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\tdetails: { decision: \"keepPlanning\", title },\n\t\t\t};\n\t\t},\n\n\t\trenderCall(args, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(formatExitPlanCall(args, theme, context.expanded));\n\t\t\treturn text;\n\t\t},\n\n\t\trenderResult(result, _options, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(formatExitPlanResult(result, theme));\n\t\t\treturn text;\n\t\t},\n\t});\n\n\tpi.registerCommand(PLAN_COMMAND_NAME, {\n\t\tdescription: \"Toggle plan mode (read-only planning with user-approved exit)\",\n\t\thandler: async (_args, ctx) => {\n\t\t\tsetActivePlanSession(ctx.sessionManager.getSessionId());\n\t\t\tconst state = getPlanState();\n\t\t\tif (menuOpen) {\n\t\t\t\tctx.ui.notify(\"The exit_plan approval menu is open — answer it first.\", \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (state.awaitingCompact) {\n\t\t\t\tctx.ui.notify(\"Waiting for compaction to finish before leaving plan mode.\", \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (state.planning) {\n\t\t\t\texitPlanMode(ctx, \"Plan mode disabled: full tool access restored.\");\n\t\t\t} else {\n\t\t\t\tenterPlanMode(ctx);\n\t\t\t}\n\t\t},\n\t});\n\n\t// Defense in depth: the active-tools change lands on the next LLM request,\n\t// so a request already in flight when plan mode toggled can still carry\n\t// blocked tool calls.\n\tpi.on(\"tool_call\", async (event, ctx) => {\n\t\tsetActivePlanSession(ctx.sessionManager.getSessionId());\n\t\tconst state = getPlanState();\n\t\tif (!state.planning || !PLAN_BLOCKED_TOOLS.has(event.toolName)) return;\n\t\treturn {\n\t\t\tblock: true,\n\t\t\treason: `Plan mode: ${event.toolName} is disabled. Keep exploring with read-only tools, or call ${EXIT_PLAN_TOOL_NAME} to submit the plan for approval.`,\n\t\t};\n\t});\n\n\tpi.on(\"before_agent_start\", async (event, ctx) => {\n\t\tsetActivePlanSession(ctx.sessionManager.getSessionId());\n\t\tconst state = getPlanState();\n\t\tif (!state.planning) return;\n\t\treturn { systemPrompt: `${event.systemPrompt}\\n\\n${buildPlanModePrompt(state.planFiles)}` };\n\t});\n\n\t// agent_settled fires once per fully settled run (agent_end can repeat on\n\t// retries and queued continuations), so the approved compaction starts here.\n\tpi.on(\"agent_settled\", async (_event, ctx) => {\n\t\tsetActivePlanSession(ctx.sessionManager.getSessionId());\n\t\tif (!getPlanState().awaitingCompact || !pendingCompact) return;\n\t\tconst pending = pendingCompact;\n\t\tpendingCompact = undefined;\n\t\tctx.compact({\n\t\t\tcustomInstructions: `The user approved an implementation plan saved at ${pending.planPath}. Preserve decisions, constraints, and rejected alternatives from the planning discussion that are not already written in the plan file.`,\n\t\t\tonComplete: () => {\n\t\t\t\texitPlanMode(ctx, \"Context compacted; executing the plan.\");\n\t\t\t\tpi.sendUserMessage(buildKickoffMessage(pending));\n\t\t\t},\n\t\t\tonError: (error) => {\n\t\t\t\t// Never leave the user stuck read-only: degrade to keep-context execution.\n\t\t\t\texitPlanMode(ctx, `Compaction failed (${error.message}); executing with full context instead.`);\n\t\t\t\tpi.sendUserMessage(buildKickoffMessage(pending));\n\t\t\t},\n\t\t});\n\t});\n\n\tpi.on(\"session_start\", async (_event, ctx) => {\n\t\tsyncFromBranch(ctx, { applyFlag: true });\n\t});\n\n\tpi.on(\"session_tree\", async (_event, ctx) => {\n\t\tsyncFromBranch(ctx);\n\t});\n\n\tpi.on(\"session_shutdown\", async (_event, ctx) => {\n\t\tpendingCompact = undefined;\n\t\tdisposePlanSession(ctx.sessionManager.getSessionId());\n\t});\n}\n"]}
|