gsd-pi 2.76.0-dev.4100bd590 → 2.76.0-dev.479ad0e78

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 (362) hide show
  1. package/dist/claude-cli-check.js +32 -3
  2. package/dist/mcp-server.d.ts +7 -0
  3. package/dist/mcp-server.js +35 -1
  4. package/dist/onboarding.js +45 -0
  5. package/dist/resource-loader.d.ts +1 -1
  6. package/dist/resource-loader.js +2 -8
  7. package/dist/resources/extensions/claude-code-cli/readiness.js +4 -3
  8. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +77 -17
  9. package/dist/resources/extensions/gsd/auto/loop.js +9 -0
  10. package/dist/resources/extensions/gsd/auto/phases.js +58 -5
  11. package/dist/resources/extensions/gsd/auto/run-unit.js +38 -2
  12. package/dist/resources/extensions/gsd/auto/session.js +22 -1
  13. package/dist/resources/extensions/gsd/auto-dispatch.js +16 -3
  14. package/dist/resources/extensions/gsd/auto-model-selection.js +14 -3
  15. package/dist/resources/extensions/gsd/auto-post-unit.js +25 -2
  16. package/dist/resources/extensions/gsd/auto-prompts.js +14 -0
  17. package/dist/resources/extensions/gsd/auto-recovery.js +32 -1
  18. package/dist/resources/extensions/gsd/auto-start.js +58 -57
  19. package/dist/resources/extensions/gsd/auto-worktree.js +51 -53
  20. package/dist/resources/extensions/gsd/auto.js +70 -28
  21. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +17 -1
  22. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +39 -9
  23. package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +93 -0
  24. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +2 -0
  25. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +52 -6
  26. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +34 -2
  27. package/dist/resources/extensions/gsd/clean-root-preflight.js +93 -0
  28. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +968 -23
  29. package/dist/resources/extensions/gsd/compaction-snapshot.js +121 -0
  30. package/dist/resources/extensions/gsd/complexity-classifier.js +5 -3
  31. package/dist/resources/extensions/gsd/error-classifier.js +10 -3
  32. package/dist/resources/extensions/gsd/exec-history.js +120 -0
  33. package/dist/resources/extensions/gsd/exec-sandbox.js +258 -0
  34. package/dist/resources/extensions/gsd/gitignore.js +1 -0
  35. package/dist/resources/extensions/gsd/gsd-db.js +149 -31
  36. package/dist/resources/extensions/gsd/guided-flow.js +190 -1
  37. package/dist/resources/extensions/gsd/health-widget.js +4 -1
  38. package/dist/resources/extensions/gsd/init-wizard.js +15 -1
  39. package/dist/resources/extensions/gsd/key-manager.js +28 -0
  40. package/dist/resources/extensions/gsd/model-router.js +36 -3
  41. package/dist/resources/extensions/gsd/pre-execution-checks.js +44 -9
  42. package/dist/resources/extensions/gsd/preferences-types.js +9 -0
  43. package/dist/resources/extensions/gsd/preferences-validation.js +83 -0
  44. package/dist/resources/extensions/gsd/preferences.js +17 -17
  45. package/dist/resources/extensions/gsd/prompt-loader.js +22 -7
  46. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +8 -0
  47. package/dist/resources/extensions/gsd/prompts/discuss.md +29 -2
  48. package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +5 -2
  49. package/dist/resources/extensions/gsd/safety/evidence-collector.js +96 -0
  50. package/dist/resources/extensions/gsd/safety/file-change-validator.js +13 -5
  51. package/dist/resources/extensions/gsd/safety/safety-harness.js +5 -1
  52. package/dist/resources/extensions/gsd/token-counter.js +22 -5
  53. package/dist/resources/extensions/gsd/tools/complete-milestone.js +16 -10
  54. package/dist/resources/extensions/gsd/tools/exec-search-tool.js +59 -0
  55. package/dist/resources/extensions/gsd/tools/exec-tool.js +126 -0
  56. package/dist/resources/extensions/gsd/tools/resume-tool.js +23 -0
  57. package/dist/resources/extensions/gsd/uok/plan-v2.js +20 -3
  58. package/dist/resources/extensions/gsd/workflow-mcp.js +3 -0
  59. package/dist/resources/extensions/gsd/worktree-resolver.js +50 -10
  60. package/dist/resources/skills/verify-before-complete/SKILL.md +2 -1
  61. package/dist/resources/skills/write-docs/SKILL.md +2 -1
  62. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  63. package/dist/web/standalone/.next/BUILD_ID +1 -1
  64. package/dist/web/standalone/.next/app-path-routes-manifest.json +17 -17
  65. package/dist/web/standalone/.next/build-manifest.json +2 -2
  66. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  67. package/dist/web/standalone/.next/required-server-files.json +1 -1
  68. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  69. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  77. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/index.html +1 -1
  85. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  86. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  88. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  91. package/dist/web/standalone/.next/server/app-paths-manifest.json +17 -17
  92. package/dist/web/standalone/.next/server/chunks/6897.js +2 -2
  93. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  94. package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
  95. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  96. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  97. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  98. package/dist/web/standalone/server.js +1 -1
  99. package/dist/welcome-screen.js +6 -1
  100. package/dist/wizard.js +2 -0
  101. package/package.json +1 -1
  102. package/packages/mcp-server/dist/remote-questions.d.ts +45 -0
  103. package/packages/mcp-server/dist/remote-questions.d.ts.map +1 -0
  104. package/packages/mcp-server/dist/remote-questions.js +732 -0
  105. package/packages/mcp-server/dist/remote-questions.js.map +1 -0
  106. package/packages/mcp-server/dist/server.d.ts +7 -0
  107. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  108. package/packages/mcp-server/dist/server.js +70 -8
  109. package/packages/mcp-server/dist/server.js.map +1 -1
  110. package/packages/mcp-server/dist/session-manager.d.ts +14 -0
  111. package/packages/mcp-server/dist/session-manager.d.ts.map +1 -1
  112. package/packages/mcp-server/dist/session-manager.js +49 -1
  113. package/packages/mcp-server/dist/session-manager.js.map +1 -1
  114. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  115. package/packages/mcp-server/dist/workflow-tools.js +64 -25
  116. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  117. package/packages/mcp-server/package.json +2 -1
  118. package/packages/mcp-server/src/mcp-server.test.ts +67 -0
  119. package/packages/mcp-server/src/remote-questions.test.ts +294 -0
  120. package/packages/mcp-server/src/remote-questions.ts +916 -0
  121. package/packages/mcp-server/src/server.ts +89 -14
  122. package/packages/mcp-server/src/session-manager.ts +43 -1
  123. package/packages/mcp-server/src/workflow-tools.test.ts +146 -1
  124. package/packages/mcp-server/src/workflow-tools.ts +84 -43
  125. package/packages/mcp-server/tsconfig.test.json +19 -0
  126. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  127. package/packages/pi-ai/dist/models/custom.d.ts +38 -0
  128. package/packages/pi-ai/dist/models/custom.d.ts.map +1 -1
  129. package/packages/pi-ai/dist/models/custom.js +41 -0
  130. package/packages/pi-ai/dist/models/custom.js.map +1 -1
  131. package/packages/pi-ai/dist/providers/anthropic-auth.test.js +1 -1
  132. package/packages/pi-ai/dist/providers/anthropic-auth.test.js.map +1 -1
  133. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
  134. package/packages/pi-ai/dist/providers/anthropic-shared.js +27 -4
  135. package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
  136. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  137. package/packages/pi-ai/dist/providers/anthropic.js +8 -3
  138. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  139. package/packages/pi-ai/dist/providers/minimax-tool-name.test.d.ts +2 -0
  140. package/packages/pi-ai/dist/providers/minimax-tool-name.test.d.ts.map +1 -0
  141. package/packages/pi-ai/dist/providers/minimax-tool-name.test.js +80 -0
  142. package/packages/pi-ai/dist/providers/minimax-tool-name.test.js.map +1 -0
  143. package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
  144. package/packages/pi-ai/dist/providers/openai-completions.js +60 -15
  145. package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
  146. package/packages/pi-ai/dist/providers/simple-options.d.ts +10 -0
  147. package/packages/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
  148. package/packages/pi-ai/dist/providers/simple-options.js +16 -1
  149. package/packages/pi-ai/dist/providers/simple-options.js.map +1 -1
  150. package/packages/pi-ai/dist/providers/think-tag-parser.d.ts +17 -0
  151. package/packages/pi-ai/dist/providers/think-tag-parser.d.ts.map +1 -0
  152. package/packages/pi-ai/dist/providers/think-tag-parser.js +75 -0
  153. package/packages/pi-ai/dist/providers/think-tag-parser.js.map +1 -0
  154. package/packages/pi-ai/dist/providers/think-tag-parser.test.d.ts +2 -0
  155. package/packages/pi-ai/dist/providers/think-tag-parser.test.d.ts.map +1 -0
  156. package/packages/pi-ai/dist/providers/think-tag-parser.test.js +41 -0
  157. package/packages/pi-ai/dist/providers/think-tag-parser.test.js.map +1 -0
  158. package/packages/pi-ai/src/models/custom.ts +42 -0
  159. package/packages/pi-ai/src/providers/anthropic-auth.test.ts +1 -1
  160. package/packages/pi-ai/src/providers/anthropic-shared.ts +26 -5
  161. package/packages/pi-ai/src/providers/anthropic.ts +9 -3
  162. package/packages/pi-ai/src/providers/minimax-tool-name.test.ts +98 -0
  163. package/packages/pi-ai/src/providers/openai-completions.ts +57 -16
  164. package/packages/pi-ai/src/providers/simple-options.ts +17 -1
  165. package/packages/pi-ai/src/providers/think-tag-parser.test.ts +44 -0
  166. package/packages/pi-ai/src/providers/think-tag-parser.ts +94 -0
  167. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  168. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js +3 -2
  169. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js.map +1 -1
  170. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +2 -0
  171. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  172. package/packages/pi-coding-agent/dist/core/agent-session.js +7 -0
  173. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  174. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +2 -0
  175. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  176. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  177. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +7 -0
  178. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  179. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  180. package/packages/pi-coding-agent/dist/core/model-discovery.d.ts +3 -1
  181. package/packages/pi-coding-agent/dist/core/model-discovery.d.ts.map +1 -1
  182. package/packages/pi-coding-agent/dist/core/model-discovery.js +92 -12
  183. package/packages/pi-coding-agent/dist/core/model-discovery.js.map +1 -1
  184. package/packages/pi-coding-agent/dist/core/model-discovery.test.js +16 -1
  185. package/packages/pi-coding-agent/dist/core/model-discovery.test.js.map +1 -1
  186. package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.d.ts +2 -0
  187. package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.d.ts.map +1 -0
  188. package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.js +203 -0
  189. package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.js.map +1 -0
  190. package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.js +61 -1
  191. package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.js.map +1 -1
  192. package/packages/pi-coding-agent/dist/core/model-registry.d.ts +5 -0
  193. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  194. package/packages/pi-coding-agent/dist/core/model-registry.js +90 -10
  195. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  196. package/packages/pi-coding-agent/dist/core/redact-secrets.d.ts +2 -0
  197. package/packages/pi-coding-agent/dist/core/redact-secrets.d.ts.map +1 -0
  198. package/packages/pi-coding-agent/dist/core/redact-secrets.js +49 -0
  199. package/packages/pi-coding-agent/dist/core/redact-secrets.js.map +1 -0
  200. package/packages/pi-coding-agent/dist/core/redact-secrets.test.d.ts +2 -0
  201. package/packages/pi-coding-agent/dist/core/redact-secrets.test.d.ts.map +1 -0
  202. package/packages/pi-coding-agent/dist/core/redact-secrets.test.js +67 -0
  203. package/packages/pi-coding-agent/dist/core/redact-secrets.test.js.map +1 -0
  204. package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
  205. package/packages/pi-coding-agent/dist/core/session-manager.js +10 -6
  206. package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
  207. package/packages/pi-coding-agent/dist/core/session-manager.test.js +45 -1
  208. package/packages/pi-coding-agent/dist/core/session-manager.test.js.map +1 -1
  209. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts +1 -1
  210. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts.map +1 -1
  211. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js +5 -4
  212. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js.map +1 -1
  213. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts.map +1 -1
  214. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js +13 -7
  215. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js.map +1 -1
  216. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.d.ts +7 -6
  217. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
  218. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js +29 -21
  219. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
  220. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  221. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +13 -1
  222. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  223. package/packages/pi-coding-agent/src/core/agent-session-abort-order.test.ts +3 -2
  224. package/packages/pi-coding-agent/src/core/agent-session.ts +11 -0
  225. package/packages/pi-coding-agent/src/core/extensions/runner.ts +2 -0
  226. package/packages/pi-coding-agent/src/core/extensions/types.ts +7 -0
  227. package/packages/pi-coding-agent/src/core/model-discovery.test.ts +19 -0
  228. package/packages/pi-coding-agent/src/core/model-discovery.ts +99 -12
  229. package/packages/pi-coding-agent/src/core/model-registry-custom-caps.test.ts +245 -0
  230. package/packages/pi-coding-agent/src/core/model-registry-discovery.test.ts +75 -0
  231. package/packages/pi-coding-agent/src/core/model-registry.ts +102 -10
  232. package/packages/pi-coding-agent/src/core/redact-secrets.test.ts +86 -0
  233. package/packages/pi-coding-agent/src/core/redact-secrets.ts +58 -0
  234. package/packages/pi-coding-agent/src/core/session-manager.test.ts +65 -1
  235. package/packages/pi-coding-agent/src/core/session-manager.ts +10 -6
  236. package/packages/pi-coding-agent/src/modes/interactive/components/chat-frame.ts +6 -6
  237. package/packages/pi-coding-agent/src/modes/interactive/components/provider-manager.ts +16 -7
  238. package/packages/pi-coding-agent/src/modes/interactive/components/skill-invocation-message.ts +36 -22
  239. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +13 -1
  240. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  241. package/scripts/link-workspace-packages.cjs +1 -0
  242. package/src/resources/extensions/claude-code-cli/readiness.ts +4 -3
  243. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +78 -17
  244. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +149 -5
  245. package/src/resources/extensions/gsd/auto/loop-deps.ts +14 -0
  246. package/src/resources/extensions/gsd/auto/loop.ts +9 -0
  247. package/src/resources/extensions/gsd/auto/phases.ts +82 -4
  248. package/src/resources/extensions/gsd/auto/run-unit.ts +40 -2
  249. package/src/resources/extensions/gsd/auto/session.ts +35 -2
  250. package/src/resources/extensions/gsd/auto-dispatch.ts +16 -3
  251. package/src/resources/extensions/gsd/auto-model-selection.ts +17 -2
  252. package/src/resources/extensions/gsd/auto-post-unit.ts +29 -3
  253. package/src/resources/extensions/gsd/auto-prompts.ts +28 -1
  254. package/src/resources/extensions/gsd/auto-recovery.ts +26 -1
  255. package/src/resources/extensions/gsd/auto-start.ts +60 -68
  256. package/src/resources/extensions/gsd/auto-worktree.ts +62 -63
  257. package/src/resources/extensions/gsd/auto.ts +73 -28
  258. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +23 -1
  259. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +40 -9
  260. package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +109 -0
  261. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +2 -0
  262. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +54 -6
  263. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +35 -2
  264. package/src/resources/extensions/gsd/clean-root-preflight.ts +111 -0
  265. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +898 -32
  266. package/src/resources/extensions/gsd/compaction-snapshot.ts +165 -0
  267. package/src/resources/extensions/gsd/complexity-classifier.ts +5 -3
  268. package/src/resources/extensions/gsd/error-classifier.ts +10 -3
  269. package/src/resources/extensions/gsd/exec-history.ts +153 -0
  270. package/src/resources/extensions/gsd/exec-sandbox.ts +326 -0
  271. package/src/resources/extensions/gsd/gitignore.ts +1 -1
  272. package/src/resources/extensions/gsd/gsd-db.ts +157 -33
  273. package/src/resources/extensions/gsd/guided-flow.ts +222 -1
  274. package/src/resources/extensions/gsd/health-widget.ts +3 -1
  275. package/src/resources/extensions/gsd/init-wizard.ts +15 -1
  276. package/src/resources/extensions/gsd/journal.ts +2 -1
  277. package/src/resources/extensions/gsd/key-manager.ts +28 -0
  278. package/src/resources/extensions/gsd/model-router.ts +42 -1
  279. package/src/resources/extensions/gsd/pre-execution-checks.ts +46 -10
  280. package/src/resources/extensions/gsd/preferences-types.ts +46 -0
  281. package/src/resources/extensions/gsd/preferences-validation.ts +79 -0
  282. package/src/resources/extensions/gsd/preferences.ts +17 -17
  283. package/src/resources/extensions/gsd/prompt-loader.ts +30 -7
  284. package/src/resources/extensions/gsd/prompts/discuss-headless.md +8 -0
  285. package/src/resources/extensions/gsd/prompts/discuss.md +29 -2
  286. package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +5 -2
  287. package/src/resources/extensions/gsd/safety/evidence-collector.ts +119 -0
  288. package/src/resources/extensions/gsd/safety/file-change-validator.ts +17 -4
  289. package/src/resources/extensions/gsd/safety/safety-harness.ts +9 -0
  290. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +188 -2
  291. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +12 -0
  292. package/src/resources/extensions/gsd/tests/auto-paused-session-validation.test.ts +12 -0
  293. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +49 -0
  294. package/src/resources/extensions/gsd/tests/auto-start-bootstrap-await-3420.test.ts +141 -0
  295. package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +33 -3
  296. package/src/resources/extensions/gsd/tests/auto-thinking-restore.test.ts +38 -0
  297. package/src/resources/extensions/gsd/tests/auto-wrapup-inflight-guard.test.ts +23 -0
  298. package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +186 -0
  299. package/src/resources/extensions/gsd/tests/compaction-snapshot.test.ts +123 -0
  300. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +61 -1
  301. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +2 -2
  302. package/src/resources/extensions/gsd/tests/complete-task.test.ts +2 -2
  303. package/src/resources/extensions/gsd/tests/complexity-classifier.test.ts +3 -3
  304. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +2 -0
  305. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +31 -0
  306. package/src/resources/extensions/gsd/tests/double-merge-guard.test.ts +1 -1
  307. package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +1 -1
  308. package/src/resources/extensions/gsd/tests/escalation.test.ts +1 -1
  309. package/src/resources/extensions/gsd/tests/exec-history.test.ts +237 -0
  310. package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +210 -0
  311. package/src/resources/extensions/gsd/tests/file-change-validator.test.ts +58 -0
  312. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +447 -1
  313. package/src/resources/extensions/gsd/tests/init-wizard.test.ts +27 -0
  314. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +1 -0
  315. package/src/resources/extensions/gsd/tests/integration/gitignore-tracked-gsd.test.ts +1 -0
  316. package/src/resources/extensions/gsd/tests/integration/idle-recovery.test.ts +30 -0
  317. package/src/resources/extensions/gsd/tests/isolation-none-branch-guard.test.ts +1 -1
  318. package/src/resources/extensions/gsd/tests/issue-4540-regressions.test.ts +288 -0
  319. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +2 -0
  320. package/src/resources/extensions/gsd/tests/key-manager.test.ts +9 -0
  321. package/src/resources/extensions/gsd/tests/md-importer.test.ts +1 -1
  322. package/src/resources/extensions/gsd/tests/memory-pressure-stuck-state.test.ts +12 -0
  323. package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
  324. package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +19 -0
  325. package/src/resources/extensions/gsd/tests/plan-gate-failed-doctor-heal-hint.test.ts +37 -0
  326. package/src/resources/extensions/gsd/tests/pre-exec-backtick-strip.test.ts +14 -0
  327. package/src/resources/extensions/gsd/tests/pre-exec-gate-loop.test.ts +272 -0
  328. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +356 -0
  329. package/src/resources/extensions/gsd/tests/preferences.test.ts +110 -0
  330. package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +44 -0
  331. package/src/resources/extensions/gsd/tests/prompt-loader-extension-dir.test.ts +49 -0
  332. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +48 -0
  333. package/src/resources/extensions/gsd/tests/ready-phrase-no-files-4573.test.ts +388 -0
  334. package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +9 -3
  335. package/src/resources/extensions/gsd/tests/resume-dispatch-worktree.test.ts +230 -0
  336. package/src/resources/extensions/gsd/tests/safety-harness-false-positives.test.ts +205 -0
  337. package/src/resources/extensions/gsd/tests/save-gate-result-render.test.ts +95 -0
  338. package/src/resources/extensions/gsd/tests/schema-v21-sequence.test.ts +413 -0
  339. package/src/resources/extensions/gsd/tests/session-start-footer.test.ts +32 -40
  340. package/src/resources/extensions/gsd/tests/stash-queued-context-files.test.ts +56 -0
  341. package/src/resources/extensions/gsd/tests/token-counter.test.ts +105 -1
  342. package/src/resources/extensions/gsd/tests/tool-compatibility.test.ts +107 -0
  343. package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +23 -0
  344. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +65 -2
  345. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +35 -0
  346. package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +6 -1
  347. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +78 -5
  348. package/src/resources/extensions/gsd/tests/write-gate.test.ts +64 -0
  349. package/src/resources/extensions/gsd/tests/zombie-gsd-state.test.ts +3 -1
  350. package/src/resources/extensions/gsd/token-counter.ts +22 -5
  351. package/src/resources/extensions/gsd/tools/complete-milestone.ts +15 -9
  352. package/src/resources/extensions/gsd/tools/exec-search-tool.ts +81 -0
  353. package/src/resources/extensions/gsd/tools/exec-tool.ts +183 -0
  354. package/src/resources/extensions/gsd/tools/resume-tool.ts +40 -0
  355. package/src/resources/extensions/gsd/uok/plan-v2.ts +26 -3
  356. package/src/resources/extensions/gsd/workflow-logger.ts +3 -1
  357. package/src/resources/extensions/gsd/workflow-mcp.ts +3 -0
  358. package/src/resources/extensions/gsd/worktree-resolver.ts +54 -9
  359. package/src/resources/skills/verify-before-complete/SKILL.md +2 -1
  360. package/src/resources/skills/write-docs/SKILL.md +2 -1
  361. /package/dist/web/standalone/.next/static/{YnUwu2WWaT0_hyTLUF4nq → JgU2F-5N9mTyB7kUSSk9A}/_buildManifest.js +0 -0
  362. /package/dist/web/standalone/.next/static/{YnUwu2WWaT0_hyTLUF4nq → JgU2F-5N9mTyB7kUSSk9A}/_ssgManifest.js +0 -0
@@ -29,7 +29,7 @@ import { getAgentDir } from "../config.js";
29
29
  import type { AuthStorage } from "./auth-storage.js";
30
30
  import { ModelDiscoveryCache } from "./discovery-cache.js";
31
31
  import type { DiscoveredModel, DiscoveryResult } from "./model-discovery.js";
32
- import { getDefaultTTL, getDiscoverableProviders, getDiscoveryAdapter } from "./model-discovery.js";
32
+ import { getDefaultTTL, getDiscoverableProviders, getDiscoveryAdapter, supportsDiscoveryForApi } from "./model-discovery.js";
33
33
  import { clearConfigValueCache, resolveConfigValue, resolveHeaders } from "./resolve-config-value.js";
34
34
  import { isLocalModel } from "./local-model-check.js";
35
35
 
@@ -48,6 +48,14 @@ const VercelGatewayRoutingSchema = Type.Object({
48
48
  order: Type.Optional(Type.Array(Type.String())),
49
49
  });
50
50
 
51
+ // Schema for model capability declarations (mirrors ModelCapabilities in pi-ai types)
52
+ const ModelCapabilitiesSchema = Type.Object({
53
+ supportsXhigh: Type.Optional(Type.Boolean()),
54
+ requiresToolCallId: Type.Optional(Type.Boolean()),
55
+ supportsServiceTier: Type.Optional(Type.Boolean()),
56
+ charsPerToken: Type.Optional(Type.Number()),
57
+ });
58
+
51
59
  // Schema for OpenAI compatibility settings
52
60
  const OpenAICompletionsCompatSchema = Type.Object({
53
61
  supportsStore: Type.Optional(Type.Boolean()),
@@ -91,6 +99,7 @@ const ModelDefinitionSchema = Type.Object({
91
99
  maxTokens: Type.Optional(Type.Number()),
92
100
  headers: Type.Optional(Type.Record(Type.String(), Type.String())),
93
101
  compat: Type.Optional(OpenAICompatSchema),
102
+ capabilities: Type.Optional(ModelCapabilitiesSchema),
94
103
  });
95
104
 
96
105
  // Schema for per-model overrides (all fields optional, merged with built-in model)
@@ -110,6 +119,7 @@ const ModelOverrideSchema = Type.Object({
110
119
  maxTokens: Type.Optional(Type.Number()),
111
120
  headers: Type.Optional(Type.Record(Type.String(), Type.String())),
112
121
  compat: Type.Optional(OpenAICompatSchema),
122
+ capabilities: Type.Optional(ModelCapabilitiesSchema),
113
123
  });
114
124
 
115
125
  type ModelOverride = Static<typeof ModelOverrideSchema>;
@@ -219,6 +229,11 @@ function applyModelOverride(model: Model<Api>, override: ModelOverride): Model<A
219
229
  // Deep merge compat
220
230
  result.compat = mergeCompat(model.compat, override.compat);
221
231
 
232
+ // Merge capabilities (override wins per-field)
233
+ if (override.capabilities) {
234
+ result.capabilities = { ...model.capabilities, ...override.capabilities };
235
+ }
236
+
222
237
  return result;
223
238
  }
224
239
 
@@ -514,6 +529,7 @@ export class ModelRegistry {
514
529
  maxTokens: modelDef.maxTokens ?? 16384,
515
530
  headers,
516
531
  compat: modelDef.compat,
532
+ capabilities: modelDef.capabilities,
517
533
  } as Model<Api>);
518
534
  }
519
535
  }
@@ -788,11 +804,12 @@ export class ModelRegistry {
788
804
  * Results are cached and merged into the registry (never overrides existing models).
789
805
  */
790
806
  async discoverModels(providers?: string[]): Promise<DiscoveryResult[]> {
791
- const targetProviders = providers ?? getDiscoverableProviders();
807
+ const targetProviders = providers ?? this.getAutoDiscoverableProviders();
792
808
  const results: DiscoveryResult[] = [];
793
809
 
794
810
  for (const providerName of targetProviders) {
795
- const adapter = getDiscoveryAdapter(providerName);
811
+ const providerApis = this.getProviderApis(providerName);
812
+ const adapter = getDiscoveryAdapter(providerName, providerApis);
796
813
  if (!adapter.supportsDiscovery) continue;
797
814
 
798
815
  // Skip if cache is still fresh
@@ -812,8 +829,10 @@ export class ModelRegistry {
812
829
  const apiKey = await this.authStorage.getApiKey(providerName);
813
830
  if (!apiKey && !this.isProviderRequestReady(providerName)) continue;
814
831
 
815
- const models = await adapter.fetchModels(apiKey ?? "", undefined);
816
- this.discoveryCache.set(providerName, models);
832
+ const baseUrl = this.getProviderBaseUrl(providerName);
833
+ const models = await adapter.fetchModels(apiKey ?? "", baseUrl);
834
+ const ttlMs = this.getDiscoveryTtl(providerName, providerApis);
835
+ this.discoveryCache.set(providerName, models, ttlMs);
817
836
  results.push({
818
837
  provider: providerName,
819
838
  models,
@@ -865,24 +884,97 @@ export class ModelRegistry {
865
884
  const converted: Model<Api>[] = [];
866
885
  for (const result of results) {
867
886
  if (result.error) continue;
887
+ const providerDefaults = this.getDiscoveryProviderDefaults(result.provider);
868
888
  for (const dm of result.models) {
869
889
  converted.push({
870
890
  id: dm.id,
871
891
  name: dm.name ?? dm.id,
872
- api: "openai" as Api,
892
+ api: providerDefaults.api,
873
893
  provider: result.provider,
874
- baseUrl: "",
894
+ baseUrl: providerDefaults.baseUrl,
875
895
  reasoning: dm.reasoning ?? false,
876
- input: dm.input ?? ["text"],
896
+ input: dm.input ?? providerDefaults.input,
877
897
  cost: dm.cost ?? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
878
- contextWindow: dm.contextWindow ?? 128000,
879
- maxTokens: dm.maxTokens ?? 16384,
898
+ contextWindow: dm.contextWindow ?? providerDefaults.contextWindow,
899
+ maxTokens: dm.maxTokens ?? providerDefaults.maxTokens,
880
900
  } as Model<Api>);
881
901
  }
882
902
  }
883
903
  return converted;
884
904
  }
885
905
 
906
+ private getProviderApis(provider: string): Set<string> {
907
+ const apis = new Set<string>();
908
+ for (const model of this.models) {
909
+ if (model.provider === provider && typeof model.api === "string" && model.api.length > 0) {
910
+ apis.add(model.api);
911
+ }
912
+ }
913
+
914
+ const providerConfig = this.registeredProviders.get(provider);
915
+ if (providerConfig?.api) apis.add(providerConfig.api);
916
+ for (const modelDef of providerConfig?.models ?? []) {
917
+ if (modelDef.api) apis.add(modelDef.api);
918
+ }
919
+ return apis;
920
+ }
921
+
922
+ private getAutoDiscoverableProviders(): string[] {
923
+ const discoverable = new Set<string>(getDiscoverableProviders());
924
+ for (const provider of new Set(this.models.map((m) => m.provider))) {
925
+ const apis = this.getProviderApis(provider);
926
+ for (const api of apis) {
927
+ if (supportsDiscoveryForApi(api)) {
928
+ discoverable.add(provider);
929
+ break;
930
+ }
931
+ }
932
+ }
933
+ return [...discoverable];
934
+ }
935
+
936
+ private getProviderBaseUrl(provider: string): string | undefined {
937
+ const fromModels = this.models.find((m) => m.provider === provider && typeof m.baseUrl === "string" && m.baseUrl.length > 0);
938
+ if (fromModels?.baseUrl) return fromModels.baseUrl;
939
+ return this.registeredProviders.get(provider)?.baseUrl;
940
+ }
941
+
942
+ private getDiscoveryProviderDefaults(provider: string): {
943
+ api: Api;
944
+ baseUrl: string;
945
+ input: ("text" | "image")[];
946
+ contextWindow: number;
947
+ maxTokens: number;
948
+ } {
949
+ const first = this.models.find((m) => m.provider === provider);
950
+ if (first) {
951
+ return {
952
+ api: first.api,
953
+ baseUrl: first.baseUrl,
954
+ input: first.input,
955
+ contextWindow: first.contextWindow,
956
+ maxTokens: first.maxTokens,
957
+ };
958
+ }
959
+
960
+ return {
961
+ api: "openai-completions",
962
+ baseUrl: this.registeredProviders.get(provider)?.baseUrl ?? "",
963
+ input: ["text"],
964
+ contextWindow: 128000,
965
+ maxTokens: 16384,
966
+ };
967
+ }
968
+
969
+ private getDiscoveryTtl(provider: string, providerApis: Set<string>): number {
970
+ for (const api of providerApis) {
971
+ if (supportsDiscoveryForApi(api)) {
972
+ return getDefaultTTL("openai");
973
+ }
974
+ }
975
+ return getDefaultTTL(provider);
976
+ }
977
+
886
978
  /**
887
979
  * Check if a model's baseUrl points to a local endpoint.
888
980
  * Delegates to standalone isLocalModel() function.
@@ -0,0 +1,86 @@
1
+ // pi-coding-agent — unit tests for session-log secret redaction
2
+
3
+ import assert from "node:assert/strict";
4
+ import { describe, it } from "node:test";
5
+
6
+ import { redactSecrets } from "./redact-secrets.js";
7
+
8
+ describe("redactSecrets", () => {
9
+ it("is a no-op on plain text with no secret markers", () => {
10
+ const input = "Hello world — this is just some prose with numbers 12345 and dashes - - -.";
11
+ assert.equal(redactSecrets(input), input);
12
+ });
13
+
14
+ it("redacts Anthropic keys before generic openai sk- pattern", () => {
15
+ const out = redactSecrets("key=sk-ant-api03-abcDEF1234567890abcDEF1234567890");
16
+ assert.equal(out, "key=[REDACTED:anthropic]");
17
+ });
18
+
19
+ it("redacts legacy OpenAI sk- keys", () => {
20
+ const out = redactSecrets("OPENAI_API_KEY=sk-abcDEF1234567890abcDEF12");
21
+ assert.equal(out, "OPENAI_API_KEY=[REDACTED:openai]");
22
+ });
23
+
24
+ it("redacts OpenAI project sk-proj- keys with hyphens/underscores in body", () => {
25
+ const out = redactSecrets("OPENAI_API_KEY=sk-proj-AbCd_1234-EfGh_5678-IjKl_9012");
26
+ assert.equal(out, "OPENAI_API_KEY=[REDACTED:openai]");
27
+ });
28
+
29
+ it("redacts OpenAI admin sk-admin- keys", () => {
30
+ const out = redactSecrets("OPENAI_ADMIN_KEY=sk-admin-AbCd1234EfGh5678IjKl9012");
31
+ assert.equal(out, "OPENAI_ADMIN_KEY=[REDACTED:openai]");
32
+ });
33
+
34
+ it("redacts LlamaCloud llx- keys", () => {
35
+ const out = redactSecrets("LLAMA_CLOUD_API_KEY=llx-abcDEF1234567890abcDEF1234567890");
36
+ assert.equal(out, "LLAMA_CLOUD_API_KEY=[REDACTED:llamacloud]");
37
+ });
38
+
39
+ it("redacts AWS access key ids", () => {
40
+ const out = redactSecrets("aws_access_key_id = AKIAIOSFODNN7EXAMPLE");
41
+ assert.equal(out, "aws_access_key_id = [REDACTED:aws-access-key]");
42
+ });
43
+
44
+ it("redacts GitHub personal/oauth/app/server/refresh tokens", () => {
45
+ const out = redactSecrets("token=ghp_abcdefghijklmnopqrstuvwxyz0123456789");
46
+ assert.equal(out, "token=[REDACTED:github-token]");
47
+ });
48
+
49
+ it("redacts Slack tokens", () => {
50
+ const out = redactSecrets("slack=xoxb-1234567890-abcdefghij");
51
+ assert.equal(out, "slack=[REDACTED:slack-token]");
52
+ });
53
+
54
+ it("redacts Google API keys", () => {
55
+ // Google API keys are exactly AIza + 35 chars (39 total).
56
+ const out = redactSecrets("key=AIzaSyA-1234567890abcdefghijklmnopqrstu");
57
+ assert.equal(out, "key=[REDACTED:google-api-key]");
58
+ });
59
+
60
+ it("redacts PEM private key blocks across newlines", () => {
61
+ const pem = [
62
+ "-----BEGIN RSA PRIVATE KEY-----",
63
+ "MIIEowIBAAKCAQEAabcDEF...",
64
+ "morekeymaterial==",
65
+ "-----END RSA PRIVATE KEY-----",
66
+ ].join("\n");
67
+ const out = redactSecrets(`before\n${pem}\nafter`);
68
+ assert.equal(out, "before\n[REDACTED:pem-private-key]\nafter");
69
+ });
70
+
71
+ it("redacts multiple secrets in the same string", () => {
72
+ const out = redactSecrets(
73
+ "AZURE_CLIENT_SECRET: also llx-abcDEF1234567890abcDEF1234567890 and AKIAIOSFODNN7EXAMPLE",
74
+ );
75
+ assert.equal(
76
+ out,
77
+ "AZURE_CLIENT_SECRET: also [REDACTED:llamacloud] and [REDACTED:aws-access-key]",
78
+ );
79
+ });
80
+
81
+ it("does not redact short strings that merely contain sk- prose", () => {
82
+ // "sk-foo" is too short to match the openai pattern — must be 20+ chars.
83
+ const input = "the sk- prefix isn't always a secret";
84
+ assert.equal(redactSecrets(input), input);
85
+ });
86
+ });
@@ -0,0 +1,58 @@
1
+ // pi-coding-agent — secret redaction for session log persistence
2
+ //
3
+ // Called by prepareForPersistence() in session-manager.ts on every string
4
+ // before it lands in the JSONL transcript. Replaces well-known secret shapes
5
+ // with [REDACTED:<kind>] placeholders so credentials pasted by the user or
6
+ // read from .env-style files never persist to disk.
7
+ //
8
+ // Pattern selection bias: high-specificity shapes only. Loose patterns
9
+ // (e.g. FOO_SECRET=...) produce too many false positives in docs and code
10
+ // samples and are intentionally excluded.
11
+
12
+ interface SecretPattern {
13
+ kind: string;
14
+ regex: RegExp;
15
+ }
16
+
17
+ // Order matters: more-specific patterns first (sk-ant- before generic sk-).
18
+ const PATTERNS: readonly SecretPattern[] = [
19
+ { kind: "anthropic", regex: /sk-ant-[A-Za-z0-9_-]{20,}/g },
20
+ { kind: "llamacloud", regex: /llx-[A-Za-z0-9_-]{20,}/g },
21
+ // Covers all three official OpenAI key shapes: legacy `sk-…`, project `sk-proj-…`,
22
+ // and admin `sk-admin-…`. Hyphens and underscores appear inside real project keys
23
+ // so the remainder class must allow them. `sk-ant-` is matched earlier by the
24
+ // anthropic pattern and already replaced by the time this runs.
25
+ { kind: "openai", regex: /sk-(?:proj-|admin-)?[A-Za-z0-9_-]{20,}/g },
26
+ { kind: "aws-access-key", regex: /\b(?:AKIA|ASIA|AROA)[0-9A-Z]{16}\b/g },
27
+ { kind: "github-token", regex: /\bgh[pousr]_[A-Za-z0-9]{30,}\b/g },
28
+ { kind: "slack-token", regex: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g },
29
+ { kind: "google-api-key", regex: /\bAIza[0-9A-Za-z_-]{35}\b/g },
30
+ {
31
+ kind: "pem-private-key",
32
+ regex: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g,
33
+ },
34
+ ];
35
+
36
+ export function redactSecrets(input: string): string {
37
+ // Short-circuit: skip regex work on strings with no plausible secret markers.
38
+ // Cheap heuristic — if none of these substrings are present, no pattern can match.
39
+ if (
40
+ !input.includes("sk-") &&
41
+ !input.includes("llx-") &&
42
+ !input.includes("AKIA") &&
43
+ !input.includes("ASIA") &&
44
+ !input.includes("AROA") &&
45
+ !input.includes("gh") &&
46
+ !input.includes("xox") &&
47
+ !input.includes("AIza") &&
48
+ !input.includes("PRIVATE KEY")
49
+ ) {
50
+ return input;
51
+ }
52
+
53
+ let out = input;
54
+ for (const { kind, regex } of PATTERNS) {
55
+ out = out.replace(regex, `[REDACTED:${kind}]`);
56
+ }
57
+ return out;
58
+ }
@@ -1,6 +1,6 @@
1
1
  import assert from "node:assert/strict";
2
2
  import { describe, it, afterEach } from "node:test";
3
- import { mkdtempSync, rmSync } from "node:fs";
3
+ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  import { tmpdir } from "node:os";
6
6
 
@@ -63,3 +63,67 @@ describe("SessionManager usage totals", () => {
63
63
  });
64
64
  });
65
65
  });
66
+
67
+ describe("SessionManager secret redaction on persistence", () => {
68
+ let dir: string;
69
+
70
+ afterEach(() => {
71
+ if (dir) {
72
+ rmSync(dir, { recursive: true, force: true });
73
+ }
74
+ });
75
+
76
+ it("scrubs known secret shapes from JSONL on disk", () => {
77
+ dir = mkdtempSync(join(tmpdir(), "gsd-session-redact-test-"));
78
+ const manager = SessionManager.create(dir, dir);
79
+
80
+ const leakedKey = "llx-abcDEF1234567890abcDEF1234567890";
81
+ manager.appendMessage({
82
+ role: "user",
83
+ content: [{ type: "text", text: `here is my key: ${leakedKey}` }],
84
+ } as any);
85
+ // Persistence is gated on an assistant message being present.
86
+ manager.appendMessage(makeAssistantMessage(1, 1, 0, 0, 0));
87
+
88
+ const sessionFile = manager.getSessionFile();
89
+ assert.ok(sessionFile, "session file should be set");
90
+ const contents = readFileSync(sessionFile!, "utf8");
91
+ assert.ok(
92
+ !contents.includes(leakedKey),
93
+ "raw secret must not appear in persisted JSONL",
94
+ );
95
+ assert.ok(
96
+ contents.includes("[REDACTED:llamacloud]"),
97
+ "redaction placeholder must appear in persisted JSONL",
98
+ );
99
+ });
100
+
101
+ it("scrubs secrets from JSONL rewritten by _rewriteFile() during migration", () => {
102
+ // Write a v1 session file (no id/parentId on entries) containing a secret.
103
+ // setSessionFile() will detect version < 3, run migration, and call _rewriteFile()
104
+ // which previously serialised entries without passing them through redaction.
105
+ dir = mkdtempSync(join(tmpdir(), "gsd-session-rewrite-redact-test-"));
106
+ const leakedKey = "sk-ant-api03-abcDEF1234567890abcDEF1234567890xYz";
107
+ const v1Header = JSON.stringify({ type: "session", version: 1, id: "test-session-id", timestamp: new Date().toISOString(), cwd: dir });
108
+ const v1UserMsg = JSON.stringify({ type: "message", message: { role: "user", content: [{ type: "text", text: `secret: ${leakedKey}` }] } });
109
+ const v1AssistantMsg = JSON.stringify({ type: "message", message: { role: "assistant", content: [{ type: "text", text: "ok" }], usage: { input: 1, output: 1, cacheRead: 0, cacheWrite: 0, total: 2, cost: { total: 0 } } } });
110
+ const sessionFile = join(dir, "test-session.jsonl");
111
+ writeFileSync(sessionFile, [v1Header, v1UserMsg, v1AssistantMsg].join("\n") + "\n", "utf8");
112
+
113
+ // Loading this file triggers migrateToCurrentVersion() which returns true (v1 → v3),
114
+ // causing _rewriteFile() to rewrite the file. The bug: _rewriteFile() called
115
+ // JSON.stringify(e) without redaction, so the secret would survive on disk.
116
+ const manager = SessionManager.create(dir, dir);
117
+ manager.setSessionFile(sessionFile);
118
+
119
+ const contents = readFileSync(sessionFile, "utf8");
120
+ assert.ok(
121
+ !contents.includes(leakedKey),
122
+ "raw secret must not appear in JSONL rewritten by _rewriteFile()",
123
+ );
124
+ assert.ok(
125
+ contents.includes("[REDACTED:anthropic]"),
126
+ "redaction placeholder must appear in JSONL rewritten by _rewriteFile()",
127
+ );
128
+ });
129
+ });
@@ -26,6 +26,7 @@ import {
26
26
  createCustomMessage,
27
27
  } from "./messages.js";
28
28
  import { BlobStore, externalizeImageData, isBlobRef, resolveImageData } from "./blob-store.js";
29
+ import { redactSecrets } from "./redact-secrets.js";
29
30
 
30
31
  /** Inline concurrency limiter to cap parallel async operations. */
31
32
  function pLimit(concurrency: number) {
@@ -499,15 +500,18 @@ function prepareForPersistence(obj: unknown, blobStore: BlobStore, key?: string)
499
500
  if (obj === null || obj === undefined) return obj;
500
501
 
501
502
  if (typeof obj === "string") {
502
- if (obj.length > MAX_PERSIST_CHARS) {
503
- // Cryptographic signatures must be preserved exactly or cleared entirely
504
- if (key === "thinkingSignature" || key === "thoughtSignature" || key === "textSignature") {
503
+ // Cryptographic signatures must be preserved byte-exact — never redact or truncate
504
+ // their contents, only the oversize-clear path below handles them.
505
+ const isSignature = key === "thinkingSignature" || key === "thoughtSignature" || key === "textSignature";
506
+ const redacted = isSignature ? obj : redactSecrets(obj);
507
+ if (redacted.length > MAX_PERSIST_CHARS) {
508
+ if (isSignature) {
505
509
  return "";
506
510
  }
507
511
  const limit = Math.max(0, MAX_PERSIST_CHARS - TRUNCATION_NOTICE.length);
508
- return `${truncateString(obj, limit)}${TRUNCATION_NOTICE}`;
512
+ return `${truncateString(redacted, limit)}${TRUNCATION_NOTICE}`;
509
513
  }
510
- return obj;
514
+ return redacted;
511
515
  }
512
516
 
513
517
  if (Array.isArray(obj)) {
@@ -955,7 +959,7 @@ export class SessionManager {
955
959
 
956
960
  private _rewriteFile(): void {
957
961
  if (!this.persist || !this.sessionFile) return;
958
- const content = `${this.fileEntries.map((e) => JSON.stringify(e)).join("\n")}\n`;
962
+ const content = `${this.fileEntries.map((e) => JSON.stringify(prepareForPersistence(e, this.blobStore))).join("\n")}\n`;
959
963
  let release: (() => void) | undefined;
960
964
  try {
961
965
  release = tryAcquireLockSync(this.sessionFile);
@@ -2,7 +2,7 @@ import { truncateToWidth, visibleWidth } from "@gsd/pi-tui";
2
2
  import { theme } from "../theme/theme.js";
3
3
  import { formatTimestamp, type TimestampFormat } from "./timestamp.js";
4
4
 
5
- type FrameTone = "assistant" | "user" | "compaction";
5
+ type FrameTone = "assistant" | "user" | "compaction" | "skill";
6
6
 
7
7
  function trimOuterBlankLines(lines: string[]): string[] {
8
8
  let start = 0;
@@ -25,14 +25,14 @@ export function renderChatFrame(
25
25
  ): string[] {
26
26
  const outerWidth = Math.max(20, width);
27
27
  const contentWidth = Math.max(1, outerWidth - 2); // "│ " + content
28
+ const isPurple = opts.tone === "compaction" || opts.tone === "skill";
28
29
  const borderColor =
29
30
  opts.tone === "user"
30
31
  ? "border"
31
- : opts.tone === "compaction"
32
+ : isPurple
32
33
  ? "customMessageLabel"
33
34
  : "borderAccent";
34
- const borderMuted =
35
- opts.tone === "compaction" ? "customMessageLabel" : "borderMuted";
35
+ const borderMuted = isPurple ? "customMessageLabel" : "borderMuted";
36
36
  const border = (s: string) => theme.fg(borderColor, s);
37
37
  const leftRaw = `• ${opts.label}`;
38
38
  const rightRaw =
@@ -47,7 +47,7 @@ export function renderChatFrame(
47
47
  const labelColor =
48
48
  opts.tone === "user"
49
49
  ? "border"
50
- : opts.tone === "compaction"
50
+ : isPurple
51
51
  ? "customMessageLabel"
52
52
  : "borderAccent";
53
53
  const dashIdx = left.indexOf(" - ");
@@ -71,7 +71,7 @@ export function renderChatFrame(
71
71
  const bodyColor =
72
72
  opts.tone === "user"
73
73
  ? "userMessageText"
74
- : opts.tone === "compaction"
74
+ : isPurple
75
75
  ? "customMessageText"
76
76
  : "assistantMessageText";
77
77
  const bodyLines = (sourceLines.length > 0 ? sourceLines : [""]).map((line) => {
@@ -12,7 +12,7 @@ import {
12
12
  type TUI,
13
13
  } from "@gsd/pi-tui";
14
14
  import type { AuthStorage } from "../../../core/auth-storage.js";
15
- import { getDiscoverableProviders } from "../../../core/model-discovery.js";
15
+ import { getDiscoverableProviders, getDiscoveryAdapter } from "../../../core/model-discovery.js";
16
16
  import { providerDisplayName } from "./model-selector.js";
17
17
  import type { ModelRegistry } from "../../../core/model-registry.js";
18
18
  import { ModelsJsonWriter } from "../../../core/models-json-writer.js";
@@ -102,12 +102,21 @@ export class ProviderManagerComponent extends Container implements Focusable {
102
102
 
103
103
  this.providers = Array.from(providerNames)
104
104
  .sort()
105
- .map((name) => ({
106
- name,
107
- hasAuth: this.authStorage.hasAuth(name),
108
- supportsDiscovery: discoverableSet.has(name),
109
- modelCount: providerModelCounts.get(name) ?? 0,
110
- }));
105
+ .map((name) => {
106
+ const providerApis = new Set(
107
+ allModels
108
+ .filter((m) => m.provider === name)
109
+ .map((m) => m.api)
110
+ .filter((api): api is string => typeof api === "string" && api.length > 0),
111
+ );
112
+ return {
113
+ name,
114
+ hasAuth: this.authStorage.hasAuth(name),
115
+ supportsDiscovery:
116
+ discoverableSet.has(name) || getDiscoveryAdapter(name, providerApis).supportsDiscovery,
117
+ modelCount: providerModelCounts.get(name) ?? 0,
118
+ };
119
+ });
111
120
  this.clampSelectedIndex();
112
121
  }
113
122
 
@@ -1,55 +1,69 @@
1
- import { Box, Markdown, type MarkdownTheme, Text } from "@gsd/pi-tui";
1
+ // GSD / pi-coding-agent Skill invocation message component
2
+ import { Container, Markdown, type MarkdownTheme, Text } from "@gsd/pi-tui";
2
3
  import type { ParsedSkillBlock } from "../../../core/agent-session.js";
3
4
  import { getMarkdownTheme, theme } from "../theme/theme.js";
5
+ import { renderChatFrame } from "./chat-frame.js";
4
6
  import { editorKey } from "./keybinding-hints.js";
5
7
 
6
8
  /**
7
- * Component that renders a skill invocation message with collapsed/expanded state.
8
- * Uses same background color as custom messages for visual consistency.
9
- * Only renders the skill block itself - user message is rendered separately.
9
+ * Renders a skill invocation in the shared chat-frame style (top rule,
10
+ * `• skill - <name>` header, `│ ` body margin) with purple border/label
11
+ * matching compaction so it visually aligns with user/assistant messages.
10
12
  */
11
- export class SkillInvocationMessageComponent extends Box {
13
+ export class SkillInvocationMessageComponent extends Container {
12
14
  private expanded = false;
13
15
  private skillBlock: ParsedSkillBlock;
14
16
  private markdownTheme: MarkdownTheme;
15
17
 
16
18
  constructor(skillBlock: ParsedSkillBlock, markdownTheme: MarkdownTheme = getMarkdownTheme()) {
17
- super(1, 1, (t) => theme.bg("customMessageBg", t));
19
+ super();
18
20
  this.skillBlock = skillBlock;
19
21
  this.markdownTheme = markdownTheme;
20
- this.updateDisplay();
22
+ this.rebuild();
21
23
  }
22
24
 
23
25
  setExpanded(expanded: boolean): void {
24
- this.expanded = expanded;
25
- this.updateDisplay();
26
+ if (this.expanded !== expanded) {
27
+ this.expanded = expanded;
28
+ this.rebuild();
29
+ }
26
30
  }
27
31
 
28
32
  override invalidate(): void {
29
33
  super.invalidate();
30
- this.updateDisplay();
34
+ this.rebuild();
31
35
  }
32
36
 
33
- private updateDisplay(): void {
37
+ private rebuild(): void {
34
38
  this.clear();
35
39
 
36
40
  if (this.expanded) {
37
- // Expanded: label + skill name header + full content
38
- const label = theme.fg("customMessageLabel", theme.bold("[skill]"));
39
- this.addChild(new Text(label, 0, 0));
40
- const header = `**${this.skillBlock.name}**\n\n`;
41
41
  this.addChild(
42
- new Markdown(header + this.skillBlock.content, 0, 0, this.markdownTheme, {
42
+ new Markdown(this.skillBlock.content, 0, 0, this.markdownTheme, {
43
43
  color: (text: string) => theme.fg("customMessageText", text),
44
44
  }),
45
45
  );
46
46
  } else {
47
- // Collapsed: single line - [skill] name (hint to expand)
48
- const line =
49
- theme.fg("customMessageLabel", theme.bold("[skill]") + " ") +
50
- theme.fg("customMessageText", this.skillBlock.name) +
51
- theme.fg("dim", ` (${editorKey("expandTools")} to expand)`);
52
- this.addChild(new Text(line, 0, 0));
47
+ this.addChild(
48
+ new Text(
49
+ theme.fg("dim", `(${editorKey("expandTools")} to expand)`),
50
+ 0,
51
+ 0,
52
+ ),
53
+ );
53
54
  }
54
55
  }
56
+
57
+ override render(width: number): string[] {
58
+ const frameWidth = Math.max(20, width);
59
+ const contentWidth = Math.max(1, frameWidth - 4);
60
+ const lines = super.render(contentWidth);
61
+ const framed = renderChatFrame(lines, frameWidth, {
62
+ label: `skill - ${this.skillBlock.name}`,
63
+ tone: "skill",
64
+ timestampFormat: "date-time-iso",
65
+ showTimestamp: false,
66
+ });
67
+ return framed.length > 0 ? ["", ...framed] : framed;
68
+ }
55
69
  }
@@ -3585,7 +3585,19 @@ export class InteractiveMode {
3585
3585
  this.ui.requestRender();
3586
3586
  },
3587
3587
  async (provider: string) => {
3588
- // Enter key → auth setup for selected provider (#3579)
3588
+ // Enter key → auth setup for selected provider (#3579).
3589
+ // Only OAuth providers support the login dialog flow.
3590
+ // externalCli providers (e.g. claude-code) authenticate through
3591
+ // their own CLI — sending them to the OAuth dialog produces
3592
+ // "Unknown OAuth provider: claude-code" (#4548).
3593
+ const isOAuthProvider = this.session.modelRegistry.authStorage
3594
+ .getOAuthProviders()
3595
+ .some((p) => p.id === provider);
3596
+ if (!isOAuthProvider) {
3597
+ done();
3598
+ this.showStatus(`${provider} uses external CLI auth — use /model to select a model or run the provider's own auth command.`);
3599
+ return;
3600
+ }
3589
3601
  done();
3590
3602
  await this.showLoginDialog(provider);
3591
3603
  },