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.
Files changed (55) hide show
  1. package/README.md +13 -15
  2. package/docs/ARCHITECTURE.md +32 -27
  3. package/docs/DEVELOPMENT.md +25 -48
  4. package/docs/EXTENSIONS.md +16 -35
  5. package/extensions/ask-user/README.md +0 -4
  6. package/extensions/ask-user/index.ts +68 -40
  7. package/extensions/background-terminals/README.md +9 -13
  8. package/extensions/background-terminals/index.ts +33 -31
  9. package/extensions/background-terminals/src/manager.ts +32 -7
  10. package/extensions/background-terminals/src/pty.ts +25 -8
  11. package/extensions/background-terminals/src/ui.ts +6 -15
  12. package/extensions/context-usage/index.ts +50 -22
  13. package/extensions/firecrawl-web/README.md +5 -8
  14. package/extensions/firecrawl-web/auth.ts +1 -1
  15. package/extensions/firecrawl-web/client.ts +15 -5
  16. package/extensions/firecrawl-web/index.ts +16 -17
  17. package/extensions/firecrawl-web/output.ts +4 -1
  18. package/extensions/goal/README.md +1 -5
  19. package/extensions/goal/extension.ts +65 -45
  20. package/extensions/input-mode/index.ts +10 -2
  21. package/extensions/performance-status/README.md +1 -1
  22. package/extensions/performance-status/index.ts +29 -20
  23. package/extensions/shared/README.md +12 -16
  24. package/extensions/shared/activity-dock.ts +33 -22
  25. package/extensions/shared/child-session.ts +14 -6
  26. package/extensions/shared/model-picker.ts +2 -2
  27. package/extensions/shared/settlement-delivery.ts +7 -1
  28. package/extensions/shared/tool-call-timeout.ts +8 -1
  29. package/extensions/shared/tool-card.ts +5 -3
  30. package/extensions/shell-shortcuts/README.md +0 -4
  31. package/extensions/smart-compaction/README.md +49 -42
  32. package/extensions/smart-compaction/engine.ts +39 -21
  33. package/extensions/smart-compaction/index.ts +17 -3
  34. package/extensions/smart-compaction/prompt.ts +1 -1
  35. package/extensions/subagents/README.md +55 -42
  36. package/extensions/subagents/index.ts +114 -81
  37. package/extensions/subagents/src/backends/pi.ts +53 -136
  38. package/extensions/subagents/src/catalog.ts +74 -4
  39. package/extensions/subagents/src/config.ts +13 -8
  40. package/extensions/subagents/src/manager.ts +4 -29
  41. package/extensions/subagents/src/prompt.ts +19 -27
  42. package/extensions/subagents/src/ui/takeover.ts +1 -1
  43. package/extensions/task-list/README.md +30 -82
  44. package/extensions/task-list/index.ts +44 -84
  45. package/extensions/task-list/state.ts +19 -3
  46. package/extensions/web-fetch/README.md +3 -1
  47. package/extensions/web-fetch/index.ts +258 -268
  48. package/package.json +11 -9
  49. package/skills/background-terminals/SKILL.md +16 -26
  50. package/skills/subagents/SKILL.md +40 -53
  51. package/extensions/shared/activity-status.ts +0 -31
  52. package/extensions/shared/dashboard-state.ts +0 -55
  53. package/extensions/shared/redaction.ts +0 -55
  54. package/extensions/subagents/src/format.ts +0 -74
  55. 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 operating skills for the Pi coding agent. Credentials and runtime state stay on each machine and are never bundled with the package.
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
- The package contains:
24
-
25
- - structured user questions
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 plus scraping
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 includes the `ember-warm-dark` TUI theme and declares the `background-terminals` and `subagents` skills so Pi loads their operating guidance automatically. Pi reads them from the installed package; duplicating files under `<agent-dir>/skills` is unnecessary and would leave stale copies after removal.
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
 
@@ -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 has one Pi package boundary at its root. Every extension is an independently declared resource in `package.json#pi.extensions`. Pi therefore loads only intentional extension factories, and `pi config` can toggle each one without separate installation.
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 suite extension entrypoints
13
- extensions/<name>/src/ extension-owned implementation
14
- extensions/shared/ runtime helpers shared across extensions
15
- skills/ operating guidance paired with async extensions
16
- docs/ package-level maintenance documentation
17
- scripts/ repository validation helpers
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
- Extension entrypoints register tools, commands, providers, events, or UI. Shared code stays in `extensions/shared` only when more than one extension owns the behavior. The shared activity dock combines active Task List, terminal, and subagent rows under one bounded widget; shared tool cards keep lifecycle operations compact in the timeline while preserving expanded output. Provider-specific protocol and authentication code remains inside its provider directory. The background-terminal and subagent skills ship beside their extensions so a new machine receives the required lifecycle guidance with the tools.
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
- ## Dependency model
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
- Pi-owned packages and `typebox` are optional peer dependencies at runtime. Pi supplies them when it loads the package. Exact versions are development dependencies so local typechecking and tests use a reproducible API surface.
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
- Third-party runtime dependencies are normal package dependencies:
26
+ ## Model-facing text
27
27
 
28
- - `@lydell/node-pty` powers managed background terminals;
29
- - `effect` powers subagent lifecycle and coordination.
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
- The repository uses one root manifest and lockfile for the full extension suite.
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 immutable. Extensions resolve writable state through Pi's APIs:
41
+ Installed package directories are treated as read-only. Writable state goes through Pi's APIs:
36
42
 
37
- - Smart Compaction configuration and details ledgers: `<agent-dir>/smart-compaction.json`
38
- - Input mode configuration: `<agent-dir>/input-mode.json`
39
- - Subagent configuration and catalog: paths derived from `getAgentDir()`
40
- - Goal and Task List state: branch-local Pi session entries; neither writes a separate runtime-state file
41
- - project configuration: paths derived from Pi's `CONFIG_DIR_NAME`
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 remain in Pi auth storage, environment variables, service credential stores, or ignored machine-local files. Package source never contains an API key, OAuth token, database, cache, or session.
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
- Runtime paths use `node:path`, `getAgentDir()`, `CONFIG_DIR_NAME`, `os.homedir()`, and platform checks where service credential locations differ. Code must not assume `/Users`, `/home`, Homebrew, a particular shell, or a package installation path. Native PTY support is installed through npm for the active Node platform.
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 package installations and may replace their contents during updates, so user state must never be written beside extension source.
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`.
@@ -1,14 +1,12 @@
1
- # Development and cutover
2
-
3
- Last verified: 2026-09-04
1
+ # Development
4
2
 
5
3
  ## Requirements
6
4
 
7
5
  - mise
8
- - Pi matching the pinned development dependency version
9
- - platform build support required by `@lydell/node-pty`
6
+ - Pi at or above the version in `devDependencies`
7
+ - platform build support for `@lydell/node-pty`
10
8
 
11
- Install the exact toolchain and dependencies:
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
- The root validation checks TypeScript, runtime tests, declared extension and skill entrypoints, and forbidden runtime files. Inspect the package file list before committing. It must not contain credentials, `.env`, first-party caches, databases, sessions, logs, or `node_modules`.
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. Add a self-contained directory under `extensions/` with an `index.ts` entrypoint.
32
- 2. Use relative imports for extension-owned code and `extensions/shared` only for genuinely shared behavior.
33
- 3. Put external runtime modules in root `dependencies`.
34
- 4. Add the exact entrypoint to root `package.json#pi.extensions`.
35
- 5. Add focused runtime tests and update `docs/EXTENSIONS.md`.
36
- 6. Run both validations and inspect the package payload.
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 to default-suite extensions. The workspace root owns their dependencies and tooling.
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. Use `node:path` and explicit platform branches where third-party credential locations differ. Never infer a writable location from `import.meta.url` or the installed package directory.
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
- The root manifest declares `skills/background-terminals` and `skills/subagents`. Pi loads them directly from the managed package. Do not copy them into the agent directory from install scripts; that would create duplicates and leave stale files after removal.
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
- Do not remove a working local extension set before the managed package is available.
47
+ ## Upgrading Pi
55
48
 
56
- 1. Push and validate the public GitHub repository.
57
- 2. Run `pi install git:https://github.com/shariqriazz/shariq-pi-extensions`; do not reload yet.
58
- 3. Move old auto-discovered extension directories out of the active Pi extension directory so package and local copies cannot load together.
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
- Rollback is the reverse: remove the Git package from Pi settings, restore the local extension and skill directories, and reload. Never delete credentials or databases during rollback.
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
- Before publishing a new npm version:
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
- 1. Update `package.json#version` using semantic versioning.
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
- Use npm for stable versioned installs. Use an unpinned Git source for updates directly from `main`, and a tag or commit when a machine should remain fixed to a known revision.
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.
@@ -1,80 +1,61 @@
1
1
  # Extension catalog
2
2
 
3
- Last verified: 2026-09-04
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` presents one blocking multiple-choice decision with an optional custom answer. It is intended for decisions that cannot be resolved safely from available context.
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
- The goal extension adds persistent, branch-safe objectives, progress evidence, budgets, pause/resume controls, and strict completion/blocker gates. Use `/goal` for the operator UI and the `create_goal`, `get_goal`, `update_goal_progress`, and `update_goal` tools for agent-controlled state.
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` gives the active model a branch-safe ordered checklist for ordinary multi-step work; it is separate from persistent Goals and may be used alongside them. Writes replace the full list, retain stable IDs, support pending/in-progress/completed/blocked/cancelled states and priorities, and require an active item while pending work remains. The prompt contract requires same-message list/action calls and verified updates only at task-level transitions—not after routine file reads, edits, commands, or other tool calls. A runtime reminder catches multi-action work that starts without a list; final reconciliation catches unfinished bookkeeping.
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
- The subagent extension runs flat Pi child agents with profiles, capability policies, continuation, result delivery, optional worktrees, pre-warmed task dispatch, instant cascading cancellation, cross-session persistence, and a dashboard. Configuration lives in `<agent-dir>/subagents.json`; trusted projects may override it through their Pi config directory. The configured concurrency ceiling is 50.
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 standard context compaction with a defensive, high-fidelity continuity engine. It intercepts `session_before_compact` events and synthesizes multi-turn conversations into structured checkpoint summaries capturing primary goals and negative constraints, progress ledgers (`Done`/`In Progress`/`Blocked`), verbatim code snippets for active/uncommitted edits, exact error root causes, architectural decisions, resume anchors, and deterministic file/diff state.
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 support servers, watchers, long builds, downloads, and interactive processes. The extension tracks up to eight concurrent terminals, retains bounded output, stores full logs in restrictive temporary directories, and stops process groups during shutdown or reload.
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` discovers current web, news, images, GitHub, research, and PDF sources. `web_scrape` renders difficult or JavaScript-heavy pages. `dev_search` queries the Firecrawl Developer Index across 70M+ technical docs, GitHub issues, merged PRs, and READMEs with matched code passages. Authentication resolves from environment variables, the active Pi agent `.env`, or Firecrawl CLI credentials.
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 a known HTTP or HTTPS URL without spending Firecrawl credits. It supports Markdown, text, and HTML output with bounded time and response size.
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 and quality of life
41
+ ## Interface
61
42
 
62
43
  ### [Context Usage](../extensions/context-usage/README.md)
63
44
 
64
- Displays the estimated prompt and session contribution to the active context window. The default summary is a compact context-budget card; use `Ctrl+O` or `/context-usage` to cycle to compact and expanded audit views.
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
- Adds a responsive footer-area status row while each assistant message is active and for an eight-second completion linger, then clears it to avoid permanent footer noise. Live TPS/output are marked as estimates; final TPS uses conventional decode throughput from first streamed token to message completion. TTFT and total elapsed time separately expose provider latency, prefill, and hidden reasoning; output tokens and active tools remain separate.
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` chooses what ordinary Enter does while Pi is running: steer before the next model step, interrupt the active run, or wait as a follow-up. The global selection lives in `<agent-dir>/input-mode.json`; explicit Alt+Enter follow-ups and non-interactive inputs retain native behavior. Pi does not expose extension rows in core `/settings`, so this is a dedicated extension settings picker rather than a private TUI patch.
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 Pi's normal quit command. Keep this extension limited to small, low-risk conveniences.
57
+ Adds `/exit` as an alias for `/quit`.
77
58
 
78
- ## Per-extension details
59
+ ## Tool exposure
79
60
 
80
- Implementation-specific behavior and maintenance notes remain next to each extension in its own README. Package-wide installation, dependencies, state boundaries, and release rules belong in the root documentation.
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: "Concise answer shown to the user",
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: "Optional consequence or clarification",
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: "One concrete decision the user needs to make",
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; include the recommended safe default when one exists",
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) || matchesKey(data, Key.ctrl("c"))) {
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 lines: string[] = [frameTop(this.theme, safeWidth, "Decision needed")];
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
- const label = `${prefix} ${marker} ${oneLine(option.label)}`;
244
- lines.push(
245
- padLine(
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
- lines.push(padLine(` ${this.theme.fg("muted", description)}`, safeWidth));
268
+ rest.push(padLine(` ${this.theme.fg("muted", description)}`, safeWidth));
255
269
  }
256
270
  }
257
271
  });
258
-
259
272
  if (this.editing) {
260
- lines.push(padLine("", safeWidth));
261
- lines.push(padLine(` ${this.theme.fg("muted", "Your answer")}`, safeWidth));
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
- lines.push(padLine(` ${line}`, safeWidth));
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
- lines.push(padLine("", safeWidth));
268
- lines.push(
269
- padLine(
270
- ` ${this.theme.fg(
271
- "dim",
272
- this.editing
273
- ? "enter submit · escape choices"
274
- : `up/down or 1-${this.options.length} choose · enter confirm · escape dismiss`,
275
- )}`,
276
- safeWidth,
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 materially blocking multiple-choice question with an optional custom answer",
375
+ promptSnippet: "Ask the user one blocking multiple-choice question.",
352
376
  promptGuidelines: [
353
- "Use ask_user only when a missing choice materially blocks safe progress; first inspect available context and prefer a reversible low-risk default when that would not change scope or authority.",
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 text = !details || details.cancelled
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() {} };