@vellumai/assistant 0.8.4 → 0.8.5
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/ARCHITECTURE.md +2 -2
- package/docs/browser-use-architecture-phase2.md +1 -1
- package/knip.json +2 -1
- package/openapi.yaml +809 -11
- package/package.json +1 -1
- package/src/__tests__/anthropic-provider.test.ts +34 -37
- package/src/__tests__/assistant-event-hub-self-exclusion.test.ts +293 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +3 -3
- package/src/__tests__/audit-log-rotation.test.ts +70 -16
- package/src/__tests__/background-workers-disk-pressure.test.ts +3 -3
- package/src/__tests__/btw-routes.test.ts +2 -3
- package/src/__tests__/call-controller.test.ts +0 -1
- package/src/__tests__/cancel-resolves-conversation-key.test.ts +1 -1
- package/src/__tests__/channel-guardian.test.ts +3 -3
- package/src/__tests__/checker.test.ts +6 -15
- package/src/__tests__/compaction-events.test.ts +1 -0
- package/src/__tests__/compactor-call-site-logging.test.ts +214 -0
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +5 -11
- package/src/__tests__/computer-use-tools.test.ts +2 -4
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +197 -2
- package/src/__tests__/conversation-agent-loop.test.ts +163 -122
- package/src/__tests__/conversation-app-control-instantiation.test.ts +2 -5
- package/src/__tests__/conversation-clear-safety.test.ts +25 -25
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +1 -1
- package/src/__tests__/conversation-disk-view-integration.test.ts +2 -2
- package/src/__tests__/conversation-error.test.ts +31 -0
- package/src/__tests__/conversation-fork-crud.test.ts +178 -15
- package/src/__tests__/conversation-lifecycle.test.ts +52 -11
- package/src/__tests__/{conversation-load-cleaned-at.test.ts → conversation-load-history-stripped.test.ts} +13 -13
- package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -0
- package/src/__tests__/conversation-routes-disk-view.test.ts +109 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +35 -0
- package/src/__tests__/conversation-skill-tools.test.ts +2 -5
- package/src/__tests__/conversation-store.test.ts +1 -1
- package/src/__tests__/conversation-sync-tags.test.ts +99 -32
- package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +1 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
- package/src/__tests__/credential-execution-feature-gates.test.ts +9 -7
- package/src/__tests__/credential-execution-tools.test.ts +6 -6
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/credential-vault-unit.test.ts +2 -2
- package/src/__tests__/dynamic-page-surface.test.ts +2 -2
- package/src/__tests__/email-html-renderer.test.ts +12 -0
- package/src/__tests__/gateway-flag-listener.test.ts +237 -0
- package/src/__tests__/gemini-provider.test.ts +78 -0
- package/src/__tests__/guardian-dispatch.test.ts +0 -1
- package/src/__tests__/guardian-outbound-http.test.ts +7 -5
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
- package/src/__tests__/heartbeat-disk-pressure.test.ts +4 -0
- package/src/__tests__/heartbeat-service.test.ts +4 -0
- package/src/__tests__/host-shell-tool.test.ts +1 -1
- package/src/__tests__/init-feature-flag-overrides.test.ts +5 -6
- package/src/__tests__/list-messages-tool-merge.test.ts +70 -11
- package/src/__tests__/llm-request-log-call-site.test.ts +136 -0
- package/src/__tests__/llm-request-log-source-clickhouse.test.ts +26 -0
- package/src/__tests__/llm-resolver.test.ts +77 -9
- package/src/__tests__/llm-usage-store.test.ts +66 -0
- package/src/__tests__/logger.test.ts +89 -0
- package/src/__tests__/mcp-abort-signal.test.ts +2 -2
- package/src/__tests__/media-generate-image.test.ts +31 -0
- package/src/__tests__/memory-v2-static-injector.test.ts +7 -7
- package/src/__tests__/model-intents.test.ts +2 -4
- package/src/__tests__/notification-guardian-path.test.ts +0 -1
- package/src/__tests__/onboarding-template-contract.test.ts +1 -1
- package/src/__tests__/openai-provider.test.ts +46 -0
- package/src/__tests__/openai-responses-provider.test.ts +114 -12
- package/src/__tests__/pending-interactions-resolved-event.test.ts +0 -1
- package/src/__tests__/platform-bash-auto-approve.test.ts +2 -2
- package/src/__tests__/platform.test.ts +2 -2
- package/src/__tests__/plugin-api-tool-definition.test.ts +92 -0
- package/src/__tests__/plugin-bootstrap.test.ts +2 -2
- package/src/__tests__/plugin-tool-contribution.test.ts +13 -6
- package/src/__tests__/plugin-types.test.ts +3 -2
- package/src/__tests__/prechat-onboarding-contract.test.ts +131 -98
- package/src/__tests__/pricing.test.ts +12 -0
- package/src/__tests__/prune-jobs-changes-parser.test.ts +61 -0
- package/src/__tests__/registry.test.ts +2 -8
- package/src/__tests__/require-fresh-approval.test.ts +2 -2
- package/src/__tests__/runtime-events-sse-bilingual.test.ts +154 -0
- package/src/__tests__/shell-tool-proxy-mode.test.ts +1 -1
- package/src/__tests__/skill-feature-flags.test.ts +2 -2
- package/src/__tests__/skill-projection-feature-flag.test.ts +4 -7
- package/src/__tests__/skill-projection.benchmark.test.ts +2 -6
- package/src/__tests__/skill-tool-factory.test.ts +1 -1
- package/src/__tests__/subagent-notify-parent.test.ts +1 -1
- package/src/__tests__/suggestion-routes.test.ts +1 -0
- package/src/__tests__/sync-message-contract.test.ts +59 -0
- package/src/__tests__/system-prompt.test.ts +145 -131
- package/src/__tests__/terminal-tools.test.ts +1 -1
- package/src/__tests__/tool-approval-handler.test.ts +1 -5
- package/src/__tests__/tool-execute-pipeline.test.ts +2 -2
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -5
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +15 -5
- package/src/__tests__/tool-executor.test.ts +9 -62
- package/src/__tests__/tool-grant-request-escalation.test.ts +1 -6
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +1 -6
- package/src/__tests__/trusted-contact-multichannel.test.ts +0 -1
- package/src/__tests__/ui-file-upload-surface.test.ts +2 -2
- package/src/__tests__/usage-routes.test.ts +3 -0
- package/src/__tests__/verification-control-plane-policy.test.ts +2 -2
- package/src/__tests__/workspace-git-service.test.ts +6 -5
- package/src/__tests__/workspace-migration-089-move-memory-tree-out-of-v3.test.ts +86 -0
- package/src/acp/__tests__/prepare-agent-env.test.ts +146 -0
- package/src/acp/prepare-agent-env.ts +78 -0
- package/src/acp/session-manager.ts +1 -1
- package/src/agent/loop.ts +8 -0
- package/src/api/README.md +5 -0
- package/src/api/index.ts +4 -0
- package/src/api/package.json +10 -0
- package/src/background-wake/background-wake-routes.test.ts +233 -0
- package/src/background-wake/runtime-registry.ts +24 -0
- package/src/cli/commands/__tests__/browser.test.ts +23 -5
- package/src/cli/commands/__tests__/domain-register.test.ts +110 -0
- package/src/cli/commands/__tests__/domain-status.test.ts +33 -33
- package/src/cli/commands/__tests__/inference-send.test.ts +108 -5
- package/src/cli/commands/__tests__/memory-v2-compare-render.test.ts +98 -0
- package/src/cli/commands/__tests__/memory-v2.test.ts +1 -0
- package/src/cli/commands/__tests__/memory-v3-render.test.ts +340 -0
- package/src/cli/commands/browser.ts +247 -0
- package/src/cli/commands/domain.ts +91 -41
- package/src/cli/commands/inference.ts +93 -40
- package/src/cli/commands/memory-v2-compare-render.ts +115 -0
- package/src/cli/commands/memory-v2.ts +176 -1
- package/src/cli/commands/memory-v3-render.ts +344 -0
- package/src/cli/commands/memory-v3.ts +316 -0
- package/src/cli/program.ts +2 -0
- package/src/config/assistant-feature-flags.ts +21 -9
- package/src/config/bundled-skills/document-editor/SKILL.md +11 -2
- package/src/config/bundled-skills/document-editor/TOOLS.json +18 -0
- package/src/config/bundled-skills/document-editor/tools/document-open.ts +12 -0
- package/src/config/bundled-skills/image-studio/SKILL.md +4 -0
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +2 -2
- package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +13 -8
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +10 -3
- package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +16 -14
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +7 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +7 -2
- package/src/config/bundled-tool-registry.ts +2 -0
- package/src/config/call-site-defaults.ts +7 -6
- package/src/config/feature-flag-registry.json +16 -0
- package/src/config/schemas/__tests__/memory-v2.test.ts +213 -1
- package/src/config/schemas/call-site-catalog.ts +21 -7
- package/src/config/schemas/llm.ts +12 -1
- package/src/config/schemas/memory-v2.ts +246 -0
- package/src/config/schemas/memory.ts +2 -1
- package/src/context/compactor.ts +52 -0
- package/src/conversations/__tests__/message-consolidation.test.ts +350 -0
- package/src/conversations/message-consolidation.ts +404 -0
- package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +1 -1
- package/src/daemon/__tests__/meet-manifest-loader.test.ts +1 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +2 -13
- package/src/daemon/conversation-agent-loop.ts +126 -76
- package/src/daemon/conversation-error.ts +31 -1
- package/src/daemon/conversation-lifecycle.ts +27 -22
- package/src/daemon/conversation-runtime-assembly.ts +10 -9
- package/src/daemon/conversation-tool-setup.ts +63 -3
- package/src/daemon/conversation-usage.ts +2 -0
- package/src/daemon/conversation.ts +14 -29
- package/src/daemon/disk-pressure-guard.ts +14 -2
- package/src/daemon/handlers/config-model.test.ts +1 -0
- package/src/daemon/handlers/conversations.ts +11 -3
- package/src/daemon/host-browser-proxy.ts +5 -5
- package/src/daemon/host-cu-proxy.ts +4 -4
- package/src/daemon/host-file-proxy.ts +4 -4
- package/src/daemon/host-proxy-base.ts +4 -4
- package/src/daemon/host-transfer-proxy.ts +10 -10
- package/src/daemon/lifecycle.ts +23 -20
- package/src/daemon/meet-manifest-loader.ts +1 -7
- package/src/daemon/message-types/conversations.ts +6 -9
- package/src/daemon/message-types/home.ts +1 -13
- package/src/daemon/message-types/messages.ts +6 -14
- package/src/daemon/message-types/sync.ts +14 -0
- package/src/daemon/shutdown-handlers.ts +24 -5
- package/src/daemon/switch-inference-profile-tool.ts +52 -0
- package/src/daemon/tool-setup-types.ts +13 -0
- package/src/events/relationship-state-updated.ts +25 -0
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +1 -1
- package/src/home/home-greeting.ts +0 -9
- package/src/home/suggested-prompts.ts +0 -9
- package/src/ipc/gateway-flag-listener.ts +123 -0
- package/src/ipc/skill-routes/registries.ts +8 -12
- package/src/memory/__tests__/db-async-query.test.ts +165 -0
- package/src/memory/__tests__/db-maintenance.test.ts +115 -0
- package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +241 -0
- package/src/memory/__tests__/jobs-store-job-classes.test.ts +28 -1
- package/src/memory/__tests__/memory-retrospective-job.test.ts +7 -0
- package/src/memory/auto-analysis-enqueue.ts +5 -1
- package/src/memory/conversation-crud.ts +71 -70
- package/src/memory/conversation-starters-cadence.ts +3 -1
- package/src/memory/conversation-title-service.ts +19 -3
- package/src/memory/db-async-query.ts +214 -0
- package/src/memory/db-init.ts +10 -0
- package/src/memory/db-maintenance.ts +30 -21
- package/src/memory/graph/bootstrap.ts +8 -1
- package/src/memory/graph/capability-seed.ts +7 -3
- package/src/memory/graph/conversation-graph-memory.ts +100 -17
- package/src/memory/graph/extraction.ts +1 -5
- package/src/memory/graph/graph-search.ts +7 -1
- package/src/memory/indexer.ts +28 -18
- package/src/memory/job-handlers/cleanup.ts +76 -18
- package/src/memory/job-handlers/conversation-starters.ts +1 -4
- package/src/memory/jobs/embed-pkb-file.ts +6 -1
- package/src/memory/jobs-store.ts +14 -0
- package/src/memory/jobs-worker.ts +55 -22
- package/src/memory/llm-request-log-source-clickhouse.ts +42 -2
- package/src/memory/llm-request-log-source-local.ts +7 -0
- package/src/memory/llm-request-log-source.ts +9 -2
- package/src/memory/llm-request-log-store.ts +43 -1
- package/src/memory/llm-usage-store.ts +24 -0
- package/src/memory/memory-retrospective-enqueue.ts +8 -1
- package/src/memory/memory-retrospective-job.ts +5 -0
- package/src/memory/memory-v2-activation-log-store.ts +15 -6
- package/src/memory/migrations/260-rename-cleaned-at.ts +44 -0
- package/src/memory/migrations/261-llm-usage-add-raw-usage.ts +36 -0
- package/src/memory/migrations/262-memory-v3-coactivation.ts +57 -0
- package/src/memory/migrations/263-memory-v3-auto-edges.ts +50 -0
- package/src/memory/migrations/264-llm-request-log-call-site.ts +29 -0
- package/src/memory/migrations/index.ts +17 -0
- package/src/memory/migrations/registry.ts +33 -0
- package/src/memory/schema/conversations.ts +1 -1
- package/src/memory/schema/infrastructure.ts +21 -0
- package/src/memory/tool-usage-store.ts +36 -8
- package/src/memory/v2/__tests__/consolidation-job.test.ts +1 -0
- package/src/memory/v2/__tests__/harness-compare.test.ts +186 -0
- package/src/memory/v2/__tests__/harness-metrics.test.ts +74 -0
- package/src/memory/v2/__tests__/harness-oracle.test.ts +257 -0
- package/src/memory/v2/__tests__/harness-replay-input.test.ts +225 -0
- package/src/memory/v2/__tests__/harness-runner.test.ts +109 -0
- package/src/memory/v2/__tests__/injection.test.ts +127 -98
- package/src/memory/v2/__tests__/qdrant.test.ts +36 -0
- package/src/memory/v2/__tests__/router.test.ts +171 -3
- package/src/memory/v2/harness/compare.ts +57 -0
- package/src/memory/v2/harness/metrics.ts +124 -0
- package/src/memory/v2/harness/oracle.ts +145 -0
- package/src/memory/v2/harness/replay-input.ts +224 -0
- package/src/memory/v2/harness/retriever.ts +74 -0
- package/src/memory/v2/harness/router-retriever.ts +43 -0
- package/src/memory/v2/harness/runner.ts +106 -0
- package/src/memory/v2/harness/trace.ts +58 -0
- package/src/memory/v2/injection.ts +21 -15
- package/src/memory/v2/prompts/router.ts +26 -1
- package/src/memory/v2/qdrant.ts +14 -2
- package/src/memory/v2/router.ts +171 -18
- package/src/memory/v3/__tests__/coactivation-store.test.ts +422 -0
- package/src/memory/v3/__tests__/consolidation-job.test.ts +468 -0
- package/src/memory/v3/__tests__/edge-learning-job.test.ts +324 -0
- package/src/memory/v3/__tests__/edges.test.ts +563 -0
- package/src/memory/v3/__tests__/filter.test.ts +512 -0
- package/src/memory/v3/__tests__/gate.test.ts +574 -0
- package/src/memory/v3/__tests__/index-composition.test.ts +233 -0
- package/src/memory/v3/__tests__/loop.test.ts +530 -0
- package/src/memory/v3/__tests__/retriever.test.ts +226 -0
- package/src/memory/v3/__tests__/scouts.test.ts +440 -0
- package/src/memory/v3/__tests__/shadow-middleware.test.ts +312 -0
- package/src/memory/v3/__tests__/system-prompts.test.ts +154 -0
- package/src/memory/v3/__tests__/traversal.test.ts +469 -0
- package/src/memory/v3/__tests__/tree-index.test.ts +280 -0
- package/src/memory/v3/__tests__/tree-store.test.ts +529 -0
- package/src/memory/v3/__tests__/tree-walk.test.ts +707 -0
- package/src/memory/v3/__tests__/validate.test.ts +245 -0
- package/src/memory/v3/auto-edges.ts +223 -0
- package/src/memory/v3/coactivation-store.ts +124 -0
- package/src/memory/v3/consolidation-job.ts +323 -0
- package/src/memory/v3/edge-learning-job.ts +160 -0
- package/src/memory/v3/edges.ts +249 -0
- package/src/memory/v3/filter.ts +281 -0
- package/src/memory/v3/gate.ts +334 -0
- package/src/memory/v3/index-composition.ts +113 -0
- package/src/memory/v3/llm-capture.ts +46 -0
- package/src/memory/v3/loop.ts +382 -0
- package/src/memory/v3/maintenance.ts +144 -0
- package/src/memory/v3/prompt-context.ts +33 -0
- package/src/memory/v3/prompts/consolidation.ts +458 -0
- package/src/memory/v3/prompts/system-prompts.ts +196 -0
- package/src/memory/v3/retriever.ts +33 -0
- package/src/memory/v3/scouts.ts +420 -0
- package/src/memory/v3/shadow-middleware.ts +305 -0
- package/src/memory/v3/traversal.ts +206 -0
- package/src/memory/v3/tree-index.ts +237 -0
- package/src/memory/v3/tree-store.ts +394 -0
- package/src/memory/v3/tree-walk.ts +351 -0
- package/src/memory/v3/types.ts +65 -0
- package/src/memory/v3/validate.ts +300 -0
- package/src/notifications/adapters/macos.ts +18 -1
- package/src/notifications/adapters/platform.ts +1 -1
- package/src/notifications/decision-engine.ts +1 -4
- package/src/notifications/emit-signal.ts +29 -49
- package/src/permissions/prompter.ts +3 -3
- package/src/permissions/question-prompter.ts +5 -2
- package/src/permissions/secret-prompter.ts +2 -2
- package/src/plugin-api/index.ts +4 -0
- package/src/plugin-api/types.ts +7 -33
- package/src/plugins/defaults/index.ts +6 -0
- package/src/plugins/defaults/injectors.ts +18 -11
- package/src/plugins/external-plugin-loader.ts +5 -68
- package/src/plugins/types.ts +11 -16
- package/src/proactive-artifact/aux-message-injector.ts +17 -4
- package/src/prompts/__tests__/task-progress-hint-section.test.ts +3 -9
- package/src/prompts/persona-resolver.ts +36 -21
- package/src/prompts/sections.ts +39 -7
- package/src/prompts/system-prompt.ts +50 -185
- package/src/prompts/templates/BOOTSTRAP.md +2 -2
- package/src/prompts/templates/system-sections.ts +230 -8
- package/src/providers/__tests__/connection-model-compat.test.ts +234 -0
- package/src/providers/__tests__/retry-callsite.test.ts +85 -5
- package/src/providers/anthropic/client.ts +32 -66
- package/src/providers/call-site-routing.ts +14 -2
- package/src/providers/connection-model-compat.ts +38 -0
- package/src/providers/connection-resolution.ts +16 -2
- package/src/providers/gemini/client.ts +49 -6
- package/src/providers/inference/adapter-factory.ts +3 -0
- package/src/providers/minimax/client.ts +106 -0
- package/src/providers/model-catalog.ts +43 -0
- package/src/providers/model-intents.ts +1 -1
- package/src/providers/openai/chat-completions-provider.ts +6 -3
- package/src/providers/openai/codex-models.ts +18 -0
- package/src/providers/openai/responses-provider.ts +78 -21
- package/src/providers/provider-send-message.ts +7 -1
- package/src/providers/retry.ts +34 -3
- package/src/providers/thinking-config.ts +26 -1
- package/src/providers/usage-tracking.ts +2 -0
- package/src/runtime/AGENTS.md +2 -2
- package/src/runtime/agent-wake.ts +1 -0
- package/src/runtime/assistant-event-hub.ts +76 -6
- package/src/runtime/auth/route-policy.ts +36 -0
- package/src/runtime/btw-sidechain.ts +0 -6
- package/src/runtime/http-types.ts +0 -2
- package/src/runtime/migrations/vbundle-builder.ts +10 -3
- package/src/runtime/pending-interactions.ts +0 -1
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +106 -0
- package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +25 -6
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +512 -0
- package/src/runtime/routes/acp-routes.test.ts +255 -6
- package/src/runtime/routes/acp-routes.ts +8 -1
- package/src/runtime/routes/avatar-routes.ts +10 -10
- package/src/runtime/routes/background-wake-routes.ts +188 -0
- package/src/runtime/routes/browser-tabs-routes.ts +200 -0
- package/src/runtime/routes/btw-routes.ts +0 -6
- package/src/runtime/routes/conversation-cli-routes.ts +1 -1
- package/src/runtime/routes/conversation-list-routes.ts +12 -4
- package/src/runtime/routes/conversation-management-routes.ts +77 -20
- package/src/runtime/routes/conversation-query-routes.ts +142 -36
- package/src/runtime/routes/conversation-routes.ts +252 -410
- package/src/runtime/routes/conversation-starter-routes.ts +6 -3
- package/src/runtime/routes/disk-pressure-routes.ts +1 -1
- package/src/runtime/routes/domain-routes.ts +60 -10
- package/src/runtime/routes/email-routes.ts +5 -2
- package/src/runtime/routes/events-routes.ts +54 -10
- package/src/runtime/routes/group-routes.ts +24 -8
- package/src/runtime/routes/host-browser-routes.ts +10 -2
- package/src/runtime/routes/host-cu-routes.ts +2 -2
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +96 -3
- package/src/runtime/routes/index.ts +8 -0
- package/src/runtime/routes/inference-profile-session-handler.ts +22 -12
- package/src/runtime/routes/inference-profile-session-routes.ts +7 -1
- package/src/runtime/routes/llm-call-sites-routes.ts +32 -5
- package/src/runtime/routes/memory-item-routes.ts +8 -3
- package/src/runtime/routes/memory-v2-routes.ts +215 -5
- package/src/runtime/routes/memory-v3-routes.ts +316 -0
- package/src/runtime/routes/migration-routes.ts +21 -24
- package/src/runtime/routes/plugins-routes.ts +337 -0
- package/src/runtime/routes/rename-conversation-routes.ts +6 -2
- package/src/runtime/routes/secret-routes.ts +25 -5
- package/src/runtime/routes/settings-routes.ts +12 -11
- package/src/runtime/routes/slack-channel-routes.ts +5 -4
- package/src/runtime/routes/workspace-routes.ts +25 -10
- package/src/runtime/sync/resource-sync-events.ts +106 -38
- package/src/runtime/sync/sync-publisher.test.ts +49 -0
- package/src/runtime/sync/sync-publisher.ts +2 -1
- package/src/runtime/verification-outbound-actions.ts +73 -1
- package/src/telemetry/types.ts +12 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +48 -0
- package/src/telemetry/usage-telemetry-reporter.ts +1 -0
- package/src/tools/acp/spawn.test.ts +119 -0
- package/src/tools/acp/spawn.ts +15 -2
- package/src/tools/apps/definitions.ts +2 -8
- package/src/tools/ask-question/ask-question-tool.test.ts +3 -3
- package/src/tools/ask-question/ask-question-tool.ts +38 -45
- package/src/tools/browser/__tests__/pinned-tabs.test.ts +70 -0
- package/src/tools/browser/browser-execution.ts +16 -3
- package/src/tools/browser/cdp-client/__tests__/browser-tabs-factory.test.ts +402 -0
- package/src/tools/browser/cdp-client/__tests__/types.test.ts +3 -0
- package/src/tools/browser/cdp-client/cdp-inspect-client.ts +12 -0
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +27 -1
- package/src/tools/browser/cdp-client/factory.ts +100 -17
- package/src/tools/browser/cdp-client/local-cdp-client.ts +12 -0
- package/src/tools/browser/cdp-client/types.ts +65 -0
- package/src/tools/browser/pinned-tabs.ts +96 -40
- package/src/tools/computer-use/definitions.ts +22 -78
- package/src/tools/credential-execution/make-authenticated-request.ts +3 -9
- package/src/tools/credential-execution/manage-secure-command-tool.ts +3 -9
- package/src/tools/credential-execution/run-authenticated-command.ts +3 -9
- package/src/tools/credentials/vault.ts +3 -9
- package/src/tools/document/document-tool.ts +59 -0
- package/src/tools/execution-target.ts +21 -23
- package/src/tools/executor.ts +6 -1
- package/src/tools/filesystem/edit.ts +3 -9
- package/src/tools/filesystem/list.ts +3 -9
- package/src/tools/filesystem/read.ts +3 -9
- package/src/tools/filesystem/write.ts +3 -9
- package/src/tools/host-filesystem/edit.ts +3 -9
- package/src/tools/host-filesystem/read.ts +3 -9
- package/src/tools/host-filesystem/transfer.ts +3 -9
- package/src/tools/host-filesystem/write.ts +3 -9
- package/src/tools/host-terminal/host-shell.ts +3 -9
- package/src/tools/mcp/mcp-tool-factory.ts +1 -8
- package/src/tools/memory/register.test.ts +1 -1
- package/src/tools/memory/register.ts +4 -9
- package/src/tools/network/web-fetch.ts +3 -9
- package/src/tools/network/web-search.ts +25 -32
- package/src/tools/registry.ts +7 -23
- package/src/tools/schema-transforms.ts +1 -1
- package/src/tools/skills/execute.ts +3 -9
- package/src/tools/skills/load.ts +3 -9
- package/src/tools/skills/skill-tool-factory.ts +1 -8
- package/src/tools/subagent/notify-parent.ts +3 -9
- package/src/tools/system/request-permission.ts +3 -9
- package/src/tools/terminal/shell.ts +3 -9
- package/src/tools/tool-defaults.ts +94 -0
- package/src/tools/types.ts +27 -98
- package/src/tools/ui-surface/definitions.ts +6 -22
- package/src/usage/pricing.ts +23 -0
- package/src/usage/types.ts +12 -0
- package/src/util/logger.ts +16 -7
- package/src/util/platform.ts +7 -2
- package/src/util/sqlite3-runtime.ts +65 -0
- package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +1 -0
- package/src/workspace/migrations/089-move-memory-tree-out-of-v3.ts +86 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/__tests__/compaction-strip-metadata-clear.test.ts +0 -206
- package/src/__tests__/message-complete-display-id.test.ts +0 -175
- package/src/daemon/query-complexity-router.ts +0 -75
- package/src/prompts/cache-boundary.ts +0 -8
|
@@ -9,15 +9,18 @@ import { getAvatarImagePath } from "../../util/platform.js";
|
|
|
9
9
|
import { broadcastMessage } from "../assistant-event-hub.js";
|
|
10
10
|
import { publishSyncInvalidation } from "./sync-publisher.js";
|
|
11
11
|
|
|
12
|
-
export function publishAvatarChanged(): void {
|
|
12
|
+
export function publishAvatarChanged(originClientId?: string): void {
|
|
13
13
|
broadcastMessage({
|
|
14
14
|
type: "avatar_updated",
|
|
15
15
|
avatarPath: getAvatarImagePath(),
|
|
16
16
|
});
|
|
17
|
-
void publishSyncInvalidation([SYNC_TAGS.assistantAvatar]);
|
|
17
|
+
void publishSyncInvalidation([SYNC_TAGS.assistantAvatar], originClientId);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
export function publishIdentityChanged(
|
|
20
|
+
export function publishIdentityChanged(
|
|
21
|
+
fields: IdentityFields,
|
|
22
|
+
originClientId?: string,
|
|
23
|
+
): void {
|
|
21
24
|
broadcastMessage({
|
|
22
25
|
type: "identity_changed",
|
|
23
26
|
name: fields.name,
|
|
@@ -26,59 +29,112 @@ export function publishIdentityChanged(fields: IdentityFields): void {
|
|
|
26
29
|
emoji: fields.emoji,
|
|
27
30
|
home: fields.home,
|
|
28
31
|
});
|
|
29
|
-
void publishSyncInvalidation([SYNC_TAGS.assistantIdentity]);
|
|
32
|
+
void publishSyncInvalidation([SYNC_TAGS.assistantIdentity], originClientId);
|
|
30
33
|
}
|
|
31
34
|
|
|
32
|
-
export function publishConfigChanged(): void {
|
|
35
|
+
export function publishConfigChanged(originClientId?: string): void {
|
|
33
36
|
broadcastMessage({ type: "config_changed" });
|
|
34
|
-
void publishSyncInvalidation([SYNC_TAGS.assistantConfig]);
|
|
37
|
+
void publishSyncInvalidation([SYNC_TAGS.assistantConfig], originClientId);
|
|
35
38
|
}
|
|
36
39
|
|
|
37
|
-
export function publishSoundsConfigUpdated(): void {
|
|
40
|
+
export function publishSoundsConfigUpdated(originClientId?: string): void {
|
|
38
41
|
broadcastMessage({ type: "sounds_config_updated" });
|
|
39
|
-
void publishSyncInvalidation([SYNC_TAGS.assistantSounds]);
|
|
42
|
+
void publishSyncInvalidation([SYNC_TAGS.assistantSounds], originClientId);
|
|
40
43
|
}
|
|
41
44
|
|
|
42
|
-
export function publishSchedulesChanged(): void {
|
|
43
|
-
void publishSyncInvalidation([SYNC_TAGS.assistantSchedules]);
|
|
45
|
+
export function publishSchedulesChanged(originClientId?: string): void {
|
|
46
|
+
void publishSyncInvalidation([SYNC_TAGS.assistantSchedules], originClientId);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Reasons that change the *shape* of the conversation list — a row is
|
|
51
|
+
* added, removed, or its position changes. These require web clients to
|
|
52
|
+
* refetch the paginated list because the row patch path (`refreshConversationRow`)
|
|
53
|
+
* only handles a single known id. Reasons not in this set are *content-only*
|
|
54
|
+
* changes to an existing row (e.g. `seen_changed`, `renamed`) and are
|
|
55
|
+
* delivered exclusively via the per-conversation `sync_changed` tag, which
|
|
56
|
+
* web consumes by GET-and-patching the single row.
|
|
57
|
+
*/
|
|
58
|
+
const SHAPE_CHANGING_REASONS: ReadonlySet<ConversationListInvalidatedReason> =
|
|
59
|
+
new Set(["created", "deleted", "reordered"]);
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Publish the legacy `conversation_list_invalidated` broadcast to macOS
|
|
63
|
+
* subscribers only.
|
|
64
|
+
*
|
|
65
|
+
* Web consumes `sync_changed` (`conversationsList` for shape changes,
|
|
66
|
+
* `conversation:<id>:metadata` for content changes) directly and patches
|
|
67
|
+
* the cached list in place — see `useAssistantSyncStream` for the consumer
|
|
68
|
+
* side. macOS (`ConversationRestorer.swift`) still listens for the typed
|
|
69
|
+
* broadcast.
|
|
70
|
+
*
|
|
71
|
+
* TODO(electron-cutover): remove this helper and all callers once macOS
|
|
72
|
+
* migrates to the Electron client and consumes `sync_changed` directly.
|
|
73
|
+
* At that point the `conversation_list_invalidated` message type can be
|
|
74
|
+
* retired entirely.
|
|
75
|
+
*/
|
|
76
|
+
function broadcastConversationListInvalidatedToMacos(
|
|
77
|
+
reason: ConversationListInvalidatedReason,
|
|
78
|
+
): void {
|
|
79
|
+
broadcastMessage(
|
|
80
|
+
{
|
|
81
|
+
type: "conversation_list_invalidated",
|
|
82
|
+
reason,
|
|
83
|
+
},
|
|
84
|
+
undefined,
|
|
85
|
+
{ targetInterfaceId: "macos" },
|
|
86
|
+
);
|
|
44
87
|
}
|
|
45
88
|
|
|
46
89
|
export function publishConversationListChanged(
|
|
47
90
|
reason: ConversationListInvalidatedReason,
|
|
91
|
+
originClientId?: string,
|
|
48
92
|
): void {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
reason,
|
|
52
|
-
});
|
|
53
|
-
void publishSyncInvalidation([SYNC_TAGS.conversationsList]);
|
|
93
|
+
broadcastConversationListInvalidatedToMacos(reason);
|
|
94
|
+
void publishSyncInvalidation([SYNC_TAGS.conversationsList], originClientId);
|
|
54
95
|
}
|
|
55
96
|
|
|
56
97
|
export function publishConversationMessagesChanged(
|
|
57
98
|
conversationId: string,
|
|
99
|
+
originClientId?: string,
|
|
58
100
|
): void {
|
|
59
|
-
void publishSyncInvalidation(
|
|
101
|
+
void publishSyncInvalidation(
|
|
102
|
+
[conversationMessagesSyncTag(conversationId)],
|
|
103
|
+
originClientId,
|
|
104
|
+
);
|
|
60
105
|
}
|
|
61
106
|
|
|
62
107
|
export function publishConversationListAndMetadataChanged(
|
|
63
108
|
reason: ConversationListInvalidatedReason,
|
|
64
109
|
conversationIds: string | string[],
|
|
110
|
+
originClientId?: string,
|
|
65
111
|
): void {
|
|
66
112
|
const ids = Array.isArray(conversationIds)
|
|
67
113
|
? conversationIds
|
|
68
114
|
: [conversationIds];
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
115
|
+
broadcastConversationListInvalidatedToMacos(reason);
|
|
116
|
+
|
|
117
|
+
// Shape-changing reasons (`created`, `deleted`, `reordered`) add or
|
|
118
|
+
// remove rows or change the order of the paginated window — web must
|
|
119
|
+
// refetch the list, so include the `conversationsList` umbrella tag.
|
|
120
|
+
// Content-only reasons (`renamed`, `seen_changed`) modify an existing
|
|
121
|
+
// row; web consumes the per-conversation metadata tag and GET-and-
|
|
122
|
+
// patches the single row, avoiding the full paginated list drain
|
|
123
|
+
// (`limit=50&offset=0..N` × foreground + background — ~14 requests
|
|
124
|
+
// per write at a few hundred conversations).
|
|
125
|
+
const tags: string[] = ids.map((conversationId) =>
|
|
126
|
+
conversationMetadataSyncTag(conversationId),
|
|
127
|
+
);
|
|
128
|
+
if (SHAPE_CHANGING_REASONS.has(reason)) {
|
|
129
|
+
tags.unshift(SYNC_TAGS.conversationsList);
|
|
130
|
+
}
|
|
131
|
+
void publishSyncInvalidation(tags, originClientId);
|
|
77
132
|
}
|
|
78
133
|
|
|
79
134
|
export function publishConversationTitleChanged(
|
|
80
135
|
conversationId: string,
|
|
81
136
|
title: string,
|
|
137
|
+
originClientId?: string,
|
|
82
138
|
): void {
|
|
83
139
|
broadcastMessage(
|
|
84
140
|
{
|
|
@@ -88,18 +144,27 @@ export function publishConversationTitleChanged(
|
|
|
88
144
|
},
|
|
89
145
|
conversationId,
|
|
90
146
|
);
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
147
|
+
// Renames are content-only — the paired typed `conversation_title_updated`
|
|
148
|
+
// event already carries the new title and patches the row in place on
|
|
149
|
+
// web; macOS receives the per-interface `conversation_list_invalidated`
|
|
150
|
+
// emitted from `broadcastMessage` (see `assistant-event-hub.ts`). The
|
|
151
|
+
// `sync_changed` metadata tag is included as a belt-and-suspenders signal
|
|
152
|
+
// for any sibling-tab consumer that missed the typed event.
|
|
153
|
+
void publishSyncInvalidation(
|
|
154
|
+
[conversationMetadataSyncTag(conversationId)],
|
|
155
|
+
originClientId,
|
|
156
|
+
);
|
|
95
157
|
}
|
|
96
158
|
|
|
97
|
-
export function publishConversationInferenceProfileChanged(
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
159
|
+
export function publishConversationInferenceProfileChanged(
|
|
160
|
+
params: {
|
|
161
|
+
conversationId: string;
|
|
162
|
+
profile: string | null;
|
|
163
|
+
sessionId?: string | null;
|
|
164
|
+
expiresAt?: number | null;
|
|
165
|
+
},
|
|
166
|
+
originClientId?: string,
|
|
167
|
+
): void {
|
|
103
168
|
broadcastMessage(
|
|
104
169
|
{
|
|
105
170
|
type: "conversation_inference_profile_updated",
|
|
@@ -110,8 +175,11 @@ export function publishConversationInferenceProfileChanged(params: {
|
|
|
110
175
|
},
|
|
111
176
|
params.conversationId,
|
|
112
177
|
);
|
|
113
|
-
void publishSyncInvalidation(
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
178
|
+
void publishSyncInvalidation(
|
|
179
|
+
[
|
|
180
|
+
SYNC_TAGS.conversationsList,
|
|
181
|
+
conversationMetadataSyncTag(params.conversationId),
|
|
182
|
+
],
|
|
183
|
+
originClientId,
|
|
184
|
+
);
|
|
117
185
|
}
|
|
@@ -102,4 +102,53 @@ describe("sync publisher", () => {
|
|
|
102
102
|
subscription.dispose();
|
|
103
103
|
}
|
|
104
104
|
});
|
|
105
|
+
|
|
106
|
+
test("forwards originClientId when provided", async () => {
|
|
107
|
+
const received: AssistantEvent[] = [];
|
|
108
|
+
const subscription = assistantEventHub.subscribe({
|
|
109
|
+
type: "process",
|
|
110
|
+
callback: (event) => {
|
|
111
|
+
received.push(event);
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
const message = await publishSyncInvalidation(
|
|
117
|
+
[SYNC_TAGS.assistantAvatar],
|
|
118
|
+
"client-abc",
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
await waitFor(() => received.length === 1);
|
|
122
|
+
|
|
123
|
+
expect(message).toEqual({
|
|
124
|
+
type: "sync_changed",
|
|
125
|
+
tags: [SYNC_TAGS.assistantAvatar],
|
|
126
|
+
originClientId: "client-abc",
|
|
127
|
+
});
|
|
128
|
+
expect(received[0].message).toEqual(message);
|
|
129
|
+
} finally {
|
|
130
|
+
subscription.dispose();
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("omits originClientId when not provided", async () => {
|
|
135
|
+
const received: AssistantEvent[] = [];
|
|
136
|
+
const subscription = assistantEventHub.subscribe({
|
|
137
|
+
type: "process",
|
|
138
|
+
callback: (event) => {
|
|
139
|
+
received.push(event);
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
const message = await publishSyncInvalidation([SYNC_TAGS.assistantAvatar]);
|
|
145
|
+
|
|
146
|
+
await waitFor(() => received.length === 1);
|
|
147
|
+
|
|
148
|
+
expect("originClientId" in message).toBe(false);
|
|
149
|
+
expect(received[0].message).toEqual(message);
|
|
150
|
+
} finally {
|
|
151
|
+
subscription.dispose();
|
|
152
|
+
}
|
|
153
|
+
});
|
|
105
154
|
});
|
|
@@ -10,8 +10,9 @@ const log = getLogger("sync-publisher");
|
|
|
10
10
|
|
|
11
11
|
export async function publishSyncInvalidation(
|
|
12
12
|
tags: SyncInvalidationTag[],
|
|
13
|
+
originClientId?: string,
|
|
13
14
|
): Promise<SyncChangedMessage> {
|
|
14
|
-
const message = buildSyncChangedMessage(tags);
|
|
15
|
+
const message = buildSyncChangedMessage(tags, originClientId);
|
|
15
16
|
try {
|
|
16
17
|
broadcastMessage(message);
|
|
17
18
|
} catch (err) {
|
|
@@ -234,12 +234,20 @@ export async function startOutbound(
|
|
|
234
234
|
params.rebind,
|
|
235
235
|
originConversationId,
|
|
236
236
|
);
|
|
237
|
+
} else if (channel === "email") {
|
|
238
|
+
return startOutboundEmail(
|
|
239
|
+
params.destination,
|
|
240
|
+
assistantId,
|
|
241
|
+
channel,
|
|
242
|
+
params.rebind,
|
|
243
|
+
originConversationId,
|
|
244
|
+
);
|
|
237
245
|
}
|
|
238
246
|
|
|
239
247
|
return {
|
|
240
248
|
success: false,
|
|
241
249
|
error: "unsupported_channel",
|
|
242
|
-
message: `Outbound verification is
|
|
250
|
+
message: `Outbound verification is not supported for ${channel}. Supported channels: Telegram, phone, Slack, email.`,
|
|
243
251
|
channel,
|
|
244
252
|
};
|
|
245
253
|
}
|
|
@@ -582,6 +590,70 @@ function startOutboundSlack(
|
|
|
582
590
|
};
|
|
583
591
|
}
|
|
584
592
|
|
|
593
|
+
function startOutboundEmail(
|
|
594
|
+
destination: string | undefined,
|
|
595
|
+
assistantId: string,
|
|
596
|
+
channel: ChannelId,
|
|
597
|
+
rebind?: boolean,
|
|
598
|
+
originConversationId?: string,
|
|
599
|
+
): OutboundActionResult {
|
|
600
|
+
if (!destination) {
|
|
601
|
+
return {
|
|
602
|
+
success: false,
|
|
603
|
+
error: "missing_destination",
|
|
604
|
+
message:
|
|
605
|
+
"An email address is required for outbound email verification.",
|
|
606
|
+
channel,
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
const normalizedEmail = destination.trim().toLowerCase();
|
|
611
|
+
|
|
612
|
+
const existingBinding = getGuardianBinding(assistantId, channel);
|
|
613
|
+
if (existingBinding && !rebind) {
|
|
614
|
+
return {
|
|
615
|
+
success: false,
|
|
616
|
+
error: "already_bound",
|
|
617
|
+
message:
|
|
618
|
+
"A guardian is already bound for this channel. Set rebind: true to replace.",
|
|
619
|
+
channel,
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
const recentSendCount = countRecentSendsToDestination(
|
|
624
|
+
channel,
|
|
625
|
+
normalizedEmail,
|
|
626
|
+
DESTINATION_RATE_WINDOW_MS,
|
|
627
|
+
);
|
|
628
|
+
if (recentSendCount >= MAX_SENDS_PER_DESTINATION_WINDOW) {
|
|
629
|
+
return {
|
|
630
|
+
success: false,
|
|
631
|
+
error: "rate_limited",
|
|
632
|
+
message:
|
|
633
|
+
"Too many verification attempts to this email address. Please try again later.",
|
|
634
|
+
channel,
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
const sessionResult = createOutboundSession({
|
|
639
|
+
channel,
|
|
640
|
+
expectedExternalUserId: normalizedEmail,
|
|
641
|
+
expectedChatId: normalizedEmail,
|
|
642
|
+
identityBindingStatus: "bound",
|
|
643
|
+
destinationAddress: normalizedEmail,
|
|
644
|
+
verificationPurpose: "guardian",
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
return {
|
|
648
|
+
success: true,
|
|
649
|
+
verificationSessionId: sessionResult.sessionId,
|
|
650
|
+
secret: sessionResult.secret,
|
|
651
|
+
expiresAt: sessionResult.expiresAt,
|
|
652
|
+
channel,
|
|
653
|
+
originConversationId,
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
|
|
585
657
|
// ---------------------------------------------------------------------------
|
|
586
658
|
// Resend outbound
|
|
587
659
|
// ---------------------------------------------------------------------------
|
package/src/telemetry/types.ts
CHANGED
|
@@ -38,6 +38,18 @@ export interface LlmUsageTelemetryEvent extends TelemetryEventBase {
|
|
|
38
38
|
output_tokens: number;
|
|
39
39
|
cache_creation_input_tokens: number | null;
|
|
40
40
|
cache_read_input_tokens: number | null;
|
|
41
|
+
/**
|
|
42
|
+
* The provider's untouched `usage` block. Anthropic surfaces a TTL
|
|
43
|
+
* breakdown under `cache_creation.ephemeral_{5m,1h}_input_tokens`;
|
|
44
|
+
* OpenAI surfaces cached-read counts under
|
|
45
|
+
* `prompt_tokens_details.cached_tokens` (and reasoning tokens under
|
|
46
|
+
* `completion_tokens_details`). Both shapes are forwarded verbatim so
|
|
47
|
+
* downstream consumers (admin charts, dbt models) can extract any
|
|
48
|
+
* provider-specific detail without requiring a schema change here.
|
|
49
|
+
* Null when the provider did not return a usage payload, and for
|
|
50
|
+
* daemons that predate `260-llm-usage-add-raw-usage`.
|
|
51
|
+
*/
|
|
52
|
+
raw_usage: Record<string, unknown> | null;
|
|
41
53
|
actor: string;
|
|
42
54
|
llm_call_site: LLMCallSite | null;
|
|
43
55
|
inference_profile: string | null;
|
|
@@ -159,6 +159,7 @@ function makeUsageEvent(
|
|
|
159
159
|
outputTokens: 50,
|
|
160
160
|
cacheCreationInputTokens: 10,
|
|
161
161
|
cacheReadInputTokens: 5,
|
|
162
|
+
rawUsage: null,
|
|
162
163
|
actor: "main_agent",
|
|
163
164
|
callSite: null,
|
|
164
165
|
inferenceProfile: null,
|
|
@@ -435,6 +436,53 @@ describe("UsageTelemetryReporter", () => {
|
|
|
435
436
|
expect(e.inference_profile_source).toBe("conversation");
|
|
436
437
|
expect(e.cost).toBe(0.001);
|
|
437
438
|
expect(e.recorded_at).toBe(1700000099000);
|
|
439
|
+
// raw_usage defaults to null on this fixture (the makeUsageEvent default),
|
|
440
|
+
// confirming the wire shape carries the key as `null` rather than
|
|
441
|
+
// dropping it for legacy rows or providers that did not return a
|
|
442
|
+
// usage block.
|
|
443
|
+
expect(e.raw_usage).toBeNull();
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
test("payload forwards the provider's raw_usage block verbatim", async () => {
|
|
447
|
+
// The reporter must surface the literal usage object the provider
|
|
448
|
+
// returned (Anthropic nests TTL breakdown under `cache_creation`,
|
|
449
|
+
// OpenAI nests cached-read counts under `prompt_tokens_details`,
|
|
450
|
+
// etc.) so downstream consumers can extract any provider-specific
|
|
451
|
+
// detail without a wire-level schema change. Anything that
|
|
452
|
+
// transforms, summarises, or strips fields here destroys data the
|
|
453
|
+
// admin charts and dbt models depend on.
|
|
454
|
+
const rawUsage = {
|
|
455
|
+
input_tokens: 200,
|
|
456
|
+
output_tokens: 100,
|
|
457
|
+
cache_creation_input_tokens: 750,
|
|
458
|
+
cache_creation: {
|
|
459
|
+
ephemeral_5m_input_tokens: 250,
|
|
460
|
+
ephemeral_1h_input_tokens: 500,
|
|
461
|
+
},
|
|
462
|
+
cache_read_input_tokens: 15,
|
|
463
|
+
service_tier: "standard",
|
|
464
|
+
};
|
|
465
|
+
const event = makeUsageEvent({
|
|
466
|
+
id: "evt-raw-usage",
|
|
467
|
+
provider: "anthropic",
|
|
468
|
+
model: "claude-sonnet-4-20250514",
|
|
469
|
+
cacheCreationInputTokens: 750,
|
|
470
|
+
rawUsage,
|
|
471
|
+
});
|
|
472
|
+
mockQueryUnreportedUsageEvents.mockReturnValue([event]);
|
|
473
|
+
mockFetch.mockImplementation(() =>
|
|
474
|
+
Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
|
|
475
|
+
);
|
|
476
|
+
|
|
477
|
+
const reporter = new UsageTelemetryReporter();
|
|
478
|
+
await reporter.flush();
|
|
479
|
+
|
|
480
|
+
const body = JSON.parse(
|
|
481
|
+
(mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
|
|
482
|
+
);
|
|
483
|
+
const e = body.events[0];
|
|
484
|
+
expect(e.cache_creation_input_tokens).toBe(750);
|
|
485
|
+
expect(e.raw_usage).toEqual(rawUsage);
|
|
438
486
|
});
|
|
439
487
|
|
|
440
488
|
test("payload preserves null attribution for historical usage rows", async () => {
|
|
@@ -234,6 +234,7 @@ export class UsageTelemetryReporter {
|
|
|
234
234
|
output_tokens: e.outputTokens,
|
|
235
235
|
cache_creation_input_tokens: e.cacheCreationInputTokens ?? null,
|
|
236
236
|
cache_read_input_tokens: e.cacheReadInputTokens ?? null,
|
|
237
|
+
raw_usage: e.rawUsage,
|
|
237
238
|
actor: e.actor,
|
|
238
239
|
llm_call_site: e.callSite,
|
|
239
240
|
inference_profile: e.inferenceProfile,
|
|
@@ -82,6 +82,25 @@ mock.module("../../util/logger.js", () => ({
|
|
|
82
82
|
}),
|
|
83
83
|
}));
|
|
84
84
|
|
|
85
|
+
// Stub secure-keys so the `prepareAgentEnv` preflight finds a token without
|
|
86
|
+
// the test having to populate the real OS keyring. Driven via `secureKeyStore`
|
|
87
|
+
// per test in beforeEach; the default seeds a vault token so existing tests
|
|
88
|
+
// (which assume claude spawns succeed) keep passing.
|
|
89
|
+
//
|
|
90
|
+
// The real module's other exports are spread in so transitive importers
|
|
91
|
+
// (e.g. session-manager → pending-interactions → credential-routes, which
|
|
92
|
+
// imports `getSecureKeyResultAsync`) still resolve at parse time. Bun's
|
|
93
|
+
// `mock.module` is process-global and returns *exactly* the keys the factory
|
|
94
|
+
// returns — without the spread, any consumer pulling a non-`getSecureKeyAsync`
|
|
95
|
+
// export errors with "Export named '<X>' not found".
|
|
96
|
+
const secureKeyStore = new Map<string, string>();
|
|
97
|
+
const realSecureKeys = await import("../../security/secure-keys.js");
|
|
98
|
+
|
|
99
|
+
mock.module("../../security/secure-keys.js", () => ({
|
|
100
|
+
...realSecureKeys,
|
|
101
|
+
getSecureKeyAsync: async (key: string) => secureKeyStore.get(key),
|
|
102
|
+
}));
|
|
103
|
+
|
|
85
104
|
// Stub session manager so we don't actually spawn child processes.
|
|
86
105
|
const spawnMock = mock(
|
|
87
106
|
async (
|
|
@@ -131,6 +150,14 @@ beforeEach(() => {
|
|
|
131
150
|
_resetAdapterVersionCacheForTests();
|
|
132
151
|
config.setConfig({ agents: DEFAULT_TEST_AGENTS });
|
|
133
152
|
which.setWhich((cmd) => `/usr/local/bin/${cmd}`);
|
|
153
|
+
// Default: vault has a claude token so the preflight in `prepareAgentEnv`
|
|
154
|
+
// succeeds for tests that don't care about env injection. Env-injection
|
|
155
|
+
// tests below clear/override this explicitly.
|
|
156
|
+
secureKeyStore.clear();
|
|
157
|
+
secureKeyStore.set(
|
|
158
|
+
"credential/acp/claude_oauth_token",
|
|
159
|
+
"default-test-token",
|
|
160
|
+
);
|
|
134
161
|
});
|
|
135
162
|
|
|
136
163
|
// ---------------------------------------------------------------------------
|
|
@@ -376,3 +403,95 @@ describe("executeAcpSpawn — per-agent resume hint", () => {
|
|
|
376
403
|
expect(payload.message).not.toContain("To resume this session later");
|
|
377
404
|
});
|
|
378
405
|
});
|
|
406
|
+
|
|
407
|
+
// ---------------------------------------------------------------------------
|
|
408
|
+
// executeAcpSpawn — CLAUDE_CODE_OAUTH_TOKEN env injection + preflight
|
|
409
|
+
//
|
|
410
|
+
// Mirrors the HTTP-route test block in
|
|
411
|
+
// `runtime/routes/acp-routes.test.ts` — the skill tool calls into the same
|
|
412
|
+
// `prepareAgentEnv` helper, and the contract must be identical so a
|
|
413
|
+
// missing token fails the spawn at the tool boundary (`isError: true`)
|
|
414
|
+
// instead of letting a token-less subprocess zombie out. PR-history
|
|
415
|
+
// context: the inline env-injection used to live in the route only; this
|
|
416
|
+
// tool path was bypassing it entirely, causing every skill-driven ACP
|
|
417
|
+
// spawn to die on first prompt with "Authentication required". Pin both
|
|
418
|
+
// the happy paths and the throw path here so future drift on either
|
|
419
|
+
// caller fails the suite loudly.
|
|
420
|
+
// ---------------------------------------------------------------------------
|
|
421
|
+
|
|
422
|
+
describe("executeAcpSpawn — CLAUDE_CODE_OAUTH_TOKEN injection", () => {
|
|
423
|
+
test("injects CLAUDE_CODE_OAUTH_TOKEN from the secure store for the claude agent", async () => {
|
|
424
|
+
secureKeyStore.clear();
|
|
425
|
+
secureKeyStore.set(
|
|
426
|
+
"credential/acp/claude_oauth_token",
|
|
427
|
+
"tool-vault-token-abc",
|
|
428
|
+
);
|
|
429
|
+
execScripts.set("npm ls", { error: new Error("npm not installed") });
|
|
430
|
+
execScripts.set("npm view", { error: new Error("npm not installed") });
|
|
431
|
+
|
|
432
|
+
const result = await executeAcpSpawn(
|
|
433
|
+
{ agent: "claude", task: "do something" },
|
|
434
|
+
makeContext(),
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
expect(result.isError).toBe(false);
|
|
438
|
+
expect(spawnMock).toHaveBeenCalledTimes(1);
|
|
439
|
+
const agentConfigArg = spawnMock.mock.calls[0][1] as {
|
|
440
|
+
env?: Record<string, string>;
|
|
441
|
+
};
|
|
442
|
+
expect(agentConfigArg.env?.CLAUDE_CODE_OAUTH_TOKEN).toBe(
|
|
443
|
+
"tool-vault-token-abc",
|
|
444
|
+
);
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
test("accepts CLAUDE_CODE_OAUTH_TOKEN from config.json agent.env without a vault entry", async () => {
|
|
448
|
+
// Mirrors the route-level precedence test: a config.json env override
|
|
449
|
+
// is the first-priority provisioning route. The resolver surfaces it
|
|
450
|
+
// on `resolved.agent.env`, which the helper preserves.
|
|
451
|
+
secureKeyStore.clear();
|
|
452
|
+
config.setConfig({
|
|
453
|
+
agents: {
|
|
454
|
+
claude: {
|
|
455
|
+
command: "claude-agent-acp",
|
|
456
|
+
args: [],
|
|
457
|
+
env: { CLAUDE_CODE_OAUTH_TOKEN: "tool-config-token-xyz" },
|
|
458
|
+
},
|
|
459
|
+
codex: { command: "codex-acp", args: [] },
|
|
460
|
+
"unknown-agent": { command: "some-other-binary", args: [] },
|
|
461
|
+
},
|
|
462
|
+
});
|
|
463
|
+
execScripts.set("npm ls", { error: new Error("npm not installed") });
|
|
464
|
+
execScripts.set("npm view", { error: new Error("npm not installed") });
|
|
465
|
+
|
|
466
|
+
const result = await executeAcpSpawn(
|
|
467
|
+
{ agent: "claude", task: "do something" },
|
|
468
|
+
makeContext(),
|
|
469
|
+
);
|
|
470
|
+
|
|
471
|
+
expect(result.isError).toBe(false);
|
|
472
|
+
const agentConfigArg = spawnMock.mock.calls[0][1] as {
|
|
473
|
+
env?: Record<string, string>;
|
|
474
|
+
};
|
|
475
|
+
expect(agentConfigArg.env?.CLAUDE_CODE_OAUTH_TOKEN).toBe(
|
|
476
|
+
"tool-config-token-xyz",
|
|
477
|
+
);
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
test("returns isError when no token is available from either route (preflight throw mapped to tool result)", async () => {
|
|
481
|
+
// Both routes empty. The helper throws `FailedDependencyError`; the
|
|
482
|
+
// tool catches it and returns `{ isError: true, content: <msg> }`
|
|
483
|
+
// rather than letting it propagate (the tool boundary is a sync
|
|
484
|
+
// ToolExecutionResult, not an HTTP response).
|
|
485
|
+
secureKeyStore.clear();
|
|
486
|
+
|
|
487
|
+
const result = await executeAcpSpawn(
|
|
488
|
+
{ agent: "claude", task: "do something" },
|
|
489
|
+
makeContext(),
|
|
490
|
+
);
|
|
491
|
+
|
|
492
|
+
expect(result.isError).toBe(true);
|
|
493
|
+
expect(result.content).toContain("CLAUDE_CODE_OAUTH_TOKEN");
|
|
494
|
+
// Spawn was NEVER called — preflight fired before the subprocess started.
|
|
495
|
+
expect(spawnMock).not.toHaveBeenCalled();
|
|
496
|
+
});
|
|
497
|
+
});
|
package/src/tools/acp/spawn.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
2
|
|
|
3
3
|
import { getAcpSessionManager } from "../../acp/index.js";
|
|
4
|
+
import { prepareAgentEnv } from "../../acp/prepare-agent-env.js";
|
|
4
5
|
import { resolveAcpAgent } from "../../acp/resolve-agent.js";
|
|
5
6
|
import { DEFAULT_AGENT_NPM_PACKAGES } from "../../config/acp-defaults.js";
|
|
6
7
|
import { getLogger } from "../../util/logger.js";
|
|
@@ -159,8 +160,6 @@ export async function executeAcpSpawn(
|
|
|
159
160
|
}
|
|
160
161
|
}
|
|
161
162
|
}
|
|
162
|
-
const agentConfig = resolved.agent;
|
|
163
|
-
|
|
164
163
|
const sendToClient = context.sendToClient as
|
|
165
164
|
| ((msg: { type: string; [key: string]: unknown }) => void)
|
|
166
165
|
| undefined;
|
|
@@ -171,6 +170,20 @@ export async function executeAcpSpawn(
|
|
|
171
170
|
};
|
|
172
171
|
}
|
|
173
172
|
|
|
173
|
+
// Inject required env vars and preflight via the shared helper. Mirrors
|
|
174
|
+
// the HTTP route at `runtime/routes/acp-routes.ts:spawnSession` — both
|
|
175
|
+
// call sites MUST go through `prepareAgentEnv` before `manager.spawn`,
|
|
176
|
+
// otherwise the spawned subprocess starts with no auth and dies as a
|
|
177
|
+
// zombie after the first prompt. See `acp/prepare-agent-env.ts` for
|
|
178
|
+
// the full rationale.
|
|
179
|
+
let agentConfig;
|
|
180
|
+
try {
|
|
181
|
+
agentConfig = await prepareAgentEnv(resolved.agent);
|
|
182
|
+
} catch (err) {
|
|
183
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
184
|
+
return { content: msg, isError: true };
|
|
185
|
+
}
|
|
186
|
+
|
|
174
187
|
// Best-effort version check — never blocks the spawn. If outdated, we
|
|
175
188
|
// append a non-blocking warning to the success payload.
|
|
176
189
|
const versionInfo = await checkAdapterVersion(agentConfig.command);
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { RiskLevel } from "../../permissions/types.js";
|
|
12
|
-
import type { ToolDefinition } from "../../providers/types.js";
|
|
13
12
|
import type { Tool, ToolExecutionResult } from "../types.js";
|
|
14
13
|
|
|
15
14
|
// ---------------------------------------------------------------------------
|
|
@@ -33,12 +32,9 @@ const appOpenTool: Tool = {
|
|
|
33
32
|
category: "apps",
|
|
34
33
|
defaultRiskLevel: RiskLevel.Low,
|
|
35
34
|
executionMode: "proxy",
|
|
35
|
+
executionTarget: "host",
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
return {
|
|
39
|
-
name: this.name,
|
|
40
|
-
description: this.description,
|
|
41
|
-
input_schema: {
|
|
37
|
+
input_schema: {
|
|
42
38
|
type: "object",
|
|
43
39
|
properties: {
|
|
44
40
|
app_id: {
|
|
@@ -54,8 +50,6 @@ const appOpenTool: Tool = {
|
|
|
54
50
|
},
|
|
55
51
|
required: ["app_id"],
|
|
56
52
|
},
|
|
57
|
-
};
|
|
58
|
-
},
|
|
59
53
|
|
|
60
54
|
execute: proxyExecute,
|
|
61
55
|
};
|
|
@@ -51,7 +51,7 @@ const singleQ = {
|
|
|
51
51
|
|
|
52
52
|
describe("AskQuestionTool definition", () => {
|
|
53
53
|
test("exposes the expected schema shape and description language", () => {
|
|
54
|
-
const def = new AskQuestionTool()
|
|
54
|
+
const def = new AskQuestionTool();
|
|
55
55
|
expect(def.name).toBe("ask_question");
|
|
56
56
|
expect(def.description).toContain("free-text fallback is always added");
|
|
57
57
|
expect(def.description).toContain("do not");
|
|
@@ -463,8 +463,8 @@ describe("AskQuestionTool batched input", () => {
|
|
|
463
463
|
|
|
464
464
|
describe("AskQuestionTool definition (batched schema)", () => {
|
|
465
465
|
test("exposes `questions[]` shape, keeps legacy fields, omits per-question id", () => {
|
|
466
|
-
const def = new AskQuestionTool()
|
|
467
|
-
const schema = def.input_schema as {
|
|
466
|
+
const def = new AskQuestionTool();
|
|
467
|
+
const schema = def.input_schema as unknown as {
|
|
468
468
|
properties: Record<
|
|
469
469
|
string,
|
|
470
470
|
{
|