gsd-pi 2.80.0-dev.96f4d89d5 → 2.80.0-dev.b2a374d5b

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 (239) hide show
  1. package/dist/resources/.managed-resources-content-hash +1 -1
  2. package/dist/resources/GSD-WORKFLOW.md +2 -2
  3. package/dist/resources/extensions/gsd/auto/loop.js +32 -1
  4. package/dist/resources/extensions/gsd/auto/phases.js +37 -30
  5. package/dist/resources/extensions/gsd/auto-dispatch.js +10 -0
  6. package/dist/resources/extensions/gsd/auto-post-unit.js +10 -10
  7. package/dist/resources/extensions/gsd/auto-prompts.js +111 -1
  8. package/dist/resources/extensions/gsd/auto-recovery.js +154 -8
  9. package/dist/resources/extensions/gsd/auto-start.js +2 -3
  10. package/dist/resources/extensions/gsd/auto.js +16 -3
  11. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +32 -1
  12. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +129 -1
  13. package/dist/resources/extensions/gsd/clean-root-preflight.js +42 -4
  14. package/dist/resources/extensions/gsd/commands/dispatcher.js +5 -0
  15. package/dist/resources/extensions/gsd/commands-extract-learnings.js +17 -12
  16. package/dist/resources/extensions/gsd/crash-recovery.js +56 -10
  17. package/dist/resources/extensions/gsd/db-base-schema.js +14 -0
  18. package/dist/resources/extensions/gsd/db-migration-steps.js +16 -0
  19. package/dist/resources/extensions/gsd/detection.js +106 -0
  20. package/dist/resources/extensions/gsd/gsd-db.js +102 -2
  21. package/dist/resources/extensions/gsd/guided-flow.js +63 -12
  22. package/dist/resources/extensions/gsd/migrate/command.js +40 -1
  23. package/dist/resources/extensions/gsd/migration-auto-check.js +87 -0
  24. package/dist/resources/extensions/gsd/planning-path-scope.js +26 -0
  25. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +7 -8
  26. package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
  27. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +3 -1
  28. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  29. package/dist/resources/extensions/gsd/prompts/quick-task.md +1 -5
  30. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -2
  31. package/dist/resources/extensions/gsd/quick.js +34 -2
  32. package/dist/resources/extensions/gsd/safety/evidence-collector.js +10 -2
  33. package/dist/resources/extensions/gsd/tools/plan-slice.js +9 -0
  34. package/dist/resources/extensions/gsd/tools/plan-task.js +9 -0
  35. package/dist/resources/extensions/gsd/unit-runtime.js +11 -0
  36. package/dist/resources/extensions/gsd/worktree-manager.js +16 -14
  37. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  38. package/dist/web/standalone/.next/BUILD_ID +1 -1
  39. package/dist/web/standalone/.next/app-path-routes-manifest.json +13 -13
  40. package/dist/web/standalone/.next/build-manifest.json +2 -2
  41. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  42. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  43. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  44. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  47. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  48. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  51. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  53. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  55. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  56. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app/index.html +1 -1
  59. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app-paths-manifest.json +13 -13
  66. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  67. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  68. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  69. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  70. package/package.json +4 -2
  71. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  72. package/packages/pi-coding-agent/dist/core/agent-session.js +3 -0
  73. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  74. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +76 -0
  75. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
  76. package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts +11 -0
  77. package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts.map +1 -1
  78. package/packages/pi-coding-agent/dist/core/compaction/compaction.js +9 -0
  79. package/packages/pi-coding-agent/dist/core/compaction/compaction.js.map +1 -1
  80. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.d.ts +2 -0
  81. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.d.ts.map +1 -0
  82. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.js +103 -0
  83. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.js.map +1 -0
  84. package/packages/pi-coding-agent/dist/core/db-snapshot.d.ts +15 -0
  85. package/packages/pi-coding-agent/dist/core/db-snapshot.d.ts.map +1 -0
  86. package/packages/pi-coding-agent/dist/core/db-snapshot.js +66 -0
  87. package/packages/pi-coding-agent/dist/core/db-snapshot.js.map +1 -0
  88. package/packages/pi-coding-agent/dist/core/db-snapshot.test.d.ts +2 -0
  89. package/packages/pi-coding-agent/dist/core/db-snapshot.test.d.ts.map +1 -0
  90. package/packages/pi-coding-agent/dist/core/db-snapshot.test.js +24 -0
  91. package/packages/pi-coding-agent/dist/core/db-snapshot.test.js.map +1 -0
  92. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +3 -0
  93. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  94. package/packages/pi-coding-agent/dist/core/extensions/runner.js +17 -1
  95. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  96. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +99 -0
  97. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -1
  98. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +7 -0
  99. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  100. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  101. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +20 -0
  102. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  103. package/packages/pi-coding-agent/dist/core/settings-manager.js +25 -0
  104. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  105. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/chat-frame-compaction-tone.test.js +6 -4
  106. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/chat-frame-compaction-tone.test.js.map +1 -1
  107. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +50 -19
  108. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -1
  109. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.js +2 -2
  110. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.js.map +1 -1
  111. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  112. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
  113. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts.map +1 -1
  114. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js +2 -1
  115. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js.map +1 -1
  116. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.js +7 -6
  117. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.js.map +1 -1
  118. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +13 -0
  119. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  120. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +77 -13
  121. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  122. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  123. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +68 -2
  124. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  125. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js +53 -0
  126. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js.map +1 -1
  127. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  128. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +3 -0
  129. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  130. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.d.ts.map +1 -1
  131. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +36 -27
  132. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -1
  133. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.js +21 -0
  134. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.js.map +1 -1
  135. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.d.ts +2 -0
  136. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.d.ts.map +1 -0
  137. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.js +10 -0
  138. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.js.map +1 -0
  139. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.d.ts.map +1 -1
  140. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.js +3 -2
  141. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.js.map +1 -1
  142. package/packages/pi-coding-agent/src/core/agent-session.ts +3 -0
  143. package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +89 -0
  144. package/packages/pi-coding-agent/src/core/compaction/compaction.ts +18 -0
  145. package/packages/pi-coding-agent/src/core/compaction-threshold.test.ts +121 -0
  146. package/packages/pi-coding-agent/src/core/db-snapshot.test.ts +32 -0
  147. package/packages/pi-coding-agent/src/core/db-snapshot.ts +66 -0
  148. package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +110 -0
  149. package/packages/pi-coding-agent/src/core/extensions/runner.ts +19 -1
  150. package/packages/pi-coding-agent/src/core/extensions/types.ts +7 -0
  151. package/packages/pi-coding-agent/src/core/settings-manager.ts +39 -1
  152. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/chat-frame-compaction-tone.test.ts +7 -5
  153. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +68 -19
  154. package/packages/pi-coding-agent/src/modes/interactive/components/adaptive-layout.ts +2 -2
  155. package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +1 -0
  156. package/packages/pi-coding-agent/src/modes/interactive/components/chat-frame.ts +2 -1
  157. package/packages/pi-coding-agent/src/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.ts +10 -9
  158. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +88 -13
  159. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.test.ts +56 -0
  160. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +91 -3
  161. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +3 -0
  162. package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +36 -27
  163. package/packages/pi-coding-agent/src/modes/interactive/tui-mode.test.ts +25 -0
  164. package/packages/pi-coding-agent/src/resources/extensions/memory/storage-safety-guard.test.ts +14 -0
  165. package/packages/pi-coding-agent/src/resources/extensions/memory/storage.ts +3 -2
  166. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  167. package/packages/pi-tui/dist/tui.d.ts.map +1 -1
  168. package/packages/pi-tui/dist/tui.js +17 -7
  169. package/packages/pi-tui/dist/tui.js.map +1 -1
  170. package/packages/pi-tui/src/tui.ts +19 -7
  171. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  172. package/pkg/dist/modes/interactive/theme/themes.d.ts.map +1 -1
  173. package/pkg/dist/modes/interactive/theme/themes.js +36 -27
  174. package/pkg/dist/modes/interactive/theme/themes.js.map +1 -1
  175. package/src/resources/GSD-WORKFLOW.md +2 -2
  176. package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
  177. package/src/resources/extensions/gsd/auto/loop.ts +50 -8
  178. package/src/resources/extensions/gsd/auto/phases.ts +42 -28
  179. package/src/resources/extensions/gsd/auto-dispatch.ts +17 -0
  180. package/src/resources/extensions/gsd/auto-post-unit.ts +10 -10
  181. package/src/resources/extensions/gsd/auto-prompts.ts +116 -1
  182. package/src/resources/extensions/gsd/auto-recovery.ts +153 -7
  183. package/src/resources/extensions/gsd/auto-start.ts +7 -6
  184. package/src/resources/extensions/gsd/auto.ts +23 -3
  185. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +39 -1
  186. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +135 -1
  187. package/src/resources/extensions/gsd/clean-root-preflight.ts +41 -3
  188. package/src/resources/extensions/gsd/commands/dispatcher.ts +6 -0
  189. package/src/resources/extensions/gsd/commands-extract-learnings.ts +17 -12
  190. package/src/resources/extensions/gsd/crash-recovery.ts +67 -10
  191. package/src/resources/extensions/gsd/db-base-schema.ts +15 -0
  192. package/src/resources/extensions/gsd/db-migration-steps.ts +17 -0
  193. package/src/resources/extensions/gsd/detection.ts +128 -0
  194. package/src/resources/extensions/gsd/gsd-db.ts +119 -1
  195. package/src/resources/extensions/gsd/guided-flow.ts +66 -12
  196. package/src/resources/extensions/gsd/migrate/command.ts +47 -1
  197. package/src/resources/extensions/gsd/migration-auto-check.ts +129 -0
  198. package/src/resources/extensions/gsd/planning-path-scope.ts +35 -0
  199. package/src/resources/extensions/gsd/prompts/complete-milestone.md +7 -8
  200. package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
  201. package/src/resources/extensions/gsd/prompts/plan-milestone.md +3 -1
  202. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  203. package/src/resources/extensions/gsd/prompts/quick-task.md +1 -5
  204. package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -2
  205. package/src/resources/extensions/gsd/quick.ts +37 -2
  206. package/src/resources/extensions/gsd/safety/evidence-collector.ts +11 -2
  207. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +1 -1
  208. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +170 -1
  209. package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +88 -2
  210. package/src/resources/extensions/gsd/tests/commands-extract-learnings.test.ts +9 -0
  211. package/src/resources/extensions/gsd/tests/crash-handler-secondary.test.ts +55 -0
  212. package/src/resources/extensions/gsd/tests/crash-recovery-via-db.test.ts +22 -0
  213. package/src/resources/extensions/gsd/tests/db-schema-metadata.test.ts +2 -2
  214. package/src/resources/extensions/gsd/tests/detection.test.ts +140 -0
  215. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +44 -0
  216. package/src/resources/extensions/gsd/tests/has-pending-deep-stage.test.ts +33 -1
  217. package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +48 -0
  218. package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +127 -0
  219. package/src/resources/extensions/gsd/tests/plan-slice.test.ts +50 -0
  220. package/src/resources/extensions/gsd/tests/plan-task.test.ts +21 -0
  221. package/src/resources/extensions/gsd/tests/prompt-path-audit.test.ts +40 -0
  222. package/src/resources/extensions/gsd/tests/quick-external-gsd.test.ts +40 -0
  223. package/src/resources/extensions/gsd/tests/right-sized-workflow-prompts.test.ts +192 -0
  224. package/src/resources/extensions/gsd/tests/safety-harness-false-positives.test.ts +29 -0
  225. package/src/resources/extensions/gsd/tests/smart-entry-complete.test.ts +38 -0
  226. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +63 -2
  227. package/src/resources/extensions/gsd/tests/status-db-open.test.ts +9 -0
  228. package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +7 -0
  229. package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +1 -1
  230. package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +37 -6
  231. package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +7 -0
  232. package/src/resources/extensions/gsd/tests/worktree-nested-git-safety.test.ts +9 -2
  233. package/src/resources/extensions/gsd/tests/worktree-write-gate.test.ts +179 -0
  234. package/src/resources/extensions/gsd/tools/plan-slice.ts +13 -0
  235. package/src/resources/extensions/gsd/tools/plan-task.ts +10 -0
  236. package/src/resources/extensions/gsd/unit-runtime.ts +14 -0
  237. package/src/resources/extensions/gsd/worktree-manager.ts +15 -4
  238. /package/dist/web/standalone/.next/static/{y7d--cnc6Cibuva-Lzzw_ → FU0iL9dhdc2IBzEYBDOlY}/_buildManifest.js +0 -0
  239. /package/dist/web/standalone/.next/static/{y7d--cnc6Cibuva-Lzzw_ → FU0iL9dhdc2IBzEYBDOlY}/_ssgManifest.js +0 -0
@@ -1,3 +1,4 @@
1
+ // GSD2 TUI - Shared chat frame renderer for assistant, user, and system cards.
1
2
  import { style, truncateToWidth, visibleWidth } from "@gsd/pi-tui";
2
3
  import { theme } from "../theme/theme.js";
3
4
  import { formatTimestamp, type TimestampFormat } from "./timestamp.js";
@@ -34,7 +35,7 @@ export function renderChatFrame(
34
35
  : "borderAccent";
35
36
  const borderMuted = isPurple ? "customMessageLabel" : "borderMuted";
36
37
  const border = (s: string) => theme.fg(borderColor, s);
37
- const leftRaw = `• ${opts.label}`;
38
+ const leftRaw = opts.label;
38
39
  const rightRaw =
39
40
  opts.showTimestamp === false || !opts.timestamp
40
41
  ? ""
@@ -1,3 +1,4 @@
1
+ // GSD2 TUI Tests - Runtime coverage for tool-card cleanup and success notification rendering.
1
2
  // Runtime regression tests for the post-compaction tool-card cleanup and the
2
3
  // green-bordered success-notification rendering. Replaces the source-grep
3
4
  // `src/tests/tui-running-and-success-box.test.ts` that was deleted in #4875
@@ -74,7 +75,7 @@ function makeMockTUI(): MockTui {
74
75
  // ─── Bug 1: tool cards stuck in "Running" after compaction ──────────────
75
76
 
76
77
  describe("ToolExecutionComponent post-compaction cleanup", () => {
77
- it("renders 'Running' status while the tool call has no result", () => {
78
+ it("renders 'running' status while the tool call has no result", () => {
78
79
  // Baseline: a freshly-constructed component (mid-stream) must show
79
80
  // the running badge — this is the state we need to flip OUT of when
80
81
  // compaction removes the result message.
@@ -88,12 +89,12 @@ describe("ToolExecutionComponent post-compaction cleanup", () => {
88
89
  );
89
90
  const rendered = c.render(60).map(stripAnsi).join("\n");
90
91
  assert.ok(
91
- rendered.includes("Running"),
92
- "freshly constructed component should render 'Running' badge",
92
+ rendered.includes("running"),
93
+ "freshly constructed component should render 'running' badge",
93
94
  );
94
95
  });
95
96
 
96
- it("markHistoricalNoResult flips a stuck tool card OUT of 'Running'", () => {
97
+ it("markHistoricalNoResult flips a stuck tool card OUT of 'running'", () => {
97
98
  // Real bug: after session-history replay (post-compaction or session
98
99
  // switch), tool calls without matching tool_result messages stay in
99
100
  // isPartial = true forever. markHistoricalNoResult must produce a
@@ -111,13 +112,13 @@ describe("ToolExecutionComponent post-compaction cleanup", () => {
111
112
 
112
113
  const rendered = c.render(60).map(stripAnsi).join("\n");
113
114
  assert.ok(
114
- !rendered.includes("Running"),
115
- "after markHistoricalNoResult, the tool card must NOT render 'Running' got:\n" +
115
+ !rendered.includes("running"),
116
+ "after markHistoricalNoResult, the tool card must NOT render 'running' -- got:\n" +
116
117
  rendered,
117
118
  );
118
119
  assert.ok(
119
- rendered.includes("Done"),
120
- "flipped card should render 'Done' status (no-result success)",
120
+ rendered.includes("success"),
121
+ "flipped card should render 'success' status (no-result success)",
121
122
  );
122
123
  });
123
124
 
@@ -145,7 +146,7 @@ describe("ToolExecutionComponent post-compaction cleanup", () => {
145
146
  );
146
147
 
147
148
  const before = c.render(60).map(stripAnsi).join("\n");
148
- assert.ok(before.includes("Done"), "after complete(), card shows 'Done'");
149
+ assert.ok(before.includes("success"), "after complete(), card shows 'success'");
149
150
 
150
151
  c.markHistoricalNoResult(); // must early-return
151
152
  const after = c.render(60).map(stripAnsi).join("\n");
@@ -1,3 +1,4 @@
1
+ // GSD-2 Interactive Tool Execution Rendering
1
2
  import {
2
3
  Box,
3
4
  Container,
@@ -106,9 +107,9 @@ function renderToolFrame(
106
107
 
107
108
  const borderColor = opts.tone === "error" ? "toolError" : opts.tone === "pending" ? "toolRunning" : "toolSuccess";
108
109
  const topColor = borderColor;
109
- const labelColor = opts.tone === "error" ? "toolError" : "surfaceTitle";
110
+ const labelColor = opts.tone === "error" ? "toolError" : opts.tone === "pending" ? "toolRunning" : "toolSuccess";
110
111
  const statusColor = opts.tone === "error" ? "toolError" : opts.tone === "pending" ? "toolRunning" : "toolSuccess";
111
- const leftStyled = theme.fg(labelColor, theme.bold(`• ${opts.label}`));
112
+ const leftStyled = theme.fg(labelColor, theme.bold(opts.label));
112
113
  const rightStyled = theme.fg(statusColor, opts.status);
113
114
  const gap = Math.max(1, outerWidth - visibleWidth(leftStyled) - visibleWidth(rightStyled));
114
115
  const headerRow = `${leftStyled}${" ".repeat(gap)}${rightStyled}`;
@@ -127,10 +128,37 @@ function renderToolFrame(
127
128
  .render(bodyLines, outerWidth);
128
129
  }
129
130
 
131
+ function renderCollapsedToolRow(
132
+ label: string,
133
+ status: string,
134
+ width: number,
135
+ tone: Extract<ToolFrameTone, "success">,
136
+ ): string[] {
137
+ const outerWidth = Math.max(20, width);
138
+ const contentWidth = Math.max(1, outerWidth - 2);
139
+ const statusColor = tone === "success" ? "toolSuccess" : "toolMuted";
140
+ const labelStyled = theme.fg(statusColor, label);
141
+ const statusStyled = theme.fg(statusColor, status);
142
+ const labelWidth = Math.max(1, contentWidth - visibleWidth(statusStyled) - 1);
143
+ const clippedLabel = truncateToWidth(labelStyled, labelWidth, "");
144
+ const gap = Math.max(1, contentWidth - visibleWidth(clippedLabel) - visibleWidth(statusStyled));
145
+ const line = `${clippedLabel}${" ".repeat(gap)}${statusStyled}`;
146
+ return style()
147
+ .border("minimal")
148
+ .borderColor((text) => theme.fg(statusColor, text))
149
+ .render([line], outerWidth);
150
+ }
151
+
130
152
  const COMPACT_ARG_VALUE_LIMIT = 60;
131
153
  const GENERIC_OUTPUT_PREVIEW_LINES = 10;
132
154
  const GENERIC_ARGS_JSON_PREVIEW_LINES = 10;
133
155
 
156
+ export type ToolExecutionPhase = {
157
+ label: string;
158
+ count: number;
159
+ durationMs: number;
160
+ };
161
+
134
162
  function formatElapsed(ms: number): string {
135
163
  if (ms < 1000) return `${ms}ms`;
136
164
  return `${Math.max(1, Math.round(ms / 1000))}s`;
@@ -552,18 +580,13 @@ export class ToolExecutionComponent extends Container {
552
580
  const frameTone: ToolFrameTone =
553
581
  this.result?.isError ? "error" : this.isPartial || !this.result ? "pending" : "success";
554
582
  const elapsed = formatElapsed((this.endedAt ?? Date.now()) - this.startedAt);
555
- const frameStatus = `${this.isPartial || !this.result ? "Running" : this.result.isError ? "Error" : "Done"} · ${elapsed}`;
583
+ const frameStatus = `${this.isPartial || !this.result ? "running" : this.result.isError ? "failed" : "success"} · ${elapsed}`;
556
584
  const parsed = parseMcpToolName(this.toolName);
557
585
  const frameLabel = parsed
558
- ? `Tool ${parsed.server}·${parsed.tool}`
559
- : `Tool ${prettifyToolName(this.toolName, this.toolDefinition?.label) || "unknown"}`;
586
+ ? `${parsed.server}·${parsed.tool}`
587
+ : prettifyToolName(this.toolName, this.toolDefinition?.label) || "unknown";
560
588
  if (this.shouldRenderCompactSuccess()) {
561
- const availableWidth = Math.max(1, frameWidth - 2);
562
- const summaryWidth = Math.max(1, availableWidth - visibleWidth(frameStatus) - 1);
563
- const summary = truncateToWidth(this.getCompactSummary(frameLabel), summaryWidth, "");
564
- const gap = Math.max(1, availableWidth - visibleWidth(summary) - visibleWidth(frameStatus));
565
- const line = `${theme.fg("toolSuccess", summary)}${" ".repeat(gap)}${theme.fg("toolSuccess", frameStatus)}`;
566
- return ["", ...style().border("minimal").borderColor((text) => theme.fg("toolSuccess", text)).render([line], frameWidth)];
589
+ return ["", ...renderCollapsedToolRow(this.getCompactSummary(frameLabel), frameStatus, frameWidth, "success")];
567
590
  }
568
591
  const lines = super.render(contentWidth);
569
592
  const framed = renderToolFrame(lines, frameWidth, {
@@ -577,11 +600,38 @@ export class ToolExecutionComponent extends Container {
577
600
  private shouldRenderCompactSuccess(): boolean {
578
601
  if (this.expanded || this.isPartial || !this.result || this.result.isError) return false;
579
602
  const hasImages = this.result.content?.some((block) => block.type === "image") ?? false;
580
- return !hasImages && this.getTextOutput().trim().length === 0;
603
+ return !hasImages;
604
+ }
605
+
606
+ getRollupPhase(): ToolExecutionPhase | null {
607
+ if (!this.shouldRenderCompactSuccess()) return null;
608
+ const label = this.getPhaseLabel();
609
+ const endedAt = this.endedAt ?? Date.now();
610
+ return {
611
+ label,
612
+ count: 1,
613
+ durationMs: Math.max(0, endedAt - this.startedAt),
614
+ };
615
+ }
616
+
617
+ private getPhaseLabel(): string {
618
+ const name = this.normalizedToolName;
619
+ const displayName = prettifyToolName(this.toolName, this.toolDefinition?.label);
620
+
621
+ if (name === "bash") return "Setup / shell";
622
+ if (name === "read" || name === "ls" || name === "find" || name === "grep") return "Context reads";
623
+ if (name === "write" || name === "edit") return "File changes";
624
+ if (name === "web_search" || displayName === "ToolSearch") return "Discovery";
625
+ if (displayName === "Memory Query" || displayName === "Memory Capture" || displayName === "Gsd Graph") {
626
+ return "Memory lookups";
627
+ }
628
+ if (displayName === "Update Requirement" || displayName === "Save Requirement") return "Requirement writes";
629
+ if (displayName.startsWith("Complete ")) return "Finalization";
630
+ return "Other tool actions";
581
631
  }
582
632
 
583
633
  private getCompactSummary(frameLabel: string): string {
584
- return `${frameLabel} Completed`;
634
+ return frameLabel;
585
635
  }
586
636
 
587
637
  private updateDisplay(): void {
@@ -1231,3 +1281,28 @@ export class ToolExecutionComponent extends Container {
1231
1281
  return text;
1232
1282
  }
1233
1283
  }
1284
+
1285
+ export class ToolPhaseSummaryComponent extends Container {
1286
+ constructor(private readonly phases: ToolExecutionPhase[]) {
1287
+ super();
1288
+ }
1289
+
1290
+ getPhases(): ToolExecutionPhase[] {
1291
+ return this.phases.map((phase) => ({ ...phase }));
1292
+ }
1293
+
1294
+ override render(width: number): string[] {
1295
+ const frameWidth = Math.max(20, width);
1296
+ const rows = this.phases.map((phase) => {
1297
+ const left = `${phase.label} ${phase.count} ${phase.count === 1 ? "action" : "actions"}`;
1298
+ const right = `success · ${formatElapsed(phase.durationMs)}`;
1299
+ const contentWidth = Math.max(1, frameWidth - 2);
1300
+ const leftWidth = Math.max(1, contentWidth - visibleWidth(right) - 1);
1301
+ const leftText = truncateToWidth(left, leftWidth, "");
1302
+ const gap = Math.max(1, contentWidth - visibleWidth(leftText) - visibleWidth(right));
1303
+ return `${theme.fg("toolSuccess", leftText)}${" ".repeat(gap)}${theme.fg("toolSuccess", right)}`;
1304
+ });
1305
+
1306
+ return ["", ...style().border("minimal").borderColor((text) => theme.fg("toolSuccess", text)).render(rows, frameWidth)];
1307
+ }
1308
+ }
@@ -1,5 +1,7 @@
1
1
  import assert from "node:assert/strict";
2
2
  import test from "node:test";
3
+ import { Container } from "@gsd/pi-tui";
4
+ import stripAnsi from "strip-ansi";
3
5
 
4
6
  import { findLatestPinnableText, handleAgentEvent } from "./chat-controller.js";
5
7
  import { initTheme } from "../theme/theme.js";
@@ -111,3 +113,57 @@ test("handleAgentEvent: agent_start clears stale adaptive blocking error", async
111
113
  assert.equal(cleared, true);
112
114
  assert.equal(requestedRender, true);
113
115
  });
116
+
117
+ test("handleAgentEvent: standalone completed tool events roll up incrementally", async () => {
118
+ initTheme("dark", false);
119
+ const chatContainer = new Container();
120
+ let renderCount = 0;
121
+ const host = {
122
+ isInitialized: true,
123
+ footer: { invalidate() {} },
124
+ settingsManager: {
125
+ getTimestampFormat() {
126
+ return "date-time-iso";
127
+ },
128
+ getShowImages() {
129
+ return false;
130
+ },
131
+ },
132
+ getRegisteredToolDefinition() {
133
+ return undefined;
134
+ },
135
+ chatContainer,
136
+ pendingTools: new Map(),
137
+ ui: {
138
+ requestRender() {
139
+ renderCount++;
140
+ },
141
+ },
142
+ } as any;
143
+
144
+ for (const [toolCallId, toolName] of [
145
+ ["read-1", "read"],
146
+ ["read-2", "read"],
147
+ ["edit-1", "edit"],
148
+ ] as const) {
149
+ await handleAgentEvent(host, {
150
+ type: "tool_execution_start",
151
+ toolCallId,
152
+ toolName,
153
+ args: { path: `/tmp/${toolCallId}.txt` },
154
+ } as any);
155
+ await handleAgentEvent(host, {
156
+ type: "tool_execution_end",
157
+ toolCallId,
158
+ toolName,
159
+ result: { content: [], isError: false },
160
+ isError: false,
161
+ } as any);
162
+ }
163
+
164
+ const rendered = stripAnsi(chatContainer.render(100).join("\n"));
165
+ assert.match(rendered, /Context reads 2 actions\s+success · \d+(ms|s)/);
166
+ assert.match(rendered, /File changes 1 action\s+success · \d+(ms|s)/);
167
+ assert.doesNotMatch(rendered, /\bread\s+success ·/);
168
+ assert.ok(renderCount > 0);
169
+ });
@@ -1,9 +1,14 @@
1
+ // GSD-2 Interactive Chat Controller
1
2
  import { Loader, Markdown, Spacer, Text } from "@gsd/pi-tui";
2
3
 
3
4
  import type { InteractiveModeEvent, InteractiveModeStateHost } from "../interactive-mode-state.js";
4
5
  import { theme } from "../theme/theme.js";
5
6
  import { AssistantMessageComponent } from "../components/assistant-message.js";
6
- import { ToolExecutionComponent } from "../components/tool-execution.js";
7
+ import {
8
+ ToolExecutionComponent,
9
+ ToolPhaseSummaryComponent,
10
+ type ToolExecutionPhase,
11
+ } from "../components/tool-execution.js";
7
12
  import { DynamicBorder } from "../components/dynamic-border.js";
8
13
  import { appKey } from "../components/keybinding-hints.js";
9
14
 
@@ -23,7 +28,8 @@ type RenderedSegment =
23
28
  contentType: "text" | "thinking";
24
29
  component: AssistantMessageComponent;
25
30
  }
26
- | { kind: "tool"; contentIndex: number; component: ToolExecutionComponent };
31
+ | { kind: "tool"; contentIndex: number; component: ToolExecutionComponent }
32
+ | { kind: "tool-summary"; component: ToolPhaseSummaryComponent; phases: ToolExecutionPhase[] };
27
33
 
28
34
  let renderedSegments: RenderedSegment[] = [];
29
35
  // When providers reuse one assistant lifecycle across internal sub-turns,
@@ -100,6 +106,84 @@ let pinnedBorder: DynamicBorder | undefined;
100
106
  // Reference to the pinned markdown component below the border
101
107
  let pinnedTextComponent: Markdown | undefined;
102
108
 
109
+ function mergeToolPhases(phases: ToolExecutionPhase[]): ToolExecutionPhase[] {
110
+ const merged: ToolExecutionPhase[] = [];
111
+ for (const phase of phases) {
112
+ const previous = merged[merged.length - 1];
113
+ if (previous?.label === phase.label) {
114
+ previous.count += phase.count;
115
+ previous.durationMs += phase.durationMs;
116
+ } else {
117
+ merged.push({ ...phase });
118
+ }
119
+ }
120
+ return merged;
121
+ }
122
+
123
+ function replaceCompactToolRowsWithPhaseSummary(
124
+ host: InteractiveModeStateHost & { ui: { requestRender: () => void } },
125
+ ): void {
126
+ let changed = false;
127
+ const nextRenderedSegments: RenderedSegment[] = [];
128
+ let rollupRun: Array<{
129
+ seg: Extract<RenderedSegment, { kind: "tool" | "tool-summary" }>;
130
+ phases: ToolExecutionPhase[];
131
+ }> = [];
132
+
133
+ const flushRollupRun = () => {
134
+ const actionCount = rollupRun.reduce(
135
+ (total, item) => total + item.phases.reduce((sum, phase) => sum + phase.count, 0),
136
+ 0,
137
+ );
138
+ if (actionCount < 2) {
139
+ nextRenderedSegments.push(...rollupRun.map((item) => item.seg));
140
+ rollupRun = [];
141
+ return;
142
+ }
143
+
144
+ const firstIndex = Math.max(0, host.chatContainer.children.indexOf(rollupRun[0].seg.component));
145
+ const phases = mergeToolPhases(rollupRun.flatMap((item) => item.phases));
146
+ const summary = new ToolPhaseSummaryComponent(phases);
147
+
148
+ for (const { seg } of rollupRun) {
149
+ host.chatContainer.removeChild(seg.component);
150
+ }
151
+
152
+ host.chatContainer.addChild(summary);
153
+ const summaryIndex = host.chatContainer.children.indexOf(summary);
154
+ if (summaryIndex !== -1 && summaryIndex !== firstIndex) {
155
+ host.chatContainer.children.splice(summaryIndex, 1);
156
+ host.chatContainer.children.splice(firstIndex, 0, summary);
157
+ (host.chatContainer as unknown as { _prevRender: string[] | null })._prevRender = null;
158
+ }
159
+
160
+ changed = true;
161
+ nextRenderedSegments.push({ kind: "tool-summary", component: summary, phases });
162
+ rollupRun = [];
163
+ };
164
+
165
+ for (const seg of renderedSegments) {
166
+ const phase = seg.kind === "tool" ? seg.component.getRollupPhase() : null;
167
+ if (seg.kind === "tool" && phase) {
168
+ rollupRun.push({ seg, phases: [phase] });
169
+ continue;
170
+ }
171
+ if (seg.kind === "tool-summary") {
172
+ rollupRun.push({ seg, phases: seg.component.getPhases() });
173
+ continue;
174
+ }
175
+
176
+ flushRollupRun();
177
+ nextRenderedSegments.push(seg);
178
+ }
179
+ flushRollupRun();
180
+
181
+ if (changed) {
182
+ renderedSegments = nextRenderedSegments;
183
+ host.ui.requestRender();
184
+ }
185
+ }
186
+
103
187
  export async function handleAgentEvent(host: InteractiveModeStateHost & {
104
188
  init: () => Promise<void>;
105
189
  getMarkdownThemeWithSettings: () => any;
@@ -339,6 +423,7 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
339
423
  details: externalToolResult.details,
340
424
  isError: externalToolResult.isError,
341
425
  });
426
+ replaceCompactToolRowsWithPhaseSummary(host);
342
427
  }
343
428
  }
344
429
 
@@ -791,6 +876,7 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
791
876
  for (const [, component] of host.pendingTools.entries()) {
792
877
  component.setArgsComplete();
793
878
  }
879
+ replaceCompactToolRowsWithPhaseSummary(host);
794
880
  }
795
881
  host.streamingComponent = undefined;
796
882
  host.streamingMessage = undefined;
@@ -823,6 +909,7 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
823
909
  component.setExpanded(host.toolOutputExpanded);
824
910
  host.chatContainer.addChild(component);
825
911
  host.pendingTools.set(event.toolCallId, component);
912
+ renderedSegments.push({ kind: "tool", contentIndex: Number.MAX_SAFE_INTEGER, component });
826
913
  host.ui.requestRender();
827
914
  }
828
915
  break;
@@ -840,7 +927,7 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
840
927
  const component = host.pendingTools.get(event.toolCallId);
841
928
  if (component) {
842
929
  component.updateResult({ ...event.result, isError: event.isError });
843
- host.pendingTools.delete(event.toolCallId);
930
+ replaceCompactToolRowsWithPhaseSummary(host);
844
931
  host.ui.requestRender();
845
932
  }
846
933
  break;
@@ -856,6 +943,7 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
856
943
  host.streamingComponent.setShowMetadata(true);
857
944
  host.streamingComponent.updateContent(host.streamingMessage);
858
945
  }
946
+ replaceCompactToolRowsWithPhaseSummary(host);
859
947
  host.streamingComponent = undefined;
860
948
  host.streamingMessage = undefined;
861
949
  renderedSegments = [];
@@ -1378,6 +1378,9 @@ export class InteractiveMode {
1378
1378
  })();
1379
1379
  },
1380
1380
  getSystemPrompt: () => this.session.systemPrompt,
1381
+ setCompactionThresholdOverride: (percent) => {
1382
+ this.session.settingsManager.setCompactionThresholdOverride(percent);
1383
+ },
1381
1384
  });
1382
1385
 
1383
1386
  // Set up the extension shortcut handler on the default editor
@@ -1,3 +1,4 @@
1
+ // GSD2 TUI - Built-in terminal theme definitions.
1
2
  /**
2
3
  * Built-in theme definitions.
3
4
  *
@@ -18,27 +19,30 @@ import type { ThemeJson } from "./theme-schema.js";
18
19
  const dark: ThemeJson = {
19
20
  name: "dark",
20
21
  vars: {
21
- cyan: "#22d3ee",
22
- blue: "#60a5fa",
23
- green: "#34d399",
24
- red: "#fb7185",
25
- yellow: "#facc15",
26
- gray: "#a1a1aa",
27
- dimGray: "#8b93a7",
28
- darkGray: "#4b5563",
29
- accent: "#2dd4bf",
30
- selectedBg: "#1f2a44",
31
- userMsgBg: "#1e2535",
32
- toolPendingBg: "#1f2330",
33
- toolSuccessBg: "#1f3128",
34
- toolErrorBg: "#3a202a",
35
- customMsgBg: "#2a2140",
22
+ cyan: "#5cc8c8",
23
+ blue: "#79b8ff",
24
+ green: "#7ad38b",
25
+ red: "#f27b7b",
26
+ yellow: "#d6b85f",
27
+ gray: "#87949b",
28
+ dimGray: "#657178",
29
+ darkGray: "#303a40",
30
+ line: "#52616a",
31
+ lineSoft: "#303a40",
32
+ textSoft: "#cbd6dc",
33
+ accent: "#5cc8c8",
34
+ selectedBg: "#1d282a",
35
+ userMsgBg: "#202a2f",
36
+ toolPendingBg: "#252721",
37
+ toolSuccessBg: "#1c251f",
38
+ toolErrorBg: "#251e20",
39
+ customMsgBg: "#202a2f",
36
40
  },
37
41
  colors: {
38
42
  accent: "accent",
39
- border: "blue",
40
- borderAccent: "cyan",
41
- borderMuted: "darkGray",
43
+ border: "line",
44
+ borderAccent: "accent",
45
+ borderMuted: "lineSoft",
42
46
  success: "green",
43
47
  error: "red",
44
48
  warning: "yellow",
@@ -49,16 +53,21 @@ const dark: ThemeJson = {
49
53
 
50
54
  selectedBg: "selectedBg",
51
55
  userMessageBg: "userMsgBg",
52
- userMessageText: "gray",
53
- assistantMessageText: "",
56
+ userMessageText: "textSoft",
57
+ assistantMessageText: "textSoft",
54
58
  customMessageBg: "customMsgBg",
55
- customMessageText: "",
56
- customMessageLabel: "#c084fc",
59
+ customMessageText: "textSoft",
60
+ customMessageLabel: "blue",
57
61
  toolPendingBg: "toolPendingBg",
58
62
  toolSuccessBg: "toolSuccessBg",
59
63
  toolErrorBg: "toolErrorBg",
60
- toolTitle: "#8a8a8a",
61
- toolOutput: "#6e6e6e",
64
+ toolTitle: "gray",
65
+ toolOutput: "textSoft",
66
+ surfaceTitle: "accent",
67
+ surfaceAccent: "accent",
68
+ toolRunning: "yellow",
69
+ toolSuccess: "green",
70
+ toolError: "red",
62
71
 
63
72
  mdHeading: "#fbbf24",
64
73
  mdLink: "#93c5fd",
@@ -95,9 +104,9 @@ const dark: ThemeJson = {
95
104
  bashMode: "green",
96
105
  },
97
106
  export: {
98
- pageBg: "#101522",
99
- cardBg: "#171f33",
100
- infoBg: "#3b321d",
107
+ pageBg: "#111416",
108
+ cardBg: "#1e2529",
109
+ infoBg: "#252721",
101
110
  },
102
111
  };
103
112
 
@@ -2,9 +2,14 @@
2
2
 
3
3
  import assert from "node:assert/strict";
4
4
  import { describe, test } from "node:test";
5
+ import stripAnsi from "strip-ansi";
5
6
 
7
+ import { AdaptiveLayoutComponent } from "./components/adaptive-layout.js";
8
+ import { initTheme } from "./theme/theme.js";
6
9
  import { resolveTuiMode } from "./tui-mode.js";
7
10
 
11
+ initTheme("dark", false);
12
+
8
13
  describe("resolveTuiMode", () => {
9
14
  test("explicit overrides beat auto selection", () => {
10
15
  assert.equal(
@@ -38,3 +43,23 @@ describe("resolveTuiMode", () => {
38
43
  assert.equal(resolveTuiMode({ terminalWidth: 100 }), "chat");
39
44
  });
40
45
  });
46
+
47
+ describe("AdaptiveLayoutComponent", () => {
48
+ test("renders workflow layout with prototype rule frames", () => {
49
+ const layout = new AdaptiveLayoutComponent(() => ({
50
+ override: "workflow",
51
+ activeToolCount: 2,
52
+ gsdPhase: "execute-task",
53
+ sessionName: "main",
54
+ cwd: "/Users/example/project",
55
+ }));
56
+
57
+ const plain = layout.render(120).map(stripAnsi);
58
+
59
+ assert.match(plain[0], /^─+/, "workflow layout should start with a rule frame");
60
+ assert.ok(plain.some((line) => line.includes("GSD Command Center")), "workflow title should render");
61
+ assert.ok(plain.some((line) => line.includes("signals")), "inspector title should render");
62
+ assert.ok(plain.some((line) => line.includes("│ Active")), "body rows should keep prototype gutter");
63
+ assert.ok(!plain.some((line) => /[╭╮╰╯]/.test(line)), "workflow layout should not use rounded box corners");
64
+ });
65
+ });
@@ -0,0 +1,14 @@
1
+ import assert from "node:assert/strict";
2
+ import { readFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import test from "node:test";
5
+
6
+ test("MemoryStorage persists sql.js snapshots through the atomic DB snapshot writer", () => {
7
+ const src = readFileSync(
8
+ join(process.cwd(), "packages", "pi-coding-agent", "src", "resources", "extensions", "memory", "storage.ts"),
9
+ "utf-8",
10
+ );
11
+
12
+ assert.match(src, /atomicWriteDbSnapshotSync\(/, "storage must use atomic DB snapshot writes");
13
+ assert.doesNotMatch(src, /writeFileSync\(this\.dbPath/, "direct live DB overwrite is forbidden");
14
+ });
@@ -9,8 +9,9 @@
9
9
 
10
10
  import initSqlJs, { type Database as SqlJsDatabase } from "sql.js";
11
11
  import { randomUUID } from "crypto";
12
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
12
+ import { existsSync, mkdirSync, readFileSync } from "fs";
13
13
  import { dirname } from "path";
14
+ import { atomicWriteDbSnapshotSync } from "../../../core/db-snapshot.js";
14
15
 
15
16
  export interface ThreadRow {
16
17
  thread_id: string;
@@ -74,7 +75,7 @@ export class MemoryStorage {
74
75
 
75
76
  private persist(): void {
76
77
  const data = this.db.export();
77
- writeFileSync(this.dbPath, Buffer.from(data));
78
+ atomicWriteDbSnapshotSync(this.dbPath, data);
78
79
  }
79
80
 
80
81
  private schedulePersist(): void {