gsd-pi 2.45.0 → 2.46.0-dev.cc9d310

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 (347) hide show
  1. package/dist/help-text.js +1 -1
  2. package/dist/loader.js +34 -0
  3. package/dist/resources/extensions/gsd/auto/phases.js +27 -42
  4. package/dist/resources/extensions/gsd/auto/run-unit.js +6 -3
  5. package/dist/resources/extensions/gsd/auto/session.js +0 -11
  6. package/dist/resources/extensions/gsd/auto-artifact-paths.js +112 -0
  7. package/dist/resources/extensions/gsd/auto-post-unit.js +25 -96
  8. package/dist/resources/extensions/gsd/auto-start.js +2 -3
  9. package/dist/resources/extensions/gsd/auto-worktree.js +5 -4
  10. package/dist/resources/extensions/gsd/auto.js +12 -57
  11. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +15 -12
  12. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +18 -0
  13. package/dist/resources/extensions/gsd/commands/context.js +0 -4
  14. package/dist/resources/extensions/gsd/commands/handlers/parallel.js +1 -1
  15. package/dist/resources/extensions/gsd/crash-recovery.js +2 -4
  16. package/dist/resources/extensions/gsd/dashboard-overlay.js +0 -44
  17. package/dist/resources/extensions/gsd/db-writer.js +9 -9
  18. package/dist/resources/extensions/gsd/doctor-checks.js +167 -2
  19. package/dist/resources/extensions/gsd/doctor.js +5 -3
  20. package/dist/resources/extensions/gsd/gsd-db.js +16 -3
  21. package/dist/resources/extensions/gsd/guided-flow.js +1 -2
  22. package/dist/resources/extensions/gsd/parallel-merge.js +1 -1
  23. package/dist/resources/extensions/gsd/parallel-orchestrator.js +5 -18
  24. package/dist/resources/extensions/gsd/preferences-types.js +2 -2
  25. package/dist/resources/extensions/gsd/preferences.js +8 -4
  26. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +21 -8
  27. package/dist/resources/extensions/gsd/prompts/complete-slice.md +10 -23
  28. package/dist/resources/extensions/gsd/prompts/discuss.md +2 -2
  29. package/dist/resources/extensions/gsd/prompts/execute-task.md +5 -15
  30. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
  31. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
  32. package/dist/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
  33. package/dist/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
  34. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  35. package/dist/resources/extensions/gsd/prompts/plan-slice.md +4 -2
  36. package/dist/resources/extensions/gsd/prompts/queue.md +2 -2
  37. package/dist/resources/extensions/gsd/prompts/quick-task.md +2 -0
  38. package/dist/resources/extensions/gsd/prompts/reactive-execute.md +1 -1
  39. package/dist/resources/extensions/gsd/prompts/research-slice.md +3 -3
  40. package/dist/resources/extensions/gsd/prompts/rethink.md +7 -2
  41. package/dist/resources/extensions/gsd/prompts/system.md +1 -1
  42. package/dist/resources/extensions/gsd/session-lock.js +1 -3
  43. package/dist/resources/extensions/gsd/state.js +7 -0
  44. package/dist/resources/extensions/gsd/sync-lock.js +89 -0
  45. package/dist/resources/extensions/gsd/tools/complete-milestone.js +61 -11
  46. package/dist/resources/extensions/gsd/tools/complete-slice.js +56 -11
  47. package/dist/resources/extensions/gsd/tools/complete-task.js +50 -2
  48. package/dist/resources/extensions/gsd/tools/plan-milestone.js +37 -1
  49. package/dist/resources/extensions/gsd/tools/plan-slice.js +30 -1
  50. package/dist/resources/extensions/gsd/tools/plan-task.js +27 -1
  51. package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +32 -2
  52. package/dist/resources/extensions/gsd/tools/reopen-slice.js +86 -0
  53. package/dist/resources/extensions/gsd/tools/reopen-task.js +90 -0
  54. package/dist/resources/extensions/gsd/tools/replan-slice.js +32 -2
  55. package/dist/resources/extensions/gsd/unit-ownership.js +85 -0
  56. package/dist/resources/extensions/gsd/workflow-events.js +102 -0
  57. package/dist/resources/extensions/gsd/workflow-logger.js +193 -0
  58. package/dist/resources/extensions/gsd/workflow-manifest.js +244 -0
  59. package/dist/resources/extensions/gsd/workflow-migration.js +280 -0
  60. package/dist/resources/extensions/gsd/workflow-projections.js +373 -0
  61. package/dist/resources/extensions/gsd/workflow-reconcile.js +411 -0
  62. package/dist/resources/extensions/gsd/worktree-manager.js +4 -3
  63. package/dist/resources/extensions/gsd/worktree-resolver.js +37 -0
  64. package/dist/resources/extensions/gsd/write-intercept.js +84 -0
  65. package/dist/resources/extensions/voice/index.js +11 -16
  66. package/dist/resources/extensions/voice/linux-ready.js +67 -0
  67. package/dist/web/standalone/.next/BUILD_ID +1 -1
  68. package/dist/web/standalone/.next/app-path-routes-manifest.json +17 -17
  69. package/dist/web/standalone/.next/build-manifest.json +3 -3
  70. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  71. package/dist/web/standalone/.next/required-server-files.json +3 -3
  72. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  73. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  74. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  75. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  83. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  84. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  85. package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
  86. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  87. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  88. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  89. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  91. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  93. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  94. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  95. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  96. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  97. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  98. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  99. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  100. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  101. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  102. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  103. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  104. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  105. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  106. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  107. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  108. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  109. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  111. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  113. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  115. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  121. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  122. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  123. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  124. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  125. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  126. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  127. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  128. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  129. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  130. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  131. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  132. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  133. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  134. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  135. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  136. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +5 -5
  137. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  138. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  139. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  140. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  141. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  142. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  143. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  144. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  145. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  146. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  147. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  148. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  149. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  150. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  151. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  152. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  153. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  154. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  155. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  156. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
  157. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  158. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  159. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  160. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
  161. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  162. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
  163. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  164. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  165. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  166. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  167. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  168. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  169. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  170. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  171. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  172. package/dist/web/standalone/.next/server/app/index.html +1 -1
  173. package/dist/web/standalone/.next/server/app/index.rsc +4 -4
  174. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  175. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  176. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  177. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  178. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  179. package/dist/web/standalone/.next/server/app/page.js +2 -2
  180. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  181. package/dist/web/standalone/.next/server/app-paths-manifest.json +17 -17
  182. package/dist/web/standalone/.next/server/chunks/229.js +1 -1
  183. package/dist/web/standalone/.next/server/chunks/471.js +3 -3
  184. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  185. package/dist/web/standalone/.next/server/middleware.js +2 -2
  186. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  187. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  188. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  189. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  190. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  191. package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
  192. package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
  193. package/dist/web/standalone/.next/static/chunks/app/page-6654a8cca61a3d1c.js +1 -0
  194. package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
  195. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
  196. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  197. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  198. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  199. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  200. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  201. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  202. package/dist/web/standalone/server.js +1 -1
  203. package/package.json +2 -1
  204. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.d.ts.map +1 -1
  205. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js +2 -0
  206. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js.map +1 -1
  207. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +2 -1
  208. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  209. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  210. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.d.ts +4 -0
  211. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.d.ts.map +1 -1
  212. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.js +10 -5
  213. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.js.map +1 -1
  214. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.test.d.ts +2 -0
  215. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.test.d.ts.map +1 -0
  216. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.test.js +185 -0
  217. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.test.js.map +1 -0
  218. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js +239 -10
  219. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js.map +1 -1
  220. package/packages/pi-coding-agent/dist/core/model-registry.d.ts +2 -1
  221. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  222. package/packages/pi-coding-agent/dist/core/model-registry.js +20 -2
  223. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  224. package/packages/pi-coding-agent/dist/core/package-commands.test.js +206 -195
  225. package/packages/pi-coding-agent/dist/core/package-commands.test.js.map +1 -1
  226. package/packages/pi-coding-agent/package.json +1 -1
  227. package/packages/pi-coding-agent/src/core/compaction-orchestrator.ts +2 -0
  228. package/packages/pi-coding-agent/src/core/extensions/types.ts +2 -1
  229. package/packages/pi-coding-agent/src/core/lifecycle-hooks.test.ts +227 -0
  230. package/packages/pi-coding-agent/src/core/lifecycle-hooks.ts +11 -5
  231. package/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts +297 -11
  232. package/packages/pi-coding-agent/src/core/model-registry.ts +30 -3
  233. package/packages/pi-coding-agent/src/core/package-commands.test.ts +227 -205
  234. package/pkg/package.json +1 -1
  235. package/src/resources/extensions/gsd/auto/loop-deps.ts +0 -19
  236. package/src/resources/extensions/gsd/auto/phases.ts +24 -44
  237. package/src/resources/extensions/gsd/auto/run-unit.ts +6 -3
  238. package/src/resources/extensions/gsd/auto/session.ts +0 -18
  239. package/src/resources/extensions/gsd/auto-artifact-paths.ts +131 -0
  240. package/src/resources/extensions/gsd/auto-dashboard.ts +0 -1
  241. package/src/resources/extensions/gsd/auto-post-unit.ts +25 -106
  242. package/src/resources/extensions/gsd/auto-start.ts +1 -3
  243. package/src/resources/extensions/gsd/auto-worktree.ts +8 -5
  244. package/src/resources/extensions/gsd/auto.ts +7 -83
  245. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +15 -12
  246. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +22 -0
  247. package/src/resources/extensions/gsd/commands/context.ts +0 -5
  248. package/src/resources/extensions/gsd/commands/handlers/parallel.ts +1 -1
  249. package/src/resources/extensions/gsd/crash-recovery.ts +1 -5
  250. package/src/resources/extensions/gsd/dashboard-overlay.ts +0 -50
  251. package/src/resources/extensions/gsd/db-writer.ts +9 -17
  252. package/src/resources/extensions/gsd/doctor-checks.ts +180 -2
  253. package/src/resources/extensions/gsd/doctor-types.ts +7 -1
  254. package/src/resources/extensions/gsd/doctor.ts +6 -3
  255. package/src/resources/extensions/gsd/gsd-db.ts +16 -3
  256. package/src/resources/extensions/gsd/guided-flow.ts +1 -2
  257. package/src/resources/extensions/gsd/journal.ts +6 -1
  258. package/src/resources/extensions/gsd/parallel-merge.ts +1 -1
  259. package/src/resources/extensions/gsd/parallel-orchestrator.ts +5 -21
  260. package/src/resources/extensions/gsd/preferences-types.ts +2 -2
  261. package/src/resources/extensions/gsd/preferences.ts +7 -3
  262. package/src/resources/extensions/gsd/prompts/complete-milestone.md +21 -8
  263. package/src/resources/extensions/gsd/prompts/complete-slice.md +10 -23
  264. package/src/resources/extensions/gsd/prompts/discuss.md +2 -2
  265. package/src/resources/extensions/gsd/prompts/execute-task.md +5 -15
  266. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
  267. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
  268. package/src/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
  269. package/src/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
  270. package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  271. package/src/resources/extensions/gsd/prompts/plan-slice.md +4 -2
  272. package/src/resources/extensions/gsd/prompts/queue.md +2 -2
  273. package/src/resources/extensions/gsd/prompts/quick-task.md +2 -0
  274. package/src/resources/extensions/gsd/prompts/reactive-execute.md +1 -1
  275. package/src/resources/extensions/gsd/prompts/research-slice.md +3 -3
  276. package/src/resources/extensions/gsd/prompts/rethink.md +7 -2
  277. package/src/resources/extensions/gsd/prompts/system.md +1 -1
  278. package/src/resources/extensions/gsd/session-lock.ts +0 -4
  279. package/src/resources/extensions/gsd/state.ts +8 -0
  280. package/src/resources/extensions/gsd/sync-lock.ts +94 -0
  281. package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +5 -13
  282. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +6 -10
  283. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +96 -0
  284. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +264 -228
  285. package/src/resources/extensions/gsd/tests/complete-task.test.ts +317 -250
  286. package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +2 -8
  287. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +0 -3
  288. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +1 -1
  289. package/src/resources/extensions/gsd/tests/idle-recovery.test.ts +1 -1
  290. package/src/resources/extensions/gsd/tests/integration-proof.test.ts +15 -24
  291. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +0 -3
  292. package/src/resources/extensions/gsd/tests/md-importer.test.ts +1 -1
  293. package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
  294. package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +1 -1
  295. package/src/resources/extensions/gsd/tests/milestone-transition-state-rebuild.test.ts +8 -9
  296. package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +42 -3
  297. package/src/resources/extensions/gsd/tests/parallel-budget-atomicity.test.ts +0 -1
  298. package/src/resources/extensions/gsd/tests/parallel-crash-recovery.test.ts +0 -7
  299. package/src/resources/extensions/gsd/tests/parallel-merge.test.ts +7 -8
  300. package/src/resources/extensions/gsd/tests/parallel-orchestration.test.ts +20 -24
  301. package/src/resources/extensions/gsd/tests/parallel-worker-monitoring.test.ts +0 -2
  302. package/src/resources/extensions/gsd/tests/plan-milestone.test.ts +9 -6
  303. package/src/resources/extensions/gsd/tests/post-mutation-hook.test.ts +171 -0
  304. package/src/resources/extensions/gsd/tests/preferences.test.ts +7 -9
  305. package/src/resources/extensions/gsd/tests/projection-regression.test.ts +174 -0
  306. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +15 -14
  307. package/src/resources/extensions/gsd/tests/reopen-slice.test.ts +155 -0
  308. package/src/resources/extensions/gsd/tests/reopen-task.test.ts +165 -0
  309. package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +1 -4
  310. package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +2 -3
  311. package/src/resources/extensions/gsd/tests/sync-lock.test.ts +122 -0
  312. package/src/resources/extensions/gsd/tests/unit-ownership.test.ts +175 -0
  313. package/src/resources/extensions/gsd/tests/workflow-events.test.ts +205 -0
  314. package/src/resources/extensions/gsd/tests/workflow-logger.test.ts +275 -0
  315. package/src/resources/extensions/gsd/tests/workflow-manifest.test.ts +186 -0
  316. package/src/resources/extensions/gsd/tests/workflow-projections.test.ts +171 -0
  317. package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +220 -0
  318. package/src/resources/extensions/gsd/tests/write-intercept.test.ts +76 -0
  319. package/src/resources/extensions/gsd/tools/complete-milestone.ts +74 -11
  320. package/src/resources/extensions/gsd/tools/complete-slice.ts +68 -11
  321. package/src/resources/extensions/gsd/tools/complete-task.ts +63 -1
  322. package/src/resources/extensions/gsd/tools/plan-milestone.ts +45 -0
  323. package/src/resources/extensions/gsd/tools/plan-slice.ts +38 -0
  324. package/src/resources/extensions/gsd/tools/plan-task.ts +35 -1
  325. package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +39 -1
  326. package/src/resources/extensions/gsd/tools/reopen-slice.ts +125 -0
  327. package/src/resources/extensions/gsd/tools/reopen-task.ts +129 -0
  328. package/src/resources/extensions/gsd/tools/replan-slice.ts +38 -1
  329. package/src/resources/extensions/gsd/types.ts +8 -0
  330. package/src/resources/extensions/gsd/unit-ownership.ts +104 -0
  331. package/src/resources/extensions/gsd/workflow-events.ts +154 -0
  332. package/src/resources/extensions/gsd/workflow-logger.ts +243 -0
  333. package/src/resources/extensions/gsd/workflow-manifest.ts +334 -0
  334. package/src/resources/extensions/gsd/workflow-migration.ts +345 -0
  335. package/src/resources/extensions/gsd/workflow-projections.ts +425 -0
  336. package/src/resources/extensions/gsd/workflow-reconcile.ts +503 -0
  337. package/src/resources/extensions/gsd/worktree-manager.ts +4 -9
  338. package/src/resources/extensions/gsd/worktree-resolver.ts +37 -0
  339. package/src/resources/extensions/gsd/write-intercept.ts +90 -0
  340. package/src/resources/extensions/voice/index.ts +11 -21
  341. package/src/resources/extensions/voice/linux-ready.ts +87 -0
  342. package/src/resources/extensions/voice/tests/linux-ready.test.ts +124 -0
  343. package/dist/web/standalone/.next/static/chunks/app/page-12dd5ece0df4badc.js +0 -1
  344. package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
  345. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
  346. /package/dist/web/standalone/.next/static/{wUzEX1U3CmFcMry2SUDJn → ZIDqryyYDroh_8AnaAOSG}/_buildManifest.js +0 -0
  347. /package/dist/web/standalone/.next/static/{wUzEX1U3CmFcMry2SUDJn → ZIDqryyYDroh_8AnaAOSG}/_ssgManifest.js +0 -0
@@ -1,6 +1,7 @@
1
1
  import assert from "node:assert/strict";
2
2
  import { describe, it } from "node:test";
3
- import type { Api, Model } from "@gsd/pi-ai";
3
+ import type { Api, Model, SimpleStreamOptions, Context, AssistantMessageEventStream } from "@gsd/pi-ai";
4
+ import { getApiProvider } from "@gsd/pi-ai";
4
5
  import type { AuthStorage } from "./auth-storage.js";
5
6
  import { ModelRegistry } from "./model-registry.js";
6
7
 
@@ -17,11 +18,11 @@ function createRegistry(hasAuthFn?: (provider: string) => boolean): ModelRegistr
17
18
  return new ModelRegistry(authStorage, undefined);
18
19
  }
19
20
 
20
- function createProviderModel(id: string): NonNullable<Parameters<ModelRegistry["registerProvider"]>[1]["models"]>[number] {
21
+ function createProviderModel(id: string, api?: string): NonNullable<Parameters<ModelRegistry["registerProvider"]>[1]["models"]>[number] {
21
22
  return {
22
23
  id,
23
24
  name: id,
24
- api: "openai-completions",
25
+ api: (api ?? "openai-completions") as Api,
25
26
  reasoning: false,
26
27
  input: ["text"],
27
28
  cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
@@ -34,34 +35,89 @@ function findModel(registry: ModelRegistry, provider: string, id: string): Model
34
35
  return registry.getAvailable().find((m) => m.provider === provider && m.id === id);
35
36
  }
36
37
 
38
+ function makeModel(provider: string, id: string, api: string): Model<Api> {
39
+ return {
40
+ id,
41
+ name: id,
42
+ api: api as Api,
43
+ provider,
44
+ baseUrl: `${provider}:`,
45
+ reasoning: false,
46
+ input: ["text"],
47
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
48
+ contextWindow: 128000,
49
+ maxTokens: 16384,
50
+ };
51
+ }
52
+
53
+ function makeContext(): Context {
54
+ return {
55
+ systemPrompt: "test",
56
+ messages: [{ role: "user", content: "hello", timestamp: Date.now() }],
57
+ };
58
+ }
59
+
60
+ /** No-op streamSimple for tests that need one to pass validation but don't inspect it. */
61
+ const noopStreamSimple = (_model: Model<Api>, _context: Context, _options?: SimpleStreamOptions) => {
62
+ return {
63
+ [Symbol.asyncIterator]() { return { next: async () => ({ value: undefined, done: true as const }) }; },
64
+ result: () => Promise.resolve({ role: "assistant" as const, content: [], api: "test" as Api, provider: "test", model: "test", usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } }, stopReason: "stop" as const, timestamp: Date.now() }),
65
+ push: () => {},
66
+ end: () => {},
67
+ } as unknown as AssistantMessageEventStream;
68
+ };
69
+
70
+ /** Create a spy streamSimple that captures the options it receives and returns a stub stream. */
71
+ function createStreamSpy(): {
72
+ streamSimple: (model: Model<Api>, context: Context, options?: SimpleStreamOptions) => AssistantMessageEventStream;
73
+ getCapturedOptions: () => SimpleStreamOptions | undefined;
74
+ } {
75
+ let capturedOptions: SimpleStreamOptions | undefined;
76
+ const streamSimple = (_model: Model<Api>, _context: Context, options?: SimpleStreamOptions) => {
77
+ capturedOptions = options;
78
+ // Return a minimal stub that satisfies AssistantMessageEventStream
79
+ return {
80
+ [Symbol.asyncIterator]() { return { next: async () => ({ value: undefined, done: true as const }) }; },
81
+ result: () => Promise.resolve({ role: "assistant" as const, content: [], api: "test" as Api, provider: "test", model: "test", usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } }, stopReason: "stop" as const, timestamp: Date.now() }),
82
+ push: () => {},
83
+ end: () => {},
84
+ } as unknown as AssistantMessageEventStream;
85
+ };
86
+ return { streamSimple, getCapturedOptions: () => capturedOptions };
87
+ }
88
+
37
89
  // ─── Registration ─────────────────────────────────────────────────────────────
38
90
 
39
91
  describe("ModelRegistry authMode — registration", () => {
40
- it("registers externalCli provider without apiKey/oauth", () => {
92
+ it("registers externalCli provider with streamSimple and without apiKey/oauth", () => {
41
93
  const registry = createRegistry();
94
+ const spy = createStreamSpy();
42
95
  assert.doesNotThrow(() => {
43
96
  registry.registerProvider("cli-provider", {
44
97
  authMode: "externalCli",
45
98
  baseUrl: "https://cli.local",
46
99
  api: "openai-completions",
100
+ streamSimple: spy.streamSimple,
47
101
  models: [createProviderModel("cli-model")],
48
102
  });
49
103
  });
50
104
  });
51
105
 
52
- it("registers none provider without apiKey/oauth", () => {
106
+ it("registers none provider with streamSimple and without apiKey/oauth", () => {
53
107
  const registry = createRegistry();
108
+ const spy = createStreamSpy();
54
109
  assert.doesNotThrow(() => {
55
110
  registry.registerProvider("none-provider", {
56
111
  authMode: "none",
57
112
  baseUrl: "http://localhost:11434",
58
113
  api: "openai-completions",
114
+ streamSimple: spy.streamSimple,
59
115
  models: [createProviderModel("local-model")],
60
116
  });
61
117
  });
62
118
  });
63
119
 
64
- it("rejects apiKey provider without apiKey or oauth", () => {
120
+ it("rejects apiKey provider without apiKey or oauth — message mentions authMode", () => {
65
121
  const registry = createRegistry();
66
122
  assert.throws(() => {
67
123
  registry.registerProvider("apikey-provider", {
@@ -70,6 +126,10 @@ describe("ModelRegistry authMode — registration", () => {
70
126
  api: "openai-completions",
71
127
  models: [createProviderModel("model")],
72
128
  });
129
+ }, (err: Error) => {
130
+ assert.ok(err.message.includes("authMode"), "error message must mention authMode");
131
+ assert.ok(err.message.includes("externalCli"), "error message must suggest externalCli");
132
+ return true;
73
133
  });
74
134
  });
75
135
 
@@ -81,6 +141,79 @@ describe("ModelRegistry authMode — registration", () => {
81
141
  api: "openai-completions",
82
142
  models: [createProviderModel("model")],
83
143
  });
144
+ }, (err: Error) => {
145
+ assert.ok(err.message.includes("authMode"), "error message must mention authMode");
146
+ return true;
147
+ });
148
+ });
149
+
150
+ it("rejects externalCli provider without streamSimple", () => {
151
+ const registry = createRegistry();
152
+ assert.throws(() => {
153
+ registry.registerProvider("cli-no-stream", {
154
+ authMode: "externalCli",
155
+ baseUrl: "https://cli.local",
156
+ api: "openai-completions",
157
+ models: [createProviderModel("model")],
158
+ });
159
+ }, (err: Error) => {
160
+ assert.ok(err.message.includes("streamSimple"), "error message must mention streamSimple");
161
+ assert.ok(err.message.includes("externalCli"), "error message must mention authMode");
162
+ return true;
163
+ });
164
+ });
165
+
166
+ it("rejects none provider without streamSimple", () => {
167
+ const registry = createRegistry();
168
+ assert.throws(() => {
169
+ registry.registerProvider("none-no-stream", {
170
+ authMode: "none",
171
+ baseUrl: "http://localhost:11434",
172
+ api: "openai-completions",
173
+ models: [createProviderModel("model")],
174
+ });
175
+ }, (err: Error) => {
176
+ assert.ok(err.message.includes("streamSimple"), "error message must mention streamSimple");
177
+ assert.ok(err.message.includes("none"), "error message must mention authMode");
178
+ return true;
179
+ });
180
+ });
181
+
182
+ it("rejects externalCli provider that also sets apiKey", () => {
183
+ const registry = createRegistry();
184
+ const spy = createStreamSpy();
185
+ assert.throws(() => {
186
+ registry.registerProvider("cli-with-key", {
187
+ authMode: "externalCli",
188
+ baseUrl: "https://cli.local",
189
+ api: "openai-completions",
190
+ apiKey: "SHOULD_NOT_EXIST",
191
+ streamSimple: spy.streamSimple,
192
+ models: [createProviderModel("model")],
193
+ });
194
+ }, (err: Error) => {
195
+ assert.ok(err.message.includes("apiKey"), "error message must mention apiKey");
196
+ assert.ok(err.message.includes("externalCli"), "error message must mention authMode");
197
+ return true;
198
+ });
199
+ });
200
+
201
+ it("rejects none provider that also sets apiKey", () => {
202
+ const registry = createRegistry();
203
+ const spy = createStreamSpy();
204
+ assert.throws(() => {
205
+ registry.registerProvider("none-with-key", {
206
+ authMode: "none",
207
+ baseUrl: "http://localhost:11434",
208
+ api: "openai-completions",
209
+ apiKey: "SHOULD_NOT_EXIST",
210
+ streamSimple: spy.streamSimple,
211
+ models: [createProviderModel("model")],
212
+ });
213
+ }, (err: Error) => {
214
+ assert.ok(err.message.includes("apiKey"), "error message must mention apiKey");
215
+ assert.ok(err.message.includes("none"), "error message must mention authMode");
216
+ return true;
84
217
  });
85
218
  });
86
219
  });
@@ -99,6 +232,7 @@ describe("ModelRegistry authMode — getProviderAuthMode", () => {
99
232
  authMode: "externalCli",
100
233
  baseUrl: "https://cli.local",
101
234
  api: "openai-completions",
235
+ streamSimple: noopStreamSimple,
102
236
  models: [createProviderModel("m")],
103
237
  });
104
238
  assert.equal(registry.getProviderAuthMode("cli"), "externalCli");
@@ -110,6 +244,7 @@ describe("ModelRegistry authMode — getProviderAuthMode", () => {
110
244
  authMode: "none",
111
245
  baseUrl: "http://localhost:11434",
112
246
  api: "openai-completions",
247
+ streamSimple: noopStreamSimple,
113
248
  models: [createProviderModel("m")],
114
249
  });
115
250
  assert.equal(registry.getProviderAuthMode("local"), "none");
@@ -125,6 +260,7 @@ describe("ModelRegistry authMode — isProviderRequestReady", () => {
125
260
  authMode: "externalCli",
126
261
  baseUrl: "https://cli.local",
127
262
  api: "openai-completions",
263
+ streamSimple: noopStreamSimple,
128
264
  models: [createProviderModel("m")],
129
265
  });
130
266
  assert.equal(registry.isProviderRequestReady("cli"), true);
@@ -136,6 +272,7 @@ describe("ModelRegistry authMode — isProviderRequestReady", () => {
136
272
  authMode: "none",
137
273
  baseUrl: "http://localhost:11434",
138
274
  api: "openai-completions",
275
+ streamSimple: noopStreamSimple,
139
276
  models: [createProviderModel("m")],
140
277
  });
141
278
  assert.equal(registry.isProviderRequestReady("local"), true);
@@ -161,6 +298,7 @@ describe("ModelRegistry authMode — isReady callback", () => {
161
298
  authMode: "externalCli",
162
299
  baseUrl: "https://cli.local",
163
300
  api: "openai-completions",
301
+ streamSimple: noopStreamSimple,
164
302
  isReady: () => false,
165
303
  models: [createProviderModel("m")],
166
304
  });
@@ -185,6 +323,7 @@ describe("ModelRegistry authMode — isReady callback", () => {
185
323
  authMode: "externalCli",
186
324
  baseUrl: "https://cli.local",
187
325
  api: "openai-completions",
326
+ streamSimple: noopStreamSimple,
188
327
  isReady: () => true,
189
328
  models: [createProviderModel("m")],
190
329
  });
@@ -197,6 +336,7 @@ describe("ModelRegistry authMode — isReady callback", () => {
197
336
  authMode: "externalCli",
198
337
  baseUrl: "https://cli.local",
199
338
  api: "openai-completions",
339
+ streamSimple: noopStreamSimple,
200
340
  models: [createProviderModel("m")],
201
341
  });
202
342
  // externalCli without isReady → true (default)
@@ -213,6 +353,7 @@ describe("ModelRegistry authMode — getAvailable", () => {
213
353
  authMode: "externalCli",
214
354
  baseUrl: "https://cli.local",
215
355
  api: "openai-completions",
356
+ streamSimple: noopStreamSimple,
216
357
  models: [createProviderModel("cli-model")],
217
358
  });
218
359
  assert.ok(findModel(registry, "cli", "cli-model"));
@@ -224,6 +365,7 @@ describe("ModelRegistry authMode — getAvailable", () => {
224
365
  authMode: "none",
225
366
  baseUrl: "http://localhost:11434",
226
367
  api: "openai-completions",
368
+ streamSimple: noopStreamSimple,
227
369
  models: [createProviderModel("local-model")],
228
370
  });
229
371
  assert.ok(findModel(registry, "local", "local-model"));
@@ -235,6 +377,7 @@ describe("ModelRegistry authMode — getAvailable", () => {
235
377
  authMode: "externalCli",
236
378
  baseUrl: "https://cli.local",
237
379
  api: "openai-completions",
380
+ streamSimple: noopStreamSimple,
238
381
  isReady: () => false,
239
382
  models: [createProviderModel("m")],
240
383
  });
@@ -243,10 +386,7 @@ describe("ModelRegistry authMode — getAvailable", () => {
243
386
 
244
387
  it("excludes apiKey models without stored auth", () => {
245
388
  const registry = createRegistry(() => false);
246
- // Built-in providers have no registeredProviders entry, so authMode defaults to apiKey
247
- // getAvailable filters by isProviderRequestReady → hasAuth → false
248
389
  const available = registry.getAvailable();
249
- // No models should be available since hasAuth returns false for everything
250
390
  assert.equal(available.length, 0);
251
391
  });
252
392
  });
@@ -260,6 +400,7 @@ describe("ModelRegistry authMode — getApiKey", () => {
260
400
  authMode: "externalCli",
261
401
  baseUrl: "https://cli.local",
262
402
  api: "openai-completions",
403
+ streamSimple: noopStreamSimple,
263
404
  models: [createProviderModel("m")],
264
405
  });
265
406
  const model = registry.getAll().find((m) => m.provider === "cli")!;
@@ -272,6 +413,7 @@ describe("ModelRegistry authMode — getApiKey", () => {
272
413
  authMode: "none",
273
414
  baseUrl: "http://localhost:11434",
274
415
  api: "openai-completions",
416
+ streamSimple: noopStreamSimple,
275
417
  models: [createProviderModel("m")],
276
418
  });
277
419
  const model = registry.getAll().find((m) => m.provider === "local")!;
@@ -280,9 +422,153 @@ describe("ModelRegistry authMode — getApiKey", () => {
280
422
 
281
423
  it("delegates to authStorage for apiKey provider", async () => {
282
424
  const registry = createRegistry();
283
- // authStorage.getApiKey returns undefined (no key configured)
284
- // For apiKey providers this is an expected "no key" response, not early exit
285
425
  const key = await registry.getApiKeyForProvider("anthropic");
286
426
  assert.equal(key, undefined);
287
427
  });
288
428
  });
429
+
430
+ // ─── streamSimple apiKey stripping ────────────────────────────────────────────
431
+
432
+ describe("ModelRegistry authMode — streamSimple apiKey boundary", () => {
433
+ it("strips apiKey from options for externalCli provider", () => {
434
+ const registry = createRegistry();
435
+ const spy = createStreamSpy();
436
+ const apiType = `ext-cli-strip-${Date.now()}`;
437
+
438
+ registry.registerProvider("cli-strip", {
439
+ authMode: "externalCli",
440
+ baseUrl: "https://cli.local",
441
+ api: apiType as Api,
442
+ streamSimple: spy.streamSimple,
443
+ models: [createProviderModel("m", apiType)],
444
+ });
445
+
446
+ const provider = getApiProvider(apiType as Api);
447
+ assert.ok(provider, "provider must be registered in api registry");
448
+
449
+ provider.streamSimple(
450
+ makeModel("cli-strip", "m", apiType),
451
+ makeContext(),
452
+ { apiKey: "should-be-stripped", maxTokens: 1024 } as SimpleStreamOptions,
453
+ );
454
+
455
+ const captured = spy.getCapturedOptions();
456
+ assert.ok(captured, "streamSimple must have been called");
457
+ assert.equal("apiKey" in captured, false, "apiKey must not exist in options for externalCli provider");
458
+ assert.equal(captured.maxTokens, 1024, "other options must pass through");
459
+ });
460
+
461
+ it("strips apiKey from options for none provider", () => {
462
+ const registry = createRegistry();
463
+ const spy = createStreamSpy();
464
+ const apiType = `none-strip-${Date.now()}`;
465
+
466
+ registry.registerProvider("none-strip", {
467
+ authMode: "none",
468
+ baseUrl: "http://localhost:11434",
469
+ api: apiType as Api,
470
+ streamSimple: spy.streamSimple,
471
+ models: [createProviderModel("m", apiType)],
472
+ });
473
+
474
+ const provider = getApiProvider(apiType as Api);
475
+ assert.ok(provider, "provider must be registered in api registry");
476
+
477
+ provider.streamSimple(
478
+ makeModel("none-strip", "m", apiType),
479
+ makeContext(),
480
+ { apiKey: "should-be-stripped", maxTokens: 2048 } as SimpleStreamOptions,
481
+ );
482
+
483
+ const captured = spy.getCapturedOptions();
484
+ assert.ok(captured, "streamSimple must have been called");
485
+ assert.equal("apiKey" in captured, false, "apiKey must not exist in options for none provider");
486
+ assert.equal(captured.maxTokens, 2048, "other options must pass through");
487
+ });
488
+
489
+ it("preserves apiKey in options for apiKey provider", () => {
490
+ const registry = createRegistry();
491
+ const spy = createStreamSpy();
492
+ const apiType = `apikey-preserve-${Date.now()}`;
493
+
494
+ registry.registerProvider("apikey-preserve", {
495
+ apiKey: "MY_KEY",
496
+ baseUrl: "https://api.local",
497
+ api: apiType as Api,
498
+ streamSimple: spy.streamSimple,
499
+ models: [createProviderModel("m", apiType)],
500
+ });
501
+
502
+ const provider = getApiProvider(apiType as Api);
503
+ assert.ok(provider, "provider must be registered in api registry");
504
+
505
+ provider.streamSimple(
506
+ makeModel("apikey-preserve", "m", apiType),
507
+ makeContext(),
508
+ { apiKey: "sk-real-key", maxTokens: 4096 } as SimpleStreamOptions,
509
+ );
510
+
511
+ const captured = spy.getCapturedOptions();
512
+ assert.ok(captured, "streamSimple must have been called");
513
+ assert.equal(captured.apiKey, "sk-real-key", "apiKey must be preserved for apiKey provider");
514
+ assert.equal(captured.maxTokens, 4096, "other options must pass through");
515
+ });
516
+
517
+ it("handles undefined options for externalCli provider", () => {
518
+ const registry = createRegistry();
519
+ const spy = createStreamSpy();
520
+ const apiType = `ext-cli-undef-${Date.now()}`;
521
+
522
+ registry.registerProvider("cli-undef", {
523
+ authMode: "externalCli",
524
+ baseUrl: "https://cli.local",
525
+ api: apiType as Api,
526
+ streamSimple: spy.streamSimple,
527
+ models: [createProviderModel("m", apiType)],
528
+ });
529
+
530
+ const provider = getApiProvider(apiType as Api);
531
+ assert.ok(provider, "provider must be registered in api registry");
532
+
533
+ provider.streamSimple(
534
+ makeModel("cli-undef", "m", apiType),
535
+ makeContext(),
536
+ undefined,
537
+ );
538
+
539
+ const captured = spy.getCapturedOptions();
540
+ assert.ok(captured !== undefined, "streamSimple must have been called");
541
+ assert.equal("apiKey" in captured, false, "apiKey must not exist even when options is undefined");
542
+ });
543
+
544
+ it("strips apiKey but preserves signal and other fields for externalCli", () => {
545
+ const registry = createRegistry();
546
+ const spy = createStreamSpy();
547
+ const apiType = `ext-cli-fields-${Date.now()}`;
548
+ const abortController = new AbortController();
549
+
550
+ registry.registerProvider("cli-fields", {
551
+ authMode: "externalCli",
552
+ baseUrl: "https://cli.local",
553
+ api: apiType as Api,
554
+ streamSimple: spy.streamSimple,
555
+ models: [createProviderModel("m", apiType)],
556
+ });
557
+
558
+ const provider = getApiProvider(apiType as Api);
559
+ assert.ok(provider, "provider must be registered in api registry");
560
+
561
+ provider.streamSimple(
562
+ makeModel("cli-fields", "m", apiType),
563
+ makeContext(),
564
+ { apiKey: "strip-me", maxTokens: 8192, signal: abortController.signal, reasoning: "high" } as SimpleStreamOptions,
565
+ );
566
+
567
+ const captured = spy.getCapturedOptions();
568
+ assert.ok(captured, "streamSimple must have been called");
569
+ assert.equal("apiKey" in captured, false, "apiKey must be stripped");
570
+ assert.equal(captured.maxTokens, 8192, "maxTokens must pass through");
571
+ assert.equal(captured.signal, abortController.signal, "signal must pass through");
572
+ assert.equal((captured as Record<string, unknown>).reasoning, "high", "reasoning must pass through");
573
+ });
574
+ });
@@ -623,7 +623,18 @@ export class ModelRegistry {
623
623
  if (!config.api) {
624
624
  throw new Error(`Provider ${providerName}: "api" is required when registering streamSimple.`);
625
625
  }
626
- const streamSimple = config.streamSimple;
626
+ const rawStreamSimple = config.streamSimple;
627
+ const authMode = config.authMode ?? "apiKey";
628
+
629
+ // Keyless providers never see apiKey in options — enforced at registration,
630
+ // not by convention. Prevents undefined from reaching any handler.
631
+ const streamSimple = (authMode === "externalCli" || authMode === "none")
632
+ ? ((model: Model<Api>, context: Context, options?: SimpleStreamOptions) => {
633
+ const { apiKey: _, ...opts } = options ?? {};
634
+ return rawStreamSimple(model, context, opts as SimpleStreamOptions);
635
+ })
636
+ : rawStreamSimple;
637
+
627
638
  registerApiProvider(
628
639
  {
629
640
  api: config.api,
@@ -649,7 +660,22 @@ export class ModelRegistry {
649
660
  }
650
661
  const authMode = config.authMode ?? (config.oauth ? "oauth" : config.apiKey ? "apiKey" : "apiKey");
651
662
  if (authMode === "apiKey" && !config.apiKey && !config.oauth) {
652
- throw new Error(`Provider ${providerName}: "apiKey" or "oauth" is required when defining models.`);
663
+ throw new Error(
664
+ `Provider ${providerName}: "apiKey" or "oauth" is required when authMode is "apiKey" (the default). ` +
665
+ `Set authMode to "externalCli" or "none" for keyless providers.`,
666
+ );
667
+ }
668
+ if ((authMode === "externalCli" || authMode === "none") && !config.streamSimple) {
669
+ throw new Error(
670
+ `Provider ${providerName}: "streamSimple" is required when authMode is "${authMode}". ` +
671
+ `Keyless providers must supply their own stream handler.`,
672
+ );
673
+ }
674
+ if ((authMode === "externalCli" || authMode === "none") && config.apiKey) {
675
+ throw new Error(
676
+ `Provider ${providerName}: "apiKey" cannot be set when authMode is "${authMode}". ` +
677
+ `Keyless providers should not provide API key credentials.`,
678
+ );
653
679
  }
654
680
 
655
681
  // Parse and add new models
@@ -834,7 +860,8 @@ export class ModelRegistry {
834
860
  */
835
861
  export interface ProviderConfigInput {
836
862
  authMode?: ProviderAuthMode;
837
- /** Optional readiness check. Called by isProviderRequestReady() before default auth checks. */
863
+ /** Optional readiness check. Called by isProviderRequestReady() before default auth checks.
864
+ * Trusted at the same level as extension code — extensions already have arbitrary code execution. */
838
865
  isReady?: () => boolean;
839
866
  baseUrl?: string;
840
867
  apiKey?: string;