@opengsd/gsd-pi 1.3.0-dev.65546769 → 1.3.0-dev.72e3af2a

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 (357) hide show
  1. package/dist/resources/.managed-resources-content-hash +1 -1
  2. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +11 -2
  3. package/dist/resources/extensions/google-cli/stream-adapter.js +82 -15
  4. package/dist/resources/extensions/gsd/artifact-verification.js +427 -0
  5. package/dist/resources/extensions/gsd/auto/orchestrator.js +12 -3
  6. package/dist/resources/extensions/gsd/auto/session.js +3 -0
  7. package/dist/resources/extensions/gsd/auto-artifact-paths.js +28 -1
  8. package/dist/resources/extensions/gsd/auto-dispatch.js +20 -19
  9. package/dist/resources/extensions/gsd/auto-prompts.js +26 -11
  10. package/dist/resources/extensions/gsd/auto-recovery.js +6 -507
  11. package/dist/resources/extensions/gsd/auto-runtime-state.js +4 -5
  12. package/dist/resources/extensions/gsd/auto-timeout-recovery.js +3 -3
  13. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +103 -13
  14. package/dist/resources/extensions/gsd/bootstrap/core-session-tools.js +38 -0
  15. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +6 -1
  16. package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +2 -0
  17. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +10 -19
  18. package/dist/resources/extensions/gsd/bootstrap/system-context.js +46 -19
  19. package/dist/resources/extensions/gsd/bootstrap/tool-call-loop-guard.js +68 -10
  20. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +1 -1
  21. package/dist/resources/extensions/gsd/commands-context.js +19 -1
  22. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +16 -10
  23. package/dist/resources/extensions/gsd/commands-worktree.js +12 -10
  24. package/dist/resources/extensions/gsd/dashboard-overlay.js +32 -3
  25. package/dist/resources/extensions/gsd/db/queries.js +60 -0
  26. package/dist/resources/extensions/gsd/db-workspace.js +55 -3
  27. package/dist/resources/extensions/gsd/doctor-providers.js +92 -8
  28. package/dist/resources/extensions/gsd/exec-sandbox.js +45 -9
  29. package/dist/resources/extensions/gsd/forensics.js +2 -32
  30. package/dist/resources/extensions/gsd/git-service.js +4 -4
  31. package/dist/resources/extensions/gsd/guided-flow-queue.js +66 -5
  32. package/dist/resources/extensions/gsd/health-widget.js +55 -29
  33. package/dist/resources/extensions/gsd/layout-policy.js +3 -1
  34. package/dist/resources/extensions/gsd/markdown-renderer.js +8 -9
  35. package/dist/resources/extensions/gsd/memory-consolidation-scanner.js +44 -21
  36. package/dist/resources/extensions/gsd/migration-auto-check.js +22 -0
  37. package/dist/resources/extensions/gsd/milestone-ids.js +32 -2
  38. package/dist/resources/extensions/gsd/milestone-implementation-evidence.js +26 -20
  39. package/dist/resources/extensions/gsd/prompts/code-review.md +6 -4
  40. package/dist/resources/extensions/gsd/quick.js +45 -2
  41. package/dist/resources/extensions/gsd/session-forensics.js +11 -1
  42. package/dist/resources/extensions/gsd/skills/gsd-headless/references/commands.md +1 -1
  43. package/dist/resources/extensions/gsd/state/derive/cache.js +28 -0
  44. package/dist/resources/extensions/gsd/state/derive/db-open.js +39 -0
  45. package/dist/resources/extensions/gsd/state/derive/from-db.js +452 -0
  46. package/dist/resources/extensions/gsd/state/derive/index.js +75 -0
  47. package/dist/resources/extensions/gsd/state/derive/interrupted-work.js +21 -0
  48. package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-render.js +45 -2
  49. package/dist/resources/extensions/gsd/state-reconciliation/index.js +48 -23
  50. package/dist/resources/extensions/gsd/state-reconciliation/registry.js +32 -28
  51. package/dist/resources/extensions/gsd/state.js +12 -611
  52. package/dist/resources/extensions/gsd/tools/complete-slice.js +2 -2
  53. package/dist/resources/extensions/gsd/tools/complete-task.js +43 -14
  54. package/dist/resources/extensions/gsd/tools/exec-tool.js +7 -2
  55. package/dist/resources/extensions/gsd/unit-context-composer.js +23 -7
  56. package/dist/resources/extensions/gsd/unit-registry.js +32 -4
  57. package/dist/resources/extensions/gsd/unmerged-milestone-guard.js +33 -3
  58. package/dist/resources/extensions/gsd/validation-block-guard.js +9 -4
  59. package/dist/resources/extensions/gsd/workflow-projections.js +19 -14
  60. package/dist/resources/extensions/gsd/workspace-git-preflight.js +30 -1
  61. package/dist/resources/extensions/gsd/worktree-manager.js +44 -2
  62. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  63. package/dist/web/standalone/.next/BUILD_ID +1 -1
  64. package/dist/web/standalone/.next/app-path-routes-manifest.json +10 -10
  65. package/dist/web/standalone/.next/build-manifest.json +3 -3
  66. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  67. package/dist/web/standalone/.next/react-loadable-manifest.json +9 -9
  68. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  69. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  70. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  78. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  79. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  86. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  87. package/dist/web/standalone/.next/server/app/index.html +1 -1
  88. package/dist/web/standalone/.next/server/app/index.rsc +2 -2
  89. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  90. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +2 -2
  91. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  94. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  95. package/dist/web/standalone/.next/server/app-paths-manifest.json +10 -10
  96. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  97. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  98. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  99. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  100. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  101. package/dist/web/standalone/.next/static/chunks/{2659.b7b129ee6a769448.js → 2659.58e950899a9bb82f.js} +1 -1
  102. package/dist/web/standalone/.next/static/chunks/2772.a7c1fcc69a4685ef.js +1 -0
  103. package/dist/web/standalone/.next/static/chunks/{3616.3c60753b8ffcbd2e.js → 3616.61a2af74bb8833c8.js} +1 -1
  104. package/dist/web/standalone/.next/static/chunks/{4283.8e446784528ed9dc.js → 4283.d0d9e0a955e441cb.js} +1 -1
  105. package/dist/web/standalone/.next/static/chunks/{5826.a46ecdd1cfe8dabc.js → 5826.5421d66c72b9f34e.js} +1 -1
  106. package/dist/web/standalone/.next/static/chunks/{8785.481aa5869991b760.js → 8785.e29b3134cab1d153.js} +1 -1
  107. package/dist/web/standalone/.next/static/chunks/8937.640dc9c2aaa1dfad.js +10 -0
  108. package/dist/web/standalone/.next/static/chunks/app/{page-6644fc6ee8ca1247.js → page-72a856634ad14c10.js} +1 -1
  109. package/dist/web/standalone/.next/static/chunks/webpack-9c401904f87ded16.js +1 -0
  110. package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
  111. package/package.json +1 -1
  112. package/packages/cloud-mcp-gateway/package.json +2 -2
  113. package/packages/contracts/dist/workflow.d.ts +1 -0
  114. package/packages/contracts/dist/workflow.d.ts.map +1 -1
  115. package/packages/contracts/dist/workflow.js +2 -0
  116. package/packages/contracts/dist/workflow.js.map +1 -1
  117. package/packages/contracts/package.json +1 -1
  118. package/packages/daemon/package.json +4 -4
  119. package/packages/gsd-agent-core/dist/agent-session.d.ts +1 -0
  120. package/packages/gsd-agent-core/dist/agent-session.d.ts.map +1 -1
  121. package/packages/gsd-agent-core/dist/agent-session.js +3 -0
  122. package/packages/gsd-agent-core/dist/agent-session.js.map +1 -1
  123. package/packages/gsd-agent-core/dist/extension-ui-snapshot.d.ts +41 -0
  124. package/packages/gsd-agent-core/dist/extension-ui-snapshot.d.ts.map +1 -0
  125. package/packages/gsd-agent-core/dist/extension-ui-snapshot.js +62 -0
  126. package/packages/gsd-agent-core/dist/extension-ui-snapshot.js.map +1 -0
  127. package/packages/gsd-agent-core/dist/index.d.ts +2 -0
  128. package/packages/gsd-agent-core/dist/index.d.ts.map +1 -1
  129. package/packages/gsd-agent-core/dist/index.js +2 -0
  130. package/packages/gsd-agent-core/dist/index.js.map +1 -1
  131. package/packages/gsd-agent-core/dist/session/agent-session-events.js +1 -1
  132. package/packages/gsd-agent-core/dist/session/agent-session-events.js.map +1 -1
  133. package/packages/gsd-agent-core/dist/session/agent-session-host.d.ts +1 -0
  134. package/packages/gsd-agent-core/dist/session/agent-session-host.d.ts.map +1 -1
  135. package/packages/gsd-agent-core/dist/session/agent-session-host.js.map +1 -1
  136. package/packages/gsd-agent-core/dist/session/agent-session-prompt.d.ts +5 -0
  137. package/packages/gsd-agent-core/dist/session/agent-session-prompt.d.ts.map +1 -1
  138. package/packages/gsd-agent-core/dist/session/agent-session-prompt.js +60 -3
  139. package/packages/gsd-agent-core/dist/session/agent-session-prompt.js.map +1 -1
  140. package/packages/gsd-agent-core/dist/transcript-store.d.ts +58 -0
  141. package/packages/gsd-agent-core/dist/transcript-store.d.ts.map +1 -0
  142. package/packages/gsd-agent-core/dist/transcript-store.js +132 -0
  143. package/packages/gsd-agent-core/dist/transcript-store.js.map +1 -0
  144. package/packages/gsd-agent-core/package.json +5 -5
  145. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts +2 -0
  146. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  147. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +25 -11
  148. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
  149. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller-latency.d.ts +4 -0
  150. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller-latency.d.ts.map +1 -0
  151. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller-latency.js +7 -0
  152. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller-latency.js.map +1 -0
  153. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts +3 -24
  154. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  155. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +26 -829
  156. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  157. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-handoff-filter.d.ts +58 -0
  158. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-handoff-filter.d.ts.map +1 -0
  159. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-handoff-filter.js +312 -0
  160. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-handoff-filter.js.map +1 -0
  161. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-pinned-zone.d.ts +31 -0
  162. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-pinned-zone.d.ts.map +1 -0
  163. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-pinned-zone.js +130 -0
  164. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-pinned-zone.js.map +1 -0
  165. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.d.ts +15 -0
  166. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.d.ts.map +1 -0
  167. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.js +258 -0
  168. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.js.map +1 -0
  169. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-tool-rollup.d.ts +13 -0
  170. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-tool-rollup.d.ts.map +1 -0
  171. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-tool-rollup.js +118 -0
  172. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-tool-rollup.js.map +1 -0
  173. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-state.d.ts +9 -0
  174. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
  175. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-state.js +1 -1
  176. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-state.js.map +1 -1
  177. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-ui-state.d.ts +54 -0
  178. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-ui-state.d.ts.map +1 -0
  179. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-ui-state.js +20 -0
  180. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-ui-state.js.map +1 -0
  181. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.d.ts +4 -1
  182. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  183. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js +9 -0
  184. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js.map +1 -1
  185. package/packages/gsd-agent-modes/dist/modes/interactive/streaming-render-state.d.ts +56 -0
  186. package/packages/gsd-agent-modes/dist/modes/interactive/streaming-render-state.d.ts.map +1 -0
  187. package/packages/gsd-agent-modes/dist/modes/interactive/streaming-render-state.js +44 -0
  188. package/packages/gsd-agent-modes/dist/modes/interactive/streaming-render-state.js.map +1 -0
  189. package/packages/gsd-agent-modes/dist/modes/interactive/tui-transcript-tracker.d.ts +5 -0
  190. package/packages/gsd-agent-modes/dist/modes/interactive/tui-transcript-tracker.d.ts.map +1 -0
  191. package/packages/gsd-agent-modes/dist/modes/interactive/tui-transcript-tracker.js +77 -0
  192. package/packages/gsd-agent-modes/dist/modes/interactive/tui-transcript-tracker.js.map +1 -0
  193. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  194. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.js +18 -0
  195. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.js.map +1 -1
  196. package/packages/gsd-agent-modes/package.json +7 -7
  197. package/packages/mcp-server/README.md +1 -1
  198. package/packages/mcp-server/dist/server.d.ts +1 -1
  199. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  200. package/packages/mcp-server/dist/server.js +3 -3
  201. package/packages/mcp-server/dist/server.js.map +1 -1
  202. package/packages/mcp-server/dist/workflow-tools.d.ts +13 -1
  203. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  204. package/packages/mcp-server/dist/workflow-tools.js +34 -20
  205. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  206. package/packages/mcp-server/package.json +4 -4
  207. package/packages/native/package.json +1 -1
  208. package/packages/pi-agent-core/package.json +1 -1
  209. package/packages/pi-ai/package.json +1 -1
  210. package/packages/pi-coding-agent/README.md +3 -2
  211. package/packages/pi-coding-agent/dist/core/session-manager-context.d.ts +9 -0
  212. package/packages/pi-coding-agent/dist/core/session-manager-context.d.ts.map +1 -0
  213. package/packages/pi-coding-agent/dist/core/session-manager-context.js +94 -0
  214. package/packages/pi-coding-agent/dist/core/session-manager-context.js.map +1 -0
  215. package/packages/pi-coding-agent/dist/core/session-manager-list.d.ts +8 -0
  216. package/packages/pi-coding-agent/dist/core/session-manager-list.d.ts.map +1 -0
  217. package/packages/pi-coding-agent/dist/core/session-manager-list.js +244 -0
  218. package/packages/pi-coding-agent/dist/core/session-manager-list.js.map +1 -0
  219. package/packages/pi-coding-agent/dist/core/session-manager-migration.d.ts +12 -0
  220. package/packages/pi-coding-agent/dist/core/session-manager-migration.d.ts.map +1 -0
  221. package/packages/pi-coding-agent/dist/core/session-manager-migration.js +84 -0
  222. package/packages/pi-coding-agent/dist/core/session-manager-migration.js.map +1 -0
  223. package/packages/pi-coding-agent/dist/core/session-manager-types.d.ts +135 -0
  224. package/packages/pi-coding-agent/dist/core/session-manager-types.d.ts.map +1 -0
  225. package/packages/pi-coding-agent/dist/core/session-manager-types.js +2 -0
  226. package/packages/pi-coding-agent/dist/core/session-manager-types.js.map +1 -0
  227. package/packages/pi-coding-agent/dist/core/session-manager.d.ts +6 -154
  228. package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
  229. package/packages/pi-coding-agent/dist/core/session-manager.js +22 -459
  230. package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
  231. package/packages/pi-coding-agent/dist/theme/theme-schema.d.ts +75 -75
  232. package/packages/pi-coding-agent/dist/theme/theme-schema.d.ts.map +1 -1
  233. package/packages/pi-coding-agent/dist/theme/theme-schema.js +1 -1
  234. package/packages/pi-coding-agent/dist/theme/theme-schema.js.map +1 -1
  235. package/packages/pi-coding-agent/dist/theme/theme.d.ts.map +1 -1
  236. package/packages/pi-coding-agent/dist/theme/theme.js +11 -7
  237. package/packages/pi-coding-agent/dist/theme/theme.js.map +1 -1
  238. package/packages/pi-coding-agent/package.json +7 -7
  239. package/packages/pi-tui/package.json +2 -2
  240. package/packages/rpc-client/package.json +2 -2
  241. package/pkg/dist/theme/theme-schema.d.ts +75 -75
  242. package/pkg/dist/theme/theme-schema.d.ts.map +1 -1
  243. package/pkg/dist/theme/theme-schema.js +1 -1
  244. package/pkg/dist/theme/theme-schema.js.map +1 -1
  245. package/pkg/dist/theme/theme.d.ts.map +1 -1
  246. package/pkg/dist/theme/theme.js +11 -7
  247. package/pkg/dist/theme/theme.js.map +1 -1
  248. package/pkg/package.json +1 -1
  249. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +20 -2
  250. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +80 -0
  251. package/src/resources/extensions/google-cli/stream-adapter.ts +106 -19
  252. package/src/resources/extensions/gsd/artifact-verification.ts +464 -0
  253. package/src/resources/extensions/gsd/auto/orchestrator.ts +25 -11
  254. package/src/resources/extensions/gsd/auto/session.ts +5 -0
  255. package/src/resources/extensions/gsd/auto-artifact-paths.ts +47 -1
  256. package/src/resources/extensions/gsd/auto-dispatch.ts +21 -23
  257. package/src/resources/extensions/gsd/auto-prompts.ts +38 -12
  258. package/src/resources/extensions/gsd/auto-recovery.ts +10 -508
  259. package/src/resources/extensions/gsd/auto-runtime-state.ts +4 -5
  260. package/src/resources/extensions/gsd/auto-timeout-recovery.ts +3 -2
  261. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +125 -12
  262. package/src/resources/extensions/gsd/bootstrap/core-session-tools.ts +43 -0
  263. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +6 -1
  264. package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +2 -0
  265. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +11 -19
  266. package/src/resources/extensions/gsd/bootstrap/system-context.ts +52 -18
  267. package/src/resources/extensions/gsd/bootstrap/tool-call-loop-guard.ts +74 -10
  268. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +1 -1
  269. package/src/resources/extensions/gsd/commands-context.ts +18 -1
  270. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +14 -9
  271. package/src/resources/extensions/gsd/commands-worktree.ts +12 -10
  272. package/src/resources/extensions/gsd/dashboard-overlay.ts +32 -3
  273. package/src/resources/extensions/gsd/db/queries.ts +79 -0
  274. package/src/resources/extensions/gsd/db-workspace.ts +61 -3
  275. package/src/resources/extensions/gsd/doctor-providers.ts +103 -9
  276. package/src/resources/extensions/gsd/exec-sandbox.ts +49 -9
  277. package/src/resources/extensions/gsd/forensics.ts +2 -33
  278. package/src/resources/extensions/gsd/git-service.ts +5 -5
  279. package/src/resources/extensions/gsd/guided-flow-queue.ts +89 -4
  280. package/src/resources/extensions/gsd/health-widget.ts +69 -32
  281. package/src/resources/extensions/gsd/layout-policy.ts +2 -1
  282. package/src/resources/extensions/gsd/markdown-renderer.ts +8 -11
  283. package/src/resources/extensions/gsd/memory-consolidation-scanner.ts +51 -19
  284. package/src/resources/extensions/gsd/migration-auto-check.ts +23 -0
  285. package/src/resources/extensions/gsd/milestone-ids.ts +31 -2
  286. package/src/resources/extensions/gsd/milestone-implementation-evidence.ts +35 -21
  287. package/src/resources/extensions/gsd/prompts/code-review.md +6 -4
  288. package/src/resources/extensions/gsd/quick.ts +43 -2
  289. package/src/resources/extensions/gsd/session-forensics.ts +11 -1
  290. package/src/resources/extensions/gsd/skills/gsd-headless/references/commands.md +1 -1
  291. package/src/resources/extensions/gsd/state/derive/cache.ts +46 -0
  292. package/src/resources/extensions/gsd/state/derive/db-open.ts +45 -0
  293. package/src/resources/extensions/gsd/state/derive/from-db.ts +561 -0
  294. package/src/resources/extensions/gsd/state/derive/index.ts +104 -0
  295. package/src/resources/extensions/gsd/state/derive/interrupted-work.ts +31 -0
  296. package/src/resources/extensions/gsd/state-reconciliation/drift/stale-render.ts +81 -7
  297. package/src/resources/extensions/gsd/state-reconciliation/index.ts +50 -24
  298. package/src/resources/extensions/gsd/state-reconciliation/registry.ts +43 -28
  299. package/src/resources/extensions/gsd/state.ts +32 -732
  300. package/src/resources/extensions/gsd/tests/auto-artifact-paths.test.ts +98 -1
  301. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +111 -1
  302. package/src/resources/extensions/gsd/tests/commands-context.test.ts +26 -0
  303. package/src/resources/extensions/gsd/tests/commands-gsd-core.test.ts +1 -0
  304. package/src/resources/extensions/gsd/tests/commands-worktree-clean.test.ts +80 -0
  305. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +11 -0
  306. package/src/resources/extensions/gsd/tests/complete-task-rollback-evidence.test.ts +48 -8
  307. package/src/resources/extensions/gsd/tests/complete-task.test.ts +75 -0
  308. package/src/resources/extensions/gsd/tests/dashboard-overlay.test.ts +55 -2
  309. package/src/resources/extensions/gsd/tests/dispatch-rule-coverage.test.ts +26 -1
  310. package/src/resources/extensions/gsd/tests/doctor-forensics-db-open-regression.test.ts +70 -2
  311. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +107 -0
  312. package/src/resources/extensions/gsd/tests/exec-graceful-kill.test.ts +38 -0
  313. package/src/resources/extensions/gsd/tests/exec-tool.test.ts +45 -1
  314. package/src/resources/extensions/gsd/tests/forensics-error-filter.test.ts +88 -0
  315. package/src/resources/extensions/gsd/tests/guided-discuss-milestone-prompt-rendering.test.ts +42 -0
  316. package/src/resources/extensions/gsd/tests/health-widget.test.ts +268 -3
  317. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +119 -1
  318. package/src/resources/extensions/gsd/tests/integration/queue-active-milestone-context-budget.test.ts +93 -0
  319. package/src/resources/extensions/gsd/tests/integration/quick-branch-lifecycle.test.ts +56 -9
  320. package/src/resources/extensions/gsd/tests/knowledge-cold-start.test.ts +14 -0
  321. package/src/resources/extensions/gsd/tests/memory-consolidation-scanner.test.ts +78 -0
  322. package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +70 -0
  323. package/src/resources/extensions/gsd/tests/orchestrator-logs.test.ts +43 -1
  324. package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +26 -0
  325. package/src/resources/extensions/gsd/tests/parsers-legacy-importers.test.ts +2 -1
  326. package/src/resources/extensions/gsd/tests/pipeline-variant-dispatch.test.ts +1 -1
  327. package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +54 -1
  328. package/src/resources/extensions/gsd/tests/progressive-planning.test.ts +50 -14
  329. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +195 -1
  330. package/src/resources/extensions/gsd/tests/read-uat-gate-verdict.test.ts +185 -0
  331. package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +87 -0
  332. package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +7 -0
  333. package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +56 -0
  334. package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +191 -0
  335. package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +151 -0
  336. package/src/resources/extensions/gsd/tests/tool-param-optionality.test.ts +26 -0
  337. package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +193 -14
  338. package/src/resources/extensions/gsd/tests/unmerged-milestone-guard.test.ts +25 -0
  339. package/src/resources/extensions/gsd/tests/validation-block-guard.test.ts +79 -0
  340. package/src/resources/extensions/gsd/tests/verify-artifact-tightened.test.ts +66 -0
  341. package/src/resources/extensions/gsd/tests/workspace-git-preflight.test.ts +151 -2
  342. package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +39 -0
  343. package/src/resources/extensions/gsd/tools/complete-slice.ts +2 -2
  344. package/src/resources/extensions/gsd/tools/complete-task.ts +53 -15
  345. package/src/resources/extensions/gsd/tools/exec-tool.ts +7 -3
  346. package/src/resources/extensions/gsd/unit-context-composer.ts +33 -7
  347. package/src/resources/extensions/gsd/unit-registry.ts +32 -4
  348. package/src/resources/extensions/gsd/unmerged-milestone-guard.ts +41 -5
  349. package/src/resources/extensions/gsd/validation-block-guard.ts +13 -7
  350. package/src/resources/extensions/gsd/workflow-projections.ts +20 -14
  351. package/src/resources/extensions/gsd/workspace-git-preflight.ts +31 -0
  352. package/src/resources/extensions/gsd/worktree-manager.ts +41 -1
  353. package/dist/web/standalone/.next/static/chunks/2772.bfa657f49f955239.js +0 -1
  354. package/dist/web/standalone/.next/static/chunks/796.e0bdc932325d7e03.js +0 -10
  355. package/dist/web/standalone/.next/static/chunks/webpack-f46ea08200a0227e.js +0 -1
  356. /package/dist/web/standalone/.next/static/{BTKtGFF1Y-hvVJEGhBRo9 → O7xDYXO0r4zFhIzY1hrWV}/_buildManifest.js +0 -0
  357. /package/dist/web/standalone/.next/static/{BTKtGFF1Y-hvVJEGhBRo9 → O7xDYXO0r4zFhIzY1hrWV}/_ssgManifest.js +0 -0
@@ -1,63 +1,17 @@
1
1
  // Project/App: gsd-pi
2
2
  // File Purpose: Interactive TUI chat stream controller.
3
- import { Loader, Markdown, Spacer, Text } from "@gsd/pi-tui";
3
+ import { Loader, Spacer, Text } from "@gsd/pi-tui";
4
4
  import { theme } from "@gsd/pi-coding-agent/theme/theme.js";
5
5
  import { AssistantMessageComponent } from "../components/assistant-message.js";
6
6
  import { reconcileChatTurnConnections } from "../components/chat-turn-connect.js";
7
- import { ToolExecutionComponent, ToolPhaseSummaryComponent, } from "../components/tool-execution.js";
8
- import { DynamicBorder } from "../components/dynamic-border.js";
7
+ import { ToolExecutionComponent } from "../components/tool-execution.js";
9
8
  import { appKey } from "../components/keybinding-hints.js";
10
- // Tracks the last processed content index to avoid re-scanning all blocks on every message_update
11
- let lastProcessedContentIndex = 0;
12
- // Tracks the previous content[] length so we can detect when an adapter resets
13
- // the assistant content array for a new provider sub-turn within one lifecycle.
14
- let lastContentLength = 0;
15
- let renderedSegments = [];
16
- // When providers reuse one assistant lifecycle across internal sub-turns,
17
- // a content[] shrink resets renderedSegments. Keep the displaced segments so
18
- // claude-code MCP pruning can remove stale provisional text later.
19
- let orphanedSegments = [];
20
- // Invocation matching only reconciles IDs reported by different event streams.
21
- // Same-source identical invocations are separate concurrent tool calls.
22
- const toolRegistrationSources = new WeakMap();
23
- const PROVISIONAL_PRE_TOOL_ACTIONS = "check|inspect|look(?:\\s+into)?|read|open|search|grep|scan|run|verify|test|trace|review|investigate|reproduce|use|gather|find|pull|query|take a look|update|patch|edit|change|modify|write|create|add|remove|apply";
24
- const FIRST_PERSON_PROVISIONAL_PRE_TOOL_RE = new RegExp(`^(?:i(?:'ll| will)|i(?:'m| am) going to|let me|i need to)\\s+(?:${PROVISIONAL_PRE_TOOL_ACTIONS})\\b`, "i");
25
- const GERUND_PROVISIONAL_PRE_TOOL_RE = /^(?:checking|inspecting|reading|searching|running|verifying|testing|tracing|reviewing|investigating|scanning|updating|patching|editing|writing|creating|applying)\b/i;
26
- function findPendingToolByInvocation(pendingTools, toolName, args, source) {
27
- let fallback;
28
- for (const component of pendingTools.values()) {
29
- if (!component.isInFlight())
30
- continue;
31
- if (!component.matchesInvocation(toolName, args))
32
- continue;
33
- const sources = toolRegistrationSources.get(component);
34
- if (!sources?.has(source)) {
35
- return component;
36
- }
37
- if (sources.size > 1 && !fallback)
38
- fallback = component;
39
- }
40
- return fallback;
41
- }
42
- function registerPendingToolComponent(host, toolCallId, toolName, args, source, createComponent) {
43
- const existing = host.pendingTools.get(toolCallId);
44
- if (existing) {
45
- return { component: existing, created: false };
46
- }
47
- const matched = findPendingToolByInvocation(host.pendingTools, toolName, args, source);
48
- if (matched) {
49
- host.pendingTools.set(toolCallId, matched);
50
- toolRegistrationSources.get(matched)?.add(source);
51
- return { component: matched, created: false };
52
- }
53
- const component = createComponent();
54
- component.setExpanded(host.toolOutputExpanded);
55
- host.chatContainer.addChild(component);
56
- markFirstVisibleAssistantOutput(host, "tool", { toolName, source });
57
- host.pendingTools.set(toolCallId, component);
58
- toolRegistrationSources.set(component, new Set([source]));
59
- return { component, created: true };
60
- }
9
+ import { markFirstVisibleAssistantOutput, markTuiLatency } from "./chat-controller-latency.js";
10
+ import { findLatestPinnableCandidates, findLatestPinnableText, tearDownPinnedZone, updatePinnedMessageZone, } from "./chat-pinned-zone.js";
11
+ import { hasAssistantToolBlocks, hasVisibleAssistantContent, isProvisionalPreToolProse, isRedundantDiscussRestatement, priorAssistantTextFromSession, shouldSuppressEntireAssistantMessage, textInvitesUserReply, } from "./chat-handoff-filter.js";
12
+ import { registerPendingToolComponent, replaceCompactToolRowsWithPhaseSummary, } from "./chat-tool-rollup.js";
13
+ import { applySubTurnContentShrink, rebuildSegmentsOnMessageEnd, runSegmentWalker, scanNewContentBlocks, } from "./chat-segment-walker.js";
14
+ export { findLatestPinnableCandidates, findLatestPinnableText, isProvisionalPreToolProse, isRedundantDiscussRestatement, priorAssistantTextFromSession, textInvitesUserReply, };
61
15
  function startLoadingAnimation(host) {
62
16
  if (host.pendingWorkingMessage === null) {
63
17
  host.loadingAnimation = undefined;
@@ -69,7 +23,7 @@ function startLoadingAnimation(host) {
69
23
  host.statusContainer.addChild(host.loadingAnimation);
70
24
  if (host.pendingWorkingMessage !== undefined) {
71
25
  if (host.pendingWorkingMessage) {
72
- host.loadingAnimation.setMessage(host.pendingWorkingMessage);
26
+ host.loadingAnimation?.setMessage?.(host.pendingWorkingMessage);
73
27
  }
74
28
  host.pendingWorkingMessage = undefined;
75
29
  }
@@ -93,473 +47,16 @@ export function stopActivityIndicator(host) {
93
47
  host.statusContainer.clear();
94
48
  }
95
49
  }
96
- function markTuiLatency(host, phase, data) {
97
- host.session?.markTurnLatency?.(phase, data);
98
- }
99
- function markFirstVisibleAssistantOutput(host, kind, data) {
100
- host.session?.markFirstVisibleTurnLatency?.(kind, data);
101
- }
102
- function getVisibleTextLikeBlockType(block, hideThinkingBlock = false) {
103
- if (block?.type === "text" && typeof block.text === "string" && block.text.trim().length > 0)
104
- return "text";
105
- if (hideThinkingBlock)
106
- return undefined;
107
- if (block?.type === "thinking" && typeof block.thinking === "string" && block.thinking.trim().length > 0)
108
- return "thinking";
109
- return undefined;
110
- }
111
- /** True when assistant prose is handing off to the user (question or explicit invite). */
112
- export function textInvitesUserReply(text) {
113
- const trimmed = text.trim();
114
- if (!trimmed)
115
- return false;
116
- if (/\?(?:\s|$)/m.test(trimmed))
117
- return true;
118
- return /\b(?:what do you want|what's on your mind|let me know|tell me what|help me understand)\b/i.test(trimmed);
119
- }
120
- const DISCUSS_RESTATE_RE = /\b(?:what do you want|what should we|before i can write|context file|placeholder name|need to understand what|what(?:'s| is) (?:on your mind|the next)|help me understand what you want)\b/i;
121
- /** Second sub-turn that only says it is waiting after questions were already asked. */
122
- const HANDOFF_WAIT_RESTATE_RE = /\b(?:holding\s+(?:here|for)|waiting\s+(?:here|for)|no\s+need\s+for\s+anything\s+else|until\s+you\s+(?:point|tell|let\s+me\s+know|answer|reply)|i(?:'ve| have)\s+asked)\b/i;
123
- function isWaitOnlyQuestionFragment(fragment) {
124
- return /^(?:i(?:'ve| have)\s+asked\b|(?:i'?m|i am)\s+(?:holding|waiting)\b|no\s+need\s+for\s+anything\s+else\b)/i.test(fragment)
125
- && !/\b(?:should|do you|would you|can we|could we|what|which|how|where|when|who|also|add|include)\b/i.test(fragment);
126
- }
127
- /** True when text adds a question beyond wait/hold boilerplate. */
128
- function containsNewSubstantiveQuestion(text) {
129
- for (let i = 0; i < text.length; i++) {
130
- if (text[i] !== "?")
131
- continue;
132
- const previousBreak = Math.max(text.lastIndexOf("\n", i), text.lastIndexOf(".", i), text.lastIndexOf("!", i), text.lastIndexOf("?", i - 1), -1);
133
- const fragment = text.slice(previousBreak + 1, i + 1).trim();
134
- if (fragment.length < 8)
135
- continue;
136
- if (isWaitOnlyQuestionFragment(fragment))
137
- continue;
138
- return true;
139
- }
140
- return false;
141
- }
142
- function isHandoffWaitRestatement(next) {
143
- if (!HANDOFF_WAIT_RESTATE_RE.test(next))
144
- return false;
145
- // Keep follow-ups that add a real question even when they also say holding/waiting.
146
- if (containsNewSubstantiveQuestion(next))
147
- return false;
148
- // Only classify as a pure wait ack when the text is short; long text likely
149
- // contains substantive content alongside incidental wait language.
150
- if (next.length > 400)
151
- return false;
152
- return true;
153
- }
154
- /**
155
- * Claude Code can emit a second text sub-turn that restates the same milestone
156
- * discuss ask. Drop it when the prior sub-turn already invited a user reply.
157
- */
158
- export function isRedundantDiscussRestatement(priorText, newText) {
159
- const prior = priorText.trim();
160
- const next = newText.trim();
161
- if (!prior || !next)
162
- return false;
163
- if (!textInvitesUserReply(prior))
164
- return false;
165
- const isDiscussRestate = DISCUSS_RESTATE_RE.test(next);
166
- const isWaitRestate = isHandoffWaitRestatement(next);
167
- if (!isDiscussRestate && !isWaitRestate)
168
- return false;
169
- // Wait acks are gated on length and no-? inside isHandoffWaitRestatement.
170
- if (isWaitRestate)
171
- return true;
172
- if (next.length > prior.length * 1.1)
173
- return false;
174
- return next.length <= prior.length || next.length < 900;
175
- }
176
- function isSubTurnTextReplacement(blocks, rendered) {
177
- for (const seg of rendered) {
178
- if (seg.kind !== "text-run")
179
- continue;
180
- const oldText = (seg.cachedText ?? "").trim();
181
- if (!oldText)
182
- continue;
183
- const newText = getTextFromContentBlocks(blocks, seg.startIndex, seg.endIndex).trim();
184
- if (!newText || newText === oldText)
185
- continue;
186
- // Streaming growth extends prior text; a new sub-turn replaces it wholesale.
187
- if (!newText.startsWith(oldText) && !oldText.startsWith(newText))
188
- return seg.startIndex;
189
- }
190
- return null;
191
- }
192
- function getTextFromContentBlocks(blocks, startIndex, endIndex) {
193
- const parts = [];
194
- for (let i = startIndex; i <= endIndex && i < blocks.length; i++) {
195
- const block = blocks[i];
196
- if (block?.type === "text" && typeof block.text === "string" && block.text.trim()) {
197
- parts.push(block.text.trim());
198
- }
199
- }
200
- return parts.join("\n\n");
201
- }
202
- function filterRedundantDiscussTextRuns(desired, blocks) {
203
- const textRuns = desired.filter((seg) => seg.kind === "text-run" && seg.contentType === "text");
204
- if (textRuns.length < 2)
205
- return desired;
206
- const skipStarts = new Set();
207
- let lastKeptText;
208
- for (const seg of textRuns) {
209
- const text = getTextFromContentBlocks(blocks, seg.startIndex, seg.endIndex);
210
- if (lastKeptText && isRedundantDiscussRestatement(lastKeptText, text)) {
211
- skipStarts.add(seg.startIndex);
212
- }
213
- else {
214
- lastKeptText = text;
215
- }
216
- }
217
- return desired.filter((seg) => !(seg.kind === "text-run" && seg.contentType === "text" && skipStarts.has(seg.startIndex)));
218
- }
219
- function extractAssistantText(msg) {
220
- if (!msg)
221
- return "";
222
- const content = msg.content;
223
- if (typeof content === "string")
224
- return content;
225
- if (!Array.isArray(content))
226
- return "";
227
- const parts = [];
228
- for (const block of content) {
229
- if (!block || typeof block !== "object")
230
- continue;
231
- if (block.type === "text" && typeof block.text === "string") {
232
- parts.push(block.text);
233
- }
234
- }
235
- return parts.join("\n");
236
- }
237
- function latestPriorUserFacingText(orphaned, rendered) {
238
- const runs = [...orphaned, ...rendered].filter((seg) => seg.kind === "text-run" && seg.contentType === "text");
239
- return runs.at(-1)?.cachedText;
240
- }
241
- /**
242
- * Walk session history backward for the previous assistant prose block, skipping
243
- * toolResult rows. Used when Claude Code emits a second assistant message
244
- * (new timestamp) after tools in the same prompt.
245
- */
246
- export function priorAssistantTextFromSession(messages, opts) {
247
- let assistantFromEnd = 0;
248
- for (let i = messages.length - 1; i >= 0; i--) {
249
- const message = messages[i];
250
- if (!message)
251
- continue;
252
- if (message.role === "user")
253
- return undefined;
254
- if (message.role === "toolResult")
255
- continue;
256
- if (message.role === "assistant") {
257
- const text = extractAssistantText(message).trim();
258
- if (!text)
259
- continue;
260
- if (opts?.skipLastAssistant) {
261
- assistantFromEnd += 1;
262
- if (assistantFromEnd === 1)
263
- continue;
264
- }
265
- return text;
266
- }
267
- }
268
- return undefined;
269
- }
270
- function shouldSuppressEntireAssistantMessage(message, sessionMessages, orphaned) {
271
- const textBlocks = (message.content ?? []).filter((block) => block?.type === "text" && typeof block.text === "string" && block.text.trim());
272
- if (textBlocks.length !== 1)
273
- return false;
274
- return shouldSuppressRedundantHandoffText(sessionMessages, textBlocks[0].text, orphaned, []);
275
- }
276
- function shouldSuppressRedundantHandoffText(sessionMessages, currentText, orphaned, rendered) {
277
- const next = currentText.trim();
278
- if (!next)
279
- return false;
280
- const priorInline = latestPriorUserFacingText(orphaned, rendered);
281
- if (priorInline && isRedundantDiscussRestatement(priorInline, next)) {
282
- return true;
283
- }
284
- const last = sessionMessages[sessionMessages.length - 1];
285
- const skipLastAssistant = last?.role === "assistant" && extractAssistantText(last).trim() === next;
286
- const priorSession = priorAssistantTextFromSession(sessionMessages, { skipLastAssistant });
287
- return !!(priorSession && isRedundantDiscussRestatement(priorSession, next));
288
- }
289
- function buildDesiredSegments(blocks, options = {}) {
290
- const desired = [];
291
- let runStart = -1;
292
- let runEnd = -1;
293
- let runType;
294
- const closeRun = () => {
295
- if (runStart !== -1 && runType) {
296
- desired.push({ kind: "text-run", startIndex: runStart, endIndex: runEnd, contentType: runType });
297
- runStart = -1;
298
- runEnd = -1;
299
- runType = undefined;
300
- }
301
- };
302
- for (let i = 0; i < blocks.length; i++) {
303
- const block = blocks[i];
304
- const blockType = getVisibleTextLikeBlockType(block, options.hideThinkingBlock);
305
- const isInvisibleTextLike = blockType === undefined && (block?.type === "text" || block?.type === "thinking");
306
- const isTool = block?.type === "toolCall" || block?.type === "serverToolUse";
307
- if (blockType) {
308
- if (options.shouldSkipTextBlock?.(block, i)) {
309
- closeRun();
310
- continue;
311
- }
312
- if (runStart === -1) {
313
- runStart = i;
314
- runEnd = i;
315
- runType = blockType;
316
- }
317
- else if (runType !== blockType) {
318
- closeRun();
319
- runStart = i;
320
- runEnd = i;
321
- runType = blockType;
322
- }
323
- else {
324
- runEnd = i;
325
- }
326
- }
327
- else {
328
- if (isInvisibleTextLike)
329
- continue;
330
- closeRun();
331
- if (isTool) {
332
- desired.push({ kind: "tool", contentIndex: i, toolId: block.id });
333
- }
334
- }
335
- }
336
- closeRun();
337
- return desired;
338
- }
339
- function isToolUseBlock(block) {
340
- return block?.type === "toolCall" || block?.type === "serverToolUse";
341
- }
342
- function isMcpToolBlock(block) {
343
- if (!isToolUseBlock(block))
344
- return false;
345
- const toolName = typeof block?.name === "string" ? block.name : "";
346
- return typeof block?.mcpServer === "string" || toolName.startsWith("mcp__");
347
- }
348
- function hasPostToolText(blocks, firstToolIdx) {
349
- if (firstToolIdx < 0)
350
- return false;
351
- return blocks.some((b, idx) => (idx > firstToolIdx
352
- && b?.type === "text"
353
- && typeof b?.text === "string"
354
- && b.text.trim().length > 0));
355
- }
356
- function normalizeProvisionalText(text) {
357
- return text
358
- .trim()
359
- .replace(/[’`]/g, "'")
360
- .replace(/\s+/g, " ");
361
- }
362
- export function isProvisionalPreToolProse(text) {
363
- const normalized = normalizeProvisionalText(text);
364
- if (!normalized)
365
- return false;
366
- if (textInvitesUserReply(normalized))
367
- return false;
368
- if (/\?\s*$/.test(normalized))
369
- return false;
370
- return FIRST_PERSON_PROVISIONAL_PRE_TOOL_RE.test(normalized)
371
- || GERUND_PROVISIONAL_PRE_TOOL_RE.test(normalized);
372
- }
373
- function getProvisionalPreToolPrunePlan(message) {
374
- const blocks = message.content;
375
- const firstToolIdx = blocks.findIndex(isToolUseBlock);
376
- return {
377
- firstToolIdx,
378
- shouldPrune: message.provider === "claude-code"
379
- && firstToolIdx >= 0
380
- && blocks.some(isMcpToolBlock)
381
- && hasPostToolText(blocks, firstToolIdx),
382
- };
383
- }
384
- function buildDesiredSegmentsForMessage(message, options = {}) {
385
- const { shouldPrune, firstToolIdx } = getProvisionalPreToolPrunePlan(message);
386
- return buildDesiredSegments(message.content, {
387
- hideThinkingBlock: options.hideThinkingBlock,
388
- shouldSkipTextBlock: (block, index) => {
389
- if (!shouldPrune || firstToolIdx < 0 || index >= firstToolIdx)
390
- return false;
391
- if (getVisibleTextLikeBlockType(block, options.hideThinkingBlock) !== "text")
392
- return false;
393
- const textValue = typeof block?.text === "string" ? block.text : "";
394
- return isProvisionalPreToolProse(textValue);
395
- },
396
- });
397
- }
398
- function hasVisibleAssistantContent(message, hideThinkingBlock = false) {
399
- return message.content.some((c) => getVisibleTextLikeBlockType(c, hideThinkingBlock) !== undefined);
400
- }
401
- function hasAssistantToolBlocks(message) {
402
- return message.content.some((c) => c.type === "toolCall" || c.type === "serverToolUse");
403
- }
404
- // Pinnable text candidates: non-empty text blocks that appear strictly before
405
- // the most recent tool call, returned newest-first. Text blocks after the last
406
- // tool call are still streaming live into the chat container.
407
- export function findLatestPinnableCandidates(contentBlocks) {
408
- let lastToolIdx = -1;
409
- for (let i = contentBlocks.length - 1; i >= 0; i--) {
410
- const c = contentBlocks[i];
411
- if (c?.type === "toolCall" || c?.type === "serverToolUse") {
412
- lastToolIdx = i;
413
- break;
414
- }
415
- }
416
- const out = [];
417
- for (let i = lastToolIdx - 1; i >= 0; i--) {
418
- const c = contentBlocks[i];
419
- if (c?.type === "text" && typeof c.text === "string" && c.text.trim()) {
420
- out.push({ text: c.text.trim(), contentIndex: i });
421
- }
422
- }
423
- return out;
424
- }
425
- export function findLatestPinnableText(contentBlocks) {
426
- return findLatestPinnableCandidates(contentBlocks)[0]?.text ?? "";
427
- }
428
- // Sum rendered line counts of segments that appear strictly after the given
429
- // content-block index. Used to decide whether a pinnable text block has
430
- // scrolled out of the viewport and therefore warrants mirroring.
431
- function rowsRenderedAfterContentIndex(contentIndex, width) {
432
- let rows = 0;
433
- for (const seg of renderedSegments) {
434
- try {
435
- if (seg.kind === "text-run" && seg.startIndex > contentIndex) {
436
- rows += seg.component.render(width).length;
437
- }
438
- else if (seg.kind === "tool" && seg.contentIndex > contentIndex) {
439
- rows += seg.component.render(width).length;
440
- }
441
- }
442
- catch {
443
- // Defensive: a component that throws during measurement shouldn't
444
- // destabilize pinned-zone logic. Skip it.
445
- }
446
- }
447
- return rows;
448
- }
449
- // Tracks the latest assistant text for the pinned message zone
450
- let lastPinnedText = "";
451
- // Whether any tool execution has been added in this assistant turn (triggers pinned display)
452
- let hasToolsInTurn = false;
453
- // Reference to the pinned border so we can toggle its label between working/idle
454
- let pinnedBorder;
455
- // Reference to the pinned markdown component below the border
456
- let pinnedTextComponent;
457
- // Set when the pinned zone was shown this turn; used to realign viewport after teardown.
458
- let pinnedZoneNeedsViewportRealign = false;
459
- function tearDownPinnedZone(host, options) {
460
- const needsRealign = pinnedZoneNeedsViewportRealign;
461
- if (pinnedBorder)
462
- pinnedBorder.stopSpinner();
463
- pinnedBorder = undefined;
464
- pinnedTextComponent = undefined;
465
- host.pinnedMessageContainer.clear();
466
- lastPinnedText = "";
467
- hasToolsInTurn = false;
468
- pinnedZoneNeedsViewportRealign = false;
469
- if (options?.realignViewport && needsRealign) {
470
- host.ui.requestRender(true);
471
- }
472
- }
473
- function mergeToolPhases(phases) {
474
- const merged = [];
475
- for (const phase of phases) {
476
- const previous = merged[merged.length - 1];
477
- if (previous?.label === phase.label) {
478
- previous.count += phase.count;
479
- previous.durationMs += phase.durationMs;
480
- previous.targets = mergeTargets(previous.targets, phase.targets);
481
- if (previous.actionLabel !== phase.actionLabel) {
482
- previous.actionLabel = undefined;
483
- }
484
- }
485
- else {
486
- merged.push({ ...phase, targets: phase.targets ? [...phase.targets] : undefined });
487
- }
488
- }
489
- return merged;
490
- }
491
- function mergeTargets(existing, incoming) {
492
- if (!existing && !incoming)
493
- return undefined;
494
- const seen = new Set();
495
- const merged = [];
496
- for (const target of [...(existing ?? []), ...(incoming ?? [])]) {
497
- if (!target || seen.has(target))
498
- continue;
499
- seen.add(target);
500
- merged.push(target);
501
- }
502
- return merged;
503
- }
504
- function replaceCompactToolRowsWithPhaseSummary(host) {
505
- let changed = false;
506
- const nextRenderedSegments = [];
507
- let rollupRun = [];
508
- const flushRollupRun = () => {
509
- const actionCount = rollupRun.reduce((total, item) => total + item.phases.reduce((sum, phase) => sum + phase.count, 0), 0);
510
- if (actionCount < 2) {
511
- nextRenderedSegments.push(...rollupRun.map((item) => item.seg));
512
- rollupRun = [];
513
- return;
514
- }
515
- const firstIndex = Math.max(0, host.chatContainer.children.indexOf(rollupRun[0].seg.component));
516
- const phases = mergeToolPhases(rollupRun.flatMap((item) => item.phases));
517
- const summary = new ToolPhaseSummaryComponent(phases);
518
- for (const { seg } of rollupRun) {
519
- host.chatContainer.removeChild(seg.component);
520
- }
521
- host.chatContainer.addChild(summary);
522
- const summaryIndex = host.chatContainer.children.indexOf(summary);
523
- if (summaryIndex !== -1 && summaryIndex !== firstIndex) {
524
- host.chatContainer.children.splice(summaryIndex, 1);
525
- host.chatContainer.children.splice(firstIndex, 0, summary);
526
- host.chatContainer._prevRender = null;
527
- }
528
- changed = true;
529
- nextRenderedSegments.push({ kind: "tool-summary", component: summary, phases });
530
- rollupRun = [];
531
- };
532
- for (const seg of renderedSegments) {
533
- const phase = seg.kind === "tool" ? seg.component.getRollupPhase() : null;
534
- if (seg.kind === "tool" && phase) {
535
- rollupRun.push({ seg, phases: [phase] });
536
- continue;
537
- }
538
- if (seg.kind === "tool-summary") {
539
- rollupRun.push({ seg, phases: seg.component.getPhases() });
540
- continue;
541
- }
542
- flushRollupRun();
543
- nextRenderedSegments.push(seg);
544
- }
545
- flushRollupRun();
546
- if (changed) {
547
- renderedSegments = nextRenderedSegments;
548
- host.ui.requestRender();
549
- }
550
- }
551
50
  export async function handleAgentEvent(host, event) {
552
51
  if (!host.isInitialized) {
553
52
  await host.init();
554
53
  }
555
54
  host.footer.invalidate();
556
55
  const timestampFormat = host.settingsManager.getTimestampFormat();
56
+ const rs = host.streamingRenderState;
557
57
  // Reset content index tracker and pinned state when a new assistant message starts
558
58
  if (event.type === "message_start" && event.message.role === "assistant") {
559
- lastProcessedContentIndex = 0;
560
- lastContentLength = 0;
561
- renderedSegments = [];
562
- orphanedSegments = [];
59
+ rs.resetForNewAssistantMessage();
563
60
  tearDownPinnedZone(host);
564
61
  }
565
62
  switch (event.type) {
@@ -573,9 +70,7 @@ export async function handleAgentEvent(host, event) {
573
70
  host.pendingTools.clear();
574
71
  host.pendingMessagesContainer.clear();
575
72
  tearDownPinnedZone(host);
576
- renderedSegments = [];
577
- orphanedSegments = [];
578
- lastContentLength = 0;
73
+ rs.resetForSessionChange();
579
74
  host.compactionQueuedMessages = [];
580
75
  host.rebuildChatFromMessages();
581
76
  host.updatePendingMessagesDisplay();
@@ -675,68 +170,12 @@ export async function handleAgentEvent(host, event) {
675
170
  // lifecycle while internally spanning multiple provider sub-turns.
676
171
  // When a new sub-turn starts, content[] length shrinks back to 0/1.
677
172
  // The scan loop needs its index reset, AND the segment walker's
678
- // renderedSegments map must be cleared so existing text-run
173
+ // rs.renderedSegments map must be cleared so existing text-run
679
174
  // components don't get overwritten in place with new sub-turn
680
175
  // content (#4144 regression). Prior sub-turn children stay in
681
176
  // chatContainer as frozen history; new segments append after them.
682
- const replacedAt = contentBlocks.length <= lastContentLength
683
- ? isSubTurnTextReplacement(contentBlocks, renderedSegments)
684
- : null;
685
- if (contentBlocks.length < lastContentLength) {
686
- // Accumulate across successive shrinks — overwriting would drop
687
- // segments displaced by an earlier shrink, leaving them stranded
688
- // in chatContainer once the prune pass finally runs.
689
- orphanedSegments = [...orphanedSegments, ...renderedSegments];
690
- renderedSegments = [];
691
- lastPinnedText = "";
692
- lastProcessedContentIndex = 0;
693
- }
694
- else if (replacedAt !== null) {
695
- // Same-index wholesale replacement: orphan only the replaced
696
- // text-run and any text-runs after it. Earlier unchanged text
697
- // and tool segments stay in renderedSegments so they are not
698
- // re-rendered and duplicated in chatContainer.
699
- orphanedSegments = [
700
- ...orphanedSegments,
701
- ...renderedSegments.filter((seg) => seg.kind === "text-run" && seg.startIndex >= replacedAt),
702
- ];
703
- renderedSegments = renderedSegments.filter((seg) => !(seg.kind === "text-run" && seg.startIndex >= replacedAt));
704
- lastPinnedText = "";
705
- lastProcessedContentIndex = replacedAt;
706
- }
707
- else if (lastProcessedContentIndex >= contentBlocks.length) {
708
- lastProcessedContentIndex = 0;
709
- }
710
- lastContentLength = contentBlocks.length;
711
- for (let i = lastProcessedContentIndex; i < contentBlocks.length; i++) {
712
- const content = contentBlocks[i];
713
- if (content.type === "toolCall") {
714
- const { component } = registerPendingToolComponent(host, content.id, content.name, content.arguments, "content", () => new ToolExecutionComponent(content.name, content.arguments, { showImages: host.settingsManager.getShowImages() }, host.getRegisteredToolDefinition(content.name), host.ui));
715
- component.updateArgs(content.arguments);
716
- }
717
- else if (content.type === "serverToolUse") {
718
- registerPendingToolComponent(host, content.id, content.name, content.input ?? {}, "content", () => new ToolExecutionComponent(content.name, content.input ?? {}, { showImages: host.settingsManager.getShowImages() }, undefined, host.ui));
719
- }
720
- else if (content.type === "webSearchResult") {
721
- const component = host.pendingTools.get(content.toolUseId);
722
- if (component) {
723
- if (process.env.PI_OFFLINE === "1") {
724
- component.updateResult({
725
- content: [{ type: "text", text: "Web search disabled (offline mode)" }],
726
- isError: false,
727
- });
728
- }
729
- else {
730
- const searchContent = content.content;
731
- const isError = searchContent && typeof searchContent === "object" && "type" in searchContent && searchContent.type === "web_search_tool_result_error";
732
- component.updateResult({
733
- content: [{ type: "text", text: host.formatWebSearchResult(searchContent) }],
734
- isError: !!isError,
735
- });
736
- }
737
- }
738
- }
739
- }
177
+ applySubTurnContentShrink(rs, contentBlocks);
178
+ scanNewContentBlocks(host, rs, contentBlocks);
740
179
  // When the stream adapter signals a completed tool call with an
741
180
  // external result (from Claude Code SDK), update the pending
742
181
  // ToolExecutionComponent immediately so output is visible in
@@ -752,189 +191,19 @@ export async function handleAgentEvent(host, event) {
752
191
  replaceCompactToolRowsWithPhaseSummary(host);
753
192
  }
754
193
  }
755
- // Segment walker: render content blocks in stream order, append-only.
756
- // Build desired segment plan from content[].
757
- {
758
- const blocks = host.streamingMessage.content;
759
- // Only prune provisional pre-tool prose after post-tool prose exists,
760
- // so MCP tool-only windows do not blank the assistant content.
761
- const { shouldPrune: shouldPruneProvisionalPreToolProse } = getProvisionalPreToolPrunePlan(host.streamingMessage);
762
- let desired = buildDesiredSegmentsForMessage(host.streamingMessage, {
763
- hideThinkingBlock: host.hideThinkingBlock,
764
- });
765
- desired = filterRedundantDiscussTextRuns(desired, blocks);
766
- // Claude Code MCP can emit provisional pre-tool prose that gets
767
- // superseded by post-tool output. Prune stale text-run segments so
768
- // the final assistant output remains below tool output.
769
- if (shouldPruneProvisionalPreToolProse) {
770
- if (orphanedSegments.length > 0) {
771
- const remainingOrphans = [];
772
- for (const orphan of orphanedSegments) {
773
- if (orphan.kind === "text-run"
774
- && orphan.contentType === "text"
775
- && isProvisionalPreToolProse(orphan.cachedText ?? "")) {
776
- host.chatContainer.removeChild(orphan.component);
777
- if (host.streamingComponent === orphan.component) {
778
- host.streamingComponent = undefined;
779
- }
780
- continue;
781
- }
782
- remainingOrphans.push(orphan);
783
- }
784
- orphanedSegments = remainingOrphans;
785
- }
786
- const desiredTextKeys = new Set(desired
787
- .filter((seg) => seg.kind === "text-run")
788
- .map((seg) => `${seg.contentType}:${seg.startIndex}`));
789
- const desiredToolIndices = new Set(desired
790
- .filter((seg) => seg.kind === "tool")
791
- .map((seg) => seg.contentIndex));
792
- const nextRendered = [];
793
- for (const seg of renderedSegments) {
794
- if (seg.kind === "text-run"
795
- && seg.contentType === "text"
796
- && !desiredTextKeys.has(`${seg.contentType}:${seg.startIndex}`)) {
797
- host.chatContainer.removeChild(seg.component);
798
- if (host.streamingComponent === seg.component) {
799
- host.streamingComponent = undefined;
800
- }
801
- continue;
802
- }
803
- if (seg.kind === "tool" && !desiredToolIndices.has(seg.contentIndex)) {
804
- continue;
805
- }
806
- nextRendered.push(seg);
807
- }
808
- renderedSegments = nextRendered;
809
- }
810
- // Append any newly needed segments (never reorder existing ones).
811
- for (const seg of desired) {
812
- if (seg.kind === "tool") {
813
- // Tool segments are already handled above via pendingTools; just
814
- // register them in renderedSegments if not yet tracked.
815
- const existing = renderedSegments.find((s) => s.kind === "tool" && s.contentIndex === seg.contentIndex);
816
- if (!existing) {
817
- const comp = host.pendingTools.get(seg.toolId);
818
- if (comp) {
819
- renderedSegments.push({ kind: "tool", contentIndex: seg.contentIndex, component: comp });
820
- }
821
- }
822
- }
823
- else {
824
- // text-run segment
825
- const existing = renderedSegments.find((s) => s.kind === "text-run" && s.startIndex === seg.startIndex && s.contentType === seg.contentType);
826
- if (!existing) {
827
- const segmentText = getTextFromContentBlocks(blocks, seg.startIndex, seg.endIndex);
828
- if (shouldSuppressRedundantHandoffText(host.session.messages, segmentText, orphanedSegments, renderedSegments)) {
829
- continue;
830
- }
831
- const comp = new AssistantMessageComponent(undefined, host.hideThinkingBlock, host.getMarkdownThemeWithSettings(), timestampFormat, { startIndex: seg.startIndex, endIndex: seg.endIndex });
832
- host.chatContainer.addChild(comp);
833
- markFirstVisibleAssistantOutput(host, seg.contentType, {
834
- contentIndex: seg.startIndex,
835
- });
836
- renderedSegments.push({
837
- kind: "text-run",
838
- startIndex: seg.startIndex,
839
- endIndex: seg.endIndex,
840
- contentType: seg.contentType,
841
- component: comp,
842
- cachedText: segmentText,
843
- });
844
- host.streamingComponent = comp;
845
- reconcileChatTurnConnections(host.chatContainer.children);
846
- }
847
- }
848
- }
849
- // Update all trailing text-run segments with the latest message so
850
- // streaming text grows in place.
851
- for (const seg of renderedSegments) {
852
- if (seg.kind === "text-run") {
853
- // Find corresponding desired segment to get current endIndex
854
- const d = desired.find((ds) => ds.kind === "text-run" && ds.startIndex === seg.startIndex && ds.contentType === seg.contentType);
855
- if (d && d.kind === "text-run" && d.endIndex !== seg.endIndex) {
856
- seg.endIndex = d.endIndex;
857
- seg.component.setRange({ startIndex: seg.startIndex, endIndex: seg.endIndex });
858
- }
859
- seg.cachedText = getTextFromContentBlocks(blocks, seg.startIndex, seg.endIndex);
860
- seg.component.updateContent(host.streamingMessage);
861
- }
862
- }
863
- // Keep streamingComponent pointing at the last text-run for message_end compatibility.
864
- const lastTextSeg = [...renderedSegments].reverse().find((s) => s.kind === "text-run");
865
- if (lastTextSeg && lastTextSeg.kind === "text-run") {
866
- host.streamingComponent = lastTextSeg.component;
867
- }
868
- }
194
+ runSegmentWalker(host, rs, timestampFormat);
869
195
  // Update index: fully processed blocks won't need re-scanning.
870
196
  // Keep the last block's index (it may still be accumulating data),
871
197
  // so we re-check it next time but skip all earlier ones.
872
198
  if (contentBlocks.length > 0) {
873
- lastProcessedContentIndex = Math.max(0, contentBlocks.length - 1);
199
+ rs.lastProcessedContentIndex = Math.max(0, contentBlocks.length - 1);
874
200
  }
875
201
  // Pinned message: mirror the latest assistant text above the editor
876
202
  // when tool executions push it out of the viewport.
877
- const hasTools = contentBlocks.some((c) => c.type === "toolCall" || c.type === "serverToolUse");
878
- if (hasTools)
879
- hasToolsInTurn = true;
880
- if (hasToolsInTurn) {
881
- const candidates = findLatestPinnableCandidates(contentBlocks);
882
- const termRows = host.ui.terminal.rows;
883
- const termCols = host.ui.terminal.columns;
884
- const pinnedMax = Math.max(3, Math.floor(termRows * 0.4));
885
- // Reserve rows for pinned zone + its border + editor + footer chrome.
886
- // Anything below this row budget is still in the viewport.
887
- const offscreenThreshold = Math.max(1, termRows - pinnedMax - 8);
888
- // Walk candidates newest→oldest; pick the first whose following
889
- // segments have pushed enough rows to scroll it off-screen.
890
- let picked;
891
- for (const c of candidates) {
892
- if (rowsRenderedAfterContentIndex(c.contentIndex, termCols) >= offscreenThreshold) {
893
- picked = c;
894
- break;
895
- }
896
- }
897
- if (picked) {
898
- if (picked.text !== lastPinnedText) {
899
- lastPinnedText = picked.text;
900
- if (!pinnedBorder) {
901
- // First time: create border + text component
902
- host.pinnedMessageContainer.clear();
903
- pinnedBorder = new DynamicBorder((str) => theme.fg("dim", str), "Working · Latest Output");
904
- pinnedBorder.startSpinner(host.ui, (str) => theme.fg("accent", str));
905
- host.pinnedMessageContainer.addChild(pinnedBorder);
906
- pinnedTextComponent = new Markdown(picked.text, 1, 0, host.getMarkdownThemeWithSettings());
907
- // Cap pinned content to ~40% of terminal height so tall output
908
- // doesn't exceed the viewport and cause render flashing.
909
- pinnedTextComponent.maxLines = pinnedMax;
910
- host.pinnedMessageContainer.addChild(pinnedTextComponent);
911
- pinnedZoneNeedsViewportRealign = true;
912
- // Hide the separate status loader — the pinned zone replaces it
913
- if (host.loadingAnimation) {
914
- host.loadingAnimation.stop();
915
- host.loadingAnimation = undefined;
916
- }
917
- host.statusContainer.clear();
918
- }
919
- else {
920
- // Update existing markdown component in-place
921
- pinnedTextComponent?.setText(picked.text);
922
- // Refresh maxLines in case terminal was resized
923
- if (pinnedTextComponent) {
924
- pinnedTextComponent.maxLines = pinnedMax;
925
- }
926
- }
927
- }
928
- }
929
- else if (pinnedBorder) {
930
- // Every candidate is still visible in the chat scrollback —
931
- // tear down the pinned zone so we don't duplicate on-screen text.
932
- tearDownPinnedZone(host);
933
- if (!host.loadingAnimation) {
934
- host.statusContainer.clear();
935
- startLoadingAnimation(host);
936
- }
937
- }
203
+ const { toreDownPinnedZone } = updatePinnedMessageZone(host, rs, contentBlocks);
204
+ if (toreDownPinnedZone && !host.loadingAnimation) {
205
+ host.statusContainer.clear();
206
+ startLoadingAnimation(host);
938
207
  }
939
208
  host.ui.requestRender();
940
209
  }
@@ -955,80 +224,12 @@ export async function handleAgentEvent(host, event) {
955
224
  const shouldRenderAssistant = hasVisibleAssistantContent(host.streamingMessage, host.hideThinkingBlock) ||
956
225
  ((host.streamingMessage.stopReason === "aborted" || host.streamingMessage.stopReason === "error") &&
957
226
  !hasAssistantToolBlocks(host.streamingMessage));
958
- const suppressRedundantHandoff = shouldSuppressEntireAssistantMessage(host.streamingMessage, host.session.messages, orphanedSegments);
227
+ const suppressRedundantHandoff = shouldSuppressEntireAssistantMessage(host.streamingMessage, host.session.messages, rs.orphanedSegments);
959
228
  // The final message_end payload can contain additional text/thinking
960
229
  // blocks that never arrived via message_update (e.g. SDK result
961
230
  // aggregation). Rebuild this in-flight turn from final content so
962
231
  // ranges/components don't keep stale partial indices.
963
- if (renderedSegments.length > 0) {
964
- const finalBlocks = host.streamingMessage.content;
965
- const desired = filterRedundantDiscussTextRuns(buildDesiredSegmentsForMessage(host.streamingMessage, {
966
- hideThinkingBlock: host.hideThinkingBlock,
967
- }), finalBlocks);
968
- const toolComponentsById = new Map();
969
- for (const [toolId, component] of host.pendingTools.entries()) {
970
- toolComponentsById.set(toolId, component);
971
- }
972
- for (const seg of renderedSegments) {
973
- host.chatContainer.removeChild(seg.component);
974
- if (seg.kind === "tool") {
975
- const priorBlocks = host.streamingMessage.content;
976
- const priorBlock = priorBlocks[seg.contentIndex];
977
- if (priorBlock?.id && !toolComponentsById.has(priorBlock.id)) {
978
- toolComponentsById.set(priorBlock.id, seg.component);
979
- }
980
- }
981
- }
982
- renderedSegments = [];
983
- host.streamingComponent = undefined;
984
- for (const seg of desired) {
985
- if (seg.kind === "tool") {
986
- const finalBlock = finalBlocks[seg.contentIndex];
987
- let component = toolComponentsById.get(seg.toolId);
988
- if (!component && finalBlock?.id) {
989
- component = host.pendingTools.get(finalBlock.id);
990
- }
991
- if (!component && finalBlock?.type === "toolCall") {
992
- component = new ToolExecutionComponent(finalBlock.name, finalBlock.arguments, { showImages: host.settingsManager.getShowImages() }, host.getRegisteredToolDefinition(finalBlock.name), host.ui);
993
- component.setExpanded(host.toolOutputExpanded);
994
- host.pendingTools.set(finalBlock.id, component);
995
- toolComponentsById.set(finalBlock.id, component);
996
- }
997
- else if (!component && finalBlock?.type === "serverToolUse") {
998
- component = new ToolExecutionComponent(finalBlock.name, finalBlock.input ?? {}, { showImages: host.settingsManager.getShowImages() }, undefined, host.ui);
999
- component.setExpanded(host.toolOutputExpanded);
1000
- host.pendingTools.set(finalBlock.id, component);
1001
- toolComponentsById.set(finalBlock.id, component);
1002
- }
1003
- if (component) {
1004
- host.chatContainer.addChild(component);
1005
- renderedSegments.push({ kind: "tool", contentIndex: seg.contentIndex, component });
1006
- }
1007
- continue;
1008
- }
1009
- const comp = new AssistantMessageComponent(undefined, host.hideThinkingBlock, host.getMarkdownThemeWithSettings(), timestampFormat, { startIndex: seg.startIndex, endIndex: seg.endIndex });
1010
- comp.updateContent(host.streamingMessage);
1011
- const segmentText = getTextFromContentBlocks(finalBlocks, seg.startIndex, seg.endIndex);
1012
- if (shouldSuppressRedundantHandoffText(host.session.messages, segmentText, orphanedSegments, renderedSegments)) {
1013
- continue;
1014
- }
1015
- host.chatContainer.addChild(comp);
1016
- markFirstVisibleAssistantOutput(host, seg.contentType, {
1017
- contentIndex: seg.startIndex,
1018
- source: "message_end_rebuild",
1019
- });
1020
- renderedSegments.push({
1021
- kind: "text-run",
1022
- startIndex: seg.startIndex,
1023
- endIndex: seg.endIndex,
1024
- contentType: seg.contentType,
1025
- component: comp,
1026
- cachedText: segmentText,
1027
- });
1028
- host.streamingComponent = comp;
1029
- }
1030
- reconcileChatTurnConnections(host.chatContainer.children);
1031
- }
232
+ rebuildSegmentsOnMessageEnd(host, rs, timestampFormat);
1032
233
  if (!host.streamingComponent && shouldRenderAssistant && !suppressRedundantHandoff) {
1033
234
  host.streamingComponent = new AssistantMessageComponent(undefined, host.hideThinkingBlock, host.getMarkdownThemeWithSettings(), timestampFormat, undefined);
1034
235
  host.chatContainer.addChild(host.streamingComponent);
@@ -1061,9 +262,7 @@ export async function handleAgentEvent(host, event) {
1061
262
  }
1062
263
  host.streamingComponent = undefined;
1063
264
  host.streamingMessage = undefined;
1064
- renderedSegments = [];
1065
- orphanedSegments = [];
1066
- lastContentLength = 0;
265
+ rs.resetStreamingSegments();
1067
266
  // Clear pinned output once the message is finalized in the chat
1068
267
  // container — prevents duplicate display when the agent continues
1069
268
  // (e.g. form elicitation) after the assistant message ends.
@@ -1075,7 +274,7 @@ export async function handleAgentEvent(host, event) {
1075
274
  case "tool_execution_start": {
1076
275
  const { component, created } = registerPendingToolComponent(host, event.toolCallId, event.toolName, event.args, "standalone", () => new ToolExecutionComponent(event.toolName, event.args, { showImages: host.settingsManager.getShowImages() }, host.getRegisteredToolDefinition(event.toolName), host.ui));
1077
276
  if (created) {
1078
- renderedSegments.push({ kind: "tool", contentIndex: Number.MAX_SAFE_INTEGER, component });
277
+ rs.renderedSegments.push({ kind: "tool", contentIndex: Number.MAX_SAFE_INTEGER, component });
1079
278
  }
1080
279
  host.ui.requestRender();
1081
280
  break;
@@ -1121,9 +320,7 @@ export async function handleAgentEvent(host, event) {
1121
320
  replaceCompactToolRowsWithPhaseSummary(host);
1122
321
  host.streamingComponent = undefined;
1123
322
  host.streamingMessage = undefined;
1124
- renderedSegments = [];
1125
- orphanedSegments = [];
1126
- lastContentLength = 0;
323
+ rs.resetForSessionChange();
1127
324
  host.pendingTools.clear();
1128
325
  // Pinned output is only useful while work is actively streaming.
1129
326
  // Keep chat history as the single source after completion.