gsd-pi 2.77.0-dev.58d3d4d6c → 2.77.0-dev.cfd69e714

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 (429) hide show
  1. package/README.md +1 -1
  2. package/dist/claude-cli-check.js +5 -1
  3. package/dist/headless.js +49 -4
  4. package/dist/resource-loader.d.ts +40 -0
  5. package/dist/resource-loader.js +32 -13
  6. package/dist/resources/extensions/browser-tools/capture.js +9 -0
  7. package/dist/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +8 -59
  8. package/dist/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +36 -24
  9. package/dist/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +69 -71
  10. package/dist/resources/extensions/browser-tools/tools/forms.js +5 -1
  11. package/dist/resources/extensions/browser-tools/tools/intent.js +5 -1
  12. package/dist/resources/extensions/claude-code-cli/readiness.js +5 -1
  13. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +481 -17
  14. package/dist/resources/extensions/gsd/auto/loop.js +43 -0
  15. package/dist/resources/extensions/gsd/auto/phases.js +15 -21
  16. package/dist/resources/extensions/gsd/auto/session.js +0 -2
  17. package/dist/resources/extensions/gsd/auto-dispatch.js +102 -24
  18. package/dist/resources/extensions/gsd/auto-model-selection.js +124 -4
  19. package/dist/resources/extensions/gsd/auto-post-unit.js +71 -64
  20. package/dist/resources/extensions/gsd/auto-prompts.js +329 -102
  21. package/dist/resources/extensions/gsd/auto-recovery.js +195 -23
  22. package/dist/resources/extensions/gsd/auto-start.js +34 -24
  23. package/dist/resources/extensions/gsd/auto-tool-tracking.js +47 -7
  24. package/dist/resources/extensions/gsd/auto-worktree.js +122 -26
  25. package/dist/resources/extensions/gsd/auto.js +31 -20
  26. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +9 -1
  27. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +209 -0
  28. package/dist/resources/extensions/gsd/bootstrap/provider-error-resume.js +3 -6
  29. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +7 -3
  30. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +127 -9
  31. package/dist/resources/extensions/gsd/component-loader.js +447 -0
  32. package/dist/resources/extensions/gsd/component-types.js +69 -0
  33. package/dist/resources/extensions/gsd/detection.js +49 -1
  34. package/dist/resources/extensions/gsd/docs/preferences-reference.md +1 -1
  35. package/dist/resources/extensions/gsd/gate-registry.js +2 -2
  36. package/dist/resources/extensions/gsd/git-constants.js +28 -1
  37. package/dist/resources/extensions/gsd/git-self-heal.js +27 -0
  38. package/dist/resources/extensions/gsd/git-service.js +126 -2
  39. package/dist/resources/extensions/gsd/gsd-db.js +6 -3
  40. package/dist/resources/extensions/gsd/guided-flow.js +17 -5
  41. package/dist/resources/extensions/gsd/memory-extractor.js +7 -1
  42. package/dist/resources/extensions/gsd/milestone-scope-classifier.js +299 -0
  43. package/dist/resources/extensions/gsd/model-cost-table.js +3 -0
  44. package/dist/resources/extensions/gsd/model-router.js +6 -0
  45. package/dist/resources/extensions/gsd/native-git-bridge.js +34 -4
  46. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +6 -2
  47. package/dist/resources/extensions/gsd/prompts/plan-slice.md +15 -2
  48. package/dist/resources/extensions/gsd/safety/git-checkpoint.js +11 -0
  49. package/dist/resources/extensions/gsd/service-tier.js +5 -2
  50. package/dist/resources/extensions/gsd/session-lock.js +19 -10
  51. package/dist/resources/extensions/gsd/skill-manifest.js +168 -0
  52. package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +278 -8
  53. package/dist/resources/extensions/gsd/state.js +44 -33
  54. package/dist/resources/extensions/gsd/sync-lock.js +98 -42
  55. package/dist/resources/extensions/gsd/unit-context-composer.js +147 -0
  56. package/dist/resources/extensions/gsd/unit-context-manifest.js +370 -0
  57. package/dist/resources/extensions/gsd/uok/gate-runner.js +53 -5
  58. package/dist/resources/extensions/gsd/workflow-mcp.js +6 -0
  59. package/dist/resources/extensions/gsd/worktree-manager.js +34 -8
  60. package/dist/resources/extensions/mcp-client/index.js +3 -1
  61. package/dist/resources/extensions/ollama/index.js +5 -1
  62. package/dist/resources/extensions/remote-questions/manager.js +11 -5
  63. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  64. package/dist/web/standalone/.next/BUILD_ID +1 -1
  65. package/dist/web/standalone/.next/app-path-routes-manifest.json +5 -5
  66. package/dist/web/standalone/.next/build-manifest.json +2 -2
  67. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  68. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  69. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  77. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  85. package/dist/web/standalone/.next/server/app/index.html +1 -1
  86. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  88. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  91. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app-paths-manifest.json +5 -5
  93. package/dist/web/standalone/.next/server/chunks/1926.js +1 -1
  94. package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
  95. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  96. package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
  97. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  98. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  99. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  100. package/package.json +2 -3
  101. package/packages/daemon/src/logger.ts +4 -3
  102. package/packages/mcp-server/dist/server.d.ts +24 -0
  103. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  104. package/packages/mcp-server/dist/server.js +88 -87
  105. package/packages/mcp-server/dist/server.js.map +1 -1
  106. package/packages/mcp-server/src/mcp-server.test.ts +25 -3
  107. package/packages/mcp-server/src/readers/graph.test.ts +87 -15
  108. package/packages/mcp-server/src/secure-env-collect.test.ts +232 -237
  109. package/packages/mcp-server/src/server.ts +131 -105
  110. package/packages/mcp-server/src/workflow-tools.test.ts +80 -39
  111. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  112. package/packages/native/package.json +1 -1
  113. package/packages/native/src/__tests__/_test-coverage-guard.test.mjs +98 -0
  114. package/packages/native/src/__tests__/module-compat.test.mjs +59 -27
  115. package/packages/native/src/__tests__/ps.test.mjs +14 -8
  116. package/packages/native/src/__tests__/stream-process.test.mjs +23 -2
  117. package/packages/native/src/__tests__/truncate.test.mjs +17 -2
  118. package/packages/pi-agent-core/src/agent-loop.test.ts +5 -15
  119. package/packages/pi-agent-core/src/agent.test.ts +96 -102
  120. package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
  121. package/packages/pi-ai/dist/models/capability-patches.d.ts.map +1 -1
  122. package/packages/pi-ai/dist/models/capability-patches.js +9 -2
  123. package/packages/pi-ai/dist/models/capability-patches.js.map +1 -1
  124. package/packages/pi-ai/dist/models/generated/index.d.ts +34 -0
  125. package/packages/pi-ai/dist/models/generated/index.d.ts.map +1 -1
  126. package/packages/pi-ai/dist/models/generated/openai-codex.d.ts +17 -0
  127. package/packages/pi-ai/dist/models/generated/openai-codex.d.ts.map +1 -1
  128. package/packages/pi-ai/dist/models/generated/openai-codex.js +17 -0
  129. package/packages/pi-ai/dist/models/generated/openai-codex.js.map +1 -1
  130. package/packages/pi-ai/dist/models/generated/openai.d.ts +17 -0
  131. package/packages/pi-ai/dist/models/generated/openai.d.ts.map +1 -1
  132. package/packages/pi-ai/dist/models/generated/openai.js +17 -0
  133. package/packages/pi-ai/dist/models/generated/openai.js.map +1 -1
  134. package/packages/pi-ai/dist/models.generated.test.js +43 -70
  135. package/packages/pi-ai/dist/models.generated.test.js.map +1 -1
  136. package/packages/pi-ai/dist/models.test.js +36 -11
  137. package/packages/pi-ai/dist/models.test.js.map +1 -1
  138. package/packages/pi-ai/scripts/generate-models.ts +44 -0
  139. package/packages/pi-ai/src/models/capability-patches.ts +10 -2
  140. package/packages/pi-ai/src/models/generated/openai-codex.ts +17 -0
  141. package/packages/pi-ai/src/models/generated/openai.ts +17 -0
  142. package/packages/pi-ai/src/models.generated.test.ts +46 -73
  143. package/packages/pi-ai/src/models.test.ts +48 -11
  144. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  145. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js +96 -32
  146. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js.map +1 -1
  147. package/packages/pi-coding-agent/dist/core/agent-session-model-switch.test.js +75 -12
  148. package/packages/pi-coding-agent/dist/core/agent-session-model-switch.test.js.map +1 -1
  149. package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js +99 -31
  150. package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js.map +1 -1
  151. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts +5 -0
  152. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  153. package/packages/pi-coding-agent/dist/core/extensions/loader.js +61 -0
  154. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  155. package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.js +30 -4
  156. package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.js.map +1 -1
  157. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js +17 -0
  158. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js.map +1 -1
  159. package/packages/pi-coding-agent/dist/core/resource-loader-cache-reset.test.js +76 -18
  160. package/packages/pi-coding-agent/dist/core/resource-loader-cache-reset.test.js.map +1 -1
  161. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -1
  162. package/packages/pi-coding-agent/dist/core/retry-handler.js +2 -6
  163. package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
  164. package/packages/pi-coding-agent/dist/core/retry-handler.test.js +5 -1
  165. package/packages/pi-coding-agent/dist/core/retry-handler.test.js.map +1 -1
  166. package/packages/pi-coding-agent/dist/core/retryable-error-regex.d.ts +18 -0
  167. package/packages/pi-coding-agent/dist/core/retryable-error-regex.d.ts.map +1 -0
  168. package/packages/pi-coding-agent/dist/core/retryable-error-regex.js +18 -0
  169. package/packages/pi-coding-agent/dist/core/retryable-error-regex.js.map +1 -0
  170. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts +20 -0
  171. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  172. package/packages/pi-coding-agent/dist/core/system-prompt.js +16 -2
  173. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  174. package/packages/pi-coding-agent/dist/index.d.ts +1 -0
  175. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  176. package/packages/pi-coding-agent/dist/index.js +1 -0
  177. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  178. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.js +20 -13
  179. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.js.map +1 -1
  180. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
  181. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +18 -3
  182. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  183. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js +125 -0
  184. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js.map +1 -1
  185. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +2 -0
  186. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
  187. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
  188. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +4 -0
  189. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  190. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +105 -13
  191. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  192. package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.d.ts +2 -0
  193. package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.d.ts.map +1 -0
  194. package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.js +130 -0
  195. package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.js.map +1 -0
  196. package/packages/pi-coding-agent/src/core/agent-session-abort-order.test.ts +113 -37
  197. package/packages/pi-coding-agent/src/core/agent-session-model-switch.test.ts +89 -17
  198. package/packages/pi-coding-agent/src/core/agent-session-tool-refresh.test.ts +112 -43
  199. package/packages/pi-coding-agent/src/core/extensions/loader.ts +58 -0
  200. package/packages/pi-coding-agent/src/core/lsp/lsp-integration.test.ts +35 -4
  201. package/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts +20 -0
  202. package/packages/pi-coding-agent/src/core/resource-loader-cache-reset.test.ts +93 -28
  203. package/packages/pi-coding-agent/src/core/retry-handler.test.ts +5 -1
  204. package/packages/pi-coding-agent/src/core/retry-handler.ts +2 -8
  205. package/packages/pi-coding-agent/src/core/retryable-error-regex.ts +18 -0
  206. package/packages/pi-coding-agent/src/core/system-prompt.ts +35 -1
  207. package/packages/pi-coding-agent/src/index.ts +1 -0
  208. package/packages/pi-coding-agent/src/modes/interactive/components/dynamic-border.test.ts +26 -20
  209. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.test.ts +146 -1
  210. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +20 -3
  211. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +2 -0
  212. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +119 -13
  213. package/packages/pi-coding-agent/src/tests/system-prompt-skill-filter.test.ts +157 -0
  214. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  215. package/packages/pi-tui/dist/__tests__/autocomplete.test.js +18 -8
  216. package/packages/pi-tui/dist/__tests__/autocomplete.test.js.map +1 -1
  217. package/packages/pi-tui/dist/__tests__/overlay-layout.test.js +128 -17
  218. package/packages/pi-tui/dist/__tests__/overlay-layout.test.js.map +1 -1
  219. package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js +36 -12
  220. package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js.map +1 -1
  221. package/packages/pi-tui/dist/__tests__/tui.test.js +18 -30
  222. package/packages/pi-tui/dist/__tests__/tui.test.js.map +1 -1
  223. package/packages/pi-tui/dist/components/__tests__/input.test.js +10 -3
  224. package/packages/pi-tui/dist/components/__tests__/input.test.js.map +1 -1
  225. package/packages/pi-tui/dist/components/__tests__/loader.test.js +53 -9
  226. package/packages/pi-tui/dist/components/__tests__/loader.test.js.map +1 -1
  227. package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.js +6 -2
  228. package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.js.map +1 -1
  229. package/packages/pi-tui/dist/components/editor.d.ts +14 -0
  230. package/packages/pi-tui/dist/components/editor.d.ts.map +1 -1
  231. package/packages/pi-tui/dist/components/editor.js +19 -0
  232. package/packages/pi-tui/dist/components/editor.js.map +1 -1
  233. package/packages/pi-tui/dist/components/image.test.js +6 -5
  234. package/packages/pi-tui/dist/components/image.test.js.map +1 -1
  235. package/packages/pi-tui/dist/editor-component.d.ts +2 -0
  236. package/packages/pi-tui/dist/editor-component.d.ts.map +1 -1
  237. package/packages/pi-tui/dist/editor-component.js.map +1 -1
  238. package/packages/pi-tui/src/__tests__/autocomplete.test.ts +24 -8
  239. package/packages/pi-tui/src/__tests__/overlay-layout.test.ts +140 -17
  240. package/packages/pi-tui/src/__tests__/stdin-buffer.test.ts +41 -12
  241. package/packages/pi-tui/src/__tests__/tui.test.ts +18 -37
  242. package/packages/pi-tui/src/components/__tests__/input.test.ts +19 -3
  243. package/packages/pi-tui/src/components/__tests__/loader.test.ts +112 -35
  244. package/packages/pi-tui/src/components/__tests__/markdown-maxlines.test.ts +9 -2
  245. package/packages/pi-tui/src/components/editor.ts +22 -0
  246. package/packages/pi-tui/src/components/image.test.ts +10 -5
  247. package/packages/pi-tui/src/editor-component.ts +3 -0
  248. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  249. package/packages/rpc-client/dist/rpc-client.test.js +101 -51
  250. package/packages/rpc-client/dist/rpc-client.test.js.map +1 -1
  251. package/packages/rpc-client/src/rpc-client.test.ts +109 -52
  252. package/packages/rpc-client/tsconfig.tsbuildinfo +1 -1
  253. package/scripts/install.js +15 -1
  254. package/src/resources/extensions/browser-tools/capture.ts +12 -0
  255. package/src/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +8 -59
  256. package/src/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +36 -24
  257. package/src/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +69 -71
  258. package/src/resources/extensions/browser-tools/tools/forms.ts +5 -1
  259. package/src/resources/extensions/browser-tools/tools/intent.ts +5 -1
  260. package/src/resources/extensions/claude-code-cli/readiness.ts +5 -1
  261. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +518 -19
  262. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +919 -75
  263. package/src/resources/extensions/github-sync/tests/cli.test.ts +76 -7
  264. package/src/resources/extensions/github-sync/tests/templates.test.ts +33 -1
  265. package/src/resources/extensions/gsd/auto/loop.ts +47 -0
  266. package/src/resources/extensions/gsd/auto/phases.ts +16 -20
  267. package/src/resources/extensions/gsd/auto/session.ts +0 -2
  268. package/src/resources/extensions/gsd/auto-dispatch.ts +113 -24
  269. package/src/resources/extensions/gsd/auto-model-selection.ts +131 -4
  270. package/src/resources/extensions/gsd/auto-post-unit.ts +82 -73
  271. package/src/resources/extensions/gsd/auto-prompts.ts +330 -90
  272. package/src/resources/extensions/gsd/auto-recovery.ts +225 -24
  273. package/src/resources/extensions/gsd/auto-start.ts +54 -6
  274. package/src/resources/extensions/gsd/auto-tool-tracking.ts +51 -7
  275. package/src/resources/extensions/gsd/auto-worktree.ts +130 -26
  276. package/src/resources/extensions/gsd/auto.ts +43 -22
  277. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +9 -1
  278. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +221 -0
  279. package/src/resources/extensions/gsd/bootstrap/provider-error-resume.ts +3 -7
  280. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +7 -3
  281. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +158 -9
  282. package/src/resources/extensions/gsd/component-loader.ts +598 -0
  283. package/src/resources/extensions/gsd/component-types.ts +362 -0
  284. package/src/resources/extensions/gsd/detection.ts +58 -1
  285. package/src/resources/extensions/gsd/docs/preferences-reference.md +1 -1
  286. package/src/resources/extensions/gsd/gate-registry.ts +2 -2
  287. package/src/resources/extensions/gsd/git-constants.ts +30 -1
  288. package/src/resources/extensions/gsd/git-self-heal.ts +31 -0
  289. package/src/resources/extensions/gsd/git-service.ts +133 -2
  290. package/src/resources/extensions/gsd/gsd-db.ts +6 -3
  291. package/src/resources/extensions/gsd/guided-flow.ts +20 -5
  292. package/src/resources/extensions/gsd/memory-extractor.ts +11 -3
  293. package/src/resources/extensions/gsd/milestone-scope-classifier.ts +366 -0
  294. package/src/resources/extensions/gsd/model-cost-table.ts +3 -0
  295. package/src/resources/extensions/gsd/model-router.ts +6 -0
  296. package/src/resources/extensions/gsd/native-git-bridge.ts +34 -4
  297. package/src/resources/extensions/gsd/prompts/complete-milestone.md +6 -2
  298. package/src/resources/extensions/gsd/prompts/plan-slice.md +15 -2
  299. package/src/resources/extensions/gsd/safety/git-checkpoint.ts +15 -0
  300. package/src/resources/extensions/gsd/service-tier.ts +5 -2
  301. package/src/resources/extensions/gsd/session-lock.ts +20 -10
  302. package/src/resources/extensions/gsd/skill-manifest.ts +175 -0
  303. package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +309 -8
  304. package/src/resources/extensions/gsd/state.ts +49 -44
  305. package/src/resources/extensions/gsd/sync-lock.ts +97 -39
  306. package/src/resources/extensions/gsd/tests/artifact-retry-cap.test.ts +270 -0
  307. package/src/resources/extensions/gsd/tests/auto-deterministic-error-classification-4973.test.ts +341 -0
  308. package/src/resources/extensions/gsd/tests/auto-discuss-milestone-deadlock-4973.test.ts +264 -0
  309. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +94 -289
  310. package/src/resources/extensions/gsd/tests/auto-model-selection-tool-poisoning.test.ts +742 -0
  311. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +78 -0
  312. package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +61 -0
  313. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +93 -0
  314. package/src/resources/extensions/gsd/tests/auto-retry-mcp-churn-fixes.test.ts +8 -197
  315. package/src/resources/extensions/gsd/tests/auto-start-needs-discussion.test.ts +15 -58
  316. package/src/resources/extensions/gsd/tests/cache-staleness-regression.test.ts +17 -21
  317. package/src/resources/extensions/gsd/tests/complete-milestone-excerpt.test.ts +263 -0
  318. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +25 -0
  319. package/src/resources/extensions/gsd/tests/complete-slice-composer.test.ts +192 -0
  320. package/src/resources/extensions/gsd/tests/complete-task.test.ts +16 -8
  321. package/src/resources/extensions/gsd/tests/component-loader.test.ts +589 -0
  322. package/src/resources/extensions/gsd/tests/component-types.test.ts +127 -0
  323. package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +50 -1
  324. package/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts +3 -3
  325. package/src/resources/extensions/gsd/tests/derive-state-db-disk-reconcile.test.ts +40 -0
  326. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +91 -3
  327. package/src/resources/extensions/gsd/tests/derive-state.test.ts +4 -3
  328. package/src/resources/extensions/gsd/tests/dispatcher-stuck-planning.test.ts +3 -2
  329. package/src/resources/extensions/gsd/tests/extension-bootstrap-isolation.test.ts +139 -129
  330. package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +9 -105
  331. package/src/resources/extensions/gsd/tests/gate-state-canonicalization.test.ts +102 -0
  332. package/src/resources/extensions/gsd/tests/gate-storage.test.ts +1 -1
  333. package/src/resources/extensions/gsd/tests/hook-key-parsing.test.ts +4 -55
  334. package/src/resources/extensions/gsd/tests/integration/all-milestones-complete-merge.test.ts +7 -57
  335. package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +20 -0
  336. package/src/resources/extensions/gsd/tests/integration/doctor-proactive.test.ts +18 -2
  337. package/src/resources/extensions/gsd/tests/integration/queue-completed-milestone-perf.test.ts +10 -4
  338. package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +144 -7
  339. package/src/resources/extensions/gsd/tests/integration/state-machine-live-validation.test.ts +4 -0
  340. package/src/resources/extensions/gsd/tests/integration/state-machine-runtime-failures.test.ts +2 -16
  341. package/src/resources/extensions/gsd/tests/interrupted-session-ui.test.ts +6 -9
  342. package/src/resources/extensions/gsd/tests/mcp-client-security.test.ts +8 -37
  343. package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +5 -15
  344. package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +227 -62
  345. package/src/resources/extensions/gsd/tests/milestone-scope-classifier.test.ts +187 -0
  346. package/src/resources/extensions/gsd/tests/model-cost-table.test.ts +9 -1
  347. package/src/resources/extensions/gsd/tests/model-router.test.ts +1 -1
  348. package/src/resources/extensions/gsd/tests/native-git-bridge-exec-fallback.test.ts +6 -49
  349. package/src/resources/extensions/gsd/tests/notification-widget.test.ts +6 -3
  350. package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +273 -133
  351. package/src/resources/extensions/gsd/tests/pipeline-variant-dispatch.test.ts +301 -0
  352. package/src/resources/extensions/gsd/tests/pre-execution-pause-wiring.test.ts +32 -1
  353. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +23 -24
  354. package/src/resources/extensions/gsd/tests/queue-auto-guard.test.ts +32 -0
  355. package/src/resources/extensions/gsd/tests/ready-phrase-no-files-4573.test.ts +75 -2
  356. package/src/resources/extensions/gsd/tests/reassess-default-optin.test.ts +132 -0
  357. package/src/resources/extensions/gsd/tests/recovery-attempts-reset.test.ts +8 -40
  358. package/src/resources/extensions/gsd/tests/regex-hardening.test.ts +136 -256
  359. package/src/resources/extensions/gsd/tests/research-milestone-composer.test.ts +114 -0
  360. package/src/resources/extensions/gsd/tests/run-uat-composer.test.ts +148 -0
  361. package/src/resources/extensions/gsd/tests/service-tier.test.ts +4 -0
  362. package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +29 -0
  363. package/src/resources/extensions/gsd/tests/silent-catch-diagnostics.test.ts +55 -95
  364. package/src/resources/extensions/gsd/tests/single-writer-v3-tool-surface.test.ts +158 -0
  365. package/src/resources/extensions/gsd/tests/skill-activation.test.ts +120 -1
  366. package/src/resources/extensions/gsd/tests/skill-manifest.test.ts +112 -0
  367. package/src/resources/extensions/gsd/tests/slice-parallel-orchestrator.test.ts +164 -1
  368. package/src/resources/extensions/gsd/tests/state-machine-full-walkthrough.test.ts +5 -5
  369. package/src/resources/extensions/gsd/tests/structured-data-formatter.test.ts +11 -92
  370. package/src/resources/extensions/gsd/tests/survivor-branch-complete.test.ts +102 -101
  371. package/src/resources/extensions/gsd/tests/sync-lock.test.ts +31 -0
  372. package/src/resources/extensions/gsd/tests/test-helpers.test.ts +12 -61
  373. package/src/resources/extensions/gsd/tests/test-helpers.ts +21 -8
  374. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +61 -1
  375. package/src/resources/extensions/gsd/tests/tool-naming.test.ts +8 -1
  376. package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +355 -0
  377. package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +258 -0
  378. package/src/resources/extensions/gsd/tests/uok-gate-runner.test.ts +75 -0
  379. package/src/resources/extensions/gsd/tests/uok-gitops-wiring.test.ts +49 -26
  380. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +1 -0
  381. package/src/resources/extensions/gsd/tests/verify-artifact-tightened.test.ts +144 -81
  382. package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +20 -54
  383. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +342 -277
  384. package/src/resources/extensions/gsd/tests/worker-model-override.test.ts +37 -29
  385. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +226 -266
  386. package/src/resources/extensions/gsd/tests/worktree-health-monorepo.test.ts +103 -67
  387. package/src/resources/extensions/gsd/tests/worktree-nested-git-safety.test.ts +92 -90
  388. package/src/resources/extensions/gsd/tests/worktree-submodule-safety.test.ts +238 -59
  389. package/src/resources/extensions/gsd/tests/worktree-sync-overwrite-loop.test.ts +113 -161
  390. package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +262 -0
  391. package/src/resources/extensions/gsd/tests/write-gate-predicates.test.ts +186 -0
  392. package/src/resources/extensions/gsd/tests/write-gate.test.ts +7 -5
  393. package/src/resources/extensions/gsd/tests/zombie-gsd-state.test.ts +80 -96
  394. package/src/resources/extensions/gsd/types.ts +3 -3
  395. package/src/resources/extensions/gsd/unit-context-composer.ts +218 -0
  396. package/src/resources/extensions/gsd/unit-context-manifest.ts +574 -0
  397. package/src/resources/extensions/gsd/uok/gate-runner.ts +65 -5
  398. package/src/resources/extensions/gsd/workflow-mcp.ts +6 -0
  399. package/src/resources/extensions/gsd/worktree-manager.ts +55 -7
  400. package/src/resources/extensions/mcp-client/index.ts +3 -1
  401. package/src/resources/extensions/mcp-client/tests/server-name-spaces.test.ts +70 -36
  402. package/src/resources/extensions/ollama/index.ts +5 -1
  403. package/src/resources/extensions/ollama/ollama-auth-mode.test.ts +123 -15
  404. package/src/resources/extensions/ollama/ollama-status-indicator.test.ts +206 -19
  405. package/src/resources/extensions/remote-questions/manager.ts +36 -4
  406. package/src/resources/extensions/remote-questions/tests/command-polling.test.ts +200 -190
  407. package/src/resources/extensions/shared/tests/interview-preview.test.ts +11 -3
  408. package/src/resources/extensions/voice/tests/linux-ready.test.ts +129 -113
  409. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.d.ts +0 -2
  410. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.d.ts.map +0 -1
  411. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.js +0 -289
  412. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.js.map +0 -1
  413. package/packages/pi-ai/src/utils/oauth/oauth-providers.test.ts +0 -363
  414. package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +0 -144
  415. package/src/resources/extensions/gsd/tests/complete-milestone-false-merge.test.ts +0 -157
  416. package/src/resources/extensions/gsd/tests/dashboard-model-label-ordering.test.ts +0 -107
  417. package/src/resources/extensions/gsd/tests/find-missing-summaries-closed.test.ts +0 -48
  418. package/src/resources/extensions/gsd/tests/forensics-context-persist.test.ts +0 -159
  419. package/src/resources/extensions/gsd/tests/forensics-db-completion.test.ts +0 -96
  420. package/src/resources/extensions/gsd/tests/forensics-dedup.test.ts +0 -79
  421. package/src/resources/extensions/gsd/tests/forensics-hook-key-parse.test.ts +0 -75
  422. package/src/resources/extensions/gsd/tests/forensics-journal.test.ts +0 -162
  423. package/src/resources/extensions/gsd/tests/forensics-worktree-telemetry.test.ts +0 -145
  424. package/src/resources/extensions/gsd/tests/gitignore-bg-shell.test.ts +0 -38
  425. package/src/resources/extensions/gsd/tests/gsd-no-project-error.test.ts +0 -73
  426. package/src/resources/extensions/gsd/tests/idle-watchdog-stall-override.test.ts +0 -130
  427. package/src/resources/extensions/gsd/tests/import-done-milestones.test.ts +0 -43
  428. /package/dist/web/standalone/.next/static/{Cev5xrAYA3ZGTRLyjR2fX → SvCJDZPQW104bR1KnBQg1}/_buildManifest.js +0 -0
  429. /package/dist/web/standalone/.next/static/{Cev5xrAYA3ZGTRLyjR2fX → SvCJDZPQW104bR1KnBQg1}/_ssgManifest.js +0 -0
@@ -33,6 +33,7 @@ const MCP_WORKFLOW_TOOL_SURFACE = new Set([
33
33
  "gsd_journal_query",
34
34
  "gsd_milestone_complete",
35
35
  "gsd_milestone_generate_id",
36
+ "gsd_milestone_reopen",
36
37
  "gsd_checkpoint_db",
37
38
  "gsd_milestone_status",
38
39
  "gsd_milestone_validate",
@@ -41,6 +42,9 @@ const MCP_WORKFLOW_TOOL_SURFACE = new Set([
41
42
  "gsd_plan_slice",
42
43
  "gsd_replan_slice",
43
44
  "gsd_reassess_roadmap",
45
+ "gsd_reopen_milestone",
46
+ "gsd_reopen_slice",
47
+ "gsd_reopen_task",
44
48
  "gsd_requirement_save",
45
49
  "gsd_requirement_update",
46
50
  "gsd_roadmap_reassess",
@@ -50,9 +54,11 @@ const MCP_WORKFLOW_TOOL_SURFACE = new Set([
50
54
  "gsd_skip_slice",
51
55
  "gsd_slice_replan",
52
56
  "gsd_slice_complete",
57
+ "gsd_slice_reopen",
53
58
  "gsd_summary_save",
54
59
  "gsd_task_plan",
55
60
  "gsd_task_complete",
61
+ "gsd_task_reopen",
56
62
  "gsd_update_requirement",
57
63
  "gsd_validate_milestone",
58
64
  ]);
@@ -30,6 +30,7 @@ import {
30
30
  nativeDiffNameStatus,
31
31
  nativeDiffNumstat,
32
32
  nativeGetCurrentBranch,
33
+ nativeIsAncestor,
33
34
  nativeLogOneline,
34
35
  nativeMergeSquash,
35
36
  nativeWorktreeAdd,
@@ -227,6 +228,17 @@ export function createWorktree(basePath: string, name: string, opts: { branch?:
227
228
  // otherwise fall back to the repo's detected main branch.
228
229
  const startPoint = opts.startPoint ?? nativeDetectMainBranch(basePath);
229
230
 
231
+ // Reject early if startPoint resolves to an empty/invalid ref. On an
232
+ // unborn branch (zero-commit repo) nativeDetectMainBranch returns "",
233
+ // which would flow into `git worktree add ... ""` and crash with
234
+ // `fatal: not a valid object name`. (Issue #4980 HIGH-9)
235
+ if (!startPoint || startPoint.length === 0) {
236
+ throw new GSDError(
237
+ GSD_GIT_ERROR,
238
+ "Repository has no commits yet (unborn branch). Make an initial commit before creating worktrees.",
239
+ );
240
+ }
241
+
230
242
  // Check if the branch already exists (leftover from a previous worktree)
231
243
  const branchAlreadyExists = nativeBranchExists(basePath, branch);
232
244
 
@@ -249,6 +261,21 @@ export function createWorktree(basePath: string, name: string, opts: { branch?:
249
261
  // from prior sessions that must not be reset.
250
262
  nativeWorktreeAdd(basePath, wtPath, branch);
251
263
  } else {
264
+ // Ancestry guard: refuse to force-reset a branch that has commits not
265
+ // reachable from startPoint. A crash-then-resume cycle that didn't
266
+ // write the resume file would silently orphan prior-session commits
267
+ // (recoverable from reflog for 90d, then gone — branch is also
268
+ // deleted at teardown). (Issue #4980 HIGH-3)
269
+ const branchIsAncestor = nativeIsAncestor(basePath, branch, startPoint);
270
+ if (!branchIsAncestor) {
271
+ throw new GSDError(
272
+ GSD_GIT_ERROR,
273
+ `Branch "${branch}" already exists with commits not reachable from "${startPoint}". ` +
274
+ `Refusing to force-reset — would orphan prior work. ` +
275
+ `If you intend to keep those commits, retry with reuseExistingBranch=true. ` +
276
+ `If you intend to discard, run \`git branch -D ${branch}\` manually first.`,
277
+ );
278
+ }
252
279
  // Reset the stale branch to the start point, then attach worktree to it
253
280
  nativeBranchForceReset(basePath, branch, startPoint);
254
281
  nativeWorktreeAdd(basePath, wtPath, branch);
@@ -504,17 +531,38 @@ export function removeWorktree(
504
531
  (line: string) => line.startsWith("+") || line.startsWith("-"),
505
532
  );
506
533
  if (hasSubmoduleChanges) {
507
- // Stash submodule changes so they are not lost during force removal.
508
- // The stash is created in the worktree before it's torn down.
534
+ // Save submodule changes to a labeled rescue branch instead of the
535
+ // shared stash list. Stash is per-repo (not per-worktree), so an
536
+ // entry created here would appear in the user's main-tree stash
537
+ // list and reference paths that disappear after worktree removal.
538
+ // A branch persists in the shared .git refs after worktree removal
539
+ // and is discoverable via `git branch --list 'gsd/submodule-rescue/*'`.
540
+ // (Issue #4980 HIGH-11)
541
+ const rescueBranch = `gsd/submodule-rescue/${name}-${Date.now()}`;
509
542
  try {
510
543
  execFileSync(
511
- "git", ["stash", "push", "-m", "gsd: auto-stash submodule changes before worktree teardown"],
544
+ "git", ["add", "-A"],
512
545
  { cwd: resolvedWtPath, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" },
513
546
  );
514
- logWarning("reconcile", `Stashed uncommitted submodule changes before worktree teardown`, { worktree: name, path: resolvedWtPath });
515
- } catch {
516
- // Stash failed warn the user that submodule changes may be lost
517
- logWarning("reconcile", `Submodule changes detected — stash failed, changes may be lost during force removal`, { worktree: name, path: resolvedWtPath });
547
+ execFileSync(
548
+ "git", ["commit", "-m", `gsd: rescue submodule changes from worktree ${name}`, "--allow-empty"],
549
+ { cwd: resolvedWtPath, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" },
550
+ );
551
+ execFileSync(
552
+ "git", ["branch", rescueBranch, "HEAD"],
553
+ { cwd: resolvedWtPath, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" },
554
+ );
555
+ logWarning(
556
+ "reconcile",
557
+ `Saved uncommitted submodule changes to rescue branch ${rescueBranch}`,
558
+ { worktree: name, path: resolvedWtPath, rescueBranch },
559
+ );
560
+ } catch (err) {
561
+ logWarning(
562
+ "reconcile",
563
+ `Submodule rescue branch creation failed — changes may be lost during force removal: ${err instanceof Error ? err.message : String(err)}`,
564
+ { worktree: name, path: resolvedWtPath },
565
+ );
518
566
  }
519
567
  }
520
568
  } catch (e) {
@@ -203,7 +203,9 @@ async function assertTrustedStdioServer(
203
203
  return trustKey;
204
204
  }
205
205
 
206
- function getServerConfig(name: string): McpServerConfig | undefined {
206
+ // Exported for tests (see tests/server-name-spaces.test.ts).
207
+ // Production call sites treat this as module-private.
208
+ export function getServerConfig(name: string): McpServerConfig | undefined {
207
209
  const trimmed = name.trim();
208
210
  return readConfigs().find((s) =>
209
211
  s.name === trimmed ||
@@ -1,55 +1,89 @@
1
1
  /**
2
2
  * Regression test for #3029 — mcp_discover fails for server names with spaces.
3
3
  *
4
- * The getServerConfig lookup must handle:
5
- * 1. Exact match (already works)
4
+ * getServerConfig must handle:
5
+ * 1. Exact match
6
6
  * 2. Names with leading/trailing whitespace (trimming)
7
7
  * 3. Case-insensitive matching (e.g. "Langgraph code" vs "langgraph Code")
8
8
  *
9
- * We test at the source level since getServerConfig is not exported.
9
+ * getOrConnect must use the canonical (config.name) as the cache key so that
10
+ * subsequent lookups with variant casing/whitespace hit the same connection.
11
+ *
12
+ * These are behaviour tests against the real exported getServerConfig — no
13
+ * source grep.
10
14
  */
11
15
 
12
- import test from "node:test";
16
+ import test, { before, after } from "node:test";
13
17
  import assert from "node:assert/strict";
14
- import { readFileSync } from "node:fs";
15
- import { dirname, join } from "node:path";
16
- import { fileURLToPath } from "node:url";
18
+ import { writeFileSync, unlinkSync, existsSync, mkdirSync, rmSync } from "node:fs";
19
+ import { join } from "node:path";
20
+ import { mkdtempSync } from "node:fs";
21
+ import { tmpdir } from "node:os";
22
+
23
+ import { getServerConfig } from "../index.js";
24
+
25
+ // readConfigs() anchors to process.cwd() — run each test in a sandbox dir
26
+ // with a purpose-built .mcp.json so the extension reads our fixture, not
27
+ // whatever .mcp.json happens to live in the current working directory.
28
+ let sandboxDir: string;
29
+ let originalCwd: string;
30
+
31
+ before(() => {
32
+ originalCwd = process.cwd();
33
+ sandboxDir = mkdtempSync(join(tmpdir(), "mcp-name-spaces-"));
34
+ const mcpConfig = {
35
+ mcpServers: {
36
+ "Langgraph Code": {
37
+ command: "echo",
38
+ args: ["test"],
39
+ },
40
+ "other-server": {
41
+ url: "https://example.com",
42
+ },
43
+ },
44
+ };
45
+ writeFileSync(join(sandboxDir, ".mcp.json"), JSON.stringify(mcpConfig), "utf-8");
46
+ process.chdir(sandboxDir);
47
+ });
17
48
 
18
- const __filename = fileURLToPath(import.meta.url);
19
- const __dirname = dirname(__filename);
49
+ after(() => {
50
+ process.chdir(originalCwd);
51
+ try {
52
+ rmSync(sandboxDir, { recursive: true, force: true });
53
+ } catch {
54
+ // Best-effort cleanup
55
+ }
56
+ });
20
57
 
21
- const source = readFileSync(join(__dirname, "..", "index.ts"), "utf-8");
58
+ test("#3029: getServerConfig finds exact match", () => {
59
+ const cfg = getServerConfig("Langgraph Code");
60
+ assert.ok(cfg, "exact name must resolve");
61
+ assert.equal(cfg?.name, "Langgraph Code");
62
+ });
22
63
 
23
64
  test("#3029: getServerConfig trims whitespace from input name", () => {
24
- assert.ok(
25
- source.includes(".trim()"),
26
- "getServerConfig should trim the input name before comparison",
27
- );
65
+ const cfg = getServerConfig(" Langgraph Code ");
66
+ assert.ok(cfg, "whitespace-padded name must resolve to the same server");
67
+ assert.equal(cfg?.name, "Langgraph Code");
28
68
  });
29
69
 
30
70
  test("#3029: getServerConfig performs case-insensitive matching", () => {
31
- assert.ok(
32
- source.includes(".toLowerCase()"),
33
- "getServerConfig should compare names case-insensitively",
34
- );
71
+ const cfg = getServerConfig("langgraph code");
72
+ assert.ok(cfg, "lower-cased name must resolve");
73
+ assert.equal(cfg?.name, "Langgraph Code");
74
+
75
+ const mixed = getServerConfig("LANGGRAPH CODE");
76
+ assert.ok(mixed, "upper-cased name must resolve");
77
+ assert.equal(mixed?.name, "Langgraph Code");
78
+ });
79
+
80
+ test("#3029: getServerConfig combines trim + case-insensitive", () => {
81
+ const cfg = getServerConfig(" LANGGRAPH code ");
82
+ assert.ok(cfg, "padded + mixed-case must resolve");
83
+ assert.equal(cfg?.name, "Langgraph Code");
35
84
  });
36
85
 
37
- test("#3029: getOrConnect normalizes name for connection cache lookup", () => {
38
- // The connections Map key must use the canonical (config) name, not the
39
- // raw user input, so that subsequent lookups hit the cache even when the
40
- // user's casing differs.
41
- const getOrConnectMatch = source.match(
42
- /async function getOrConnect\(name: string[\s\S]*?const existing = connections\.get\(/,
43
- );
44
- assert.ok(
45
- getOrConnectMatch,
46
- "getOrConnect function should exist",
47
- );
48
- // After the fix, getOrConnect should normalize the name via getServerConfig
49
- // or use config.name as the canonical cache key.
50
- assert.ok(
51
- source.includes("connections.get(config.name") ||
52
- source.includes("connections.set(config.name"),
53
- "getOrConnect should use config.name (canonical) as the connections cache key",
54
- );
86
+ test("#3029: getServerConfig returns undefined for unknown name", () => {
87
+ const cfg = getServerConfig("does-not-exist");
88
+ assert.equal(cfg, undefined);
55
89
  });
@@ -45,8 +45,12 @@ let providerRegistered = false;
45
45
  /**
46
46
  * Probe Ollama and register discovered models.
47
47
  * Safe to call multiple times — re-discovers and re-registers.
48
+ *
49
+ * Exported for tests (see ollama-auth-mode.test.ts, ollama-status-indicator.test.ts)
50
+ * so a fake HTTP endpoint can drive the registration/unregistration paths.
51
+ * Production callers always go through the session_start handler below.
48
52
  */
49
- async function probeAndRegister(pi: ExtensionAPI): Promise<boolean> {
53
+ export async function probeAndRegister(pi: ExtensionAPI): Promise<boolean> {
50
54
  const running = await client.isRunning();
51
55
  if (!running) {
52
56
  if (providerRegistered) {
@@ -1,20 +1,128 @@
1
1
  /**
2
2
  * Regression test for #3440: Ollama extension must register with
3
- * authMode "apiKey" (not "none") to avoid streamSimple requirement.
3
+ * authMode "apiKey" (not "none"), otherwise the core bails out because
4
+ * the provider has no streamSimple.
5
+ *
6
+ * Behaviour test: spin up a fake Ollama endpoint, point OLLAMA_HOST at
7
+ * it, and invoke probeAndRegister with a mock pi. Assert that
8
+ * registerProvider was called with authMode "apiKey".
4
9
  */
5
- import { test } from "node:test";
10
+ import { test, before, after, beforeEach } from "node:test";
6
11
  import assert from "node:assert/strict";
7
- import { readFileSync } from "node:fs";
8
- import { join, dirname } from "node:path";
9
- import { fileURLToPath } from "node:url";
10
-
11
- const __dirname = dirname(fileURLToPath(import.meta.url));
12
-
13
- test("Ollama registers with authMode apiKey, not none (#3440)", () => {
14
- const src = readFileSync(join(__dirname, "index.ts"), "utf-8");
15
- // Find the registerProvider call
16
- const registerBlock = src.slice(src.indexOf("pi.registerProvider(\"ollama\""));
17
- const authLine = registerBlock.match(/authMode:\s*"(\w+)"/);
18
- assert.ok(authLine, "registerProvider must specify authMode");
19
- assert.equal(authLine![1], "apiKey", "authMode must be apiKey, not none");
12
+ import { createServer, type Server } from "node:http";
13
+ import type { AddressInfo } from "node:net";
14
+
15
+ import { probeAndRegister } from "./index.js";
16
+
17
+ type RegisterCall = [string, Record<string, unknown>];
18
+
19
+ function makeMockPi() {
20
+ const calls: { registerProvider: RegisterCall[]; unregisterProvider: string[] } = {
21
+ registerProvider: [],
22
+ unregisterProvider: [],
23
+ };
24
+ const pi = {
25
+ registerProvider(id: string, spec: Record<string, unknown>) {
26
+ calls.registerProvider.push([id, spec]);
27
+ },
28
+ unregisterProvider(id: string) {
29
+ calls.unregisterProvider.push(id);
30
+ },
31
+ } as unknown as Parameters<typeof probeAndRegister>[0];
32
+ return { pi, calls };
33
+ }
34
+
35
+ let server: Server;
36
+ let savedHost: string | undefined;
37
+
38
+ before(async () => {
39
+ // Fake Ollama endpoint that:
40
+ // GET / → 200 (isRunning probe)
41
+ // GET /api/tags → one model
42
+ // POST /api/show → minimal capability info
43
+ server = createServer((req, res) => {
44
+ if (req.method === "GET" && req.url === "/") {
45
+ res.writeHead(200, { "Content-Type": "text/plain" });
46
+ res.end("Ollama is running");
47
+ return;
48
+ }
49
+ if (req.method === "GET" && req.url === "/api/tags") {
50
+ res.writeHead(200, { "Content-Type": "application/json" });
51
+ res.end(
52
+ JSON.stringify({
53
+ models: [
54
+ {
55
+ name: "llama3:latest",
56
+ modified_at: new Date().toISOString(),
57
+ size: 1_000_000,
58
+ digest: "abc",
59
+ details: {
60
+ parent_model: "",
61
+ format: "gguf",
62
+ family: "llama",
63
+ families: ["llama"],
64
+ parameter_size: "8B",
65
+ quantization_level: "Q4_0",
66
+ },
67
+ },
68
+ ],
69
+ }),
70
+ );
71
+ return;
72
+ }
73
+ if (req.method === "POST" && req.url === "/api/show") {
74
+ res.writeHead(200, { "Content-Type": "application/json" });
75
+ res.end(
76
+ JSON.stringify({
77
+ modelfile: "",
78
+ parameters: "",
79
+ template: "",
80
+ details: {
81
+ parent_model: "",
82
+ format: "gguf",
83
+ family: "llama",
84
+ families: ["llama"],
85
+ parameter_size: "8B",
86
+ quantization_level: "Q4_0",
87
+ },
88
+ model_info: { "llama.context_length": 8192 },
89
+ capabilities: [],
90
+ }),
91
+ );
92
+ return;
93
+ }
94
+ res.writeHead(404);
95
+ res.end();
96
+ });
97
+ await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
98
+ const { port } = server.address() as AddressInfo;
99
+ savedHost = process.env.OLLAMA_HOST;
100
+ process.env.OLLAMA_HOST = `http://127.0.0.1:${port}`;
101
+ });
102
+
103
+ after(async () => {
104
+ await new Promise<void>((resolve) => server.close(() => resolve()));
105
+ if (savedHost === undefined) delete process.env.OLLAMA_HOST;
106
+ else process.env.OLLAMA_HOST = savedHost;
107
+ });
108
+
109
+ beforeEach(() => {
110
+ // Each test starts from a clean providerRegistered state in the module.
111
+ // probeAndRegister is idempotent — calling it is safe regardless.
112
+ });
113
+
114
+ test("Ollama registers with authMode apiKey, not none (#3440)", async () => {
115
+ const { pi, calls } = makeMockPi();
116
+
117
+ const found = await probeAndRegister(pi);
118
+ assert.equal(found, true, "probeAndRegister should return true when models are discovered");
119
+
120
+ assert.equal(calls.registerProvider.length, 1, "registerProvider should be called exactly once");
121
+ const [providerId, spec] = calls.registerProvider[0];
122
+ assert.equal(providerId, "ollama");
123
+ assert.equal(
124
+ spec.authMode,
125
+ "apiKey",
126
+ "authMode must be apiKey so the core doesn't require streamSimple for every model",
127
+ );
20
128
  });
@@ -1,28 +1,215 @@
1
1
  /**
2
2
  * Regression test: don't show an Ollama footer status unless Ollama is
3
3
  * actually usable (running with at least one discovered model).
4
+ *
5
+ * Behaviour tests:
6
+ * 1. probeAndRegister returns false when /api/tags returns no models
7
+ * (running-without-models should not be treated as available).
8
+ * 2. The session_start handler calls ctx.ui.setStatus("ollama", "Ollama")
9
+ * when probeAndRegister reports true, and setStatus("ollama", undefined)
10
+ * when it reports false — keeping the footer clean on unavailable Ollama.
4
11
  */
5
- import { test } from "node:test";
12
+ import { test, before, after } from "node:test";
6
13
  import assert from "node:assert/strict";
7
- import { readFileSync } from "node:fs";
8
- import { join, dirname } from "node:path";
9
- import { fileURLToPath } from "node:url";
10
-
11
- const __dirname = dirname(fileURLToPath(import.meta.url));
12
- const src = readFileSync(join(__dirname, "index.ts"), "utf-8");
13
-
14
- test("probeAndRegister returns false when no Ollama models are discovered", () => {
15
- assert.match(
16
- src,
17
- /if \(models\.length === 0\)[\s\S]*return false;/,
18
- "running-without-models should not be treated as available",
19
- );
14
+ import { createServer, type Server } from "node:http";
15
+ import type { AddressInfo } from "node:net";
16
+
17
+ import ollamaExtension, { probeAndRegister } from "./index.js";
18
+
19
+ type RegisterCall = [string, Record<string, unknown>];
20
+
21
+ function makeMockPi() {
22
+ const calls: {
23
+ registerProvider: RegisterCall[];
24
+ unregisterProvider: string[];
25
+ registerTool: unknown[];
26
+ onHandlers: Map<string, Array<(...args: unknown[]) => unknown>>;
27
+ } = {
28
+ registerProvider: [],
29
+ unregisterProvider: [],
30
+ registerTool: [],
31
+ onHandlers: new Map(),
32
+ };
33
+ const pi = {
34
+ registerProvider(id: string, spec: Record<string, unknown>) {
35
+ calls.registerProvider.push([id, spec]);
36
+ },
37
+ unregisterProvider(id: string) {
38
+ calls.unregisterProvider.push(id);
39
+ },
40
+ registerTool(tool: unknown) {
41
+ calls.registerTool.push(tool);
42
+ },
43
+ registerCommand() {
44
+ /* no-op */
45
+ },
46
+ on(event: string, handler: (...args: unknown[]) => unknown) {
47
+ if (!calls.onHandlers.has(event)) calls.onHandlers.set(event, []);
48
+ calls.onHandlers.get(event)!.push(handler);
49
+ },
50
+ } as unknown as Parameters<typeof probeAndRegister>[0];
51
+ return { pi, calls };
52
+ }
53
+
54
+ // Server mode:
55
+ // "empty" → /api/tags returns { models: [] }
56
+ // "loaded" → /api/tags returns one model + /api/show with 8k context
57
+ let server: Server;
58
+ let serverMode: "empty" | "loaded" = "empty";
59
+ let savedHost: string | undefined;
60
+
61
+ before(async () => {
62
+ server = createServer((req, res) => {
63
+ if (req.method === "GET" && req.url === "/") {
64
+ res.writeHead(200);
65
+ res.end("Ollama is running");
66
+ return;
67
+ }
68
+ if (req.method === "GET" && req.url === "/api/tags") {
69
+ res.writeHead(200, { "Content-Type": "application/json" });
70
+ if (serverMode === "empty") {
71
+ res.end(JSON.stringify({ models: [] }));
72
+ } else {
73
+ res.end(
74
+ JSON.stringify({
75
+ models: [
76
+ {
77
+ name: "llama3:latest",
78
+ modified_at: new Date().toISOString(),
79
+ size: 1_000_000,
80
+ digest: "abc",
81
+ details: {
82
+ parent_model: "",
83
+ format: "gguf",
84
+ family: "llama",
85
+ families: ["llama"],
86
+ parameter_size: "8B",
87
+ quantization_level: "Q4_0",
88
+ },
89
+ },
90
+ ],
91
+ }),
92
+ );
93
+ }
94
+ return;
95
+ }
96
+ if (req.method === "POST" && req.url === "/api/show") {
97
+ res.writeHead(200, { "Content-Type": "application/json" });
98
+ res.end(
99
+ JSON.stringify({
100
+ modelfile: "",
101
+ parameters: "",
102
+ template: "",
103
+ details: {
104
+ parent_model: "",
105
+ format: "gguf",
106
+ family: "llama",
107
+ families: ["llama"],
108
+ parameter_size: "8B",
109
+ quantization_level: "Q4_0",
110
+ },
111
+ model_info: { "llama.context_length": 8192 },
112
+ capabilities: [],
113
+ }),
114
+ );
115
+ return;
116
+ }
117
+ res.writeHead(404);
118
+ res.end();
119
+ });
120
+ await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
121
+ const { port } = server.address() as AddressInfo;
122
+ savedHost = process.env.OLLAMA_HOST;
123
+ process.env.OLLAMA_HOST = `http://127.0.0.1:${port}`;
124
+ });
125
+
126
+ after(async () => {
127
+ await new Promise<void>((resolve) => server.close(() => resolve()));
128
+ if (savedHost === undefined) delete process.env.OLLAMA_HOST;
129
+ else process.env.OLLAMA_HOST = savedHost;
20
130
  });
21
131
 
22
- test("interactive session clears ollama footer status when unavailable", () => {
23
- assert.match(
24
- src,
25
- /ctx\.ui\.setStatus\("ollama", found \? "Ollama" : undefined\)/,
26
- "status should be cleared when probeAndRegister reports unavailable",
132
+ test("probeAndRegister returns false when no Ollama models are discovered", async () => {
133
+ serverMode = "empty";
134
+ const { pi, calls } = makeMockPi();
135
+ const found = await probeAndRegister(pi);
136
+ assert.equal(found, false, "no models should be reported as unavailable");
137
+ assert.equal(
138
+ calls.registerProvider.length,
139
+ 0,
140
+ "provider must not be registered when no models are discoverable",
27
141
  );
28
142
  });
143
+
144
+ test("probeAndRegister returns true when Ollama has at least one model", async () => {
145
+ serverMode = "loaded";
146
+ const { pi, calls } = makeMockPi();
147
+ const found = await probeAndRegister(pi);
148
+ assert.equal(found, true);
149
+ assert.equal(calls.registerProvider.length, 1);
150
+ });
151
+
152
+ test("interactive session sets ollama status based on probeAndRegister result", async () => {
153
+ // Load case: status should be set to "Ollama".
154
+ {
155
+ serverMode = "loaded";
156
+ const { pi, calls } = makeMockPi();
157
+ ollamaExtension(pi);
158
+ const handlers = calls.onHandlers.get("session_start") ?? [];
159
+ assert.equal(handlers.length, 1, "extension registers one session_start handler");
160
+
161
+ const statusCalls: Array<[string, string | undefined]> = [];
162
+ const ctx = {
163
+ hasUI: true,
164
+ ui: {
165
+ setStatus: (slot: string, value: string | undefined) => {
166
+ statusCalls.push([slot, value]);
167
+ },
168
+ notify: () => {},
169
+ },
170
+ };
171
+
172
+ // Fire session_start; wait a tick for the internal promise chain to resolve.
173
+ await handlers[0]({}, ctx);
174
+ // Give probeAndRegister + .then(setStatus) time to complete.
175
+ for (let i = 0; i < 50; i++) {
176
+ if (statusCalls.length > 0) break;
177
+ await new Promise((r) => setTimeout(r, 20));
178
+ }
179
+ assert.deepEqual(
180
+ statusCalls,
181
+ [["ollama", "Ollama"]],
182
+ "status should be set to 'Ollama' when probe reports available",
183
+ );
184
+ }
185
+
186
+ // Unavailable case: status should be cleared (undefined).
187
+ {
188
+ serverMode = "empty";
189
+ const { pi, calls } = makeMockPi();
190
+ ollamaExtension(pi);
191
+ const handlers = calls.onHandlers.get("session_start") ?? [];
192
+
193
+ const statusCalls: Array<[string, string | undefined]> = [];
194
+ const ctx = {
195
+ hasUI: true,
196
+ ui: {
197
+ setStatus: (slot: string, value: string | undefined) => {
198
+ statusCalls.push([slot, value]);
199
+ },
200
+ notify: () => {},
201
+ },
202
+ };
203
+
204
+ await handlers[0]({}, ctx);
205
+ for (let i = 0; i < 50; i++) {
206
+ if (statusCalls.length > 0) break;
207
+ await new Promise((r) => setTimeout(r, 20));
208
+ }
209
+ assert.deepEqual(
210
+ statusCalls,
211
+ [["ollama", undefined]],
212
+ "status must be cleared (undefined) when probe reports unavailable",
213
+ );
214
+ }
215
+ });