gsd-pi 2.74.0-dev.14c45ac → 2.74.0-dev.20f79a8

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 (254) 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/ecosystem/gsd-extension-api.js +144 -0
  5. package/dist/resources/extensions/gsd/ecosystem/loader.js +145 -0
  6. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  7. package/dist/web/standalone/.next/BUILD_ID +1 -1
  8. package/dist/web/standalone/.next/app-path-routes-manifest.json +16 -16
  9. package/dist/web/standalone/.next/build-manifest.json +2 -2
  10. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  11. package/dist/web/standalone/.next/required-server-files.json +1 -1
  12. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  13. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  14. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  15. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  16. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  17. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  18. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  19. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  20. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  21. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  22. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  23. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  24. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  25. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  26. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  27. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  28. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  29. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +1 -1
  30. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +1 -1
  31. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +1 -1
  32. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +1 -1
  33. package/dist/web/standalone/.next/server/app/index.html +1 -1
  34. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  35. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  36. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app-paths-manifest.json +16 -16
  41. package/dist/web/standalone/.next/server/chunks/6897.js +3 -3
  42. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  43. package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
  44. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  45. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  46. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  47. package/dist/web/standalone/server.js +1 -1
  48. package/package.json +1 -1
  49. package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
  50. package/packages/pi-ai/dist/index.d.ts +1 -9
  51. package/packages/pi-ai/dist/index.d.ts.map +1 -1
  52. package/packages/pi-ai/dist/index.js +1 -9
  53. package/packages/pi-ai/dist/index.js.map +1 -1
  54. package/packages/pi-ai/dist/models/capability-patches.d.ts +19 -0
  55. package/packages/pi-ai/dist/models/capability-patches.d.ts.map +1 -0
  56. package/packages/pi-ai/dist/models/capability-patches.js +36 -0
  57. package/packages/pi-ai/dist/models/capability-patches.js.map +1 -0
  58. package/packages/pi-ai/dist/{models.custom.d.ts → models/custom.d.ts} +1 -1
  59. package/packages/pi-ai/dist/models/custom.d.ts.map +1 -0
  60. package/packages/pi-ai/dist/{models.custom.js → models/custom.js} +4 -4
  61. package/packages/pi-ai/dist/models/custom.js.map +1 -0
  62. package/packages/pi-ai/dist/models/generated/amazon-bedrock.d.ts +1482 -0
  63. package/packages/pi-ai/dist/models/generated/amazon-bedrock.d.ts.map +1 -0
  64. package/packages/pi-ai/dist/models/generated/amazon-bedrock.js +1484 -0
  65. package/packages/pi-ai/dist/models/generated/amazon-bedrock.js.map +1 -0
  66. package/packages/pi-ai/dist/models/generated/anthropic.d.ts +377 -0
  67. package/packages/pi-ai/dist/models/generated/anthropic.d.ts.map +1 -0
  68. package/packages/pi-ai/dist/models/generated/anthropic.js +379 -0
  69. package/packages/pi-ai/dist/models/generated/anthropic.js.map +1 -0
  70. package/packages/pi-ai/dist/models/generated/azure-openai-responses.d.ts +700 -0
  71. package/packages/pi-ai/dist/models/generated/azure-openai-responses.d.ts.map +1 -0
  72. package/packages/pi-ai/dist/models/generated/azure-openai-responses.js +702 -0
  73. package/packages/pi-ai/dist/models/generated/azure-openai-responses.js.map +1 -0
  74. package/packages/pi-ai/dist/models/generated/cerebras.d.ts +71 -0
  75. package/packages/pi-ai/dist/models/generated/cerebras.d.ts.map +1 -0
  76. package/packages/pi-ai/dist/models/generated/cerebras.js +73 -0
  77. package/packages/pi-ai/dist/models/generated/cerebras.js.map +1 -0
  78. package/packages/pi-ai/dist/models/generated/github-copilot.d.ts +590 -0
  79. package/packages/pi-ai/dist/models/generated/github-copilot.d.ts.map +1 -0
  80. package/packages/pi-ai/dist/models/generated/github-copilot.js +444 -0
  81. package/packages/pi-ai/dist/models/generated/github-copilot.js.map +1 -0
  82. package/packages/pi-ai/dist/models/generated/google-antigravity.d.ts +156 -0
  83. package/packages/pi-ai/dist/models/generated/google-antigravity.d.ts.map +1 -0
  84. package/packages/pi-ai/dist/models/generated/google-antigravity.js +158 -0
  85. package/packages/pi-ai/dist/models/generated/google-antigravity.js.map +1 -0
  86. package/packages/pi-ai/dist/models/generated/google-gemini-cli.d.ts +105 -0
  87. package/packages/pi-ai/dist/models/generated/google-gemini-cli.d.ts.map +1 -0
  88. package/packages/pi-ai/dist/models/generated/google-gemini-cli.js +107 -0
  89. package/packages/pi-ai/dist/models/generated/google-gemini-cli.js.map +1 -0
  90. package/packages/pi-ai/dist/models/generated/google-vertex.d.ts +207 -0
  91. package/packages/pi-ai/dist/models/generated/google-vertex.d.ts.map +1 -0
  92. package/packages/pi-ai/dist/models/generated/google-vertex.js +209 -0
  93. package/packages/pi-ai/dist/models/generated/google-vertex.js.map +1 -0
  94. package/packages/pi-ai/dist/models/generated/google.d.ts +462 -0
  95. package/packages/pi-ai/dist/models/generated/google.d.ts.map +1 -0
  96. package/packages/pi-ai/dist/models/generated/google.js +464 -0
  97. package/packages/pi-ai/dist/models/generated/google.js.map +1 -0
  98. package/packages/pi-ai/dist/models/generated/groq.d.ts +309 -0
  99. package/packages/pi-ai/dist/models/generated/groq.d.ts.map +1 -0
  100. package/packages/pi-ai/dist/models/generated/groq.js +311 -0
  101. package/packages/pi-ai/dist/models/generated/groq.js.map +1 -0
  102. package/packages/pi-ai/dist/models/generated/huggingface.d.ts +383 -0
  103. package/packages/pi-ai/dist/models/generated/huggingface.d.ts.map +1 -0
  104. package/packages/pi-ai/dist/models/generated/huggingface.js +347 -0
  105. package/packages/pi-ai/dist/models/generated/huggingface.js.map +1 -0
  106. package/packages/pi-ai/dist/{models.generated.d.ts → models/generated/index.d.ts} +1 -1
  107. package/packages/pi-ai/dist/{models.generated.d.ts.map → models/generated/index.d.ts.map} +1 -1
  108. package/packages/pi-ai/dist/models/generated/index.js +51 -0
  109. package/packages/pi-ai/dist/models/generated/index.js.map +1 -0
  110. package/packages/pi-ai/dist/models/generated/kimi-coding.d.ts +37 -0
  111. package/packages/pi-ai/dist/models/generated/kimi-coding.d.ts.map +1 -0
  112. package/packages/pi-ai/dist/models/generated/kimi-coding.js +39 -0
  113. package/packages/pi-ai/dist/models/generated/kimi-coding.js.map +1 -0
  114. package/packages/pi-ai/dist/models/generated/minimax-cn.d.ts +105 -0
  115. package/packages/pi-ai/dist/models/generated/minimax-cn.d.ts.map +1 -0
  116. package/packages/pi-ai/dist/models/generated/minimax-cn.js +107 -0
  117. package/packages/pi-ai/dist/models/generated/minimax-cn.js.map +1 -0
  118. package/packages/pi-ai/dist/models/generated/minimax.d.ts +105 -0
  119. package/packages/pi-ai/dist/models/generated/minimax.d.ts.map +1 -0
  120. package/packages/pi-ai/dist/models/generated/minimax.js +107 -0
  121. package/packages/pi-ai/dist/models/generated/minimax.js.map +1 -0
  122. package/packages/pi-ai/dist/models/generated/mistral.d.ts +445 -0
  123. package/packages/pi-ai/dist/models/generated/mistral.d.ts.map +1 -0
  124. package/packages/pi-ai/dist/models/generated/mistral.js +447 -0
  125. package/packages/pi-ai/dist/models/generated/mistral.js.map +1 -0
  126. package/packages/pi-ai/dist/models/generated/openai-codex.d.ts +139 -0
  127. package/packages/pi-ai/dist/models/generated/openai-codex.d.ts.map +1 -0
  128. package/packages/pi-ai/dist/models/generated/openai-codex.js +141 -0
  129. package/packages/pi-ai/dist/models/generated/openai-codex.js.map +1 -0
  130. package/packages/pi-ai/dist/models/generated/openai.d.ts +700 -0
  131. package/packages/pi-ai/dist/models/generated/openai.d.ts.map +1 -0
  132. package/packages/pi-ai/dist/models/generated/openai.js +702 -0
  133. package/packages/pi-ai/dist/models/generated/openai.js.map +1 -0
  134. package/packages/pi-ai/dist/models/generated/opencode-go.d.ts +122 -0
  135. package/packages/pi-ai/dist/models/generated/opencode-go.d.ts.map +1 -0
  136. package/packages/pi-ai/dist/models/generated/opencode-go.js +124 -0
  137. package/packages/pi-ai/dist/models/generated/opencode-go.js.map +1 -0
  138. package/packages/pi-ai/dist/models/generated/opencode.d.ts +530 -0
  139. package/packages/pi-ai/dist/models/generated/opencode.d.ts.map +1 -0
  140. package/packages/pi-ai/dist/models/generated/opencode.js +532 -0
  141. package/packages/pi-ai/dist/models/generated/opencode.js.map +1 -0
  142. package/packages/pi-ai/dist/models/generated/openrouter.d.ts +4270 -0
  143. package/packages/pi-ai/dist/models/generated/openrouter.d.ts.map +1 -0
  144. package/packages/pi-ai/dist/models/generated/openrouter.js +4272 -0
  145. package/packages/pi-ai/dist/models/generated/openrouter.js.map +1 -0
  146. package/packages/pi-ai/dist/models/generated/vercel-ai-gateway.d.ts +2604 -0
  147. package/packages/pi-ai/dist/models/generated/vercel-ai-gateway.d.ts.map +1 -0
  148. package/packages/pi-ai/dist/models/generated/vercel-ai-gateway.js +2606 -0
  149. package/packages/pi-ai/dist/models/generated/vercel-ai-gateway.js.map +1 -0
  150. package/packages/pi-ai/dist/models/generated/xai.d.ts +411 -0
  151. package/packages/pi-ai/dist/models/generated/xai.d.ts.map +1 -0
  152. package/packages/pi-ai/dist/models/generated/xai.js +413 -0
  153. package/packages/pi-ai/dist/models/generated/xai.js.map +1 -0
  154. package/packages/pi-ai/dist/models/generated/zai.d.ts +276 -0
  155. package/packages/pi-ai/dist/models/generated/zai.d.ts.map +1 -0
  156. package/packages/pi-ai/dist/models/generated/zai.js +239 -0
  157. package/packages/pi-ai/dist/models/generated/zai.js.map +1 -0
  158. package/packages/pi-ai/dist/models/index.d.ts +27 -0
  159. package/packages/pi-ai/dist/models/index.d.ts.map +1 -0
  160. package/packages/pi-ai/dist/models/index.js +80 -0
  161. package/packages/pi-ai/dist/models/index.js.map +1 -0
  162. package/packages/pi-ai/dist/models.d.ts +1 -36
  163. package/packages/pi-ai/dist/models.d.ts.map +1 -1
  164. package/packages/pi-ai/dist/models.generated.test.js +1 -2
  165. package/packages/pi-ai/dist/models.generated.test.js.map +1 -1
  166. package/packages/pi-ai/dist/models.js +3 -112
  167. package/packages/pi-ai/dist/models.js.map +1 -1
  168. package/packages/pi-ai/dist/models.test.js +6 -5
  169. package/packages/pi-ai/dist/models.test.js.map +1 -1
  170. package/packages/pi-ai/scripts/generate-models.ts +74 -40
  171. package/packages/pi-ai/src/index.ts +1 -9
  172. package/packages/pi-ai/src/models/capability-patches.ts +40 -0
  173. package/packages/pi-ai/src/{models.custom.ts → models/custom.ts} +4 -4
  174. package/packages/pi-ai/src/models/generated/amazon-bedrock.ts +1486 -0
  175. package/packages/pi-ai/src/models/generated/anthropic.ts +381 -0
  176. package/packages/pi-ai/src/models/generated/azure-openai-responses.ts +704 -0
  177. package/packages/pi-ai/src/models/generated/cerebras.ts +75 -0
  178. package/packages/pi-ai/src/models/generated/github-copilot.ts +446 -0
  179. package/packages/pi-ai/src/models/generated/google-antigravity.ts +160 -0
  180. package/packages/pi-ai/src/models/generated/google-gemini-cli.ts +109 -0
  181. package/packages/pi-ai/src/models/generated/google-vertex.ts +211 -0
  182. package/packages/pi-ai/src/models/generated/google.ts +466 -0
  183. package/packages/pi-ai/src/models/generated/groq.ts +313 -0
  184. package/packages/pi-ai/src/models/generated/huggingface.ts +349 -0
  185. package/packages/pi-ai/src/models/generated/index.ts +52 -0
  186. package/packages/pi-ai/src/models/generated/kimi-coding.ts +41 -0
  187. package/packages/pi-ai/src/models/generated/minimax-cn.ts +109 -0
  188. package/packages/pi-ai/src/models/generated/minimax.ts +109 -0
  189. package/packages/pi-ai/src/models/generated/mistral.ts +449 -0
  190. package/packages/pi-ai/src/models/generated/openai-codex.ts +143 -0
  191. package/packages/pi-ai/src/models/generated/openai.ts +704 -0
  192. package/packages/pi-ai/src/models/generated/opencode-go.ts +126 -0
  193. package/packages/pi-ai/src/models/generated/opencode.ts +534 -0
  194. package/packages/pi-ai/src/models/generated/openrouter.ts +4274 -0
  195. package/packages/pi-ai/src/models/generated/vercel-ai-gateway.ts +2608 -0
  196. package/packages/pi-ai/src/models/generated/xai.ts +415 -0
  197. package/packages/pi-ai/src/models/generated/zai.ts +241 -0
  198. package/packages/pi-ai/src/models/index.ts +106 -0
  199. package/packages/pi-ai/src/models.generated.test.ts +1 -2
  200. package/packages/pi-ai/src/models.test.ts +6 -5
  201. package/packages/pi-ai/src/models.ts +3 -153
  202. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  203. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  204. package/packages/pi-coding-agent/dist/core/agent-session.js +8 -2
  205. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  206. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +284 -10
  207. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
  208. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts +1 -0
  209. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  210. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js +23 -9
  211. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
  212. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts +11 -0
  213. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts.map +1 -0
  214. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js +47 -0
  215. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js.map +1 -0
  216. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  217. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js +22 -22
  218. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js.map +1 -1
  219. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  220. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +171 -24
  221. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  222. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-ordering.test.d.ts +2 -0
  223. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-ordering.test.d.ts.map +1 -0
  224. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-ordering.test.js +38 -0
  225. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-ordering.test.js.map +1 -0
  226. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +13 -0
  227. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  228. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +53 -6
  229. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  230. package/packages/pi-coding-agent/src/core/agent-session.ts +12 -6
  231. package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +357 -10
  232. package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +25 -10
  233. package/packages/pi-coding-agent/src/modes/interactive/components/chat-frame.ts +67 -0
  234. package/packages/pi-coding-agent/src/modes/interactive/components/user-message.ts +23 -26
  235. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +232 -47
  236. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-ordering.test.ts +44 -0
  237. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +73 -6
  238. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  239. package/src/resources/extensions/gsd/auto-post-unit.ts +7 -3
  240. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +15 -1
  241. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +56 -3
  242. package/src/resources/extensions/gsd/ecosystem/gsd-extension-api.ts +228 -0
  243. package/src/resources/extensions/gsd/ecosystem/loader.ts +201 -0
  244. package/src/resources/extensions/gsd/tests/health-widget.test.ts +1 -1
  245. package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +1 -1
  246. package/src/resources/extensions/gsd/types.ts +13 -0
  247. package/src/resources/extensions/gsd/workflow-logger.ts +2 -1
  248. package/packages/pi-ai/dist/models.custom.d.ts.map +0 -1
  249. package/packages/pi-ai/dist/models.custom.js.map +0 -1
  250. package/packages/pi-ai/dist/models.generated.js +0 -14343
  251. package/packages/pi-ai/dist/models.generated.js.map +0 -1
  252. package/packages/pi-ai/src/models.generated.ts +0 -14345
  253. /package/dist/web/standalone/.next/static/{ZMKM0OI0CrTgzKWbgfPOg → ZDXqgjuglsRoazETSKw1J}/_buildManifest.js +0 -0
  254. /package/dist/web/standalone/.next/static/{ZMKM0OI0CrTgzKWbgfPOg → ZDXqgjuglsRoazETSKw1J}/_ssgManifest.js +0 -0
@@ -16,7 +16,13 @@ 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[] = [];
@@ -89,6 +95,7 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
89
95
  }
90
96
 
91
97
  host.footer.invalidate();
98
+ const timestampFormat = host.settingsManager.getTimestampFormat();
92
99
 
93
100
  // Reset content index tracker and pinned state when a new assistant message starts
94
101
  if (event.type === "message_start" && event.message.role === "assistant") {
@@ -232,7 +239,10 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
232
239
  // content (#4144 regression). Prior sub-turn children stay in
233
240
  // chatContainer as frozen history; new segments append after them.
234
241
  if (contentBlocks.length < lastContentLength) {
235
- orphanedSegments = [...renderedSegments];
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];
236
246
  renderedSegments = [];
237
247
  lastPinnedText = "";
238
248
  lastProcessedContentIndex = 0;
@@ -319,44 +329,81 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
319
329
  }
320
330
  return false;
321
331
  });
322
- const shouldDropPreToolText = isClaudeCodeProvider && hasMcpToolBlock;
323
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;
324
345
  type DesiredSegment =
325
- | { kind: "text-run"; startIndex: number; endIndex: number }
346
+ | { kind: "text-run"; startIndex: number; endIndex: number; contentType: "text" | "thinking" }
326
347
  | { kind: "tool"; contentIndex: number; toolId: string };
327
- const desired: DesiredSegment[] = [];
328
- let runStart = -1;
329
- for (let i = 0; i < blocks.length; i++) {
330
- const b = blocks[i];
331
- const isText = b.type === "text" || b.type === "thinking";
332
- const isTool = b.type === "toolCall" || b.type === "serverToolUse";
333
- if (isText) {
334
- if (shouldDropPreToolText && firstToolIdx >= 0 && i < firstToolIdx) {
335
- 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;
336
389
  }
337
- if (runStart === -1) runStart = i;
338
390
  } else {
339
- if (runStart !== -1) {
340
- desired.push({ kind: "text-run", startIndex: runStart, endIndex: i - 1 });
341
- runStart = -1;
342
- }
391
+ closeRun();
343
392
  if (isTool) {
344
393
  desired.push({ kind: "tool", contentIndex: i, toolId: b.id });
345
394
  }
346
395
  }
347
396
  }
348
- if (runStart !== -1) {
349
- desired.push({ kind: "text-run", startIndex: runStart, endIndex: blocks.length - 1 });
350
- }
397
+ closeRun();
351
398
 
352
399
  // Claude Code MCP can emit provisional pre-tool prose that gets
353
400
  // superseded by post-tool output. Prune stale text-run segments so
354
401
  // the final assistant output remains below tool output.
355
- if (shouldDropPreToolText && firstToolIdx >= 0) {
402
+ if (shouldDropPreToolProse && firstToolIdx >= 0) {
356
403
  if (orphanedSegments.length > 0) {
357
404
  const remainingOrphans: RenderedSegment[] = [];
358
405
  for (const orphan of orphanedSegments) {
359
- if (orphan.kind === "text-run") {
406
+ if (orphan.kind === "text-run" && orphan.contentType === "text") {
360
407
  host.chatContainer.removeChild(orphan.component);
361
408
  if (host.streamingComponent === orphan.component) {
362
409
  host.streamingComponent = undefined;
@@ -367,10 +414,10 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
367
414
  }
368
415
  orphanedSegments = remainingOrphans;
369
416
  }
370
- const desiredTextStarts = new Set(
417
+ const desiredTextKeys = new Set(
371
418
  desired
372
419
  .filter((seg): seg is Extract<DesiredSegment, { kind: "text-run" }> => seg.kind === "text-run")
373
- .map((seg) => seg.startIndex),
420
+ .map((seg) => `${seg.contentType}:${seg.startIndex}`),
374
421
  );
375
422
  const desiredToolIndices = new Set(
376
423
  desired
@@ -379,7 +426,11 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
379
426
  );
380
427
  const nextRendered: RenderedSegment[] = [];
381
428
  for (const seg of renderedSegments) {
382
- 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
+ ) {
383
434
  host.chatContainer.removeChild(seg.component);
384
435
  if (host.streamingComponent === seg.component) {
385
436
  host.streamingComponent = undefined;
@@ -411,18 +462,24 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
411
462
  } else {
412
463
  // text-run segment
413
464
  const existing = renderedSegments.find(
414
- (s) => s.kind === "text-run" && s.startIndex === seg.startIndex,
465
+ (s) => s.kind === "text-run" && s.startIndex === seg.startIndex && s.contentType === seg.contentType,
415
466
  );
416
467
  if (!existing) {
417
468
  const comp = new AssistantMessageComponent(
418
469
  undefined,
419
470
  host.hideThinkingBlock,
420
471
  host.getMarkdownThemeWithSettings(),
421
- host.settingsManager.getTimestampFormat(),
472
+ timestampFormat,
422
473
  { startIndex: seg.startIndex, endIndex: seg.endIndex },
423
474
  );
424
475
  host.chatContainer.addChild(comp);
425
- 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
+ });
426
483
  host.streamingComponent = comp;
427
484
  }
428
485
  }
@@ -433,7 +490,9 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
433
490
  for (const seg of renderedSegments) {
434
491
  if (seg.kind === "text-run") {
435
492
  // Find corresponding desired segment to get current endIndex
436
- 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
+ );
437
496
  if (d && d.kind === "text-run" && d.endIndex !== seg.endIndex) {
438
497
  seg.endIndex = d.endIndex;
439
498
  seg.component.setRange({ startIndex: seg.startIndex, endIndex: seg.endIndex });
@@ -504,11 +563,11 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
504
563
  }
505
564
  break;
506
565
 
507
- case "message_end":
508
- if (event.message.role === "user") break;
509
- if (event.message.role === "assistant") {
510
- host.streamingMessage = event.message;
511
- 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;
512
571
  if (host.streamingMessage.stopReason === "aborted") {
513
572
  const retryAttempt = host.session.retryAttempt;
514
573
  errorMessage = retryAttempt > 0
@@ -517,18 +576,144 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
517
576
  host.streamingMessage.errorMessage = errorMessage;
518
577
  }
519
578
 
520
- const shouldRenderAssistant = hasVisibleAssistantContent(host.streamingMessage)
521
- || (
522
- (host.streamingMessage.stopReason === "aborted" || host.streamingMessage.stopReason === "error")
523
- && !hasAssistantToolBlocks(host.streamingMessage)
524
- );
525
- if (!host.streamingComponent && shouldRenderAssistant) {
526
- host.streamingComponent = new AssistantMessageComponent(
527
- undefined,
528
- host.hideThinkingBlock,
529
- host.getMarkdownThemeWithSettings(),
530
- host.settingsManager.getTimestampFormat(),
531
- );
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
+ );
532
717
  host.chatContainer.addChild(host.streamingComponent);
533
718
  }
534
719
  if (host.streamingComponent) {
@@ -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
+ });
@@ -127,6 +127,45 @@ function isExpandable(obj: unknown): obj is Expandable {
127
127
  return typeof obj === "object" && obj !== null && "setExpanded" in obj && typeof obj.setExpanded === "function";
128
128
  }
129
129
 
130
+ export type AssistantReplaySegment =
131
+ | { kind: "assistant"; startIndex: number; endIndex: number }
132
+ | { kind: "tool"; contentIndex: number };
133
+
134
+ /**
135
+ * Build replay segments for historical assistant messages so rebuild paths
136
+ * preserve the original content[] ordering between assistant prose and tools.
137
+ */
138
+ export function buildAssistantReplaySegments(contentBlocks: Array<any>): AssistantReplaySegment[] {
139
+ const segments: AssistantReplaySegment[] = [];
140
+ let runStart = -1;
141
+
142
+ for (let i = 0; i < contentBlocks.length; i++) {
143
+ const block = contentBlocks[i];
144
+ const isAssistantText = block?.type === "text" || block?.type === "thinking";
145
+ const isTool = block?.type === "toolCall" || block?.type === "serverToolUse";
146
+
147
+ if (isAssistantText) {
148
+ if (runStart === -1) runStart = i;
149
+ continue;
150
+ }
151
+
152
+ if (runStart !== -1) {
153
+ segments.push({ kind: "assistant", startIndex: runStart, endIndex: i - 1 });
154
+ runStart = -1;
155
+ }
156
+
157
+ if (isTool) {
158
+ segments.push({ kind: "tool", contentIndex: i });
159
+ }
160
+ }
161
+
162
+ if (runStart !== -1) {
163
+ segments.push({ kind: "assistant", startIndex: runStart, endIndex: contentBlocks.length - 1 });
164
+ }
165
+
166
+ return segments;
167
+ }
168
+
130
169
  type CompactionQueuedMessage = {
131
170
  text: string;
132
171
  mode: "steer" | "followUp";
@@ -2078,6 +2117,7 @@ export class InteractiveMode {
2078
2117
  }
2079
2118
 
2080
2119
  private addMessageToChat(message: AgentMessage, options?: { populateHistory?: boolean }): void {
2120
+ const timestampFormat = this.settingsManager.getTimestampFormat();
2081
2121
  switch (message.role) {
2082
2122
  case "bashExecution": {
2083
2123
  const component = new BashExecutionComponent(message.command, this.ui, message.excludeFromContext);
@@ -2135,12 +2175,12 @@ export class InteractiveMode {
2135
2175
  skillBlock.userMessage,
2136
2176
  this.getMarkdownThemeWithSettings(),
2137
2177
  message.timestamp,
2138
- this.settingsManager.getTimestampFormat(),
2178
+ timestampFormat,
2139
2179
  );
2140
2180
  this.chatContainer.addChild(userComponent);
2141
2181
  }
2142
2182
  } else {
2143
- const userComponent = new UserMessageComponent(textContent, this.getMarkdownThemeWithSettings(), message.timestamp, this.settingsManager.getTimestampFormat());
2183
+ const userComponent = new UserMessageComponent(textContent, this.getMarkdownThemeWithSettings(), message.timestamp, timestampFormat);
2144
2184
  this.chatContainer.addChild(userComponent);
2145
2185
  }
2146
2186
  if (options?.populateHistory) {
@@ -2154,7 +2194,7 @@ export class InteractiveMode {
2154
2194
  message,
2155
2195
  this.hideThinkingBlock,
2156
2196
  this.getMarkdownThemeWithSettings(),
2157
- this.settingsManager.getTimestampFormat(),
2197
+ timestampFormat,
2158
2198
  );
2159
2199
  this.chatContainer.addChild(assistantComponent);
2160
2200
  break;
@@ -2192,6 +2232,7 @@ export class InteractiveMode {
2192
2232
  options: { updateFooter?: boolean; populateHistory?: boolean } = {},
2193
2233
  ): void {
2194
2234
  this.pendingTools.clear();
2235
+ const timestampFormat = this.settingsManager.getTimestampFormat();
2195
2236
 
2196
2237
  if (options.updateFooter) {
2197
2238
  this.footer.invalidate();
@@ -2201,9 +2242,30 @@ export class InteractiveMode {
2201
2242
  for (const message of sessionContext.messages) {
2202
2243
  // Assistant messages need special handling for tool calls
2203
2244
  if (message.role === "assistant") {
2204
- this.addMessageToChat(message);
2205
- // Render tool call components
2206
- for (const content of message.content) {
2245
+ const hasToolBlocks = message.content.some((c) => c.type === "toolCall" || c.type === "serverToolUse");
2246
+ if (!hasToolBlocks) {
2247
+ this.addMessageToChat(message);
2248
+ continue;
2249
+ }
2250
+
2251
+ const assistantSegments: AssistantMessageComponent[] = [];
2252
+ const replaySegments = buildAssistantReplaySegments(message.content);
2253
+
2254
+ for (const segment of replaySegments) {
2255
+ if (segment.kind === "assistant") {
2256
+ const assistantComponent = new AssistantMessageComponent(
2257
+ message,
2258
+ this.hideThinkingBlock,
2259
+ this.getMarkdownThemeWithSettings(),
2260
+ timestampFormat,
2261
+ { startIndex: segment.startIndex, endIndex: segment.endIndex },
2262
+ );
2263
+ this.chatContainer.addChild(assistantComponent);
2264
+ assistantSegments.push(assistantComponent);
2265
+ continue;
2266
+ }
2267
+
2268
+ const content = message.content[segment.contentIndex];
2207
2269
  if (content.type === "toolCall") {
2208
2270
  const component = new ToolExecutionComponent(
2209
2271
  content.name,
@@ -2259,6 +2321,11 @@ export class InteractiveMode {
2259
2321
  }
2260
2322
  }
2261
2323
  }
2324
+
2325
+ // Match streaming-mode behavior: show metadata once on the final
2326
+ // assistant prose segment for this message.
2327
+ const lastAssistantSegment = assistantSegments[assistantSegments.length - 1];
2328
+ lastAssistantSegment?.setShowMetadata(true);
2262
2329
  } else if (message.role === "toolResult") {
2263
2330
  // Match tool results to pending tool components
2264
2331
  const component = this.pendingTools.get(message.toolCallId);