@vellumai/assistant 0.5.10 → 0.5.12
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/AGENTS.md +8 -0
- package/ARCHITECTURE.md +43 -43
- package/Dockerfile +3 -0
- package/docs/architecture/integrations.md +37 -42
- package/docs/architecture/memory.md +7 -12
- package/docs/credential-execution-service.md +9 -9
- package/docs/skills.md +1 -1
- package/node_modules/@vellumai/ces-contracts/src/__tests__/grants.test.ts +7 -7
- package/node_modules/@vellumai/ces-contracts/src/handles.ts +5 -4
- package/node_modules/@vellumai/credential-storage/src/index.ts +3 -3
- package/node_modules/@vellumai/credential-storage/src/static-credentials.ts +1 -1
- package/openapi.yaml +7208 -0
- package/package.json +2 -1
- package/scripts/generate-openapi.ts +562 -0
- package/src/__tests__/acp-session.test.ts +239 -44
- package/src/__tests__/assistant-feature-flag-guard.test.ts +8 -8
- package/src/__tests__/assistant-feature-flag-guardrails.test.ts +5 -86
- package/src/__tests__/assistant-feature-flags-integration.test.ts +7 -14
- package/src/__tests__/browser-skill-endstate.test.ts +1 -1
- package/src/__tests__/btw-routes.test.ts +8 -0
- package/src/__tests__/bundled-skill-retrieval-guard.test.ts +10 -10
- package/src/__tests__/catalog-cache.test.ts +164 -0
- package/src/__tests__/catalog-search.test.ts +61 -0
- package/src/__tests__/channel-approvals.test.ts +7 -7
- package/src/__tests__/channel-readiness-service.test.ts +41 -0
- package/src/__tests__/cli-command-risk-guard.test.ts +181 -6
- package/src/__tests__/config-schema.test.ts +10 -2
- package/src/__tests__/context-memory-e2e.test.ts +2 -6
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +396 -0
- package/src/__tests__/conversation-error.test.ts +3 -2
- package/src/__tests__/conversation-skill-tools.test.ts +1 -3
- package/src/__tests__/conversation-title-service.test.ts +2 -15
- package/src/__tests__/credential-execution-feature-gates.test.ts +4 -8
- package/src/__tests__/credential-execution-managed-contract.test.ts +8 -8
- package/src/__tests__/credential-security-e2e.test.ts +4 -4
- package/src/__tests__/credential-security-invariants.test.ts +12 -18
- package/src/__tests__/credential-vault-unit.test.ts +32 -34
- package/src/__tests__/credential-vault.test.ts +25 -33
- package/src/__tests__/credentials-cli.test.ts +3 -3
- package/src/__tests__/daemon-credential-client.test.ts +2 -2
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +1 -1
- package/src/__tests__/gateway-only-guard.test.ts +3 -0
- package/src/__tests__/heartbeat-service.test.ts +35 -0
- package/src/__tests__/host-bash-proxy.test.ts +79 -0
- package/src/__tests__/host-cu-proxy.test.ts +90 -0
- package/src/__tests__/host-file-proxy.test.ts +89 -0
- package/src/__tests__/host-shell-tool.test.ts +1 -1
- package/src/__tests__/inline-skill-load-permissions.test.ts +3 -3
- package/src/__tests__/integration-status.test.ts +5 -5
- package/src/__tests__/list-messages-attachments.test.ts +171 -0
- package/src/__tests__/llm-request-log-turn-query.test.ts +64 -0
- package/src/__tests__/log-export-workspace.test.ts +1 -1
- package/src/__tests__/mcp-abort-signal.test.ts +205 -0
- package/src/__tests__/mcp-client-auth.test.ts +1 -1
- package/src/__tests__/memory-lifecycle-e2e.test.ts +2 -2
- package/src/__tests__/memory-recall-log-store.test.ts +182 -0
- package/src/__tests__/memory-recall-quality.test.ts +6 -8
- package/src/__tests__/memory-regressions.test.ts +53 -42
- package/src/__tests__/memory-retrieval.benchmark.test.ts +5 -9
- package/src/__tests__/messaging-send-tool.test.ts +5 -5
- package/src/__tests__/messaging-skill-split.test.ts +2 -17
- package/src/__tests__/notification-telegram-adapter.test.ts +125 -0
- package/src/__tests__/oauth-cli.test.ts +203 -649
- package/src/__tests__/oauth-provider-profiles.test.ts +55 -20
- package/src/__tests__/oauth-scope-policy.test.ts +4 -6
- package/src/__tests__/onboarding-template-contract.test.ts +2 -2
- package/src/__tests__/platform-callback-registration.test.ts +119 -0
- package/src/__tests__/secret-ingress-channel.test.ts +261 -0
- package/src/__tests__/secret-ingress-cli.test.ts +201 -0
- package/src/__tests__/secret-ingress-http.test.ts +312 -0
- package/src/__tests__/secret-ingress.test.ts +283 -0
- package/src/__tests__/secret-onetime-send.test.ts +4 -4
- package/src/__tests__/secret-routes-managed-proxy.test.ts +78 -0
- package/src/__tests__/secure-keys-managed-failover.test.ts +73 -0
- package/src/__tests__/skill-feature-flags-integration.test.ts +4 -4
- package/src/__tests__/skill-feature-flags.test.ts +11 -19
- package/src/__tests__/skill-load-feature-flag.test.ts +1 -1
- package/src/__tests__/skill-load-inline-command.test.ts +3 -3
- package/src/__tests__/skill-load-inline-includes.test.ts +2 -2
- package/src/__tests__/skill-memory.test.ts +2 -4
- package/src/__tests__/skill-projection-feature-flag.test.ts +2 -4
- package/src/__tests__/skill-projection.benchmark.test.ts +1 -3
- package/src/__tests__/skills-uninstall.test.ts +2 -2
- package/src/__tests__/skills.test.ts +16 -2
- package/src/__tests__/slack-channel-config.test.ts +1 -1
- package/src/__tests__/slack-messaging-token-resolution.test.ts +22 -24
- package/src/__tests__/slack-share-routes.test.ts +5 -5
- package/src/__tests__/slack-skill.test.ts +5 -69
- package/src/__tests__/system-prompt.test.ts +39 -0
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +1 -1
- package/src/__tests__/workspace-migration-018-rekey-compound-credential-keys.test.ts +181 -0
- package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +5 -4
- package/src/acp/client-handler.ts +113 -31
- package/src/acp/session-manager.ts +29 -27
- package/src/approvals/guardian-request-resolvers.ts +1 -1
- package/src/cli/AGENTS.md +113 -0
- package/src/cli/commands/autonomy.ts +3 -5
- package/src/cli/commands/browser-relay.ts +2 -17
- package/src/cli/commands/contacts.ts +6 -4
- package/src/cli/commands/conversations.ts +13 -1
- package/src/cli/commands/credential-execution.ts +17 -3
- package/src/cli/commands/credentials.ts +2 -8
- package/src/cli/commands/memory.ts +2 -3
- package/src/cli/commands/oauth/__tests__/connect.test.ts +706 -0
- package/src/cli/commands/oauth/__tests__/disconnect.test.ts +686 -0
- package/src/cli/commands/oauth/__tests__/mode.test.ts +625 -0
- package/src/cli/commands/oauth/__tests__/ping.test.ts +631 -0
- package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +574 -0
- package/src/cli/commands/oauth/__tests__/providers-update.test.ts +416 -0
- package/src/cli/commands/oauth/__tests__/status.test.ts +551 -0
- package/src/cli/commands/oauth/__tests__/token.test.ts +420 -0
- package/src/cli/commands/oauth/apps.ts +87 -50
- package/src/cli/commands/oauth/connect.ts +405 -0
- package/src/cli/commands/oauth/disconnect.ts +285 -0
- package/src/cli/commands/oauth/index.ts +62 -20
- package/src/cli/commands/oauth/mode.ts +251 -0
- package/src/cli/commands/oauth/ping.ts +196 -0
- package/src/cli/commands/oauth/providers.ts +589 -55
- package/src/cli/commands/oauth/request.ts +564 -0
- package/src/cli/commands/oauth/shared.ts +114 -0
- package/src/cli/commands/oauth/status.ts +191 -0
- package/src/cli/commands/oauth/token.ts +150 -0
- package/src/cli/commands/platform/connect.ts +104 -0
- package/src/cli/commands/platform/disconnect.ts +118 -0
- package/src/cli/commands/platform/index.ts +252 -0
- package/src/cli/commands/sequence.ts +5 -4
- package/src/cli/commands/shotgun.ts +16 -0
- package/src/cli/commands/skills.ts +173 -41
- package/src/cli/commands/usage.ts +5 -11
- package/src/cli/lib/daemon-credential-client.ts +22 -38
- package/src/cli/program.ts +1 -1
- package/src/cli.ts +82 -17
- package/src/config/assistant-feature-flags.ts +77 -18
- package/src/config/bundled-skills/_shared/CLI_RETRIEVAL_PATTERN.md +1 -1
- package/src/config/bundled-skills/app-builder/tools/app-create.ts +1 -1
- package/src/config/bundled-skills/contacts/tools/google-contacts.ts +1 -1
- package/src/config/bundled-skills/conversations/SKILL.md +20 -0
- package/src/config/bundled-skills/conversations/TOOLS.json +23 -0
- package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +66 -0
- package/src/config/bundled-skills/gmail/SKILL.md +13 -13
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +3 -3
- package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +2 -2
- package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-label.ts +2 -2
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +1 -1
- package/src/config/bundled-skills/google-calendar/SKILL.md +10 -4
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +1 -1
- package/src/config/bundled-skills/messaging/SKILL.md +19 -42
- package/src/config/bundled-skills/messaging/TOOLS.json +9 -9
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +1 -1
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +5 -2
- package/src/config/bundled-skills/messaging/tools/shared.ts +5 -6
- package/src/config/bundled-skills/notifications/SKILL.md +1 -1
- package/src/config/bundled-skills/schedule/SKILL.md +2 -2
- package/src/config/bundled-skills/settings/SKILL.md +5 -3
- package/src/config/bundled-skills/settings/TOOLS.json +17 -0
- package/src/config/bundled-skills/settings/tools/avatar-get.ts +50 -0
- package/src/config/bundled-skills/settings/tools/avatar-remove.ts +7 -0
- package/src/config/bundled-skills/settings/tools/avatar-update.ts +6 -1
- package/src/config/bundled-skills/settings/tools/identity-avatar.ts +55 -0
- package/src/config/bundled-skills/skills-catalog/SKILL.md +3 -3
- package/src/config/bundled-skills/slack/SKILL.md +58 -44
- package/src/config/bundled-tool-registry.ts +7 -19
- package/src/config/env.ts +5 -1
- package/src/config/feature-flag-registry.json +58 -42
- package/src/config/loader.ts +4 -0
- package/src/config/schemas/platform.ts +0 -8
- package/src/config/schemas/security.ts +9 -1
- package/src/config/schemas/services.ts +1 -1
- package/src/config/skill-state.ts +1 -3
- package/src/config/skills.ts +2 -4
- package/src/credential-execution/client.ts +1 -1
- package/src/credential-execution/feature-gates.ts +9 -16
- package/src/credential-execution/process-manager.ts +12 -0
- package/src/daemon/config-watcher.ts +4 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +10 -0
- package/src/daemon/conversation-agent-loop.ts +51 -2
- package/src/daemon/conversation-error.ts +36 -6
- package/src/daemon/conversation-memory.ts +0 -1
- package/src/daemon/conversation-messaging.ts +9 -0
- package/src/daemon/conversation-runtime-assembly.ts +33 -0
- package/src/daemon/conversation-surfaces.ts +120 -14
- package/src/daemon/conversation.ts +5 -0
- package/src/daemon/handlers/config-slack-channel.ts +43 -1
- package/src/daemon/handlers/conversations.ts +41 -33
- package/src/daemon/handlers/skills.ts +148 -3
- package/src/daemon/host-bash-proxy.ts +16 -0
- package/src/daemon/host-cu-proxy.ts +16 -0
- package/src/daemon/host-file-proxy.ts +16 -0
- package/src/daemon/lifecycle.ts +73 -3
- package/src/daemon/message-types/acp.ts +0 -15
- package/src/daemon/message-types/conversations.ts +1 -0
- package/src/daemon/message-types/guardian-actions.ts +2 -0
- package/src/daemon/message-types/host-bash.ts +6 -1
- package/src/daemon/message-types/host-cu.ts +6 -1
- package/src/daemon/message-types/host-file.ts +6 -1
- package/src/daemon/message-types/integrations.ts +0 -1
- package/src/daemon/message-types/memory.ts +0 -1
- package/src/daemon/message-types/messages.ts +9 -1
- package/src/daemon/message-types/schedules.ts +9 -0
- package/src/daemon/server.ts +48 -9
- package/src/email/feature-gate.ts +3 -3
- package/src/heartbeat/heartbeat-service.ts +48 -0
- package/src/hooks/cli.ts +74 -0
- package/src/inbound/platform-callback-registration.ts +68 -19
- package/src/mcp/client.ts +6 -1
- package/src/mcp/manager.ts +2 -1
- package/src/mcp/mcp-oauth-provider.ts +3 -3
- package/src/memory/app-store.ts +3 -3
- package/src/memory/conversation-crud.ts +213 -0
- package/src/memory/conversation-key-store.ts +26 -0
- package/src/memory/conversation-title-service.ts +7 -17
- package/src/memory/db-init.ts +24 -0
- package/src/memory/embedding-local.ts +47 -2
- package/src/memory/indexer.ts +13 -10
- package/src/memory/items-extractor.ts +12 -4
- package/src/memory/job-utils.ts +5 -0
- package/src/memory/jobs-store.ts +10 -2
- package/src/memory/journal-memory.ts +6 -2
- package/src/memory/llm-request-log-store.ts +88 -21
- package/src/memory/memory-recall-log-store.ts +128 -0
- package/src/memory/migrations/194-memory-recall-logs.ts +50 -0
- package/src/memory/migrations/195-oauth-providers-ping-config.ts +23 -0
- package/src/memory/migrations/196-messages-conversation-created-at-index.ts +9 -0
- package/src/memory/migrations/196-strip-integration-prefix-from-provider-keys.ts +186 -0
- package/src/memory/migrations/197-oauth-providers-behavior-columns.ts +29 -0
- package/src/memory/migrations/198-drop-setup-skill-id-column.ts +11 -0
- package/src/memory/migrations/index.ts +6 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/retriever.test.ts +4 -5
- package/src/memory/schema/infrastructure.ts +31 -0
- package/src/memory/schema/oauth.ts +14 -0
- package/src/messaging/provider.ts +13 -12
- package/src/messaging/providers/gmail/adapter.ts +44 -35
- package/src/messaging/providers/slack/adapter.ts +63 -33
- package/src/messaging/providers/telegram-bot/adapter.ts +7 -9
- package/src/messaging/providers/whatsapp/adapter.ts +6 -8
- package/src/notifications/adapters/telegram.ts +78 -2
- package/src/oauth/__tests__/identity-verifier.test.ts +464 -0
- package/src/oauth/byo-connection.test.ts +22 -24
- package/src/oauth/connect-orchestrator.ts +79 -64
- package/src/oauth/connect-types.ts +7 -65
- package/src/oauth/connection-resolver.test.ts +13 -13
- package/src/oauth/connection-resolver.ts +3 -4
- package/src/oauth/identity-verifier.ts +177 -0
- package/src/oauth/manual-token-connection.ts +5 -5
- package/src/oauth/oauth-store.ts +251 -5
- package/src/oauth/platform-connection.test.ts +56 -6
- package/src/oauth/platform-connection.ts +8 -1
- package/src/oauth/seed-providers.ts +256 -34
- package/src/permissions/checker.ts +129 -3
- package/src/permissions/trust-client.ts +2 -2
- package/src/platform/client.ts +2 -2
- package/src/prompts/journal-context.ts +6 -1
- package/src/prompts/system-prompt.ts +43 -9
- package/src/prompts/templates/BOOTSTRAP.md +16 -5
- package/src/providers/anthropic/client.ts +139 -28
- package/src/runtime/auth/__tests__/middleware.test.ts +19 -0
- package/src/runtime/auth/route-policy.ts +0 -1
- package/src/runtime/btw-sidechain.ts +7 -1
- package/src/runtime/channel-approvals.ts +2 -2
- package/src/runtime/channel-readiness-service.ts +30 -7
- package/src/runtime/guardian-action-service.ts +7 -2
- package/src/runtime/http-router.ts +31 -0
- package/src/runtime/http-server.ts +26 -7
- package/src/runtime/http-types.ts +9 -0
- package/src/runtime/pending-interactions.ts +21 -3
- package/src/runtime/routes/acp-routes.ts +46 -28
- package/src/runtime/routes/app-management-routes.ts +123 -0
- package/src/runtime/routes/app-routes.ts +31 -0
- package/src/runtime/routes/approval-routes.ts +108 -3
- package/src/runtime/routes/attachment-routes.ts +45 -0
- package/src/runtime/routes/avatar-routes.ts +16 -0
- package/src/runtime/routes/brain-graph-routes.ts +18 -0
- package/src/runtime/routes/btw-routes.ts +20 -0
- package/src/runtime/routes/call-routes.ts +81 -0
- package/src/runtime/routes/channel-readiness-routes.ts +48 -7
- package/src/runtime/routes/channel-routes.ts +18 -0
- package/src/runtime/routes/channel-verification-routes.ts +49 -1
- package/src/runtime/routes/contact-routes.ts +77 -0
- package/src/runtime/routes/conversation-attention-routes.ts +37 -0
- package/src/runtime/routes/conversation-management-routes.ts +125 -0
- package/src/runtime/routes/conversation-query-routes.ts +78 -0
- package/src/runtime/routes/conversation-routes.ts +191 -39
- package/src/runtime/routes/conversation-starter-routes.ts +29 -0
- package/src/runtime/routes/debug-routes.ts +23 -0
- package/src/runtime/routes/diagnostics-routes.ts +30 -0
- package/src/runtime/routes/documents-routes.ts +42 -0
- package/src/runtime/routes/events-routes.ts +10 -0
- package/src/runtime/routes/global-search-routes.ts +35 -0
- package/src/runtime/routes/guardian-action-routes.ts +61 -3
- package/src/runtime/routes/guardian-approval-prompt.ts +77 -2
- package/src/runtime/routes/heartbeat-routes.ts +278 -0
- package/src/runtime/routes/host-bash-routes.ts +16 -1
- package/src/runtime/routes/host-cu-routes.ts +23 -1
- package/src/runtime/routes/host-file-routes.ts +18 -1
- package/src/runtime/routes/identity-routes.ts +35 -0
- package/src/runtime/routes/inbound-message-handler.ts +46 -25
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +21 -8
- package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +30 -2
- package/src/runtime/routes/inbound-stages/transcribe-audio.ts +1 -2
- package/src/runtime/routes/integrations/slack/share.ts +1 -1
- package/src/runtime/routes/integrations/twilio.ts +32 -22
- package/src/runtime/routes/invite-routes.ts +83 -0
- package/src/runtime/routes/log-export-routes.ts +14 -0
- package/src/runtime/routes/memory-item-routes.ts +99 -1
- package/src/runtime/routes/migration-rollback-routes.ts +25 -0
- package/src/runtime/routes/migration-routes.ts +40 -0
- package/src/runtime/routes/notification-routes.ts +20 -0
- package/src/runtime/routes/oauth-apps.ts +13 -4
- package/src/runtime/routes/pairing-routes.ts +15 -0
- package/src/runtime/routes/recording-routes.ts +72 -0
- package/src/runtime/routes/schedule-routes.ts +77 -5
- package/src/runtime/routes/secret-routes.ts +99 -14
- package/src/runtime/routes/settings-routes.ts +102 -19
- package/src/runtime/routes/skills-routes.ts +141 -18
- package/src/runtime/routes/subagents-routes.ts +38 -3
- package/src/runtime/routes/surface-action-routes.ts +66 -24
- package/src/runtime/routes/surface-content-routes.ts +20 -0
- package/src/runtime/routes/telemetry-routes.ts +12 -0
- package/src/runtime/routes/trace-event-routes.ts +25 -0
- package/src/runtime/routes/trust-rules-routes.ts +46 -0
- package/src/runtime/routes/tts-routes.ts +15 -4
- package/src/runtime/routes/upgrade-broadcast-routes.ts +38 -0
- package/src/runtime/routes/usage-routes.ts +59 -0
- package/src/runtime/routes/watch-routes.ts +28 -0
- package/src/runtime/routes/work-items-routes.ts +59 -0
- package/src/runtime/routes/workspace-commit-routes.ts +12 -0
- package/src/runtime/routes/workspace-routes.ts +102 -0
- package/src/schedule/integration-status.ts +2 -2
- package/src/schedule/scheduler.ts +7 -1
- package/src/security/AGENTS.md +7 -0
- package/src/security/ces-rpc-credential-backend.ts +19 -16
- package/src/security/credential-backend.ts +1 -1
- package/src/security/encrypted-store.ts +3 -3
- package/src/security/oauth-completion-page.ts +153 -0
- package/src/security/oauth2.ts +58 -17
- package/src/security/secret-ingress.ts +174 -0
- package/src/security/secret-patterns.ts +133 -0
- package/src/security/secret-scanner.ts +28 -117
- package/src/security/secure-keys.ts +207 -7
- package/src/security/token-manager.ts +3 -6
- package/src/signals/bash.ts +6 -1
- package/src/signals/confirm.ts +12 -8
- package/src/signals/user-message.ts +18 -3
- package/src/skills/catalog-cache.ts +44 -0
- package/src/skills/catalog-search.ts +18 -0
- package/src/skills/skill-memory.ts +1 -2
- package/src/tasks/task-runner.ts +7 -1
- package/src/tools/credentials/broker.ts +1 -1
- package/src/tools/credentials/metadata-store.ts +1 -1
- package/src/tools/credentials/post-connect-hooks.ts +1 -1
- package/src/tools/credentials/vault.ts +36 -48
- package/src/tools/host-terminal/host-shell.ts +16 -3
- package/src/tools/mcp/mcp-tool-factory.ts +2 -1
- package/src/tools/memory/definitions.ts +1 -1
- package/src/tools/memory/handlers.test.ts +2 -4
- package/src/tools/skills/load.ts +1 -1
- package/src/tools/skills/sandbox-runner.ts +16 -3
- package/src/tools/terminal/safe-env.ts +7 -0
- package/src/tools/terminal/shell.ts +16 -3
- package/src/tools/tool-manifest.ts +1 -1
- package/src/util/log-redact.ts +9 -34
- package/src/util/logger.ts +11 -1
- package/src/util/sentry-log-stream.ts +51 -0
- package/src/watcher/providers/github.ts +2 -2
- package/src/watcher/providers/gmail.ts +1 -1
- package/src/watcher/providers/google-calendar.ts +1 -1
- package/src/watcher/providers/linear.ts +2 -2
- package/src/workspace/migrations/011-backfill-installation-id.ts +5 -3
- package/src/workspace/migrations/020-rename-oauth-skill-dirs.ts +119 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/docs/architecture/keychain-broker.md +0 -68
- package/src/cli/commands/oauth/connections.ts +0 -734
- package/src/cli/commands/oauth/platform.ts +0 -525
- package/src/cli/commands/platform.ts +0 -176
- package/src/config/bundled-skills/slack/TOOLS.json +0 -272
- package/src/config/bundled-skills/slack/tools/shared.ts +0 -34
- package/src/config/bundled-skills/slack/tools/slack-add-reaction.ts +0 -27
- package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +0 -38
- package/src/config/bundled-skills/slack/tools/slack-channel-permissions.ts +0 -146
- package/src/config/bundled-skills/slack/tools/slack-configure-channels.ts +0 -105
- package/src/config/bundled-skills/slack/tools/slack-delete-message.ts +0 -26
- package/src/config/bundled-skills/slack/tools/slack-edit-message.ts +0 -27
- package/src/config/bundled-skills/slack/tools/slack-leave-channel.ts +0 -25
- package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +0 -372
- package/src/oauth/provider-behaviors.ts +0 -634
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* the same pattern as other gateway-forwarded control-plane endpoints.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
|
|
10
12
|
import { getWorkspaceDir } from "../../util/platform.js";
|
|
11
13
|
import { getWorkspaceGitService } from "../../workspace/git-service.js";
|
|
12
14
|
import { httpError } from "../http-errors.js";
|
|
@@ -17,6 +19,16 @@ export function workspaceCommitRouteDefinitions(): RouteDefinition[] {
|
|
|
17
19
|
{
|
|
18
20
|
endpoint: "admin/workspace-commit",
|
|
19
21
|
method: "POST",
|
|
22
|
+
summary: "Commit workspace changes",
|
|
23
|
+
description:
|
|
24
|
+
"Create a git commit in the workspace directory with all pending changes.",
|
|
25
|
+
tags: ["admin"],
|
|
26
|
+
requestBody: z.object({
|
|
27
|
+
message: z.string().describe("Commit message"),
|
|
28
|
+
}),
|
|
29
|
+
responseBody: z.object({
|
|
30
|
+
ok: z.boolean(),
|
|
31
|
+
}),
|
|
20
32
|
handler: async ({ req }) => {
|
|
21
33
|
let body: unknown;
|
|
22
34
|
try {
|
|
@@ -16,6 +16,8 @@ import {
|
|
|
16
16
|
} from "node:fs";
|
|
17
17
|
import { basename, dirname, join } from "node:path";
|
|
18
18
|
|
|
19
|
+
import { z } from "zod";
|
|
20
|
+
|
|
19
21
|
import { getWorkspaceDir } from "../../util/platform.js";
|
|
20
22
|
import { httpError } from "../http-errors.js";
|
|
21
23
|
import type { RouteContext, RouteDefinition } from "../http-router.js";
|
|
@@ -382,36 +384,136 @@ export function workspaceRouteDefinitions(): RouteDefinition[] {
|
|
|
382
384
|
{
|
|
383
385
|
endpoint: "workspace/tree",
|
|
384
386
|
method: "GET",
|
|
387
|
+
summary: "List workspace directory",
|
|
388
|
+
description: "Return directory entries for a workspace path.",
|
|
389
|
+
tags: ["workspace"],
|
|
390
|
+
queryParams: [
|
|
391
|
+
{
|
|
392
|
+
name: "path",
|
|
393
|
+
schema: { type: "string" },
|
|
394
|
+
description: "Relative path (default root)",
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
name: "showHidden",
|
|
398
|
+
schema: { type: "string" },
|
|
399
|
+
description: "Include dotfiles (true/false)",
|
|
400
|
+
},
|
|
401
|
+
],
|
|
402
|
+
responseBody: z.object({
|
|
403
|
+
path: z.string(),
|
|
404
|
+
entries: z.array(z.unknown()).describe("Directory entry objects"),
|
|
405
|
+
}),
|
|
385
406
|
handler: (ctx) => handleWorkspaceTree(ctx),
|
|
386
407
|
},
|
|
387
408
|
{
|
|
388
409
|
endpoint: "workspace/file/content",
|
|
389
410
|
method: "GET",
|
|
411
|
+
summary: "Get workspace file content",
|
|
412
|
+
description: "Return raw file bytes with HTTP range support.",
|
|
413
|
+
tags: ["workspace"],
|
|
414
|
+
queryParams: [
|
|
415
|
+
{
|
|
416
|
+
name: "path",
|
|
417
|
+
schema: { type: "string" },
|
|
418
|
+
description: "Relative file path (required)",
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
name: "showHidden",
|
|
422
|
+
schema: { type: "string" },
|
|
423
|
+
description: "Allow hidden files (true/false)",
|
|
424
|
+
},
|
|
425
|
+
],
|
|
390
426
|
handler: (ctx) => handleWorkspaceFileContent(ctx),
|
|
391
427
|
},
|
|
392
428
|
{
|
|
393
429
|
endpoint: "workspace/file",
|
|
394
430
|
method: "GET",
|
|
431
|
+
summary: "Get workspace file metadata",
|
|
432
|
+
description:
|
|
433
|
+
"Return file metadata and inline text content (if small enough).",
|
|
434
|
+
tags: ["workspace"],
|
|
435
|
+
queryParams: [
|
|
436
|
+
{
|
|
437
|
+
name: "path",
|
|
438
|
+
schema: { type: "string" },
|
|
439
|
+
description: "Relative file path (required)",
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
name: "showHidden",
|
|
443
|
+
schema: { type: "string" },
|
|
444
|
+
description: "Allow hidden files (true/false)",
|
|
445
|
+
},
|
|
446
|
+
],
|
|
447
|
+
responseBody: z.object({
|
|
448
|
+
path: z.string(),
|
|
449
|
+
name: z.string(),
|
|
450
|
+
size: z.number(),
|
|
451
|
+
mimeType: z.string(),
|
|
452
|
+
modifiedAt: z.string(),
|
|
453
|
+
content: z.string().describe("Inline text content or null"),
|
|
454
|
+
isBinary: z.boolean(),
|
|
455
|
+
}),
|
|
395
456
|
handler: (ctx) => handleWorkspaceFile(ctx),
|
|
396
457
|
},
|
|
397
458
|
{
|
|
398
459
|
endpoint: "workspace/write",
|
|
399
460
|
method: "POST",
|
|
461
|
+
summary: "Write workspace file",
|
|
462
|
+
description: "Create or overwrite a file in the workspace.",
|
|
463
|
+
tags: ["workspace"],
|
|
464
|
+
requestBody: z.object({
|
|
465
|
+
path: z.string().describe("Relative file path"),
|
|
466
|
+
content: z.string().describe("File content").optional(),
|
|
467
|
+
encoding: z
|
|
468
|
+
.string()
|
|
469
|
+
.describe("Content encoding (base64 or utf-8)")
|
|
470
|
+
.optional(),
|
|
471
|
+
}),
|
|
472
|
+
responseBody: z.object({
|
|
473
|
+
path: z.string(),
|
|
474
|
+
size: z.number(),
|
|
475
|
+
}),
|
|
400
476
|
handler: (ctx) => handleWorkspaceWrite(ctx),
|
|
401
477
|
},
|
|
402
478
|
{
|
|
403
479
|
endpoint: "workspace/mkdir",
|
|
404
480
|
method: "POST",
|
|
481
|
+
summary: "Create workspace directory",
|
|
482
|
+
description: "Create directories recursively in the workspace.",
|
|
483
|
+
tags: ["workspace"],
|
|
484
|
+
requestBody: z.object({
|
|
485
|
+
path: z.string().describe("Relative directory path"),
|
|
486
|
+
}),
|
|
487
|
+
responseBody: z.object({
|
|
488
|
+
path: z.string(),
|
|
489
|
+
}),
|
|
405
490
|
handler: (ctx) => handleWorkspaceMkdir(ctx),
|
|
406
491
|
},
|
|
407
492
|
{
|
|
408
493
|
endpoint: "workspace/rename",
|
|
409
494
|
method: "POST",
|
|
495
|
+
summary: "Rename workspace entry",
|
|
496
|
+
description: "Rename or move a file or directory in the workspace.",
|
|
497
|
+
tags: ["workspace"],
|
|
498
|
+
requestBody: z.object({
|
|
499
|
+
oldPath: z.string().describe("Current relative path"),
|
|
500
|
+
newPath: z.string().describe("New relative path"),
|
|
501
|
+
}),
|
|
502
|
+
responseBody: z.object({
|
|
503
|
+
oldPath: z.string(),
|
|
504
|
+
newPath: z.string(),
|
|
505
|
+
}),
|
|
410
506
|
handler: (ctx) => handleWorkspaceRename(ctx),
|
|
411
507
|
},
|
|
412
508
|
{
|
|
413
509
|
endpoint: "workspace/delete",
|
|
414
510
|
method: "POST",
|
|
511
|
+
summary: "Delete workspace entry",
|
|
512
|
+
description: "Delete a file or directory from the workspace.",
|
|
513
|
+
tags: ["workspace"],
|
|
514
|
+
requestBody: z.object({
|
|
515
|
+
path: z.string().describe("Relative path to delete"),
|
|
516
|
+
}),
|
|
415
517
|
handler: (ctx) => handleWorkspaceDelete(ctx),
|
|
416
518
|
},
|
|
417
519
|
];
|
|
@@ -15,12 +15,12 @@ const INTEGRATION_PROBES: IntegrationProbe[] = [
|
|
|
15
15
|
{
|
|
16
16
|
name: "Gmail",
|
|
17
17
|
category: "email",
|
|
18
|
-
isConnected: () => isProviderConnected("
|
|
18
|
+
isConnected: () => isProviderConnected("google"),
|
|
19
19
|
},
|
|
20
20
|
{
|
|
21
21
|
name: "Slack",
|
|
22
22
|
category: "messaging",
|
|
23
|
-
isConnected: () => isProviderConnected("
|
|
23
|
+
isConnected: () => isProviderConnected("slack"),
|
|
24
24
|
},
|
|
25
25
|
{
|
|
26
26
|
name: "Twilio",
|
|
@@ -188,7 +188,7 @@ async function runScheduleOnce(
|
|
|
188
188
|
);
|
|
189
189
|
const { runTask } = await import("../tasks/task-runner.js");
|
|
190
190
|
const result = await runTask(
|
|
191
|
-
{ taskId, workingDir: process.cwd(), source: "schedule" },
|
|
191
|
+
{ taskId, workingDir: process.cwd(), source: "schedule", scheduleJobId: job.id },
|
|
192
192
|
processMessage as (
|
|
193
193
|
conversationId: string,
|
|
194
194
|
message: string,
|
|
@@ -196,6 +196,12 @@ async function runScheduleOnce(
|
|
|
196
196
|
) => Promise<void>,
|
|
197
197
|
);
|
|
198
198
|
|
|
199
|
+
onScheduleConversationCreated?.({
|
|
200
|
+
conversationId: result.conversationId,
|
|
201
|
+
scheduleJobId: job.id,
|
|
202
|
+
title: result.status === "failed" ? `${job.name}: Error` : job.name,
|
|
203
|
+
});
|
|
204
|
+
|
|
199
205
|
// Track the schedule run using the task's conversation
|
|
200
206
|
const runId = createScheduleRun(job.id, result.conversationId);
|
|
201
207
|
if (result.status === "failed") {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Security — Agent Instructions
|
|
2
|
+
|
|
3
|
+
## Integration API Key Patterns
|
|
4
|
+
|
|
5
|
+
When adding a new third-party integration, check whether the service uses a recognizable API key prefix (e.g., `lin_api_`, `sk-ant-`, `ghp_`). If it does, add a corresponding entry to `PREFIX_PATTERNS` in `secret-patterns.ts`. This is the single source of truth for prefix-based secret detection — ingress blocking, tool output scanning, and log redaction all consume this list.
|
|
6
|
+
|
|
7
|
+
OAuth-only services with opaque access tokens (no fixed prefix) do not need a pattern.
|
|
@@ -30,11 +30,13 @@ export class CesRpcCredentialBackend implements CredentialBackend {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
async get(account: string): Promise<CredentialGetResult> {
|
|
33
|
+
if (!this.isAvailable()) {
|
|
34
|
+
return { value: undefined, unreachable: true };
|
|
35
|
+
}
|
|
33
36
|
try {
|
|
34
|
-
const result = await this.client.call(
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
);
|
|
37
|
+
const result = await this.client.call(CesRpcMethod.GetCredential, {
|
|
38
|
+
account,
|
|
39
|
+
});
|
|
38
40
|
return {
|
|
39
41
|
value: result.found ? result.value : undefined,
|
|
40
42
|
unreachable: false,
|
|
@@ -46,11 +48,12 @@ export class CesRpcCredentialBackend implements CredentialBackend {
|
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
async set(account: string, value: string): Promise<boolean> {
|
|
51
|
+
if (!this.isAvailable()) return false;
|
|
49
52
|
try {
|
|
50
|
-
const result = await this.client.call(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
);
|
|
53
|
+
const result = await this.client.call(CesRpcMethod.SetCredential, {
|
|
54
|
+
account,
|
|
55
|
+
value,
|
|
56
|
+
});
|
|
54
57
|
return result.ok;
|
|
55
58
|
} catch (err) {
|
|
56
59
|
log.warn({ err, account }, "CES RPC credential set failed");
|
|
@@ -59,11 +62,11 @@ export class CesRpcCredentialBackend implements CredentialBackend {
|
|
|
59
62
|
}
|
|
60
63
|
|
|
61
64
|
async delete(account: string): Promise<DeleteResult> {
|
|
65
|
+
if (!this.isAvailable()) return "error";
|
|
62
66
|
try {
|
|
63
|
-
const result = await this.client.call(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
);
|
|
67
|
+
const result = await this.client.call(CesRpcMethod.DeleteCredential, {
|
|
68
|
+
account,
|
|
69
|
+
});
|
|
67
70
|
return result.result;
|
|
68
71
|
} catch (err) {
|
|
69
72
|
log.warn({ err, account }, "CES RPC credential delete failed");
|
|
@@ -72,11 +75,11 @@ export class CesRpcCredentialBackend implements CredentialBackend {
|
|
|
72
75
|
}
|
|
73
76
|
|
|
74
77
|
async list(): Promise<CredentialListResult> {
|
|
78
|
+
if (!this.isAvailable()) {
|
|
79
|
+
return { accounts: [], unreachable: true };
|
|
80
|
+
}
|
|
75
81
|
try {
|
|
76
|
-
const result = await this.client.call(
|
|
77
|
-
CesRpcMethod.ListCredentials,
|
|
78
|
-
{},
|
|
79
|
-
);
|
|
82
|
+
const result = await this.client.call(CesRpcMethod.ListCredentials, {});
|
|
80
83
|
return { accounts: result.accounts, unreachable: false };
|
|
81
84
|
} catch (err) {
|
|
82
85
|
log.warn({ err }, "CES RPC credential list failed");
|
|
@@ -30,7 +30,7 @@ export interface CredentialListResult {
|
|
|
30
30
|
// ---------------------------------------------------------------------------
|
|
31
31
|
|
|
32
32
|
export interface CredentialBackend {
|
|
33
|
-
/** Human-readable name for logging (e.g. "
|
|
33
|
+
/** Human-readable name for logging (e.g. "encrypted-store"). */
|
|
34
34
|
readonly name: string;
|
|
35
35
|
|
|
36
36
|
/** Whether this backend is currently reachable. Sync and cheap. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Encrypted-at-rest key storage
|
|
2
|
+
* Encrypted-at-rest key storage.
|
|
3
3
|
*
|
|
4
4
|
* v2 stores use a cryptographically random 32-byte `store.key` file as the
|
|
5
5
|
* AES-256-GCM key directly (no key derivation). The key file lives alongside
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* v1 stores (legacy) derived the AES key from machine-specific entropy via
|
|
9
9
|
* PBKDF2. Existing v1 stores are automatically migrated to v2 on first access.
|
|
10
10
|
*
|
|
11
|
-
* Provides the
|
|
11
|
+
* Provides the standard get/set/delete credential storage interface.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import {
|
|
@@ -418,7 +418,7 @@ function decrypt(entry: EncryptedEntry, key: Buffer): string {
|
|
|
418
418
|
}
|
|
419
419
|
|
|
420
420
|
// ---------------------------------------------------------------------------
|
|
421
|
-
// Public API
|
|
421
|
+
// Public API
|
|
422
422
|
// ---------------------------------------------------------------------------
|
|
423
423
|
|
|
424
424
|
/**
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared HTML rendering for OAuth completion pages shown in the browser
|
|
3
|
+
* after a loopback/redirect OAuth flow completes.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export function escapeHtml(s: string): string {
|
|
7
|
+
return s
|
|
8
|
+
.replace(/&/g, "&")
|
|
9
|
+
.replace(/</g, "<")
|
|
10
|
+
.replace(/>/g, ">")
|
|
11
|
+
.replace(/"/g, """)
|
|
12
|
+
.replace(/'/g, "'");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function formatProviderName(provider: string): string {
|
|
16
|
+
// Capitalize first letter of each word, handle common acronyms
|
|
17
|
+
return provider
|
|
18
|
+
.split(/[-_]/)
|
|
19
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
20
|
+
.join(" ");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function renderOAuthCompletionPage(
|
|
24
|
+
message: string,
|
|
25
|
+
success: boolean,
|
|
26
|
+
provider?: string,
|
|
27
|
+
): string {
|
|
28
|
+
const displayProvider = provider ? formatProviderName(provider) : "";
|
|
29
|
+
const title = success
|
|
30
|
+
? displayProvider
|
|
31
|
+
? `Connected to ${escapeHtml(displayProvider)}`
|
|
32
|
+
: "Authorization Successful"
|
|
33
|
+
: "Authorization Failed";
|
|
34
|
+
const subtitle = success
|
|
35
|
+
? "You can close this tab and return to your assistant."
|
|
36
|
+
: escapeHtml(message);
|
|
37
|
+
|
|
38
|
+
const checkmarkSvg = `<svg class="icon" viewBox="0 0 56 56" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
39
|
+
<circle cx="28" cy="28" r="28" fill="var(--positive-bg)"/>
|
|
40
|
+
<path class="check" d="M17 28.5L24.5 36L39 21" stroke="var(--positive-fg)" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
|
41
|
+
</svg>`;
|
|
42
|
+
|
|
43
|
+
const errorSvg = `<svg class="icon" viewBox="0 0 56 56" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
44
|
+
<circle cx="28" cy="28" r="28" fill="var(--negative-bg)"/>
|
|
45
|
+
<path class="cross cross-1" d="M20 20L36 36" stroke="var(--negative-fg)" stroke-width="3.5" stroke-linecap="round" fill="none"/>
|
|
46
|
+
<path class="cross cross-2" d="M36 20L20 36" stroke="var(--negative-fg)" stroke-width="3.5" stroke-linecap="round" fill="none"/>
|
|
47
|
+
</svg>`;
|
|
48
|
+
|
|
49
|
+
return `<!DOCTYPE html>
|
|
50
|
+
<html lang="en">
|
|
51
|
+
<head>
|
|
52
|
+
<meta charset="utf-8">
|
|
53
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
54
|
+
<title>${escapeHtml(title)}</title>
|
|
55
|
+
<style>
|
|
56
|
+
:root {
|
|
57
|
+
--surface: #F5F3EB;
|
|
58
|
+
--surface-card: #FFFFFF;
|
|
59
|
+
--card-border: #E8E6DA;
|
|
60
|
+
--text-primary: #2A2A28;
|
|
61
|
+
--text-secondary: #4A4A46;
|
|
62
|
+
--text-tertiary: #A1A096;
|
|
63
|
+
--positive-bg: #D4DFD0;
|
|
64
|
+
--positive-fg: #516748;
|
|
65
|
+
--negative-bg: #F7DAC9;
|
|
66
|
+
--negative-fg: #DA491A;
|
|
67
|
+
--shadow: 0 1px 3px rgba(0,0,0,0.04), 0 4px 12px rgba(0,0,0,0.06);
|
|
68
|
+
--font: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", sans-serif;
|
|
69
|
+
}
|
|
70
|
+
@media (prefers-color-scheme: dark) {
|
|
71
|
+
:root {
|
|
72
|
+
--surface: #1A1A18;
|
|
73
|
+
--surface-card: #2A2A28;
|
|
74
|
+
--card-border: #3A3A37;
|
|
75
|
+
--text-primary: #F5F3EB;
|
|
76
|
+
--text-secondary: #BDB9A9;
|
|
77
|
+
--text-tertiary: #6B6B65;
|
|
78
|
+
--positive-bg: #1A2316;
|
|
79
|
+
--positive-fg: #7A8B6F;
|
|
80
|
+
--negative-bg: #4E281D;
|
|
81
|
+
--negative-fg: #E86B40;
|
|
82
|
+
--shadow: 0 1px 3px rgba(0,0,0,0.2), 0 4px 12px rgba(0,0,0,0.3);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
86
|
+
body {
|
|
87
|
+
font-family: var(--font);
|
|
88
|
+
background: var(--surface);
|
|
89
|
+
color: var(--text-primary);
|
|
90
|
+
display: flex;
|
|
91
|
+
align-items: center;
|
|
92
|
+
justify-content: center;
|
|
93
|
+
min-height: 100vh;
|
|
94
|
+
-webkit-font-smoothing: antialiased;
|
|
95
|
+
}
|
|
96
|
+
.card {
|
|
97
|
+
text-align: center;
|
|
98
|
+
padding: 48px 40px 40px;
|
|
99
|
+
background: var(--surface-card);
|
|
100
|
+
border: 1px solid var(--card-border);
|
|
101
|
+
border-radius: 16px;
|
|
102
|
+
box-shadow: var(--shadow);
|
|
103
|
+
max-width: 380px;
|
|
104
|
+
width: 100%;
|
|
105
|
+
opacity: 0;
|
|
106
|
+
transform: translateY(8px) scale(0.98);
|
|
107
|
+
animation: cardIn 0.5s cubic-bezier(0.16, 1, 0.3, 1) 0.1s forwards;
|
|
108
|
+
}
|
|
109
|
+
@keyframes cardIn {
|
|
110
|
+
to { opacity: 1; transform: translateY(0) scale(1); }
|
|
111
|
+
}
|
|
112
|
+
.icon {
|
|
113
|
+
width: 56px;
|
|
114
|
+
height: 56px;
|
|
115
|
+
margin-bottom: 20px;
|
|
116
|
+
}
|
|
117
|
+
.check {
|
|
118
|
+
stroke-dasharray: 32;
|
|
119
|
+
stroke-dashoffset: 32;
|
|
120
|
+
animation: draw 0.4s ease-out 0.45s forwards;
|
|
121
|
+
}
|
|
122
|
+
.cross {
|
|
123
|
+
stroke-dasharray: 22;
|
|
124
|
+
stroke-dashoffset: 22;
|
|
125
|
+
}
|
|
126
|
+
.cross-1 { animation: draw 0.3s ease-out 0.45s forwards; }
|
|
127
|
+
.cross-2 { animation: draw 0.3s ease-out 0.55s forwards; }
|
|
128
|
+
@keyframes draw {
|
|
129
|
+
to { stroke-dashoffset: 0; }
|
|
130
|
+
}
|
|
131
|
+
h1 {
|
|
132
|
+
font-size: 18px;
|
|
133
|
+
font-weight: 600;
|
|
134
|
+
letter-spacing: -0.2px;
|
|
135
|
+
color: var(--text-primary);
|
|
136
|
+
margin-bottom: 6px;
|
|
137
|
+
}
|
|
138
|
+
p {
|
|
139
|
+
font-size: 13px;
|
|
140
|
+
line-height: 1.5;
|
|
141
|
+
color: var(--text-secondary);
|
|
142
|
+
}
|
|
143
|
+
</style>
|
|
144
|
+
</head>
|
|
145
|
+
<body>
|
|
146
|
+
<div class="card">
|
|
147
|
+
${success ? checkmarkSvg : errorSvg}
|
|
148
|
+
<h1>${escapeHtml(title)}</h1>
|
|
149
|
+
<p>${subtitle}</p>
|
|
150
|
+
</div>
|
|
151
|
+
</body>
|
|
152
|
+
</html>`;
|
|
153
|
+
}
|
package/src/security/oauth2.ts
CHANGED
|
@@ -20,6 +20,7 @@ import { createHash, randomBytes } from "node:crypto";
|
|
|
20
20
|
import { createServer, type Server } from "node:http";
|
|
21
21
|
|
|
22
22
|
import { getLogger } from "../util/logger.js";
|
|
23
|
+
import { renderOAuthCompletionPage as renderLoopbackPage } from "./oauth-completion-page.js";
|
|
23
24
|
|
|
24
25
|
const log = getLogger("oauth2");
|
|
25
26
|
|
|
@@ -289,6 +290,15 @@ function startLoopbackServerAndWaitForCode(
|
|
|
289
290
|
let boundRedirectUri = "";
|
|
290
291
|
|
|
291
292
|
const server: Server = createServer((req, res) => {
|
|
293
|
+
log.info(
|
|
294
|
+
{
|
|
295
|
+
method: req.method,
|
|
296
|
+
path: new URL(req.url ?? "/", "http://127.0.0.1").pathname,
|
|
297
|
+
settled,
|
|
298
|
+
},
|
|
299
|
+
"oauth2 loopback: received request",
|
|
300
|
+
);
|
|
301
|
+
|
|
292
302
|
if (settled) {
|
|
293
303
|
res.writeHead(400, { "Content-Type": "text/html" });
|
|
294
304
|
res.end(renderLoopbackPage("Authorization already completed", false));
|
|
@@ -298,6 +308,10 @@ function startLoopbackServerAndWaitForCode(
|
|
|
298
308
|
const url = new URL(req.url ?? "/", `http://127.0.0.1`);
|
|
299
309
|
|
|
300
310
|
if (url.pathname !== LOOPBACK_CALLBACK_PATH) {
|
|
311
|
+
log.info(
|
|
312
|
+
{ pathname: url.pathname },
|
|
313
|
+
"oauth2 loopback: non-callback path, returning 404",
|
|
314
|
+
);
|
|
301
315
|
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
302
316
|
res.end("Not found");
|
|
303
317
|
return;
|
|
@@ -308,6 +322,7 @@ function startLoopbackServerAndWaitForCode(
|
|
|
308
322
|
const error = url.searchParams.get("error");
|
|
309
323
|
|
|
310
324
|
if (callbackState !== state) {
|
|
325
|
+
log.warn("oauth2 loopback: state mismatch in callback");
|
|
311
326
|
res.writeHead(400, { "Content-Type": "text/html" });
|
|
312
327
|
res.end(renderLoopbackPage("Invalid state parameter", false));
|
|
313
328
|
return;
|
|
@@ -317,6 +332,10 @@ function startLoopbackServerAndWaitForCode(
|
|
|
317
332
|
|
|
318
333
|
if (error) {
|
|
319
334
|
const errorDesc = url.searchParams.get("error_description") ?? error;
|
|
335
|
+
log.error(
|
|
336
|
+
{ error, errorDesc },
|
|
337
|
+
"oauth2 loopback: authorization denied by user/provider",
|
|
338
|
+
);
|
|
320
339
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
321
340
|
res.end(
|
|
322
341
|
renderLoopbackPage(`Authorization failed: ${errorDesc}`, false),
|
|
@@ -327,6 +346,7 @@ function startLoopbackServerAndWaitForCode(
|
|
|
327
346
|
}
|
|
328
347
|
|
|
329
348
|
if (!code) {
|
|
349
|
+
log.error("oauth2 loopback: callback missing authorization code");
|
|
330
350
|
res.writeHead(400, { "Content-Type": "text/html" });
|
|
331
351
|
res.end(renderLoopbackPage("Missing authorization code", false));
|
|
332
352
|
cleanup();
|
|
@@ -334,10 +354,13 @@ function startLoopbackServerAndWaitForCode(
|
|
|
334
354
|
return;
|
|
335
355
|
}
|
|
336
356
|
|
|
357
|
+
log.info(
|
|
358
|
+
"oauth2 loopback: authorization code received, exchanging for tokens",
|
|
359
|
+
);
|
|
337
360
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
338
361
|
res.end(
|
|
339
362
|
renderLoopbackPage(
|
|
340
|
-
"
|
|
363
|
+
"You can close this tab and return to your assistant.",
|
|
341
364
|
true,
|
|
342
365
|
),
|
|
343
366
|
);
|
|
@@ -347,6 +370,10 @@ function startLoopbackServerAndWaitForCode(
|
|
|
347
370
|
|
|
348
371
|
const timeout = setTimeout(() => {
|
|
349
372
|
if (!settled) {
|
|
373
|
+
log.warn(
|
|
374
|
+
{ timeoutMs: LOOPBACK_TIMEOUT_MS, state },
|
|
375
|
+
"oauth2 loopback: callback timed out — no authorization code received",
|
|
376
|
+
);
|
|
350
377
|
settled = true;
|
|
351
378
|
cleanup();
|
|
352
379
|
reject(new Error("OAuth2 loopback callback timed out"));
|
|
@@ -359,10 +386,20 @@ function startLoopbackServerAndWaitForCode(
|
|
|
359
386
|
server.close();
|
|
360
387
|
}
|
|
361
388
|
|
|
389
|
+
log.info(
|
|
390
|
+
{ requestedPort: loopbackPort ?? "random" },
|
|
391
|
+
"oauth2 loopback: binding server",
|
|
392
|
+
);
|
|
393
|
+
|
|
362
394
|
server.listen(loopbackPort ?? 0, "localhost", () => {
|
|
363
395
|
const addr = server.address() as { port: number };
|
|
364
396
|
boundRedirectUri = `http://localhost:${addr.port}${LOOPBACK_CALLBACK_PATH}`;
|
|
365
397
|
|
|
398
|
+
log.info(
|
|
399
|
+
{ port: addr.port, redirectUri: boundRedirectUri },
|
|
400
|
+
"oauth2 loopback: server listening",
|
|
401
|
+
);
|
|
402
|
+
|
|
366
403
|
const authParams = new URLSearchParams({
|
|
367
404
|
...config.extraParams,
|
|
368
405
|
client_id: config.clientId,
|
|
@@ -375,10 +412,19 @@ function startLoopbackServerAndWaitForCode(
|
|
|
375
412
|
});
|
|
376
413
|
|
|
377
414
|
const authUrl = `${config.authUrl}?${authParams}`;
|
|
415
|
+
log.info(
|
|
416
|
+
{ authUrlLength: authUrl.length, state },
|
|
417
|
+
"oauth2 loopback: built auth URL, calling openUrl callback",
|
|
418
|
+
);
|
|
378
419
|
callbacks.openUrl(authUrl);
|
|
420
|
+
log.info("oauth2 loopback: openUrl callback returned");
|
|
379
421
|
});
|
|
380
422
|
|
|
381
423
|
server.on("error", (err) => {
|
|
424
|
+
log.error(
|
|
425
|
+
{ err: err.message, loopbackPort },
|
|
426
|
+
"oauth2 loopback: server error",
|
|
427
|
+
);
|
|
382
428
|
if (!settled) {
|
|
383
429
|
settled = true;
|
|
384
430
|
cleanup();
|
|
@@ -388,21 +434,6 @@ function startLoopbackServerAndWaitForCode(
|
|
|
388
434
|
});
|
|
389
435
|
}
|
|
390
436
|
|
|
391
|
-
function escapeHtml(s: string): string {
|
|
392
|
-
return s
|
|
393
|
-
.replace(/&/g, "&")
|
|
394
|
-
.replace(/</g, "<")
|
|
395
|
-
.replace(/>/g, ">")
|
|
396
|
-
.replace(/"/g, """)
|
|
397
|
-
.replace(/'/g, "'");
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
function renderLoopbackPage(message: string, success: boolean): string {
|
|
401
|
-
const title = success ? "Authorization Successful" : "Authorization Failed";
|
|
402
|
-
const color = success ? "#4CAF50" : "#f44336";
|
|
403
|
-
return `<!DOCTYPE html><html><head><title>${escapeHtml(title)}</title><style>body{font-family:system-ui,sans-serif;display:flex;justify-content:center;align-items:center;min-height:100vh;margin:0;background:#f5f5f5}div{text-align:center;padding:2rem;background:white;border-radius:8px;box-shadow:0 2px 4px rgba(0,0,0,0.1)}h1{color:${color}}</style></head><body><div><h1>${escapeHtml(title)}</h1><p>${escapeHtml(message)}</p></div></body></html>`;
|
|
404
|
-
}
|
|
405
|
-
|
|
406
437
|
// ---------------------------------------------------------------------------
|
|
407
438
|
// Public API
|
|
408
439
|
// ---------------------------------------------------------------------------
|
|
@@ -595,7 +626,7 @@ function startLoopbackServerForPreparedFlow(
|
|
|
595
626
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
596
627
|
res.end(
|
|
597
628
|
renderLoopbackPage(
|
|
598
|
-
"
|
|
629
|
+
"You can close this tab and return to your assistant.",
|
|
599
630
|
true,
|
|
600
631
|
),
|
|
601
632
|
);
|
|
@@ -687,6 +718,16 @@ export async function startOAuth2Flow(
|
|
|
687
718
|
const transport =
|
|
688
719
|
options?.callbackTransport ?? (hasPublicUrl ? "gateway" : "loopback");
|
|
689
720
|
|
|
721
|
+
log.info(
|
|
722
|
+
{
|
|
723
|
+
transport,
|
|
724
|
+
hasPublicUrl,
|
|
725
|
+
explicitTransport: options?.callbackTransport,
|
|
726
|
+
loopbackPort: options?.loopbackPort,
|
|
727
|
+
},
|
|
728
|
+
"startOAuth2Flow: resolved transport",
|
|
729
|
+
);
|
|
730
|
+
|
|
690
731
|
if (transport === "gateway") {
|
|
691
732
|
if (!hasPublicUrl) {
|
|
692
733
|
throw new Error(
|