gsd-pi 2.73.1 → 2.74.0-dev.16f2f3b

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 (480) hide show
  1. package/dist/cli-web-branch.d.ts +4 -3
  2. package/dist/cli-web-branch.js +10 -7
  3. package/dist/cli.js +184 -206
  4. package/dist/headless-query.js +4 -1
  5. package/dist/help-text.js +23 -0
  6. package/dist/logo.d.ts +1 -1
  7. package/dist/logo.js +1 -1
  8. package/dist/onboarding.js +59 -53
  9. package/dist/resource-loader.js +2 -2
  10. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +68 -4
  11. package/dist/resources/extensions/gsd/activity-log.js +16 -0
  12. package/dist/resources/extensions/gsd/auto/detect-stuck.js +11 -4
  13. package/dist/resources/extensions/gsd/auto/loop.js +147 -10
  14. package/dist/resources/extensions/gsd/auto/phases.js +173 -13
  15. package/dist/resources/extensions/gsd/auto/session.js +10 -0
  16. package/dist/resources/extensions/gsd/auto-dispatch.js +22 -4
  17. package/dist/resources/extensions/gsd/auto-model-selection.js +105 -16
  18. package/dist/resources/extensions/gsd/auto-post-unit.js +254 -15
  19. package/dist/resources/extensions/gsd/auto-prompts.js +12 -0
  20. package/dist/resources/extensions/gsd/auto-start.js +23 -6
  21. package/dist/resources/extensions/gsd/auto-timeout-recovery.js +13 -0
  22. package/dist/resources/extensions/gsd/auto-unit-closeout.js +18 -0
  23. package/dist/resources/extensions/gsd/auto-verification.js +186 -3
  24. package/dist/resources/extensions/gsd/auto.js +65 -12
  25. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +21 -8
  26. package/dist/resources/extensions/gsd/commands/catalog.js +26 -1
  27. package/dist/resources/extensions/gsd/commands/handlers/ops.js +25 -0
  28. package/dist/resources/extensions/gsd/commands/handlers/workflow.js +68 -9
  29. package/dist/resources/extensions/gsd/commands-add-tests.js +111 -0
  30. package/dist/resources/extensions/gsd/commands-backlog.js +140 -0
  31. package/dist/resources/extensions/gsd/commands-do.js +79 -0
  32. package/dist/resources/extensions/gsd/commands-extract-learnings.js +225 -0
  33. package/dist/resources/extensions/gsd/commands-handlers.js +8 -2
  34. package/dist/resources/extensions/gsd/commands-maintenance.js +6 -6
  35. package/dist/resources/extensions/gsd/commands-pr-branch.js +180 -0
  36. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
  37. package/dist/resources/extensions/gsd/commands-session-report.js +82 -0
  38. package/dist/resources/extensions/gsd/commands-ship.js +187 -0
  39. package/dist/resources/extensions/gsd/db-writer.js +3 -5
  40. package/dist/resources/extensions/gsd/docs/preferences-reference.md +15 -2
  41. package/dist/resources/extensions/gsd/git-service.js +49 -1
  42. package/dist/resources/extensions/gsd/graph-context.js +157 -0
  43. package/dist/resources/extensions/gsd/gsd-db.js +581 -2
  44. package/dist/resources/extensions/gsd/guided-flow.js +23 -0
  45. package/dist/resources/extensions/gsd/index.js +15 -2
  46. package/dist/resources/extensions/gsd/init-wizard.js +1 -0
  47. package/dist/resources/extensions/gsd/journal.js +27 -0
  48. package/dist/resources/extensions/gsd/md-importer.js +3 -4
  49. package/dist/resources/extensions/gsd/memory-store.js +19 -51
  50. package/dist/resources/extensions/gsd/metrics.js +19 -0
  51. package/dist/resources/extensions/gsd/milestone-validation-gates.js +13 -12
  52. package/dist/resources/extensions/gsd/native-git-bridge.js +7 -4
  53. package/dist/resources/extensions/gsd/notification-widget.js +2 -2
  54. package/dist/resources/extensions/gsd/parallel-orchestrator.js +33 -1
  55. package/dist/resources/extensions/gsd/preferences-models.js +63 -3
  56. package/dist/resources/extensions/gsd/preferences-types.js +2 -0
  57. package/dist/resources/extensions/gsd/preferences-validation.js +130 -2
  58. package/dist/resources/extensions/gsd/preferences.js +26 -0
  59. package/dist/resources/extensions/gsd/prompts/add-tests.md +35 -0
  60. package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +12 -2
  61. package/dist/resources/extensions/gsd/state.js +66 -15
  62. package/dist/resources/extensions/gsd/templates/PREFERENCES.md +18 -0
  63. package/dist/resources/extensions/gsd/tools/complete-slice.js +20 -0
  64. package/dist/resources/extensions/gsd/tools/validate-milestone.js +39 -4
  65. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +3 -14
  66. package/dist/resources/extensions/gsd/triage-resolution.js +2 -5
  67. package/dist/resources/extensions/gsd/unit-ownership.js +1 -1
  68. package/dist/resources/extensions/gsd/uok/audit-toggle.js +7 -0
  69. package/dist/resources/extensions/gsd/uok/audit.js +40 -0
  70. package/dist/resources/extensions/gsd/uok/contracts.js +1 -0
  71. package/dist/resources/extensions/gsd/uok/execution-graph.js +179 -0
  72. package/dist/resources/extensions/gsd/uok/flags.js +29 -0
  73. package/dist/resources/extensions/gsd/uok/gate-runner.js +109 -0
  74. package/dist/resources/extensions/gsd/uok/gitops.js +53 -0
  75. package/dist/resources/extensions/gsd/uok/kernel.js +80 -0
  76. package/dist/resources/extensions/gsd/uok/loop-adapter.js +133 -0
  77. package/dist/resources/extensions/gsd/uok/model-policy.js +66 -0
  78. package/dist/resources/extensions/gsd/uok/plan-v2.js +132 -0
  79. package/dist/resources/extensions/gsd/workflow-logger.js +22 -0
  80. package/dist/resources/extensions/gsd/workflow-manifest.js +8 -69
  81. package/dist/resources/extensions/gsd/workflow-migration.js +21 -22
  82. package/dist/resources/extensions/gsd/workflow-projections.js +4 -1
  83. package/dist/resources/extensions/gsd/workflow-reconcile.js +14 -11
  84. package/dist/resources/extensions/ttsr/ttsr-manager.js +3 -1
  85. package/dist/tsconfig.extensions.tsbuildinfo +1 -0
  86. package/dist/update-check.d.ts +1 -0
  87. package/dist/update-check.js +13 -5
  88. package/dist/update-cmd.js +4 -3
  89. package/dist/web/standalone/.next/BUILD_ID +1 -1
  90. package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
  91. package/dist/web/standalone/.next/build-manifest.json +3 -3
  92. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  93. package/dist/web/standalone/.next/required-server-files.json +3 -3
  94. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  95. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  96. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  97. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  98. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  99. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  100. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  101. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  102. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  103. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  104. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  105. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  106. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  107. package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
  108. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  109. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  110. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  111. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  112. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  113. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  114. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  115. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  121. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  122. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  123. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  124. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  125. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  126. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  127. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  128. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  129. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  130. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  131. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  132. package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
  133. package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
  134. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  135. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  136. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  137. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  138. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  139. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  140. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  141. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  142. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  143. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  144. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  145. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  146. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  147. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  148. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  149. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  150. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  151. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  152. package/dist/web/standalone/.next/server/app/api/notifications/route.js +2 -2
  153. package/dist/web/standalone/.next/server/app/api/notifications/route_client-reference-manifest.js +1 -1
  154. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  155. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  156. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  157. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  158. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  159. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  160. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  161. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  162. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
  163. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  164. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  165. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  166. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  167. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  168. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  169. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  170. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  171. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  172. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  173. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  174. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  175. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  176. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  177. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  178. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  179. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  180. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  181. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  182. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
  183. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  184. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  185. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  186. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
  187. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  188. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
  189. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  190. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  191. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  192. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  193. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  194. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  195. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  196. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  197. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  198. package/dist/web/standalone/.next/server/app/index.html +1 -1
  199. package/dist/web/standalone/.next/server/app/index.rsc +4 -4
  200. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  201. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  202. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  203. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  204. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  205. package/dist/web/standalone/.next/server/app/page.js +2 -2
  206. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  207. package/dist/web/standalone/.next/server/app-paths-manifest.json +12 -12
  208. package/dist/web/standalone/.next/server/chunks/63.js +3 -3
  209. package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
  210. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  211. package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
  212. package/dist/web/standalone/.next/server/middleware.js +2 -2
  213. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  214. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  215. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  216. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  217. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  218. package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
  219. package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
  220. package/dist/web/standalone/.next/static/chunks/app/page-f1e30ab6bb269149.js +1 -0
  221. package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
  222. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
  223. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  224. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  225. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  226. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  227. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  228. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  229. package/dist/web/standalone/server.js +1 -1
  230. package/package.json +3 -3
  231. package/packages/daemon/package.json +2 -2
  232. package/packages/mcp-server/dist/index.d.ts +3 -0
  233. package/packages/mcp-server/dist/index.d.ts.map +1 -1
  234. package/packages/mcp-server/dist/index.js +3 -0
  235. package/packages/mcp-server/dist/index.js.map +1 -1
  236. package/packages/mcp-server/dist/readers/graph.d.ts +87 -0
  237. package/packages/mcp-server/dist/readers/graph.d.ts.map +1 -0
  238. package/packages/mcp-server/dist/readers/graph.js +655 -0
  239. package/packages/mcp-server/dist/readers/graph.js.map +1 -0
  240. package/packages/mcp-server/dist/readers/index.d.ts +2 -0
  241. package/packages/mcp-server/dist/readers/index.d.ts.map +1 -1
  242. package/packages/mcp-server/dist/readers/index.js +1 -0
  243. package/packages/mcp-server/dist/readers/index.js.map +1 -1
  244. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  245. package/packages/mcp-server/dist/server.js +65 -0
  246. package/packages/mcp-server/dist/server.js.map +1 -1
  247. package/packages/mcp-server/package.json +2 -2
  248. package/packages/mcp-server/src/index.ts +15 -0
  249. package/packages/mcp-server/src/readers/graph.test.ts +604 -0
  250. package/packages/mcp-server/src/readers/graph.ts +855 -0
  251. package/packages/mcp-server/src/readers/index.ts +12 -0
  252. package/packages/mcp-server/src/server.ts +83 -0
  253. package/packages/mcp-server/tsconfig.json +1 -0
  254. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -0
  255. package/packages/native/package.json +2 -2
  256. package/packages/native/tsconfig.tsbuildinfo +1 -0
  257. package/packages/pi-agent-core/package.json +1 -1
  258. package/packages/pi-agent-core/tsconfig.json +1 -0
  259. package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -0
  260. package/packages/pi-ai/dist/index.d.ts +1 -0
  261. package/packages/pi-ai/dist/index.d.ts.map +1 -1
  262. package/packages/pi-ai/dist/index.js +1 -0
  263. package/packages/pi-ai/dist/index.js.map +1 -1
  264. package/packages/pi-ai/dist/utils/overflow.d.ts.map +1 -1
  265. package/packages/pi-ai/dist/utils/overflow.js +12 -0
  266. package/packages/pi-ai/dist/utils/overflow.js.map +1 -1
  267. package/packages/pi-ai/dist/utils/tests/overflow.test.d.ts +2 -0
  268. package/packages/pi-ai/dist/utils/tests/overflow.test.d.ts.map +1 -0
  269. package/packages/pi-ai/dist/utils/tests/overflow.test.js +50 -0
  270. package/packages/pi-ai/dist/utils/tests/overflow.test.js.map +1 -0
  271. package/packages/pi-ai/package.json +1 -1
  272. package/packages/pi-ai/src/index.ts +4 -0
  273. package/packages/pi-ai/src/utils/overflow.ts +14 -1
  274. package/packages/pi-ai/src/utils/tests/overflow.test.ts +58 -0
  275. package/packages/pi-ai/tsconfig.json +1 -0
  276. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -0
  277. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +571 -8
  278. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
  279. package/packages/pi-coding-agent/dist/core/compaction/utils.js +5 -5
  280. package/packages/pi-coding-agent/dist/core/compaction/utils.js.map +1 -1
  281. package/packages/pi-coding-agent/dist/core/compaction-utils.test.d.ts +2 -0
  282. package/packages/pi-coding-agent/dist/core/compaction-utils.test.d.ts.map +1 -0
  283. package/packages/pi-coding-agent/dist/core/compaction-utils.test.js +45 -0
  284. package/packages/pi-coding-agent/dist/core/compaction-utils.test.js.map +1 -0
  285. package/packages/pi-coding-agent/dist/core/model-registry-env-fallback.test.d.ts +2 -0
  286. package/packages/pi-coding-agent/dist/core/model-registry-env-fallback.test.d.ts.map +1 -0
  287. package/packages/pi-coding-agent/dist/core/model-registry-env-fallback.test.js +52 -0
  288. package/packages/pi-coding-agent/dist/core/model-registry-env-fallback.test.js.map +1 -0
  289. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  290. package/packages/pi-coding-agent/dist/core/model-registry.js +2 -2
  291. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  292. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts +12 -2
  293. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  294. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js +65 -28
  295. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
  296. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.d.ts +2 -1
  297. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -1
  298. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.js +9 -3
  299. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.js.map +1 -1
  300. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.d.ts +2 -0
  301. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.d.ts.map +1 -0
  302. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.js +52 -0
  303. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.js.map +1 -0
  304. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  305. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +201 -20
  306. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  307. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-ordering.test.d.ts +2 -0
  308. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-ordering.test.d.ts.map +1 -0
  309. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-ordering.test.js +38 -0
  310. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-ordering.test.js.map +1 -0
  311. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +13 -0
  312. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  313. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +59 -6
  314. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  315. package/packages/pi-coding-agent/package.json +1 -1
  316. package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +694 -8
  317. package/packages/pi-coding-agent/src/core/compaction/utils.ts +5 -5
  318. package/packages/pi-coding-agent/src/core/compaction-utils.test.ts +50 -0
  319. package/packages/pi-coding-agent/src/core/model-registry-env-fallback.test.ts +59 -0
  320. package/packages/pi-coding-agent/src/core/model-registry.ts +2 -1
  321. package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +78 -32
  322. package/packages/pi-coding-agent/src/modes/interactive/components/dynamic-border.test.ts +73 -0
  323. package/packages/pi-coding-agent/src/modes/interactive/components/dynamic-border.ts +9 -3
  324. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +238 -25
  325. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-ordering.test.ts +44 -0
  326. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +79 -6
  327. package/packages/pi-coding-agent/src/types/ambient-modules.d.ts +69 -0
  328. package/packages/pi-coding-agent/tsconfig.json +3 -2
  329. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -0
  330. package/packages/pi-tui/dist/__tests__/tui.test.js +60 -1
  331. package/packages/pi-tui/dist/__tests__/tui.test.js.map +1 -1
  332. package/packages/pi-tui/dist/tui.d.ts +8 -0
  333. package/packages/pi-tui/dist/tui.d.ts.map +1 -1
  334. package/packages/pi-tui/dist/tui.js +32 -3
  335. package/packages/pi-tui/dist/tui.js.map +1 -1
  336. package/packages/pi-tui/package.json +1 -1
  337. package/packages/pi-tui/src/__tests__/tui.test.ts +76 -1
  338. package/packages/pi-tui/src/tui.ts +31 -3
  339. package/packages/pi-tui/tsconfig.json +1 -0
  340. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -0
  341. package/packages/rpc-client/package.json +1 -1
  342. package/packages/rpc-client/tsconfig.json +1 -0
  343. package/packages/rpc-client/tsconfig.tsbuildinfo +1 -0
  344. package/pkg/package.json +1 -1
  345. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +107 -5
  346. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +111 -2
  347. package/src/resources/extensions/gsd/activity-log.ts +21 -0
  348. package/src/resources/extensions/gsd/auto/detect-stuck.ts +12 -4
  349. package/src/resources/extensions/gsd/auto/loop-deps.ts +10 -0
  350. package/src/resources/extensions/gsd/auto/loop.ts +159 -10
  351. package/src/resources/extensions/gsd/auto/phases.ts +213 -13
  352. package/src/resources/extensions/gsd/auto/session.ts +10 -0
  353. package/src/resources/extensions/gsd/auto-dispatch.ts +26 -10
  354. package/src/resources/extensions/gsd/auto-model-selection.ts +151 -16
  355. package/src/resources/extensions/gsd/auto-post-unit.ts +278 -16
  356. package/src/resources/extensions/gsd/auto-prompts.ts +13 -0
  357. package/src/resources/extensions/gsd/auto-start.ts +30 -6
  358. package/src/resources/extensions/gsd/auto-timeout-recovery.ts +17 -0
  359. package/src/resources/extensions/gsd/auto-unit-closeout.ts +25 -1
  360. package/src/resources/extensions/gsd/auto-verification.ts +225 -3
  361. package/src/resources/extensions/gsd/auto.ts +72 -16
  362. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +24 -8
  363. package/src/resources/extensions/gsd/commands/catalog.ts +26 -1
  364. package/src/resources/extensions/gsd/commands/handlers/ops.ts +25 -0
  365. package/src/resources/extensions/gsd/commands/handlers/workflow.ts +74 -9
  366. package/src/resources/extensions/gsd/commands-add-tests.ts +137 -0
  367. package/src/resources/extensions/gsd/commands-backlog.ts +182 -0
  368. package/src/resources/extensions/gsd/commands-do.ts +109 -0
  369. package/src/resources/extensions/gsd/commands-extract-learnings.ts +304 -0
  370. package/src/resources/extensions/gsd/commands-handlers.ts +8 -2
  371. package/src/resources/extensions/gsd/commands-maintenance.ts +6 -6
  372. package/src/resources/extensions/gsd/commands-pr-branch.ts +234 -0
  373. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
  374. package/src/resources/extensions/gsd/commands-session-report.ts +101 -0
  375. package/src/resources/extensions/gsd/commands-ship.ts +219 -0
  376. package/src/resources/extensions/gsd/db-writer.ts +3 -5
  377. package/src/resources/extensions/gsd/docs/preferences-reference.md +15 -2
  378. package/src/resources/extensions/gsd/git-service.ts +68 -0
  379. package/src/resources/extensions/gsd/graph-context.ts +212 -0
  380. package/src/resources/extensions/gsd/gsd-db.ts +788 -3
  381. package/src/resources/extensions/gsd/guided-flow.ts +32 -0
  382. package/src/resources/extensions/gsd/index.ts +18 -2
  383. package/src/resources/extensions/gsd/init-wizard.ts +3 -2
  384. package/src/resources/extensions/gsd/journal.ts +30 -0
  385. package/src/resources/extensions/gsd/md-importer.ts +3 -5
  386. package/src/resources/extensions/gsd/memory-store.ts +31 -62
  387. package/src/resources/extensions/gsd/metrics.ts +26 -0
  388. package/src/resources/extensions/gsd/milestone-validation-gates.ts +13 -14
  389. package/src/resources/extensions/gsd/native-git-bridge.ts +11 -12
  390. package/src/resources/extensions/gsd/notification-widget.ts +2 -2
  391. package/src/resources/extensions/gsd/parallel-orchestrator.ts +40 -1
  392. package/src/resources/extensions/gsd/preferences-models.ts +61 -3
  393. package/src/resources/extensions/gsd/preferences-types.ts +44 -0
  394. package/src/resources/extensions/gsd/preferences-validation.ts +130 -2
  395. package/src/resources/extensions/gsd/preferences.ts +28 -0
  396. package/src/resources/extensions/gsd/prompts/add-tests.md +35 -0
  397. package/src/resources/extensions/gsd/session-lock.ts +14 -2
  398. package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +20 -1
  399. package/src/resources/extensions/gsd/state.ts +80 -17
  400. package/src/resources/extensions/gsd/templates/PREFERENCES.md +18 -0
  401. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +9 -5
  402. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +20 -0
  403. package/src/resources/extensions/gsd/tests/auto-post-unit-step-message.test.ts +53 -0
  404. package/src/resources/extensions/gsd/tests/auto-project-root-env.test.ts +7 -3
  405. package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +51 -2
  406. package/src/resources/extensions/gsd/tests/cold-resume-db-reopen.test.ts +6 -2
  407. package/src/resources/extensions/gsd/tests/commands-backlog.test.ts +158 -0
  408. package/src/resources/extensions/gsd/tests/commands-do.test.ts +127 -0
  409. package/src/resources/extensions/gsd/tests/commands-extract-learnings.test.ts +340 -0
  410. package/src/resources/extensions/gsd/tests/commands-pr-branch.test.ts +68 -0
  411. package/src/resources/extensions/gsd/tests/commands-session-report.test.ts +82 -0
  412. package/src/resources/extensions/gsd/tests/commands-ship.test.ts +71 -0
  413. package/src/resources/extensions/gsd/tests/commands-workflow-custom.test.ts +14 -0
  414. package/src/resources/extensions/gsd/tests/complete-milestone-false-merge.test.ts +142 -0
  415. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +2 -2
  416. package/src/resources/extensions/gsd/tests/complete-task.test.ts +2 -2
  417. package/src/resources/extensions/gsd/tests/completed-at-reconcile.test.ts +42 -0
  418. package/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts +3 -2
  419. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +3 -2
  420. package/src/resources/extensions/gsd/tests/derive-state-helpers.test.ts +68 -8
  421. package/src/resources/extensions/gsd/tests/derive-state.test.ts +3 -3
  422. package/src/resources/extensions/gsd/tests/extension-bootstrap-isolation.test.ts +154 -0
  423. package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +10 -7
  424. package/src/resources/extensions/gsd/tests/flat-rate-routing-guard.test.ts +137 -1
  425. package/src/resources/extensions/gsd/tests/graph-context.test.ts +337 -0
  426. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +1 -1
  427. package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +4 -2
  428. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +68 -1
  429. package/src/resources/extensions/gsd/tests/md-importer.test.ts +1 -2
  430. package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -3
  431. package/src/resources/extensions/gsd/tests/model-isolation.test.ts +91 -2
  432. package/src/resources/extensions/gsd/tests/native-git-bridge-exec-fallback.test.ts +140 -0
  433. package/src/resources/extensions/gsd/tests/post-exec-retry-bypass.test.ts +79 -1
  434. package/src/resources/extensions/gsd/tests/post-unit-state-rebuild.test.ts +2 -1
  435. package/src/resources/extensions/gsd/tests/pre-execution-pause-wiring.test.ts +40 -1
  436. package/src/resources/extensions/gsd/tests/preferences.test.ts +47 -0
  437. package/src/resources/extensions/gsd/tests/single-writer-invariant.test.ts +180 -0
  438. package/src/resources/extensions/gsd/tests/state-machine-full-walkthrough.test.ts +5 -7
  439. package/src/resources/extensions/gsd/tests/token-profile.test.ts +9 -6
  440. package/src/resources/extensions/gsd/tests/uok-audit-unified.test.ts +101 -0
  441. package/src/resources/extensions/gsd/tests/uok-contracts.test.ts +85 -0
  442. package/src/resources/extensions/gsd/tests/uok-execution-graph.test.ts +69 -0
  443. package/src/resources/extensions/gsd/tests/uok-flags.test.ts +39 -0
  444. package/src/resources/extensions/gsd/tests/uok-gate-runner.test.ts +70 -0
  445. package/src/resources/extensions/gsd/tests/uok-gitops-turn-action.test.ts +85 -0
  446. package/src/resources/extensions/gsd/tests/uok-gitops-wiring.test.ts +35 -0
  447. package/src/resources/extensions/gsd/tests/uok-model-policy.test.ts +89 -0
  448. package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +167 -0
  449. package/src/resources/extensions/gsd/tests/uok-preferences.test.ts +42 -0
  450. package/src/resources/extensions/gsd/tests/validate-milestone-stuck-guard.test.ts +179 -0
  451. package/src/resources/extensions/gsd/tests/validate-milestone-write-order.test.ts +39 -0
  452. package/src/resources/extensions/gsd/tests/workflow-logger-wiring.test.ts +223 -0
  453. package/src/resources/extensions/gsd/tools/complete-slice.ts +26 -0
  454. package/src/resources/extensions/gsd/tools/validate-milestone.ts +48 -3
  455. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +3 -11
  456. package/src/resources/extensions/gsd/triage-resolution.ts +2 -7
  457. package/src/resources/extensions/gsd/types.ts +1 -1
  458. package/src/resources/extensions/gsd/unit-ownership.ts +2 -2
  459. package/src/resources/extensions/gsd/uok/audit-toggle.ts +9 -0
  460. package/src/resources/extensions/gsd/uok/audit.ts +51 -0
  461. package/src/resources/extensions/gsd/uok/contracts.ts +135 -0
  462. package/src/resources/extensions/gsd/uok/execution-graph.ts +241 -0
  463. package/src/resources/extensions/gsd/uok/flags.ts +45 -0
  464. package/src/resources/extensions/gsd/uok/gate-runner.ts +146 -0
  465. package/src/resources/extensions/gsd/uok/gitops.ts +75 -0
  466. package/src/resources/extensions/gsd/uok/kernel.ts +105 -0
  467. package/src/resources/extensions/gsd/uok/loop-adapter.ts +162 -0
  468. package/src/resources/extensions/gsd/uok/model-policy.ts +112 -0
  469. package/src/resources/extensions/gsd/uok/plan-v2.ts +156 -0
  470. package/src/resources/extensions/gsd/workflow-logger.ts +25 -0
  471. package/src/resources/extensions/gsd/workflow-manifest.ts +9 -104
  472. package/src/resources/extensions/gsd/workflow-migration.ts +21 -29
  473. package/src/resources/extensions/gsd/workflow-projections.ts +8 -1
  474. package/src/resources/extensions/gsd/workflow-reconcile.ts +15 -15
  475. package/src/resources/extensions/ttsr/ttsr-manager.ts +10 -5
  476. package/dist/web/standalone/.next/static/chunks/app/page-7115e62689b5fd84.js +0 -1
  477. package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
  478. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
  479. /package/dist/web/standalone/.next/static/{Qr27MOHx0lxRGnJvlhxxu → C7qugsXHwdw4-b4ROHvOE}/_buildManifest.js +0 -0
  480. /package/dist/web/standalone/.next/static/{Qr27MOHx0lxRGnJvlhxxu → C7qugsXHwdw4-b4ROHvOE}/_ssgManifest.js +0 -0
@@ -88,7 +88,7 @@ function createHost() {
88
88
  };
89
89
  return host;
90
90
  }
91
- test("chat-controller keeps tool output ahead of delayed assistant text for external tool streams", async () => {
91
+ test("chat-controller renders content blocks in content[] index order (tool-first stream)", async () => {
92
92
  // ToolExecutionComponent uses the global theme singleton.
93
93
  // Install a minimal no-op theme implementation for this unit test.
94
94
  globalThis[Symbol.for("@gsd/pi-coding-agent:theme")] = {
@@ -107,7 +107,6 @@ test("chat-controller keeps tool output ahead of delayed assistant text for exte
107
107
  arguments: { cmd: "echo hi" },
108
108
  };
109
109
  await handleAgentEvent(host, { type: "message_start", message: makeAssistant([]) });
110
- assert.equal(host.streamingComponent, undefined, "assistant component should be deferred at message_start");
111
110
  assert.equal(host.chatContainer.children.length, 0, "nothing should render before content arrives");
112
111
  await handleAgentEvent(host, {
113
112
  type: "message_update",
@@ -126,10 +125,9 @@ test("chat-controller keeps tool output ahead of delayed assistant text for exte
126
125
  partial: makeAssistant([toolCall]),
127
126
  },
128
127
  });
129
- assert.equal(host.streamingComponent, undefined, "assistant text container should remain deferred for tool-only updates");
128
+ // content[0] = toolCall ToolExecutionComponent renders first
130
129
  assert.equal(host.chatContainer.children.length, 1, "tool execution block should render immediately");
131
130
  assert.equal(host.chatContainer.children[0]?.constructor?.name, "ToolExecutionComponent");
132
- // Re-assert required host method before the text-bearing update path.
133
131
  host.getMarkdownThemeWithSettings = () => ({});
134
132
  await handleAgentEvent(host, {
135
133
  type: "message_update",
@@ -141,11 +139,12 @@ test("chat-controller keeps tool output ahead of delayed assistant text for exte
141
139
  partial: makeAssistant([toolCall, { type: "text", text: "done" }]),
142
140
  },
143
141
  });
144
- assert.equal(host.chatContainer.children.length, 2, "assistant content should render after existing tool output");
142
+ // content[0]=toolCall, content[1]=text order: tool, then text
143
+ assert.equal(host.chatContainer.children.length, 2, "text run should render after tool in content[] order");
145
144
  assert.equal(host.chatContainer.children[0]?.constructor?.name, "ToolExecutionComponent");
146
145
  assert.equal(host.chatContainer.children[1]?.constructor?.name, "AssistantMessageComponent");
147
146
  });
148
- test("chat-controller keeps serverToolUse output ahead of assistant text when external results arrive", async () => {
147
+ test("chat-controller renders serverToolUse before trailing text matching content[] index order", async () => {
149
148
  globalThis[Symbol.for("@gsd/pi-coding-agent:theme")] = {
150
149
  fg: (_key, text) => text,
151
150
  bg: (_key, text) => text,
@@ -171,7 +170,7 @@ test("chat-controller keeps serverToolUse output ahead of assistant text when ex
171
170
  partial: makeAssistant([serverToolUse]),
172
171
  },
173
172
  });
174
- assert.equal(host.streamingComponent, undefined, "assistant content should stay deferred while only tool content streams");
173
+ // content[0] = serverToolUse ToolExecutionComponent renders first
175
174
  assert.equal(host.chatContainer.children.length, 1, "server tool block should render immediately");
176
175
  assert.equal(host.chatContainer.children[0]?.constructor?.name, "ToolExecutionComponent");
177
176
  host.getMarkdownThemeWithSettings = () => ({});
@@ -195,10 +194,218 @@ test("chat-controller keeps serverToolUse output ahead of assistant text when ex
195
194
  partial: resultMessage,
196
195
  },
197
196
  });
198
- assert.equal(host.chatContainer.children.length, 2, "assistant text should render after existing server tool output");
197
+ // content[0]=serverToolUse, content[1]=text order: tool, then text
198
+ assert.equal(host.chatContainer.children.length, 2, "text run should render after server tool in content[] order");
199
199
  assert.equal(host.chatContainer.children[0]?.constructor?.name, "ToolExecutionComponent");
200
200
  assert.equal(host.chatContainer.children[1]?.constructor?.name, "AssistantMessageComponent");
201
201
  });
202
+ test("chat-controller keeps pre-tool prose visible until post-tool prose arrives, then prunes it", async () => {
203
+ globalThis[Symbol.for("@gsd/pi-coding-agent:theme")] = {
204
+ fg: (_key, text) => text,
205
+ bg: (_key, text) => text,
206
+ bold: (text) => text,
207
+ italic: (text) => text,
208
+ truncate: (text) => text,
209
+ };
210
+ const host = createHost();
211
+ host.getMarkdownThemeWithSettings = () => ({});
212
+ const mcpTool = {
213
+ type: "toolCall",
214
+ id: "mcp-tool-1",
215
+ name: "read",
216
+ mcpServer: "filesystem",
217
+ arguments: { filePath: "/tmp/demo.txt" },
218
+ };
219
+ await handleAgentEvent(host, { type: "message_start", message: makeAssistant([]) });
220
+ // Provisional assistant text arrives first.
221
+ await handleAgentEvent(host, {
222
+ type: "message_update",
223
+ message: makeAssistant([{ type: "text", text: "Let me inspect the workspace first." }]),
224
+ assistantMessageEvent: {
225
+ type: "text_delta",
226
+ contentIndex: 0,
227
+ delta: "Let me inspect the workspace first.",
228
+ partial: makeAssistant([{ type: "text", text: "Let me inspect the workspace first." }]),
229
+ },
230
+ });
231
+ assert.equal(host.chatContainer.children.length, 1);
232
+ assert.equal(host.chatContainer.children[0]?.constructor?.name, "AssistantMessageComponent");
233
+ // MCP tool appears; provisional text should remain visible until post-tool prose exists.
234
+ await handleAgentEvent(host, {
235
+ type: "message_update",
236
+ message: makeAssistant([{ type: "text", text: "Let me inspect the workspace first." }, mcpTool]),
237
+ assistantMessageEvent: {
238
+ type: "toolcall_end",
239
+ contentIndex: 1,
240
+ toolCall: {
241
+ ...mcpTool,
242
+ externalResult: {
243
+ content: [{ type: "text", text: "file preview" }],
244
+ details: {},
245
+ isError: false,
246
+ },
247
+ },
248
+ partial: makeAssistant([{ type: "text", text: "Let me inspect the workspace first." }, mcpTool]),
249
+ },
250
+ });
251
+ assert.equal(host.chatContainer.children.length, 2, "pre-tool prose should remain during tool-only window");
252
+ assert.equal(host.chatContainer.children[0]?.constructor?.name, "AssistantMessageComponent");
253
+ assert.equal(host.chatContainer.children[1]?.constructor?.name, "ToolExecutionComponent");
254
+ // Post-tool prose arrives: pre-tool prose should now be pruned.
255
+ const finalContent = [
256
+ { type: "text", text: "Let me inspect the workspace first." },
257
+ mcpTool,
258
+ { type: "text", text: "Which missing feature matters most to you?" },
259
+ ];
260
+ await handleAgentEvent(host, {
261
+ type: "message_update",
262
+ message: makeAssistant(finalContent),
263
+ assistantMessageEvent: {
264
+ type: "text_delta",
265
+ contentIndex: 2,
266
+ delta: "Which missing feature matters most to you?",
267
+ partial: makeAssistant(finalContent),
268
+ },
269
+ });
270
+ assert.equal(host.chatContainer.children.length, 2);
271
+ assert.equal(host.chatContainer.children[0]?.constructor?.name, "ToolExecutionComponent");
272
+ assert.equal(host.chatContainer.children[1]?.constructor?.name, "AssistantMessageComponent");
273
+ // Finalize to tear down any pinned spinner state.
274
+ await handleAgentEvent(host, { type: "message_end", message: makeAssistant(finalContent) });
275
+ });
276
+ test("chat-controller keeps pre-tool thinking visible for claude-code MCP turns without post-tool prose", async () => {
277
+ globalThis[Symbol.for("@gsd/pi-coding-agent:theme")] = {
278
+ fg: (_key, text) => text,
279
+ bg: (_key, text) => text,
280
+ bold: (text) => text,
281
+ italic: (text) => text,
282
+ truncate: (text) => text,
283
+ };
284
+ const host = createHost();
285
+ host.getMarkdownThemeWithSettings = () => ({});
286
+ const mcpTool = {
287
+ type: "toolCall",
288
+ id: "mcp-tool-thinking-1",
289
+ name: "read",
290
+ mcpServer: "filesystem",
291
+ arguments: { filePath: "/tmp/demo.txt" },
292
+ };
293
+ await handleAgentEvent(host, { type: "message_start", message: makeAssistant([]) });
294
+ const thinkingOnly = [{ type: "thinking", thinking: "I should inspect the workspace." }];
295
+ await handleAgentEvent(host, {
296
+ type: "message_update",
297
+ message: makeAssistant(thinkingOnly),
298
+ assistantMessageEvent: {
299
+ type: "thinking_delta",
300
+ contentIndex: 0,
301
+ delta: "I should inspect the workspace.",
302
+ partial: makeAssistant(thinkingOnly),
303
+ },
304
+ });
305
+ assert.equal(host.chatContainer.children.length, 1);
306
+ assert.equal(host.chatContainer.children[0]?.constructor?.name, "AssistantMessageComponent");
307
+ await handleAgentEvent(host, {
308
+ type: "message_update",
309
+ message: makeAssistant([thinkingOnly[0], mcpTool]),
310
+ assistantMessageEvent: {
311
+ type: "toolcall_end",
312
+ contentIndex: 1,
313
+ toolCall: {
314
+ ...mcpTool,
315
+ externalResult: {
316
+ content: [{ type: "text", text: "file preview" }],
317
+ details: {},
318
+ isError: false,
319
+ },
320
+ },
321
+ partial: makeAssistant([thinkingOnly[0], mcpTool]),
322
+ },
323
+ });
324
+ assert.equal(host.chatContainer.children.length, 2, "thinking should remain visible while only tool output is present");
325
+ assert.equal(host.chatContainer.children[0]?.constructor?.name, "AssistantMessageComponent");
326
+ assert.equal(host.chatContainer.children[1]?.constructor?.name, "ToolExecutionComponent");
327
+ await handleAgentEvent(host, { type: "message_end", message: makeAssistant([thinkingOnly[0], mcpTool]) });
328
+ });
329
+ test("chat-controller prunes orphaned provisional text after claude-code sub-turn shrink when MCP tools appear", async () => {
330
+ globalThis[Symbol.for("@gsd/pi-coding-agent:theme")] = {
331
+ fg: (_key, text) => text,
332
+ bg: (_key, text) => text,
333
+ bold: (text) => text,
334
+ italic: (text) => text,
335
+ truncate: (text) => text,
336
+ };
337
+ const host = createHost();
338
+ host.getMarkdownThemeWithSettings = () => ({});
339
+ const mcpTool = {
340
+ type: "toolCall",
341
+ id: "mcp-tool-shrink-1",
342
+ name: "glob",
343
+ mcpServer: "filesystem",
344
+ arguments: { pattern: "**/*" },
345
+ };
346
+ await handleAgentEvent(host, { type: "message_start", message: makeAssistant([]) });
347
+ // Sub-turn 1: generate longer provisional text content.
348
+ await handleAgentEvent(host, {
349
+ type: "message_update",
350
+ message: makeAssistant([{ type: "text", text: "Old provisional preface." }, { type: "text", text: "More old text." }]),
351
+ assistantMessageEvent: {
352
+ type: "text_delta",
353
+ contentIndex: 1,
354
+ delta: "More old text.",
355
+ partial: makeAssistant([{ type: "text", text: "Old provisional preface." }, { type: "text", text: "More old text." }]),
356
+ },
357
+ });
358
+ assert.equal(host.chatContainer.children.length, 1, "first sub-turn text run should render");
359
+ // Sub-turn 2 starts (content shrink): old component is orphaned by design.
360
+ await handleAgentEvent(host, {
361
+ type: "message_update",
362
+ message: makeAssistant([{ type: "text", text: "New provisional text before tool." }]),
363
+ assistantMessageEvent: {
364
+ type: "text_delta",
365
+ contentIndex: 0,
366
+ delta: "New provisional text before tool.",
367
+ partial: makeAssistant([{ type: "text", text: "New provisional text before tool." }]),
368
+ },
369
+ });
370
+ assert.equal(host.chatContainer.children.length, 2, "shrink keeps prior text until MCP tool context appears");
371
+ // MCP tool appears in sub-turn 2: tool-only windows keep provisional prose visible.
372
+ await handleAgentEvent(host, {
373
+ type: "message_update",
374
+ message: makeAssistant([{ type: "text", text: "New provisional text before tool." }, mcpTool]),
375
+ assistantMessageEvent: {
376
+ type: "toolcall_end",
377
+ contentIndex: 1,
378
+ toolCall: {
379
+ ...mcpTool,
380
+ externalResult: {
381
+ content: [{ type: "text", text: "glob output" }],
382
+ details: {},
383
+ isError: false,
384
+ },
385
+ },
386
+ partial: makeAssistant([{ type: "text", text: "New provisional text before tool." }, mcpTool]),
387
+ },
388
+ });
389
+ assert.equal(host.chatContainer.children.length, 3, "stale text runs are deferred until post-tool prose arrives");
390
+ assert.equal(host.chatContainer.children[0]?.constructor?.name, "AssistantMessageComponent");
391
+ assert.equal(host.chatContainer.children[1]?.constructor?.name, "AssistantMessageComponent");
392
+ assert.equal(host.chatContainer.children[2]?.constructor?.name, "ToolExecutionComponent");
393
+ const finalContent = [mcpTool, { type: "text", text: "Final visible question?" }];
394
+ await handleAgentEvent(host, {
395
+ type: "message_update",
396
+ message: makeAssistant(finalContent),
397
+ assistantMessageEvent: {
398
+ type: "text_delta",
399
+ contentIndex: 1,
400
+ delta: "Final visible question?",
401
+ partial: makeAssistant(finalContent),
402
+ },
403
+ });
404
+ assert.equal(host.chatContainer.children.length, 2);
405
+ assert.equal(host.chatContainer.children[0]?.constructor?.name, "ToolExecutionComponent");
406
+ assert.equal(host.chatContainer.children[1]?.constructor?.name, "AssistantMessageComponent");
407
+ await handleAgentEvent(host, { type: "message_end", message: makeAssistant(finalContent) });
408
+ });
202
409
  test("chat-controller pins latest assistant text above editor when tool calls are present", async () => {
203
410
  globalThis[Symbol.for("@gsd/pi-coding-agent:theme")] = {
204
411
  fg: (_key, text) => text,
@@ -385,4 +592,360 @@ test("chat-controller does not pin when there are no tool calls", async () => {
385
592
  });
386
593
  assert.equal(host.pinnedMessageContainer.children.length, 0, "pinned zone should stay empty without tool calls");
387
594
  });
595
+ // Regression test for issue #4144: interleaved text/tool content must render in content[] index order.
596
+ // Stream: [text "A", toolCall T1, text "B", toolCall T2, text "C"]
597
+ // Expected chatContainer order: textRun(A), toolExec(T1), textRun(B), toolExec(T2), textRun(C)
598
+ // Each AssistantMessageComponent must render ONLY its own text — no duplication after message_end.
599
+ test("chat-controller renders interleaved text and tool blocks in content[] index order (#4144)", async () => {
600
+ globalThis[Symbol.for("@gsd/pi-coding-agent:theme")] = {
601
+ fg: (_key, text) => text,
602
+ bg: (_key, text) => text,
603
+ bold: (text) => text,
604
+ italic: (text) => text,
605
+ truncate: (text) => text,
606
+ };
607
+ const host = createHost();
608
+ host.getMarkdownThemeWithSettings = () => ({});
609
+ const t1 = { type: "toolCall", id: "t1", name: "tool_one", arguments: {} };
610
+ const t2 = { type: "toolCall", id: "t2", name: "tool_two", arguments: {} };
611
+ await handleAgentEvent(host, { type: "message_start", message: makeAssistant([]) });
612
+ // Stream text "A" at index 0
613
+ await handleAgentEvent(host, {
614
+ type: "message_update",
615
+ message: makeAssistant([{ type: "text", text: "A" }]),
616
+ assistantMessageEvent: {
617
+ type: "text_delta",
618
+ contentIndex: 0,
619
+ delta: "A",
620
+ partial: makeAssistant([{ type: "text", text: "A" }]),
621
+ },
622
+ });
623
+ // Stream toolCall T1 at index 1
624
+ await handleAgentEvent(host, {
625
+ type: "message_update",
626
+ message: makeAssistant([{ type: "text", text: "A" }, t1]),
627
+ assistantMessageEvent: {
628
+ type: "toolcall_end",
629
+ contentIndex: 1,
630
+ toolCall: {
631
+ ...t1,
632
+ externalResult: { content: [{ type: "text", text: "result1" }], details: {}, isError: false },
633
+ },
634
+ partial: makeAssistant([{ type: "text", text: "A" }, t1]),
635
+ },
636
+ });
637
+ // Stream text "B" at index 2
638
+ await handleAgentEvent(host, {
639
+ type: "message_update",
640
+ message: makeAssistant([{ type: "text", text: "A" }, t1, { type: "text", text: "B" }]),
641
+ assistantMessageEvent: {
642
+ type: "text_delta",
643
+ contentIndex: 2,
644
+ delta: "B",
645
+ partial: makeAssistant([{ type: "text", text: "A" }, t1, { type: "text", text: "B" }]),
646
+ },
647
+ });
648
+ // Stream toolCall T2 at index 3
649
+ await handleAgentEvent(host, {
650
+ type: "message_update",
651
+ message: makeAssistant([{ type: "text", text: "A" }, t1, { type: "text", text: "B" }, t2]),
652
+ assistantMessageEvent: {
653
+ type: "toolcall_end",
654
+ contentIndex: 3,
655
+ toolCall: {
656
+ ...t2,
657
+ externalResult: { content: [{ type: "text", text: "result2" }], details: {}, isError: false },
658
+ },
659
+ partial: makeAssistant([{ type: "text", text: "A" }, t1, { type: "text", text: "B" }, t2]),
660
+ },
661
+ });
662
+ // Stream text "C" at index 4
663
+ const finalContent = [
664
+ { type: "text", text: "A" }, t1, { type: "text", text: "B" }, t2, { type: "text", text: "C" },
665
+ ];
666
+ await handleAgentEvent(host, {
667
+ type: "message_update",
668
+ message: makeAssistant(finalContent),
669
+ assistantMessageEvent: {
670
+ type: "text_delta",
671
+ contentIndex: 4,
672
+ delta: "C",
673
+ partial: makeAssistant(finalContent),
674
+ },
675
+ });
676
+ // Finalize — exercises the message_end path where a buggy setRange(undefined) would cause duplication
677
+ await handleAgentEvent(host, { type: "message_end", message: makeAssistant(finalContent) });
678
+ // Assert interleaved order: textRun(A), toolExec(T1), textRun(B), toolExec(T2), textRun(C)
679
+ assert.equal(host.chatContainer.children.length, 5, "should have 5 children in interleaved order");
680
+ assert.equal(host.chatContainer.children[0]?.constructor?.name, "AssistantMessageComponent", "index 0: text run A");
681
+ assert.equal(host.chatContainer.children[1]?.constructor?.name, "ToolExecutionComponent", "index 1: tool T1");
682
+ assert.equal(host.chatContainer.children[2]?.constructor?.name, "AssistantMessageComponent", "index 2: text run B");
683
+ assert.equal(host.chatContainer.children[3]?.constructor?.name, "ToolExecutionComponent", "index 3: tool T2");
684
+ assert.equal(host.chatContainer.children[4]?.constructor?.name, "AssistantMessageComponent", "index 4: text run C");
685
+ // Helper: collect the text of all Markdown children inside an AssistantMessageComponent.
686
+ // Structure: AssistantMessageComponent (Container) -> contentContainer (children[0]) -> Markdown nodes.
687
+ function getRenderedTexts(comp) {
688
+ const contentContainer = comp.children?.[0];
689
+ if (!contentContainer)
690
+ return [];
691
+ return (contentContainer.children ?? [])
692
+ .filter((c) => c.constructor?.name === "Markdown")
693
+ .map((c) => c.text);
694
+ }
695
+ // Each text-run component must contain only its own text — no cross-contamination after message_end
696
+ assert.deepEqual(getRenderedTexts(host.chatContainer.children[0]), ["A"], "text run A must contain only 'A'");
697
+ assert.deepEqual(getRenderedTexts(host.chatContainer.children[2]), ["B"], "text run B must contain only 'B'");
698
+ assert.deepEqual(getRenderedTexts(host.chatContainer.children[4]), ["C"], "text run C must contain only 'C'");
699
+ });
700
+ test("chat-controller does not duplicate text when content is [text, tool, text] (interleaved stream)", async () => {
701
+ globalThis[Symbol.for("@gsd/pi-coding-agent:theme")] = {
702
+ fg: (_key, text) => text,
703
+ bg: (_key, text) => text,
704
+ bold: (text) => text,
705
+ italic: (text) => text,
706
+ truncate: (text) => text,
707
+ };
708
+ const host = createHost();
709
+ host.getMarkdownThemeWithSettings = () => ({});
710
+ const t1 = { type: "toolCall", id: "t1", name: "tool_one", arguments: {} };
711
+ await handleAgentEvent(host, { type: "message_start", message: makeAssistant([]) });
712
+ // Step 1: text "A" at index 0
713
+ await handleAgentEvent(host, {
714
+ type: "message_update",
715
+ message: makeAssistant([{ type: "text", text: "A" }]),
716
+ assistantMessageEvent: {
717
+ type: "text_delta",
718
+ contentIndex: 0,
719
+ delta: "A",
720
+ partial: makeAssistant([{ type: "text", text: "A" }]),
721
+ },
722
+ });
723
+ // Step 2: toolCall at index 1
724
+ await handleAgentEvent(host, {
725
+ type: "message_update",
726
+ message: makeAssistant([{ type: "text", text: "A" }, t1]),
727
+ assistantMessageEvent: {
728
+ type: "toolcall_end",
729
+ contentIndex: 1,
730
+ toolCall: {
731
+ ...t1,
732
+ externalResult: { content: [{ type: "text", text: "result1" }], details: {}, isError: false },
733
+ },
734
+ partial: makeAssistant([{ type: "text", text: "A" }, t1]),
735
+ },
736
+ });
737
+ // Step 3: text "B" at index 2
738
+ const finalContent = [{ type: "text", text: "A" }, t1, { type: "text", text: "B" }];
739
+ await handleAgentEvent(host, {
740
+ type: "message_update",
741
+ message: makeAssistant(finalContent),
742
+ assistantMessageEvent: {
743
+ type: "text_delta",
744
+ contentIndex: 2,
745
+ delta: "B",
746
+ partial: makeAssistant(finalContent),
747
+ },
748
+ });
749
+ assert.equal(host.chatContainer.children.length, 3);
750
+ assert.equal(host.chatContainer.children[0]?.constructor?.name, "AssistantMessageComponent");
751
+ assert.equal(host.chatContainer.children[1]?.constructor?.name, "ToolExecutionComponent");
752
+ assert.equal(host.chatContainer.children[2]?.constructor?.name, "AssistantMessageComponent");
753
+ const firstText = host.chatContainer.children[0];
754
+ const secondText = host.chatContainer.children[2];
755
+ assert.notEqual(firstText, secondText, "text-before-tool and text-after-tool must be separate component instances");
756
+ assert.deepEqual(firstText.range, { startIndex: 0, endIndex: 0 }, "first text-run covers only content[0]");
757
+ assert.deepEqual(secondText.range, { startIndex: 2, endIndex: 2 }, "second text-run covers only content[2]");
758
+ // Finalize — regression guard: range must NOT be cleared on message_end (would cause duplication)
759
+ await handleAgentEvent(host, { type: "message_end", message: makeAssistant(finalContent) });
760
+ assert.deepEqual(secondText.range, { startIndex: 2, endIndex: 2 }, "range must not be cleared on message_end (would cause duplication)");
761
+ });
762
+ // Regression for the claude-code sub-turn bug that followed #4144:
763
+ // an adapter can reset content[] back to 0/1 mid-lifecycle when a new provider
764
+ // sub-turn begins. The segment walker must NOT update prior-sub-turn text-run
765
+ // components in place (which would destroy earlier history) and must NOT reuse
766
+ // stale tool registrations for a new tool at the same contentIndex. Prior
767
+ // sub-turn children must stay frozen; new sub-turn segments must append after
768
+ // them, and the pinned "Latest Output" mirror must re-evaluate for the new sub-turn.
769
+ test("chat-controller freezes prior sub-turn and appends new segments when content shrinks", async () => {
770
+ globalThis[Symbol.for("@gsd/pi-coding-agent:theme")] = {
771
+ fg: (_key, text) => text,
772
+ bg: (_key, text) => text,
773
+ bold: (text) => text,
774
+ italic: (text) => text,
775
+ truncate: (text) => text,
776
+ };
777
+ const host = createHost();
778
+ host.getMarkdownThemeWithSettings = () => ({});
779
+ const t1 = { type: "toolCall", id: "t1", name: "tool_one", arguments: {} };
780
+ const t2 = { type: "toolCall", id: "t2", name: "tool_two", arguments: {} };
781
+ await handleAgentEvent(host, { type: "message_start", message: makeAssistant([]) });
782
+ // Sub-turn 1: grow to [A, T1, B]
783
+ await handleAgentEvent(host, {
784
+ type: "message_update",
785
+ message: makeAssistant([{ type: "text", text: "A" }]),
786
+ assistantMessageEvent: {
787
+ type: "text_delta", contentIndex: 0, delta: "A",
788
+ partial: makeAssistant([{ type: "text", text: "A" }]),
789
+ },
790
+ });
791
+ await handleAgentEvent(host, {
792
+ type: "message_update",
793
+ message: makeAssistant([{ type: "text", text: "A" }, t1]),
794
+ assistantMessageEvent: {
795
+ type: "toolcall_end", contentIndex: 1,
796
+ toolCall: { ...t1, externalResult: { content: [{ type: "text", text: "r1" }], details: {}, isError: false } },
797
+ partial: makeAssistant([{ type: "text", text: "A" }, t1]),
798
+ },
799
+ });
800
+ await handleAgentEvent(host, {
801
+ type: "message_update",
802
+ message: makeAssistant([{ type: "text", text: "A" }, t1, { type: "text", text: "B" }]),
803
+ assistantMessageEvent: {
804
+ type: "text_delta", contentIndex: 2, delta: "B",
805
+ partial: makeAssistant([{ type: "text", text: "A" }, t1, { type: "text", text: "B" }]),
806
+ },
807
+ });
808
+ assert.equal(host.chatContainer.children.length, 3, "sub-turn 1 renders 3 children");
809
+ const priorA = host.chatContainer.children[0];
810
+ const priorT1 = host.chatContainer.children[1];
811
+ const priorB = host.chatContainer.children[2];
812
+ // Sub-turn boundary: adapter resets content[] to [C]
813
+ await handleAgentEvent(host, {
814
+ type: "message_update",
815
+ message: makeAssistant([{ type: "text", text: "C" }]),
816
+ assistantMessageEvent: {
817
+ type: "text_delta", contentIndex: 0, delta: "C",
818
+ partial: makeAssistant([{ type: "text", text: "C" }]),
819
+ },
820
+ });
821
+ // Prior 3 children must still exist in DOM — and a NEW text-run for "C" appended after them.
822
+ assert.equal(host.chatContainer.children.length, 4, "shrink must append new segment, not replace prior history");
823
+ assert.equal(host.chatContainer.children[0], priorA, "prior A component stays at index 0");
824
+ assert.equal(host.chatContainer.children[1], priorT1, "prior T1 component stays at index 1");
825
+ assert.equal(host.chatContainer.children[2], priorB, "prior B component stays at index 2");
826
+ assert.notEqual(host.chatContainer.children[3], priorA, "new C text-run must be a different component from prior A");
827
+ assert.equal(host.chatContainer.children[3]?.constructor?.name, "AssistantMessageComponent");
828
+ // Prior A component must still render "A", not be overwritten with "C".
829
+ function getRenderedTexts(comp) {
830
+ const contentContainer = comp.children?.[0];
831
+ if (!contentContainer)
832
+ return [];
833
+ return (contentContainer.children ?? [])
834
+ .filter((c) => c.constructor?.name === "Markdown")
835
+ .map((c) => c.text);
836
+ }
837
+ assert.deepEqual(getRenderedTexts(priorA), ["A"], "prior A text-run must still contain 'A' after shrink");
838
+ assert.deepEqual(getRenderedTexts(priorB), ["B"], "prior B text-run must still contain 'B' after shrink");
839
+ assert.deepEqual(getRenderedTexts(host.chatContainer.children[3]), ["C"], "new text-run must contain only 'C'");
840
+ // Sub-turn 2 grows with a new tool T2 at contentIndex=1.
841
+ await handleAgentEvent(host, {
842
+ type: "message_update",
843
+ message: makeAssistant([{ type: "text", text: "C" }, t2]),
844
+ assistantMessageEvent: {
845
+ type: "toolcall_end", contentIndex: 1,
846
+ toolCall: { ...t2, externalResult: { content: [{ type: "text", text: "r2" }], details: {}, isError: false } },
847
+ partial: makeAssistant([{ type: "text", text: "C" }, t2]),
848
+ },
849
+ });
850
+ // T2 must be appended after the new C text-run, not conflated with the stale T1 registration.
851
+ assert.equal(host.chatContainer.children.length, 5, "new tool appends after new text-run");
852
+ assert.equal(host.chatContainer.children[4]?.constructor?.name, "ToolExecutionComponent");
853
+ assert.notEqual(host.chatContainer.children[4], priorT1, "new T2 must be a different component from prior T1");
854
+ // Finalize so the module-level pinned spinner (setInterval) is torn down and the test process can exit.
855
+ await handleAgentEvent(host, { type: "message_end", message: makeAssistant([{ type: "text", text: "C" }, t2]) });
856
+ });
857
+ // Regression: after a sub-turn shrink, lastPinnedText must be cleared so the
858
+ // pinned "Latest Output" mirror can display text from the new sub-turn instead
859
+ // of staying frozen on a stale snapshot (the "bottom green stays" symptom).
860
+ test("chat-controller updates pinned zone after sub-turn shrink", async () => {
861
+ globalThis[Symbol.for("@gsd/pi-coding-agent:theme")] = {
862
+ fg: (_key, text) => text,
863
+ bg: (_key, text) => text,
864
+ bold: (text) => text,
865
+ italic: (text) => text,
866
+ truncate: (text) => text,
867
+ };
868
+ const host = createHost();
869
+ host.getMarkdownThemeWithSettings = () => ({});
870
+ const t1 = { type: "toolCall", id: "t1", name: "tool_one", arguments: {} };
871
+ const t2 = { type: "toolCall", id: "t2", name: "tool_two", arguments: {} };
872
+ await handleAgentEvent(host, { type: "message_start", message: makeAssistant([]) });
873
+ // Sub-turn 1 with pinnable text before a tool → populates pinned zone with "first".
874
+ await handleAgentEvent(host, {
875
+ type: "message_update",
876
+ message: makeAssistant([{ type: "text", text: "first" }, t1]),
877
+ assistantMessageEvent: {
878
+ type: "toolcall_end", contentIndex: 1,
879
+ toolCall: { ...t1, externalResult: { content: [{ type: "text", text: "r1" }], details: {}, isError: false } },
880
+ partial: makeAssistant([{ type: "text", text: "first" }, t1]),
881
+ },
882
+ });
883
+ const pinnedMarkdown = host.pinnedMessageContainer.children[1];
884
+ assert.equal(pinnedMarkdown?.text, "first", "pinned zone seeded with sub-turn 1 text");
885
+ // Sub-turn boundary: content resets to [second, t2].
886
+ await handleAgentEvent(host, {
887
+ type: "message_update",
888
+ message: makeAssistant([{ type: "text", text: "second" }, t2]),
889
+ assistantMessageEvent: {
890
+ type: "toolcall_end", contentIndex: 1,
891
+ toolCall: { ...t2, externalResult: { content: [{ type: "text", text: "r2" }], details: {}, isError: false } },
892
+ partial: makeAssistant([{ type: "text", text: "second" }, t2]),
893
+ },
894
+ });
895
+ // Pinned markdown must now reflect the new sub-turn's text, not stay frozen on "first".
896
+ assert.equal(pinnedMarkdown?.text, "second", "pinned zone must update after sub-turn shrink (#4144 regression)");
897
+ // Finalize so the module-level pinned spinner (setInterval) is torn down and the test process can exit.
898
+ await handleAgentEvent(host, { type: "message_end", message: makeAssistant([{ type: "text", text: "second" }, t2]) });
899
+ });
900
+ test("chat-controller: agent_end without message_end must not remove streaming component from DOM (regression #4197)", async () => {
901
+ const host = createHost();
902
+ await handleAgentEvent(host, {
903
+ type: "message_start",
904
+ message: makeAssistant([]),
905
+ });
906
+ // Simulate partial streaming that creates an AssistantMessageComponent
907
+ await handleAgentEvent(host, {
908
+ type: "message_update",
909
+ message: makeAssistant([{ type: "text", text: "partial answer" }]),
910
+ assistantMessageEvent: {
911
+ type: "text_delta",
912
+ contentIndex: 0,
913
+ delta: "partial answer",
914
+ partial: makeAssistant([{ type: "text", text: "partial answer" }]),
915
+ },
916
+ });
917
+ // Precondition: component is in DOM
918
+ assert.equal(host.chatContainer.children.length, 1, "streaming component must be in DOM after message_update");
919
+ const comp = host.chatContainer.children[0];
920
+ // Simulate abort: agent_end fires WITHOUT message_end
921
+ await handleAgentEvent(host, { type: "agent_end" });
922
+ assert.equal(host.chatContainer.children.length, 1, "agent_end must NOT remove the streaming component from the DOM (issue #4197)");
923
+ assert.equal(host.chatContainer.children[0], comp, "the same component instance must remain in the DOM after agent_end");
924
+ });
925
+ test("chat-controller: agent_end after message_end must not alter DOM", async () => {
926
+ const host = createHost();
927
+ const content = [{ type: "text", text: "complete answer" }];
928
+ await handleAgentEvent(host, {
929
+ type: "message_start",
930
+ message: makeAssistant([]),
931
+ });
932
+ await handleAgentEvent(host, {
933
+ type: "message_update",
934
+ message: makeAssistant(content),
935
+ assistantMessageEvent: {
936
+ type: "text_delta",
937
+ contentIndex: 0,
938
+ delta: "complete answer",
939
+ partial: makeAssistant(content),
940
+ },
941
+ });
942
+ await handleAgentEvent(host, {
943
+ type: "message_end",
944
+ message: makeAssistant(content),
945
+ });
946
+ const countAfterMessageEnd = host.chatContainer.children.length;
947
+ assert.ok(countAfterMessageEnd > 0, "component must be present after message_end");
948
+ await handleAgentEvent(host, { type: "agent_end" });
949
+ assert.equal(host.chatContainer.children.length, countAfterMessageEnd, "agent_end after message_end must not add or remove DOM nodes");
950
+ });
388
951
  //# sourceMappingURL=chat-controller-ordering.test.js.map