shariq-pi-extensions 0.3.7 → 0.5.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/README.md +13 -15
- package/docs/ARCHITECTURE.md +32 -27
- package/docs/DEVELOPMENT.md +25 -48
- package/docs/EXTENSIONS.md +16 -35
- package/extensions/ask-user/README.md +0 -4
- package/extensions/ask-user/index.ts +68 -40
- package/extensions/background-terminals/README.md +9 -13
- package/extensions/background-terminals/index.ts +33 -31
- package/extensions/background-terminals/src/manager.ts +32 -7
- package/extensions/background-terminals/src/pty.ts +25 -8
- package/extensions/background-terminals/src/ui.ts +6 -15
- package/extensions/context-usage/index.ts +50 -22
- package/extensions/firecrawl-web/README.md +5 -8
- package/extensions/firecrawl-web/auth.ts +1 -1
- package/extensions/firecrawl-web/client.ts +15 -5
- package/extensions/firecrawl-web/index.ts +16 -17
- package/extensions/firecrawl-web/output.ts +4 -1
- package/extensions/goal/README.md +1 -5
- package/extensions/goal/extension.ts +65 -45
- package/extensions/input-mode/index.ts +10 -2
- package/extensions/performance-status/README.md +1 -1
- package/extensions/performance-status/index.ts +29 -20
- package/extensions/shared/README.md +12 -16
- package/extensions/shared/activity-dock.ts +33 -22
- package/extensions/shared/child-session.ts +14 -6
- package/extensions/shared/model-picker.ts +2 -2
- package/extensions/shared/settlement-delivery.ts +7 -1
- package/extensions/shared/tool-call-timeout.ts +8 -1
- package/extensions/shared/tool-card.ts +5 -3
- package/extensions/shell-shortcuts/README.md +0 -4
- package/extensions/smart-compaction/README.md +49 -42
- package/extensions/smart-compaction/engine.ts +39 -21
- package/extensions/smart-compaction/index.ts +17 -3
- package/extensions/smart-compaction/prompt.ts +1 -1
- package/extensions/subagents/README.md +55 -42
- package/extensions/subagents/index.ts +114 -81
- package/extensions/subagents/src/backends/pi.ts +53 -136
- package/extensions/subagents/src/catalog.ts +74 -4
- package/extensions/subagents/src/config.ts +13 -8
- package/extensions/subagents/src/manager.ts +4 -29
- package/extensions/subagents/src/prompt.ts +19 -27
- package/extensions/subagents/src/ui/takeover.ts +1 -1
- package/extensions/task-list/README.md +30 -82
- package/extensions/task-list/index.ts +44 -84
- package/extensions/task-list/state.ts +19 -3
- package/extensions/web-fetch/README.md +3 -1
- package/extensions/web-fetch/index.ts +258 -268
- package/package.json +11 -9
- package/skills/background-terminals/SKILL.md +16 -26
- package/skills/subagents/SKILL.md +40 -53
- package/extensions/shared/activity-status.ts +0 -31
- package/extensions/shared/dashboard-state.ts +0 -55
- package/extensions/shared/redaction.ts +0 -55
- package/extensions/subagents/src/format.ts +0 -74
- package/extensions/subagents/src/storage.ts +0 -65
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Shariq's Pi extensions
|
|
2
2
|
|
|
3
|
-
Cross-platform extensions and
|
|
3
|
+
Cross-platform extensions and skills for the [Pi](https://github.com/earendil-works/pi) coding agent. Credentials and runtime state stay on each machine and are never bundled with the package.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -20,24 +20,22 @@ Run `/reload` in an existing Pi session after installation; new sessions load th
|
|
|
20
20
|
|
|
21
21
|
## Included extensions
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
-
|
|
23
|
+
- structured user questions (`ask_user`)
|
|
24
|
+
- persistent goals with evidence checklists and budgets
|
|
25
|
+
- a task list for multi-deliverable work, with a live progress dock
|
|
26
|
+
- Pi subagents with profiles, worktree isolation, and a takeover dashboard
|
|
27
|
+
- Smart Compaction: structured checkpoints with file and worktree state
|
|
26
28
|
- managed background terminals
|
|
27
|
-
- Firecrawl web and developer search
|
|
28
|
-
- persistent task goals
|
|
29
|
-
- branch-safe model-maintained task lists with live progress UI and compaction continuity
|
|
30
|
-
- configurable steer, interrupt, or follow-up input behavior
|
|
31
|
-
- Smart Compaction with high-fidelity checkpointing, delta-merging, and custom model routing
|
|
32
|
-
- per-response TPS, TTFT, elapsed-time, and output status
|
|
33
|
-
- Context Usage display
|
|
34
|
-
- small shell and command shortcuts
|
|
35
|
-
- Pi subagents
|
|
29
|
+
- Firecrawl web search, scraping, and developer search
|
|
36
30
|
- lightweight URL fetching
|
|
31
|
+
- Context Usage startup card
|
|
32
|
+
- per-response TPS, time-to-first-token, and output status
|
|
33
|
+
- configurable steer, interrupt, or follow-up Enter behavior
|
|
34
|
+
- `/exit` as an alias for `/quit`
|
|
37
35
|
|
|
38
|
-
The package also
|
|
36
|
+
The package also ships the `ember-warm-dark` theme and the `background-terminals` and `subagents` skills. Pi loads the skills from the installed package; copying them into `<agent-dir>/skills` would leave stale duplicates after removal.
|
|
39
37
|
|
|
40
|
-
See [`docs/EXTENSIONS.md`](docs/EXTENSIONS.md) for commands, tools, configuration, and external dependencies.
|
|
38
|
+
The suite is developed and tested against Pi 1.0. See [`docs/EXTENSIONS.md`](docs/EXTENSIONS.md) for commands, tools, configuration, and external dependencies.
|
|
41
39
|
|
|
42
40
|
## Enable or disable extensions
|
|
43
41
|
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -1,53 +1,58 @@
|
|
|
1
1
|
# Architecture
|
|
2
2
|
|
|
3
|
-
Last verified: 2026-09-04
|
|
4
|
-
|
|
5
3
|
## Package boundary
|
|
6
4
|
|
|
7
|
-
The repository
|
|
5
|
+
The repository is one Pi package. Every extension is declared on its own in `package.json#pi.extensions`, so Pi loads only intended entrypoints and `pi config` can enable or disable each one.
|
|
8
6
|
|
|
9
7
|
## Source layout
|
|
10
8
|
|
|
11
9
|
```text
|
|
12
|
-
extensions/<name>/index.ts
|
|
13
|
-
extensions/<name>/src/ extension-owned implementation
|
|
14
|
-
extensions/shared/
|
|
15
|
-
skills/ operating guidance
|
|
16
|
-
|
|
17
|
-
|
|
10
|
+
extensions/<name>/index.ts extension entrypoint
|
|
11
|
+
extensions/<name>/src/ extension-owned implementation, when large enough to split
|
|
12
|
+
extensions/shared/ helpers used by more than one extension
|
|
13
|
+
skills/ operating guidance shipped with the async extensions
|
|
14
|
+
themes/ TUI themes
|
|
15
|
+
docs/ package-level documentation
|
|
16
|
+
scripts/ test runner and package validation
|
|
18
17
|
```
|
|
19
18
|
|
|
20
|
-
|
|
19
|
+
Entrypoints register tools, commands, events, renderers, and UI. Code moves to `extensions/shared` only when several extensions use the same contract. Two shared pieces tie the async extensions together:
|
|
21
20
|
|
|
22
|
-
|
|
21
|
+
- the **Active work** dock merges live rows from Task List, Background Terminals, and Subagents into one bounded widget;
|
|
22
|
+
- settlement delivery gives each Pi session one private queue for terminal and subagent results, delivered at the parent's idle edge.
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
The background-terminal and subagent skills ship beside their extensions, so a new machine gets the lifecycle guidance together with the tools.
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
## Model-facing text
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
Tool descriptions, parameter descriptions, prompt snippets, and prompt guidelines are sent with every model request, and guidelines become system-prompt rules. They cost tokens on every turn and are read literally. Each rule has one home: a tool's description says what it does and its contract, one guideline at most says when to use it, and longer operating guidance belongs in a skill. Extensions do not inject reminders that push the model toward a tool.
|
|
29
|
+
|
|
30
|
+
## Dependency model
|
|
30
31
|
|
|
31
|
-
|
|
32
|
+
Pi packages (`@earendil-works/pi-*`) and `typebox` are optional peer dependencies; Pi provides them at runtime. Pinned development dependencies give typechecking and tests a reproducible API surface. Third-party runtime modules are normal dependencies:
|
|
33
|
+
|
|
34
|
+
- `@lydell/node-pty` runs background terminals;
|
|
35
|
+
- `effect` runs the subagent lifecycle.
|
|
36
|
+
|
|
37
|
+
`bun.lock` is the lockfile CI installs from. `package-lock.json` is kept in sync for npm-based installs: Pi installs Git packages with `npm install`.
|
|
32
38
|
|
|
33
39
|
## Runtime state
|
|
34
40
|
|
|
35
|
-
Installed package directories are treated as
|
|
41
|
+
Installed package directories are treated as read-only. Writable state goes through Pi's APIs:
|
|
36
42
|
|
|
37
|
-
- Smart Compaction
|
|
38
|
-
- Input mode
|
|
39
|
-
- Subagent configuration and
|
|
40
|
-
- Goal and Task List state: branch-local Pi session entries
|
|
41
|
-
-
|
|
43
|
+
- Smart Compaction settings: `<agent-dir>/smart-compaction.json`
|
|
44
|
+
- Input mode setting: `<agent-dir>/input-mode.json`
|
|
45
|
+
- Subagent configuration, catalog, run snapshots, and worktrees: under `getAgentDir()`
|
|
46
|
+
- Goal and Task List state: branch-local Pi session entries
|
|
47
|
+
- Project configuration: under Pi's `CONFIG_DIR_NAME`
|
|
48
|
+
- Terminal logs and oversized web responses: the system temporary directory
|
|
42
49
|
|
|
43
|
-
Credentials
|
|
50
|
+
Credentials stay in Pi auth storage, environment variables, service credential stores, or ignored machine-local files.
|
|
44
51
|
|
|
45
52
|
## Portability
|
|
46
53
|
|
|
47
|
-
|
|
54
|
+
Paths are built with `node:path`, `getAgentDir()`, `CONFIG_DIR_NAME`, and `os.homedir()`, with platform branches only where a third-party credential location differs. No code assumes `/Users`, `/home`, Homebrew, a shell, or the package's install path. The PTY layer uses Bun's terminal API under Bun and `@lydell/node-pty` under Node.
|
|
48
55
|
|
|
49
56
|
## Loading and updates
|
|
50
57
|
|
|
51
|
-
Pi manages npm and Git
|
|
52
|
-
|
|
53
|
-
`pi config` controls every individual package resource. Disabling an extension prevents its factory from loading after reload.
|
|
58
|
+
Pi manages npm and Git installs and may replace their contents on update, so user state is never written beside extension source. Disabling an extension in `pi config` stops its factory from loading after `/reload`.
|
package/docs/DEVELOPMENT.md
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
# Development
|
|
2
|
-
|
|
3
|
-
Last verified: 2026-09-04
|
|
1
|
+
# Development
|
|
4
2
|
|
|
5
3
|
## Requirements
|
|
6
4
|
|
|
7
5
|
- mise
|
|
8
|
-
- Pi
|
|
9
|
-
- platform build support
|
|
6
|
+
- Pi at or above the version in `devDependencies`
|
|
7
|
+
- platform build support for `@lydell/node-pty`
|
|
10
8
|
|
|
11
|
-
Install the
|
|
9
|
+
Install the pinned toolchain and dependencies:
|
|
12
10
|
|
|
13
11
|
```bash
|
|
14
12
|
mise install --locked
|
|
@@ -17,66 +15,45 @@ mise exec --locked -- bun install
|
|
|
17
15
|
|
|
18
16
|
## Validation
|
|
19
17
|
|
|
20
|
-
Validate the complete suite and inspect the package payload:
|
|
21
|
-
|
|
22
18
|
```bash
|
|
23
19
|
mise exec --locked -- bun run validate
|
|
24
20
|
mise exec --locked -- bun run pack:inspect
|
|
25
21
|
```
|
|
26
22
|
|
|
27
|
-
|
|
23
|
+
`validate` runs the TypeScript check, every `*.test.ts` file under `extensions/` with Node's test runner, and `scripts/validate-package.mjs`. The package check confirms that declared extensions and skills exist, are sorted, have READMEs and tests, and that the npm payload contains no credentials, `.env` files, caches, databases, sessions, logs, or first-party tests.
|
|
24
|
+
|
|
25
|
+
Read the `pack:inspect` file list before committing a package change.
|
|
28
26
|
|
|
29
27
|
## Adding an extension
|
|
30
28
|
|
|
31
|
-
1.
|
|
32
|
-
2.
|
|
33
|
-
3. Put
|
|
34
|
-
4. Add the
|
|
35
|
-
5.
|
|
36
|
-
6.
|
|
29
|
+
1. Create `extensions/<name>/index.ts` and `extensions/<name>/README.md`.
|
|
30
|
+
2. Import extension-owned code relatively, and `extensions/shared` only for behavior another extension already shares.
|
|
31
|
+
3. Put third-party runtime modules in root `dependencies`.
|
|
32
|
+
4. Add the entrypoint to `package.json#pi.extensions` in sorted order.
|
|
33
|
+
5. For each tool, choose its `exposure` and `annotations` (see [Tool exposure](EXTENSIONS.md#tool-exposure)) and keep its model-facing text short (see [Model-facing text](ARCHITECTURE.md#model-facing-text)).
|
|
34
|
+
6. Add focused tests and a section in `docs/EXTENSIONS.md`.
|
|
35
|
+
7. Run both validations.
|
|
37
36
|
|
|
38
|
-
Do not add nested package manifests or lockfiles
|
|
37
|
+
Do not add nested package manifests or lockfiles under `extensions/`.
|
|
39
38
|
|
|
40
39
|
## Runtime configuration
|
|
41
40
|
|
|
42
|
-
Use `getAgentDir()` for user-level state and `CONFIG_DIR_NAME` for project-level Pi configuration.
|
|
43
|
-
|
|
44
|
-
Keep secrets out of examples and fixtures. Tests should inject temporary roots, fake environment objects, or local test servers rather than reading real credentials.
|
|
41
|
+
Use `getAgentDir()` for user-level state and `CONFIG_DIR_NAME` for project-level Pi configuration. Never derive a writable location from `import.meta.url` or the install directory. Tests use temporary roots, fake environments, or local servers, never real credentials.
|
|
45
42
|
|
|
46
43
|
## Skills paired with extensions
|
|
47
44
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
When any paired skill changes, validate its structure and keep its behavior aligned with the corresponding extension tools.
|
|
51
|
-
|
|
52
|
-
## Current-machine cutover
|
|
45
|
+
`package.json#pi.skills` declares `skills/background-terminals` and `skills/subagents`, and Pi loads them from the installed package. Do not copy them into the agent directory. When a paired extension's tools or delivery behavior change, update its skill in the same change.
|
|
53
46
|
|
|
54
|
-
|
|
47
|
+
## Upgrading Pi
|
|
55
48
|
|
|
56
|
-
1.
|
|
57
|
-
2.
|
|
58
|
-
3.
|
|
59
|
-
4. Move the old paired skill directories out of active discovery; the Git package supplies them.
|
|
60
|
-
5. Run `/reload` once.
|
|
61
|
-
6. Verify package provenance through `pi list`, then check representative commands, tools, providers, and both packaged skills.
|
|
49
|
+
1. Raise the `@earendil-works/pi-*` development dependencies, then run `mise exec --locked -- bun install` and `npm install --package-lock-only --ignore-scripts` to refresh both lockfiles.
|
|
50
|
+
2. Read the Pi changelog for extension API changes, especially tool, event, and TUI changes.
|
|
51
|
+
3. Run both validations, then load the working tree in Pi (`pi -ne -e ./extensions/<name>/index.ts`) to check commands and overlays that tests do not render.
|
|
62
52
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
## Distribution
|
|
66
|
-
|
|
67
|
-
GitHub hosts the public source and npm distributes versioned releases. Push validated changes to `main` under the repository's normal Git policy.
|
|
68
|
-
|
|
69
|
-
```bash
|
|
70
|
-
git push origin main
|
|
71
|
-
gh repo view shariqriazz/shariq-pi-extensions --json visibility,url
|
|
72
|
-
```
|
|
53
|
+
## Releases
|
|
73
54
|
|
|
74
|
-
|
|
55
|
+
GitHub hosts the source and npm distributes versioned releases. Any functional change bumps `package.json#version` (semantic versioning). Pushing to `main` runs `.github/workflows/ci.yml` and `.github/workflows/publish-npm.yml`; the publish workflow validates and publishes with npm trusted publishing and provenance whenever the version is not yet on npm.
|
|
75
56
|
|
|
76
|
-
|
|
77
|
-
2. Run `mise exec --locked -- bun install` and `mise exec --locked -- bun run validate`.
|
|
78
|
-
3. Inspect `mise exec --locked -- bun run pack:inspect` for secrets, runtime state, and accidental files.
|
|
79
|
-
4. Run the `Publish npm package` workflow. npm trusted publishing authenticates it through GitHub OIDC and records provenance without a long-lived token.
|
|
80
|
-
5. Verify the registry version and install it with `pi install npm:shariq-pi-extensions`.
|
|
57
|
+
After the publish run succeeds, confirm the release with `npm view shariq-pi-extensions version` and update installed copies with `pi update --extensions`.
|
|
81
58
|
|
|
82
|
-
|
|
59
|
+
An npm install follows published versions. An unpinned Git install follows `main`; pin a tag or commit to keep a machine on a known revision.
|
package/docs/EXTENSIONS.md
CHANGED
|
@@ -1,80 +1,61 @@
|
|
|
1
1
|
# Extension catalog
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Each extension's README covers its full behavior. This page is the short version.
|
|
4
4
|
|
|
5
5
|
## Agent workflow
|
|
6
6
|
|
|
7
7
|
### [Structured questions](../extensions/ask-user/README.md)
|
|
8
8
|
|
|
9
|
-
`ask_user`
|
|
9
|
+
`ask_user` shows one multiple-choice decision, with an optional written answer, when a missing user choice blocks safe progress. Outside the TUI it reports that no interactive answer is available.
|
|
10
10
|
|
|
11
11
|
### [Goals](../extensions/goal/README.md)
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Persistent, branch-safe objectives with an evidence checklist, token budgets, pause and resume, automatic continuation, and strict completion and blocker gates. `/goal` opens the control center; the model uses `create_goal`, `get_goal`, `update_goal_progress`, and `update_goal`. Goals are created only on explicit request.
|
|
14
14
|
|
|
15
15
|
### [Task List](../extensions/task-list/README.md)
|
|
16
16
|
|
|
17
|
-
`task_list`
|
|
18
|
-
|
|
19
|
-
State is stored in Pi session entries and reconstructed on resume, reload, and tree navigation. Active work is re-injected when compaction removes the latest snapshot from model context. `/tasks` opens the interactive dashboard and direct editor; the compact live widget auto-hides after all work finishes. Subagents receive the same tool under every capability policy, but each child maintains its own session-local list rather than changing the parent's list.
|
|
17
|
+
`task_list` keeps a branch-safe checklist for requests with several separate deliverables, long multi-phase work, or an explicit request for a plan. Single-objective work goes ahead without a list, and the extension never prompts the model to create one. When compaction removes the latest list from context, the unfinished items are added back. `/tasks` opens the dashboard and editor. Task List is independent of Goals and can run alongside one.
|
|
20
18
|
|
|
21
19
|
### [Subagents](../extensions/subagents/README.md)
|
|
22
20
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
The extension supplies tools including `spawn_agent`, `task`, `check_agent`, `list_agents`, `wait_agent`, `send_message`, `close_agent`, `reply_question`, and `apply_agent_changes`. These lifecycle operations render as compact expandable main-chat cards; only active manager entries contribute to the bounded Active work dock, while workflow progress is intentionally omitted from the footer so persisted history cannot inflate or duplicate live status. Cancellation and reload interruption use a distinct `cancelled` state rather than `error`. All completed and cancelled production subagent snapshots and transcripts persist across Pi restarts (`<agent-dir>/subagents/runs/`), allowing `resume_from` to resume completed workers at any point. Interruption immediately cascades across all child fibers in `<10ms`. Child settlement stays in a private extension queue while the parent is active, then starts one custom-result turn at Pi's safe idle edge with the summary guaranteed in model context and never rendered as user-authored or follow-up input; status tools are for explicit inspection, not waiting.
|
|
21
|
+
Flat Pi child agents with profiles, capability policies, context forks, resumable sessions, optional git worktree isolation, peer messaging, and a takeover dashboard. Children cannot start agents. The model delegates only when the user asks. Results are delivered as custom messages that start a parent turn when the parent is idle. Configuration lives in `<agent-dir>/subagents.json`, with trusted-project overrides in `.pi/subagents.json`.
|
|
26
22
|
|
|
27
23
|
### [Smart Compaction](../extensions/smart-compaction/README.md)
|
|
28
24
|
|
|
29
|
-
Replaces
|
|
30
|
-
|
|
31
|
-
Key capabilities include:
|
|
32
|
-
- **Fail-Closed Validation**: Strictly enforces `stopReason === "stop"`, rejects tool calls and length-truncated output, and requires all 6 section headings.
|
|
33
|
-
- **Deterministic State Ledger (Schema v3)**: Machine-readable tracking of `touchedReadFiles`, `touchedModifiedFiles`, and asynchronous NUL-delimited Git worktree parsing capturing `activeDirtyFiles`, staged diffs, unstaged diffs, and untracked file previews in `CompactionEntry.details` and `<uncommitted-diff>` context.
|
|
34
|
-
- **Lockfile & Bundle Diff Filtering**: Automatically isolates `package-lock.json`, `Cargo.lock`, `yarn.lock`, and minified assets from raw diffs to preserve token budgets for source code logic.
|
|
35
|
-
- **Active Background Terminal Awareness**: Automatically identifies running background processes and records them under `<active-background-processes>` to prevent duplicate server launches.
|
|
36
|
-
- **Hierarchical Delta-Merging & Protected-Fact Validation**: Carries forward goals and constraints, extracts negative instructions and opaque identifiers into an explicit protected-facts ledger, and rejects summaries that omit them.
|
|
37
|
-
- **Closed-Record Execution Guard & Zero-Chatter Resumption**: Marks historical tasks as closed milestones to prevent accidental re-execution of completed actions, and injects a resumption directive to immediately execute the next step without conversational chatter.
|
|
38
|
-
- **Classified Retry Ladder**: Distinguishes non-retryable fatal auth/quota errors from transient reasoning/length limits (retrying with reasoning off) and falling back to the active session model.
|
|
39
|
-
- **Tool-Aware Bounded Serialization**: Cleans terminal control/progress noise, preserves line-safe head and tail excerpts, prioritizes failures and mutations, and bounds large write/edit arguments without modifying active session history.
|
|
40
|
-
- **Selectable Threshold Policy**: Supports percentage, hard-token, and hybrid safeguards; hybrid defaults to the earlier of 95% or 400,000 tokens without changing model catalogue context windows. Pi's native reserve-token threshold may still compact earlier.
|
|
41
|
-
- **Engineering-State Coverage & Telemetry**: Keeps a complete changed-file inventory before allocating bounded per-patch excerpts, and records source/serialized/summary sizes, retries, and duration in compaction details.
|
|
42
|
-
- **Custom Model Routing**: `/compaction-model` selects any custom compaction model (e.g. `anthropic/claude-3-5-sonnet`, `openai/gpt-4o`) or defaults to inheriting the active session model (`inherit`). `/smart-compaction` manages settings stored in `<agent-dir>/smart-compaction.json`.
|
|
25
|
+
Replaces Pi's compaction summary with a six-section checkpoint plus computed file, worktree, and background-terminal state. Summaries that are truncated, incomplete, or drop protected identifiers are rejected or repaired; if every retry fails, compaction is cancelled instead of falling back to a weaker summary. An optional percent, hard, or hybrid threshold (default: 95% or 400,000 tokens, whichever is first) adds an upper bound to Pi's own trigger. `/compaction-model` and `/smart-compaction` manage `<agent-dir>/smart-compaction.json`.
|
|
43
26
|
|
|
44
27
|
### [Background terminals](../extensions/background-terminals/README.md)
|
|
45
28
|
|
|
46
|
-
Managed PTYs
|
|
47
|
-
|
|
48
|
-
Its tools are `start_terminal`, `read_terminal`, `write_terminal`, `list_terminals`, and `stop_terminal`. All use compact expandable main-chat cards, while running PTYs share the bounded Active work dock with elapsed time and a latest-output preview. A model-started terminal keeps completion or failure in a private extension queue while the parent is active, then starts one custom-result turn at Pi's safe idle edge with bounded output guaranteed in model context and never rendered as user-authored or follow-up input. Reading a terminal does not suppress that delivery; agents should inspect only for explicit progress requests or immediate interaction.
|
|
29
|
+
Managed PTYs for servers, watchers, long builds, and interactive programs: up to eight at once, bounded output in memory, private size-limited logs, and process-group shutdown on reload or exit. The model uses `start_terminal`, `read_terminal`, `write_terminal`, `list_terminals`, and `stop_terminal`; `/term` and `/ps` open the control center. When a model-started terminal exits, its result arrives as a custom message instead of requiring the model to poll.
|
|
49
30
|
|
|
50
31
|
## Web access
|
|
51
32
|
|
|
52
33
|
### [Firecrawl web](../extensions/firecrawl-web/README.md)
|
|
53
34
|
|
|
54
|
-
`web_search`
|
|
35
|
+
`web_search` finds web, news, image, GitHub, research, and PDF results. `web_scrape` extracts rendered or JavaScript-heavy pages. `dev_search` searches the Firecrawl Developer Index of technical docs, GitHub issues, pull requests, and READMEs. Credentials come from the environment, `<agent-dir>/.env`, or the Firecrawl CLI login.
|
|
55
36
|
|
|
56
37
|
### [Web fetch](../extensions/web-fetch/README.md)
|
|
57
38
|
|
|
58
|
-
`web_fetch` retrieves
|
|
39
|
+
`web_fetch` retrieves one known HTTP or HTTPS URL as Markdown, text, or HTML, with time and size limits and no Firecrawl credits. Local and private network addresses are blocked unless `PI_WEB_FETCH_ALLOW_PRIVATE=1` is set.
|
|
59
40
|
|
|
60
|
-
## Interface
|
|
41
|
+
## Interface
|
|
61
42
|
|
|
62
43
|
### [Context Usage](../extensions/context-usage/README.md)
|
|
63
44
|
|
|
64
|
-
|
|
45
|
+
A startup card estimating how much of the context window the system prompt, `AGENTS.md` files, skill index, tools, and session take. `Ctrl+O` or `/context-usage` cycles summary, compact, and expanded views.
|
|
65
46
|
|
|
66
47
|
### [Performance status](../extensions/performance-status/README.md)
|
|
67
48
|
|
|
68
|
-
|
|
49
|
+
A status row for the current response with TPS, time to first token, elapsed time, output tokens, and the running tool. Live values are estimates; it clears eight seconds after the response ends.
|
|
69
50
|
|
|
70
51
|
### [Input mode](../extensions/input-mode/README.md)
|
|
71
52
|
|
|
72
|
-
`/input-mode`
|
|
53
|
+
`/input-mode` sets what Enter does while the agent runs: `steer` (default) queues the message before the next model step, `interrupt` aborts the run and resends the message, and `follow-up` waits until the run ends. The setting lives in `<agent-dir>/input-mode.json`.
|
|
73
54
|
|
|
74
55
|
### [Shell shortcuts](../extensions/shell-shortcuts/README.md)
|
|
75
56
|
|
|
76
|
-
Adds `/exit` as an alias for
|
|
57
|
+
Adds `/exit` as an alias for `/quit`.
|
|
77
58
|
|
|
78
|
-
##
|
|
59
|
+
## Tool exposure
|
|
79
60
|
|
|
80
|
-
|
|
61
|
+
Pi 1.0 lets each tool choose how the model reaches it. Tools that ask the user, manage lifecycles, or change session state (questions, goals, task list, terminals, subagents) use `model-only` exposure: they stay declared to the model even with `codemode.mode: "only"`, and codemode scripts cannot call them. The read-only web tools keep the default `direct` exposure so scripts can call them in parallel. Read-only tools also declare `readOnlyHint` annotations for permission extensions.
|
|
@@ -15,7 +15,3 @@ The tool is deliberately narrow: agents should first inspect available context a
|
|
|
15
15
|
- Escape or Ctrl+C dismisses the question without inventing an answer.
|
|
16
16
|
- In non-TUI modes, returns an unavailable result so the agent can ask plainly only if still blocked.
|
|
17
17
|
- Questions, labels, descriptions, and custom answers are bounded and sanitized before display.
|
|
18
|
-
|
|
19
|
-
## Validation
|
|
20
|
-
|
|
21
|
-
From the repository root, run `npm run validate`.
|
|
@@ -29,12 +29,12 @@ const OptionSchema = Type.Object({
|
|
|
29
29
|
label: Type.String({
|
|
30
30
|
minLength: 1,
|
|
31
31
|
maxLength: 200,
|
|
32
|
-
description: "
|
|
32
|
+
description: "Short answer shown to the user",
|
|
33
33
|
}),
|
|
34
34
|
description: Type.Optional(
|
|
35
35
|
Type.String({
|
|
36
36
|
maxLength: 500,
|
|
37
|
-
description: "
|
|
37
|
+
description: "Consequence or clarification",
|
|
38
38
|
}),
|
|
39
39
|
),
|
|
40
40
|
});
|
|
@@ -43,12 +43,12 @@ const AskUserParameters = Type.Object({
|
|
|
43
43
|
question: Type.String({
|
|
44
44
|
minLength: 1,
|
|
45
45
|
maxLength: 4_000,
|
|
46
|
-
description: "
|
|
46
|
+
description: "The one decision the user needs to make",
|
|
47
47
|
}),
|
|
48
48
|
options: Type.Array(OptionSchema, {
|
|
49
49
|
minItems: MIN_OPTIONS,
|
|
50
50
|
maxItems: MAX_OPTIONS,
|
|
51
|
-
description: "Distinct choices;
|
|
51
|
+
description: "Distinct choices; the user can also write their own answer",
|
|
52
52
|
}),
|
|
53
53
|
});
|
|
54
54
|
|
|
@@ -77,8 +77,10 @@ function cleanMultiline(text: string): string {
|
|
|
77
77
|
return sanitizeTerminalText(text).trim();
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
// Compare labels the way they are displayed (whitespace collapsed), so two
|
|
81
|
+
// labels that look identical on screen are rejected as duplicates.
|
|
80
82
|
function normalizedChoiceKey(label: string): string {
|
|
81
|
-
return label.normalize("NFKC").toLocaleLowerCase("en-US");
|
|
83
|
+
return oneLine(label).normalize("NFKC").toLocaleLowerCase("en-US");
|
|
82
84
|
}
|
|
83
85
|
|
|
84
86
|
function normalizeQuestionInput(
|
|
@@ -124,6 +126,7 @@ function createEditorTheme(theme: Theme): EditorTheme {
|
|
|
124
126
|
export class AskUserView implements Focusable {
|
|
125
127
|
private selected = 0;
|
|
126
128
|
private editing = false;
|
|
129
|
+
private questionOffset = 0;
|
|
127
130
|
private cachedWidth?: number;
|
|
128
131
|
private cachedLines?: string[];
|
|
129
132
|
private _focused = false;
|
|
@@ -133,6 +136,7 @@ export class AskUserView implements Focusable {
|
|
|
133
136
|
private readonly requestRender: () => void;
|
|
134
137
|
private readonly finish: (selection: Selection | null) => void;
|
|
135
138
|
private readonly editor: Editor;
|
|
139
|
+
private readonly maxRows: () => number;
|
|
136
140
|
|
|
137
141
|
constructor(
|
|
138
142
|
question: string,
|
|
@@ -141,6 +145,7 @@ export class AskUserView implements Focusable {
|
|
|
141
145
|
requestRender: () => void,
|
|
142
146
|
finish: (selection: Selection | null) => void,
|
|
143
147
|
editor: Editor,
|
|
148
|
+
maxRows: () => number = () => Number.POSITIVE_INFINITY,
|
|
144
149
|
) {
|
|
145
150
|
this.question = question;
|
|
146
151
|
this.options = options;
|
|
@@ -148,6 +153,7 @@ export class AskUserView implements Focusable {
|
|
|
148
153
|
this.requestRender = requestRender;
|
|
149
154
|
this.finish = finish;
|
|
150
155
|
this.editor = editor;
|
|
156
|
+
this.maxRows = maxRows;
|
|
151
157
|
this.editor.onSubmit = (value) => {
|
|
152
158
|
const answer = cleanMultiline(value).slice(0, MAX_ANSWER_LENGTH);
|
|
153
159
|
if (!answer) {
|
|
@@ -189,6 +195,15 @@ export class AskUserView implements Focusable {
|
|
|
189
195
|
}
|
|
190
196
|
|
|
191
197
|
handleInput(data: string): void {
|
|
198
|
+
if (matchesKey(data, Key.ctrl("c"))) {
|
|
199
|
+
this.finish(null);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
if (matchesKey(data, Key.pageUp) || matchesKey(data, Key.pageDown)) {
|
|
203
|
+
this.questionOffset = Math.max(0, this.questionOffset + (matchesKey(data, Key.pageUp) ? -5 : 5));
|
|
204
|
+
this.refresh();
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
192
207
|
if (this.editing) {
|
|
193
208
|
if (matchesKey(data, Key.escape)) {
|
|
194
209
|
this.editing = false;
|
|
@@ -220,7 +235,7 @@ export class AskUserView implements Focusable {
|
|
|
220
235
|
this.choose(this.selected);
|
|
221
236
|
return;
|
|
222
237
|
}
|
|
223
|
-
if (matchesKey(data, Key.escape)
|
|
238
|
+
if (matchesKey(data, Key.escape)) {
|
|
224
239
|
this.finish(null);
|
|
225
240
|
}
|
|
226
241
|
}
|
|
@@ -229,54 +244,61 @@ export class AskUserView implements Focusable {
|
|
|
229
244
|
if (this.cachedWidth === width && this.cachedLines) return this.cachedLines;
|
|
230
245
|
const safeWidth = Math.max(1, width);
|
|
231
246
|
const inner = Math.max(1, safeWidth - 4);
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
for (const line of wrapTextWithAnsi(this.theme.bold(cleanMultiline(this.question)), inner)) {
|
|
235
|
-
lines.push(padLine(` ${this.theme.fg("text", line)}`, safeWidth));
|
|
236
|
-
}
|
|
237
|
-
lines.push(padLine("", safeWidth));
|
|
247
|
+
const questionLines = wrapTextWithAnsi(this.theme.bold(cleanMultiline(this.question)), inner)
|
|
248
|
+
.map((line) => padLine(` ${this.theme.fg("text", line)}`, safeWidth));
|
|
238
249
|
|
|
250
|
+
const rest: string[] = [padLine("", safeWidth)];
|
|
239
251
|
this.options.forEach((option, index) => {
|
|
240
252
|
const active = index === this.selected;
|
|
241
253
|
const marker = option.custom ? "✎" : `${index + 1}.`;
|
|
242
254
|
const prefix = active ? this.theme.fg("accent", "❯") : " ";
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
255
|
+
// Wrap long labels so options that differ only near the end stay distinguishable.
|
|
256
|
+
const labelLines = wrapTextWithAnsi(oneLine(option.label), Math.max(8, inner - 4));
|
|
257
|
+
labelLines.forEach((text, lineIndex) => {
|
|
258
|
+
const label = lineIndex === 0 ? `${prefix} ${marker} ${text}` : `${" ".repeat(marker.length + 3)}${text}`;
|
|
259
|
+
rest.push(padLine(
|
|
246
260
|
active
|
|
247
261
|
? this.theme.bg("selectedBg", this.theme.fg("accent", label))
|
|
248
262
|
: this.theme.fg(option.custom ? "muted" : "text", label),
|
|
249
263
|
safeWidth,
|
|
250
|
-
)
|
|
251
|
-
);
|
|
264
|
+
));
|
|
265
|
+
});
|
|
252
266
|
if (option.description) {
|
|
253
267
|
for (const description of wrapTextWithAnsi(oneLine(option.description), Math.max(8, inner - 4))) {
|
|
254
|
-
|
|
268
|
+
rest.push(padLine(` ${this.theme.fg("muted", description)}`, safeWidth));
|
|
255
269
|
}
|
|
256
270
|
}
|
|
257
271
|
});
|
|
258
|
-
|
|
259
272
|
if (this.editing) {
|
|
260
|
-
|
|
261
|
-
|
|
273
|
+
rest.push(padLine("", safeWidth));
|
|
274
|
+
rest.push(padLine(` ${this.theme.fg("muted", "Your answer")}`, safeWidth));
|
|
262
275
|
for (const line of this.editor.render(Math.max(10, safeWidth - 4))) {
|
|
263
|
-
|
|
276
|
+
rest.push(padLine(` ${line}`, safeWidth));
|
|
264
277
|
}
|
|
265
278
|
}
|
|
279
|
+
rest.push(padLine("", safeWidth));
|
|
280
|
+
|
|
281
|
+
// Keep the choices, editor, and controls on screen; a long question scrolls.
|
|
282
|
+
const questionRoom = this.maxRows() - rest.length - 3;
|
|
283
|
+
let visibleQuestion = questionLines;
|
|
284
|
+
let scrollHint = "";
|
|
285
|
+
if (questionLines.length > questionRoom) {
|
|
286
|
+
const room = Math.max(1, questionRoom);
|
|
287
|
+
this.questionOffset = Math.min(this.questionOffset, Math.max(0, questionLines.length - room));
|
|
288
|
+
visibleQuestion = questionLines.slice(this.questionOffset, this.questionOffset + room);
|
|
289
|
+
scrollHint = `pgup/pgdn question ${this.questionOffset + 1}-${this.questionOffset + visibleQuestion.length}/${questionLines.length} · `;
|
|
290
|
+
}
|
|
266
291
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
),
|
|
278
|
-
);
|
|
279
|
-
lines.push(frameBottom(this.theme, safeWidth));
|
|
292
|
+
const controls = this.editing
|
|
293
|
+
? "enter submit · escape choices · ctrl+c dismiss"
|
|
294
|
+
: `up/down or 1-${this.options.length} choose · enter confirm · escape dismiss`;
|
|
295
|
+
const lines = [
|
|
296
|
+
frameTop(this.theme, safeWidth, "Decision needed"),
|
|
297
|
+
...visibleQuestion,
|
|
298
|
+
...rest,
|
|
299
|
+
padLine(` ${this.theme.fg("dim", `${scrollHint}${controls}`)}`, safeWidth),
|
|
300
|
+
frameBottom(this.theme, safeWidth),
|
|
301
|
+
];
|
|
280
302
|
this.cachedWidth = width;
|
|
281
303
|
this.cachedLines = lines.map((line) => truncateToWidth(line, safeWidth, ""));
|
|
282
304
|
return this.cachedLines;
|
|
@@ -315,6 +337,8 @@ async function openQuestion(
|
|
|
315
337
|
() => tui.requestRender(),
|
|
316
338
|
complete,
|
|
317
339
|
new Editor(tui, createEditorTheme(theme)),
|
|
340
|
+
// Matches the overlay's maxHeight (86%) minus its margin.
|
|
341
|
+
() => Math.max(8, Math.floor((tui.terminal.rows || 24) * 0.86) - 2),
|
|
318
342
|
);
|
|
319
343
|
return {
|
|
320
344
|
get focused() {
|
|
@@ -348,11 +372,12 @@ export default function askUserExtension(pi: ExtensionAPI) {
|
|
|
348
372
|
label: "Ask User",
|
|
349
373
|
description:
|
|
350
374
|
"Ask one multiple-choice question only when a missing user decision materially blocks safe progress. Do not use it when the answer is discoverable, the user already supplied it, or a reversible low-risk default is reasonable.",
|
|
351
|
-
promptSnippet: "Ask one
|
|
375
|
+
promptSnippet: "Ask the user one blocking multiple-choice question.",
|
|
352
376
|
promptGuidelines: [
|
|
353
|
-
"Use ask_user only when a missing choice
|
|
354
|
-
"When using ask_user, ask one decision at a time with distinct options, explain consequences briefly, and include the recommended safe default when one exists.",
|
|
377
|
+
"Use ask_user only after checking available context, when a missing choice blocks safe progress and no reversible default fits; ask one decision with distinct options, brief consequences, and the recommended option when one exists.",
|
|
355
378
|
],
|
|
379
|
+
exposure: "model-only",
|
|
380
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
356
381
|
parameters: AskUserParameters,
|
|
357
382
|
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
358
383
|
const normalized = normalizeQuestionInput(params.question, params.options);
|
|
@@ -406,9 +431,12 @@ export default function askUserExtension(pi: ExtensionAPI) {
|
|
|
406
431
|
invalidate() {},
|
|
407
432
|
};
|
|
408
433
|
},
|
|
409
|
-
renderResult(result, _options, theme) {
|
|
434
|
+
renderResult(result, _options, theme, context) {
|
|
410
435
|
const details = result.details as AskUserDetails | undefined;
|
|
411
|
-
const
|
|
436
|
+
const errorText = result.content.find((item) => item.type === "text")?.text ?? "invalid question";
|
|
437
|
+
const text = context.isError
|
|
438
|
+
? `${theme.fg("error", "● not asked")} ${theme.fg("muted", oneLine(errorText))}`
|
|
439
|
+
: !details || details.cancelled
|
|
412
440
|
? theme.fg("warning", "● dismissed")
|
|
413
441
|
: `${theme.fg("success", "● answered")} ${theme.fg("accent", oneLine(details.answer ?? ""))}`;
|
|
414
442
|
return { render: (width: number) => [truncateToWidth(text, width)], invalidate() {} };
|