@selesai/code 0.13.12 → 0.13.14

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 (203) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/extensions/pi-subagents/CHANGELOG.md +77 -1
  3. package/dist/extensions/pi-subagents/VISION.md +12 -1
  4. package/dist/extensions/pi-subagents/docs/agents.md +13 -6
  5. package/dist/extensions/pi-subagents/docs/configuration.md +35 -9
  6. package/dist/extensions/pi-subagents/docs/extension-api.md +1 -1
  7. package/dist/extensions/pi-subagents/docs/missions.md +1 -1
  8. package/dist/extensions/pi-subagents/docs/models.md +6 -6
  9. package/dist/extensions/pi-subagents/docs/observability.md +6 -3
  10. package/dist/extensions/pi-subagents/docs/tool-reference.md +3 -3
  11. package/dist/extensions/pi-subagents/docs/watchdog.md +92 -114
  12. package/dist/extensions/pi-subagents/docs/workflows.md +1 -1
  13. package/dist/extensions/pi-subagents/install.mjs +1 -2
  14. package/dist/extensions/pi-subagents/package.json +1 -1
  15. package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +1 -1
  16. package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +6 -6
  17. package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +2 -2
  18. package/dist/extensions/pi-subagents/skills/pi-subagents/references/multi-lane-orchestration.md +1 -1
  19. package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +4 -4
  20. package/dist/extensions/pi-subagents/skills/pi-subagents/references/review-and-validation.md +1 -1
  21. package/dist/extensions/pi-subagents/src/agents/agent-management.ts +41 -4
  22. package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +3 -0
  23. package/dist/extensions/pi-subagents/src/agents/agents.ts +120 -124
  24. package/dist/extensions/pi-subagents/src/agents/runtime-agent-registry.ts +5 -1
  25. package/dist/extensions/pi-subagents/src/api/preflight.ts +4 -0
  26. package/dist/extensions/pi-subagents/src/api/shared-types.ts +3 -0
  27. package/dist/extensions/pi-subagents/src/extension/config.ts +20 -0
  28. package/dist/extensions/pi-subagents/src/extension/public-execution.ts +1 -0
  29. package/dist/extensions/pi-subagents/src/extension/schemas.ts +6 -2
  30. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +1 -1
  31. package/dist/extensions/pi-subagents/src/inspectors/herdr/inspector-runner.ts +19 -13
  32. package/dist/extensions/pi-subagents/src/runs/background/active-async-capacity.ts +26 -8
  33. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +103 -23
  34. package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +6 -2
  35. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +18 -2
  36. package/dist/extensions/pi-subagents/src/runs/background/notify.ts +54 -3
  37. package/dist/extensions/pi-subagents/src/runs/background/process-terminal.ts +16 -0
  38. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +22 -2
  39. package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +63 -6
  40. package/dist/extensions/pi-subagents/src/runs/background/steering.ts +4 -1
  41. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +69 -12
  42. package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +13 -0
  43. package/dist/extensions/pi-subagents/src/runs/background/wait-tool.ts +3 -1
  44. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +21 -8
  45. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +59 -6
  46. package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +95 -18
  47. package/dist/extensions/pi-subagents/src/runs/shared/async-status-projection.ts +11 -43
  48. package/dist/extensions/pi-subagents/src/runs/shared/capability-ceiling.ts +1 -0
  49. package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +1 -1
  50. package/dist/extensions/pi-subagents/src/runs/shared/lane-metadata.ts +24 -3
  51. package/dist/extensions/pi-subagents/src/runs/shared/parallel-handoff.ts +4 -0
  52. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +2 -6
  53. package/dist/extensions/pi-subagents/src/runs/shared/permissions.ts +1 -0
  54. package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +33 -18
  55. package/dist/extensions/pi-subagents/src/runs/shared/pi-spawn.ts +73 -37
  56. package/dist/extensions/pi-subagents/src/runs/shared/structured-output.ts +33 -6
  57. package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +4 -2
  58. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +20 -3
  59. package/dist/extensions/pi-subagents/src/runs/shared/task-intent.ts +21 -7
  60. package/dist/extensions/pi-subagents/src/runs/shared/tool-timeout.ts +1 -1
  61. package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +467 -63
  62. package/dist/extensions/pi-subagents/src/shared/atomic-json.ts +3 -1
  63. package/dist/extensions/pi-subagents/src/shared/fork-context.ts +0 -12
  64. package/dist/extensions/pi-subagents/src/shared/fork-session-cwd.ts +27 -0
  65. package/dist/extensions/pi-subagents/src/shared/launch-contract.ts +3 -0
  66. package/dist/extensions/pi-subagents/src/shared/types.ts +42 -4
  67. package/dist/extensions/pi-subagents/src/shared/utils.ts +18 -7
  68. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +1 -1
  69. package/dist/extensions/pi-subagents/src/slash/subagents-admin.ts +26 -12
  70. package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +61 -2
  71. package/dist/extensions/pi-subagents/src/tui/fleet.ts +12 -7
  72. package/dist/extensions/pi-subagents/src/tui/render.ts +227 -14
  73. package/dist/extensions/pi-subagents/src/watchdog/child-status.ts +56 -34
  74. package/dist/extensions/pi-subagents/src/watchdog/diff-tool.ts +77 -0
  75. package/dist/extensions/pi-subagents/src/watchdog/emission-guard.ts +5 -3
  76. package/dist/extensions/pi-subagents/src/watchdog/guidance.ts +20 -0
  77. package/dist/extensions/pi-subagents/src/watchdog/register-child.ts +16 -13
  78. package/dist/extensions/pi-subagents/src/watchdog/register-main.ts +10 -9
  79. package/dist/extensions/pi-subagents/src/watchdog/render.ts +4 -5
  80. package/dist/extensions/pi-subagents/src/watchdog/review.ts +15 -4
  81. package/dist/extensions/pi-subagents/src/watchdog/rules.ts +70 -0
  82. package/dist/extensions/pi-subagents/src/watchdog/runtime.ts +75 -92
  83. package/dist/extensions/pi-subagents/src/watchdog/scope.ts +2 -1
  84. package/dist/extensions/pi-subagents/src/watchdog/settings.ts +48 -104
  85. package/dist/extensions/pi-subagents/src/watchdog/types.ts +18 -32
  86. package/dist/extensions/pi-subagents/src/watchdog/warning-format.ts +0 -1
  87. package/dist/extensions/pi-subagents/src/workflows/chat-progress.ts +3 -2
  88. package/dist/extensions/pi-subagents/src/workflows/workflow-checklist.ts +439 -0
  89. package/dist/extensions/pi-subagents/src/workflows/workflow-preflight.ts +28 -1
  90. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +335 -21
  91. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +4 -1
  92. package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +1 -1
  93. package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +22 -18
  94. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +0 -1
  95. package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +21 -18
  96. package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +307 -22
  97. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +1 -0
  98. package/dist/extensions/pi-subagents/test/support/helpers.ts +25 -0
  99. package/dist/extensions/pi-subagents/test/support/isolated-temp-root.mjs +15 -4
  100. package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +17 -2
  101. package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +96 -15
  102. package/dist/extensions/pi-subagents/test/unit/active-async-capacity.test.ts +53 -5
  103. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +20 -0
  104. package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +221 -8
  105. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +45 -24
  106. package/dist/extensions/pi-subagents/test/unit/agent-scan-dirs.test.ts +133 -0
  107. package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +28 -2
  108. package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +1 -1
  109. package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +2 -0
  110. package/dist/extensions/pi-subagents/test/unit/async-retention.test.ts +4 -1
  111. package/dist/extensions/pi-subagents/test/unit/async-status-projection.test.ts +18 -4
  112. package/dist/extensions/pi-subagents/test/unit/atomic-json.test.ts +16 -0
  113. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +38 -0
  114. package/dist/extensions/pi-subagents/test/unit/default-extensions.test.ts +2 -2
  115. package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +21 -0
  116. package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +19 -6
  117. package/dist/extensions/pi-subagents/test/unit/get-final-output.test.ts +26 -0
  118. package/dist/extensions/pi-subagents/test/unit/herdr-inspector.test.ts +22 -1
  119. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +2 -6
  120. package/dist/extensions/pi-subagents/test/unit/notify.test.ts +66 -1
  121. package/dist/extensions/pi-subagents/test/unit/pi-args-permission-system.test.ts +1 -1
  122. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +116 -10
  123. package/dist/extensions/pi-subagents/test/unit/pi-spawn.test.ts +136 -18
  124. package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +20 -0
  125. package/dist/extensions/pi-subagents/test/unit/process-terminal.test.ts +22 -0
  126. package/dist/extensions/pi-subagents/test/unit/profiles.test.ts +1 -0
  127. package/dist/extensions/pi-subagents/test/unit/project-panes-public-api.test.ts +8 -0
  128. package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +127 -48
  129. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +68 -0
  130. package/dist/extensions/pi-subagents/test/unit/runtime-agent-registration.test.ts +17 -0
  131. package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +88 -0
  132. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +4 -2
  133. package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +9 -9
  134. package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +2 -2
  135. package/dist/extensions/pi-subagents/test/unit/steering.test.ts +9 -3
  136. package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +22 -9
  137. package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +91 -8
  138. package/dist/extensions/pi-subagents/test/unit/task-intent.test.ts +27 -0
  139. package/dist/extensions/pi-subagents/test/unit/temp-paths.test.ts +50 -0
  140. package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +0 -1
  141. package/dist/extensions/pi-subagents/test/unit/tool-timeout.test.ts +2 -2
  142. package/dist/extensions/pi-subagents/test/unit/wait-completions.test.ts +34 -0
  143. package/dist/extensions/pi-subagents/test/unit/wait-subscriptions.test.ts +1 -2
  144. package/dist/extensions/pi-subagents/test/unit/watchdog-child-status.test.ts +73 -13
  145. package/dist/extensions/pi-subagents/test/unit/watchdog-diff-tool.test.ts +79 -0
  146. package/dist/extensions/pi-subagents/test/unit/watchdog-permission-arbiter.test.ts +2 -4
  147. package/dist/extensions/pi-subagents/test/unit/watchdog-render.test.ts +7 -8
  148. package/dist/extensions/pi-subagents/test/unit/watchdog-review.test.ts +48 -3
  149. package/dist/extensions/pi-subagents/test/unit/watchdog-rules.test.ts +37 -0
  150. package/dist/extensions/pi-subagents/test/unit/watchdog-runtime.test.ts +36 -118
  151. package/dist/extensions/pi-subagents/test/unit/watchdog-scope.test.ts +1 -1
  152. package/dist/extensions/pi-subagents/test/unit/watchdog-settings.test.ts +37 -14
  153. package/dist/extensions/pi-subagents/test/unit/workflow-chat-progress.test.ts +45 -26
  154. package/dist/extensions/pi-subagents/test/unit/workflow-checklist.test.ts +223 -0
  155. package/dist/extensions/pi-subagents/test/unit/workflow-launch-params.test.ts +50 -1
  156. package/dist/extensions/pi-subagents/test/unit/workflow-preflight.test.ts +22 -0
  157. package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +136 -4
  158. package/dist/extensions/pi-zentui/README.md +19 -5
  159. package/dist/extensions/pi-zentui/docs/configuration.md +37 -9
  160. package/dist/extensions/pi-zentui/extensions/zentui/config.ts +29 -0
  161. package/dist/extensions/pi-zentui/extensions/zentui/index.ts +31 -2
  162. package/dist/extensions/pi-zentui/extensions/zentui/prototype-patch-registry.ts +55 -11
  163. package/dist/extensions/pi-zentui/extensions/zentui/settings-command.ts +115 -3
  164. package/dist/extensions/pi-zentui/extensions/zentui/settings-previews.ts +119 -2
  165. package/dist/extensions/pi-zentui/extensions/zentui/thinking-experimental.ts +1768 -0
  166. package/dist/extensions/pi-zentui/extensions/zentui/thinking-status.ts +69 -0
  167. package/dist/extensions/pi-zentui/extensions/zentui/thinking-steps.ts +155 -0
  168. package/dist/extensions/pi-zentui/extensions/zentui/ui.ts +2 -1
  169. package/dist/extensions/pi-zentui/extensions/zentui/user-message-osc.ts +16 -2
  170. package/dist/extensions/pi-zentui/extensions/zentui/user-message-styles.ts +1 -1
  171. package/dist/extensions/pi-zentui/test/config-load-lifecycle.test.ts +124 -0
  172. package/dist/extensions/pi-zentui/test/config.test.ts +82 -2
  173. package/dist/extensions/pi-zentui/test/editor-viewport-indicators.test.ts +19 -4
  174. package/dist/extensions/pi-zentui/test/extension-compliance.test.ts +58 -9
  175. package/dist/extensions/pi-zentui/test/package-contents.mjs +33 -0
  176. package/dist/extensions/pi-zentui/test/prototype-patch-registry.test.ts +131 -0
  177. package/dist/extensions/pi-zentui/test/responsive-dependencies.test.ts +4 -4
  178. package/dist/extensions/pi-zentui/test/settings-command.test.ts +289 -7
  179. package/dist/extensions/pi-zentui/test/settings-previews.test.ts +106 -0
  180. package/dist/extensions/pi-zentui/test/thinking-experimental-docs.test.ts +51 -0
  181. package/dist/extensions/pi-zentui/test/thinking-experimental-loader-diagnostics.mjs +54 -0
  182. package/dist/extensions/pi-zentui/test/thinking-experimental-missing-export.test.ts +44 -0
  183. package/dist/extensions/pi-zentui/test/thinking-experimental-rows.test.ts +297 -0
  184. package/dist/extensions/pi-zentui/test/thinking-experimental-tui.mjs +2329 -0
  185. package/dist/extensions/pi-zentui/test/thinking-experimental.test.ts +1668 -0
  186. package/dist/extensions/pi-zentui/test/thinking-status.test.ts +85 -0
  187. package/dist/extensions/pi-zentui/test/thinking-steps.test.ts +122 -0
  188. package/dist/extensions/pi-zentui/test/user-message-osc.test.ts +58 -0
  189. package/dist/skills/brandkit/SKILL.md +0 -2
  190. package/dist/skills/{industrial-brutalist-ui → brutalist}/SKILL.md +1 -3
  191. package/dist/skills/gpt-taste/SKILL.md +0 -2
  192. package/dist/skills/image-to-code/SKILL.md +0 -2
  193. package/dist/skills/imagegen-frontend-mobile/SKILL.md +0 -2
  194. package/dist/skills/imagegen-frontend-web/SKILL.md +0 -2
  195. package/dist/skills/{minimalist-ui → minimalist}/SKILL.md +1 -3
  196. package/dist/skills/{full-output-enforcement → output}/SKILL.md +1 -3
  197. package/dist/skills/{redesign-existing-projects → redesign}/SKILL.md +1 -3
  198. package/dist/skills/{high-end-visual-design → soft}/SKILL.md +1 -3
  199. package/dist/skills/stitch/DESIGN.md +121 -0
  200. package/dist/skills/{stitch-design-taste → stitch}/SKILL.md +1 -3
  201. package/dist/skills/{design-taste-frontend → taste}/SKILL.md +1 -3
  202. package/dist/skills/{design-taste-frontend-v1 → taste-v1}/SKILL.md +2 -4
  203. package/package.json +1 -1
@@ -6,18 +6,19 @@ Zentui reads optional user configuration from `~/.pi/agent/zentui.json`. Missing
6
6
 
7
7
  ## `/zentui` settings
8
8
 
9
- The interactive `/zentui` menu is split into eight component-oriented sections. Use `Tab` and `Shift+Tab` to switch sections:
9
+ The interactive `/zentui` menu is split into nine component-oriented sections. Use `Tab` and `Shift+Tab` to switch sections:
10
10
 
11
11
  1. **Appearance** — selector-border enablement, style, and colors; icon mode.
12
12
  2. **Editor** — enablement, style, colors, model label, border behavior, viewport indicators, settings for the selected editor style, and a static synthetic preview.
13
13
  3. **User messages** — enablement, style, colors, and a static synthetic Markdown preview.
14
- 4. **Working line** — ownership, settled Turn summary, spinner and text speeds, optional spinner-color motion, text animation, color source, custom messages, Tool/Elapsed/Thinking/Tokens segments, and animated preview.
15
- 5. **Footer** — Native, Starship, or Hidden. Starship additionally exposes colors, model label, responsive layout, separator, context style, and path display.
16
- 6. **Segments** — visibility toggles for non-Git Starship segments.
17
- 7. **Git** — Starship Git segment and probe controls.
18
- 8. **Extensions** — Starship extension-status placement and color controls for active keys.
14
+ 4. **Thinking (Experimental)** — private Rail, Tree, or Streaming rendering; active Streaming can switch live to Rail or Tree, Rail and Tree can switch live between each other, and the private renderer may break after Pi updates.
15
+ 5. **Working line** — ownership, settled Turn summary, spinner and text speeds, optional spinner-color motion, text animation, color source, custom messages, Tool/Elapsed/Thinking time/Tokens segments, and animated preview.
16
+ 6. **Footer** — Native, Starship, or Hidden. Starship additionally exposes colors, model label, responsive layout, separator, context style, and path display.
17
+ 7. **Segments** — visibility toggles for non-Git Starship segments.
18
+ 8. **Git** — Starship Git segment and probe controls.
19
+ 9. **Extensions** — Starship extension-status placement and color controls for active keys.
19
20
 
20
- Editor, User messages, and Working line retain independent configuration. Editor and User-message previews remain visible while their component is disabled. Only the Working-line preview owns an animation timer. Footer-specific rows are shown only while Starship is selected, while Segments, Git, and Extensions remain available for preconfiguration under every Footer style.
21
+ Editor, User messages, Thinking (Experimental), and Working line retain independent configuration. Editor, User-message, and Thinking previews remain visible while their component is disabled. Only the Working-line preview owns an animation timer. Footer-specific rows are shown only while Starship is selected, while Segments, Git, and Extensions remain available for preconfiguration under every Footer style.
21
22
 
22
23
  Free-form values such as custom formats, Opencode metadata formats, raw colors/styles, and inactive extension keys remain JSON-only. Working-line speed accepts validated custom milliseconds in `/zentui`.
23
24
 
@@ -99,6 +100,10 @@ Copy this example and change only the values you need. Optional editor source-aw
99
100
  "labeled": {}
100
101
  }
101
102
  },
103
+ "thinkingSteps": {
104
+ "enabled": false,
105
+ "mode": "tree"
106
+ },
102
107
  "workingLine": {
103
108
  "enabled": false,
104
109
  "turnSummary": true,
@@ -267,7 +272,9 @@ Copy this example and change only the values you need. Optional editor source-aw
267
272
  - `components.editor` owns Editor enablement, `opencode | opencode-copy-friendly | accent-rail | minimalist` style selection, color source, border mode, model label, viewport indicators, and all four style configurations.
268
273
  - Editor `modelLabel` uses `id` by default; `name` uses the display name with ID fallback. Footer has an independent `modelLabel` control.
269
274
  - `components.userMessages` owns User-message enablement, `framed | framed-copy-friendly | compact | labeled` style selection, and color source. Disabling it delegates byte-for-byte to Pi's native renderer.
270
- - `components.workingLine.enabled` is the sole Working-line ownership switch.
275
+ - `components.thinkingSteps` independently owns opt-in **Thinking (Experimental)** display. It defaults to `{ "enabled": false, "mode": "tree" }`; canonical modes are `rail | tree | streaming`. The former persisted `streaming-experimental` value is accepted only as a migration alias and is normalized to `streaming` on save.
276
+ - All three modes decorate Pi's private host renderer and are tested on exact Pi versions 0.80.5, 0.82.1, 0.83.0, 0.84.0, and 0.84.4. Active Streaming can switch live to Rail or Tree, and Rail and Tree can switch live between each other. Entering Streaming from a structural mode, first enable, and re-enable after live disable require restart; live disable restores native thinking. Disabled mode changes only preconfigure.
277
+ - `components.workingLine.enabled` is the sole Working-line ownership switch. Thinking (Experimental) never enables, configures, or owns the Working line and leaves the existing **Thinking time** option unchanged.
271
278
  - `components.selectorBorders` owns selector-border enablement, fixed `zentui` style, and color source. Disable it for native Pi behavior.
272
279
  - `components.footer` owns `native | starship | hidden` style selection, color source, model label, and Starship options. Hidden installs an empty component with zero rows.
273
280
  - Starship's package-version segment reads the project manifest and is distinct from the runtime segment, which reports the installed toolchain.
@@ -381,6 +388,27 @@ Missing, non-string, or empty values use `$model $provider( $thinking)`. A non
381
388
  - Disabling styling delegates to Pi's native renderer; native is not a style ID.
382
389
  - Zentui intentionally provides no custom `plain` style.
383
390
 
391
+ ## Thinking (Experimental)
392
+
393
+ Rail, Tree, and Streaming share one private `AssistantMessageComponent` wrapper. The saved `{ enabled, mode }` value is read at session start before transcript restoration; disabled startup installs nothing. Once installed and healthy, active Streaming can switch live to Rail or Tree, and Rail and Tree can switch live between each other, without reinstalling the patch. Each supported transition rerenders tracked components once. Selecting Streaming from Rail or Tree saves Streaming but keeps the active structural mode unchanged and reports restart required; it never acquires input or timer resources live. Leaving Streaming releases those resources. If cleanup throws, a requested Rail or Tree mode still becomes active, while disable still restores native children; either successful change warns that Streaming is unavailable for the rest of the session. Entering Streaming from a structural mode, first enable, and re-enable after a live disable require restart, while mode changes when disabled only preconfigure. Shutdown restores native children, exact hidden-state ownership, and the predecessor descriptor. Startup acquisition or private constructor, layout, Markdown identity, parser, theme, rendering, width, or displacement failure uses native thinking. Private APIs may break after any Pi update.
394
+
395
+ Rail parses each native contiguous thinking run and shows every label in that run. Tree shows the latest five in each run; neither aggregates across intervening text or tool blocks. Both follow Pi's thinking visibility. Labels come from headings, top-level list items, and blank-line-separated prose. Complete strict 7-bit CSI SGR styling is stripped before parsing; every other terminal control, unsafe or unstructured content, malformed or over-limit input, unterminated fences/math, and unsupported structure leave that complete run native. Fenced code, Mermaid, display math, and indented nested content remain opaque bodies.
396
+
397
+ Each selected SGR-free label is rendered as Markdown by a fresh Pi `Markdown` with the host child's exact theme, default `thinkingText`/italic style, and transform options. Native emphasis, code, links, HTML, LaTeX, and custom theme/transform callbacks therefore remain authoritative. Host horizontal padding is applied externally. Each label is exactly one terminal row; Pi TUI's ANSI/OSC/grapheme-aware width utilities crop the first rendered row and reserve one cell for `…` only when required. Empty, image, non-text, impossible-width, or throwing output restores the whole native run. Connectors are separate from Markdown and call the current `theme.fg("accent", connector)` every render, so custom themes directly control their appearance. Visible forms are:
398
+
399
+ ```text
400
+ │ Thinking ┆ Thinking
401
+ │ First ├─ · Earlier
402
+ │ Latest └─ · Latest
403
+ │ • Open └─ • Open
404
+ ```
405
+
406
+ Only an actually open thinking phase uses `•`; a text/tool transition or restored completion is settled. Rail and Tree preserve Pi's hidden state and native hidden label. The `/zentui` Mode action offers all three modes, but entering Streaming from Rail or Tree is saved for restart rather than applied live.
407
+
408
+ Streaming keeps the reviewed host-rendered behavior: while open it shows the latest five rendered terminal rows beneath `Thinking 7.1s`; completion folds under `Thought` or current-session `Thought for Ns`. Restored entries have no duration because Pi does not persist a reliable thinking-end timestamp. Only a session started in active Streaming owns its validated configured `app.thinking.toggle` binding and one-second timer. Ctrl+T expands/refolds native reasoning. Startup resource failures and private-shape/render failures use complete native thinking. A cleanup callback that throws while leaving Streaming is contained: Rail or Tree remains active, while Streaming becomes unavailable for that session. Component and timing tracking are bounded to 256; evicted entries are restored natively first.
409
+
410
+ The exact all-mode private matrix covers Pi 0.80.5, 0.82.1, 0.83.0, 0.84.0, and 0.84.4 under dark, light, and current themes, narrow/wide widths and resize; Pi 0.84.4 also has a fullscreen live-transition PTY smoke. Thinking (Experimental) never owns or writes the Working line, including its unchanged **Thinking time** option, and does not own Footer, Editor, widgets, statuses, or model behavior.
411
+
384
412
  ## Working line
385
413
 
386
414
  When enabled, Zentui owns Pi's complete working-row message and indicator. Five fixed-width spinner presets are available: Braille Orbit, Star Bloom, ASCII Pinwheel, Claude-inspired, and three-cell Pulse.
@@ -389,7 +417,7 @@ When enabled, Zentui owns Pi's complete working-row message and indicator. Five
389
417
 
390
418
  Committed totals stay provider-reported across tool loops, retries, compaction retries, and queued continuations. During a response, live output follows Pi's `↓N` convention whether usage is provider-reported or temporarily estimated. Final usage reconciles atomically; input is never estimated. Labels are sanitized and width-bounded.
391
419
 
392
- When Pi settles, the default-on **Turn summary** appends a persistent context-free row such as `Turn took 56s · thought for 10s · ↑7.1k ↓779`. Thought is cumulative wall-clock time from Pi's public thinking stream; overlaps count once and zero is omitted. Output already includes reasoning tokens, so reasoning is not added separately. Summaries always include both token totals, even when live Tokens or Thinking is hidden or zero, and can be disabled without changing historical rows. They use the fixed high style and are inactive while Working line is disabled.
420
+ When Pi settles, the default-on **Turn summary** appends a persistent context-free row such as `Turn took 56s · thought for 10s · ↑7.1k ↓779`. Thought is cumulative wall-clock time from Pi's public thinking stream; overlaps count once and zero is omitted. Output already includes reasoning tokens, so reasoning is not added separately. Summaries always include both token totals, even when live Tokens or **Thinking time** is hidden or zero, and can be disabled without changing historical rows. They use the fixed high style and are inactive while Working line is disabled.
393
421
 
394
422
  Classic and KITT move color across message and segments. **Animate spinner color** optionally includes spinner cells and separator. Static colors the full row uniformly and ignores text speed/spinner-color participation without changing saved values. Spinner glyph motion always remains active.
395
423
 
@@ -46,6 +46,7 @@ export type WorkingLineSpinner =
46
46
  | "claude-inspired"
47
47
  | "pulse";
48
48
  export type WorkingLineTextAnimation = "classic" | "kitt" | "disabled";
49
+ export type ThinkingStepsMode = "rail" | "tree" | "streaming";
49
50
  export type ComponentStyleOwner = "editor" | "userMessages" | "selectorBorders" | "footer";
50
51
  export type MinimalistPathDisplayMode = "compact" | "project" | "full";
51
52
  export type MinimalistContextFormat = "percent" | "percent-total";
@@ -250,9 +251,15 @@ export type WorkingLineComponentPatch = Partial<
250
251
  segments?: Partial<WorkingLineSegmentsConfig>;
251
252
  };
252
253
 
254
+ export type ThinkingStepsComponentConfig = {
255
+ enabled: boolean;
256
+ mode: ThinkingStepsMode;
257
+ };
258
+
253
259
  export type ComponentsConfig = {
254
260
  editor: EditorComponentConfig;
255
261
  userMessages: UserMessagesComponentConfig;
262
+ thinkingSteps: ThinkingStepsComponentConfig;
256
263
  workingLine: WorkingLineComponentConfig;
257
264
  selectorBorders: SelectorBordersComponentConfig;
258
265
  footer: FooterComponentConfig;
@@ -498,6 +505,7 @@ const defaultComponents: ComponentsConfig = {
498
505
  colorSource: "theme",
499
506
  styles: { framed: {}, "framed-copy-friendly": {}, compact: {}, labeled: {} },
500
507
  },
508
+ thinkingSteps: { enabled: false, mode: "tree" },
501
509
  workingLine: {
502
510
  enabled: true,
503
511
  turnSummary: true,
@@ -1194,6 +1202,7 @@ function resolveComponents(config: ConfigRecord): ComponentsConfig {
1194
1202
  const userMessages = recordValue(components.userMessages);
1195
1203
  const userMessageStyles = recordValue(userMessages.styles);
1196
1204
  const framed = recordValue(userMessageStyles.framed);
1205
+ const thinkingSteps = recordValue(components.thinkingSteps);
1197
1206
  const workingLine = recordValue(components.workingLine);
1198
1207
  const workingLineMessages = recordValue(workingLine.messages);
1199
1208
  const workingLineSegments = recordValue(workingLine.segments);
@@ -1301,6 +1310,15 @@ function resolveComponents(config: ConfigRecord): ComponentsConfig {
1301
1310
  labeled: {},
1302
1311
  },
1303
1312
  },
1313
+ thinkingSteps: {
1314
+ enabled: parseBoolean(thinkingSteps.enabled, defaultComponents.thinkingSteps.enabled),
1315
+ mode:
1316
+ thinkingSteps.mode === "rail" || thinkingSteps.mode === "tree"
1317
+ ? thinkingSteps.mode
1318
+ : thinkingSteps.mode === "streaming" || thinkingSteps.mode === "streaming-experimental"
1319
+ ? "streaming"
1320
+ : defaultComponents.thinkingSteps.mode,
1321
+ },
1304
1322
  workingLine: {
1305
1323
  enabled: parseBoolean(workingLine.enabled, defaultComponents.workingLine.enabled),
1306
1324
  turnSummary: parseBoolean(workingLine.turnSummary, defaultComponents.workingLine.turnSummary),
@@ -1746,6 +1764,17 @@ export function saveUserMessagesComponentPatch(
1746
1764
  );
1747
1765
  }
1748
1766
 
1767
+ export function saveThinkingStepsComponentPatch(
1768
+ patch: Partial<ThinkingStepsComponentConfig>,
1769
+ path = configPath,
1770
+ ): PolishedTuiConfig {
1771
+ return saveComponentsMutation((components) => {
1772
+ const component = components.thinkingSteps;
1773
+ if (patch.enabled !== undefined) component.enabled = patch.enabled;
1774
+ if (patch.mode !== undefined) component.mode = patch.mode;
1775
+ }, path);
1776
+ }
1777
+
1749
1778
  export function saveWorkingLineComponentPatch(
1750
1779
  patch: WorkingLineComponentPatch,
1751
1780
  path = configPath,
@@ -14,6 +14,7 @@ import {
14
14
  import {
15
15
  type AccentRailEditorStyleConfig,
16
16
  type ContextStyle,
17
+ defaultConfig,
17
18
  type EditorComponentConfig,
18
19
  type ExtensionStatusColorMode,
19
20
  type ExtensionStatusPlacement,
@@ -46,8 +47,10 @@ import {
46
47
  savePolishedEditorStylePatch,
47
48
  saveSelectorBordersComponentPatch,
48
49
  saveStarshipFooterStylePatch,
50
+ saveThinkingStepsComponentPatch,
49
51
  saveUserMessagesComponentPatch,
50
52
  saveWorkingLineComponentPatch,
53
+ type ThinkingStepsComponentConfig,
51
54
  type UserMessagesComponentConfig,
52
55
  type WorkingLineComponentPatch,
53
56
  type ZentuiConfig,
@@ -86,6 +89,7 @@ import { SessionLifecycle } from "./session-lifecycle";
86
89
  import { registerZentuiSettingsCommand } from "./settings-command";
87
90
  import { createInitialState, type FooterState, modelLabelFor, syncState } from "./state";
88
91
  import { resolveFooterTelemetry } from "./telemetry";
92
+ import { ThinkingExperimentalController } from "./thinking-experimental";
89
93
  import { PolishedEditor, WrappedPolishedEditor } from "./ui";
90
94
  import { installUserMessageStyle, removeUserMessageStyle } from "./user-message";
91
95
  import {
@@ -180,7 +184,7 @@ export default function (pi: ExtensionAPI) {
180
184
  const sessionLifecycle = new SessionLifecycle();
181
185
  const editorOwnerToken = Symbol("zentui-editor-owner");
182
186
 
183
- let currentConfig: PolishedTuiConfig = loadConfig();
187
+ let currentConfig: PolishedTuiConfig = structuredClone(defaultConfig);
184
188
  // Keep the capability guard defensive for hosts with incomplete extension APIs.
185
189
  if (typeof pi.registerEntryRenderer === "function") {
186
190
  pi.registerEntryRenderer(TURN_SUMMARY_ENTRY_TYPE, (entry, options, theme) =>
@@ -278,6 +282,14 @@ export default function (pi: ExtensionAPI) {
278
282
  requestFooterRender?.();
279
283
  requestEditorRender?.();
280
284
  };
285
+ const thinkingExperimental = new ThinkingExperimentalController(
286
+ () => currentConfig.components.thinkingSteps,
287
+ );
288
+ const thinkingStepsCapability = {
289
+ get state() {
290
+ return thinkingExperimental.state;
291
+ },
292
+ };
281
293
  const liveContext = new LiveContextController(sessionLifecycle, refresh);
282
294
  const getActiveTheme = () => activeTheme;
283
295
  const getCurrentConfig = () => currentConfig;
@@ -1028,7 +1040,6 @@ export default function (pi: ExtensionAPI) {
1028
1040
  const staleFooterOwner = ctxFooterOwner(ctx);
1029
1041
  if (typeof staleFooterOwner === "symbol") installedFooterToken = staleFooterOwner;
1030
1042
  ensureConfigExists();
1031
- currentConfig = loadConfig();
1032
1043
  syncFooterState(ctx);
1033
1044
  stopProjectRefresh();
1034
1045
 
@@ -1136,6 +1147,10 @@ export default function (pi: ExtensionAPI) {
1136
1147
 
1137
1148
  pi.on("session_start", async (_event, ctx) => {
1138
1149
  const lifecycleGeneration = sessionLifecycle.start();
1150
+ // Reload synchronously so private ownership uses this session's disk snapshot before
1151
+ // any await or transcript restoration.
1152
+ currentConfig = loadConfig();
1153
+ thinkingExperimental.startSession(ctx);
1139
1154
  const layoutInstallSerial = ++accentRailLayoutPatchInstallSerial;
1140
1155
  cleanupAccentRailLayoutPatch();
1141
1156
  cleanupAccentRailLayoutPatch = () => {};
@@ -1216,6 +1231,14 @@ export default function (pi: ExtensionAPI) {
1216
1231
  if (patch.enabled !== undefined || patch.style !== undefined) reconcileUserMessages();
1217
1232
  refresh();
1218
1233
  },
1234
+ thinkingStepsCapability,
1235
+ setThinkingStepsComponent(
1236
+ patch: Partial<ThinkingStepsComponentConfig>,
1237
+ _ctx: ExtensionContext,
1238
+ ) {
1239
+ currentConfig = saveThinkingStepsComponentPatch(patch);
1240
+ return thinkingExperimental.reconcile();
1241
+ },
1219
1242
  setWorkingLineComponent(patch: WorkingLineComponentPatch, ctx: ExtensionContext) {
1220
1243
  currentConfig = saveWorkingLineComponentPatch(patch);
1221
1244
  return workingLine.reconcile(ctx);
@@ -1303,6 +1326,7 @@ export default function (pi: ExtensionAPI) {
1303
1326
  });
1304
1327
 
1305
1328
  pi.on("session_shutdown", async (_event, ctx) => {
1329
+ thinkingExperimental.shutdown();
1306
1330
  liveContext.clear();
1307
1331
  interactionMetrics.shutdown();
1308
1332
  workingLine.dispose(ctx);
@@ -1314,6 +1338,8 @@ export default function (pi: ExtensionAPI) {
1314
1338
  refreshInteractiveState(ctx, true);
1315
1339
  };
1316
1340
 
1341
+ pi.on("message_start", (event) => thinkingExperimental.beginMessage(event));
1342
+
1317
1343
  pi.on("agent_start", (event, ctx) => {
1318
1344
  liveContext.clear();
1319
1345
  const { interactionStarted } = interactionMetrics.agentStart();
@@ -1326,6 +1352,7 @@ export default function (pi: ExtensionAPI) {
1326
1352
  workingLine.startTurn(ctx);
1327
1353
  });
1328
1354
  pi.on("agent_end", (event, ctx) => {
1355
+ thinkingExperimental.endAgent();
1329
1356
  liveContext.clear();
1330
1357
  const displayTokens = interactionMetrics.currentDisplayTokens();
1331
1358
  interactionMetrics.agentEnd();
@@ -1342,6 +1369,7 @@ export default function (pi: ExtensionAPI) {
1342
1369
  pi.on("thinking_level_select", syncInteractiveState);
1343
1370
  pi.on("session_info_changed", syncInteractiveState);
1344
1371
  pi.on("message_update", (event, ctx) => {
1372
+ thinkingExperimental.updateMessage(event);
1345
1373
  liveContext.update(event.message);
1346
1374
  const metrics = interactionMetrics.messageUpdate(
1347
1375
  event.message,
@@ -1352,6 +1380,7 @@ export default function (pi: ExtensionAPI) {
1352
1380
  }
1353
1381
  });
1354
1382
  pi.on("message_end", (event, ctx) => {
1383
+ thinkingExperimental.endMessage(event);
1355
1384
  const result = interactionMetrics.messageEnd(event.message);
1356
1385
  if (result.status === "accepted") {
1357
1386
  workingLine.updateMetrics(result.displayTokens, interactionMetrics.currentThought(), ctx);
@@ -3,7 +3,8 @@ export const ZENTUI_PROTOTYPE_PATCH_REGISTRY = Symbol.for("pi-zentui.prototype-p
3
3
  type PrototypePatchAdapter =
4
4
  | "user-message-render"
5
5
  | "user-message-invalidate"
6
- | "selector-border-render";
6
+ | "selector-border-render"
7
+ | "thinking-experimental-update-content";
7
8
 
8
9
  type PrototypeMethod = (this: unknown, ...args: unknown[]) => unknown;
9
10
 
@@ -18,10 +19,13 @@ type PatchBehavior = (invocation: PatchInvocation) => unknown;
18
19
  type Registration = {
19
20
  token: symbol;
20
21
  behavior?: PatchBehavior;
22
+ onDisplaced?: () => void;
21
23
  };
22
24
 
25
+ type PatchMethod = "render" | "invalidate" | "updateContent";
26
+
23
27
  type PatchRecord = {
24
- method: "render" | "invalidate";
28
+ method: PatchMethod;
25
29
  predecessor: PrototypeMethod;
26
30
  predecessorDescriptor?: PropertyDescriptor;
27
31
  wrapper: PrototypeMethod;
@@ -48,10 +52,20 @@ function registryFor(target: PatchTarget): PatchRegistry {
48
52
  return registry;
49
53
  }
50
54
 
51
- function deactivateRecord(record: PatchRecord): void {
55
+ function deactivateRecord(record: PatchRecord, displaced = false): void {
52
56
  if (!record.registration) return;
53
- record.registration.behavior = undefined;
57
+ const registration = record.registration;
58
+ const onDisplaced = registration.onDisplaced;
59
+ registration.behavior = undefined;
60
+ registration.onDisplaced = undefined;
54
61
  record.registration = undefined;
62
+ if (displaced) {
63
+ try {
64
+ onDisplaced?.();
65
+ } catch {
66
+ // Registration handoff must not prevent the newer owner from installing.
67
+ }
68
+ }
55
69
  }
56
70
 
57
71
  function restorePredecessor(target: PatchTarget, record: PatchRecord): void {
@@ -77,15 +91,17 @@ function installWrapper(target: PatchTarget, record: PatchRecord): void {
77
91
  });
78
92
  }
79
93
 
94
+ export type PrototypePatchRegistration = (() => void) & Readonly<{ token: symbol }>;
95
+
80
96
  function createCleanup(
81
97
  target: PatchTarget,
82
98
  adapter: PrototypePatchAdapter,
83
99
  registry: PatchRegistry,
84
100
  record: PatchRecord,
85
101
  token: symbol,
86
- ): () => void {
102
+ ): PrototypePatchRegistration {
87
103
  let cleaned = false;
88
- return () => {
104
+ const cleanup = () => {
89
105
  if (cleaned) return;
90
106
  cleaned = true;
91
107
  if (record.registration?.token !== token) return;
@@ -97,14 +113,16 @@ function createCleanup(
97
113
  registry.delete(adapter);
98
114
  if (registry.size === 0) delete target[ZENTUI_PROTOTYPE_PATCH_REGISTRY];
99
115
  };
116
+ return Object.assign(cleanup, { token });
100
117
  }
101
118
 
102
119
  export function installPrototypePatch(
103
120
  targetValue: object,
104
- method: "render" | "invalidate",
121
+ method: PatchMethod,
105
122
  adapter: PrototypePatchAdapter,
106
123
  behavior: PatchBehavior,
107
- ): () => void {
124
+ onDisplaced?: () => void,
125
+ ): PrototypePatchRegistration {
108
126
  const target = targetValue as PatchTarget;
109
127
  const registry = registryFor(target);
110
128
  let record = registry.get(adapter);
@@ -141,17 +159,43 @@ export function installPrototypePatch(
141
159
  }
142
160
  record = nextRecord;
143
161
  registry.set(adapter, record);
144
- if (displacedRecord && displacedRecord !== record) deactivateRecord(displacedRecord);
162
+ if (displacedRecord && displacedRecord !== record) deactivateRecord(displacedRecord, true);
145
163
  }
146
164
 
165
+ const previousRegistration = record.registration;
147
166
  const token = Symbol(adapter);
148
- record.registration = { token, behavior };
167
+ record.registration = { token, behavior, onDisplaced };
168
+ if (previousRegistration) {
169
+ previousRegistration.behavior = undefined;
170
+ try {
171
+ previousRegistration.onDisplaced?.();
172
+ } catch {
173
+ // Registration handoff must not prevent the newer owner from installing.
174
+ }
175
+ previousRegistration.onDisplaced = undefined;
176
+ }
149
177
  return createCleanup(target, adapter, registry, record, token);
150
178
  }
151
179
 
180
+ export function isPrototypePatchCurrent(
181
+ targetValue: object,
182
+ method: PatchMethod,
183
+ adapter: PrototypePatchAdapter,
184
+ token?: symbol,
185
+ ): boolean {
186
+ const target = targetValue as PatchTarget;
187
+ const record = existingRegistry(target)?.get(adapter);
188
+ return Boolean(
189
+ record &&
190
+ record.method === method &&
191
+ target[method] === record.wrapper &&
192
+ (token === undefined || record.registration?.token === token),
193
+ );
194
+ }
195
+
152
196
  export function removePrototypePatch(
153
197
  targetValue: object,
154
- method: "render" | "invalidate",
198
+ method: PatchMethod,
155
199
  adapter: PrototypePatchAdapter,
156
200
  ): void {
157
201
  const target = targetValue as PatchTarget;
@@ -45,6 +45,8 @@ import {
45
45
  type PolishedTuiConfig,
46
46
  type SelectorBordersComponentConfig,
47
47
  type SeparatorStyle,
48
+ type ThinkingStepsComponentConfig,
49
+ type ThinkingStepsMode,
48
50
  type UserMessageStyle,
49
51
  type UserMessagesComponentConfig,
50
52
  type WorkingLineComponentPatch,
@@ -56,10 +58,12 @@ import { isIconMode } from "./icons";
56
58
  import type { SessionLifecycle } from "./session-lifecycle";
57
59
  import {
58
60
  renderEditorSettingsPreview,
61
+ renderThinkingStepsSettingsPreview,
59
62
  renderUserMessageSettingsPreview,
60
63
  SETTINGS_PREVIEW_MAX_WIDTH,
61
64
  } from "./settings-previews";
62
65
  import { EDITOR_BORDER_STYLE, renderChromeBorder, safeThemeFg } from "./style";
66
+ import { formatThinkingStatus, thinkingStatusLabels } from "./thinking-status";
63
67
  import {
64
68
  buildWorkingLinePreviewFrames,
65
69
  normalizeWorkingLineMessages,
@@ -132,11 +136,22 @@ const speedValues = (presets: readonly { label: string }[]) => [
132
136
  "Custom…",
133
137
  ];
134
138
  const workingLineTextAnimationValues: WorkingLineTextAnimation[] = ["classic", "kitt", "disabled"];
139
+ const thinkingStepsModeLabels: Record<ThinkingStepsMode, string> = {
140
+ rail: "Rail",
141
+ tree: "Tree",
142
+ streaming: "Streaming",
143
+ };
144
+ const thinkingStepsModeValues = (startupMode: ThinkingStepsMode): string[] => {
145
+ if (startupMode === "streaming") return ["Streaming", "Tree", "Rail"];
146
+ if (startupMode === "rail") return ["Rail", "Tree", "Streaming"];
147
+ return ["Tree", "Rail", "Streaming"];
148
+ };
135
149
 
136
150
  const settingsSections = [
137
151
  "appearance",
138
152
  "editor",
139
153
  "userMessages",
154
+ "thinkingSteps",
140
155
  "workingLine",
141
156
  "footer",
142
157
  "segments",
@@ -164,6 +179,37 @@ type SettingsOutcome =
164
179
  | "edit-working-line-spinner-speed"
165
180
  | "edit-working-line-text-speed";
166
181
 
182
+ type ThinkingControllerState = Readonly<{
183
+ available: boolean;
184
+ rendererAvailable?: boolean;
185
+ streamingAvailable?: boolean;
186
+ streamingPoisoned?: boolean;
187
+ active: boolean;
188
+ activeMode?: ThinkingStepsMode;
189
+ startup: Readonly<ThinkingStepsComponentConfig>;
190
+ displaced: boolean;
191
+ restartRequired: boolean;
192
+ reason?: string;
193
+ }>;
194
+
195
+ type ThinkingStepsSettingsCapability =
196
+ | Readonly<{ available: boolean }>
197
+ | Readonly<{ readonly state: ThinkingControllerState }>;
198
+
199
+ function experimentalThinkingCapability(
200
+ capability: ThinkingStepsSettingsCapability,
201
+ ): ThinkingControllerState {
202
+ return "state" in capability
203
+ ? capability.state
204
+ : {
205
+ available: capability.available,
206
+ active: false,
207
+ startup: { enabled: false, mode: "tree" },
208
+ displaced: false,
209
+ restartRequired: false,
210
+ };
211
+ }
212
+
167
213
  type SettingsCommandDeps = {
168
214
  sessionLifecycle: SessionLifecycle;
169
215
  getConfig: () => PolishedTuiConfig;
@@ -176,6 +222,11 @@ type SettingsCommandDeps = {
176
222
  setAccentRail: (patch: Partial<AccentRailEditorStyleConfig>, ctx: ExtensionContext) => void;
177
223
  setMinimalist: (patch: Partial<MinimalistConfig>, ctx: ExtensionContext) => void;
178
224
  setUserMessagesComponent: (patch: UserMessagesPatch, ctx: ExtensionContext) => void;
225
+ thinkingStepsCapability: ThinkingStepsSettingsCapability;
226
+ setThinkingStepsComponent: (
227
+ patch: Partial<ThinkingStepsComponentConfig>,
228
+ ctx: ExtensionContext,
229
+ ) => ApplyResult;
179
230
  setWorkingLineComponent: (patch: WorkingLineComponentPatch, ctx: ExtensionContext) => ApplyResult;
180
231
  setSelectorBordersComponent: (
181
232
  patch: Partial<SelectorBordersComponentConfig>,
@@ -210,6 +261,7 @@ const sectionLabels: Record<SettingsSection, string> = {
210
261
  appearance: "Appearance",
211
262
  editor: "Editor",
212
263
  userMessages: "User messages",
264
+ thinkingSteps: "Thinking (Experimental)",
213
265
  workingLine: "Working line",
214
266
  footer: "Footer",
215
267
  segments: "Segments",
@@ -308,6 +360,11 @@ function userMessageStyleId(label: string): UserMessageStyle | undefined {
308
360
  ([, value]) => value === label,
309
361
  )?.[0];
310
362
  }
363
+ function thinkingStepsModeId(label: string): ThinkingStepsMode | undefined {
364
+ return (Object.entries(thinkingStepsModeLabels) as Array<[ThinkingStepsMode, string]>).find(
365
+ ([, value]) => value === label,
366
+ )?.[0];
367
+ }
311
368
  function workingLineSpinnerId(label: string): WorkingLineSpinner | undefined {
312
369
  return (Object.entries(workingLineSpinnerLabels) as Array<[WorkingLineSpinner, string]>).find(
313
370
  ([, value]) => value === label,
@@ -610,6 +667,32 @@ function buildUserMessagesItems(config: PolishedTuiConfig): SettingItem[] {
610
667
  },
611
668
  ];
612
669
  }
670
+ function buildThinkingStepsItems(
671
+ config: PolishedTuiConfig,
672
+ capability: ThinkingStepsSettingsCapability,
673
+ ): SettingItem[] {
674
+ const thinkingSteps = config.components.thinkingSteps;
675
+ const controller = experimentalThinkingCapability(capability);
676
+ const status = formatThinkingStatus(thinkingStatusLabels(thinkingSteps, controller));
677
+ const enabledDescription = `${status} Live switching supports Streaming → Rail/Tree and Rail ↔ Tree. Entering Streaming, first enable, and re-enable after live disable require restart. Private renderer may break after Pi updates.`;
678
+ return [
679
+ {
680
+ id: "thinkingStepsEnabled",
681
+ label: "Enabled",
682
+ description: enabledDescription,
683
+ currentValue: featureValue(thinkingSteps.enabled),
684
+ values: featureStateValues,
685
+ },
686
+ {
687
+ id: "thinkingStepsMode",
688
+ label: "Mode",
689
+ description: `${status} Rail shows every parsed label; Tree shows the latest five per contiguous run; both follow Pi thinking visibility, and unsafe or unstructured content remains native. Streaming folds to the latest five host-rendered rows and owns the configured thinking toggle only when active. Incompatibility uses native thinking.`,
690
+ currentValue: thinkingStepsModeLabels[thinkingSteps.mode],
691
+ values: thinkingStepsModeValues(controller.startup.mode),
692
+ },
693
+ ];
694
+ }
695
+
613
696
  function buildWorkingLineItems(config: PolishedTuiConfig): SettingItem[] {
614
697
  const workingLine = config.components.workingLine;
615
698
  const staticText = workingLine.textAnimation === "disabled";
@@ -700,7 +783,7 @@ function buildWorkingLineItems(config: PolishedTuiConfig): SettingItem[] {
700
783
  },
701
784
  {
702
785
  id: "workingLineThought",
703
- label: "Thinking",
786
+ label: "Thinking time",
704
787
  description: "Show cumulative wall-clock thinking time and active updates.",
705
788
  currentValue: featureValue(workingLine.segments.thought),
706
789
  values: featureStateValues,
@@ -957,6 +1040,7 @@ function buildSectionItems(
957
1040
  section: SettingsSection,
958
1041
  config: PolishedTuiConfig,
959
1042
  active: ReadonlyMap<string, string>,
1043
+ thinkingStepsCapability: ThinkingStepsSettingsCapability,
960
1044
  ): SettingItem[] {
961
1045
  switch (section) {
962
1046
  case "appearance":
@@ -977,6 +1061,8 @@ function buildSectionItems(
977
1061
  ];
978
1062
  case "userMessages":
979
1063
  return buildUserMessagesItems(config);
1064
+ case "thinkingSteps":
1065
+ return buildThinkingStepsItems(config, thinkingStepsCapability);
980
1066
  case "workingLine":
981
1067
  return buildWorkingLineItems(config);
982
1068
  case "footer":
@@ -1195,9 +1281,10 @@ export function registerZentuiSettingsCommand(pi: ExtensionAPI, deps: SettingsCo
1195
1281
  };
1196
1282
  const notifyChange = (label: string, value: string, result?: ApplyResult) => {
1197
1283
  deps.requestRender();
1284
+ const detail = result?.reason?.trim();
1198
1285
  ctx.ui.notify(
1199
- `${label}: ${value}${result && !result.applied ? ` (${result.reason ?? "reload Pi to apply this change"})` : ""}`,
1200
- "info",
1286
+ `${label}: ${value}${detail ? ` (${detail})` : result && !result.applied ? " (reload Pi to apply this change)" : ""}`,
1287
+ result && (!result.applied || detail) ? "warning" : "info",
1201
1288
  );
1202
1289
  startPreview();
1203
1290
  tui.requestRender();
@@ -1220,6 +1307,7 @@ export function registerZentuiSettingsCommand(pi: ExtensionAPI, deps: SettingsCo
1220
1307
  activeSection,
1221
1308
  deps.getConfig(),
1222
1309
  deps.getActiveExtensionStatuses(),
1310
+ deps.thinkingStepsCapability,
1223
1311
  );
1224
1312
  const list = new SettingsList(
1225
1313
  items,
@@ -1362,6 +1450,23 @@ export function registerZentuiSettingsCommand(pi: ExtensionAPI, deps: SettingsCo
1362
1450
  notifyChange("Message colors", newValue);
1363
1451
  return;
1364
1452
  }
1453
+ if (id === "thinkingStepsEnabled" && enabled !== undefined) {
1454
+ const result = deps.setThinkingStepsComponent({ enabled }, ctx);
1455
+ settingsList = makeSettingsList("thinkingStepsEnabled");
1456
+ notifyChange("Thinking (Experimental)", newValue, result);
1457
+ return;
1458
+ }
1459
+ const selectedThinkingStepsMode =
1460
+ id === "thinkingStepsMode" ? thinkingStepsModeId(newValue) : undefined;
1461
+ if (selectedThinkingStepsMode) {
1462
+ const result = deps.setThinkingStepsComponent(
1463
+ { mode: selectedThinkingStepsMode },
1464
+ ctx,
1465
+ );
1466
+ settingsList = makeSettingsList("thinkingStepsMode");
1467
+ notifyChange("Thinking (Experimental)", newValue, result);
1468
+ return;
1469
+ }
1365
1470
  if (id === "workingLineEnabled" && enabled !== undefined) {
1366
1471
  const result = deps.setWorkingLineComponent({ enabled }, ctx);
1367
1472
  settingsList.updateValue(id, newValue);
@@ -1661,6 +1766,13 @@ export function registerZentuiSettingsCommand(pi: ExtensionAPI, deps: SettingsCo
1661
1766
  return renderEditorSettingsPreview(deps.getConfig(), theme, previewWidth);
1662
1767
  if (activeSection === "userMessages")
1663
1768
  return renderUserMessageSettingsPreview(deps.getConfig(), theme, previewWidth);
1769
+ if (activeSection === "thinkingSteps")
1770
+ return renderThinkingStepsSettingsPreview(
1771
+ deps.getConfig(),
1772
+ theme,
1773
+ previewWidth,
1774
+ deps.thinkingStepsCapability,
1775
+ );
1664
1776
  if (activeSection === "workingLine" && preview && preview.frames.length > 0)
1665
1777
  return [
1666
1778
  truncateToWidth(