gsd-pi 2.78.0-dev.aeeb2ca00 → 2.78.1-dev.0fdacd524

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 (471) hide show
  1. package/README.md +8 -7
  2. package/dist/bundled-resource-path.d.ts +7 -0
  3. package/dist/bundled-resource-path.js +34 -2
  4. package/dist/claude-cli-check.js +77 -38
  5. package/dist/cli-policy.d.ts +13 -0
  6. package/dist/cli-policy.js +17 -0
  7. package/dist/cli.js +95 -55
  8. package/dist/headless-query.d.ts +22 -0
  9. package/dist/headless-query.js +43 -8
  10. package/dist/headless.d.ts +10 -0
  11. package/dist/headless.js +16 -1
  12. package/dist/loader.js +9 -13
  13. package/dist/onboarding.d.ts +10 -0
  14. package/dist/onboarding.js +2 -2
  15. package/dist/provider-migrations.d.ts +2 -2
  16. package/dist/provider-migrations.js +5 -2
  17. package/dist/resource-loader.d.ts +5 -2
  18. package/dist/resource-loader.js +30 -13
  19. package/dist/resources/.managed-resources-content-hash +1 -0
  20. package/dist/resources/extensions/claude-code-cli/readiness.js +90 -46
  21. package/dist/resources/extensions/google-search/index.js +2 -6
  22. package/dist/resources/extensions/gsd/auto/loop.js +23 -0
  23. package/dist/resources/extensions/gsd/auto/phases.js +5 -13
  24. package/dist/resources/extensions/gsd/auto/run-unit.js +26 -12
  25. package/dist/resources/extensions/gsd/auto/session.js +5 -6
  26. package/dist/resources/extensions/gsd/auto-dashboard.js +3 -2
  27. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +55 -21
  28. package/dist/resources/extensions/gsd/auto-dispatch.js +18 -6
  29. package/dist/resources/extensions/gsd/auto-prompts.js +69 -2
  30. package/dist/resources/extensions/gsd/auto-recovery.js +43 -4
  31. package/dist/resources/extensions/gsd/auto-runtime-state.js +31 -0
  32. package/dist/resources/extensions/gsd/auto-start.js +1 -1
  33. package/dist/resources/extensions/gsd/auto-tool-tracking.js +2 -2
  34. package/dist/resources/extensions/gsd/auto-worktree.js +60 -13
  35. package/dist/resources/extensions/gsd/auto.js +39 -14
  36. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +14 -2
  37. package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +7 -5
  38. package/dist/resources/extensions/gsd/bootstrap/query-tools.js +2 -2
  39. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +5 -4
  40. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +112 -31
  41. package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +11 -6
  42. package/dist/resources/extensions/gsd/bootstrap/subagent-input.js +22 -0
  43. package/dist/resources/extensions/gsd/bootstrap/system-context.js +45 -8
  44. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +121 -3
  45. package/dist/resources/extensions/gsd/commands/catalog.js +76 -5
  46. package/dist/resources/extensions/gsd/commands/handlers/core.js +23 -1
  47. package/dist/resources/extensions/gsd/commands/handlers/ops.js +8 -0
  48. package/dist/resources/extensions/gsd/commands-config.js +3 -2
  49. package/dist/resources/extensions/gsd/commands-extensions.js +46 -3
  50. package/dist/resources/extensions/gsd/commands-handlers.js +3 -2
  51. package/dist/resources/extensions/gsd/commands-mcp-status.js +3 -1
  52. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +10 -1
  53. package/dist/resources/extensions/gsd/commands-worktree.js +309 -0
  54. package/dist/resources/extensions/gsd/dashboard-overlay.js +1 -1
  55. package/dist/resources/extensions/gsd/docs/preferences-reference.md +10 -0
  56. package/dist/resources/extensions/gsd/doctor-providers.js +2 -1
  57. package/dist/resources/extensions/gsd/doctor-runtime-checks.js +39 -1
  58. package/dist/resources/extensions/gsd/error-classifier.js +1 -1
  59. package/dist/resources/extensions/gsd/forensics.js +10 -8
  60. package/dist/resources/extensions/gsd/git-service.js +12 -5
  61. package/dist/resources/extensions/gsd/gsd-db.js +11 -2
  62. package/dist/resources/extensions/gsd/guided-flow.js +25 -24
  63. package/dist/resources/extensions/gsd/home-dir.js +16 -0
  64. package/dist/resources/extensions/gsd/key-manager.js +2 -1
  65. package/dist/resources/extensions/gsd/memory-store.js +66 -31
  66. package/dist/resources/extensions/gsd/migrate/command.js +3 -2
  67. package/dist/resources/extensions/gsd/milestone-id-reservation.js +36 -0
  68. package/dist/resources/extensions/gsd/model-router.js +114 -9
  69. package/dist/resources/extensions/gsd/native-git-bridge.js +7 -1
  70. package/dist/resources/extensions/gsd/preferences-models.js +91 -15
  71. package/dist/resources/extensions/gsd/preferences-types.js +2 -0
  72. package/dist/resources/extensions/gsd/preferences-validation.js +32 -0
  73. package/dist/resources/extensions/gsd/preferences.js +5 -3
  74. package/dist/resources/extensions/gsd/prompt-loader.js +23 -12
  75. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +10 -0
  76. package/dist/resources/extensions/gsd/prompts/complete-slice.md +10 -0
  77. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
  78. package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +2 -0
  79. package/dist/resources/extensions/gsd/prompts/plan-slice.md +10 -0
  80. package/dist/resources/extensions/gsd/prompts/refine-slice.md +10 -0
  81. package/dist/resources/extensions/gsd/prompts/rewrite-docs.md +2 -0
  82. package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +9 -3
  83. package/dist/resources/extensions/gsd/state.js +42 -0
  84. package/dist/resources/extensions/gsd/templates/PREFERENCES.md +1 -0
  85. package/dist/resources/extensions/gsd/tools/memory-tools.js +18 -1
  86. package/dist/resources/extensions/gsd/unit-context-manifest.js +29 -4
  87. package/dist/resources/extensions/gsd/visualizer-overlay.js +1 -1
  88. package/dist/resources/extensions/gsd/watch/header-renderer.js +3 -1
  89. package/dist/resources/extensions/gsd/worktree-command.js +26 -46
  90. package/dist/resources/extensions/gsd/worktree-manager.js +20 -1
  91. package/dist/resources/extensions/gsd/worktree-resolver.js +4 -13
  92. package/dist/resources/extensions/gsd/worktree-root.js +124 -0
  93. package/dist/resources/extensions/gsd/worktree-session-state.js +33 -0
  94. package/dist/resources/extensions/gsd/worktree.js +4 -115
  95. package/dist/resources/extensions/mcp-client/index.js +6 -9
  96. package/dist/resources/extensions/ollama/index.js +15 -2
  97. package/dist/resources/extensions/ollama/model-capabilities.js +31 -0
  98. package/dist/resources/extensions/ollama/ollama-client.js +40 -4
  99. package/dist/resources/extensions/slash-commands/create-extension.js +36 -22
  100. package/dist/resources/extensions/subagent/index.js +324 -178
  101. package/dist/resources/skills/create-gsd-extension/SKILL.md +9 -5
  102. package/dist/resources/skills/create-gsd-extension/references/custom-commands.md +1 -1
  103. package/dist/resources/skills/create-gsd-extension/references/custom-rendering.md +5 -5
  104. package/dist/resources/skills/create-gsd-extension/references/custom-tools.md +4 -4
  105. package/dist/resources/skills/create-gsd-extension/references/custom-ui.md +6 -6
  106. package/dist/resources/skills/create-gsd-extension/references/events-reference.md +3 -3
  107. package/dist/resources/skills/create-gsd-extension/references/packaging-distribution.md +1 -1
  108. package/dist/resources/skills/create-gsd-extension/references/remote-execution-overrides.md +3 -3
  109. package/dist/resources/skills/create-gsd-extension/workflows/create-extension.md +32 -12
  110. package/dist/resources/skills/lint/SKILL.md +4 -0
  111. package/dist/resources/skills/review/SKILL.md +4 -0
  112. package/dist/resources/skills/test/SKILL.md +3 -0
  113. package/dist/rtk-shared.d.ts +3 -0
  114. package/dist/rtk-shared.js +17 -0
  115. package/dist/rtk.d.ts +2 -5
  116. package/dist/rtk.js +3 -20
  117. package/dist/runtime-checks.d.ts +27 -0
  118. package/dist/runtime-checks.js +38 -0
  119. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  120. package/dist/web/standalone/.next/BUILD_ID +1 -1
  121. package/dist/web/standalone/.next/app-path-routes-manifest.json +13 -13
  122. package/dist/web/standalone/.next/build-manifest.json +3 -3
  123. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  124. package/dist/web/standalone/.next/react-loadable-manifest.json +44 -4
  125. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  126. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  127. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  128. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  129. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  130. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  131. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  132. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  133. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  134. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  135. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  136. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  137. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  138. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  139. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  140. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  141. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  142. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  143. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  144. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  145. package/dist/web/standalone/.next/server/app/api/session/events/route.js +4 -2
  146. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  147. package/dist/web/standalone/.next/server/app/index.html +1 -1
  148. package/dist/web/standalone/.next/server/app/index.rsc +2 -2
  149. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  150. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +2 -2
  151. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  152. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  153. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  154. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  155. package/dist/web/standalone/.next/server/app-paths-manifest.json +13 -13
  156. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  157. package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
  158. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  159. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  160. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  161. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  162. package/dist/web/standalone/.next/server/webpack-runtime.js +1 -1
  163. package/dist/web/standalone/.next/static/chunks/2556.0527fea66e123b7f.js +1 -0
  164. package/dist/web/standalone/.next/static/chunks/2824.08296bc2f9654698.js +1 -0
  165. package/dist/web/standalone/.next/static/chunks/3026.3af53b279375f082.js +1 -0
  166. package/dist/web/standalone/.next/static/chunks/315.6f68ae79b67d25cf.js +1 -0
  167. package/dist/web/standalone/.next/static/chunks/3497.4bfc60a3b3dea717.js +1 -0
  168. package/dist/web/standalone/.next/static/chunks/5516.4a07c872b5c3a663.js +1 -0
  169. package/dist/web/standalone/.next/static/chunks/8336.31b019697882acfb.js +10 -0
  170. package/dist/web/standalone/.next/static/chunks/8845.c9702695e8c5a9c5.js +2 -0
  171. package/dist/web/standalone/.next/static/chunks/9058.01ef3a463bda88f1.js +20 -0
  172. package/dist/web/standalone/.next/static/chunks/9441.1081da1125d1764f.js +1 -0
  173. package/dist/web/standalone/.next/static/chunks/app/{page-5b113fd32bc2a1c3.js → page-9bf2e0c50fb2ca05.js} +1 -1
  174. package/dist/web/standalone/.next/static/chunks/webpack-f9f0dc45e4f3ac10.js +1 -0
  175. package/dist/web/standalone/package.json +2 -1
  176. package/dist/welcome-screen.js +27 -1
  177. package/dist/worktree-cli.d.ts +1 -0
  178. package/dist/worktree-cli.js +9 -3
  179. package/dist/worktree-status-banner.d.ts +1 -0
  180. package/dist/worktree-status-banner.js +132 -0
  181. package/package.json +1 -3
  182. package/packages/daemon/package.json +2 -2
  183. package/packages/mcp-server/dist/alias-telemetry.d.ts +8 -0
  184. package/packages/mcp-server/dist/alias-telemetry.d.ts.map +1 -0
  185. package/packages/mcp-server/dist/alias-telemetry.js +30 -0
  186. package/packages/mcp-server/dist/alias-telemetry.js.map +1 -0
  187. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  188. package/packages/mcp-server/dist/workflow-tools.js +74 -46
  189. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  190. package/packages/mcp-server/package.json +2 -2
  191. package/packages/mcp-server/src/alias-telemetry.test.ts +78 -0
  192. package/packages/mcp-server/src/alias-telemetry.ts +30 -0
  193. package/packages/mcp-server/src/workflow-tools.test.ts +78 -0
  194. package/packages/mcp-server/src/workflow-tools.ts +93 -58
  195. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  196. package/packages/native/package.json +1 -1
  197. package/packages/native/tsconfig.tsbuildinfo +1 -1
  198. package/packages/pi-agent-core/package.json +1 -1
  199. package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
  200. package/packages/pi-ai/dist/providers/anthropic-shared.cache-breakpoint.test.d.ts +2 -0
  201. package/packages/pi-ai/dist/providers/anthropic-shared.cache-breakpoint.test.d.ts.map +1 -0
  202. package/packages/pi-ai/dist/providers/anthropic-shared.cache-breakpoint.test.js +231 -0
  203. package/packages/pi-ai/dist/providers/anthropic-shared.cache-breakpoint.test.js.map +1 -0
  204. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
  205. package/packages/pi-ai/dist/providers/anthropic-shared.js +48 -19
  206. package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
  207. package/packages/pi-ai/dist/types.d.ts +13 -0
  208. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  209. package/packages/pi-ai/dist/types.js.map +1 -1
  210. package/packages/pi-ai/dist/utils/repair-tool-json.d.ts.map +1 -1
  211. package/packages/pi-ai/dist/utils/repair-tool-json.js +24 -3
  212. package/packages/pi-ai/dist/utils/repair-tool-json.js.map +1 -1
  213. package/packages/pi-ai/dist/utils/tests/repair-tool-json.test.js +26 -0
  214. package/packages/pi-ai/dist/utils/tests/repair-tool-json.test.js.map +1 -1
  215. package/packages/pi-ai/package.json +1 -1
  216. package/packages/pi-ai/src/providers/anthropic-shared.cache-breakpoint.test.ts +289 -0
  217. package/packages/pi-ai/src/providers/anthropic-shared.ts +52 -20
  218. package/packages/pi-ai/src/types.ts +13 -0
  219. package/packages/pi-ai/src/utils/repair-tool-json.ts +24 -3
  220. package/packages/pi-ai/src/utils/tests/repair-tool-json.test.ts +32 -0
  221. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  222. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  223. package/packages/pi-coding-agent/dist/core/agent-session.js +6 -0
  224. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  225. package/packages/pi-coding-agent/dist/core/messages.d.ts.map +1 -1
  226. package/packages/pi-coding-agent/dist/core/messages.js +4 -0
  227. package/packages/pi-coding-agent/dist/core/messages.js.map +1 -1
  228. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js +19 -2
  229. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js.map +1 -1
  230. package/packages/pi-coding-agent/dist/core/model-registry.d.ts +10 -0
  231. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  232. package/packages/pi-coding-agent/dist/core/model-registry.js +18 -0
  233. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  234. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts +13 -0
  235. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  236. package/packages/pi-coding-agent/dist/core/system-prompt.js +20 -16
  237. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  238. package/packages/pi-coding-agent/dist/core/token-telemetry.d.ts +37 -0
  239. package/packages/pi-coding-agent/dist/core/token-telemetry.d.ts.map +1 -0
  240. package/packages/pi-coding-agent/dist/core/token-telemetry.js +49 -0
  241. package/packages/pi-coding-agent/dist/core/token-telemetry.js.map +1 -0
  242. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.d.ts +2 -0
  243. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.d.ts.map +1 -0
  244. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.js +133 -0
  245. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.js.map +1 -0
  246. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +1 -1
  247. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  248. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js +14 -1
  249. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js.map +1 -1
  250. package/packages/pi-coding-agent/dist/tests/system-prompt-cache-stability.test.d.ts +2 -0
  251. package/packages/pi-coding-agent/dist/tests/system-prompt-cache-stability.test.d.ts.map +1 -0
  252. package/packages/pi-coding-agent/dist/tests/system-prompt-cache-stability.test.js +78 -0
  253. package/packages/pi-coding-agent/dist/tests/system-prompt-cache-stability.test.js.map +1 -0
  254. package/packages/pi-coding-agent/dist/tests/token-telemetry.test.d.ts +2 -0
  255. package/packages/pi-coding-agent/dist/tests/token-telemetry.test.d.ts.map +1 -0
  256. package/packages/pi-coding-agent/dist/tests/token-telemetry.test.js +181 -0
  257. package/packages/pi-coding-agent/dist/tests/token-telemetry.test.js.map +1 -0
  258. package/packages/pi-coding-agent/package.json +1 -1
  259. package/packages/pi-coding-agent/src/core/agent-session.ts +7 -0
  260. package/packages/pi-coding-agent/src/core/messages.ts +4 -0
  261. package/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts +32 -2
  262. package/packages/pi-coding-agent/src/core/model-registry.ts +21 -0
  263. package/packages/pi-coding-agent/src/core/system-prompt.ts +33 -15
  264. package/packages/pi-coding-agent/src/core/token-telemetry.ts +77 -0
  265. package/packages/pi-coding-agent/src/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.ts +212 -0
  266. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.test.ts +17 -1
  267. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +1 -1
  268. package/packages/pi-coding-agent/src/tests/system-prompt-cache-stability.test.ts +102 -0
  269. package/packages/pi-coding-agent/src/tests/token-telemetry.test.ts +200 -0
  270. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  271. package/packages/pi-tui/dist/__tests__/autocomplete.test.js +17 -3
  272. package/packages/pi-tui/dist/__tests__/autocomplete.test.js.map +1 -1
  273. package/packages/pi-tui/dist/components/__tests__/leak-fixes-runtime.test.d.ts +2 -0
  274. package/packages/pi-tui/dist/components/__tests__/leak-fixes-runtime.test.d.ts.map +1 -0
  275. package/packages/pi-tui/dist/components/__tests__/leak-fixes-runtime.test.js +161 -0
  276. package/packages/pi-tui/dist/components/__tests__/leak-fixes-runtime.test.js.map +1 -0
  277. package/packages/pi-tui/package.json +1 -1
  278. package/packages/pi-tui/src/__tests__/autocomplete.test.ts +20 -3
  279. package/packages/pi-tui/src/components/__tests__/leak-fixes-runtime.test.ts +219 -0
  280. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  281. package/packages/rpc-client/package.json +1 -1
  282. package/pkg/package.json +1 -1
  283. package/src/resources/extensions/claude-code-cli/readiness.ts +92 -47
  284. package/src/resources/extensions/google-search/index.ts +2 -9
  285. package/src/resources/extensions/gsd/auto/loop.ts +24 -2
  286. package/src/resources/extensions/gsd/auto/phases.ts +6 -14
  287. package/src/resources/extensions/gsd/auto/run-unit.ts +26 -12
  288. package/src/resources/extensions/gsd/auto/session.ts +5 -6
  289. package/src/resources/extensions/gsd/auto/types.ts +1 -0
  290. package/src/resources/extensions/gsd/auto-dashboard.ts +3 -2
  291. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +60 -24
  292. package/src/resources/extensions/gsd/auto-dispatch.ts +18 -6
  293. package/src/resources/extensions/gsd/auto-prompts.ts +66 -2
  294. package/src/resources/extensions/gsd/auto-recovery.ts +46 -8
  295. package/src/resources/extensions/gsd/auto-runtime-state.ts +51 -0
  296. package/src/resources/extensions/gsd/auto-start.ts +1 -1
  297. package/src/resources/extensions/gsd/auto-tool-tracking.ts +2 -4
  298. package/src/resources/extensions/gsd/auto-worktree.ts +82 -12
  299. package/src/resources/extensions/gsd/auto.ts +37 -10
  300. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +15 -13
  301. package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +8 -7
  302. package/src/resources/extensions/gsd/bootstrap/query-tools.ts +2 -2
  303. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +10 -9
  304. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +121 -31
  305. package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +12 -6
  306. package/src/resources/extensions/gsd/bootstrap/subagent-input.ts +20 -0
  307. package/src/resources/extensions/gsd/bootstrap/system-context.ts +50 -8
  308. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +141 -11
  309. package/src/resources/extensions/gsd/commands/catalog.ts +82 -5
  310. package/src/resources/extensions/gsd/commands/handlers/core.ts +23 -1
  311. package/src/resources/extensions/gsd/commands/handlers/ops.ts +10 -0
  312. package/src/resources/extensions/gsd/commands-config.ts +3 -2
  313. package/src/resources/extensions/gsd/commands-extensions.ts +43 -3
  314. package/src/resources/extensions/gsd/commands-handlers.ts +3 -2
  315. package/src/resources/extensions/gsd/commands-mcp-status.ts +3 -1
  316. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +15 -1
  317. package/src/resources/extensions/gsd/commands-worktree.ts +383 -0
  318. package/src/resources/extensions/gsd/dashboard-overlay.ts +1 -1
  319. package/src/resources/extensions/gsd/docs/preferences-reference.md +10 -0
  320. package/src/resources/extensions/gsd/doctor-providers.ts +2 -1
  321. package/src/resources/extensions/gsd/doctor-runtime-checks.ts +39 -1
  322. package/src/resources/extensions/gsd/doctor-types.ts +3 -1
  323. package/src/resources/extensions/gsd/error-classifier.ts +1 -1
  324. package/src/resources/extensions/gsd/forensics.ts +12 -7
  325. package/src/resources/extensions/gsd/git-service.ts +13 -5
  326. package/src/resources/extensions/gsd/gsd-db.ts +12 -2
  327. package/src/resources/extensions/gsd/guided-flow.ts +27 -26
  328. package/src/resources/extensions/gsd/home-dir.ts +19 -0
  329. package/src/resources/extensions/gsd/journal.ts +4 -1
  330. package/src/resources/extensions/gsd/key-manager.ts +2 -1
  331. package/src/resources/extensions/gsd/memory-store.ts +81 -28
  332. package/src/resources/extensions/gsd/migrate/command.ts +3 -2
  333. package/src/resources/extensions/gsd/milestone-id-reservation.ts +47 -0
  334. package/src/resources/extensions/gsd/model-router.ts +172 -9
  335. package/src/resources/extensions/gsd/native-git-bridge.ts +7 -1
  336. package/src/resources/extensions/gsd/preferences-models.ts +101 -15
  337. package/src/resources/extensions/gsd/preferences-types.ts +6 -0
  338. package/src/resources/extensions/gsd/preferences-validation.ts +35 -0
  339. package/src/resources/extensions/gsd/preferences.ts +16 -2
  340. package/src/resources/extensions/gsd/prompt-loader.ts +26 -12
  341. package/src/resources/extensions/gsd/prompts/complete-milestone.md +10 -0
  342. package/src/resources/extensions/gsd/prompts/complete-slice.md +10 -0
  343. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
  344. package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +2 -0
  345. package/src/resources/extensions/gsd/prompts/plan-slice.md +10 -0
  346. package/src/resources/extensions/gsd/prompts/refine-slice.md +10 -0
  347. package/src/resources/extensions/gsd/prompts/rewrite-docs.md +2 -0
  348. package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +9 -3
  349. package/src/resources/extensions/gsd/state.ts +42 -0
  350. package/src/resources/extensions/gsd/templates/PREFERENCES.md +1 -0
  351. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +179 -1
  352. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +58 -0
  353. package/src/resources/extensions/gsd/tests/auto-session-encapsulation.test.ts +24 -5
  354. package/src/resources/extensions/gsd/tests/auto-supervisor.test.mjs +21 -4
  355. package/src/resources/extensions/gsd/tests/bootstrap-derive-state-db-open.test.ts +1 -1
  356. package/src/resources/extensions/gsd/tests/budget-prediction.test.ts +138 -211
  357. package/src/resources/extensions/gsd/tests/bundled-skill-triggers.test.ts +50 -27
  358. package/src/resources/extensions/gsd/tests/commands-extensions-version-compare.test.ts +58 -0
  359. package/src/resources/extensions/gsd/tests/commands-worktree-clean.test.ts +48 -0
  360. package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +142 -59
  361. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +7 -4
  362. package/src/resources/extensions/gsd/tests/completed-at-reconcile.test.ts +89 -32
  363. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +41 -23
  364. package/src/resources/extensions/gsd/tests/db-path-worktree-symlink.test.ts +3 -43
  365. package/src/resources/extensions/gsd/tests/debug-logger.test.ts +5 -3
  366. package/src/resources/extensions/gsd/tests/deferred-milestone-dir-4996.test.ts +116 -0
  367. package/src/resources/extensions/gsd/tests/discuss-empty-db-fallback.test.ts +22 -87
  368. package/src/resources/extensions/gsd/tests/discuss-queued-milestones.test.ts +7 -118
  369. package/src/resources/extensions/gsd/tests/discuss-tool-scope-leak.test.ts +18 -60
  370. package/src/resources/extensions/gsd/tests/doctor-orphan-milestone-4996.test.ts +100 -0
  371. package/src/resources/extensions/gsd/tests/double-merge-guard.test.ts +14 -76
  372. package/src/resources/extensions/gsd/tests/ensure-preconditions-guard-4996.test.ts +93 -0
  373. package/src/resources/extensions/gsd/tests/false-degraded-mode-warning.test.ts +22 -83
  374. package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +1 -63
  375. package/src/resources/extensions/gsd/tests/find-missing-summaries-closed-runtime.test.ts +47 -0
  376. package/src/resources/extensions/gsd/tests/forensics-stuck-loops.test.ts +26 -1
  377. package/src/resources/extensions/gsd/tests/gitignore-bg-shell-runtime.test.ts +63 -0
  378. package/src/resources/extensions/gsd/tests/google-search-stub.test.ts +25 -65
  379. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +30 -0
  380. package/src/resources/extensions/gsd/tests/gsd-no-project-error-runtime.test.ts +81 -0
  381. package/src/resources/extensions/gsd/tests/headless-answers.test.ts +14 -4
  382. package/src/resources/extensions/gsd/tests/health-widget.test.ts +22 -12
  383. package/src/resources/extensions/gsd/tests/help-menu-coverage.test.ts +57 -0
  384. package/src/resources/extensions/gsd/tests/home-dir.test.ts +52 -0
  385. package/src/resources/extensions/gsd/tests/import-done-milestones-runtime.test.ts +145 -0
  386. package/src/resources/extensions/gsd/tests/init-prefs-routing.test.ts +64 -1
  387. package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +72 -1
  388. package/src/resources/extensions/gsd/tests/integration/token-savings.test.ts +0 -23
  389. package/src/resources/extensions/gsd/tests/memory-store.test.ts +128 -0
  390. package/src/resources/extensions/gsd/tests/memory-tools.test.ts +33 -1
  391. package/src/resources/extensions/gsd/tests/merge-self-branch-guard.test.ts +124 -0
  392. package/src/resources/extensions/gsd/tests/milestone-id-gap-reuse-4996.test.ts +152 -0
  393. package/src/resources/extensions/gsd/tests/milestone-report-path.test.ts +18 -1
  394. package/src/resources/extensions/gsd/tests/model-router.test.ts +169 -8
  395. package/src/resources/extensions/gsd/tests/native-git-infra-errors.test.ts +50 -0
  396. package/src/resources/extensions/gsd/tests/orphaned-worktree-audit.test.ts +8 -0
  397. package/src/resources/extensions/gsd/tests/parallel-crash-recovery.test.ts +32 -43
  398. package/src/resources/extensions/gsd/tests/phases-merge-error-stops-auto.test.ts +4 -10
  399. package/src/resources/extensions/gsd/tests/preferences.test.ts +127 -0
  400. package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +16 -0
  401. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +7 -0
  402. package/src/resources/extensions/gsd/tests/quick-turn-end-cleanup.test.ts +6 -6
  403. package/src/resources/extensions/gsd/tests/register-hooks-compaction-checkpoint.test.ts +93 -0
  404. package/src/resources/extensions/gsd/tests/safety-harness-false-positives.test.ts +34 -0
  405. package/src/resources/extensions/gsd/tests/session-start-footer.test.ts +168 -19
  406. package/src/resources/extensions/gsd/tests/slice-parallel-orchestrator.test.ts +7 -1
  407. package/src/resources/extensions/gsd/tests/smart-entry-complete.test.ts +23 -1
  408. package/src/resources/extensions/gsd/tests/steer-worktree-path.test.ts +17 -1
  409. package/src/resources/extensions/gsd/tests/system-context-message-routing.test.ts +101 -0
  410. package/src/resources/extensions/gsd/tests/token-profile.test.ts +51 -4
  411. package/src/resources/extensions/gsd/tests/turn-epoch.test.ts +7 -16
  412. package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +38 -3
  413. package/src/resources/extensions/gsd/tests/unstructured-continue-context-injection.test.ts +5 -7
  414. package/src/resources/extensions/gsd/tests/uok-gitops-turn-action.test.ts +15 -1
  415. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +6 -6
  416. package/src/resources/extensions/gsd/tests/worktree-path-injection.test.ts +235 -0
  417. package/src/resources/extensions/gsd/tests/worktree-symlink-removal.test.ts +34 -33
  418. package/src/resources/extensions/gsd/tests/worktree.test.ts +8 -0
  419. package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +131 -1
  420. package/src/resources/extensions/gsd/tools/memory-tools.ts +17 -1
  421. package/src/resources/extensions/gsd/unit-context-manifest.ts +44 -12
  422. package/src/resources/extensions/gsd/visualizer-overlay.ts +1 -1
  423. package/src/resources/extensions/gsd/watch/header-renderer.ts +3 -1
  424. package/src/resources/extensions/gsd/workflow-logger.ts +1 -0
  425. package/src/resources/extensions/gsd/worktree-command.ts +31 -44
  426. package/src/resources/extensions/gsd/worktree-manager.ts +40 -1
  427. package/src/resources/extensions/gsd/worktree-resolver.ts +4 -14
  428. package/src/resources/extensions/gsd/worktree-root.ts +144 -0
  429. package/src/resources/extensions/gsd/worktree-session-state.ts +35 -0
  430. package/src/resources/extensions/gsd/worktree.ts +8 -119
  431. package/src/resources/extensions/mcp-client/index.ts +6 -10
  432. package/src/resources/extensions/mcp-client/tests/global-config.test.ts +91 -0
  433. package/src/resources/extensions/ollama/index.ts +16 -2
  434. package/src/resources/extensions/ollama/model-capabilities.ts +34 -0
  435. package/src/resources/extensions/ollama/ollama-client.ts +41 -4
  436. package/src/resources/extensions/ollama/tests/model-capabilities.test.ts +96 -0
  437. package/src/resources/extensions/ollama/tests/ollama-client-timeout-env.test.ts +147 -0
  438. package/src/resources/extensions/slash-commands/create-extension.ts +38 -24
  439. package/src/resources/extensions/subagent/index.ts +165 -7
  440. package/src/resources/skills/create-gsd-extension/SKILL.md +9 -5
  441. package/src/resources/skills/create-gsd-extension/references/custom-commands.md +1 -1
  442. package/src/resources/skills/create-gsd-extension/references/custom-rendering.md +5 -5
  443. package/src/resources/skills/create-gsd-extension/references/custom-tools.md +4 -4
  444. package/src/resources/skills/create-gsd-extension/references/custom-ui.md +6 -6
  445. package/src/resources/skills/create-gsd-extension/references/events-reference.md +3 -3
  446. package/src/resources/skills/create-gsd-extension/references/packaging-distribution.md +1 -1
  447. package/src/resources/skills/create-gsd-extension/references/remote-execution-overrides.md +3 -3
  448. package/src/resources/skills/create-gsd-extension/templates/extension-skeleton.ts +2 -2
  449. package/src/resources/skills/create-gsd-extension/templates/stateful-tool-skeleton.ts +3 -3
  450. package/src/resources/skills/create-gsd-extension/templates/templates.test.ts +58 -0
  451. package/src/resources/skills/create-gsd-extension/workflows/create-extension.md +32 -12
  452. package/src/resources/skills/lint/SKILL.md +4 -0
  453. package/src/resources/skills/review/SKILL.md +4 -0
  454. package/src/resources/skills/test/SKILL.md +3 -0
  455. package/dist/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +0 -601
  456. package/dist/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +0 -651
  457. package/dist/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +0 -91
  458. package/dist/resources/extensions/gsd/tests/auto-supervisor.test.mjs +0 -53
  459. package/dist/resources/extensions/gsd/tests/dist-redirect.mjs +0 -112
  460. package/dist/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +0 -23
  461. package/dist/resources/extensions/gsd/tests/resolve-ts.mjs +0 -5
  462. package/dist/resources/skills/github-workflows/references/gh/tests/__init__.py +0 -0
  463. package/dist/resources/skills/github-workflows/references/gh/tests/test_github_project_setup.py +0 -608
  464. package/dist/web/standalone/.next/static/chunks/2826.e9f5195e91f9cad2.js +0 -11
  465. package/dist/web/standalone/.next/static/chunks/3621.fc7480022c972438.js +0 -20
  466. package/dist/web/standalone/.next/static/chunks/webpack-2e68521d7c82f7c2.js +0 -1
  467. package/src/resources/extensions/gsd/tests/copy-planning-artifacts-samepath.test.ts +0 -22
  468. package/src/resources/extensions/gsd/tests/discuss-slice-structured-questions.test.ts +0 -47
  469. package/src/resources/extensions/gsd/tests/empty-content-abort-loop.test.ts +0 -75
  470. /package/dist/web/standalone/.next/static/{cAJH99yNS1UPbeSEiNRrV → 4iu6IYeYfxOq8OidlDqp6}/_buildManifest.js +0 -0
  471. /package/dist/web/standalone/.next/static/{cAJH99yNS1UPbeSEiNRrV → 4iu6IYeYfxOq8OidlDqp6}/_ssgManifest.js +0 -0
@@ -0,0 +1,181 @@
1
+ // @gsd/pi-coding-agent + token-telemetry.test — coverage for #5023.
2
+ // Verifies the env-gated emitter:
3
+ // - is silent by default (no behavior change for existing users)
4
+ // - emits a single valid JSON line when PI_TOKEN_TELEMETRY=1
5
+ // - record shape captures the cache breakdown the providers already extract
6
+ // - cacheHitRatio math is correct, including the no-input edge case
7
+ import { describe, test, beforeEach, afterEach } from "node:test";
8
+ import assert from "node:assert/strict";
9
+ import { buildTokenTelemetryRecord, emitTokenTelemetry } from "../core/token-telemetry.js";
10
+ function makeAssistantMessage(overrides = {}) {
11
+ return {
12
+ role: "assistant",
13
+ content: [],
14
+ api: "anthropic-messages",
15
+ provider: "anthropic",
16
+ model: "claude-sonnet-4-6",
17
+ usage: {
18
+ input: 100,
19
+ output: 50,
20
+ cacheRead: 0,
21
+ cacheWrite: 0,
22
+ totalTokens: 150,
23
+ cost: { input: 0.3, output: 0.75, cacheRead: 0, cacheWrite: 0, total: 1.05 },
24
+ },
25
+ stopReason: "stop",
26
+ timestamp: 1700000000000,
27
+ ...overrides,
28
+ };
29
+ }
30
+ // ─── buildTokenTelemetryRecord ─────────────────────────────────────────────
31
+ describe("buildTokenTelemetryRecord", () => {
32
+ test("captures all fields from a typical message", () => {
33
+ const msg = makeAssistantMessage();
34
+ const record = buildTokenTelemetryRecord(msg);
35
+ assert.deepEqual(record, {
36
+ ts: 1700000000000,
37
+ model: "claude-sonnet-4-6",
38
+ stopReason: "stop",
39
+ input: 100,
40
+ output: 50,
41
+ cacheRead: 0,
42
+ cacheWrite: 0,
43
+ costTotal: 1.05,
44
+ cacheHitRatio: 0,
45
+ });
46
+ });
47
+ test("cacheHitRatio = read / (read + input) when both present", () => {
48
+ const msg = makeAssistantMessage({
49
+ usage: {
50
+ input: 200,
51
+ output: 50,
52
+ cacheRead: 800,
53
+ cacheWrite: 0,
54
+ totalTokens: 1050,
55
+ cost: { input: 0.6, output: 0.75, cacheRead: 0.24, cacheWrite: 0, total: 1.59 },
56
+ },
57
+ });
58
+ const record = buildTokenTelemetryRecord(msg);
59
+ assert.equal(record.cacheRead, 800);
60
+ assert.equal(record.input, 200);
61
+ assert.equal(record.cacheHitRatio, 0.8);
62
+ });
63
+ test("cacheHitRatio = 0 when both read and input are 0 (no division-by-zero)", () => {
64
+ const msg = makeAssistantMessage({
65
+ usage: {
66
+ input: 0,
67
+ output: 50,
68
+ cacheRead: 0,
69
+ cacheWrite: 0,
70
+ totalTokens: 50,
71
+ cost: { input: 0, output: 0.75, cacheRead: 0, cacheWrite: 0, total: 0.75 },
72
+ },
73
+ });
74
+ const record = buildTokenTelemetryRecord(msg);
75
+ assert.equal(record.cacheHitRatio, 0);
76
+ });
77
+ test("cacheHitRatio = 1 when only cacheRead present (full hit)", () => {
78
+ const msg = makeAssistantMessage({
79
+ usage: {
80
+ input: 0,
81
+ output: 50,
82
+ cacheRead: 5000,
83
+ cacheWrite: 0,
84
+ totalTokens: 5050,
85
+ cost: { input: 0, output: 0.75, cacheRead: 1.5, cacheWrite: 0, total: 2.25 },
86
+ },
87
+ });
88
+ const record = buildTokenTelemetryRecord(msg);
89
+ assert.equal(record.cacheHitRatio, 1);
90
+ });
91
+ test("cacheWrite is captured (the cache-miss-with-cache-control case from #5019)", () => {
92
+ const msg = makeAssistantMessage({
93
+ usage: {
94
+ input: 50,
95
+ output: 100,
96
+ cacheRead: 0,
97
+ cacheWrite: 5000,
98
+ totalTokens: 5150,
99
+ cost: { input: 0.15, output: 1.5, cacheRead: 0, cacheWrite: 18.75, total: 20.4 },
100
+ },
101
+ });
102
+ const record = buildTokenTelemetryRecord(msg);
103
+ assert.equal(record.cacheWrite, 5000);
104
+ assert.equal(record.cacheHitRatio, 0, "no read = ratio 0 even when write is large");
105
+ });
106
+ test("error stopReason is captured verbatim", () => {
107
+ const msg = makeAssistantMessage({ stopReason: "error", errorMessage: "rate_limit" });
108
+ assert.equal(buildTokenTelemetryRecord(msg).stopReason, "error");
109
+ });
110
+ });
111
+ // ─── emitTokenTelemetry ────────────────────────────────────────────────────
112
+ describe("emitTokenTelemetry", () => {
113
+ let captured;
114
+ let originalWrite;
115
+ let originalEnv;
116
+ beforeEach(() => {
117
+ captured = [];
118
+ originalWrite = process.stderr.write.bind(process.stderr);
119
+ // Replace stderr.write with a capture; preserve the same return contract.
120
+ process.stderr.write = ((chunk) => {
121
+ captured.push(typeof chunk === "string" ? chunk : Buffer.from(chunk).toString());
122
+ return true;
123
+ });
124
+ originalEnv = process.env.PI_TOKEN_TELEMETRY;
125
+ });
126
+ afterEach(() => {
127
+ process.stderr.write = originalWrite;
128
+ if (originalEnv === undefined) {
129
+ delete process.env.PI_TOKEN_TELEMETRY;
130
+ }
131
+ else {
132
+ process.env.PI_TOKEN_TELEMETRY = originalEnv;
133
+ }
134
+ });
135
+ test("silent by default (env var unset)", () => {
136
+ delete process.env.PI_TOKEN_TELEMETRY;
137
+ emitTokenTelemetry(makeAssistantMessage());
138
+ assert.equal(captured.length, 0);
139
+ });
140
+ test("silent when env var has any non-'1' value", () => {
141
+ process.env.PI_TOKEN_TELEMETRY = "true"; // not literally "1"
142
+ emitTokenTelemetry(makeAssistantMessage());
143
+ assert.equal(captured.length, 0, "only literal '1' should enable telemetry");
144
+ });
145
+ test("emits a single JSON line when PI_TOKEN_TELEMETRY=1", () => {
146
+ process.env.PI_TOKEN_TELEMETRY = "1";
147
+ emitTokenTelemetry(makeAssistantMessage());
148
+ assert.equal(captured.length, 1);
149
+ assert.ok(captured[0].endsWith("\n"), "line must terminate with newline");
150
+ const parsed = JSON.parse(captured[0].trimEnd());
151
+ assert.equal(parsed.model, "claude-sonnet-4-6");
152
+ assert.equal(parsed.input, 100);
153
+ assert.equal(parsed.output, 50);
154
+ });
155
+ test("emitted JSON has the documented shape", () => {
156
+ process.env.PI_TOKEN_TELEMETRY = "1";
157
+ emitTokenTelemetry(makeAssistantMessage());
158
+ const parsed = JSON.parse(captured[0].trimEnd());
159
+ const keys = Object.keys(parsed).sort();
160
+ assert.deepEqual(keys, [
161
+ "cacheHitRatio",
162
+ "cacheRead",
163
+ "cacheWrite",
164
+ "costTotal",
165
+ "input",
166
+ "model",
167
+ "output",
168
+ "stopReason",
169
+ "ts",
170
+ ]);
171
+ });
172
+ test("never throws — telemetry must not break the agent loop", () => {
173
+ process.env.PI_TOKEN_TELEMETRY = "1";
174
+ // Force a write failure to exercise the swallow path.
175
+ process.stderr.write = (() => {
176
+ throw new Error("simulated stderr failure");
177
+ });
178
+ assert.doesNotThrow(() => emitTokenTelemetry(makeAssistantMessage()));
179
+ });
180
+ });
181
+ //# sourceMappingURL=token-telemetry.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-telemetry.test.js","sourceRoot":"","sources":["../../src/tests/token-telemetry.test.ts"],"names":[],"mappings":"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","sourcesContent":["// @gsd/pi-coding-agent + token-telemetry.test — coverage for #5023.\n// Verifies the env-gated emitter:\n// - is silent by default (no behavior change for existing users)\n// - emits a single valid JSON line when PI_TOKEN_TELEMETRY=1\n// - record shape captures the cache breakdown the providers already extract\n// - cacheHitRatio math is correct, including the no-input edge case\n\nimport { describe, test, beforeEach, afterEach } from \"node:test\";\nimport assert from \"node:assert/strict\";\n\nimport type { AssistantMessage } from \"@gsd/pi-ai\";\n\nimport { buildTokenTelemetryRecord, emitTokenTelemetry } from \"../core/token-telemetry.js\";\n\nfunction makeAssistantMessage(overrides: Partial<AssistantMessage> = {}): AssistantMessage {\n\treturn {\n\t\trole: \"assistant\",\n\t\tcontent: [],\n\t\tapi: \"anthropic-messages\",\n\t\tprovider: \"anthropic\",\n\t\tmodel: \"claude-sonnet-4-6\",\n\t\tusage: {\n\t\t\tinput: 100,\n\t\t\toutput: 50,\n\t\t\tcacheRead: 0,\n\t\t\tcacheWrite: 0,\n\t\t\ttotalTokens: 150,\n\t\t\tcost: { input: 0.3, output: 0.75, cacheRead: 0, cacheWrite: 0, total: 1.05 },\n\t\t},\n\t\tstopReason: \"stop\",\n\t\ttimestamp: 1700000000000,\n\t\t...overrides,\n\t};\n}\n\n// ─── buildTokenTelemetryRecord ─────────────────────────────────────────────\n\ndescribe(\"buildTokenTelemetryRecord\", () => {\n\ttest(\"captures all fields from a typical message\", () => {\n\t\tconst msg = makeAssistantMessage();\n\t\tconst record = buildTokenTelemetryRecord(msg);\n\t\tassert.deepEqual(record, {\n\t\t\tts: 1700000000000,\n\t\t\tmodel: \"claude-sonnet-4-6\",\n\t\t\tstopReason: \"stop\",\n\t\t\tinput: 100,\n\t\t\toutput: 50,\n\t\t\tcacheRead: 0,\n\t\t\tcacheWrite: 0,\n\t\t\tcostTotal: 1.05,\n\t\t\tcacheHitRatio: 0,\n\t\t});\n\t});\n\n\ttest(\"cacheHitRatio = read / (read + input) when both present\", () => {\n\t\tconst msg = makeAssistantMessage({\n\t\t\tusage: {\n\t\t\t\tinput: 200,\n\t\t\t\toutput: 50,\n\t\t\t\tcacheRead: 800,\n\t\t\t\tcacheWrite: 0,\n\t\t\t\ttotalTokens: 1050,\n\t\t\t\tcost: { input: 0.6, output: 0.75, cacheRead: 0.24, cacheWrite: 0, total: 1.59 },\n\t\t\t},\n\t\t});\n\t\tconst record = buildTokenTelemetryRecord(msg);\n\t\tassert.equal(record.cacheRead, 800);\n\t\tassert.equal(record.input, 200);\n\t\tassert.equal(record.cacheHitRatio, 0.8);\n\t});\n\n\ttest(\"cacheHitRatio = 0 when both read and input are 0 (no division-by-zero)\", () => {\n\t\tconst msg = makeAssistantMessage({\n\t\t\tusage: {\n\t\t\t\tinput: 0,\n\t\t\t\toutput: 50,\n\t\t\t\tcacheRead: 0,\n\t\t\t\tcacheWrite: 0,\n\t\t\t\ttotalTokens: 50,\n\t\t\t\tcost: { input: 0, output: 0.75, cacheRead: 0, cacheWrite: 0, total: 0.75 },\n\t\t\t},\n\t\t});\n\t\tconst record = buildTokenTelemetryRecord(msg);\n\t\tassert.equal(record.cacheHitRatio, 0);\n\t});\n\n\ttest(\"cacheHitRatio = 1 when only cacheRead present (full hit)\", () => {\n\t\tconst msg = makeAssistantMessage({\n\t\t\tusage: {\n\t\t\t\tinput: 0,\n\t\t\t\toutput: 50,\n\t\t\t\tcacheRead: 5000,\n\t\t\t\tcacheWrite: 0,\n\t\t\t\ttotalTokens: 5050,\n\t\t\t\tcost: { input: 0, output: 0.75, cacheRead: 1.5, cacheWrite: 0, total: 2.25 },\n\t\t\t},\n\t\t});\n\t\tconst record = buildTokenTelemetryRecord(msg);\n\t\tassert.equal(record.cacheHitRatio, 1);\n\t});\n\n\ttest(\"cacheWrite is captured (the cache-miss-with-cache-control case from #5019)\", () => {\n\t\tconst msg = makeAssistantMessage({\n\t\t\tusage: {\n\t\t\t\tinput: 50,\n\t\t\t\toutput: 100,\n\t\t\t\tcacheRead: 0,\n\t\t\t\tcacheWrite: 5000,\n\t\t\t\ttotalTokens: 5150,\n\t\t\t\tcost: { input: 0.15, output: 1.5, cacheRead: 0, cacheWrite: 18.75, total: 20.4 },\n\t\t\t},\n\t\t});\n\t\tconst record = buildTokenTelemetryRecord(msg);\n\t\tassert.equal(record.cacheWrite, 5000);\n\t\tassert.equal(record.cacheHitRatio, 0, \"no read = ratio 0 even when write is large\");\n\t});\n\n\ttest(\"error stopReason is captured verbatim\", () => {\n\t\tconst msg = makeAssistantMessage({ stopReason: \"error\", errorMessage: \"rate_limit\" });\n\t\tassert.equal(buildTokenTelemetryRecord(msg).stopReason, \"error\");\n\t});\n});\n\n// ─── emitTokenTelemetry ────────────────────────────────────────────────────\n\ndescribe(\"emitTokenTelemetry\", () => {\n\tlet captured: string[];\n\tlet originalWrite: typeof process.stderr.write;\n\tlet originalEnv: string | undefined;\n\n\tbeforeEach(() => {\n\t\tcaptured = [];\n\t\toriginalWrite = process.stderr.write.bind(process.stderr);\n\t\t// Replace stderr.write with a capture; preserve the same return contract.\n\t\tprocess.stderr.write = ((chunk: string | Uint8Array): boolean => {\n\t\t\tcaptured.push(typeof chunk === \"string\" ? chunk : Buffer.from(chunk).toString());\n\t\t\treturn true;\n\t\t}) as typeof process.stderr.write;\n\t\toriginalEnv = process.env.PI_TOKEN_TELEMETRY;\n\t});\n\n\tafterEach(() => {\n\t\tprocess.stderr.write = originalWrite;\n\t\tif (originalEnv === undefined) {\n\t\t\tdelete process.env.PI_TOKEN_TELEMETRY;\n\t\t} else {\n\t\t\tprocess.env.PI_TOKEN_TELEMETRY = originalEnv;\n\t\t}\n\t});\n\n\ttest(\"silent by default (env var unset)\", () => {\n\t\tdelete process.env.PI_TOKEN_TELEMETRY;\n\t\temitTokenTelemetry(makeAssistantMessage());\n\t\tassert.equal(captured.length, 0);\n\t});\n\n\ttest(\"silent when env var has any non-'1' value\", () => {\n\t\tprocess.env.PI_TOKEN_TELEMETRY = \"true\"; // not literally \"1\"\n\t\temitTokenTelemetry(makeAssistantMessage());\n\t\tassert.equal(captured.length, 0, \"only literal '1' should enable telemetry\");\n\t});\n\n\ttest(\"emits a single JSON line when PI_TOKEN_TELEMETRY=1\", () => {\n\t\tprocess.env.PI_TOKEN_TELEMETRY = \"1\";\n\t\temitTokenTelemetry(makeAssistantMessage());\n\t\tassert.equal(captured.length, 1);\n\t\tassert.ok(captured[0].endsWith(\"\\n\"), \"line must terminate with newline\");\n\t\tconst parsed = JSON.parse(captured[0].trimEnd());\n\t\tassert.equal(parsed.model, \"claude-sonnet-4-6\");\n\t\tassert.equal(parsed.input, 100);\n\t\tassert.equal(parsed.output, 50);\n\t});\n\n\ttest(\"emitted JSON has the documented shape\", () => {\n\t\tprocess.env.PI_TOKEN_TELEMETRY = \"1\";\n\t\temitTokenTelemetry(makeAssistantMessage());\n\t\tconst parsed = JSON.parse(captured[0].trimEnd());\n\t\tconst keys = Object.keys(parsed).sort();\n\t\tassert.deepEqual(keys, [\n\t\t\t\"cacheHitRatio\",\n\t\t\t\"cacheRead\",\n\t\t\t\"cacheWrite\",\n\t\t\t\"costTotal\",\n\t\t\t\"input\",\n\t\t\t\"model\",\n\t\t\t\"output\",\n\t\t\t\"stopReason\",\n\t\t\t\"ts\",\n\t\t]);\n\t});\n\n\ttest(\"never throws — telemetry must not break the agent loop\", () => {\n\t\tprocess.env.PI_TOKEN_TELEMETRY = \"1\";\n\t\t// Force a write failure to exercise the swallow path.\n\t\tprocess.stderr.write = (() => {\n\t\t\tthrow new Error(\"simulated stderr failure\");\n\t\t}) as typeof process.stderr.write;\n\t\tassert.doesNotThrow(() => emitTokenTelemetry(makeAssistantMessage()));\n\t});\n});\n"]}
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gsd/pi-coding-agent",
3
- "version": "2.78.0",
3
+ "version": "2.78.1",
4
4
  "description": "Coding agent CLI (vendored from pi-mono)",
5
5
  "type": "module",
6
6
  "gsd": {
@@ -78,6 +78,7 @@ import { getLatestCompactionEntry } from "./session-manager.js";
78
78
  import type { SettingsManager } from "./settings-manager.js";
79
79
  import { BUILTIN_SLASH_COMMANDS, type SlashCommandInfo, type SlashCommandLocation } from "./slash-commands.js";
80
80
  import { buildSystemPrompt } from "./system-prompt.js";
81
+ import { emitTokenTelemetry } from "./token-telemetry.js";
81
82
  import type { BashOperations } from "./tools/bash.js";
82
83
  import { createAllTools } from "./tools/index.js";
83
84
 
@@ -470,6 +471,12 @@ export class AgentSession {
470
471
  this._cumulativeOutputTokens += assistantMsg.usage?.output ?? 0;
471
472
  this._cumulativeToolCalls += assistantMsg.content.filter((c) => c.type === "toolCall").length;
472
473
 
474
+ // Per-call token telemetry (off by default; gated by PI_TOKEN_TELEMETRY=1).
475
+ // Note: a turn that retries emits one record per attempt — group by
476
+ // session/turn downstream if you want a deduplicated view. Both records
477
+ // are valid (each was a billed/attempted API call). #5023
478
+ emitTokenTelemetry(assistantMsg);
479
+
473
480
  if (assistantMsg.stopReason !== "error") {
474
481
  this._compactionOrchestrator.clearOverflowRecovery();
475
482
  }
@@ -207,6 +207,10 @@ export function convertToLlm(messages: AgentMessage[]): Message[] {
207
207
  { type: "text" as const, text: COMPACTION_SUMMARY_PREFIX + m.summary + COMPACTION_SUMMARY_SUFFIX },
208
208
  ],
209
209
  timestamp: m.timestamp,
210
+ // Stable point in the conversation — earn prompt-cache reads on the
211
+ // summary + kept history block on every post-compaction turn until
212
+ // the next compaction. (#5027)
213
+ cacheBreakpoint: true,
210
214
  };
211
215
  case "user":
212
216
  case "assistant":
@@ -5,14 +5,17 @@ import { getApiProvider } from "@gsd/pi-ai";
5
5
  import { AuthStorage, type AuthStorageData } from "./auth-storage.js";
6
6
  import { ModelRegistry } from "./model-registry.js";
7
7
 
8
- function createRegistry(hasAuthFn?: (provider: string) => boolean): ModelRegistry {
8
+ function createRegistry(
9
+ hasAuthFn?: (provider: string) => boolean,
10
+ getApiKeyFn?: (provider: string) => Promise<string | undefined>,
11
+ ): ModelRegistry {
9
12
  const authStorage = {
10
13
  setFallbackResolver: () => {},
11
14
  onCredentialChange: () => {},
12
15
  getOAuthProviders: () => [],
13
16
  get: () => undefined,
14
17
  hasAuth: hasAuthFn ?? (() => false),
15
- getApiKey: async () => undefined,
18
+ getApiKey: async (provider: string) => getApiKeyFn ? getApiKeyFn(provider) : undefined,
16
19
  } as unknown as AuthStorage;
17
20
 
18
21
  return new ModelRegistry(authStorage, "");
@@ -311,6 +314,12 @@ describe("ModelRegistry authMode — isProviderRequestReady", () => {
311
314
  const registry = createRegistry(() => true);
312
315
  assert.equal(registry.isProviderRequestReady("anthropic"), true);
313
316
  });
317
+
318
+ it("returns false for denylisted providers even when auth exists", () => {
319
+ const registry = createRegistry(() => true);
320
+ registry.setDisabledModelProviders(["anthropic"]);
321
+ assert.equal(registry.isProviderRequestReady("anthropic"), false);
322
+ });
314
323
  });
315
324
 
316
325
  // ─── isReady callback ─────────────────────────────────────────────────────────
@@ -414,6 +423,17 @@ describe("ModelRegistry authMode — getAvailable", () => {
414
423
  assert.equal(available.length, 0);
415
424
  });
416
425
 
426
+ it("excludes denylisted providers from available models", () => {
427
+ const registry = createRegistry(() => true);
428
+ registry.setDisabledModelProviders(["google-gemini-cli"]);
429
+ const available = registry.getAvailable();
430
+ assert.equal(
431
+ available.some((m) => m.provider === "google-gemini-cli"),
432
+ false,
433
+ "google-gemini-cli models must be hidden when provider is denylisted",
434
+ );
435
+ });
436
+
417
437
  it("prunes Codex models removed from ChatGPT-backed openai-codex OAuth", () => {
418
438
  const registry = createInMemoryRegistry({
419
439
  "openai-codex": {
@@ -481,6 +501,16 @@ describe("ModelRegistry authMode — getApiKey", () => {
481
501
  const key = await registry.getApiKeyForProvider("anthropic");
482
502
  assert.equal(key, undefined);
483
503
  });
504
+
505
+ it("still resolves provider keys for denylisted providers", async () => {
506
+ const registry = createRegistry(
507
+ () => true,
508
+ async (provider: string) => provider === "google-gemini-cli" ? "ya29.test-token" : undefined,
509
+ );
510
+ registry.setDisabledModelProviders(["google-gemini-cli"]);
511
+ const key = await registry.getApiKeyForProvider("google-gemini-cli");
512
+ assert.equal(key, "ya29.test-token");
513
+ });
484
514
  });
485
515
 
486
516
  // ─── streamSimple apiKey stripping ────────────────────────────────────────────
@@ -247,6 +247,7 @@ export class ModelRegistry {
247
247
  private discoveryCache: ModelDiscoveryCache;
248
248
  private customProviderApiKeys: Map<string, string> = new Map();
249
249
  private registeredProviders: Map<string, ProviderConfigInput> = new Map();
250
+ private disabledModelProviders: Set<string> = new Set();
250
251
  private loadError: string | undefined = undefined;
251
252
 
252
253
  constructor(
@@ -553,6 +554,25 @@ export class ModelRegistry {
553
554
  return this.models.filter((m) => this.isProviderRequestReady(m.provider));
554
555
  }
555
556
 
557
+ /**
558
+ * Set provider IDs that should be excluded from model selection/routing.
559
+ * This does not affect direct tool auth flows that resolve provider keys.
560
+ */
561
+ setDisabledModelProviders(providers: string[]): void {
562
+ this.disabledModelProviders = new Set(
563
+ providers
564
+ .map((provider) => provider.trim().toLowerCase())
565
+ .filter((provider) => provider.length > 0),
566
+ );
567
+ }
568
+
569
+ /**
570
+ * Get current provider denylist used for model selection/routing.
571
+ */
572
+ getDisabledModelProviders(): string[] {
573
+ return Array.from(this.disabledModelProviders);
574
+ }
575
+
556
576
  /**
557
577
  * Get auth mode for a provider.
558
578
  * Defaults to "apiKey" for built-ins and providers without explicit mode.
@@ -570,6 +590,7 @@ export class ModelRegistry {
570
590
  * Whether a provider can be used for requests/fallback without hard auth gating.
571
591
  */
572
592
  isProviderRequestReady(provider: string): boolean {
593
+ if (this.disabledModelProviders.has(provider.trim().toLowerCase())) return false;
573
594
  const config = this.registeredProviders.get(provider);
574
595
  if (config?.isReady) return config.isReady();
575
596
  const authMode = this.getProviderAuthMode(provider);
@@ -55,6 +55,19 @@ export interface BuildSystemPromptOptions {
55
55
  * exception and the session stays consistent.
56
56
  */
57
57
  skillFilter?: (skill: Skill) => boolean;
58
+ /**
59
+ * Append a `Current date and time: <toLocaleString>` line to the system
60
+ * prompt. Default: `false`.
61
+ *
62
+ * Anthropic prompt caching matches on byte-for-byte prefix equality.
63
+ * Embedding a per-call timestamp in the system prompt invalidates the
64
+ * cache on every request, forcing a full re-write that costs *more*
65
+ * than an uncached call (cache-write premium). Most agentic flows do
66
+ * not need wall-clock awareness in the system prompt — opt in only
67
+ * when the consumer genuinely needs it (e.g. a clock-sensitive agent),
68
+ * and inject it via a non-cached channel (user message) when possible.
69
+ */
70
+ includeDateTime?: boolean;
58
71
  }
59
72
 
60
73
  /** Build the system prompt with tools, guidelines, and context */
@@ -69,20 +82,25 @@ export function buildSystemPrompt(options: BuildSystemPromptOptions = {}): strin
69
82
  contextFiles: providedContextFiles,
70
83
  skills: providedSkills,
71
84
  skillFilter,
85
+ includeDateTime = false,
72
86
  } = options;
73
87
  const resolvedCwd = toPosixPath(cwd ?? process.cwd());
74
88
 
75
- const now = new Date();
76
- const dateTime = now.toLocaleString("en-US", {
77
- weekday: "long",
78
- year: "numeric",
79
- month: "long",
80
- day: "numeric",
81
- hour: "2-digit",
82
- minute: "2-digit",
83
- second: "2-digit",
84
- timeZoneName: "short",
85
- });
89
+ // Per-call timestamps invalidate Anthropic prompt caching (the cache
90
+ // matches on byte-for-byte prefix equality). Compute lazily and only
91
+ // when explicitly opted in via `includeDateTime`.
92
+ const dateTimeLine = includeDateTime
93
+ ? `\nCurrent date and time: ${new Date().toLocaleString("en-US", {
94
+ weekday: "long",
95
+ year: "numeric",
96
+ month: "long",
97
+ day: "numeric",
98
+ hour: "2-digit",
99
+ minute: "2-digit",
100
+ second: "2-digit",
101
+ timeZoneName: "short",
102
+ })}`
103
+ : "";
86
104
 
87
105
  const appendSection = appendSystemPrompt ? `\n\n${appendSystemPrompt}` : "";
88
106
 
@@ -124,8 +142,8 @@ export function buildSystemPrompt(options: BuildSystemPromptOptions = {}): strin
124
142
  prompt += formatSkillsForPrompt(skills);
125
143
  }
126
144
 
127
- // Add date/time and working directory last
128
- prompt += `\nCurrent date and time: ${dateTime}`;
145
+ // Add date/time (only when opted in — see includeDateTime docs) and working directory last
146
+ prompt += dateTimeLine;
129
147
  prompt += `\nCurrent working directory: ${resolvedCwd}`;
130
148
 
131
149
  // Append promptGuidelines from extension-registered tools.
@@ -272,8 +290,8 @@ Pi documentation (read only when the user asks about pi itself, its SDK, extensi
272
290
  prompt += formatSkillsForPrompt(skills);
273
291
  }
274
292
 
275
- // Add date/time and working directory last
276
- prompt += `\nCurrent date and time: ${dateTime}`;
293
+ // Add date/time (only when opted in — see includeDateTime docs) and working directory last
294
+ prompt += dateTimeLine;
277
295
  prompt += `\nCurrent working directory: ${resolvedCwd}`;
278
296
 
279
297
  return prompt;
@@ -0,0 +1,77 @@
1
+ // @gsd/pi-coding-agent + token-telemetry — opt-in per-call token observability
2
+ //
3
+ // Emits a single JSON line per assistant message to stderr when
4
+ // `PI_TOKEN_TELEMETRY=1` is set. Captures the cache_read_input_tokens and
5
+ // cache_creation_input_tokens fields the providers already extract — so we
6
+ // can empirically measure prompt-cache effectiveness (e.g. for #5019 and
7
+ // future cache strategy work). Off by default — no behavior change.
8
+ //
9
+ // Capture pattern: `PI_TOKEN_TELEMETRY=1 npm start 2> token-telemetry.jsonl`
10
+
11
+ import type { AssistantMessage } from "@gsd/pi-ai";
12
+
13
+ /** Schema of one telemetry line. JSON-stable for downstream ingestion. */
14
+ export interface TokenTelemetryRecord {
15
+ ts: number;
16
+ model: string;
17
+ stopReason: string;
18
+ input: number;
19
+ output: number;
20
+ cacheRead: number;
21
+ cacheWrite: number;
22
+ /**
23
+ * `usage.cost.total` from the provider. `0` when the provider's cost
24
+ * registry has no rates for this model (e.g. unknown third-party providers)
25
+ * — distinguish from a true zero-cost call by checking your model registry.
26
+ */
27
+ costTotal: number;
28
+ /**
29
+ * Fraction of new prompt tokens served from cache:
30
+ * `cacheRead / (cacheRead + input)`. Range [0, 1].
31
+ * - `0` when neither cacheRead nor input is present (no division by zero).
32
+ * - `1` on a full cache hit (input = 0, cacheRead > 0).
33
+ * Note: `input` here is `input_tokens` from the API, which already excludes
34
+ * cache reads/writes — the denominator is total prompt tokens consumed.
35
+ */
36
+ cacheHitRatio: number;
37
+ }
38
+
39
+ /** Build a telemetry record from a finished assistant message. */
40
+ export function buildTokenTelemetryRecord(msg: AssistantMessage): TokenTelemetryRecord {
41
+ const input = msg.usage?.input ?? 0;
42
+ const output = msg.usage?.output ?? 0;
43
+ const cacheRead = msg.usage?.cacheRead ?? 0;
44
+ const cacheWrite = msg.usage?.cacheWrite ?? 0;
45
+ const costTotal = msg.usage?.cost?.total ?? 0;
46
+ const denom = cacheRead + input;
47
+ const cacheHitRatio = denom > 0 ? cacheRead / denom : 0;
48
+
49
+ return {
50
+ ts: msg.timestamp,
51
+ model: msg.model,
52
+ stopReason: msg.stopReason,
53
+ input,
54
+ output,
55
+ cacheRead,
56
+ cacheWrite,
57
+ costTotal,
58
+ cacheHitRatio,
59
+ };
60
+ }
61
+
62
+ /**
63
+ * Emit a token-telemetry line if `PI_TOKEN_TELEMETRY=1`. No-op otherwise.
64
+ *
65
+ * Writes to stderr so it doesn't interfere with TUI/stdout. One JSON object
66
+ * per line. Errors during emission are swallowed — telemetry must never
67
+ * break the agent loop.
68
+ */
69
+ export function emitTokenTelemetry(msg: AssistantMessage): void {
70
+ if (process.env.PI_TOKEN_TELEMETRY !== "1") return;
71
+ try {
72
+ const record = buildTokenTelemetryRecord(msg);
73
+ process.stderr.write(`${JSON.stringify(record)}\n`);
74
+ } catch {
75
+ // Telemetry must never break the agent loop. Swallow.
76
+ }
77
+ }