@kingingwang/pi-windows-arm64 1.1.0 → 1.2.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 (38) hide show
  1. package/bin/CHANGELOG.md +63 -0
  2. package/bin/docs/cli-integration.md +1 -1
  3. package/bin/docs/cli.md +1 -1
  4. package/bin/docs/codemode.md +1 -1
  5. package/bin/docs/compaction.md +2 -2
  6. package/bin/docs/environment-variables.md +2 -1
  7. package/bin/docs/extensions.md +7 -4
  8. package/bin/docs/keybindings.md +1 -0
  9. package/bin/docs/mcp.md +1 -1
  10. package/bin/docs/models.md +1 -0
  11. package/bin/docs/providers.md +20 -0
  12. package/bin/docs/settings.md +3 -1
  13. package/bin/docs/themes.md +4 -2
  14. package/bin/docs/tui.md +1 -1
  15. package/bin/examples/README.md +0 -3
  16. package/bin/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  17. package/bin/examples/extensions/custom-provider-anthropic/package.json +1 -1
  18. package/bin/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  19. package/bin/examples/extensions/dynamic-resources/dynamic.json +1 -1
  20. package/bin/examples/extensions/gondolin/package-lock.json +2 -2
  21. package/bin/examples/extensions/gondolin/package.json +1 -1
  22. package/bin/examples/extensions/overlay-test.ts +2 -2
  23. package/bin/examples/extensions/plan-mode/index.ts +6 -2
  24. package/bin/examples/extensions/sandbox/package-lock.json +2 -2
  25. package/bin/examples/extensions/sandbox/package.json +1 -1
  26. package/bin/examples/extensions/with-deps/package-lock.json +2 -2
  27. package/bin/examples/extensions/with-deps/package.json +1 -1
  28. package/bin/package.json +9 -20
  29. package/bin/pi.exe +2 -2
  30. package/bin/theme/dark.json +1 -1
  31. package/bin/theme/light.json +1 -1
  32. package/package.json +1 -1
  33. package/bin/examples/plugins/pi-example-plugin/README.md +0 -38
  34. package/bin/examples/plugins/pi-example-plugin/package.json +0 -10
  35. package/bin/examples/plugins/pi-example-plugin/src/contract.ts +0 -13
  36. package/bin/examples/plugins/pi-example-plugin/src/session.ts +0 -24
  37. package/bin/examples/plugins/pi-example-plugin/src/tui.ts +0 -31
  38. package/bin/theme/theme-schema.json +0 -361
package/bin/CHANGELOG.md CHANGED
@@ -1,5 +1,68 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.2.0] - 2026-10-12
4
+
5
+ ### New Features
6
+
7
+ - **Per-key OpenRouter model lists**: with an OpenRouter key, Pi lists only the models that key may use, with the key's context size, max output, and prices, including regional endpoints set in `models.json`. See [OpenRouter](docs/providers.md#openrouter).
8
+ - **Published configuration schemas**: add `$schema` to `settings.json`, `models.json`, keybindings, and themes for editor validation and completion. See [Settings](docs/settings.md), [Models](docs/models.md), [Keybindings](docs/keybindings.md), and [Themes](docs/themes.md).
9
+ - **Clipboard copies over remote sessions**: `PI_CLIPBOARD_OSC52=1` sends copied text to a remote client's clipboard when Pi cannot detect the remote session, such as a multiplexer attached from another machine. See [Pi Process Configuration](docs/environment-variables.md#pi-process-configuration).
10
+ - **Codemode improvements**: tool declarations show input constraints such as integer, minimum/maximum, and default, and codemode rows show elapsed and total time. See [Codemode](docs/codemode.md).
11
+ - **`before_agent_start` for extension-triggered runs**: runs started by `pi.sendMessage(..., { triggerTurn: true })` go through the same setup as prompts. See [Events and concurrency](docs/extensions.md#events-and-concurrency).
12
+
13
+ ### Breaking Changes
14
+
15
+ - Published generated configuration schemas for models, settings, keybindings, and themes at `https://pi.dev/schemas/*.schema.json`. The theme schema moved to `https://pi.dev/schemas/theme.schema.json`; themes loaded during startup now receive the same schema validation as later reloads. Update theme `$schema` references, define reusable colors under `vars`, and remove unsupported metadata ([#9880](https://github.com/earendil-works/pi/pull/9880), [#10751](https://github.com/earendil-works/pi/pull/10751) by [@christianklotz](https://github.com/christianklotz))
16
+ - Changed `before_agent_start` to also fire for runs started by `pi.sendMessage(..., { triggerTurn: true })` while idle. `BeforeAgentStartEvent` is now a union on `messageType`: `"user"` events carry `prompt` and `images`, and `"custom"` events carry the starting `message` and no `prompt`. Messages returned by handlers are added to both kinds of run, and pending `deliverAs: "nextTurn"` messages are delivered with the custom-message run. Handlers that should act only on user prompts must check `event.messageType === "user"` ([#10267](https://github.com/earendil-works/pi/issues/10267), [#5581](https://github.com/earendil-works/pi/issues/5581), [#10739](https://github.com/earendil-works/pi/pull/10739) by [@davidbrai](https://github.com/davidbrai))
17
+ - Changed the session to count as busy while a run starts, including during `before_agent_start` handlers: `ctx.isIdle()` is false, `pi.sendMessage()` queues into the starting run instead of starting a second run that failed with "Agent is already processing", `pi.sendUserMessage()` requires `deliverAs`, and an abort requested meanwhile aborts the run once it starts ([#10739](https://github.com/earendil-works/pi/pull/10739) by [@davidbrai](https://github.com/davidbrai))
18
+ - Changed `pi mcp list --json` to report `resources: true` for servers that offer resources instead of `resources` and `resourceTemplates` counts; Pi no longer lists resources when connecting ([#10526](https://github.com/earendil-works/pi/issues/10526))
19
+
20
+ ### Added
21
+
22
+ - Added `reachesTools` to tool definitions, so tools that reach other tools (such as `codemode` and `tool_search`) declare which tools a call may reach; the MCP extension uses it to wait for the servers a call needs and to decide whether a replacement `codemode` or `tool_search` tool can reach MCP tools
23
+ - Added `aliases` to tool namespaces; codemode's `describeNamespace()` and the `namespace` option of `searchTools()` match them, and MCP namespaces use the server name
24
+ - Added per-key OpenRouter model lists: with an OpenRouter key, Pi lists only the chat models that key may use, with the key's context size, max output, and prices, including guardrails and regional endpoints set as the base URL in `models.json`. The list is saved and used offline ([#10353](https://github.com/earendil-works/pi/issues/10353), [#10672](https://github.com/earendil-works/pi/pull/10672) by [@davidbrai](https://github.com/davidbrai))
25
+ - Added `PI_CLIPBOARD_OSC52=1` to send copied text to the terminal with OSC 52 after the local clipboard write, so copies reach a remote client's clipboard when Pi cannot detect the remote session, such as multiplexer attaches from another machine or remote IDE daemons ([#10826](https://github.com/earendil-works/pi/issues/10826), [#10835](https://github.com/earendil-works/pi/issues/10835), [#10114](https://github.com/earendil-works/pi/issues/10114))
26
+ - Added elapsed time while a codemode script runs and its total time once finished to the codemode tool row, like bash rows ([#10708](https://github.com/earendil-works/pi/issues/10708))
27
+
28
+ ### Changed
29
+
30
+ - Changed the `showHardwareCursor` setting to use only the terminal cursor instead of also drawing Pi's reverse-video cursor
31
+ - Changed codemode tool declarations to show input constraints such as integer, minimum/maximum, length, pattern, and default as `@` tags on property comments, so scripts see them without the tool repeating them in descriptions ([#10707](https://github.com/earendil-works/pi/issues/10707))
32
+ - Changed codemode declarations of MCP tools to spell out the result type (`content`, `structuredContent`, `isError`) instead of referring to `CallToolResult<T>`, which `describeTool()`, `searchTools()`, and `ALL_TOOLS` showed without defining it; the codemode description no longer includes the shared MCP types
33
+ - Changed `serializeConversation()` (compaction, branch summaries, bug reports) to omit thinking blocks; replayed thinking is often not in the live context, so including it could make a summarization request larger than the context it summarizes and fail
34
+
35
+ ### Fixed
36
+
37
+ - Fixed codemode ignoring a `// @options:` line that is not the first line of the script, which ran the script without the requested `timeout_ms`; the call now fails with an error before the script runs ([#10631](https://github.com/earendil-works/pi/issues/10631))
38
+ - Fixed Mistral streams being aborted mid-generation (e.g. during long thinking) after the request timeout; the timeout now applies only to waiting for response headers ([#10609](https://github.com/earendil-works/pi/issues/10609))
39
+ - Fixed a symlinked `AGENTS.md` in a git worktree nested inside its main repo causing no project context file to load ([#10681](https://github.com/earendil-works/pi/issues/10681))
40
+ - Fixed the code mode tool description claiming every failed tool call rejects; a tool that declares an output schema resolves with its value on failure too (e.g. `bash` with a nonzero exit)
41
+ - Fixed runs started by `pi.sendMessage(..., { triggerTurn: true })` removing system prompt sections added in `before_agent_start` after their first tool call, which changed the prompt mid-run and broke the provider prompt cache twice per run ([#10267](https://github.com/earendil-works/pi/issues/10267), [#10739](https://github.com/earendil-works/pi/pull/10739) by [@davidbrai](https://github.com/davidbrai))
42
+ - Fixed codemode blocking the process for seconds to minutes after scripts with many `text()` calls; joining the output items was quadratic ([#10678](https://github.com/earendil-works/pi/issues/10678))
43
+ - Fixed print mode (`-p`) exiting 0 without a warning when the final response was cut off at the model's output token limit; it now prints the partial text, writes a warning to stderr, and exits 1 ([#10799](https://github.com/earendil-works/pi/issues/10799))
44
+ - Fixed `pi mcp login --help` and the CLI docs describing `--timeout` as only the wait for the browser; it limits the whole sign-in ([#10636](https://github.com/earendil-works/pi/issues/10636))
45
+ - Fixed `~` not being expanded in `!pattern`, `+path`, and `-path` entries of the `extensions`, `skills`, `prompts`, and `themes` settings, so entries like `!~/skills/pdf` matched nothing ([#10765](https://github.com/earendil-works/pi/issues/10765))
46
+ - Fixed MCP servers with resources registering no tools until every resource was listed; Pi no longer lists resources when connecting, so `/mcp` and `pi mcp list` show that a server offers resources instead of counting them ([#10526](https://github.com/earendil-works/pi/issues/10526))
47
+ - Fixed MCP tool calls failing when the server returns structured content that is not an object (an array, `null`, or a primitive, as MCP 2026-07-28 allows); codemode declarations of MCP tools without an output schema now type `structuredContent` as `unknown` instead of an object ([#10416](https://github.com/earendil-works/pi/issues/10416))
48
+ - Fixed reading or writing `auth.json` on Windows failing with `EPERM` when the previous lock folder was still being deleted; Pi now retries briefly before reporting the error ([#10801](https://github.com/earendil-works/pi/issues/10801))
49
+ - Fixed OpenRouter router models such as `openrouter/auto` showing a cost of -$1,000,000 per 1M tokens; OpenRouter's negative placeholder prices are now 0 ([#10672](https://github.com/earendil-works/pi/pull/10672) by [@davidbrai](https://github.com/davidbrai))
50
+ - Fixed Anthropic requests failing when the provider rejects the strict tool set for grammar size, tool count, or union limits; Pi now retries once with preferred tools non-strict ([#10409](https://github.com/earendil-works/pi/issues/10409))
51
+ - Fixed "The upstream provider returned an error" not being retried as a transient provider error ([#10842](https://github.com/earendil-works/pi/issues/10842))
52
+ - Fixed focused fullscreen overlays blocking mouse-wheel scrolling of the transcript ([#10769](https://github.com/earendil-works/pi/issues/10769))
53
+ - Fixed fragmented terminal color replies leaking hex values into the prompt and opening the external editor (related reports: [#10256](https://github.com/earendil-works/pi/issues/10256), [#10657](https://github.com/earendil-works/pi/issues/10657))
54
+ - Fixed inline images not rendering in Herdr since 1.1.0 ([#10774](https://github.com/earendil-works/pi/pull/10774) by [@ogulcancelik](https://github.com/ogulcancelik))
55
+ - Fixed key bindings that name a shifted symbol, such as `+` or `ctrl++`, not matching under the Kitty keyboard protocol or modifyOtherKeys ([#9880](https://github.com/earendil-works/pi/pull/9880) by [@christianklotz](https://github.com/christianklotz))
56
+ - Fixed the compaction status indicator embedded in a custom editor using the default muted colors instead of the editor border or thinking-level color, as the working indicator does ([#10809](https://github.com/earendil-works/pi/issues/10809), [#10810](https://github.com/earendil-works/pi/pull/10810) by [@Jay-Madden](https://github.com/Jay-Madden))
57
+ - Fixed MCP tool calls that were cancelled or timed out while waiting for an OAuth refresh still being sent to the server once the refresh finished ([#10598](https://github.com/earendil-works/pi/issues/10598))
58
+ - Fixed MCP OAuth sign-in accepting an authorization response with an `iss` parameter that could not be checked because no authorization server metadata was discovered; sign-in now fails with an issuer mismatch. Reported by [@AizenvoltPrime](https://github.com/AizenvoltPrime)
59
+ - Fixed codemode failing with a broken bridge error when Pi runs under `node --watch` on Node 24 or 26 ([#10725](https://github.com/earendil-works/pi/issues/10725))
60
+
61
+ ### Removed
62
+
63
+ - Removed the unreleased experimental server, client, and plugin stack (`src/experimental`, `src/cli/experimental`) and its source-only `./client` and `./experimental/plugin` exports; none of it was part of the published package. The `@earendil-works/pi-client`, `@earendil-works/pi-protocol`, and `@earendil-works/pi-server` packages it used are removed and no longer released
64
+ - Removed the dependency on `@earendil-works/chord`
65
+
3
66
  ## [1.1.0] - 2026-10-07
4
67
 
5
68
  ### New Features
@@ -27,7 +27,7 @@ pi --print "Summarize the changes in this repository"
27
27
 
28
28
  Use print mode when only the final text is needed, including command substitution, pipelines, and one-shot jobs. Intermediate events are not exposed.
29
29
 
30
- Print mode writes errors to stderr. A final assistant response with an `error` or `aborted` stop reason produces a nonzero exit status.
30
+ Print mode writes errors to stderr. A final assistant response with an `error` or `aborted` stop reason produces a nonzero exit status. A response cut off at the model's output token limit (`length` stop reason) still prints its partial text to stdout, writes a warning to stderr, and produces a nonzero exit status.
31
31
 
32
32
  When no mode is selected explicitly, non-TTY stdin or stdout also selects print mode. This allows piped input and output without adding `--print`.
33
33
 
package/bin/docs/cli.md CHANGED
@@ -325,7 +325,7 @@ These commands work outside a session, so agents can run them through `bash`. Se
325
325
  | `pi mcp add <server> [options] --url <url>` | Add or replace a streamable HTTP server; `--header KEY=VALUE` (repeatable), `--bearer-token-env-var <NAME>` (sends `Authorization: Bearer ${NAME}`), `--oauth-client-id`, `--oauth-client-secret`, `--oauth-callback-port`, and `--oauth-client-name` configure authentication |
326
326
  | `pi mcp remove <server>` | Remove a server from `mcp.json`; stored OAuth credentials are kept |
327
327
  | `pi mcp list [--json]` | Connect to every enabled server and print its state, tools, and errors; exit with `1` when a config entry is invalid or an enabled server is not connected |
328
- | `pi mcp login <server> [--timeout <seconds>]` | Sign in to an OAuth server: open the authorization page and wait for the browser (default 300 seconds); a terminal also accepts the pasted redirect URL |
328
+ | `pi mcp login <server> [--timeout <seconds>]` | Sign in to an OAuth server: open the authorization page and wait for the browser; a terminal also accepts the pasted redirect URL. `--timeout` limits the whole sign-in, including requests to the authorization server (default 300 seconds) |
329
329
  | `pi mcp logout <server>` | Delete the stored OAuth credentials of a server |
330
330
 
331
331
  `add` and `remove` change `~/.pi/agent/mcp.json`, or `.pi/mcp.json` in the current directory with `--local` (`-l`). `add` also takes `--exposure <mode>` (see [Exposure](mcp.md#exposure)) and `--description <text>` and does not connect; run `pi mcp list` to check the server.
@@ -6,7 +6,7 @@ The `codemode` tool lets the model write a JavaScript script that calls pi's oth
6
6
 
7
7
  The tool input is raw JavaScript source, not JSON and not a markdown code fence. It runs as the body of an async function in a QuickJS sandbox, so top-level `await` and `return` work. The sandbox has no Node APIs, file system, network, or timers; scripts reach the outside world only through tools and `models`.
8
8
 
9
- A script may start with an options line:
9
+ A script may start with an options line. It must be the first line; an options line anywhere else fails the call before the script runs:
10
10
 
11
11
  ```js
12
12
  // @options: {"max_output_tokens": 2000, "timeout_ms": 60000}
@@ -279,7 +279,6 @@ Before summarization, messages are serialized to text via [`serializeConversatio
279
279
 
280
280
  ```
281
281
  [User]: What they said
282
- [Assistant thinking]: Internal reasoning
283
282
  [Assistant]: Response text
284
283
  [Assistant tool calls]: read(path="foo.ts"); edit(path="bar.ts", ...)
285
284
  [Tool result]: Output from tool
@@ -287,6 +286,8 @@ Before summarization, messages are serialized to text via [`serializeConversatio
287
286
 
288
287
  This prevents the model from treating it as a conversation to continue.
289
288
 
289
+ Thinking blocks are omitted. Providers return summaries of the real reasoning, and replayed thinking is often not part of the model's live context, so including it could make the summarization request larger than the context it summarizes.
290
+
290
291
  Tool results are truncated to 2000 characters during serialization. Content beyond that limit is replaced with a marker indicating how many characters were truncated. This keeps summarization requests within reasonable token budgets, since tool results (especially from `read` and `bash`) are typically the largest contributors to context size.
291
292
 
292
293
  ## Custom Summarization via Extensions
@@ -346,7 +347,6 @@ pi.on("session_before_compact", async (event, ctx) => {
346
347
  );
347
348
  // Returns:
348
349
  // [User]: message text
349
- // [Assistant thinking]: thinking content
350
350
  // [Assistant]: response text
351
351
  // [Assistant tool calls]: read(path="..."); bash(command="...")
352
352
  // [Tool result]: output text
@@ -86,8 +86,9 @@ These variables are read by Pi itself:
86
86
  | `PI_TELEMETRY` | Override install/update telemetry and provider attribution headers: `1`/`true`/`yes` or `0`/`false`/`no` |
87
87
  | `PI_CACHE_RETENTION` | Set to `long` for extended provider prompt caching where supported |
88
88
  | `PI_SHARE_VIEWER_URL` | Override the base URL used by `/share` |
89
- | `PI_RADIUS_GATEWAY` | Override the Radius gateway origin used by `/bug` uploads and Radius relay connections |
89
+ | `PI_RADIUS_GATEWAY` | Override the Radius gateway origin used by `/bug` uploads |
90
90
  | `PI_HARDWARE_CURSOR` | Set to `1` to show the hardware cursor; see [Terminal setup](terminal-setup.md) |
91
+ | `PI_CLIPBOARD_OSC52` | Set to `1` to also send copied text to the terminal with OSC 52 after the local clipboard write, as Pi does over SSH. Use it when a remote client reaches Pi without SSH variables, such as a multiplexer attached from another machine or a remote IDE daemon |
91
92
  | `PI_HYPERLINKS` | Override OSC 8 hyperlink detection with `1`, `0`, or `auto` |
92
93
  | `PI_PROGRAM_STATUS` | Override OSC 7501 program status detection: `1` always reports, `0` never reports; otherwise Pi reports only after the terminal confirms support. See [Terminal setup](terminal-setup.md#program-status) |
93
94
  | `PI_IMAGE_PROTOCOL` | Override inline image detection with `kitty`, `iterm2`, `none`, or `auto` |
@@ -60,6 +60,7 @@ Start long-lived resources from `session_start` or from the command or tool that
60
60
  Close session-scoped resources from an idempotent `session_shutdown` handler.
61
61
 
62
62
  A run proceeds from input and `before_agent_start`, through model, message, and tool events, to `agent_end`.
63
+ Pi counts as busy while a run starts, including the compaction check and `before_agent_start`, so messages sent meanwhile go into the starting run.
63
64
  Automatic retries, recovery, compaction, or queued work can continue afterward.
64
65
  <a id="agent_start--agent_end--agent_before_settle--agent_settled"></a>
65
66
 
@@ -100,7 +101,7 @@ Use each event’s declared result type rather than assuming every return value
100
101
 
101
102
  Events cover resource discovery, sessions, agent and message lifecycle, providers, tools, and raw input.
102
103
 
103
- `before_agent_start` exposes both the current prompt and its structured `systemPromptOptions`. Prefer changing prompt sections, selected tools, or guidelines so Pi can append a transcript delta. Returning `systemPrompt`, or setting `forceSystemPrompt`, replaces the whole prompt for that run while the transcript continues recording the structured sections. Providers receive the forced text as their leading system prompt.
104
+ `before_agent_start` exposes both the current prompt and its structured `systemPromptOptions`. `event.messageType` says what started the run: `"user"` for a prompt or `pi.sendUserMessage()`, with `prompt` and `images`; `"custom"` for `pi.sendMessage(..., { triggerTurn: true })`, with the starting `message` and no `prompt`. A returned `message` is added to either kind of run; check `messageType` to skip runs started by custom messages. Prefer changing prompt sections, selected tools, or guidelines so Pi can append a transcript delta. Returning `systemPrompt`, or setting `forceSystemPrompt`, replaces the whole prompt for that run while the transcript continues recording the structured sections. Providers receive the forced text as their leading system prompt.
104
105
 
105
106
  `message_end` can replace a finalized message while preserving its role. `tool_call` can mutate input or block execution. `tool_result` handlers compose, with each handler seeing prior changes.
106
107
 
@@ -159,9 +160,9 @@ See [`hello.ts`](../examples/extensions/hello.ts), [`todo.ts`](../examples/exten
159
160
  - `deferred`: like `codemode`, but codemode tools do not list it; `tool_search` can find and activate it.
160
161
  - `hidden`: registered but unreachable. Re-register a tool with `exposure: "hidden"` to withdraw it, since tools cannot be unregistered.
161
162
 
162
- `namespace: { name, description, instructions }` groups related tools, as MCP servers do. Codemode tools list a namespace under one heading with its `description`. `instructions` holds longer usage guidance; it is not listed, and codemode scripts read it with `describeNamespace(name)`.
163
+ `namespace: { name, aliases, description, instructions }` groups related tools, as MCP servers do. Codemode tools list a namespace under one heading with its `description`. `instructions` holds longer usage guidance; it is not listed, and codemode scripts read it with `describeNamespace(name)`. `aliases` are other names scripts may look the namespace up by, for example the server name `docs` for `mcp__docs`; a name and the same name as a script identifier (`my-docs`, `my_docs`) match alike.
163
164
 
164
- Registering a `direct` or `model-only` tool activates it; the other exposures are not activated on registration. The active set (`pi.getActiveTools()`, `pi.setActiveTools()`) is the set of tools declared to the model. `pi.getAllTools()` reports each tool's `exposure`, `namespace`, and `annotations`.
165
+ Registering a `direct` or `model-only` tool activates it; the other exposures are not activated on registration. The active set (`pi.getActiveTools()`, `pi.setActiveTools()`) is the set of tools declared to the model. `pi.getAllTools()` reports each tool's `exposure`, `namespace`, `annotations`, and `reachesTools`.
165
166
 
166
167
  `annotations` are hints about what a tool does, with the meaning of MCP tool annotations: `readOnlyHint`, `destructiveHint`, `idempotentHint`, and `openWorldHint`. MCP tools carry the hints their server declares. Missing hints take the MCP defaults: a tool is not read-only, and may be destructive and reach an open world. The hints are not verified, but a permission extension can use them to decide which calls to confirm. This confirms the calls Codex asks approval for:
167
168
 
@@ -177,7 +178,9 @@ pi.on("tool_call", async (event, ctx) => {
177
178
  });
178
179
  ```
179
180
 
180
- A tool that orchestrates other tools can adjust what the model sees while it is active with `prepareLoadout(loadout)`. It runs whenever the active tools change and receives the declared tools, the callable tools, and every registered tool with its exposure, namespace, and prompt guidelines. It returns replacement `descriptions` for declared tools (including its own) and `hiddenDeclarations`: active tools whose declarations requests leave out while they stay active and callable. The default system prompt leaves hidden tools out of its tool list and rules, and names no tool in the skills hint when the file reader is hidden, so the orchestrating tool should present their guidelines itself. `codemode` uses only this hook, `exposure`, and `ctx.executeTool()`, so another tool can implement the same behavior under a different name.
181
+ A tool that orchestrates other tools can adjust what the model sees while it is active with `prepareLoadout(loadout)`. It runs whenever the active tools change and receives the declared tools, the callable tools, and every registered tool with its exposure, namespace, and prompt guidelines. It returns replacement `descriptions` for declared tools (including its own) and `hiddenDeclarations`: active tools whose declarations requests leave out while they stay active and callable. The default system prompt leaves hidden tools out of its tool list and rules, and names no tool in the skills hint when the file reader is hidden, so the orchestrating tool should present their guidelines itself. `codemode` uses only this hook, `exposure`, `reachesTools`, and `ctx.executeTool()`, so another tool can implement the same behavior under a different name.
182
+
183
+ A tool that reaches other tools declares which ones a call may reach with `reachesTools(input)`: `"all"`, or the words of the input that may name them, where a word refers to every tool or namespace whose name it contains. `codemode` returns `"all"` for scripts that search or list tools and the words of the script otherwise; `tool_search` always returns `"all"`. Extensions that register tools late use it from their `tool_call` handler to finish registering the tools a call needs before it runs, as the MCP extension does for servers that are still connecting. The MCP extension also activates `codemode` or `tool_search` for MCP tools only when the tool of that name declares `reachesTools`.
181
184
 
182
185
  ### Activate tools dynamically
183
186
 
@@ -12,6 +12,7 @@ Map each action identifier to one key or a list of keys:
12
12
 
13
13
  ```json
14
14
  {
15
+ "$schema": "https://pi.dev/schemas/keybindings.schema.json",
15
16
  "app.session.new": "ctrl+shift+n",
16
17
  "app.session.tree": ["ctrl+shift+t", "alt+shift+t"]
17
18
  }
package/bin/docs/mcp.md CHANGED
@@ -265,7 +265,7 @@ Changes to enabled state or exposure apply only to the current session. A file-c
265
265
 
266
266
  An installed extension that registers `/mcp`, such as `pi-mcp-adapter`, replaces the built-in MCP support for sessions. Pi then does not read `mcp.json` or connect its servers in a session, and `/mcp` belongs to the extension. Remove the extension to restore the built-in behavior. To disable built-in MCP support without a replacement, disable `mcp` under Built-in in `pi config`, or set `"extensions": ["-builtin:mcp"]` in [settings](settings.md#resources). `--no-mcp` disables it for one run.
267
267
 
268
- An extension that registers `codemode` or `tool_search` similarly replaces the built-in tool with that name. Shell-level `pi mcp` commands always use the built-in implementation.
268
+ An extension that registers `codemode` or `tool_search` similarly replaces the built-in tool with that name. Pi activates the replacement for MCP servers and waits for the servers its calls need only when it declares [`reachesTools`](extensions.md#tool-exposure). Shell-level `pi mcp` commands always use the built-in implementation.
269
269
 
270
270
  ### Use MCP from the SDK
271
271
 
@@ -48,6 +48,7 @@ Use [`models.json`](configuration.md#agent-directory) when an endpoint speaks an
48
48
 
49
49
  ```json
50
50
  {
51
+ "$schema": "https://pi.dev/schemas/models.schema.json",
51
52
  "providers": {
52
53
  "ollama": {
53
54
  "baseUrl": "http://localhost:11434/v1",
@@ -113,6 +113,26 @@ Radius is currently in early alpha and evolving quickly. See [radius.earendil.co
113
113
 
114
114
  Radius authentication uses its gateway catalog and caches refreshed model metadata for later offline startup. A custom Radius gateway configured in `models.json` uses its own catalog rather than inheriting the public `radius.pi.dev` catalog.
115
115
 
116
+ ### OpenRouter
117
+
118
+ Authenticate with `/login openrouter` or `OPENROUTER_API_KEY`.
119
+
120
+ With a key, Pi lists only the OpenRouter chat models that key may use, with the key's context size, max output, and prices. These come from OpenRouter's `/models/user` endpoint and reflect guardrails and provider restrictions set in OpenRouter. Image and classifier models are not filtered.
121
+
122
+ Pi refreshes the list in the background at startup in interactive mode, when `/model` opens, after `/login`, and on `pi update --models`. The last list is saved and used offline and in `pi -p` and RPC mode. After switching keys outside `/login`, the previous key's list stays until the next refresh.
123
+
124
+ Regional endpoints filter models to their region. To use one, set the base URL in [`models.json`](models.md#configure-a-compatible-endpoint):
125
+
126
+ ```json
127
+ {
128
+ "providers": {
129
+ "openrouter": { "baseUrl": "https://us.openrouter.ai/api/v1" }
130
+ }
131
+ }
132
+ ```
133
+
134
+ If the base URL rejects the key at `/models/user` or does not have that endpoint, for example a proxy configured as the OpenRouter base URL, Pi lists every OpenRouter model.
135
+
116
136
  ### Azure OpenAI
117
137
 
118
138
  The provider ID is `azure` (formerly `azure-openai-responses`). Use it as the key in `auth.json`, `models.json`, and `settings.json`, and in model references such as `--model azure/gpt-5.4`.
@@ -2,6 +2,8 @@
2
2
 
3
3
  This reference lists user-configurable settings, their types, defaults, and purposes. Project settings override agent-directory settings. Resource lists are combined. See [Configuration](configuration.md) for file locations and trust behavior.
4
4
 
5
+ Add `"$schema": "https://pi.dev/schemas/settings.schema.json"` to `settings.json` for editor validation and completion.
6
+
5
7
  ## Model and thinking
6
8
 
7
9
  <a id="model-cycling"></a>
@@ -99,7 +101,7 @@ See [Compaction Reference](compaction.md) for trigger, summarization, and valida
99
101
  | `editorPaddingX` | number | `0` | Horizontal editor padding from 0 to 3 cells. |
100
102
  | `outputPad` | `0 \| 1` | `1` | Horizontal transcript padding for messages, tool output, `!` command output, and summary blocks. |
101
103
  | `autocompleteMaxVisible` | number | `5` | Visible autocomplete entries, from 3 to 20. |
102
- | `showHardwareCursor` | boolean | `false` | Show the terminal cursor while Pi positions it for input methods. |
104
+ | `showHardwareCursor` | boolean | `false` | Use the terminal cursor instead of Pi's drawn cursor. Pi still positions it for input methods. |
103
105
  | `terminal.showImages` | boolean | `true` | Display inline images when supported. |
104
106
  | `terminal.imageWidthCells` | number | `60` | Preferred inline image width in terminal cells. |
105
107
  | `terminal.clearOnShrink` | boolean | `false` | Clear empty rows when rendered content shrinks. |
@@ -58,7 +58,7 @@ See [CLI resources](cli.md#resources) for the command-line option.
58
58
 
59
59
  ## Create a custom theme
60
60
 
61
- Copy one of the [built-in themes](https://github.com/earendil-works/pi/tree/main/packages/coding-agent/src/modes/interactive/theme) or create a new JSON file conforming to the [schema](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json). The built-in themes use OKHSL colors, with variables for colors that several roles share, so you can adjust a hue, saturation, or lightness directly.
61
+ Copy one of the [built-in themes](https://github.com/earendil-works/pi/tree/main/packages/coding-agent/src/modes/interactive/theme) or create a new JSON file conforming to the [schema](https://pi.dev/schemas/theme.schema.json). The built-in themes use OKHSL colors, with variables for colors that several roles share, so you can adjust a hue, saturation, or lightness directly.
62
62
 
63
63
  1. Save the file as `<agent-dir>/themes/my-theme.json`. The agent directory defaults to `~/.pi/agent`.
64
64
  2. Set its `name` to `my-theme`.
@@ -78,6 +78,8 @@ Use the theme name as the filename. Pi hot-reloads the active user theme only fr
78
78
  | `colors` | Yes | Assigns colors to terminal UI roles. The schema identifies required and optional roles. |
79
79
  | `export` | No | Overrides page and panel backgrounds in HTML exports. |
80
80
 
81
+ Theme objects are strict: only documented top-level fields and color tokens are accepted. Define reusable custom colors under `vars`; custom keys under `colors` or `export` and additional top-level metadata are rejected.
82
+
81
83
  A color can be written in six forms:
82
84
 
83
85
  | Form | Example | Meaning |
@@ -93,7 +95,7 @@ Terminal default colors render as the terminal's own colors. Where Pi needs a co
93
95
 
94
96
  Pi resolves chained variable references. A missing variable or circular reference makes the theme invalid. Pi uses truecolor when available, gamut-maps OKLCH to sRGB, and approximates colors for 256-color terminals. HTML exports convert OKHSL values to hexadecimal because CSS does not support them. If colors differ from their source values, check your terminal's truecolor detection and contrast settings. See [Configure Your Terminal](terminal-setup.md#override-detected-capabilities).
95
97
 
96
- Use the [theme JSON schema](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json) for the exact properties, required colors, and accepted value types.
98
+ Use the [theme JSON schema](https://pi.dev/schemas/theme.schema.json) for the exact properties, required colors, and accepted value types.
97
99
 
98
100
  Pi reports invalid theme files during startup and `/reload`.
99
101
 
package/bin/docs/tui.md CHANGED
@@ -45,7 +45,7 @@ Prefer these components over rebuilding selection, scrolling, text editing, or w
45
45
 
46
46
  Use `matchesKey()` and `Key` for terminal keyboard input. The parser accounts for supported terminal protocols and key modifiers. Extension components should use the injected `KeybindingsManager` for configurable application actions.
47
47
 
48
- A component that displays a text cursor should implement `Focusable` and place `CURSOR_MARKER` immediately before its visual cursor. The TUI uses that marker to position the hardware cursor for input method editors.
48
+ A component that displays a text cursor should implement `Focusable` and place `CURSOR_MARKER` immediately before its visual cursor. Wrap the cursor cell in `renderFakeCursor()` so the TUI draws it in reverse video, or hides it after `CURSOR_MARKER` when the hardware cursor is shown. The TUI uses the marker to position the hardware cursor for input method editors.
49
49
 
50
50
  Containers that wrap an `Input` or `Editor` must propagate their `focused` state to that child. Without propagation, Chinese, Japanese, Korean, and other IME candidate windows can appear at the wrong screen position.
51
51
 
@@ -28,9 +28,6 @@ Example extensions demonstrating:
28
28
  - External integrations (SSH, file watchers, system theme sync)
29
29
  - Custom providers (Anthropic with custom streaming, GitLab Duo)
30
30
 
31
- ### [plugins/pi-example-plugin/](plugins/pi-example-plugin/)
32
- An experimental plugin package that Pi automatically builds into separate Session-worker and TUI Chord facets.
33
-
34
31
  ## Documentation
35
32
 
36
33
  - [SDK Examples](sdk/README.md)
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pi-extension-custom-provider",
9
- "version": "1.1.0",
9
+ "version": "1.2.0",
10
10
  "dependencies": {
11
11
  "@anthropic-ai/sdk": "^0.52.0"
12
12
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider-anthropic",
3
3
  "private": true,
4
- "version": "1.1.0",
4
+ "version": "1.2.0",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider-gitlab-duo",
3
3
  "private": true,
4
- "version": "1.1.0",
4
+ "version": "1.2.0",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,5 +1,5 @@
1
1
  {
2
- "$schema": "https://raw.githubusercontent.com/earendil-works/pi/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
2
+ "$schema": "https://pi.dev/schemas/theme.schema.json",
3
3
  "name": "dynamic-resources",
4
4
  "vars": {
5
5
  "cyan": "#00d7ff",
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pi-extension-gondolin",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pi-extension-gondolin",
9
- "version": "1.1.0",
9
+ "version": "1.2.0",
10
10
  "dependencies": {
11
11
  "@earendil-works/gondolin": "0.12.0"
12
12
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-gondolin",
3
3
  "private": true,
4
- "version": "1.1.0",
4
+ "version": "1.2.0",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -9,7 +9,7 @@
9
9
  */
10
10
 
11
11
  import type { ExtensionAPI, ExtensionCommandContext, Theme } from "@earendil-works/pi-coding-agent";
12
- import { CURSOR_MARKER, type Focusable, matchesKey, visibleWidth } from "@earendil-works/pi-tui";
12
+ import { CURSOR_MARKER, type Focusable, matchesKey, renderFakeCursor, visibleWidth } from "@earendil-works/pi-tui";
13
13
 
14
14
  export default function (pi: ExtensionAPI) {
15
15
  pi.registerCommand("overlay-test", {
@@ -131,7 +131,7 @@ class OverlayTestComponent implements Focusable {
131
131
  const after = inputDisplay.slice(item.cursor + 1);
132
132
  // Emit hardware cursor marker for IME support when focused
133
133
  const marker = this.focused ? CURSOR_MARKER : "";
134
- inputDisplay = `${before}${marker}\x1b[7m${cursorChar}\x1b[27m${after}`;
134
+ inputDisplay = `${before}${marker}${renderFakeCursor(cursorChar)}${after}`;
135
135
  }
136
136
  content = `${prefix + label} ${inputDisplay}`;
137
137
  } else {
@@ -197,8 +197,12 @@ export default function planModeExtension(pi: ExtensionAPI): void {
197
197
  };
198
198
  });
199
199
 
200
- // Inject plan/execution context before agent starts
201
- pi.on("before_agent_start", async () => {
200
+ // Inject plan/execution context before a user prompt. Runs started by custom messages, such as
201
+ // background task notifications, skip it: the context from the last prompt is still in the
202
+ // conversation, and adding it again on every notification would only repeat it.
203
+ pi.on("before_agent_start", async (event) => {
204
+ if (event.messageType !== "user") return;
205
+
202
206
  if (planModeEnabled) {
203
207
  return {
204
208
  message: {
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pi-extension-sandbox",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pi-extension-sandbox",
9
- "version": "1.1.0",
9
+ "version": "1.2.0",
10
10
  "dependencies": {
11
11
  "@anthropic-ai/sandbox-runtime": "^0.0.26"
12
12
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-sandbox",
3
3
  "private": true,
4
- "version": "1.1.0",
4
+ "version": "1.2.0",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pi-extension-with-deps",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pi-extension-with-deps",
9
- "version": "1.1.0",
9
+ "version": "1.2.0",
10
10
  "dependencies": {
11
11
  "ms": "^2.1.3"
12
12
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-with-deps",
3
3
  "private": true,
4
- "version": "1.1.0",
4
+ "version": "1.2.0",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
package/bin/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@earendil-works/pi-coding-agent",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "piConfig": {
@@ -18,21 +18,13 @@
18
18
  },
19
19
  "./rpc-entry": {
20
20
  "import": "./dist/bundle/rpc-entry.js"
21
- },
22
- "./client": {
23
- "source": "./src/client/index.ts"
24
- },
25
- "./experimental/plugin": {
26
- "source": "./src/experimental/plugin.ts"
27
21
  }
28
22
  },
29
23
  "files": [
30
24
  "dist",
31
- "!dist/client",
32
- "!dist/experimental",
33
- "!dist/cli/experimental",
34
25
  "docs",
35
26
  "examples",
27
+ "schemas",
36
28
  "containerization.md",
37
29
  "CHANGELOG.md"
38
30
  ],
@@ -40,19 +32,19 @@
40
32
  "clean": "shx rm -rf dist",
41
33
  "build": "npm run build:unbundled && node ../../scripts/build-coding-agent-bundle.mjs",
42
34
  "build:unbundled": "tsc -p tsconfig.build.json && shx chmod +x dist/cli.js dist/rpc-entry.js && npm run copy-assets",
43
- "build:binary": "npm --prefix ../tui run build && npm --prefix ../telemetry run build && npm --prefix ../ai run build && npm --prefix ../agent run build && npm --prefix ../protocol run build && npm --prefix ../client run build && npm run build && bun build --compile --no-compile-autoload-bunfig --no-compile-autoload-dotenv ./dist/bun/cli.js ./src/utils/image-resize-worker.ts ./src/extensions/codemode/worker.ts --outfile dist/pi && npm run copy-binary-assets",
35
+ "build:binary": "npm --prefix ../tui run build && npm --prefix ../telemetry run build && npm --prefix ../ai run build && npm --prefix ../agent run build && npm run build && bun build --compile --no-compile-autoload-bunfig --no-compile-autoload-dotenv ./dist/bun/cli.js ./src/utils/image-resize-worker.ts ./src/extensions/codemode/worker.ts --outfile dist/pi && npm run copy-binary-assets",
44
36
  "copy-assets": "shx mkdir -p dist/modes/interactive/theme && shx cp src/modes/interactive/theme/*.json dist/modes/interactive/theme/ && shx mkdir -p dist/modes/interactive/assets && shx cp src/modes/interactive/assets/*.png dist/modes/interactive/assets/ && shx mkdir -p dist/core/export-html/vendor && shx cp src/core/export-html/template.html src/core/export-html/template.css src/core/export-html/template.js dist/core/export-html/ && shx cp src/core/export-html/vendor/*.js dist/core/export-html/vendor/",
45
37
  "copy-binary-assets": "shx cp package.json dist/ && shx cp README.md dist/ && shx cp CHANGELOG.md dist/ && shx mkdir -p dist/theme && shx cp src/modes/interactive/theme/*.json dist/theme/ && shx mkdir -p dist/assets && shx cp src/modes/interactive/assets/*.png dist/assets/ && shx mkdir -p dist/export-html/vendor && shx cp src/core/export-html/template.html dist/export-html/ && shx cp src/core/export-html/vendor/*.js dist/export-html/vendor/ && shx cp -r docs dist/ && shx cp -r examples dist/ && shx cp ../../node_modules/@silvia-odwyer/photon-node/photon_rs_bg.wasm dist/",
46
38
  "test": "vitest --run",
39
+ "generate:schemas": "node --import ./src/experimental/source-resolver.ts scripts/generate-schemas.ts",
47
40
  "prepublishOnly": "npm run clean && npm run build"
48
41
  },
49
42
  "dependencies": {
50
- "@earendil-works/chord": "^1.1.0",
51
- "@earendil-works/pi-agent-core": "^1.1.0",
52
- "@earendil-works/pi-ai": "^1.1.0",
53
- "@earendil-works/pi-codemode": "^1.1.0",
54
- "@earendil-works/pi-mcp": "^1.1.0",
55
- "@earendil-works/pi-tui": "^1.1.0",
43
+ "@earendil-works/pi-agent-core": "^1.2.0",
44
+ "@earendil-works/pi-ai": "^1.2.0",
45
+ "@earendil-works/pi-codemode": "^1.2.0",
46
+ "@earendil-works/pi-mcp": "^1.2.0",
47
+ "@earendil-works/pi-tui": "^1.2.0",
56
48
  "@silvia-odwyer/photon-node": "0.3.4",
57
49
  "brace-expansion": "5.0.12",
58
50
  "chalk": "6.0.0",
@@ -75,9 +67,6 @@
75
67
  "protobufjs": "7.6.6"
76
68
  },
77
69
  "devDependencies": {
78
- "@earendil-works/pi-client": "^1.1.0",
79
- "@earendil-works/pi-protocol": "^1.1.0",
80
- "@earendil-works/pi-server": "^1.1.0",
81
70
  "@types/cross-spawn": "6.0.6",
82
71
  "@types/hosted-git-info": "3.0.5",
83
72
  "@types/node": "22.19.19",
package/bin/pi.exe CHANGED
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: package/bin/pi.exe
3
- size: 107966976 bytes
4
- sha256: d9f8bb9964cbe655f94c0f316b0d44d83c3600c33811921af1838cb8168f8448
3
+ size: 108142080 bytes
4
+ sha256: 0d8ebac9354d1e795b628ae67937c10cd12fc22b53c4e4d4bfd846229d1b5710
@@ -1,5 +1,5 @@
1
1
  {
2
- "$schema": "https://raw.githubusercontent.com/earendil-works/pi/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
2
+ "$schema": "https://pi.dev/schemas/theme.schema.json",
3
3
  "name": "dark",
4
4
  "appearance": "dark",
5
5
  "vars": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$schema": "https://raw.githubusercontent.com/earendil-works/pi/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
2
+ "$schema": "https://pi.dev/schemas/theme.schema.json",
3
3
  "name": "light",
4
4
  "appearance": "light",
5
5
  "vars": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kingingwang/pi-windows-arm64",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Pi standalone binary for windows-arm64 (fork distribution)",
5
5
  "license": "MIT",
6
6
  "os": [
@@ -1,38 +0,0 @@
1
- # `@earendil-works/pi-example-plugin`
2
-
3
- This package provides conventional `session` and `tui` Chord facets. The Session-worker facet provides a remote greeting service. The TUI facet contributes `/hello` and calls that service.
4
-
5
- The package needs no build script. Pi asks Chord to discover `src/session.ts` and `src/tui.ts`, builds both entries into its server-owned plugin cache, and sends the TUI artifact to clients.
6
-
7
- From the repository root:
8
-
9
- ```bash
10
- PI_EXPERIMENTAL=1 ./pi-test.sh server \
11
- -e "$PWD/packages/coding-agent/examples/plugins/pi-example-plugin"
12
- ```
13
-
14
- Alternatively, a client can select the plugin for the Session it creates or resumes on one local server:
15
-
16
- ```bash
17
- PI_EXPERIMENTAL=1 ./pi-test.sh client \
18
- -e "$PWD/packages/coding-agent/examples/plugins/pi-example-plugin"
19
- ```
20
-
21
- Repeat `-e` to select multiple plugin packages. Client paths are resolved locally and sent only to a Unix server; Radius clients cannot select server filesystem paths. The Session and matching TUI facets are stored with that Session, so later server generations and clients can resume it without plugin arguments. Other Sessions and their workers are unaffected. An active Session rejects a different package selection instead of being restarted.
22
-
23
- `server -e` establishes the server profile's default Session and TUI facets. Starting an explicit foreground server without `-e` clears that default. Client selection never changes the server's root facet generation.
24
-
25
- Run `/hello Armin` in the TUI. After editing a facet, run `/reload`. The server atomically rebuilds the package, reloads the attached Session-worker generation, updates the current TUI generation, and serves the new artifact to future clients.
26
-
27
- Package metadata can override or disable conventional entries:
28
-
29
- ```json
30
- {
31
- "chord": {
32
- "facets": {
33
- "session": "./src/worker.ts",
34
- "tui": false
35
- }
36
- }
37
- }
38
- ```
@@ -1,10 +0,0 @@
1
- {
2
- "name": "@earendil-works/pi-example-plugin",
3
- "version": "1.0.0",
4
- "private": true,
5
- "type": "module",
6
- "peerDependencies": {
7
- "@earendil-works/chord": "^0.84.4",
8
- "@earendil-works/pi-coding-agent": "^0.84.4"
9
- }
10
- }
@@ -1,13 +0,0 @@
1
- import { type Context, defineService, type ReplicatedState } from "@earendil-works/chord";
2
-
3
- export interface ExampleFacetReply {
4
- readonly message: string;
5
- readonly workerActivations: number;
6
- }
7
-
8
- export interface ExampleFacetService {
9
- readonly workerActivations: ReplicatedState<{ count: number }>;
10
- greet(input: { readonly name: string }, context: Context): Promise<ExampleFacetReply>;
11
- }
12
-
13
- export const ExampleFacetService = defineService<ExampleFacetService>("pi.example-plugin.greeting");
@@ -1,24 +0,0 @@
1
- import { defineFacet } from "@earendil-works/chord";
2
- import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
3
- import { ExampleFacetService } from "./contract.ts";
4
-
5
- export default defineFacet({
6
- id: "@earendil-works/pi-example-plugin/session",
7
- setup(env) {
8
- const workerActivations = env.replicatedState({ count: 0 });
9
- env.provide(ExampleFacetService, {
10
- workerActivations,
11
- async greet({ name }) {
12
- return {
13
- message: `Hello ${name} from the bundled Session worker facet!!!`,
14
- workerActivations: workerActivations.value.count,
15
- };
16
- },
17
- });
18
- env.onActivate(() => {
19
- workerActivations.change(BACKGROUND_CONTEXT, (draft) => {
20
- draft.count += 1;
21
- });
22
- });
23
- },
24
- });
@@ -1,31 +0,0 @@
1
- import { defineFacet } from "@earendil-works/chord";
2
- import { AgentController, PresentationUI, SlashCommands } from "@earendil-works/pi-coding-agent/experimental/plugin";
3
- import { ExampleFacetService } from "./contract.ts";
4
-
5
- export default defineFacet({
6
- id: "@earendil-works/pi-example-plugin/tui",
7
- setup(env) {
8
- const example = env.use(ExampleFacetService);
9
- const commands = env.use(SlashCommands);
10
- const controller = env.use(AgentController);
11
- const ui = env.use(PresentationUI);
12
- env.onActivate(() => {
13
- env.own(
14
- commands.replace({
15
- name: "hello",
16
- description: "Call the bundled Session worker facet",
17
- argumentHint: "<name>",
18
- async run(args, context) {
19
- const message = args.length === 0 ? "from the TUI" : args;
20
- const reply = await example.greet({ name: message }, context);
21
- ui.showStatus(
22
- `We got this: ${reply.message} Worker activations: ${reply.workerActivations}.`,
23
- context,
24
- );
25
- return controller.prompt({ message: `pong: ${message}`, images: null }, context);
26
- },
27
- }),
28
- );
29
- });
30
- },
31
- });
@@ -1,361 +0,0 @@
1
- {
2
- "$schema": "http://json-schema.org/draft-07/schema#",
3
- "title": "Pi Coding Agent Theme",
4
- "description": "Theme schema for Pi coding agent",
5
- "type": "object",
6
- "required": ["name", "colors"],
7
- "properties": {
8
- "$schema": {
9
- "type": "string",
10
- "description": "JSON schema reference"
11
- },
12
- "name": {
13
- "type": "string",
14
- "pattern": "^[^/]+$",
15
- "description": "Theme name. Must not contain '/' because it is reserved for automatic light/dark theme settings."
16
- },
17
- "appearance": {
18
- "type": "string",
19
- "enum": ["dark", "light"],
20
- "description": "Background the theme is designed for. Detected from the theme colors when omitted."
21
- },
22
- "vars": {
23
- "type": "object",
24
- "description": "Reusable color variables",
25
- "additionalProperties": {
26
- "oneOf": [
27
- {
28
- "type": "string",
29
- "description": "Hex color (#RGB or #RRGGBB), OKLCH or OKHSL color, variable reference, or empty string for terminal default"
30
- },
31
- {
32
- "type": "integer",
33
- "minimum": 0,
34
- "maximum": 255,
35
- "description": "256-color palette index (0-255)"
36
- }
37
- ]
38
- }
39
- },
40
- "colors": {
41
- "type": "object",
42
- "description": "Theme color definitions (scrollbar, thinkingMax, and search highlight colors are optional and use compatible fallbacks)",
43
- "required": [
44
- "accent",
45
- "border",
46
- "borderAccent",
47
- "borderMuted",
48
- "success",
49
- "error",
50
- "warning",
51
- "muted",
52
- "dim",
53
- "text",
54
- "thinkingText",
55
- "selectedBg",
56
- "userMessageBg",
57
- "userMessageText",
58
- "customMessageBg",
59
- "customMessageText",
60
- "customMessageLabel",
61
- "toolPendingBg",
62
- "toolSuccessBg",
63
- "toolErrorBg",
64
- "toolTitle",
65
- "toolOutput",
66
- "mdHeading",
67
- "mdLink",
68
- "mdLinkUrl",
69
- "mdCode",
70
- "mdCodeBlock",
71
- "mdCodeBlockBorder",
72
- "mdQuote",
73
- "mdQuoteBorder",
74
- "mdHr",
75
- "mdListBullet",
76
- "toolDiffAdded",
77
- "toolDiffRemoved",
78
- "toolDiffContext",
79
- "syntaxComment",
80
- "syntaxKeyword",
81
- "syntaxFunction",
82
- "syntaxVariable",
83
- "syntaxString",
84
- "syntaxNumber",
85
- "syntaxType",
86
- "syntaxOperator",
87
- "syntaxPunctuation",
88
- "thinkingOff",
89
- "thinkingMinimal",
90
- "thinkingLow",
91
- "thinkingMedium",
92
- "thinkingHigh",
93
- "thinkingXhigh",
94
- "bashMode"
95
- ],
96
- "properties": {
97
- "accent": {
98
- "$ref": "#/$defs/colorValue",
99
- "description": "Primary accent color (logo, selected items, cursor)"
100
- },
101
- "border": {
102
- "$ref": "#/$defs/colorValue",
103
- "description": "Normal borders"
104
- },
105
- "borderAccent": {
106
- "$ref": "#/$defs/colorValue",
107
- "description": "Highlighted borders"
108
- },
109
- "borderMuted": {
110
- "$ref": "#/$defs/colorValue",
111
- "description": "Subtle borders"
112
- },
113
- "success": {
114
- "$ref": "#/$defs/colorValue",
115
- "description": "Success states"
116
- },
117
- "error": {
118
- "$ref": "#/$defs/colorValue",
119
- "description": "Error states"
120
- },
121
- "warning": {
122
- "$ref": "#/$defs/colorValue",
123
- "description": "Warning states"
124
- },
125
- "muted": {
126
- "$ref": "#/$defs/colorValue",
127
- "description": "Secondary/dimmed text"
128
- },
129
- "dim": {
130
- "$ref": "#/$defs/colorValue",
131
- "description": "Very dimmed text (more subtle than muted)"
132
- },
133
- "text": {
134
- "$ref": "#/$defs/colorValue",
135
- "description": "Default text color (usually empty string)"
136
- },
137
- "thinkingText": {
138
- "$ref": "#/$defs/colorValue",
139
- "description": "Thinking block text color"
140
- },
141
- "selectedBg": {
142
- "$ref": "#/$defs/colorValue",
143
- "description": "Selected item background"
144
- },
145
- "scrollbarTrack": {
146
- "$ref": "#/$defs/colorValue",
147
- "description": "Fullscreen scrollbar track foreground (falls back to muted when omitted)"
148
- },
149
- "scrollbarThumb": {
150
- "$ref": "#/$defs/colorValue",
151
- "description": "Fullscreen scrollbar thumb foreground (falls back to text when omitted)"
152
- },
153
- "searchMatchBg": {
154
- "$ref": "#/$defs/colorValue",
155
- "description": "Transcript search match background and current-match text (falls back to selectedBg when omitted)"
156
- },
157
- "searchMatchText": {
158
- "$ref": "#/$defs/colorValue",
159
- "description": "Transcript search match text and current-match background (falls back to text when omitted)"
160
- },
161
- "userMessageBg": {
162
- "$ref": "#/$defs/colorValue",
163
- "description": "User message background"
164
- },
165
- "userMessageText": {
166
- "$ref": "#/$defs/colorValue",
167
- "description": "User message text color"
168
- },
169
- "customMessageBg": {
170
- "$ref": "#/$defs/colorValue",
171
- "description": "Custom message background (hook-injected messages)"
172
- },
173
- "customMessageText": {
174
- "$ref": "#/$defs/colorValue",
175
- "description": "Custom message text color"
176
- },
177
- "customMessageLabel": {
178
- "$ref": "#/$defs/colorValue",
179
- "description": "Custom message type label color"
180
- },
181
- "toolPendingBg": {
182
- "$ref": "#/$defs/colorValue",
183
- "description": "Tool execution box (pending state)"
184
- },
185
- "toolSuccessBg": {
186
- "$ref": "#/$defs/colorValue",
187
- "description": "Tool execution box (success state)"
188
- },
189
- "toolErrorBg": {
190
- "$ref": "#/$defs/colorValue",
191
- "description": "Tool execution box (error state)"
192
- },
193
- "toolTitle": {
194
- "$ref": "#/$defs/colorValue",
195
- "description": "Tool execution box title color"
196
- },
197
- "toolOutput": {
198
- "$ref": "#/$defs/colorValue",
199
- "description": "Tool execution box output text color"
200
- },
201
- "mdHeading": {
202
- "$ref": "#/$defs/colorValue",
203
- "description": "Markdown heading text"
204
- },
205
- "mdLink": {
206
- "$ref": "#/$defs/colorValue",
207
- "description": "Markdown link text"
208
- },
209
- "mdLinkUrl": {
210
- "$ref": "#/$defs/colorValue",
211
- "description": "Markdown link URL"
212
- },
213
- "mdCode": {
214
- "$ref": "#/$defs/colorValue",
215
- "description": "Markdown inline code"
216
- },
217
- "mdCodeBlock": {
218
- "$ref": "#/$defs/colorValue",
219
- "description": "Markdown code block content"
220
- },
221
- "mdCodeBlockBorder": {
222
- "$ref": "#/$defs/colorValue",
223
- "description": "Markdown code block fences"
224
- },
225
- "mdQuote": {
226
- "$ref": "#/$defs/colorValue",
227
- "description": "Markdown blockquote text"
228
- },
229
- "mdQuoteBorder": {
230
- "$ref": "#/$defs/colorValue",
231
- "description": "Markdown blockquote border"
232
- },
233
- "mdHr": {
234
- "$ref": "#/$defs/colorValue",
235
- "description": "Markdown horizontal rule"
236
- },
237
- "mdListBullet": {
238
- "$ref": "#/$defs/colorValue",
239
- "description": "Markdown list bullets/numbers"
240
- },
241
- "toolDiffAdded": {
242
- "$ref": "#/$defs/colorValue",
243
- "description": "Added lines in tool diffs"
244
- },
245
- "toolDiffRemoved": {
246
- "$ref": "#/$defs/colorValue",
247
- "description": "Removed lines in tool diffs"
248
- },
249
- "toolDiffContext": {
250
- "$ref": "#/$defs/colorValue",
251
- "description": "Context lines in tool diffs"
252
- },
253
- "syntaxComment": {
254
- "$ref": "#/$defs/colorValue",
255
- "description": "Syntax highlighting: comments"
256
- },
257
- "syntaxKeyword": {
258
- "$ref": "#/$defs/colorValue",
259
- "description": "Syntax highlighting: keywords"
260
- },
261
- "syntaxFunction": {
262
- "$ref": "#/$defs/colorValue",
263
- "description": "Syntax highlighting: function names"
264
- },
265
- "syntaxVariable": {
266
- "$ref": "#/$defs/colorValue",
267
- "description": "Syntax highlighting: variable names"
268
- },
269
- "syntaxString": {
270
- "$ref": "#/$defs/colorValue",
271
- "description": "Syntax highlighting: string literals"
272
- },
273
- "syntaxNumber": {
274
- "$ref": "#/$defs/colorValue",
275
- "description": "Syntax highlighting: number literals"
276
- },
277
- "syntaxType": {
278
- "$ref": "#/$defs/colorValue",
279
- "description": "Syntax highlighting: type names"
280
- },
281
- "syntaxOperator": {
282
- "$ref": "#/$defs/colorValue",
283
- "description": "Syntax highlighting: operators"
284
- },
285
- "syntaxPunctuation": {
286
- "$ref": "#/$defs/colorValue",
287
- "description": "Syntax highlighting: punctuation"
288
- },
289
- "thinkingOff": {
290
- "$ref": "#/$defs/colorValue",
291
- "description": "Thinking level border: off"
292
- },
293
- "thinkingMinimal": {
294
- "$ref": "#/$defs/colorValue",
295
- "description": "Thinking level border: minimal"
296
- },
297
- "thinkingLow": {
298
- "$ref": "#/$defs/colorValue",
299
- "description": "Thinking level border: low"
300
- },
301
- "thinkingMedium": {
302
- "$ref": "#/$defs/colorValue",
303
- "description": "Thinking level border: medium"
304
- },
305
- "thinkingHigh": {
306
- "$ref": "#/$defs/colorValue",
307
- "description": "Thinking level border: high"
308
- },
309
- "thinkingXhigh": {
310
- "$ref": "#/$defs/colorValue",
311
- "description": "Thinking level border: xhigh"
312
- },
313
- "thinkingMax": {
314
- "$ref": "#/$defs/colorValue",
315
- "description": "Thinking level border: max (falls back to thinkingXhigh when omitted)"
316
- },
317
- "bashMode": {
318
- "$ref": "#/$defs/colorValue",
319
- "description": "Editor border color in bash mode"
320
- }
321
- },
322
- "additionalProperties": false
323
- },
324
- "export": {
325
- "type": "object",
326
- "description": "Optional colors for HTML export (defaults derived from userMessageBg if not specified)",
327
- "properties": {
328
- "pageBg": {
329
- "$ref": "#/$defs/colorValue",
330
- "description": "Page background color"
331
- },
332
- "cardBg": {
333
- "$ref": "#/$defs/colorValue",
334
- "description": "Card/container background color"
335
- },
336
- "infoBg": {
337
- "$ref": "#/$defs/colorValue",
338
- "description": "Info sections background (system prompt, notices)"
339
- }
340
- },
341
- "additionalProperties": false
342
- }
343
- },
344
- "additionalProperties": false,
345
- "$defs": {
346
- "colorValue": {
347
- "oneOf": [
348
- {
349
- "type": "string",
350
- "description": "Hex color (#RGB or #RRGGBB), OKLCH or OKHSL color, variable reference, or empty string for terminal default"
351
- },
352
- {
353
- "type": "integer",
354
- "minimum": 0,
355
- "maximum": 255,
356
- "description": "256-color palette index (0-255)"
357
- }
358
- ]
359
- }
360
- }
361
- }