@vellumai/assistant 0.4.46 → 0.4.49
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 +7 -7
- package/README.md +2 -23
- package/docs/architecture/integrations.md +45 -41
- package/docs/architecture/keychain-broker.md +3 -3
- package/docs/architecture/security.md +5 -5
- package/docs/runbook-trusted-contacts.md +3 -8
- package/hook-templates/debug-prompt-logger/hook.json +1 -1
- package/hook-templates/debug-prompt-logger/run.sh +1 -3
- package/package.json +1 -1
- package/src/__tests__/actor-token-service.test.ts +0 -1
- package/src/__tests__/anthropic-provider.test.ts +156 -0
- package/src/__tests__/approval-cascade.test.ts +810 -0
- package/src/__tests__/approval-primitive.test.ts +0 -1
- package/src/__tests__/approval-routes-http.test.ts +2 -0
- package/src/__tests__/assistant-attachments.test.ts +12 -34
- package/src/__tests__/assistant-feature-flag-guardrails.test.ts +76 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +0 -1
- package/src/__tests__/browser-fill-credential.test.ts +5 -2
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
- package/src/__tests__/bundled-skill-retrieval-guard.test.ts +2 -1
- package/src/__tests__/channel-guardian.test.ts +0 -2
- package/src/__tests__/channel-readiness-routes.test.ts +35 -25
- package/src/__tests__/channel-readiness-service.test.ts +10 -9
- package/src/__tests__/checker.test.ts +9 -29
- package/src/__tests__/cli.test.ts +23 -0
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
- package/src/__tests__/computer-use-tools.test.ts +2 -19
- package/src/__tests__/config-watcher.test.ts +0 -1
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
- package/src/__tests__/context-image-dimensions.test.ts +332 -0
- package/src/__tests__/context-token-estimator.test.ts +196 -13
- package/src/__tests__/conversation-attention-store.test.ts +0 -1
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +144 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
- package/src/__tests__/credential-broker-browser-fill.test.ts +23 -22
- package/src/__tests__/credential-broker-server-use.test.ts +22 -21
- package/src/__tests__/credential-broker.test.ts +2 -1
- package/src/__tests__/credential-metadata-store.test.ts +239 -26
- package/src/__tests__/credential-resolve.test.ts +5 -4
- package/src/__tests__/credential-security-e2e.test.ts +8 -8
- package/src/__tests__/credential-security-invariants.test.ts +111 -7
- package/src/__tests__/credential-vault-unit.test.ts +287 -54
- package/src/__tests__/credential-vault.test.ts +406 -12
- package/src/__tests__/credentials-cli.test.ts +82 -6
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
- package/src/__tests__/ephemeral-permissions.test.ts +3 -3
- package/src/__tests__/gateway-only-enforcement.test.ts +4 -2
- package/src/__tests__/gateway-only-guard.test.ts +0 -1
- package/src/__tests__/gemini-image-service.test.ts +75 -45
- package/src/__tests__/gemini-provider.test.ts +9 -6
- package/src/__tests__/guardian-action-conversation-turn.test.ts +1 -33
- package/src/__tests__/guardian-action-copy-generator.test.ts +0 -20
- package/src/__tests__/guardian-action-followup-executor.test.ts +1 -28
- package/src/__tests__/guardian-action-followup-store.test.ts +1 -1
- package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -1
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +0 -1
- package/src/__tests__/guardian-grant-minting.test.ts +35 -0
- package/src/__tests__/guardian-routing-invariants.test.ts +0 -1
- package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -1
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -39
- package/src/__tests__/heartbeat-service.test.ts +0 -1
- package/src/__tests__/host-cu-proxy.test.ts +629 -0
- package/src/__tests__/host-shell-tool.test.ts +27 -15
- package/src/__tests__/http-user-message-parity.test.ts +1 -0
- package/src/__tests__/ingress-url-consistency.test.ts +14 -21
- package/src/__tests__/integration-status.test.ts +38 -25
- package/src/__tests__/intent-routing.test.ts +0 -1
- package/src/__tests__/invite-routes-http.test.ts +10 -9
- package/src/__tests__/keychain-broker-client.test.ts +11 -43
- package/src/__tests__/managed-proxy-context.test.ts +5 -3
- package/src/__tests__/media-generate-image.test.ts +63 -2
- package/src/__tests__/media-reuse-story.e2e.test.ts +7 -3
- package/src/__tests__/messaging-send-tool.test.ts +4 -6
- package/src/__tests__/notification-routing-intent.test.ts +0 -1
- package/src/__tests__/oauth-cli.test.ts +373 -14
- package/src/__tests__/oauth-provider-profiles.test.ts +9 -9
- package/src/__tests__/oauth-scope-policy.test.ts +4 -6
- package/src/__tests__/oauth-store.test.ts +756 -0
- package/src/__tests__/onboarding-starter-tasks.test.ts +0 -1
- package/src/__tests__/provider-error-scenarios.test.ts +0 -1
- package/src/__tests__/provider-fail-open-selection.test.ts +3 -1
- package/src/__tests__/provider-managed-proxy-integration.test.ts +70 -6
- package/src/__tests__/provider-streaming.benchmark.test.ts +0 -1
- package/src/__tests__/public-ingress-urls.test.ts +15 -21
- package/src/__tests__/recording-handler.test.ts +3 -4
- package/src/__tests__/registry.test.ts +2 -2
- package/src/__tests__/runtime-events-sse.test.ts +55 -7
- package/src/__tests__/schedule-store.test.ts +0 -1
- package/src/__tests__/scheduler-recurrence.test.ts +0 -1
- package/src/__tests__/schema-transforms.test.ts +226 -0
- package/src/__tests__/scoped-approval-grants.test.ts +0 -1
- package/src/__tests__/scoped-grant-security-matrix.test.ts +0 -1
- package/src/__tests__/script-proxy-injection-runtime.test.ts +23 -13
- package/src/__tests__/script-proxy-policy-runtime.test.ts +1 -1
- package/src/__tests__/script-proxy-session-manager.test.ts +1 -1
- package/src/__tests__/secret-ingress-handler.test.ts +0 -1
- package/src/__tests__/secret-onetime-send.test.ts +5 -3
- package/src/__tests__/send-endpoint-busy.test.ts +21 -6
- package/src/__tests__/sequence-store.test.ts +0 -1
- package/src/__tests__/session-init.benchmark.test.ts +4 -5
- package/src/__tests__/session-messaging-secret-redirect.test.ts +5 -4
- package/src/__tests__/skill-include-graph.test.ts +66 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +0 -1
- package/src/__tests__/skill-load-tool.test.ts +149 -1
- package/src/__tests__/skill-projection-feature-flag.test.ts +0 -1
- package/src/__tests__/skills-uninstall.test.ts +3 -3
- package/src/__tests__/skills.test.ts +3 -12
- package/src/__tests__/slack-channel-config.test.ts +76 -11
- package/src/__tests__/slack-share-routes.test.ts +17 -14
- package/src/__tests__/system-prompt.test.ts +0 -1
- package/src/__tests__/telegram-bot-username-resolution.test.ts +3 -0
- package/src/__tests__/telegram-invite-adapter.test.ts +18 -22
- package/src/__tests__/terminal-tools.test.ts +4 -3
- package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
- package/src/__tests__/tool-approval-handler.test.ts +0 -1
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
- package/src/__tests__/tool-executor-shell-integration.test.ts +0 -1
- package/src/__tests__/tool-executor.test.ts +0 -1
- package/src/__tests__/tool-grant-request-escalation.test.ts +0 -1
- package/src/__tests__/trust-store-pattern-matches.test.ts +29 -0
- package/src/__tests__/trust-store.test.ts +1 -22
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +0 -1
- package/src/__tests__/twilio-config.test.ts +2 -1
- package/src/__tests__/twilio-provider.test.ts +4 -2
- package/src/__tests__/twilio-routes.test.ts +5 -20
- package/src/__tests__/verification-control-plane-policy.test.ts +0 -1
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -1
- package/src/agent/ax-tree-compaction.test.ts +235 -0
- package/src/agent/loop.ts +76 -130
- package/src/calls/call-domain.ts +8 -10
- package/src/calls/relay-server.ts +9 -13
- package/src/calls/twilio-config.ts +4 -8
- package/src/calls/twilio-provider.ts +2 -1
- package/src/calls/twilio-rest.ts +2 -1
- package/src/calls/twilio-routes.ts +1 -2
- package/src/calls/voice-ingress-preflight.ts +1 -1
- package/src/cli/commands/browser-relay.ts +46 -15
- package/src/cli/commands/completions.ts +0 -3
- package/src/cli/commands/credentials.ts +110 -23
- package/src/cli/commands/oauth/apps.ts +255 -0
- package/src/cli/commands/oauth/connections.ts +299 -0
- package/src/cli/commands/oauth/index.ts +52 -0
- package/src/cli/commands/oauth/providers.ts +242 -0
- package/src/cli/commands/skills.ts +4 -338
- package/src/cli/program.ts +1 -5
- package/src/cli/reference.ts +1 -3
- package/src/cli.ts +3 -2
- package/src/config/assistant-feature-flags.ts +0 -3
- package/src/config/bundled-skills/_shared/CLI_RETRIEVAL_PATTERN.md +1 -1
- package/src/config/bundled-skills/claude-code/TOOLS.json +0 -4
- package/src/config/bundled-skills/computer-use/SKILL.md +3 -6
- package/src/config/bundled-skills/computer-use/TOOLS.json +22 -4
- package/src/config/bundled-skills/contacts/tools/google-contacts.ts +29 -32
- package/src/config/bundled-skills/gmail/SKILL.md +4 -4
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +54 -61
- package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +25 -28
- package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +14 -17
- package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +39 -44
- package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +61 -58
- package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +50 -49
- package/src/config/bundled-skills/gmail/tools/gmail-label.ts +11 -13
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +148 -146
- package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +4 -7
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +175 -173
- package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +4 -7
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +71 -76
- package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +32 -38
- package/src/config/bundled-skills/google-calendar/SKILL.md +2 -2
- package/src/config/bundled-skills/google-calendar/calendar-client.ts +90 -44
- package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +9 -10
- package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +5 -6
- package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +4 -5
- package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +14 -15
- package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +37 -37
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +4 -9
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +24 -3
- package/src/config/bundled-skills/messaging/SKILL.md +6 -6
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +62 -63
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +15 -16
- package/src/config/bundled-skills/messaging/tools/messaging-auth-test.ts +4 -5
- package/src/config/bundled-skills/messaging/tools/messaging-list-conversations.ts +6 -7
- package/src/config/bundled-skills/messaging/tools/messaging-mark-read.ts +4 -5
- package/src/config/bundled-skills/messaging/tools/messaging-read.ts +14 -15
- package/src/config/bundled-skills/messaging/tools/messaging-search.ts +4 -5
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +128 -128
- package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +33 -34
- package/src/config/bundled-skills/messaging/tools/shared.ts +12 -15
- package/src/config/bundled-skills/settings/SKILL.md +1 -1
- package/src/config/bundled-skills/settings/TOOLS.json +2 -8
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +5 -33
- package/src/config/bundled-skills/slack/tools/shared.ts +4 -10
- package/src/config/bundled-skills/slack/tools/slack-add-reaction.ts +4 -5
- package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +15 -16
- package/src/config/bundled-skills/slack/tools/slack-delete-message.ts +4 -5
- package/src/config/bundled-skills/slack/tools/slack-edit-message.ts +4 -5
- package/src/config/bundled-skills/slack/tools/slack-leave-channel.ts +4 -5
- package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +95 -92
- package/src/config/env-registry.ts +14 -83
- package/src/config/env.ts +11 -50
- package/src/config/feature-flag-registry.json +16 -16
- package/src/config/schema.ts +3 -1
- package/src/config/skills.ts +21 -2
- package/src/context/image-dimensions.ts +229 -0
- package/src/context/token-estimator.ts +75 -12
- package/src/context/window-manager.ts +49 -10
- package/src/daemon/assistant-attachments.ts +1 -13
- package/src/daemon/guardian-action-generators.ts +4 -5
- package/src/daemon/handlers/config-ingress.ts +8 -33
- package/src/daemon/handlers/config-slack-channel.ts +76 -56
- package/src/daemon/handlers/config-telegram.ts +53 -24
- package/src/daemon/handlers/sessions.ts +10 -24
- package/src/daemon/handlers/shared.ts +0 -130
- package/src/daemon/host-cu-proxy.ts +401 -0
- package/src/daemon/lifecycle.ts +39 -63
- package/src/daemon/message-protocol.ts +3 -0
- package/src/daemon/message-types/computer-use.ts +2 -119
- package/src/daemon/message-types/host-cu.ts +19 -0
- package/src/daemon/message-types/integrations.ts +1 -0
- package/src/daemon/message-types/messages.ts +3 -0
- package/src/daemon/server.ts +14 -21
- package/src/daemon/session-agent-loop-handlers.ts +2 -0
- package/src/daemon/session-attachments.ts +1 -2
- package/src/daemon/session-messaging.ts +3 -1
- package/src/daemon/session-slash.ts +1 -1
- package/src/daemon/session-surfaces.ts +40 -28
- package/src/daemon/session-tool-setup.ts +20 -11
- package/src/daemon/session.ts +139 -16
- package/src/daemon/tool-side-effects.ts +2 -8
- package/src/daemon/watch-handler.ts +2 -2
- package/src/email/providers/index.ts +2 -1
- package/src/events/tool-metrics-listener.ts +2 -2
- package/src/hooks/manager.ts +1 -4
- package/src/inbound/public-ingress-urls.ts +7 -7
- package/src/instrument.ts +15 -1
- package/src/logfire.ts +16 -5
- package/src/media/app-icon-generator.ts +30 -4
- package/src/media/avatar-router.ts +26 -3
- package/src/media/gemini-image-service.ts +28 -2
- package/src/memory/conversation-key-store.ts +21 -0
- package/src/memory/db-init.ts +4 -0
- package/src/memory/guardian-action-store.ts +1 -1
- package/src/memory/migrations/149-oauth-tables.ts +60 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/schema/guardian.ts +1 -1
- package/src/memory/schema/index.ts +1 -0
- package/src/memory/schema/oauth.ts +65 -0
- package/src/messaging/provider.ts +19 -13
- package/src/messaging/providers/gmail/adapter.ts +40 -23
- package/src/messaging/providers/gmail/client.ts +283 -122
- package/src/messaging/providers/gmail/people-client.ts +32 -24
- package/src/messaging/providers/slack/adapter.ts +29 -19
- package/src/messaging/providers/slack/client.ts +265 -78
- package/src/messaging/providers/telegram-bot/adapter.ts +19 -18
- package/src/messaging/providers/whatsapp/adapter.ts +17 -11
- package/src/messaging/registry.ts +2 -31
- package/src/notifications/copy-composer.ts +0 -5
- package/src/notifications/signal.ts +4 -5
- package/src/oauth/byo-connection.test.ts +537 -0
- package/src/oauth/byo-connection.ts +128 -0
- package/src/oauth/connect-orchestrator.ts +139 -56
- package/src/oauth/connect-types.ts +17 -23
- package/src/oauth/connection-resolver.ts +58 -0
- package/src/oauth/connection.ts +38 -0
- package/src/oauth/manual-token-connection.ts +104 -0
- package/src/oauth/oauth-store.ts +496 -0
- package/src/oauth/platform-connection.test.ts +192 -0
- package/src/oauth/platform-connection.ts +111 -0
- package/src/oauth/provider-behaviors.ts +124 -0
- package/src/oauth/scope-policy.ts +9 -2
- package/src/oauth/seed-providers.ts +161 -0
- package/src/oauth/token-persistence.ts +74 -78
- package/src/permissions/checker.ts +8 -4
- package/src/permissions/defaults.ts +0 -1
- package/src/permissions/prompter.ts +10 -1
- package/src/permissions/trust-store.ts +13 -0
- package/src/prompts/__tests__/build-cli-reference-section.test.ts +3 -1
- package/src/prompts/system-prompt.ts +70 -45
- package/src/providers/anthropic/client.ts +133 -24
- package/src/providers/gemini/client.ts +15 -6
- package/src/providers/managed-proxy/constants.ts +2 -2
- package/src/providers/managed-proxy/context.ts +5 -1
- package/src/providers/ratelimit.ts +17 -0
- package/src/providers/registry.ts +2 -2
- package/src/providers/retry.ts +1 -27
- package/src/runtime/AGENTS.md +17 -0
- package/src/runtime/auth/route-policy.ts +0 -3
- package/src/runtime/channel-invite-transports/telegram.ts +2 -1
- package/src/runtime/channel-readiness-service.ts +168 -195
- package/src/runtime/channel-readiness-types.ts +4 -0
- package/src/runtime/channel-reply-delivery.ts +0 -40
- package/src/runtime/gateway-client.ts +0 -7
- package/src/runtime/guardian-action-conversation-turn.ts +1 -3
- package/src/runtime/guardian-action-followup-executor.ts +1 -1
- package/src/runtime/guardian-action-message-composer.ts +3 -23
- package/src/runtime/http-server.ts +17 -10
- package/src/runtime/http-types.ts +2 -3
- package/src/runtime/middleware/rate-limiter.ts +74 -20
- package/src/runtime/middleware/twilio-validation.ts +1 -11
- package/src/runtime/pending-interactions.ts +14 -12
- package/src/runtime/routes/channel-delivery-routes.ts +0 -1
- package/src/runtime/routes/channel-readiness-routes.ts +2 -0
- package/src/runtime/routes/conversation-routes.ts +73 -19
- package/src/runtime/routes/diagnostics-routes.ts +11 -9
- package/src/runtime/routes/events-routes.ts +21 -11
- package/src/runtime/routes/guardian-approval-interception.ts +20 -5
- package/src/runtime/routes/host-cu-routes.ts +97 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +12 -111
- package/src/runtime/routes/integrations/slack/share.ts +6 -6
- package/src/runtime/routes/integrations/twilio.ts +6 -5
- package/src/runtime/routes/log-export-routes.ts +126 -8
- package/src/runtime/routes/secret-routes.ts +3 -2
- package/src/runtime/routes/settings-routes.ts +113 -48
- package/src/runtime/routes/surface-action-routes.ts +1 -1
- package/src/runtime/routes/watch-routes.ts +128 -0
- package/src/schedule/integration-status.ts +10 -8
- package/src/security/credential-key.ts +14 -0
- package/src/security/keychain-broker-client.ts +5 -6
- package/src/security/oauth2.ts +1 -1
- package/src/security/token-manager.ts +145 -43
- package/src/skills/catalog-install.ts +358 -0
- package/src/skills/include-graph.ts +32 -0
- package/src/telegram/bot-username.ts +2 -3
- package/src/tools/apps/definitions.ts +0 -5
- package/src/tools/assets/materialize.ts +0 -5
- package/src/tools/assets/search.ts +0 -5
- package/src/tools/browser/headless-browser.ts +1 -67
- package/src/tools/browser/network-recorder.ts +1 -1
- package/src/tools/browser/network-recording-types.ts +1 -1
- package/src/tools/claude-code/claude-code.ts +0 -5
- package/src/tools/computer-use/definitions.ts +46 -11
- package/src/tools/computer-use/registry.ts +4 -5
- package/src/tools/credentials/broker.ts +5 -4
- package/src/tools/credentials/metadata-store.ts +22 -74
- package/src/tools/credentials/resolve.ts +2 -1
- package/src/tools/credentials/vault.ts +139 -151
- package/src/tools/filesystem/edit.ts +1 -6
- package/src/tools/filesystem/read.ts +0 -5
- package/src/tools/filesystem/write.ts +1 -6
- package/src/tools/host-filesystem/edit.ts +1 -6
- package/src/tools/host-filesystem/read.ts +1 -6
- package/src/tools/host-filesystem/write.ts +1 -6
- package/src/tools/mcp/mcp-tool-factory.ts +18 -1
- package/src/tools/memory/definitions.ts +0 -5
- package/src/tools/network/web-fetch.ts +0 -5
- package/src/tools/network/web-search.ts +0 -5
- package/src/tools/registry.ts +2 -7
- package/src/tools/schema-transforms.ts +99 -0
- package/src/tools/skills/load.ts +62 -8
- package/src/tools/swarm/delegate.ts +0 -5
- package/src/tools/system/avatar-generator.ts +0 -5
- package/src/tools/ui-surface/definitions.ts +0 -15
- package/src/tools/watch/screen-watch.ts +0 -5
- package/src/tools/watch/watch-state.ts +0 -12
- package/src/util/logger.ts +7 -41
- package/src/util/platform.ts +9 -28
- package/src/version.ts +10 -0
- package/src/watcher/providers/github.ts +51 -52
- package/src/watcher/providers/gmail.ts +88 -80
- package/src/watcher/providers/google-calendar.ts +94 -86
- package/src/watcher/providers/linear.ts +87 -93
- package/src/__tests__/computer-use-session-compaction.test.ts +0 -143
- package/src/__tests__/computer-use-session-lifecycle.test.ts +0 -322
- package/src/__tests__/computer-use-session-working-dir.test.ts +0 -166
- package/src/__tests__/computer-use-skill-baseline.test.ts +0 -78
- package/src/__tests__/computer-use-skill-endstate.test.ts +0 -105
- package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +0 -249
- package/src/__tests__/ride-shotgun-handler.test.ts +0 -452
- package/src/cli/commands/dev.ts +0 -129
- package/src/cli/commands/map.ts +0 -391
- package/src/cli/commands/oauth.ts +0 -77
- package/src/config/bundled-skills/computer-use/tools/computer-use-request-control.ts +0 -16
- package/src/daemon/computer-use-session.ts +0 -1020
- package/src/daemon/ride-shotgun-handler.ts +0 -567
- package/src/oauth/provider-profiles.ts +0 -192
- package/src/prompts/computer-use-prompt.ts +0 -98
- package/src/runtime/routes/computer-use-routes.ts +0 -641
- package/src/runtime/telegram-streaming-delivery.test.ts +0 -597
- package/src/runtime/telegram-streaming-delivery.ts +0 -383
- package/src/tools/computer-use/request-computer-control.ts +0 -61
|
@@ -47,12 +47,12 @@ function readMockTwilioAccountSid(): string | undefined {
|
|
|
47
47
|
const twilio = (mockRawConfigStore.twilio ?? {}) as Record<string, unknown>;
|
|
48
48
|
return (
|
|
49
49
|
(twilio.accountSid as string | undefined) ??
|
|
50
|
-
mockSecureKeyStore["
|
|
50
|
+
mockSecureKeyStore[credentialKey("twilio", "account_sid")]
|
|
51
51
|
);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
function readMockTwilioAuthToken(): string | undefined {
|
|
55
|
-
return mockSecureKeyStore["
|
|
55
|
+
return mockSecureKeyStore[credentialKey("twilio", "auth_token")];
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
function readMockTwilioPhoneNumber(): string | undefined {
|
|
@@ -77,7 +77,6 @@ function resolveIngressBaseUrlFromConfig(ingressConfig: unknown): string {
|
|
|
77
77
|
|
|
78
78
|
mock.module("../config/env.js", () => ({
|
|
79
79
|
isHttpAuthDisabled: () => true,
|
|
80
|
-
getCallWelcomeGreeting: () => process.env.CALL_WELCOME_GREETING || undefined,
|
|
81
80
|
getGatewayInternalBaseUrl: () => "http://gateway.internal:7830",
|
|
82
81
|
getIngressPublicBaseUrl: () => mockIngressPublicBaseUrl,
|
|
83
82
|
setIngressPublicBaseUrl: (value: string | undefined) => {
|
|
@@ -109,13 +108,11 @@ mock.module("../util/logger.js", () => ({
|
|
|
109
108
|
debug: () => {},
|
|
110
109
|
trace: () => {},
|
|
111
110
|
fatal: () => {},
|
|
112
|
-
isDebug: () => false,
|
|
113
111
|
child: () => ({
|
|
114
112
|
info: () => {},
|
|
115
113
|
warn: () => {},
|
|
116
114
|
error: () => {},
|
|
117
115
|
debug: () => {},
|
|
118
|
-
isDebug: () => false,
|
|
119
116
|
}),
|
|
120
117
|
}),
|
|
121
118
|
}));
|
|
@@ -333,6 +330,7 @@ import {
|
|
|
333
330
|
handleProvisionTwilioNumber,
|
|
334
331
|
handleSetTwilioCredentials,
|
|
335
332
|
} from "../runtime/routes/integrations/twilio.js";
|
|
333
|
+
import { credentialKey } from "../security/credential-key.js";
|
|
336
334
|
|
|
337
335
|
initializeDb();
|
|
338
336
|
|
|
@@ -429,15 +427,14 @@ describe("twilio webhook routes", () => {
|
|
|
429
427
|
twilio: { accountSid: "AC_existing", phoneNumber: "+15550001111" },
|
|
430
428
|
};
|
|
431
429
|
mockSecureKeyStore = {
|
|
432
|
-
"
|
|
433
|
-
"
|
|
430
|
+
[credentialKey("twilio", "account_sid")]: "AC_existing",
|
|
431
|
+
[credentialKey("twilio", "auth_token")]: "test-auth-token",
|
|
434
432
|
};
|
|
435
433
|
mockAvailableNumbers = [{ phoneNumber: "+15556667777" }];
|
|
436
434
|
mockProvisionedNumber = { phoneNumber: "+15556667777" };
|
|
437
435
|
updatePhoneNumberWebhookCalls = [];
|
|
438
436
|
mockTwilioApiValidationStatus = 200;
|
|
439
437
|
mockTwilioApiValidationBody = JSON.stringify({ sid: "AC_validated" });
|
|
440
|
-
delete process.env.CALL_WELCOME_GREETING;
|
|
441
438
|
|
|
442
439
|
globalThis.fetch = (async (
|
|
443
440
|
url: string | URL | Request,
|
|
@@ -818,18 +815,6 @@ describe("twilio webhook routes", () => {
|
|
|
818
815
|
const twiml = await res.text();
|
|
819
816
|
expect(twiml).not.toContain("welcomeGreeting=");
|
|
820
817
|
});
|
|
821
|
-
|
|
822
|
-
test("TwiML includes explicit welcome greeting override when configured", async () => {
|
|
823
|
-
process.env.CALL_WELCOME_GREETING = "Custom transport greeting";
|
|
824
|
-
const session = createTestSession("conv-twiml-4", "CA_twiml_4");
|
|
825
|
-
const req = makeVoiceRequest(session.id, { CallSid: "CA_twiml_4" });
|
|
826
|
-
|
|
827
|
-
const res = await handleVoiceWebhook(req);
|
|
828
|
-
|
|
829
|
-
expect(res.status).toBe(200);
|
|
830
|
-
const twiml = await res.text();
|
|
831
|
-
expect(twiml).toContain('welcomeGreeting="Custom transport greeting"');
|
|
832
|
-
});
|
|
833
818
|
});
|
|
834
819
|
|
|
835
820
|
// ── Handler-level idempotency concurrency tests ─────────────────
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import type { Message } from "../providers/types.js";
|
|
4
|
+
import { compactAxTreeHistory, escapeAxTreeContent } from "./loop.js";
|
|
5
|
+
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// Helpers
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
/** Build a user message with a single tool_result containing an AX tree. */
|
|
11
|
+
function axTreeToolResult(id: string, axContent: string): Message {
|
|
12
|
+
return {
|
|
13
|
+
role: "user",
|
|
14
|
+
content: [
|
|
15
|
+
{
|
|
16
|
+
type: "tool_result",
|
|
17
|
+
tool_use_id: id,
|
|
18
|
+
content: `Some preamble\n<ax-tree>\n${axContent}\n</ax-tree>`,
|
|
19
|
+
is_error: false,
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Build an assistant message (no tool use). */
|
|
26
|
+
function assistantText(text: string): Message {
|
|
27
|
+
return {
|
|
28
|
+
role: "assistant",
|
|
29
|
+
content: [{ type: "text", text }],
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Build a user message without AX tree content. */
|
|
34
|
+
function userText(text: string): Message {
|
|
35
|
+
return {
|
|
36
|
+
role: "user",
|
|
37
|
+
content: [{ type: "text", text }],
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// compactAxTreeHistory
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
describe("compactAxTreeHistory", () => {
|
|
46
|
+
test("returns messages unchanged when fewer than MAX_AX_TREES_IN_HISTORY AX trees", () => {
|
|
47
|
+
const messages: Message[] = [
|
|
48
|
+
axTreeToolResult("t1", "tree-1"),
|
|
49
|
+
assistantText("ok"),
|
|
50
|
+
axTreeToolResult("t2", "tree-2"),
|
|
51
|
+
];
|
|
52
|
+
const result = compactAxTreeHistory(messages);
|
|
53
|
+
expect(result).toBe(messages); // same reference — no copy
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("returns messages unchanged when exactly MAX_AX_TREES_IN_HISTORY AX trees", () => {
|
|
57
|
+
const messages: Message[] = [
|
|
58
|
+
axTreeToolResult("t1", "tree-1"),
|
|
59
|
+
assistantText("ok"),
|
|
60
|
+
axTreeToolResult("t2", "tree-2"),
|
|
61
|
+
];
|
|
62
|
+
const result = compactAxTreeHistory(messages);
|
|
63
|
+
expect(result).toBe(messages);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("strips oldest AX trees, keeps only last 2 from 5", () => {
|
|
67
|
+
const messages: Message[] = [
|
|
68
|
+
axTreeToolResult("t1", "tree-1"),
|
|
69
|
+
assistantText("ok"),
|
|
70
|
+
axTreeToolResult("t2", "tree-2"),
|
|
71
|
+
assistantText("ok"),
|
|
72
|
+
axTreeToolResult("t3", "tree-3"),
|
|
73
|
+
assistantText("ok"),
|
|
74
|
+
axTreeToolResult("t4", "tree-4"),
|
|
75
|
+
assistantText("ok"),
|
|
76
|
+
axTreeToolResult("t5", "tree-5"),
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
const result = compactAxTreeHistory(messages);
|
|
80
|
+
|
|
81
|
+
// Messages at indices 0, 2, 4 should have AX trees stripped (t1, t2, t3)
|
|
82
|
+
for (const idx of [0, 2, 4]) {
|
|
83
|
+
const block = result[idx].content[0];
|
|
84
|
+
expect(block.type).toBe("tool_result");
|
|
85
|
+
if (block.type === "tool_result") {
|
|
86
|
+
expect(block.content).not.toContain("<ax-tree>");
|
|
87
|
+
expect(block.content).toContain("<ax_tree_omitted />");
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Messages at indices 6, 8 should still have AX trees (t4, t5)
|
|
92
|
+
for (const idx of [6, 8]) {
|
|
93
|
+
const block = result[idx].content[0];
|
|
94
|
+
expect(block.type).toBe("tool_result");
|
|
95
|
+
if (block.type === "tool_result") {
|
|
96
|
+
expect(block.content).toContain("<ax-tree>");
|
|
97
|
+
expect(block.content).not.toContain("<ax_tree_omitted />");
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("does not modify assistant messages", () => {
|
|
103
|
+
const messages: Message[] = [
|
|
104
|
+
axTreeToolResult("t1", "tree-1"),
|
|
105
|
+
assistantText("ok"),
|
|
106
|
+
axTreeToolResult("t2", "tree-2"),
|
|
107
|
+
assistantText("response with <ax-tree>fake</ax-tree>"),
|
|
108
|
+
axTreeToolResult("t3", "tree-3"),
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
const result = compactAxTreeHistory(messages);
|
|
112
|
+
|
|
113
|
+
// Assistant message should be unchanged
|
|
114
|
+
const assistantMsg = result[3];
|
|
115
|
+
expect(assistantMsg.content[0].type).toBe("text");
|
|
116
|
+
if (assistantMsg.content[0].type === "text") {
|
|
117
|
+
expect(assistantMsg.content[0].text).toContain("<ax-tree>");
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("does not modify user messages without tool_result blocks", () => {
|
|
122
|
+
const messages: Message[] = [
|
|
123
|
+
axTreeToolResult("t1", "tree-1"),
|
|
124
|
+
assistantText("ok"),
|
|
125
|
+
axTreeToolResult("t2", "tree-2"),
|
|
126
|
+
assistantText("ok"),
|
|
127
|
+
userText("Please help"),
|
|
128
|
+
axTreeToolResult("t3", "tree-3"),
|
|
129
|
+
];
|
|
130
|
+
|
|
131
|
+
const result = compactAxTreeHistory(messages);
|
|
132
|
+
|
|
133
|
+
// The plain user text message should be untouched
|
|
134
|
+
expect(result[4]).toBe(messages[4]);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("preserves non-AX-tree tool_result blocks in stripped messages", () => {
|
|
138
|
+
const messages: Message[] = [
|
|
139
|
+
{
|
|
140
|
+
role: "user",
|
|
141
|
+
content: [
|
|
142
|
+
{
|
|
143
|
+
type: "tool_result",
|
|
144
|
+
tool_use_id: "t1",
|
|
145
|
+
content: "normal result without ax tree",
|
|
146
|
+
is_error: false,
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
type: "tool_result",
|
|
150
|
+
tool_use_id: "t1-ax",
|
|
151
|
+
content: "<ax-tree>\ntree-1\n</ax-tree>",
|
|
152
|
+
is_error: false,
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
},
|
|
156
|
+
assistantText("ok"),
|
|
157
|
+
axTreeToolResult("t2", "tree-2"),
|
|
158
|
+
assistantText("ok"),
|
|
159
|
+
axTreeToolResult("t3", "tree-3"),
|
|
160
|
+
];
|
|
161
|
+
|
|
162
|
+
const result = compactAxTreeHistory(messages);
|
|
163
|
+
|
|
164
|
+
// First message should have the AX tree stripped but normal result preserved
|
|
165
|
+
const firstMsg = result[0];
|
|
166
|
+
const normalBlock = firstMsg.content[0];
|
|
167
|
+
expect(normalBlock.type).toBe("tool_result");
|
|
168
|
+
if (normalBlock.type === "tool_result") {
|
|
169
|
+
expect(normalBlock.content).toBe("normal result without ax tree");
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const axBlock = firstMsg.content[1];
|
|
173
|
+
expect(axBlock.type).toBe("tool_result");
|
|
174
|
+
if (axBlock.type === "tool_result") {
|
|
175
|
+
expect(axBlock.content).toContain("<ax_tree_omitted />");
|
|
176
|
+
expect(axBlock.content).not.toContain("<ax-tree>");
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("returns empty array for empty input", () => {
|
|
181
|
+
const result = compactAxTreeHistory([]);
|
|
182
|
+
expect(result).toEqual([]);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test("is pure — does not mutate input messages", () => {
|
|
186
|
+
const messages: Message[] = [
|
|
187
|
+
axTreeToolResult("t1", "tree-1"),
|
|
188
|
+
assistantText("ok"),
|
|
189
|
+
axTreeToolResult("t2", "tree-2"),
|
|
190
|
+
assistantText("ok"),
|
|
191
|
+
axTreeToolResult("t3", "tree-3"),
|
|
192
|
+
];
|
|
193
|
+
|
|
194
|
+
// Deep copy to compare later
|
|
195
|
+
const originalContent = messages[0].content[0];
|
|
196
|
+
const originalText =
|
|
197
|
+
originalContent.type === "tool_result" ? originalContent.content : "";
|
|
198
|
+
|
|
199
|
+
compactAxTreeHistory(messages);
|
|
200
|
+
|
|
201
|
+
// Original message should be unchanged
|
|
202
|
+
const afterContent = messages[0].content[0];
|
|
203
|
+
const afterText =
|
|
204
|
+
afterContent.type === "tool_result" ? afterContent.content : "";
|
|
205
|
+
expect(afterText).toBe(originalText);
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// ---------------------------------------------------------------------------
|
|
210
|
+
// escapeAxTreeContent
|
|
211
|
+
// ---------------------------------------------------------------------------
|
|
212
|
+
|
|
213
|
+
describe("escapeAxTreeContent", () => {
|
|
214
|
+
test("escapes literal </ax-tree> inside content", () => {
|
|
215
|
+
const input = "Some XML: <div></ax-tree></div>";
|
|
216
|
+
const result = escapeAxTreeContent(input);
|
|
217
|
+
expect(result).toBe("Some XML: <div></ax-tree></div>");
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test("handles case-insensitive matches", () => {
|
|
221
|
+
const input = "</AX-TREE> and </Ax-Tree>";
|
|
222
|
+
const result = escapeAxTreeContent(input);
|
|
223
|
+
expect(result).toBe("</ax-tree> and </ax-tree>");
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test("returns content unchanged when no closing tags present", () => {
|
|
227
|
+
const input = "Normal AX tree content with <ax-tree> opening tag";
|
|
228
|
+
const result = escapeAxTreeContent(input);
|
|
229
|
+
expect(result).toBe(input);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test("handles empty string", () => {
|
|
233
|
+
expect(escapeAxTreeContent("")).toBe("");
|
|
234
|
+
});
|
|
235
|
+
});
|
package/src/agent/loop.ts
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
applyStreamingSubstitution,
|
|
15
15
|
applySubstitutions,
|
|
16
16
|
} from "../tools/sensitive-output-placeholders.js";
|
|
17
|
-
import { getLogger
|
|
17
|
+
import { getLogger } from "../util/logger.js";
|
|
18
18
|
|
|
19
19
|
const log = getLogger("agent-loop");
|
|
20
20
|
|
|
@@ -179,7 +179,6 @@ export class AgentLoop {
|
|
|
179
179
|
let toolUseTurns = 0;
|
|
180
180
|
let nudgedForEmptyResponse = false;
|
|
181
181
|
let lastLlmCallTime = 0;
|
|
182
|
-
const debug = isDebug();
|
|
183
182
|
const rlog = requestId ? log.child({ requestId }) : log;
|
|
184
183
|
|
|
185
184
|
// Per-run substitution map for sensitive output placeholders.
|
|
@@ -191,7 +190,6 @@ export class AgentLoop {
|
|
|
191
190
|
while (true) {
|
|
192
191
|
if (signal?.aborted) break;
|
|
193
192
|
|
|
194
|
-
const turnStart = Date.now();
|
|
195
193
|
let toolUseBlocks: Extract<ContentBlock, { type: "tool_use" }>[] = [];
|
|
196
194
|
|
|
197
195
|
try {
|
|
@@ -227,22 +225,6 @@ export class AgentLoop {
|
|
|
227
225
|
providerConfig.tool_choice = this.config.toolChoice;
|
|
228
226
|
}
|
|
229
227
|
|
|
230
|
-
if (debug) {
|
|
231
|
-
rlog.debug(
|
|
232
|
-
{
|
|
233
|
-
systemPrompt: truncateForLog(turnSystemPrompt, 200),
|
|
234
|
-
messageCount: history.length,
|
|
235
|
-
lastMessage:
|
|
236
|
-
history.length > 0
|
|
237
|
-
? summarizeMessage(history[history.length - 1])
|
|
238
|
-
: null,
|
|
239
|
-
toolCount: currentTools.length,
|
|
240
|
-
config: providerConfig,
|
|
241
|
-
},
|
|
242
|
-
"Sending request to provider",
|
|
243
|
-
);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
228
|
const preLlmResult = await getHookManager().trigger("pre-llm-call", {
|
|
247
229
|
systemPrompt: turnSystemPrompt,
|
|
248
230
|
messages: history,
|
|
@@ -275,7 +257,11 @@ export class AgentLoop {
|
|
|
275
257
|
// screenshots from accumulating in the context window. The LLM
|
|
276
258
|
// already saw each image on the turn it was captured; keeping
|
|
277
259
|
// base64 blobs in history rapidly exhausts the context budget.
|
|
278
|
-
|
|
260
|
+
// Also strip old AX tree snapshots to keep TTFT from growing
|
|
261
|
+
// linearly with step count in computer-use sessions.
|
|
262
|
+
const providerHistory = compactAxTreeHistory(
|
|
263
|
+
stripOldImageBlocks(history),
|
|
264
|
+
);
|
|
279
265
|
|
|
280
266
|
const response = await this.provider.sendMessage(
|
|
281
267
|
providerHistory,
|
|
@@ -328,33 +314,6 @@ export class AgentLoop {
|
|
|
328
314
|
|
|
329
315
|
const providerDurationMs = Date.now() - providerStart;
|
|
330
316
|
|
|
331
|
-
if (debug) {
|
|
332
|
-
rlog.debug(
|
|
333
|
-
{
|
|
334
|
-
providerDurationMs,
|
|
335
|
-
model: response.model,
|
|
336
|
-
stopReason: response.stopReason,
|
|
337
|
-
inputTokens: response.usage.inputTokens,
|
|
338
|
-
outputTokens: response.usage.outputTokens,
|
|
339
|
-
cacheCreationInputTokens: response.usage.cacheCreationInputTokens,
|
|
340
|
-
cacheReadInputTokens: response.usage.cacheReadInputTokens,
|
|
341
|
-
contentBlocks: response.content.map((b) => ({
|
|
342
|
-
type: b.type,
|
|
343
|
-
...(b.type === "text"
|
|
344
|
-
? { text: truncateForLog(b.text, 1200) }
|
|
345
|
-
: {}),
|
|
346
|
-
...(b.type === "tool_use"
|
|
347
|
-
? {
|
|
348
|
-
name: b.name,
|
|
349
|
-
input: truncateForLog(JSON.stringify(b.input), 1200),
|
|
350
|
-
}
|
|
351
|
-
: {}),
|
|
352
|
-
})),
|
|
353
|
-
},
|
|
354
|
-
"Provider response received",
|
|
355
|
-
);
|
|
356
|
-
}
|
|
357
|
-
|
|
358
317
|
onEvent({
|
|
359
318
|
type: "usage",
|
|
360
319
|
inputTokens: response.usage.inputTokens,
|
|
@@ -443,16 +402,6 @@ export class AgentLoop {
|
|
|
443
402
|
name: toolUse.name,
|
|
444
403
|
input: toolUse.input,
|
|
445
404
|
});
|
|
446
|
-
|
|
447
|
-
if (debug) {
|
|
448
|
-
rlog.debug(
|
|
449
|
-
{
|
|
450
|
-
tool: toolUse.name,
|
|
451
|
-
input: truncateForLog(JSON.stringify(toolUse.input), 300),
|
|
452
|
-
},
|
|
453
|
-
"Executing tool",
|
|
454
|
-
);
|
|
455
|
-
}
|
|
456
405
|
}
|
|
457
406
|
|
|
458
407
|
// If already cancelled, synthesize cancelled results and stop
|
|
@@ -469,49 +418,11 @@ export class AgentLoop {
|
|
|
469
418
|
break;
|
|
470
419
|
}
|
|
471
420
|
|
|
472
|
-
// Guard against dual-control-mode conflicts in a single turn.
|
|
473
|
-
// If the model escalates to foreground computer control, browser_* tools
|
|
474
|
-
// in the same response create competing browser sessions/windows and can
|
|
475
|
-
// thrash renderer CPU. Reject browser_* calls in that turn.
|
|
476
|
-
const hasComputerUseEscalation = toolUseBlocks.some(
|
|
477
|
-
(toolUse) => toolUse.name === "computer_use_request_control",
|
|
478
|
-
);
|
|
479
|
-
const blockedBrowserToolIds = hasComputerUseEscalation
|
|
480
|
-
? new Set(
|
|
481
|
-
toolUseBlocks
|
|
482
|
-
.filter((toolUse) => toolUse.name.startsWith("browser_"))
|
|
483
|
-
.map((toolUse) => toolUse.id),
|
|
484
|
-
)
|
|
485
|
-
: new Set<string>();
|
|
486
|
-
|
|
487
|
-
if (blockedBrowserToolIds.size > 0) {
|
|
488
|
-
log.warn(
|
|
489
|
-
{
|
|
490
|
-
blockedBrowserToolCount: blockedBrowserToolIds.size,
|
|
491
|
-
toolNames: toolUseBlocks.map((toolUse) => toolUse.name),
|
|
492
|
-
},
|
|
493
|
-
"Blocking browser_* tools: computer_use_request_control was requested in same turn",
|
|
494
|
-
);
|
|
495
|
-
}
|
|
496
|
-
|
|
497
421
|
// Execute all tools concurrently for reduced latency.
|
|
498
422
|
// Race against the abort signal so cancellation isn't blocked by
|
|
499
423
|
// stuck tools (e.g. a hung browser navigation).
|
|
500
424
|
const toolExecutionPromise = Promise.all(
|
|
501
425
|
toolUseBlocks.map(async (toolUse) => {
|
|
502
|
-
const toolStart = Date.now();
|
|
503
|
-
|
|
504
|
-
if (blockedBrowserToolIds.has(toolUse.id)) {
|
|
505
|
-
return {
|
|
506
|
-
toolUse,
|
|
507
|
-
result: {
|
|
508
|
-
content:
|
|
509
|
-
"Error: browser_* tools cannot run in the same turn as computer_use_request_control. Continue using the foreground computer-use session only.",
|
|
510
|
-
isError: true,
|
|
511
|
-
},
|
|
512
|
-
};
|
|
513
|
-
}
|
|
514
|
-
|
|
515
426
|
const result = await this.toolExecutor!(
|
|
516
427
|
toolUse.name,
|
|
517
428
|
toolUse.input,
|
|
@@ -525,20 +436,6 @@ export class AgentLoop {
|
|
|
525
436
|
toolUse.id,
|
|
526
437
|
);
|
|
527
438
|
|
|
528
|
-
const toolDurationMs = Date.now() - toolStart;
|
|
529
|
-
|
|
530
|
-
if (debug) {
|
|
531
|
-
rlog.debug(
|
|
532
|
-
{
|
|
533
|
-
tool: toolUse.name,
|
|
534
|
-
toolDurationMs,
|
|
535
|
-
isError: result.isError,
|
|
536
|
-
output: truncateForLog(result.content, 300),
|
|
537
|
-
},
|
|
538
|
-
"Tool execution complete",
|
|
539
|
-
);
|
|
540
|
-
}
|
|
541
|
-
|
|
542
439
|
return { toolUse, result };
|
|
543
440
|
}),
|
|
544
441
|
);
|
|
@@ -658,19 +555,6 @@ export class AgentLoop {
|
|
|
658
555
|
// Add tool results as a user message and continue the loop
|
|
659
556
|
history.push({ role: "user", content: resultBlocks });
|
|
660
557
|
|
|
661
|
-
if (debug) {
|
|
662
|
-
const turnDurationMs = Date.now() - turnStart;
|
|
663
|
-
rlog.debug(
|
|
664
|
-
{
|
|
665
|
-
turnDurationMs,
|
|
666
|
-
providerDurationMs,
|
|
667
|
-
toolCount: toolUseBlocks.length,
|
|
668
|
-
turn: toolUseTurns,
|
|
669
|
-
},
|
|
670
|
-
"Turn complete",
|
|
671
|
-
);
|
|
672
|
-
}
|
|
673
|
-
|
|
674
558
|
// Invoke checkpoint callback after tool results are in history
|
|
675
559
|
if (onCheckpoint) {
|
|
676
560
|
const decision = onCheckpoint({
|
|
@@ -715,14 +599,76 @@ export class AgentLoop {
|
|
|
715
599
|
}
|
|
716
600
|
}
|
|
717
601
|
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
602
|
+
/** Number of most-recent AX tree snapshots to keep in conversation history. */
|
|
603
|
+
const MAX_AX_TREES_IN_HISTORY = 2;
|
|
604
|
+
|
|
605
|
+
/** Regex that matches the `<ax-tree>...</ax-tree>` markers. */
|
|
606
|
+
const AX_TREE_PATTERN = /<ax-tree>[\s\S]*?<\/ax-tree>/g;
|
|
607
|
+
const AX_TREE_PLACEHOLDER = "<ax_tree_omitted />";
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* Escapes any literal `</ax-tree>` occurrences inside AX tree content so
|
|
611
|
+
* that the non-greedy compaction regex (`AX_TREE_PATTERN`) does not stop
|
|
612
|
+
* prematurely when the user happens to be viewing XML/HTML source that
|
|
613
|
+
* contains the closing tag. The escaped content does not need to be
|
|
614
|
+
* unescaped because compaction replaces the entire block with a placeholder.
|
|
615
|
+
*/
|
|
616
|
+
export function escapeAxTreeContent(content: string): string {
|
|
617
|
+
return content.replace(/<\/ax-tree>/gi, "</ax-tree>");
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Returns a shallow copy of `messages` where all but the most recent
|
|
622
|
+
* `MAX_AX_TREES_IN_HISTORY` `<ax-tree>` blocks have been replaced with a
|
|
623
|
+
* short placeholder. This keeps the conversation context small so that
|
|
624
|
+
* TTFT does not grow linearly with step count in computer-use sessions.
|
|
625
|
+
*/
|
|
626
|
+
export function compactAxTreeHistory(messages: Message[]): Message[] {
|
|
627
|
+
// Collect indices of user messages that contain an <ax-tree> block
|
|
628
|
+
const indicesWithAxTree: number[] = [];
|
|
629
|
+
for (let i = 0; i < messages.length; i++) {
|
|
630
|
+
const msg = messages[i];
|
|
631
|
+
if (msg.role !== "user") continue;
|
|
632
|
+
for (const block of msg.content) {
|
|
633
|
+
if (
|
|
634
|
+
block.type === "tool_result" &&
|
|
635
|
+
typeof block.content === "string" &&
|
|
636
|
+
block.content.includes("<ax-tree>")
|
|
637
|
+
) {
|
|
638
|
+
indicesWithAxTree.push(i);
|
|
639
|
+
break;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
if (indicesWithAxTree.length <= MAX_AX_TREES_IN_HISTORY) {
|
|
645
|
+
return messages;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
const toStrip = new Set(indicesWithAxTree.slice(0, -MAX_AX_TREES_IN_HISTORY));
|
|
649
|
+
|
|
650
|
+
return messages.map((msg, idx) => {
|
|
651
|
+
if (!toStrip.has(idx)) return msg;
|
|
652
|
+
return {
|
|
653
|
+
...msg,
|
|
654
|
+
content: msg.content.map((block) => {
|
|
655
|
+
if (
|
|
656
|
+
block.type === "tool_result" &&
|
|
657
|
+
typeof block.content === "string" &&
|
|
658
|
+
block.content.includes("<ax-tree>")
|
|
659
|
+
) {
|
|
660
|
+
return {
|
|
661
|
+
...block,
|
|
662
|
+
content: block.content.replace(
|
|
663
|
+
AX_TREE_PATTERN,
|
|
664
|
+
AX_TREE_PLACEHOLDER,
|
|
665
|
+
),
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
return block;
|
|
669
|
+
}),
|
|
670
|
+
};
|
|
671
|
+
});
|
|
726
672
|
}
|
|
727
673
|
|
|
728
674
|
/**
|
package/src/calls/call-domain.ts
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
* to these functions so business logic lives in one place.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { getTwilioUserPhoneNumber } from "../config/env.js";
|
|
9
8
|
import { loadConfig } from "../config/loader.js";
|
|
10
9
|
import { VALID_CALLER_IDENTITY_MODES } from "../config/schema.js";
|
|
11
10
|
import type { AssistantConfig } from "../config/types.js";
|
|
@@ -21,6 +20,7 @@ import { upsertBinding } from "../memory/external-conversation-store.js";
|
|
|
21
20
|
import { revokeScopedApprovalGrantsForContext } from "../memory/scoped-approval-grants.js";
|
|
22
21
|
import { DAEMON_INTERNAL_ASSISTANT_ID } from "../runtime/assistant-scope.js";
|
|
23
22
|
import { isGuardian } from "../runtime/channel-verification-service.js";
|
|
23
|
+
import { credentialKey } from "../security/credential-key.js";
|
|
24
24
|
import { getSecureKey } from "../security/secure-keys.js";
|
|
25
25
|
import { getLogger } from "../util/logger.js";
|
|
26
26
|
import { upsertActiveCallLease } from "./active-call-lease.js";
|
|
@@ -110,8 +110,7 @@ export type CallerIdentitySource =
|
|
|
110
110
|
| "per_call_override"
|
|
111
111
|
| "implicit_default"
|
|
112
112
|
| "user_config"
|
|
113
|
-
| "secure_key"
|
|
114
|
-
| "env_var";
|
|
113
|
+
| "secure_key";
|
|
115
114
|
|
|
116
115
|
export type CallerIdentityResult =
|
|
117
116
|
| {
|
|
@@ -135,7 +134,7 @@ export type CallerIdentityResult =
|
|
|
135
134
|
* For `assistant_number`: uses the Twilio phone number from
|
|
136
135
|
* `getTwilioConfig()`. No eligibility check is performed — this is a fast path.
|
|
137
136
|
* For `user_number`: uses `config.calls.callerIdentity.userNumber` or the
|
|
138
|
-
* secure key `credential
|
|
137
|
+
* secure key `credential/twilio/user_phone_number`, then validates that the
|
|
139
138
|
* number is usable as an outbound caller ID via the Twilio API.
|
|
140
139
|
*/
|
|
141
140
|
export async function resolveCallerIdentity(
|
|
@@ -193,11 +192,10 @@ export async function resolveCallerIdentity(
|
|
|
193
192
|
if (identityConfig.userNumber) {
|
|
194
193
|
userNumber = identityConfig.userNumber;
|
|
195
194
|
numberSource = "user_config";
|
|
196
|
-
} else if (getTwilioUserPhoneNumber()) {
|
|
197
|
-
userNumber = getTwilioUserPhoneNumber()!;
|
|
198
|
-
numberSource = "env_var";
|
|
199
195
|
} else {
|
|
200
|
-
const secureKeyValue = getSecureKey(
|
|
196
|
+
const secureKeyValue = getSecureKey(
|
|
197
|
+
credentialKey("twilio", "user_phone_number"),
|
|
198
|
+
);
|
|
201
199
|
if (secureKeyValue) {
|
|
202
200
|
userNumber = secureKeyValue;
|
|
203
201
|
numberSource = "secure_key";
|
|
@@ -212,7 +210,7 @@ export async function resolveCallerIdentity(
|
|
|
212
210
|
return {
|
|
213
211
|
ok: false,
|
|
214
212
|
error:
|
|
215
|
-
"user_number mode requires a user phone number. Set calls.callerIdentity.userNumber in config or store credential
|
|
213
|
+
"user_number mode requires a user phone number. Set calls.callerIdentity.userNumber in config or store credential/twilio/user_phone_number via the credential_store tool.",
|
|
216
214
|
};
|
|
217
215
|
}
|
|
218
216
|
|
|
@@ -223,7 +221,7 @@ export async function resolveCallerIdentity(
|
|
|
223
221
|
);
|
|
224
222
|
return {
|
|
225
223
|
ok: false,
|
|
226
|
-
error: `User phone number "${userNumber}" is not in E.164 format (must start with + followed by digits, e.g. +14155551234). Check calls.callerIdentity.userNumber in config or credential
|
|
224
|
+
error: `User phone number "${userNumber}" is not in E.164 format (must start with + followed by digits, e.g. +14155551234). Check calls.callerIdentity.userNumber in config or credential/twilio/user_phone_number.`,
|
|
227
225
|
};
|
|
228
226
|
}
|
|
229
227
|
|