gsd-pi 2.63.0-dev.026d309 → 2.63.0-dev.786f0ff

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 (350) hide show
  1. package/README.md +46 -134
  2. package/dist/cli.js +48 -6
  3. package/dist/headless-query.js +11 -1
  4. package/dist/help-text.js +4 -1
  5. package/dist/onboarding.js +15 -8
  6. package/dist/resource-loader.js +18 -3
  7. package/dist/resources/extensions/cmux/index.js +21 -12
  8. package/dist/resources/extensions/gsd/auto/detect-stuck.js +27 -0
  9. package/dist/resources/extensions/gsd/auto/finalize-timeout.js +40 -0
  10. package/dist/resources/extensions/gsd/auto/loop.js +4 -0
  11. package/dist/resources/extensions/gsd/auto/phases.js +157 -22
  12. package/dist/resources/extensions/gsd/auto/session.js +12 -0
  13. package/dist/resources/extensions/gsd/auto-dashboard.js +9 -3
  14. package/dist/resources/extensions/gsd/auto-model-selection.js +32 -0
  15. package/dist/resources/extensions/gsd/auto-post-unit.js +124 -10
  16. package/dist/resources/extensions/gsd/auto-prompts.js +25 -0
  17. package/dist/resources/extensions/gsd/auto-recovery.js +15 -7
  18. package/dist/resources/extensions/gsd/auto-start.js +10 -21
  19. package/dist/resources/extensions/gsd/auto-timers.js +2 -1
  20. package/dist/resources/extensions/gsd/auto-tool-tracking.js +17 -0
  21. package/dist/resources/extensions/gsd/auto-worktree.js +13 -7
  22. package/dist/resources/extensions/gsd/auto.js +19 -2
  23. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +147 -75
  24. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +13 -0
  25. package/dist/resources/extensions/gsd/bootstrap/query-tools.js +85 -0
  26. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +3 -0
  27. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +32 -1
  28. package/dist/resources/extensions/gsd/bootstrap/sanitize-complete-milestone.js +54 -0
  29. package/dist/resources/extensions/gsd/bootstrap/system-context.js +30 -2
  30. package/dist/resources/extensions/gsd/commands-handlers.js +9 -4
  31. package/dist/resources/extensions/gsd/constants.js +42 -0
  32. package/dist/resources/extensions/gsd/db-writer.js +72 -4
  33. package/dist/resources/extensions/gsd/forensics.js +20 -4
  34. package/dist/resources/extensions/gsd/gsd-db.js +64 -17
  35. package/dist/resources/extensions/gsd/guided-flow.js +19 -0
  36. package/dist/resources/extensions/gsd/metrics.js +27 -1
  37. package/dist/resources/extensions/gsd/native-git-bridge.js +5 -3
  38. package/dist/resources/extensions/gsd/preferences-types.js +2 -0
  39. package/dist/resources/extensions/gsd/preferences.js +7 -2
  40. package/dist/resources/extensions/gsd/prompt-loader.js +7 -0
  41. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
  42. package/dist/resources/extensions/gsd/prompts/complete-slice.md +2 -0
  43. package/dist/resources/extensions/gsd/prompts/doctor-heal.md +1 -0
  44. package/dist/resources/extensions/gsd/prompts/forensics.md +2 -0
  45. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +2 -0
  46. package/dist/resources/extensions/gsd/prompts/system.md +4 -7
  47. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
  48. package/dist/resources/extensions/gsd/roadmap-mutations.js +1 -1
  49. package/dist/resources/extensions/gsd/roadmap-slices.js +9 -5
  50. package/dist/resources/extensions/gsd/safety/content-validator.js +73 -0
  51. package/dist/resources/extensions/gsd/safety/destructive-guard.js +34 -0
  52. package/dist/resources/extensions/gsd/safety/evidence-collector.js +109 -0
  53. package/dist/resources/extensions/gsd/safety/evidence-cross-ref.js +83 -0
  54. package/dist/resources/extensions/gsd/safety/file-change-validator.js +71 -0
  55. package/dist/resources/extensions/gsd/safety/git-checkpoint.js +91 -0
  56. package/dist/resources/extensions/gsd/safety/safety-harness.js +64 -0
  57. package/dist/resources/extensions/gsd/slice-parallel-conflict.js +67 -0
  58. package/dist/resources/extensions/gsd/slice-parallel-eligibility.js +51 -0
  59. package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +378 -0
  60. package/dist/resources/extensions/gsd/state.js +74 -14
  61. package/dist/resources/extensions/gsd/status-guards.js +11 -0
  62. package/dist/resources/extensions/gsd/tools/complete-milestone.js +17 -12
  63. package/dist/resources/extensions/gsd/tools/complete-slice.js +40 -26
  64. package/dist/resources/extensions/gsd/tools/complete-task.js +12 -12
  65. package/dist/resources/extensions/gsd/tools/plan-milestone.js +33 -25
  66. package/dist/resources/extensions/gsd/tools/plan-slice.js +5 -8
  67. package/dist/resources/extensions/gsd/workflow-projections.js +21 -5
  68. package/dist/resources/extensions/gsd/worktree-manager.js +82 -29
  69. package/dist/resources/extensions/gsd/worktree-resolver.js +4 -3
  70. package/dist/resources/extensions/mcp-client/auth.js +101 -0
  71. package/dist/resources/extensions/mcp-client/index.js +10 -1
  72. package/dist/resources/extensions/ollama/index.js +28 -22
  73. package/dist/resources/extensions/ollama/model-capabilities.js +37 -34
  74. package/dist/resources/extensions/ollama/ndjson-stream.js +54 -0
  75. package/dist/resources/extensions/ollama/ollama-chat-provider.js +380 -0
  76. package/dist/resources/extensions/ollama/ollama-client.js +23 -32
  77. package/dist/resources/extensions/ollama/ollama-discovery.js +2 -7
  78. package/dist/resources/extensions/ollama/ollama-tool.js +62 -0
  79. package/dist/resources/extensions/ollama/thinking-parser.js +104 -0
  80. package/dist/update-cmd.js +4 -2
  81. package/dist/web/standalone/.next/BUILD_ID +1 -1
  82. package/dist/web/standalone/.next/app-path-routes-manifest.json +20 -20
  83. package/dist/web/standalone/.next/build-manifest.json +2 -2
  84. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  85. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  86. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  88. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  91. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  94. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  96. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  97. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  98. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  99. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  100. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  101. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  102. package/dist/web/standalone/.next/server/app/api/boot/route.js.nft.json +1 -1
  103. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  104. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
  105. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  106. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
  107. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  108. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
  109. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
  111. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
  113. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
  115. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
  117. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
  119. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
  121. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  122. package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
  123. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  124. package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
  125. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  126. package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
  127. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  128. package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
  129. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  130. package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
  131. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  132. package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
  133. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  134. package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
  135. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  136. package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
  137. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  138. package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
  139. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  140. package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
  141. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  142. package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
  143. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  144. package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
  145. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  146. package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
  147. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  148. package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
  149. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  150. package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
  151. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  152. package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
  153. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  154. package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
  155. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
  156. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
  157. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +2 -2
  158. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
  159. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  160. package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
  161. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  162. package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
  163. package/dist/web/standalone/.next/server/app/index.html +1 -1
  164. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  165. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  166. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  167. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  168. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  169. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  170. package/dist/web/standalone/.next/server/app-paths-manifest.json +20 -20
  171. package/dist/web/standalone/.next/server/chunks/6897.js +12 -0
  172. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  173. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  174. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  175. package/package.json +1 -1
  176. package/packages/pi-agent-core/dist/agent-loop.d.ts +8 -0
  177. package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  178. package/packages/pi-agent-core/dist/agent-loop.js +50 -0
  179. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  180. package/packages/pi-agent-core/src/agent-loop.test.ts +221 -5
  181. package/packages/pi-agent-core/src/agent-loop.ts +53 -0
  182. package/packages/pi-ai/dist/types.d.ts +16 -1
  183. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  184. package/packages/pi-ai/dist/types.js.map +1 -1
  185. package/packages/pi-ai/src/types.ts +18 -1
  186. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +9 -0
  187. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  188. package/packages/pi-coding-agent/dist/core/auth-storage.js +50 -1
  189. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  190. package/packages/pi-coding-agent/dist/core/auth-storage.test.js +41 -0
  191. package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
  192. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts +7 -0
  193. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  194. package/packages/pi-coding-agent/dist/core/extensions/loader.js +31 -4
  195. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  196. package/packages/pi-coding-agent/dist/core/extensions/loader.test.js +28 -1
  197. package/packages/pi-coding-agent/dist/core/extensions/loader.test.js.map +1 -1
  198. package/packages/pi-coding-agent/dist/core/extensions/provider-registration.test.d.ts +2 -0
  199. package/packages/pi-coding-agent/dist/core/extensions/provider-registration.test.d.ts.map +1 -0
  200. package/packages/pi-coding-agent/dist/core/extensions/provider-registration.test.js +46 -0
  201. package/packages/pi-coding-agent/dist/core/extensions/provider-registration.test.js.map +1 -0
  202. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +2 -0
  203. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  204. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  205. package/packages/pi-coding-agent/dist/core/model-registry.d.ts +1 -0
  206. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  207. package/packages/pi-coding-agent/dist/core/model-registry.js +12 -0
  208. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  209. package/packages/pi-coding-agent/dist/core/model-resolver.js +3 -3
  210. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  211. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +23 -1
  212. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
  213. package/packages/pi-coding-agent/dist/core/resource-loader.js +80 -56
  214. package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
  215. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  216. package/packages/pi-coding-agent/dist/core/sdk.js +9 -0
  217. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  218. package/packages/pi-coding-agent/src/core/auth-storage.test.ts +53 -0
  219. package/packages/pi-coding-agent/src/core/auth-storage.ts +66 -1
  220. package/packages/pi-coding-agent/src/core/extensions/loader.test.ts +39 -1
  221. package/packages/pi-coding-agent/src/core/extensions/loader.ts +34 -4
  222. package/packages/pi-coding-agent/src/core/extensions/provider-registration.test.ts +81 -0
  223. package/packages/pi-coding-agent/src/core/extensions/types.ts +2 -0
  224. package/packages/pi-coding-agent/src/core/model-registry.ts +14 -0
  225. package/packages/pi-coding-agent/src/core/model-resolver.ts +3 -3
  226. package/packages/pi-coding-agent/src/core/resource-loader.ts +89 -56
  227. package/packages/pi-coding-agent/src/core/sdk.ts +10 -0
  228. package/src/resources/extensions/cmux/index.ts +18 -12
  229. package/src/resources/extensions/gsd/auto/detect-stuck.ts +27 -0
  230. package/src/resources/extensions/gsd/auto/finalize-timeout.ts +46 -0
  231. package/src/resources/extensions/gsd/auto/loop.ts +5 -0
  232. package/src/resources/extensions/gsd/auto/phases.ts +194 -33
  233. package/src/resources/extensions/gsd/auto/session.ts +14 -0
  234. package/src/resources/extensions/gsd/auto-dashboard.ts +11 -3
  235. package/src/resources/extensions/gsd/auto-model-selection.ts +36 -0
  236. package/src/resources/extensions/gsd/auto-post-unit.ts +141 -12
  237. package/src/resources/extensions/gsd/auto-prompts.ts +21 -0
  238. package/src/resources/extensions/gsd/auto-recovery.ts +9 -8
  239. package/src/resources/extensions/gsd/auto-start.ts +11 -20
  240. package/src/resources/extensions/gsd/auto-timers.ts +2 -1
  241. package/src/resources/extensions/gsd/auto-tool-tracking.ts +19 -0
  242. package/src/resources/extensions/gsd/auto-worktree.ts +14 -6
  243. package/src/resources/extensions/gsd/auto.ts +22 -1
  244. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +160 -88
  245. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +15 -0
  246. package/src/resources/extensions/gsd/bootstrap/query-tools.ts +98 -0
  247. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +4 -0
  248. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +36 -1
  249. package/src/resources/extensions/gsd/bootstrap/sanitize-complete-milestone.ts +57 -0
  250. package/src/resources/extensions/gsd/bootstrap/system-context.ts +31 -2
  251. package/src/resources/extensions/gsd/commands-handlers.ts +10 -4
  252. package/src/resources/extensions/gsd/constants.ts +44 -0
  253. package/src/resources/extensions/gsd/db-writer.ts +78 -4
  254. package/src/resources/extensions/gsd/forensics.ts +21 -5
  255. package/src/resources/extensions/gsd/gsd-db.ts +64 -17
  256. package/src/resources/extensions/gsd/guided-flow.ts +22 -0
  257. package/src/resources/extensions/gsd/metrics.ts +28 -1
  258. package/src/resources/extensions/gsd/native-git-bridge.ts +5 -3
  259. package/src/resources/extensions/gsd/preferences-types.ts +16 -0
  260. package/src/resources/extensions/gsd/preferences.ts +9 -2
  261. package/src/resources/extensions/gsd/prompt-loader.ts +8 -0
  262. package/src/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
  263. package/src/resources/extensions/gsd/prompts/complete-slice.md +2 -0
  264. package/src/resources/extensions/gsd/prompts/doctor-heal.md +1 -0
  265. package/src/resources/extensions/gsd/prompts/forensics.md +2 -0
  266. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +2 -0
  267. package/src/resources/extensions/gsd/prompts/system.md +4 -7
  268. package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
  269. package/src/resources/extensions/gsd/roadmap-mutations.ts +1 -1
  270. package/src/resources/extensions/gsd/roadmap-slices.ts +10 -5
  271. package/src/resources/extensions/gsd/safety/content-validator.ts +98 -0
  272. package/src/resources/extensions/gsd/safety/destructive-guard.ts +49 -0
  273. package/src/resources/extensions/gsd/safety/evidence-collector.ts +151 -0
  274. package/src/resources/extensions/gsd/safety/evidence-cross-ref.ts +120 -0
  275. package/src/resources/extensions/gsd/safety/file-change-validator.ts +108 -0
  276. package/src/resources/extensions/gsd/safety/git-checkpoint.ts +106 -0
  277. package/src/resources/extensions/gsd/safety/safety-harness.ts +105 -0
  278. package/src/resources/extensions/gsd/slice-parallel-conflict.ts +86 -0
  279. package/src/resources/extensions/gsd/slice-parallel-eligibility.ts +73 -0
  280. package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +477 -0
  281. package/src/resources/extensions/gsd/state.ts +67 -12
  282. package/src/resources/extensions/gsd/status-guards.ts +13 -0
  283. package/src/resources/extensions/gsd/tests/artifact-corruption-2630.test.ts +288 -0
  284. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +34 -13
  285. package/src/resources/extensions/gsd/tests/cmux.test.ts +58 -0
  286. package/src/resources/extensions/gsd/tests/cold-resume-db-reopen.test.ts +51 -0
  287. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +140 -0
  288. package/src/resources/extensions/gsd/tests/complete-slice-string-coercion.test.ts +211 -0
  289. package/src/resources/extensions/gsd/tests/complete-task.test.ts +39 -0
  290. package/src/resources/extensions/gsd/tests/dashboard-model-label-ordering.test.ts +107 -0
  291. package/src/resources/extensions/gsd/tests/db-access-guardrails.test.ts +109 -0
  292. package/src/resources/extensions/gsd/tests/db-path-worktree-symlink.test.ts +13 -9
  293. package/src/resources/extensions/gsd/tests/db-writer.test.ts +134 -0
  294. package/src/resources/extensions/gsd/tests/deferred-slice-dispatch.test.ts +203 -0
  295. package/src/resources/extensions/gsd/tests/discuss-tool-scoping.test.ts +130 -0
  296. package/src/resources/extensions/gsd/tests/doctor-fix-flag.test.ts +92 -0
  297. package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +116 -0
  298. package/src/resources/extensions/gsd/tests/flat-rate-routing-guard.test.ts +50 -0
  299. package/src/resources/extensions/gsd/tests/forensics-stuck-loops.test.ts +103 -0
  300. package/src/resources/extensions/gsd/tests/git-checkpoint.test.ts +94 -0
  301. package/src/resources/extensions/gsd/tests/insert-slice-no-wipe.test.ts +88 -0
  302. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +27 -7
  303. package/src/resources/extensions/gsd/tests/integration/idle-recovery.test.ts +34 -0
  304. package/src/resources/extensions/gsd/tests/metrics.test.ts +116 -1
  305. package/src/resources/extensions/gsd/tests/milestone-status-tool.test.ts +201 -0
  306. package/src/resources/extensions/gsd/tests/plan-milestone-title.test.ts +2 -1
  307. package/src/resources/extensions/gsd/tests/plan-milestone.test.ts +82 -18
  308. package/src/resources/extensions/gsd/tests/preferences.test.ts +10 -0
  309. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +25 -0
  310. package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +69 -0
  311. package/src/resources/extensions/gsd/tests/shared-wal.test.ts +30 -0
  312. package/src/resources/extensions/gsd/tests/slice-context-injection.test.ts +50 -0
  313. package/src/resources/extensions/gsd/tests/slice-parallel-conflict.test.ts +92 -0
  314. package/src/resources/extensions/gsd/tests/slice-parallel-eligibility.test.ts +95 -0
  315. package/src/resources/extensions/gsd/tests/slice-parallel-orchestrator.test.ts +83 -0
  316. package/src/resources/extensions/gsd/tests/stuck-detection-coverage.test.ts +42 -0
  317. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +103 -0
  318. package/src/resources/extensions/gsd/tests/tool-param-optionality.test.ts +349 -0
  319. package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +35 -2
  320. package/src/resources/extensions/gsd/tests/worktree-health-monorepo.test.ts +73 -0
  321. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +34 -0
  322. package/src/resources/extensions/gsd/tests/worktree-submodule-safety.test.ts +1 -1
  323. package/src/resources/extensions/gsd/tests/worktree-teardown-safety.test.ts +148 -0
  324. package/src/resources/extensions/gsd/tools/complete-milestone.ts +34 -20
  325. package/src/resources/extensions/gsd/tools/complete-slice.ts +41 -26
  326. package/src/resources/extensions/gsd/tools/complete-task.ts +12 -12
  327. package/src/resources/extensions/gsd/tools/plan-milestone.ts +55 -30
  328. package/src/resources/extensions/gsd/tools/plan-slice.ts +13 -8
  329. package/src/resources/extensions/gsd/types.ts +44 -22
  330. package/src/resources/extensions/gsd/workflow-logger.ts +2 -1
  331. package/src/resources/extensions/gsd/workflow-projections.ts +23 -5
  332. package/src/resources/extensions/gsd/worktree-manager.ts +76 -28
  333. package/src/resources/extensions/gsd/worktree-resolver.ts +4 -3
  334. package/src/resources/extensions/mcp-client/auth.ts +149 -0
  335. package/src/resources/extensions/mcp-client/index.ts +16 -1
  336. package/src/resources/extensions/ollama/index.ts +26 -25
  337. package/src/resources/extensions/ollama/model-capabilities.ts +41 -34
  338. package/src/resources/extensions/ollama/ndjson-stream.ts +63 -0
  339. package/src/resources/extensions/ollama/ollama-auth-mode.test.ts +20 -0
  340. package/src/resources/extensions/ollama/ollama-chat-provider.ts +459 -0
  341. package/src/resources/extensions/ollama/ollama-client.ts +30 -30
  342. package/src/resources/extensions/ollama/ollama-discovery.ts +5 -8
  343. package/src/resources/extensions/ollama/ollama-tool.ts +69 -0
  344. package/src/resources/extensions/ollama/tests/ollama-chat-provider-stream.test.ts +82 -0
  345. package/src/resources/extensions/ollama/tests/ollama-discovery.test.ts +0 -27
  346. package/src/resources/extensions/ollama/thinking-parser.ts +116 -0
  347. package/src/resources/extensions/ollama/types.ts +23 -0
  348. package/dist/web/standalone/.next/server/chunks/2229.js +0 -12
  349. /package/dist/web/standalone/.next/static/{TTlAguZQ5vR9EOv6G8cel → SDB1T-4NqkMjYirjjqQhr}/_buildManifest.js +0 -0
  350. /package/dist/web/standalone/.next/static/{TTlAguZQ5vR9EOv6G8cel → SDB1T-4NqkMjYirjjqQhr}/_ssgManifest.js +0 -0
@@ -65,6 +65,21 @@ export function worktreePath(basePath, name) {
65
65
  export function worktreeBranchName(name) {
66
66
  return `worktree/${name}`;
67
67
  }
68
+ /**
69
+ * Validate that a path is inside the .gsd/worktrees/ directory.
70
+ * Resolves symlinks and normalizes ".." traversals before comparison
71
+ * so that a symlink-resolved or crafted path cannot escape containment.
72
+ *
73
+ * Used as a safety gate before any destructive operation (rmSync,
74
+ * nativeWorktreeRemove --force) to prevent #2365-style data loss.
75
+ */
76
+ export function isInsideWorktreesDir(basePath, targetPath) {
77
+ const wtDir = resolve(worktreesDir(basePath));
78
+ const resolved = resolve(targetPath);
79
+ // The resolved path must start with the worktrees dir followed by a separator,
80
+ // not merely be a prefix match (e.g. ".gsd/worktrees-extra" must not match).
81
+ return resolved === wtDir || resolved.startsWith(wtDir + sep);
82
+ }
68
83
  // ─── Core Operations ───────────────────────────────────────────────────────
69
84
  /**
70
85
  * Create a new git worktree under .gsd/worktrees/<name>/ with branch worktree/<name>.
@@ -286,17 +301,40 @@ export function removeWorktree(basePath, name, opts = {}) {
286
301
  // time, so its registered path points to the resolved external location.
287
302
  // If syncStateToProjectRoot later creates a real .gsd/ directory that
288
303
  // shadows the symlink, the computed path diverges from git's record.
304
+ let gitReportedPath = null;
289
305
  try {
290
306
  const entries = nativeWorktreeList(basePath);
291
307
  const entry = entries.find(e => e.branch === branch);
292
308
  if (entry?.path) {
293
- wtPath = entry.path;
309
+ gitReportedPath = entry.path;
294
310
  }
295
311
  }
296
312
  catch (e) {
297
313
  logWarning("worktree", `nativeWorktreeList parse failed: ${e.message}`);
298
314
  }
315
+ // Safety gate (#2365): only use the git-reported path if it is actually
316
+ // inside .gsd/worktrees/. When .gsd/ was a symlink, git may have resolved
317
+ // it to an external directory (e.g. a project data folder). Using that
318
+ // path for removal would destroy user data.
319
+ if (gitReportedPath && isInsideWorktreesDir(basePath, gitReportedPath)) {
320
+ wtPath = gitReportedPath;
321
+ }
322
+ else if (gitReportedPath) {
323
+ console.error(`[GSD] WARNING: git worktree list reported path outside .gsd/worktrees/: ${gitReportedPath}\n` +
324
+ ` Refusing to use it for removal — falling back to computed path: ${wtPath}`);
325
+ // Still tell git to unregister the worktree entry via its reported path,
326
+ // but do NOT use force and do NOT fall back to rmSync on this path.
327
+ try {
328
+ nativeWorktreeRemove(basePath, gitReportedPath, false);
329
+ }
330
+ catch (e) {
331
+ logWarning("worktree", `non-force worktree remove failed for ${gitReportedPath}: ${e instanceof Error ? e.message : String(e)}`);
332
+ }
333
+ }
299
334
  const resolvedWtPath = existsSync(wtPath) ? realpathSync(wtPath) : wtPath;
335
+ // Double-check: the resolved path (after symlink resolution) must also be
336
+ // inside .gsd/worktrees/ — a symlink inside the directory could point out.
337
+ const resolvedPathSafe = isInsideWorktreesDir(basePath, resolvedWtPath);
300
338
  // If we're inside the worktree, move out first — git can't remove an in-use directory
301
339
  const cwd = process.cwd();
302
340
  const resolvedCwd = existsSync(cwd) ? realpathSync(cwd) : cwd;
@@ -360,41 +398,56 @@ export function removeWorktree(basePath, name, opts = {}) {
360
398
  }
361
399
  }
362
400
  }
363
- // Remove worktree: try non-force first when submodules have changes,
364
- // falling back to force only after submodule state has been preserved.
365
- const useForce = hasSubmoduleChanges ? false : force;
366
- try {
367
- nativeWorktreeRemove(basePath, resolvedWtPath, useForce);
368
- }
369
- catch (e) {
370
- logWarning("worktree", `nativeWorktreeRemove failed: ${e.message}`);
371
- }
372
- // If the directory is still there (e.g. locked), try harder with force
373
- if (existsSync(resolvedWtPath)) {
401
+ // Remove worktree only use force/rmSync when the path is safely contained
402
+ if (resolvedPathSafe) {
403
+ // Remove worktree: try non-force first when submodules have changes,
404
+ // falling back to force only after submodule state has been preserved.
405
+ const useForce = hasSubmoduleChanges ? false : force;
374
406
  try {
375
- nativeWorktreeRemove(basePath, resolvedWtPath, true);
407
+ nativeWorktreeRemove(basePath, resolvedWtPath, useForce);
376
408
  }
377
409
  catch (e) {
378
- logWarning("worktree", `nativeWorktreeRemove (force) failed: ${e.message}`);
410
+ logWarning("worktree", `nativeWorktreeRemove failed: ${e.message}`);
411
+ }
412
+ // If the directory is still there (e.g. locked), try harder with force
413
+ if (existsSync(resolvedWtPath)) {
414
+ try {
415
+ nativeWorktreeRemove(basePath, resolvedWtPath, true);
416
+ }
417
+ catch (e) {
418
+ logWarning("worktree", `nativeWorktreeRemove (force) failed: ${e.message}`);
419
+ }
420
+ }
421
+ // (#2821) If the worktree directory STILL exists after both native removal
422
+ // attempts (e.g. untracked files like ASSESSMENT/UAT-RESULT prevent git
423
+ // worktree remove), force-remove the git internal worktree metadata first,
424
+ // then remove the filesystem directory. Without this, the .git/worktrees/<name>
425
+ // lock prevents rmSync from cleaning up, and the orphaned worktree directory
426
+ // causes every subsequent `/gsd auto` to re-enter the stale worktree.
427
+ if (existsSync(resolvedWtPath)) {
428
+ try {
429
+ const wtInternalDir = join(basePath, ".git", "worktrees", name);
430
+ if (existsSync(wtInternalDir)) {
431
+ rmSync(wtInternalDir, { recursive: true, force: true });
432
+ }
433
+ rmSync(resolvedWtPath, { recursive: true, force: true });
434
+ }
435
+ catch {
436
+ logWarning("reconcile", `Worktree directory could not be removed after git internal cleanup: ${resolvedWtPath}. ` +
437
+ `Manual cleanup: rm -rf "${resolvedWtPath.replaceAll("\\", "/")}"`, { worktree: name });
438
+ }
379
439
  }
380
440
  }
381
- // (#2821) If the worktree directory STILL exists after both native removal
382
- // attempts (e.g. untracked files like ASSESSMENT/UAT-RESULT prevent git
383
- // worktree remove), force-remove the git internal worktree metadata first,
384
- // then remove the filesystem directory. Without this, the .git/worktrees/<name>
385
- // lock prevents rmSync from cleaning up, and the orphaned worktree directory
386
- // causes every subsequent `/gsd auto` to re-enter the stale worktree.
387
- if (existsSync(resolvedWtPath)) {
441
+ else {
442
+ // Path is outside containment only do a non-force git worktree remove
443
+ // (which refuses to delete dirty worktrees) and never fall back to rmSync.
444
+ console.error(`[GSD] WARNING: Resolved worktree path is outside .gsd/worktrees/: ${resolvedWtPath}\n` +
445
+ ` Skipping forced removal to prevent data loss.`);
388
446
  try {
389
- const wtInternalDir = join(basePath, ".git", "worktrees", name);
390
- if (existsSync(wtInternalDir)) {
391
- rmSync(wtInternalDir, { recursive: true, force: true });
392
- }
393
- rmSync(resolvedWtPath, { recursive: true, force: true });
447
+ nativeWorktreeRemove(basePath, resolvedWtPath, false);
394
448
  }
395
- catch {
396
- logWarning("reconcile", `Worktree directory could not be removed after git internal cleanup: ${resolvedWtPath}. ` +
397
- `Manual cleanup: rm -rf "${resolvedWtPath.replaceAll("\\", "/")}"`, { worktree: name });
449
+ catch (e) {
450
+ logWarning("worktree", `non-force worktree remove failed for ${resolvedWtPath}: ${e instanceof Error ? e.message : String(e)}`);
398
451
  }
399
452
  }
400
453
  // Prune stale entries so git knows the worktree is gone
@@ -365,9 +365,10 @@ export class WorktreeResolver {
365
365
  });
366
366
  // Surface a clear, actionable error. The worktree and milestone branch are
367
367
  // intentionally preserved — nothing has been deleted. The user can retry
368
- // /gsd dispatch complete-milestone or merge manually once the underlying issue is fixed
369
- // (e.g. checkout to wrong branch, unresolved conflicts). (#1668)
370
- ctx.notify(`Milestone merge failed: ${msg}. Your worktree and milestone branch are preserved — retry /gsd dispatch complete-milestone or merge manually.`, "warning");
368
+ // /gsd dispatch complete-milestone or merge manually once the underlying
369
+ // issue is fixed (e.g. checkout to wrong branch, unresolved conflicts).
370
+ // (#1668, #1891)
371
+ ctx.notify(`Milestone merge failed: ${msg}. Your worktree and milestone branch are preserved — retry with \`/gsd dispatch complete-milestone\` or merge manually.`, "warning");
371
372
  // Clean up stale merge state left by failed squash-merge (#1389)
372
373
  try {
373
374
  const gitDir = join(originalBase || this.s.basePath, ".git");
@@ -0,0 +1,101 @@
1
+ /**
2
+ * MCP Client OAuth / Auth helpers
3
+ *
4
+ * Builds transport options (headers, OAuthClientProvider) from MCP server
5
+ * config entries so that HTTP transports can authenticate with remote
6
+ * servers (Sentry, Linear, etc.).
7
+ *
8
+ * Fixes #2160 — MCP HTTP transport lacked an OAuth auth provider.
9
+ */
10
+ // ─── Env resolution ───────────────────────────────────────────────────────────
11
+ /** Resolve `${VAR}` references in a string against `process.env`. */
12
+ function resolveEnvValue(value) {
13
+ return value.replace(/\$\{([^}]+)\}/g, (_match, varName) => process.env[varName] ?? "");
14
+ }
15
+ function resolveHeaders(raw) {
16
+ const resolved = {};
17
+ for (const [key, value] of Object.entries(raw)) {
18
+ resolved[key] = typeof value === "string" ? resolveEnvValue(value) : value;
19
+ }
20
+ return resolved;
21
+ }
22
+ // ─── OAuth provider (minimal CLI-friendly implementation) ─────────────────────
23
+ /**
24
+ * Creates a minimal `OAuthClientProvider` suitable for CLI / headless use.
25
+ *
26
+ * This provider supports:
27
+ * - Pre-configured client credentials (client_id, optional client_secret)
28
+ * - Token storage in memory (per-session)
29
+ * - Scopes
30
+ *
31
+ * For full interactive OAuth flows (browser redirect), a richer provider would
32
+ * be needed, but for server-to-server and pre-authed scenarios this is
33
+ * sufficient.
34
+ */
35
+ function createCliOAuthProvider(config) {
36
+ let storedTokens;
37
+ let storedCodeVerifier = "";
38
+ return {
39
+ get redirectUrl() {
40
+ return config.redirectUrl ?? "http://localhost:0/callback";
41
+ },
42
+ get clientMetadata() {
43
+ return {
44
+ redirect_uris: [config.redirectUrl ?? "http://localhost:0/callback"],
45
+ client_name: "gsd",
46
+ ...(config.scopes ? { scope: config.scopes.join(" ") } : {}),
47
+ };
48
+ },
49
+ clientInformation() {
50
+ return {
51
+ client_id: config.clientId,
52
+ ...(config.clientSecret ? { client_secret: config.clientSecret } : {}),
53
+ };
54
+ },
55
+ tokens() {
56
+ return storedTokens;
57
+ },
58
+ saveTokens(tokens) {
59
+ storedTokens = tokens;
60
+ },
61
+ redirectToAuthorization(authorizationUrl) {
62
+ // In a CLI context we can't open a browser automatically.
63
+ // Log the URL so the user can manually visit it.
64
+ // eslint-disable-next-line no-console
65
+ console.error(`[MCP OAuth] Authorization required. Visit:\n ${authorizationUrl.toString()}`);
66
+ },
67
+ saveCodeVerifier(codeVerifier) {
68
+ storedCodeVerifier = codeVerifier;
69
+ },
70
+ codeVerifier() {
71
+ return storedCodeVerifier;
72
+ },
73
+ };
74
+ }
75
+ // ─── Public API ───────────────────────────────────────────────────────────────
76
+ /**
77
+ * Build `StreamableHTTPClientTransportOptions` from an MCP server config's
78
+ * auth-related fields.
79
+ *
80
+ * Supports two auth strategies:
81
+ * 1. **`headers`** — static Authorization (or other) headers, with `${VAR}` env resolution.
82
+ * 2. **`oauth`** — full OAuthClientProvider for servers that implement MCP OAuth.
83
+ *
84
+ * When both are provided, `oauth` takes precedence (the SDK's built-in OAuth
85
+ * flow handles token refresh automatically).
86
+ */
87
+ export function buildHttpTransportOpts(authConfig) {
88
+ const opts = {};
89
+ // OAuth takes precedence
90
+ if (authConfig.oauth) {
91
+ opts.authProvider = createCliOAuthProvider(authConfig.oauth);
92
+ return opts;
93
+ }
94
+ // Static headers (with env var resolution)
95
+ if (authConfig.headers && Object.keys(authConfig.headers).length > 0) {
96
+ opts.requestInit = {
97
+ headers: resolveHeaders(authConfig.headers),
98
+ };
99
+ }
100
+ return opts;
101
+ }
@@ -18,6 +18,7 @@ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"
18
18
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
19
19
  import { readFileSync, existsSync } from "node:fs";
20
20
  import { join } from "node:path";
21
+ import { buildHttpTransportOpts } from "./auth.js";
21
22
  // ─── Connection Manager ───────────────────────────────────────────────────────
22
23
  const connections = new Map();
23
24
  let configCache = null;
@@ -51,6 +52,8 @@ function readConfigs() {
51
52
  : hasUrl
52
53
  ? "http"
53
54
  : "unknown";
55
+ const hasHeaders = hasUrl && config.headers && typeof config.headers === "object";
56
+ const hasOAuth = hasUrl && config.oauth && typeof config.oauth === "object";
54
57
  servers.push({
55
58
  name,
56
59
  transport,
@@ -63,6 +66,8 @@ function readConfigs() {
63
66
  cwd: typeof config.cwd === "string" ? config.cwd : undefined,
64
67
  }),
65
68
  ...(hasUrl && { url: config.url }),
69
+ headers: hasHeaders ? config.headers : undefined,
70
+ oauth: hasOAuth ? config.oauth : undefined,
66
71
  });
67
72
  }
68
73
  }
@@ -113,7 +118,11 @@ async function getOrConnect(name, signal) {
113
118
  }
114
119
  else if (config.transport === "http" && config.url) {
115
120
  const resolvedUrl = config.url.replace(/\$\{([^}]+)\}/g, (_, varName) => process.env[varName] ?? "");
116
- transport = new StreamableHTTPClientTransport(new URL(resolvedUrl));
121
+ const httpOpts = buildHttpTransportOpts({
122
+ headers: config.headers,
123
+ oauth: config.oauth,
124
+ });
125
+ transport = new StreamableHTTPClientTransport(new URL(resolvedUrl), httpOpts);
117
126
  }
118
127
  else {
119
128
  throw new Error(`Server "${config.name}" has unsupported transport: ${config.transport}`);
@@ -17,16 +17,9 @@
17
17
  */
18
18
  import { importExtensionModule } from "@gsd/pi-coding-agent";
19
19
  import * as client from "./ollama-client.js";
20
- import { discoverModels, getOllamaOpenAIBaseUrl } from "./ollama-discovery.js";
20
+ import { discoverModels } from "./ollama-discovery.js";
21
21
  import { registerOllamaCommands } from "./ollama-commands.js";
22
- /** Default compat settings for Ollama models via OpenAI-compat endpoint */
23
- const OLLAMA_COMPAT = {
24
- supportsDeveloperRole: false,
25
- supportsReasoningEffort: false,
26
- supportsUsageInStreaming: false,
27
- maxTokensField: "max_tokens",
28
- supportsStore: false,
29
- };
22
+ import { streamOllamaChat } from "./ollama-chat-provider.js";
30
23
  let toolsPromise = null;
31
24
  async function registerOllamaTools(pi) {
32
25
  if (!toolsPromise) {
@@ -58,11 +51,17 @@ async function probeAndRegister(pi) {
58
51
  const models = await discoverModels();
59
52
  if (models.length === 0)
60
53
  return true; // Running but no models pulled
61
- const baseUrl = getOllamaOpenAIBaseUrl();
54
+ const baseUrl = client.getOllamaHost();
55
+ // Use authMode "apiKey" with a dummy key (#3440).
56
+ // authMode "none" requires a custom streamSimple handler, but Ollama uses
57
+ // the standard OpenAI-compatible streaming endpoint. Ollama ignores the
58
+ // Authorization header so the dummy key is harmless.
62
59
  pi.registerProvider("ollama", {
63
- authMode: "none",
60
+ authMode: "apiKey",
61
+ apiKey: "ollama",
64
62
  baseUrl,
65
- api: "openai-completions",
63
+ api: "ollama-chat",
64
+ streamSimple: streamOllamaChat,
66
65
  isReady: () => true,
67
66
  models: models.map((m) => ({
68
67
  id: m.id,
@@ -72,7 +71,7 @@ async function probeAndRegister(pi) {
72
71
  cost: m.cost,
73
72
  contextWindow: m.contextWindow,
74
73
  maxTokens: m.maxTokens,
75
- compat: OLLAMA_COMPAT,
74
+ providerOptions: (m.ollamaOptions ?? {}),
76
75
  })),
77
76
  });
78
77
  providerRegistered = true;
@@ -91,16 +90,23 @@ export default function ollama(pi) {
91
90
  else {
92
91
  await registerOllamaTools(pi);
93
92
  }
94
- // Async probe don't block startup
95
- probeAndRegister(pi)
96
- .then((found) => {
97
- if (found && ctx.hasUI) {
98
- ctx.ui.setStatus("ollama", "Ollama");
93
+ // In headless/auto mode, await the probe so the fallback resolver can
94
+ // see Ollama before the first LLM call (#3531 race condition).
95
+ // In interactive mode, keep it async for fast startup.
96
+ if (!ctx.hasUI) {
97
+ try {
98
+ await probeAndRegister(pi);
99
99
  }
100
- })
101
- .catch(() => {
102
- // Silently ignore probe failures
103
- });
100
+ catch { /* non-fatal */ }
101
+ }
102
+ else {
103
+ probeAndRegister(pi)
104
+ .then((found) => {
105
+ if (found)
106
+ ctx.ui.setStatus("ollama", "Ollama");
107
+ })
108
+ .catch(() => { });
109
+ }
104
110
  });
105
111
  pi.on("session_shutdown", async () => {
106
112
  if (providerRegistered) {
@@ -4,50 +4,53 @@
4
4
  * Keys are matched as prefixes against the model name (before the colon/tag).
5
5
  * More specific entries should appear first.
6
6
  */
7
+ // Note: ollamaOptions.num_ctx is set for known model families where the context
8
+ // window is authoritative. For unknown/estimated models, num_ctx is NOT sent
9
+ // to avoid OOM risk — Ollama uses its own safe default instead.
7
10
  const KNOWN_MODELS = [
8
11
  // ─── Reasoning models ───────────────────────────────────────────────
9
- ["deepseek-r1", { contextWindow: 131072, reasoning: true }],
10
- ["qwq", { contextWindow: 131072, reasoning: true }],
12
+ ["deepseek-r1", { contextWindow: 131072, reasoning: true, ollamaOptions: { num_ctx: 131072 } }],
13
+ ["qwq", { contextWindow: 131072, reasoning: true, ollamaOptions: { num_ctx: 131072 } }],
11
14
  // ─── Vision models ──────────────────────────────────────────────────
12
- ["llava", { contextWindow: 4096, input: ["text", "image"] }],
13
- ["bakllava", { contextWindow: 4096, input: ["text", "image"] }],
14
- ["moondream", { contextWindow: 8192, input: ["text", "image"] }],
15
- ["llama3.2-vision", { contextWindow: 131072, input: ["text", "image"] }],
16
- ["minicpm-v", { contextWindow: 4096, input: ["text", "image"] }],
15
+ ["llava", { contextWindow: 4096, input: ["text", "image"], ollamaOptions: { num_ctx: 4096 } }],
16
+ ["bakllava", { contextWindow: 4096, input: ["text", "image"], ollamaOptions: { num_ctx: 4096 } }],
17
+ ["moondream", { contextWindow: 8192, input: ["text", "image"], ollamaOptions: { num_ctx: 8192 } }],
18
+ ["llama3.2-vision", { contextWindow: 131072, input: ["text", "image"], ollamaOptions: { num_ctx: 131072 } }],
19
+ ["minicpm-v", { contextWindow: 4096, input: ["text", "image"], ollamaOptions: { num_ctx: 4096 } }],
17
20
  // ─── Code models ────────────────────────────────────────────────────
18
- ["codestral", { contextWindow: 262144, maxTokens: 32768 }],
19
- ["qwen2.5-coder", { contextWindow: 131072, maxTokens: 32768 }],
20
- ["deepseek-coder-v2", { contextWindow: 131072, maxTokens: 16384 }],
21
- ["starcoder2", { contextWindow: 16384, maxTokens: 8192 }],
22
- ["codegemma", { contextWindow: 8192, maxTokens: 8192 }],
23
- ["codellama", { contextWindow: 16384, maxTokens: 8192 }],
24
- ["devstral", { contextWindow: 131072, maxTokens: 32768 }],
21
+ ["codestral", { contextWindow: 262144, maxTokens: 32768, ollamaOptions: { num_ctx: 262144 } }],
22
+ ["qwen2.5-coder", { contextWindow: 131072, maxTokens: 32768, ollamaOptions: { num_ctx: 131072 } }],
23
+ ["deepseek-coder-v2", { contextWindow: 131072, maxTokens: 16384, ollamaOptions: { num_ctx: 131072 } }],
24
+ ["starcoder2", { contextWindow: 16384, maxTokens: 8192, ollamaOptions: { num_ctx: 16384 } }],
25
+ ["codegemma", { contextWindow: 8192, maxTokens: 8192, ollamaOptions: { num_ctx: 8192 } }],
26
+ ["codellama", { contextWindow: 16384, maxTokens: 8192, ollamaOptions: { num_ctx: 16384 } }],
27
+ ["devstral", { contextWindow: 131072, maxTokens: 32768, ollamaOptions: { num_ctx: 131072 } }],
25
28
  // ─── Llama family ───────────────────────────────────────────────────
26
- ["llama3.3", { contextWindow: 131072, maxTokens: 16384 }],
27
- ["llama3.2", { contextWindow: 131072, maxTokens: 16384 }],
28
- ["llama3.1", { contextWindow: 131072, maxTokens: 16384 }],
29
- ["llama3", { contextWindow: 8192, maxTokens: 8192 }],
30
- ["llama2", { contextWindow: 4096, maxTokens: 4096 }],
29
+ ["llama3.3", { contextWindow: 131072, maxTokens: 16384, ollamaOptions: { num_ctx: 131072 } }],
30
+ ["llama3.2", { contextWindow: 131072, maxTokens: 16384, ollamaOptions: { num_ctx: 131072 } }],
31
+ ["llama3.1", { contextWindow: 131072, maxTokens: 16384, ollamaOptions: { num_ctx: 131072 } }],
32
+ ["llama3", { contextWindow: 8192, maxTokens: 8192, ollamaOptions: { num_ctx: 8192 } }],
33
+ ["llama2", { contextWindow: 4096, maxTokens: 4096, ollamaOptions: { num_ctx: 4096 } }],
31
34
  // ─── Qwen family ────────────────────────────────────────────────────
32
- ["qwen3", { contextWindow: 131072, maxTokens: 32768 }],
33
- ["qwen2.5", { contextWindow: 131072, maxTokens: 32768 }],
34
- ["qwen2", { contextWindow: 131072, maxTokens: 32768 }],
35
+ ["qwen3", { contextWindow: 131072, maxTokens: 32768, ollamaOptions: { num_ctx: 131072 } }],
36
+ ["qwen2.5", { contextWindow: 131072, maxTokens: 32768, ollamaOptions: { num_ctx: 131072 } }],
37
+ ["qwen2", { contextWindow: 131072, maxTokens: 32768, ollamaOptions: { num_ctx: 131072 } }],
35
38
  // ─── Gemma family ───────────────────────────────────────────────────
36
- ["gemma3", { contextWindow: 131072, maxTokens: 16384 }],
37
- ["gemma2", { contextWindow: 8192, maxTokens: 8192 }],
39
+ ["gemma3", { contextWindow: 131072, maxTokens: 16384, ollamaOptions: { num_ctx: 131072 } }],
40
+ ["gemma2", { contextWindow: 8192, maxTokens: 8192, ollamaOptions: { num_ctx: 8192 } }],
38
41
  // ─── Mistral family ─────────────────────────────────────────────────
39
- ["mistral-large", { contextWindow: 131072, maxTokens: 16384 }],
40
- ["mistral-small", { contextWindow: 131072, maxTokens: 16384 }],
41
- ["mistral-nemo", { contextWindow: 131072, maxTokens: 16384 }],
42
- ["mistral", { contextWindow: 32768, maxTokens: 8192 }],
43
- ["mixtral", { contextWindow: 32768, maxTokens: 8192 }],
42
+ ["mistral-large", { contextWindow: 131072, maxTokens: 16384, ollamaOptions: { num_ctx: 131072 } }],
43
+ ["mistral-small", { contextWindow: 131072, maxTokens: 16384, ollamaOptions: { num_ctx: 131072 } }],
44
+ ["mistral-nemo", { contextWindow: 131072, maxTokens: 16384, ollamaOptions: { num_ctx: 131072 } }],
45
+ ["mistral", { contextWindow: 32768, maxTokens: 8192, ollamaOptions: { num_ctx: 32768 } }],
46
+ ["mixtral", { contextWindow: 32768, maxTokens: 8192, ollamaOptions: { num_ctx: 32768 } }],
44
47
  // ─── Phi family ─────────────────────────────────────────────────────
45
- ["phi4", { contextWindow: 16384, maxTokens: 16384 }],
46
- ["phi3.5", { contextWindow: 131072, maxTokens: 16384 }],
47
- ["phi3", { contextWindow: 131072, maxTokens: 4096 }],
48
+ ["phi4", { contextWindow: 16384, maxTokens: 16384, ollamaOptions: { num_ctx: 16384 } }],
49
+ ["phi3.5", { contextWindow: 131072, maxTokens: 16384, ollamaOptions: { num_ctx: 131072 } }],
50
+ ["phi3", { contextWindow: 131072, maxTokens: 4096, ollamaOptions: { num_ctx: 131072 } }],
48
51
  // ─── Command R ──────────────────────────────────────────────────────
49
- ["command-r-plus", { contextWindow: 131072, maxTokens: 16384 }],
50
- ["command-r", { contextWindow: 131072, maxTokens: 16384 }],
52
+ ["command-r-plus", { contextWindow: 131072, maxTokens: 16384, ollamaOptions: { num_ctx: 131072 } }],
53
+ ["command-r", { contextWindow: 131072, maxTokens: 16384, ollamaOptions: { num_ctx: 131072 } }],
51
54
  ];
52
55
  /**
53
56
  * Look up capabilities for a model by name.
@@ -0,0 +1,54 @@
1
+ // GSD2 — Ollama Extension: NDJSON streaming parser
2
+ /**
3
+ * Parses a streaming NDJSON (newline-delimited JSON) response body into
4
+ * typed objects. Used for Ollama's /api/chat and /api/pull endpoints.
5
+ *
6
+ * @param strict When true, malformed JSON lines throw instead of being skipped.
7
+ * Use strict mode for inference streams where silent data loss is unacceptable.
8
+ * Use permissive mode (default) for progress endpoints like /api/pull.
9
+ */
10
+ export async function* parseNDJsonStream(body, signal, strict = false) {
11
+ const reader = body.getReader();
12
+ const decoder = new TextDecoder();
13
+ let buffer = "";
14
+ try {
15
+ while (true) {
16
+ if (signal?.aborted)
17
+ break;
18
+ const { done, value } = await reader.read();
19
+ if (done)
20
+ break;
21
+ buffer += decoder.decode(value, { stream: true });
22
+ const lines = buffer.split("\n");
23
+ buffer = lines.pop() ?? "";
24
+ for (const line of lines) {
25
+ const trimmed = line.trim();
26
+ if (!trimmed)
27
+ continue;
28
+ try {
29
+ yield JSON.parse(trimmed);
30
+ }
31
+ catch (err) {
32
+ if (strict) {
33
+ throw new Error(`Malformed NDJSON line from Ollama: ${trimmed.slice(0, 200)}`);
34
+ }
35
+ // Permissive mode: skip malformed lines
36
+ }
37
+ }
38
+ }
39
+ // Flush remaining buffer (skip if aborted)
40
+ if (buffer.trim() && !signal?.aborted) {
41
+ try {
42
+ yield JSON.parse(buffer.trim());
43
+ }
44
+ catch (err) {
45
+ if (strict) {
46
+ throw new Error(`Malformed NDJSON line from Ollama: ${buffer.trim().slice(0, 200)}`);
47
+ }
48
+ }
49
+ }
50
+ }
51
+ finally {
52
+ reader.releaseLock();
53
+ }
54
+ }