gsd-pi 2.43.0-next.8 → 2.44.0-dev.0b97ffd

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 (399) hide show
  1. package/README.md +30 -12
  2. package/dist/cli.js +13 -1
  3. package/dist/help-text.js +24 -0
  4. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +21 -8
  5. package/dist/resources/extensions/gsd/auto-prompts.js +130 -51
  6. package/dist/resources/extensions/gsd/auto-start.js +10 -0
  7. package/dist/resources/extensions/gsd/auto-worktree.js +16 -2
  8. package/dist/resources/extensions/gsd/commands/handlers/workflow.js +5 -0
  9. package/dist/resources/extensions/gsd/dispatch-guard.js +34 -10
  10. package/dist/resources/extensions/gsd/markdown-renderer.js +7 -5
  11. package/dist/resources/extensions/gsd/reactive-graph.js +13 -2
  12. package/dist/resources/extensions/gsd/skill-health.js +3 -1
  13. package/dist/resources/extensions/gsd/tools/plan-milestone.js +2 -11
  14. package/dist/resources/extensions/gsd/tools/plan-slice.js +2 -10
  15. package/dist/resources/extensions/gsd/visualizer-data.js +45 -13
  16. package/dist/resources/extensions/gsd/workspace-index.js +46 -15
  17. package/dist/web/standalone/.next/BUILD_ID +1 -1
  18. package/dist/web/standalone/.next/app-path-routes-manifest.json +18 -18
  19. package/dist/web/standalone/.next/build-manifest.json +3 -3
  20. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  21. package/dist/web/standalone/.next/required-server-files.json +4 -4
  22. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  23. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  24. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  25. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  26. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  27. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  28. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  29. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  30. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  31. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  32. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  33. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  34. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  35. package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
  36. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  37. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  39. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  43. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  44. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  45. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  46. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  47. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  48. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  49. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  50. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  51. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  52. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  53. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  54. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  55. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  56. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  57. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  58. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  59. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  60. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  61. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  62. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  63. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  64. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  65. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  66. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  67. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  68. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  69. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  70. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  71. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  72. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  73. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  74. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  75. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  76. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  77. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  78. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  79. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  80. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  81. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  82. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  83. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  84. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  85. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  86. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +5 -5
  87. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  88. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  89. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  90. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  91. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  92. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  93. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  94. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  95. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  96. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  97. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  98. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  99. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  100. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  101. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  102. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  103. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  104. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  105. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  106. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +1 -1
  107. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  108. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  109. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
  111. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
  113. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  115. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  121. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  122. package/dist/web/standalone/.next/server/app/index.html +1 -1
  123. package/dist/web/standalone/.next/server/app/index.rsc +4 -4
  124. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  125. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  126. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  127. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  128. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  129. package/dist/web/standalone/.next/server/app/page.js +2 -2
  130. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  131. package/dist/web/standalone/.next/server/app-paths-manifest.json +18 -18
  132. package/dist/web/standalone/.next/server/chunks/229.js +1 -1
  133. package/dist/web/standalone/.next/server/chunks/471.js +3 -3
  134. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  135. package/dist/web/standalone/.next/server/middleware.js +2 -2
  136. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  137. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  138. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  139. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  140. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  141. package/dist/web/standalone/.next/static/chunks/app/_not-found/page-f2a7482d42a5614b.js +1 -0
  142. package/dist/web/standalone/.next/static/chunks/app/layout-a16c7a7ecdf0c2cf.js +1 -0
  143. package/dist/web/standalone/.next/static/chunks/app/page-b9367c5ae13b99c6.js +1 -0
  144. package/dist/web/standalone/.next/static/chunks/{main-app-2f2ee7b85712c2bd.js → main-app-fdab67f7802d7832.js} +1 -1
  145. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
  146. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  147. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  148. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  149. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  150. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  151. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  152. package/dist/web/standalone/server.js +1 -1
  153. package/package.json +4 -4
  154. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +3 -3
  155. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  156. package/packages/pi-coding-agent/dist/core/agent-session.js +11 -34
  157. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  158. package/packages/pi-coding-agent/dist/core/auth-storage.test.js +6 -8
  159. package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
  160. package/packages/pi-coding-agent/dist/core/compaction/branch-summarization.d.ts +2 -2
  161. package/packages/pi-coding-agent/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  162. package/packages/pi-coding-agent/dist/core/compaction/branch-summarization.js.map +1 -1
  163. package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts +2 -2
  164. package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts.map +1 -1
  165. package/packages/pi-coding-agent/dist/core/compaction/compaction.js.map +1 -1
  166. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js +4 -4
  167. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js.map +1 -1
  168. package/packages/pi-coding-agent/dist/core/extensions/index.d.ts +1 -1
  169. package/packages/pi-coding-agent/dist/core/extensions/index.d.ts.map +1 -1
  170. package/packages/pi-coding-agent/dist/core/extensions/index.js.map +1 -1
  171. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  172. package/packages/pi-coding-agent/dist/core/extensions/loader.js +18 -0
  173. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  174. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +24 -26
  175. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -1
  176. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +37 -0
  177. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  178. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  179. package/packages/pi-coding-agent/dist/core/fallback-resolver.d.ts.map +1 -1
  180. package/packages/pi-coding-agent/dist/core/fallback-resolver.js +2 -3
  181. package/packages/pi-coding-agent/dist/core/fallback-resolver.js.map +1 -1
  182. package/packages/pi-coding-agent/dist/core/fallback-resolver.test.js +12 -2
  183. package/packages/pi-coding-agent/dist/core/fallback-resolver.test.js.map +1 -1
  184. package/packages/pi-coding-agent/dist/core/fs-utils.test.js +29 -48
  185. package/packages/pi-coding-agent/dist/core/fs-utils.test.js.map +1 -1
  186. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.d.ts +38 -0
  187. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.d.ts.map +1 -0
  188. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.js +192 -0
  189. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.js.map +1 -0
  190. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.d.ts +2 -0
  191. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.d.ts.map +1 -0
  192. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js +255 -0
  193. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js.map +1 -0
  194. package/packages/pi-coding-agent/dist/core/model-registry.d.ts +15 -0
  195. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  196. package/packages/pi-coding-agent/dist/core/model-registry.js +40 -3
  197. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  198. package/packages/pi-coding-agent/dist/core/package-commands.d.ts +25 -0
  199. package/packages/pi-coding-agent/dist/core/package-commands.d.ts.map +1 -0
  200. package/packages/pi-coding-agent/dist/core/package-commands.js +253 -0
  201. package/packages/pi-coding-agent/dist/core/package-commands.js.map +1 -0
  202. package/packages/pi-coding-agent/dist/core/package-commands.test.d.ts +2 -0
  203. package/packages/pi-coding-agent/dist/core/package-commands.test.d.ts.map +1 -0
  204. package/packages/pi-coding-agent/dist/core/package-commands.test.js +225 -0
  205. package/packages/pi-coding-agent/dist/core/package-commands.test.js.map +1 -0
  206. package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js +34 -44
  207. package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js.map +1 -1
  208. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  209. package/packages/pi-coding-agent/dist/core/sdk.js +4 -0
  210. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  211. package/packages/pi-coding-agent/dist/core/session-manager.test.js +30 -34
  212. package/packages/pi-coding-agent/dist/core/session-manager.test.js.map +1 -1
  213. package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js +10 -12
  214. package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js.map +1 -1
  215. package/packages/pi-coding-agent/dist/index.d.ts +3 -1
  216. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  217. package/packages/pi-coding-agent/dist/index.js +1 -0
  218. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  219. package/packages/pi-coding-agent/dist/main.d.ts.map +1 -1
  220. package/packages/pi-coding-agent/dist/main.js +11 -199
  221. package/packages/pi-coding-agent/dist/main.js.map +1 -1
  222. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.test.js +43 -47
  223. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.test.js.map +1 -1
  224. package/packages/pi-coding-agent/package.json +1 -1
  225. package/packages/pi-coding-agent/src/core/agent-session.ts +13 -37
  226. package/packages/pi-coding-agent/src/core/auth-storage.test.ts +7 -7
  227. package/packages/pi-coding-agent/src/core/compaction/branch-summarization.ts +2 -2
  228. package/packages/pi-coding-agent/src/core/compaction/compaction.ts +3 -3
  229. package/packages/pi-coding-agent/src/core/compaction-orchestrator.ts +4 -4
  230. package/packages/pi-coding-agent/src/core/extensions/index.ts +5 -0
  231. package/packages/pi-coding-agent/src/core/extensions/loader.ts +23 -0
  232. package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +26 -26
  233. package/packages/pi-coding-agent/src/core/extensions/types.ts +44 -0
  234. package/packages/pi-coding-agent/src/core/fallback-resolver.test.ts +15 -2
  235. package/packages/pi-coding-agent/src/core/fallback-resolver.ts +2 -3
  236. package/packages/pi-coding-agent/src/core/fs-utils.test.ts +31 -43
  237. package/packages/pi-coding-agent/src/core/lifecycle-hooks.ts +274 -0
  238. package/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts +288 -0
  239. package/packages/pi-coding-agent/src/core/model-registry.ts +39 -3
  240. package/packages/pi-coding-agent/src/core/package-commands.test.ts +240 -0
  241. package/packages/pi-coding-agent/src/core/package-commands.ts +310 -0
  242. package/packages/pi-coding-agent/src/core/resolve-config-value.test.ts +40 -45
  243. package/packages/pi-coding-agent/src/core/sdk.ts +4 -0
  244. package/packages/pi-coding-agent/src/core/session-manager.test.ts +33 -33
  245. package/packages/pi-coding-agent/src/core/tools/edit-diff.test.ts +17 -17
  246. package/packages/pi-coding-agent/src/index.ts +7 -0
  247. package/packages/pi-coding-agent/src/main.ts +11 -232
  248. package/packages/pi-coding-agent/src/resources/extensions/memory/storage.test.ts +74 -74
  249. package/pkg/package.json +1 -1
  250. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +22 -7
  251. package/src/resources/extensions/gsd/auto-prompts.ts +109 -42
  252. package/src/resources/extensions/gsd/auto-start.ts +14 -0
  253. package/src/resources/extensions/gsd/auto-worktree.ts +16 -3
  254. package/src/resources/extensions/gsd/commands/handlers/workflow.ts +8 -0
  255. package/src/resources/extensions/gsd/dispatch-guard.ts +28 -10
  256. package/src/resources/extensions/gsd/markdown-renderer.ts +7 -5
  257. package/src/resources/extensions/gsd/reactive-graph.ts +12 -2
  258. package/src/resources/extensions/gsd/skill-health.ts +2 -1
  259. package/src/resources/extensions/gsd/tests/all-milestones-complete-merge.test.ts +99 -99
  260. package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +14 -16
  261. package/src/resources/extensions/gsd/tests/auto-paused-session-validation.test.ts +43 -57
  262. package/src/resources/extensions/gsd/tests/auto-preflight.test.ts +11 -13
  263. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +465 -523
  264. package/src/resources/extensions/gsd/tests/auto-secrets-gate.test.ts +73 -75
  265. package/src/resources/extensions/gsd/tests/auto-start-needs-discussion.test.ts +34 -56
  266. package/src/resources/extensions/gsd/tests/auto-stash-merge.test.ts +3 -3
  267. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +533 -656
  268. package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +165 -143
  269. package/src/resources/extensions/gsd/tests/cache-staleness-regression.test.ts +29 -52
  270. package/src/resources/extensions/gsd/tests/captures.test.ts +148 -176
  271. package/src/resources/extensions/gsd/tests/claude-import-tui.test.ts +32 -33
  272. package/src/resources/extensions/gsd/tests/collect-from-manifest.test.ts +141 -143
  273. package/src/resources/extensions/gsd/tests/commands-inspect-open-db.test.ts +25 -25
  274. package/src/resources/extensions/gsd/tests/commands-logs.test.ts +81 -81
  275. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +38 -59
  276. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +228 -263
  277. package/src/resources/extensions/gsd/tests/complete-task.test.ts +250 -302
  278. package/src/resources/extensions/gsd/tests/context-store.test.ts +354 -367
  279. package/src/resources/extensions/gsd/tests/continue-here.test.ts +68 -72
  280. package/src/resources/extensions/gsd/tests/cost-projection.test.ts +92 -106
  281. package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +27 -35
  282. package/src/resources/extensions/gsd/tests/dashboard-budget.test.ts +220 -237
  283. package/src/resources/extensions/gsd/tests/db-writer.test.ts +390 -420
  284. package/src/resources/extensions/gsd/tests/definition-loader.test.ts +76 -92
  285. package/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts +68 -83
  286. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +152 -183
  287. package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +78 -101
  288. package/src/resources/extensions/gsd/tests/derive-state.test.ts +192 -227
  289. package/src/resources/extensions/gsd/tests/detection.test.ts +232 -278
  290. package/src/resources/extensions/gsd/tests/dev-engine-wrapper.test.ts +30 -34
  291. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +164 -180
  292. package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +43 -49
  293. package/src/resources/extensions/gsd/tests/dispatch-uat-last-completed.test.ts +28 -32
  294. package/src/resources/extensions/gsd/tests/doctor-completion-deferral.test.ts +27 -29
  295. package/src/resources/extensions/gsd/tests/doctor-delimiter-fix.test.ts +34 -38
  296. package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +54 -75
  297. package/src/resources/extensions/gsd/tests/doctor-environment-worktree.test.ts +21 -32
  298. package/src/resources/extensions/gsd/tests/doctor-environment.test.ts +72 -97
  299. package/src/resources/extensions/gsd/tests/doctor-fixlevel.test.ts +38 -44
  300. package/src/resources/extensions/gsd/tests/doctor-git.test.ts +104 -145
  301. package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +84 -106
  302. package/src/resources/extensions/gsd/tests/doctor-roadmap-summary-atomicity.test.ts +54 -60
  303. package/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +72 -93
  304. package/src/resources/extensions/gsd/tests/doctor.test.ts +104 -134
  305. package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +123 -131
  306. package/src/resources/extensions/gsd/tests/exit-command.test.ts +20 -24
  307. package/src/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +48 -57
  308. package/src/resources/extensions/gsd/tests/files-loadfile-eisdir.test.ts +5 -7
  309. package/src/resources/extensions/gsd/tests/flag-file-db.test.ts +30 -42
  310. package/src/resources/extensions/gsd/tests/freeform-decisions.test.ts +198 -206
  311. package/src/resources/extensions/gsd/tests/git-locale.test.ts +13 -27
  312. package/src/resources/extensions/gsd/tests/git-service.test.ts +285 -388
  313. package/src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts +31 -39
  314. package/src/resources/extensions/gsd/tests/graph-operations.test.ts +63 -69
  315. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +255 -264
  316. package/src/resources/extensions/gsd/tests/gsd-inspect.test.ts +108 -119
  317. package/src/resources/extensions/gsd/tests/gsd-recover.test.ts +81 -103
  318. package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +229 -262
  319. package/src/resources/extensions/gsd/tests/headless-answers.test.ts +13 -13
  320. package/src/resources/extensions/gsd/tests/health-widget.test.ts +29 -37
  321. package/src/resources/extensions/gsd/tests/idle-recovery.test.ts +81 -102
  322. package/src/resources/extensions/gsd/tests/init-wizard.test.ts +16 -18
  323. package/src/resources/extensions/gsd/tests/integration-edge.test.ts +41 -46
  324. package/src/resources/extensions/gsd/tests/integration-lifecycle.test.ts +42 -53
  325. package/src/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +75 -91
  326. package/src/resources/extensions/gsd/tests/integration-proof.test.ts +18 -18
  327. package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +150 -194
  328. package/src/resources/extensions/gsd/tests/md-importer.test.ts +101 -125
  329. package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +45 -54
  330. package/src/resources/extensions/gsd/tests/memory-store.test.ts +80 -93
  331. package/src/resources/extensions/gsd/tests/migrate-command.test.ts +57 -66
  332. package/src/resources/extensions/gsd/tests/migrate-hierarchy.test.ts +83 -93
  333. package/src/resources/extensions/gsd/tests/migrate-parser.test.ts +161 -170
  334. package/src/resources/extensions/gsd/tests/migrate-transformer.test.ts +125 -141
  335. package/src/resources/extensions/gsd/tests/migrate-validator-parsers.test.ts +107 -131
  336. package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +87 -96
  337. package/src/resources/extensions/gsd/tests/migrate-writer.test.ts +125 -164
  338. package/src/resources/extensions/gsd/tests/must-have-parser.test.ts +81 -94
  339. package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +35 -36
  340. package/src/resources/extensions/gsd/tests/overrides.test.ts +99 -106
  341. package/src/resources/extensions/gsd/tests/parallel-crash-recovery.test.ts +40 -47
  342. package/src/resources/extensions/gsd/tests/parallel-worker-monitoring.test.ts +25 -28
  343. package/src/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +66 -83
  344. package/src/resources/extensions/gsd/tests/park-edge-cases.test.ts +54 -77
  345. package/src/resources/extensions/gsd/tests/park-milestone.test.ts +68 -115
  346. package/src/resources/extensions/gsd/tests/parsers.test.ts +546 -611
  347. package/src/resources/extensions/gsd/tests/paths.test.ts +72 -87
  348. package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +77 -117
  349. package/src/resources/extensions/gsd/tests/prompt-db.test.ts +56 -56
  350. package/src/resources/extensions/gsd/tests/queue-draft-detection.test.ts +93 -119
  351. package/src/resources/extensions/gsd/tests/queue-order.test.ts +70 -82
  352. package/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +42 -55
  353. package/src/resources/extensions/gsd/tests/quick-auto-guard.test.ts +100 -0
  354. package/src/resources/extensions/gsd/tests/quick-branch-lifecycle.test.ts +45 -73
  355. package/src/resources/extensions/gsd/tests/reassess-prompt.test.ts +28 -38
  356. package/src/resources/extensions/gsd/tests/replan-slice.test.ts +73 -80
  357. package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +71 -74
  358. package/src/resources/extensions/gsd/tests/requirements.test.ts +70 -75
  359. package/src/resources/extensions/gsd/tests/retry-state-reset.test.ts +44 -66
  360. package/src/resources/extensions/gsd/tests/roadmap-parse-regression.test.ts +114 -181
  361. package/src/resources/extensions/gsd/tests/rule-registry.test.ts +63 -65
  362. package/src/resources/extensions/gsd/tests/run-uat.test.ts +66 -128
  363. package/src/resources/extensions/gsd/tests/session-lock-multipath.test.ts +18 -25
  364. package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +37 -44
  365. package/src/resources/extensions/gsd/tests/shared-wal.test.ts +19 -26
  366. package/src/resources/extensions/gsd/tests/sqlite-unavailable-gate.test.ts +63 -0
  367. package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +6 -8
  368. package/src/resources/extensions/gsd/tests/symlink-numbered-variants.test.ts +22 -28
  369. package/src/resources/extensions/gsd/tests/token-savings.test.ts +54 -56
  370. package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +23 -25
  371. package/src/resources/extensions/gsd/tests/tool-naming.test.ts +9 -11
  372. package/src/resources/extensions/gsd/tests/unique-milestone-ids.test.ts +66 -82
  373. package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +46 -47
  374. package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +20 -22
  375. package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +84 -86
  376. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +41 -43
  377. package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +94 -96
  378. package/src/resources/extensions/gsd/tests/windows-path-normalization.test.ts +11 -13
  379. package/src/resources/extensions/gsd/tests/worker-registry.test.ts +27 -29
  380. package/src/resources/extensions/gsd/tests/workflow-templates.test.ts +50 -52
  381. package/src/resources/extensions/gsd/tests/worktree-bugfix.test.ts +10 -13
  382. package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +14 -18
  383. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +38 -39
  384. package/src/resources/extensions/gsd/tests/worktree-e2e.test.ts +17 -21
  385. package/src/resources/extensions/gsd/tests/worktree-health.test.ts +25 -30
  386. package/src/resources/extensions/gsd/tests/worktree-integration.test.ts +30 -37
  387. package/src/resources/extensions/gsd/tests/worktree-symlink-removal.test.ts +15 -22
  388. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +59 -66
  389. package/src/resources/extensions/gsd/tests/worktree.test.ts +44 -50
  390. package/src/resources/extensions/gsd/tools/plan-milestone.ts +1 -18
  391. package/src/resources/extensions/gsd/tools/plan-slice.ts +1 -15
  392. package/src/resources/extensions/gsd/visualizer-data.ts +46 -14
  393. package/src/resources/extensions/gsd/workspace-index.ts +49 -18
  394. package/dist/web/standalone/.next/static/chunks/app/_not-found/page-e07acdb7dd069836.js +0 -1
  395. package/dist/web/standalone/.next/static/chunks/app/layout-745c6ed5fea5fb06.js +0 -1
  396. package/dist/web/standalone/.next/static/chunks/app/page-801b53eff6e83579.js +0 -1
  397. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-e6255954dccfcf0a.js +0 -1
  398. /package/dist/web/standalone/.next/static/{drUWS0zys9uepCfCwecJv → alS4hoANx0TK4UVZY27da}/_buildManifest.js +0 -0
  399. /package/dist/web/standalone/.next/static/{drUWS0zys9uepCfCwecJv → alS4hoANx0TK4UVZY27da}/_ssgManifest.js +0 -0
@@ -0,0 +1,288 @@
1
+ import assert from "node:assert/strict";
2
+ import { describe, it } from "node:test";
3
+ import type { Api, Model } from "@gsd/pi-ai";
4
+ import type { AuthStorage } from "./auth-storage.js";
5
+ import { ModelRegistry } from "./model-registry.js";
6
+
7
+ function createRegistry(hasAuthFn?: (provider: string) => boolean): ModelRegistry {
8
+ const authStorage = {
9
+ setFallbackResolver: () => {},
10
+ onCredentialChange: () => {},
11
+ getOAuthProviders: () => [],
12
+ get: () => undefined,
13
+ hasAuth: hasAuthFn ?? (() => false),
14
+ getApiKey: async () => undefined,
15
+ } as unknown as AuthStorage;
16
+
17
+ return new ModelRegistry(authStorage, undefined);
18
+ }
19
+
20
+ function createProviderModel(id: string): NonNullable<Parameters<ModelRegistry["registerProvider"]>[1]["models"]>[number] {
21
+ return {
22
+ id,
23
+ name: id,
24
+ api: "openai-completions",
25
+ reasoning: false,
26
+ input: ["text"],
27
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
28
+ contextWindow: 128000,
29
+ maxTokens: 16384,
30
+ };
31
+ }
32
+
33
+ function findModel(registry: ModelRegistry, provider: string, id: string): Model<Api> | undefined {
34
+ return registry.getAvailable().find((m) => m.provider === provider && m.id === id);
35
+ }
36
+
37
+ // ─── Registration ─────────────────────────────────────────────────────────────
38
+
39
+ describe("ModelRegistry authMode — registration", () => {
40
+ it("registers externalCli provider without apiKey/oauth", () => {
41
+ const registry = createRegistry();
42
+ assert.doesNotThrow(() => {
43
+ registry.registerProvider("cli-provider", {
44
+ authMode: "externalCli",
45
+ baseUrl: "https://cli.local",
46
+ api: "openai-completions",
47
+ models: [createProviderModel("cli-model")],
48
+ });
49
+ });
50
+ });
51
+
52
+ it("registers none provider without apiKey/oauth", () => {
53
+ const registry = createRegistry();
54
+ assert.doesNotThrow(() => {
55
+ registry.registerProvider("none-provider", {
56
+ authMode: "none",
57
+ baseUrl: "http://localhost:11434",
58
+ api: "openai-completions",
59
+ models: [createProviderModel("local-model")],
60
+ });
61
+ });
62
+ });
63
+
64
+ it("rejects apiKey provider without apiKey or oauth", () => {
65
+ const registry = createRegistry();
66
+ assert.throws(() => {
67
+ registry.registerProvider("apikey-provider", {
68
+ authMode: "apiKey",
69
+ baseUrl: "https://api.local",
70
+ api: "openai-completions",
71
+ models: [createProviderModel("model")],
72
+ });
73
+ });
74
+ });
75
+
76
+ it("rejects provider with no authMode and no apiKey/oauth (defaults to apiKey)", () => {
77
+ const registry = createRegistry();
78
+ assert.throws(() => {
79
+ registry.registerProvider("bare-provider", {
80
+ baseUrl: "https://api.local",
81
+ api: "openai-completions",
82
+ models: [createProviderModel("model")],
83
+ });
84
+ });
85
+ });
86
+ });
87
+
88
+ // ─── getProviderAuthMode ──────────────────────────────────────────────────────
89
+
90
+ describe("ModelRegistry authMode — getProviderAuthMode", () => {
91
+ it("returns apiKey for unregistered (built-in) providers", () => {
92
+ const registry = createRegistry();
93
+ assert.equal(registry.getProviderAuthMode("anthropic"), "apiKey");
94
+ });
95
+
96
+ it("returns explicit authMode when set", () => {
97
+ const registry = createRegistry();
98
+ registry.registerProvider("cli", {
99
+ authMode: "externalCli",
100
+ baseUrl: "https://cli.local",
101
+ api: "openai-completions",
102
+ models: [createProviderModel("m")],
103
+ });
104
+ assert.equal(registry.getProviderAuthMode("cli"), "externalCli");
105
+ });
106
+
107
+ it("returns none when authMode is none", () => {
108
+ const registry = createRegistry();
109
+ registry.registerProvider("local", {
110
+ authMode: "none",
111
+ baseUrl: "http://localhost:11434",
112
+ api: "openai-completions",
113
+ models: [createProviderModel("m")],
114
+ });
115
+ assert.equal(registry.getProviderAuthMode("local"), "none");
116
+ });
117
+ });
118
+
119
+ // ─── isProviderRequestReady ───────────────────────────────────────────────────
120
+
121
+ describe("ModelRegistry authMode — isProviderRequestReady", () => {
122
+ it("returns true for externalCli without stored auth", () => {
123
+ const registry = createRegistry(() => false);
124
+ registry.registerProvider("cli", {
125
+ authMode: "externalCli",
126
+ baseUrl: "https://cli.local",
127
+ api: "openai-completions",
128
+ models: [createProviderModel("m")],
129
+ });
130
+ assert.equal(registry.isProviderRequestReady("cli"), true);
131
+ });
132
+
133
+ it("returns true for none without stored auth", () => {
134
+ const registry = createRegistry(() => false);
135
+ registry.registerProvider("local", {
136
+ authMode: "none",
137
+ baseUrl: "http://localhost:11434",
138
+ api: "openai-completions",
139
+ models: [createProviderModel("m")],
140
+ });
141
+ assert.equal(registry.isProviderRequestReady("local"), true);
142
+ });
143
+
144
+ it("returns false for apiKey provider without stored auth", () => {
145
+ const registry = createRegistry(() => false);
146
+ assert.equal(registry.isProviderRequestReady("anthropic"), false);
147
+ });
148
+
149
+ it("returns true for apiKey provider with stored auth", () => {
150
+ const registry = createRegistry(() => true);
151
+ assert.equal(registry.isProviderRequestReady("anthropic"), true);
152
+ });
153
+ });
154
+
155
+ // ─── isReady callback ─────────────────────────────────────────────────────────
156
+
157
+ describe("ModelRegistry authMode — isReady callback", () => {
158
+ it("calls isReady and returns its result for externalCli provider", () => {
159
+ const registry = createRegistry(() => false);
160
+ registry.registerProvider("cli-down", {
161
+ authMode: "externalCli",
162
+ baseUrl: "https://cli.local",
163
+ api: "openai-completions",
164
+ isReady: () => false,
165
+ models: [createProviderModel("m")],
166
+ });
167
+ assert.equal(registry.isProviderRequestReady("cli-down"), false);
168
+ });
169
+
170
+ it("calls isReady for apiKey provider (overrides hasAuth)", () => {
171
+ const registry = createRegistry(() => true);
172
+ registry.registerProvider("strict-provider", {
173
+ apiKey: "MY_KEY",
174
+ baseUrl: "https://api.local",
175
+ api: "openai-completions",
176
+ isReady: () => false,
177
+ models: [createProviderModel("m")],
178
+ });
179
+ assert.equal(registry.isProviderRequestReady("strict-provider"), false);
180
+ });
181
+
182
+ it("isReady returning true makes provider available", () => {
183
+ const registry = createRegistry(() => false);
184
+ registry.registerProvider("healthy-cli", {
185
+ authMode: "externalCli",
186
+ baseUrl: "https://cli.local",
187
+ api: "openai-completions",
188
+ isReady: () => true,
189
+ models: [createProviderModel("m")],
190
+ });
191
+ assert.equal(registry.isProviderRequestReady("healthy-cli"), true);
192
+ });
193
+
194
+ it("falls through to default behavior when isReady not provided", () => {
195
+ const registry = createRegistry(() => false);
196
+ registry.registerProvider("no-callback", {
197
+ authMode: "externalCli",
198
+ baseUrl: "https://cli.local",
199
+ api: "openai-completions",
200
+ models: [createProviderModel("m")],
201
+ });
202
+ // externalCli without isReady → true (default)
203
+ assert.equal(registry.isProviderRequestReady("no-callback"), true);
204
+ });
205
+ });
206
+
207
+ // ─── getAvailable ─────────────────────────────────────────────────────────────
208
+
209
+ describe("ModelRegistry authMode — getAvailable", () => {
210
+ it("includes externalCli models without stored auth", () => {
211
+ const registry = createRegistry(() => false);
212
+ registry.registerProvider("cli", {
213
+ authMode: "externalCli",
214
+ baseUrl: "https://cli.local",
215
+ api: "openai-completions",
216
+ models: [createProviderModel("cli-model")],
217
+ });
218
+ assert.ok(findModel(registry, "cli", "cli-model"));
219
+ });
220
+
221
+ it("includes none models without stored auth", () => {
222
+ const registry = createRegistry(() => false);
223
+ registry.registerProvider("local", {
224
+ authMode: "none",
225
+ baseUrl: "http://localhost:11434",
226
+ api: "openai-completions",
227
+ models: [createProviderModel("local-model")],
228
+ });
229
+ assert.ok(findModel(registry, "local", "local-model"));
230
+ });
231
+
232
+ it("excludes externalCli models when isReady returns false", () => {
233
+ const registry = createRegistry(() => false);
234
+ registry.registerProvider("cli-down", {
235
+ authMode: "externalCli",
236
+ baseUrl: "https://cli.local",
237
+ api: "openai-completions",
238
+ isReady: () => false,
239
+ models: [createProviderModel("m")],
240
+ });
241
+ assert.equal(findModel(registry, "cli-down", "m"), undefined);
242
+ });
243
+
244
+ it("excludes apiKey models without stored auth", () => {
245
+ 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
+ const available = registry.getAvailable();
249
+ // No models should be available since hasAuth returns false for everything
250
+ assert.equal(available.length, 0);
251
+ });
252
+ });
253
+
254
+ // ─── getApiKey ────────────────────────────────────────────────────────────────
255
+
256
+ describe("ModelRegistry authMode — getApiKey", () => {
257
+ it("returns undefined for externalCli provider", async () => {
258
+ const registry = createRegistry();
259
+ registry.registerProvider("cli", {
260
+ authMode: "externalCli",
261
+ baseUrl: "https://cli.local",
262
+ api: "openai-completions",
263
+ models: [createProviderModel("m")],
264
+ });
265
+ const model = registry.getAll().find((m) => m.provider === "cli")!;
266
+ assert.equal(await registry.getApiKey(model), undefined);
267
+ });
268
+
269
+ it("returns undefined for none provider", async () => {
270
+ const registry = createRegistry();
271
+ registry.registerProvider("local", {
272
+ authMode: "none",
273
+ baseUrl: "http://localhost:11434",
274
+ api: "openai-completions",
275
+ models: [createProviderModel("m")],
276
+ });
277
+ const model = registry.getAll().find((m) => m.provider === "local")!;
278
+ assert.equal(await registry.getApiKey(model), undefined);
279
+ });
280
+
281
+ it("delegates to authStorage for apiKey provider", async () => {
282
+ 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
+ const key = await registry.getApiKeyForProvider("anthropic");
286
+ assert.equal(key, undefined);
287
+ });
288
+ });
@@ -128,6 +128,8 @@ ajv.addSchema(ModelsConfigSchema, "ModelsConfig");
128
128
 
129
129
  type ModelsConfig = Static<typeof ModelsConfigSchema>;
130
130
 
131
+ export type ProviderAuthMode = "apiKey" | "oauth" | "externalCli" | "none";
132
+
131
133
  /** Provider override config (baseUrl, headers, apiKey) without custom models */
132
134
  interface ProviderOverride {
133
135
  baseUrl?: string;
@@ -513,7 +515,31 @@ export class ModelRegistry {
513
515
  * This is a fast check that doesn't refresh OAuth tokens.
514
516
  */
515
517
  getAvailable(): Model<Api>[] {
516
- return this.models.filter((m) => this.authStorage.hasAuth(m.provider));
518
+ return this.models.filter((m) => this.isProviderRequestReady(m.provider));
519
+ }
520
+
521
+ /**
522
+ * Get auth mode for a provider.
523
+ * Defaults to "apiKey" for built-ins and providers without explicit mode.
524
+ */
525
+ getProviderAuthMode(provider: string): ProviderAuthMode {
526
+ const config = this.registeredProviders.get(provider);
527
+ if (!config) return "apiKey";
528
+ if (config.authMode) return config.authMode;
529
+ if (config.oauth) return "oauth";
530
+ if (config.apiKey) return "apiKey";
531
+ return "apiKey";
532
+ }
533
+
534
+ /**
535
+ * Whether a provider can be used for requests/fallback without hard auth gating.
536
+ */
537
+ isProviderRequestReady(provider: string): boolean {
538
+ const config = this.registeredProviders.get(provider);
539
+ if (config?.isReady) return config.isReady();
540
+ const authMode = this.getProviderAuthMode(provider);
541
+ if (authMode === "externalCli" || authMode === "none") return true;
542
+ return this.authStorage.hasAuth(provider);
517
543
  }
518
544
 
519
545
  /**
@@ -525,17 +551,23 @@ export class ModelRegistry {
525
551
 
526
552
  /**
527
553
  * Get API key for a model.
554
+ * Returns undefined for externalCli/none providers (no key needed).
528
555
  * @param sessionId - Optional session ID for sticky credential selection
529
556
  */
530
557
  async getApiKey(model: Model<Api>, sessionId?: string): Promise<string | undefined> {
558
+ const authMode = this.getProviderAuthMode(model.provider);
559
+ if (authMode === "externalCli" || authMode === "none") return undefined;
531
560
  return this.authStorage.getApiKey(model.provider, sessionId);
532
561
  }
533
562
 
534
563
  /**
535
564
  * Get API key for a provider.
565
+ * Returns undefined for externalCli/none providers (no key needed).
536
566
  * @param sessionId - Optional session ID for sticky credential selection
537
567
  */
538
568
  async getApiKeyForProvider(provider: string, sessionId?: string): Promise<string | undefined> {
569
+ const authMode = this.getProviderAuthMode(provider);
570
+ if (authMode === "externalCli" || authMode === "none") return undefined;
539
571
  return this.authStorage.getApiKey(provider, sessionId);
540
572
  }
541
573
 
@@ -614,7 +646,8 @@ export class ModelRegistry {
614
646
  if (!config.baseUrl) {
615
647
  throw new Error(`Provider ${providerName}: "baseUrl" is required when defining models.`);
616
648
  }
617
- if (!config.apiKey && !config.oauth) {
649
+ const authMode = config.authMode ?? (config.oauth ? "oauth" : config.apiKey ? "apiKey" : "apiKey");
650
+ if (authMode === "apiKey" && !config.apiKey && !config.oauth) {
618
651
  throw new Error(`Provider ${providerName}: "apiKey" or "oauth" is required when defining models.`);
619
652
  }
620
653
 
@@ -702,7 +735,7 @@ export class ModelRegistry {
702
735
 
703
736
  try {
704
737
  const apiKey = await this.authStorage.getApiKey(providerName);
705
- if (!apiKey && providerName !== "ollama") continue;
738
+ if (!apiKey && !this.isProviderRequestReady(providerName)) continue;
706
739
 
707
740
  const models = await adapter.fetchModels(apiKey ?? "", undefined);
708
741
  this.discoveryCache.set(providerName, models);
@@ -780,6 +813,9 @@ export class ModelRegistry {
780
813
  * Input type for registerProvider API.
781
814
  */
782
815
  export interface ProviderConfigInput {
816
+ authMode?: ProviderAuthMode;
817
+ /** Optional readiness check. Called by isProviderRequestReady() before default auth checks. */
818
+ isReady?: () => boolean;
783
819
  baseUrl?: string;
784
820
  apiKey?: string;
785
821
  api?: Api;
@@ -0,0 +1,240 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { Writable } from "node:stream";
6
+ import { describe, it } from "node:test";
7
+ import { runPackageCommand } from "./package-commands.js";
8
+
9
+ function createCaptureStream() {
10
+ let output = "";
11
+ const stream = new Writable({
12
+ write(chunk, _encoding, callback) {
13
+ output += chunk.toString();
14
+ callback();
15
+ },
16
+ }) as unknown as NodeJS.WriteStream;
17
+ return { stream, getOutput: () => output };
18
+ }
19
+
20
+ function writePackage(root: string, files: Record<string, string>): void {
21
+ for (const [relPath, content] of Object.entries(files)) {
22
+ const abs = join(root, relPath);
23
+ mkdirSync(join(abs, ".."), { recursive: true });
24
+ writeFileSync(abs, content, "utf-8");
25
+ }
26
+ }
27
+
28
+ describe("runPackageCommand lifecycle hooks", () => {
29
+ it("executes registered beforeInstall and afterInstall handlers for local packages", async () => {
30
+ const root = mkdtempSync(join(tmpdir(), "pi-lifecycle-install-"));
31
+ const cwd = join(root, "cwd");
32
+ const agentDir = join(root, "agent");
33
+ const extensionDir = join(root, "ext-registered");
34
+ mkdirSync(cwd, { recursive: true });
35
+ mkdirSync(agentDir, { recursive: true });
36
+ mkdirSync(extensionDir, { recursive: true });
37
+
38
+ try {
39
+ writePackage(extensionDir, {
40
+ "package.json": JSON.stringify({
41
+ name: "ext-registered",
42
+ type: "module",
43
+ pi: { extensions: ["./index.js"] },
44
+ }),
45
+ "index.js": `
46
+ import { writeFileSync } from "node:fs";
47
+ import { join } from "node:path";
48
+ export default function (pi) {
49
+ pi.registerBeforeInstall((ctx) => {
50
+ writeFileSync(join(ctx.installedPath, "before-install-ran.txt"), "ok", "utf-8");
51
+ });
52
+ pi.registerAfterInstall((ctx) => {
53
+ writeFileSync(join(ctx.installedPath, "after-install-ran.txt"), "ok", "utf-8");
54
+ });
55
+ }
56
+ `,
57
+ });
58
+
59
+ const stdout = createCaptureStream();
60
+ const stderr = createCaptureStream();
61
+ const result = await runPackageCommand({
62
+ appName: "pi",
63
+ args: ["install", extensionDir],
64
+ cwd,
65
+ agentDir,
66
+ stdout: stdout.stream,
67
+ stderr: stderr.stream,
68
+ });
69
+
70
+ assert.equal(result.handled, true);
71
+ assert.equal(result.exitCode, 0);
72
+ assert.equal(readFileSync(join(extensionDir, "before-install-ran.txt"), "utf-8"), "ok");
73
+ assert.equal(readFileSync(join(extensionDir, "after-install-ran.txt"), "utf-8"), "ok");
74
+ assert.ok(stdout.getOutput().includes(`Installed ${extensionDir}`));
75
+ } finally {
76
+ rmSync(root, { recursive: true, force: true });
77
+ }
78
+ });
79
+
80
+ it("runs legacy named lifecycle hooks when no registered hooks exist", async () => {
81
+ const root = mkdtempSync(join(tmpdir(), "pi-lifecycle-legacy-"));
82
+ const cwd = join(root, "cwd");
83
+ const agentDir = join(root, "agent");
84
+ const extensionDir = join(root, "ext-legacy");
85
+ mkdirSync(cwd, { recursive: true });
86
+ mkdirSync(agentDir, { recursive: true });
87
+ mkdirSync(extensionDir, { recursive: true });
88
+
89
+ try {
90
+ writePackage(extensionDir, {
91
+ "package.json": JSON.stringify({
92
+ name: "ext-legacy",
93
+ type: "module",
94
+ pi: { extensions: ["./index.js"] },
95
+ }),
96
+ "index.js": `
97
+ import { writeFileSync } from "node:fs";
98
+ import { join } from "node:path";
99
+ export default function () {}
100
+ export async function beforeInstall(ctx) {
101
+ writeFileSync(join(ctx.installedPath, "legacy-before-install.txt"), "ok", "utf-8");
102
+ }
103
+ export async function afterInstall(ctx) {
104
+ writeFileSync(join(ctx.installedPath, "legacy-after-install.txt"), "ok", "utf-8");
105
+ }
106
+ export async function beforeRemove(ctx) {
107
+ writeFileSync(join(ctx.installedPath, "legacy-before-remove.txt"), "ok", "utf-8");
108
+ }
109
+ export async function afterRemove(ctx) {
110
+ writeFileSync(join(ctx.installedPath, "legacy-after-remove.txt"), "ok", "utf-8");
111
+ }
112
+ `,
113
+ });
114
+
115
+ const stdout = createCaptureStream();
116
+ const stderr = createCaptureStream();
117
+ const installResult = await runPackageCommand({
118
+ appName: "pi",
119
+ args: ["install", extensionDir],
120
+ cwd,
121
+ agentDir,
122
+ stdout: stdout.stream,
123
+ stderr: stderr.stream,
124
+ });
125
+
126
+ assert.equal(installResult.handled, true);
127
+ assert.equal(installResult.exitCode, 0);
128
+ assert.equal(readFileSync(join(extensionDir, "legacy-before-install.txt"), "utf-8"), "ok");
129
+ assert.equal(readFileSync(join(extensionDir, "legacy-after-install.txt"), "utf-8"), "ok");
130
+
131
+ const removeResult = await runPackageCommand({
132
+ appName: "pi",
133
+ args: ["remove", extensionDir],
134
+ cwd,
135
+ agentDir,
136
+ stdout: stdout.stream,
137
+ stderr: stderr.stream,
138
+ });
139
+
140
+ assert.equal(removeResult.handled, true);
141
+ assert.equal(removeResult.exitCode, 0);
142
+ assert.equal(readFileSync(join(extensionDir, "legacy-before-remove.txt"), "utf-8"), "ok");
143
+ assert.equal(readFileSync(join(extensionDir, "legacy-after-remove.txt"), "utf-8"), "ok");
144
+ } finally {
145
+ rmSync(root, { recursive: true, force: true });
146
+ }
147
+ });
148
+
149
+ it("skips lifecycle phases with no hooks declared", async () => {
150
+ const root = mkdtempSync(join(tmpdir(), "pi-lifecycle-skip-"));
151
+ const cwd = join(root, "cwd");
152
+ const agentDir = join(root, "agent");
153
+ const extensionDir = join(root, "ext-empty");
154
+ mkdirSync(cwd, { recursive: true });
155
+ mkdirSync(agentDir, { recursive: true });
156
+ mkdirSync(extensionDir, { recursive: true });
157
+
158
+ try {
159
+ writePackage(extensionDir, {
160
+ "package.json": JSON.stringify({
161
+ name: "ext-empty",
162
+ type: "module",
163
+ pi: { extensions: ["./index.js"] },
164
+ }),
165
+ "index.js": `export default function () {}`,
166
+ });
167
+
168
+ const stdout = createCaptureStream();
169
+ const stderr = createCaptureStream();
170
+ const installResult = await runPackageCommand({
171
+ appName: "pi",
172
+ args: ["install", extensionDir],
173
+ cwd,
174
+ agentDir,
175
+ stdout: stdout.stream,
176
+ stderr: stderr.stream,
177
+ });
178
+ assert.equal(installResult.handled, true);
179
+ assert.equal(installResult.exitCode, 0);
180
+
181
+ const removeResult = await runPackageCommand({
182
+ appName: "pi",
183
+ args: ["remove", extensionDir],
184
+ cwd,
185
+ agentDir,
186
+ stdout: stdout.stream,
187
+ stderr: stderr.stream,
188
+ });
189
+ assert.equal(removeResult.handled, true);
190
+ assert.equal(removeResult.exitCode, 0);
191
+ assert.equal(stderr.getOutput().includes("Hook failed"), false);
192
+ } finally {
193
+ rmSync(root, { recursive: true, force: true });
194
+ }
195
+ });
196
+
197
+ it("fails install when manifest runtime dependency is missing", async () => {
198
+ const root = mkdtempSync(join(tmpdir(), "pi-lifecycle-deps-"));
199
+ const cwd = join(root, "cwd");
200
+ const agentDir = join(root, "agent");
201
+ const extensionDir = join(root, "ext-runtime-deps");
202
+ mkdirSync(cwd, { recursive: true });
203
+ mkdirSync(agentDir, { recursive: true });
204
+ mkdirSync(extensionDir, { recursive: true });
205
+
206
+ try {
207
+ writePackage(extensionDir, {
208
+ "package.json": JSON.stringify({
209
+ name: "ext-runtime-deps",
210
+ type: "module",
211
+ pi: { extensions: ["./index.js"] },
212
+ }),
213
+ "index.js": `export default function () {}`,
214
+ "extension-manifest.json": JSON.stringify({
215
+ id: "ext-runtime-deps",
216
+ name: "Runtime Dep Test",
217
+ version: "1.0.0",
218
+ dependencies: { runtime: ["__definitely_missing_command_for_test__"] },
219
+ }),
220
+ });
221
+
222
+ const stdout = createCaptureStream();
223
+ const stderr = createCaptureStream();
224
+ const result = await runPackageCommand({
225
+ appName: "pi",
226
+ args: ["install", extensionDir],
227
+ cwd,
228
+ agentDir,
229
+ stdout: stdout.stream,
230
+ stderr: stderr.stream,
231
+ });
232
+
233
+ assert.equal(result.handled, true);
234
+ assert.equal(result.exitCode, 1);
235
+ assert.ok(stderr.getOutput().includes("Missing runtime dependencies"));
236
+ } finally {
237
+ rmSync(root, { recursive: true, force: true });
238
+ }
239
+ });
240
+ });