lsd-pi 1.3.6 → 1.3.7

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 (83) hide show
  1. package/dist/cli.js +2 -1
  2. package/dist/lsd-settings-manager.d.ts +2 -0
  3. package/dist/lsd-settings-manager.js +5 -0
  4. package/dist/resource-loader.js +33 -3
  5. package/dist/resources/extensions/cache-timer/index.js +3 -2
  6. package/dist/welcome-screen.js +2 -2
  7. package/package.json +1 -1
  8. package/packages/pi-coding-agent/dist/core/settings-manager.collapse-tool-calls.test.d.ts +2 -0
  9. package/packages/pi-coding-agent/dist/core/settings-manager.collapse-tool-calls.test.d.ts.map +1 -0
  10. package/packages/pi-coding-agent/dist/core/settings-manager.collapse-tool-calls.test.js +35 -0
  11. package/packages/pi-coding-agent/dist/core/settings-manager.collapse-tool-calls.test.js.map +1 -0
  12. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +6 -0
  13. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  14. package/packages/pi-coding-agent/dist/core/settings-manager.js +12 -0
  15. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  16. package/packages/pi-coding-agent/dist/core/tools/edit-diff.d.ts +5 -0
  17. package/packages/pi-coding-agent/dist/core/tools/edit-diff.d.ts.map +1 -1
  18. package/packages/pi-coding-agent/dist/core/tools/edit-diff.js +21 -0
  19. package/packages/pi-coding-agent/dist/core/tools/edit-diff.js.map +1 -1
  20. package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js +16 -1
  21. package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js.map +1 -1
  22. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.js +12 -4
  23. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.js.map +1 -1
  24. package/packages/pi-coding-agent/dist/modes/interactive/components/diff.d.ts +7 -5
  25. package/packages/pi-coding-agent/dist/modes/interactive/components/diff.d.ts.map +1 -1
  26. package/packages/pi-coding-agent/dist/modes/interactive/components/diff.js +86 -28
  27. package/packages/pi-coding-agent/dist/modes/interactive/components/diff.js.map +1 -1
  28. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts +2 -0
  29. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
  30. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +16 -10
  31. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
  32. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +4 -0
  33. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  34. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +26 -4
  35. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
  36. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +14 -1
  37. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  38. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +111 -12
  39. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  40. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.d.ts +1 -0
  41. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.d.ts.map +1 -1
  42. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.js +47 -3
  43. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.js.map +1 -1
  44. package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.js +137 -6
  45. package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.js.map +1 -1
  46. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  47. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +40 -14
  48. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  49. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js +1 -1
  50. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js.map +1 -1
  51. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +1 -0
  52. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
  53. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
  54. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +5 -1
  55. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  56. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +70 -25
  57. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  58. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +4 -4
  59. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -1
  60. package/packages/pi-coding-agent/package.json +1 -1
  61. package/packages/pi-coding-agent/src/core/settings-manager.collapse-tool-calls.test.ts +46 -0
  62. package/packages/pi-coding-agent/src/core/settings-manager.ts +18 -0
  63. package/packages/pi-coding-agent/src/core/tools/edit-diff.test.ts +20 -0
  64. package/packages/pi-coding-agent/src/core/tools/edit-diff.ts +26 -0
  65. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-summary-line.test.ts +14 -4
  66. package/packages/pi-coding-agent/src/modes/interactive/components/diff.ts +105 -28
  67. package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +13 -6
  68. package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +31 -4
  69. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +119 -13
  70. package/packages/pi-coding-agent/src/modes/interactive/components/tool-summary-line.ts +59 -3
  71. package/packages/pi-coding-agent/src/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.ts +174 -6
  72. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +50 -14
  73. package/packages/pi-coding-agent/src/modes/interactive/controllers/extension-ui-controller.ts +1 -1
  74. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +1 -0
  75. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +73 -25
  76. package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +4 -4
  77. package/packages/pi-tui/dist/components/editor.js +3 -3
  78. package/packages/pi-tui/dist/components/editor.js.map +1 -1
  79. package/packages/pi-tui/src/components/editor.ts +3 -3
  80. package/pkg/dist/modes/interactive/theme/themes.js +4 -4
  81. package/pkg/dist/modes/interactive/theme/themes.js.map +1 -1
  82. package/pkg/package.json +1 -1
  83. package/src/resources/extensions/cache-timer/index.ts +3 -2
@@ -69,8 +69,8 @@ const dark = {
69
69
  mdQuoteBorder: "gray",
70
70
  mdHr: "gray",
71
71
  mdListBullet: "accent",
72
- toolDiffAdded: "green",
73
- toolDiffRemoved: "red",
72
+ toolDiffAdded: "#4ade80",
73
+ toolDiffRemoved: "#fb7185",
74
74
  toolDiffContext: "gray",
75
75
  syntaxComment: "#6A9955",
76
76
  syntaxKeyword: "#569CD6",
@@ -157,8 +157,8 @@ const light = {
157
157
  mdQuoteBorder: "mediumGray",
158
158
  mdHr: "mediumGray",
159
159
  mdListBullet: "green",
160
- toolDiffAdded: "green",
161
- toolDiffRemoved: "red",
160
+ toolDiffAdded: "#15803d",
161
+ toolDiffRemoved: "#b91c1c",
162
162
  toolDiffContext: "mediumGray",
163
163
  syntaxComment: "#008000",
164
164
  syntaxKeyword: "#0000FF",
@@ -1 +1 @@
1
- {"version":3,"file":"themes.js","sourceRoot":"","sources":["../../../../src/modes/interactive/theme/themes.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Built-in theme definitions.\n *\n * Each theme is a self-contained record of color values. Variable references\n * (e.g. \"accent\") are resolved against the `vars` map at load time by the\n * theme engine in theme.ts.\n *\n * To add a new built-in theme, add an entry to `builtinThemes` below.\n */\n\n// Re-use the ThemeJson type from the schema defined in theme.ts.\n// We import only the type to avoid circular runtime dependencies.\nimport type { ThemeJson } from \"./theme.js\";\n\n// ---------------------------------------------------------------------------\n// Dark theme\n// ---------------------------------------------------------------------------\n\nconst dark: ThemeJson = {\n\tname: \"dark\",\n\tvars: {\n\t\tcyan: \"#4a8cf7\",\n\t\tblue: \"#4a8cf7\",\n\t\tgreen: \"#b5bd68\",\n\t\tred: \"#cc6666\",\n\t\tyellow: \"#facc15\",\n\t\tviolet: \"#a78bfa\",\n\t\tgray: \"#bec8d6\",\n\t\tdimGray: \"#8793a3\",\n\t\tdarkGray: \"#505050\",\n\t\taccent: \"#60a5fa\",\n\t\tblueMuted: \"#1e3a8a\",\n\t\tblueLow: \"#2563eb\",\n\t\tblueMedium: \"#4a8cf7\",\n\t\tblueHigh: \"#60a5fa\",\n\t\tblueXhigh: \"#93c5fd\",\n\t\tselectedBg: \"#323640\",\n\t\tuserMsgBg: \"#272727\",\n\t\ttoolPendingBg: \"#1e2230\",\n\t\ttoolSuccessBg: \"#1a2330\",\n\t\ttoolErrorBg: \"#2a1e30\",\n\t\tcustomMsgBg: \"#2d2838\",\n\t},\n\tcolors: {\n\t\taccent: \"accent\",\n\t\tborder: \"blue\",\n\t\tborderAccent: \"cyan\",\n\t\tborderMuted: \"blue\",\n\t\tsuccess: \"green\",\n\t\terror: \"red\",\n\t\twarning: \"yellow\",\n\t\tviolet: \"violet\",\n\t\tmuted: \"gray\",\n\t\tdim: \"dimGray\",\n\t\ttext: \"\",\n\t\tthinkingText: \"gray\",\n\n\t\tselectedBg: \"selectedBg\",\n\t\tuserMessageBg: \"userMsgBg\",\n\t\tuserMessageText: \"\",\n\t\tcustomMessageBg: \"customMsgBg\",\n\t\tcustomMessageText: \"\",\n\t\tcustomMessageLabel: \"#9575cd\",\n\t\ttoolPendingBg: \"toolPendingBg\",\n\t\ttoolSuccessBg: \"toolSuccessBg\",\n\t\ttoolErrorBg: \"toolErrorBg\",\n\t\ttoolTitle: \"\",\n\t\ttoolOutput: \"gray\",\n\n\t\tmdHeading: \"#f0c674\",\n\t\tmdLink: \"#5a8aaa\",\n\t\tmdLinkUrl: \"dimGray\",\n\t\tmdCode: \"accent\",\n\t\tmdCodeBlock: \"green\",\n\t\tmdCodeBlockBorder: \"gray\",\n\t\tmdQuote: \"gray\",\n\t\tmdQuoteBorder: \"gray\",\n\t\tmdHr: \"gray\",\n\t\tmdListBullet: \"accent\",\n\n\t\ttoolDiffAdded: \"green\",\n\t\ttoolDiffRemoved: \"red\",\n\t\ttoolDiffContext: \"gray\",\n\n\t\tsyntaxComment: \"#6A9955\",\n\t\tsyntaxKeyword: \"#569CD6\",\n\t\tsyntaxFunction: \"#DCDCAA\",\n\t\tsyntaxVariable: \"#9CDCFE\",\n\t\tsyntaxString: \"#CE9178\",\n\t\tsyntaxNumber: \"#B5CEA8\",\n\t\tsyntaxType: \"#4EC9B0\",\n\t\tsyntaxOperator: \"#D4D4D4\",\n\t\tsyntaxPunctuation: \"#D4D4D4\",\n\n\t\tthinkingOff: \"blueMuted\",\n\t\tthinkingMinimal: \"blueLow\",\n\t\tthinkingLow: \"blueMedium\",\n\t\tthinkingMedium: \"blueHigh\",\n\t\tthinkingHigh: \"blueXhigh\",\n\t\tthinkingXhigh: \"cyan\",\n\n\t\tbashMode: \"accent\",\n\t},\n\texport: {\n\t\tpageBg: \"#18181e\",\n\t\tcardBg: \"#1e1e24\",\n\t\tinfoBg: \"#3c3728\",\n\t},\n};\n\n// ---------------------------------------------------------------------------\n// Light theme\n// ---------------------------------------------------------------------------\n\nconst light: ThemeJson = {\n\tname: \"light\",\n\tvars: {\n\t\tteal: \"#3b82f6\",\n\t\tblue: \"#547da7\",\n\t\tgreen: \"#588458\",\n\t\tred: \"#aa5555\",\n\t\tyellow: \"#eab308\",\n\t\twarning: \"#7a5a00\",\n\t\tviolet: \"#8b5cf6\",\n\t\tmediumGray: \"#6c6c6c\",\n\t\tdimGray: \"#767676\",\n\t\tlightGray: \"#b0b0b0\",\n\t\tblueMuted: \"#6b8fb8\",\n\t\tblueLow: \"#547da7\",\n\t\tblueMedium: \"#3b82f6\",\n\t\tblueHigh: \"#2563eb\",\n\t\tblueXhigh: \"#1d4ed8\",\n\t\tselectedBg: \"#d0d0e0\",\n\t\tuserMsgBg: \"#e8e8e8\",\n\t\ttoolPendingBg: \"#e8eaf0\",\n\t\ttoolSuccessBg: \"#e8f0f0\",\n\t\ttoolErrorBg: \"#f0e8ee\",\n\t\tcustomMsgBg: \"#ede7f6\",\n\t},\n\tcolors: {\n\t\taccent: \"teal\",\n\t\tborder: \"blue\",\n\t\tborderAccent: \"teal\",\n\t\tborderMuted: \"lightGray\",\n\t\tsuccess: \"green\",\n\t\terror: \"red\",\n\t\twarning: \"warning\",\n\t\tviolet: \"violet\",\n\t\tmuted: \"mediumGray\",\n\t\tdim: \"dimGray\",\n\t\ttext: \"\",\n\t\tthinkingText: \"mediumGray\",\n\n\t\tselectedBg: \"selectedBg\",\n\t\tuserMessageBg: \"userMsgBg\",\n\t\tuserMessageText: \"\",\n\t\tcustomMessageBg: \"customMsgBg\",\n\t\tcustomMessageText: \"\",\n\t\tcustomMessageLabel: \"#7e57c2\",\n\t\ttoolPendingBg: \"toolPendingBg\",\n\t\ttoolSuccessBg: \"toolSuccessBg\",\n\t\ttoolErrorBg: \"toolErrorBg\",\n\t\ttoolTitle: \"\",\n\t\ttoolOutput: \"mediumGray\",\n\n\t\tmdHeading: \"yellow\",\n\t\tmdLink: \"blue\",\n\t\tmdLinkUrl: \"dimGray\",\n\t\tmdCode: \"teal\",\n\t\tmdCodeBlock: \"green\",\n\t\tmdCodeBlockBorder: \"mediumGray\",\n\t\tmdQuote: \"mediumGray\",\n\t\tmdQuoteBorder: \"mediumGray\",\n\t\tmdHr: \"mediumGray\",\n\t\tmdListBullet: \"green\",\n\n\t\ttoolDiffAdded: \"green\",\n\t\ttoolDiffRemoved: \"red\",\n\t\ttoolDiffContext: \"mediumGray\",\n\n\t\tsyntaxComment: \"#008000\",\n\t\tsyntaxKeyword: \"#0000FF\",\n\t\tsyntaxFunction: \"#795E26\",\n\t\tsyntaxVariable: \"#001080\",\n\t\tsyntaxString: \"#A31515\",\n\t\tsyntaxNumber: \"#098658\",\n\t\tsyntaxType: \"#267F99\",\n\t\tsyntaxOperator: \"#000000\",\n\t\tsyntaxPunctuation: \"#000000\",\n\n\t\tthinkingOff: \"blueMuted\",\n\t\tthinkingMinimal: \"blueLow\",\n\t\tthinkingLow: \"blueMedium\",\n\t\tthinkingMedium: \"teal\",\n\t\tthinkingHigh: \"blueHigh\",\n\t\tthinkingXhigh: \"blueXhigh\",\n\n\t\tbashMode: \"accent\",\n\t},\n\texport: {\n\t\tpageBg: \"#f8f8f8\",\n\t\tcardBg: \"#ffffff\",\n\t\tinfoBg: \"#fffae6\",\n\t},\n};\n\n// ---------------------------------------------------------------------------\n// Export\n// ---------------------------------------------------------------------------\n\nexport const builtinThemes: Record<string, ThemeJson> = { dark, light };\n"]}
1
+ {"version":3,"file":"themes.js","sourceRoot":"","sources":["../../../../src/modes/interactive/theme/themes.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Built-in theme definitions.\n *\n * Each theme is a self-contained record of color values. Variable references\n * (e.g. \"accent\") are resolved against the `vars` map at load time by the\n * theme engine in theme.ts.\n *\n * To add a new built-in theme, add an entry to `builtinThemes` below.\n */\n\n// Re-use the ThemeJson type from the schema defined in theme.ts.\n// We import only the type to avoid circular runtime dependencies.\nimport type { ThemeJson } from \"./theme.js\";\n\n// ---------------------------------------------------------------------------\n// Dark theme\n// ---------------------------------------------------------------------------\n\nconst dark: ThemeJson = {\n\tname: \"dark\",\n\tvars: {\n\t\tcyan: \"#4a8cf7\",\n\t\tblue: \"#4a8cf7\",\n\t\tgreen: \"#b5bd68\",\n\t\tred: \"#cc6666\",\n\t\tyellow: \"#facc15\",\n\t\tviolet: \"#a78bfa\",\n\t\tgray: \"#bec8d6\",\n\t\tdimGray: \"#8793a3\",\n\t\tdarkGray: \"#505050\",\n\t\taccent: \"#60a5fa\",\n\t\tblueMuted: \"#1e3a8a\",\n\t\tblueLow: \"#2563eb\",\n\t\tblueMedium: \"#4a8cf7\",\n\t\tblueHigh: \"#60a5fa\",\n\t\tblueXhigh: \"#93c5fd\",\n\t\tselectedBg: \"#323640\",\n\t\tuserMsgBg: \"#272727\",\n\t\ttoolPendingBg: \"#1e2230\",\n\t\ttoolSuccessBg: \"#1a2330\",\n\t\ttoolErrorBg: \"#2a1e30\",\n\t\tcustomMsgBg: \"#2d2838\",\n\t},\n\tcolors: {\n\t\taccent: \"accent\",\n\t\tborder: \"blue\",\n\t\tborderAccent: \"cyan\",\n\t\tborderMuted: \"blue\",\n\t\tsuccess: \"green\",\n\t\terror: \"red\",\n\t\twarning: \"yellow\",\n\t\tviolet: \"violet\",\n\t\tmuted: \"gray\",\n\t\tdim: \"dimGray\",\n\t\ttext: \"\",\n\t\tthinkingText: \"gray\",\n\n\t\tselectedBg: \"selectedBg\",\n\t\tuserMessageBg: \"userMsgBg\",\n\t\tuserMessageText: \"\",\n\t\tcustomMessageBg: \"customMsgBg\",\n\t\tcustomMessageText: \"\",\n\t\tcustomMessageLabel: \"#9575cd\",\n\t\ttoolPendingBg: \"toolPendingBg\",\n\t\ttoolSuccessBg: \"toolSuccessBg\",\n\t\ttoolErrorBg: \"toolErrorBg\",\n\t\ttoolTitle: \"\",\n\t\ttoolOutput: \"gray\",\n\n\t\tmdHeading: \"#f0c674\",\n\t\tmdLink: \"#5a8aaa\",\n\t\tmdLinkUrl: \"dimGray\",\n\t\tmdCode: \"accent\",\n\t\tmdCodeBlock: \"green\",\n\t\tmdCodeBlockBorder: \"gray\",\n\t\tmdQuote: \"gray\",\n\t\tmdQuoteBorder: \"gray\",\n\t\tmdHr: \"gray\",\n\t\tmdListBullet: \"accent\",\n\n\t\ttoolDiffAdded: \"#4ade80\",\n\t\ttoolDiffRemoved: \"#fb7185\",\n\t\ttoolDiffContext: \"gray\",\n\n\t\tsyntaxComment: \"#6A9955\",\n\t\tsyntaxKeyword: \"#569CD6\",\n\t\tsyntaxFunction: \"#DCDCAA\",\n\t\tsyntaxVariable: \"#9CDCFE\",\n\t\tsyntaxString: \"#CE9178\",\n\t\tsyntaxNumber: \"#B5CEA8\",\n\t\tsyntaxType: \"#4EC9B0\",\n\t\tsyntaxOperator: \"#D4D4D4\",\n\t\tsyntaxPunctuation: \"#D4D4D4\",\n\n\t\tthinkingOff: \"blueMuted\",\n\t\tthinkingMinimal: \"blueLow\",\n\t\tthinkingLow: \"blueMedium\",\n\t\tthinkingMedium: \"blueHigh\",\n\t\tthinkingHigh: \"blueXhigh\",\n\t\tthinkingXhigh: \"cyan\",\n\n\t\tbashMode: \"accent\",\n\t},\n\texport: {\n\t\tpageBg: \"#18181e\",\n\t\tcardBg: \"#1e1e24\",\n\t\tinfoBg: \"#3c3728\",\n\t},\n};\n\n// ---------------------------------------------------------------------------\n// Light theme\n// ---------------------------------------------------------------------------\n\nconst light: ThemeJson = {\n\tname: \"light\",\n\tvars: {\n\t\tteal: \"#3b82f6\",\n\t\tblue: \"#547da7\",\n\t\tgreen: \"#588458\",\n\t\tred: \"#aa5555\",\n\t\tyellow: \"#eab308\",\n\t\twarning: \"#7a5a00\",\n\t\tviolet: \"#8b5cf6\",\n\t\tmediumGray: \"#6c6c6c\",\n\t\tdimGray: \"#767676\",\n\t\tlightGray: \"#b0b0b0\",\n\t\tblueMuted: \"#6b8fb8\",\n\t\tblueLow: \"#547da7\",\n\t\tblueMedium: \"#3b82f6\",\n\t\tblueHigh: \"#2563eb\",\n\t\tblueXhigh: \"#1d4ed8\",\n\t\tselectedBg: \"#d0d0e0\",\n\t\tuserMsgBg: \"#e8e8e8\",\n\t\ttoolPendingBg: \"#e8eaf0\",\n\t\ttoolSuccessBg: \"#e8f0f0\",\n\t\ttoolErrorBg: \"#f0e8ee\",\n\t\tcustomMsgBg: \"#ede7f6\",\n\t},\n\tcolors: {\n\t\taccent: \"teal\",\n\t\tborder: \"blue\",\n\t\tborderAccent: \"teal\",\n\t\tborderMuted: \"lightGray\",\n\t\tsuccess: \"green\",\n\t\terror: \"red\",\n\t\twarning: \"warning\",\n\t\tviolet: \"violet\",\n\t\tmuted: \"mediumGray\",\n\t\tdim: \"dimGray\",\n\t\ttext: \"\",\n\t\tthinkingText: \"mediumGray\",\n\n\t\tselectedBg: \"selectedBg\",\n\t\tuserMessageBg: \"userMsgBg\",\n\t\tuserMessageText: \"\",\n\t\tcustomMessageBg: \"customMsgBg\",\n\t\tcustomMessageText: \"\",\n\t\tcustomMessageLabel: \"#7e57c2\",\n\t\ttoolPendingBg: \"toolPendingBg\",\n\t\ttoolSuccessBg: \"toolSuccessBg\",\n\t\ttoolErrorBg: \"toolErrorBg\",\n\t\ttoolTitle: \"\",\n\t\ttoolOutput: \"mediumGray\",\n\n\t\tmdHeading: \"yellow\",\n\t\tmdLink: \"blue\",\n\t\tmdLinkUrl: \"dimGray\",\n\t\tmdCode: \"teal\",\n\t\tmdCodeBlock: \"green\",\n\t\tmdCodeBlockBorder: \"mediumGray\",\n\t\tmdQuote: \"mediumGray\",\n\t\tmdQuoteBorder: \"mediumGray\",\n\t\tmdHr: \"mediumGray\",\n\t\tmdListBullet: \"green\",\n\n\t\ttoolDiffAdded: \"#15803d\",\n\t\ttoolDiffRemoved: \"#b91c1c\",\n\t\ttoolDiffContext: \"mediumGray\",\n\n\t\tsyntaxComment: \"#008000\",\n\t\tsyntaxKeyword: \"#0000FF\",\n\t\tsyntaxFunction: \"#795E26\",\n\t\tsyntaxVariable: \"#001080\",\n\t\tsyntaxString: \"#A31515\",\n\t\tsyntaxNumber: \"#098658\",\n\t\tsyntaxType: \"#267F99\",\n\t\tsyntaxOperator: \"#000000\",\n\t\tsyntaxPunctuation: \"#000000\",\n\n\t\tthinkingOff: \"blueMuted\",\n\t\tthinkingMinimal: \"blueLow\",\n\t\tthinkingLow: \"blueMedium\",\n\t\tthinkingMedium: \"teal\",\n\t\tthinkingHigh: \"blueHigh\",\n\t\tthinkingXhigh: \"blueXhigh\",\n\n\t\tbashMode: \"accent\",\n\t},\n\texport: {\n\t\tpageBg: \"#f8f8f8\",\n\t\tcardBg: \"#ffffff\",\n\t\tinfoBg: \"#fffae6\",\n\t},\n};\n\n// ---------------------------------------------------------------------------\n// Export\n// ---------------------------------------------------------------------------\n\nexport const builtinThemes: Record<string, ThemeJson> = { dark, light };\n"]}
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gsd/pi-coding-agent",
3
- "version": "1.3.6",
3
+ "version": "1.3.7",
4
4
  "description": "Coding agent CLI (vendored from pi-mono)",
5
5
  "type": "module",
6
6
  "piConfig": {
@@ -0,0 +1,46 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdtempSync, readFileSync, rmSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { describe, it } from "node:test";
6
+
7
+ import { SettingsManager } from "./settings-manager.js";
8
+
9
+ describe("SettingsManager collapseToolCalls", () => {
10
+ it("defaults collapseToolCalls to false", () => {
11
+ const manager = SettingsManager.inMemory();
12
+ assert.equal(manager.getCollapseToolCalls(), false);
13
+ });
14
+
15
+ it("persists collapseToolCalls changes to settings.json", async () => {
16
+ const root = mkdtempSync(join(tmpdir(), "pi-collapse-tool-calls-settings-"));
17
+ const cwd = join(root, "project");
18
+ const agentDir = join(root, "agent");
19
+
20
+ try {
21
+ const manager = SettingsManager.create(cwd, agentDir);
22
+ assert.equal(manager.getCollapseToolCalls(), false);
23
+
24
+ manager.setCollapseToolCalls(true);
25
+ await manager.flush();
26
+
27
+ const rawEnabled = JSON.parse(readFileSync(join(agentDir, "settings.json"), "utf8")) as {
28
+ collapseToolCalls?: boolean;
29
+ };
30
+ assert.equal(rawEnabled.collapseToolCalls, true);
31
+
32
+ const reloaded = SettingsManager.create(cwd, agentDir);
33
+ assert.equal(reloaded.getCollapseToolCalls(), true);
34
+
35
+ reloaded.setCollapseToolCalls(false);
36
+ await reloaded.flush();
37
+
38
+ const rawDisabled = JSON.parse(readFileSync(join(agentDir, "settings.json"), "utf8")) as {
39
+ collapseToolCalls?: boolean;
40
+ };
41
+ assert.equal(rawDisabled.collapseToolCalls, false);
42
+ } finally {
43
+ rmSync(root, { recursive: true, force: true });
44
+ }
45
+ });
46
+ });
@@ -161,6 +161,7 @@ export interface Settings {
161
161
  codexRotate?: boolean; // Enable the bundled codex-rotate extension (default: false)
162
162
  fastMode?: boolean; // Enable OpenAI/Codex fast tier routing (priority service tier where supported)
163
163
  cacheTimer?: boolean; // Show elapsed time since last response in the footer (default: true)
164
+ verboseFooter?: boolean; // Show extra footer usage stats like input/output/cache tokens (default: false)
164
165
  pinLastPrompt?: boolean; // Pin last sent prompt above the editor as a reminder (default: false)
165
166
  doubleEscapeAction?: "fork" | "tree" | "none"; // Action for double-escape with empty editor (default: "tree")
166
167
  treeFilterMode?: "default" | "no-tools" | "user-only" | "labeled-only" | "all"; // Default filter when opening /tree
@@ -181,6 +182,7 @@ export interface Settings {
181
182
  editMode?: "standard" | "hashline"; // Edit tool mode: "standard" (text match) or "hashline" (LINE#ID anchors). Default: "standard"
182
183
  timestampFormat?: "date-time-iso" | "date-time-us"; // Timestamp display format for messages. Default: "date-time-iso"
183
184
  toolOutputMode?: "minimal" | "normal"; // Collapsed tool rendering mode. "minimal" hides previews until expanded.
185
+ collapseToolCalls?: boolean; // default: false — group low-priority tool calls into collapsed summary lines
184
186
  lspAutoInstall?: boolean; // default: false — whether to auto-install missing language servers during onboarding
185
187
  lspInstalledServers?: string[]; // list of server names installed via the onboarding wizard
186
188
  rtk?: boolean; // default: false — enable RTK shell-command compression (requires restart)
@@ -1051,6 +1053,14 @@ export class SettingsManager {
1051
1053
  this.setGlobalSetting("toolOutputMode", mode);
1052
1054
  }
1053
1055
 
1056
+ getCollapseToolCalls(): boolean {
1057
+ return this.settings.collapseToolCalls ?? false;
1058
+ }
1059
+
1060
+ setCollapseToolCalls(enabled: boolean): void {
1061
+ this.setGlobalSetting("collapseToolCalls", enabled);
1062
+ }
1063
+
1054
1064
  getPackages(): PackageSource[] {
1055
1065
  return [...(this.settings.packages ?? [])];
1056
1066
  }
@@ -1205,6 +1215,14 @@ export class SettingsManager {
1205
1215
  this.setGlobalSetting("cacheTimer", enabled);
1206
1216
  }
1207
1217
 
1218
+ getVerboseFooter(): boolean {
1219
+ return this.settings.verboseFooter ?? false;
1220
+ }
1221
+
1222
+ setVerboseFooter(enabled: boolean): void {
1223
+ this.setGlobalSetting("verboseFooter", enabled);
1224
+ }
1225
+
1208
1226
  getPinLastPrompt(): boolean {
1209
1227
  return this.settings.pinLastPrompt ?? false;
1210
1228
  }
@@ -6,6 +6,7 @@ import { describe, it } from "node:test";
6
6
 
7
7
  import {
8
8
  computeEditDiff,
9
+ computeWriteDiff,
9
10
  fuzzyFindText,
10
11
  generateDiffString,
11
12
  normalizeForFuzzyMatch,
@@ -60,6 +61,25 @@ describe("edit-diff", () => {
60
61
  assert.match(result.diff, /CHANGED/);
61
62
  });
62
63
 
64
+ it("computes diffs for write preview against existing files", async (t) => {
65
+ const dir = mkdtempSync(join(tmpdir(), "write-diff-test-"));
66
+ t.after(() => {
67
+ rmSync(dir, { recursive: true, force: true });
68
+ });
69
+
70
+ const file = join(dir, "sample.ts");
71
+ writeFileSync(file, "const title = \"Hello\";\n", "utf-8");
72
+
73
+ const result = await computeWriteDiff(file, "const title = \"Hi\";\n", dir);
74
+
75
+ assert.ok(!("error" in result), "expected a diff result");
76
+ if (!("error" in result)) {
77
+ assert.equal(result.firstChangedLine, 1);
78
+ assert.match(result.diff, /-1 const title = "Hello";/);
79
+ assert.match(result.diff, /\+1 const title = "Hi";/);
80
+ }
81
+ });
82
+
63
83
  it("computes diffs for preview without native helpers", async (t) => {
64
84
  const dir = mkdtempSync(join(tmpdir(), "edit-diff-test-"));
65
85
  t.after(() => {
@@ -329,6 +329,32 @@ function buildLineDiffLinear(oldLines: string[], newLines: string[]): LineDiffOp
329
329
  return ops;
330
330
  }
331
331
 
332
+ /**
333
+ * Compute the diff for a write operation without applying it.
334
+ * Used for preview rendering in the TUI before the tool executes.
335
+ */
336
+ export async function computeWriteDiff(
337
+ path: string,
338
+ newContent: string,
339
+ cwd: string,
340
+ ): Promise<EditDiffResult | EditDiffError> {
341
+ const absolutePath = resolveToCwd(path, cwd);
342
+
343
+ try {
344
+ let oldContent = "";
345
+ try {
346
+ await access(absolutePath, constants.R_OK);
347
+ oldContent = await readFile(absolutePath, "utf-8");
348
+ } catch {
349
+ oldContent = "";
350
+ }
351
+
352
+ return generateDiffString(normalizeToLF(stripBom(oldContent).text), normalizeToLF(newContent));
353
+ } catch (err) {
354
+ return { error: err instanceof Error ? err.message : String(err) };
355
+ }
356
+ }
357
+
332
358
  /**
333
359
  * Compute the diff for an edit operation without applying it.
334
360
  * Used for preview rendering in the TUI before the tool executes.
@@ -8,18 +8,28 @@ import { initTheme } from "../../theme/theme.js";
8
8
  initTheme("dark");
9
9
 
10
10
  describe("ToolSummaryLine", () => {
11
- it("aggregates repeated tools with tool-row style formatting", () => {
11
+ it("renders action-based summaries for grouped identical tools", () => {
12
12
  const summary = new ToolSummaryLine();
13
13
  summary.addTool("read", 600);
14
- summary.addTool("lsp", 250);
15
14
  summary.addTool("read", 150);
16
15
 
17
16
  const rendered = stripAnsi(summary.render(160).join("\n"));
18
- assert.match(rendered, /^ ● collapsed tools /);
19
- assert.ok(rendered.includes("read ×2 · lsp · 1.0s"));
17
+ assert.match(rendered, /^ ● /);
18
+ assert.ok(rendered.includes("reading 2 files · 0.8s"));
19
+ assert.equal(summary.canGroupWith("read"), true);
20
+ assert.equal(summary.canGroupWith("find"), false);
21
+ assert.equal(rendered.includes("collapsed tools"), false);
20
22
  assert.equal(rendered.includes("⎯"), false);
21
23
  });
22
24
 
25
+ it("keeps fallback format for unknown tools", () => {
26
+ const summary = new ToolSummaryLine();
27
+ summary.addTool("custom_tool", 100);
28
+
29
+ const rendered = stripAnsi(summary.render(160).join("\n"));
30
+ assert.ok(rendered.includes("custom_tool · 0.1s"));
31
+ });
32
+
23
33
  it("renders nothing when empty or hidden", () => {
24
34
  const summary = new ToolSummaryLine();
25
35
  assert.deepEqual(summary.render(80), []);
@@ -1,6 +1,15 @@
1
+ import { visibleWidth, wrapTextWithAnsi } from "@gsd/pi-tui";
1
2
  import * as Diff from "diff";
3
+ import chalk from "chalk";
2
4
  import { theme } from "../theme/theme.js";
3
5
 
6
+ const DIFF_BG = {
7
+ addedLine: "#0f2f1a",
8
+ removedLine: "#3a1116",
9
+ addedToken: "#1b5e20",
10
+ removedToken: "#7f1d1d",
11
+ } as const;
12
+
4
13
  /**
5
14
  * Parse diff line to extract prefix, line number, and content.
6
15
  * Format: "+123 content" or "-123 content" or " 123 content" or " ..."
@@ -18,10 +27,52 @@ function replaceTabs(text: string): string {
18
27
  return text.replace(/\t/g, " ");
19
28
  }
20
29
 
30
+ function formatLineNum(raw: string, width: number): string {
31
+ const trimmed = raw.trim();
32
+ if (!trimmed) return "".padStart(width, " ");
33
+ return trimmed.padStart(width, " ");
34
+ }
35
+
36
+ function padToWidth(text: string, width: number): string {
37
+ if (!Number.isFinite(width) || width > 10_000) {
38
+ return text;
39
+ }
40
+ const paddingNeeded = Math.max(0, width - visibleWidth(text));
41
+ return text + " ".repeat(paddingNeeded);
42
+ }
43
+
44
+ function styleRemovedPrefix(prefix: string): string {
45
+ return chalk.hex("#fb7185")(prefix);
46
+ }
47
+
48
+ function styleAddedPrefix(prefix: string): string {
49
+ return chalk.hex("#4ade80")(prefix);
50
+ }
51
+
52
+ function styleRemovedToken(value: string): string {
53
+ return chalk.bgHex(DIFF_BG.removedToken).whiteBright(value);
54
+ }
55
+
56
+ function styleAddedToken(value: string): string {
57
+ return chalk.bgHex(DIFF_BG.addedToken).whiteBright(value);
58
+ }
59
+
60
+ function styleAddedLine(text: string, width: number): string {
61
+ const prefix = text.startsWith("+") ? styleAddedPrefix("+") : "";
62
+ const rest = prefix ? text.slice(1) : text;
63
+ return chalk.bgHex(DIFF_BG.addedLine).whiteBright(padToWidth(`${prefix}${rest}`, width));
64
+ }
65
+
66
+ function styleRemovedLine(text: string, width: number): string {
67
+ const prefix = text.startsWith("-") ? styleRemovedPrefix("-") : "";
68
+ const rest = prefix ? text.slice(1) : text;
69
+ return chalk.bgHex(DIFF_BG.removedLine).whiteBright(padToWidth(`${prefix}${rest}`, width));
70
+ }
71
+
21
72
  /**
22
- * Compute word-level diff and render with inverse on changed parts.
73
+ * Compute word-level diff and render changed tokens with subtle emphasis.
23
74
  * Uses diffWords which groups whitespace with adjacent words for cleaner highlighting.
24
- * Strips leading whitespace from inverse to avoid highlighting indentation.
75
+ * Strips leading whitespace from highlighted token spans.
25
76
  */
26
77
  function renderIntraLineDiff(oldContent: string, newContent: string): { removedLine: string; addedLine: string } {
27
78
  const wordDiff = Diff.diffWords(oldContent, newContent);
@@ -34,7 +85,6 @@ function renderIntraLineDiff(oldContent: string, newContent: string): { removedL
34
85
  for (const part of wordDiff) {
35
86
  if (part.removed) {
36
87
  let value = part.value;
37
- // Strip leading whitespace from the first removed part
38
88
  if (isFirstRemoved) {
39
89
  const leadingWs = value.match(/^(\s*)/)?.[1] || "";
40
90
  value = value.slice(leadingWs.length);
@@ -42,11 +92,10 @@ function renderIntraLineDiff(oldContent: string, newContent: string): { removedL
42
92
  isFirstRemoved = false;
43
93
  }
44
94
  if (value) {
45
- removedLine += theme.inverse(value);
95
+ removedLine += styleRemovedToken(value);
46
96
  }
47
97
  } else if (part.added) {
48
98
  let value = part.value;
49
- // Strip leading whitespace from the first added part
50
99
  if (isFirstAdded) {
51
100
  const leadingWs = value.match(/^(\s*)/)?.[1] || "";
52
101
  value = value.slice(leadingWs.length);
@@ -54,7 +103,7 @@ function renderIntraLineDiff(oldContent: string, newContent: string): { removedL
54
103
  isFirstAdded = false;
55
104
  }
56
105
  if (value) {
57
- addedLine += theme.inverse(value);
106
+ addedLine += styleAddedToken(value);
58
107
  }
59
108
  } else {
60
109
  removedLine += part.value;
@@ -70,29 +119,52 @@ export interface RenderDiffOptions {
70
119
  filePath?: string;
71
120
  }
72
121
 
73
- /**
74
- * Render a diff string with colored lines and intra-line change highlighting.
75
- * - Context lines: dim/gray
76
- * - Removed lines: red, with inverse on changed tokens
77
- * - Added lines: green, with inverse on changed tokens
78
- */
79
- export function renderDiff(diffText: string, _options: RenderDiffOptions = {}): string {
122
+ function renderStyledLine(
123
+ text: string,
124
+ width: number,
125
+ kind: "context" | "added" | "removed",
126
+ ): string[] {
127
+ const wrapWidth = Math.max(1, width);
128
+ const wrapped = wrapTextWithAnsi(text, wrapWidth);
129
+ if (kind === "added") {
130
+ return wrapped.map((line) => styleAddedLine(line, width));
131
+ }
132
+ if (kind === "removed") {
133
+ return wrapped.map((line) => styleRemovedLine(line, width));
134
+ }
135
+ return wrapped.map((line) => padToWidth(theme.fg("toolDiffContext", line), width));
136
+ }
137
+
138
+ export function renderDiffLines(diffText: string, width: number, _options: RenderDiffOptions = {}): string[] {
80
139
  const lines = diffText.split("\n");
81
140
  const result: string[] = [];
82
141
 
142
+ const parsedLines = lines.map(parseDiffLine).filter((p): p is { prefix: string; lineNum: string; content: string } => !!p);
143
+ const lineNumWidth = Math.max(
144
+ 1,
145
+ ...parsedLines
146
+ .map((p) => p.lineNum.trim())
147
+ .filter(Boolean)
148
+ .map((n) => n.length),
149
+ );
150
+
151
+ const formatLine = (prefix: "+" | "-" | " ", lineNum: string, content: string): string => {
152
+ const num = formatLineNum(lineNum, lineNumWidth);
153
+ return `${prefix}${num} ${replaceTabs(content)}`;
154
+ };
155
+
83
156
  let i = 0;
84
157
  while (i < lines.length) {
85
158
  const line = lines[i];
86
159
  const parsed = parseDiffLine(line);
87
160
 
88
161
  if (!parsed) {
89
- result.push(theme.fg("toolDiffContext", line));
162
+ result.push(...renderStyledLine(line, width, "context"));
90
163
  i++;
91
164
  continue;
92
165
  }
93
166
 
94
167
  if (parsed.prefix === "-") {
95
- // Collect consecutive removed lines
96
168
  const removedLines: { lineNum: string; content: string }[] = [];
97
169
  while (i < lines.length) {
98
170
  const p = parseDiffLine(lines[i]);
@@ -101,7 +173,6 @@ export function renderDiff(diffText: string, _options: RenderDiffOptions = {}):
101
173
  i++;
102
174
  }
103
175
 
104
- // Collect consecutive added lines
105
176
  const addedLines: { lineNum: string; content: string }[] = [];
106
177
  while (i < lines.length) {
107
178
  const p = parseDiffLine(lines[i]);
@@ -110,8 +181,6 @@ export function renderDiff(diffText: string, _options: RenderDiffOptions = {}):
110
181
  i++;
111
182
  }
112
183
 
113
- // Only do intra-line diffing when there's exactly one removed and one added line
114
- // (indicating a single line modification). Otherwise, show lines as-is.
115
184
  if (removedLines.length === 1 && addedLines.length === 1) {
116
185
  const removed = removedLines[0];
117
186
  const added = addedLines[0];
@@ -121,27 +190,35 @@ export function renderDiff(diffText: string, _options: RenderDiffOptions = {}):
121
190
  replaceTabs(added.content),
122
191
  );
123
192
 
124
- result.push(theme.fg("toolDiffRemoved", `-${removed.lineNum} ${removedLine}`));
125
- result.push(theme.fg("toolDiffAdded", `+${added.lineNum} ${addedLine}`));
193
+ result.push(...renderStyledLine(formatLine("-", removed.lineNum, removedLine), width, "removed"));
194
+ result.push(...renderStyledLine(formatLine("+", added.lineNum, addedLine), width, "added"));
126
195
  } else {
127
- // Show all removed lines first, then all added lines
128
196
  for (const removed of removedLines) {
129
- result.push(theme.fg("toolDiffRemoved", `-${removed.lineNum} ${replaceTabs(removed.content)}`));
197
+ result.push(...renderStyledLine(formatLine("-", removed.lineNum, removed.content), width, "removed"));
130
198
  }
131
199
  for (const added of addedLines) {
132
- result.push(theme.fg("toolDiffAdded", `+${added.lineNum} ${replaceTabs(added.content)}`));
200
+ result.push(...renderStyledLine(formatLine("+", added.lineNum, added.content), width, "added"));
133
201
  }
134
202
  }
135
203
  } else if (parsed.prefix === "+") {
136
- // Standalone added line
137
- result.push(theme.fg("toolDiffAdded", `+${parsed.lineNum} ${replaceTabs(parsed.content)}`));
204
+ result.push(...renderStyledLine(formatLine("+", parsed.lineNum, parsed.content), width, "added"));
138
205
  i++;
139
206
  } else {
140
- // Context line
141
- result.push(theme.fg("toolDiffContext", ` ${parsed.lineNum} ${replaceTabs(parsed.content)}`));
207
+ result.push(...renderStyledLine(formatLine(" ", parsed.lineNum, parsed.content), width, "context"));
142
208
  i++;
143
209
  }
144
210
  }
145
211
 
146
- return result.join("\n");
212
+ return result;
213
+ }
214
+
215
+ /**
216
+ * Render a diff string with Claude-like colored lines.
217
+ * - Context lines: muted gray
218
+ * - Removed lines: red text on subtle red background
219
+ * - Added lines: green text on subtle green background
220
+ * - Changed tokens: slightly stronger background + bold
221
+ */
222
+ export function renderDiff(diffText: string, options: RenderDiffOptions = {}): string {
223
+ return renderDiffLines(diffText, Number.MAX_SAFE_INTEGER, options).join("\n");
147
224
  }
@@ -47,6 +47,7 @@ export class FooterComponent implements Component {
47
47
  private autoCompactEnabled = true;
48
48
  private permissionMode: PermissionMode = "danger-full-access";
49
49
  private notificationSoundEnabled = false;
50
+ private verboseFooterEnabled = false;
50
51
 
51
52
  constructor(
52
53
  private session: AgentSession,
@@ -65,6 +66,10 @@ export class FooterComponent implements Component {
65
66
  this.notificationSoundEnabled = enabled;
66
67
  }
67
68
 
69
+ setVerboseFooterEnabled(enabled: boolean): void {
70
+ this.verboseFooterEnabled = enabled;
71
+ }
72
+
68
73
  /**
69
74
  * No-op: git branch caching now handled by provider.
70
75
  * Kept for compatibility with existing call sites in interactive-mode.
@@ -125,7 +130,7 @@ export class FooterComponent implements Component {
125
130
  const extensionStatuses = this.footerData.getExtensionStatuses();
126
131
  const cacheTimerStatusRaw = extensionStatuses.get("cache-timer");
127
132
  const cacheTimerStatus = cacheTimerStatusRaw ? sanitizeStatusText(cacheTimerStatusRaw) : "";
128
- const hotkeysHints = ["Ctrl+K • /hotkeys", "/hotkeys", "Ctrl+K"];
133
+ const hotkeysHints = ["/hotkeys"];
129
134
  const firstLineMinPadding = 2;
130
135
  const firstLineRightParts = cacheTimerStatus ? [cacheTimerStatus] : [];
131
136
  if (this.notificationSoundEnabled) {
@@ -155,10 +160,12 @@ export class FooterComponent implements Component {
155
160
 
156
161
  // Build stats line
157
162
  const statsParts = [];
158
- if (totalInput) statsParts.push(`↑${formatTokens(totalInput)}`);
159
- if (totalOutput) statsParts.push(`↓${formatTokens(totalOutput)}`);
160
- if (totalCacheRead) statsParts.push(`Cache ${formatTokens(totalCacheRead)}`);
161
- if (totalCacheWrite) statsParts.push(`W${formatTokens(totalCacheWrite)}`);
163
+ if (this.verboseFooterEnabled) {
164
+ if (totalInput) statsParts.push(`↑${formatTokens(totalInput)}`);
165
+ if (totalOutput) statsParts.push(`↓${formatTokens(totalOutput)}`);
166
+ if (totalCacheRead) statsParts.push(`Cache ${formatTokens(totalCacheRead)}`);
167
+ if (totalCacheWrite) statsParts.push(`W${formatTokens(totalCacheWrite)}`);
168
+ }
162
169
 
163
170
  // Show cost with "(sub)" indicator if using OAuth subscription
164
171
  const usingSubscription = displayModel ? this.session.modelRegistry.isUsingOAuth(displayModel) : false;
@@ -168,7 +175,7 @@ export class FooterComponent implements Component {
168
175
  }
169
176
 
170
177
  // Per-prompt cost annotation (opt-in via show_token_cost preference, #1515)
171
- if (process.env.GSD_SHOW_TOKEN_COST === "1") {
178
+ if (this.verboseFooterEnabled && process.env.GSD_SHOW_TOKEN_COST === "1") {
172
179
  const lastTurnCost = this.session.getLastTurnCost();
173
180
  if (lastTurnCost > 0) {
174
181
  statsParts.push(`(last: ${formatPromptCost(lastTurnCost)})`);
@@ -43,6 +43,7 @@ export interface SettingsConfig {
43
43
  codexRotate: boolean;
44
44
  fastMode: boolean;
45
45
  cacheTimer: boolean;
46
+ verboseFooter: boolean;
46
47
  pinLastPrompt: boolean;
47
48
  steeringMode: "all" | "one-at-a-time";
48
49
  followUpMode: "all" | "one-at-a-time";
@@ -67,6 +68,7 @@ export interface SettingsConfig {
67
68
  clearOnShrink: boolean;
68
69
  timestampFormat: "date-time-iso" | "date-time-us";
69
70
  toolOutputMode: "minimal" | "normal";
71
+ collapseToolCalls: boolean;
70
72
  rtk: boolean;
71
73
  editorScheme: "auto" | "vscode" | "cursor" | "zed" | "jetbrains" | "sublime" | "file";
72
74
  autoDream: boolean;
@@ -110,6 +112,7 @@ export interface SettingsCallbacks {
110
112
  onCodexRotateChange: (enabled: boolean) => void;
111
113
  onFastModeChange: (enabled: boolean) => void;
112
114
  onCacheTimerChange: (enabled: boolean) => void;
115
+ onVerboseFooterChange: (enabled: boolean) => void;
113
116
  onPinLastPromptChange: (enabled: boolean) => void;
114
117
  onSteeringModeChange: (mode: "all" | "one-at-a-time") => void;
115
118
  onFollowUpModeChange: (mode: "all" | "one-at-a-time") => void;
@@ -132,6 +135,7 @@ export interface SettingsCallbacks {
132
135
  onClearOnShrinkChange: (enabled: boolean) => void;
133
136
  onTimestampFormatChange: (format: "date-time-iso" | "date-time-us") => void;
134
137
  onToolOutputModeChange: (mode: "minimal" | "normal") => void;
138
+ onCollapseToolCallsChange: (enabled: boolean) => void;
135
139
  onRtkChange: (enabled: boolean) => void;
136
140
  onEditorSchemeChange: (scheme: "auto" | "vscode" | "cursor" | "zed" | "jetbrains" | "sublime" | "file") => void;
137
141
  onCancel: () => void;
@@ -318,6 +322,13 @@ export class SettingsSelectorComponent extends Container {
318
322
  currentValue: config.hideThinkingBlock ? "true" : "false",
319
323
  values: ["true", "false"],
320
324
  },
325
+ {
326
+ id: "collapse-tool-calls",
327
+ label: "Collapse tool calls",
328
+ description: "Group low-priority tool calls into collapsed summary lines",
329
+ currentValue: config.collapseToolCalls ? "true" : "false",
330
+ values: ["true", "false"],
331
+ },
321
332
  {
322
333
  id: "tool-output-mode",
323
334
  label: "Tool output mode",
@@ -537,9 +548,19 @@ export class SettingsSelectorComponent extends Container {
537
548
  values: ["true", "false"],
538
549
  });
539
550
 
540
- // Pin last prompt toggle (insert after cache-timer)
551
+ // Verbose footer toggle (insert after cache-timer)
541
552
  const cacheTimerIdx = items.findIndex((item) => item.id === "cache-timer");
542
553
  items.splice(cacheTimerIdx + 1, 0, {
554
+ id: "verbose-footer",
555
+ label: "Verbose footer",
556
+ description: "Show extra footer stats like input, output, and cache tokens",
557
+ currentValue: config.verboseFooter ? "true" : "false",
558
+ values: ["true", "false"],
559
+ });
560
+
561
+ // Pin last prompt toggle (insert after verbose-footer)
562
+ const verboseFooterIdx = items.findIndex((item) => item.id === "verbose-footer");
563
+ items.splice(verboseFooterIdx + 1, 0, {
543
564
  id: "pin-last-prompt",
544
565
  label: "Pin last prompt",
545
566
  description: "Show your last sent message above the editor so you remember the topic",
@@ -587,9 +608,9 @@ export class SettingsSelectorComponent extends Container {
587
608
  values: ["true", "false"],
588
609
  });
589
610
 
590
- // RTK toggle (insert after cache-timer)
591
- const cacheTimerIndex = items.findIndex((item) => item.id === "cache-timer");
592
- items.splice(cacheTimerIndex + 1, 0, {
611
+ // RTK toggle (insert after verbose-footer)
612
+ const verboseFooterIndex = items.findIndex((item) => item.id === "verbose-footer");
613
+ items.splice(verboseFooterIndex + 1, 0, {
593
614
  id: "rtk",
594
615
  label: "RTK",
595
616
  description: "Enable RTK shell-command compression (requires restart)",
@@ -733,6 +754,9 @@ export class SettingsSelectorComponent extends Container {
733
754
  case "cache-timer":
734
755
  callbacks.onCacheTimerChange(newValue === "true");
735
756
  break;
757
+ case "verbose-footer":
758
+ callbacks.onVerboseFooterChange(newValue === "true");
759
+ break;
736
760
  case "pin-last-prompt":
737
761
  callbacks.onPinLastPromptChange(newValue === "true");
738
762
  break;
@@ -769,6 +793,9 @@ export class SettingsSelectorComponent extends Container {
769
793
  case "hide-thinking":
770
794
  callbacks.onHideThinkingBlockChange(newValue === "true");
771
795
  break;
796
+ case "collapse-tool-calls":
797
+ callbacks.onCollapseToolCallsChange(newValue === "true");
798
+ break;
772
799
  case "tool-output-mode":
773
800
  callbacks.onToolOutputModeChange(newValue as "minimal" | "normal");
774
801
  break;