shariq-pi-extensions 0.3.7 → 0.4.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 (33) 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 +8 -7
  7. package/extensions/background-terminals/README.md +4 -8
  8. package/extensions/background-terminals/index.ts +32 -30
  9. package/extensions/context-usage/index.ts +10 -5
  10. package/extensions/firecrawl-web/README.md +5 -8
  11. package/extensions/firecrawl-web/index.ts +16 -17
  12. package/extensions/goal/README.md +1 -5
  13. package/extensions/goal/extension.ts +8 -12
  14. package/extensions/shared/README.md +11 -17
  15. package/extensions/shared/child-session.ts +10 -6
  16. package/extensions/shell-shortcuts/README.md +0 -4
  17. package/extensions/smart-compaction/README.md +49 -42
  18. package/extensions/subagents/README.md +53 -42
  19. package/extensions/subagents/index.ts +41 -55
  20. package/extensions/subagents/src/backends/pi.ts +12 -127
  21. package/extensions/subagents/src/prompt.ts +19 -27
  22. package/extensions/subagents/src/ui/takeover.ts +1 -1
  23. package/extensions/task-list/README.md +30 -82
  24. package/extensions/task-list/index.ts +24 -75
  25. package/extensions/web-fetch/README.md +1 -1
  26. package/extensions/web-fetch/index.ts +6 -5
  27. package/package.json +9 -9
  28. package/skills/background-terminals/SKILL.md +16 -26
  29. package/skills/subagents/SKILL.md +40 -53
  30. package/extensions/shared/activity-status.ts +0 -31
  31. package/extensions/shared/dashboard-state.ts +0 -55
  32. package/extensions/shared/redaction.ts +0 -55
  33. package/extensions/subagents/src/format.ts +0 -74
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.
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
 
@@ -348,11 +348,12 @@ export default function askUserExtension(pi: ExtensionAPI) {
348
348
  label: "Ask User",
349
349
  description:
350
350
  "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",
351
+ promptSnippet: "Ask the user one blocking multiple-choice question.",
352
352
  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.",
353
+ "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
354
  ],
355
+ exposure: "model-only",
356
+ annotations: { readOnlyHint: true, openWorldHint: false },
356
357
  parameters: AskUserParameters,
357
358
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
358
359
  const normalized = normalizeQuestionInput(params.question, params.options);
@@ -10,7 +10,9 @@ Session-scoped background pseudo-terminals for Pi. The extension combines Codex-
10
10
  - `list_terminals` — list running and settled terminals.
11
11
  - `stop_terminal` — stop complete process groups with TERM-to-KILL escalation.
12
12
 
13
- Each output response carries a byte cursor. Pass it to the next read/write operation to avoid repeating output. Terminal lifecycle tools render as compact main-chat cards with expandable output, and running terminals appear in the shared bounded **Active work** dock with elapsed time and the latest output line. Long or uncertain commands should use `start_terminal` instead of a large blocking `bash` timeout. 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 bounded output guaranteed in model context and never rendered as user-authored or follow-up input, so the parent can continue other work or end its turn rather than poll.
13
+ Each output response carries a byte cursor; pass it to the next read or write to get only new output. The tools render as compact cards with expandable output, use `model-only` exposure, and running terminals appear in the shared **Active work** dock with elapsed time and the latest output line.
14
+
15
+ When a model-started terminal exits, its status and a bounded output tail are delivered as a custom message. An idle parent starts a new turn for it; a busy parent receives it, together with any other pending results, when its run settles. The message never appears as user-authored or follow-up input, and reading the terminal does not suppress it.
14
16
 
15
17
  ## User interface
16
18
 
@@ -34,10 +36,4 @@ Each output response carries a byte cursor. Pass it to the next read/write opera
34
36
  - Output is sanitized before TUI or model rendering.
35
37
  - Processes run in their own PTY process group and are stopped on session shutdown, replacement, or reload.
36
38
  - Shutdown and stop operations are bounded and escalate from SIGTERM to SIGKILL.
37
- - Model-started terminals immediately hand one model-visible completion/failure follow-up to Pi; Pi queues it while the parent is active or starts the next parent turn when idle.
38
- - Reading settled output does not consume or suppress the automatic completion delivery.
39
- - Completion delivery is keyed by terminal id to prevent duplicate follow-ups.
40
-
41
- ## Validation
42
-
43
- From the repository root, run `npm run validate`.
39
+ - Completion delivery is keyed by terminal id, so each terminal produces at most one result message.
@@ -149,21 +149,20 @@ export default function backgroundTerminals(pi: ExtensionAPI) {
149
149
  name: "start_terminal",
150
150
  label: "Start Background Terminal",
151
151
  description:
152
- "Start a command in a real background pseudo-terminal (PTY) and return its terminal id. " +
153
- "Use for development servers, watchers, long builds, and commands that need terminal semantics. " +
154
- "The PTY accepts later input through write_terminal, captures a bounded live tail in memory, writes a size-limited private temporary log, and is stopped at session shutdown. " +
155
- `At most ${MAX_RUNNING_TERMINALS} terminals may run concurrently.`,
156
- promptSnippet: "Start an interactive or long-running command in a managed background PTY.",
152
+ "Run a shell command in a background pseudo-terminal (PTY) and return its terminal id. " +
153
+ "The process keeps running while you work; when it exits, its final output arrives as a new message that starts your next turn. " +
154
+ `At most ${MAX_RUNNING_TERMINALS} terminals run at once, and all of them stop when the session ends.`,
155
+ promptSnippet: "Run a long or interactive command in a background PTY.",
157
156
  promptGuidelines: [
158
- "Use start_terminal by default for servers, watchers, downloads, long or uncertain builds and tests, interactive shells, and any command that should not occupy the main turn; reserve bash for short commands whose result is needed immediately. Never use a large bash timeout merely to wait for long work.",
159
- "After start_terminal returns, continue only genuinely independent work. If none remains, end the turn immediately. Terminal settlement stays in a private extension queue while the parent is active and starts one custom-result turn at Pi's safe idle edge. When that result invokes the parent, continue the original task immediately without waiting for the user or rereading the same terminal; do not call read_terminal, list_terminals, or start a timer merely to check whether it finished.",
160
- "Use stop_terminal when a managed process is no longer needed. Background terminals are session-scoped and are stopped during session shutdown or reload.",
157
+ "Use start_terminal for servers, watchers, long builds or test runs, and interactive commands. Use bash for short commands whose output you need now, not with a long timeout.",
158
+ "After start_terminal, continue independent work or end the turn. When the terminal's result message arrives, continue the task from it instead of polling with read_terminal or list_terminals.",
161
159
  ],
160
+ exposure: "model-only",
162
161
  parameters: Type.Object({
163
- command: Type.String({ minLength: 1, maxLength: 32_000, description: "Shell command to run in the PTY." }),
164
- title: Type.Optional(Type.String({ maxLength: 100, description: "Short human-readable terminal name; defaults to the command." })),
165
- working_dir: Type.Optional(Type.String({ description: "Working directory relative to the current workspace, or an absolute directory." })),
166
- wait_ms: Type.Optional(Type.Integer({ minimum: 0, maximum: 5_000, description: "Optional initial wait for startup output; default 300ms." })),
162
+ command: Type.String({ minLength: 1, maxLength: 32_000, description: "Shell command." }),
163
+ title: Type.Optional(Type.String({ maxLength: 100, description: "Short name; defaults to the command." })),
164
+ working_dir: Type.Optional(Type.String({ description: "Directory, relative to the workspace or absolute." })),
165
+ wait_ms: Type.Optional(Type.Integer({ minimum: 0, maximum: 5_000, description: "Wait for startup output; default 300." })),
167
166
  }),
168
167
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
169
168
  const command = params.command.trim();
@@ -229,14 +228,14 @@ export default function backgroundTerminals(pi: ExtensionAPI) {
229
228
  name: "read_terminal",
230
229
  label: "Read Background Terminal",
231
230
  description:
232
- "Read output from a managed background terminal only when the user asks for progress or current output is required for immediate interaction. " +
233
- "Do not use it to wait for completion: terminal settlement automatically sends a follow-up that starts the next parent turn. " +
234
- "Pass the cursor from the previous terminal result to receive only newer output. Output is tail-truncated for model context; the private log path is reported when available.",
235
- promptSnippet: "Read new output from a managed background terminal by id and cursor.",
231
+ "Read a background terminal's output since a cursor; omit the cursor for the retained tail. " +
232
+ "Use it when the user asks for progress or the process is waiting for input. Completion output arrives on its own.",
233
+ exposure: "model-only",
234
+ annotations: { readOnlyHint: true, openWorldHint: false },
236
235
  parameters: Type.Object({
237
- id: Type.String({ description: 'Terminal id, for example "term-1".' }),
238
- cursor: Type.Optional(Type.Integer({ minimum: 0, description: "Cursor returned by the previous terminal operation; omit for the retained tail." })),
239
- wait_ms: Type.Optional(Type.Integer({ minimum: 0, maximum: 10_000, description: "Long-poll duration when no newer output exists; default 0." })),
236
+ id: Type.String({ description: 'Terminal id, such as "term-1".' }),
237
+ cursor: Type.Optional(Type.Integer({ minimum: 0, description: "Cursor from the previous terminal result." })),
238
+ wait_ms: Type.Optional(Type.Integer({ minimum: 0, maximum: 10_000, description: "Wait for new output; default 0." })),
240
239
  }),
241
240
  async execute(_toolCallId, params, signal) {
242
241
  const result = await getManager().read(params.id, {
@@ -271,15 +270,15 @@ export default function backgroundTerminals(pi: ExtensionAPI) {
271
270
  name: "write_terminal",
272
271
  label: "Write Background Terminal",
273
272
  description:
274
- "Send input to a running background PTY, optionally press Enter, wait briefly, and return output produced since the supplied cursor. " +
275
- "Use input \\u0003 with press_enter=false to send Ctrl+C inside the PTY; use stop_terminal to terminate the managed process tree.",
276
- promptSnippet: "Send input to a running background PTY and collect its response.",
273
+ "Send input to a running background terminal and return the output it produces. Enter is appended unless press_enter is false; " +
274
+ "send \\u0003 with press_enter=false for Ctrl+C. Use stop_terminal to end the process.",
275
+ exposure: "model-only",
277
276
  parameters: Type.Object({
278
277
  id: Type.String({ description: "Terminal id." }),
279
- input: Type.String({ maxLength: 65_536, description: "Text or control character to write." }),
280
- press_enter: Type.Optional(Type.Boolean({ description: "Append Enter after the input; default true." })),
281
- cursor: Type.Optional(Type.Integer({ minimum: 0, description: "Previous output cursor for incremental output." })),
282
- wait_ms: Type.Optional(Type.Integer({ minimum: 0, maximum: 5_000, description: "Wait for resulting output; default 250ms." })),
278
+ input: Type.String({ maxLength: 65_536, description: "Text or control characters." }),
279
+ press_enter: Type.Optional(Type.Boolean({ description: "Default true." })),
280
+ cursor: Type.Optional(Type.Integer({ minimum: 0, description: "Cursor from the previous terminal result." })),
281
+ wait_ms: Type.Optional(Type.Integer({ minimum: 0, maximum: 5_000, description: "Wait for output; default 250." })),
283
282
  }),
284
283
  async execute(_toolCallId, params, signal) {
285
284
  const current = getManager().get(params.id);
@@ -317,7 +316,9 @@ export default function backgroundTerminals(pi: ExtensionAPI) {
317
316
  pi.registerTool({
318
317
  name: "list_terminals",
319
318
  label: "List Background Terminals",
320
- description: "List managed background terminals with status, pid, elapsed time, output size, and working directory.",
319
+ description: "List background terminals with id, status, pid, elapsed time, and working directory.",
320
+ exposure: "model-only",
321
+ annotations: { readOnlyHint: true, openWorldHint: false },
321
322
  parameters: Type.Object({}),
322
323
  async execute() {
323
324
  const terminals = getManager().list();
@@ -340,10 +341,11 @@ export default function backgroundTerminals(pi: ExtensionAPI) {
340
341
  pi.registerTool({
341
342
  name: "stop_terminal",
342
343
  label: "Stop Background Terminals",
343
- description: "Stop one or more managed background terminal process groups, escalating from SIGTERM to SIGKILL when needed, and return their final states.",
344
- promptSnippet: "Stop managed background terminals and their process trees.",
344
+ description: "Stop background terminals by id (SIGTERM, then SIGKILL) and return their final status.",
345
+ exposure: "model-only",
346
+ annotations: { destructiveHint: true, openWorldHint: false },
345
347
  parameters: Type.Object({
346
- ids: Type.Array(Type.String(), { minItems: 1, maxItems: 32, description: "Terminal ids to stop." }),
348
+ ids: Type.Array(Type.String(), { minItems: 1, maxItems: 32 }),
347
349
  }),
348
350
  async execute(_toolCallId, params) {
349
351
  const terminals = await getManager().kill(params.ids);
@@ -168,9 +168,11 @@ type ContextUsageGlobal = typeof globalThis & {
168
168
 
169
169
  const g = globalThis as ContextUsageGlobal;
170
170
 
171
- const PROJECT_CONTEXT_RE = /\n?<project_context>\n\n[\s\S]*?\n<\/project_context>\n?/;
171
+ // Pi 1.0 renders structured sections as `<name>\n...\n</name>`; older releases
172
+ // separated the project context with a blank line. Accept both.
173
+ const PROJECT_CONTEXT_RE = /\n?<project_context>\n\n?[\s\S]*?\n<\/project_context>\n?/;
172
174
  const PROJECT_INSTRUCTIONS_RE = /<project_instructions path="([^"]*)">\n([\s\S]*?)\n<\/project_instructions>/g;
173
- const AVAILABLE_SKILLS_RE = /\n\nThe following skills provide specialized instructions for specific tasks\.[\s\S]*?<available_skills>[\s\S]*?<\/available_skills>/;
175
+ const AVAILABLE_SKILLS_RE = /(?:\n?<skills>\n|\n\n)The following skills provide specialized instructions for specific tasks\.[\s\S]*?<available_skills>[\s\S]*?<\/available_skills>(?:\n<\/skills>)?/;
174
176
  const SKILL_RE = /<skill>\s*<name>([\s\S]*?)<\/name>[\s\S]*?<description>([\s\S]*?)<\/description>[\s\S]*?<location>([\s\S]*?)<\/location>\s*<\/skill>/g;
175
177
  const DEFAULT_MODE: ViewMode = "summary";
176
178
  const OPENAI_TOOL_TEXT_FRAGMENT_DENOMINATOR = 6.6;
@@ -1538,9 +1540,12 @@ function renderSummary(snapshot: PrefixSnapshot, theme: Theme, width = 80): stri
1538
1540
  window && window > 0 ? `/ ${contextWindowLabel(window)}` : undefined,
1539
1541
  percent ? `· ${percent}` : undefined,
1540
1542
  ].filter(Boolean).join(" ");
1541
- const breakdown = snapshot.sections
1542
- .map((section) => `${summarySectionLabel(section.title)} ~${compactCount(sectionTokens(section))}`)
1543
- .join(SEP);
1543
+ const totals = new Map<string, number>();
1544
+ for (const section of snapshot.sections) {
1545
+ const label = summarySectionLabel(section.title);
1546
+ totals.set(label, (totals.get(label) ?? 0) + sectionTokens(section));
1547
+ }
1548
+ const breakdown = [...totals].map(([label, tokens]) => `${label} ~${compactCount(tokens)}`).join(SEP);
1544
1549
  const ctrlO = keyText("app.tools.expand") || "Ctrl+O";
1545
1550
  const rows = [
1546
1551
  ` ${theme.fg("text", budgetParts)}`,