gsd-pi 2.78.1-dev.84a383f51 → 2.78.1
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.
- package/README.md +7 -7
- package/dist/cli.js +55 -95
- package/dist/headless-query.d.ts +0 -22
- package/dist/headless-query.js +4 -24
- package/dist/headless.d.ts +0 -10
- package/dist/headless.js +1 -16
- package/dist/loader.js +10 -7
- package/dist/onboarding.d.ts +0 -10
- package/dist/onboarding.js +2 -2
- package/dist/provider-migrations.d.ts +2 -2
- package/dist/provider-migrations.js +2 -5
- package/dist/resource-loader.d.ts +2 -5
- package/dist/resource-loader.js +5 -28
- package/dist/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +601 -0
- package/dist/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +651 -0
- package/dist/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +91 -0
- package/dist/resources/extensions/gsd/auto/loop.js +0 -23
- package/dist/resources/extensions/gsd/auto/phases.js +2 -2
- package/dist/resources/extensions/gsd/auto/run-unit.js +1 -3
- package/dist/resources/extensions/gsd/auto/session.js +0 -3
- package/dist/resources/extensions/gsd/auto-recovery.js +4 -43
- package/dist/resources/extensions/gsd/auto-start.js +1 -1
- package/dist/resources/extensions/gsd/auto-tool-tracking.js +2 -2
- package/dist/resources/extensions/gsd/auto-worktree.js +0 -30
- package/dist/resources/extensions/gsd/auto.js +5 -14
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +2 -14
- package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +5 -7
- package/dist/resources/extensions/gsd/bootstrap/query-tools.js +2 -2
- package/dist/resources/extensions/gsd/bootstrap/register-extension.js +4 -5
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +31 -94
- package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +6 -11
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +8 -34
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +2 -38
- package/dist/resources/extensions/gsd/commands/catalog.js +5 -69
- package/dist/resources/extensions/gsd/commands/handlers/core.js +1 -22
- package/dist/resources/extensions/gsd/commands-mcp-status.js +1 -3
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -10
- package/dist/resources/extensions/gsd/dashboard-overlay.js +1 -1
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +0 -4
- package/dist/resources/extensions/gsd/doctor-runtime-checks.js +1 -39
- package/dist/resources/extensions/gsd/error-classifier.js +1 -1
- package/dist/resources/extensions/gsd/forensics.js +2 -2
- package/dist/resources/extensions/gsd/git-service.js +5 -12
- package/dist/resources/extensions/gsd/gsd-db.js +2 -11
- package/dist/resources/extensions/gsd/guided-flow.js +23 -23
- package/dist/resources/extensions/gsd/memory-store.js +31 -66
- package/dist/resources/extensions/gsd/model-router.js +9 -114
- package/dist/resources/extensions/gsd/native-git-bridge.js +1 -7
- package/dist/resources/extensions/gsd/preferences-models.js +15 -91
- package/dist/resources/extensions/gsd/preferences-types.js +0 -2
- package/dist/resources/extensions/gsd/preferences-validation.js +0 -32
- package/dist/resources/extensions/gsd/preferences.js +3 -5
- package/dist/resources/extensions/gsd/prompt-loader.js +12 -23
- package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +3 -9
- package/dist/resources/extensions/gsd/state.js +0 -42
- package/dist/resources/extensions/gsd/templates/PREFERENCES.md +0 -1
- package/dist/resources/extensions/gsd/tests/auto-supervisor.test.mjs +53 -0
- package/dist/resources/extensions/gsd/tests/dist-redirect.mjs +112 -0
- package/dist/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +23 -0
- package/dist/resources/extensions/gsd/tests/resolve-ts.mjs +5 -0
- package/dist/resources/extensions/gsd/tools/memory-tools.js +1 -18
- package/dist/resources/extensions/gsd/visualizer-overlay.js +1 -1
- package/dist/resources/extensions/gsd/watch/header-renderer.js +1 -3
- package/dist/resources/extensions/gsd/worktree-command.js +46 -26
- package/dist/resources/extensions/mcp-client/index.js +3 -6
- package/dist/resources/extensions/slash-commands/create-extension.js +22 -36
- package/dist/resources/skills/create-gsd-extension/SKILL.md +5 -9
- package/dist/resources/skills/create-gsd-extension/references/custom-commands.md +1 -1
- package/dist/resources/skills/create-gsd-extension/references/custom-rendering.md +5 -5
- package/dist/resources/skills/create-gsd-extension/references/custom-tools.md +4 -4
- package/dist/resources/skills/create-gsd-extension/references/custom-ui.md +6 -6
- package/dist/resources/skills/create-gsd-extension/references/events-reference.md +3 -3
- package/dist/resources/skills/create-gsd-extension/references/packaging-distribution.md +1 -1
- package/dist/resources/skills/create-gsd-extension/references/remote-execution-overrides.md +3 -3
- package/dist/resources/skills/create-gsd-extension/workflows/create-extension.md +12 -32
- package/dist/resources/skills/github-workflows/references/gh/tests/__init__.py +0 -0
- package/dist/resources/skills/github-workflows/references/gh/tests/test_github_project_setup.py +608 -0
- package/dist/rtk-shared.d.ts +0 -3
- package/dist/rtk-shared.js +0 -17
- package/dist/rtk.d.ts +5 -2
- package/dist/rtk.js +20 -3
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +13 -13
- package/dist/web/standalone/.next/build-manifest.json +4 -4
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/react-loadable-manifest.json +4 -44
- package/dist/web/standalone/.next/required-server-files.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
- package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/notifications/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/notifications/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -4
- package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +4 -4
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/page.js +2 -2
- package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +13 -13
- package/dist/web/standalone/.next/server/chunks/63.js +3 -3
- package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
- package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware.js +2 -2
- package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
- package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/server/webpack-runtime.js +1 -1
- package/dist/web/standalone/.next/static/chunks/2826.e9f5195e91f9cad2.js +11 -0
- package/dist/web/standalone/.next/static/chunks/3621.fc7480022c972438.js +20 -0
- package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-f2a7482d42a5614b.js → page-2f24283c162b6ab3.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/app/{layout-a16c7a7ecdf0c2cf.js → layout-9ecfd95f343793f0.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/app/page-151349214571e2b6.js +1 -0
- package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +1 -0
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +1 -0
- package/dist/web/standalone/.next/static/chunks/webpack-2e68521d7c82f7c2.js +1 -0
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
- package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
- package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
- package/dist/web/standalone/package.json +1 -2
- package/dist/web/standalone/server.js +1 -1
- package/package.json +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +46 -74
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/src/workflow-tools.test.ts +0 -26
- package/packages/mcp-server/src/workflow-tools.ts +58 -93
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.js +19 -48
- package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +0 -13
- package/packages/pi-ai/dist/types.d.ts.map +1 -1
- package/packages/pi-ai/dist/types.js.map +1 -1
- package/packages/pi-ai/dist/utils/repair-tool-json.d.ts.map +1 -1
- package/packages/pi-ai/dist/utils/repair-tool-json.js +3 -24
- package/packages/pi-ai/dist/utils/repair-tool-json.js.map +1 -1
- package/packages/pi-ai/dist/utils/tests/repair-tool-json.test.js +0 -26
- package/packages/pi-ai/dist/utils/tests/repair-tool-json.test.js.map +1 -1
- package/packages/pi-ai/src/providers/anthropic-shared.ts +20 -52
- package/packages/pi-ai/src/types.ts +0 -13
- package/packages/pi-ai/src/utils/repair-tool-json.ts +3 -24
- package/packages/pi-ai/src/utils/tests/repair-tool-json.test.ts +0 -32
- package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +0 -6
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/messages.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/messages.js +0 -4
- package/packages/pi-coding-agent/dist/core/messages.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js +2 -19
- package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts +0 -10
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js +0 -18
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts +0 -13
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js +16 -20
- package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js +1 -14
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js.map +1 -1
- package/packages/pi-coding-agent/src/core/agent-session.ts +0 -7
- package/packages/pi-coding-agent/src/core/messages.ts +0 -4
- package/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts +2 -32
- package/packages/pi-coding-agent/src/core/model-registry.ts +0 -21
- package/packages/pi-coding-agent/src/core/system-prompt.ts +15 -33
- package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.test.ts +1 -17
- package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +1 -1
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-tui/dist/__tests__/autocomplete.test.js +3 -17
- package/packages/pi-tui/dist/__tests__/autocomplete.test.js.map +1 -1
- package/packages/pi-tui/src/__tests__/autocomplete.test.ts +3 -20
- package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
- package/src/resources/extensions/gsd/auto/loop.ts +2 -24
- package/src/resources/extensions/gsd/auto/phases.ts +3 -3
- package/src/resources/extensions/gsd/auto/run-unit.ts +1 -3
- package/src/resources/extensions/gsd/auto/session.ts +0 -3
- package/src/resources/extensions/gsd/auto/types.ts +0 -1
- package/src/resources/extensions/gsd/auto-recovery.ts +8 -46
- package/src/resources/extensions/gsd/auto-start.ts +1 -1
- package/src/resources/extensions/gsd/auto-tool-tracking.ts +4 -2
- package/src/resources/extensions/gsd/auto-worktree.ts +0 -38
- package/src/resources/extensions/gsd/auto.ts +4 -14
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +13 -15
- package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +7 -8
- package/src/resources/extensions/gsd/bootstrap/query-tools.ts +2 -2
- package/src/resources/extensions/gsd/bootstrap/register-extension.ts +9 -10
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +31 -102
- package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +6 -12
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +8 -39
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +11 -39
- package/src/resources/extensions/gsd/commands/catalog.ts +5 -75
- package/src/resources/extensions/gsd/commands/handlers/core.ts +1 -22
- package/src/resources/extensions/gsd/commands-mcp-status.ts +1 -3
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -15
- package/src/resources/extensions/gsd/dashboard-overlay.ts +1 -1
- package/src/resources/extensions/gsd/docs/preferences-reference.md +0 -4
- package/src/resources/extensions/gsd/doctor-runtime-checks.ts +1 -39
- package/src/resources/extensions/gsd/doctor-types.ts +1 -3
- package/src/resources/extensions/gsd/error-classifier.ts +1 -1
- package/src/resources/extensions/gsd/forensics.ts +2 -2
- package/src/resources/extensions/gsd/git-service.ts +5 -13
- package/src/resources/extensions/gsd/gsd-db.ts +2 -12
- package/src/resources/extensions/gsd/guided-flow.ts +25 -25
- package/src/resources/extensions/gsd/memory-store.ts +28 -81
- package/src/resources/extensions/gsd/model-router.ts +9 -172
- package/src/resources/extensions/gsd/native-git-bridge.ts +1 -7
- package/src/resources/extensions/gsd/preferences-models.ts +15 -101
- package/src/resources/extensions/gsd/preferences-types.ts +0 -6
- package/src/resources/extensions/gsd/preferences-validation.ts +0 -35
- package/src/resources/extensions/gsd/preferences.ts +2 -16
- package/src/resources/extensions/gsd/prompt-loader.ts +12 -26
- package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +3 -9
- package/src/resources/extensions/gsd/state.ts +0 -42
- package/src/resources/extensions/gsd/templates/PREFERENCES.md +0 -1
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +1 -178
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +0 -58
- package/src/resources/extensions/gsd/tests/auto-session-encapsulation.test.ts +5 -9
- package/src/resources/extensions/gsd/tests/auto-supervisor.test.mjs +4 -21
- package/src/resources/extensions/gsd/tests/bootstrap-derive-state-db-open.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/budget-prediction.test.ts +211 -138
- package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +59 -142
- package/src/resources/extensions/gsd/tests/complete-slice.test.ts +4 -7
- package/src/resources/extensions/gsd/tests/completed-at-reconcile.test.ts +32 -89
- package/src/resources/extensions/gsd/tests/copy-planning-artifacts-samepath.test.ts +22 -0
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +23 -41
- package/src/resources/extensions/gsd/tests/db-path-worktree-symlink.test.ts +43 -3
- package/src/resources/extensions/gsd/tests/debug-logger.test.ts +3 -5
- package/src/resources/extensions/gsd/tests/discuss-empty-db-fallback.test.ts +87 -22
- package/src/resources/extensions/gsd/tests/discuss-queued-milestones.test.ts +118 -7
- package/src/resources/extensions/gsd/tests/discuss-slice-structured-questions.test.ts +47 -0
- package/src/resources/extensions/gsd/tests/discuss-tool-scope-leak.test.ts +60 -18
- package/src/resources/extensions/gsd/tests/double-merge-guard.test.ts +76 -14
- package/src/resources/extensions/gsd/tests/empty-content-abort-loop.test.ts +75 -0
- package/src/resources/extensions/gsd/tests/false-degraded-mode-warning.test.ts +83 -22
- package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +63 -1
- package/src/resources/extensions/gsd/tests/forensics-stuck-loops.test.ts +1 -26
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +0 -30
- package/src/resources/extensions/gsd/tests/headless-answers.test.ts +4 -14
- package/src/resources/extensions/gsd/tests/health-widget.test.ts +12 -22
- package/src/resources/extensions/gsd/tests/init-prefs-routing.test.ts +1 -64
- package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +0 -22
- package/src/resources/extensions/gsd/tests/integration/token-savings.test.ts +23 -0
- package/src/resources/extensions/gsd/tests/memory-store.test.ts +0 -128
- package/src/resources/extensions/gsd/tests/memory-tools.test.ts +1 -33
- package/src/resources/extensions/gsd/tests/model-router.test.ts +8 -169
- package/src/resources/extensions/gsd/tests/orphaned-worktree-audit.test.ts +0 -8
- package/src/resources/extensions/gsd/tests/parallel-crash-recovery.test.ts +43 -32
- package/src/resources/extensions/gsd/tests/phases-merge-error-stops-auto.test.ts +10 -4
- package/src/resources/extensions/gsd/tests/preferences.test.ts +0 -127
- package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +0 -16
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +0 -7
- package/src/resources/extensions/gsd/tests/quick-turn-end-cleanup.test.ts +6 -6
- package/src/resources/extensions/gsd/tests/session-start-footer.test.ts +19 -168
- package/src/resources/extensions/gsd/tests/slice-parallel-orchestrator.test.ts +1 -7
- package/src/resources/extensions/gsd/tests/smart-entry-complete.test.ts +1 -23
- package/src/resources/extensions/gsd/tests/token-profile.test.ts +4 -51
- package/src/resources/extensions/gsd/tests/turn-epoch.test.ts +16 -7
- package/src/resources/extensions/gsd/tests/unstructured-continue-context-injection.test.ts +7 -5
- package/src/resources/extensions/gsd/tests/uok-gitops-turn-action.test.ts +1 -15
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +6 -6
- package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +0 -15
- package/src/resources/extensions/gsd/tools/memory-tools.ts +1 -17
- package/src/resources/extensions/gsd/unit-context-manifest.ts +8 -8
- package/src/resources/extensions/gsd/visualizer-overlay.ts +1 -1
- package/src/resources/extensions/gsd/watch/header-renderer.ts +1 -3
- package/src/resources/extensions/gsd/workflow-logger.ts +0 -1
- package/src/resources/extensions/gsd/worktree-command.ts +44 -31
- package/src/resources/extensions/mcp-client/index.ts +3 -6
- package/src/resources/extensions/slash-commands/create-extension.ts +24 -38
- package/src/resources/skills/create-gsd-extension/SKILL.md +5 -9
- package/src/resources/skills/create-gsd-extension/references/custom-commands.md +1 -1
- package/src/resources/skills/create-gsd-extension/references/custom-rendering.md +5 -5
- package/src/resources/skills/create-gsd-extension/references/custom-tools.md +4 -4
- package/src/resources/skills/create-gsd-extension/references/custom-ui.md +6 -6
- package/src/resources/skills/create-gsd-extension/references/events-reference.md +3 -3
- package/src/resources/skills/create-gsd-extension/references/packaging-distribution.md +1 -1
- package/src/resources/skills/create-gsd-extension/references/remote-execution-overrides.md +3 -3
- package/src/resources/skills/create-gsd-extension/templates/extension-skeleton.ts +2 -2
- package/src/resources/skills/create-gsd-extension/templates/stateful-tool-skeleton.ts +3 -3
- package/src/resources/skills/create-gsd-extension/workflows/create-extension.md +12 -32
- package/dist/cli-policy.d.ts +0 -13
- package/dist/cli-policy.js +0 -17
- package/dist/resources/.managed-resources-content-hash +0 -1
- package/dist/resources/extensions/gsd/auto-runtime-state.js +0 -31
- package/dist/resources/extensions/gsd/milestone-id-reservation.js +0 -36
- package/dist/resources/extensions/gsd/worktree-session-state.js +0 -33
- package/dist/runtime-checks.d.ts +0 -27
- package/dist/runtime-checks.js +0 -38
- package/dist/web/standalone/.next/static/chunks/2556.0527fea66e123b7f.js +0 -1
- package/dist/web/standalone/.next/static/chunks/2824.08296bc2f9654698.js +0 -1
- package/dist/web/standalone/.next/static/chunks/3026.3af53b279375f082.js +0 -1
- package/dist/web/standalone/.next/static/chunks/315.6f68ae79b67d25cf.js +0 -1
- package/dist/web/standalone/.next/static/chunks/3497.4bfc60a3b3dea717.js +0 -1
- package/dist/web/standalone/.next/static/chunks/5516.4a07c872b5c3a663.js +0 -1
- package/dist/web/standalone/.next/static/chunks/8336.31b019697882acfb.js +0 -10
- package/dist/web/standalone/.next/static/chunks/8845.c9702695e8c5a9c5.js +0 -2
- package/dist/web/standalone/.next/static/chunks/9058.01ef3a463bda88f1.js +0 -20
- package/dist/web/standalone/.next/static/chunks/9441.1081da1125d1764f.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/page-9bf2e0c50fb2ca05.js +0 -1
- package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +0 -1
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +0 -1
- package/dist/web/standalone/.next/static/chunks/webpack-f9f0dc45e4f3ac10.js +0 -1
- package/dist/worktree-status-banner.d.ts +0 -1
- package/dist/worktree-status-banner.js +0 -132
- package/packages/mcp-server/dist/alias-telemetry.d.ts +0 -8
- package/packages/mcp-server/dist/alias-telemetry.d.ts.map +0 -1
- package/packages/mcp-server/dist/alias-telemetry.js +0 -30
- package/packages/mcp-server/dist/alias-telemetry.js.map +0 -1
- package/packages/mcp-server/src/alias-telemetry.test.ts +0 -78
- package/packages/mcp-server/src/alias-telemetry.ts +0 -30
- package/packages/pi-ai/dist/providers/anthropic-shared.cache-breakpoint.test.d.ts +0 -2
- package/packages/pi-ai/dist/providers/anthropic-shared.cache-breakpoint.test.d.ts.map +0 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.cache-breakpoint.test.js +0 -231
- package/packages/pi-ai/dist/providers/anthropic-shared.cache-breakpoint.test.js.map +0 -1
- package/packages/pi-ai/src/providers/anthropic-shared.cache-breakpoint.test.ts +0 -289
- package/packages/pi-coding-agent/dist/core/token-telemetry.d.ts +0 -37
- package/packages/pi-coding-agent/dist/core/token-telemetry.d.ts.map +0 -1
- package/packages/pi-coding-agent/dist/core/token-telemetry.js +0 -49
- package/packages/pi-coding-agent/dist/core/token-telemetry.js.map +0 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.d.ts +0 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.d.ts.map +0 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.js +0 -133
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.js.map +0 -1
- package/packages/pi-coding-agent/dist/tests/system-prompt-cache-stability.test.d.ts +0 -2
- package/packages/pi-coding-agent/dist/tests/system-prompt-cache-stability.test.d.ts.map +0 -1
- package/packages/pi-coding-agent/dist/tests/system-prompt-cache-stability.test.js +0 -78
- package/packages/pi-coding-agent/dist/tests/system-prompt-cache-stability.test.js.map +0 -1
- package/packages/pi-coding-agent/dist/tests/token-telemetry.test.d.ts +0 -2
- package/packages/pi-coding-agent/dist/tests/token-telemetry.test.d.ts.map +0 -1
- package/packages/pi-coding-agent/dist/tests/token-telemetry.test.js +0 -181
- package/packages/pi-coding-agent/dist/tests/token-telemetry.test.js.map +0 -1
- package/packages/pi-coding-agent/src/core/token-telemetry.ts +0 -77
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.ts +0 -212
- package/packages/pi-coding-agent/src/tests/system-prompt-cache-stability.test.ts +0 -102
- package/packages/pi-coding-agent/src/tests/token-telemetry.test.ts +0 -200
- package/packages/pi-tui/dist/components/__tests__/leak-fixes-runtime.test.d.ts +0 -2
- package/packages/pi-tui/dist/components/__tests__/leak-fixes-runtime.test.d.ts.map +0 -1
- package/packages/pi-tui/dist/components/__tests__/leak-fixes-runtime.test.js +0 -161
- package/packages/pi-tui/dist/components/__tests__/leak-fixes-runtime.test.js.map +0 -1
- package/packages/pi-tui/src/components/__tests__/leak-fixes-runtime.test.ts +0 -219
- package/src/resources/extensions/gsd/auto-runtime-state.ts +0 -51
- package/src/resources/extensions/gsd/milestone-id-reservation.ts +0 -47
- package/src/resources/extensions/gsd/tests/deferred-milestone-dir-4996.test.ts +0 -116
- package/src/resources/extensions/gsd/tests/doctor-orphan-milestone-4996.test.ts +0 -100
- package/src/resources/extensions/gsd/tests/ensure-preconditions-guard-4996.test.ts +0 -93
- package/src/resources/extensions/gsd/tests/find-missing-summaries-closed-runtime.test.ts +0 -47
- package/src/resources/extensions/gsd/tests/gitignore-bg-shell-runtime.test.ts +0 -63
- package/src/resources/extensions/gsd/tests/gsd-no-project-error-runtime.test.ts +0 -81
- package/src/resources/extensions/gsd/tests/help-menu-coverage.test.ts +0 -57
- package/src/resources/extensions/gsd/tests/import-done-milestones-runtime.test.ts +0 -145
- package/src/resources/extensions/gsd/tests/merge-self-branch-guard.test.ts +0 -124
- package/src/resources/extensions/gsd/tests/milestone-id-gap-reuse-4996.test.ts +0 -152
- package/src/resources/extensions/gsd/tests/native-git-infra-errors.test.ts +0 -50
- package/src/resources/extensions/gsd/tests/register-hooks-compaction-checkpoint.test.ts +0 -93
- package/src/resources/extensions/gsd/tests/system-context-message-routing.test.ts +0 -101
- package/src/resources/extensions/gsd/worktree-session-state.ts +0 -35
- package/src/resources/extensions/mcp-client/tests/global-config.test.ts +0 -91
- package/src/resources/skills/create-gsd-extension/templates/templates.test.ts +0 -58
- /package/dist/web/standalone/.next/static/{UF5VF4F1tB0miEtJS7LyX → 7afp7gq8-DVbxum83zRQ-}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{UF5VF4F1tB0miEtJS7LyX → 7afp7gq8-DVbxum83zRQ-}/_ssgManifest.js +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"token-telemetry.js","sourceRoot":"","sources":["../../src/core/token-telemetry.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,EAAE;AACF,gEAAgE;AAChE,0EAA0E;AAC1E,2EAA2E;AAC3E,yEAAyE;AACzE,oEAAoE;AACpE,EAAE;AACF,6EAA6E;AA8B7E,kEAAkE;AAClE,MAAM,UAAU,yBAAyB,CAAC,GAAqB;IAC9D,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,EAAE,KAAK,IAAI,CAAC,CAAC;IACpC,MAAM,MAAM,GAAG,GAAG,CAAC,KAAK,EAAE,MAAM,IAAI,CAAC,CAAC;IACtC,MAAM,SAAS,GAAG,GAAG,CAAC,KAAK,EAAE,SAAS,IAAI,CAAC,CAAC;IAC5C,MAAM,UAAU,GAAG,GAAG,CAAC,KAAK,EAAE,UAAU,IAAI,CAAC,CAAC;IAC9C,MAAM,SAAS,GAAG,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC,CAAC;IAC9C,MAAM,KAAK,GAAG,SAAS,GAAG,KAAK,CAAC;IAChC,MAAM,aAAa,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAExD,OAAO;QACN,EAAE,EAAE,GAAG,CAAC,SAAS;QACjB,KAAK,EAAE,GAAG,CAAC,KAAK;QAChB,UAAU,EAAE,GAAG,CAAC,UAAU;QAC1B,KAAK;QACL,MAAM;QACN,SAAS;QACT,UAAU;QACV,SAAS;QACT,aAAa;KACb,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAAqB;IACvD,IAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB,KAAK,GAAG;QAAE,OAAO;IACnD,IAAI,CAAC;QACJ,MAAM,MAAM,GAAG,yBAAyB,CAAC,GAAG,CAAC,CAAC;QAC9C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACrD,CAAC;IAAC,MAAM,CAAC;QACR,sDAAsD;IACvD,CAAC;AACF,CAAC","sourcesContent":["// @gsd/pi-coding-agent + token-telemetry — opt-in per-call token observability\n//\n// Emits a single JSON line per assistant message to stderr when\n// `PI_TOKEN_TELEMETRY=1` is set. Captures the cache_read_input_tokens and\n// cache_creation_input_tokens fields the providers already extract — so we\n// can empirically measure prompt-cache effectiveness (e.g. for #5019 and\n// future cache strategy work). Off by default — no behavior change.\n//\n// Capture pattern: `PI_TOKEN_TELEMETRY=1 npm start 2> token-telemetry.jsonl`\n\nimport type { AssistantMessage } from \"@gsd/pi-ai\";\n\n/** Schema of one telemetry line. JSON-stable for downstream ingestion. */\nexport interface TokenTelemetryRecord {\n\tts: number;\n\tmodel: string;\n\tstopReason: string;\n\tinput: number;\n\toutput: number;\n\tcacheRead: number;\n\tcacheWrite: number;\n\t/**\n\t * `usage.cost.total` from the provider. `0` when the provider's cost\n\t * registry has no rates for this model (e.g. unknown third-party providers)\n\t * — distinguish from a true zero-cost call by checking your model registry.\n\t */\n\tcostTotal: number;\n\t/**\n\t * Fraction of new prompt tokens served from cache:\n\t * `cacheRead / (cacheRead + input)`. Range [0, 1].\n\t * - `0` when neither cacheRead nor input is present (no division by zero).\n\t * - `1` on a full cache hit (input = 0, cacheRead > 0).\n\t * Note: `input` here is `input_tokens` from the API, which already excludes\n\t * cache reads/writes — the denominator is total prompt tokens consumed.\n\t */\n\tcacheHitRatio: number;\n}\n\n/** Build a telemetry record from a finished assistant message. */\nexport function buildTokenTelemetryRecord(msg: AssistantMessage): TokenTelemetryRecord {\n\tconst input = msg.usage?.input ?? 0;\n\tconst output = msg.usage?.output ?? 0;\n\tconst cacheRead = msg.usage?.cacheRead ?? 0;\n\tconst cacheWrite = msg.usage?.cacheWrite ?? 0;\n\tconst costTotal = msg.usage?.cost?.total ?? 0;\n\tconst denom = cacheRead + input;\n\tconst cacheHitRatio = denom > 0 ? cacheRead / denom : 0;\n\n\treturn {\n\t\tts: msg.timestamp,\n\t\tmodel: msg.model,\n\t\tstopReason: msg.stopReason,\n\t\tinput,\n\t\toutput,\n\t\tcacheRead,\n\t\tcacheWrite,\n\t\tcostTotal,\n\t\tcacheHitRatio,\n\t};\n}\n\n/**\n * Emit a token-telemetry line if `PI_TOKEN_TELEMETRY=1`. No-op otherwise.\n *\n * Writes to stderr so it doesn't interfere with TUI/stdout. One JSON object\n * per line. Errors during emission are swallowed — telemetry must never\n * break the agent loop.\n */\nexport function emitTokenTelemetry(msg: AssistantMessage): void {\n\tif (process.env.PI_TOKEN_TELEMETRY !== \"1\") return;\n\ttry {\n\t\tconst record = buildTokenTelemetryRecord(msg);\n\t\tprocess.stderr.write(`${JSON.stringify(record)}\\n`);\n\t} catch {\n\t\t// Telemetry must never break the agent loop. Swallow.\n\t}\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"tool-card-cleanup-and-success-runtime.test.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
// Runtime regression tests for the post-compaction tool-card cleanup and the
|
|
2
|
-
// green-bordered success-notification rendering. Replaces the source-grep
|
|
3
|
-
// `src/tests/tui-running-and-success-box.test.ts` that was deleted in #4875
|
|
4
|
-
// (tracked as #4872).
|
|
5
|
-
//
|
|
6
|
-
// The previous tests asserted on identifier presence and method signature
|
|
7
|
-
// shape via regex. A regression that routed `success` notifications through
|
|
8
|
-
// `showStatus` (dim text) by accident would not have failed because the
|
|
9
|
-
// `showSuccess` method would still exist and still match the regex. These
|
|
10
|
-
// tests instead drive the components through the actual scenario and assert
|
|
11
|
-
// on rendered output.
|
|
12
|
-
import assert from "node:assert/strict";
|
|
13
|
-
import { describe, it, before } from "node:test";
|
|
14
|
-
import { Container, Text } from "@gsd/pi-tui";
|
|
15
|
-
import stripAnsi from "strip-ansi";
|
|
16
|
-
import { initTheme, theme } from "../theme/theme.js";
|
|
17
|
-
import { DynamicBorder } from "./dynamic-border.js";
|
|
18
|
-
import { ToolExecutionComponent } from "./tool-execution.js";
|
|
19
|
-
// Theme is a globalThis-shared singleton that throws if not initialized.
|
|
20
|
-
// Initialize once before any test that exercises themed rendering.
|
|
21
|
-
before(() => {
|
|
22
|
-
initTheme("dark");
|
|
23
|
-
});
|
|
24
|
-
function makeMockTUI() {
|
|
25
|
-
return {
|
|
26
|
-
renderCount: 0,
|
|
27
|
-
requestRender() {
|
|
28
|
-
this.renderCount++;
|
|
29
|
-
},
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
// ─── Bug 1: tool cards stuck in "Running" after compaction ──────────────
|
|
33
|
-
describe("ToolExecutionComponent post-compaction cleanup", () => {
|
|
34
|
-
it("renders 'Running' status while the tool call has no result", () => {
|
|
35
|
-
// Baseline: a freshly-constructed component (mid-stream) must show
|
|
36
|
-
// the running badge — this is the state we need to flip OUT of when
|
|
37
|
-
// compaction removes the result message.
|
|
38
|
-
const ui = makeMockTUI();
|
|
39
|
-
const c = new ToolExecutionComponent("read_file", { path: "/tmp/x.txt" }, {}, undefined, ui);
|
|
40
|
-
const rendered = c.render(60).map(stripAnsi).join("\n");
|
|
41
|
-
assert.ok(rendered.includes("Running"), "freshly constructed component should render 'Running' badge");
|
|
42
|
-
});
|
|
43
|
-
it("markHistoricalNoResult flips a stuck tool card OUT of 'Running'", () => {
|
|
44
|
-
// Real bug: after session-history replay (post-compaction or session
|
|
45
|
-
// switch), tool calls without matching tool_result messages stay in
|
|
46
|
-
// isPartial = true forever. markHistoricalNoResult must produce a
|
|
47
|
-
// rendered output that no longer reads "Running".
|
|
48
|
-
const ui = makeMockTUI();
|
|
49
|
-
const c = new ToolExecutionComponent("read_file", { path: "/tmp/x.txt" }, {}, undefined, ui);
|
|
50
|
-
c.markHistoricalNoResult();
|
|
51
|
-
const rendered = c.render(60).map(stripAnsi).join("\n");
|
|
52
|
-
assert.ok(!rendered.includes("Running"), "after markHistoricalNoResult, the tool card must NOT render 'Running' — got:\n" +
|
|
53
|
-
rendered);
|
|
54
|
-
assert.ok(rendered.includes("Done"), "flipped card should render 'Done' status (no-result success)");
|
|
55
|
-
});
|
|
56
|
-
it("markHistoricalNoResult is idempotent when a real result already exists", () => {
|
|
57
|
-
// Late-arriving stream events must not clobber legitimate results.
|
|
58
|
-
// We observe the no-clobber via behaviour: a card that completed
|
|
59
|
-
// with a real result keeps its "Done" badge and content even if
|
|
60
|
-
// markHistoricalNoResult fires again afterwards.
|
|
61
|
-
const ui = makeMockTUI();
|
|
62
|
-
const c = new ToolExecutionComponent("read_file", { path: "/tmp/x.txt" }, {}, undefined, ui);
|
|
63
|
-
// Use the public completion path the runtime calls.
|
|
64
|
-
c.updateResult({
|
|
65
|
-
content: [{ type: "text", text: "real-result-payload" }],
|
|
66
|
-
isError: false,
|
|
67
|
-
}, false);
|
|
68
|
-
const before = c.render(60).map(stripAnsi).join("\n");
|
|
69
|
-
assert.ok(before.includes("Done"), "after complete(), card shows 'Done'");
|
|
70
|
-
c.markHistoricalNoResult(); // must early-return
|
|
71
|
-
const after = c.render(60).map(stripAnsi).join("\n");
|
|
72
|
-
assert.equal(after, before, "markHistoricalNoResult must NOT mutate state when a real result is already present");
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
// ─── Bug 2: success notifications render as a green bordered box ────────
|
|
76
|
-
describe("Success-notification rendering — DynamicBorder + success theme", () => {
|
|
77
|
-
// We drive the same component composition that interactive-mode.showSuccess
|
|
78
|
-
// builds (DynamicBorder + Text + DynamicBorder, all themed with
|
|
79
|
-
// theme.fg("success", …)) and assert on the rendered output, rather than
|
|
80
|
-
// trying to instantiate the full InteractiveMode (which requires a real
|
|
81
|
-
// session, bridge, and TUI runtime).
|
|
82
|
-
function buildSuccessNotification(message) {
|
|
83
|
-
const c = new Container();
|
|
84
|
-
const successColor = (text) => theme.fg("success", text);
|
|
85
|
-
c.addChild(new DynamicBorder(successColor));
|
|
86
|
-
c.addChild(new Text(theme.fg("success", message), 1, 0));
|
|
87
|
-
c.addChild(new DynamicBorder(successColor));
|
|
88
|
-
return c;
|
|
89
|
-
}
|
|
90
|
-
it("renders a top and bottom horizontal border framing the message", () => {
|
|
91
|
-
const c = buildSuccessNotification("Milestone M042 ready.");
|
|
92
|
-
const lines = c.render(40);
|
|
93
|
-
// First and last lines should be horizontal-rule borders
|
|
94
|
-
// (DynamicBorder renders a row of "─" characters).
|
|
95
|
-
const firstStripped = stripAnsi(lines[0]);
|
|
96
|
-
const lastStripped = stripAnsi(lines[lines.length - 1]);
|
|
97
|
-
assert.ok(firstStripped.includes("─"), `first line should be a border (got: ${JSON.stringify(firstStripped)})`);
|
|
98
|
-
assert.ok(lastStripped.includes("─"), `last line should be a border (got: ${JSON.stringify(lastStripped)})`);
|
|
99
|
-
assert.ok(lines.length >= 3, `success notification must have at least 3 lines (top border, message, bottom border) — got ${lines.length}`);
|
|
100
|
-
});
|
|
101
|
-
it("the message text appears between the two borders", () => {
|
|
102
|
-
const c = buildSuccessNotification("Milestone M042 ready.");
|
|
103
|
-
const lines = c.render(40);
|
|
104
|
-
const messageRow = lines.findIndex((l) => stripAnsi(l).includes("Milestone M042 ready."));
|
|
105
|
-
assert.ok(messageRow > 0, "message must appear after the top border");
|
|
106
|
-
assert.ok(messageRow < lines.length - 1, "message must appear before the bottom border");
|
|
107
|
-
});
|
|
108
|
-
it("borders carry ANSI styling (not plain dim text like showStatus)", () => {
|
|
109
|
-
// Goodhart-resistant: if a regression routed success through
|
|
110
|
-
// showStatus, the borders would be missing AND the rendered text
|
|
111
|
-
// would be plain (or only dim-styled, not success-colored). We
|
|
112
|
-
// observe the border lines carry ANSI escape codes — the literal
|
|
113
|
-
// foreground color depends on the theme (which is system-dependent
|
|
114
|
-
// in CI), so we don't pin a specific code but DO require styling
|
|
115
|
-
// to be present.
|
|
116
|
-
const c = buildSuccessNotification("ok");
|
|
117
|
-
const lines = c.render(40);
|
|
118
|
-
const top = lines[0];
|
|
119
|
-
assert.ok(top !== stripAnsi(top), "top border must contain ANSI styling — a plain unstyled border " +
|
|
120
|
-
"would indicate the rendering bypassed theme.fg('success', ...)");
|
|
121
|
-
});
|
|
122
|
-
it("plain Text status (the showStatus path) does NOT produce a bordered box", () => {
|
|
123
|
-
// Counter-test: this is what the bug looked like — a single dim Text
|
|
124
|
-
// with no surrounding border. If anyone ever "fixes" the showSuccess
|
|
125
|
-
// regression by also adding borders to showStatus, this test will
|
|
126
|
-
// surface the conflation.
|
|
127
|
-
const plain = new Text(theme.fg("dim", "Milestone M042 ready."), 1, 0);
|
|
128
|
-
const lines = plain.render(40);
|
|
129
|
-
const joined = lines.map(stripAnsi).join("\n");
|
|
130
|
-
assert.ok(!joined.includes("─"), "plain Text (showStatus path) must NOT contain border characters");
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
//# sourceMappingURL=tool-card-cleanup-and-success-runtime.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"tool-card-cleanup-and-success-runtime.test.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.ts"],"names":[],"mappings":"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","sourcesContent":["// Runtime regression tests for the post-compaction tool-card cleanup and the\n// green-bordered success-notification rendering. Replaces the source-grep\n// `src/tests/tui-running-and-success-box.test.ts` that was deleted in #4875\n// (tracked as #4872).\n//\n// The previous tests asserted on identifier presence and method signature\n// shape via regex. A regression that routed `success` notifications through\n// `showStatus` (dim text) by accident would not have failed because the\n// `showSuccess` method would still exist and still match the regex. These\n// tests instead drive the components through the actual scenario and assert\n// on rendered output.\n\nimport assert from \"node:assert/strict\";\nimport { describe, it, before } from \"node:test\";\n\nimport { Container, Text } from \"@gsd/pi-tui\";\nimport stripAnsi from \"strip-ansi\";\n\nimport { initTheme, theme } from \"../theme/theme.js\";\nimport { DynamicBorder } from \"./dynamic-border.js\";\nimport { ToolExecutionComponent } from \"./tool-execution.js\";\n\n// Theme is a globalThis-shared singleton that throws if not initialized.\n// Initialize once before any test that exercises themed rendering.\nbefore(() => {\n\tinitTheme(\"dark\");\n});\n\ninterface MockTui {\n\trenderCount: number;\n\trequestRender(): void;\n}\n\nfunction makeMockTUI(): MockTui {\n\treturn {\n\t\trenderCount: 0,\n\t\trequestRender() {\n\t\t\tthis.renderCount++;\n\t\t},\n\t};\n}\n\n// ─── Bug 1: tool cards stuck in \"Running\" after compaction ──────────────\n\ndescribe(\"ToolExecutionComponent post-compaction cleanup\", () => {\n\tit(\"renders 'Running' status while the tool call has no result\", () => {\n\t\t// Baseline: a freshly-constructed component (mid-stream) must show\n\t\t// the running badge — this is the state we need to flip OUT of when\n\t\t// compaction removes the result message.\n\t\tconst ui = makeMockTUI();\n\t\tconst c = new ToolExecutionComponent(\n\t\t\t\"read_file\",\n\t\t\t{ path: \"/tmp/x.txt\" },\n\t\t\t{},\n\t\t\tundefined,\n\t\t\tui as never,\n\t\t);\n\t\tconst rendered = c.render(60).map(stripAnsi).join(\"\\n\");\n\t\tassert.ok(\n\t\t\trendered.includes(\"Running\"),\n\t\t\t\"freshly constructed component should render 'Running' badge\",\n\t\t);\n\t});\n\n\tit(\"markHistoricalNoResult flips a stuck tool card OUT of 'Running'\", () => {\n\t\t// Real bug: after session-history replay (post-compaction or session\n\t\t// switch), tool calls without matching tool_result messages stay in\n\t\t// isPartial = true forever. markHistoricalNoResult must produce a\n\t\t// rendered output that no longer reads \"Running\".\n\t\tconst ui = makeMockTUI();\n\t\tconst c = new ToolExecutionComponent(\n\t\t\t\"read_file\",\n\t\t\t{ path: \"/tmp/x.txt\" },\n\t\t\t{},\n\t\t\tundefined,\n\t\t\tui as never,\n\t\t);\n\n\t\tc.markHistoricalNoResult();\n\n\t\tconst rendered = c.render(60).map(stripAnsi).join(\"\\n\");\n\t\tassert.ok(\n\t\t\t!rendered.includes(\"Running\"),\n\t\t\t\"after markHistoricalNoResult, the tool card must NOT render 'Running' — got:\\n\" +\n\t\t\t\trendered,\n\t\t);\n\t\tassert.ok(\n\t\t\trendered.includes(\"Done\"),\n\t\t\t\"flipped card should render 'Done' status (no-result success)\",\n\t\t);\n\t});\n\n\tit(\"markHistoricalNoResult is idempotent when a real result already exists\", () => {\n\t\t// Late-arriving stream events must not clobber legitimate results.\n\t\t// We observe the no-clobber via behaviour: a card that completed\n\t\t// with a real result keeps its \"Done\" badge and content even if\n\t\t// markHistoricalNoResult fires again afterwards.\n\t\tconst ui = makeMockTUI();\n\t\tconst c = new ToolExecutionComponent(\n\t\t\t\"read_file\",\n\t\t\t{ path: \"/tmp/x.txt\" },\n\t\t\t{},\n\t\t\tundefined,\n\t\t\tui as never,\n\t\t);\n\n\t\t// Use the public completion path the runtime calls.\n\t\tc.updateResult(\n\t\t\t{\n\t\t\t\tcontent: [{ type: \"text\", text: \"real-result-payload\" }],\n\t\t\t\tisError: false,\n\t\t\t},\n\t\t\tfalse, // isPartial = false → \"Done\"\n\t\t);\n\n\t\tconst before = c.render(60).map(stripAnsi).join(\"\\n\");\n\t\tassert.ok(before.includes(\"Done\"), \"after complete(), card shows 'Done'\");\n\n\t\tc.markHistoricalNoResult(); // must early-return\n\t\tconst after = c.render(60).map(stripAnsi).join(\"\\n\");\n\t\tassert.equal(\n\t\t\tafter,\n\t\t\tbefore,\n\t\t\t\"markHistoricalNoResult must NOT mutate state when a real result is already present\",\n\t\t);\n\t});\n});\n\n// ─── Bug 2: success notifications render as a green bordered box ────────\n\ndescribe(\"Success-notification rendering — DynamicBorder + success theme\", () => {\n\t// We drive the same component composition that interactive-mode.showSuccess\n\t// builds (DynamicBorder + Text + DynamicBorder, all themed with\n\t// theme.fg(\"success\", …)) and assert on the rendered output, rather than\n\t// trying to instantiate the full InteractiveMode (which requires a real\n\t// session, bridge, and TUI runtime).\n\tfunction buildSuccessNotification(message: string): Container {\n\t\tconst c = new Container();\n\t\tconst successColor = (text: string) => theme.fg(\"success\", text);\n\t\tc.addChild(new DynamicBorder(successColor));\n\t\tc.addChild(new Text(theme.fg(\"success\", message), 1, 0));\n\t\tc.addChild(new DynamicBorder(successColor));\n\t\treturn c;\n\t}\n\n\tit(\"renders a top and bottom horizontal border framing the message\", () => {\n\t\tconst c = buildSuccessNotification(\"Milestone M042 ready.\");\n\t\tconst lines = c.render(40);\n\n\t\t// First and last lines should be horizontal-rule borders\n\t\t// (DynamicBorder renders a row of \"─\" characters).\n\t\tconst firstStripped = stripAnsi(lines[0]);\n\t\tconst lastStripped = stripAnsi(lines[lines.length - 1]);\n\n\t\tassert.ok(\n\t\t\tfirstStripped.includes(\"─\"),\n\t\t\t`first line should be a border (got: ${JSON.stringify(firstStripped)})`,\n\t\t);\n\t\tassert.ok(\n\t\t\tlastStripped.includes(\"─\"),\n\t\t\t`last line should be a border (got: ${JSON.stringify(lastStripped)})`,\n\t\t);\n\t\tassert.ok(\n\t\t\tlines.length >= 3,\n\t\t\t`success notification must have at least 3 lines (top border, message, bottom border) — got ${lines.length}`,\n\t\t);\n\t});\n\n\tit(\"the message text appears between the two borders\", () => {\n\t\tconst c = buildSuccessNotification(\"Milestone M042 ready.\");\n\t\tconst lines = c.render(40);\n\n\t\tconst messageRow = lines.findIndex((l) => stripAnsi(l).includes(\"Milestone M042 ready.\"));\n\t\tassert.ok(messageRow > 0, \"message must appear after the top border\");\n\t\tassert.ok(\n\t\t\tmessageRow < lines.length - 1,\n\t\t\t\"message must appear before the bottom border\",\n\t\t);\n\t});\n\n\tit(\"borders carry ANSI styling (not plain dim text like showStatus)\", () => {\n\t\t// Goodhart-resistant: if a regression routed success through\n\t\t// showStatus, the borders would be missing AND the rendered text\n\t\t// would be plain (or only dim-styled, not success-colored). We\n\t\t// observe the border lines carry ANSI escape codes — the literal\n\t\t// foreground color depends on the theme (which is system-dependent\n\t\t// in CI), so we don't pin a specific code but DO require styling\n\t\t// to be present.\n\t\tconst c = buildSuccessNotification(\"ok\");\n\t\tconst lines = c.render(40);\n\t\tconst top = lines[0];\n\t\tassert.ok(\n\t\t\ttop !== stripAnsi(top),\n\t\t\t\"top border must contain ANSI styling — a plain unstyled border \" +\n\t\t\t\t\"would indicate the rendering bypassed theme.fg('success', ...)\",\n\t\t);\n\t});\n\n\tit(\"plain Text status (the showStatus path) does NOT produce a bordered box\", () => {\n\t\t// Counter-test: this is what the bug looked like — a single dim Text\n\t\t// with no surrounding border. If anyone ever \"fixes\" the showSuccess\n\t\t// regression by also adding borders to showStatus, this test will\n\t\t// surface the conflation.\n\t\tconst plain = new Text(theme.fg(\"dim\", \"Milestone M042 ready.\"), 1, 0);\n\t\tconst lines = plain.render(40);\n\t\tconst joined = lines.map(stripAnsi).join(\"\\n\");\n\t\tassert.ok(\n\t\t\t!joined.includes(\"─\"),\n\t\t\t\"plain Text (showStatus path) must NOT contain border characters\",\n\t\t);\n\t});\n});\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"system-prompt-cache-stability.test.d.ts","sourceRoot":"","sources":["../../src/tests/system-prompt-cache-stability.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
// @gsd/pi-coding-agent + system-prompt-cache-stability.test — regression
|
|
2
|
-
// coverage for #5019. The system prompt must NOT include a per-call timestamp
|
|
3
|
-
// by default; embedding `Date.toLocaleString()` in the cached prefix
|
|
4
|
-
// invalidates Anthropic prompt-cache hits on every request and incurs the
|
|
5
|
-
// cache-write premium. The opt-in `includeDateTime` flag preserves the prior
|
|
6
|
-
// behavior for callers that explicitly want clock awareness.
|
|
7
|
-
import test from "node:test";
|
|
8
|
-
import assert from "node:assert/strict";
|
|
9
|
-
import { buildSystemPrompt } from "../core/system-prompt.js";
|
|
10
|
-
// ─── Default branch (no customPrompt) ──────────────────────────────────────
|
|
11
|
-
test("buildSystemPrompt: default omits 'Current date and time' line", () => {
|
|
12
|
-
const prompt = buildSystemPrompt({ selectedTools: ["read", "edit"] });
|
|
13
|
-
assert.ok(!prompt.includes("Current date and time:"), `prompt should not include the dateTime line by default. Got:\n${prompt}`);
|
|
14
|
-
});
|
|
15
|
-
test("buildSystemPrompt: includeDateTime=true emits 'Current date and time' line", () => {
|
|
16
|
-
const prompt = buildSystemPrompt({
|
|
17
|
-
selectedTools: ["read", "edit"],
|
|
18
|
-
includeDateTime: true,
|
|
19
|
-
});
|
|
20
|
-
assert.match(prompt, /Current date and time: /, "prompt should include the dateTime line when explicitly opted in");
|
|
21
|
-
});
|
|
22
|
-
test("buildSystemPrompt: includeDateTime=false explicit also omits the line", () => {
|
|
23
|
-
const prompt = buildSystemPrompt({
|
|
24
|
-
selectedTools: ["read", "edit"],
|
|
25
|
-
includeDateTime: false,
|
|
26
|
-
});
|
|
27
|
-
assert.ok(!prompt.includes("Current date and time:"));
|
|
28
|
-
});
|
|
29
|
-
// ─── Custom-prompt branch ──────────────────────────────────────────────────
|
|
30
|
-
test("buildSystemPrompt (customPrompt): default omits 'Current date and time' line", () => {
|
|
31
|
-
const prompt = buildSystemPrompt({
|
|
32
|
-
customPrompt: "CUSTOM BASE",
|
|
33
|
-
selectedTools: ["read"],
|
|
34
|
-
});
|
|
35
|
-
assert.ok(!prompt.includes("Current date and time:"));
|
|
36
|
-
});
|
|
37
|
-
test("buildSystemPrompt (customPrompt): includeDateTime=true emits the line", () => {
|
|
38
|
-
const prompt = buildSystemPrompt({
|
|
39
|
-
customPrompt: "CUSTOM BASE",
|
|
40
|
-
selectedTools: ["read"],
|
|
41
|
-
includeDateTime: true,
|
|
42
|
-
});
|
|
43
|
-
assert.match(prompt, /Current date and time: /);
|
|
44
|
-
});
|
|
45
|
-
// ─── Cache-stability invariant ─────────────────────────────────────────────
|
|
46
|
-
test("buildSystemPrompt: two back-to-back default calls produce identical prompts", async () => {
|
|
47
|
-
// The bug: the previous default-on `dateTime` line included `second: "2-digit"`,
|
|
48
|
-
// so two calls within the same second could match but any longer gap busted
|
|
49
|
-
// the cache. Asserting equality across a deliberate sub-second sleep proves
|
|
50
|
-
// the byte-for-byte stability that Anthropic prompt caching requires.
|
|
51
|
-
const first = buildSystemPrompt({ selectedTools: ["read", "edit"], cwd: "/tmp/example" });
|
|
52
|
-
await new Promise((resolve) => setTimeout(resolve, 1100));
|
|
53
|
-
const second = buildSystemPrompt({ selectedTools: ["read", "edit"], cwd: "/tmp/example" });
|
|
54
|
-
assert.equal(first, second, "system prompt must be byte-for-byte stable across calls so the prompt cache can hit");
|
|
55
|
-
});
|
|
56
|
-
test("buildSystemPrompt: includeDateTime=true intentionally produces different prompts across the second boundary", async () => {
|
|
57
|
-
// Inverse of the cache-stability test: when callers opt in, the dateTime
|
|
58
|
-
// line is expected to vary. This documents the trade-off the flag exists
|
|
59
|
-
// to surface.
|
|
60
|
-
const first = buildSystemPrompt({
|
|
61
|
-
selectedTools: ["read"],
|
|
62
|
-
cwd: "/tmp/example",
|
|
63
|
-
includeDateTime: true,
|
|
64
|
-
});
|
|
65
|
-
await new Promise((resolve) => setTimeout(resolve, 1100));
|
|
66
|
-
const second = buildSystemPrompt({
|
|
67
|
-
selectedTools: ["read"],
|
|
68
|
-
cwd: "/tmp/example",
|
|
69
|
-
includeDateTime: true,
|
|
70
|
-
});
|
|
71
|
-
assert.notEqual(first, second, "opt-in dateTime is expected to vary across the second boundary");
|
|
72
|
-
});
|
|
73
|
-
// ─── Cwd preservation ──────────────────────────────────────────────────────
|
|
74
|
-
test("buildSystemPrompt: 'Current working directory' line is preserved (only dateTime is removed)", () => {
|
|
75
|
-
const prompt = buildSystemPrompt({ selectedTools: ["read"], cwd: "/tmp/example" });
|
|
76
|
-
assert.match(prompt, /Current working directory: \/tmp\/example/);
|
|
77
|
-
});
|
|
78
|
-
//# sourceMappingURL=system-prompt-cache-stability.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"system-prompt-cache-stability.test.js","sourceRoot":"","sources":["../../src/tests/system-prompt-cache-stability.test.ts"],"names":[],"mappings":"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","sourcesContent":["// @gsd/pi-coding-agent + system-prompt-cache-stability.test — regression\n// coverage for #5019. The system prompt must NOT include a per-call timestamp\n// by default; embedding `Date.toLocaleString()` in the cached prefix\n// invalidates Anthropic prompt-cache hits on every request and incurs the\n// cache-write premium. The opt-in `includeDateTime` flag preserves the prior\n// behavior for callers that explicitly want clock awareness.\n\nimport test from \"node:test\";\nimport assert from \"node:assert/strict\";\n\nimport { buildSystemPrompt } from \"../core/system-prompt.js\";\n\n// ─── Default branch (no customPrompt) ──────────────────────────────────────\n\ntest(\"buildSystemPrompt: default omits 'Current date and time' line\", () => {\n\tconst prompt = buildSystemPrompt({ selectedTools: [\"read\", \"edit\"] });\n\tassert.ok(\n\t\t!prompt.includes(\"Current date and time:\"),\n\t\t`prompt should not include the dateTime line by default. Got:\\n${prompt}`,\n\t);\n});\n\ntest(\"buildSystemPrompt: includeDateTime=true emits 'Current date and time' line\", () => {\n\tconst prompt = buildSystemPrompt({\n\t\tselectedTools: [\"read\", \"edit\"],\n\t\tincludeDateTime: true,\n\t});\n\tassert.match(\n\t\tprompt,\n\t\t/Current date and time: /,\n\t\t\"prompt should include the dateTime line when explicitly opted in\",\n\t);\n});\n\ntest(\"buildSystemPrompt: includeDateTime=false explicit also omits the line\", () => {\n\tconst prompt = buildSystemPrompt({\n\t\tselectedTools: [\"read\", \"edit\"],\n\t\tincludeDateTime: false,\n\t});\n\tassert.ok(!prompt.includes(\"Current date and time:\"));\n});\n\n// ─── Custom-prompt branch ──────────────────────────────────────────────────\n\ntest(\"buildSystemPrompt (customPrompt): default omits 'Current date and time' line\", () => {\n\tconst prompt = buildSystemPrompt({\n\t\tcustomPrompt: \"CUSTOM BASE\",\n\t\tselectedTools: [\"read\"],\n\t});\n\tassert.ok(!prompt.includes(\"Current date and time:\"));\n});\n\ntest(\"buildSystemPrompt (customPrompt): includeDateTime=true emits the line\", () => {\n\tconst prompt = buildSystemPrompt({\n\t\tcustomPrompt: \"CUSTOM BASE\",\n\t\tselectedTools: [\"read\"],\n\t\tincludeDateTime: true,\n\t});\n\tassert.match(prompt, /Current date and time: /);\n});\n\n// ─── Cache-stability invariant ─────────────────────────────────────────────\n\ntest(\"buildSystemPrompt: two back-to-back default calls produce identical prompts\", async () => {\n\t// The bug: the previous default-on `dateTime` line included `second: \"2-digit\"`,\n\t// so two calls within the same second could match but any longer gap busted\n\t// the cache. Asserting equality across a deliberate sub-second sleep proves\n\t// the byte-for-byte stability that Anthropic prompt caching requires.\n\tconst first = buildSystemPrompt({ selectedTools: [\"read\", \"edit\"], cwd: \"/tmp/example\" });\n\tawait new Promise((resolve) => setTimeout(resolve, 1100));\n\tconst second = buildSystemPrompt({ selectedTools: [\"read\", \"edit\"], cwd: \"/tmp/example\" });\n\tassert.equal(\n\t\tfirst,\n\t\tsecond,\n\t\t\"system prompt must be byte-for-byte stable across calls so the prompt cache can hit\",\n\t);\n});\n\ntest(\"buildSystemPrompt: includeDateTime=true intentionally produces different prompts across the second boundary\", async () => {\n\t// Inverse of the cache-stability test: when callers opt in, the dateTime\n\t// line is expected to vary. This documents the trade-off the flag exists\n\t// to surface.\n\tconst first = buildSystemPrompt({\n\t\tselectedTools: [\"read\"],\n\t\tcwd: \"/tmp/example\",\n\t\tincludeDateTime: true,\n\t});\n\tawait new Promise((resolve) => setTimeout(resolve, 1100));\n\tconst second = buildSystemPrompt({\n\t\tselectedTools: [\"read\"],\n\t\tcwd: \"/tmp/example\",\n\t\tincludeDateTime: true,\n\t});\n\tassert.notEqual(first, second, \"opt-in dateTime is expected to vary across the second boundary\");\n});\n\n// ─── Cwd preservation ──────────────────────────────────────────────────────\n\ntest(\"buildSystemPrompt: 'Current working directory' line is preserved (only dateTime is removed)\", () => {\n\tconst prompt = buildSystemPrompt({ selectedTools: [\"read\"], cwd: \"/tmp/example\" });\n\tassert.match(prompt, /Current working directory: \\/tmp\\/example/);\n});\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"token-telemetry.test.d.ts","sourceRoot":"","sources":["../../src/tests/token-telemetry.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,181 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"token-telemetry.test.js","sourceRoot":"","sources":["../../src/tests/token-telemetry.test.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,kCAAkC;AAClC,mEAAmE;AACnE,+DAA+D;AAC/D,8EAA8E;AAC9E,sEAAsE;AAEtE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAClE,OAAO,MAAM,MAAM,oBAAoB,CAAC;AAIxC,OAAO,EAAE,yBAAyB,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAE3F,SAAS,oBAAoB,CAAC,YAAuC,EAAE;IACtE,OAAO;QACN,IAAI,EAAE,WAAW;QACjB,OAAO,EAAE,EAAE;QACX,GAAG,EAAE,oBAAoB;QACzB,QAAQ,EAAE,WAAW;QACrB,KAAK,EAAE,mBAAmB;QAC1B,KAAK,EAAE;YACN,KAAK,EAAE,GAAG;YACV,MAAM,EAAE,EAAE;YACV,SAAS,EAAE,CAAC;YACZ,UAAU,EAAE,CAAC;YACb,WAAW,EAAE,GAAG;YAChB,IAAI,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE;SAC5E;QACD,UAAU,EAAE,MAAM;QAClB,SAAS,EAAE,aAAa;QACxB,GAAG,SAAS;KACZ,CAAC;AACH,CAAC;AAED,8EAA8E;AAE9E,QAAQ,CAAC,2BAA2B,EAAE,GAAG,EAAE;IAC1C,IAAI,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACvD,MAAM,GAAG,GAAG,oBAAoB,EAAE,CAAC;QACnC,MAAM,MAAM,GAAG,yBAAyB,CAAC,GAAG,CAAC,CAAC;QAC9C,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE;YACxB,EAAE,EAAE,aAAa;YACjB,KAAK,EAAE,mBAAmB;YAC1B,UAAU,EAAE,MAAM;YAClB,KAAK,EAAE,GAAG;YACV,MAAM,EAAE,EAAE;YACV,SAAS,EAAE,CAAC;YACZ,UAAU,EAAE,CAAC;YACb,SAAS,EAAE,IAAI;YACf,aAAa,EAAE,CAAC;SAChB,CAAC,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,yDAAyD,EAAE,GAAG,EAAE;QACpE,MAAM,GAAG,GAAG,oBAAoB,CAAC;YAChC,KAAK,EAAE;gBACN,KAAK,EAAE,GAAG;gBACV,MAAM,EAAE,EAAE;gBACV,SAAS,EAAE,GAAG;gBACd,UAAU,EAAE,CAAC;gBACb,WAAW,EAAE,IAAI;gBACjB,IAAI,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE;aAC/E;SACD,CAAC,CAAC;QACH,MAAM,MAAM,GAAG,yBAAyB,CAAC,GAAG,CAAC,CAAC;QAC9C,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;QACpC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QAChC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,wEAAwE,EAAE,GAAG,EAAE;QACnF,MAAM,GAAG,GAAG,oBAAoB,CAAC;YAChC,KAAK,EAAE;gBACN,KAAK,EAAE,CAAC;gBACR,MAAM,EAAE,EAAE;gBACV,SAAS,EAAE,CAAC;gBACZ,UAAU,EAAE,CAAC;gBACb,WAAW,EAAE,EAAE;gBACf,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE;aAC1E;SACD,CAAC,CAAC;QACH,MAAM,MAAM,GAAG,yBAAyB,CAAC,GAAG,CAAC,CAAC;QAC9C,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,0DAA0D,EAAE,GAAG,EAAE;QACrE,MAAM,GAAG,GAAG,oBAAoB,CAAC;YAChC,KAAK,EAAE;gBACN,KAAK,EAAE,CAAC;gBACR,MAAM,EAAE,EAAE;gBACV,SAAS,EAAE,IAAI;gBACf,UAAU,EAAE,CAAC;gBACb,WAAW,EAAE,IAAI;gBACjB,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,UAAU,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE;aAC5E;SACD,CAAC,CAAC;QACH,MAAM,MAAM,GAAG,yBAAyB,CAAC,GAAG,CAAC,CAAC;QAC9C,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,4EAA4E,EAAE,GAAG,EAAE;QACvF,MAAM,GAAG,GAAG,oBAAoB,CAAC;YAChC,KAAK,EAAE;gBACN,KAAK,EAAE,EAAE;gBACT,MAAM,EAAE,GAAG;gBACX,SAAS,EAAE,CAAC;gBACZ,UAAU,EAAE,IAAI;gBAChB,WAAW,EAAE,IAAI;gBACjB,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE;aAChF;SACD,CAAC,CAAC;QACH,MAAM,MAAM,GAAG,yBAAyB,CAAC,GAAG,CAAC,CAAC;QAC9C,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;QACtC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC,EAAE,4CAA4C,CAAC,CAAC;IACrF,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAClD,MAAM,GAAG,GAAG,oBAAoB,CAAC,EAAE,UAAU,EAAE,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,CAAC,CAAC;QACtF,MAAM,CAAC,KAAK,CAAC,yBAAyB,CAAC,GAAG,CAAC,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IAClE,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,8EAA8E;AAE9E,QAAQ,CAAC,oBAAoB,EAAE,GAAG,EAAE;IACnC,IAAI,QAAkB,CAAC;IACvB,IAAI,aAA0C,CAAC;IAC/C,IAAI,WAA+B,CAAC;IAEpC,UAAU,CAAC,GAAG,EAAE;QACf,QAAQ,GAAG,EAAE,CAAC;QACd,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAC1D,0EAA0E;QAC1E,OAAO,CAAC,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,KAA0B,EAAW,EAAE;YAC/D,QAAQ,CAAC,IAAI,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;YACjF,OAAO,IAAI,CAAC;QACb,CAAC,CAAgC,CAAC;QAClC,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;IAC9C,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,GAAG,EAAE;QACd,OAAO,CAAC,MAAM,CAAC,KAAK,GAAG,aAAa,CAAC;QACrC,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;YAC/B,OAAO,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;QACvC,CAAC;aAAM,CAAC;YACP,OAAO,CAAC,GAAG,CAAC,kBAAkB,GAAG,WAAW,CAAC;QAC9C,CAAC;IACF,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,mCAAmC,EAAE,GAAG,EAAE;QAC9C,OAAO,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;QACtC,kBAAkB,CAAC,oBAAoB,EAAE,CAAC,CAAC;QAC3C,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAClC,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACtD,OAAO,CAAC,GAAG,CAAC,kBAAkB,GAAG,MAAM,CAAC,CAAC,oBAAoB;QAC7D,kBAAkB,CAAC,oBAAoB,EAAE,CAAC,CAAC;QAC3C,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,0CAA0C,CAAC,CAAC;IAC9E,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC/D,OAAO,CAAC,GAAG,CAAC,kBAAkB,GAAG,GAAG,CAAC;QACrC,kBAAkB,CAAC,oBAAoB,EAAE,CAAC,CAAC;QAC3C,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QACjC,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,kCAAkC,CAAC,CAAC;QAC1E,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;QACjD,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,mBAAmB,CAAC,CAAC;QAChD,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QAChC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACjC,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAClD,OAAO,CAAC,GAAG,CAAC,kBAAkB,GAAG,GAAG,CAAC;QACrC,kBAAkB,CAAC,oBAAoB,EAAE,CAAC,CAAC;QAC3C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;QACjD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;QACxC,MAAM,CAAC,SAAS,CAAC,IAAI,EAAE;YACtB,eAAe;YACf,WAAW;YACX,YAAY;YACZ,WAAW;YACX,OAAO;YACP,OAAO;YACP,QAAQ;YACR,YAAY;YACZ,IAAI;SACJ,CAAC,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,wDAAwD,EAAE,GAAG,EAAE;QACnE,OAAO,CAAC,GAAG,CAAC,kBAAkB,GAAG,GAAG,CAAC;QACrC,sDAAsD;QACtD,OAAO,CAAC,MAAM,CAAC,KAAK,GAAG,CAAC,GAAG,EAAE;YAC5B,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;QAC7C,CAAC,CAAgC,CAAC;QAClC,MAAM,CAAC,YAAY,CAAC,GAAG,EAAE,CAAC,kBAAkB,CAAC,oBAAoB,EAAE,CAAC,CAAC,CAAC;IACvE,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC","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,77 +0,0 @@
|
|
|
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
|
-
}
|