@earendil-works/pi-coding-agent 0.84.0 → 0.84.2

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 (169) hide show
  1. package/CHANGELOG.md +87 -2
  2. package/README.md +1 -0
  3. package/dist/cli/args.d.ts +1 -0
  4. package/dist/cli/args.d.ts.map +1 -1
  5. package/dist/cli/args.js +14 -3
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/cli/auth-check.d.ts +19 -0
  8. package/dist/cli/auth-check.d.ts.map +1 -0
  9. package/dist/cli/auth-check.js +50 -0
  10. package/dist/cli/auth-check.js.map +1 -0
  11. package/dist/cli/auth-command.d.ts +24 -0
  12. package/dist/cli/auth-command.d.ts.map +1 -0
  13. package/dist/cli/auth-command.js +103 -0
  14. package/dist/cli/auth-command.js.map +1 -0
  15. package/dist/cli/credential-print.d.ts +4 -14
  16. package/dist/cli/credential-print.d.ts.map +1 -1
  17. package/dist/cli/credential-print.js +38 -79
  18. package/dist/cli/credential-print.js.map +1 -1
  19. package/dist/core/agent-session.d.ts +7 -2
  20. package/dist/core/agent-session.d.ts.map +1 -1
  21. package/dist/core/agent-session.js +6 -6
  22. package/dist/core/agent-session.js.map +1 -1
  23. package/dist/core/auth-storage.d.ts +10 -0
  24. package/dist/core/auth-storage.d.ts.map +1 -1
  25. package/dist/core/auth-storage.js +78 -1
  26. package/dist/core/auth-storage.js.map +1 -1
  27. package/dist/core/experimental.d.ts +4 -0
  28. package/dist/core/experimental.d.ts.map +1 -1
  29. package/dist/core/experimental.js +4 -0
  30. package/dist/core/experimental.js.map +1 -1
  31. package/dist/core/extensions/types.d.ts +9 -0
  32. package/dist/core/extensions/types.d.ts.map +1 -1
  33. package/dist/core/extensions/types.js.map +1 -1
  34. package/dist/core/keybindings.d.ts +32 -0
  35. package/dist/core/keybindings.d.ts.map +1 -1
  36. package/dist/core/model-config.d.ts +5 -0
  37. package/dist/core/model-config.d.ts.map +1 -1
  38. package/dist/core/model-config.js +1 -0
  39. package/dist/core/model-config.js.map +1 -1
  40. package/dist/core/model-resolver.d.ts.map +1 -1
  41. package/dist/core/model-resolver.js +1 -0
  42. package/dist/core/model-resolver.js.map +1 -1
  43. package/dist/core/model-runtime.d.ts +2 -0
  44. package/dist/core/model-runtime.d.ts.map +1 -1
  45. package/dist/core/model-runtime.js +3 -1
  46. package/dist/core/model-runtime.js.map +1 -1
  47. package/dist/core/project-trust.d.ts.map +1 -1
  48. package/dist/core/project-trust.js +2 -2
  49. package/dist/core/project-trust.js.map +1 -1
  50. package/dist/core/sdk.d.ts +5 -3
  51. package/dist/core/sdk.d.ts.map +1 -1
  52. package/dist/core/sdk.js +2 -1
  53. package/dist/core/sdk.js.map +1 -1
  54. package/dist/core/session-manager.d.ts.map +1 -1
  55. package/dist/core/session-manager.js +2 -2
  56. package/dist/core/session-manager.js.map +1 -1
  57. package/dist/core/settings-manager.d.ts +6 -0
  58. package/dist/core/settings-manager.d.ts.map +1 -1
  59. package/dist/core/settings-manager.js +12 -0
  60. package/dist/core/settings-manager.js.map +1 -1
  61. package/dist/core/system-prompt.d.ts.map +1 -1
  62. package/dist/core/system-prompt.js +1 -1
  63. package/dist/core/system-prompt.js.map +1 -1
  64. package/dist/core/tools/bash.d.ts +4 -0
  65. package/dist/core/tools/bash.d.ts.map +1 -1
  66. package/dist/core/tools/bash.js +8 -4
  67. package/dist/core/tools/bash.js.map +1 -1
  68. package/dist/core/tools/edit.d.ts +4 -0
  69. package/dist/core/tools/edit.d.ts.map +1 -1
  70. package/dist/core/tools/edit.js +13 -7
  71. package/dist/core/tools/edit.js.map +1 -1
  72. package/dist/core/tools/find.d.ts +4 -0
  73. package/dist/core/tools/find.d.ts.map +1 -1
  74. package/dist/core/tools/find.js +6 -2
  75. package/dist/core/tools/find.js.map +1 -1
  76. package/dist/core/tools/grep.d.ts +4 -0
  77. package/dist/core/tools/grep.d.ts.map +1 -1
  78. package/dist/core/tools/grep.js +6 -2
  79. package/dist/core/tools/grep.js.map +1 -1
  80. package/dist/core/tools/ls.d.ts +4 -0
  81. package/dist/core/tools/ls.d.ts.map +1 -1
  82. package/dist/core/tools/ls.js +5 -1
  83. package/dist/core/tools/ls.js.map +1 -1
  84. package/dist/core/tools/read.d.ts +4 -0
  85. package/dist/core/tools/read.d.ts.map +1 -1
  86. package/dist/core/tools/read.js +8 -2
  87. package/dist/core/tools/read.js.map +1 -1
  88. package/dist/core/tools/write.d.ts +4 -0
  89. package/dist/core/tools/write.d.ts.map +1 -1
  90. package/dist/core/tools/write.js +8 -2
  91. package/dist/core/tools/write.js.map +1 -1
  92. package/dist/index.d.ts +1 -1
  93. package/dist/index.d.ts.map +1 -1
  94. package/dist/index.js.map +1 -1
  95. package/dist/main.d.ts.map +1 -1
  96. package/dist/main.js +62 -22
  97. package/dist/main.js.map +1 -1
  98. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  99. package/dist/modes/interactive/components/model-selector.js +2 -1
  100. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  101. package/dist/modes/interactive/components/settings-selector.d.ts +3 -1
  102. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  103. package/dist/modes/interactive/components/settings-selector.js +10 -0
  104. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  105. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  106. package/dist/modes/interactive/components/tool-execution.js +10 -1
  107. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  108. package/dist/modes/interactive/interactive-mode.d.ts +11 -2
  109. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  110. package/dist/modes/interactive/interactive-mode.js +113 -31
  111. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  112. package/dist/modes/interactive/model-catalog-refresh.d.ts +7 -0
  113. package/dist/modes/interactive/model-catalog-refresh.d.ts.map +1 -0
  114. package/dist/modes/interactive/model-catalog-refresh.js +34 -0
  115. package/dist/modes/interactive/model-catalog-refresh.js.map +1 -0
  116. package/dist/modes/interactive/theme/dark.json +2 -0
  117. package/dist/modes/interactive/theme/light.json +2 -0
  118. package/dist/modes/interactive/theme/theme-controller.d.ts +10 -2
  119. package/dist/modes/interactive/theme/theme-controller.d.ts.map +1 -1
  120. package/dist/modes/interactive/theme/theme-controller.js +25 -11
  121. package/dist/modes/interactive/theme/theme-controller.js.map +1 -1
  122. package/dist/modes/interactive/theme/theme-schema.json +9 -1
  123. package/dist/modes/interactive/theme/theme.d.ts +5 -3
  124. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  125. package/dist/modes/interactive/theme/theme.js +23 -5
  126. package/dist/modes/interactive/theme/theme.js.map +1 -1
  127. package/dist/modes/json-event.d.ts +4 -1
  128. package/dist/modes/json-event.d.ts.map +1 -1
  129. package/dist/modes/json-event.js +5 -2
  130. package/dist/modes/json-event.js.map +1 -1
  131. package/dist/package-manager-cli.d.ts.map +1 -1
  132. package/dist/package-manager-cli.js +1 -1
  133. package/dist/package-manager-cli.js.map +1 -1
  134. package/dist/server/create-harness.d.ts +26 -0
  135. package/dist/server/create-harness.d.ts.map +1 -0
  136. package/dist/server/create-harness.js +106 -0
  137. package/dist/server/create-harness.js.map +1 -0
  138. package/dist/utils/tools-manager.d.ts +10 -1
  139. package/dist/utils/tools-manager.d.ts.map +1 -1
  140. package/dist/utils/tools-manager.js +14 -19
  141. package/dist/utils/tools-manager.js.map +1 -1
  142. package/docs/compaction.md +6 -6
  143. package/docs/custom-provider.md +1 -1
  144. package/docs/environment-variables.md +8 -2
  145. package/docs/extensions.md +7 -2
  146. package/docs/json.md +6 -4
  147. package/docs/keybindings.md +16 -6
  148. package/docs/providers.md +8 -1
  149. package/docs/rpc.md +15 -4
  150. package/docs/sdk.md +9 -0
  151. package/docs/settings.md +31 -0
  152. package/docs/terminal-setup.md +23 -2
  153. package/docs/themes.md +24 -3
  154. package/docs/tui.md +2 -2
  155. package/docs/usage.md +3 -2
  156. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  157. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  158. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  159. package/examples/extensions/gondolin/package-lock.json +2 -2
  160. package/examples/extensions/gondolin/package.json +1 -1
  161. package/examples/extensions/sandbox/package-lock.json +2 -2
  162. package/examples/extensions/sandbox/package.json +1 -1
  163. package/examples/extensions/subagent/README.md +2 -0
  164. package/examples/extensions/subagent/agents.ts +40 -9
  165. package/examples/extensions/subagent/index.ts +21 -3
  166. package/examples/extensions/with-deps/package-lock.json +2 -2
  167. package/examples/extensions/with-deps/package.json +1 -1
  168. package/npm-shrinkwrap.json +27 -78
  169. package/package.json +7 -7
package/docs/sdk.md CHANGED
@@ -372,6 +372,13 @@ import { ModelRuntime } from "@earendil-works/pi-coding-agent";
372
372
 
373
373
  const modelRuntime = await ModelRuntime.create();
374
374
 
375
+ // create() restores cached catalogs but does not refresh them from pi.dev by default.
376
+ // Opt in to a create-time network refresh and bound how long it may take:
377
+ const refreshedRuntime = await ModelRuntime.create({
378
+ allowModelNetwork: true,
379
+ modelRefreshTimeoutMs: 15_000,
380
+ });
381
+
375
382
  // Find specific built-in model (doesn't check if API key exists)
376
383
  const opus = getModel("anthropic", "claude-opus-4-5");
377
384
  if (!opus) throw new Error("Model not found");
@@ -402,6 +409,8 @@ If no model is provided:
402
409
  2. Uses default from settings
403
410
  3. Falls back to first available model
404
411
 
412
+ Remote catalogs are persisted locally so later runtimes can restore them without a network request. The default file is `~/.pi/agent/models-store.json`; set `modelsStorePath` to choose another location, or inject `modelsStore` to control persistence. Network refreshes are throttled to once per provider every four hours unless forced. To force an immediate refresh, call `await modelRuntime.refresh({ allowNetwork: true, force: true, signal })`. Setting `PI_OFFLINE` disables model network access.
413
+
405
414
  To match CLI model parsing, use the exported resolver helpers:
406
415
 
407
416
  ```typescript
package/docs/settings.md CHANGED
@@ -66,6 +66,7 @@ Use `/trust` in interactive mode to save a project trust decision for future ses
66
66
  | `autocompleteMaxVisible` | number | `5` | Max visible items in autocomplete dropdown (3-20) |
67
67
  | `showHardwareCursor` | boolean | `false` | Show the terminal cursor while TUI positions it for IME support |
68
68
  | `tuiMode` | string | `"regular"` | Interactive TUI mode: `"regular"` or experimental `"fullscreen"`. Changes from `/settings` apply immediately; `--tui-mode` overrides this setting at startup |
69
+ | `fullscreenExitOutput` | string | `"transcript"` | Fullscreen exit output: `"transcript"` prints the final transcript and resume hint, while `"resume-hint"` restores the previous screen and prints only the resume hint. Has no effect in regular TUI mode |
69
70
  | `fullscreenScrollbar` | string | `"auto"` | Fullscreen transcript scrollbar: `"auto"` shows it temporarily while scrolling, `"always"` reserves the rightmost column and keeps it visible, and `"hidden"` hides it. Has no effect in regular TUI mode |
70
71
 
71
72
  For VS Code, include `--wait` so pi resumes after the editor exits:
@@ -191,6 +192,20 @@ Keep `retry.provider.maxRetries` at `0` unless provider-level retries are explic
191
192
  | `shellCommandPrefix` | string | - | Prefix for every bash command (e.g., `"shopt -s expand_aliases"`) |
192
193
  | `npmCommand` | string[] | - | Command argv used for npm package lookup/install operations (e.g., `["mise", "exec", "node@20", "--", "npm"]`) |
193
194
 
195
+ Windows paths in JSON must use forward slashes or escaped backslashes:
196
+
197
+ ```json
198
+ {
199
+ "shellPath": "C:/Program Files/Git/bin/bash.exe"
200
+ }
201
+ ```
202
+
203
+ ```json
204
+ {
205
+ "shellPath": "C:\\Program Files\\Git\\bin\\bash.exe"
206
+ }
207
+ ```
208
+
194
209
  ```json
195
210
  {
196
211
  "npmCommand": ["mise", "exec", "node@20", "--", "npm"]
@@ -199,6 +214,22 @@ Keep `retry.provider.maxRetries` at `0` unless provider-level retries are explic
199
214
 
200
215
  `npmCommand` is used for all npm package-manager operations, including installs, uninstalls, and dependency installs inside git packages. User-scoped npm packages install under `~/.pi/agent/npm/`; project-scoped npm packages install under `.pi/npm/`. Use argv-style entries exactly as the process should be launched. When `npmCommand` is configured, git package dependency installs use plain `install` to avoid npm-specific flags in wrappers or alternate package managers.
201
216
 
217
+ ### Tools
218
+
219
+ | Setting | Type | Default | Description |
220
+ |---------|------|---------|-------------|
221
+ | `defaultTools` | string[] | - | Built-in tools enabled initially. When omitted, Pi uses its standard defaults |
222
+
223
+ `defaultTools` selects the built-in tools enabled at startup. Extension and SDK custom tools remain enabled:
224
+
225
+ ```json
226
+ {
227
+ "defaultTools": ["bash", "edit", "write"]
228
+ }
229
+ ```
230
+
231
+ An empty array starts with no built-in tools while preserving extension and SDK custom tools. `--tools` replaces this behavior with a strict allowlist for all tools, `--no-tools` disables all tools, and `--no-builtin-tools` disables the built-in defaults. `--exclude-tools` filters the resulting list. A project `defaultTools` array replaces the global array.
232
+
202
233
  ### Sessions
203
234
 
204
235
  | Setting | Type | Default | Description |
@@ -2,9 +2,26 @@
2
2
 
3
3
  Pi uses the [Kitty keyboard protocol](https://sw.kovidgoyal.net/kitty/keyboard-protocol/) for reliable modifier key detection. Most modern terminals support this protocol, but some require configuration.
4
4
 
5
- ## Kitty, iTerm2
5
+ ## Kitty
6
6
 
7
- Work out of the box.
7
+ Works out of the box.
8
+
9
+ ## iTerm2
10
+
11
+ ### Regular TUI mode
12
+
13
+ Works out of the box.
14
+
15
+ ### Fullscreen TUI mode
16
+
17
+ Pi owns the viewport, so iTerm2 sends mouse-wheel reports instead of scrolling its native scrollback. With iTerm2's default fast-trackpad behavior, those reports can lose most of an accelerated wheel delta, making fullscreen scrolling much slower than regular scrolling.
18
+
19
+ If fast mouse-wheel gestures move only about one line at a time in fullscreen mode:
20
+
21
+ 1. Open **iTerm2 → Settings → Advanced**.
22
+ 2. Search for **Trackpad scrolls fast?** and set it to **No**.
23
+
24
+ This is an iTerm2-wide workaround and may also change native trackpad scrolling. The underlying behavior is tracked in [iTerm2 issue 9619](https://gitlab.com/gnachman/iterm2/-/work_items/9619).
8
25
 
9
26
  ## Apple Terminal
10
27
 
@@ -32,6 +49,10 @@ If Claude Code 2.x or newer is the only reason you added that mapping, you can r
32
49
 
33
50
  Pi binds `Ctrl+J` as a default newline alias, so `Shift+Enter` keeps working in tmux via that remap without extra pi configuration.
34
51
 
52
+ ### Fullscreen TUI mode
53
+
54
+ In fullscreen mode, links remain clickable, but Ghostty does not show its hover underline or lower-left URL preview while pi captures mouse input. Hold `Shift+Command` on macOS or `Shift+Ctrl` on Linux to use Ghostty's native link handling.
55
+
35
56
  ## WezTerm
36
57
 
37
58
  WezTerm usually works out of the box for `Shift+Enter` via xterm modifyOtherKeys. To use the Kitty keyboard protocol explicitly, create `~/.wezterm.lua`:
package/docs/themes.md CHANGED
@@ -39,6 +39,23 @@ Select a theme via `/settings` or in `settings.json`:
39
39
 
40
40
  On first run, pi detects your terminal background and defaults to `dark` or `light`.
41
41
 
42
+ ### Initial Theme
43
+
44
+ Start an interactive run with a theme without changing the saved setting:
45
+
46
+ ```bash
47
+ pi --use-theme light
48
+ ```
49
+
50
+ To follow terminal appearance, use `lightTheme/darkTheme` syntax:
51
+
52
+ ```bash
53
+ pi --use-theme light/dark
54
+ ```
55
+
56
+ The CLI value is the initial theme for that run. Choosing another theme later in `/settings` applies it immediately
57
+ and saves it normally.
58
+
42
59
  ## Creating a Custom Theme
43
60
 
44
61
  1. Create a theme file:
@@ -72,6 +89,8 @@ vim ~/.pi/agent/themes/my-theme.json
72
89
  "thinkingText": "secondary",
73
90
  "selectedBg": "#2d2d30",
74
91
  "scrollbarThumb": "#555566",
92
+ "searchMatchBg": "#2d2d30",
93
+ "searchMatchText": "",
75
94
  "userMessageBg": "#2d2d30",
76
95
  "userMessageText": "",
77
96
  "customMessageBg": "#2d2d30",
@@ -141,13 +160,13 @@ vim ~/.pi/agent/themes/my-theme.json
141
160
 
142
161
  - `name` is required, must be unique, and must not contain `/`.
143
162
  - `vars` is optional. Define reusable colors here, then reference them in `colors`.
144
- - `colors` must define all 51 required tokens. `thinkingMax` is optional and falls back to `thinkingXhigh`; `scrollbarThumb` is optional and falls back to `selectedBg`.
163
+ - `colors` must define all 51 required tokens. `thinkingMax`, `scrollbarThumb`, and the two search highlight tokens are optional and use the fallbacks listed below.
145
164
 
146
165
  The `$schema` field enables editor auto-completion and validation.
147
166
 
148
167
  ## Color Tokens
149
168
 
150
- Every theme must define all 51 required color tokens. `thinkingMax` and `scrollbarThumb` are optional for compatibility with existing themes; when omitted, they use `thinkingXhigh` and `selectedBg`, respectively.
169
+ Every theme must define all 51 required color tokens. The optional tokens preserve compatibility with existing themes: `thinkingMax` falls back to `thinkingXhigh`, `scrollbarThumb` and `searchMatchBg` fall back to `selectedBg`, and `searchMatchText` falls back to `text`. Other search matches use `searchMatchText` on `searchMatchBg` with an underline; the current match reverses that foreground/background pair and uses bold text.
151
170
 
152
171
  ### Core UI (11 colors)
153
172
 
@@ -165,12 +184,14 @@ Every theme must define all 51 required color tokens. `thinkingMax` and `scrollb
165
184
  | `text` | Default text (usually `""`) |
166
185
  | `thinkingText` | Thinking block text |
167
186
 
168
- ### Backgrounds & Content (11 required, 1 optional)
187
+ ### Backgrounds & Content (11 required, 3 optional)
169
188
 
170
189
  | Token | Purpose |
171
190
  |-------|---------|
172
191
  | `selectedBg` | Selected line background |
173
192
  | `scrollbarThumb` | Fullscreen scrollbar thumb background; optional, falls back to `selectedBg` |
193
+ | `searchMatchBg` | Transcript search match background and current-match text; optional, falls back to `selectedBg` |
194
+ | `searchMatchText` | Transcript search match text and current-match background; optional, falls back to `text` |
174
195
  | `userMessageBg` | User message background |
175
196
  | `userMessageText` | User message text |
176
197
  | `customMessageBg` | Extension message background |
package/docs/tui.md CHANGED
@@ -431,7 +431,7 @@ renderResult(result, options, theme, context) {
431
431
 
432
432
  | Category | Colors |
433
433
  |----------|--------|
434
- | General | `text`, `accent`, `muted`, `dim` |
434
+ | General | `text`, `accent`, `muted`, `dim`, `searchMatchText` |
435
435
  | Status | `success`, `error`, `warning` |
436
436
  | Borders | `border`, `borderAccent`, `borderMuted` |
437
437
  | Messages | `userMessageText`, `customMessageText`, `customMessageLabel` |
@@ -444,7 +444,7 @@ renderResult(result, options, theme, context) {
444
444
 
445
445
  **Background colors** (`theme.bg(color, text)`):
446
446
 
447
- `selectedBg`, `userMessageBg`, `customMessageBg`, `toolPendingBg`, `toolSuccessBg`, `toolErrorBg`
447
+ `selectedBg`, `searchMatchBg`, `userMessageBg`, `customMessageBg`, `toolPendingBg`, `toolSuccessBg`, `toolErrorBg`
448
448
 
449
449
  **For Markdown**, use `getMarkdownTheme()`:
450
450
 
package/docs/usage.md CHANGED
@@ -242,15 +242,16 @@ pi --no-extensions -e ./my-extension.ts
242
242
  | `--system-prompt <text>` | Replace default prompt; context files and skills are still appended |
243
243
  | `--append-system-prompt <text>` | Append to system prompt |
244
244
  | `--tui-mode <mode>` | TUI mode: `regular` (default) or experimental `fullscreen` |
245
+ | `--use-theme <name[/name]>` | Set the initial interactive theme for this run without changing settings |
245
246
  | `--verbose` | Force verbose startup |
246
247
  | `-a`, `--approve` | Trust project-local files for this run |
247
248
  | `-na`, `--no-approve` | Ignore project-local files for this run |
248
249
  | `-h`, `--help` | Show help |
249
250
  | `-v`, `--version` | Show version |
250
251
 
251
- In `fullscreen` mode, the transcript scrolls inside the terminal viewport while queued messages, working status, extension widgets, editor, and footer remain fixed at the bottom. Mouse/trackpad input scrolls the region under the pointer; keyboard viewport actions always remain available. Inline images work in terminals that support the Kitty graphics protocol, including Kitty and Ghostty. In iTerm2 they render as text placeholders because its inline-image protocol cannot delete or crop placements during application-owned scrolling. In `regular` mode, pi uses the main screen and terminal-owned scrollback, and iTerm2 inline images continue to render normally.
252
+ In `fullscreen` mode, the transcript scrolls inside the terminal viewport while queued messages, working status, extension widgets, editor, and footer remain fixed at the bottom. Mouse/trackpad input scrolls the region under the pointer; keyboard viewport actions always remain available. Inline images work in terminals that support the Kitty graphics protocol, including Kitty and Ghostty. In iTerm2 they render as text placeholders because its inline-image protocol cannot delete or crop placements during application-owned scrolling. In `regular` mode, pi uses the main screen and terminal-owned scrollback, and iTerm2 inline images continue to render normally. See [Terminal setup](terminal-setup.md) for terminal-specific settings and workarounds.
252
253
 
253
- Set **TUI mode** in `/settings` to switch between `regular` and `fullscreen` immediately and choose the default for future sessions.
254
+ Set **TUI mode** in `/settings` to switch between `regular` and `fullscreen` immediately and choose the default for future sessions. **Fullscreen exit output** controls whether exiting fullscreen prints the final transcript or restores the previous screen and prints only the session resume hint.
254
255
 
255
256
  ### File Arguments
256
257
 
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider",
3
- "version": "0.84.0",
3
+ "version": "0.84.2",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pi-extension-custom-provider",
9
- "version": "0.84.0",
9
+ "version": "0.84.2",
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": "0.84.0",
4
+ "version": "0.84.2",
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": "0.84.0",
4
+ "version": "0.84.2",
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-gondolin",
3
- "version": "0.84.0",
3
+ "version": "0.84.2",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pi-extension-gondolin",
9
- "version": "0.84.0",
9
+ "version": "0.84.2",
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": "0.84.0",
4
+ "version": "0.84.2",
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-sandbox",
3
- "version": "1.14.0",
3
+ "version": "1.14.2",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pi-extension-sandbox",
9
- "version": "1.14.0",
9
+ "version": "1.14.2",
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.14.0",
4
+ "version": "1.14.2",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -137,6 +137,8 @@ model: claude-haiku-4-5
137
137
  System prompt for the agent goes here.
138
138
  ```
139
139
 
140
+ When `model` is omitted, the subagent inherits the dispatching session's active model and thinking level.
141
+
140
142
  **Locations:**
141
143
  - `~/.pi/agent/agents/*.md` - User-level (always loaded)
142
144
  - `.pi/agents/*.md` - Project-level (only with `agentScope: "project"` or `"both"`)
@@ -23,6 +23,42 @@ export interface AgentDiscoveryResult {
23
23
  projectAgentsDir: string | null;
24
24
  }
25
25
 
26
+ /**
27
+ * Raw agent frontmatter. Values are `unknown` because `parseFrontmatter` runs a
28
+ * real YAML parser, so any scalar or collection can appear here.
29
+ *
30
+ * A type alias rather than an interface: `parseFrontmatter` constrains its
31
+ * parameter to `Record<string, unknown>`, and only an alias picks up the
32
+ * implicit index signature that satisfies it.
33
+ */
34
+ type AgentFrontmatter = {
35
+ name?: unknown;
36
+ description?: unknown;
37
+ tools?: unknown;
38
+ model?: unknown;
39
+ };
40
+
41
+ /**
42
+ * Normalize a frontmatter `tools` value to a list of tool names.
43
+ *
44
+ * Both spellings are valid YAML and both are in use:
45
+ *
46
+ * tools: read, bash # string
47
+ * tools: [read, bash] # array
48
+ *
49
+ * so accept either. Anything else (a number, a map, a nested list) yields no
50
+ * tools rather than throwing: this runs inside agent discovery, where a single
51
+ * bad file must not take down every other agent in the same directory.
52
+ */
53
+ function parseToolList(value: unknown): string[] | undefined {
54
+ const raw = Array.isArray(value) ? value : typeof value === "string" ? value.split(",") : [];
55
+ const tools = raw
56
+ .filter((t): t is string => typeof t === "string")
57
+ .map((t) => t.trim())
58
+ .filter(Boolean);
59
+ return tools.length > 0 ? tools : undefined;
60
+ }
61
+
26
62
  function loadAgentsFromDir(dir: string, source: "user" | "project"): AgentConfig[] {
27
63
  const agents: AgentConfig[] = [];
28
64
 
@@ -49,22 +85,17 @@ function loadAgentsFromDir(dir: string, source: "user" | "project"): AgentConfig
49
85
  continue;
50
86
  }
51
87
 
52
- const { frontmatter, body } = parseFrontmatter<Record<string, string>>(content);
88
+ const { frontmatter, body } = parseFrontmatter<AgentFrontmatter>(content);
53
89
 
54
- if (!frontmatter.name || !frontmatter.description) {
90
+ if (typeof frontmatter.name !== "string" || typeof frontmatter.description !== "string") {
55
91
  continue;
56
92
  }
57
93
 
58
- const tools = frontmatter.tools
59
- ?.split(",")
60
- .map((t: string) => t.trim())
61
- .filter(Boolean);
62
-
63
94
  agents.push({
64
95
  name: frontmatter.name,
65
96
  description: frontmatter.description,
66
- tools: tools && tools.length > 0 ? tools : undefined,
67
- model: frontmatter.model,
97
+ tools: parseToolList(frontmatter.tools),
98
+ model: typeof frontmatter.model === "string" ? frontmatter.model : undefined,
68
99
  systemPrompt: body,
69
100
  source,
70
101
  filePath,
@@ -16,7 +16,7 @@ import { spawn } from "node:child_process";
16
16
  import * as fs from "node:fs";
17
17
  import * as os from "node:os";
18
18
  import * as path from "node:path";
19
- import type { AgentToolResult } from "@earendil-works/pi-agent-core";
19
+ import type { AgentToolResult, ThinkingLevel } from "@earendil-works/pi-agent-core";
20
20
  import type { Message } from "@earendil-works/pi-ai";
21
21
  import { StringEnum } from "@earendil-works/pi-ai";
22
22
  import {
@@ -264,8 +264,14 @@ function getPiInvocation(args: string[]): { command: string; args: string[] } {
264
264
 
265
265
  type OnUpdateCallback = (partial: AgentToolResult<SubagentDetails>) => void;
266
266
 
267
+ interface DispatchDefaults {
268
+ model?: string;
269
+ thinkingLevel?: ThinkingLevel;
270
+ }
271
+
267
272
  async function runSingleAgent(
268
273
  defaultCwd: string,
274
+ dispatchDefaults: DispatchDefaults,
269
275
  agents: AgentConfig[],
270
276
  agentName: string,
271
277
  task: string,
@@ -292,7 +298,12 @@ async function runSingleAgent(
292
298
  }
293
299
 
294
300
  const args: string[] = ["--mode", "json", "-p", "--no-session"];
295
- if (agent.model) args.push("--model", agent.model);
301
+ const inheritsDispatchConfig = !agent.model;
302
+ const model = agent.model ?? dispatchDefaults.model;
303
+ if (model) args.push("--model", model);
304
+ if (inheritsDispatchConfig && dispatchDefaults.thinkingLevel) {
305
+ args.push("--thinking", dispatchDefaults.thinkingLevel);
306
+ }
296
307
  if (agent.tools && agent.tools.length > 0) args.push("--tools", agent.tools.join(","));
297
308
 
298
309
  let tmpPromptDir: string | null = null;
@@ -306,7 +317,7 @@ async function runSingleAgent(
306
317
  messages: [],
307
318
  stderr: "",
308
319
  usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 },
309
- model: agent.model,
320
+ model,
310
321
  step,
311
322
  };
312
323
 
@@ -471,6 +482,10 @@ export default function (pi: ExtensionAPI) {
471
482
 
472
483
  async execute(_toolCallId, params, signal, onUpdate, ctx) {
473
484
  const agentScope: AgentScope = params.agentScope ?? "user";
485
+ const dispatchDefaults: DispatchDefaults = {
486
+ model: ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined,
487
+ thinkingLevel: ctx.thinkingLevel,
488
+ };
474
489
  const discovery = discoverAgents(ctx.cwd, agentScope);
475
490
  const agents = discovery.agents;
476
491
  const confirmProjectAgents = params.confirmProjectAgents ?? true;
@@ -552,6 +567,7 @@ export default function (pi: ExtensionAPI) {
552
567
 
553
568
  const result = await runSingleAgent(
554
569
  ctx.cwd,
570
+ dispatchDefaults,
555
571
  agents,
556
572
  step.agent,
557
573
  taskWithContext,
@@ -624,6 +640,7 @@ export default function (pi: ExtensionAPI) {
624
640
  const results = await mapWithConcurrencyLimit(params.tasks, MAX_CONCURRENCY, async (t, index) => {
625
641
  const result = await runSingleAgent(
626
642
  ctx.cwd,
643
+ dispatchDefaults,
627
644
  agents,
628
645
  t.agent,
629
646
  t.task,
@@ -666,6 +683,7 @@ export default function (pi: ExtensionAPI) {
666
683
  if (params.agent && params.task) {
667
684
  const result = await runSingleAgent(
668
685
  ctx.cwd,
686
+ dispatchDefaults,
669
687
  agents,
670
688
  params.agent,
671
689
  params.task,
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pi-extension-with-deps",
3
- "version": "0.84.0",
3
+ "version": "0.84.2",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pi-extension-with-deps",
9
- "version": "0.84.0",
9
+ "version": "0.84.2",
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": "0.84.0",
4
+ "version": "0.84.2",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",