@astralyn/pi 0.82.5 → 0.82.7

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 (223) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/README.md +1 -1
  3. package/dist/cli/project-trust.d.ts.map +1 -1
  4. package/dist/cli/project-trust.js +4 -1
  5. package/dist/cli/project-trust.js.map +1 -1
  6. package/dist/cli/session-picker.d.ts.map +1 -1
  7. package/dist/cli/session-picker.js +11 -13
  8. package/dist/cli/session-picker.js.map +1 -1
  9. package/dist/core/bash-executor.d.ts.map +1 -1
  10. package/dist/core/bash-executor.js +4 -22
  11. package/dist/core/bash-executor.js.map +1 -1
  12. package/dist/core/extensions/index.d.ts +1 -1
  13. package/dist/core/extensions/index.d.ts.map +1 -1
  14. package/dist/core/extensions/index.js.map +1 -1
  15. package/dist/core/extensions/types.d.ts +32 -5
  16. package/dist/core/extensions/types.d.ts.map +1 -1
  17. package/dist/core/extensions/types.js.map +1 -1
  18. package/dist/core/session-manager.d.ts.map +1 -1
  19. package/dist/core/session-manager.js +2 -2
  20. package/dist/core/session-manager.js.map +1 -1
  21. package/dist/core/tools/bash.d.ts +1 -5
  22. package/dist/core/tools/bash.d.ts.map +1 -1
  23. package/dist/core/tools/bash.js +16 -41
  24. package/dist/core/tools/bash.js.map +1 -1
  25. package/dist/core/tools/edit.d.ts.map +1 -1
  26. package/dist/core/tools/edit.js +42 -11
  27. package/dist/core/tools/edit.js.map +1 -1
  28. package/dist/core/tools/find.d.ts.map +1 -1
  29. package/dist/core/tools/find.js +6 -7
  30. package/dist/core/tools/find.js.map +1 -1
  31. package/dist/core/tools/grep.d.ts.map +1 -1
  32. package/dist/core/tools/grep.js +17 -8
  33. package/dist/core/tools/grep.js.map +1 -1
  34. package/dist/core/tools/ls.d.ts.map +1 -1
  35. package/dist/core/tools/ls.js +3 -3
  36. package/dist/core/tools/ls.js.map +1 -1
  37. package/dist/core/tools/output-accumulator.d.ts +3 -13
  38. package/dist/core/tools/output-accumulator.d.ts.map +1 -1
  39. package/dist/core/tools/output-accumulator.js +6 -36
  40. package/dist/core/tools/output-accumulator.js.map +1 -1
  41. package/dist/core/tools/read.d.ts.map +1 -1
  42. package/dist/core/tools/read.js +4 -4
  43. package/dist/core/tools/read.js.map +1 -1
  44. package/dist/core/tools/render-utils.d.ts +7 -0
  45. package/dist/core/tools/render-utils.d.ts.map +1 -1
  46. package/dist/core/tools/render-utils.js +11 -0
  47. package/dist/core/tools/render-utils.js.map +1 -1
  48. package/dist/core/tools/write.d.ts.map +1 -1
  49. package/dist/core/tools/write.js +2 -3
  50. package/dist/core/tools/write.js.map +1 -1
  51. package/dist/extensions/deepwiki/render.d.ts.map +1 -1
  52. package/dist/extensions/deepwiki/render.js +34 -22
  53. package/dist/extensions/deepwiki/render.js.map +1 -1
  54. package/dist/extensions/plan/constants.d.ts +43 -6
  55. package/dist/extensions/plan/constants.d.ts.map +1 -1
  56. package/dist/extensions/plan/constants.js +51 -8
  57. package/dist/extensions/plan/constants.js.map +1 -1
  58. package/dist/extensions/plan/index.d.ts +12 -3
  59. package/dist/extensions/plan/index.d.ts.map +1 -1
  60. package/dist/extensions/plan/index.js +173 -35
  61. package/dist/extensions/plan/index.js.map +1 -1
  62. package/dist/extensions/plan/schema.d.ts +5 -0
  63. package/dist/extensions/plan/schema.d.ts.map +1 -1
  64. package/dist/extensions/plan/schema.js.map +1 -1
  65. package/dist/extensions/plan/state.d.ts +11 -2
  66. package/dist/extensions/plan/state.d.ts.map +1 -1
  67. package/dist/extensions/plan/state.js +31 -9
  68. package/dist/extensions/plan/state.js.map +1 -1
  69. package/dist/extensions/plan/storage.d.ts +11 -3
  70. package/dist/extensions/plan/storage.d.ts.map +1 -1
  71. package/dist/extensions/plan/storage.js +45 -20
  72. package/dist/extensions/plan/storage.js.map +1 -1
  73. package/dist/extensions/plan/view.d.ts +44 -0
  74. package/dist/extensions/plan/view.d.ts.map +1 -0
  75. package/dist/extensions/plan/view.js +149 -0
  76. package/dist/extensions/plan/view.js.map +1 -0
  77. package/dist/extensions/question/dialog.d.ts.map +1 -1
  78. package/dist/extensions/question/dialog.js +29 -10
  79. package/dist/extensions/question/dialog.js.map +1 -1
  80. package/dist/extensions/question/index.d.ts.map +1 -1
  81. package/dist/extensions/question/index.js +7 -2
  82. package/dist/extensions/question/index.js.map +1 -1
  83. package/dist/extensions/question/render.d.ts +4 -8
  84. package/dist/extensions/question/render.d.ts.map +1 -1
  85. package/dist/extensions/question/render.js +202 -29
  86. package/dist/extensions/question/render.js.map +1 -1
  87. package/dist/extensions/question/results.d.ts.map +1 -1
  88. package/dist/extensions/question/results.js +1 -0
  89. package/dist/extensions/question/results.js.map +1 -1
  90. package/dist/extensions/question/schema.d.ts.map +1 -1
  91. package/dist/extensions/question/schema.js +2 -2
  92. package/dist/extensions/question/schema.js.map +1 -1
  93. package/dist/extensions/question/types.d.ts +1 -0
  94. package/dist/extensions/question/types.d.ts.map +1 -1
  95. package/dist/extensions/question/types.js +1 -1
  96. package/dist/extensions/question/types.js.map +1 -1
  97. package/dist/extensions/router/stream.d.ts +1 -0
  98. package/dist/extensions/router/stream.d.ts.map +1 -1
  99. package/dist/extensions/router/stream.js +75 -0
  100. package/dist/extensions/router/stream.js.map +1 -1
  101. package/dist/extensions/subagent/activity.d.ts.map +1 -1
  102. package/dist/extensions/subagent/activity.js +25 -4
  103. package/dist/extensions/subagent/activity.js.map +1 -1
  104. package/dist/extensions/subagent/constants.d.ts +1 -0
  105. package/dist/extensions/subagent/constants.d.ts.map +1 -1
  106. package/dist/extensions/subagent/constants.js +1 -0
  107. package/dist/extensions/subagent/constants.js.map +1 -1
  108. package/dist/extensions/subagent/index.d.ts.map +1 -1
  109. package/dist/extensions/subagent/index.js +16 -0
  110. package/dist/extensions/subagent/index.js.map +1 -1
  111. package/dist/extensions/subagent/render.d.ts.map +1 -1
  112. package/dist/extensions/subagent/render.js +352 -152
  113. package/dist/extensions/subagent/render.js.map +1 -1
  114. package/dist/extensions/subagent/resolve.d.ts.map +1 -1
  115. package/dist/extensions/subagent/resolve.js +13 -9
  116. package/dist/extensions/subagent/resolve.js.map +1 -1
  117. package/dist/extensions/subagent/retry.d.ts +17 -0
  118. package/dist/extensions/subagent/retry.d.ts.map +1 -0
  119. package/dist/extensions/subagent/retry.js +41 -0
  120. package/dist/extensions/subagent/retry.js.map +1 -0
  121. package/dist/extensions/subagent/runner.d.ts.map +1 -1
  122. package/dist/extensions/subagent/runner.js +37 -7
  123. package/dist/extensions/subagent/runner.js.map +1 -1
  124. package/dist/extensions/subagent/schema.d.ts.map +1 -1
  125. package/dist/extensions/subagent/schema.js +2 -2
  126. package/dist/extensions/subagent/schema.js.map +1 -1
  127. package/dist/extensions/subagent/sdk-runner.d.ts +6 -0
  128. package/dist/extensions/subagent/sdk-runner.d.ts.map +1 -1
  129. package/dist/extensions/subagent/sdk-runner.js +46 -24
  130. package/dist/extensions/subagent/sdk-runner.js.map +1 -1
  131. package/dist/extensions/subagent/types.d.ts +7 -0
  132. package/dist/extensions/subagent/types.d.ts.map +1 -1
  133. package/dist/extensions/subagent/types.js.map +1 -1
  134. package/dist/extensions/todo/constants.d.ts +17 -6
  135. package/dist/extensions/todo/constants.d.ts.map +1 -1
  136. package/dist/extensions/todo/constants.js +22 -9
  137. package/dist/extensions/todo/constants.js.map +1 -1
  138. package/dist/extensions/todo/index.d.ts +0 -14
  139. package/dist/extensions/todo/index.d.ts.map +1 -1
  140. package/dist/extensions/todo/index.js +13 -1
  141. package/dist/extensions/todo/index.js.map +1 -1
  142. package/dist/extensions/todo/schema.d.ts +53 -2
  143. package/dist/extensions/todo/schema.d.ts.map +1 -1
  144. package/dist/extensions/todo/schema.js +57 -8
  145. package/dist/extensions/todo/schema.js.map +1 -1
  146. package/dist/extensions/todo/state.d.ts +16 -5
  147. package/dist/extensions/todo/state.d.ts.map +1 -1
  148. package/dist/extensions/todo/state.js +874 -130
  149. package/dist/extensions/todo/state.js.map +1 -1
  150. package/dist/extensions/todo/view.d.ts +16 -1
  151. package/dist/extensions/todo/view.d.ts.map +1 -1
  152. package/dist/extensions/todo/view.js +428 -1
  153. package/dist/extensions/todo/view.js.map +1 -1
  154. package/dist/migrations.d.ts.map +1 -1
  155. package/dist/migrations.js +2 -1
  156. package/dist/migrations.js.map +1 -1
  157. package/dist/modes/interactive/components/armin.d.ts.map +1 -1
  158. package/dist/modes/interactive/components/armin.js +5 -0
  159. package/dist/modes/interactive/components/armin.js.map +1 -1
  160. package/dist/modes/interactive/components/bash-execution.d.ts +1 -0
  161. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  162. package/dist/modes/interactive/components/bash-execution.js +4 -2
  163. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  164. package/dist/modes/interactive/components/countdown-timer.d.ts +13 -6
  165. package/dist/modes/interactive/components/countdown-timer.d.ts.map +1 -1
  166. package/dist/modes/interactive/components/countdown-timer.js +54 -17
  167. package/dist/modes/interactive/components/countdown-timer.js.map +1 -1
  168. package/dist/modes/interactive/components/extension-selector.d.ts +6 -1
  169. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
  170. package/dist/modes/interactive/components/extension-selector.js +23 -11
  171. package/dist/modes/interactive/components/extension-selector.js.map +1 -1
  172. package/dist/modes/interactive/components/keybinding-hints.d.ts +11 -1
  173. package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -1
  174. package/dist/modes/interactive/components/keybinding-hints.js +56 -0
  175. package/dist/modes/interactive/components/keybinding-hints.js.map +1 -1
  176. package/dist/modes/interactive/components/session-selector.d.ts +3 -0
  177. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  178. package/dist/modes/interactive/components/session-selector.js +52 -2
  179. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  180. package/dist/modes/interactive/components/tool-execution.d.ts +10 -0
  181. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  182. package/dist/modes/interactive/components/tool-execution.js +139 -18
  183. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  184. package/dist/modes/interactive/components/tool-group.d.ts +1 -0
  185. package/dist/modes/interactive/components/tool-group.d.ts.map +1 -1
  186. package/dist/modes/interactive/components/tool-group.js +5 -0
  187. package/dist/modes/interactive/components/tool-group.js.map +1 -1
  188. package/dist/modes/interactive/components/tree-selector.d.ts +10 -1
  189. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  190. package/dist/modes/interactive/components/tree-selector.js +55 -2
  191. package/dist/modes/interactive/components/tree-selector.js.map +1 -1
  192. package/dist/modes/interactive/interactive-mode.d.ts +5 -0
  193. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  194. package/dist/modes/interactive/interactive-mode.js +79 -19
  195. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  196. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  197. package/dist/modes/interactive/theme/theme.js +3 -2
  198. package/dist/modes/interactive/theme/theme.js.map +1 -1
  199. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  200. package/dist/modes/rpc/rpc-mode.js +8 -1
  201. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  202. package/dist/utils/paths.d.ts +6 -0
  203. package/dist/utils/paths.d.ts.map +1 -1
  204. package/dist/utils/paths.js +8 -0
  205. package/dist/utils/paths.js.map +1 -1
  206. package/docs/bundled/README.md +1 -0
  207. package/docs/bundled/extensions/deepwiki.md +1 -1
  208. package/docs/bundled/extensions/plan.md +13 -10
  209. package/docs/bundled/extensions/question.md +7 -5
  210. package/docs/bundled/extensions/router.md +3 -4
  211. package/docs/bundled/extensions/statusline.md +0 -1
  212. package/docs/bundled/extensions/subagent.md +5 -4
  213. package/docs/bundled/extensions/todo.md +9 -7
  214. package/docs/bundled/tool-presentation.md +17 -16
  215. package/docs/extensions.md +34 -5
  216. package/docs/rpc.md +1 -1
  217. package/docs/usage.md +1 -1
  218. package/npm-shrinkwrap.json +6 -6
  219. package/package.json +1 -1
  220. package/dist/utils/output-decoder.d.ts +0 -52
  221. package/dist/utils/output-decoder.d.ts.map +0 -1
  222. package/dist/utils/output-decoder.js +0 -134
  223. package/dist/utils/output-decoder.js.map +0 -1
@@ -6,17 +6,15 @@ The `@astralyn/pi` package uses Pi's native tool transcript presentation rather
6
6
 
7
7
  ```text
8
8
  ● ToolName(args)
9
- │ result
9
+ │ first result line
10
+ │ second result line
11
+ │
12
+ │ final result line
10
13
  ```
11
14
 
12
- States use the same shell:
15
+ The dim result rail continues through every visual line; an empty output line renders as a bare `│`. Calls keep their status in the leading dot: warning while pending or running, green after success, and red after failure. For default-shell tools that are still running after two seconds, the shell adds a live result row such as `Running… (2.1s)` and removes it when the call settles. Tools with purpose-built progress UI can opt out through `rendersOwnProgress`; tools using `renderShell: "self"` continue to own their entire presentation.
13
16
 
14
- ```text
15
- ● ToolName(args) completed call
16
- ● ToolName Working... pending call
17
- ● ToolName(args) failed call (error color)
18
- │ result successful or failed output
19
- ```
17
+ Time-driven renderers use `renderRefreshIntervalMs` separately from `rendersOwnProgress`. The former periodically rebuilds call/result components only during the partial lifecycle; the latter only hides the generic `Running…` row. The native shell shares the shortest needed interval, constrains explicit intervals to 250ms–60s, and stops refreshing on final result, abort, disposal, session replacement, chat rebuild, or shutdown. Renderers compute elapsed/countdown values from absolute timestamps or deadlines, so a delayed repaint never changes the underlying wall-clock meaning.
20
18
 
21
19
  ## Implementation boundary
22
20
 
@@ -31,7 +29,7 @@ The native tool presentation owns:
31
29
 
32
30
  Built-in renderers remain responsible for semantic content such as file paths, syntax highlighting, search results, Diff previews, and command output. The outer shell is native so built-in tools, bundled extensions, and compatible third-party tools share the same presentation.
33
31
 
34
- Consecutive tools may opt into a shared collapsed group through `toolGroup`. Built-in `read` and `find` calls use the `explore` group: their call rows render as one compact run with a single leading gap, while `Ctrl+O` restores each tool's complete call and result.
32
+ Consecutive tools may opt into a shared collapsed group through `toolGroup`. Built-in `read`, `find`, `grep`, and `ls` calls use the `explore` group, and the bundled `todo` tool uses the `todo` group: their call rows render as one compact run with a single leading gap, while the configured expand-tools key (`Ctrl+O` by default) restores each tool's complete call and result. Collapsed Todo rows are result-aware: successful current snapshots summarize the completed operation, including task IDs and subjects for creation, rather than only its arguments. Failures include a sanitized reason capped at 120 characters; missing or unrecognized result details fall back safely to the call summary.
35
33
 
36
34
  ## Renderer inheritance
37
35
 
@@ -51,9 +49,10 @@ When no semantic renderer is available:
51
49
 
52
50
  - arguments are serialized into a bounded one-line summary;
53
51
  - output is collapsed to the most recent ten visual lines;
54
- - `Ctrl+O` expands the complete output;
52
+ - a shared hint above the tail reports the number of hidden earlier lines and the configured expand key;
53
+ - expanding restores the complete output;
55
54
  - historical tools that are no longer registered still receive the same shell;
56
- - failed calls use the error-colored bullet while result details keep the result bar.
55
+ - failed calls use the error-colored bullet while result details keep the result rail.
57
56
 
58
57
  The fallback does not change tool schemas, execution logic, or result protocols.
59
58
 
@@ -62,15 +61,17 @@ The fallback does not change tool schemas, execution logic, or result protocols.
62
61
  The native path continues to preserve:
63
62
 
64
63
  - `read`, `bash`, `grep`, `find`, `ls`, `write`, and `edit` semantics;
65
- - faithful width-aware raw command previews for Bash;
66
- - running Bash duration after the two-second progress threshold, without a permanent completion timer;
67
- - Diff previews and first-change-line metadata for `edit`;
64
+ - faithful width-aware raw command previews for Bash, with honest multi-line and width truncation markers;
65
+ - shell-wide running duration after the two-second progress threshold, with lifecycle-safe refresh disposal and no permanent completion timer;
66
+ - path links and consistent search flags/limits across built-in renderers;
67
+ - Diff statistics plus a ten-line collapsed Diff preview for `edit`, with the complete Diff restored on expand;
68
68
  - syntax highlighting;
69
69
  - image output and Kitty conversion;
70
70
  - native collapsed/expanded handling;
71
- - custom UI explicitly using `renderShell: "self"`.
71
+ - custom UI explicitly using `renderShell: "self"`;
72
+ - independently refreshed custom elapsed time and retry countdowns without a duplicate generic progress row.
72
73
 
73
- `edit` now uses the native outer shell while keeping its asynchronous Diff preview and final Diff result.
74
+ Built-in result truncation hints share the same `… (N earlier/more lines, … to expand)` language and correct singular/plural forms. `edit` uses the native outer shell while keeping its asynchronous Diff preview and final Diff result.
74
75
 
75
76
  ## Deliberately rejected approaches
76
77
 
@@ -1640,7 +1640,7 @@ const all = pi.getAllTools();
1640
1640
  const builtinTools = all.filter((t) => t.sourceInfo.source === "builtin");
1641
1641
  const extensionTools = all.filter((t) => t.sourceInfo.source !== "builtin" && t.sourceInfo.source !== "sdk");
1642
1642
  pi.setActiveTools([...new Set([...active, "my_custom_tool"])]); // Keep current tools and enable my_custom_tool
1643
- pi.setActiveTools(["read", "bash"]); // Switch to read-only
1643
+ pi.setActiveTools(["read", "bash"]); // Activate only read and bash
1644
1644
  ```
1645
1645
 
1646
1646
  `pi.getAllTools()` returns `name`, `description`, `parameters`, `promptGuidelines`, and `sourceInfo`.
@@ -1951,6 +1951,10 @@ pi.registerTool({
1951
1951
  },
1952
1952
 
1953
1953
  // Optional: Custom rendering
1954
+ // rendersOwnProgress suppresses only the shell's generic Running… row.
1955
+ rendersOwnProgress: true,
1956
+ // While the result is partial, rebuild renderCall/renderResult every second.
1957
+ renderRefreshIntervalMs: 1000,
1954
1958
  renderCall(args, theme, context) { ... },
1955
1959
  renderResult(result, options, theme, context) { ... },
1956
1960
  });
@@ -2184,9 +2188,18 @@ export default function (pi: ExtensionAPI) {
2184
2188
 
2185
2189
  Tools can provide `renderCall` and `renderResult` for custom TUI display. See [tui.md](tui.md) for the full component API and [tool-execution.ts](https://github.com/ming-kang/pi/blob/main/src/modes/interactive/components/tool-execution.ts) for how tool rows are composed.
2186
2190
 
2187
- By default, tool output is wrapped in a `Box` that handles padding and background. A defined `renderCall` or `renderResult` must return a `Component`. If a slot renderer is not defined, `tool-execution.ts` uses fallback rendering for that slot.
2191
+ By default, tool output is wrapped in Pi's native call/result shell, which owns the status dot, result rail, padding, and fallback rendering. A defined `renderCall` or `renderResult` must return a `Component`. If a slot renderer is not defined, `tool-execution.ts` uses fallback rendering for that slot.
2188
2192
 
2189
- Set `renderShell: "self"` when the tool should render its own shell instead of using the default `Box`. This is useful for tools that need complete control over framing or background behavior, for example large previews that must stay visually stable after the tool settles.
2193
+ Set `renderShell: "self"` when the tool should render its own shell instead of using the native shell. This is useful for tools that need complete control over framing or background behavior, for example large previews that must stay visually stable after the tool settles.
2194
+
2195
+ Two independent options control live presentation:
2196
+
2197
+ - `rendersOwnProgress: true` suppresses only the native shell's generic `Running… (Ns)` row. It does not schedule renderer updates.
2198
+ - `renderRefreshIntervalMs` periodically rebuilds `renderCall` and `renderResult` while the tool result is partial. It does not suppress generic progress.
2199
+
2200
+ Use both when a tool renders its own elapsed time, retry countdown, or similar progress UI. Refresh intervals are rounded and constrained to 250–60,000ms. The shell may rebuild sooner when another native concern needs it, so the configured interval is the maximum refresh gap rather than an exclusive cadence. Refresh stops when the result settles or the row is disposed.
2201
+
2202
+ Derive displayed time from an absolute timestamp or deadline (`Date.now()`), not by decrementing a counter on every refresh. A refresh is only a repaint opportunity; event-loop stalls and machine sleep must not extend a wall-clock timeout.
2190
2203
 
2191
2204
  ```typescript
2192
2205
  pi.registerTool({
@@ -2289,15 +2302,17 @@ Custom editors and `ctx.ui.custom()` components receive `keybindings: Keybinding
2289
2302
 
2290
2303
  #### Best Practices
2291
2304
 
2292
- - Use `Text` with padding `(0, 0)`. The default Box handles padding.
2305
+ - Use `Text` with padding `(0, 0)`. The native shell handles padding.
2293
2306
  - Use `\n` for multi-line content.
2294
2307
  - Handle `isPartial` for streaming progress.
2308
+ - Use `renderRefreshIntervalMs` only for values that must change without a new partial result, and compute those values from absolute time.
2309
+ - Set `rendersOwnProgress` separately when the renderer replaces the native `Running…` row.
2295
2310
  - Support `expanded` for detail on demand.
2296
2311
  - Keep default view compact.
2297
2312
  - Read `context.args` in `renderResult` instead of copying args into `context.state`.
2298
2313
  - Use `context.state` only for data that must be shared across call and result slots.
2299
2314
  - Reuse `context.lastComponent` when the same component instance can be updated in place.
2300
- - Use `renderShell: "self"` only when the default boxed shell gets in the way. In self-shell mode the tool is responsible for its own framing, padding, and background.
2315
+ - Use `renderShell: "self"` only when the native shell gets in the way. In self-shell mode the tool is responsible for its own framing, padding, and background.
2301
2316
 
2302
2317
  #### Fallback
2303
2318
 
@@ -2462,6 +2477,20 @@ Extensions can interact with users via `ctx.ui` methods and customize how messag
2462
2477
  // Select from options
2463
2478
  const choice = await ctx.ui.select("Pick one:", ["A", "B", "C"]);
2464
2479
 
2480
+ // Options can carry a muted description line, so the action and its
2481
+ // trade-off can be scanned separately. select() still resolves to the label.
2482
+ const action = await ctx.ui.select(
2483
+ "Approve?",
2484
+ [
2485
+ { label: "Start executing", description: "keep full context" },
2486
+ { label: "Compact, then execute", description: "best for long tasks" },
2487
+ ],
2488
+ {
2489
+ subtitle: "Context now 29% full", // muted line under the title
2490
+ cancelHint: "keep planning", // Esc is not destructive here
2491
+ },
2492
+ );
2493
+
2465
2494
  // Confirm dialog
2466
2495
  const ok = await ctx.ui.confirm("Delete?", "This cannot be undone");
2467
2496
 
package/docs/rpc.md CHANGED
@@ -210,7 +210,7 @@ Response:
210
210
  }
211
211
  ```
212
212
 
213
- Messages are `AgentMessage` objects (see [Message Types](#message-types)).
213
+ Messages are `AgentMessage` objects (see [Types](#types)).
214
214
 
215
215
  ### Model
216
216
 
package/docs/usage.md CHANGED
@@ -293,6 +293,6 @@ pi --exclude-tools ask_question
293
293
 
294
294
  Pi keeps the core small and pushes workflow-specific behavior into extensions, skills, prompt templates, and packages.
295
295
 
296
- It intentionally does not include built-in MCP, sub-agents, permission popups, plan mode, to-dos, or background bash. You can build or install those workflows as extensions or packages, or use external tools such as containers and tmux.
296
+ This distribution ships selected workflows as hidden built-in extensions, including plan mode, sub-agents, and to-dos. It does not include built-in MCP, permission popups, or background bash. You can build or install additional workflows as extensions or packages, or use external tools such as containers and tmux.
297
297
 
298
298
  For the full rationale, read the [blog post](https://mariozechner.at/posts/2025-11-30-pi-coding-agent/).
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@astralyn/pi",
3
- "version": "0.82.5",
3
+ "version": "0.82.7",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@astralyn/pi",
9
- "version": "0.82.5",
9
+ "version": "0.82.7",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@earendil-works/pi-agent-core": "0.82.1",
@@ -2476,15 +2476,15 @@
2476
2476
  "license": "MIT"
2477
2477
  },
2478
2478
  "node_modules/brace-expansion": {
2479
- "version": "5.0.7",
2480
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz",
2481
- "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==",
2479
+ "version": "5.0.8",
2480
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz",
2481
+ "integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==",
2482
2482
  "license": "MIT",
2483
2483
  "dependencies": {
2484
2484
  "balanced-match": "^4.0.2"
2485
2485
  },
2486
2486
  "engines": {
2487
- "node": "18 || 20 || >=22"
2487
+ "node": "20 || >=22"
2488
2488
  }
2489
2489
  },
2490
2490
  "node_modules/braces": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astralyn/pi",
3
- "version": "0.82.5",
3
+ "version": "0.82.7",
4
4
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "piConfig": {
@@ -1,52 +0,0 @@
1
- /**
2
- * Resolve the fallback encoding for console output on this system.
3
- *
4
- * On Windows, cmd.exe builtins, Windows PowerShell, and classic console tools
5
- * such as ipconfig emit piped output in the legacy OEM code page (GBK on
6
- * Simplified Chinese systems, Shift_JIS on Japanese systems, and so on)
7
- * rather than UTF-8. Returns the TextDecoder label for that code page, or
8
- * null when output should stay UTF-8: non-Windows platforms, UTF-8 systems
9
- * (code page 65001), and code pages TextDecoder cannot represent.
10
- */
11
- export declare function getConsoleFallbackEncoding(): string | null;
12
- export interface DecodedChunk {
13
- /** Decoded text for this push, or the full re-decoded transcript when rewound. */
14
- text: string;
15
- /**
16
- * True when the decoder switched to the fallback encoding. All previously
17
- * returned text is invalid; `text` replaces the entire transcript.
18
- */
19
- rewound: boolean;
20
- }
21
- /**
22
- * Streaming decoder that assumes UTF-8 and falls back to the system console
23
- * encoding when the stream turns out not to be UTF-8.
24
- *
25
- * Decoding starts as plain streaming UTF-8 — the fast path for virtually all
26
- * output. When a replacement character appears, the bytes seen so far are
27
- * checked with isUtf8: if they are genuinely invalid UTF-8, everything is
28
- * re-decoded with the fallback encoding and the result is reported with
29
- * `rewound: true` so the caller can rebuild its derived state. If the bytes
30
- * are valid UTF-8 (the source itself contained replacement characters),
31
- * detection stops and UTF-8 decoding continues.
32
- *
33
- * Raw bytes are retained only while detection is active and only up to
34
- * `detectionLimitBytes`; larger streams lock to UTF-8 and memory stays flat.
35
- */
36
- export declare class OutputDecoder {
37
- private decoder;
38
- private readonly fallbackEncoding;
39
- private readonly detectionLimitBytes;
40
- private pending;
41
- private pendingBytes;
42
- private detecting;
43
- constructor(options?: {
44
- fallbackEncoding?: string | null;
45
- detectionLimitBytes?: number;
46
- });
47
- push(data: Buffer): DecodedChunk;
48
- flush(): DecodedChunk;
49
- private tryRewind;
50
- private stopDetecting;
51
- }
52
- //# sourceMappingURL=output-decoder.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"output-decoder.d.ts","sourceRoot":"","sources":["../../src/utils/output-decoder.ts"],"names":[],"mappings":"AAqBA;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,IAAI,MAAM,GAAG,IAAI,CAK1D;AA4BD,MAAM,WAAW,YAAY;IAC5B,kFAAkF;IAClF,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,OAAO,EAAE,OAAO,CAAC;CACjB;AAID;;;;;;;;;;;;;;GAcG;AACH,qBAAa,aAAa;IACzB,OAAO,CAAC,OAAO,CAAqB;IACpC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAgB;IACjD,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAS;IAC7C,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,SAAS,CAAU;IAE3B,YAAY,OAAO,GAAE;QAAE,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,mBAAmB,CAAC,EAAE,MAAM,CAAA;KAAO,EAK3F;IAED,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CAc/B;IAED,KAAK,IAAI,YAAY,CAOpB;IAED,OAAO,CAAC,SAAS;IAmBjB,OAAO,CAAC,aAAa;CAKrB","sourcesContent":["import { isUtf8 } from \"node:buffer\";\nimport { spawnSync } from \"child_process\";\n\n/**\n * Windows OEM code pages mapped to WHATWG TextDecoder labels. Console\n * programs emit piped output in the OEM code page, so only code pages that\n * TextDecoder can represent are listed; others keep UTF-8 decoding.\n */\nconst OEM_CODE_PAGE_ENCODINGS: Record<string, string> = {\n\t\"866\": \"ibm866\",\n\t\"874\": \"windows-874\",\n\t\"932\": \"shift_jis\",\n\t\"936\": \"gbk\",\n\t\"949\": \"euc-kr\",\n\t\"950\": \"big5\",\n\t\"1251\": \"windows-1251\",\n\t\"1252\": \"windows-1252\",\n};\n\nlet cachedConsoleFallbackEncoding: string | null | undefined;\n\n/**\n * Resolve the fallback encoding for console output on this system.\n *\n * On Windows, cmd.exe builtins, Windows PowerShell, and classic console tools\n * such as ipconfig emit piped output in the legacy OEM code page (GBK on\n * Simplified Chinese systems, Shift_JIS on Japanese systems, and so on)\n * rather than UTF-8. Returns the TextDecoder label for that code page, or\n * null when output should stay UTF-8: non-Windows platforms, UTF-8 systems\n * (code page 65001), and code pages TextDecoder cannot represent.\n */\nexport function getConsoleFallbackEncoding(): string | null {\n\tif (cachedConsoleFallbackEncoding === undefined) {\n\t\tcachedConsoleFallbackEncoding = detectConsoleFallbackEncoding();\n\t}\n\treturn cachedConsoleFallbackEncoding;\n}\n\nfunction detectConsoleFallbackEncoding(): string | null {\n\tif (process.platform !== \"win32\") {\n\t\treturn null;\n\t}\n\ttry {\n\t\tconst result = spawnSync(\n\t\t\t\"reg\",\n\t\t\t[\"query\", \"HKLM\\\\SYSTEM\\\\CurrentControlSet\\\\Control\\\\Nls\\\\CodePage\", \"/v\", \"OEMCP\"],\n\t\t\t{ encoding: \"utf-8\", timeout: 5000, windowsHide: true },\n\t\t);\n\t\tif (result.status !== 0 || !result.stdout) {\n\t\t\treturn null;\n\t\t}\n\t\tconst match = result.stdout.match(/OEMCP\\s+REG_SZ\\s+(\\d+)/);\n\t\tconst encoding = match ? OEM_CODE_PAGE_ENCODINGS[match[1]] : undefined;\n\t\tif (!encoding) {\n\t\t\treturn null;\n\t\t}\n\t\t// Node builds with small-icu lack these decoders; verify availability.\n\t\tnew TextDecoder(encoding);\n\t\treturn encoding;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nexport interface DecodedChunk {\n\t/** Decoded text for this push, or the full re-decoded transcript when rewound. */\n\ttext: string;\n\t/**\n\t * True when the decoder switched to the fallback encoding. All previously\n\t * returned text is invalid; `text` replaces the entire transcript.\n\t */\n\trewound: boolean;\n}\n\nconst DEFAULT_DETECTION_LIMIT_BYTES = 256 * 1024;\n\n/**\n * Streaming decoder that assumes UTF-8 and falls back to the system console\n * encoding when the stream turns out not to be UTF-8.\n *\n * Decoding starts as plain streaming UTF-8 — the fast path for virtually all\n * output. When a replacement character appears, the bytes seen so far are\n * checked with isUtf8: if they are genuinely invalid UTF-8, everything is\n * re-decoded with the fallback encoding and the result is reported with\n * `rewound: true` so the caller can rebuild its derived state. If the bytes\n * are valid UTF-8 (the source itself contained replacement characters),\n * detection stops and UTF-8 decoding continues.\n *\n * Raw bytes are retained only while detection is active and only up to\n * `detectionLimitBytes`; larger streams lock to UTF-8 and memory stays flat.\n */\nexport class OutputDecoder {\n\tprivate decoder = new TextDecoder();\n\tprivate readonly fallbackEncoding: string | null;\n\tprivate readonly detectionLimitBytes: number;\n\tprivate pending: Buffer[] = [];\n\tprivate pendingBytes = 0;\n\tprivate detecting: boolean;\n\n\tconstructor(options: { fallbackEncoding?: string | null; detectionLimitBytes?: number } = {}) {\n\t\tthis.fallbackEncoding =\n\t\t\toptions.fallbackEncoding === undefined ? getConsoleFallbackEncoding() : options.fallbackEncoding;\n\t\tthis.detectionLimitBytes = options.detectionLimitBytes ?? DEFAULT_DETECTION_LIMIT_BYTES;\n\t\tthis.detecting = this.fallbackEncoding !== null;\n\t}\n\n\tpush(data: Buffer): DecodedChunk {\n\t\tconst text = this.decoder.decode(data, { stream: true });\n\t\tif (!this.detecting) {\n\t\t\treturn { text, rewound: false };\n\t\t}\n\t\tthis.pending.push(data);\n\t\tthis.pendingBytes += data.length;\n\t\tif (text.includes(\"�\")) {\n\t\t\treturn this.tryRewind(text);\n\t\t}\n\t\tif (this.pendingBytes > this.detectionLimitBytes) {\n\t\t\tthis.stopDetecting();\n\t\t}\n\t\treturn { text, rewound: false };\n\t}\n\n\tflush(): DecodedChunk {\n\t\tconst text = this.decoder.decode();\n\t\tif (this.detecting && text.includes(\"�\")) {\n\t\t\treturn this.tryRewind(text);\n\t\t}\n\t\tthis.stopDetecting();\n\t\treturn { text, rewound: false };\n\t}\n\n\tprivate tryRewind(utf8Text: string): DecodedChunk {\n\t\tconst raw = Buffer.concat(this.pending);\n\t\tif (isUtf8(raw)) {\n\t\t\t// The replacement characters exist in the source bytes themselves;\n\t\t\t// the stream is valid UTF-8, so stop watching and keep it.\n\t\t\tthis.stopDetecting();\n\t\t\treturn { text: utf8Text, rewound: false };\n\t\t}\n\t\ttry {\n\t\t\tthis.decoder = new TextDecoder(this.fallbackEncoding as string);\n\t\t} catch {\n\t\t\tthis.stopDetecting();\n\t\t\treturn { text: utf8Text, rewound: false };\n\t\t}\n\t\tconst text = this.decoder.decode(raw, { stream: true });\n\t\tthis.stopDetecting();\n\t\treturn { text, rewound: true };\n\t}\n\n\tprivate stopDetecting(): void {\n\t\tthis.detecting = false;\n\t\tthis.pending = [];\n\t\tthis.pendingBytes = 0;\n\t}\n}\n"]}
@@ -1,134 +0,0 @@
1
- import { isUtf8 } from "node:buffer";
2
- import { spawnSync } from "child_process";
3
- /**
4
- * Windows OEM code pages mapped to WHATWG TextDecoder labels. Console
5
- * programs emit piped output in the OEM code page, so only code pages that
6
- * TextDecoder can represent are listed; others keep UTF-8 decoding.
7
- */
8
- const OEM_CODE_PAGE_ENCODINGS = {
9
- "866": "ibm866",
10
- "874": "windows-874",
11
- "932": "shift_jis",
12
- "936": "gbk",
13
- "949": "euc-kr",
14
- "950": "big5",
15
- "1251": "windows-1251",
16
- "1252": "windows-1252",
17
- };
18
- let cachedConsoleFallbackEncoding;
19
- /**
20
- * Resolve the fallback encoding for console output on this system.
21
- *
22
- * On Windows, cmd.exe builtins, Windows PowerShell, and classic console tools
23
- * such as ipconfig emit piped output in the legacy OEM code page (GBK on
24
- * Simplified Chinese systems, Shift_JIS on Japanese systems, and so on)
25
- * rather than UTF-8. Returns the TextDecoder label for that code page, or
26
- * null when output should stay UTF-8: non-Windows platforms, UTF-8 systems
27
- * (code page 65001), and code pages TextDecoder cannot represent.
28
- */
29
- export function getConsoleFallbackEncoding() {
30
- if (cachedConsoleFallbackEncoding === undefined) {
31
- cachedConsoleFallbackEncoding = detectConsoleFallbackEncoding();
32
- }
33
- return cachedConsoleFallbackEncoding;
34
- }
35
- function detectConsoleFallbackEncoding() {
36
- if (process.platform !== "win32") {
37
- return null;
38
- }
39
- try {
40
- const result = spawnSync("reg", ["query", "HKLM\\SYSTEM\\CurrentControlSet\\Control\\Nls\\CodePage", "/v", "OEMCP"], { encoding: "utf-8", timeout: 5000, windowsHide: true });
41
- if (result.status !== 0 || !result.stdout) {
42
- return null;
43
- }
44
- const match = result.stdout.match(/OEMCP\s+REG_SZ\s+(\d+)/);
45
- const encoding = match ? OEM_CODE_PAGE_ENCODINGS[match[1]] : undefined;
46
- if (!encoding) {
47
- return null;
48
- }
49
- // Node builds with small-icu lack these decoders; verify availability.
50
- new TextDecoder(encoding);
51
- return encoding;
52
- }
53
- catch {
54
- return null;
55
- }
56
- }
57
- const DEFAULT_DETECTION_LIMIT_BYTES = 256 * 1024;
58
- /**
59
- * Streaming decoder that assumes UTF-8 and falls back to the system console
60
- * encoding when the stream turns out not to be UTF-8.
61
- *
62
- * Decoding starts as plain streaming UTF-8 — the fast path for virtually all
63
- * output. When a replacement character appears, the bytes seen so far are
64
- * checked with isUtf8: if they are genuinely invalid UTF-8, everything is
65
- * re-decoded with the fallback encoding and the result is reported with
66
- * `rewound: true` so the caller can rebuild its derived state. If the bytes
67
- * are valid UTF-8 (the source itself contained replacement characters),
68
- * detection stops and UTF-8 decoding continues.
69
- *
70
- * Raw bytes are retained only while detection is active and only up to
71
- * `detectionLimitBytes`; larger streams lock to UTF-8 and memory stays flat.
72
- */
73
- export class OutputDecoder {
74
- decoder = new TextDecoder();
75
- fallbackEncoding;
76
- detectionLimitBytes;
77
- pending = [];
78
- pendingBytes = 0;
79
- detecting;
80
- constructor(options = {}) {
81
- this.fallbackEncoding =
82
- options.fallbackEncoding === undefined ? getConsoleFallbackEncoding() : options.fallbackEncoding;
83
- this.detectionLimitBytes = options.detectionLimitBytes ?? DEFAULT_DETECTION_LIMIT_BYTES;
84
- this.detecting = this.fallbackEncoding !== null;
85
- }
86
- push(data) {
87
- const text = this.decoder.decode(data, { stream: true });
88
- if (!this.detecting) {
89
- return { text, rewound: false };
90
- }
91
- this.pending.push(data);
92
- this.pendingBytes += data.length;
93
- if (text.includes("�")) {
94
- return this.tryRewind(text);
95
- }
96
- if (this.pendingBytes > this.detectionLimitBytes) {
97
- this.stopDetecting();
98
- }
99
- return { text, rewound: false };
100
- }
101
- flush() {
102
- const text = this.decoder.decode();
103
- if (this.detecting && text.includes("�")) {
104
- return this.tryRewind(text);
105
- }
106
- this.stopDetecting();
107
- return { text, rewound: false };
108
- }
109
- tryRewind(utf8Text) {
110
- const raw = Buffer.concat(this.pending);
111
- if (isUtf8(raw)) {
112
- // The replacement characters exist in the source bytes themselves;
113
- // the stream is valid UTF-8, so stop watching and keep it.
114
- this.stopDetecting();
115
- return { text: utf8Text, rewound: false };
116
- }
117
- try {
118
- this.decoder = new TextDecoder(this.fallbackEncoding);
119
- }
120
- catch {
121
- this.stopDetecting();
122
- return { text: utf8Text, rewound: false };
123
- }
124
- const text = this.decoder.decode(raw, { stream: true });
125
- this.stopDetecting();
126
- return { text, rewound: true };
127
- }
128
- stopDetecting() {
129
- this.detecting = false;
130
- this.pending = [];
131
- this.pendingBytes = 0;
132
- }
133
- }
134
- //# sourceMappingURL=output-decoder.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"output-decoder.js","sourceRoot":"","sources":["../../src/utils/output-decoder.ts"],"names":[],"mappings":"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","sourcesContent":["import { isUtf8 } from \"node:buffer\";\nimport { spawnSync } from \"child_process\";\n\n/**\n * Windows OEM code pages mapped to WHATWG TextDecoder labels. Console\n * programs emit piped output in the OEM code page, so only code pages that\n * TextDecoder can represent are listed; others keep UTF-8 decoding.\n */\nconst OEM_CODE_PAGE_ENCODINGS: Record<string, string> = {\n\t\"866\": \"ibm866\",\n\t\"874\": \"windows-874\",\n\t\"932\": \"shift_jis\",\n\t\"936\": \"gbk\",\n\t\"949\": \"euc-kr\",\n\t\"950\": \"big5\",\n\t\"1251\": \"windows-1251\",\n\t\"1252\": \"windows-1252\",\n};\n\nlet cachedConsoleFallbackEncoding: string | null | undefined;\n\n/**\n * Resolve the fallback encoding for console output on this system.\n *\n * On Windows, cmd.exe builtins, Windows PowerShell, and classic console tools\n * such as ipconfig emit piped output in the legacy OEM code page (GBK on\n * Simplified Chinese systems, Shift_JIS on Japanese systems, and so on)\n * rather than UTF-8. Returns the TextDecoder label for that code page, or\n * null when output should stay UTF-8: non-Windows platforms, UTF-8 systems\n * (code page 65001), and code pages TextDecoder cannot represent.\n */\nexport function getConsoleFallbackEncoding(): string | null {\n\tif (cachedConsoleFallbackEncoding === undefined) {\n\t\tcachedConsoleFallbackEncoding = detectConsoleFallbackEncoding();\n\t}\n\treturn cachedConsoleFallbackEncoding;\n}\n\nfunction detectConsoleFallbackEncoding(): string | null {\n\tif (process.platform !== \"win32\") {\n\t\treturn null;\n\t}\n\ttry {\n\t\tconst result = spawnSync(\n\t\t\t\"reg\",\n\t\t\t[\"query\", \"HKLM\\\\SYSTEM\\\\CurrentControlSet\\\\Control\\\\Nls\\\\CodePage\", \"/v\", \"OEMCP\"],\n\t\t\t{ encoding: \"utf-8\", timeout: 5000, windowsHide: true },\n\t\t);\n\t\tif (result.status !== 0 || !result.stdout) {\n\t\t\treturn null;\n\t\t}\n\t\tconst match = result.stdout.match(/OEMCP\\s+REG_SZ\\s+(\\d+)/);\n\t\tconst encoding = match ? OEM_CODE_PAGE_ENCODINGS[match[1]] : undefined;\n\t\tif (!encoding) {\n\t\t\treturn null;\n\t\t}\n\t\t// Node builds with small-icu lack these decoders; verify availability.\n\t\tnew TextDecoder(encoding);\n\t\treturn encoding;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nexport interface DecodedChunk {\n\t/** Decoded text for this push, or the full re-decoded transcript when rewound. */\n\ttext: string;\n\t/**\n\t * True when the decoder switched to the fallback encoding. All previously\n\t * returned text is invalid; `text` replaces the entire transcript.\n\t */\n\trewound: boolean;\n}\n\nconst DEFAULT_DETECTION_LIMIT_BYTES = 256 * 1024;\n\n/**\n * Streaming decoder that assumes UTF-8 and falls back to the system console\n * encoding when the stream turns out not to be UTF-8.\n *\n * Decoding starts as plain streaming UTF-8 — the fast path for virtually all\n * output. When a replacement character appears, the bytes seen so far are\n * checked with isUtf8: if they are genuinely invalid UTF-8, everything is\n * re-decoded with the fallback encoding and the result is reported with\n * `rewound: true` so the caller can rebuild its derived state. If the bytes\n * are valid UTF-8 (the source itself contained replacement characters),\n * detection stops and UTF-8 decoding continues.\n *\n * Raw bytes are retained only while detection is active and only up to\n * `detectionLimitBytes`; larger streams lock to UTF-8 and memory stays flat.\n */\nexport class OutputDecoder {\n\tprivate decoder = new TextDecoder();\n\tprivate readonly fallbackEncoding: string | null;\n\tprivate readonly detectionLimitBytes: number;\n\tprivate pending: Buffer[] = [];\n\tprivate pendingBytes = 0;\n\tprivate detecting: boolean;\n\n\tconstructor(options: { fallbackEncoding?: string | null; detectionLimitBytes?: number } = {}) {\n\t\tthis.fallbackEncoding =\n\t\t\toptions.fallbackEncoding === undefined ? getConsoleFallbackEncoding() : options.fallbackEncoding;\n\t\tthis.detectionLimitBytes = options.detectionLimitBytes ?? DEFAULT_DETECTION_LIMIT_BYTES;\n\t\tthis.detecting = this.fallbackEncoding !== null;\n\t}\n\n\tpush(data: Buffer): DecodedChunk {\n\t\tconst text = this.decoder.decode(data, { stream: true });\n\t\tif (!this.detecting) {\n\t\t\treturn { text, rewound: false };\n\t\t}\n\t\tthis.pending.push(data);\n\t\tthis.pendingBytes += data.length;\n\t\tif (text.includes(\"�\")) {\n\t\t\treturn this.tryRewind(text);\n\t\t}\n\t\tif (this.pendingBytes > this.detectionLimitBytes) {\n\t\t\tthis.stopDetecting();\n\t\t}\n\t\treturn { text, rewound: false };\n\t}\n\n\tflush(): DecodedChunk {\n\t\tconst text = this.decoder.decode();\n\t\tif (this.detecting && text.includes(\"�\")) {\n\t\t\treturn this.tryRewind(text);\n\t\t}\n\t\tthis.stopDetecting();\n\t\treturn { text, rewound: false };\n\t}\n\n\tprivate tryRewind(utf8Text: string): DecodedChunk {\n\t\tconst raw = Buffer.concat(this.pending);\n\t\tif (isUtf8(raw)) {\n\t\t\t// The replacement characters exist in the source bytes themselves;\n\t\t\t// the stream is valid UTF-8, so stop watching and keep it.\n\t\t\tthis.stopDetecting();\n\t\t\treturn { text: utf8Text, rewound: false };\n\t\t}\n\t\ttry {\n\t\t\tthis.decoder = new TextDecoder(this.fallbackEncoding as string);\n\t\t} catch {\n\t\t\tthis.stopDetecting();\n\t\t\treturn { text: utf8Text, rewound: false };\n\t\t}\n\t\tconst text = this.decoder.decode(raw, { stream: true });\n\t\tthis.stopDetecting();\n\t\treturn { text, rewound: true };\n\t}\n\n\tprivate stopDetecting(): void {\n\t\tthis.detecting = false;\n\t\tthis.pending = [];\n\t\tthis.pendingBytes = 0;\n\t}\n}\n"]}