@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
|
@@ -186,6 +186,7 @@ async function handleDiagnosticsExport(body: {
|
|
|
186
186
|
// been persisted — the user message and in-flight tool/usage data are
|
|
187
187
|
// still captured.
|
|
188
188
|
let anchorMessage;
|
|
189
|
+
let anchorIsFallback = false;
|
|
189
190
|
if (anchorMessageId) {
|
|
190
191
|
anchorMessage = db
|
|
191
192
|
.select()
|
|
@@ -220,6 +221,7 @@ async function handleDiagnosticsExport(body: {
|
|
|
220
221
|
.orderBy(desc(messages.createdAt))
|
|
221
222
|
.limit(1)
|
|
222
223
|
.get();
|
|
224
|
+
anchorIsFallback = true;
|
|
223
225
|
}
|
|
224
226
|
|
|
225
227
|
// 2. Compute the export time range.
|
|
@@ -250,15 +252,15 @@ async function handleDiagnosticsExport(body: {
|
|
|
250
252
|
rangeStart =
|
|
251
253
|
precedingUserMessage?.createdAt ?? anchorMessage.createdAt - 2000;
|
|
252
254
|
|
|
253
|
-
// When the anchor
|
|
254
|
-
//
|
|
255
|
-
//
|
|
256
|
-
//
|
|
257
|
-
|
|
258
|
-
rangeEnd =
|
|
259
|
-
usageRangeEnd =
|
|
260
|
-
?
|
|
261
|
-
:
|
|
255
|
+
// When the anchor was selected via the fallback "any message" path
|
|
256
|
+
// (because the assistant reply hasn't been persisted yet), extend the
|
|
257
|
+
// range to the current time so in-flight tool invocations and usage
|
|
258
|
+
// recorded after the user message are captured. An explicit anchor to a
|
|
259
|
+
// non-assistant message uses the message's own timestamp.
|
|
260
|
+
rangeEnd = anchorIsFallback ? now : anchorMessage.createdAt;
|
|
261
|
+
usageRangeEnd = anchorIsFallback
|
|
262
|
+
? now + 5000
|
|
263
|
+
: anchorMessage.createdAt + 5000;
|
|
262
264
|
} else {
|
|
263
265
|
// No messages at all — use the current time so we capture any
|
|
264
266
|
// in-flight LLM usage or tool invocations.
|
|
@@ -7,12 +7,17 @@
|
|
|
7
7
|
* is called. The AuthContext is threaded through from the HTTP server
|
|
8
8
|
* layer, so no additional actor-token verification is needed here.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
10
|
+
* When `conversationKey` is provided, subscribers receive events scoped to
|
|
11
|
+
* that conversation. When omitted, subscribers receive events from ALL
|
|
12
|
+
* conversations for this assistant (unfiltered).
|
|
11
13
|
*/
|
|
12
14
|
|
|
13
15
|
import { getOrCreateConversation } from "../../memory/conversation-key-store.js";
|
|
14
16
|
import { formatSseFrame, formatSseHeartbeat } from "../assistant-event.js";
|
|
15
|
-
import type {
|
|
17
|
+
import type {
|
|
18
|
+
AssistantEventFilter,
|
|
19
|
+
AssistantEventSubscription,
|
|
20
|
+
} from "../assistant-event-hub.js";
|
|
16
21
|
import {
|
|
17
22
|
AssistantEventHub,
|
|
18
23
|
assistantEventHub,
|
|
@@ -26,10 +31,12 @@ import type { RouteDefinition } from "../http-router.js";
|
|
|
26
31
|
const DEFAULT_HEARTBEAT_INTERVAL_MS = 30_000;
|
|
27
32
|
|
|
28
33
|
/**
|
|
29
|
-
* Stream assistant events as Server-Sent Events
|
|
34
|
+
* Stream assistant events as Server-Sent Events.
|
|
30
35
|
*
|
|
31
36
|
* Query params:
|
|
32
|
-
* conversationKey --
|
|
37
|
+
* conversationKey -- optional; when provided, scopes the stream to one
|
|
38
|
+
* conversation. When omitted, the stream delivers events
|
|
39
|
+
* from ALL conversations for this assistant.
|
|
33
40
|
*
|
|
34
41
|
* Options (for testing):
|
|
35
42
|
* hub -- override the event hub (defaults to process singleton).
|
|
@@ -56,15 +63,21 @@ export function handleSubscribeAssistantEvents(
|
|
|
56
63
|
// scope and principal type requirements.
|
|
57
64
|
|
|
58
65
|
const conversationKey = url.searchParams.get("conversationKey");
|
|
59
|
-
if (!conversationKey) {
|
|
60
|
-
return httpError("BAD_REQUEST", "conversationKey
|
|
66
|
+
if (url.searchParams.has("conversationKey") && !conversationKey?.trim()) {
|
|
67
|
+
return httpError("BAD_REQUEST", "conversationKey must not be empty", 400);
|
|
61
68
|
}
|
|
62
69
|
|
|
63
70
|
const hub = options?.hub ?? assistantEventHub;
|
|
64
71
|
const heartbeatIntervalMs =
|
|
65
72
|
options?.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS;
|
|
66
73
|
|
|
67
|
-
const
|
|
74
|
+
const filter: AssistantEventFilter = {
|
|
75
|
+
assistantId: DAEMON_INTERNAL_ASSISTANT_ID,
|
|
76
|
+
};
|
|
77
|
+
if (conversationKey) {
|
|
78
|
+
const mapping = getOrCreateConversation(conversationKey);
|
|
79
|
+
filter.sessionId = mapping.conversationId;
|
|
80
|
+
}
|
|
68
81
|
const encoder = new TextEncoder();
|
|
69
82
|
|
|
70
83
|
// -- Eager subscribe --------------------------------------------------------
|
|
@@ -90,10 +103,7 @@ export function handleSubscribeAssistantEvents(
|
|
|
90
103
|
|
|
91
104
|
try {
|
|
92
105
|
sub = hub.subscribe(
|
|
93
|
-
|
|
94
|
-
assistantId: DAEMON_INTERNAL_ASSISTANT_ID,
|
|
95
|
-
sessionId: mapping.conversationId,
|
|
96
|
-
},
|
|
106
|
+
filter,
|
|
97
107
|
(event) => {
|
|
98
108
|
const controller = controllerRef;
|
|
99
109
|
if (!controller) return;
|
|
@@ -10,6 +10,7 @@ import { applyGuardianDecision } from "../../approvals/guardian-decision-primiti
|
|
|
10
10
|
import type { ChannelId } from "../../channels/types.js";
|
|
11
11
|
import type { TrustContext } from "../../daemon/session-runtime-assembly.js";
|
|
12
12
|
import {
|
|
13
|
+
getAllPendingApprovalsByGuardianChat,
|
|
13
14
|
getPendingApprovalForRequest,
|
|
14
15
|
getUnresolvedApprovalForRequest,
|
|
15
16
|
updateApprovalDecision,
|
|
@@ -123,7 +124,7 @@ export async function handleApprovalInterception(
|
|
|
123
124
|
// Only guardians can approve via reaction — non-guardian reactions are
|
|
124
125
|
// silently ignored to prevent self-approval.
|
|
125
126
|
if (callbackData?.startsWith("reaction:")) {
|
|
126
|
-
if (trustCtx.trustClass !== "guardian") {
|
|
127
|
+
if (trustCtx.trustClass !== "guardian" || !actorExternalId) {
|
|
127
128
|
return { handled: true, type: "stale_ignored" };
|
|
128
129
|
}
|
|
129
130
|
const reactionDecision = parseReactionCallbackData(callbackData);
|
|
@@ -131,13 +132,27 @@ export async function handleApprovalInterception(
|
|
|
131
132
|
// Unknown emoji — ignore silently
|
|
132
133
|
return { handled: true, type: "stale_ignored" };
|
|
133
134
|
}
|
|
134
|
-
|
|
135
|
-
|
|
135
|
+
|
|
136
|
+
const allPending = getAllPendingApprovalsByGuardianChat(
|
|
137
|
+
sourceChannel,
|
|
138
|
+
conversationExternalId,
|
|
139
|
+
);
|
|
140
|
+
const guardianPending = allPending.filter(
|
|
141
|
+
(approval) => approval.guardianExternalUserId === actorExternalId,
|
|
142
|
+
);
|
|
143
|
+
if (guardianPending.length !== 1) {
|
|
136
144
|
return { handled: true, type: "stale_ignored" };
|
|
137
145
|
}
|
|
138
|
-
|
|
146
|
+
|
|
147
|
+
const result = applyGuardianDecision({
|
|
148
|
+
approval: guardianPending[0],
|
|
149
|
+
decision: reactionDecision,
|
|
150
|
+
actorPrincipalId: undefined,
|
|
151
|
+
actorExternalUserId: actorExternalId,
|
|
152
|
+
actorChannel: sourceChannel,
|
|
153
|
+
});
|
|
139
154
|
if (result.applied) {
|
|
140
|
-
return { handled: true, type: "
|
|
155
|
+
return { handled: true, type: "guardian_decision_applied" };
|
|
141
156
|
}
|
|
142
157
|
return { handled: true, type: "stale_ignored" };
|
|
143
158
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route handler for host CU (computer-use) result submissions.
|
|
3
|
+
*
|
|
4
|
+
* Resolves pending host CU proxy requests by requestId when the desktop
|
|
5
|
+
* client returns observation results via HTTP.
|
|
6
|
+
*/
|
|
7
|
+
import { requireBoundGuardian } from "../auth/require-bound-guardian.js";
|
|
8
|
+
import type { AuthContext } from "../auth/types.js";
|
|
9
|
+
import { httpError } from "../http-errors.js";
|
|
10
|
+
import type { RouteDefinition } from "../http-router.js";
|
|
11
|
+
import * as pendingInteractions from "../pending-interactions.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* POST /v1/host-cu-result — resolve a pending host CU request by requestId.
|
|
15
|
+
* Requires AuthContext with guardian-bound actor.
|
|
16
|
+
*/
|
|
17
|
+
export async function handleHostCuResult(
|
|
18
|
+
req: Request,
|
|
19
|
+
authContext: AuthContext,
|
|
20
|
+
): Promise<Response> {
|
|
21
|
+
const authError = requireBoundGuardian(authContext);
|
|
22
|
+
if (authError) return authError;
|
|
23
|
+
|
|
24
|
+
const body = (await req.json()) as {
|
|
25
|
+
requestId?: string;
|
|
26
|
+
axTree?: string;
|
|
27
|
+
axDiff?: string;
|
|
28
|
+
screenshot?: string;
|
|
29
|
+
screenshotWidthPx?: number;
|
|
30
|
+
screenshotHeightPx?: number;
|
|
31
|
+
screenWidthPt?: number;
|
|
32
|
+
screenHeightPt?: number;
|
|
33
|
+
executionResult?: string;
|
|
34
|
+
executionError?: string;
|
|
35
|
+
secondaryWindows?: string;
|
|
36
|
+
userGuidance?: string;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const { requestId } = body;
|
|
40
|
+
|
|
41
|
+
if (!requestId || typeof requestId !== "string") {
|
|
42
|
+
return httpError("BAD_REQUEST", "requestId is required", 400);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Peek first (non-destructive) so we can validate the interaction kind
|
|
46
|
+
// without accidentally consuming a confirmation or secret interaction.
|
|
47
|
+
const peeked = pendingInteractions.get(requestId);
|
|
48
|
+
if (!peeked) {
|
|
49
|
+
return httpError(
|
|
50
|
+
"NOT_FOUND",
|
|
51
|
+
"No pending interaction found for this requestId",
|
|
52
|
+
404,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (peeked.kind !== "host_cu") {
|
|
57
|
+
return httpError(
|
|
58
|
+
"CONFLICT",
|
|
59
|
+
`Pending interaction is of kind "${peeked.kind}", expected "host_cu"`,
|
|
60
|
+
409,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Validation passed — consume the pending interaction.
|
|
65
|
+
const interaction = pendingInteractions.resolve(requestId)!;
|
|
66
|
+
|
|
67
|
+
interaction.session.resolveHostCu(requestId, {
|
|
68
|
+
axTree: body.axTree,
|
|
69
|
+
axDiff: body.axDiff,
|
|
70
|
+
screenshot: body.screenshot,
|
|
71
|
+
screenshotWidthPx: body.screenshotWidthPx,
|
|
72
|
+
screenshotHeightPx: body.screenshotHeightPx,
|
|
73
|
+
screenWidthPt: body.screenWidthPt,
|
|
74
|
+
screenHeightPt: body.screenHeightPt,
|
|
75
|
+
executionResult: body.executionResult,
|
|
76
|
+
executionError: body.executionError,
|
|
77
|
+
secondaryWindows: body.secondaryWindows,
|
|
78
|
+
userGuidance: body.userGuidance,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
return Response.json({ accepted: true });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
// Route definitions
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
|
|
88
|
+
export function hostCuRouteDefinitions(): RouteDefinition[] {
|
|
89
|
+
return [
|
|
90
|
+
{
|
|
91
|
+
endpoint: "host-cu-result",
|
|
92
|
+
method: "POST",
|
|
93
|
+
handler: async ({ req, authContext }) =>
|
|
94
|
+
handleHostCuResult(req, authContext),
|
|
95
|
+
},
|
|
96
|
+
];
|
|
97
|
+
}
|
|
@@ -31,12 +31,8 @@ import type {
|
|
|
31
31
|
ApprovalCopyGenerator,
|
|
32
32
|
MessageProcessor,
|
|
33
33
|
} from "../../http-types.js";
|
|
34
|
-
import { TelegramStreamingDelivery } from "../../telegram-streaming-delivery.js";
|
|
35
34
|
import { resolveRoutingState } from "../../trust-context-resolver.js";
|
|
36
|
-
import {
|
|
37
|
-
deliverAttachmentsOnly,
|
|
38
|
-
deliverReplyViaCallback,
|
|
39
|
-
} from "../channel-delivery-routes.js";
|
|
35
|
+
import { deliverReplyViaCallback } from "../channel-delivery-routes.js";
|
|
40
36
|
import { deliverGeneratedApprovalPrompt } from "../guardian-approval-prompt.js";
|
|
41
37
|
|
|
42
38
|
const log = getLogger("runtime-http");
|
|
@@ -112,12 +108,6 @@ export function processChannelMessageInBackground(
|
|
|
112
108
|
} = params;
|
|
113
109
|
|
|
114
110
|
(async () => {
|
|
115
|
-
const boundGuardianActor = isBoundGuardianActor({
|
|
116
|
-
trustClass: trustCtx.trustClass,
|
|
117
|
-
guardianExternalUserId: trustCtx.guardianExternalUserId,
|
|
118
|
-
requesterExternalUserId: trustCtx.requesterExternalUserId,
|
|
119
|
-
});
|
|
120
|
-
|
|
121
111
|
const typingCallbackUrl = shouldEmitTelegramTyping(
|
|
122
112
|
sourceChannel,
|
|
123
113
|
replyCallbackUrl,
|
|
@@ -181,16 +171,6 @@ export function processChannelMessageInBackground(
|
|
|
181
171
|
}
|
|
182
172
|
}
|
|
183
173
|
|
|
184
|
-
const telegramStreaming =
|
|
185
|
-
sourceChannel === "telegram" && replyCallbackUrl
|
|
186
|
-
? new TelegramStreamingDelivery({
|
|
187
|
-
callbackUrl: replyCallbackUrl,
|
|
188
|
-
chatId: externalChatId,
|
|
189
|
-
mintBearerToken,
|
|
190
|
-
assistantId,
|
|
191
|
-
})
|
|
192
|
-
: undefined;
|
|
193
|
-
|
|
194
174
|
try {
|
|
195
175
|
const cmdIntent =
|
|
196
176
|
commandIntent && typeof commandIntent.type === "string"
|
|
@@ -218,9 +198,6 @@ export function processChannelMessageInBackground(
|
|
|
218
198
|
trustContext: trustCtx,
|
|
219
199
|
isInteractive: resolveRoutingState(trustCtx).promptWaitingAllowed,
|
|
220
200
|
...(cmdIntent ? { commandIntent: cmdIntent } : {}),
|
|
221
|
-
...(telegramStreaming
|
|
222
|
-
? { onEvent: (msg) => telegramStreaming.onEvent(msg) }
|
|
223
|
-
: {}),
|
|
224
201
|
},
|
|
225
202
|
sourceChannel,
|
|
226
203
|
sourceInterface,
|
|
@@ -228,94 +205,18 @@ export function processChannelMessageInBackground(
|
|
|
228
205
|
deliveryCrud.linkMessage(eventId, userMessageId);
|
|
229
206
|
deliveryStatus.markProcessed(eventId);
|
|
230
207
|
|
|
231
|
-
if (telegramStreaming) {
|
|
232
|
-
// Retrieve approval metadata from pending interactions (if any)
|
|
233
|
-
// so approval buttons can be attached to the final streamed message.
|
|
234
|
-
// Approval prompts are guardian-only and must never be attached for
|
|
235
|
-
// non-guardian or unverified actors.
|
|
236
|
-
const prompt = boundGuardianActor
|
|
237
|
-
? getChannelApprovalPrompt(conversationId)
|
|
238
|
-
: undefined;
|
|
239
|
-
const pending = boundGuardianActor
|
|
240
|
-
? getApprovalInfoByConversation(conversationId)
|
|
241
|
-
: [];
|
|
242
|
-
const approvalMeta =
|
|
243
|
-
prompt && pending.length > 0
|
|
244
|
-
? buildApprovalUIMetadata(prompt, pending[0])
|
|
245
|
-
: undefined;
|
|
246
|
-
try {
|
|
247
|
-
await telegramStreaming.finish(approvalMeta);
|
|
248
|
-
deliveryChannels.updateDeliveredSegmentCount(eventId, 1);
|
|
249
|
-
} catch (err) {
|
|
250
|
-
log.error(
|
|
251
|
-
{ err, conversationId },
|
|
252
|
-
"Telegram streaming finalization failed",
|
|
253
|
-
);
|
|
254
|
-
// Fallback: deliver approval as a standalone message so buttons
|
|
255
|
-
// are not permanently lost when finish() fails.
|
|
256
|
-
if (approvalMeta && replyCallbackUrl) {
|
|
257
|
-
try {
|
|
258
|
-
await deliverChannelReply(
|
|
259
|
-
replyCallbackUrl,
|
|
260
|
-
{
|
|
261
|
-
chatId: externalChatId,
|
|
262
|
-
text: approvalMeta.plainTextFallback ?? "Action needed:",
|
|
263
|
-
approval: approvalMeta,
|
|
264
|
-
assistantId,
|
|
265
|
-
},
|
|
266
|
-
mintBearerToken(),
|
|
267
|
-
);
|
|
268
|
-
} catch (fallbackErr) {
|
|
269
|
-
log.error(
|
|
270
|
-
{ err: fallbackErr, conversationId },
|
|
271
|
-
"Fallback approval delivery also failed",
|
|
272
|
-
);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
208
|
if (replyCallbackUrl) {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
mintBearerToken(),
|
|
291
|
-
assistantId,
|
|
292
|
-
);
|
|
293
|
-
} else {
|
|
294
|
-
// Non-streaming path, or streaming partially failed (some text
|
|
295
|
-
// was delivered but finish/finalization threw). In the partial
|
|
296
|
-
// failure case the user has a truncated message, so we deliver
|
|
297
|
-
// the full response to ensure nothing is lost.
|
|
298
|
-
if (
|
|
299
|
-
telegramStreaming?.hasDeliveredText &&
|
|
300
|
-
!telegramStreaming.finishSucceeded
|
|
301
|
-
) {
|
|
302
|
-
log.warn(
|
|
303
|
-
{ conversationId },
|
|
304
|
-
"Telegram streaming partially failed — falling back to full text delivery",
|
|
305
|
-
);
|
|
306
|
-
}
|
|
307
|
-
await deliverReplyViaCallback(
|
|
308
|
-
conversationId,
|
|
309
|
-
externalChatId,
|
|
310
|
-
replyCallbackUrl,
|
|
311
|
-
mintBearerToken(),
|
|
312
|
-
assistantId,
|
|
313
|
-
{
|
|
314
|
-
onSegmentDelivered: (count) =>
|
|
315
|
-
deliveryChannels.updateDeliveredSegmentCount(eventId, count),
|
|
316
|
-
},
|
|
317
|
-
);
|
|
318
|
-
}
|
|
209
|
+
await deliverReplyViaCallback(
|
|
210
|
+
conversationId,
|
|
211
|
+
externalChatId,
|
|
212
|
+
replyCallbackUrl,
|
|
213
|
+
mintBearerToken(),
|
|
214
|
+
assistantId,
|
|
215
|
+
{
|
|
216
|
+
onSegmentDelivered: (count) =>
|
|
217
|
+
deliveryChannels.updateDeliveredSegmentCount(eventId, count),
|
|
218
|
+
},
|
|
219
|
+
);
|
|
319
220
|
}
|
|
320
221
|
} catch (err) {
|
|
321
222
|
log.error(
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
userInfo,
|
|
13
13
|
} from "../../../../messaging/providers/slack/client.js";
|
|
14
14
|
import type { SlackConversation } from "../../../../messaging/providers/slack/types.js";
|
|
15
|
+
import { getConnectionByProvider } from "../../../../oauth/oauth-store.js";
|
|
15
16
|
import { getSecureKey } from "../../../../security/secure-keys.js";
|
|
16
17
|
import { getLogger } from "../../../../util/logger.js";
|
|
17
18
|
import { httpError } from "../../../http-errors.js";
|
|
@@ -24,14 +25,13 @@ const log = getLogger("slack-share");
|
|
|
24
25
|
// ---------------------------------------------------------------------------
|
|
25
26
|
|
|
26
27
|
/**
|
|
27
|
-
* Resolve the Slack bot token from
|
|
28
|
-
* Prefers the OAuth integration token, falls back to the legacy channel token.
|
|
28
|
+
* Resolve the Slack bot token from the OAuth connection store.
|
|
29
29
|
*/
|
|
30
30
|
function resolveSlackToken(): string | undefined {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
getSecureKey(
|
|
34
|
-
|
|
31
|
+
const conn = getConnectionByProvider("integration:slack");
|
|
32
|
+
return conn
|
|
33
|
+
? getSecureKey(`oauth_connection/${conn.id}/access_token`)
|
|
34
|
+
: undefined;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
// ---------------------------------------------------------------------------
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
import { loadRawConfig, saveRawConfig } from "../../../config/loader.js";
|
|
22
22
|
import { syncTwilioWebhooks } from "../../../daemon/handlers/config-ingress.js";
|
|
23
23
|
import type { IngressConfig } from "../../../inbound/public-ingress-urls.js";
|
|
24
|
+
import { credentialKey } from "../../../security/credential-key.js";
|
|
24
25
|
import {
|
|
25
26
|
deleteSecureKeyAsync,
|
|
26
27
|
setSecureKeyAsync,
|
|
@@ -136,7 +137,7 @@ export async function handleSetTwilioCredentials(
|
|
|
136
137
|
// validation (gateway/src/credential-reader.ts), while the assistant reads
|
|
137
138
|
// from config via resolveAccountSid(). Both stores must stay in sync.
|
|
138
139
|
const sidStored = await setSecureKeyAsync(
|
|
139
|
-
"
|
|
140
|
+
credentialKey("twilio", "account_sid"),
|
|
140
141
|
body.accountSid,
|
|
141
142
|
);
|
|
142
143
|
if (!sidStored) {
|
|
@@ -148,11 +149,11 @@ export async function handleSetTwilioCredentials(
|
|
|
148
149
|
}
|
|
149
150
|
|
|
150
151
|
const tokenStored = await setSecureKeyAsync(
|
|
151
|
-
"
|
|
152
|
+
credentialKey("twilio", "auth_token"),
|
|
152
153
|
body.authToken,
|
|
153
154
|
);
|
|
154
155
|
if (!tokenStored) {
|
|
155
|
-
await deleteSecureKeyAsync("
|
|
156
|
+
await deleteSecureKeyAsync(credentialKey("twilio", "account_sid"));
|
|
156
157
|
return Response.json({
|
|
157
158
|
success: false,
|
|
158
159
|
hasCredentials: false,
|
|
@@ -202,8 +203,8 @@ export async function handleSetTwilioCredentials(
|
|
|
202
203
|
* DELETE /v1/integrations/twilio/credentials
|
|
203
204
|
*/
|
|
204
205
|
export async function handleClearTwilioCredentials(): Promise<Response> {
|
|
205
|
-
const r1 = await deleteSecureKeyAsync("
|
|
206
|
-
const r2 = await deleteSecureKeyAsync("
|
|
206
|
+
const r1 = await deleteSecureKeyAsync(credentialKey("twilio", "account_sid"));
|
|
207
|
+
const r2 = await deleteSecureKeyAsync(credentialKey("twilio", "auth_token"));
|
|
207
208
|
|
|
208
209
|
if (r1 === "error" || r2 === "error") {
|
|
209
210
|
return Response.json(
|
|
@@ -14,7 +14,11 @@ import { desc } from "drizzle-orm";
|
|
|
14
14
|
import { getDb } from "../../memory/db.js";
|
|
15
15
|
import { toolInvocations } from "../../memory/schema.js";
|
|
16
16
|
import { getLogger } from "../../util/logger.js";
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
getDataDir,
|
|
19
|
+
getRootDir,
|
|
20
|
+
getWorkspaceConfigPath,
|
|
21
|
+
} from "../../util/platform.js";
|
|
18
22
|
import { httpError } from "../http-errors.js";
|
|
19
23
|
import type { RouteDefinition } from "../http-router.js";
|
|
20
24
|
|
|
@@ -31,6 +35,7 @@ interface ExportResponse {
|
|
|
31
35
|
success: true;
|
|
32
36
|
auditRows: Array<Record<string, unknown>>;
|
|
33
37
|
logFiles: Record<string, string>;
|
|
38
|
+
configSnapshot?: Record<string, unknown>;
|
|
34
39
|
}
|
|
35
40
|
|
|
36
41
|
/**
|
|
@@ -83,21 +88,134 @@ async function handleExport(body: ExportRequestBody): Promise<Response> {
|
|
|
83
88
|
}
|
|
84
89
|
}
|
|
85
90
|
|
|
91
|
+
// --- Sanitized config snapshot ---
|
|
92
|
+
const configSnapshot = readSanitizedConfig();
|
|
93
|
+
|
|
86
94
|
log.info(
|
|
87
|
-
{
|
|
95
|
+
{
|
|
96
|
+
auditCount: auditRows.length,
|
|
97
|
+
logFileCount: Object.keys(logFiles).length,
|
|
98
|
+
totalBytes,
|
|
99
|
+
hasConfig: configSnapshot !== undefined,
|
|
100
|
+
},
|
|
88
101
|
"Export completed",
|
|
89
102
|
);
|
|
90
103
|
|
|
91
|
-
const payload: ExportResponse = {
|
|
104
|
+
const payload: ExportResponse = {
|
|
105
|
+
success: true,
|
|
106
|
+
auditRows,
|
|
107
|
+
logFiles,
|
|
108
|
+
configSnapshot,
|
|
109
|
+
};
|
|
92
110
|
return Response.json(payload);
|
|
93
111
|
} catch (err) {
|
|
94
112
|
const message = err instanceof Error ? err.message : String(err);
|
|
95
113
|
log.error({ err }, "Failed to export");
|
|
96
|
-
return httpError(
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
114
|
+
return httpError("INTERNAL_ERROR", `Failed to export: ${message}`, 500);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Replaces a string value with a presence flag: "(set)" if truthy, "(empty)" otherwise.
|
|
120
|
+
*/
|
|
121
|
+
function redactStringValue(val: unknown): string {
|
|
122
|
+
return val ? "(set)" : "(empty)";
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Reads the workspace config.json and strips sensitive fields.
|
|
127
|
+
* Returns undefined if the file is missing or unreadable.
|
|
128
|
+
*/
|
|
129
|
+
function readSanitizedConfig(): Record<string, unknown> | undefined {
|
|
130
|
+
const configPath = getWorkspaceConfigPath();
|
|
131
|
+
if (!existsSync(configPath)) return undefined;
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
135
|
+
const config = JSON.parse(raw) as Record<string, unknown>;
|
|
136
|
+
|
|
137
|
+
// Strip API key values — preserve which providers have keys configured
|
|
138
|
+
if (config.apiKeys && typeof config.apiKeys === "object") {
|
|
139
|
+
const keys = config.apiKeys as Record<string, unknown>;
|
|
140
|
+
config.apiKeys = Object.fromEntries(
|
|
141
|
+
Object.keys(keys).map((k) => [k, redactStringValue(keys[k])]),
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Strip ingress webhook secret
|
|
146
|
+
if (config.ingress && typeof config.ingress === "object") {
|
|
147
|
+
const ingress = config.ingress as Record<string, unknown>;
|
|
148
|
+
if (ingress.webhook && typeof ingress.webhook === "object") {
|
|
149
|
+
const webhook = ingress.webhook as Record<string, unknown>;
|
|
150
|
+
webhook.secret = redactStringValue(webhook.secret);
|
|
151
|
+
ingress.webhook = webhook;
|
|
152
|
+
}
|
|
153
|
+
config.ingress = ingress;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Strip skill-level API keys and env vars
|
|
157
|
+
if (config.skills && typeof config.skills === "object") {
|
|
158
|
+
const skills = config.skills as Record<string, unknown>;
|
|
159
|
+
if (skills.entries && typeof skills.entries === "object") {
|
|
160
|
+
const entries = skills.entries as Record<string, unknown>;
|
|
161
|
+
for (const name of Object.keys(entries)) {
|
|
162
|
+
const entry = entries[name];
|
|
163
|
+
if (entry && typeof entry === "object") {
|
|
164
|
+
const e = entry as Record<string, unknown>;
|
|
165
|
+
if ("apiKey" in e) e.apiKey = redactStringValue(e.apiKey);
|
|
166
|
+
if (e.env && typeof e.env === "object") {
|
|
167
|
+
const env = e.env as Record<string, unknown>;
|
|
168
|
+
e.env = Object.fromEntries(
|
|
169
|
+
Object.keys(env).map((k) => [k, redactStringValue(env[k])]),
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Strip Twilio accountSid
|
|
178
|
+
if (config.twilio && typeof config.twilio === "object") {
|
|
179
|
+
const twilio = config.twilio as Record<string, unknown>;
|
|
180
|
+
twilio.accountSid = redactStringValue(twilio.accountSid);
|
|
181
|
+
config.twilio = twilio;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Strip MCP transport headers (SSE/streamable-http) and env vars (stdio)
|
|
185
|
+
if (config.mcp && typeof config.mcp === "object") {
|
|
186
|
+
const mcp = config.mcp as Record<string, unknown>;
|
|
187
|
+
if (mcp.servers && typeof mcp.servers === "object") {
|
|
188
|
+
const servers = mcp.servers as Record<string, unknown>;
|
|
189
|
+
for (const name of Object.keys(servers)) {
|
|
190
|
+
const server = servers[name];
|
|
191
|
+
if (server && typeof server === "object") {
|
|
192
|
+
const s = server as Record<string, unknown>;
|
|
193
|
+
if (s.transport && typeof s.transport === "object") {
|
|
194
|
+
const transport = s.transport as Record<string, unknown>;
|
|
195
|
+
if (transport.headers && typeof transport.headers === "object") {
|
|
196
|
+
const headers = transport.headers as Record<string, unknown>;
|
|
197
|
+
transport.headers = Object.fromEntries(
|
|
198
|
+
Object.keys(headers).map((k) => [
|
|
199
|
+
k,
|
|
200
|
+
redactStringValue(headers[k]),
|
|
201
|
+
]),
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
if (transport.env && typeof transport.env === "object") {
|
|
205
|
+
const env = transport.env as Record<string, unknown>;
|
|
206
|
+
transport.env = Object.fromEntries(
|
|
207
|
+
Object.keys(env).map((k) => [k, redactStringValue(env[k])]),
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return config;
|
|
217
|
+
} catch {
|
|
218
|
+
return undefined;
|
|
101
219
|
}
|
|
102
220
|
}
|
|
103
221
|
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
invalidateConfigCache,
|
|
5
5
|
} from "../../config/loader.js";
|
|
6
6
|
import { initializeProviders } from "../../providers/registry.js";
|
|
7
|
+
import { credentialKey } from "../../security/credential-key.js";
|
|
7
8
|
import {
|
|
8
9
|
deleteSecureKeyAsync,
|
|
9
10
|
getSecureKeyAsync,
|
|
@@ -78,7 +79,7 @@ export async function handleAddSecret(req: Request): Promise<Response> {
|
|
|
78
79
|
assertMetadataWritable();
|
|
79
80
|
const service = name.slice(0, colonIdx);
|
|
80
81
|
const field = name.slice(colonIdx + 1);
|
|
81
|
-
const key =
|
|
82
|
+
const key = credentialKey(service, field);
|
|
82
83
|
const stored = await setSecureKeyAsync(key, value);
|
|
83
84
|
if (!stored) {
|
|
84
85
|
return httpError(
|
|
@@ -164,7 +165,7 @@ export async function handleDeleteSecret(req: Request): Promise<Response> {
|
|
|
164
165
|
const service = name.slice(0, colonIdx);
|
|
165
166
|
const field = name.slice(colonIdx + 1);
|
|
166
167
|
assertMetadataWritable();
|
|
167
|
-
const key =
|
|
168
|
+
const key = credentialKey(service, field);
|
|
168
169
|
// Check existence first — the broker always returns "deleted" even
|
|
169
170
|
// for keys that don't exist, so we need a pre-check for 404 semantics.
|
|
170
171
|
const existing = await getSecureKeyAsync(key);
|