gsd-pi 2.74.0-dev.6e23363 → 2.74.0-dev.b2838e6

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 (329) hide show
  1. package/dist/resources/extensions/gsd/auto/phases.js +51 -6
  2. package/dist/resources/extensions/gsd/auto-model-selection.js +3 -3
  3. package/dist/resources/extensions/gsd/auto-post-unit.js +7 -3
  4. package/dist/resources/extensions/gsd/auto-recovery.js +24 -10
  5. package/dist/resources/extensions/gsd/auto-worktree.js +2 -0
  6. package/dist/resources/extensions/gsd/bootstrap/provider-error-resume.js +5 -3
  7. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +10 -1
  8. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +61 -9
  9. package/dist/resources/extensions/gsd/cache.js +16 -5
  10. package/dist/resources/extensions/gsd/commands/catalog.js +6 -1
  11. package/dist/resources/extensions/gsd/commands/handlers/core.js +5 -1
  12. package/dist/resources/extensions/gsd/commands/handlers/ops.js +5 -0
  13. package/dist/resources/extensions/gsd/commands-extract-learnings.js +225 -0
  14. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +50 -3
  15. package/dist/resources/extensions/gsd/docs/preferences-reference.md +2 -0
  16. package/dist/resources/extensions/gsd/ecosystem/gsd-extension-api.js +144 -0
  17. package/dist/resources/extensions/gsd/ecosystem/loader.js +145 -0
  18. package/dist/resources/extensions/gsd/guided-flow.js +8 -6
  19. package/dist/resources/extensions/gsd/preferences-types.js +1 -0
  20. package/dist/resources/extensions/gsd/preferences-validation.js +10 -0
  21. package/dist/resources/extensions/gsd/preferences.js +5 -0
  22. package/dist/resources/extensions/gsd/safety/evidence-collector.js +15 -30
  23. package/dist/resources/extensions/gsd/templates/PREFERENCES.md +1 -0
  24. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  25. package/dist/web/standalone/.next/BUILD_ID +1 -1
  26. package/dist/web/standalone/.next/app-path-routes-manifest.json +8 -8
  27. package/dist/web/standalone/.next/build-manifest.json +2 -2
  28. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  29. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  30. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  31. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  32. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  33. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  34. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  35. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  36. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  38. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  44. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  46. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +1 -1
  47. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +1 -1
  48. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +1 -1
  49. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +1 -1
  50. package/dist/web/standalone/.next/server/app/index.html +1 -1
  51. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  53. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  55. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  56. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app-paths-manifest.json +8 -8
  58. package/dist/web/standalone/.next/server/chunks/6897.js +3 -3
  59. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  60. package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
  61. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  62. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  63. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  64. package/package.json +1 -1
  65. package/packages/mcp-server/dist/readers/graph.d.ts +1 -1
  66. package/packages/mcp-server/dist/readers/graph.d.ts.map +1 -1
  67. package/packages/mcp-server/dist/readers/graph.js +107 -0
  68. package/packages/mcp-server/dist/readers/graph.js.map +1 -1
  69. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  70. package/packages/mcp-server/dist/workflow-tools.js +88 -6
  71. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  72. package/packages/mcp-server/src/readers/graph.test.ts +178 -0
  73. package/packages/mcp-server/src/readers/graph.ts +148 -1
  74. package/packages/mcp-server/src/workflow-tools.ts +95 -10
  75. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  76. package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
  77. package/packages/pi-ai/dist/index.d.ts +1 -9
  78. package/packages/pi-ai/dist/index.d.ts.map +1 -1
  79. package/packages/pi-ai/dist/index.js +1 -9
  80. package/packages/pi-ai/dist/index.js.map +1 -1
  81. package/packages/pi-ai/dist/models/capability-patches.d.ts +19 -0
  82. package/packages/pi-ai/dist/models/capability-patches.d.ts.map +1 -0
  83. package/packages/pi-ai/dist/models/capability-patches.js +36 -0
  84. package/packages/pi-ai/dist/models/capability-patches.js.map +1 -0
  85. package/packages/pi-ai/dist/{models.custom.d.ts → models/custom.d.ts} +1 -1
  86. package/packages/pi-ai/dist/models/custom.d.ts.map +1 -0
  87. package/packages/pi-ai/dist/{models.custom.js → models/custom.js} +4 -4
  88. package/packages/pi-ai/dist/models/custom.js.map +1 -0
  89. package/packages/pi-ai/dist/models/generated/amazon-bedrock.d.ts +1482 -0
  90. package/packages/pi-ai/dist/models/generated/amazon-bedrock.d.ts.map +1 -0
  91. package/packages/pi-ai/dist/models/generated/amazon-bedrock.js +1484 -0
  92. package/packages/pi-ai/dist/models/generated/amazon-bedrock.js.map +1 -0
  93. package/packages/pi-ai/dist/models/generated/anthropic.d.ts +377 -0
  94. package/packages/pi-ai/dist/models/generated/anthropic.d.ts.map +1 -0
  95. package/packages/pi-ai/dist/models/generated/anthropic.js +379 -0
  96. package/packages/pi-ai/dist/models/generated/anthropic.js.map +1 -0
  97. package/packages/pi-ai/dist/models/generated/azure-openai-responses.d.ts +700 -0
  98. package/packages/pi-ai/dist/models/generated/azure-openai-responses.d.ts.map +1 -0
  99. package/packages/pi-ai/dist/models/generated/azure-openai-responses.js +702 -0
  100. package/packages/pi-ai/dist/models/generated/azure-openai-responses.js.map +1 -0
  101. package/packages/pi-ai/dist/models/generated/cerebras.d.ts +71 -0
  102. package/packages/pi-ai/dist/models/generated/cerebras.d.ts.map +1 -0
  103. package/packages/pi-ai/dist/models/generated/cerebras.js +73 -0
  104. package/packages/pi-ai/dist/models/generated/cerebras.js.map +1 -0
  105. package/packages/pi-ai/dist/models/generated/github-copilot.d.ts +590 -0
  106. package/packages/pi-ai/dist/models/generated/github-copilot.d.ts.map +1 -0
  107. package/packages/pi-ai/dist/models/generated/github-copilot.js +444 -0
  108. package/packages/pi-ai/dist/models/generated/github-copilot.js.map +1 -0
  109. package/packages/pi-ai/dist/models/generated/google-antigravity.d.ts +156 -0
  110. package/packages/pi-ai/dist/models/generated/google-antigravity.d.ts.map +1 -0
  111. package/packages/pi-ai/dist/models/generated/google-antigravity.js +158 -0
  112. package/packages/pi-ai/dist/models/generated/google-antigravity.js.map +1 -0
  113. package/packages/pi-ai/dist/models/generated/google-gemini-cli.d.ts +105 -0
  114. package/packages/pi-ai/dist/models/generated/google-gemini-cli.d.ts.map +1 -0
  115. package/packages/pi-ai/dist/models/generated/google-gemini-cli.js +107 -0
  116. package/packages/pi-ai/dist/models/generated/google-gemini-cli.js.map +1 -0
  117. package/packages/pi-ai/dist/models/generated/google-vertex.d.ts +207 -0
  118. package/packages/pi-ai/dist/models/generated/google-vertex.d.ts.map +1 -0
  119. package/packages/pi-ai/dist/models/generated/google-vertex.js +209 -0
  120. package/packages/pi-ai/dist/models/generated/google-vertex.js.map +1 -0
  121. package/packages/pi-ai/dist/models/generated/google.d.ts +462 -0
  122. package/packages/pi-ai/dist/models/generated/google.d.ts.map +1 -0
  123. package/packages/pi-ai/dist/models/generated/google.js +464 -0
  124. package/packages/pi-ai/dist/models/generated/google.js.map +1 -0
  125. package/packages/pi-ai/dist/models/generated/groq.d.ts +309 -0
  126. package/packages/pi-ai/dist/models/generated/groq.d.ts.map +1 -0
  127. package/packages/pi-ai/dist/models/generated/groq.js +311 -0
  128. package/packages/pi-ai/dist/models/generated/groq.js.map +1 -0
  129. package/packages/pi-ai/dist/models/generated/huggingface.d.ts +383 -0
  130. package/packages/pi-ai/dist/models/generated/huggingface.d.ts.map +1 -0
  131. package/packages/pi-ai/dist/models/generated/huggingface.js +347 -0
  132. package/packages/pi-ai/dist/models/generated/huggingface.js.map +1 -0
  133. package/packages/pi-ai/dist/{models.generated.d.ts → models/generated/index.d.ts} +1 -1
  134. package/packages/pi-ai/dist/{models.generated.d.ts.map → models/generated/index.d.ts.map} +1 -1
  135. package/packages/pi-ai/dist/models/generated/index.js +51 -0
  136. package/packages/pi-ai/dist/models/generated/index.js.map +1 -0
  137. package/packages/pi-ai/dist/models/generated/kimi-coding.d.ts +37 -0
  138. package/packages/pi-ai/dist/models/generated/kimi-coding.d.ts.map +1 -0
  139. package/packages/pi-ai/dist/models/generated/kimi-coding.js +39 -0
  140. package/packages/pi-ai/dist/models/generated/kimi-coding.js.map +1 -0
  141. package/packages/pi-ai/dist/models/generated/minimax-cn.d.ts +105 -0
  142. package/packages/pi-ai/dist/models/generated/minimax-cn.d.ts.map +1 -0
  143. package/packages/pi-ai/dist/models/generated/minimax-cn.js +107 -0
  144. package/packages/pi-ai/dist/models/generated/minimax-cn.js.map +1 -0
  145. package/packages/pi-ai/dist/models/generated/minimax.d.ts +105 -0
  146. package/packages/pi-ai/dist/models/generated/minimax.d.ts.map +1 -0
  147. package/packages/pi-ai/dist/models/generated/minimax.js +107 -0
  148. package/packages/pi-ai/dist/models/generated/minimax.js.map +1 -0
  149. package/packages/pi-ai/dist/models/generated/mistral.d.ts +445 -0
  150. package/packages/pi-ai/dist/models/generated/mistral.d.ts.map +1 -0
  151. package/packages/pi-ai/dist/models/generated/mistral.js +447 -0
  152. package/packages/pi-ai/dist/models/generated/mistral.js.map +1 -0
  153. package/packages/pi-ai/dist/models/generated/openai-codex.d.ts +139 -0
  154. package/packages/pi-ai/dist/models/generated/openai-codex.d.ts.map +1 -0
  155. package/packages/pi-ai/dist/models/generated/openai-codex.js +141 -0
  156. package/packages/pi-ai/dist/models/generated/openai-codex.js.map +1 -0
  157. package/packages/pi-ai/dist/models/generated/openai.d.ts +700 -0
  158. package/packages/pi-ai/dist/models/generated/openai.d.ts.map +1 -0
  159. package/packages/pi-ai/dist/models/generated/openai.js +702 -0
  160. package/packages/pi-ai/dist/models/generated/openai.js.map +1 -0
  161. package/packages/pi-ai/dist/models/generated/opencode-go.d.ts +122 -0
  162. package/packages/pi-ai/dist/models/generated/opencode-go.d.ts.map +1 -0
  163. package/packages/pi-ai/dist/models/generated/opencode-go.js +124 -0
  164. package/packages/pi-ai/dist/models/generated/opencode-go.js.map +1 -0
  165. package/packages/pi-ai/dist/models/generated/opencode.d.ts +530 -0
  166. package/packages/pi-ai/dist/models/generated/opencode.d.ts.map +1 -0
  167. package/packages/pi-ai/dist/models/generated/opencode.js +532 -0
  168. package/packages/pi-ai/dist/models/generated/opencode.js.map +1 -0
  169. package/packages/pi-ai/dist/models/generated/openrouter.d.ts +4270 -0
  170. package/packages/pi-ai/dist/models/generated/openrouter.d.ts.map +1 -0
  171. package/packages/pi-ai/dist/models/generated/openrouter.js +4272 -0
  172. package/packages/pi-ai/dist/models/generated/openrouter.js.map +1 -0
  173. package/packages/pi-ai/dist/models/generated/vercel-ai-gateway.d.ts +2604 -0
  174. package/packages/pi-ai/dist/models/generated/vercel-ai-gateway.d.ts.map +1 -0
  175. package/packages/pi-ai/dist/models/generated/vercel-ai-gateway.js +2606 -0
  176. package/packages/pi-ai/dist/models/generated/vercel-ai-gateway.js.map +1 -0
  177. package/packages/pi-ai/dist/models/generated/xai.d.ts +411 -0
  178. package/packages/pi-ai/dist/models/generated/xai.d.ts.map +1 -0
  179. package/packages/pi-ai/dist/models/generated/xai.js +413 -0
  180. package/packages/pi-ai/dist/models/generated/xai.js.map +1 -0
  181. package/packages/pi-ai/dist/models/generated/zai.d.ts +276 -0
  182. package/packages/pi-ai/dist/models/generated/zai.d.ts.map +1 -0
  183. package/packages/pi-ai/dist/models/generated/zai.js +239 -0
  184. package/packages/pi-ai/dist/models/generated/zai.js.map +1 -0
  185. package/packages/pi-ai/dist/models/index.d.ts +27 -0
  186. package/packages/pi-ai/dist/models/index.d.ts.map +1 -0
  187. package/packages/pi-ai/dist/models/index.js +80 -0
  188. package/packages/pi-ai/dist/models/index.js.map +1 -0
  189. package/packages/pi-ai/dist/models.d.ts +1 -36
  190. package/packages/pi-ai/dist/models.d.ts.map +1 -1
  191. package/packages/pi-ai/dist/models.generated.test.js +1 -2
  192. package/packages/pi-ai/dist/models.generated.test.js.map +1 -1
  193. package/packages/pi-ai/dist/models.js +3 -112
  194. package/packages/pi-ai/dist/models.js.map +1 -1
  195. package/packages/pi-ai/dist/models.test.js +6 -5
  196. package/packages/pi-ai/dist/models.test.js.map +1 -1
  197. package/packages/pi-ai/scripts/generate-models.ts +74 -40
  198. package/packages/pi-ai/src/index.ts +1 -9
  199. package/packages/pi-ai/src/models/capability-patches.ts +40 -0
  200. package/packages/pi-ai/src/{models.custom.ts → models/custom.ts} +4 -4
  201. package/packages/pi-ai/src/models/generated/amazon-bedrock.ts +1486 -0
  202. package/packages/pi-ai/src/models/generated/anthropic.ts +381 -0
  203. package/packages/pi-ai/src/models/generated/azure-openai-responses.ts +704 -0
  204. package/packages/pi-ai/src/models/generated/cerebras.ts +75 -0
  205. package/packages/pi-ai/src/models/generated/github-copilot.ts +446 -0
  206. package/packages/pi-ai/src/models/generated/google-antigravity.ts +160 -0
  207. package/packages/pi-ai/src/models/generated/google-gemini-cli.ts +109 -0
  208. package/packages/pi-ai/src/models/generated/google-vertex.ts +211 -0
  209. package/packages/pi-ai/src/models/generated/google.ts +466 -0
  210. package/packages/pi-ai/src/models/generated/groq.ts +313 -0
  211. package/packages/pi-ai/src/models/generated/huggingface.ts +349 -0
  212. package/packages/pi-ai/src/models/generated/index.ts +52 -0
  213. package/packages/pi-ai/src/models/generated/kimi-coding.ts +41 -0
  214. package/packages/pi-ai/src/models/generated/minimax-cn.ts +109 -0
  215. package/packages/pi-ai/src/models/generated/minimax.ts +109 -0
  216. package/packages/pi-ai/src/models/generated/mistral.ts +449 -0
  217. package/packages/pi-ai/src/models/generated/openai-codex.ts +143 -0
  218. package/packages/pi-ai/src/models/generated/openai.ts +704 -0
  219. package/packages/pi-ai/src/models/generated/opencode-go.ts +126 -0
  220. package/packages/pi-ai/src/models/generated/opencode.ts +534 -0
  221. package/packages/pi-ai/src/models/generated/openrouter.ts +4274 -0
  222. package/packages/pi-ai/src/models/generated/vercel-ai-gateway.ts +2608 -0
  223. package/packages/pi-ai/src/models/generated/xai.ts +415 -0
  224. package/packages/pi-ai/src/models/generated/zai.ts +241 -0
  225. package/packages/pi-ai/src/models/index.ts +106 -0
  226. package/packages/pi-ai/src/models.generated.test.ts +1 -2
  227. package/packages/pi-ai/src/models.test.ts +6 -5
  228. package/packages/pi-ai/src/models.ts +3 -153
  229. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  230. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  231. package/packages/pi-coding-agent/dist/core/agent-session.js +8 -2
  232. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  233. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +359 -7
  234. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
  235. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/chat-frame-compaction-tone.test.d.ts +2 -0
  236. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/chat-frame-compaction-tone.test.d.ts.map +1 -0
  237. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/chat-frame-compaction-tone.test.js +61 -0
  238. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/chat-frame-compaction-tone.test.js.map +1 -0
  239. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +11 -0
  240. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -1
  241. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts +1 -0
  242. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  243. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js +23 -9
  244. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
  245. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts +11 -0
  246. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts.map +1 -0
  247. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js +53 -0
  248. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js.map +1 -0
  249. package/packages/pi-coding-agent/dist/modes/interactive/components/compaction-summary-message.d.ts +8 -5
  250. package/packages/pi-coding-agent/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
  251. package/packages/pi-coding-agent/dist/modes/interactive/components/compaction-summary-message.js +27 -13
  252. package/packages/pi-coding-agent/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
  253. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +8 -0
  254. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  255. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +68 -8
  256. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  257. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  258. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js +22 -22
  259. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js.map +1 -1
  260. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  261. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +192 -22
  262. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  263. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-ordering.test.d.ts +2 -0
  264. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-ordering.test.d.ts.map +1 -0
  265. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-ordering.test.js +38 -0
  266. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-ordering.test.js.map +1 -0
  267. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +14 -0
  268. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  269. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +70 -6
  270. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  271. package/packages/pi-coding-agent/src/core/agent-session.ts +12 -6
  272. package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +453 -7
  273. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/chat-frame-compaction-tone.test.ts +92 -0
  274. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +19 -0
  275. package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +25 -10
  276. package/packages/pi-coding-agent/src/modes/interactive/components/chat-frame.ts +75 -0
  277. package/packages/pi-coding-agent/src/modes/interactive/components/compaction-summary-message.ts +36 -15
  278. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +83 -7
  279. package/packages/pi-coding-agent/src/modes/interactive/components/user-message.ts +23 -26
  280. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +253 -45
  281. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-ordering.test.ts +44 -0
  282. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +92 -6
  283. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  284. package/packages/pi-tui/dist/tui.d.ts.map +1 -1
  285. package/packages/pi-tui/dist/tui.js +9 -2
  286. package/packages/pi-tui/dist/tui.js.map +1 -1
  287. package/packages/pi-tui/src/tui.ts +9 -1
  288. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  289. package/src/resources/extensions/gsd/auto/phases.ts +70 -6
  290. package/src/resources/extensions/gsd/auto-model-selection.ts +3 -3
  291. package/src/resources/extensions/gsd/auto-post-unit.ts +7 -3
  292. package/src/resources/extensions/gsd/auto-recovery.ts +29 -9
  293. package/src/resources/extensions/gsd/auto-worktree.ts +1 -0
  294. package/src/resources/extensions/gsd/bootstrap/provider-error-resume.ts +5 -3
  295. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +15 -1
  296. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +72 -8
  297. package/src/resources/extensions/gsd/cache.ts +16 -5
  298. package/src/resources/extensions/gsd/commands/catalog.ts +6 -1
  299. package/src/resources/extensions/gsd/commands/handlers/core.ts +5 -1
  300. package/src/resources/extensions/gsd/commands/handlers/ops.ts +5 -0
  301. package/src/resources/extensions/gsd/commands-extract-learnings.ts +304 -0
  302. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +57 -3
  303. package/src/resources/extensions/gsd/docs/preferences-reference.md +2 -0
  304. package/src/resources/extensions/gsd/ecosystem/gsd-extension-api.ts +228 -0
  305. package/src/resources/extensions/gsd/ecosystem/loader.ts +201 -0
  306. package/src/resources/extensions/gsd/guided-flow.ts +4 -2
  307. package/src/resources/extensions/gsd/preferences-types.ts +6 -0
  308. package/src/resources/extensions/gsd/preferences-validation.ts +10 -0
  309. package/src/resources/extensions/gsd/preferences.ts +6 -0
  310. package/src/resources/extensions/gsd/safety/evidence-collector.ts +15 -31
  311. package/src/resources/extensions/gsd/templates/PREFERENCES.md +1 -0
  312. package/src/resources/extensions/gsd/tests/artifacts-table-preserved-on-cache-invalidate.test.ts +177 -0
  313. package/src/resources/extensions/gsd/tests/auto-retry-mcp-churn-fixes.test.ts +272 -0
  314. package/src/resources/extensions/gsd/tests/auto-warning-noise-regression.test.ts +117 -0
  315. package/src/resources/extensions/gsd/tests/commands-extract-learnings.test.ts +340 -0
  316. package/src/resources/extensions/gsd/tests/health-widget.test.ts +1 -1
  317. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +3 -3
  318. package/src/resources/extensions/gsd/tests/preferences.test.ts +145 -0
  319. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +57 -2
  320. package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +1 -1
  321. package/src/resources/extensions/gsd/types.ts +13 -0
  322. package/src/resources/extensions/gsd/workflow-logger.ts +2 -1
  323. package/packages/pi-ai/dist/models.custom.d.ts.map +0 -1
  324. package/packages/pi-ai/dist/models.custom.js.map +0 -1
  325. package/packages/pi-ai/dist/models.generated.js +0 -14343
  326. package/packages/pi-ai/dist/models.generated.js.map +0 -1
  327. package/packages/pi-ai/src/models.generated.ts +0 -14345
  328. /package/dist/web/standalone/.next/static/{bc2gRVFTgD7j--BsJE7vP → wuiYdNtJdo9ISED55DAkz}/_buildManifest.js +0 -0
  329. /package/dist/web/standalone/.next/static/{bc2gRVFTgD7j--BsJE7vP → wuiYdNtJdo9ISED55DAkz}/_ssgManifest.js +0 -0
@@ -1558,9 +1558,12 @@ export class AgentSession {
1558
1558
  }
1559
1559
  }
1560
1560
 
1561
- this._disconnectFromAgent();
1562
- await this.abort();
1563
- this.agent.reset();
1561
+ // #4243: Must call abort() BEFORE _disconnectFromAgent() so that
1562
+ // message_end/agent_end events fire and the #4216 finalization code
1563
+ // can run before we unsubscribe from the event bus.
1564
+ await this.abort();
1565
+ this._disconnectFromAgent();
1566
+ this.agent.reset();
1564
1567
  // Update cwd to current process directory — auto-mode may have chdir'd
1565
1568
  // into a worktree since the original session was created.
1566
1569
  const previousCwd = this._cwd;
@@ -2411,9 +2414,12 @@ export class AgentSession {
2411
2414
  }
2412
2415
  }
2413
2416
 
2414
- this._disconnectFromAgent();
2415
- await this.abort();
2416
- this._steeringMessages = [];
2417
+ // #4243: Must call abort() BEFORE _disconnectFromAgent() so that
2418
+ // message_end/agent_end events fire and the #4216 finalization code
2419
+ // can run before we unsubscribe from the event bus.
2420
+ await this.abort();
2421
+ this._disconnectFromAgent();
2422
+ this._steeringMessages = [];
2417
2423
  this._followUpMessages = [];
2418
2424
  this._pendingNextTurnMessages = [];
2419
2425
 
@@ -234,7 +234,65 @@ test("chat-controller renders serverToolUse before trailing text matching conten
234
234
  assert.equal(host.chatContainer.children[1]?.constructor?.name, "AssistantMessageComponent");
235
235
  });
236
236
 
237
- test("chat-controller drops provisional pre-tool text for claude-code MCP turns", async () => {
237
+ test("chat-controller replays final message_end content when result adds unstreamed trailing text", async () => {
238
+ (globalThis as any)[Symbol.for("@gsd/pi-coding-agent:theme")] = {
239
+ fg: (_key: string, text: string) => text,
240
+ bg: (_key: string, text: string) => text,
241
+ bold: (text: string) => text,
242
+ italic: (text: string) => text,
243
+ truncate: (text: string) => text,
244
+ };
245
+
246
+ const host = createHost();
247
+ host.getMarkdownThemeWithSettings = () => ({});
248
+
249
+ const tool = {
250
+ type: "toolCall",
251
+ id: "mcp-end-replay-1",
252
+ name: "read",
253
+ mcpServer: "filesystem",
254
+ arguments: { filePath: "/tmp/demo.txt" },
255
+ };
256
+
257
+ await handleAgentEvent(host, { type: "message_start", message: makeAssistant([]) } as any);
258
+
259
+ const streamedContent = [
260
+ tool,
261
+ { type: "thinking", thinking: "I am analyzing tool output..." },
262
+ ];
263
+ await handleAgentEvent(
264
+ host,
265
+ {
266
+ type: "message_update",
267
+ message: makeAssistant(streamedContent),
268
+ assistantMessageEvent: {
269
+ type: "thinking_delta",
270
+ contentIndex: 1,
271
+ delta: "I am analyzing tool output...",
272
+ partial: makeAssistant(streamedContent),
273
+ },
274
+ } as any,
275
+ );
276
+
277
+ assert.equal(host.chatContainer.children.length, 2, "streaming shows tool + thinking only");
278
+ assert.equal(host.chatContainer.children[0]?.constructor?.name, "ToolExecutionComponent");
279
+ assert.equal(host.chatContainer.children[1]?.constructor?.name, "AssistantMessageComponent");
280
+
281
+ // Final payload includes trailing text that never arrived as message_update.
282
+ const finalContent = [
283
+ tool,
284
+ { type: "thinking", thinking: "I am analyzing tool output..." },
285
+ { type: "text", text: "Correct anything important I missed?" },
286
+ ];
287
+ await handleAgentEvent(host, { type: "message_end", message: makeAssistant(finalContent) } as any);
288
+
289
+ assert.equal(host.chatContainer.children.length, 3, "message_end should replay and include trailing text segment");
290
+ assert.equal(host.chatContainer.children[0]?.constructor?.name, "ToolExecutionComponent");
291
+ assert.equal(host.chatContainer.children[1]?.constructor?.name, "AssistantMessageComponent");
292
+ assert.equal(host.chatContainer.children[2]?.constructor?.name, "AssistantMessageComponent");
293
+ });
294
+
295
+ test("chat-controller keeps pre-tool prose visible until post-tool prose arrives, then prunes it", async () => {
238
296
  (globalThis as any)[Symbol.for("@gsd/pi-coding-agent:theme")] = {
239
297
  fg: (_key: string, text: string) => text,
240
298
  bg: (_key: string, text: string) => text,
@@ -273,7 +331,7 @@ test("chat-controller drops provisional pre-tool text for claude-code MCP turns"
273
331
  assert.equal(host.chatContainer.children.length, 1);
274
332
  assert.equal(host.chatContainer.children[0]?.constructor?.name, "AssistantMessageComponent");
275
333
 
276
- // MCP tool appears; provisional text should be removed from the chat stack.
334
+ // MCP tool appears; provisional text should remain visible until post-tool prose exists.
277
335
  await handleAgentEvent(
278
336
  host,
279
337
  {
@@ -294,11 +352,16 @@ test("chat-controller drops provisional pre-tool text for claude-code MCP turns"
294
352
  },
295
353
  } as any,
296
354
  );
297
- assert.equal(host.chatContainer.children.length, 1, "provisional pre-tool text should be pruned");
298
- assert.equal(host.chatContainer.children[0]?.constructor?.name, "ToolExecutionComponent");
355
+ assert.equal(host.chatContainer.children.length, 2, "pre-tool prose should remain during tool-only window");
356
+ assert.equal(host.chatContainer.children[0]?.constructor?.name, "AssistantMessageComponent");
357
+ assert.equal(host.chatContainer.children[1]?.constructor?.name, "ToolExecutionComponent");
299
358
 
300
- // Final assistant output should render below the tool.
301
- const finalContent = [mcpTool, { type: "text", text: "Which missing feature matters most to you?" }];
359
+ // Post-tool prose arrives: pre-tool prose should now be pruned.
360
+ const finalContent = [
361
+ { type: "text", text: "Let me inspect the workspace first." },
362
+ mcpTool,
363
+ { type: "text", text: "Which missing feature matters most to you?" },
364
+ ];
302
365
  await handleAgentEvent(
303
366
  host,
304
367
  {
@@ -306,7 +369,7 @@ test("chat-controller drops provisional pre-tool text for claude-code MCP turns"
306
369
  message: makeAssistant(finalContent),
307
370
  assistantMessageEvent: {
308
371
  type: "text_delta",
309
- contentIndex: 1,
372
+ contentIndex: 2,
310
373
  delta: "Which missing feature matters most to you?",
311
374
  partial: makeAssistant(finalContent),
312
375
  },
@@ -320,6 +383,389 @@ test("chat-controller drops provisional pre-tool text for claude-code MCP turns"
320
383
  await handleAgentEvent(host, { type: "message_end", message: makeAssistant(finalContent) } as any);
321
384
  });
322
385
 
386
+ test("chat-controller keeps pre-tool thinking visible for claude-code MCP turns without post-tool prose", async () => {
387
+ (globalThis as any)[Symbol.for("@gsd/pi-coding-agent:theme")] = {
388
+ fg: (_key: string, text: string) => text,
389
+ bg: (_key: string, text: string) => text,
390
+ bold: (text: string) => text,
391
+ italic: (text: string) => text,
392
+ truncate: (text: string) => text,
393
+ };
394
+
395
+ const host = createHost();
396
+ host.getMarkdownThemeWithSettings = () => ({});
397
+
398
+ const mcpTool = {
399
+ type: "toolCall",
400
+ id: "mcp-tool-thinking-1",
401
+ name: "read",
402
+ mcpServer: "filesystem",
403
+ arguments: { filePath: "/tmp/demo.txt" },
404
+ };
405
+
406
+ await handleAgentEvent(host, { type: "message_start", message: makeAssistant([]) } as any);
407
+
408
+ const thinkingOnly = [{ type: "thinking", thinking: "I should inspect the workspace." }];
409
+ await handleAgentEvent(
410
+ host,
411
+ {
412
+ type: "message_update",
413
+ message: makeAssistant(thinkingOnly),
414
+ assistantMessageEvent: {
415
+ type: "thinking_delta",
416
+ contentIndex: 0,
417
+ delta: "I should inspect the workspace.",
418
+ partial: makeAssistant(thinkingOnly),
419
+ },
420
+ } as any,
421
+ );
422
+ assert.equal(host.chatContainer.children.length, 1);
423
+ assert.equal(host.chatContainer.children[0]?.constructor?.name, "AssistantMessageComponent");
424
+
425
+ await handleAgentEvent(
426
+ host,
427
+ {
428
+ type: "message_update",
429
+ message: makeAssistant([thinkingOnly[0], mcpTool]),
430
+ assistantMessageEvent: {
431
+ type: "toolcall_end",
432
+ contentIndex: 1,
433
+ toolCall: {
434
+ ...mcpTool,
435
+ externalResult: {
436
+ content: [{ type: "text", text: "file preview" }],
437
+ details: {},
438
+ isError: false,
439
+ },
440
+ },
441
+ partial: makeAssistant([thinkingOnly[0], mcpTool]),
442
+ },
443
+ } as any,
444
+ );
445
+
446
+ assert.equal(host.chatContainer.children.length, 2, "thinking should remain visible while only tool output is present");
447
+ assert.equal(host.chatContainer.children[0]?.constructor?.name, "AssistantMessageComponent");
448
+ assert.equal(host.chatContainer.children[1]?.constructor?.name, "ToolExecutionComponent");
449
+
450
+ await handleAgentEvent(host, { type: "message_end", message: makeAssistant([thinkingOnly[0], mcpTool]) } as any);
451
+ });
452
+
453
+ test("chat-controller keeps pre-tool question text for claude-code MCP when post-tool prose exists", async () => {
454
+ (globalThis as any)[Symbol.for("@gsd/pi-coding-agent:theme")] = {
455
+ fg: (_key: string, text: string) => text,
456
+ bg: (_key: string, text: string) => text,
457
+ bold: (text: string) => text,
458
+ italic: (text: string) => text,
459
+ truncate: (text: string) => text,
460
+ };
461
+
462
+ const host = createHost();
463
+ host.getMarkdownThemeWithSettings = () => ({});
464
+
465
+ const mcpTool = {
466
+ type: "toolCall",
467
+ id: "mcp-tool-question-1",
468
+ name: "glob",
469
+ mcpServer: "filesystem",
470
+ arguments: { pattern: "**/*" },
471
+ };
472
+
473
+ await handleAgentEvent(host, { type: "message_start", message: makeAssistant([]) } as any);
474
+
475
+ const questionText = { type: "text", text: "Which file should I inspect?" };
476
+
477
+ await handleAgentEvent(
478
+ host,
479
+ {
480
+ type: "message_update",
481
+ message: makeAssistant([questionText]),
482
+ assistantMessageEvent: {
483
+ type: "text_delta",
484
+ contentIndex: 0,
485
+ delta: questionText.text,
486
+ partial: makeAssistant([questionText]),
487
+ },
488
+ } as any,
489
+ );
490
+
491
+ await handleAgentEvent(
492
+ host,
493
+ {
494
+ type: "message_update",
495
+ message: makeAssistant([questionText, mcpTool]),
496
+ assistantMessageEvent: {
497
+ type: "toolcall_end",
498
+ contentIndex: 1,
499
+ toolCall: {
500
+ ...mcpTool,
501
+ externalResult: {
502
+ content: [{ type: "text", text: "glob output" }],
503
+ details: {},
504
+ isError: false,
505
+ },
506
+ },
507
+ partial: makeAssistant([questionText, mcpTool]),
508
+ },
509
+ } as any,
510
+ );
511
+
512
+ const postTool = { type: "text", text: "I'll review that next." };
513
+ const finalContent = [questionText, mcpTool, postTool];
514
+ await handleAgentEvent(
515
+ host,
516
+ {
517
+ type: "message_update",
518
+ message: makeAssistant(finalContent),
519
+ assistantMessageEvent: {
520
+ type: "text_delta",
521
+ contentIndex: 2,
522
+ delta: postTool.text,
523
+ partial: makeAssistant(finalContent),
524
+ },
525
+ } as any,
526
+ );
527
+
528
+ assert.equal(host.chatContainer.children.length, 3, "question text should remain alongside MCP tool and post-tool prose");
529
+ assert.equal(host.chatContainer.children[0]?.constructor?.name, "AssistantMessageComponent", "pre-tool question stays visible");
530
+ assert.equal(host.chatContainer.children[1]?.constructor?.name, "ToolExecutionComponent", "tool renders in the middle");
531
+ assert.equal(host.chatContainer.children[2]?.constructor?.name, "AssistantMessageComponent", "post-tool prose renders last");
532
+
533
+ await handleAgentEvent(host, { type: "message_end", message: makeAssistant(finalContent) } as any);
534
+ });
535
+
536
+ test("chat-controller prunes orphaned provisional text after claude-code sub-turn shrink when MCP tools appear", async () => {
537
+ (globalThis as any)[Symbol.for("@gsd/pi-coding-agent:theme")] = {
538
+ fg: (_key: string, text: string) => text,
539
+ bg: (_key: string, text: string) => text,
540
+ bold: (text: string) => text,
541
+ italic: (text: string) => text,
542
+ truncate: (text: string) => text,
543
+ };
544
+
545
+ const host = createHost();
546
+ host.getMarkdownThemeWithSettings = () => ({});
547
+
548
+ const mcpTool = {
549
+ type: "toolCall",
550
+ id: "mcp-tool-shrink-1",
551
+ name: "glob",
552
+ mcpServer: "filesystem",
553
+ arguments: { pattern: "**/*" },
554
+ };
555
+
556
+ await handleAgentEvent(host, { type: "message_start", message: makeAssistant([]) } as any);
557
+
558
+ // Sub-turn 1: generate longer provisional text content.
559
+ await handleAgentEvent(
560
+ host,
561
+ {
562
+ type: "message_update",
563
+ message: makeAssistant([{ type: "text", text: "Old provisional preface." }, { type: "text", text: "More old text." }]),
564
+ assistantMessageEvent: {
565
+ type: "text_delta",
566
+ contentIndex: 1,
567
+ delta: "More old text.",
568
+ partial: makeAssistant([{ type: "text", text: "Old provisional preface." }, { type: "text", text: "More old text." }]),
569
+ },
570
+ } as any,
571
+ );
572
+ assert.equal(host.chatContainer.children.length, 1, "first sub-turn text run should render");
573
+
574
+ // Sub-turn 2 starts (content shrink): old component is orphaned by design.
575
+ await handleAgentEvent(
576
+ host,
577
+ {
578
+ type: "message_update",
579
+ message: makeAssistant([{ type: "text", text: "New provisional text before tool." }]),
580
+ assistantMessageEvent: {
581
+ type: "text_delta",
582
+ contentIndex: 0,
583
+ delta: "New provisional text before tool.",
584
+ partial: makeAssistant([{ type: "text", text: "New provisional text before tool." }]),
585
+ },
586
+ } as any,
587
+ );
588
+ assert.equal(host.chatContainer.children.length, 2, "shrink keeps prior text until MCP tool context appears");
589
+
590
+ // MCP tool appears in sub-turn 2: tool-only windows keep provisional prose visible.
591
+ await handleAgentEvent(
592
+ host,
593
+ {
594
+ type: "message_update",
595
+ message: makeAssistant([{ type: "text", text: "New provisional text before tool." }, mcpTool]),
596
+ assistantMessageEvent: {
597
+ type: "toolcall_end",
598
+ contentIndex: 1,
599
+ toolCall: {
600
+ ...mcpTool,
601
+ externalResult: {
602
+ content: [{ type: "text", text: "glob output" }],
603
+ details: {},
604
+ isError: false,
605
+ },
606
+ },
607
+ partial: makeAssistant([{ type: "text", text: "New provisional text before tool." }, mcpTool]),
608
+ },
609
+ } as any,
610
+ );
611
+ assert.equal(host.chatContainer.children.length, 3, "stale text runs are deferred until post-tool prose arrives");
612
+ assert.equal(host.chatContainer.children[0]?.constructor?.name, "AssistantMessageComponent");
613
+ assert.equal(host.chatContainer.children[1]?.constructor?.name, "AssistantMessageComponent");
614
+ assert.equal(host.chatContainer.children[2]?.constructor?.name, "ToolExecutionComponent");
615
+
616
+ const finalContent = [mcpTool, { type: "text", text: "Final visible question?" }];
617
+ await handleAgentEvent(
618
+ host,
619
+ {
620
+ type: "message_update",
621
+ message: makeAssistant(finalContent),
622
+ assistantMessageEvent: {
623
+ type: "text_delta",
624
+ contentIndex: 1,
625
+ delta: "Final visible question?",
626
+ partial: makeAssistant(finalContent),
627
+ },
628
+ } as any,
629
+ );
630
+ assert.equal(host.chatContainer.children.length, 2);
631
+ assert.equal(host.chatContainer.children[0]?.constructor?.name, "ToolExecutionComponent");
632
+ assert.equal(host.chatContainer.children[1]?.constructor?.name, "AssistantMessageComponent");
633
+
634
+ await handleAgentEvent(host, { type: "message_end", message: makeAssistant(finalContent) } as any);
635
+ });
636
+
637
+ test("chat-controller prunes orphans from multiple sub-turn shrinks before MCP post-tool prose", async () => {
638
+ (globalThis as any)[Symbol.for("@gsd/pi-coding-agent:theme")] = {
639
+ fg: (_key: string, text: string) => text,
640
+ bg: (_key: string, text: string) => text,
641
+ bold: (text: string) => text,
642
+ italic: (text: string) => text,
643
+ truncate: (text: string) => text,
644
+ };
645
+
646
+ const host = createHost();
647
+ host.getMarkdownThemeWithSettings = () => ({});
648
+
649
+ const mcpTool = {
650
+ type: "toolCall",
651
+ id: "mcp-tool-multi-shrink-1",
652
+ name: "glob",
653
+ mcpServer: "filesystem",
654
+ arguments: { pattern: "**/*" },
655
+ };
656
+
657
+ await handleAgentEvent(host, { type: "message_start", message: makeAssistant([]) } as any);
658
+
659
+ // Sub-turn 1: 3 text blocks (merged into one text-run).
660
+ const subTurn1 = [
661
+ { type: "text", text: "First provisional A." },
662
+ { type: "text", text: "First provisional B." },
663
+ { type: "text", text: "First provisional C." },
664
+ ];
665
+ await handleAgentEvent(
666
+ host,
667
+ {
668
+ type: "message_update",
669
+ message: makeAssistant(subTurn1),
670
+ assistantMessageEvent: {
671
+ type: "text_delta",
672
+ contentIndex: 2,
673
+ delta: "First provisional C.",
674
+ partial: makeAssistant(subTurn1),
675
+ },
676
+ } as any,
677
+ );
678
+ assert.equal(host.chatContainer.children.length, 1, "first sub-turn renders 1 text-run");
679
+
680
+ // Sub-turn 2 (first shrink 3 → 2 blocks).
681
+ const subTurn2 = [
682
+ { type: "text", text: "Second provisional A." },
683
+ { type: "text", text: "Second provisional B." },
684
+ ];
685
+ await handleAgentEvent(
686
+ host,
687
+ {
688
+ type: "message_update",
689
+ message: makeAssistant(subTurn2),
690
+ assistantMessageEvent: {
691
+ type: "text_delta",
692
+ contentIndex: 1,
693
+ delta: "Second provisional B.",
694
+ partial: makeAssistant(subTurn2),
695
+ },
696
+ } as any,
697
+ );
698
+ assert.equal(host.chatContainer.children.length, 2, "first shrink appends, keeps prior text as frozen history");
699
+
700
+ // Sub-turn 3 (second shrink 2 → 1 block). This is the critical step —
701
+ // without orphan accumulation, sub-turn 1's orphaned segment would be
702
+ // dropped from tracking here and later strand in the container.
703
+ const subTurn3 = [{ type: "text", text: "Third provisional." }];
704
+ await handleAgentEvent(
705
+ host,
706
+ {
707
+ type: "message_update",
708
+ message: makeAssistant(subTurn3),
709
+ assistantMessageEvent: {
710
+ type: "text_delta",
711
+ contentIndex: 0,
712
+ delta: "Third provisional.",
713
+ partial: makeAssistant(subTurn3),
714
+ },
715
+ } as any,
716
+ );
717
+ assert.equal(host.chatContainer.children.length, 3, "second shrink appends again, still no prune (no post-tool text)");
718
+
719
+ // MCP tool appears — tool-only window still keeps provisional prose visible.
720
+ await handleAgentEvent(
721
+ host,
722
+ {
723
+ type: "message_update",
724
+ message: makeAssistant([{ type: "text", text: "Third provisional." }, mcpTool]),
725
+ assistantMessageEvent: {
726
+ type: "toolcall_end",
727
+ contentIndex: 1,
728
+ toolCall: {
729
+ ...mcpTool,
730
+ externalResult: {
731
+ content: [{ type: "text", text: "glob output" }],
732
+ details: {},
733
+ isError: false,
734
+ },
735
+ },
736
+ partial: makeAssistant([{ type: "text", text: "Third provisional." }, mcpTool]),
737
+ },
738
+ } as any,
739
+ );
740
+ assert.equal(host.chatContainer.children.length, 4, "tool-only window keeps all three provisional text-runs");
741
+
742
+ // Final post-tool text arrives — prune must drop ALL three pre-tool
743
+ // provisional text-runs across both shrinks, leaving only tool + final text.
744
+ const finalContent = [mcpTool, { type: "text", text: "Final answer." }];
745
+ await handleAgentEvent(
746
+ host,
747
+ {
748
+ type: "message_update",
749
+ message: makeAssistant(finalContent),
750
+ assistantMessageEvent: {
751
+ type: "text_delta",
752
+ contentIndex: 1,
753
+ delta: "Final answer.",
754
+ partial: makeAssistant(finalContent),
755
+ },
756
+ } as any,
757
+ );
758
+ assert.equal(
759
+ host.chatContainer.children.length,
760
+ 2,
761
+ "all pre-tool provisional segments from every shrink must be pruned once post-tool prose arrives",
762
+ );
763
+ assert.equal(host.chatContainer.children[0]?.constructor?.name, "ToolExecutionComponent");
764
+ assert.equal(host.chatContainer.children[1]?.constructor?.name, "AssistantMessageComponent");
765
+
766
+ await handleAgentEvent(host, { type: "message_end", message: makeAssistant(finalContent) } as any);
767
+ });
768
+
323
769
  test("chat-controller pins latest assistant text above editor when tool calls are present", async () => {
324
770
  (globalThis as any)[Symbol.for("@gsd/pi-coding-agent:theme")] = {
325
771
  fg: (_key: string, text: string) => text,
@@ -0,0 +1,92 @@
1
+ import { test, describe } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import stripAnsi from "strip-ansi";
4
+ import { renderChatFrame } from "../chat-frame.js";
5
+ import { initTheme } from "../../theme/theme.js";
6
+
7
+ initTheme("dark", false);
8
+
9
+ // Regression tests for the "compaction" tone added to renderChatFrame.
10
+ // The compaction notice shares the same visual frame as user / assistant
11
+ // messages (top rule, `• label` header, `│ ` body prefix) but uses the
12
+ // purple `customMessageLabel` color key so it is visually distinct from
13
+ // conversation turns.
14
+
15
+ describe("renderChatFrame — compaction tone", () => {
16
+ test("produces a top rule, `• compaction` header row, and a │ body margin", () => {
17
+ const lines = renderChatFrame(
18
+ ["Compacted from 1,224,262 tokens (ctrl+o to expand)"],
19
+ 60,
20
+ {
21
+ label: "compaction",
22
+ tone: "compaction",
23
+ timestampFormat: "date-time-iso",
24
+ showTimestamp: false,
25
+ },
26
+ );
27
+
28
+ // Structure: top rule, header, body line(s)
29
+ assert.ok(lines.length >= 3, `expected at least 3 frame lines, got ${lines.length}`);
30
+
31
+ const plain = lines.map((line) => stripAnsi(line));
32
+
33
+ // Top rule is a solid horizontal bar
34
+ assert.match(plain[0], /^─+$/, "first line should be the solid top rule");
35
+
36
+ // Header row contains `• compaction`
37
+ assert.ok(
38
+ plain[1].includes("• compaction"),
39
+ `expected header to contain "• compaction", got ${JSON.stringify(plain[1])}`,
40
+ );
41
+
42
+ // Body line(s) start with `│ `
43
+ assert.ok(
44
+ plain[2].startsWith("│ "),
45
+ `expected body line to start with "│ ", got ${JSON.stringify(plain[2])}`,
46
+ );
47
+ assert.ok(
48
+ plain[2].includes("Compacted from 1,224,262 tokens"),
49
+ "body line should include the original content",
50
+ );
51
+ });
52
+
53
+ test("does not render a right-aligned timestamp when showTimestamp is false", () => {
54
+ const lines = renderChatFrame(["body"], 60, {
55
+ label: "compaction",
56
+ tone: "compaction",
57
+ timestamp: Date.now(),
58
+ timestampFormat: "date-time-iso",
59
+ showTimestamp: false,
60
+ });
61
+
62
+ const header = stripAnsi(lines[1]);
63
+ // No four-digit year should appear anywhere in the header row
64
+ assert.ok(
65
+ !/\b20\d{2}\b/.test(header),
66
+ `timestamp should be suppressed when showTimestamp=false, got ${JSON.stringify(header)}`,
67
+ );
68
+ });
69
+
70
+ test("emits ANSI color codes distinct from the assistant tone", () => {
71
+ const assistantFrame = renderChatFrame(["body"], 60, {
72
+ label: "claude",
73
+ tone: "assistant",
74
+ timestampFormat: "date-time-iso",
75
+ showTimestamp: false,
76
+ }).join("\n");
77
+
78
+ const compactionFrame = renderChatFrame(["body"], 60, {
79
+ label: "compaction",
80
+ tone: "compaction",
81
+ timestampFormat: "date-time-iso",
82
+ showTimestamp: false,
83
+ }).join("\n");
84
+
85
+ // Both frames carry ANSI; the compaction frame should not be identical
86
+ // to the assistant frame (different color mappings).
87
+ assert.ok(
88
+ assistantFrame !== compactionFrame,
89
+ "compaction tone must produce a different styled output than assistant tone",
90
+ );
91
+ });
92
+ });
@@ -48,6 +48,25 @@ function renderToolCollapsed(
48
48
  }
49
49
 
50
50
  describe("ToolExecutionComponent", () => {
51
+ test("renders framed header with Running status while tool is partial", () => {
52
+ const rendered = renderToolCollapsed("mcp__demo__do_thing", { ok: true });
53
+
54
+ assert.match(rendered, /Tool demo\u00b7do_thing/);
55
+ assert.match(rendered, /Running/);
56
+ });
57
+
58
+ test("renders framed header with Error status for failed tool result", () => {
59
+ const rendered = renderTool(
60
+ "mcp__demo__do_thing",
61
+ { ok: true },
62
+ { content: [{ type: "text", text: "boom" }], isError: true },
63
+ );
64
+
65
+ assert.match(rendered, /Tool demo\u00b7do_thing/);
66
+ assert.match(rendered, /Error/);
67
+ assert.match(rendered, /boom/);
68
+ });
69
+
51
70
  test("renders capitalized Claude Code Bash tool names with bash output instead of generic args JSON", () => {
52
71
  const rendered = renderTool(
53
72
  "Bash",