gsd-pi 2.74.0-dev.ffbcc03 → 2.75.0-dev.063e5a3

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 (97) 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-worktree.js +2 -0
  4. package/dist/resources/extensions/gsd/bootstrap/provider-error-resume.js +5 -3
  5. package/dist/resources/extensions/gsd/commands/catalog.js +6 -1
  6. package/dist/resources/extensions/gsd/commands/handlers/core.js +5 -1
  7. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +50 -3
  8. package/dist/resources/extensions/gsd/docs/preferences-reference.md +2 -0
  9. package/dist/resources/extensions/gsd/guided-flow.js +7 -5
  10. package/dist/resources/extensions/gsd/preferences-types.js +1 -0
  11. package/dist/resources/extensions/gsd/preferences-validation.js +10 -0
  12. package/dist/resources/extensions/gsd/preferences.js +5 -0
  13. package/dist/resources/extensions/gsd/templates/PREFERENCES.md +1 -0
  14. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  15. package/dist/web/standalone/.next/BUILD_ID +1 -1
  16. package/dist/web/standalone/.next/app-path-routes-manifest.json +6 -6
  17. package/dist/web/standalone/.next/build-manifest.json +2 -2
  18. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  19. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  20. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  21. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  22. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  23. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  24. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  25. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  26. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  27. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  28. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  29. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  30. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  31. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  32. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  33. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  34. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  35. package/dist/web/standalone/.next/server/app/index.html +1 -1
  36. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app-paths-manifest.json +6 -6
  43. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  44. package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
  45. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  46. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  47. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  48. package/package.json +1 -1
  49. package/packages/daemon/package.json +2 -2
  50. package/packages/mcp-server/package.json +2 -2
  51. package/packages/native/package.json +1 -1
  52. package/packages/pi-agent-core/package.json +1 -1
  53. package/packages/pi-ai/package.json +1 -1
  54. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/chat-frame-compaction-tone.test.d.ts +2 -0
  55. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/chat-frame-compaction-tone.test.d.ts.map +1 -0
  56. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/chat-frame-compaction-tone.test.js +61 -0
  57. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/chat-frame-compaction-tone.test.js.map +1 -0
  58. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts +1 -1
  59. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts.map +1 -1
  60. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js +9 -3
  61. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js.map +1 -1
  62. package/packages/pi-coding-agent/dist/modes/interactive/components/compaction-summary-message.d.ts +8 -5
  63. package/packages/pi-coding-agent/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
  64. package/packages/pi-coding-agent/dist/modes/interactive/components/compaction-summary-message.js +27 -13
  65. package/packages/pi-coding-agent/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
  66. package/packages/pi-coding-agent/package.json +1 -1
  67. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/chat-frame-compaction-tone.test.ts +92 -0
  68. package/packages/pi-coding-agent/src/modes/interactive/components/chat-frame.ts +12 -4
  69. package/packages/pi-coding-agent/src/modes/interactive/components/compaction-summary-message.ts +36 -15
  70. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  71. package/packages/pi-tui/dist/tui.d.ts.map +1 -1
  72. package/packages/pi-tui/dist/tui.js +9 -2
  73. package/packages/pi-tui/dist/tui.js.map +1 -1
  74. package/packages/pi-tui/package.json +1 -1
  75. package/packages/pi-tui/src/tui.ts +9 -1
  76. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  77. package/packages/rpc-client/package.json +1 -1
  78. package/pkg/package.json +1 -1
  79. package/src/resources/extensions/gsd/auto/phases.ts +70 -6
  80. package/src/resources/extensions/gsd/auto-model-selection.ts +3 -3
  81. package/src/resources/extensions/gsd/auto-worktree.ts +1 -0
  82. package/src/resources/extensions/gsd/bootstrap/provider-error-resume.ts +5 -3
  83. package/src/resources/extensions/gsd/commands/catalog.ts +6 -1
  84. package/src/resources/extensions/gsd/commands/handlers/core.ts +5 -1
  85. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +57 -3
  86. package/src/resources/extensions/gsd/docs/preferences-reference.md +2 -0
  87. package/src/resources/extensions/gsd/guided-flow.ts +3 -1
  88. package/src/resources/extensions/gsd/preferences-types.ts +6 -0
  89. package/src/resources/extensions/gsd/preferences-validation.ts +10 -0
  90. package/src/resources/extensions/gsd/preferences.ts +6 -0
  91. package/src/resources/extensions/gsd/templates/PREFERENCES.md +1 -0
  92. package/src/resources/extensions/gsd/tests/auto-warning-noise-regression.test.ts +117 -0
  93. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +3 -3
  94. package/src/resources/extensions/gsd/tests/preferences.test.ts +145 -0
  95. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +57 -2
  96. /package/dist/web/standalone/.next/static/{kn6xzWKYnogsxp2b6RpDD → j7IBD35UgrL2b298GLK3V}/_buildManifest.js +0 -0
  97. /package/dist/web/standalone/.next/static/{kn6xzWKYnogsxp2b6RpDD → j7IBD35UgrL2b298GLK3V}/_ssgManifest.js +0 -0
@@ -1,32 +1,34 @@
1
- import { Box, Markdown, Spacer, Text } from "@gsd/pi-tui";
1
+ import { Container, Markdown, Text } from "@gsd/pi-tui";
2
2
  import { getMarkdownTheme, theme } from "../theme/theme.js";
3
+ import { renderChatFrame } from "./chat-frame.js";
3
4
  import { editorKey } from "./keybinding-hints.js";
4
5
  /**
5
- * Component that renders a compaction message with collapsed/expanded state.
6
- * Uses same background color as custom messages for visual consistency.
6
+ * Renders a compaction notice in the shared chat-frame style (top rule,
7
+ * `• compaction` header, `│ ` body margin) with purple border/label so it
8
+ * visually matches the other framed messages (user / assistant / tool
9
+ * execution) while standing apart from the conversation flow.
7
10
  */
8
- export class CompactionSummaryMessageComponent extends Box {
11
+ export class CompactionSummaryMessageComponent extends Container {
9
12
  constructor(message, markdownTheme = getMarkdownTheme()) {
10
- super(1, 1, (t) => theme.bg("customMessageBg", t));
13
+ super();
11
14
  this.expanded = false;
12
15
  this.message = message;
13
16
  this.markdownTheme = markdownTheme;
14
- this.updateDisplay();
17
+ this.rebuild();
15
18
  }
16
19
  setExpanded(expanded) {
17
- this.expanded = expanded;
18
- this.updateDisplay();
20
+ if (this.expanded !== expanded) {
21
+ this.expanded = expanded;
22
+ this.rebuild();
23
+ }
19
24
  }
20
25
  invalidate() {
21
26
  super.invalidate();
22
- this.updateDisplay();
27
+ this.rebuild();
23
28
  }
24
- updateDisplay() {
29
+ rebuild() {
25
30
  this.clear();
26
31
  const tokenStr = this.message.tokensBefore.toLocaleString();
27
- const label = theme.fg("customMessageLabel", theme.bold("[compaction]"));
28
- this.addChild(new Text(label, 0, 0));
29
- this.addChild(new Spacer(1));
30
32
  if (this.expanded) {
31
33
  const header = `**Compacted from ${tokenStr} tokens**\n\n`;
32
34
  this.addChild(new Markdown(header + this.message.summary, 0, 0, this.markdownTheme, {
@@ -39,5 +41,17 @@ export class CompactionSummaryMessageComponent extends Box {
39
41
  theme.fg("customMessageText", " to expand)"), 0, 0));
40
42
  }
41
43
  }
44
+ render(width) {
45
+ const frameWidth = Math.max(20, width);
46
+ const contentWidth = Math.max(1, frameWidth - 4);
47
+ const lines = super.render(contentWidth);
48
+ const framed = renderChatFrame(lines, frameWidth, {
49
+ label: "compaction",
50
+ tone: "compaction",
51
+ timestampFormat: "date-time-iso",
52
+ showTimestamp: false,
53
+ });
54
+ return framed.length > 0 ? ["", ...framed] : framed;
55
+ }
42
56
  }
43
57
  //# sourceMappingURL=compaction-summary-message.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"compaction-summary-message.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/compaction-summary-message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAsB,MAAM,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAE9E,OAAO,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD;;;GAGG;AACH,MAAM,OAAO,iCAAkC,SAAQ,GAAG;IAKzD,YAAY,OAAiC,EAAE,gBAA+B,gBAAgB,EAAE;QAC/F,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,iBAAiB,EAAE,CAAC,CAAC,CAAC,CAAC;QAL5C,aAAQ,GAAG,KAAK,CAAC;QAMxB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;QACnC,IAAI,CAAC,aAAa,EAAE,CAAC;IACtB,CAAC;IAED,WAAW,CAAC,QAAiB;QAC5B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,aAAa,EAAE,CAAC;IACtB,CAAC;IAEQ,UAAU;QAClB,KAAK,CAAC,UAAU,EAAE,CAAC;QACnB,IAAI,CAAC,aAAa,EAAE,CAAC;IACtB,CAAC;IAEO,aAAa;QACpB,IAAI,CAAC,KAAK,EAAE,CAAC;QAEb,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,cAAc,EAAE,CAAC;QAC5D,MAAM,KAAK,GAAG,KAAK,CAAC,EAAE,CAAC,oBAAoB,EAAE,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC;QACzE,IAAI,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACrC,IAAI,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QAE7B,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnB,MAAM,MAAM,GAAG,oBAAoB,QAAQ,eAAe,CAAC;YAC3D,IAAI,CAAC,QAAQ,CACZ,IAAI,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,aAAa,EAAE;gBACrE,KAAK,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,mBAAmB,EAAE,IAAI,CAAC;aAC5D,CAAC,CACF,CAAC;QACH,CAAC;aAAM,CAAC;YACP,IAAI,CAAC,QAAQ,CACZ,IAAI,IAAI,CACP,KAAK,CAAC,EAAE,CAAC,mBAAmB,EAAE,kBAAkB,QAAQ,WAAW,CAAC;gBACnE,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,aAAa,CAAC,CAAC;gBACzC,KAAK,CAAC,EAAE,CAAC,mBAAmB,EAAE,aAAa,CAAC,EAC7C,CAAC,EACD,CAAC,CACD,CACD,CAAC;QACH,CAAC;IACF,CAAC;CACD","sourcesContent":["import { Box, Markdown, type MarkdownTheme, Spacer, Text } from \"@gsd/pi-tui\";\nimport type { CompactionSummaryMessage } from \"../../../core/messages.js\";\nimport { getMarkdownTheme, theme } from \"../theme/theme.js\";\nimport { editorKey } from \"./keybinding-hints.js\";\n\n/**\n * Component that renders a compaction message with collapsed/expanded state.\n * Uses same background color as custom messages for visual consistency.\n */\nexport class CompactionSummaryMessageComponent extends Box {\n\tprivate expanded = false;\n\tprivate message: CompactionSummaryMessage;\n\tprivate markdownTheme: MarkdownTheme;\n\n\tconstructor(message: CompactionSummaryMessage, markdownTheme: MarkdownTheme = getMarkdownTheme()) {\n\t\tsuper(1, 1, (t) => theme.bg(\"customMessageBg\", t));\n\t\tthis.message = message;\n\t\tthis.markdownTheme = markdownTheme;\n\t\tthis.updateDisplay();\n\t}\n\n\tsetExpanded(expanded: boolean): void {\n\t\tthis.expanded = expanded;\n\t\tthis.updateDisplay();\n\t}\n\n\toverride invalidate(): void {\n\t\tsuper.invalidate();\n\t\tthis.updateDisplay();\n\t}\n\n\tprivate updateDisplay(): void {\n\t\tthis.clear();\n\n\t\tconst tokenStr = this.message.tokensBefore.toLocaleString();\n\t\tconst label = theme.fg(\"customMessageLabel\", theme.bold(\"[compaction]\"));\n\t\tthis.addChild(new Text(label, 0, 0));\n\t\tthis.addChild(new Spacer(1));\n\n\t\tif (this.expanded) {\n\t\t\tconst header = `**Compacted from ${tokenStr} tokens**\\n\\n`;\n\t\t\tthis.addChild(\n\t\t\t\tnew Markdown(header + this.message.summary, 0, 0, this.markdownTheme, {\n\t\t\t\t\tcolor: (text: string) => theme.fg(\"customMessageText\", text),\n\t\t\t\t}),\n\t\t\t);\n\t\t} else {\n\t\t\tthis.addChild(\n\t\t\t\tnew Text(\n\t\t\t\t\ttheme.fg(\"customMessageText\", `Compacted from ${tokenStr} tokens (`) +\n\t\t\t\t\t\ttheme.fg(\"dim\", editorKey(\"expandTools\")) +\n\t\t\t\t\t\ttheme.fg(\"customMessageText\", \" to expand)\"),\n\t\t\t\t\t0,\n\t\t\t\t\t0,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"compaction-summary-message.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/compaction-summary-message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAsB,IAAI,EAAE,MAAM,aAAa,CAAC;AAE5E,OAAO,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD;;;;;GAKG;AACH,MAAM,OAAO,iCAAkC,SAAQ,SAAS;IAK/D,YACC,OAAiC,EACjC,gBAA+B,gBAAgB,EAAE;QAEjD,KAAK,EAAE,CAAC;QARD,aAAQ,GAAG,KAAK,CAAC;QASxB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;QACnC,IAAI,CAAC,OAAO,EAAE,CAAC;IAChB,CAAC;IAED,WAAW,CAAC,QAAiB;QAC5B,IAAI,IAAI,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;YAChC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;YACzB,IAAI,CAAC,OAAO,EAAE,CAAC;QAChB,CAAC;IACF,CAAC;IAEQ,UAAU;QAClB,KAAK,CAAC,UAAU,EAAE,CAAC;QACnB,IAAI,CAAC,OAAO,EAAE,CAAC;IAChB,CAAC;IAEO,OAAO;QACd,IAAI,CAAC,KAAK,EAAE,CAAC;QAEb,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,cAAc,EAAE,CAAC;QAE5D,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnB,MAAM,MAAM,GAAG,oBAAoB,QAAQ,eAAe,CAAC;YAC3D,IAAI,CAAC,QAAQ,CACZ,IAAI,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,aAAa,EAAE;gBACrE,KAAK,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,mBAAmB,EAAE,IAAI,CAAC;aAC5D,CAAC,CACF,CAAC;QACH,CAAC;aAAM,CAAC;YACP,IAAI,CAAC,QAAQ,CACZ,IAAI,IAAI,CACP,KAAK,CAAC,EAAE,CACP,mBAAmB,EACnB,kBAAkB,QAAQ,WAAW,CACrC;gBACA,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,aAAa,CAAC,CAAC;gBACzC,KAAK,CAAC,EAAE,CAAC,mBAAmB,EAAE,aAAa,CAAC,EAC7C,CAAC,EACD,CAAC,CACD,CACD,CAAC;QACH,CAAC;IACF,CAAC;IAEQ,MAAM,CAAC,KAAa;QAC5B,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;QACvC,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,GAAG,CAAC,CAAC,CAAC;QACjD,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;QACzC,MAAM,MAAM,GAAG,eAAe,CAAC,KAAK,EAAE,UAAU,EAAE;YACjD,KAAK,EAAE,YAAY;YACnB,IAAI,EAAE,YAAY;YAClB,eAAe,EAAE,eAAe;YAChC,aAAa,EAAE,KAAK;SACpB,CAAC,CAAC;QACH,OAAO,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IACrD,CAAC;CACD","sourcesContent":["import { Container, Markdown, type MarkdownTheme, Text } from \"@gsd/pi-tui\";\nimport type { CompactionSummaryMessage } from \"../../../core/messages.js\";\nimport { getMarkdownTheme, theme } from \"../theme/theme.js\";\nimport { renderChatFrame } from \"./chat-frame.js\";\nimport { editorKey } from \"./keybinding-hints.js\";\n\n/**\n * Renders a compaction notice in the shared chat-frame style (top rule,\n * `• compaction` header, `│ ` body margin) with purple border/label so it\n * visually matches the other framed messages (user / assistant / tool\n * execution) while standing apart from the conversation flow.\n */\nexport class CompactionSummaryMessageComponent extends Container {\n\tprivate expanded = false;\n\tprivate message: CompactionSummaryMessage;\n\tprivate markdownTheme: MarkdownTheme;\n\n\tconstructor(\n\t\tmessage: CompactionSummaryMessage,\n\t\tmarkdownTheme: MarkdownTheme = getMarkdownTheme(),\n\t) {\n\t\tsuper();\n\t\tthis.message = message;\n\t\tthis.markdownTheme = markdownTheme;\n\t\tthis.rebuild();\n\t}\n\n\tsetExpanded(expanded: boolean): void {\n\t\tif (this.expanded !== expanded) {\n\t\t\tthis.expanded = expanded;\n\t\t\tthis.rebuild();\n\t\t}\n\t}\n\n\toverride invalidate(): void {\n\t\tsuper.invalidate();\n\t\tthis.rebuild();\n\t}\n\n\tprivate rebuild(): void {\n\t\tthis.clear();\n\n\t\tconst tokenStr = this.message.tokensBefore.toLocaleString();\n\n\t\tif (this.expanded) {\n\t\t\tconst header = `**Compacted from ${tokenStr} tokens**\\n\\n`;\n\t\t\tthis.addChild(\n\t\t\t\tnew Markdown(header + this.message.summary, 0, 0, this.markdownTheme, {\n\t\t\t\t\tcolor: (text: string) => theme.fg(\"customMessageText\", text),\n\t\t\t\t}),\n\t\t\t);\n\t\t} else {\n\t\t\tthis.addChild(\n\t\t\t\tnew Text(\n\t\t\t\t\ttheme.fg(\n\t\t\t\t\t\t\"customMessageText\",\n\t\t\t\t\t\t`Compacted from ${tokenStr} tokens (`,\n\t\t\t\t\t) +\n\t\t\t\t\t\ttheme.fg(\"dim\", editorKey(\"expandTools\")) +\n\t\t\t\t\t\ttheme.fg(\"customMessageText\", \" to expand)\"),\n\t\t\t\t\t0,\n\t\t\t\t\t0,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t}\n\n\toverride render(width: number): string[] {\n\t\tconst frameWidth = Math.max(20, width);\n\t\tconst contentWidth = Math.max(1, frameWidth - 4);\n\t\tconst lines = super.render(contentWidth);\n\t\tconst framed = renderChatFrame(lines, frameWidth, {\n\t\t\tlabel: \"compaction\",\n\t\t\ttone: \"compaction\",\n\t\t\ttimestampFormat: \"date-time-iso\",\n\t\t\tshowTimestamp: false,\n\t\t});\n\t\treturn framed.length > 0 ? [\"\", ...framed] : framed;\n\t}\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gsd/pi-coding-agent",
3
- "version": "2.74.0",
3
+ "version": "2.75.0",
4
4
  "description": "Coding agent CLI (vendored from pi-mono)",
5
5
  "type": "module",
6
6
  "piConfig": {
@@ -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
+ });
@@ -2,7 +2,7 @@ import { truncateToWidth, visibleWidth } from "@gsd/pi-tui";
2
2
  import { theme } from "../theme/theme.js";
3
3
  import { formatTimestamp, type TimestampFormat } from "./timestamp.js";
4
4
 
5
- type FrameTone = "assistant" | "user";
5
+ type FrameTone = "assistant" | "user" | "compaction";
6
6
 
7
7
  function trimOuterBlankLines(lines: string[]): string[] {
8
8
  let start = 0;
@@ -25,8 +25,14 @@ export function renderChatFrame(
25
25
  ): string[] {
26
26
  const outerWidth = Math.max(20, width);
27
27
  const contentWidth = Math.max(1, outerWidth - 2); // "│ " + content
28
- const borderColor = opts.tone === "user" ? "borderAccent" : "border";
29
- const borderMuted = opts.tone === "user" ? "borderMuted" : "borderMuted";
28
+ const borderColor =
29
+ opts.tone === "user"
30
+ ? "borderAccent"
31
+ : opts.tone === "compaction"
32
+ ? "customMessageLabel"
33
+ : "border";
34
+ const borderMuted =
35
+ opts.tone === "compaction" ? "customMessageLabel" : "borderMuted";
30
36
  const border = (s: string) => theme.fg(borderColor, s);
31
37
  const leftRaw = `• ${opts.label}`;
32
38
  const rightRaw =
@@ -41,7 +47,9 @@ export function renderChatFrame(
41
47
  const leftStyled =
42
48
  opts.tone === "user"
43
49
  ? theme.fg("accent", theme.bold(left))
44
- : theme.fg("muted", theme.bold(left));
50
+ : opts.tone === "compaction"
51
+ ? theme.fg("customMessageLabel", theme.bold(left))
52
+ : theme.fg("muted", theme.bold(left));
45
53
  const rightStyled = rightRaw ? theme.fg("dim", rightRaw) : "";
46
54
  const gap =
47
55
  rightRaw.length > 0
@@ -1,41 +1,46 @@
1
- import { Box, Markdown, type MarkdownTheme, Spacer, Text } from "@gsd/pi-tui";
1
+ import { Container, Markdown, type MarkdownTheme, Text } from "@gsd/pi-tui";
2
2
  import type { CompactionSummaryMessage } from "../../../core/messages.js";
3
3
  import { getMarkdownTheme, theme } from "../theme/theme.js";
4
+ import { renderChatFrame } from "./chat-frame.js";
4
5
  import { editorKey } from "./keybinding-hints.js";
5
6
 
6
7
  /**
7
- * Component that renders a compaction message with collapsed/expanded state.
8
- * Uses same background color as custom messages for visual consistency.
8
+ * Renders a compaction notice in the shared chat-frame style (top rule,
9
+ * `• compaction` header, `│ ` body margin) with purple border/label so it
10
+ * visually matches the other framed messages (user / assistant / tool
11
+ * execution) while standing apart from the conversation flow.
9
12
  */
10
- export class CompactionSummaryMessageComponent extends Box {
13
+ export class CompactionSummaryMessageComponent extends Container {
11
14
  private expanded = false;
12
15
  private message: CompactionSummaryMessage;
13
16
  private markdownTheme: MarkdownTheme;
14
17
 
15
- constructor(message: CompactionSummaryMessage, markdownTheme: MarkdownTheme = getMarkdownTheme()) {
16
- super(1, 1, (t) => theme.bg("customMessageBg", t));
18
+ constructor(
19
+ message: CompactionSummaryMessage,
20
+ markdownTheme: MarkdownTheme = getMarkdownTheme(),
21
+ ) {
22
+ super();
17
23
  this.message = message;
18
24
  this.markdownTheme = markdownTheme;
19
- this.updateDisplay();
25
+ this.rebuild();
20
26
  }
21
27
 
22
28
  setExpanded(expanded: boolean): void {
23
- this.expanded = expanded;
24
- this.updateDisplay();
29
+ if (this.expanded !== expanded) {
30
+ this.expanded = expanded;
31
+ this.rebuild();
32
+ }
25
33
  }
26
34
 
27
35
  override invalidate(): void {
28
36
  super.invalidate();
29
- this.updateDisplay();
37
+ this.rebuild();
30
38
  }
31
39
 
32
- private updateDisplay(): void {
40
+ private rebuild(): void {
33
41
  this.clear();
34
42
 
35
43
  const tokenStr = this.message.tokensBefore.toLocaleString();
36
- const label = theme.fg("customMessageLabel", theme.bold("[compaction]"));
37
- this.addChild(new Text(label, 0, 0));
38
- this.addChild(new Spacer(1));
39
44
 
40
45
  if (this.expanded) {
41
46
  const header = `**Compacted from ${tokenStr} tokens**\n\n`;
@@ -47,7 +52,10 @@ export class CompactionSummaryMessageComponent extends Box {
47
52
  } else {
48
53
  this.addChild(
49
54
  new Text(
50
- theme.fg("customMessageText", `Compacted from ${tokenStr} tokens (`) +
55
+ theme.fg(
56
+ "customMessageText",
57
+ `Compacted from ${tokenStr} tokens (`,
58
+ ) +
51
59
  theme.fg("dim", editorKey("expandTools")) +
52
60
  theme.fg("customMessageText", " to expand)"),
53
61
  0,
@@ -56,4 +64,17 @@ export class CompactionSummaryMessageComponent extends Box {
56
64
  );
57
65
  }
58
66
  }
67
+
68
+ override render(width: number): string[] {
69
+ const frameWidth = Math.max(20, width);
70
+ const contentWidth = Math.max(1, frameWidth - 4);
71
+ const lines = super.render(contentWidth);
72
+ const framed = renderChatFrame(lines, frameWidth, {
73
+ label: "compaction",
74
+ tone: "compaction",
75
+ timestampFormat: "date-time-iso",
76
+ showTimestamp: false,
77
+ });
78
+ return framed.length > 0 ? ["", ...framed] : framed;
79
+ }
59
80
  }