@opengsd/gsd-pi 1.3.0-dev.eed73bea → 1.4.0

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 (505) hide show
  1. package/README.md +10 -10
  2. package/dist/cli.js +17 -4
  3. package/dist/headless-answers.js +1 -1
  4. package/dist/headless-ui.d.ts +1 -1
  5. package/dist/headless-ui.js +1 -1
  6. package/dist/headless.js +1 -1
  7. package/dist/resources/.managed-resources-content-hash +1 -1
  8. package/dist/resources/extensions/claude-code-cli/models.js +9 -0
  9. package/dist/resources/extensions/gsd/artifact-verification.js +427 -0
  10. package/dist/resources/extensions/gsd/auto/session.js +11 -0
  11. package/dist/resources/extensions/gsd/auto/verification-retry-policy.js +1 -1
  12. package/dist/resources/extensions/gsd/auto-artifact-paths.js +28 -1
  13. package/dist/resources/extensions/gsd/auto-budget.js +5 -1
  14. package/dist/resources/extensions/gsd/auto-dispatch.js +17 -6
  15. package/dist/resources/extensions/gsd/auto-post-unit.js +1 -1
  16. package/dist/resources/extensions/gsd/auto-prompts.js +8 -24
  17. package/dist/resources/extensions/gsd/auto-recovery.js +9 -517
  18. package/dist/resources/extensions/gsd/auto-runtime-state.js +4 -5
  19. package/dist/resources/extensions/gsd/auto-timeout-recovery.js +3 -3
  20. package/dist/resources/extensions/gsd/auto-verification.js +5 -2
  21. package/dist/resources/extensions/gsd/auto.js +19 -10
  22. package/dist/resources/extensions/gsd/bootstrap/core-session-tools.js +39 -0
  23. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +5 -4
  24. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +2 -0
  25. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +33 -18
  26. package/dist/resources/extensions/gsd/bootstrap/tool-call-loop-guard.js +48 -36
  27. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +15 -4
  28. package/dist/resources/extensions/gsd/closeout-consistency-gate.js +3 -1
  29. package/dist/resources/extensions/gsd/commands/handlers/auto.js +1 -1
  30. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
  31. package/dist/resources/extensions/gsd/commands-usage.js +1 -1
  32. package/dist/resources/extensions/gsd/db/engine.js +4 -1
  33. package/dist/resources/extensions/gsd/db-memory-fts-schema.js +34 -0
  34. package/dist/resources/extensions/gsd/db-workspace.js +55 -3
  35. package/dist/resources/extensions/gsd/delegation-policy.js +1 -1
  36. package/dist/resources/extensions/gsd/docs/preferences-reference.md +2 -2
  37. package/dist/resources/extensions/gsd/doctor-engine-checks.js +192 -4
  38. package/dist/resources/extensions/gsd/doctor-runtime-checks.js +3 -0
  39. package/dist/resources/extensions/gsd/exec-sandbox.js +8 -2
  40. package/dist/resources/extensions/gsd/flat-phase-migration.js +45 -0
  41. package/dist/resources/extensions/gsd/git-service.js +57 -0
  42. package/dist/resources/extensions/gsd/gitignore.js +4 -1
  43. package/dist/resources/extensions/gsd/guided-flow-queue.js +20 -13
  44. package/dist/resources/extensions/gsd/json-persistence.js +20 -0
  45. package/dist/resources/extensions/gsd/knowledge-backfill.js +18 -11
  46. package/dist/resources/extensions/gsd/layout-policy.js +3 -1
  47. package/dist/resources/extensions/gsd/markdown-renderer.js +2 -7
  48. package/dist/resources/extensions/gsd/memory-backfill.js +28 -21
  49. package/dist/resources/extensions/gsd/metrics.js +4 -4
  50. package/dist/resources/extensions/gsd/migrate-external.js +11 -2
  51. package/dist/resources/extensions/gsd/migration-auto-check.js +22 -0
  52. package/dist/resources/extensions/gsd/milestone-ids.js +32 -2
  53. package/dist/resources/extensions/gsd/orphan-stash-audit.js +92 -16
  54. package/dist/resources/extensions/gsd/prompts/code-review.md +6 -4
  55. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +2 -2
  56. package/dist/resources/extensions/gsd/prompts/plan-slice.md +3 -3
  57. package/dist/resources/extensions/gsd/root-write-leak-guard.js +7 -1
  58. package/dist/resources/extensions/gsd/safety/destructive-guard.js +8 -1
  59. package/dist/resources/extensions/gsd/safety/evidence-collector.js +16 -1
  60. package/dist/resources/extensions/gsd/safety/file-change-validator.js +5 -2
  61. package/dist/resources/extensions/gsd/skills/gsd-headless/references/commands.md +1 -1
  62. package/dist/resources/extensions/gsd/state/derive/cache.js +28 -0
  63. package/dist/resources/extensions/gsd/state/derive/db-open.js +39 -0
  64. package/dist/resources/extensions/gsd/state/derive/from-db.js +452 -0
  65. package/dist/resources/extensions/gsd/state/derive/index.js +75 -0
  66. package/dist/resources/extensions/gsd/state/derive/interrupted-work.js +21 -0
  67. package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-render.js +27 -33
  68. package/dist/resources/extensions/gsd/state-reconciliation/index.js +48 -23
  69. package/dist/resources/extensions/gsd/state-reconciliation/registry.js +32 -28
  70. package/dist/resources/extensions/gsd/state.js +12 -611
  71. package/dist/resources/extensions/gsd/tools/complete-milestone.js +4 -2
  72. package/dist/resources/extensions/gsd/tools/complete-slice.js +3 -34
  73. package/dist/resources/extensions/gsd/tools/complete-task.js +8 -41
  74. package/dist/resources/extensions/gsd/tools/plan-slice.js +59 -44
  75. package/dist/resources/extensions/gsd/tools/plan-task.js +28 -5
  76. package/dist/resources/extensions/gsd/undo.js +43 -6
  77. package/dist/resources/extensions/gsd/unit-registry.js +11 -3
  78. package/dist/resources/extensions/gsd/verification-gate.js +7 -0
  79. package/dist/resources/extensions/gsd/workflow-manifest.js +126 -13
  80. package/dist/resources/extensions/gsd/workflow-projections.js +19 -14
  81. package/dist/resources/extensions/gsd/worktree-manager.js +44 -2
  82. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  83. package/dist/web/standalone/.next/BUILD_ID +1 -1
  84. package/dist/web/standalone/.next/app-path-routes-manifest.json +10 -10
  85. package/dist/web/standalone/.next/build-manifest.json +4 -4
  86. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  87. package/dist/web/standalone/.next/react-loadable-manifest.json +9 -9
  88. package/dist/web/standalone/.next/required-server-files.json +3 -3
  89. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  90. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  91. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  92. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  94. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  96. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  97. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  98. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  99. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  100. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  101. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  102. package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
  103. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  104. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  105. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  106. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  107. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  108. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  109. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  111. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  113. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  115. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  116. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  121. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  122. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  123. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  124. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  125. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  126. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  127. package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
  128. package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
  129. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  130. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  131. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  132. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  133. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  134. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  135. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  136. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  137. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  138. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  139. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  140. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  141. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  142. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  143. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  144. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  145. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  146. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  147. package/dist/web/standalone/.next/server/app/api/mcp-connections/route.js +1 -1
  148. package/dist/web/standalone/.next/server/app/api/mcp-connections/route_client-reference-manifest.js +1 -1
  149. package/dist/web/standalone/.next/server/app/api/notifications/route.js +2 -2
  150. package/dist/web/standalone/.next/server/app/api/notifications/route_client-reference-manifest.js +1 -1
  151. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  152. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  153. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  154. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  155. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  156. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  157. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  158. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  159. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
  160. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  161. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  162. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  163. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  164. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  165. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  166. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  167. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  168. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  169. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  170. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  171. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  172. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  173. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  174. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  175. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  176. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  177. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  178. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  179. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +1 -1
  180. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  181. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  182. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  183. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +1 -1
  184. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  185. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
  186. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  187. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  188. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  189. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  190. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  191. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  192. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  193. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  194. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  195. package/dist/web/standalone/.next/server/app/index.html +1 -1
  196. package/dist/web/standalone/.next/server/app/index.rsc +4 -4
  197. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  198. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  199. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  200. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  201. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  202. package/dist/web/standalone/.next/server/app/page.js +2 -2
  203. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  204. package/dist/web/standalone/.next/server/app-paths-manifest.json +10 -10
  205. package/dist/web/standalone/.next/server/chunks/2842.js +4 -4
  206. package/dist/web/standalone/.next/server/chunks/8357.js +3 -3
  207. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  208. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  209. package/dist/web/standalone/.next/server/middleware.js +3 -3
  210. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  211. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  212. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  213. package/dist/web/standalone/.next/static/chunks/{2659.b7b129ee6a769448.js → 2659.58e950899a9bb82f.js} +1 -1
  214. package/dist/web/standalone/.next/static/chunks/2772.a7c1fcc69a4685ef.js +1 -0
  215. package/dist/web/standalone/.next/static/chunks/{3616.3c60753b8ffcbd2e.js → 3616.61a2af74bb8833c8.js} +1 -1
  216. package/dist/web/standalone/.next/static/chunks/{4283.8e446784528ed9dc.js → 4283.d0d9e0a955e441cb.js} +1 -1
  217. package/dist/web/standalone/.next/static/chunks/{5826.a46ecdd1cfe8dabc.js → 5826.5421d66c72b9f34e.js} +1 -1
  218. package/dist/web/standalone/.next/static/chunks/{8785.481aa5869991b760.js → 8785.e29b3134cab1d153.js} +1 -1
  219. package/dist/web/standalone/.next/static/chunks/8937.640dc9c2aaa1dfad.js +10 -0
  220. package/dist/web/standalone/.next/static/chunks/app/_not-found/page-49f565245e1e4afe.js +1 -0
  221. package/dist/web/standalone/.next/static/chunks/app/{layout-4ae2d68984392bbf.js → layout-b35cbfff38aaf4cf.js} +1 -1
  222. package/dist/web/standalone/.next/static/chunks/app/page-bc4c723c6a19c6f0.js +1 -0
  223. package/dist/web/standalone/.next/static/chunks/{main-app-90d1d8d5e5d2dc6b.js → main-app-590a74400e35f685.js} +1 -1
  224. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ec3eaffc9785ba48.js +1 -0
  225. package/dist/web/standalone/.next/static/chunks/webpack-9c401904f87ded16.js +1 -0
  226. package/dist/web/standalone/node_modules/@gsd/native/package.json +1 -1
  227. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  228. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  229. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  230. package/dist/web/standalone/server.js +1 -1
  231. package/package.json +6 -6
  232. package/packages/cloud-mcp-gateway/package.json +2 -2
  233. package/packages/contracts/package.json +1 -1
  234. package/packages/daemon/package.json +4 -4
  235. package/packages/gsd-agent-core/dist/agent-session.d.ts +1 -0
  236. package/packages/gsd-agent-core/dist/agent-session.d.ts.map +1 -1
  237. package/packages/gsd-agent-core/dist/agent-session.js +3 -0
  238. package/packages/gsd-agent-core/dist/agent-session.js.map +1 -1
  239. package/packages/gsd-agent-core/dist/extension-ui-snapshot.d.ts +41 -0
  240. package/packages/gsd-agent-core/dist/extension-ui-snapshot.d.ts.map +1 -0
  241. package/packages/gsd-agent-core/dist/extension-ui-snapshot.js +62 -0
  242. package/packages/gsd-agent-core/dist/extension-ui-snapshot.js.map +1 -0
  243. package/packages/gsd-agent-core/dist/index.d.ts +2 -0
  244. package/packages/gsd-agent-core/dist/index.d.ts.map +1 -1
  245. package/packages/gsd-agent-core/dist/index.js +2 -0
  246. package/packages/gsd-agent-core/dist/index.js.map +1 -1
  247. package/packages/gsd-agent-core/dist/session/agent-session-events.js +1 -1
  248. package/packages/gsd-agent-core/dist/session/agent-session-events.js.map +1 -1
  249. package/packages/gsd-agent-core/dist/session/agent-session-host.d.ts +1 -0
  250. package/packages/gsd-agent-core/dist/session/agent-session-host.d.ts.map +1 -1
  251. package/packages/gsd-agent-core/dist/session/agent-session-host.js.map +1 -1
  252. package/packages/gsd-agent-core/dist/session/agent-session-prompt.d.ts +5 -0
  253. package/packages/gsd-agent-core/dist/session/agent-session-prompt.d.ts.map +1 -1
  254. package/packages/gsd-agent-core/dist/session/agent-session-prompt.js +60 -3
  255. package/packages/gsd-agent-core/dist/session/agent-session-prompt.js.map +1 -1
  256. package/packages/gsd-agent-core/dist/transcript-store.d.ts +58 -0
  257. package/packages/gsd-agent-core/dist/transcript-store.d.ts.map +1 -0
  258. package/packages/gsd-agent-core/dist/transcript-store.js +132 -0
  259. package/packages/gsd-agent-core/dist/transcript-store.js.map +1 -0
  260. package/packages/gsd-agent-core/package.json +5 -5
  261. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts +2 -0
  262. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  263. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +25 -11
  264. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
  265. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller-latency.d.ts +4 -0
  266. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller-latency.d.ts.map +1 -0
  267. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller-latency.js +7 -0
  268. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller-latency.js.map +1 -0
  269. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts +3 -24
  270. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  271. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +26 -841
  272. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  273. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-handoff-filter.d.ts +58 -0
  274. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-handoff-filter.d.ts.map +1 -0
  275. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-handoff-filter.js +312 -0
  276. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-handoff-filter.js.map +1 -0
  277. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-pinned-zone.d.ts +31 -0
  278. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-pinned-zone.d.ts.map +1 -0
  279. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-pinned-zone.js +130 -0
  280. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-pinned-zone.js.map +1 -0
  281. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.d.ts +15 -0
  282. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.d.ts.map +1 -0
  283. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.js +258 -0
  284. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.js.map +1 -0
  285. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-tool-rollup.d.ts +13 -0
  286. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-tool-rollup.d.ts.map +1 -0
  287. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-tool-rollup.js +118 -0
  288. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-tool-rollup.js.map +1 -0
  289. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-state.d.ts +9 -0
  290. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
  291. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-state.js +1 -1
  292. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-state.js.map +1 -1
  293. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-ui-state.d.ts +54 -0
  294. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-ui-state.d.ts.map +1 -0
  295. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-ui-state.js +20 -0
  296. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-ui-state.js.map +1 -0
  297. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.d.ts +4 -1
  298. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  299. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js +9 -0
  300. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js.map +1 -1
  301. package/packages/gsd-agent-modes/dist/modes/interactive/streaming-render-state.d.ts +56 -0
  302. package/packages/gsd-agent-modes/dist/modes/interactive/streaming-render-state.d.ts.map +1 -0
  303. package/packages/gsd-agent-modes/dist/modes/interactive/streaming-render-state.js +44 -0
  304. package/packages/gsd-agent-modes/dist/modes/interactive/streaming-render-state.js.map +1 -0
  305. package/packages/gsd-agent-modes/dist/modes/interactive/tui-transcript-tracker.d.ts +5 -0
  306. package/packages/gsd-agent-modes/dist/modes/interactive/tui-transcript-tracker.d.ts.map +1 -0
  307. package/packages/gsd-agent-modes/dist/modes/interactive/tui-transcript-tracker.js +77 -0
  308. package/packages/gsd-agent-modes/dist/modes/interactive/tui-transcript-tracker.js.map +1 -0
  309. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-client.d.ts +2 -0
  310. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  311. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-client.js +23 -5
  312. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-client.js.map +1 -1
  313. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  314. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.js +18 -0
  315. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.js.map +1 -1
  316. package/packages/gsd-agent-modes/package.json +7 -7
  317. package/packages/mcp-server/dist/workflow-tools.js +1 -1
  318. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  319. package/packages/mcp-server/package.json +4 -4
  320. package/packages/native/package.json +1 -1
  321. package/packages/pi-agent-core/dist/agent-loop.js +3 -2
  322. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  323. package/packages/pi-agent-core/package.json +1 -1
  324. package/packages/pi-ai/dist/models.generated.d.ts +46 -0
  325. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  326. package/packages/pi-ai/dist/models.generated.js +38 -0
  327. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  328. package/packages/pi-ai/package.json +1 -1
  329. package/packages/pi-coding-agent/README.md +3 -2
  330. package/packages/pi-coding-agent/dist/core/session-manager-context.d.ts +9 -0
  331. package/packages/pi-coding-agent/dist/core/session-manager-context.d.ts.map +1 -0
  332. package/packages/pi-coding-agent/dist/core/session-manager-context.js +94 -0
  333. package/packages/pi-coding-agent/dist/core/session-manager-context.js.map +1 -0
  334. package/packages/pi-coding-agent/dist/core/session-manager-list.d.ts +8 -0
  335. package/packages/pi-coding-agent/dist/core/session-manager-list.d.ts.map +1 -0
  336. package/packages/pi-coding-agent/dist/core/session-manager-list.js +244 -0
  337. package/packages/pi-coding-agent/dist/core/session-manager-list.js.map +1 -0
  338. package/packages/pi-coding-agent/dist/core/session-manager-migration.d.ts +12 -0
  339. package/packages/pi-coding-agent/dist/core/session-manager-migration.d.ts.map +1 -0
  340. package/packages/pi-coding-agent/dist/core/session-manager-migration.js +84 -0
  341. package/packages/pi-coding-agent/dist/core/session-manager-migration.js.map +1 -0
  342. package/packages/pi-coding-agent/dist/core/session-manager-types.d.ts +135 -0
  343. package/packages/pi-coding-agent/dist/core/session-manager-types.d.ts.map +1 -0
  344. package/packages/pi-coding-agent/dist/core/session-manager-types.js +2 -0
  345. package/packages/pi-coding-agent/dist/core/session-manager-types.js.map +1 -0
  346. package/packages/pi-coding-agent/dist/core/session-manager.d.ts +6 -154
  347. package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
  348. package/packages/pi-coding-agent/dist/core/session-manager.js +22 -459
  349. package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
  350. package/packages/pi-coding-agent/dist/theme/theme-schema.d.ts +75 -75
  351. package/packages/pi-coding-agent/dist/theme/theme-schema.d.ts.map +1 -1
  352. package/packages/pi-coding-agent/dist/theme/theme-schema.js +1 -1
  353. package/packages/pi-coding-agent/dist/theme/theme-schema.js.map +1 -1
  354. package/packages/pi-coding-agent/dist/theme/theme.d.ts.map +1 -1
  355. package/packages/pi-coding-agent/dist/theme/theme.js +11 -7
  356. package/packages/pi-coding-agent/dist/theme/theme.js.map +1 -1
  357. package/packages/pi-coding-agent/package.json +7 -7
  358. package/packages/pi-tui/package.json +2 -2
  359. package/packages/rpc-client/package.json +2 -2
  360. package/pkg/dist/theme/theme-schema.d.ts +75 -75
  361. package/pkg/dist/theme/theme-schema.d.ts.map +1 -1
  362. package/pkg/dist/theme/theme-schema.js +1 -1
  363. package/pkg/dist/theme/theme-schema.js.map +1 -1
  364. package/pkg/dist/theme/theme.d.ts.map +1 -1
  365. package/pkg/dist/theme/theme.js +11 -7
  366. package/pkg/dist/theme/theme.js.map +1 -1
  367. package/pkg/package.json +1 -1
  368. package/src/resources/extensions/claude-code-cli/models.ts +9 -0
  369. package/src/resources/extensions/claude-code-cli/tests/index.test.ts +14 -0
  370. package/src/resources/extensions/gsd/artifact-verification.ts +464 -0
  371. package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
  372. package/src/resources/extensions/gsd/auto/session.ts +14 -0
  373. package/src/resources/extensions/gsd/auto/verification-retry-policy.ts +3 -1
  374. package/src/resources/extensions/gsd/auto-artifact-paths.ts +47 -1
  375. package/src/resources/extensions/gsd/auto-budget.ts +12 -1
  376. package/src/resources/extensions/gsd/auto-dispatch.ts +17 -7
  377. package/src/resources/extensions/gsd/auto-post-unit.ts +1 -1
  378. package/src/resources/extensions/gsd/auto-prompts.ts +7 -23
  379. package/src/resources/extensions/gsd/auto-recovery.ts +13 -518
  380. package/src/resources/extensions/gsd/auto-runtime-state.ts +4 -5
  381. package/src/resources/extensions/gsd/auto-timeout-recovery.ts +3 -2
  382. package/src/resources/extensions/gsd/auto-verification.ts +5 -1
  383. package/src/resources/extensions/gsd/auto.ts +23 -8
  384. package/src/resources/extensions/gsd/bootstrap/core-session-tools.ts +44 -0
  385. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +5 -4
  386. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +2 -0
  387. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +43 -18
  388. package/src/resources/extensions/gsd/bootstrap/tests/write-gate-basepath.test.ts +49 -1
  389. package/src/resources/extensions/gsd/bootstrap/tool-call-loop-guard.ts +50 -37
  390. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +16 -4
  391. package/src/resources/extensions/gsd/closeout-consistency-gate.ts +2 -1
  392. package/src/resources/extensions/gsd/commands/handlers/auto.ts +1 -1
  393. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
  394. package/src/resources/extensions/gsd/commands-usage.ts +1 -1
  395. package/src/resources/extensions/gsd/db/engine.ts +8 -1
  396. package/src/resources/extensions/gsd/db-memory-fts-schema.ts +45 -0
  397. package/src/resources/extensions/gsd/db-workspace.ts +61 -3
  398. package/src/resources/extensions/gsd/delegation-policy.ts +1 -1
  399. package/src/resources/extensions/gsd/docs/preferences-reference.md +2 -2
  400. package/src/resources/extensions/gsd/doctor-engine-checks.ts +233 -5
  401. package/src/resources/extensions/gsd/doctor-runtime-checks.ts +3 -0
  402. package/src/resources/extensions/gsd/doctor-types.ts +3 -0
  403. package/src/resources/extensions/gsd/exec-sandbox.ts +7 -2
  404. package/src/resources/extensions/gsd/flat-phase-migration.ts +44 -0
  405. package/src/resources/extensions/gsd/git-service.ts +64 -0
  406. package/src/resources/extensions/gsd/gitignore.ts +4 -1
  407. package/src/resources/extensions/gsd/guided-flow-queue.ts +20 -13
  408. package/src/resources/extensions/gsd/json-persistence.ts +20 -0
  409. package/src/resources/extensions/gsd/knowledge-backfill.ts +16 -10
  410. package/src/resources/extensions/gsd/layout-policy.ts +2 -1
  411. package/src/resources/extensions/gsd/markdown-renderer.ts +2 -10
  412. package/src/resources/extensions/gsd/memory-backfill.ts +26 -20
  413. package/src/resources/extensions/gsd/metrics.ts +4 -4
  414. package/src/resources/extensions/gsd/migrate-external.ts +8 -2
  415. package/src/resources/extensions/gsd/migration-auto-check.ts +23 -0
  416. package/src/resources/extensions/gsd/milestone-ids.ts +31 -2
  417. package/src/resources/extensions/gsd/orphan-stash-audit.ts +108 -20
  418. package/src/resources/extensions/gsd/prompts/code-review.md +6 -4
  419. package/src/resources/extensions/gsd/prompts/plan-milestone.md +2 -2
  420. package/src/resources/extensions/gsd/prompts/plan-slice.md +3 -3
  421. package/src/resources/extensions/gsd/root-write-leak-guard.ts +13 -1
  422. package/src/resources/extensions/gsd/safety/destructive-guard.ts +9 -1
  423. package/src/resources/extensions/gsd/safety/evidence-collector.ts +16 -1
  424. package/src/resources/extensions/gsd/safety/file-change-validator.ts +8 -2
  425. package/src/resources/extensions/gsd/skills/gsd-headless/references/commands.md +1 -1
  426. package/src/resources/extensions/gsd/state/derive/cache.ts +46 -0
  427. package/src/resources/extensions/gsd/state/derive/db-open.ts +45 -0
  428. package/src/resources/extensions/gsd/state/derive/from-db.ts +561 -0
  429. package/src/resources/extensions/gsd/state/derive/index.ts +104 -0
  430. package/src/resources/extensions/gsd/state/derive/interrupted-work.ts +31 -0
  431. package/src/resources/extensions/gsd/state-reconciliation/drift/stale-render.ts +36 -30
  432. package/src/resources/extensions/gsd/state-reconciliation/index.ts +50 -24
  433. package/src/resources/extensions/gsd/state-reconciliation/registry.ts +43 -28
  434. package/src/resources/extensions/gsd/state.ts +32 -732
  435. package/src/resources/extensions/gsd/tests/artifact-retry-cap.test.ts +4 -3
  436. package/src/resources/extensions/gsd/tests/auto-artifact-paths.test.ts +98 -1
  437. package/src/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +14 -5
  438. package/src/resources/extensions/gsd/tests/auto-pause-double-entry-guard.test.ts +36 -0
  439. package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +182 -4
  440. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +35 -0
  441. package/src/resources/extensions/gsd/tests/auto-retry-mcp-churn-fixes.test.ts +30 -0
  442. package/src/resources/extensions/gsd/tests/commands-gsd-core.test.ts +1 -0
  443. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +28 -0
  444. package/src/resources/extensions/gsd/tests/complete-slice-reopen-handoff.test.ts +69 -0
  445. package/src/resources/extensions/gsd/tests/db-memory-fts-schema.test.ts +58 -3
  446. package/src/resources/extensions/gsd/tests/decisions-projection-from-memories.test.ts +51 -0
  447. package/src/resources/extensions/gsd/tests/destructive-guard.test.ts +44 -0
  448. package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +143 -0
  449. package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +239 -2
  450. package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +16 -0
  451. package/src/resources/extensions/gsd/tests/file-change-validator.test.ts +40 -0
  452. package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +37 -2
  453. package/src/resources/extensions/gsd/tests/gitignore-bg-shell-runtime.test.ts +4 -0
  454. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +11 -2
  455. package/src/resources/extensions/gsd/tests/json-persistence-atomic.test.ts +47 -0
  456. package/src/resources/extensions/gsd/tests/knowledge-backfill-projection.test.ts +35 -0
  457. package/src/resources/extensions/gsd/tests/memory-store.test.ts +48 -0
  458. package/src/resources/extensions/gsd/tests/merge-closeout-consistency-gate.test.ts +31 -1
  459. package/src/resources/extensions/gsd/tests/metrics-prune-cache-invalidation.test.ts +6 -0
  460. package/src/resources/extensions/gsd/tests/metrics.test.ts +1 -0
  461. package/src/resources/extensions/gsd/tests/migrate-external-worktree.test.ts +57 -0
  462. package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +70 -0
  463. package/src/resources/extensions/gsd/tests/orphan-stash-audit.test.ts +91 -7
  464. package/src/resources/extensions/gsd/tests/parsers-legacy-importers.test.ts +4 -1
  465. package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +10 -0
  466. package/src/resources/extensions/gsd/tests/plan-slice.test.ts +103 -4
  467. package/src/resources/extensions/gsd/tests/plan-task.test.ts +44 -1
  468. package/src/resources/extensions/gsd/tests/post-mutation-hook.test.ts +4 -2
  469. package/src/resources/extensions/gsd/tests/progressive-planning.test.ts +50 -14
  470. package/src/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +58 -0
  471. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +8 -6
  472. package/src/resources/extensions/gsd/tests/register-hooks-loop-guard-auto.test.ts +103 -0
  473. package/src/resources/extensions/gsd/tests/root-write-leak-guard.test.ts +20 -1
  474. package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +15 -3
  475. package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +56 -0
  476. package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +115 -0
  477. package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +140 -0
  478. package/src/resources/extensions/gsd/tests/tool-param-optionality.test.ts +6 -3
  479. package/src/resources/extensions/gsd/tests/undo.test.ts +74 -0
  480. package/src/resources/extensions/gsd/tests/uok-gitops-turn-action.test.ts +38 -0
  481. package/src/resources/extensions/gsd/tests/verification-retry-policy.test.ts +69 -4
  482. package/src/resources/extensions/gsd/tests/workflow-manifest.test.ts +96 -15
  483. package/src/resources/extensions/gsd/tests/workflow-mcp-preflight.test.ts +1 -0
  484. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +8 -4
  485. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +6 -5
  486. package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +39 -0
  487. package/src/resources/extensions/gsd/tools/complete-milestone.ts +3 -2
  488. package/src/resources/extensions/gsd/tools/complete-slice.ts +3 -30
  489. package/src/resources/extensions/gsd/tools/complete-task.ts +7 -39
  490. package/src/resources/extensions/gsd/tools/plan-slice.ts +60 -47
  491. package/src/resources/extensions/gsd/tools/plan-task.ts +30 -5
  492. package/src/resources/extensions/gsd/undo.ts +47 -6
  493. package/src/resources/extensions/gsd/unit-registry.ts +11 -3
  494. package/src/resources/extensions/gsd/verification-gate.ts +8 -0
  495. package/src/resources/extensions/gsd/workflow-manifest.ts +141 -7
  496. package/src/resources/extensions/gsd/workflow-projections.ts +20 -14
  497. package/src/resources/extensions/gsd/worktree-manager.ts +41 -1
  498. package/dist/web/standalone/.next/static/chunks/2772.bfa657f49f955239.js +0 -1
  499. package/dist/web/standalone/.next/static/chunks/796.3976108148518f7d.js +0 -10
  500. package/dist/web/standalone/.next/static/chunks/app/_not-found/page-a6fb1847f67f167c.js +0 -1
  501. package/dist/web/standalone/.next/static/chunks/app/page-6644fc6ee8ca1247.js +0 -1
  502. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-1115d46ac61b9823.js +0 -1
  503. package/dist/web/standalone/.next/static/chunks/webpack-7c1d97e39be2da11.js +0 -1
  504. /package/dist/web/standalone/.next/static/{SzEuqWX37DR9MEpEuQjP1 → zQLcXm0Za8lexoXtgDpMG}/_buildManifest.js +0 -0
  505. /package/dist/web/standalone/.next/static/{SzEuqWX37DR9MEpEuQjP1 → zQLcXm0Za8lexoXtgDpMG}/_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,479 +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, seg.contentType).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, contentType = "text") {
193
- const parts = [];
194
- for (let i = startIndex; i <= endIndex && i < blocks.length; i++) {
195
- const block = blocks[i];
196
- if (contentType === "text" && block?.type === "text" && typeof block.text === "string" && block.text.trim()) {
197
- parts.push(block.text.trim());
198
- }
199
- else if (contentType === "thinking"
200
- && block?.type === "thinking"
201
- && typeof block.thinking === "string"
202
- && block.thinking.trim()) {
203
- parts.push(block.thinking.trim());
204
- }
205
- }
206
- return parts.join("\n\n");
207
- }
208
- function filterRedundantDiscussTextRuns(desired, blocks) {
209
- const textRuns = desired.filter((seg) => seg.kind === "text-run" && seg.contentType === "text");
210
- if (textRuns.length < 2)
211
- return desired;
212
- const skipStarts = new Set();
213
- let lastKeptText;
214
- for (const seg of textRuns) {
215
- const text = getTextFromContentBlocks(blocks, seg.startIndex, seg.endIndex);
216
- if (lastKeptText && isRedundantDiscussRestatement(lastKeptText, text)) {
217
- skipStarts.add(seg.startIndex);
218
- }
219
- else {
220
- lastKeptText = text;
221
- }
222
- }
223
- return desired.filter((seg) => !(seg.kind === "text-run" && seg.contentType === "text" && skipStarts.has(seg.startIndex)));
224
- }
225
- function extractAssistantText(msg) {
226
- if (!msg)
227
- return "";
228
- const content = msg.content;
229
- if (typeof content === "string")
230
- return content;
231
- if (!Array.isArray(content))
232
- return "";
233
- const parts = [];
234
- for (const block of content) {
235
- if (!block || typeof block !== "object")
236
- continue;
237
- if (block.type === "text" && typeof block.text === "string") {
238
- parts.push(block.text);
239
- }
240
- }
241
- return parts.join("\n");
242
- }
243
- function latestPriorUserFacingText(orphaned, rendered) {
244
- const runs = [...orphaned, ...rendered].filter((seg) => seg.kind === "text-run" && seg.contentType === "text");
245
- return runs.at(-1)?.cachedText;
246
- }
247
- /**
248
- * Walk session history backward for the previous assistant prose block, skipping
249
- * toolResult rows. Used when Claude Code emits a second assistant message
250
- * (new timestamp) after tools in the same prompt.
251
- */
252
- export function priorAssistantTextFromSession(messages, opts) {
253
- let assistantFromEnd = 0;
254
- for (let i = messages.length - 1; i >= 0; i--) {
255
- const message = messages[i];
256
- if (!message)
257
- continue;
258
- if (message.role === "user")
259
- return undefined;
260
- if (message.role === "toolResult")
261
- continue;
262
- if (message.role === "assistant") {
263
- const text = extractAssistantText(message).trim();
264
- if (!text)
265
- continue;
266
- if (opts?.skipLastAssistant) {
267
- assistantFromEnd += 1;
268
- if (assistantFromEnd === 1)
269
- continue;
270
- }
271
- return text;
272
- }
273
- }
274
- return undefined;
275
- }
276
- function shouldSuppressEntireAssistantMessage(message, sessionMessages, orphaned) {
277
- const textBlocks = (message.content ?? []).filter((block) => block?.type === "text" && typeof block.text === "string" && block.text.trim());
278
- if (textBlocks.length !== 1)
279
- return false;
280
- return shouldSuppressRedundantHandoffText(sessionMessages, textBlocks[0].text, orphaned, []);
281
- }
282
- function shouldSuppressRedundantHandoffText(sessionMessages, currentText, orphaned, rendered) {
283
- const next = currentText.trim();
284
- if (!next)
285
- return false;
286
- const priorInline = latestPriorUserFacingText(orphaned, rendered);
287
- if (priorInline && isRedundantDiscussRestatement(priorInline, next)) {
288
- return true;
289
- }
290
- const last = sessionMessages[sessionMessages.length - 1];
291
- const skipLastAssistant = last?.role === "assistant" && extractAssistantText(last).trim() === next;
292
- const priorSession = priorAssistantTextFromSession(sessionMessages, { skipLastAssistant });
293
- return !!(priorSession && isRedundantDiscussRestatement(priorSession, next));
294
- }
295
- function buildDesiredSegments(blocks, options = {}) {
296
- const desired = [];
297
- let runStart = -1;
298
- let runEnd = -1;
299
- let runType;
300
- const closeRun = () => {
301
- if (runStart !== -1 && runType) {
302
- desired.push({ kind: "text-run", startIndex: runStart, endIndex: runEnd, contentType: runType });
303
- runStart = -1;
304
- runEnd = -1;
305
- runType = undefined;
306
- }
307
- };
308
- for (let i = 0; i < blocks.length; i++) {
309
- const block = blocks[i];
310
- const blockType = getVisibleTextLikeBlockType(block, options.hideThinkingBlock);
311
- const isInvisibleTextLike = blockType === undefined && (block?.type === "text" || block?.type === "thinking");
312
- const isTool = block?.type === "toolCall" || block?.type === "serverToolUse";
313
- if (blockType) {
314
- if (options.shouldSkipTextBlock?.(block, i)) {
315
- closeRun();
316
- continue;
317
- }
318
- if (runStart === -1) {
319
- runStart = i;
320
- runEnd = i;
321
- runType = blockType;
322
- }
323
- else if (runType !== blockType) {
324
- closeRun();
325
- runStart = i;
326
- runEnd = i;
327
- runType = blockType;
328
- }
329
- else {
330
- runEnd = i;
331
- }
332
- }
333
- else {
334
- if (isInvisibleTextLike)
335
- continue;
336
- closeRun();
337
- if (isTool) {
338
- desired.push({ kind: "tool", contentIndex: i, toolId: block.id });
339
- }
340
- }
341
- }
342
- closeRun();
343
- return desired;
344
- }
345
- function isToolUseBlock(block) {
346
- return block?.type === "toolCall" || block?.type === "serverToolUse";
347
- }
348
- function isMcpToolBlock(block) {
349
- if (!isToolUseBlock(block))
350
- return false;
351
- const toolName = typeof block?.name === "string" ? block.name : "";
352
- return typeof block?.mcpServer === "string" || toolName.startsWith("mcp__");
353
- }
354
- function hasPostToolText(blocks, firstToolIdx) {
355
- if (firstToolIdx < 0)
356
- return false;
357
- return blocks.some((b, idx) => (idx > firstToolIdx
358
- && b?.type === "text"
359
- && typeof b?.text === "string"
360
- && b.text.trim().length > 0));
361
- }
362
- function normalizeProvisionalText(text) {
363
- return text
364
- .trim()
365
- .replace(/[’`]/g, "'")
366
- .replace(/\s+/g, " ");
367
- }
368
- export function isProvisionalPreToolProse(text) {
369
- const normalized = normalizeProvisionalText(text);
370
- if (!normalized)
371
- return false;
372
- if (textInvitesUserReply(normalized))
373
- return false;
374
- if (/\?\s*$/.test(normalized))
375
- return false;
376
- return FIRST_PERSON_PROVISIONAL_PRE_TOOL_RE.test(normalized)
377
- || GERUND_PROVISIONAL_PRE_TOOL_RE.test(normalized);
378
- }
379
- function getProvisionalPreToolPrunePlan(message) {
380
- const blocks = message.content;
381
- const firstToolIdx = blocks.findIndex(isToolUseBlock);
382
- return {
383
- firstToolIdx,
384
- shouldPrune: message.provider === "claude-code"
385
- && firstToolIdx >= 0
386
- && blocks.some(isMcpToolBlock)
387
- && hasPostToolText(blocks, firstToolIdx),
388
- };
389
- }
390
- function buildDesiredSegmentsForMessage(message, options = {}) {
391
- const { shouldPrune, firstToolIdx } = getProvisionalPreToolPrunePlan(message);
392
- return buildDesiredSegments(message.content, {
393
- hideThinkingBlock: options.hideThinkingBlock,
394
- shouldSkipTextBlock: (block, index) => {
395
- if (!shouldPrune || firstToolIdx < 0 || index >= firstToolIdx)
396
- return false;
397
- if (getVisibleTextLikeBlockType(block, options.hideThinkingBlock) !== "text")
398
- return false;
399
- const textValue = typeof block?.text === "string" ? block.text : "";
400
- return isProvisionalPreToolProse(textValue);
401
- },
402
- });
403
- }
404
- function hasVisibleAssistantContent(message, hideThinkingBlock = false) {
405
- return message.content.some((c) => getVisibleTextLikeBlockType(c, hideThinkingBlock) !== undefined);
406
- }
407
- function hasAssistantToolBlocks(message) {
408
- return message.content.some((c) => c.type === "toolCall" || c.type === "serverToolUse");
409
- }
410
- // Pinnable text candidates: non-empty text blocks that appear strictly before
411
- // the most recent tool call, returned newest-first. Text blocks after the last
412
- // tool call are still streaming live into the chat container.
413
- export function findLatestPinnableCandidates(contentBlocks) {
414
- let lastToolIdx = -1;
415
- for (let i = contentBlocks.length - 1; i >= 0; i--) {
416
- const c = contentBlocks[i];
417
- if (c?.type === "toolCall" || c?.type === "serverToolUse") {
418
- lastToolIdx = i;
419
- break;
420
- }
421
- }
422
- const out = [];
423
- for (let i = lastToolIdx - 1; i >= 0; i--) {
424
- const c = contentBlocks[i];
425
- if (c?.type === "text" && typeof c.text === "string" && c.text.trim()) {
426
- out.push({ text: c.text.trim(), contentIndex: i });
427
- }
428
- }
429
- return out;
430
- }
431
- export function findLatestPinnableText(contentBlocks) {
432
- return findLatestPinnableCandidates(contentBlocks)[0]?.text ?? "";
433
- }
434
- // Sum rendered line counts of segments that appear strictly after the given
435
- // content-block index. Used to decide whether a pinnable text block has
436
- // scrolled out of the viewport and therefore warrants mirroring.
437
- function rowsRenderedAfterContentIndex(contentIndex, width) {
438
- let rows = 0;
439
- for (const seg of renderedSegments) {
440
- try {
441
- if (seg.kind === "text-run" && seg.startIndex > contentIndex) {
442
- rows += seg.component.render(width).length;
443
- }
444
- else if (seg.kind === "tool" && seg.contentIndex > contentIndex) {
445
- rows += seg.component.render(width).length;
446
- }
447
- }
448
- catch {
449
- // Defensive: a component that throws during measurement shouldn't
450
- // destabilize pinned-zone logic. Skip it.
451
- }
452
- }
453
- return rows;
454
- }
455
- // Tracks the latest assistant text for the pinned message zone
456
- let lastPinnedText = "";
457
- // Whether any tool execution has been added in this assistant turn (triggers pinned display)
458
- let hasToolsInTurn = false;
459
- // Reference to the pinned border so we can toggle its label between working/idle
460
- let pinnedBorder;
461
- // Reference to the pinned markdown component below the border
462
- let pinnedTextComponent;
463
- // Set when the pinned zone was shown this turn; used to realign viewport after teardown.
464
- let pinnedZoneNeedsViewportRealign = false;
465
- function tearDownPinnedZone(host, options) {
466
- const needsRealign = pinnedZoneNeedsViewportRealign;
467
- if (pinnedBorder)
468
- pinnedBorder.stopSpinner();
469
- pinnedBorder = undefined;
470
- pinnedTextComponent = undefined;
471
- host.pinnedMessageContainer.clear();
472
- lastPinnedText = "";
473
- hasToolsInTurn = false;
474
- pinnedZoneNeedsViewportRealign = false;
475
- if (options?.realignViewport && needsRealign) {
476
- host.ui.requestRender(true);
477
- }
478
- }
479
- function mergeToolPhases(phases) {
480
- const merged = [];
481
- for (const phase of phases) {
482
- const previous = merged[merged.length - 1];
483
- if (previous?.label === phase.label) {
484
- previous.count += phase.count;
485
- previous.durationMs += phase.durationMs;
486
- previous.targets = mergeTargets(previous.targets, phase.targets);
487
- if (previous.actionLabel !== phase.actionLabel) {
488
- previous.actionLabel = undefined;
489
- }
490
- }
491
- else {
492
- merged.push({ ...phase, targets: phase.targets ? [...phase.targets] : undefined });
493
- }
494
- }
495
- return merged;
496
- }
497
- function mergeTargets(existing, incoming) {
498
- if (!existing && !incoming)
499
- return undefined;
500
- const seen = new Set();
501
- const merged = [];
502
- for (const target of [...(existing ?? []), ...(incoming ?? [])]) {
503
- if (!target || seen.has(target))
504
- continue;
505
- seen.add(target);
506
- merged.push(target);
507
- }
508
- return merged;
509
- }
510
- function replaceCompactToolRowsWithPhaseSummary(host) {
511
- let changed = false;
512
- const nextRenderedSegments = [];
513
- let rollupRun = [];
514
- const flushRollupRun = () => {
515
- const actionCount = rollupRun.reduce((total, item) => total + item.phases.reduce((sum, phase) => sum + phase.count, 0), 0);
516
- if (actionCount < 2) {
517
- nextRenderedSegments.push(...rollupRun.map((item) => item.seg));
518
- rollupRun = [];
519
- return;
520
- }
521
- const firstIndex = Math.max(0, host.chatContainer.children.indexOf(rollupRun[0].seg.component));
522
- const phases = mergeToolPhases(rollupRun.flatMap((item) => item.phases));
523
- const summary = new ToolPhaseSummaryComponent(phases);
524
- for (const { seg } of rollupRun) {
525
- host.chatContainer.removeChild(seg.component);
526
- }
527
- host.chatContainer.addChild(summary);
528
- const summaryIndex = host.chatContainer.children.indexOf(summary);
529
- if (summaryIndex !== -1 && summaryIndex !== firstIndex) {
530
- host.chatContainer.children.splice(summaryIndex, 1);
531
- host.chatContainer.children.splice(firstIndex, 0, summary);
532
- host.chatContainer._prevRender = null;
533
- }
534
- changed = true;
535
- nextRenderedSegments.push({ kind: "tool-summary", component: summary, phases });
536
- rollupRun = [];
537
- };
538
- for (const seg of renderedSegments) {
539
- const phase = seg.kind === "tool" ? seg.component.getRollupPhase() : null;
540
- if (seg.kind === "tool" && phase) {
541
- rollupRun.push({ seg, phases: [phase] });
542
- continue;
543
- }
544
- if (seg.kind === "tool-summary") {
545
- rollupRun.push({ seg, phases: seg.component.getPhases() });
546
- continue;
547
- }
548
- flushRollupRun();
549
- nextRenderedSegments.push(seg);
550
- }
551
- flushRollupRun();
552
- if (changed) {
553
- renderedSegments = nextRenderedSegments;
554
- host.ui.requestRender();
555
- }
556
- }
557
50
  export async function handleAgentEvent(host, event) {
558
51
  if (!host.isInitialized) {
559
52
  await host.init();
560
53
  }
561
54
  host.footer.invalidate();
562
55
  const timestampFormat = host.settingsManager.getTimestampFormat();
56
+ const rs = host.streamingRenderState;
563
57
  // Reset content index tracker and pinned state when a new assistant message starts
564
58
  if (event.type === "message_start" && event.message.role === "assistant") {
565
- lastProcessedContentIndex = 0;
566
- lastContentLength = 0;
567
- renderedSegments = [];
568
- orphanedSegments = [];
59
+ rs.resetForNewAssistantMessage();
569
60
  tearDownPinnedZone(host);
570
61
  }
571
62
  switch (event.type) {
@@ -579,9 +70,7 @@ export async function handleAgentEvent(host, event) {
579
70
  host.pendingTools.clear();
580
71
  host.pendingMessagesContainer.clear();
581
72
  tearDownPinnedZone(host);
582
- renderedSegments = [];
583
- orphanedSegments = [];
584
- lastContentLength = 0;
73
+ rs.resetForSessionChange();
585
74
  host.compactionQueuedMessages = [];
586
75
  host.rebuildChatFromMessages();
587
76
  host.updatePendingMessagesDisplay();
@@ -681,68 +170,12 @@ export async function handleAgentEvent(host, event) {
681
170
  // lifecycle while internally spanning multiple provider sub-turns.
682
171
  // When a new sub-turn starts, content[] length shrinks back to 0/1.
683
172
  // The scan loop needs its index reset, AND the segment walker's
684
- // renderedSegments map must be cleared so existing text-run
173
+ // rs.renderedSegments map must be cleared so existing text-run
685
174
  // components don't get overwritten in place with new sub-turn
686
175
  // content (#4144 regression). Prior sub-turn children stay in
687
176
  // chatContainer as frozen history; new segments append after them.
688
- const replacedAt = contentBlocks.length <= lastContentLength
689
- ? isSubTurnTextReplacement(contentBlocks, renderedSegments)
690
- : null;
691
- if (contentBlocks.length < lastContentLength) {
692
- // Accumulate across successive shrinks — overwriting would drop
693
- // segments displaced by an earlier shrink, leaving them stranded
694
- // in chatContainer once the prune pass finally runs.
695
- orphanedSegments = [...orphanedSegments, ...renderedSegments];
696
- renderedSegments = [];
697
- lastPinnedText = "";
698
- lastProcessedContentIndex = 0;
699
- }
700
- else if (replacedAt !== null) {
701
- // Same-index wholesale replacement: orphan only the replaced
702
- // text-run and any text-runs after it. Earlier unchanged text
703
- // and tool segments stay in renderedSegments so they are not
704
- // re-rendered and duplicated in chatContainer.
705
- orphanedSegments = [
706
- ...orphanedSegments,
707
- ...renderedSegments.filter((seg) => seg.kind === "text-run" && seg.startIndex >= replacedAt),
708
- ];
709
- renderedSegments = renderedSegments.filter((seg) => !(seg.kind === "text-run" && seg.startIndex >= replacedAt));
710
- lastPinnedText = "";
711
- lastProcessedContentIndex = replacedAt;
712
- }
713
- else if (lastProcessedContentIndex >= contentBlocks.length) {
714
- lastProcessedContentIndex = 0;
715
- }
716
- lastContentLength = contentBlocks.length;
717
- for (let i = lastProcessedContentIndex; i < contentBlocks.length; i++) {
718
- const content = contentBlocks[i];
719
- if (content.type === "toolCall") {
720
- 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));
721
- component.updateArgs(content.arguments);
722
- }
723
- else if (content.type === "serverToolUse") {
724
- registerPendingToolComponent(host, content.id, content.name, content.input ?? {}, "content", () => new ToolExecutionComponent(content.name, content.input ?? {}, { showImages: host.settingsManager.getShowImages() }, undefined, host.ui));
725
- }
726
- else if (content.type === "webSearchResult") {
727
- const component = host.pendingTools.get(content.toolUseId);
728
- if (component) {
729
- if (process.env.PI_OFFLINE === "1") {
730
- component.updateResult({
731
- content: [{ type: "text", text: "Web search disabled (offline mode)" }],
732
- isError: false,
733
- });
734
- }
735
- else {
736
- const searchContent = content.content;
737
- const isError = searchContent && typeof searchContent === "object" && "type" in searchContent && searchContent.type === "web_search_tool_result_error";
738
- component.updateResult({
739
- content: [{ type: "text", text: host.formatWebSearchResult(searchContent) }],
740
- isError: !!isError,
741
- });
742
- }
743
- }
744
- }
745
- }
177
+ applySubTurnContentShrink(rs, contentBlocks);
178
+ scanNewContentBlocks(host, rs, contentBlocks);
746
179
  // When the stream adapter signals a completed tool call with an
747
180
  // external result (from Claude Code SDK), update the pending
748
181
  // ToolExecutionComponent immediately so output is visible in
@@ -758,194 +191,19 @@ export async function handleAgentEvent(host, event) {
758
191
  replaceCompactToolRowsWithPhaseSummary(host);
759
192
  }
760
193
  }
761
- // Segment walker: render content blocks in stream order, append-only.
762
- // Build desired segment plan from content[].
763
- {
764
- const blocks = host.streamingMessage.content;
765
- // Only prune provisional pre-tool prose after post-tool prose exists,
766
- // so MCP tool-only windows do not blank the assistant content.
767
- const { shouldPrune: shouldPruneProvisionalPreToolProse } = getProvisionalPreToolPrunePlan(host.streamingMessage);
768
- let desired = buildDesiredSegmentsForMessage(host.streamingMessage, {
769
- hideThinkingBlock: host.hideThinkingBlock,
770
- });
771
- desired = filterRedundantDiscussTextRuns(desired, blocks);
772
- // Claude Code MCP can emit provisional pre-tool prose that gets
773
- // superseded by post-tool output. Prune stale text-run segments so
774
- // the final assistant output remains below tool output.
775
- if (shouldPruneProvisionalPreToolProse) {
776
- if (orphanedSegments.length > 0) {
777
- const remainingOrphans = [];
778
- for (const orphan of orphanedSegments) {
779
- if (orphan.kind === "text-run"
780
- && orphan.contentType === "text"
781
- && isProvisionalPreToolProse(orphan.cachedText ?? "")) {
782
- host.chatContainer.removeChild(orphan.component);
783
- if (host.streamingComponent === orphan.component) {
784
- host.streamingComponent = undefined;
785
- }
786
- continue;
787
- }
788
- remainingOrphans.push(orphan);
789
- }
790
- orphanedSegments = remainingOrphans;
791
- }
792
- const desiredTextKeys = new Set(desired
793
- .filter((seg) => seg.kind === "text-run")
794
- .map((seg) => `${seg.contentType}:${seg.startIndex}`));
795
- const desiredToolIndices = new Set(desired
796
- .filter((seg) => seg.kind === "tool")
797
- .map((seg) => seg.contentIndex));
798
- const nextRendered = [];
799
- for (const seg of renderedSegments) {
800
- if (seg.kind === "text-run"
801
- && seg.contentType === "text"
802
- && !desiredTextKeys.has(`${seg.contentType}:${seg.startIndex}`)) {
803
- host.chatContainer.removeChild(seg.component);
804
- if (host.streamingComponent === seg.component) {
805
- host.streamingComponent = undefined;
806
- }
807
- continue;
808
- }
809
- if (seg.kind === "tool" && !desiredToolIndices.has(seg.contentIndex)) {
810
- continue;
811
- }
812
- nextRendered.push(seg);
813
- }
814
- renderedSegments = nextRendered;
815
- }
816
- // Append any newly needed segments (never reorder existing ones).
817
- for (const seg of desired) {
818
- if (seg.kind === "tool") {
819
- // Tool segments are already handled above via pendingTools; just
820
- // register them in renderedSegments if not yet tracked.
821
- const existing = renderedSegments.find((s) => s.kind === "tool" && s.contentIndex === seg.contentIndex);
822
- if (!existing) {
823
- const comp = host.pendingTools.get(seg.toolId);
824
- if (comp) {
825
- renderedSegments.push({ kind: "tool", contentIndex: seg.contentIndex, component: comp });
826
- }
827
- }
828
- }
829
- else {
830
- // text-run segment
831
- const existing = renderedSegments.find((s) => s.kind === "text-run" && s.startIndex === seg.startIndex && s.contentType === seg.contentType);
832
- if (!existing) {
833
- const segmentText = getTextFromContentBlocks(blocks, seg.startIndex, seg.endIndex, seg.contentType);
834
- if (seg.contentType === "text" &&
835
- shouldSuppressRedundantHandoffText(host.session.messages, segmentText, orphanedSegments, renderedSegments)) {
836
- continue;
837
- }
838
- const comp = new AssistantMessageComponent(undefined, host.hideThinkingBlock, host.getMarkdownThemeWithSettings(), timestampFormat, { startIndex: seg.startIndex, endIndex: seg.endIndex });
839
- host.chatContainer.addChild(comp);
840
- comp.updateContent(host.streamingMessage);
841
- markFirstVisibleAssistantOutput(host, seg.contentType, {
842
- contentIndex: seg.startIndex,
843
- });
844
- renderedSegments.push({
845
- kind: "text-run",
846
- startIndex: seg.startIndex,
847
- endIndex: seg.endIndex,
848
- contentType: seg.contentType,
849
- component: comp,
850
- cachedText: segmentText,
851
- });
852
- host.streamingComponent = comp;
853
- reconcileChatTurnConnections(host.chatContainer.children);
854
- }
855
- }
856
- }
857
- // Update all trailing text-run segments with the latest message so
858
- // streaming text grows in place.
859
- for (const seg of renderedSegments) {
860
- if (seg.kind === "text-run") {
861
- // Find corresponding desired segment to get current endIndex
862
- const d = desired.find((ds) => ds.kind === "text-run" && ds.startIndex === seg.startIndex && ds.contentType === seg.contentType);
863
- if (d && d.kind === "text-run" && d.endIndex !== seg.endIndex) {
864
- seg.endIndex = d.endIndex;
865
- seg.component.setRange({ startIndex: seg.startIndex, endIndex: seg.endIndex });
866
- }
867
- const newText = getTextFromContentBlocks(blocks, seg.startIndex, seg.endIndex, seg.contentType);
868
- if (newText !== seg.cachedText) {
869
- seg.cachedText = newText;
870
- seg.component.updateContent(host.streamingMessage);
871
- }
872
- }
873
- }
874
- // Keep streamingComponent pointing at the last text-run for message_end compatibility.
875
- const lastTextSeg = [...renderedSegments].reverse().find((s) => s.kind === "text-run");
876
- if (lastTextSeg && lastTextSeg.kind === "text-run") {
877
- host.streamingComponent = lastTextSeg.component;
878
- }
879
- }
194
+ runSegmentWalker(host, rs, timestampFormat);
880
195
  // Update index: fully processed blocks won't need re-scanning.
881
196
  // Keep the last block's index (it may still be accumulating data),
882
197
  // so we re-check it next time but skip all earlier ones.
883
198
  if (contentBlocks.length > 0) {
884
- lastProcessedContentIndex = Math.max(0, contentBlocks.length - 1);
199
+ rs.lastProcessedContentIndex = Math.max(0, contentBlocks.length - 1);
885
200
  }
886
201
  // Pinned message: mirror the latest assistant text above the editor
887
202
  // when tool executions push it out of the viewport.
888
- const hasTools = contentBlocks.some((c) => c.type === "toolCall" || c.type === "serverToolUse");
889
- if (hasTools)
890
- hasToolsInTurn = true;
891
- if (hasToolsInTurn) {
892
- const candidates = findLatestPinnableCandidates(contentBlocks);
893
- const termRows = host.ui.terminal.rows;
894
- const termCols = host.ui.terminal.columns;
895
- const pinnedMax = Math.max(3, Math.floor(termRows * 0.4));
896
- // Reserve rows for pinned zone + its border + editor + footer chrome.
897
- // Anything below this row budget is still in the viewport.
898
- const offscreenThreshold = Math.max(1, termRows - pinnedMax - 8);
899
- // Walk candidates newest→oldest; pick the first whose following
900
- // segments have pushed enough rows to scroll it off-screen.
901
- let picked;
902
- for (const c of candidates) {
903
- if (rowsRenderedAfterContentIndex(c.contentIndex, termCols) >= offscreenThreshold) {
904
- picked = c;
905
- break;
906
- }
907
- }
908
- if (picked) {
909
- if (picked.text !== lastPinnedText) {
910
- lastPinnedText = picked.text;
911
- if (!pinnedBorder) {
912
- // First time: create border + text component
913
- host.pinnedMessageContainer.clear();
914
- pinnedBorder = new DynamicBorder((str) => theme.fg("dim", str), "Working · Latest Output");
915
- pinnedBorder.startSpinner(host.ui, (str) => theme.fg("accent", str));
916
- host.pinnedMessageContainer.addChild(pinnedBorder);
917
- pinnedTextComponent = new Markdown(picked.text, 1, 0, host.getMarkdownThemeWithSettings());
918
- // Cap pinned content to ~40% of terminal height so tall output
919
- // doesn't exceed the viewport and cause render flashing.
920
- pinnedTextComponent.maxLines = pinnedMax;
921
- host.pinnedMessageContainer.addChild(pinnedTextComponent);
922
- pinnedZoneNeedsViewportRealign = true;
923
- // Hide the separate status loader — the pinned zone replaces it
924
- if (host.loadingAnimation) {
925
- host.loadingAnimation.stop();
926
- host.loadingAnimation = undefined;
927
- }
928
- host.statusContainer.clear();
929
- }
930
- else {
931
- // Update existing markdown component in-place
932
- pinnedTextComponent?.setText(picked.text);
933
- // Refresh maxLines in case terminal was resized
934
- if (pinnedTextComponent) {
935
- pinnedTextComponent.maxLines = pinnedMax;
936
- }
937
- }
938
- }
939
- }
940
- else if (pinnedBorder) {
941
- // Every candidate is still visible in the chat scrollback —
942
- // tear down the pinned zone so we don't duplicate on-screen text.
943
- tearDownPinnedZone(host);
944
- if (!host.loadingAnimation) {
945
- host.statusContainer.clear();
946
- startLoadingAnimation(host);
947
- }
948
- }
203
+ const { toreDownPinnedZone } = updatePinnedMessageZone(host, rs, contentBlocks);
204
+ if (toreDownPinnedZone && !host.loadingAnimation) {
205
+ host.statusContainer.clear();
206
+ startLoadingAnimation(host);
949
207
  }
950
208
  host.ui.requestRender();
951
209
  }
@@ -966,81 +224,12 @@ export async function handleAgentEvent(host, event) {
966
224
  const shouldRenderAssistant = hasVisibleAssistantContent(host.streamingMessage, host.hideThinkingBlock) ||
967
225
  ((host.streamingMessage.stopReason === "aborted" || host.streamingMessage.stopReason === "error") &&
968
226
  !hasAssistantToolBlocks(host.streamingMessage));
969
- const suppressRedundantHandoff = shouldSuppressEntireAssistantMessage(host.streamingMessage, host.session.messages, orphanedSegments);
227
+ const suppressRedundantHandoff = shouldSuppressEntireAssistantMessage(host.streamingMessage, host.session.messages, rs.orphanedSegments);
970
228
  // The final message_end payload can contain additional text/thinking
971
229
  // blocks that never arrived via message_update (e.g. SDK result
972
230
  // aggregation). Rebuild this in-flight turn from final content so
973
231
  // ranges/components don't keep stale partial indices.
974
- if (renderedSegments.length > 0) {
975
- const finalBlocks = host.streamingMessage.content;
976
- const desired = filterRedundantDiscussTextRuns(buildDesiredSegmentsForMessage(host.streamingMessage, {
977
- hideThinkingBlock: host.hideThinkingBlock,
978
- }), finalBlocks);
979
- const toolComponentsById = new Map();
980
- for (const [toolId, component] of host.pendingTools.entries()) {
981
- toolComponentsById.set(toolId, component);
982
- }
983
- for (const seg of renderedSegments) {
984
- host.chatContainer.removeChild(seg.component);
985
- if (seg.kind === "tool") {
986
- const priorBlocks = host.streamingMessage.content;
987
- const priorBlock = priorBlocks[seg.contentIndex];
988
- if (priorBlock?.id && !toolComponentsById.has(priorBlock.id)) {
989
- toolComponentsById.set(priorBlock.id, seg.component);
990
- }
991
- }
992
- }
993
- renderedSegments = [];
994
- host.streamingComponent = undefined;
995
- for (const seg of desired) {
996
- if (seg.kind === "tool") {
997
- const finalBlock = finalBlocks[seg.contentIndex];
998
- let component = toolComponentsById.get(seg.toolId);
999
- if (!component && finalBlock?.id) {
1000
- component = host.pendingTools.get(finalBlock.id);
1001
- }
1002
- if (!component && finalBlock?.type === "toolCall") {
1003
- component = new ToolExecutionComponent(finalBlock.name, finalBlock.arguments, { showImages: host.settingsManager.getShowImages() }, host.getRegisteredToolDefinition(finalBlock.name), host.ui);
1004
- component.setExpanded(host.toolOutputExpanded);
1005
- host.pendingTools.set(finalBlock.id, component);
1006
- toolComponentsById.set(finalBlock.id, component);
1007
- }
1008
- else if (!component && finalBlock?.type === "serverToolUse") {
1009
- component = new ToolExecutionComponent(finalBlock.name, finalBlock.input ?? {}, { showImages: host.settingsManager.getShowImages() }, undefined, host.ui);
1010
- component.setExpanded(host.toolOutputExpanded);
1011
- host.pendingTools.set(finalBlock.id, component);
1012
- toolComponentsById.set(finalBlock.id, component);
1013
- }
1014
- if (component) {
1015
- host.chatContainer.addChild(component);
1016
- renderedSegments.push({ kind: "tool", contentIndex: seg.contentIndex, component });
1017
- }
1018
- continue;
1019
- }
1020
- const comp = new AssistantMessageComponent(undefined, host.hideThinkingBlock, host.getMarkdownThemeWithSettings(), timestampFormat, { startIndex: seg.startIndex, endIndex: seg.endIndex });
1021
- comp.updateContent(host.streamingMessage);
1022
- const segmentText = getTextFromContentBlocks(finalBlocks, seg.startIndex, seg.endIndex, seg.contentType);
1023
- if (seg.contentType === "text" &&
1024
- shouldSuppressRedundantHandoffText(host.session.messages, segmentText, orphanedSegments, renderedSegments)) {
1025
- continue;
1026
- }
1027
- host.chatContainer.addChild(comp);
1028
- markFirstVisibleAssistantOutput(host, seg.contentType, {
1029
- contentIndex: seg.startIndex,
1030
- source: "message_end_rebuild",
1031
- });
1032
- renderedSegments.push({
1033
- kind: "text-run",
1034
- startIndex: seg.startIndex,
1035
- endIndex: seg.endIndex,
1036
- contentType: seg.contentType,
1037
- component: comp,
1038
- cachedText: segmentText,
1039
- });
1040
- host.streamingComponent = comp;
1041
- }
1042
- reconcileChatTurnConnections(host.chatContainer.children);
1043
- }
232
+ rebuildSegmentsOnMessageEnd(host, rs, timestampFormat);
1044
233
  if (!host.streamingComponent && shouldRenderAssistant && !suppressRedundantHandoff) {
1045
234
  host.streamingComponent = new AssistantMessageComponent(undefined, host.hideThinkingBlock, host.getMarkdownThemeWithSettings(), timestampFormat, undefined);
1046
235
  host.chatContainer.addChild(host.streamingComponent);
@@ -1073,9 +262,7 @@ export async function handleAgentEvent(host, event) {
1073
262
  }
1074
263
  host.streamingComponent = undefined;
1075
264
  host.streamingMessage = undefined;
1076
- renderedSegments = [];
1077
- orphanedSegments = [];
1078
- lastContentLength = 0;
265
+ rs.resetStreamingSegments();
1079
266
  // Clear pinned output once the message is finalized in the chat
1080
267
  // container — prevents duplicate display when the agent continues
1081
268
  // (e.g. form elicitation) after the assistant message ends.
@@ -1087,7 +274,7 @@ export async function handleAgentEvent(host, event) {
1087
274
  case "tool_execution_start": {
1088
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));
1089
276
  if (created) {
1090
- renderedSegments.push({ kind: "tool", contentIndex: Number.MAX_SAFE_INTEGER, component });
277
+ rs.renderedSegments.push({ kind: "tool", contentIndex: Number.MAX_SAFE_INTEGER, component });
1091
278
  }
1092
279
  host.ui.requestRender();
1093
280
  break;
@@ -1133,9 +320,7 @@ export async function handleAgentEvent(host, event) {
1133
320
  replaceCompactToolRowsWithPhaseSummary(host);
1134
321
  host.streamingComponent = undefined;
1135
322
  host.streamingMessage = undefined;
1136
- renderedSegments = [];
1137
- orphanedSegments = [];
1138
- lastContentLength = 0;
323
+ rs.resetForSessionChange();
1139
324
  host.pendingTools.clear();
1140
325
  // Pinned output is only useful while work is actively streaming.
1141
326
  // Keep chat history as the single source after completion.