gsd-pi 2.74.0-dev.6e23363 → 2.74.0-dev.703eabc

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 (271) hide show
  1. package/dist/resources/extensions/gsd/auto-post-unit.js +7 -3
  2. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +10 -1
  3. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +45 -4
  4. package/dist/resources/extensions/gsd/commands/handlers/ops.js +5 -0
  5. package/dist/resources/extensions/gsd/commands-extract-learnings.js +225 -0
  6. package/dist/resources/extensions/gsd/ecosystem/gsd-extension-api.js +144 -0
  7. package/dist/resources/extensions/gsd/ecosystem/loader.js +145 -0
  8. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  9. package/dist/web/standalone/.next/BUILD_ID +1 -1
  10. package/dist/web/standalone/.next/app-path-routes-manifest.json +16 -16
  11. package/dist/web/standalone/.next/build-manifest.json +2 -2
  12. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  13. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  14. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  15. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  16. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  17. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  18. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  19. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  20. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  21. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  22. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  23. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  24. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  25. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  26. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  27. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  28. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  29. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  30. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +1 -1
  31. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +1 -1
  32. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +1 -1
  33. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +1 -1
  34. package/dist/web/standalone/.next/server/app/index.html +1 -1
  35. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  36. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app-paths-manifest.json +16 -16
  42. package/dist/web/standalone/.next/server/chunks/6897.js +3 -3
  43. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  44. package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
  45. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  46. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  47. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  48. package/package.json +1 -1
  49. package/packages/mcp-server/dist/readers/graph.d.ts +1 -1
  50. package/packages/mcp-server/dist/readers/graph.d.ts.map +1 -1
  51. package/packages/mcp-server/dist/readers/graph.js +107 -0
  52. package/packages/mcp-server/dist/readers/graph.js.map +1 -1
  53. package/packages/mcp-server/src/readers/graph.test.ts +178 -0
  54. package/packages/mcp-server/src/readers/graph.ts +148 -1
  55. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  56. package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
  57. package/packages/pi-ai/dist/index.d.ts +1 -9
  58. package/packages/pi-ai/dist/index.d.ts.map +1 -1
  59. package/packages/pi-ai/dist/index.js +1 -9
  60. package/packages/pi-ai/dist/index.js.map +1 -1
  61. package/packages/pi-ai/dist/models/capability-patches.d.ts +19 -0
  62. package/packages/pi-ai/dist/models/capability-patches.d.ts.map +1 -0
  63. package/packages/pi-ai/dist/models/capability-patches.js +36 -0
  64. package/packages/pi-ai/dist/models/capability-patches.js.map +1 -0
  65. package/packages/pi-ai/dist/{models.custom.d.ts → models/custom.d.ts} +1 -1
  66. package/packages/pi-ai/dist/models/custom.d.ts.map +1 -0
  67. package/packages/pi-ai/dist/{models.custom.js → models/custom.js} +4 -4
  68. package/packages/pi-ai/dist/models/custom.js.map +1 -0
  69. package/packages/pi-ai/dist/models/generated/amazon-bedrock.d.ts +1482 -0
  70. package/packages/pi-ai/dist/models/generated/amazon-bedrock.d.ts.map +1 -0
  71. package/packages/pi-ai/dist/models/generated/amazon-bedrock.js +1484 -0
  72. package/packages/pi-ai/dist/models/generated/amazon-bedrock.js.map +1 -0
  73. package/packages/pi-ai/dist/models/generated/anthropic.d.ts +377 -0
  74. package/packages/pi-ai/dist/models/generated/anthropic.d.ts.map +1 -0
  75. package/packages/pi-ai/dist/models/generated/anthropic.js +379 -0
  76. package/packages/pi-ai/dist/models/generated/anthropic.js.map +1 -0
  77. package/packages/pi-ai/dist/models/generated/azure-openai-responses.d.ts +700 -0
  78. package/packages/pi-ai/dist/models/generated/azure-openai-responses.d.ts.map +1 -0
  79. package/packages/pi-ai/dist/models/generated/azure-openai-responses.js +702 -0
  80. package/packages/pi-ai/dist/models/generated/azure-openai-responses.js.map +1 -0
  81. package/packages/pi-ai/dist/models/generated/cerebras.d.ts +71 -0
  82. package/packages/pi-ai/dist/models/generated/cerebras.d.ts.map +1 -0
  83. package/packages/pi-ai/dist/models/generated/cerebras.js +73 -0
  84. package/packages/pi-ai/dist/models/generated/cerebras.js.map +1 -0
  85. package/packages/pi-ai/dist/models/generated/github-copilot.d.ts +590 -0
  86. package/packages/pi-ai/dist/models/generated/github-copilot.d.ts.map +1 -0
  87. package/packages/pi-ai/dist/models/generated/github-copilot.js +444 -0
  88. package/packages/pi-ai/dist/models/generated/github-copilot.js.map +1 -0
  89. package/packages/pi-ai/dist/models/generated/google-antigravity.d.ts +156 -0
  90. package/packages/pi-ai/dist/models/generated/google-antigravity.d.ts.map +1 -0
  91. package/packages/pi-ai/dist/models/generated/google-antigravity.js +158 -0
  92. package/packages/pi-ai/dist/models/generated/google-antigravity.js.map +1 -0
  93. package/packages/pi-ai/dist/models/generated/google-gemini-cli.d.ts +105 -0
  94. package/packages/pi-ai/dist/models/generated/google-gemini-cli.d.ts.map +1 -0
  95. package/packages/pi-ai/dist/models/generated/google-gemini-cli.js +107 -0
  96. package/packages/pi-ai/dist/models/generated/google-gemini-cli.js.map +1 -0
  97. package/packages/pi-ai/dist/models/generated/google-vertex.d.ts +207 -0
  98. package/packages/pi-ai/dist/models/generated/google-vertex.d.ts.map +1 -0
  99. package/packages/pi-ai/dist/models/generated/google-vertex.js +209 -0
  100. package/packages/pi-ai/dist/models/generated/google-vertex.js.map +1 -0
  101. package/packages/pi-ai/dist/models/generated/google.d.ts +462 -0
  102. package/packages/pi-ai/dist/models/generated/google.d.ts.map +1 -0
  103. package/packages/pi-ai/dist/models/generated/google.js +464 -0
  104. package/packages/pi-ai/dist/models/generated/google.js.map +1 -0
  105. package/packages/pi-ai/dist/models/generated/groq.d.ts +309 -0
  106. package/packages/pi-ai/dist/models/generated/groq.d.ts.map +1 -0
  107. package/packages/pi-ai/dist/models/generated/groq.js +311 -0
  108. package/packages/pi-ai/dist/models/generated/groq.js.map +1 -0
  109. package/packages/pi-ai/dist/models/generated/huggingface.d.ts +383 -0
  110. package/packages/pi-ai/dist/models/generated/huggingface.d.ts.map +1 -0
  111. package/packages/pi-ai/dist/models/generated/huggingface.js +347 -0
  112. package/packages/pi-ai/dist/models/generated/huggingface.js.map +1 -0
  113. package/packages/pi-ai/dist/{models.generated.d.ts → models/generated/index.d.ts} +1 -1
  114. package/packages/pi-ai/dist/{models.generated.d.ts.map → models/generated/index.d.ts.map} +1 -1
  115. package/packages/pi-ai/dist/models/generated/index.js +51 -0
  116. package/packages/pi-ai/dist/models/generated/index.js.map +1 -0
  117. package/packages/pi-ai/dist/models/generated/kimi-coding.d.ts +37 -0
  118. package/packages/pi-ai/dist/models/generated/kimi-coding.d.ts.map +1 -0
  119. package/packages/pi-ai/dist/models/generated/kimi-coding.js +39 -0
  120. package/packages/pi-ai/dist/models/generated/kimi-coding.js.map +1 -0
  121. package/packages/pi-ai/dist/models/generated/minimax-cn.d.ts +105 -0
  122. package/packages/pi-ai/dist/models/generated/minimax-cn.d.ts.map +1 -0
  123. package/packages/pi-ai/dist/models/generated/minimax-cn.js +107 -0
  124. package/packages/pi-ai/dist/models/generated/minimax-cn.js.map +1 -0
  125. package/packages/pi-ai/dist/models/generated/minimax.d.ts +105 -0
  126. package/packages/pi-ai/dist/models/generated/minimax.d.ts.map +1 -0
  127. package/packages/pi-ai/dist/models/generated/minimax.js +107 -0
  128. package/packages/pi-ai/dist/models/generated/minimax.js.map +1 -0
  129. package/packages/pi-ai/dist/models/generated/mistral.d.ts +445 -0
  130. package/packages/pi-ai/dist/models/generated/mistral.d.ts.map +1 -0
  131. package/packages/pi-ai/dist/models/generated/mistral.js +447 -0
  132. package/packages/pi-ai/dist/models/generated/mistral.js.map +1 -0
  133. package/packages/pi-ai/dist/models/generated/openai-codex.d.ts +139 -0
  134. package/packages/pi-ai/dist/models/generated/openai-codex.d.ts.map +1 -0
  135. package/packages/pi-ai/dist/models/generated/openai-codex.js +141 -0
  136. package/packages/pi-ai/dist/models/generated/openai-codex.js.map +1 -0
  137. package/packages/pi-ai/dist/models/generated/openai.d.ts +700 -0
  138. package/packages/pi-ai/dist/models/generated/openai.d.ts.map +1 -0
  139. package/packages/pi-ai/dist/models/generated/openai.js +702 -0
  140. package/packages/pi-ai/dist/models/generated/openai.js.map +1 -0
  141. package/packages/pi-ai/dist/models/generated/opencode-go.d.ts +122 -0
  142. package/packages/pi-ai/dist/models/generated/opencode-go.d.ts.map +1 -0
  143. package/packages/pi-ai/dist/models/generated/opencode-go.js +124 -0
  144. package/packages/pi-ai/dist/models/generated/opencode-go.js.map +1 -0
  145. package/packages/pi-ai/dist/models/generated/opencode.d.ts +530 -0
  146. package/packages/pi-ai/dist/models/generated/opencode.d.ts.map +1 -0
  147. package/packages/pi-ai/dist/models/generated/opencode.js +532 -0
  148. package/packages/pi-ai/dist/models/generated/opencode.js.map +1 -0
  149. package/packages/pi-ai/dist/models/generated/openrouter.d.ts +4270 -0
  150. package/packages/pi-ai/dist/models/generated/openrouter.d.ts.map +1 -0
  151. package/packages/pi-ai/dist/models/generated/openrouter.js +4272 -0
  152. package/packages/pi-ai/dist/models/generated/openrouter.js.map +1 -0
  153. package/packages/pi-ai/dist/models/generated/vercel-ai-gateway.d.ts +2604 -0
  154. package/packages/pi-ai/dist/models/generated/vercel-ai-gateway.d.ts.map +1 -0
  155. package/packages/pi-ai/dist/models/generated/vercel-ai-gateway.js +2606 -0
  156. package/packages/pi-ai/dist/models/generated/vercel-ai-gateway.js.map +1 -0
  157. package/packages/pi-ai/dist/models/generated/xai.d.ts +411 -0
  158. package/packages/pi-ai/dist/models/generated/xai.d.ts.map +1 -0
  159. package/packages/pi-ai/dist/models/generated/xai.js +413 -0
  160. package/packages/pi-ai/dist/models/generated/xai.js.map +1 -0
  161. package/packages/pi-ai/dist/models/generated/zai.d.ts +276 -0
  162. package/packages/pi-ai/dist/models/generated/zai.d.ts.map +1 -0
  163. package/packages/pi-ai/dist/models/generated/zai.js +239 -0
  164. package/packages/pi-ai/dist/models/generated/zai.js.map +1 -0
  165. package/packages/pi-ai/dist/models/index.d.ts +27 -0
  166. package/packages/pi-ai/dist/models/index.d.ts.map +1 -0
  167. package/packages/pi-ai/dist/models/index.js +80 -0
  168. package/packages/pi-ai/dist/models/index.js.map +1 -0
  169. package/packages/pi-ai/dist/models.d.ts +1 -36
  170. package/packages/pi-ai/dist/models.d.ts.map +1 -1
  171. package/packages/pi-ai/dist/models.generated.test.js +1 -2
  172. package/packages/pi-ai/dist/models.generated.test.js.map +1 -1
  173. package/packages/pi-ai/dist/models.js +3 -112
  174. package/packages/pi-ai/dist/models.js.map +1 -1
  175. package/packages/pi-ai/dist/models.test.js +6 -5
  176. package/packages/pi-ai/dist/models.test.js.map +1 -1
  177. package/packages/pi-ai/scripts/generate-models.ts +74 -40
  178. package/packages/pi-ai/src/index.ts +1 -9
  179. package/packages/pi-ai/src/models/capability-patches.ts +40 -0
  180. package/packages/pi-ai/src/{models.custom.ts → models/custom.ts} +4 -4
  181. package/packages/pi-ai/src/models/generated/amazon-bedrock.ts +1486 -0
  182. package/packages/pi-ai/src/models/generated/anthropic.ts +381 -0
  183. package/packages/pi-ai/src/models/generated/azure-openai-responses.ts +704 -0
  184. package/packages/pi-ai/src/models/generated/cerebras.ts +75 -0
  185. package/packages/pi-ai/src/models/generated/github-copilot.ts +446 -0
  186. package/packages/pi-ai/src/models/generated/google-antigravity.ts +160 -0
  187. package/packages/pi-ai/src/models/generated/google-gemini-cli.ts +109 -0
  188. package/packages/pi-ai/src/models/generated/google-vertex.ts +211 -0
  189. package/packages/pi-ai/src/models/generated/google.ts +466 -0
  190. package/packages/pi-ai/src/models/generated/groq.ts +313 -0
  191. package/packages/pi-ai/src/models/generated/huggingface.ts +349 -0
  192. package/packages/pi-ai/src/models/generated/index.ts +52 -0
  193. package/packages/pi-ai/src/models/generated/kimi-coding.ts +41 -0
  194. package/packages/pi-ai/src/models/generated/minimax-cn.ts +109 -0
  195. package/packages/pi-ai/src/models/generated/minimax.ts +109 -0
  196. package/packages/pi-ai/src/models/generated/mistral.ts +449 -0
  197. package/packages/pi-ai/src/models/generated/openai-codex.ts +143 -0
  198. package/packages/pi-ai/src/models/generated/openai.ts +704 -0
  199. package/packages/pi-ai/src/models/generated/opencode-go.ts +126 -0
  200. package/packages/pi-ai/src/models/generated/opencode.ts +534 -0
  201. package/packages/pi-ai/src/models/generated/openrouter.ts +4274 -0
  202. package/packages/pi-ai/src/models/generated/vercel-ai-gateway.ts +2608 -0
  203. package/packages/pi-ai/src/models/generated/xai.ts +415 -0
  204. package/packages/pi-ai/src/models/generated/zai.ts +241 -0
  205. package/packages/pi-ai/src/models/index.ts +106 -0
  206. package/packages/pi-ai/src/models.generated.test.ts +1 -2
  207. package/packages/pi-ai/src/models.test.ts +6 -5
  208. package/packages/pi-ai/src/models.ts +3 -153
  209. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  210. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  211. package/packages/pi-coding-agent/dist/core/agent-session.js +8 -2
  212. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  213. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +359 -7
  214. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
  215. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +11 -0
  216. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -1
  217. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts +1 -0
  218. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  219. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js +23 -9
  220. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
  221. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts +11 -0
  222. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts.map +1 -0
  223. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js +47 -0
  224. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js.map +1 -0
  225. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  226. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +51 -8
  227. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  228. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  229. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js +22 -22
  230. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js.map +1 -1
  231. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  232. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +192 -22
  233. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  234. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-ordering.test.d.ts +2 -0
  235. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-ordering.test.d.ts.map +1 -0
  236. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-ordering.test.js +38 -0
  237. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-ordering.test.js.map +1 -0
  238. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +13 -0
  239. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  240. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +53 -6
  241. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  242. package/packages/pi-coding-agent/src/core/agent-session.ts +12 -6
  243. package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +453 -7
  244. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +19 -0
  245. package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +25 -10
  246. package/packages/pi-coding-agent/src/modes/interactive/components/chat-frame.ts +67 -0
  247. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +66 -7
  248. package/packages/pi-coding-agent/src/modes/interactive/components/user-message.ts +23 -26
  249. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +253 -45
  250. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-ordering.test.ts +44 -0
  251. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +73 -6
  252. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  253. package/src/resources/extensions/gsd/auto-post-unit.ts +7 -3
  254. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +15 -1
  255. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +56 -3
  256. package/src/resources/extensions/gsd/commands/handlers/ops.ts +5 -0
  257. package/src/resources/extensions/gsd/commands-extract-learnings.ts +304 -0
  258. package/src/resources/extensions/gsd/ecosystem/gsd-extension-api.ts +228 -0
  259. package/src/resources/extensions/gsd/ecosystem/loader.ts +201 -0
  260. package/src/resources/extensions/gsd/tests/commands-extract-learnings.test.ts +340 -0
  261. package/src/resources/extensions/gsd/tests/health-widget.test.ts +1 -1
  262. package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +1 -1
  263. package/src/resources/extensions/gsd/types.ts +13 -0
  264. package/src/resources/extensions/gsd/workflow-logger.ts +2 -1
  265. package/packages/pi-ai/dist/models.custom.d.ts.map +0 -1
  266. package/packages/pi-ai/dist/models.custom.js.map +0 -1
  267. package/packages/pi-ai/dist/models.generated.js +0 -14343
  268. package/packages/pi-ai/dist/models.generated.js.map +0 -1
  269. package/packages/pi-ai/src/models.generated.ts +0 -14345
  270. /package/dist/web/standalone/.next/static/{bc2gRVFTgD7j--BsJE7vP → 3U-oZ5FT59BM7sm2GInic}/_buildManifest.js +0 -0
  271. /package/dist/web/standalone/.next/static/{bc2gRVFTgD7j--BsJE7vP → 3U-oZ5FT59BM7sm2GInic}/_ssgManifest.js +0 -0
@@ -9,6 +9,7 @@ import {
9
9
  Text,
10
10
  type TUI,
11
11
  truncateToWidth,
12
+ visibleWidth,
12
13
  } from "@gsd/pi-tui";
13
14
  import stripAnsi from "strip-ansi";
14
15
  import type { ToolDefinition } from "../../../core/extensions/types.js";
@@ -65,6 +66,53 @@ function parseMcpToolName(name: string): { server: string; tool: string } | null
65
66
  return { server: rest.slice(0, delim), tool: rest.slice(delim + 2) };
66
67
  }
67
68
 
69
+ type ToolFrameTone = "pending" | "success" | "error";
70
+
71
+ function trimOuterBlankLines(lines: string[]): string[] {
72
+ let start = 0;
73
+ let end = lines.length;
74
+ while (start < end && lines[start].trim().length === 0) start++;
75
+ while (end > start && lines[end - 1].trim().length === 0) end--;
76
+ return lines.slice(start, end);
77
+ }
78
+
79
+ function renderToolFrame(
80
+ contentLines: string[],
81
+ width: number,
82
+ opts: {
83
+ label: string;
84
+ status: string;
85
+ tone: ToolFrameTone;
86
+ },
87
+ ): string[] {
88
+ const outerWidth = Math.max(20, width);
89
+ const contentWidth = Math.max(1, outerWidth - 2); // "│ " + content
90
+
91
+ const borderColor = opts.tone === "error" ? "error" : "toolTitle";
92
+ const topColor = opts.tone === "error" ? "error" : "toolTitle";
93
+ const labelColor = opts.tone === "error" ? "error" : "toolTitle";
94
+ const statusColor = opts.tone === "error" ? "error" : opts.tone === "pending" ? "warning" : "success";
95
+ const border = (s: string) => theme.fg(borderColor, s);
96
+
97
+ const leftStyled = theme.fg(labelColor, theme.bold(`• ${opts.label}`));
98
+ const rightStyled = theme.fg(statusColor, opts.status);
99
+ const gap = Math.max(1, outerWidth - visibleWidth(leftStyled) - visibleWidth(rightStyled));
100
+ const headerRow = `${leftStyled}${" ".repeat(gap)}${rightStyled}`;
101
+ const headerPad = Math.max(0, outerWidth - visibleWidth(headerRow));
102
+
103
+ const sourceLines = trimOuterBlankLines(contentLines);
104
+ const bodyLines = (sourceLines.length > 0 ? sourceLines : [""]).map((line) => {
105
+ const clipped = truncateToWidth(line, contentWidth, "");
106
+ return border("│ ") + clipped;
107
+ });
108
+
109
+ return [
110
+ theme.fg(topColor, "─".repeat(outerWidth)),
111
+ headerRow + " ".repeat(headerPad),
112
+ ...bodyLines,
113
+ ];
114
+ }
115
+
68
116
  const COMPACT_ARG_VALUE_LIMIT = 60;
69
117
  const GENERIC_OUTPUT_PREVIEW_LINES = 10;
70
118
  const GENERIC_ARGS_JSON_PREVIEW_LINES = 10;
@@ -452,16 +500,27 @@ export class ToolExecutionComponent extends Container {
452
500
  if (this.hideComponent) {
453
501
  return [];
454
502
  }
455
- return super.render(width);
503
+ const frameWidth = Math.max(20, width);
504
+ const contentWidth = Math.max(1, frameWidth - 4);
505
+ const lines = super.render(contentWidth);
506
+ const frameTone: ToolFrameTone =
507
+ this.result?.isError ? "error" : this.isPartial || !this.result ? "pending" : "success";
508
+ const frameStatus = this.isPartial || !this.result ? "Running" : this.result.isError ? "Error" : "Done";
509
+ const parsed = parseMcpToolName(this.toolName);
510
+ const frameLabel = parsed
511
+ ? `Tool ${parsed.server}·${parsed.tool}`
512
+ : `Tool ${this.normalizedToolName || this.toolName || "unknown"}`;
513
+ const framed = renderToolFrame(lines, frameWidth, {
514
+ label: frameLabel,
515
+ status: frameStatus,
516
+ tone: frameTone,
517
+ });
518
+ return framed.length > 0 ? ["", ...framed] : framed;
456
519
  }
457
520
 
458
521
  private updateDisplay(): void {
459
- // Set background based on state
460
- const bgFn = this.isPartial
461
- ? (text: string) => theme.bg("toolPendingBg", text)
462
- : this.result?.isError
463
- ? (text: string) => theme.bg("toolErrorBg", text)
464
- : (text: string) => theme.bg("toolSuccessBg", text);
522
+ // Tool body now uses transparent background; status is conveyed in the frame header.
523
+ const bgFn = (text: string) => text;
465
524
 
466
525
  const useBuiltInRenderer = this.shouldUseBuiltInRenderer();
467
526
  let customRendererHasContent = false;
@@ -1,6 +1,7 @@
1
- import { Container, Markdown, type MarkdownTheme, Spacer, Text } from "@gsd/pi-tui";
2
- import { getMarkdownTheme, theme } from "../theme/theme.js";
3
- import { formatTimestamp, type TimestampFormat } from "./timestamp.js";
1
+ import { Container, Markdown, type MarkdownTheme } from "@gsd/pi-tui";
2
+ import { getMarkdownTheme } from "../theme/theme.js";
3
+ import { type TimestampFormat } from "./timestamp.js";
4
+ import { renderChatFrame } from "./chat-frame.js";
4
5
 
5
6
  const OSC133_ZONE_START = "\x1b]133;A\x07";
6
7
  const OSC133_ZONE_END = "\x1b]133;B\x07";
@@ -16,32 +17,28 @@ export class UserMessageComponent extends Container {
16
17
  super();
17
18
  this.timestamp = timestamp;
18
19
  this.timestampFormat = timestampFormat;
19
- this.addChild(new Spacer(1));
20
- this.addChild(
21
- new Markdown(text, 1, 1, markdownTheme, {
22
- bgColor: (text: string) => theme.bg("userMessageBg", text),
23
- color: (text: string) => theme.fg("userMessageText", text),
24
- }),
25
- );
20
+ this.addChild(new Markdown(text, 0, 0, markdownTheme));
26
21
  }
27
22
 
28
23
  override render(width: number): string[] {
29
- const lines = super.render(width);
30
- if (lines.length === 0) {
31
- return lines;
24
+ const frameWidth = Math.max(20, width);
25
+ const contentWidth = Math.max(1, frameWidth - 4);
26
+ const lines = super.render(contentWidth);
27
+ const framed = renderChatFrame(lines, frameWidth, {
28
+ label: "You",
29
+ tone: "user",
30
+ timestamp: this.timestamp,
31
+ timestampFormat: this.timestampFormat,
32
+ showTimestamp: true,
33
+ });
34
+ if (framed.length === 0) {
35
+ return framed;
32
36
  }
33
-
34
- // Insert right-aligned timestamp above the message content
35
- if (this.timestamp) {
36
- const timeStr = formatTimestamp(this.timestamp, this.timestampFormat);
37
- const label = theme.fg("dim", timeStr);
38
- const padding = Math.max(0, width - timeStr.length - 1);
39
- const timestampLine = " ".repeat(padding) + label;
40
- lines.splice(0, 0, timestampLine);
41
- }
42
-
43
- lines[0] = OSC133_ZONE_START + lines[0];
44
- lines[lines.length - 1] = lines[lines.length - 1] + OSC133_ZONE_END;
45
- return lines;
37
+ const out = ["", ...framed];
38
+ const firstFrameLine = 1;
39
+ const lastFrameLine = out.length - 1;
40
+ out[firstFrameLine] = OSC133_ZONE_START + out[firstFrameLine];
41
+ out[lastFrameLine] = out[lastFrameLine] + OSC133_ZONE_END;
42
+ return out;
46
43
  }
47
44
  }
@@ -16,10 +16,20 @@ let lastContentLength = 0;
16
16
 
17
17
  // --- Segment walker state (per streaming assistant turn) ---
18
18
  type RenderedSegment =
19
- | { kind: "text-run"; startIndex: number; endIndex: number; component: AssistantMessageComponent }
19
+ | {
20
+ kind: "text-run";
21
+ startIndex: number;
22
+ endIndex: number;
23
+ contentType: "text" | "thinking";
24
+ component: AssistantMessageComponent;
25
+ }
20
26
  | { kind: "tool"; contentIndex: number; component: ToolExecutionComponent };
21
27
 
22
28
  let renderedSegments: RenderedSegment[] = [];
29
+ // When providers reuse one assistant lifecycle across internal sub-turns,
30
+ // a content[] shrink resets renderedSegments. Keep the displaced segments so
31
+ // claude-code MCP pruning can remove stale provisional text later.
32
+ let orphanedSegments: RenderedSegment[] = [];
23
33
 
24
34
  function hasVisibleAssistantContent(message: { content: Array<any> }): boolean {
25
35
  return message.content.some(
@@ -85,6 +95,7 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
85
95
  }
86
96
 
87
97
  host.footer.invalidate();
98
+ const timestampFormat = host.settingsManager.getTimestampFormat();
88
99
 
89
100
  // Reset content index tracker and pinned state when a new assistant message starts
90
101
  if (event.type === "message_start" && event.message.role === "assistant") {
@@ -93,6 +104,7 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
93
104
  lastPinnedText = "";
94
105
  hasToolsInTurn = false;
95
106
  renderedSegments = [];
107
+ orphanedSegments = [];
96
108
  if (pinnedBorder) pinnedBorder.stopSpinner();
97
109
  pinnedBorder = undefined;
98
110
  pinnedTextComponent = undefined;
@@ -113,6 +125,7 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
113
125
  lastPinnedText = "";
114
126
  hasToolsInTurn = false;
115
127
  renderedSegments = [];
128
+ orphanedSegments = [];
116
129
  lastContentLength = 0;
117
130
  if (pinnedBorder) pinnedBorder.stopSpinner();
118
131
  pinnedBorder = undefined;
@@ -226,6 +239,10 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
226
239
  // content (#4144 regression). Prior sub-turn children stay in
227
240
  // chatContainer as frozen history; new segments append after them.
228
241
  if (contentBlocks.length < lastContentLength) {
242
+ // Accumulate across successive shrinks — overwriting would drop
243
+ // segments displaced by an earlier shrink, leaving them stranded
244
+ // in chatContainer once the prune pass finally runs.
245
+ orphanedSegments = [...orphanedSegments, ...renderedSegments];
229
246
  renderedSegments = [];
230
247
  lastPinnedText = "";
231
248
  lastProcessedContentIndex = 0;
@@ -312,44 +329,95 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
312
329
  }
313
330
  return false;
314
331
  });
315
- const shouldDropPreToolText = isClaudeCodeProvider && hasMcpToolBlock;
316
332
  const firstToolIdx = blocks.findIndex((b: any) => b.type === "toolCall" || b.type === "serverToolUse");
333
+ const hasPostToolText = firstToolIdx >= 0
334
+ && blocks.some(
335
+ (b: any, idx: number) => (
336
+ idx > firstToolIdx
337
+ && b?.type === "text"
338
+ && typeof b?.text === "string"
339
+ && b.text.trim().length > 0
340
+ ),
341
+ );
342
+ // Only prune provisional pre-tool prose after post-tool prose exists,
343
+ // so MCP tool-only windows do not blank the assistant content.
344
+ const shouldDropPreToolProse = isClaudeCodeProvider && hasMcpToolBlock && hasPostToolText;
317
345
  type DesiredSegment =
318
- | { kind: "text-run"; startIndex: number; endIndex: number }
346
+ | { kind: "text-run"; startIndex: number; endIndex: number; contentType: "text" | "thinking" }
319
347
  | { kind: "tool"; contentIndex: number; toolId: string };
320
- const desired: DesiredSegment[] = [];
321
- let runStart = -1;
322
- for (let i = 0; i < blocks.length; i++) {
323
- const b = blocks[i];
324
- const isText = b.type === "text" || b.type === "thinking";
325
- const isTool = b.type === "toolCall" || b.type === "serverToolUse";
326
- if (isText) {
327
- if (shouldDropPreToolText && firstToolIdx >= 0 && i < firstToolIdx) {
328
- continue;
348
+ const desired: DesiredSegment[] = [];
349
+ let runStart = -1;
350
+ let runEnd = -1;
351
+ let runType: "text" | "thinking" | undefined;
352
+ const closeRun = () => {
353
+ if (runStart !== -1 && runType) {
354
+ desired.push({ kind: "text-run", startIndex: runStart, endIndex: runEnd, contentType: runType });
355
+ runStart = -1;
356
+ runEnd = -1;
357
+ runType = undefined;
358
+ }
359
+ };
360
+ for (let i = 0; i < blocks.length; i++) {
361
+ const b = blocks[i];
362
+ const blockType = b.type === "text" || b.type === "thinking" ? b.type : undefined;
363
+ const isTextLike = blockType === "text" || blockType === "thinking";
364
+ const isTool = b.type === "toolCall" || b.type === "serverToolUse";
365
+ // For Claude Code MCP turns, prune only pre-tool prose, never thinking.
366
+ const textValue = blockType === "text" && typeof b?.text === "string" ? b.text : "";
367
+ const isLikelyQuestion = blockType === "text" && typeof textValue === "string" && /\?\s*$/.test(textValue.trim());
368
+ const shouldSkipProse = shouldDropPreToolProse
369
+ && firstToolIdx >= 0
370
+ && i < firstToolIdx
371
+ && blockType === "text"
372
+ && !isLikelyQuestion;
373
+ if (shouldSkipProse) {
374
+ closeRun();
375
+ continue;
376
+ }
377
+ if (isTextLike) {
378
+ if (runStart === -1) {
379
+ runStart = i;
380
+ runEnd = i;
381
+ runType = blockType;
382
+ } else if (runType !== blockType) {
383
+ closeRun();
384
+ runStart = i;
385
+ runEnd = i;
386
+ runType = blockType;
387
+ } else {
388
+ runEnd = i;
329
389
  }
330
- if (runStart === -1) runStart = i;
331
390
  } else {
332
- if (runStart !== -1) {
333
- desired.push({ kind: "text-run", startIndex: runStart, endIndex: i - 1 });
334
- runStart = -1;
335
- }
391
+ closeRun();
336
392
  if (isTool) {
337
393
  desired.push({ kind: "tool", contentIndex: i, toolId: b.id });
338
394
  }
339
395
  }
340
396
  }
341
- if (runStart !== -1) {
342
- desired.push({ kind: "text-run", startIndex: runStart, endIndex: blocks.length - 1 });
343
- }
397
+ closeRun();
344
398
 
345
399
  // Claude Code MCP can emit provisional pre-tool prose that gets
346
400
  // superseded by post-tool output. Prune stale text-run segments so
347
401
  // the final assistant output remains below tool output.
348
- if (shouldDropPreToolText && firstToolIdx >= 0) {
349
- const desiredTextStarts = new Set(
402
+ if (shouldDropPreToolProse && firstToolIdx >= 0) {
403
+ if (orphanedSegments.length > 0) {
404
+ const remainingOrphans: RenderedSegment[] = [];
405
+ for (const orphan of orphanedSegments) {
406
+ if (orphan.kind === "text-run" && orphan.contentType === "text") {
407
+ host.chatContainer.removeChild(orphan.component);
408
+ if (host.streamingComponent === orphan.component) {
409
+ host.streamingComponent = undefined;
410
+ }
411
+ continue;
412
+ }
413
+ remainingOrphans.push(orphan);
414
+ }
415
+ orphanedSegments = remainingOrphans;
416
+ }
417
+ const desiredTextKeys = new Set(
350
418
  desired
351
419
  .filter((seg): seg is Extract<DesiredSegment, { kind: "text-run" }> => seg.kind === "text-run")
352
- .map((seg) => seg.startIndex),
420
+ .map((seg) => `${seg.contentType}:${seg.startIndex}`),
353
421
  );
354
422
  const desiredToolIndices = new Set(
355
423
  desired
@@ -358,7 +426,11 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
358
426
  );
359
427
  const nextRendered: RenderedSegment[] = [];
360
428
  for (const seg of renderedSegments) {
361
- if (seg.kind === "text-run" && !desiredTextStarts.has(seg.startIndex)) {
429
+ if (
430
+ seg.kind === "text-run"
431
+ && seg.contentType === "text"
432
+ && !desiredTextKeys.has(`${seg.contentType}:${seg.startIndex}`)
433
+ ) {
362
434
  host.chatContainer.removeChild(seg.component);
363
435
  if (host.streamingComponent === seg.component) {
364
436
  host.streamingComponent = undefined;
@@ -390,18 +462,24 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
390
462
  } else {
391
463
  // text-run segment
392
464
  const existing = renderedSegments.find(
393
- (s) => s.kind === "text-run" && s.startIndex === seg.startIndex,
465
+ (s) => s.kind === "text-run" && s.startIndex === seg.startIndex && s.contentType === seg.contentType,
394
466
  );
395
467
  if (!existing) {
396
468
  const comp = new AssistantMessageComponent(
397
469
  undefined,
398
470
  host.hideThinkingBlock,
399
471
  host.getMarkdownThemeWithSettings(),
400
- host.settingsManager.getTimestampFormat(),
472
+ timestampFormat,
401
473
  { startIndex: seg.startIndex, endIndex: seg.endIndex },
402
474
  );
403
475
  host.chatContainer.addChild(comp);
404
- renderedSegments.push({ kind: "text-run", startIndex: seg.startIndex, endIndex: seg.endIndex, component: comp });
476
+ renderedSegments.push({
477
+ kind: "text-run",
478
+ startIndex: seg.startIndex,
479
+ endIndex: seg.endIndex,
480
+ contentType: seg.contentType,
481
+ component: comp,
482
+ });
405
483
  host.streamingComponent = comp;
406
484
  }
407
485
  }
@@ -412,7 +490,9 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
412
490
  for (const seg of renderedSegments) {
413
491
  if (seg.kind === "text-run") {
414
492
  // Find corresponding desired segment to get current endIndex
415
- const d = desired.find((ds) => ds.kind === "text-run" && ds.startIndex === seg.startIndex);
493
+ const d = desired.find(
494
+ (ds) => ds.kind === "text-run" && ds.startIndex === seg.startIndex && ds.contentType === seg.contentType,
495
+ );
416
496
  if (d && d.kind === "text-run" && d.endIndex !== seg.endIndex) {
417
497
  seg.endIndex = d.endIndex;
418
498
  seg.component.setRange({ startIndex: seg.startIndex, endIndex: seg.endIndex });
@@ -483,11 +563,11 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
483
563
  }
484
564
  break;
485
565
 
486
- case "message_end":
487
- if (event.message.role === "user") break;
488
- if (event.message.role === "assistant") {
489
- host.streamingMessage = event.message;
490
- let errorMessage: string | undefined;
566
+ case "message_end":
567
+ if (event.message.role === "user") break;
568
+ if (event.message.role === "assistant") {
569
+ host.streamingMessage = event.message;
570
+ let errorMessage: string | undefined;
491
571
  if (host.streamingMessage.stopReason === "aborted") {
492
572
  const retryAttempt = host.session.retryAttempt;
493
573
  errorMessage = retryAttempt > 0
@@ -496,18 +576,144 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
496
576
  host.streamingMessage.errorMessage = errorMessage;
497
577
  }
498
578
 
499
- const shouldRenderAssistant = hasVisibleAssistantContent(host.streamingMessage)
500
- || (
501
- (host.streamingMessage.stopReason === "aborted" || host.streamingMessage.stopReason === "error")
502
- && !hasAssistantToolBlocks(host.streamingMessage)
503
- );
504
- if (!host.streamingComponent && shouldRenderAssistant) {
505
- host.streamingComponent = new AssistantMessageComponent(
506
- undefined,
507
- host.hideThinkingBlock,
508
- host.getMarkdownThemeWithSettings(),
509
- host.settingsManager.getTimestampFormat(),
510
- );
579
+ const shouldRenderAssistant = hasVisibleAssistantContent(host.streamingMessage)
580
+ || (
581
+ (host.streamingMessage.stopReason === "aborted" || host.streamingMessage.stopReason === "error")
582
+ && !hasAssistantToolBlocks(host.streamingMessage)
583
+ );
584
+
585
+ // The final message_end payload can contain additional text/thinking
586
+ // blocks that never arrived via message_update (e.g. SDK result
587
+ // aggregation). Rebuild this in-flight turn from final content so
588
+ // ranges/components don't keep stale partial indices.
589
+ if (renderedSegments.length > 0) {
590
+ const finalBlocks = host.streamingMessage.content;
591
+ type DesiredSegment =
592
+ | { kind: "text-run"; startIndex: number; endIndex: number; contentType: "text" | "thinking" }
593
+ | { kind: "tool"; contentIndex: number; toolId: string };
594
+ const desired: DesiredSegment[] = [];
595
+ let runStart = -1;
596
+ let runEnd = -1;
597
+ let runType: "text" | "thinking" | undefined;
598
+ const closeRun = () => {
599
+ if (runStart !== -1 && runType) {
600
+ desired.push({ kind: "text-run", startIndex: runStart, endIndex: runEnd, contentType: runType });
601
+ runStart = -1;
602
+ runEnd = -1;
603
+ runType = undefined;
604
+ }
605
+ };
606
+
607
+ for (let i = 0; i < finalBlocks.length; i++) {
608
+ const block = finalBlocks[i] as any;
609
+ const blockType = block?.type === "text" || block?.type === "thinking" ? block.type : undefined;
610
+ const isTextLike = blockType === "text" || blockType === "thinking";
611
+ const isTool = block?.type === "toolCall" || block?.type === "serverToolUse";
612
+
613
+ if (isTextLike) {
614
+ if (runStart === -1) {
615
+ runStart = i;
616
+ runEnd = i;
617
+ runType = blockType;
618
+ } else if (runType !== blockType) {
619
+ closeRun();
620
+ runStart = i;
621
+ runEnd = i;
622
+ runType = blockType;
623
+ } else {
624
+ runEnd = i;
625
+ }
626
+ } else {
627
+ closeRun();
628
+ if (isTool) {
629
+ desired.push({ kind: "tool", contentIndex: i, toolId: block.id });
630
+ }
631
+ }
632
+ }
633
+ closeRun();
634
+
635
+ const toolComponentsById = new Map<string, ToolExecutionComponent>();
636
+ for (const [toolId, component] of host.pendingTools.entries()) {
637
+ toolComponentsById.set(toolId, component);
638
+ }
639
+
640
+ for (const seg of renderedSegments) {
641
+ host.chatContainer.removeChild(seg.component);
642
+ if (seg.kind === "tool") {
643
+ const priorBlocks = host.streamingMessage.content;
644
+ const priorBlock = priorBlocks[seg.contentIndex] as any;
645
+ if (priorBlock?.id && !toolComponentsById.has(priorBlock.id)) {
646
+ toolComponentsById.set(priorBlock.id, seg.component);
647
+ }
648
+ }
649
+ }
650
+ renderedSegments = [];
651
+ host.streamingComponent = undefined;
652
+
653
+ for (const seg of desired) {
654
+ if (seg.kind === "tool") {
655
+ const finalBlock = finalBlocks[seg.contentIndex] as any;
656
+ let component = toolComponentsById.get(seg.toolId);
657
+ if (!component && finalBlock?.id) {
658
+ component = host.pendingTools.get(finalBlock.id);
659
+ }
660
+ if (!component && finalBlock?.type === "toolCall") {
661
+ component = new ToolExecutionComponent(
662
+ finalBlock.name,
663
+ finalBlock.arguments,
664
+ { showImages: host.settingsManager.getShowImages() },
665
+ host.getRegisteredToolDefinition(finalBlock.name),
666
+ host.ui,
667
+ );
668
+ component.setExpanded(host.toolOutputExpanded);
669
+ host.pendingTools.set(finalBlock.id, component);
670
+ toolComponentsById.set(finalBlock.id, component);
671
+ } else if (!component && finalBlock?.type === "serverToolUse") {
672
+ component = new ToolExecutionComponent(
673
+ finalBlock.name,
674
+ finalBlock.input ?? {},
675
+ { showImages: host.settingsManager.getShowImages() },
676
+ undefined,
677
+ host.ui,
678
+ );
679
+ component.setExpanded(host.toolOutputExpanded);
680
+ host.pendingTools.set(finalBlock.id, component);
681
+ toolComponentsById.set(finalBlock.id, component);
682
+ }
683
+ if (component) {
684
+ host.chatContainer.addChild(component);
685
+ renderedSegments.push({ kind: "tool", contentIndex: seg.contentIndex, component });
686
+ }
687
+ continue;
688
+ }
689
+
690
+ const comp = new AssistantMessageComponent(
691
+ undefined,
692
+ host.hideThinkingBlock,
693
+ host.getMarkdownThemeWithSettings(),
694
+ timestampFormat,
695
+ { startIndex: seg.startIndex, endIndex: seg.endIndex },
696
+ );
697
+ comp.updateContent(host.streamingMessage);
698
+ host.chatContainer.addChild(comp);
699
+ renderedSegments.push({
700
+ kind: "text-run",
701
+ startIndex: seg.startIndex,
702
+ endIndex: seg.endIndex,
703
+ contentType: seg.contentType,
704
+ component: comp,
705
+ });
706
+ host.streamingComponent = comp;
707
+ }
708
+ }
709
+
710
+ if (!host.streamingComponent && shouldRenderAssistant) {
711
+ host.streamingComponent = new AssistantMessageComponent(
712
+ undefined,
713
+ host.hideThinkingBlock,
714
+ host.getMarkdownThemeWithSettings(),
715
+ timestampFormat,
716
+ );
511
717
  host.chatContainer.addChild(host.streamingComponent);
512
718
  }
513
719
  if (host.streamingComponent) {
@@ -536,6 +742,7 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
536
742
  host.streamingComponent = undefined;
537
743
  host.streamingMessage = undefined;
538
744
  renderedSegments = [];
745
+ orphanedSegments = [];
539
746
  lastContentLength = 0;
540
747
  // Clear pinned output once the message is finalized in the chat
541
748
  // container — prevents duplicate display when the agent continues
@@ -599,6 +806,7 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
599
806
  host.streamingComponent = undefined;
600
807
  host.streamingMessage = undefined;
601
808
  renderedSegments = [];
809
+ orphanedSegments = [];
602
810
  lastContentLength = 0;
603
811
  host.pendingTools.clear();
604
812
  // Pinned output is only useful while work is actively streaming.
@@ -0,0 +1,44 @@
1
+ import assert from "node:assert/strict";
2
+ import { test } from "node:test";
3
+
4
+ import { buildAssistantReplaySegments } from "./interactive-mode.js";
5
+
6
+ test("buildAssistantReplaySegments preserves tool-first ordering", () => {
7
+ const segments = buildAssistantReplaySegments([
8
+ { type: "toolCall", id: "t1", name: "read", arguments: {} },
9
+ { type: "text", text: "Done." },
10
+ ]);
11
+
12
+ assert.deepEqual(segments, [
13
+ { kind: "tool", contentIndex: 0 },
14
+ { kind: "assistant", startIndex: 1, endIndex: 1 },
15
+ ]);
16
+ });
17
+
18
+ test("buildAssistantReplaySegments preserves interleaved assistant-tool-assistant runs", () => {
19
+ const segments = buildAssistantReplaySegments([
20
+ { type: "text", text: "Let me check." },
21
+ { type: "serverToolUse", id: "s1", name: "mcp__fs__glob", input: {} },
22
+ { type: "thinking", thinking: "Tool result looks good." },
23
+ { type: "text", text: "Here is the answer." },
24
+ ]);
25
+
26
+ assert.deepEqual(segments, [
27
+ { kind: "assistant", startIndex: 0, endIndex: 0 },
28
+ { kind: "tool", contentIndex: 1 },
29
+ { kind: "assistant", startIndex: 2, endIndex: 3 },
30
+ ]);
31
+ });
32
+
33
+ test("buildAssistantReplaySegments ignores non-rendered non-tool blocks", () => {
34
+ const segments = buildAssistantReplaySegments([
35
+ { type: "text", text: "before" },
36
+ { type: "webSearchResult", toolUseId: "s1", content: {} },
37
+ { type: "text", text: "after" },
38
+ ]);
39
+
40
+ assert.deepEqual(segments, [
41
+ { kind: "assistant", startIndex: 0, endIndex: 0 },
42
+ { kind: "assistant", startIndex: 2, endIndex: 2 },
43
+ ]);
44
+ });