@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
|
@@ -31,8 +31,6 @@ export interface ChannelReplyPayload {
|
|
|
31
31
|
ephemeral?: boolean;
|
|
32
32
|
/** Slack user ID — required when `ephemeral` is true. */
|
|
33
33
|
user?: string;
|
|
34
|
-
/** Telegram message_id for editing an existing message instead of sending a new one. */
|
|
35
|
-
messageId?: number;
|
|
36
34
|
/** When provided, instructs the delivery endpoint to update an existing message instead of posting a new one. */
|
|
37
35
|
messageTs?: string;
|
|
38
36
|
/** When true, auto-generate Block Kit blocks from text via textToBlocks(). */
|
|
@@ -45,8 +43,6 @@ export interface ChannelDeliveryResult {
|
|
|
45
43
|
ok: boolean;
|
|
46
44
|
/** The message timestamp returned by the delivery endpoint (e.g. Slack message ts). */
|
|
47
45
|
ts?: string;
|
|
48
|
-
/** The Telegram message_id returned when a new message was sent. */
|
|
49
|
-
messageId?: number;
|
|
50
46
|
}
|
|
51
47
|
|
|
52
48
|
interface ManagedOutboundCallbackContext {
|
|
@@ -100,9 +96,6 @@ export async function deliverChannelReply(
|
|
|
100
96
|
if (typeof responseBody.ts === "string") {
|
|
101
97
|
result.ts = responseBody.ts;
|
|
102
98
|
}
|
|
103
|
-
if (typeof responseBody.messageId === "number") {
|
|
104
|
-
result.messageId = responseBody.messageId;
|
|
105
|
-
}
|
|
106
99
|
} catch {
|
|
107
100
|
// Response may not be JSON for non-Slack channels; that's fine.
|
|
108
101
|
}
|
|
@@ -2,12 +2,11 @@
|
|
|
2
2
|
* Guardian follow-up conversation engine.
|
|
3
3
|
*
|
|
4
4
|
* When a guardian replies to a post-timeout follow-up prompt (e.g. "would you
|
|
5
|
-
* like to call them back
|
|
5
|
+
* like to call them back?"), this engine classifies the
|
|
6
6
|
* guardian's intent into a structured disposition and produces a natural reply.
|
|
7
7
|
*
|
|
8
8
|
* Dispositions:
|
|
9
9
|
* - call_back: Guardian wants to call the original caller back
|
|
10
|
-
* - message_back: Guardian wants to send a text/message to the caller
|
|
11
10
|
* - decline: Guardian declines to follow up ("never mind", "no thanks")
|
|
12
11
|
* - keep_pending: Intent is ambiguous — ask for clarification
|
|
13
12
|
*
|
|
@@ -37,7 +36,6 @@ const FALLBACK_RETRY_TEXT = getGuardianActionFallbackMessage({
|
|
|
37
36
|
|
|
38
37
|
const VALID_DISPOSITIONS: ReadonlySet<string> = new Set([
|
|
39
38
|
"call_back",
|
|
40
|
-
"message_back",
|
|
41
39
|
"decline",
|
|
42
40
|
"keep_pending",
|
|
43
41
|
]);
|
|
@@ -173,7 +173,7 @@ async function executeCallBack(
|
|
|
173
173
|
|
|
174
174
|
/**
|
|
175
175
|
* Execute a follow-up action after the conversation engine has classified
|
|
176
|
-
* the guardian's intent as call_back
|
|
176
|
+
* the guardian's intent as call_back and the follow-up
|
|
177
177
|
* state has been transitioned to `dispatching`.
|
|
178
178
|
*
|
|
179
179
|
* On success: finalizes the follow-up to `completed` and returns
|
|
@@ -36,8 +36,6 @@ export type GuardianActionMessageScenario =
|
|
|
36
36
|
| "guardian_superseded_remap"
|
|
37
37
|
| "guardian_unknown_code"
|
|
38
38
|
| "guardian_auto_matched"
|
|
39
|
-
| "outbound_message_copy"
|
|
40
|
-
| "followup_message_sent"
|
|
41
39
|
| "followup_call_started"
|
|
42
40
|
| "followup_action_failed"
|
|
43
41
|
| "guardian_answer_delivery_failed";
|
|
@@ -183,8 +181,8 @@ export function getGuardianActionFallbackMessage(
|
|
|
183
181
|
|
|
184
182
|
case "guardian_late_answer_followup":
|
|
185
183
|
return context.callerIdentifier
|
|
186
|
-
? `${context.callerIdentifier} called earlier with a question, but I wasn't able to connect them. Would you like to call them back
|
|
187
|
-
: "Someone called earlier with a question, but I wasn't able to connect them. Would you like to call them back
|
|
184
|
+
? `${context.callerIdentifier} called earlier with a question, but I wasn't able to connect them. Would you like to call them back?`
|
|
185
|
+
: "Someone called earlier with a question, but I wasn't able to connect them. Would you like to call them back?";
|
|
188
186
|
|
|
189
187
|
case "guardian_followup_dispatching":
|
|
190
188
|
return context.followupAction
|
|
@@ -205,7 +203,7 @@ export function getGuardianActionFallbackMessage(
|
|
|
205
203
|
return "No problem. Let me know if you change your mind or need anything else.";
|
|
206
204
|
|
|
207
205
|
case "guardian_followup_clarification":
|
|
208
|
-
return "Sorry, I didn't quite catch that. Would you like to call them back
|
|
206
|
+
return "Sorry, I didn't quite catch that. Would you like to call them back or skip it for now?";
|
|
209
207
|
|
|
210
208
|
case "guardian_pending_disambiguation":
|
|
211
209
|
return listedCodes
|
|
@@ -247,24 +245,6 @@ export function getGuardianActionFallbackMessage(
|
|
|
247
245
|
? `Got it! Your answer has been applied to the current active request: "${context.questionText}"`
|
|
248
246
|
: "Got it! Your answer has been applied to the current active request on the call.";
|
|
249
247
|
|
|
250
|
-
case "outbound_message_copy":
|
|
251
|
-
// This message is sent TO the original caller relaying the guardian's answer.
|
|
252
|
-
// When lateAnswerText is available, include it — that's the whole point of message_back.
|
|
253
|
-
if (context.lateAnswerText && context.questionText) {
|
|
254
|
-
return `Hi! You asked "${context.questionText}" earlier. Here's the answer: ${context.lateAnswerText}`;
|
|
255
|
-
}
|
|
256
|
-
if (context.lateAnswerText) {
|
|
257
|
-
return `Hi! Regarding your earlier question — here's the answer: ${context.lateAnswerText}`;
|
|
258
|
-
}
|
|
259
|
-
return context.questionText
|
|
260
|
-
? `Hi! You asked "${context.questionText}" earlier. We'll get back to you with an answer soon.`
|
|
261
|
-
: "Hi! Thanks for calling earlier. We'll get back to you soon.";
|
|
262
|
-
|
|
263
|
-
case "followup_message_sent":
|
|
264
|
-
return context.counterpartyPhone
|
|
265
|
-
? `Done! I've sent a text message to ${context.counterpartyPhone} with your answer.`
|
|
266
|
-
: "Done! I've sent them a text message with your answer.";
|
|
267
|
-
|
|
268
248
|
case "followup_call_started":
|
|
269
249
|
return context.counterpartyPhone
|
|
270
250
|
? `Got it! I'm calling ${context.counterpartyPhone} back now to relay your answer.`
|
|
@@ -110,7 +110,6 @@ import {
|
|
|
110
110
|
stopGuardianExpirySweep,
|
|
111
111
|
} from "./routes/channel-routes.js";
|
|
112
112
|
import { channelVerificationRouteDefinitions } from "./routes/channel-verification-routes.js";
|
|
113
|
-
import { computerUseRouteDefinitions } from "./routes/computer-use-routes.js";
|
|
114
113
|
import {
|
|
115
114
|
contactCatchAllRouteDefinitions,
|
|
116
115
|
contactRouteDefinitions,
|
|
@@ -126,6 +125,7 @@ import { guardianActionRouteDefinitions } from "./routes/guardian-action-routes.
|
|
|
126
125
|
import { handleGuardianBootstrap } from "./routes/guardian-bootstrap-routes.js";
|
|
127
126
|
import { handleGuardianRefresh } from "./routes/guardian-refresh-routes.js";
|
|
128
127
|
import { hostBashRouteDefinitions } from "./routes/host-bash-routes.js";
|
|
128
|
+
import { hostCuRouteDefinitions } from "./routes/host-cu-routes.js";
|
|
129
129
|
import { hostFileRouteDefinitions } from "./routes/host-file-routes.js";
|
|
130
130
|
import { handleHealth } from "./routes/identity-routes.js";
|
|
131
131
|
import { identityRouteDefinitions } from "./routes/identity-routes.js";
|
|
@@ -155,6 +155,7 @@ import { surfaceActionRouteDefinitions } from "./routes/surface-action-routes.js
|
|
|
155
155
|
import { surfaceContentRouteDefinitions } from "./routes/surface-content-routes.js";
|
|
156
156
|
import { trustRulesRouteDefinitions } from "./routes/trust-rules-routes.js";
|
|
157
157
|
import { usageRouteDefinitions } from "./routes/usage-routes.js";
|
|
158
|
+
import { watchRouteDefinitions } from "./routes/watch-routes.js";
|
|
158
159
|
import { workItemRouteDefinitions } from "./routes/work-items-routes.js";
|
|
159
160
|
import { workspaceRouteDefinitions } from "./routes/workspace-routes.js";
|
|
160
161
|
|
|
@@ -216,7 +217,7 @@ export class RuntimeHttpServer {
|
|
|
216
217
|
private getSkillContext?: RuntimeHttpServerOptions["getSkillContext"];
|
|
217
218
|
private sessionManagementDeps?: RuntimeHttpServerOptions["sessionManagementDeps"];
|
|
218
219
|
private getModelSetContext?: RuntimeHttpServerOptions["getModelSetContext"];
|
|
219
|
-
private
|
|
220
|
+
private getWatchDeps?: RuntimeHttpServerOptions["getWatchDeps"];
|
|
220
221
|
private getRecordingDeps?: RuntimeHttpServerOptions["getRecordingDeps"];
|
|
221
222
|
private router: HttpRouter;
|
|
222
223
|
|
|
@@ -237,7 +238,7 @@ export class RuntimeHttpServer {
|
|
|
237
238
|
this.getSkillContext = options.getSkillContext;
|
|
238
239
|
this.sessionManagementDeps = options.sessionManagementDeps;
|
|
239
240
|
this.getModelSetContext = options.getModelSetContext;
|
|
240
|
-
this.
|
|
241
|
+
this.getWatchDeps = options.getWatchDeps;
|
|
241
242
|
this.getRecordingDeps = options.getRecordingDeps;
|
|
242
243
|
this.router = new HttpRouter(this.buildRouteTable());
|
|
243
244
|
}
|
|
@@ -532,11 +533,16 @@ export class RuntimeHttpServer {
|
|
|
532
533
|
if (!isHttpAuthDisabled()) {
|
|
533
534
|
const clientIp = extractClientIp(req, server);
|
|
534
535
|
const token = extractBearerToken(req);
|
|
535
|
-
const
|
|
536
|
-
|
|
537
|
-
|
|
536
|
+
const limiter = token ? apiRateLimiter : ipRateLimiter;
|
|
537
|
+
const limiterKind = token ? "authenticated" : "unauthenticated";
|
|
538
|
+
const result = limiter.check(clientIp, path);
|
|
538
539
|
if (!result.allowed) {
|
|
539
|
-
return rateLimitResponse(result
|
|
540
|
+
return rateLimitResponse(result, {
|
|
541
|
+
clientIp,
|
|
542
|
+
deniedPath: path,
|
|
543
|
+
limiterKind: limiterKind as "authenticated" | "unauthenticated",
|
|
544
|
+
pathCounts: limiter.getRecentPathCounts(clientIp),
|
|
545
|
+
});
|
|
540
546
|
}
|
|
541
547
|
const routerResponse = await this.router.dispatch(
|
|
542
548
|
endpoint,
|
|
@@ -941,6 +947,7 @@ export class RuntimeHttpServer {
|
|
|
941
947
|
...globalSearchRouteDefinitions(),
|
|
942
948
|
...approvalRouteDefinitions(),
|
|
943
949
|
...hostBashRouteDefinitions(),
|
|
950
|
+
...hostCuRouteDefinitions(),
|
|
944
951
|
...hostFileRouteDefinitions(),
|
|
945
952
|
...(this.getSkillContext
|
|
946
953
|
? skillRouteDefinitions({
|
|
@@ -968,9 +975,9 @@ export class RuntimeHttpServer {
|
|
|
968
975
|
...channelReadinessRouteDefinitions(),
|
|
969
976
|
...attachmentRouteDefinitions(),
|
|
970
977
|
|
|
971
|
-
...(this.
|
|
972
|
-
?
|
|
973
|
-
|
|
978
|
+
...(this.getWatchDeps
|
|
979
|
+
? watchRouteDefinitions({
|
|
980
|
+
getWatchDeps: this.getWatchDeps,
|
|
974
981
|
})
|
|
975
982
|
: []),
|
|
976
983
|
...(this.getRecordingDeps
|
|
@@ -83,7 +83,6 @@ export type GuardianActionCopyGenerator = (
|
|
|
83
83
|
/** The disposition returned by the guardian follow-up conversation engine. */
|
|
84
84
|
export type GuardianFollowUpDisposition =
|
|
85
85
|
| "call_back"
|
|
86
|
-
| "message_back"
|
|
87
86
|
| "decline"
|
|
88
87
|
| "keep_pending";
|
|
89
88
|
|
|
@@ -220,8 +219,8 @@ export interface RuntimeHttpServerOptions {
|
|
|
220
219
|
sessionManagementDeps?: SessionManagementDeps;
|
|
221
220
|
/** Lazy factory for model config set context (session eviction, config reload suppression). */
|
|
222
221
|
getModelSetContext?: () => import("../daemon/handlers/config-model.js").ModelSetContext;
|
|
223
|
-
/** Provider for
|
|
224
|
-
|
|
222
|
+
/** Provider for watch observation dependencies (watch routes). */
|
|
223
|
+
getWatchDeps?: () => import("./routes/watch-routes.js").WatchDeps;
|
|
225
224
|
/** Provider for recording dependencies (recording routes). */
|
|
226
225
|
getRecordingDeps?: () => import("./routes/recording-routes.js").RecordingDeps;
|
|
227
226
|
}
|
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
// Tracks request counts per key and returns 429 when the limit is exceeded.
|
|
3
3
|
// Follows the same sliding-window pattern as gateway/src/auth-rate-limiter.ts.
|
|
4
4
|
|
|
5
|
+
import { getLogger } from "../../util/logger.js";
|
|
5
6
|
import type { HttpErrorResponse } from "../http-errors.js";
|
|
6
7
|
import { isPrivateAddress } from "./auth.js";
|
|
7
8
|
|
|
8
|
-
const
|
|
9
|
+
const log = getLogger("rate-limiter");
|
|
10
|
+
|
|
11
|
+
const DEFAULT_MAX_REQUESTS = 300;
|
|
9
12
|
const DEFAULT_WINDOW_MS = 60_000; // 60 seconds
|
|
10
13
|
const MAX_TRACKED_TOKENS = 10_000;
|
|
11
14
|
|
|
@@ -14,8 +17,13 @@ const DEFAULT_IP_MAX_REQUESTS = 20;
|
|
|
14
17
|
const DEFAULT_IP_WINDOW_MS = 60_000;
|
|
15
18
|
const MAX_TRACKED_IPS = 50_000;
|
|
16
19
|
|
|
20
|
+
interface RequestEntry {
|
|
21
|
+
timestamp: number;
|
|
22
|
+
path: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
17
25
|
export class TokenRateLimiter {
|
|
18
|
-
private requests = new Map<string,
|
|
26
|
+
private requests = new Map<string, RequestEntry[]>();
|
|
19
27
|
private readonly maxRequests: number;
|
|
20
28
|
private readonly windowMs: number;
|
|
21
29
|
private readonly maxTrackedKeys: number;
|
|
@@ -34,11 +42,11 @@ export class TokenRateLimiter {
|
|
|
34
42
|
* Check whether the request should be allowed and record it.
|
|
35
43
|
* Returns rate limit metadata for response headers.
|
|
36
44
|
*/
|
|
37
|
-
check(key: string): RateLimitResult {
|
|
45
|
+
check(key: string, path?: string): RateLimitResult {
|
|
38
46
|
const now = Date.now();
|
|
39
|
-
let
|
|
47
|
+
let entries = this.requests.get(key);
|
|
40
48
|
|
|
41
|
-
if (!
|
|
49
|
+
if (!entries) {
|
|
42
50
|
if (this.requests.size >= this.maxTrackedKeys) {
|
|
43
51
|
this.evictStale(now);
|
|
44
52
|
if (this.requests.size >= this.maxTrackedKeys) {
|
|
@@ -46,24 +54,24 @@ export class TokenRateLimiter {
|
|
|
46
54
|
if (oldest !== undefined) this.requests.delete(oldest);
|
|
47
55
|
}
|
|
48
56
|
}
|
|
49
|
-
|
|
50
|
-
this.requests.set(key,
|
|
57
|
+
entries = [];
|
|
58
|
+
this.requests.set(key, entries);
|
|
51
59
|
}
|
|
52
60
|
|
|
53
61
|
const cutoff = now - this.windowMs;
|
|
54
62
|
|
|
55
|
-
// Remove expired
|
|
56
|
-
while (
|
|
57
|
-
|
|
63
|
+
// Remove expired entries from the front
|
|
64
|
+
while (entries.length > 0 && entries[0].timestamp <= cutoff) {
|
|
65
|
+
entries.shift();
|
|
58
66
|
}
|
|
59
67
|
|
|
60
|
-
const remaining = Math.max(0, this.maxRequests -
|
|
68
|
+
const remaining = Math.max(0, this.maxRequests - entries.length);
|
|
61
69
|
const resetAt =
|
|
62
|
-
|
|
63
|
-
? Math.ceil((
|
|
70
|
+
entries.length > 0
|
|
71
|
+
? Math.ceil((entries[0].timestamp + this.windowMs) / 1000)
|
|
64
72
|
: Math.ceil((now + this.windowMs) / 1000);
|
|
65
73
|
|
|
66
|
-
if (
|
|
74
|
+
if (entries.length >= this.maxRequests) {
|
|
67
75
|
return {
|
|
68
76
|
allowed: false,
|
|
69
77
|
limit: this.maxRequests,
|
|
@@ -72,7 +80,7 @@ export class TokenRateLimiter {
|
|
|
72
80
|
};
|
|
73
81
|
}
|
|
74
82
|
|
|
75
|
-
|
|
83
|
+
entries.push({ timestamp: now, path: path ?? "unknown" });
|
|
76
84
|
|
|
77
85
|
return {
|
|
78
86
|
allowed: true,
|
|
@@ -82,13 +90,36 @@ export class TokenRateLimiter {
|
|
|
82
90
|
};
|
|
83
91
|
}
|
|
84
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Return a count of recent requests grouped by path for the given key.
|
|
95
|
+
* Sorted descending by count. Useful for diagnosing which endpoints
|
|
96
|
+
* are consuming the rate limit budget.
|
|
97
|
+
*/
|
|
98
|
+
getRecentPathCounts(key: string): Array<{ path: string; count: number }> {
|
|
99
|
+
const entries = this.requests.get(key);
|
|
100
|
+
if (!entries || entries.length === 0) return [];
|
|
101
|
+
|
|
102
|
+
const now = Date.now();
|
|
103
|
+
const cutoff = now - this.windowMs;
|
|
104
|
+
const counts = new Map<string, number>();
|
|
105
|
+
for (const entry of entries) {
|
|
106
|
+
if (entry.timestamp > cutoff) {
|
|
107
|
+
counts.set(entry.path, (counts.get(entry.path) ?? 0) + 1);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return Array.from(counts.entries())
|
|
112
|
+
.map(([path, count]) => ({ path, count }))
|
|
113
|
+
.sort((a, b) => b.count - a.count);
|
|
114
|
+
}
|
|
115
|
+
|
|
85
116
|
private evictStale(now: number): void {
|
|
86
117
|
const cutoff = now - this.windowMs;
|
|
87
|
-
for (const [key,
|
|
88
|
-
while (
|
|
89
|
-
|
|
118
|
+
for (const [key, entries] of this.requests) {
|
|
119
|
+
while (entries.length > 0 && entries[0].timestamp <= cutoff) {
|
|
120
|
+
entries.shift();
|
|
90
121
|
}
|
|
91
|
-
if (
|
|
122
|
+
if (entries.length === 0) {
|
|
92
123
|
this.requests.delete(key);
|
|
93
124
|
}
|
|
94
125
|
}
|
|
@@ -115,8 +146,31 @@ export function rateLimitHeaders(
|
|
|
115
146
|
}
|
|
116
147
|
|
|
117
148
|
/** Return a 429 response with rate limit headers and a Retry-After hint. */
|
|
118
|
-
export function rateLimitResponse(
|
|
149
|
+
export function rateLimitResponse(
|
|
150
|
+
result: RateLimitResult,
|
|
151
|
+
diagnostics?: {
|
|
152
|
+
clientIp: string;
|
|
153
|
+
deniedPath: string;
|
|
154
|
+
limiterKind: "authenticated" | "unauthenticated";
|
|
155
|
+
pathCounts: Array<{ path: string; count: number }>;
|
|
156
|
+
},
|
|
157
|
+
): Response {
|
|
119
158
|
const retryAfter = Math.max(1, result.resetAt - Math.ceil(Date.now() / 1000));
|
|
159
|
+
|
|
160
|
+
if (diagnostics) {
|
|
161
|
+
log.warn(
|
|
162
|
+
{
|
|
163
|
+
clientIp: diagnostics.clientIp,
|
|
164
|
+
deniedPath: diagnostics.deniedPath,
|
|
165
|
+
limiterKind: diagnostics.limiterKind,
|
|
166
|
+
limit: result.limit,
|
|
167
|
+
retryAfterSec: retryAfter,
|
|
168
|
+
recentRequests: diagnostics.pathCounts,
|
|
169
|
+
},
|
|
170
|
+
`Rate limited ${diagnostics.limiterKind} request: ${diagnostics.deniedPath} (${result.limit} req/min exceeded)`,
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
120
174
|
const body: HttpErrorResponse = {
|
|
121
175
|
error: { code: "RATE_LIMITED", message: "Too Many Requests" },
|
|
122
176
|
};
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { TwilioConversationRelayProvider } from "../../calls/twilio-provider.js";
|
|
6
|
-
import { isTwilioWebhookValidationDisabled } from "../../config/env.js";
|
|
7
6
|
import { loadConfig } from "../../config/loader.js";
|
|
8
7
|
import { getPublicBaseUrl } from "../../inbound/public-ingress-urls.js";
|
|
9
8
|
import { getLogger } from "../../util/logger.js";
|
|
@@ -51,22 +50,13 @@ export const GATEWAY_ONLY_BLOCKED_SUBPATHS = new Set([
|
|
|
51
50
|
* Returns a 403 Response if signature validation fails.
|
|
52
51
|
*
|
|
53
52
|
* Fail-closed: if the auth token is not configured, the request is rejected
|
|
54
|
-
* with 403 rather than silently skipping validation.
|
|
55
|
-
* bypass is available via TWILIO_WEBHOOK_VALIDATION_DISABLED=true.
|
|
53
|
+
* with 403 rather than silently skipping validation.
|
|
56
54
|
*/
|
|
57
55
|
export async function validateTwilioWebhook(
|
|
58
56
|
req: Request,
|
|
59
57
|
): Promise<{ body: string } | Response> {
|
|
60
58
|
const rawBody = await req.text();
|
|
61
59
|
|
|
62
|
-
// Allow explicit local-dev bypass -- must be exactly "true"
|
|
63
|
-
if (isTwilioWebhookValidationDisabled()) {
|
|
64
|
-
log.warn(
|
|
65
|
-
"Twilio webhook signature validation explicitly disabled via TWILIO_WEBHOOK_VALIDATION_DISABLED",
|
|
66
|
-
);
|
|
67
|
-
return { body: rawBody };
|
|
68
|
-
}
|
|
69
|
-
|
|
70
60
|
const authToken = TwilioConversationRelayProvider.getAuthToken();
|
|
71
61
|
|
|
72
62
|
if (!authToken) {
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* In-memory tracker that maps requestId to session info for pending
|
|
3
|
-
* confirmation, secret, host_bash, and
|
|
3
|
+
* confirmation, secret, host_bash, host_file, and host_cu interactions.
|
|
4
4
|
*
|
|
5
5
|
* When the agent loop emits a confirmation_request, secret_request,
|
|
6
|
-
* host_bash_request, or
|
|
7
|
-
* the interaction here. Standalone HTTP endpoints
|
|
8
|
-
* /v1/
|
|
9
|
-
*
|
|
6
|
+
* host_bash_request, host_file_request, or host_cu_request, the onEvent
|
|
7
|
+
* callback registers the interaction here. Standalone HTTP endpoints
|
|
8
|
+
* (/v1/confirm, /v1/secret, /v1/trust-rules, /v1/host-bash-result,
|
|
9
|
+
* /v1/host-file-result, /v1/host-cu-result) look up the session from
|
|
10
|
+
* this tracker to resolve the interaction.
|
|
10
11
|
*/
|
|
11
12
|
|
|
12
13
|
import type { Session } from "../daemon/session.js";
|
|
@@ -29,7 +30,7 @@ export interface ConfirmationDetails {
|
|
|
29
30
|
export interface PendingInteraction {
|
|
30
31
|
session: Session;
|
|
31
32
|
conversationId: string;
|
|
32
|
-
kind: "confirmation" | "secret" | "host_bash" | "host_file";
|
|
33
|
+
kind: "confirmation" | "secret" | "host_bash" | "host_file" | "host_cu";
|
|
33
34
|
confirmationDetails?: ConfirmationDetails;
|
|
34
35
|
}
|
|
35
36
|
|
|
@@ -82,19 +83,20 @@ export function getByConversation(
|
|
|
82
83
|
* Remove pending confirmation and secret interactions for a given session.
|
|
83
84
|
* Used when auto-denying all pending interactions (e.g. new user message).
|
|
84
85
|
*
|
|
85
|
-
* host_bash and
|
|
86
|
-
* represent in-flight tool executions proxied to the client, not
|
|
86
|
+
* host_bash, host_file, and host_cu interactions are intentionally skipped
|
|
87
|
+
* — they represent in-flight tool executions proxied to the client, not
|
|
87
88
|
* confirmations to auto-deny. Removing them would orphan the request: the
|
|
88
|
-
* client would POST to /v1/host-bash-result
|
|
89
|
-
* completing the operation, get a 404, and the
|
|
90
|
-
* a spurious timeout error.
|
|
89
|
+
* client would POST to /v1/host-bash-result, /v1/host-file-result, or
|
|
90
|
+
* /v1/host-cu-result after completing the operation, get a 404, and the
|
|
91
|
+
* proxy timer would fire with a spurious timeout error.
|
|
91
92
|
*/
|
|
92
93
|
export function removeBySession(session: Session): void {
|
|
93
94
|
for (const [requestId, interaction] of pending) {
|
|
94
95
|
if (
|
|
95
96
|
interaction.session === session &&
|
|
96
97
|
interaction.kind !== "host_bash" &&
|
|
97
|
-
interaction.kind !== "host_file"
|
|
98
|
+
interaction.kind !== "host_file" &&
|
|
99
|
+
interaction.kind !== "host_cu"
|
|
98
100
|
) {
|
|
99
101
|
pending.delete(requestId);
|
|
100
102
|
}
|
|
@@ -38,6 +38,7 @@ export async function handleGetChannelReadiness(url: URL): Promise<Response> {
|
|
|
38
38
|
return {
|
|
39
39
|
channel: s.channel,
|
|
40
40
|
ready: s.ready,
|
|
41
|
+
setupStatus: s.setupStatus,
|
|
41
42
|
checkedAt: s.checkedAt,
|
|
42
43
|
stale: s.stale,
|
|
43
44
|
reasons: s.reasons,
|
|
@@ -91,6 +92,7 @@ export async function handleRefreshChannelReadiness(
|
|
|
91
92
|
return {
|
|
92
93
|
channel: s.channel,
|
|
93
94
|
ready: s.ready,
|
|
95
|
+
setupStatus: s.setupStatus,
|
|
94
96
|
checkedAt: s.checkedAt,
|
|
95
97
|
stale: s.stale,
|
|
96
98
|
reasons: s.reasons,
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
import { getConfig } from "../../config/loader.js";
|
|
18
18
|
import { renderHistoryContent } from "../../daemon/handlers/shared.js";
|
|
19
19
|
import { HostBashProxy } from "../../daemon/host-bash-proxy.js";
|
|
20
|
+
import { HostCuProxy } from "../../daemon/host-cu-proxy.js";
|
|
20
21
|
import { HostFileProxy } from "../../daemon/host-file-proxy.js";
|
|
21
22
|
import type { ServerMessage } from "../../daemon/message-protocol.js";
|
|
22
23
|
import {
|
|
@@ -449,6 +450,12 @@ function makeHubPublisher(
|
|
|
449
450
|
conversationId,
|
|
450
451
|
kind: "host_file",
|
|
451
452
|
});
|
|
453
|
+
} else if (msg.type === "host_cu_request") {
|
|
454
|
+
pendingInteractions.register(msg.requestId, {
|
|
455
|
+
session,
|
|
456
|
+
conversationId,
|
|
457
|
+
kind: "host_cu",
|
|
458
|
+
});
|
|
452
459
|
}
|
|
453
460
|
|
|
454
461
|
// ServerMessage is a large union; sessionId exists on most but not all variants.
|
|
@@ -640,9 +647,14 @@ export async function handleSendMessage(
|
|
|
640
647
|
});
|
|
641
648
|
session.setHostFileProxy(fileProxy);
|
|
642
649
|
}
|
|
650
|
+
if (!session.isProcessing() || !session.hostCuProxy) {
|
|
651
|
+
const cuProxy = new HostCuProxy(onEvent);
|
|
652
|
+
session.setHostCuProxy(cuProxy);
|
|
653
|
+
}
|
|
643
654
|
} else if (!session.isProcessing()) {
|
|
644
655
|
session.setHostBashProxy(undefined);
|
|
645
656
|
session.setHostFileProxy(undefined);
|
|
657
|
+
session.setHostCuProxy(undefined);
|
|
646
658
|
}
|
|
647
659
|
// Wire sendToClient to the SSE hub so all subsystems can reach the HTTP client.
|
|
648
660
|
// Called after setHostBashProxy so updateSender targets the current proxy.
|
|
@@ -679,7 +691,13 @@ export async function handleSendMessage(
|
|
|
679
691
|
attachments,
|
|
680
692
|
session,
|
|
681
693
|
onEvent,
|
|
682
|
-
|
|
694
|
+
// Desktop path: disable NL classification to avoid consuming non-decision
|
|
695
|
+
// messages while a tool confirmation is pending. Deterministic code-prefix
|
|
696
|
+
// and callback parsing remain active. Mirrors session-process.ts behavior.
|
|
697
|
+
approvalConversationGenerator:
|
|
698
|
+
sourceChannel === "vellum"
|
|
699
|
+
? undefined
|
|
700
|
+
: deps.approvalConversationGenerator,
|
|
683
701
|
verifiedActorExternalUserId,
|
|
684
702
|
verifiedActorPrincipalId,
|
|
685
703
|
});
|
|
@@ -687,6 +705,7 @@ export async function handleSendMessage(
|
|
|
687
705
|
return Response.json(
|
|
688
706
|
{
|
|
689
707
|
accepted: true,
|
|
708
|
+
conversationId: mapping.conversationId,
|
|
690
709
|
...(inlineReplyResult.messageId
|
|
691
710
|
? { messageId: inlineReplyResult.messageId }
|
|
692
711
|
: {}),
|
|
@@ -751,7 +770,10 @@ export async function handleSendMessage(
|
|
|
751
770
|
pendingInteractions.removeBySession(session);
|
|
752
771
|
}
|
|
753
772
|
|
|
754
|
-
return Response.json(
|
|
773
|
+
return Response.json(
|
|
774
|
+
{ accepted: true, queued: true, conversationId: mapping.conversationId },
|
|
775
|
+
{ status: 202 },
|
|
776
|
+
);
|
|
755
777
|
}
|
|
756
778
|
|
|
757
779
|
// Session is idle — persist and fire agent loop immediately
|
|
@@ -782,6 +804,7 @@ export async function handleSendMessage(
|
|
|
782
804
|
|
|
783
805
|
if (slashResult.kind === "unknown") {
|
|
784
806
|
session.processing = true;
|
|
807
|
+
let cleanupDeferred = false;
|
|
785
808
|
try {
|
|
786
809
|
const provenance = provenanceFromTrustContext(session.trustContext);
|
|
787
810
|
const channelMeta = {
|
|
@@ -818,26 +841,54 @@ export async function handleSendMessage(
|
|
|
818
841
|
sourceInterface,
|
|
819
842
|
);
|
|
820
843
|
|
|
821
|
-
//
|
|
822
|
-
//
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
onEvent({ type: "assistant_text_delta", text: slashResult.message });
|
|
829
|
-
onEvent({
|
|
830
|
-
type: "message_complete",
|
|
831
|
-
sessionId: mapping.conversationId,
|
|
832
|
-
});
|
|
844
|
+
// Snapshot model info now so the deferred callback cannot observe
|
|
845
|
+
// a config change from a concurrent request.
|
|
846
|
+
const modelInfoEvent =
|
|
847
|
+
isModelSlashCommand(rawContent) || isProviderShortcut(rawContent)
|
|
848
|
+
? buildModelInfoEvent()
|
|
849
|
+
: null;
|
|
833
850
|
|
|
834
|
-
|
|
835
|
-
{
|
|
851
|
+
const response = Response.json(
|
|
852
|
+
{
|
|
853
|
+
accepted: true,
|
|
854
|
+
messageId: persisted.id,
|
|
855
|
+
conversationId: mapping.conversationId,
|
|
856
|
+
},
|
|
836
857
|
{ status: 202 },
|
|
837
858
|
);
|
|
859
|
+
|
|
860
|
+
// Defer event publishing to next tick so the HTTP response reaches the
|
|
861
|
+
// client first. This ensures the client's serverToLocalSessionMap is
|
|
862
|
+
// populated before SSE events arrive, preventing dropped events in new
|
|
863
|
+
// desktop threads.
|
|
864
|
+
//
|
|
865
|
+
// session.processing and drainQueue are also deferred so the current
|
|
866
|
+
// slash command's events are emitted before the next queued message
|
|
867
|
+
// starts processing.
|
|
868
|
+
const conversationId = mapping.conversationId;
|
|
869
|
+
const message = slashResult.message;
|
|
870
|
+
setTimeout(() => {
|
|
871
|
+
if (modelInfoEvent) {
|
|
872
|
+
onEvent(modelInfoEvent);
|
|
873
|
+
}
|
|
874
|
+
onEvent({ type: "assistant_text_delta", text: message });
|
|
875
|
+
onEvent({
|
|
876
|
+
type: "message_complete",
|
|
877
|
+
sessionId: conversationId,
|
|
878
|
+
});
|
|
879
|
+
session.processing = false;
|
|
880
|
+
session.drainQueue().catch(() => {});
|
|
881
|
+
}, 0);
|
|
882
|
+
|
|
883
|
+
cleanupDeferred = true;
|
|
884
|
+
return response;
|
|
838
885
|
} finally {
|
|
839
|
-
|
|
840
|
-
|
|
886
|
+
// No-op for the slash-command early-return path (handled inside
|
|
887
|
+
// setTimeout above), but still needed for error paths.
|
|
888
|
+
if (!cleanupDeferred && session.processing) {
|
|
889
|
+
session.processing = false;
|
|
890
|
+
session.drainQueue().catch(() => {});
|
|
891
|
+
}
|
|
841
892
|
}
|
|
842
893
|
}
|
|
843
894
|
|
|
@@ -874,7 +925,10 @@ export async function handleSendMessage(
|
|
|
874
925
|
);
|
|
875
926
|
});
|
|
876
927
|
|
|
877
|
-
return Response.json(
|
|
928
|
+
return Response.json(
|
|
929
|
+
{ accepted: true, messageId, conversationId: mapping.conversationId },
|
|
930
|
+
{ status: 202 },
|
|
931
|
+
);
|
|
878
932
|
}
|
|
879
933
|
|
|
880
934
|
async function generateLlmSuggestion(
|