gsd-pi 2.24.0 → 2.26.0

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 (206) hide show
  1. package/README.md +13 -3
  2. package/dist/headless.js +24 -4
  3. package/dist/models-resolver.d.ts +0 -11
  4. package/dist/models-resolver.js +0 -15
  5. package/dist/resource-loader.d.ts +0 -1
  6. package/dist/resource-loader.js +0 -9
  7. package/dist/resources/GSD-WORKFLOW.md +12 -9
  8. package/dist/resources/extensions/async-jobs/index.ts +9 -1
  9. package/dist/resources/extensions/bg-shell/index.ts +3 -2
  10. package/dist/resources/extensions/bg-shell/overlay.ts +18 -17
  11. package/dist/resources/extensions/get-secrets-from-user.ts +5 -23
  12. package/dist/resources/extensions/gsd/activity-log.ts +5 -3
  13. package/dist/resources/extensions/gsd/auto-prompts.ts +14 -0
  14. package/dist/resources/extensions/gsd/auto-recovery.ts +7 -4
  15. package/dist/resources/extensions/gsd/auto-worktree.ts +132 -3
  16. package/dist/resources/extensions/gsd/auto.ts +265 -48
  17. package/dist/resources/extensions/gsd/cache.ts +3 -1
  18. package/dist/resources/extensions/gsd/doctor-proactive.ts +7 -6
  19. package/dist/resources/extensions/gsd/doctor.ts +26 -1
  20. package/dist/resources/extensions/gsd/files.ts +13 -2
  21. package/dist/resources/extensions/gsd/git-service.ts +74 -14
  22. package/dist/resources/extensions/gsd/gsd-db.ts +78 -1
  23. package/dist/resources/extensions/gsd/guided-flow.ts +54 -22
  24. package/dist/resources/extensions/gsd/index.ts +62 -8
  25. package/dist/resources/extensions/gsd/memory-extractor.ts +352 -0
  26. package/dist/resources/extensions/gsd/memory-store.ts +441 -0
  27. package/dist/resources/extensions/gsd/migrate/command.ts +2 -2
  28. package/dist/resources/extensions/gsd/migrate/writer.ts +39 -0
  29. package/dist/resources/extensions/gsd/parallel-orchestrator.ts +122 -4
  30. package/dist/resources/extensions/gsd/preferences.ts +2 -1
  31. package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  32. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +4 -4
  33. package/dist/resources/extensions/gsd/prompts/discuss.md +5 -5
  34. package/dist/resources/extensions/gsd/prompts/execute-task.md +1 -1
  35. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
  36. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
  37. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  38. package/dist/resources/extensions/gsd/prompts/queue.md +3 -3
  39. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  40. package/dist/resources/extensions/gsd/roadmap-slices.ts +45 -1
  41. package/dist/resources/extensions/gsd/state.ts +17 -6
  42. package/dist/resources/extensions/gsd/tests/auto-recovery.test.ts +54 -0
  43. package/dist/resources/extensions/gsd/tests/auto-worktree.test.ts +58 -0
  44. package/dist/resources/extensions/gsd/tests/derive-state.test.ts +70 -0
  45. package/dist/resources/extensions/gsd/tests/doctor-proactive.test.ts +23 -3
  46. package/dist/resources/extensions/gsd/tests/git-service.test.ts +70 -4
  47. package/dist/resources/extensions/gsd/tests/gsd-db.test.ts +2 -2
  48. package/dist/resources/extensions/gsd/tests/md-importer.test.ts +2 -3
  49. package/dist/resources/extensions/gsd/tests/memory-extractor.test.ts +180 -0
  50. package/dist/resources/extensions/gsd/tests/memory-store.test.ts +345 -0
  51. package/dist/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +13 -7
  52. package/dist/resources/extensions/gsd/tests/parallel-worker-monitoring.test.ts +171 -0
  53. package/dist/resources/extensions/gsd/tests/smart-entry-draft.test.ts +1 -1
  54. package/dist/resources/extensions/gsd/tests/validate-milestone.test.ts +8 -4
  55. package/dist/resources/extensions/gsd/tests/visualizer-data.test.ts +147 -2
  56. package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +88 -10
  57. package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +314 -87
  58. package/dist/resources/extensions/gsd/triage-ui.ts +1 -1
  59. package/dist/resources/extensions/gsd/types.ts +2 -0
  60. package/dist/resources/extensions/gsd/visualizer-data.ts +291 -10
  61. package/dist/resources/extensions/gsd/visualizer-overlay.ts +237 -28
  62. package/dist/resources/extensions/gsd/visualizer-views.ts +462 -48
  63. package/dist/resources/extensions/gsd/worktree.ts +9 -2
  64. package/dist/resources/extensions/search-the-web/native-search.ts +19 -5
  65. package/dist/resources/extensions/shared/path-display.ts +19 -0
  66. package/package.json +1 -6
  67. package/packages/pi-agent-core/dist/agent-loop.js +2 -0
  68. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  69. package/packages/pi-agent-core/src/agent-loop.ts +2 -0
  70. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  71. package/packages/pi-ai/dist/providers/anthropic.js +64 -0
  72. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  73. package/packages/pi-ai/dist/providers/mistral.js +3 -0
  74. package/packages/pi-ai/dist/providers/mistral.js.map +1 -1
  75. package/packages/pi-ai/dist/types.d.ts +23 -1
  76. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  77. package/packages/pi-ai/dist/types.js.map +1 -1
  78. package/packages/pi-ai/src/providers/anthropic.ts +65 -1
  79. package/packages/pi-ai/src/providers/mistral.ts +3 -0
  80. package/packages/pi-ai/src/types.ts +19 -1
  81. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +7 -0
  82. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  83. package/packages/pi-coding-agent/dist/core/agent-session.js +32 -0
  84. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  85. package/packages/pi-coding-agent/dist/core/keybindings.js +1 -1
  86. package/packages/pi-coding-agent/dist/core/keybindings.js.map +1 -1
  87. package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
  88. package/packages/pi-coding-agent/dist/core/lsp/client.js +12 -1
  89. package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
  90. package/packages/pi-coding-agent/dist/core/lsp/index.d.ts.map +1 -1
  91. package/packages/pi-coding-agent/dist/core/lsp/index.js +7 -0
  92. package/packages/pi-coding-agent/dist/core/lsp/index.js.map +1 -1
  93. package/packages/pi-coding-agent/dist/core/sdk.d.ts +2 -2
  94. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  95. package/packages/pi-coding-agent/dist/core/sdk.js +8 -3
  96. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  97. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -0
  98. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  99. package/packages/pi-coding-agent/dist/core/settings-manager.js +8 -0
  100. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  101. package/packages/pi-coding-agent/dist/core/slash-commands.d.ts.map +1 -1
  102. package/packages/pi-coding-agent/dist/core/slash-commands.js +1 -0
  103. package/packages/pi-coding-agent/dist/core/slash-commands.js.map +1 -1
  104. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  105. package/packages/pi-coding-agent/dist/core/system-prompt.js +2 -1
  106. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  107. package/packages/pi-coding-agent/dist/index.d.ts +2 -1
  108. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  109. package/packages/pi-coding-agent/dist/index.js +5 -1
  110. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  111. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts +41 -3
  112. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  113. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js +301 -62
  114. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js.map +1 -1
  115. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  116. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +17 -0
  117. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  118. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +5 -0
  119. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  120. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +135 -30
  121. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  122. package/packages/pi-coding-agent/dist/tests/path-display.test.d.ts +8 -0
  123. package/packages/pi-coding-agent/dist/tests/path-display.test.d.ts.map +1 -0
  124. package/packages/pi-coding-agent/dist/tests/path-display.test.js +60 -0
  125. package/packages/pi-coding-agent/dist/tests/path-display.test.js.map +1 -0
  126. package/packages/pi-coding-agent/dist/utils/clipboard-image.d.ts.map +1 -1
  127. package/packages/pi-coding-agent/dist/utils/clipboard-image.js +32 -6
  128. package/packages/pi-coding-agent/dist/utils/clipboard-image.js.map +1 -1
  129. package/packages/pi-coding-agent/dist/utils/path-display.d.ts +34 -0
  130. package/packages/pi-coding-agent/dist/utils/path-display.d.ts.map +1 -0
  131. package/packages/pi-coding-agent/dist/utils/path-display.js +36 -0
  132. package/packages/pi-coding-agent/dist/utils/path-display.js.map +1 -0
  133. package/packages/pi-coding-agent/src/core/agent-session.ts +36 -0
  134. package/packages/pi-coding-agent/src/core/keybindings.ts +1 -1
  135. package/packages/pi-coding-agent/src/core/lsp/client.ts +11 -1
  136. package/packages/pi-coding-agent/src/core/lsp/index.ts +7 -0
  137. package/packages/pi-coding-agent/src/core/sdk.ts +17 -1
  138. package/packages/pi-coding-agent/src/core/settings-manager.ts +11 -0
  139. package/packages/pi-coding-agent/src/core/slash-commands.ts +1 -0
  140. package/packages/pi-coding-agent/src/core/system-prompt.ts +2 -1
  141. package/packages/pi-coding-agent/src/index.ts +15 -0
  142. package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +347 -62
  143. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +18 -0
  144. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +124 -4
  145. package/packages/pi-coding-agent/src/tests/path-display.test.ts +85 -0
  146. package/packages/pi-coding-agent/src/utils/clipboard-image.ts +33 -6
  147. package/packages/pi-coding-agent/src/utils/path-display.ts +36 -0
  148. package/src/resources/GSD-WORKFLOW.md +12 -9
  149. package/src/resources/extensions/async-jobs/index.ts +9 -1
  150. package/src/resources/extensions/bg-shell/index.ts +3 -2
  151. package/src/resources/extensions/bg-shell/overlay.ts +18 -17
  152. package/src/resources/extensions/get-secrets-from-user.ts +5 -23
  153. package/src/resources/extensions/gsd/activity-log.ts +5 -3
  154. package/src/resources/extensions/gsd/auto-prompts.ts +14 -0
  155. package/src/resources/extensions/gsd/auto-recovery.ts +7 -4
  156. package/src/resources/extensions/gsd/auto-worktree.ts +132 -3
  157. package/src/resources/extensions/gsd/auto.ts +265 -48
  158. package/src/resources/extensions/gsd/cache.ts +3 -1
  159. package/src/resources/extensions/gsd/doctor-proactive.ts +7 -6
  160. package/src/resources/extensions/gsd/doctor.ts +26 -1
  161. package/src/resources/extensions/gsd/files.ts +13 -2
  162. package/src/resources/extensions/gsd/git-service.ts +74 -14
  163. package/src/resources/extensions/gsd/gsd-db.ts +78 -1
  164. package/src/resources/extensions/gsd/guided-flow.ts +54 -22
  165. package/src/resources/extensions/gsd/index.ts +62 -8
  166. package/src/resources/extensions/gsd/memory-extractor.ts +352 -0
  167. package/src/resources/extensions/gsd/memory-store.ts +441 -0
  168. package/src/resources/extensions/gsd/migrate/command.ts +2 -2
  169. package/src/resources/extensions/gsd/migrate/writer.ts +39 -0
  170. package/src/resources/extensions/gsd/parallel-orchestrator.ts +122 -4
  171. package/src/resources/extensions/gsd/preferences.ts +2 -1
  172. package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  173. package/src/resources/extensions/gsd/prompts/discuss-headless.md +4 -4
  174. package/src/resources/extensions/gsd/prompts/discuss.md +5 -5
  175. package/src/resources/extensions/gsd/prompts/execute-task.md +1 -1
  176. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
  177. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
  178. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  179. package/src/resources/extensions/gsd/prompts/queue.md +3 -3
  180. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  181. package/src/resources/extensions/gsd/roadmap-slices.ts +45 -1
  182. package/src/resources/extensions/gsd/state.ts +17 -6
  183. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +54 -0
  184. package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +58 -0
  185. package/src/resources/extensions/gsd/tests/derive-state.test.ts +70 -0
  186. package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +23 -3
  187. package/src/resources/extensions/gsd/tests/git-service.test.ts +70 -4
  188. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +2 -2
  189. package/src/resources/extensions/gsd/tests/md-importer.test.ts +2 -3
  190. package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +180 -0
  191. package/src/resources/extensions/gsd/tests/memory-store.test.ts +345 -0
  192. package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +13 -7
  193. package/src/resources/extensions/gsd/tests/parallel-worker-monitoring.test.ts +171 -0
  194. package/src/resources/extensions/gsd/tests/smart-entry-draft.test.ts +1 -1
  195. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +8 -4
  196. package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +147 -2
  197. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +88 -10
  198. package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +314 -87
  199. package/src/resources/extensions/gsd/triage-ui.ts +1 -1
  200. package/src/resources/extensions/gsd/types.ts +2 -0
  201. package/src/resources/extensions/gsd/visualizer-data.ts +291 -10
  202. package/src/resources/extensions/gsd/visualizer-overlay.ts +237 -28
  203. package/src/resources/extensions/gsd/visualizer-views.ts +462 -48
  204. package/src/resources/extensions/gsd/worktree.ts +9 -2
  205. package/src/resources/extensions/search-the-web/native-search.ts +19 -5
  206. package/src/resources/extensions/shared/path-display.ts +19 -0
@@ -1165,6 +1165,34 @@ export class InteractiveMode {
1165
1165
  return registeredTool?.definition;
1166
1166
  }
1167
1167
 
1168
+ /**
1169
+ * Format web search result content for display in the TUI.
1170
+ */
1171
+ private formatWebSearchResult(content: unknown): string {
1172
+ if (!content) return "Web search completed";
1173
+
1174
+ // Error result
1175
+ if (typeof content === "object" && "type" in (content as any) && (content as any).type === "web_search_tool_result_error") {
1176
+ const error = content as any;
1177
+ return `Search error: ${error.error_code || "unknown"}`;
1178
+ }
1179
+
1180
+ // Array of search results
1181
+ if (Array.isArray(content)) {
1182
+ const results = content.filter((r: any) => r.type === "web_search_result");
1183
+ if (results.length === 0) return "No results found";
1184
+ return results
1185
+ .map((r: any) => {
1186
+ const title = r.title || "Untitled";
1187
+ const url = r.url || "";
1188
+ return `${title}\n ${url}`;
1189
+ })
1190
+ .join("\n");
1191
+ }
1192
+
1193
+ return "Web search completed";
1194
+ }
1195
+
1168
1196
  /**
1169
1197
  * Set up keyboard shortcuts registered by extensions.
1170
1198
  */
@@ -2035,6 +2063,12 @@ export class InteractiveMode {
2035
2063
  this.handleThinkingCommand(arg);
2036
2064
  return;
2037
2065
  }
2066
+ if (text === "/edit-mode" || text.startsWith("/edit-mode ")) {
2067
+ const arg = text.startsWith("/edit-mode ") ? text.slice(11).trim() : undefined;
2068
+ this.editor.setText("");
2069
+ this.handleEditModeCommand(arg);
2070
+ return;
2071
+ }
2038
2072
  if (text === "/debug") {
2039
2073
  this.handleDebugCommand();
2040
2074
  this.editor.setText("");
@@ -2201,6 +2235,35 @@ export class InteractiveMode {
2201
2235
  component.updateArgs(content.arguments);
2202
2236
  }
2203
2237
  }
2238
+ } else if (content.type === "serverToolUse") {
2239
+ // Server-side tool (e.g., native web search) — show as pending tool execution
2240
+ if (!this.pendingTools.has(content.id)) {
2241
+ const component = new ToolExecutionComponent(
2242
+ content.name,
2243
+ content.input ?? {},
2244
+ {
2245
+ showImages: this.settingsManager.getShowImages(),
2246
+ },
2247
+ undefined,
2248
+ this.ui,
2249
+ );
2250
+ component.setExpanded(this.toolOutputExpanded);
2251
+ this.chatContainer.addChild(component);
2252
+ this.pendingTools.set(content.id, component);
2253
+ }
2254
+ } else if (content.type === "webSearchResult") {
2255
+ // Server-side tool result — resolve the pending server tool execution
2256
+ const component = this.pendingTools.get(content.toolUseId);
2257
+ if (component) {
2258
+ const searchContent = content.content;
2259
+ const isError = searchContent && typeof searchContent === "object" && "type" in (searchContent as any) && (searchContent as any).type === "web_search_tool_result_error";
2260
+ const resultText = this.formatWebSearchResult(searchContent);
2261
+ component.updateResult({
2262
+ content: [{ type: "text", text: resultText }],
2263
+ isError: !!isError,
2264
+ });
2265
+ this.pendingTools.delete(content.toolUseId);
2266
+ }
2204
2267
  }
2205
2268
  }
2206
2269
  this.ui.requestRender();
@@ -2594,6 +2657,33 @@ export class InteractiveMode {
2594
2657
  } else {
2595
2658
  this.pendingTools.set(content.id, component);
2596
2659
  }
2660
+ } else if (content.type === "serverToolUse") {
2661
+ // Server-side tool (e.g., native web search)
2662
+ const component = new ToolExecutionComponent(
2663
+ content.name,
2664
+ content.input ?? {},
2665
+ { showImages: this.settingsManager.getShowImages() },
2666
+ undefined,
2667
+ this.ui,
2668
+ );
2669
+ component.setExpanded(this.toolOutputExpanded);
2670
+ this.chatContainer.addChild(component);
2671
+ // Find matching webSearchResult in this message's content
2672
+ const resultBlock = message.content.find(
2673
+ (c) => c.type === "webSearchResult" && c.toolUseId === content.id,
2674
+ );
2675
+ if (resultBlock && resultBlock.type === "webSearchResult") {
2676
+ const searchContent = resultBlock.content;
2677
+ const isError = searchContent && typeof searchContent === "object" && "type" in (searchContent as any) && (searchContent as any).type === "web_search_tool_result_error";
2678
+ const resultText = this.formatWebSearchResult(searchContent);
2679
+ component.updateResult({
2680
+ content: [{ type: "text", text: resultText }],
2681
+ isError: !!isError,
2682
+ });
2683
+ } else {
2684
+ // No result yet (aborted stream?) — show as pending
2685
+ this.pendingTools.set(content.id, component);
2686
+ }
2597
2687
  }
2598
2688
  }
2599
2689
  } else if (message.role === "toolResult") {
@@ -2807,6 +2897,27 @@ export class InteractiveMode {
2807
2897
  this.showThinkingSelector();
2808
2898
  }
2809
2899
 
2900
+ private handleEditModeCommand(arg?: string): void {
2901
+ const modes = ["standard", "hashline"] as const;
2902
+
2903
+ if (arg) {
2904
+ const mode = arg.toLowerCase();
2905
+ if (!modes.includes(mode as typeof modes[number])) {
2906
+ this.showStatus(`Invalid edit mode "${arg}". Available: standard, hashline`);
2907
+ return;
2908
+ }
2909
+ this.session.setEditMode(mode as "standard" | "hashline");
2910
+ this.showStatus(`Edit mode: ${mode}${mode === "hashline" ? " (LINE#ID anchored edits)" : " (text-match edits)"}`);
2911
+ return;
2912
+ }
2913
+
2914
+ // Toggle
2915
+ const current = this.session.editMode;
2916
+ const next = current === "standard" ? "hashline" : "standard";
2917
+ this.session.setEditMode(next);
2918
+ this.showStatus(`Edit mode: ${next}${next === "hashline" ? " (LINE#ID anchored edits)" : " (text-match edits)"}`);
2919
+ }
2920
+
2810
2921
  private showThinkingSelector(): void {
2811
2922
  const availableLevels = this.session.getAvailableThinkingLevels();
2812
2923
  this.showSelector((done) => {
@@ -3799,12 +3910,16 @@ export class InteractiveMode {
3799
3910
  const selector = new OAuthSelectorComponent(
3800
3911
  mode,
3801
3912
  this.session.modelRegistry.authStorage,
3802
- async (providerId: string) => {
3913
+ (providerId: string) => {
3803
3914
  done();
3804
3915
 
3805
- if (mode === "login") {
3806
- await this.showLoginDialog(providerId);
3807
- } else {
3916
+ // OAuthSelectorComponent calls this synchronously (no await),
3917
+ // so we must catch async errors here to prevent unhandled rejections
3918
+ // when the user cancels the login dialog (#821).
3919
+ const handleAsync = async () => {
3920
+ if (mode === "login") {
3921
+ await this.showLoginDialog(providerId);
3922
+ } else {
3808
3923
  // Logout flow
3809
3924
  const providerInfo = this.session.modelRegistry.authStorage
3810
3925
  .getOAuthProviders()
@@ -3835,6 +3950,11 @@ export class InteractiveMode {
3835
3950
  this.showError(`Logout failed: ${error instanceof Error ? error.message : String(error)}`);
3836
3951
  }
3837
3952
  }
3953
+ };
3954
+ handleAsync().catch(() => {
3955
+ // Swallow — showLoginDialog already handles its own errors.
3956
+ // This prevents unhandled rejections when login is cancelled.
3957
+ });
3838
3958
  },
3839
3959
  () => {
3840
3960
  done();
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Cross-platform path display tests.
3
+ *
4
+ * Verifies that toPosixPath correctly normalizes Windows paths and that
5
+ * the system prompt builder produces forward-slash paths for LLM consumption.
6
+ */
7
+
8
+ import test from "node:test";
9
+ import assert from "node:assert/strict";
10
+ import { toPosixPath } from "../utils/path-display.js";
11
+ import { buildSystemPrompt } from "../core/system-prompt.js";
12
+
13
+ // ─── toPosixPath ────────────────────────────────────────────────────────────
14
+
15
+ test("toPosixPath: converts Windows backslash paths to forward slashes", () => {
16
+ assert.equal(toPosixPath("C:\\Users\\name\\project"), "C:/Users/name/project");
17
+ });
18
+
19
+ test("toPosixPath: handles mixed separators", () => {
20
+ assert.equal(toPosixPath("C:\\Users/name\\project/src"), "C:/Users/name/project/src");
21
+ });
22
+
23
+ test("toPosixPath: no-op for Unix paths", () => {
24
+ assert.equal(toPosixPath("/home/user/project"), "/home/user/project");
25
+ });
26
+
27
+ test("toPosixPath: handles empty string", () => {
28
+ assert.equal(toPosixPath(""), "");
29
+ });
30
+
31
+ test("toPosixPath: handles Windows UNC paths", () => {
32
+ assert.equal(toPosixPath("\\\\server\\share\\dir"), "//server/share/dir");
33
+ });
34
+
35
+ test("toPosixPath: handles .gsd/worktrees path on Windows", () => {
36
+ assert.equal(
37
+ toPosixPath("C:\\Users\\name\\project\\.gsd\\worktrees\\M001"),
38
+ "C:/Users/name/project/.gsd/worktrees/M001",
39
+ );
40
+ });
41
+
42
+ // ─── System prompt path normalization ───────────────────────────────────────
43
+
44
+ test("buildSystemPrompt: cwd uses forward slashes even with Windows input", () => {
45
+ const prompt = buildSystemPrompt({
46
+ cwd: "C:\\Users\\name\\development\\app-name",
47
+ });
48
+ assert.ok(
49
+ prompt.includes("C:/Users/name/development/app-name"),
50
+ "System prompt should contain forward-slash path",
51
+ );
52
+ assert.ok(
53
+ !prompt.includes("C:\\Users\\name\\development\\app-name"),
54
+ "System prompt must NOT contain backslash path",
55
+ );
56
+ });
57
+
58
+ test("buildSystemPrompt: Unix paths pass through unchanged", () => {
59
+ const prompt = buildSystemPrompt({
60
+ cwd: "/home/user/project",
61
+ });
62
+ assert.ok(prompt.includes("/home/user/project"));
63
+ });
64
+
65
+ // ─── Regression: no backslash paths in LLM-visible text ────────────────────
66
+
67
+ /**
68
+ * Pattern that matches Windows-style absolute paths with backslashes.
69
+ * Catches: C:\Users\..., D:\Projects\..., \\server\share\...
70
+ * Does not match: escaped chars in regex, JSON strings, etc.
71
+ */
72
+ const WINDOWS_ABS_PATH_RE = /[A-Z]:\\[A-Za-z]/;
73
+
74
+ test("buildSystemPrompt: no Windows absolute paths with backslashes in output", () => {
75
+ // Simulate a Windows-like cwd
76
+ const prompt = buildSystemPrompt({
77
+ cwd: "D:\\Projects\\my-app\\.gsd\\worktrees\\M002",
78
+ });
79
+ const lines = prompt.split("\n");
80
+ const violations = lines.filter(line => WINDOWS_ABS_PATH_RE.test(line));
81
+ assert.equal(
82
+ violations.length, 0,
83
+ `System prompt contains Windows backslash paths:\n${violations.join("\n")}`,
84
+ );
85
+ });
@@ -114,16 +114,43 @@ function readClipboardImageViaWlPaste(): ClipboardImage | null {
114
114
  .filter(Boolean);
115
115
 
116
116
  const selectedType = selectPreferredImageMimeType(types);
117
- if (!selectedType) {
118
- return null;
117
+ if (selectedType) {
118
+ const data = runCommand("wl-paste", ["--type", selectedType, "--no-newline"]);
119
+ if (data.ok && data.stdout.length > 0) {
120
+ return { bytes: data.stdout, mimeType: baseMimeType(selectedType) };
121
+ }
119
122
  }
120
123
 
121
- const data = runCommand("wl-paste", ["--type", selectedType, "--no-newline"]);
122
- if (!data.ok || data.stdout.length === 0) {
123
- return null;
124
+ // Fallback for WSLg/BMP: when only image/bmp is available, ask wl-paste
125
+ // to convert to PNG on the fly. wl-paste supports format conversion for
126
+ // some compositor types. If that fails, try reading BMP and converting
127
+ // via ImageMagick (#813).
128
+ const hasBmp = types.some((t) => baseMimeType(t) === "image/bmp");
129
+ if (!selectedType && hasBmp) {
130
+ // Try requesting PNG directly — wl-paste may convert
131
+ const pngData = runCommand("wl-paste", ["--type", "image/png", "--no-newline"]);
132
+ if (pngData.ok && pngData.stdout.length > 0) {
133
+ return { bytes: pngData.stdout, mimeType: "image/png" };
134
+ }
135
+
136
+ // Try reading BMP and converting via ImageMagick convert
137
+ const bmpData = runCommand("wl-paste", ["--type", "image/bmp", "--no-newline"]);
138
+ if (bmpData.ok && bmpData.stdout.length > 0) {
139
+ const converted = spawnSync("convert", ["bmp:-", "png:-"], {
140
+ input: bmpData.stdout,
141
+ timeout: 5000,
142
+ maxBuffer: DEFAULT_MAX_BUFFER_BYTES,
143
+ });
144
+ if (!converted.error && converted.status === 0 && converted.stdout.length > 0) {
145
+ const stdout = Buffer.isBuffer(converted.stdout)
146
+ ? converted.stdout
147
+ : Buffer.from(converted.stdout);
148
+ return { bytes: stdout, mimeType: "image/png" };
149
+ }
150
+ }
124
151
  }
125
152
 
126
- return { bytes: data.stdout, mimeType: baseMimeType(selectedType) };
153
+ return null;
127
154
  }
128
155
 
129
156
  function readClipboardImageViaXclip(): ClipboardImage | null {
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Cross-platform path display utilities.
3
+ *
4
+ * Paths injected into LLM prompts, tool results, or any text the model
5
+ * processes must use forward slashes. Windows backslash paths cause bash
6
+ * failures when the model copies them into shell commands — bash interprets
7
+ * backslashes as escape characters, silently stripping them.
8
+ *
9
+ * Node's `path` module and `fs` module handle native separators correctly
10
+ * for filesystem operations. This module is ONLY for paths that enter
11
+ * text consumed by the LLM or interpreted by a shell.
12
+ *
13
+ * Usage:
14
+ * import { toPosixPath } from "./path-display.js";
15
+ * prompt += `Current working directory: ${toPosixPath(cwd)}`;
16
+ *
17
+ * NOT for:
18
+ * fs.readFile(path) — use native path as-is
19
+ * path.join(a, b) — use native path module
20
+ * spawn(cmd, { cwd: path }) — Node handles this correctly
21
+ */
22
+
23
+ /**
24
+ * Convert a filesystem path to forward-slash (POSIX) form for display.
25
+ *
26
+ * On Unix this is a no-op. On Windows it converts `C:\Users\name\project`
27
+ * to `C:/Users/name/project`, which is valid in:
28
+ * - Git Bash / MSYS2
29
+ * - WSL bash
30
+ * - PowerShell
31
+ * - Node.js APIs (which accept both separators)
32
+ * - Most Windows programs
33
+ */
34
+ export function toPosixPath(fsPath: string): string {
35
+ return fsPath.replaceAll("\\", "/");
36
+ }
@@ -565,25 +565,28 @@ One commit per slice. Individually revertable. Reads like a changelog.
565
565
 
566
566
  ```
567
567
  gsd/M001/S01:
568
- test(S01): round-trip tests passing
568
+ test(S01/T03): round-trip tests passing
569
569
  feat(S01/T03): file writer with round-trip fidelity
570
- chore(S01/T03): auto-commit after task
571
570
  feat(S01/T02): markdown parser for plan files
572
- chore(S01/T02): auto-commit after task
573
571
  feat(S01/T01): core types and interfaces
574
- chore(S01/T01): auto-commit after task
572
+ docs(S01): add slice plan
575
573
  ```
576
574
 
577
575
  ### Commit Conventions
578
576
 
579
577
  | When | Format | Example |
580
578
  |------|--------|---------|
581
- | Auto-commit (dirty state) | `chore(S01/T02): auto-commit after task` | Automatic save of work in progress |
582
- | After task verified | `feat(S01/T02): <what was built>` | The real work |
583
- | Plan/docs committed | `docs(S01): add slice plan` | Bundled with first task |
584
- | Slice squash to main | `type(M001/S01): <slice title>` | Type inferred from title (`feat`, `fix`, `docs`, etc.) |
579
+ | Task completed | `{type}(S01/T02): <one-liner from summary>` | Type inferred from title (`feat`, `fix`, `test`, etc.) |
580
+ | Plan/docs committed | `docs(S01): add slice plan` | Planning artifacts |
581
+ | Slice squash to main | `type(M001/S01): <slice title>` | Type inferred from title |
582
+ | State rebuild | `chore(S01/T02): auto-commit after state-rebuild` | Bookkeeping only |
585
583
 
586
- Commit types: `feat`, `fix`, `test`, `refactor`, `docs`, `chore`
584
+ The system reads the task summary after execution and builds a meaningful commit message:
585
+ - **Subject**: `{type}({sliceId}/{taskId}): {one-liner}` — the one-liner from the summary frontmatter
586
+ - **Type**: Inferred from the task title and one-liner (`feat`, `fix`, `test`, `refactor`, `docs`, `perf`, `chore`)
587
+ - **Body**: Key files from the summary frontmatter (up to 8 files listed)
588
+
589
+ Commit types: `feat`, `fix`, `test`, `refactor`, `docs`, `perf`, `chore`
587
590
 
588
591
  ### Squash Merge Message
589
592
 
@@ -54,6 +54,14 @@ export default function AsyncJobs(pi: ExtensionAPI) {
54
54
  ? output.slice(0, maxLen) + "\n\n[... truncated, use await_job for full output]"
55
55
  : output;
56
56
 
57
+ // Deliver as follow-up without triggering a new LLM turn (#875).
58
+ // When the agent is streaming: the message is queued and picked up
59
+ // by the agent loop's getFollowUpMessages() after the current turn.
60
+ // When the agent is idle: the message is appended to context so it's
61
+ // visible on the next user-initiated prompt. Previously triggerTurn:true
62
+ // caused spurious autonomous turns — the model would interpret completed
63
+ // job output as requiring action and cascade into unbounded self-reinforcing
64
+ // loops (running more commands, spawning more jobs, burning context).
57
65
  pi.sendMessage(
58
66
  {
59
67
  customType: "async_job_result",
@@ -64,7 +72,7 @@ export default function AsyncJobs(pi: ExtensionAPI) {
64
72
  ].join("\n"),
65
73
  display: true,
66
74
  },
67
- { deliverAs: "followUp", triggerTurn: true },
75
+ { deliverAs: "followUp" },
68
76
  );
69
77
  },
70
78
  });
@@ -66,6 +66,7 @@ import { waitForReady } from "./readiness-detector.js";
66
66
  import { queryShellEnv, sendAndWait, runOnSession } from "./interaction.js";
67
67
  import { formatUptime, formatTokenCount, resolveBgShellPersistenceCwd } from "./utilities.js";
68
68
  import { BgManagerOverlay } from "./overlay.js";
69
+ import { toPosixPath } from "../shared/path-display.js";
69
70
 
70
71
  // ── Re-exports for consumers ───────────────────────────────────────────────
71
72
 
@@ -337,7 +338,7 @@ export default function (pi: ExtensionAPI) {
337
338
  text += ` type: ${bg.processType}\n`;
338
339
  text += ` status: ${bg.status}\n`;
339
340
  text += ` command: ${bg.command}\n`;
340
- text += ` cwd: ${bg.cwd}`;
341
+ text += ` cwd: ${toPosixPath(bg.cwd)}`;
341
342
 
342
343
  if (bg.group) text += `\n group: ${bg.group}`;
343
344
  if (bg.readyPort) text += `\n ready_port: ${bg.readyPort}`;
@@ -694,7 +695,7 @@ export default function (pi: ExtensionAPI) {
694
695
  }
695
696
 
696
697
  let text = `Shell environment for ${bg.id} (${bg.label}):\n`;
697
- text += ` cwd: ${envResult.cwd}\n`;
698
+ text += ` cwd: ${toPosixPath(envResult.cwd)}\n`;
698
699
  text += ` shell: ${envResult.shell}\n`;
699
700
 
700
701
  const envEntries = Object.entries(envResult.env);
@@ -328,12 +328,9 @@ export class BgManagerOverlay {
328
328
  return this.box(inner, width);
329
329
  }
330
330
 
331
- private renderOutput(width: number): string[] {
331
+ private processStatusHeader(p: typeof this.viewingProcess, activeTab: "output" | "events"): { statusIcon: string; headerLine: string } {
332
332
  const th = this.theme;
333
- const p = this.viewingProcess;
334
- if (!p) return [""];
335
- const inner: string[] = [];
336
-
333
+ if (!p) return { statusIcon: "", headerLine: "" };
337
334
  const statusIcon = p.alive
338
335
  ? (p.status === "ready" ? th.fg("success", "●")
339
336
  : p.status === "error" ? th.fg("error", "●")
@@ -343,9 +340,21 @@ export class BgManagerOverlay {
343
340
  const uptime = th.fg("dim", formatUptime(Date.now() - p.startedAt));
344
341
  const typeTag = th.fg("dim", `[${p.processType}]`);
345
342
  const portInfo = p.ports.length > 0 ? th.fg("dim", ` :${p.ports.join(",")}`) : "";
346
- const tabIndicator = th.fg("accent", "[Output]") + " " + th.fg("dim", "Events");
343
+ const tabIndicator = activeTab === "output"
344
+ ? th.fg("accent", "[Output]") + " " + th.fg("dim", "Events")
345
+ : th.fg("dim", "Output") + " " + th.fg("accent", "[Events]");
346
+ const headerLine = `${statusIcon} ${name} ${typeTag} ${uptime}${portInfo} ${tabIndicator}`;
347
+ return { statusIcon, headerLine };
348
+ }
349
+
350
+ private renderOutput(width: number): string[] {
351
+ const th = this.theme;
352
+ const p = this.viewingProcess;
353
+ if (!p) return [""];
354
+ const inner: string[] = [];
347
355
 
348
- inner.push(`${statusIcon} ${name} ${typeTag} ${uptime}${portInfo} ${tabIndicator}`);
356
+ const { headerLine } = this.processStatusHeader(p, "output");
357
+ inner.push(headerLine);
349
358
  inner.push("");
350
359
 
351
360
  // Unified buffer is already chronologically interleaved
@@ -384,16 +393,8 @@ export class BgManagerOverlay {
384
393
  if (!p) return [""];
385
394
  const inner: string[] = [];
386
395
 
387
- const statusIcon = p.alive
388
- ? (p.status === "ready" ? th.fg("success", "●")
389
- : p.status === "error" ? th.fg("error", "●")
390
- : th.fg("warning", "●"))
391
- : th.fg("dim", "○");
392
- const name = th.fg("muted", p.label);
393
- const uptime = th.fg("dim", formatUptime(Date.now() - p.startedAt));
394
- const tabIndicator = th.fg("dim", "Output") + " " + th.fg("accent", "[Events]");
395
-
396
- inner.push(`${statusIcon} ${name} ${uptime} ${tabIndicator}`);
396
+ const { headerLine } = this.processStatusHeader(p, "events");
397
+ inner.push(headerLine);
397
398
  inner.push("");
398
399
 
399
400
  if (p.events.length === 0) {
@@ -369,32 +369,14 @@ async function applySecrets(
369
369
  }
370
370
  }
371
371
 
372
- if (destination === "vercel" && opts.exec) {
372
+ if ((destination === "vercel" || destination === "convex") && opts.exec) {
373
373
  const env = opts.environment ?? "development";
374
374
  for (const { key, value } of provided) {
375
+ const cmd = destination === "vercel"
376
+ ? `printf %s ${shellEscapeSingle(value)} | vercel env add ${key} ${env}`
377
+ : `npx convex env set ${key} ${shellEscapeSingle(value)}`;
375
378
  try {
376
- const result = await opts.exec("sh", [
377
- "-c",
378
- `printf %s ${shellEscapeSingle(value)} | vercel env add ${key} ${env}`,
379
- ]);
380
- if (result.code !== 0) {
381
- errors.push(`${key}: ${result.stderr.slice(0, 200)}`);
382
- } else {
383
- applied.push(key);
384
- }
385
- } catch (err: any) {
386
- errors.push(`${key}: ${err.message}`);
387
- }
388
- }
389
- }
390
-
391
- if (destination === "convex" && opts.exec) {
392
- for (const { key, value } of provided) {
393
- try {
394
- const result = await opts.exec("sh", [
395
- "-c",
396
- `npx convex env set ${key} ${shellEscapeSingle(value)}`,
397
- ]);
379
+ const result = await opts.exec("sh", ["-c", cmd]);
398
380
  if (result.code !== 0) {
399
381
  errors.push(`${key}: ${result.stderr.slice(0, 200)}`);
400
382
  } else {
@@ -103,10 +103,10 @@ export function saveActivityLog(
103
103
  basePath: string,
104
104
  unitType: string,
105
105
  unitId: string,
106
- ): void {
106
+ ): string | null {
107
107
  try {
108
108
  const entries = ctx.sessionManager.getEntries();
109
- if (!entries || entries.length === 0) return;
109
+ if (!entries || entries.length === 0) return null;
110
110
 
111
111
  const activityDir = join(gsdRoot(basePath), "activity");
112
112
  mkdirSync(activityDir, { recursive: true });
@@ -116,7 +116,7 @@ export function saveActivityLog(
116
116
  const unitKey = `${unitType}\0${safeUnitId}`;
117
117
  // Use lightweight fingerprint instead of serializing all entries (#611)
118
118
  const key = snapshotKey(unitType, safeUnitId, entries);
119
- if (state.lastSnapshotKeyByUnit.get(unitKey) === key) return;
119
+ if (state.lastSnapshotKeyByUnit.get(unitKey) === key) return null;
120
120
 
121
121
  const filePath = nextActivityFilePath(activityDir, state, unitType, safeUnitId);
122
122
  // Stream entries to disk line-by-line instead of building one massive string (#611).
@@ -131,9 +131,11 @@ export function saveActivityLog(
131
131
  }
132
132
  state.nextSeq += 1;
133
133
  state.lastSnapshotKeyByUnit.set(unitKey, key);
134
+ return filePath;
134
135
  } catch (e) {
135
136
  // Don't let logging failures break auto-mode
136
137
  void e;
138
+ return null;
137
139
  }
138
140
  }
139
141
 
@@ -637,6 +637,12 @@ export async function buildPlanSlicePrompt(
637
637
  const executorContextConstraints = formatExecutorConstraints();
638
638
 
639
639
  const outputRelPath = relSliceFile(base, mid, sid, "PLAN");
640
+ const prefs = loadEffectiveGSDPreferences();
641
+ const commitDocsEnabled = prefs?.preferences?.git?.commit_docs !== false;
642
+ const commitInstruction = commitDocsEnabled
643
+ ? `Commit: \`docs(${sid}): add slice plan\``
644
+ : "Do not commit — planning docs are not tracked in git for this project.";
645
+
640
646
  return loadPrompt("plan-slice", {
641
647
  workingDirectory: base,
642
648
  milestoneId: mid, sliceId: sid, sliceTitle: sTitle,
@@ -647,6 +653,7 @@ export async function buildPlanSlicePrompt(
647
653
  inlinedContext,
648
654
  dependencySummaries: depContent,
649
655
  executorContextConstraints,
656
+ commitInstruction,
650
657
  });
651
658
  }
652
659
 
@@ -1071,6 +1078,12 @@ export async function buildReassessRoadmapPrompt(
1071
1078
  // Non-fatal — captures module may not be available
1072
1079
  }
1073
1080
 
1081
+ const reassessPrefs = loadEffectiveGSDPreferences();
1082
+ const reassessCommitDocsEnabled = reassessPrefs?.preferences?.git?.commit_docs !== false;
1083
+ const reassessCommitInstruction = reassessCommitDocsEnabled
1084
+ ? `Commit: \`docs(${mid}): reassess roadmap after ${completedSliceId}\``
1085
+ : "Do not commit — planning docs are not tracked in git for this project.";
1086
+
1074
1087
  return loadPrompt("reassess-roadmap", {
1075
1088
  workingDirectory: base,
1076
1089
  milestoneId: mid,
@@ -1081,6 +1094,7 @@ export async function buildReassessRoadmapPrompt(
1081
1094
  assessmentPath,
1082
1095
  inlinedContext,
1083
1096
  deferredCaptures,
1097
+ commitInstruction: reassessCommitInstruction,
1084
1098
  });
1085
1099
  }
1086
1100
 
@@ -90,6 +90,10 @@ export function resolveExpectedArtifactPath(unitType: string, unitId: string, ba
90
90
  const dir = resolveMilestonePath(base, mid);
91
91
  return dir ? join(dir, buildMilestoneFileName(mid, "SUMMARY")) : null;
92
92
  }
93
+ case "replan-slice": {
94
+ const dir = resolveSlicePath(base, mid, sid!);
95
+ return dir ? join(dir, buildSliceFileName(sid!, "REPLAN")) : null;
96
+ }
93
97
  case "rewrite-docs":
94
98
  return null;
95
99
  default:
@@ -127,10 +131,9 @@ export function verifyExpectedArtifact(unitType: string, unitId: string, base: s
127
131
  }
128
132
 
129
133
  const absPath = resolveExpectedArtifactPath(unitType, unitId, base);
130
- // Unit types with no verifiable artifact always pass (e.g. replan-slice).
131
- // For all other types, null means the parent directory is missing on disk
132
- // treat as stale completion state so the key gets evicted (#313).
133
- if (!absPath) return unitType === "replan-slice";
134
+ // For unit types with no verifiable artifact (null path), the parent directory
135
+ // is missing on disk treat as stale completion state so the key gets evicted (#313).
136
+ if (!absPath) return false;
134
137
  if (!existsSync(absPath)) return false;
135
138
 
136
139
  // plan-slice must produce a plan with actual task entries, not just a scaffold.