gsd-pi 2.71.0-dev.e17e0ce → 2.72.0-dev.3118184

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 (352) hide show
  1. package/README.md +46 -3
  2. package/dist/cli.js +76 -3
  3. package/dist/mcp-server.js +37 -14
  4. package/dist/onboarding.js +10 -0
  5. package/dist/resources/agents/debugger.md +58 -0
  6. package/dist/resources/agents/doc-writer.md +43 -0
  7. package/dist/resources/agents/git-ops.md +56 -0
  8. package/dist/resources/agents/javascript-pro.md +46 -271
  9. package/dist/resources/agents/planner.md +55 -0
  10. package/dist/resources/agents/refactorer.md +47 -0
  11. package/dist/resources/agents/reviewer.md +48 -0
  12. package/dist/resources/agents/security.md +59 -0
  13. package/dist/resources/agents/tester.md +50 -0
  14. package/dist/resources/agents/typescript-pro.md +41 -235
  15. package/dist/resources/extensions/async-jobs/await-tool.js +7 -4
  16. package/dist/resources/extensions/async-jobs/job-manager.js +28 -3
  17. package/dist/resources/extensions/claude-code-cli/partial-builder.js +40 -12
  18. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +132 -10
  19. package/dist/resources/extensions/gsd/auto/loop.js +84 -1
  20. package/dist/resources/extensions/gsd/auto/phases.js +4 -0
  21. package/dist/resources/extensions/gsd/auto-post-unit.js +6 -0
  22. package/dist/resources/extensions/gsd/auto-prompts.js +88 -33
  23. package/dist/resources/extensions/gsd/auto-recovery.js +11 -0
  24. package/dist/resources/extensions/gsd/auto-start.js +24 -4
  25. package/dist/resources/extensions/gsd/auto.js +29 -19
  26. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +3 -3
  27. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +9 -11
  28. package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +2 -5
  29. package/dist/resources/extensions/gsd/commands-handlers.js +4 -1
  30. package/dist/resources/extensions/gsd/context-injector.js +1 -1
  31. package/dist/resources/extensions/gsd/custom-workflow-engine.js +3 -7
  32. package/dist/resources/extensions/gsd/definition-io.js +15 -0
  33. package/dist/resources/extensions/gsd/dispatch-guard.js +4 -0
  34. package/dist/resources/extensions/gsd/doctor-providers.js +23 -0
  35. package/dist/resources/extensions/gsd/doctor-runtime-checks.js +6 -3
  36. package/dist/resources/extensions/gsd/error-classifier.js +4 -1
  37. package/dist/resources/extensions/gsd/gate-registry.js +208 -0
  38. package/dist/resources/extensions/gsd/git-service.js +11 -8
  39. package/dist/resources/extensions/gsd/gitignore.js +12 -6
  40. package/dist/resources/extensions/gsd/gsd-db.js +90 -6
  41. package/dist/resources/extensions/gsd/key-manager.js +2 -0
  42. package/dist/resources/extensions/gsd/milestone-validation-gates.js +11 -12
  43. package/dist/resources/extensions/gsd/notification-overlay.js +26 -12
  44. package/dist/resources/extensions/gsd/notification-store.js +5 -4
  45. package/dist/resources/extensions/gsd/preferences-skills.js +2 -34
  46. package/dist/resources/extensions/gsd/preferences-types.js +15 -0
  47. package/dist/resources/extensions/gsd/preferences.js +16 -3
  48. package/dist/resources/extensions/gsd/prompt-loader.js +4 -1
  49. package/dist/resources/extensions/gsd/prompt-validation.js +126 -0
  50. package/dist/resources/extensions/gsd/prompts/complete-slice.md +3 -1
  51. package/dist/resources/extensions/gsd/prompts/discuss.md +122 -13
  52. package/dist/resources/extensions/gsd/prompts/execute-task.md +2 -0
  53. package/dist/resources/extensions/gsd/prompts/system.md +1 -1
  54. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
  55. package/dist/resources/extensions/gsd/shortcut-defs.js +7 -1
  56. package/dist/resources/extensions/gsd/state.js +29 -2
  57. package/dist/resources/extensions/gsd/tools/complete-slice.js +52 -1
  58. package/dist/resources/extensions/gsd/tools/complete-task.js +51 -1
  59. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +4 -1
  60. package/dist/resources/extensions/gsd/workflow-projections.js +7 -0
  61. package/dist/resources/extensions/gsd/worktree-manager.js +30 -3
  62. package/dist/resources/extensions/gsd/write-intercept.js +10 -1
  63. package/dist/resources/extensions/ollama/index.js +17 -10
  64. package/dist/resources/extensions/ollama/ollama-client.js +35 -6
  65. package/dist/resources/extensions/ollama/ollama-discovery.js +32 -6
  66. package/dist/resources/extensions/shared/gsd-phase-state.js +35 -0
  67. package/dist/resources/extensions/subagent/agents.js +8 -0
  68. package/dist/resources/extensions/subagent/index.js +17 -0
  69. package/dist/startup-model-validation.d.ts +0 -1
  70. package/dist/startup-model-validation.js +6 -2
  71. package/dist/web/standalone/.next/BUILD_ID +1 -1
  72. package/dist/web/standalone/.next/app-path-routes-manifest.json +14 -14
  73. package/dist/web/standalone/.next/build-manifest.json +2 -2
  74. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  75. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  76. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  77. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  78. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  86. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  87. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  88. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  91. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  94. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  96. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  97. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  98. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  99. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  100. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  101. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  102. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  103. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  104. package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
  105. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  106. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  107. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  108. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  109. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  111. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  113. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/notifications/route.js +2 -2
  115. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
  120. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  121. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  122. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  123. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  124. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  125. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  126. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  127. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  128. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  129. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
  130. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  131. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
  132. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +3 -3
  133. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  134. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  135. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  136. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  137. package/dist/web/standalone/.next/server/app/index.html +1 -1
  138. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  139. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  140. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  141. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  142. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  143. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  144. package/dist/web/standalone/.next/server/app/page.js +2 -2
  145. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  146. package/dist/web/standalone/.next/server/app-paths-manifest.json +14 -14
  147. package/dist/web/standalone/.next/server/chunks/2331.js +16 -16
  148. package/dist/web/standalone/.next/server/chunks/4741.js +12 -12
  149. package/dist/web/standalone/.next/server/chunks/5822.js +2 -2
  150. package/dist/web/standalone/.next/server/chunks/63.js +8 -8
  151. package/dist/web/standalone/.next/server/chunks/6897.js +3 -3
  152. package/dist/web/standalone/.next/server/edge-runtime-webpack.js +2 -0
  153. package/dist/web/standalone/.next/server/functions-config-manifest.json +0 -9
  154. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  155. package/dist/web/standalone/.next/server/middleware-manifest.json +29 -2
  156. package/dist/web/standalone/.next/server/middleware.js +4 -12
  157. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  158. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  159. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  160. package/dist/web/standalone/.next/server/webpack-runtime.js +1 -1
  161. package/package.json +1 -1
  162. package/packages/mcp-server/dist/server.d.ts +12 -1
  163. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  164. package/packages/mcp-server/dist/server.js +90 -42
  165. package/packages/mcp-server/dist/server.js.map +1 -1
  166. package/packages/mcp-server/dist/workflow-tools.js +1 -1
  167. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  168. package/packages/mcp-server/src/server.ts +110 -38
  169. package/packages/mcp-server/src/workflow-tools.ts +1 -1
  170. package/packages/pi-ai/dist/env-api-keys.js +1 -0
  171. package/packages/pi-ai/dist/env-api-keys.js.map +1 -1
  172. package/packages/pi-ai/dist/models.custom.d.ts +105 -0
  173. package/packages/pi-ai/dist/models.custom.d.ts.map +1 -1
  174. package/packages/pi-ai/dist/models.custom.js +97 -0
  175. package/packages/pi-ai/dist/models.custom.js.map +1 -1
  176. package/packages/pi-ai/dist/models.generated.d.ts +648 -140
  177. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  178. package/packages/pi-ai/dist/models.generated.js +867 -370
  179. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  180. package/packages/pi-ai/dist/models.generated.test.d.ts +2 -0
  181. package/packages/pi-ai/dist/models.generated.test.d.ts.map +1 -0
  182. package/packages/pi-ai/dist/models.generated.test.js +334 -0
  183. package/packages/pi-ai/dist/models.generated.test.js.map +1 -0
  184. package/packages/pi-ai/dist/models.test.js +105 -0
  185. package/packages/pi-ai/dist/models.test.js.map +1 -1
  186. package/packages/pi-ai/dist/types.d.ts +1 -1
  187. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  188. package/packages/pi-ai/dist/types.js.map +1 -1
  189. package/packages/pi-ai/dist/utils/oauth/github-copilot.d.ts.map +1 -1
  190. package/packages/pi-ai/dist/utils/oauth/github-copilot.js +5 -1
  191. package/packages/pi-ai/dist/utils/oauth/github-copilot.js.map +1 -1
  192. package/packages/pi-ai/dist/utils/oauth/github-copilot.test.d.ts +2 -0
  193. package/packages/pi-ai/dist/utils/oauth/github-copilot.test.d.ts.map +1 -0
  194. package/packages/pi-ai/dist/utils/oauth/github-copilot.test.js +57 -0
  195. package/packages/pi-ai/dist/utils/oauth/github-copilot.test.js.map +1 -0
  196. package/packages/pi-ai/src/env-api-keys.ts +1 -0
  197. package/packages/pi-ai/src/models.custom.ts +98 -0
  198. package/packages/pi-ai/src/models.generated.test.ts +373 -0
  199. package/packages/pi-ai/src/models.generated.ts +867 -370
  200. package/packages/pi-ai/src/models.test.ts +135 -0
  201. package/packages/pi-ai/src/types.ts +1 -0
  202. package/packages/pi-ai/src/utils/oauth/github-copilot.test.ts +71 -0
  203. package/packages/pi-ai/src/utils/oauth/github-copilot.ts +4 -1
  204. package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
  205. package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -0
  206. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  207. package/packages/pi-coding-agent/dist/core/model-resolver.test.d.ts +8 -0
  208. package/packages/pi-coding-agent/dist/core/model-resolver.test.d.ts.map +1 -0
  209. package/packages/pi-coding-agent/dist/core/model-resolver.test.js +75 -0
  210. package/packages/pi-coding-agent/dist/core/model-resolver.test.js.map +1 -0
  211. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts +5 -0
  212. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -1
  213. package/packages/pi-coding-agent/dist/core/retry-handler.js +55 -1
  214. package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
  215. package/packages/pi-coding-agent/dist/core/retry-handler.test.js +57 -0
  216. package/packages/pi-coding-agent/dist/core/retry-handler.test.js.map +1 -1
  217. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  218. package/packages/pi-coding-agent/dist/core/sdk.js +9 -0
  219. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  220. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +36 -0
  221. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -1
  222. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  223. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js +9 -2
  224. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js.map +1 -1
  225. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  226. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +87 -12
  227. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  228. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.d.ts.map +1 -1
  229. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js +6 -1
  230. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js.map +1 -1
  231. package/packages/pi-coding-agent/package.json +1 -1
  232. package/packages/pi-coding-agent/src/core/model-resolver.test.ts +85 -0
  233. package/packages/pi-coding-agent/src/core/model-resolver.ts +1 -0
  234. package/packages/pi-coding-agent/src/core/retry-handler.test.ts +83 -0
  235. package/packages/pi-coding-agent/src/core/retry-handler.ts +60 -1
  236. package/packages/pi-coding-agent/src/core/sdk.ts +10 -0
  237. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +72 -0
  238. package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +15 -6
  239. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +84 -12
  240. package/packages/pi-coding-agent/src/modes/interactive/controllers/model-controller.ts +6 -1
  241. package/packages/pi-tui/dist/components/__tests__/editor.test.js +12 -0
  242. package/packages/pi-tui/dist/components/__tests__/editor.test.js.map +1 -1
  243. package/packages/pi-tui/dist/components/__tests__/input.test.js +12 -0
  244. package/packages/pi-tui/dist/components/__tests__/input.test.js.map +1 -1
  245. package/packages/pi-tui/dist/keys.d.ts.map +1 -1
  246. package/packages/pi-tui/dist/keys.js +27 -0
  247. package/packages/pi-tui/dist/keys.js.map +1 -1
  248. package/packages/pi-tui/src/components/__tests__/editor.test.ts +18 -0
  249. package/packages/pi-tui/src/components/__tests__/input.test.ts +18 -0
  250. package/packages/pi-tui/src/keys.ts +32 -0
  251. package/pkg/package.json +1 -1
  252. package/src/resources/agents/debugger.md +58 -0
  253. package/src/resources/agents/doc-writer.md +43 -0
  254. package/src/resources/agents/git-ops.md +56 -0
  255. package/src/resources/agents/javascript-pro.md +46 -271
  256. package/src/resources/agents/planner.md +55 -0
  257. package/src/resources/agents/refactorer.md +47 -0
  258. package/src/resources/agents/reviewer.md +48 -0
  259. package/src/resources/agents/security.md +59 -0
  260. package/src/resources/agents/tester.md +50 -0
  261. package/src/resources/agents/typescript-pro.md +41 -235
  262. package/src/resources/extensions/async-jobs/await-tool.test.ts +40 -7
  263. package/src/resources/extensions/async-jobs/await-tool.ts +7 -4
  264. package/src/resources/extensions/async-jobs/job-manager.ts +33 -3
  265. package/src/resources/extensions/claude-code-cli/partial-builder.ts +45 -12
  266. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +139 -8
  267. package/src/resources/extensions/claude-code-cli/tests/partial-builder.test.ts +91 -2
  268. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +245 -2
  269. package/src/resources/extensions/gsd/auto/loop.ts +89 -1
  270. package/src/resources/extensions/gsd/auto/phases.ts +4 -0
  271. package/src/resources/extensions/gsd/auto-post-unit.ts +7 -0
  272. package/src/resources/extensions/gsd/auto-prompts.ts +111 -33
  273. package/src/resources/extensions/gsd/auto-recovery.ts +10 -0
  274. package/src/resources/extensions/gsd/auto-start.ts +31 -4
  275. package/src/resources/extensions/gsd/auto.ts +29 -20
  276. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +3 -3
  277. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +8 -10
  278. package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +2 -5
  279. package/src/resources/extensions/gsd/commands-handlers.ts +5 -1
  280. package/src/resources/extensions/gsd/context-injector.ts +1 -1
  281. package/src/resources/extensions/gsd/custom-workflow-engine.ts +4 -8
  282. package/src/resources/extensions/gsd/definition-io.ts +18 -0
  283. package/src/resources/extensions/gsd/dispatch-guard.ts +5 -0
  284. package/src/resources/extensions/gsd/doctor-providers.ts +24 -0
  285. package/src/resources/extensions/gsd/doctor-runtime-checks.ts +6 -3
  286. package/src/resources/extensions/gsd/error-classifier.ts +4 -1
  287. package/src/resources/extensions/gsd/gate-registry.ts +251 -0
  288. package/src/resources/extensions/gsd/git-service.ts +11 -8
  289. package/src/resources/extensions/gsd/gitignore.ts +12 -6
  290. package/src/resources/extensions/gsd/gsd-db.ts +105 -6
  291. package/src/resources/extensions/gsd/key-manager.ts +2 -0
  292. package/src/resources/extensions/gsd/milestone-validation-gates.ts +11 -13
  293. package/src/resources/extensions/gsd/notification-overlay.ts +27 -11
  294. package/src/resources/extensions/gsd/notification-store.ts +5 -4
  295. package/src/resources/extensions/gsd/preferences-skills.ts +2 -36
  296. package/src/resources/extensions/gsd/preferences-types.ts +16 -0
  297. package/src/resources/extensions/gsd/preferences.ts +19 -6
  298. package/src/resources/extensions/gsd/prompt-loader.ts +6 -1
  299. package/src/resources/extensions/gsd/prompt-validation.ts +157 -0
  300. package/src/resources/extensions/gsd/prompts/complete-slice.md +3 -1
  301. package/src/resources/extensions/gsd/prompts/discuss.md +122 -13
  302. package/src/resources/extensions/gsd/prompts/execute-task.md +2 -0
  303. package/src/resources/extensions/gsd/prompts/system.md +1 -1
  304. package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
  305. package/src/resources/extensions/gsd/shortcut-defs.ts +8 -1
  306. package/src/resources/extensions/gsd/state.ts +33 -2
  307. package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +27 -0
  308. package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +14 -0
  309. package/src/resources/extensions/gsd/tests/block-db-writes.test.ts +63 -0
  310. package/src/resources/extensions/gsd/tests/complete-slice-gate-closure.test.ts +167 -0
  311. package/src/resources/extensions/gsd/tests/definition-io.test.ts +57 -0
  312. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +26 -0
  313. package/src/resources/extensions/gsd/tests/doctor-heal-fixable-warnings.test.ts +14 -0
  314. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +36 -0
  315. package/src/resources/extensions/gsd/tests/false-degraded-mode-warning.test.ts +104 -0
  316. package/src/resources/extensions/gsd/tests/format-shortcut.test.ts +16 -0
  317. package/src/resources/extensions/gsd/tests/gate-dispatch.test.ts +27 -0
  318. package/src/resources/extensions/gsd/tests/gate-registry.test.ts +140 -0
  319. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +107 -5
  320. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +8 -6
  321. package/src/resources/extensions/gsd/tests/key-manager.test.ts +63 -0
  322. package/src/resources/extensions/gsd/tests/memory-pressure-stuck-state.test.ts +54 -0
  323. package/src/resources/extensions/gsd/tests/plan-milestone-artifact-verification.test.ts +62 -0
  324. package/src/resources/extensions/gsd/tests/post-unit-state-rebuild.test.ts +34 -0
  325. package/src/resources/extensions/gsd/tests/preferences-formatting.test.ts +87 -0
  326. package/src/resources/extensions/gsd/tests/preferences.test.ts +53 -0
  327. package/src/resources/extensions/gsd/tests/projection-regression.test.ts +96 -1
  328. package/src/resources/extensions/gsd/tests/prompt-loader-working-directory.test.ts +19 -0
  329. package/src/resources/extensions/gsd/tests/prompt-system-gate-coverage.test.ts +208 -0
  330. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +9 -0
  331. package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +97 -0
  332. package/src/resources/extensions/gsd/tests/register-shortcuts.test.ts +3 -2
  333. package/src/resources/extensions/gsd/tests/stale-slice-rows.test.ts +41 -0
  334. package/src/resources/extensions/gsd/tools/complete-slice.ts +63 -0
  335. package/src/resources/extensions/gsd/tools/complete-task.ts +63 -0
  336. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +4 -1
  337. package/src/resources/extensions/gsd/types.ts +26 -0
  338. package/src/resources/extensions/gsd/workflow-projections.ts +8 -0
  339. package/src/resources/extensions/gsd/worktree-manager.ts +29 -3
  340. package/src/resources/extensions/gsd/write-intercept.ts +10 -1
  341. package/src/resources/extensions/ollama/index.ts +17 -8
  342. package/src/resources/extensions/ollama/ollama-client.ts +35 -6
  343. package/src/resources/extensions/ollama/ollama-discovery.ts +37 -6
  344. package/src/resources/extensions/ollama/ollama-status-indicator.test.ts +28 -0
  345. package/src/resources/extensions/ollama/tests/ollama-discovery.test.ts +54 -0
  346. package/src/resources/extensions/shared/gsd-phase-state.ts +42 -0
  347. package/src/resources/extensions/shared/tests/gsd-phase-state.test.ts +48 -0
  348. package/src/resources/extensions/subagent/agents.ts +10 -0
  349. package/src/resources/extensions/subagent/index.ts +18 -0
  350. package/src/resources/extensions/subagent/tests/agents-conflicts.test.ts +33 -0
  351. /package/dist/web/standalone/.next/static/{cYPZv_bAhZk2ms-Pz6vsY → NzO79SOz9jHX-VY5-0t2O}/_buildManifest.js +0 -0
  352. /package/dist/web/standalone/.next/static/{cYPZv_bAhZk2ms-Pz6vsY → NzO79SOz9jHX-VY5-0t2O}/_ssgManifest.js +0 -0
@@ -171,6 +171,25 @@ describe("RetryHandler — long-context entitlement 429 (#2803)", () => {
171
171
  const retryStart = emittedEvents.find((e) => e.type === "auto_retry_start");
172
172
  assert.ok(retryStart, "Regular 429 should enter backoff retry");
173
173
  });
174
+
175
+ it("classifies OpenRouter credit affordability errors as quota_exhausted", async () => {
176
+ const { deps, emittedEvents } = createMockDeps({
177
+ model: createMockModel("openrouter", "openai/gpt-5-pro"),
178
+ markUsageLimitReachedResult: false,
179
+ fallbackResult: null,
180
+ });
181
+
182
+ const handler = new RetryHandler(deps);
183
+ const msg = errorMessage(
184
+ "402 This request requires more credits, or fewer max_tokens. You requested up to 32000 tokens, but can only afford 329.",
185
+ );
186
+
187
+ const result = await handler.handleRetryableError(msg);
188
+
189
+ assert.equal(result, true, "affordability error should trigger credit-aware retry");
190
+ const retryStart = emittedEvents.find((e) => e.type === "auto_retry_start");
191
+ assert.ok(retryStart, "Expected immediate retry after reducing max tokens");
192
+ });
174
193
  });
175
194
 
176
195
  describe("long-context model downgrade", () => {
@@ -271,6 +290,61 @@ describe("RetryHandler — long-context entitlement 429 (#2803)", () => {
271
290
  });
272
291
  });
273
292
 
293
+ describe("credit-aware maxTokens retry", () => {
294
+ it("reduces maxTokens on same model when provider reports affordable cap", async () => {
295
+ const expensiveModel = createMockModel("openrouter", "openai/gpt-5-pro");
296
+ expensiveModel.maxTokens = 128000;
297
+
298
+ const { deps, emittedEvents, onModelChangeFn } = createMockDeps({
299
+ model: expensiveModel,
300
+ markUsageLimitReachedResult: false,
301
+ fallbackResult: null,
302
+ });
303
+
304
+ const handler = new RetryHandler(deps);
305
+ const msg = errorMessage(
306
+ "402 This request requires more credits, or fewer max_tokens. You requested up to 32000 tokens, but can only afford 329.",
307
+ );
308
+
309
+ const result = await handler.handleRetryableError(msg);
310
+ assert.equal(result, true, "should retry after reducing maxTokens");
311
+
312
+ const setModelCalls = (deps.agent.setModel as any).mock.calls;
313
+ assert.equal(setModelCalls.length, 1, "should apply one model downgrade");
314
+ const downgraded = setModelCalls[0].arguments[0] as Model<Api>;
315
+ assert.equal(downgraded.provider, "openrouter");
316
+ assert.equal(downgraded.id, "openai/gpt-5-pro");
317
+ assert.equal(downgraded.maxTokens, 297, "expected affordability cap with safety buffer");
318
+
319
+ assert.equal(onModelChangeFn.mock.calls.length, 1, "should notify about model update");
320
+ const switchEvent = emittedEvents.find((e) => e.type === "fallback_provider_switch");
321
+ assert.ok(switchEvent, "should emit model-adjustment event");
322
+ assert.ok(
323
+ String(switchEvent?.reason || "").includes("credit-aware retry"),
324
+ "switch reason should mention credit-aware retry",
325
+ );
326
+ });
327
+
328
+ it("does not mark credentials in cooldown for affordability quota errors", async () => {
329
+ const expensiveModel = createMockModel("openrouter", "openai/gpt-5-pro");
330
+ expensiveModel.maxTokens = 128000;
331
+
332
+ const { deps, markUsageLimitReached } = createMockDeps({
333
+ model: expensiveModel,
334
+ markUsageLimitReachedResult: false,
335
+ fallbackResult: null,
336
+ });
337
+
338
+ const handler = new RetryHandler(deps);
339
+ const msg = errorMessage(
340
+ "402 This request requires more credits, or fewer max_tokens. You requested up to 32000 tokens, but can only afford 329.",
341
+ );
342
+
343
+ await handler.handleRetryableError(msg);
344
+ assert.equal(markUsageLimitReached.mock.calls.length, 0, "quota error should skip credential cooldown");
345
+ });
346
+ });
347
+
274
348
  describe("isRetryableError", () => {
275
349
  it("considers long-context entitlement error as retryable", () => {
276
350
  const { deps } = createMockDeps();
@@ -291,6 +365,15 @@ describe("RetryHandler — long-context entitlement 429 (#2803)", () => {
291
365
  );
292
366
  assert.equal(handler.isRetryableError(msg), false);
293
367
  });
368
+
369
+ it("considers OpenRouter affordability credit errors as retryable", () => {
370
+ const { deps } = createMockDeps();
371
+ const handler = new RetryHandler(deps);
372
+ const msg = errorMessage(
373
+ "402 This request requires more credits, or fewer max_tokens. You requested up to 32000 tokens, but can only afford 329.",
374
+ );
375
+ assert.equal(handler.isRetryableError(msg), true);
376
+ });
294
377
  });
295
378
 
296
379
  describe("third-party block claude-code fallback (#3772)", () => {
@@ -116,7 +116,7 @@ export class RetryHandler {
116
116
  // generated error from getApiKey() when credentials are in a backoff window.
117
117
  // Re-entering the retry handler for that message creates a cascade of empty
118
118
  // error entries in the session file, breaking resume (#3429).
119
- return /overloaded|rate.?limit|too many requests|429|500|502|503|504|service.?unavailable|server.?error|internal.?error|connection.?error|connection.?refused|other side closed|fetch failed|upstream.?connect|reset before headers|terminated|retry delay|network.?(?:is\s+)?unavailable|credentials.*expired|extra usage is required|(?:out of|no) extra usage|third.party.*draw from extra|third.party.*not.*available/i.test(
119
+ return /overloaded|rate.?limit|too many requests|402|429|500|502|503|504|service.?unavailable|server.?error|internal.?error|connection.?error|connection.?refused|other side closed|fetch failed|upstream.?connect|reset before headers|terminated|retry delay|network.?(?:is\s+)?unavailable|credentials.*expired|requires more credits|can only afford|insufficient credits|not enough credits|extra usage is required|(?:out of|no) extra usage|third.party.*draw from extra|third.party.*not.*available/i.test(
120
120
  err,
121
121
  );
122
122
  }
@@ -158,6 +158,14 @@ export class RetryHandler {
158
158
  const isRateLimit = errorType === "rate_limit";
159
159
  const isQuotaError = errorType === "quota_exhausted";
160
160
 
161
+ // Credit-aware retry (OpenRouter-style 402 affordability errors):
162
+ // when provider reports "can only afford N", lower maxTokens and retry
163
+ // on the same model before rotating credentials/providers.
164
+ if (isQuotaError) {
165
+ const adjusted = this._tryAffordableMaxTokensRetry(message, retryGeneration);
166
+ if (adjusted) return true;
167
+ }
168
+
161
169
  // Credential rotation — only for transient rate limits (#3430).
162
170
  // Quota errors ("Extra usage is required") are account-level billing
163
171
  // gates; rotating to another credential on the same account won't help
@@ -409,12 +417,63 @@ export class RetryHandler {
409
417
  // Long-context entitlement errors are billing gates, not transient rate limits.
410
418
  // Must be checked before the generic 429/rate_limit regex.
411
419
  if (/extra usage is required|long context required/i.test(err)) return "quota_exhausted";
420
+ if (/requires more credits|can only afford|insufficient credits|not enough credits|credit balance/i.test(err))
421
+ return "quota_exhausted";
412
422
  if (/quota|billing|exceeded.*limit|usage.*limit/i.test(err)) return "quota_exhausted";
413
423
  if (/rate.?limit|too many requests|429/i.test(err)) return "rate_limit";
414
424
  if (/500|502|503|504|server.?error|internal.?error|service.?unavailable/i.test(err)) return "server_error";
415
425
  return "unknown";
416
426
  }
417
427
 
428
+ /**
429
+ * Attempt a same-model retry by reducing maxTokens when provider reports
430
+ * an affordability cap (e.g., "can only afford 329").
431
+ */
432
+ private _tryAffordableMaxTokensRetry(message: AssistantMessage, retryGeneration: number): boolean {
433
+ const currentModel = this._deps.getModel();
434
+ if (!currentModel || !message.errorMessage) return false;
435
+
436
+ // Example: "can only afford 329"
437
+ const match = message.errorMessage.match(/can only afford\s+([\d,]+)/i);
438
+ if (!match?.[1]) return false;
439
+
440
+ const affordable = Number.parseInt(match[1].replace(/,/g, ""), 10);
441
+ if (!Number.isFinite(affordable) || affordable <= 0) return false;
442
+
443
+ // Leave a small buffer so slight input variance doesn't immediately re-fail.
444
+ const safetyBuffer = Math.min(64, Math.max(16, Math.floor(affordable * 0.1)));
445
+ const targetMaxTokens = Math.max(64, affordable - safetyBuffer);
446
+ const downgradedMaxTokens = Math.min(currentModel.maxTokens, targetMaxTokens);
447
+ if (downgradedMaxTokens >= currentModel.maxTokens) return false;
448
+
449
+ const downgradedModel = {
450
+ ...currentModel,
451
+ maxTokens: downgradedMaxTokens,
452
+ };
453
+
454
+ this._deps.agent.setModel(downgradedModel);
455
+ this._deps.onModelChange(downgradedModel);
456
+ this._removeLastAssistantError();
457
+
458
+ this._deps.emit({
459
+ type: "fallback_provider_switch",
460
+ from: `${currentModel.provider}/${currentModel.id} (maxTokens=${currentModel.maxTokens})`,
461
+ to: `${downgradedModel.provider}/${downgradedModel.id} (maxTokens=${downgradedModel.maxTokens})`,
462
+ reason: `credit-aware retry: provider affordable cap ${affordable} tokens`,
463
+ });
464
+
465
+ this._deps.emit({
466
+ type: "auto_retry_start",
467
+ attempt: this._retryAttempt + 1,
468
+ maxAttempts: this._deps.settingsManager.getRetrySettings().maxRetries,
469
+ delayMs: 0,
470
+ errorMessage: `${message.errorMessage} (reducing max tokens)`,
471
+ });
472
+
473
+ this._scheduleContinue(retryGeneration);
474
+ return true;
475
+ }
476
+
418
477
  /**
419
478
  * Attempt to downgrade a long-context model (e.g. claude-opus-4-6[1m]) to its
420
479
  * base model (claude-opus-4-6) when the account lacks the long-context billing
@@ -219,6 +219,16 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
219
219
  time("resourceLoader.reload");
220
220
  }
221
221
 
222
+ // Flush provider registrations queued during extension loading so that
223
+ // extension models (e.g. pi-claude-cli) are visible in the registry before
224
+ // findInitialModel() runs. bindCore() repeats this flush as a safety net
225
+ // for any late-arriving registrations.
226
+ const { runtime: extensionRuntime } = resourceLoader.getExtensions();
227
+ for (const { name, config } of extensionRuntime.pendingProviderRegistrations) {
228
+ modelRegistry.registerProvider(name, config);
229
+ }
230
+ extensionRuntime.pendingProviderRegistrations = [];
231
+
222
232
  // Check if session has existing data to restore
223
233
  const existingSession = sessionManager.buildSessionContext();
224
234
  const hasExistingSession = existingSession.messages.length > 0;
@@ -27,6 +27,26 @@ function renderTool(
27
27
  return stripAnsi(component.render(120).join("\n"));
28
28
  }
29
29
 
30
+ function renderToolCollapsed(
31
+ toolName: string,
32
+ args: Record<string, unknown>,
33
+ result?: {
34
+ content: Array<{ type: string; text?: string }>;
35
+ isError: boolean;
36
+ details?: Record<string, unknown>;
37
+ },
38
+ ): string {
39
+ const component = new ToolExecutionComponent(
40
+ toolName,
41
+ args,
42
+ {},
43
+ undefined,
44
+ { requestRender() {} } as any,
45
+ );
46
+ if (result) component.updateResult(result);
47
+ return stripAnsi(component.render(120).join("\n"));
48
+ }
49
+
30
50
  describe("ToolExecutionComponent", () => {
31
51
  test("renders capitalized Claude Code Bash tool names with bash output instead of generic args JSON", () => {
32
52
  const rendered = renderTool(
@@ -51,4 +71,56 @@ describe("ToolExecutionComponent", () => {
51
71
  assert.match(rendered, /hello/);
52
72
  assert.match(rendered, /world/);
53
73
  });
74
+
75
+ test("generic fallback strips mcp__<server>__ prefix and shows server·tool title", () => {
76
+ const rendered = renderTool(
77
+ "mcp__context7__resolve_library_id",
78
+ { name: "react" },
79
+ { content: [{ type: "text", text: "react@18.3.1" }], isError: false },
80
+ );
81
+
82
+ assert.match(rendered, /context7\u00b7resolve_library_id/);
83
+ assert.doesNotMatch(rendered, /mcp__/);
84
+ assert.match(rendered, /name="react"/);
85
+ assert.match(rendered, /react@18\.3\.1/);
86
+ });
87
+
88
+ test("generic fallback renders compact key=value args for primitive args", () => {
89
+ const rendered = renderTool(
90
+ "some_unknown_tool",
91
+ { count: 3, enabled: true, label: "hello" },
92
+ );
93
+
94
+ assert.match(rendered, /some_unknown_tool/);
95
+ assert.match(rendered, /count=3/);
96
+ assert.match(rendered, /enabled=true/);
97
+ assert.match(rendered, /label="hello"/);
98
+ assert.doesNotMatch(rendered, /^\{$/m);
99
+ });
100
+
101
+ test("generic fallback truncates long output when collapsed", () => {
102
+ const longOutput = Array.from({ length: 25 }, (_, i) => `line ${i + 1}`).join("\n");
103
+ const rendered = renderToolCollapsed(
104
+ "mcp__demo__do_thing",
105
+ { ok: true },
106
+ { content: [{ type: "text", text: longOutput }], isError: false },
107
+ );
108
+
109
+ assert.match(rendered, /line 1\b/);
110
+ assert.match(rendered, /line 10\b/);
111
+ assert.doesNotMatch(rendered, /line 20\b/);
112
+ assert.match(rendered, /\(15 more lines/);
113
+ });
114
+
115
+ test("generic fallback falls back to truncated JSON for complex args", () => {
116
+ const rendered = renderTool(
117
+ "mcp__demo__nested",
118
+ { payload: { nested: { deeply: ["a", "b", "c"] } }, name: "x" },
119
+ );
120
+
121
+ assert.match(rendered, /demo\u00b7nested/);
122
+ // Multi-line JSON dump for the complex payload
123
+ assert.match(rendered, /"payload"/);
124
+ assert.match(rendered, /"nested"/);
125
+ });
54
126
  });
@@ -120,7 +120,12 @@ export class ModelSelectorComponent extends Container implements Focusable {
120
120
  this.settingsManager = settingsManager;
121
121
  this.modelRegistry = modelRegistry;
122
122
  this.scopedModels = scopedModels;
123
- this.scope = scopedModels.length > 0 ? "scoped" : "all";
123
+ // Only land in "scoped" view when at least one scoped model has working
124
+ // auth — otherwise the user would see an empty picker (#unconfigured-models).
125
+ const hasReadyScopedModel = scopedModels.some((scoped) =>
126
+ modelRegistry.isProviderRequestReady(scoped.model.provider),
127
+ );
128
+ this.scope = hasReadyScopedModel ? "scoped" : "all";
124
129
  this.onSelectCallback = onSelect;
125
130
  this.onCancelCallback = onCancel;
126
131
 
@@ -215,12 +220,16 @@ export class ModelSelectorComponent extends Container implements Focusable {
215
220
  }
216
221
 
217
222
  this.allModels = this.sortModelsWithinProvider(models);
223
+ // Scoped models must also be filtered by provider readiness so users
224
+ // can't pick a scoped model whose provider has no API key / OAuth.
218
225
  this.scopedModelItems = this.sortModelsWithinProvider(
219
- this.scopedModels.map((scoped) => ({
220
- provider: scoped.model.provider,
221
- id: scoped.model.id,
222
- model: scoped.model,
223
- })),
226
+ this.scopedModels
227
+ .filter((scoped) => this.modelRegistry.isProviderRequestReady(scoped.model.provider))
228
+ .map((scoped) => ({
229
+ provider: scoped.model.provider,
230
+ id: scoped.model.id,
231
+ model: scoped.model,
232
+ })),
224
233
  );
225
234
  this.activeModels = this.scope === "scoped" ? this.scopedModelItems : this.allModels;
226
235
  this.filteredModels = this.activeModels;
@@ -51,6 +51,60 @@ function str(value: unknown): string | null {
51
51
  return null; // Invalid type
52
52
  }
53
53
 
54
+ /**
55
+ * Split a Claude Code MCP tool name (`mcp__<server>__<tool>`) into its parts.
56
+ * Returns null for non-prefixed names. Duplicated from the claude-code-cli
57
+ * extension (parseMcpToolName) so this package doesn't have to import across
58
+ * the resources/extensions boundary.
59
+ */
60
+ function parseMcpToolName(name: string): { server: string; tool: string } | null {
61
+ if (!name.startsWith("mcp__")) return null;
62
+ const rest = name.slice("mcp__".length);
63
+ const delim = rest.indexOf("__");
64
+ if (delim <= 0 || delim === rest.length - 2) return null;
65
+ return { server: rest.slice(0, delim), tool: rest.slice(delim + 2) };
66
+ }
67
+
68
+ const COMPACT_ARG_VALUE_LIMIT = 60;
69
+ const GENERIC_OUTPUT_PREVIEW_LINES = 10;
70
+ const GENERIC_ARGS_JSON_PREVIEW_LINES = 10;
71
+
72
+ /**
73
+ * Format tool args for the generic-renderer fallback. Produces a one-line
74
+ * `k=v, k=v` summary when every value is a primitive that fits inline; falls
75
+ * back to a truncated JSON dump for structurally complex args.
76
+ */
77
+ function formatCompactArgs(args: unknown, expanded: boolean): string {
78
+ if (args == null) return "";
79
+ if (typeof args !== "object") return String(args);
80
+
81
+ const entries = Object.entries(args as Record<string, unknown>);
82
+ if (entries.length === 0) return "";
83
+
84
+ const allPrimitive = entries.every(([, value]) => {
85
+ const t = typeof value;
86
+ if (t === "number" || t === "boolean") return true;
87
+ if (t === "string") return (value as string).length <= COMPACT_ARG_VALUE_LIMIT;
88
+ return value == null;
89
+ });
90
+
91
+ if (allPrimitive) {
92
+ return entries
93
+ .map(([key, value]) => {
94
+ if (typeof value === "string") return `${key}=${JSON.stringify(value)}`;
95
+ if (value == null) return `${key}=null`;
96
+ return `${key}=${String(value)}`;
97
+ })
98
+ .join(", ");
99
+ }
100
+
101
+ // Complex args: show truncated JSON.
102
+ const lines = JSON.stringify(args, null, 2).split("\n");
103
+ const maxLines = expanded ? lines.length : GENERIC_ARGS_JSON_PREVIEW_LINES;
104
+ if (lines.length <= maxLines) return lines.join("\n");
105
+ return lines.slice(0, maxLines).join("\n") + "\n...";
106
+ }
107
+
54
108
  export interface ToolExecutionOptions {
55
109
  showImages?: boolean; // default: true (only used if terminal supports images)
56
110
  }
@@ -990,19 +1044,37 @@ export class ToolExecutionComponent extends Container {
990
1044
  }
991
1045
  }
992
1046
  } else {
993
- // Generic tool (shouldn't reach here for custom tools)
994
- text = theme.fg("toolTitle", theme.bold(this.toolName));
995
-
996
- const contentLines = JSON.stringify(this.args, null, 2).split("\n");
997
- const maxContentLines = 20;
998
- const truncatedContent = contentLines.slice(0, maxContentLines);
999
- if (contentLines.length > maxContentLines) {
1000
- truncatedContent.push("...");
1047
+ // Generic tool / MCP tool without a registered renderer.
1048
+ // MCP tool names from Claude Code arrive as `mcp__<server>__<tool>`;
1049
+ // render the server prefix in muted style so the tool name reads
1050
+ // cleanly. GSD-registered MCP tools have already had their prefix
1051
+ // stripped upstream in partial-builder.ts and won't reach this branch.
1052
+ const parsed = parseMcpToolName(this.toolName);
1053
+ const displayName = parsed ? parsed.tool : this.toolName;
1054
+ const serverPrefix = parsed ? theme.fg("muted", `${parsed.server}\u00b7`) : "";
1055
+ text = serverPrefix + theme.fg("toolTitle", theme.bold(displayName));
1056
+
1057
+ const argsText = formatCompactArgs(this.args, this.expanded);
1058
+ if (argsText) {
1059
+ if (argsText.includes("\n")) {
1060
+ text += `\n\n${theme.fg("toolOutput", argsText)}`;
1061
+ } else {
1062
+ text += " " + theme.fg("toolOutput", argsText);
1063
+ }
1001
1064
  }
1002
- text += `\n\n${truncatedContent.join("\n")}`;
1003
- const output = this.getTextOutput();
1004
- if (output) {
1005
- text += `\n${output}`;
1065
+
1066
+ if (this.result) {
1067
+ const output = this.getTextOutput().trim();
1068
+ if (output) {
1069
+ const lines = output.split("\n");
1070
+ const maxLines = this.expanded ? lines.length : GENERIC_OUTPUT_PREVIEW_LINES;
1071
+ const displayLines = lines.slice(0, maxLines);
1072
+ const remaining = lines.length - maxLines;
1073
+ text += `\n\n${displayLines.map((line: string) => theme.fg("toolOutput", line)).join("\n")}`;
1074
+ if (remaining > 0) {
1075
+ text += `${theme.fg("muted", `\n... (${remaining} more lines,`)} ${keyHint("expandTools", "to expand")})`;
1076
+ }
1077
+ }
1006
1078
  }
1007
1079
  }
1008
1080
 
@@ -52,7 +52,12 @@ export async function findExactModelMatch(host: any, searchTerm: string): Promis
52
52
 
53
53
  export async function getModelCandidates(host: any): Promise<Model<any>[]> {
54
54
  if (host.session.scopedModels.length > 0) {
55
- return host.session.scopedModels.map((scoped: any) => scoped.model);
55
+ // Filter scoped models by provider auth readiness so callers like
56
+ // findExactModelMatch can't resolve a scoped-but-unconfigured model.
57
+ const registry = host.session.modelRegistry;
58
+ return host.session.scopedModels
59
+ .filter((scoped: any) => registry.isProviderRequestReady(scoped.model.provider))
60
+ .map((scoped: any) => scoped.model);
56
61
  }
57
62
 
58
63
  host.session.modelRegistry.refresh();
@@ -50,5 +50,17 @@ describe("Editor", () => {
50
50
  const rendered = editor.render(40).join("\n");
51
51
  assert.ok(rendered.includes(CURSOR_MARKER));
52
52
  });
53
+ it("maps kitty keypad digits to plain editor text", () => {
54
+ const editor = new Editor(new TUI(makeTerminal()), theme);
55
+ editor.focused = true;
56
+ editor.handleInput("\x1b[57404;129u");
57
+ assert.equal(editor.getText(), "5");
58
+ });
59
+ it("does not insert kitty keypad navigation private-use glyphs into the editor", () => {
60
+ const editor = new Editor(new TUI(makeTerminal()), theme);
61
+ editor.focused = true;
62
+ editor.handleInput("\x1b[57419u");
63
+ assert.equal(editor.getText(), "");
64
+ });
53
65
  });
54
66
  //# sourceMappingURL=editor.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"editor.test.js","sourceRoot":"","sources":["../../../src/components/__tests__/editor.test.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,EAAE,MAAM,EAAoB,MAAM,cAAc,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAGlD,SAAS,YAAY;IACpB,OAAO;QACN,KAAK,EAAE,IAAI;QACX,OAAO,EAAE,EAAE;QACX,IAAI,EAAE,EAAE;QACR,mBAAmB,EAAE,KAAK;QAC1B,KAAK,KAAI,CAAC;QACV,IAAI,KAAI,CAAC;QACT,UAAU,EAAE,KAAK,IAAI,EAAE,GAAE,CAAC;QAC1B,KAAK,KAAI,CAAC;QACV,MAAM,KAAI,CAAC;QACX,UAAU,KAAI,CAAC;QACf,UAAU,KAAI,CAAC;QACf,SAAS,KAAI,CAAC;QACd,eAAe,KAAI,CAAC;QACpB,WAAW,KAAI,CAAC;QAChB,QAAQ,KAAI,CAAC;KACb,CAAC;AACH,CAAC;AAED,MAAM,KAAK,GAAgB;IAC1B,WAAW,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI;IAC3B,UAAU,EAAE;QACX,cAAc,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI;QAC9B,YAAY,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI;QAC5B,WAAW,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI;QAC3B,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI;QAC1B,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI;KACvB;CACD,CAAC;AAEF,QAAQ,CAAC,QAAQ,EAAE,GAAG,EAAE;IACvB,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;QAC1D,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,GAAG,CAAC,YAAY,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC;QAC1D,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC;QAEtB,MAAM,CAAC,WAAW,CAAC,kBAAkB,CAAC,CAAC;QACvC,MAAM,CAAC,OAAO,GAAG,KAAK,CAAC;QACvB,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC;QACtB,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;QAE5B,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,OAAO,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qEAAqE,EAAE,GAAG,EAAE;QAC9E,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,GAAG,CAAC,YAAY,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC;QAC1D,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC;QACtB,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAErB,MAAc,CAAC,iBAAiB,GAAG,SAAS,CAAC;QAC7C,MAAc,CAAC,gBAAgB,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;QAExD,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAE9C,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC;IAC7C,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC","sourcesContent":["import assert from \"node:assert/strict\";\nimport { describe, it } from \"node:test\";\n\nimport { Editor, type EditorTheme } from \"../editor.js\";\nimport { CURSOR_MARKER, TUI } from \"../../tui.js\";\nimport type { Terminal } from \"../../terminal.js\";\n\nfunction makeTerminal(): Terminal {\n\treturn {\n\t\tisTTY: true,\n\t\tcolumns: 80,\n\t\trows: 24,\n\t\tkittyProtocolActive: false,\n\t\tstart() {},\n\t\tstop() {},\n\t\tdrainInput: async () => {},\n\t\twrite() {},\n\t\tmoveBy() {},\n\t\thideCursor() {},\n\t\tshowCursor() {},\n\t\tclearLine() {},\n\t\tclearFromCursor() {},\n\t\tclearScreen() {},\n\t\tsetTitle() {},\n\t};\n}\n\nconst theme: EditorTheme = {\n\tborderColor: (text) => text,\n\tselectList: {\n\t\tselectedPrefix: (text) => text,\n\t\tselectedText: (text) => text,\n\t\tdescription: (text) => text,\n\t\tscrollInfo: (text) => text,\n\t\tnoMatch: (text) => text,\n\t},\n};\n\ndescribe(\"Editor\", () => {\n\tit(\"clears bracketed paste state when focus is lost\", () => {\n\t\tconst editor = new Editor(new TUI(makeTerminal()), theme);\n\t\teditor.focused = true;\n\n\t\teditor.handleInput(\"\\x1b[200~partial\");\n\t\teditor.focused = false;\n\t\teditor.focused = true;\n\t\teditor.handleInput(\"hello\");\n\n\t\tassert.equal(editor.getText(), \"hello\");\n\t});\n\n\tit(\"keeps the hardware cursor marker visible while autocomplete is open\", () => {\n\t\tconst editor = new Editor(new TUI(makeTerminal()), theme);\n\t\teditor.focused = true;\n\t\teditor.setText(\"/se\");\n\n\t\t(editor as any).autocompleteState = \"regular\";\n\t\t(editor as any).autocompleteList = { render: () => [] };\n\n\t\tconst rendered = editor.render(40).join(\"\\n\");\n\n\t\tassert.ok(rendered.includes(CURSOR_MARKER));\n\t});\n});\n"]}
1
+ {"version":3,"file":"editor.test.js","sourceRoot":"","sources":["../../../src/components/__tests__/editor.test.ts"],"names":[],"mappings":"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","sourcesContent":["import assert from \"node:assert/strict\";\nimport { describe, it } from \"node:test\";\n\nimport { Editor, type EditorTheme } from \"../editor.js\";\nimport { CURSOR_MARKER, TUI } from \"../../tui.js\";\nimport type { Terminal } from \"../../terminal.js\";\n\nfunction makeTerminal(): Terminal {\n\treturn {\n\t\tisTTY: true,\n\t\tcolumns: 80,\n\t\trows: 24,\n\t\tkittyProtocolActive: false,\n\t\tstart() {},\n\t\tstop() {},\n\t\tdrainInput: async () => {},\n\t\twrite() {},\n\t\tmoveBy() {},\n\t\thideCursor() {},\n\t\tshowCursor() {},\n\t\tclearLine() {},\n\t\tclearFromCursor() {},\n\t\tclearScreen() {},\n\t\tsetTitle() {},\n\t};\n}\n\nconst theme: EditorTheme = {\n\tborderColor: (text) => text,\n\tselectList: {\n\t\tselectedPrefix: (text) => text,\n\t\tselectedText: (text) => text,\n\t\tdescription: (text) => text,\n\t\tscrollInfo: (text) => text,\n\t\tnoMatch: (text) => text,\n\t},\n};\n\ndescribe(\"Editor\", () => {\n\tit(\"clears bracketed paste state when focus is lost\", () => {\n\t\tconst editor = new Editor(new TUI(makeTerminal()), theme);\n\t\teditor.focused = true;\n\n\t\teditor.handleInput(\"\\x1b[200~partial\");\n\t\teditor.focused = false;\n\t\teditor.focused = true;\n\t\teditor.handleInput(\"hello\");\n\n\t\tassert.equal(editor.getText(), \"hello\");\n\t});\n\n\tit(\"keeps the hardware cursor marker visible while autocomplete is open\", () => {\n\t\tconst editor = new Editor(new TUI(makeTerminal()), theme);\n\t\teditor.focused = true;\n\t\teditor.setText(\"/se\");\n\n\t\t(editor as any).autocompleteState = \"regular\";\n\t\t(editor as any).autocompleteList = { render: () => [] };\n\n\t\tconst rendered = editor.render(40).join(\"\\n\");\n\n\t\tassert.ok(rendered.includes(CURSOR_MARKER));\n\t});\n\n\tit(\"maps kitty keypad digits to plain editor text\", () => {\n\t\tconst editor = new Editor(new TUI(makeTerminal()), theme);\n\t\teditor.focused = true;\n\n\t\teditor.handleInput(\"\\x1b[57404;129u\");\n\n\t\tassert.equal(editor.getText(), \"5\");\n\t});\n\n\tit(\"does not insert kitty keypad navigation private-use glyphs into the editor\", () => {\n\t\tconst editor = new Editor(new TUI(makeTerminal()), theme);\n\t\teditor.focused = true;\n\n\t\teditor.handleInput(\"\\x1b[57419u\");\n\n\t\tassert.equal(editor.getText(), \"\");\n\t});\n});\n"]}
@@ -34,5 +34,17 @@ describe("Input", () => {
34
34
  assert.ok(!line.includes("secret123"), "rendered line must not expose raw secret text");
35
35
  assert.ok(line.includes("*********"), "rendered line should include masked characters");
36
36
  });
37
+ it("maps kitty keypad digits to text instead of inserting private-use glyphs", () => {
38
+ const input = new Input();
39
+ input.focused = true;
40
+ input.handleInput("\x1b[57400;129u");
41
+ assert.equal(input.getValue(), "1");
42
+ });
43
+ it("ignores kitty keypad navigation keys in text input", () => {
44
+ const input = new Input();
45
+ input.focused = true;
46
+ input.handleInput("\x1b[57417u");
47
+ assert.equal(input.getValue(), "");
48
+ });
37
49
  });
38
50
  //# sourceMappingURL=input.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"input.test.js","sourceRoot":"","sources":["../../../src/components/__tests__/input.test.ts"],"names":[],"mappings":"AAAA,0CAA0C;AAC1C,4DAA4D;AAE5D,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEpC,QAAQ,CAAC,OAAO,EAAE,GAAG,EAAE;IACtB,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACrD,MAAM,KAAK,GAAG,IAAI,KAAK,EAAE,CAAC;QAC1B,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC;QAErB,yDAAyD;QACzD,KAAK,CAAC,WAAW,CAAC,kBAAkB,CAAC,CAAC;QAEtC,2BAA2B;QAC3B,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC;QAEtB,mDAAmD;QACnD,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC;QAErB,iEAAiE;QACjE,KAAK,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;QAC3B,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,OAAO,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAChD,MAAM,KAAK,GAAG,IAAI,KAAK,EAAE,CAAC;QAC1B,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QACnC,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC;QACrB,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QAClC,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC;QACtB,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wDAAwD,EAAE,GAAG,EAAE;QACjE,MAAM,KAAK,GAAG,IAAI,KAAK,EAAE,CAAC;QAC1B,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC;QACpB,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC;QACrB,KAAK,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC;QAE/B,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACvC,MAAM,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,+CAA+C,CAAC,CAAC;QACxF,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,gDAAgD,CAAC,CAAC;IACzF,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC","sourcesContent":["// pi-tui Input component regression tests\n// Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>\n\nimport { describe, it } from \"node:test\";\nimport assert from \"node:assert/strict\";\nimport { Input } from \"../input.js\";\n\ndescribe(\"Input\", () => {\n\tit(\"paste buffer is cleared when focus is lost\", () => {\n\t\tconst input = new Input();\n\t\tinput.focused = true;\n\n\t\t// Simulate starting a paste (bracket paste start marker)\n\t\tinput.handleInput(\"\\x1b[200~partial\");\n\n\t\t// Now lose focus mid-paste\n\t\tinput.focused = false;\n\n\t\t// Regain focus — should not have stale paste state\n\t\tinput.focused = true;\n\n\t\t// Typing normal text should work without paste buffer corruption\n\t\tinput.handleInput(\"hello\");\n\t\tassert.equal(input.getValue(), \"hello\");\n\t});\n\n\tit(\"focused getter/setter works correctly\", () => {\n\t\tconst input = new Input();\n\t\tassert.equal(input.focused, false);\n\t\tinput.focused = true;\n\t\tassert.equal(input.focused, true);\n\t\tinput.focused = false;\n\t\tassert.equal(input.focused, false);\n\t});\n\n\tit(\"secure mode obscures typed characters in render output\", () => {\n\t\tconst input = new Input();\n\t\tinput.secure = true;\n\t\tinput.focused = true;\n\t\tinput.handleInput(\"secret123\");\n\n\t\tconst line = input.render(40)[0] ?? \"\";\n\t\tassert.ok(!line.includes(\"secret123\"), \"rendered line must not expose raw secret text\");\n\t\tassert.ok(line.includes(\"*********\"), \"rendered line should include masked characters\");\n\t});\n});\n"]}
1
+ {"version":3,"file":"input.test.js","sourceRoot":"","sources":["../../../src/components/__tests__/input.test.ts"],"names":[],"mappings":"AAAA,0CAA0C;AAC1C,4DAA4D;AAE5D,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEpC,QAAQ,CAAC,OAAO,EAAE,GAAG,EAAE;IACtB,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACrD,MAAM,KAAK,GAAG,IAAI,KAAK,EAAE,CAAC;QAC1B,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC;QAErB,yDAAyD;QACzD,KAAK,CAAC,WAAW,CAAC,kBAAkB,CAAC,CAAC;QAEtC,2BAA2B;QAC3B,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC;QAEtB,mDAAmD;QACnD,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC;QAErB,iEAAiE;QACjE,KAAK,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;QAC3B,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,OAAO,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAChD,MAAM,KAAK,GAAG,IAAI,KAAK,EAAE,CAAC;QAC1B,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QACnC,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC;QACrB,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QAClC,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC;QACtB,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wDAAwD,EAAE,GAAG,EAAE;QACjE,MAAM,KAAK,GAAG,IAAI,KAAK,EAAE,CAAC;QAC1B,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC;QACpB,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC;QACrB,KAAK,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC;QAE/B,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACvC,MAAM,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,+CAA+C,CAAC,CAAC;QACxF,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,gDAAgD,CAAC,CAAC;IACzF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0EAA0E,EAAE,GAAG,EAAE;QACnF,MAAM,KAAK,GAAG,IAAI,KAAK,EAAE,CAAC;QAC1B,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC;QAErB,KAAK,CAAC,WAAW,CAAC,iBAAiB,CAAC,CAAC;QAErC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,GAAG,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC7D,MAAM,KAAK,GAAG,IAAI,KAAK,EAAE,CAAC;QAC1B,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC;QAErB,KAAK,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC;QAEjC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC","sourcesContent":["// pi-tui Input component regression tests\n// Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>\n\nimport { describe, it } from \"node:test\";\nimport assert from \"node:assert/strict\";\nimport { Input } from \"../input.js\";\n\ndescribe(\"Input\", () => {\n\tit(\"paste buffer is cleared when focus is lost\", () => {\n\t\tconst input = new Input();\n\t\tinput.focused = true;\n\n\t\t// Simulate starting a paste (bracket paste start marker)\n\t\tinput.handleInput(\"\\x1b[200~partial\");\n\n\t\t// Now lose focus mid-paste\n\t\tinput.focused = false;\n\n\t\t// Regain focus — should not have stale paste state\n\t\tinput.focused = true;\n\n\t\t// Typing normal text should work without paste buffer corruption\n\t\tinput.handleInput(\"hello\");\n\t\tassert.equal(input.getValue(), \"hello\");\n\t});\n\n\tit(\"focused getter/setter works correctly\", () => {\n\t\tconst input = new Input();\n\t\tassert.equal(input.focused, false);\n\t\tinput.focused = true;\n\t\tassert.equal(input.focused, true);\n\t\tinput.focused = false;\n\t\tassert.equal(input.focused, false);\n\t});\n\n\tit(\"secure mode obscures typed characters in render output\", () => {\n\t\tconst input = new Input();\n\t\tinput.secure = true;\n\t\tinput.focused = true;\n\t\tinput.handleInput(\"secret123\");\n\n\t\tconst line = input.render(40)[0] ?? \"\";\n\t\tassert.ok(!line.includes(\"secret123\"), \"rendered line must not expose raw secret text\");\n\t\tassert.ok(line.includes(\"*********\"), \"rendered line should include masked characters\");\n\t});\n\n\tit(\"maps kitty keypad digits to text instead of inserting private-use glyphs\", () => {\n\t\tconst input = new Input();\n\t\tinput.focused = true;\n\n\t\tinput.handleInput(\"\\x1b[57400;129u\");\n\n\t\tassert.equal(input.getValue(), \"1\");\n\t});\n\n\tit(\"ignores kitty keypad navigation keys in text input\", () => {\n\t\tconst input = new Input();\n\t\tinput.focused = true;\n\n\t\tinput.handleInput(\"\\x1b[57417u\");\n\n\t\tassert.equal(input.getValue(), \"\");\n\t});\n});\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"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"}
@@ -171,6 +171,26 @@ const CODEPOINTS = {
171
171
  backspace: 127,
172
172
  kpEnter: 57414, // Numpad Enter (Kitty protocol)
173
173
  };
174
+ const KITTY_PRIVATE_USE_RANGE = { start: 57344, end: 63743 };
175
+ const KITTY_KEYPAD_PRINTABLES = new Map([
176
+ [57399, "0"], // KP_0
177
+ [57400, "1"], // KP_1
178
+ [57401, "2"], // KP_2
179
+ [57402, "3"], // KP_3
180
+ [57403, "4"], // KP_4
181
+ [57404, "5"], // KP_5
182
+ [57405, "6"], // KP_6
183
+ [57406, "7"], // KP_7
184
+ [57407, "8"], // KP_8
185
+ [57408, "9"], // KP_9
186
+ [57409, "."], // KP_DECIMAL
187
+ [57410, "/"], // KP_DIVIDE
188
+ [57411, "*"], // KP_MULTIPLY
189
+ [57412, "-"], // KP_SUBTRACT
190
+ [57413, "+"], // KP_ADD
191
+ [57415, "="], // KP_EQUAL
192
+ [57416, ","], // KP_SEPARATOR
193
+ ]);
174
194
  const ARROW_CODEPOINTS = {
175
195
  up: -1,
176
196
  down: -2,
@@ -968,6 +988,13 @@ export function decodeKittyPrintable(data) {
968
988
  // Drop control characters or invalid codepoints.
969
989
  if (!Number.isFinite(effectiveCodepoint) || effectiveCodepoint < 32)
970
990
  return undefined;
991
+ const keypadPrintable = KITTY_KEYPAD_PRINTABLES.get(effectiveCodepoint);
992
+ if (keypadPrintable !== undefined)
993
+ return keypadPrintable;
994
+ if (effectiveCodepoint >= KITTY_PRIVATE_USE_RANGE.start &&
995
+ effectiveCodepoint <= KITTY_PRIVATE_USE_RANGE.end) {
996
+ return undefined;
997
+ }
971
998
  try {
972
999
  return String.fromCodePoint(effectiveCodepoint);
973
1000
  }