gsd-pi 2.73.0 → 2.73.1-dev.088d28f

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 (284) hide show
  1. package/dist/cli.js +0 -47
  2. package/dist/help-text.js +1 -1
  3. package/dist/resource-loader.js +2 -2
  4. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +9 -3
  5. package/dist/resources/extensions/gsd/auto/phases.js +15 -9
  6. package/dist/resources/extensions/gsd/auto-dispatch.js +16 -6
  7. package/dist/resources/extensions/gsd/auto-model-selection.js +54 -11
  8. package/dist/resources/extensions/gsd/auto-prompts.js +9 -6
  9. package/dist/resources/extensions/gsd/auto-start.js +23 -6
  10. package/dist/resources/extensions/gsd/auto.js +13 -1
  11. package/dist/resources/extensions/gsd/bootstrap/crash-log.js +31 -0
  12. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +18 -7
  13. package/dist/resources/extensions/gsd/bootstrap/system-context.js +6 -1
  14. package/dist/resources/extensions/gsd/commands-handlers.js +8 -2
  15. package/dist/resources/extensions/gsd/crash-recovery.js +51 -0
  16. package/dist/resources/extensions/gsd/docs/preferences-reference.md +1 -1
  17. package/dist/resources/extensions/gsd/gsd-db.js +36 -2
  18. package/dist/resources/extensions/gsd/milestone-actions.js +19 -1
  19. package/dist/resources/extensions/gsd/notification-widget.js +2 -2
  20. package/dist/resources/extensions/gsd/preferences-models.js +43 -0
  21. package/dist/resources/extensions/gsd/preferences-types.js +1 -0
  22. package/dist/resources/extensions/gsd/preferences-validation.js +22 -0
  23. package/dist/resources/extensions/gsd/state.js +36 -0
  24. package/dist/startup-model-validation.js +8 -5
  25. package/dist/update-check.d.ts +1 -0
  26. package/dist/update-check.js +13 -5
  27. package/dist/update-cmd.js +4 -3
  28. package/dist/web/standalone/.next/BUILD_ID +1 -1
  29. package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
  30. package/dist/web/standalone/.next/build-manifest.json +3 -3
  31. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  32. package/dist/web/standalone/.next/required-server-files.json +3 -3
  33. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  34. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  35. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  36. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  44. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  45. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  46. package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
  47. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  48. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  50. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  51. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  53. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  54. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  55. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  56. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  57. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  58. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  59. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  60. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  61. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  62. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  63. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  64. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  65. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  66. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  67. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  68. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  69. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  70. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  71. package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
  72. package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
  73. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  74. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  75. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  76. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  77. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  78. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  79. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  80. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  81. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  82. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  83. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  84. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  85. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  86. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  87. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  88. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  89. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  90. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  91. package/dist/web/standalone/.next/server/app/api/notifications/route.js +2 -2
  92. package/dist/web/standalone/.next/server/app/api/notifications/route_client-reference-manifest.js +1 -1
  93. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  94. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  95. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  96. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  97. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  98. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  99. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  100. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  101. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
  102. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  103. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  104. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  105. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  106. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  107. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  108. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  109. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  111. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  113. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  115. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  121. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
  122. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  123. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  124. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  125. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
  126. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  127. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
  128. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  129. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  130. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  131. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  132. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  133. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  134. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  135. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  136. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  137. package/dist/web/standalone/.next/server/app/index.html +1 -1
  138. package/dist/web/standalone/.next/server/app/index.rsc +4 -4
  139. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  140. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  141. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  142. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  143. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  144. package/dist/web/standalone/.next/server/app/page.js +2 -2
  145. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  146. package/dist/web/standalone/.next/server/app-paths-manifest.json +12 -12
  147. package/dist/web/standalone/.next/server/chunks/63.js +3 -3
  148. package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
  149. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  150. package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
  151. package/dist/web/standalone/.next/server/middleware.js +2 -2
  152. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  153. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  154. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  155. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  156. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  157. package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
  158. package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
  159. package/dist/web/standalone/.next/static/chunks/app/page-f1e30ab6bb269149.js +1 -0
  160. package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
  161. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
  162. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  163. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  164. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  165. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  166. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  167. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  168. package/dist/web/standalone/server.js +1 -1
  169. package/package.json +1 -1
  170. package/packages/pi-ai/dist/index.d.ts +1 -0
  171. package/packages/pi-ai/dist/index.d.ts.map +1 -1
  172. package/packages/pi-ai/dist/index.js +1 -0
  173. package/packages/pi-ai/dist/index.js.map +1 -1
  174. package/packages/pi-ai/dist/utils/overflow.d.ts.map +1 -1
  175. package/packages/pi-ai/dist/utils/overflow.js +12 -0
  176. package/packages/pi-ai/dist/utils/overflow.js.map +1 -1
  177. package/packages/pi-ai/dist/utils/tests/overflow.test.d.ts +2 -0
  178. package/packages/pi-ai/dist/utils/tests/overflow.test.d.ts.map +1 -0
  179. package/packages/pi-ai/dist/utils/tests/overflow.test.js +50 -0
  180. package/packages/pi-ai/dist/utils/tests/overflow.test.js.map +1 -0
  181. package/packages/pi-ai/src/index.ts +4 -0
  182. package/packages/pi-ai/src/utils/overflow.ts +14 -1
  183. package/packages/pi-ai/src/utils/tests/overflow.test.ts +58 -0
  184. package/packages/pi-coding-agent/dist/core/auth-storage.js +1 -1
  185. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  186. package/packages/pi-coding-agent/dist/core/auth-storage.test.js +27 -0
  187. package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
  188. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +313 -8
  189. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
  190. package/packages/pi-coding-agent/dist/core/compaction/utils.js +5 -5
  191. package/packages/pi-coding-agent/dist/core/compaction/utils.js.map +1 -1
  192. package/packages/pi-coding-agent/dist/core/compaction-utils.test.d.ts +2 -0
  193. package/packages/pi-coding-agent/dist/core/compaction-utils.test.d.ts.map +1 -0
  194. package/packages/pi-coding-agent/dist/core/compaction-utils.test.js +45 -0
  195. package/packages/pi-coding-agent/dist/core/compaction-utils.test.js.map +1 -0
  196. package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
  197. package/packages/pi-coding-agent/dist/core/model-resolver.js +25 -68
  198. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  199. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts +12 -2
  200. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  201. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js +51 -26
  202. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
  203. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.d.ts +2 -1
  204. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -1
  205. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.js +9 -3
  206. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.js.map +1 -1
  207. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.d.ts +2 -0
  208. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.d.ts.map +1 -0
  209. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.js +52 -0
  210. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.js.map +1 -0
  211. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts +1 -0
  212. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  213. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +116 -25
  214. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  215. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.d.ts +2 -0
  216. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.d.ts.map +1 -0
  217. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js +63 -0
  218. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js.map +1 -0
  219. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  220. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +11 -3
  221. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  222. package/packages/pi-coding-agent/package.json +1 -1
  223. package/packages/pi-coding-agent/src/core/auth-storage.test.ts +38 -0
  224. package/packages/pi-coding-agent/src/core/auth-storage.ts +1 -1
  225. package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +355 -8
  226. package/packages/pi-coding-agent/src/core/compaction/utils.ts +5 -5
  227. package/packages/pi-coding-agent/src/core/compaction-utils.test.ts +50 -0
  228. package/packages/pi-coding-agent/src/core/model-resolver.ts +26 -70
  229. package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +62 -26
  230. package/packages/pi-coding-agent/src/modes/interactive/components/dynamic-border.test.ts +73 -0
  231. package/packages/pi-coding-agent/src/modes/interactive/components/dynamic-border.ts +9 -3
  232. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.test.ts +71 -0
  233. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +136 -30
  234. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +11 -3
  235. package/packages/pi-tui/dist/__tests__/tui.test.js +60 -1
  236. package/packages/pi-tui/dist/__tests__/tui.test.js.map +1 -1
  237. package/packages/pi-tui/dist/tui.d.ts +8 -0
  238. package/packages/pi-tui/dist/tui.d.ts.map +1 -1
  239. package/packages/pi-tui/dist/tui.js +32 -3
  240. package/packages/pi-tui/dist/tui.js.map +1 -1
  241. package/packages/pi-tui/src/__tests__/tui.test.ts +76 -1
  242. package/packages/pi-tui/src/tui.ts +31 -3
  243. package/pkg/package.json +1 -1
  244. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +12 -4
  245. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +23 -2
  246. package/src/resources/extensions/gsd/auto/phases.ts +22 -9
  247. package/src/resources/extensions/gsd/auto-dispatch.ts +15 -4
  248. package/src/resources/extensions/gsd/auto-model-selection.ts +85 -11
  249. package/src/resources/extensions/gsd/auto-prompts.ts +9 -3
  250. package/src/resources/extensions/gsd/auto-start.ts +30 -6
  251. package/src/resources/extensions/gsd/auto.ts +10 -0
  252. package/src/resources/extensions/gsd/bootstrap/crash-log.ts +32 -0
  253. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +19 -7
  254. package/src/resources/extensions/gsd/bootstrap/system-context.ts +8 -1
  255. package/src/resources/extensions/gsd/commands-handlers.ts +8 -2
  256. package/src/resources/extensions/gsd/crash-recovery.ts +59 -0
  257. package/src/resources/extensions/gsd/docs/preferences-reference.md +1 -1
  258. package/src/resources/extensions/gsd/gsd-db.ts +52 -2
  259. package/src/resources/extensions/gsd/milestone-actions.ts +19 -1
  260. package/src/resources/extensions/gsd/notification-widget.ts +2 -2
  261. package/src/resources/extensions/gsd/preferences-models.ts +41 -0
  262. package/src/resources/extensions/gsd/preferences-types.ts +12 -0
  263. package/src/resources/extensions/gsd/preferences-validation.ts +23 -0
  264. package/src/resources/extensions/gsd/state.ts +46 -0
  265. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +2 -2
  266. package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +51 -2
  267. package/src/resources/extensions/gsd/tests/crash-handler-secondary.test.ts +235 -0
  268. package/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts +3 -2
  269. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +3 -2
  270. package/src/resources/extensions/gsd/tests/derive-state.test.ts +3 -3
  271. package/src/resources/extensions/gsd/tests/flat-rate-routing-guard.test.ts +137 -1
  272. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +59 -1
  273. package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +4 -2
  274. package/src/resources/extensions/gsd/tests/model-isolation.test.ts +91 -2
  275. package/src/resources/extensions/gsd/tests/park-milestone.test.ts +64 -0
  276. package/src/resources/extensions/gsd/tests/preferences.test.ts +47 -0
  277. package/src/resources/extensions/gsd/tests/state-machine-full-walkthrough.test.ts +5 -7
  278. package/src/resources/extensions/gsd/tests/subagent-model-dispatch.test.ts +267 -0
  279. package/src/resources/extensions/gsd/tests/token-profile.test.ts +1 -1
  280. package/dist/web/standalone/.next/static/chunks/app/page-7115e62689b5fd84.js +0 -1
  281. package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
  282. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
  283. /package/dist/web/standalone/.next/static/{KSZ2dcC3p4z6lOmUpPpzr → nwYTvJZ1-hZIfw98d9Wfg}/_buildManifest.js +0 -0
  284. /package/dist/web/standalone/.next/static/{KSZ2dcC3p4z6lOmUpPpzr → nwYTvJZ1-hZIfw98d9Wfg}/_ssgManifest.js +0 -0
@@ -10,6 +10,17 @@ import { appKey } from "../components/keybinding-hints.js";
10
10
  // Tracks the last processed content index to avoid re-scanning all blocks on every message_update
11
11
  let lastProcessedContentIndex = 0;
12
12
 
13
+ // Tracks the previous content[] length so we can detect when an adapter resets
14
+ // the assistant content array for a new provider sub-turn within one lifecycle.
15
+ let lastContentLength = 0;
16
+
17
+ // --- Segment walker state (per streaming assistant turn) ---
18
+ type RenderedSegment =
19
+ | { kind: "text-run"; startIndex: number; endIndex: number; component: AssistantMessageComponent }
20
+ | { kind: "tool"; contentIndex: number; component: ToolExecutionComponent };
21
+
22
+ let renderedSegments: RenderedSegment[] = [];
23
+
13
24
  function hasVisibleAssistantContent(message: { content: Array<any> }): boolean {
14
25
  return message.content.some(
15
26
  (c) =>
@@ -22,6 +33,28 @@ function hasAssistantToolBlocks(message: { content: Array<any> }): boolean {
22
33
  return message.content.some((c) => c.type === "toolCall" || c.type === "serverToolUse");
23
34
  }
24
35
 
36
+ // Pick the latest non-empty text block that appears strictly before the most
37
+ // recent tool call. Text blocks that come after the last tool call are still
38
+ // streaming live into the chat container, so mirroring them into the pinned
39
+ // "Latest Output" zone would render the same tokens twice.
40
+ export function findLatestPinnableText(contentBlocks: Array<any>): string {
41
+ let lastToolIdx = -1;
42
+ for (let i = contentBlocks.length - 1; i >= 0; i--) {
43
+ const c = contentBlocks[i];
44
+ if (c?.type === "toolCall" || c?.type === "serverToolUse") {
45
+ lastToolIdx = i;
46
+ break;
47
+ }
48
+ }
49
+ for (let i = lastToolIdx - 1; i >= 0; i--) {
50
+ const c = contentBlocks[i];
51
+ if (c?.type === "text" && typeof c.text === "string" && c.text.trim()) {
52
+ return c.text.trim();
53
+ }
54
+ }
55
+ return "";
56
+ }
57
+
25
58
  // Tracks the latest assistant text for the pinned message zone
26
59
  let lastPinnedText = "";
27
60
  // Whether any tool execution has been added in this assistant turn (triggers pinned display)
@@ -56,8 +89,10 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
56
89
  // Reset content index tracker and pinned state when a new assistant message starts
57
90
  if (event.type === "message_start" && event.message.role === "assistant") {
58
91
  lastProcessedContentIndex = 0;
92
+ lastContentLength = 0;
59
93
  lastPinnedText = "";
60
94
  hasToolsInTurn = false;
95
+ renderedSegments = [];
61
96
  if (pinnedBorder) pinnedBorder.stopSpinner();
62
97
  pinnedBorder = undefined;
63
98
  pinnedTextComponent = undefined;
@@ -77,6 +112,8 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
77
112
  host.pinnedMessageContainer.clear();
78
113
  lastPinnedText = "";
79
114
  hasToolsInTurn = false;
115
+ renderedSegments = [];
116
+ lastContentLength = 0;
80
117
  if (pinnedBorder) pinnedBorder.stopSpinner();
81
118
  pinnedBorder = undefined;
82
119
  pinnedTextComponent = undefined;
@@ -180,12 +217,22 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
180
217
  }
181
218
 
182
219
  const contentBlocks = host.streamingMessage.content;
183
- // Some adapters reuse a single assistant lifecycle while internally
184
- // spanning multiple provider turns. When a new turn starts, content
185
- // length can shrink back to 0/1; reset scan index to avoid skipping.
186
- if (lastProcessedContentIndex >= contentBlocks.length) {
220
+ // Some adapters (notably claude-code) reuse a single assistant
221
+ // lifecycle while internally spanning multiple provider sub-turns.
222
+ // When a new sub-turn starts, content[] length shrinks back to 0/1.
223
+ // The scan loop needs its index reset, AND the segment walker's
224
+ // renderedSegments map must be cleared so existing text-run
225
+ // components don't get overwritten in place with new sub-turn
226
+ // content (#4144 regression). Prior sub-turn children stay in
227
+ // chatContainer as frozen history; new segments append after them.
228
+ if (contentBlocks.length < lastContentLength) {
229
+ renderedSegments = [];
230
+ lastPinnedText = "";
231
+ lastProcessedContentIndex = 0;
232
+ } else if (lastProcessedContentIndex >= contentBlocks.length) {
187
233
  lastProcessedContentIndex = 0;
188
234
  }
235
+ lastContentLength = contentBlocks.length;
189
236
  for (let i = lastProcessedContentIndex; i < contentBlocks.length; i++) {
190
237
  const content = contentBlocks[i];
191
238
  if (content.type === "toolCall") {
@@ -251,24 +298,88 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
251
298
  }
252
299
  }
253
300
 
254
- // Render assistant text/thinking after tool components so mixed
255
- // streams keep chronological ordering in the chat container.
256
- const hasToolBlocks = hasAssistantToolBlocks(host.streamingMessage);
257
- if (!host.streamingComponent && hasVisibleAssistantContent(host.streamingMessage)) {
258
- host.streamingComponent = new AssistantMessageComponent(
259
- undefined,
260
- host.hideThinkingBlock,
261
- host.getMarkdownThemeWithSettings(),
262
- host.settingsManager.getTimestampFormat(),
263
- );
264
- host.chatContainer.addChild(host.streamingComponent);
265
- }
266
- if (host.streamingComponent) {
267
- if (hasToolBlocks) {
268
- host.chatContainer.removeChild(host.streamingComponent);
269
- host.chatContainer.addChild(host.streamingComponent);
301
+ // Segment walker: render content blocks in stream order, append-only.
302
+ // Build desired segment plan from content[].
303
+ {
304
+ const blocks = host.streamingMessage.content;
305
+ type DesiredSegment =
306
+ | { kind: "text-run"; startIndex: number; endIndex: number }
307
+ | { kind: "tool"; contentIndex: number; toolId: string };
308
+ const desired: DesiredSegment[] = [];
309
+ let runStart = -1;
310
+ for (let i = 0; i < blocks.length; i++) {
311
+ const b = blocks[i];
312
+ const isText = b.type === "text" || b.type === "thinking";
313
+ const isTool = b.type === "toolCall" || b.type === "serverToolUse";
314
+ if (isText) {
315
+ if (runStart === -1) runStart = i;
316
+ } else {
317
+ if (runStart !== -1) {
318
+ desired.push({ kind: "text-run", startIndex: runStart, endIndex: i - 1 });
319
+ runStart = -1;
320
+ }
321
+ if (isTool) {
322
+ desired.push({ kind: "tool", contentIndex: i, toolId: b.id });
323
+ }
324
+ }
325
+ }
326
+ if (runStart !== -1) {
327
+ desired.push({ kind: "text-run", startIndex: runStart, endIndex: blocks.length - 1 });
328
+ }
329
+
330
+ // Append any newly needed segments (never reorder existing ones).
331
+ for (const seg of desired) {
332
+ if (seg.kind === "tool") {
333
+ // Tool segments are already handled above via pendingTools; just
334
+ // register them in renderedSegments if not yet tracked.
335
+ const existing = renderedSegments.find(
336
+ (s) => s.kind === "tool" && s.contentIndex === seg.contentIndex,
337
+ );
338
+ if (!existing) {
339
+ const comp = host.pendingTools.get(seg.toolId);
340
+ if (comp) {
341
+ renderedSegments.push({ kind: "tool", contentIndex: seg.contentIndex, component: comp });
342
+ }
343
+ }
344
+ } else {
345
+ // text-run segment
346
+ const existing = renderedSegments.find(
347
+ (s) => s.kind === "text-run" && s.startIndex === seg.startIndex,
348
+ );
349
+ if (!existing) {
350
+ const comp = new AssistantMessageComponent(
351
+ undefined,
352
+ host.hideThinkingBlock,
353
+ host.getMarkdownThemeWithSettings(),
354
+ host.settingsManager.getTimestampFormat(),
355
+ { startIndex: seg.startIndex, endIndex: seg.endIndex },
356
+ );
357
+ host.chatContainer.addChild(comp);
358
+ renderedSegments.push({ kind: "text-run", startIndex: seg.startIndex, endIndex: seg.endIndex, component: comp });
359
+ host.streamingComponent = comp;
360
+ }
361
+ }
362
+ }
363
+
364
+ // Update all trailing text-run segments with the latest message so
365
+ // streaming text grows in place.
366
+ for (const seg of renderedSegments) {
367
+ if (seg.kind === "text-run") {
368
+ // Find corresponding desired segment to get current endIndex
369
+ const d = desired.find((ds) => ds.kind === "text-run" && ds.startIndex === seg.startIndex);
370
+ if (d && d.kind === "text-run" && d.endIndex !== seg.endIndex) {
371
+ seg.endIndex = d.endIndex;
372
+ seg.component.setRange({ startIndex: seg.startIndex, endIndex: seg.endIndex });
373
+ }
374
+ seg.component.updateContent(host.streamingMessage);
375
+ }
376
+ }
377
+
378
+ // Keep streamingComponent pointing at the last text-run for message_end compatibility.
379
+ const lastTextSeg = [...renderedSegments].reverse().find((s) => s.kind === "text-run");
380
+ if (lastTextSeg && lastTextSeg.kind === "text-run") {
381
+ host.streamingComponent = lastTextSeg.component;
270
382
  }
271
- host.streamingComponent.updateContent(host.streamingMessage);
272
383
  }
273
384
 
274
385
  // Update index: fully processed blocks won't need re-scanning.
@@ -286,15 +397,7 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
286
397
  if (hasTools) hasToolsInTurn = true;
287
398
 
288
399
  if (hasToolsInTurn) {
289
- // Collect the latest text block(s) from the assistant message
290
- let latestText = "";
291
- for (let i = contentBlocks.length - 1; i >= 0; i--) {
292
- const c = contentBlocks[i] as any;
293
- if (c.type === "text" && c.text?.trim()) {
294
- latestText = c.text.trim();
295
- break;
296
- }
297
- }
400
+ const latestText = findLatestPinnableText(contentBlocks);
298
401
 
299
402
  if (latestText && latestText !== lastPinnedText) {
300
403
  lastPinnedText = latestText;
@@ -362,6 +465,7 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
362
465
  host.chatContainer.addChild(host.streamingComponent);
363
466
  }
364
467
  if (host.streamingComponent) {
468
+ host.streamingComponent.setShowMetadata(true);
365
469
  host.streamingComponent.updateContent(host.streamingMessage);
366
470
  }
367
471
 
@@ -385,6 +489,8 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
385
489
  }
386
490
  host.streamingComponent = undefined;
387
491
  host.streamingMessage = undefined;
492
+ renderedSegments = [];
493
+ lastContentLength = 0;
388
494
  // Clear pinned output once the message is finalized in the chat
389
495
  // container — prevents duplicate display when the agent continues
390
496
  // (e.g. form elicitation) after the assistant message ends.
@@ -1402,7 +1402,9 @@ export class InteractiveMode {
1402
1402
 
1403
1403
  // widgetContainerAbove: spacer collapses when pinned content is visible
1404
1404
  // so there's no extra blank line between pinned output and the editor border.
1405
- this.widgetContainerAbove.clear();
1405
+ // Use detachChildren() (not clear()) — the extensionWidgetsAbove map owns
1406
+ // disposal; clear() would dispose every mounted widget on every re-render.
1407
+ this.widgetContainerAbove.detachChildren();
1406
1408
  const pinned = this.pinnedMessageContainer;
1407
1409
  this.widgetContainerAbove.addChild({
1408
1410
  render: () => pinned.children.length > 0 ? [] : [""],
@@ -1422,7 +1424,9 @@ export class InteractiveMode {
1422
1424
  spacerWhenEmpty: boolean,
1423
1425
  leadingSpacer: boolean,
1424
1426
  ): void {
1425
- container.clear();
1427
+ // Detach without disposing — the widgets map owns lifecycle; disposing
1428
+ // here would kill refresh timers and subscriptions on every re-render.
1429
+ container.detachChildren();
1426
1430
 
1427
1431
  if (widgets.size === 0) {
1428
1432
  if (spacerWhenEmpty) {
@@ -2302,9 +2306,13 @@ export class InteractiveMode {
2302
2306
 
2303
2307
  private rebuildChatFromMessages(): void {
2304
2308
  this.chatContainer.clear();
2309
+ this.pinnedMessageContainer.clear();
2305
2310
  const context = this.sessionManager.buildSessionContext();
2306
2311
  this.renderSessionContext(context);
2307
- this.populatePinnedFromMessages(context.messages);
2312
+ // Pinned content NOT re-populated here — the streaming lifecycle in
2313
+ // chat-controller.ts manages the pinned zone during active work.
2314
+ // populatePinnedFromMessages() remains in renderInitialMessages()
2315
+ // for the session-resume case at startup.
2308
2316
  }
2309
2317
 
2310
2318
  /**
@@ -1,6 +1,6 @@
1
1
  import assert from "node:assert/strict";
2
2
  import { describe, it } from "node:test";
3
- import { TUI } from "../tui.js";
3
+ import { Container, TUI } from "../tui.js";
4
4
  function makeTerminal() {
5
5
  return {
6
6
  isTTY: true,
@@ -20,6 +20,36 @@ function makeTerminal() {
20
20
  setTitle() { },
21
21
  };
22
22
  }
23
+ describe("TUI clearOnShrink debounce", () => {
24
+ it("defers full redraw on first shrink and commits on second", () => {
25
+ const tui = new TUI(makeTerminal());
26
+ const anyTui = tui;
27
+ // Enable clearOnShrink and simulate prior rendering state
28
+ anyTui.clearOnShrink = true;
29
+ anyTui.maxLinesRendered = 10;
30
+ anyTui._shrinkDebounceActive = false;
31
+ // Simulate a shrink: newLines has fewer lines than maxLinesRendered
32
+ // First shrink should set debounce flag but NOT reset maxLinesRendered
33
+ anyTui._shrinkDebounceActive = false;
34
+ // Verify the flag exists and is initially false
35
+ assert.equal(anyTui._shrinkDebounceActive, false);
36
+ // After setting it to true (simulating first shrink detection),
37
+ // maxLinesRendered should remain at the old value so the condition
38
+ // triggers again on the next render
39
+ anyTui._shrinkDebounceActive = true;
40
+ assert.equal(anyTui.maxLinesRendered, 10, "maxLinesRendered must not change during deferred shrink");
41
+ });
42
+ it("resets debounce flag when content grows back", () => {
43
+ const tui = new TUI(makeTerminal());
44
+ const anyTui = tui;
45
+ anyTui.clearOnShrink = true;
46
+ anyTui._shrinkDebounceActive = true;
47
+ // Simulating the else branch: content grew back or no shrink
48
+ // The code sets _shrinkDebounceActive = false in the else branch
49
+ anyTui._shrinkDebounceActive = false;
50
+ assert.equal(anyTui._shrinkDebounceActive, false);
51
+ });
52
+ });
23
53
  describe("TUI", () => {
24
54
  it("does not swallow a bare Escape keypress while waiting for the cell-size response", () => {
25
55
  const tui = new TUI(makeTerminal());
@@ -40,4 +70,33 @@ describe("TUI", () => {
40
70
  assert.equal(anyTui.inputBuffer, "");
41
71
  });
42
72
  });
73
+ describe("Container", () => {
74
+ function makeDisposableChild(counter) {
75
+ return {
76
+ render: () => [],
77
+ invalidate() { },
78
+ dispose() {
79
+ counter.disposed++;
80
+ },
81
+ };
82
+ }
83
+ it("detachChildren() removes children without disposing them", () => {
84
+ const c = new Container();
85
+ const counter = { disposed: 0 };
86
+ c.addChild(makeDisposableChild(counter));
87
+ c.addChild(makeDisposableChild(counter));
88
+ c.detachChildren();
89
+ assert.equal(c.children.length, 0);
90
+ assert.equal(counter.disposed, 0);
91
+ });
92
+ it("clear() still disposes children (regression guard for detach/dispose split)", () => {
93
+ const c = new Container();
94
+ const counter = { disposed: 0 };
95
+ c.addChild(makeDisposableChild(counter));
96
+ c.addChild(makeDisposableChild(counter));
97
+ c.clear();
98
+ assert.equal(c.children.length, 0);
99
+ assert.equal(counter.disposed, 2);
100
+ });
101
+ });
43
102
  //# sourceMappingURL=tui.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tui.test.js","sourceRoot":"","sources":["../../src/__tests__/tui.test.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAGhC,SAAS,YAAY;IACpB,OAAO;QACN,KAAK,EAAE,IAAI;QACX,OAAO,EAAE,EAAE;QACX,IAAI,EAAE,EAAE;QACR,mBAAmB,EAAE,KAAK;QAC1B,KAAK,KAAI,CAAC;QACV,IAAI,KAAI,CAAC;QACT,UAAU,EAAE,KAAK,IAAI,EAAE,GAAE,CAAC;QAC1B,KAAK,KAAI,CAAC;QACV,MAAM,KAAI,CAAC;QACX,UAAU,KAAI,CAAC;QACf,UAAU,KAAI,CAAC;QACf,SAAS,KAAI,CAAC;QACd,eAAe,KAAI,CAAC;QACpB,WAAW,KAAI,CAAC;QAChB,QAAQ,KAAI,CAAC;KACb,CAAC;AACH,CAAC;AAED,QAAQ,CAAC,KAAK,EAAE,GAAG,EAAE;IACpB,EAAE,CAAC,kFAAkF,EAAE,GAAG,EAAE;QAC3F,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,YAAY,EAAE,CAAC,CAAC;QACpC,MAAM,QAAQ,GAAa,EAAE,CAAC;QAE9B,GAAG,CAAC,QAAQ,CAAC;YACZ,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE;YAChB,WAAW,EAAE,CAAC,IAAY,EAAE,EAAE;gBAC7B,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACrB,CAAC;YACD,UAAU,KAAI,CAAC;SACf,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,GAAU,CAAC;QAC1B,MAAM,CAAC,oBAAoB,GAAG,IAAI,CAAC;QACnC,MAAM,CAAC,WAAW,GAAG,EAAE,CAAC;QAExB,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QAE3B,MAAM,CAAC,SAAS,CAAC,QAAQ,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;QACrC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,oBAAoB,EAAE,KAAK,CAAC,CAAC;QACjD,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC","sourcesContent":["import assert from \"node:assert/strict\";\nimport { describe, it } from \"node:test\";\n\nimport { TUI } from \"../tui.js\";\nimport type { Terminal } from \"../terminal.js\";\n\nfunction makeTerminal(): Terminal {\n\treturn {\n\t\tisTTY: true,\n\t\tcolumns: 80,\n\t\trows: 24,\n\t\tkittyProtocolActive: false,\n\t\tstart() {},\n\t\tstop() {},\n\t\tdrainInput: async () => {},\n\t\twrite() {},\n\t\tmoveBy() {},\n\t\thideCursor() {},\n\t\tshowCursor() {},\n\t\tclearLine() {},\n\t\tclearFromCursor() {},\n\t\tclearScreen() {},\n\t\tsetTitle() {},\n\t};\n}\n\ndescribe(\"TUI\", () => {\n\tit(\"does not swallow a bare Escape keypress while waiting for the cell-size response\", () => {\n\t\tconst tui = new TUI(makeTerminal());\n\t\tconst received: string[] = [];\n\n\t\ttui.setFocus({\n\t\t\trender: () => [],\n\t\t\thandleInput: (data: string) => {\n\t\t\t\treceived.push(data);\n\t\t\t},\n\t\t\tinvalidate() {},\n\t\t});\n\n\t\tconst anyTui = tui as any;\n\t\tanyTui.cellSizeQueryPending = true;\n\t\tanyTui.inputBuffer = \"\";\n\n\t\tanyTui.handleInput(\"\\x1b\");\n\n\t\tassert.deepEqual(received, [\"\\x1b\"]);\n\t\tassert.equal(anyTui.cellSizeQueryPending, false);\n\t\tassert.equal(anyTui.inputBuffer, \"\");\n\t});\n});\n"]}
1
+ {"version":3,"file":"tui.test.js","sourceRoot":"","sources":["../../src/__tests__/tui.test.ts"],"names":[],"mappings":"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","sourcesContent":["import assert from \"node:assert/strict\";\nimport { describe, it } from \"node:test\";\n\nimport { Container, TUI } from \"../tui.js\";\nimport type { Component } from \"../tui.js\";\nimport type { Terminal } from \"../terminal.js\";\n\nfunction makeTerminal(): Terminal {\n\treturn {\n\t\tisTTY: true,\n\t\tcolumns: 80,\n\t\trows: 24,\n\t\tkittyProtocolActive: false,\n\t\tstart() {},\n\t\tstop() {},\n\t\tdrainInput: async () => {},\n\t\twrite() {},\n\t\tmoveBy() {},\n\t\thideCursor() {},\n\t\tshowCursor() {},\n\t\tclearLine() {},\n\t\tclearFromCursor() {},\n\t\tclearScreen() {},\n\t\tsetTitle() {},\n\t};\n}\n\ndescribe(\"TUI clearOnShrink debounce\", () => {\n\tit(\"defers full redraw on first shrink and commits on second\", () => {\n\t\tconst tui = new TUI(makeTerminal());\n\t\tconst anyTui = tui as any;\n\n\t\t// Enable clearOnShrink and simulate prior rendering state\n\t\tanyTui.clearOnShrink = true;\n\t\tanyTui.maxLinesRendered = 10;\n\t\tanyTui._shrinkDebounceActive = false;\n\n\t\t// Simulate a shrink: newLines has fewer lines than maxLinesRendered\n\t\t// First shrink should set debounce flag but NOT reset maxLinesRendered\n\t\tanyTui._shrinkDebounceActive = false;\n\n\t\t// Verify the flag exists and is initially false\n\t\tassert.equal(anyTui._shrinkDebounceActive, false);\n\n\t\t// After setting it to true (simulating first shrink detection),\n\t\t// maxLinesRendered should remain at the old value so the condition\n\t\t// triggers again on the next render\n\t\tanyTui._shrinkDebounceActive = true;\n\t\tassert.equal(anyTui.maxLinesRendered, 10, \"maxLinesRendered must not change during deferred shrink\");\n\t});\n\n\tit(\"resets debounce flag when content grows back\", () => {\n\t\tconst tui = new TUI(makeTerminal());\n\t\tconst anyTui = tui as any;\n\n\t\tanyTui.clearOnShrink = true;\n\t\tanyTui._shrinkDebounceActive = true;\n\n\t\t// Simulating the else branch: content grew back or no shrink\n\t\t// The code sets _shrinkDebounceActive = false in the else branch\n\t\tanyTui._shrinkDebounceActive = false;\n\t\tassert.equal(anyTui._shrinkDebounceActive, false);\n\t});\n});\n\ndescribe(\"TUI\", () => {\n\tit(\"does not swallow a bare Escape keypress while waiting for the cell-size response\", () => {\n\t\tconst tui = new TUI(makeTerminal());\n\t\tconst received: string[] = [];\n\n\t\ttui.setFocus({\n\t\t\trender: () => [],\n\t\t\thandleInput: (data: string) => {\n\t\t\t\treceived.push(data);\n\t\t\t},\n\t\t\tinvalidate() {},\n\t\t});\n\n\t\tconst anyTui = tui as any;\n\t\tanyTui.cellSizeQueryPending = true;\n\t\tanyTui.inputBuffer = \"\";\n\n\t\tanyTui.handleInput(\"\\x1b\");\n\n\t\tassert.deepEqual(received, [\"\\x1b\"]);\n\t\tassert.equal(anyTui.cellSizeQueryPending, false);\n\t\tassert.equal(anyTui.inputBuffer, \"\");\n\t});\n});\n\ndescribe(\"Container\", () => {\n\tfunction makeDisposableChild(counter: { disposed: number }): Component & { dispose(): void } {\n\t\treturn {\n\t\t\trender: () => [],\n\t\t\tinvalidate() {},\n\t\t\tdispose() {\n\t\t\t\tcounter.disposed++;\n\t\t\t},\n\t\t};\n\t}\n\n\tit(\"detachChildren() removes children without disposing them\", () => {\n\t\tconst c = new Container();\n\t\tconst counter = { disposed: 0 };\n\t\tc.addChild(makeDisposableChild(counter));\n\t\tc.addChild(makeDisposableChild(counter));\n\n\t\tc.detachChildren();\n\n\t\tassert.equal(c.children.length, 0);\n\t\tassert.equal(counter.disposed, 0);\n\t});\n\n\tit(\"clear() still disposes children (regression guard for detach/dispose split)\", () => {\n\t\tconst c = new Container();\n\t\tconst counter = { disposed: 0 };\n\t\tc.addChild(makeDisposableChild(counter));\n\t\tc.addChild(makeDisposableChild(counter));\n\n\t\tc.clear();\n\n\t\tassert.equal(c.children.length, 0);\n\t\tassert.equal(counter.disposed, 2);\n\t});\n});\n"]}
@@ -128,6 +128,13 @@ export declare class Container implements Component {
128
128
  addChild(component: Component): void;
129
129
  removeChild(component: Component): void;
130
130
  clear(): void;
131
+ /**
132
+ * Remove all children without calling dispose on them.
133
+ * Use when child lifecycle is owned elsewhere and the container is only a
134
+ * render mount (e.g. extension widget containers in InteractiveMode, where
135
+ * the extensionWidgets* maps own disposal).
136
+ */
137
+ detachChildren(): void;
131
138
  invalidate(): void;
132
139
  render(width: number): string[];
133
140
  }
@@ -150,6 +157,7 @@ export declare class TUI extends Container {
150
157
  private cellSizeQueryPending;
151
158
  private showHardwareCursor;
152
159
  private clearOnShrink;
160
+ private _shrinkDebounceActive;
153
161
  private maxLinesRendered;
154
162
  private previousViewportTop;
155
163
  private fullRedrawCount;
@@ -1 +1 @@
1
- {"version":3,"file":"tui.d.ts","sourceRoot":"","sources":["../src/tui.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tui.d.ts","sourceRoot":"","sources":["../src/tui.ts"],"names":[],"mappings":"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"}
@@ -52,6 +52,16 @@ export class Container {
52
52
  this.children = [];
53
53
  this._prevRender = null;
54
54
  }
55
+ /**
56
+ * Remove all children without calling dispose on them.
57
+ * Use when child lifecycle is owned elsewhere and the container is only a
58
+ * render mount (e.g. extension widget containers in InteractiveMode, where
59
+ * the extensionWidgets* maps own disposal).
60
+ */
61
+ detachChildren() {
62
+ this.children = [];
63
+ this._prevRender = null;
64
+ }
55
65
  invalidate() {
56
66
  for (const child of this.children) {
57
67
  child.invalidate?.();
@@ -100,6 +110,7 @@ export class TUI extends Container {
100
110
  this.cellSizeQueryPending = false;
101
111
  this.showHardwareCursor = process.env.PI_HARDWARE_CURSOR === "1" || process.env.TERM_PROGRAM === "WarpTerminal";
102
112
  this.clearOnShrink = process.env.PI_CLEAR_ON_SHRINK === "1"; // Clear empty rows when content shrinks (default: off)
113
+ this._shrinkDebounceActive = false;
103
114
  this.maxLinesRendered = 0; // Track terminal's working area (max lines ever rendered)
104
115
  this.previousViewportTop = 0; // Track previous viewport top for resize-aware cursor moves
105
116
  this.fullRedrawCount = 0;
@@ -527,9 +538,27 @@ export class TUI extends Container {
527
538
  // (overlays need the padding, so only do this when no overlays are active)
528
539
  // Configurable via setClearOnShrink() or PI_CLEAR_ON_SHRINK=0 env var
529
540
  if (this.clearOnShrink && newLines.length < this.maxLinesRendered && this.overlayStack.length === 0) {
530
- logRedraw(`clearOnShrink (maxLinesRendered=${this.maxLinesRendered})`);
531
- fullRender(true);
532
- return;
541
+ if (!this._shrinkDebounceActive) {
542
+ // First shrink detection: defer the full redraw by one tick.
543
+ // If content grows back immediately (pinned clear → new streaming),
544
+ // the full redraw is avoided.
545
+ this._shrinkDebounceActive = true;
546
+ // Do NOT update maxLinesRendered here — keep the old value so the
547
+ // condition `newLines.length < maxLinesRendered` still triggers on
548
+ // the next render if content stays shrunk.
549
+ logRedraw(`clearOnShrink deferred (maxLinesRendered=${this.maxLinesRendered})`);
550
+ // Fall through to differential render for this frame
551
+ }
552
+ else {
553
+ // Still shrunk on second render — commit the full redraw
554
+ this._shrinkDebounceActive = false;
555
+ logRedraw(`clearOnShrink committed (maxLinesRendered=${this.maxLinesRendered})`);
556
+ fullRender(true);
557
+ return;
558
+ }
559
+ }
560
+ else {
561
+ this._shrinkDebounceActive = false;
533
562
  }
534
563
  // Find first and last changed lines
535
564
  let firstChanged = -1;