@vellumai/assistant 0.8.2 → 0.8.4
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 +11 -12
- package/docker-entrypoint.sh +13 -2
- package/docker-init-apt-root.sh +79 -6
- package/node_modules/@vellumai/gateway-client/src/types.ts +2 -0
- package/openapi.yaml +945 -36
- package/package.json +1 -1
- package/src/__tests__/agent-loop-exit-reason.test.ts +271 -0
- package/src/__tests__/agent-loop-override-profile.test.ts +1 -1
- package/src/__tests__/agent-loop-provider-error-recording.test.ts +195 -0
- package/src/__tests__/agent-loop.test.ts +88 -3
- package/src/__tests__/anthropic-provider.test.ts +272 -0
- package/src/__tests__/approval-cascade.test.ts +1 -1
- package/src/__tests__/background-workers-disk-pressure.test.ts +2 -1
- package/src/__tests__/channel-delivery-store.test.ts +193 -0
- package/src/__tests__/channel-reply-delivery.test.ts +284 -5
- package/src/__tests__/channel-retry-sweep.test.ts +274 -1
- package/src/__tests__/compaction-events.test.ts +1 -1
- package/src/__tests__/compactor-preserved-tail-count.test.ts +110 -0
- package/src/__tests__/compactor-tail-resolution.test.ts +107 -1
- package/src/__tests__/config-get-vision-flag.test.ts +136 -0
- package/src/__tests__/config-loader-backfill.test.ts +115 -18
- package/src/__tests__/config-watcher.test.ts +1 -1
- package/src/__tests__/context-token-estimator.test.ts +112 -57
- package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +54 -3
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +31 -6
- package/src/__tests__/conversation-agent-loop.test.ts +77 -3
- package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -1
- package/src/__tests__/conversation-clean-command.test.ts +137 -0
- package/src/__tests__/conversation-confirmation-signals.test.ts +1 -1
- package/src/__tests__/conversation-fork-crud.test.ts +161 -0
- package/src/__tests__/conversation-lifecycle.test.ts +1 -1
- package/src/__tests__/conversation-load-cleaned-at.test.ts +279 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +1 -1
- package/src/__tests__/conversation-media-retry.test.ts +19 -8
- package/src/__tests__/conversation-pairing.test.ts +2 -2
- package/src/__tests__/conversation-process-callsite.test.ts +1 -1
- package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -1
- package/src/__tests__/conversation-queue.test.ts +1 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +290 -85
- package/src/__tests__/conversation-seed-composer.test.ts +66 -4
- package/src/__tests__/conversation-slash-commands.test.ts +36 -8
- package/src/__tests__/conversation-slash-queue.test.ts +1 -1
- package/src/__tests__/conversation-slash-unknown.test.ts +1 -1
- package/src/__tests__/conversation-speed-override.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +220 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -1
- package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
- package/src/__tests__/credential-security-invariants.test.ts +6 -0
- package/src/__tests__/cu-unified-flow.test.ts +10 -1
- package/src/__tests__/date-context.test.ts +45 -0
- package/src/__tests__/dm-backfill.test.ts +64 -0
- package/src/__tests__/dm-persistence.test.ts +33 -0
- package/src/__tests__/document-find-replace.test.ts +501 -0
- package/src/__tests__/external-plugin-loader.test.ts +91 -19
- package/src/__tests__/first-greeting.test.ts +23 -2
- package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +0 -1
- package/src/__tests__/guardian-dispatch.test.ts +1 -0
- package/src/__tests__/headless-browser-navigate.test.ts +172 -0
- package/src/__tests__/heartbeat-service.test.ts +24 -164
- package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
- package/src/__tests__/host-app-control-proxy.test.ts +241 -0
- package/src/__tests__/host-bash-proxy.test.ts +6 -0
- package/src/__tests__/host-browser-proxy.test.ts +10 -0
- package/src/__tests__/host-cu-proxy.test.ts +8 -1
- package/src/__tests__/host-file-proxy.test.ts +8 -1
- package/src/__tests__/host-proxy-preactivation.test.ts +200 -13
- package/src/__tests__/host-transfer-proxy.test.ts +8 -1
- package/src/__tests__/identity-routes.test.ts +57 -0
- package/src/__tests__/inbound-slack-persistence.test.ts +3 -0
- package/src/__tests__/injector-background-turn.test.ts +153 -0
- package/src/__tests__/injector-chain.test.ts +7 -0
- package/src/__tests__/injector-document-comments.test.ts +378 -0
- package/src/__tests__/injector-pkb-v2-silenced.test.ts +4 -25
- package/src/__tests__/lifecycle-memory-v2-seed.test.ts +9 -2
- package/src/__tests__/list-messages-attachments.test.ts +21 -17
- package/src/__tests__/list-messages-hidden-metadata.test.ts +217 -0
- package/src/__tests__/list-messages-page-latest.test.ts +130 -14
- package/src/__tests__/list-messages-tool-merge.test.ts +17 -16
- package/src/__tests__/llm-callsite-catalog.test.ts +25 -0
- package/src/__tests__/llm-catalog-parity.test.ts +3 -0
- package/src/__tests__/llm-context-normalization.test.ts +0 -2
- package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +116 -0
- package/src/__tests__/llm-request-log-error-payload.test.ts +138 -0
- package/src/__tests__/llm-request-log-source-clickhouse.test.ts +2 -0
- package/src/__tests__/llm-resolver.test.ts +340 -3
- package/src/__tests__/log-export-routes.test.ts +99 -2
- package/src/__tests__/managed-profile-guard.test.ts +10 -0
- package/src/__tests__/message-queue-steer.test.ts +114 -0
- package/src/__tests__/notification-decision-fallback.test.ts +0 -91
- package/src/__tests__/notification-decision-strategy.test.ts +14 -31
- package/src/__tests__/notification-deep-link.test.ts +15 -0
- package/src/__tests__/notification-guardian-path.test.ts +1 -2
- package/src/__tests__/notification-platform-adapter.test.ts +5 -4
- package/src/__tests__/notification-telegram-adapter.test.ts +1 -0
- package/src/__tests__/notification-vellum-adapter.test.ts +113 -0
- package/src/__tests__/openai-provider.test.ts +323 -3
- package/src/__tests__/openai-responses-cutover-guard.test.ts +3 -3
- package/src/__tests__/openai-responses-provider.test.ts +4 -4
- package/src/__tests__/openrouter-provider-only.test.ts +51 -3
- package/src/__tests__/openrouter-token-estimation.test.ts +34 -25
- package/src/__tests__/outbound-slack-persistence.test.ts +187 -20
- package/src/__tests__/pending-interactions-resolved-event.test.ts +190 -0
- package/src/__tests__/platform-proxy-context.test.ts +6 -1
- package/src/__tests__/platform.test.ts +0 -3
- package/src/__tests__/plugin-source-watcher.test.ts +302 -0
- package/src/__tests__/plugin-tool-contribution.test.ts +3 -3
- package/src/__tests__/plugin-types.test.ts +2 -2
- package/src/__tests__/process-message-background-slack.test.ts +1 -51
- package/src/__tests__/process-message-display-content.test.ts +21 -16
- package/src/__tests__/provider-catalog-visibility.test.ts +16 -0
- package/src/__tests__/provider-platform-proxy-integration.test.ts +27 -25
- package/src/__tests__/secret-routes-platform-proxy.test.ts +1 -1
- package/src/__tests__/server-history-render.test.ts +83 -4
- package/src/__tests__/steer-tool-repair.test.ts +249 -0
- package/src/__tests__/system-prompt.test.ts +57 -101
- package/src/__tests__/terminal-tools.test.ts +11 -1
- package/src/__tests__/thinking-block-replay.test.ts +113 -0
- package/src/__tests__/thread-backfill.test.ts +370 -22
- package/src/__tests__/tool-executor.test.ts +90 -1
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +167 -0
- package/src/__tests__/twilio-routes.test.ts +1 -1
- package/src/__tests__/web-fetch.test.ts +2 -2
- package/src/__tests__/workspace-git-service.test.ts +88 -5
- package/src/__tests__/workspace-migration-087-memory-router-balanced-profile.test.ts +228 -0
- package/src/__tests__/workspace-migration-088-deprecate-background-conversation-override.test.ts +158 -0
- package/src/a2a/__tests__/agent-card.test.ts +98 -0
- package/src/a2a/__tests__/e2e-a2a-channel.test.ts +597 -0
- package/src/a2a/__tests__/protocol-helpers.test.ts +113 -0
- package/src/a2a/__tests__/task-store.test.ts +246 -0
- package/src/a2a/agent-card.ts +58 -0
- package/src/a2a/feature-gate.ts +8 -0
- package/src/a2a/protocol-constants.ts +21 -0
- package/src/a2a/protocol-errors.ts +50 -0
- package/src/a2a/protocol-types.ts +162 -0
- package/src/a2a/task-store.ts +168 -0
- package/src/agent/attachments.ts +1 -0
- package/src/agent/loop.ts +208 -22
- package/src/background-wake/next-wake.test.ts +289 -0
- package/src/background-wake/next-wake.ts +172 -0
- package/src/browser/operations.ts +15 -0
- package/src/channels/config.ts +9 -0
- package/src/channels/types.ts +14 -0
- package/src/cli/commands/__tests__/conversations-slack.test.ts +572 -0
- package/src/cli/commands/__tests__/memory-v2.test.ts +9 -12
- package/src/cli/{__tests__ → commands/__tests__}/notifications.test.ts +201 -28
- package/src/cli/commands/__tests__/schedules.test.ts +469 -0
- package/src/cli/commands/conversations.ts +128 -1
- package/src/cli/commands/inference-providers.ts +147 -1
- package/src/cli/commands/memory-v2.ts +308 -0
- package/src/cli/commands/notifications.ts +89 -37
- package/src/cli/commands/plugins.ts +67 -0
- package/src/cli/commands/schedules.ts +297 -5
- package/src/cli/lib/__tests__/search-plugins.test.ts +261 -0
- package/src/cli/lib/install-from-github.ts +8 -9
- package/src/cli/lib/search-plugins.ts +163 -0
- package/src/cli/program.ts +14 -0
- package/src/cli/utils/conversation-id.ts +17 -5
- package/src/config/assistant-feature-flags.ts +24 -54
- package/src/config/bundled-skills/app-builder/SKILL.md +117 -1
- package/src/config/bundled-skills/document-editor/SKILL.md +115 -0
- package/src/config/bundled-skills/document-editor/TOOLS.json +240 -0
- package/src/config/bundled-skills/document-editor/tools/comment-list.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/comment-reply.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/comment-resolve.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/document-find.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/document-replace-text.ts +12 -0
- package/src/config/bundled-skills/media-processing/SKILL.md +8 -0
- package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
- package/src/config/bundled-skills/schedule/SKILL.md +8 -0
- package/src/config/bundled-tool-registry.ts +22 -12
- package/src/config/call-site-defaults.ts +124 -0
- package/src/config/feature-flag-registry.json +111 -23
- package/src/config/llm-resolver.ts +66 -1
- package/src/config/schema.ts +2 -0
- package/src/config/schemas/__tests__/memory-v2.test.ts +7 -3
- package/src/config/schemas/call-site-catalog.ts +21 -0
- package/src/config/schemas/channels.ts +9 -0
- package/src/config/schemas/conversations.ts +10 -0
- package/src/config/schemas/heartbeat.ts +14 -0
- package/src/config/schemas/llm.ts +4 -3
- package/src/config/schemas/memory-retrospective.ts +1 -1
- package/src/config/schemas/memory-v2.ts +51 -4
- package/src/config/schemas/memory.ts +3 -1
- package/src/config/seed-inference-profiles.ts +99 -29
- package/src/context/compactor.ts +80 -13
- package/src/context/token-estimator.ts +72 -31
- package/src/context/window-manager.ts +25 -0
- package/src/credential-health/credential-health-service.ts +34 -19
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -22
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -6
- package/src/daemon/__tests__/native-web-search-metadata.test.ts +357 -0
- package/src/daemon/__tests__/web-search-status-text.test.ts +287 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +231 -23
- package/src/daemon/conversation-agent-loop.ts +252 -56
- package/src/daemon/conversation-lifecycle.ts +142 -116
- package/src/daemon/conversation-messaging.ts +3 -0
- package/src/daemon/conversation-process.ts +273 -0
- package/src/daemon/conversation-queue-manager.ts +14 -0
- package/src/daemon/conversation-runtime-assembly.ts +144 -75
- package/src/daemon/conversation-slash.ts +37 -5
- package/src/daemon/conversation-surfaces.ts +45 -2
- package/src/daemon/conversation-tool-setup.ts +7 -0
- package/src/daemon/conversation.ts +42 -12
- package/src/daemon/date-context.ts +40 -0
- package/src/daemon/first-greeting.ts +10 -0
- package/src/daemon/guardian-action-generators.ts +1 -125
- package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +498 -0
- package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +248 -0
- package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +154 -0
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +133 -0
- package/src/daemon/handlers/__tests__/config-a2a.test.ts +95 -0
- package/src/daemon/handlers/config-a2a.ts +449 -0
- package/src/daemon/handlers/config-model.test.ts +1 -0
- package/src/daemon/handlers/conversations.ts +80 -0
- package/src/daemon/handlers/shared.ts +92 -29
- package/src/daemon/host-app-control-proxy.ts +69 -18
- package/src/daemon/host-bash-proxy.ts +1 -1
- package/src/daemon/host-cu-proxy.ts +1 -1
- package/src/daemon/host-file-proxy.ts +1 -1
- package/src/daemon/host-proxy-preactivation.ts +85 -18
- package/src/daemon/host-transfer-proxy.ts +1 -1
- package/src/daemon/lifecycle.ts +67 -65
- package/src/daemon/memory-v2-startup.ts +49 -13
- package/src/daemon/message-protocol.ts +4 -0
- package/src/daemon/message-types/conversations.ts +8 -0
- package/src/daemon/message-types/document-comments.ts +50 -0
- package/src/daemon/message-types/messages.ts +68 -1
- package/src/daemon/message-types/notifications.ts +21 -0
- package/src/daemon/message-types/surfaces.ts +3 -1
- package/src/daemon/message-types/web-activity.ts +57 -0
- package/src/daemon/pkb-reminder-builder.test.ts +10 -53
- package/src/daemon/pkb-reminder-builder.ts +4 -19
- package/src/daemon/plugin-source-watcher.ts +135 -3
- package/src/daemon/process-message.ts +72 -12
- package/src/daemon/query-complexity-router.ts +75 -0
- package/src/daemon/skill-memory-refresh.ts +5 -1
- package/src/daemon/trust-context.ts +6 -0
- package/src/daemon/wake-target-adapter.ts +2 -0
- package/src/documents/document-comments-store.test.ts +338 -0
- package/src/documents/document-comments-store.ts +237 -0
- package/src/documents/document-store.ts +202 -0
- package/src/export/__tests__/transcript-formatter.test.ts +121 -0
- package/src/export/transcript-formatter.ts +54 -20
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +44 -1
- package/src/heartbeat/heartbeat-service.ts +35 -191
- package/src/home/__tests__/feed-types.test.ts +40 -0
- package/src/home/__tests__/suggested-prompts.test.ts +33 -2
- package/src/home/feed-types.ts +20 -3
- package/src/home/home-content-refresh.ts +52 -0
- package/src/home/home-greeting-cache.ts +69 -0
- package/src/home/home-greeting.ts +94 -0
- package/src/home/suggested-prompts.ts +177 -9
- package/src/ipc/cli-client.ts +147 -45
- package/src/memory/__tests__/conversation-queries.test.ts +220 -0
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +135 -2
- package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +2 -50
- package/src/memory/__tests__/memory-retrospective-job.test.ts +407 -10
- package/src/memory/conversation-crud.ts +133 -43
- package/src/memory/conversation-queries.ts +87 -1
- package/src/memory/conversation-title-service.ts +26 -4
- package/src/memory/db-init.ts +22 -0
- package/src/memory/delivery-crud.ts +41 -0
- package/src/memory/delivery-status.ts +141 -15
- package/src/memory/external-conversation-store.ts +32 -1
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +84 -3
- package/src/memory/graph/conversation-graph-memory.ts +18 -6
- package/src/memory/graph/tools.ts +6 -37
- package/src/memory/invite-store.ts +53 -0
- package/src/memory/jobs-worker.ts +21 -1
- package/src/memory/llm-request-log-source-clickhouse.ts +7 -2
- package/src/memory/llm-request-log-store.ts +92 -1
- package/src/memory/memory-retrospective-constants.ts +28 -0
- package/src/memory/memory-retrospective-enqueue.ts +4 -22
- package/src/memory/memory-retrospective-job.ts +438 -21
- package/src/memory/memory-retrospective-startup-cleanup.ts +3 -3
- package/src/memory/memory-v2-activation-log-store.ts +26 -8
- package/src/memory/migrations/100-core-tables.ts +1 -0
- package/src/memory/migrations/109-external-conversation-bindings.ts +1 -0
- package/src/memory/migrations/250-provider-connection-base-url-and-models.ts +28 -0
- package/src/memory/migrations/251-a2a-tasks.ts +49 -0
- package/src/memory/migrations/252-llm-request-log-agent-loop-exit-reason.ts +32 -0
- package/src/memory/migrations/253-conversation-last-notified-profile.ts +15 -0
- package/src/memory/migrations/253-document-comments.ts +47 -0
- package/src/memory/migrations/254-external-conversation-binding-chat-name.ts +43 -0
- package/src/memory/migrations/255-channel-inbound-delivery-attempts.ts +24 -0
- package/src/memory/migrations/256-memory-v2-injection-events.ts +113 -0
- package/src/memory/migrations/257-strip-base-url-non-openai-compatible.ts +22 -0
- package/src/memory/migrations/258-onboarding-events-prior-assistants.ts +13 -0
- package/src/memory/migrations/259-conversation-cleaned-at.ts +33 -0
- package/src/memory/migrations/index.ts +20 -0
- package/src/memory/migrations/registry.ts +33 -0
- package/src/memory/onboarding-events-store.ts +7 -0
- package/src/memory/schema/a2a.ts +15 -0
- package/src/memory/schema/calls.ts +1 -0
- package/src/memory/schema/conversations.ts +3 -0
- package/src/memory/schema/index.ts +1 -0
- package/src/memory/schema/inference.ts +2 -0
- package/src/memory/schema/infrastructure.ts +2 -0
- package/src/memory/v2/__tests__/activation-store.test.ts +25 -23
- package/src/memory/v2/__tests__/cli-command-store.test.ts +404 -0
- package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +25 -4
- package/src/memory/v2/__tests__/injection-events.test.ts +318 -0
- package/src/memory/v2/__tests__/injection.test.ts +221 -17
- package/src/memory/v2/__tests__/page-index.test.ts +365 -1
- package/src/memory/v2/__tests__/router.test.ts +489 -1
- package/src/memory/v2/__tests__/static-context.test.ts +12 -1
- package/src/memory/v2/activation-store.ts +14 -16
- package/src/memory/v2/cli-command-content.ts +19 -0
- package/src/memory/v2/cli-command-store.ts +304 -0
- package/src/memory/v2/consolidation-job.ts +14 -0
- package/src/memory/v2/frontmatter-sweep.ts +7 -1
- package/src/memory/v2/injection-events.ts +101 -0
- package/src/memory/v2/injection.ts +69 -29
- package/src/memory/v2/page-index.ts +246 -19
- package/src/memory/v2/page-store.ts +18 -0
- package/src/memory/v2/router.ts +209 -55
- package/src/memory/v2/static-context.ts +4 -4
- package/src/memory/v2/types.ts +23 -0
- package/src/messaging/providers/a2a/__tests__/deliver.test.ts +274 -0
- package/src/messaging/providers/a2a/deliver.ts +156 -0
- package/src/messaging/providers/gmail/client.ts +9 -2
- package/src/messaging/providers/index.ts +18 -3
- package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +329 -3
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +34 -1
- package/src/messaging/providers/slack/adapter.ts +178 -25
- package/src/messaging/providers/slack/api.test.ts +54 -0
- package/src/messaging/providers/slack/api.ts +119 -3
- package/src/messaging/providers/slack/client.ts +12 -0
- package/src/messaging/providers/slack/deep-link.ts +20 -1
- package/src/messaging/providers/slack/message-metadata.test.ts +48 -0
- package/src/messaging/providers/slack/message-metadata.ts +156 -0
- package/src/messaging/providers/slack/render-transcript.test.ts +107 -75
- package/src/messaging/providers/slack/render-transcript.ts +176 -49
- package/src/messaging/providers/slack/send.test.ts +77 -0
- package/src/messaging/providers/slack/send.ts +8 -2
- package/src/messaging/providers/slack/types.ts +14 -0
- package/src/notifications/__tests__/broadcaster.test.ts +203 -0
- package/src/notifications/__tests__/decision-engine.test.ts +283 -0
- package/src/notifications/__tests__/deterministic-checks.test.ts +286 -0
- package/src/notifications/__tests__/emit-signal-home-feed.test.ts +5 -1
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +521 -36
- package/src/notifications/adapters/macos.ts +12 -2
- package/src/notifications/broadcaster.ts +29 -4
- package/src/notifications/conversation-seed-composer.ts +14 -2
- package/src/notifications/copy-composer.ts +17 -64
- package/src/notifications/decision-engine.ts +111 -44
- package/src/notifications/deferred-emit.ts +135 -0
- package/src/notifications/deterministic-checks.ts +96 -0
- package/src/notifications/emit-signal.ts +10 -1
- package/src/notifications/home-feed-side-effect.ts +136 -27
- package/src/notifications/signal.ts +0 -4
- package/src/notifications/types.ts +8 -0
- package/src/oauth/connect-orchestrator.ts +3 -0
- package/src/oauth/credential-token-resolver.ts +2 -0
- package/src/oauth/manual-token-connection.ts +19 -0
- package/src/oauth/oauth-store.ts +12 -0
- package/src/oauth/platform-connection.test.ts +43 -3
- package/src/oauth/platform-connection.ts +13 -4
- package/src/oauth/seed-providers.ts +22 -0
- package/src/permissions/prompter.ts +5 -2
- package/src/permissions/secret-prompter.ts +4 -1
- package/src/plugins/defaults/injectors.ts +118 -26
- package/src/plugins/external-plugin-loader.ts +82 -10
- package/src/plugins/types.ts +16 -7
- package/src/prompts/__tests__/system-prompt.test.ts +44 -45
- package/src/prompts/__tests__/task-progress-hint-section.test.ts +4 -8
- package/src/prompts/normalize-onboarding.ts +40 -0
- package/src/prompts/sections.ts +32 -14
- package/src/prompts/system-prompt.ts +105 -76
- package/src/prompts/template-detection.ts +37 -0
- package/src/prompts/templates/BOOTSTRAP-CONTENT-AUTOMATION.md +141 -0
- package/src/prompts/templates/BOOTSTRAP.md +13 -5
- package/src/prompts/templates/VOICE.md +3 -0
- package/src/prompts/templates/system-sections.ts +51 -10
- package/src/providers/__tests__/inference.test.ts +2 -0
- package/src/providers/anthropic/client.ts +132 -5
- package/src/providers/call-site-routing.ts +24 -6
- package/src/providers/connection-resolution.ts +63 -13
- package/src/providers/fireworks/client.ts +20 -2
- package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +74 -0
- package/src/providers/inference/__tests__/base-url-route-validation.test.ts +342 -0
- package/src/providers/inference/__tests__/base-url-security.test.ts +189 -0
- package/src/providers/inference/__tests__/codex-token-refresh.test.ts +254 -0
- package/src/providers/inference/__tests__/connections-openai-compatible.test.ts +175 -0
- package/src/providers/inference/__tests__/connections-status-label.test.ts +15 -0
- package/src/providers/inference/adapter-factory.ts +24 -21
- package/src/providers/inference/auth.ts +15 -3
- package/src/providers/inference/backfill.ts +14 -1
- package/src/providers/inference/codex-token-refresh.ts +128 -0
- package/src/providers/inference/connections.ts +85 -5
- package/src/providers/inference/resolve-auth.ts +50 -5
- package/src/providers/model-catalog.ts +244 -242
- package/src/providers/model-intents.ts +3 -3
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +235 -0
- package/src/providers/openai/chat-completions-provider.ts +215 -25
- package/src/providers/openai/responses-provider.ts +9 -3
- package/src/providers/openrouter/client.ts +46 -4
- package/src/providers/platform-proxy/constants.ts +3 -4
- package/src/providers/provider-catalog-visibility.ts +3 -1
- package/src/providers/provider-send-message.ts +27 -12
- package/src/providers/registry.ts +30 -1
- package/src/providers/types.ts +25 -0
- package/src/runtime/__tests__/agent-wake.test.ts +214 -0
- package/src/runtime/__tests__/background-job-runner.test.ts +128 -0
- package/src/runtime/agent-wake.ts +212 -57
- package/src/runtime/auth/route-policy.ts +20 -3
- package/src/runtime/background-job-runner.ts +26 -0
- package/src/runtime/channel-reply-delivery.ts +182 -47
- package/src/runtime/channel-retry-sweep.ts +141 -16
- package/src/runtime/http-server.ts +7 -16
- package/src/runtime/http-types.ts +7 -51
- package/src/runtime/pending-interactions.ts +51 -8
- package/src/runtime/routes/__tests__/consolidation-routes.test.ts +258 -0
- package/src/runtime/routes/__tests__/content-source-routes.test.ts +162 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +121 -5
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +275 -44
- package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +12 -0
- package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +14 -0
- package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +271 -0
- package/src/runtime/routes/__tests__/sanity-routes.test.ts +280 -0
- package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +266 -0
- package/src/runtime/routes/approval-routes.ts +4 -1
- package/src/runtime/routes/channel-availability-routes.ts +5 -0
- package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +246 -0
- package/src/runtime/routes/consolidation-routes.ts +100 -0
- package/src/runtime/routes/content-source-routes.ts +78 -0
- package/src/runtime/routes/conversation-cli-routes.ts +146 -1
- package/src/runtime/routes/conversation-query-routes.ts +130 -12
- package/src/runtime/routes/conversation-routes.ts +288 -76
- package/src/runtime/routes/document-comments-routes.ts +287 -0
- package/src/runtime/routes/documents-routes.ts +33 -0
- package/src/runtime/routes/home-feed-routes.ts +6 -3
- package/src/runtime/routes/host-app-control-routes.ts +1 -1
- package/src/runtime/routes/host-browser-routes.ts +8 -1
- package/src/runtime/routes/identity-routes.ts +21 -0
- package/src/runtime/routes/inbound-message-handler.ts +288 -58
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +365 -6
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +283 -82
- package/src/runtime/routes/index.ts +14 -4
- package/src/runtime/routes/inference-provider-connection-routes.ts +192 -3
- package/src/runtime/routes/integrations/a2a.ts +294 -0
- package/src/runtime/routes/llm-call-sites-routes.ts +11 -1
- package/src/runtime/routes/log-export-routes.ts +39 -0
- package/src/runtime/routes/memory-v2-routes.ts +217 -0
- package/src/runtime/routes/notification-routes.ts +19 -2
- package/src/runtime/routes/question-routes.ts +4 -1
- package/src/runtime/routes/sanity-routes.ts +159 -0
- package/src/runtime/routes/slack-channel-routes.ts +187 -0
- package/src/runtime/routes/subagents-routes.ts +41 -0
- package/src/runtime/services/conversation-serializer.ts +30 -4
- package/src/schedule/integration-status.ts +3 -1
- package/src/security/__tests__/oauth2-device-code.test.ts +479 -0
- package/src/security/oauth2-device-code.ts +307 -0
- package/src/security/oauth2.ts +26 -9
- package/src/security/secure-keys.ts +5 -0
- package/src/skills/catalog-install.ts +6 -2
- package/src/subagent/manager.ts +2 -0
- package/src/tools/browser/__tests__/pinned-tabs.test.ts +80 -0
- package/src/tools/browser/browser-execution.ts +93 -0
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +28 -0
- package/src/tools/browser/cdp-client/__tests__/types.test.ts +1 -0
- package/src/tools/browser/cdp-client/cdp-inspect-client.ts +10 -0
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +15 -1
- package/src/tools/browser/cdp-client/factory.ts +87 -3
- package/src/tools/browser/cdp-client/local-cdp-client.ts +9 -0
- package/src/tools/browser/cdp-client/types.ts +36 -0
- package/src/tools/browser/pinned-tabs.ts +90 -0
- package/src/tools/document/document-comment-tool.test.ts +379 -0
- package/src/tools/document/document-comment-tool.ts +156 -0
- package/src/tools/document/document-tool.ts +128 -2
- package/src/tools/memory/register.ts +1 -9
- package/src/tools/network/__tests__/web-fetch-metadata.test.ts +229 -0
- package/src/tools/network/__tests__/web-search-metadata.test.ts +346 -0
- package/src/tools/network/domain-normalize.ts +17 -0
- package/src/tools/network/web-fetch.ts +213 -64
- package/src/tools/network/web-search.ts +191 -66
- package/src/tools/registry.ts +2 -2
- package/src/tools/terminal/safe-env.ts +3 -2
- package/src/tools/tool-approval-handler.ts +19 -12
- package/src/tools/types.ts +41 -2
- package/src/tools/ui-surface/definitions.ts +3 -1
- package/src/types/onboarding-context.ts +4 -0
- package/src/util/__tests__/favicon.test.ts +84 -0
- package/src/util/favicon.ts +40 -0
- package/src/util/platform.ts +0 -5
- package/src/workspace/git-service.ts +75 -4
- package/src/workspace/migrations/087-memory-router-balanced-profile.ts +91 -0
- package/src/workspace/migrations/088-deprecate-background-conversation-override.ts +103 -0
- package/src/workspace/migrations/registry.ts +4 -0
- package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -441
- package/src/config/bundled-skills/document/SKILL.md +0 -54
- package/src/config/bundled-skills/document/TOOLS.json +0 -106
- package/src/daemon/seed-files.ts +0 -18
- package/src/memory/graph/__tests__/remember-description.test.ts +0 -55
- package/src/runtime/guardian-action-conversation-turn.ts +0 -99
- package/src/runtime/routes/interface-routes.ts +0 -43
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-create.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-delete.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-list.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-read.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-update.ts +0 -0
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth2 Device Authorization Grant (RFC 8628).
|
|
3
|
+
*
|
|
4
|
+
* Implements the device-code flow for environments where a browser redirect
|
|
5
|
+
* is impractical (CLI, headless). The user visits a verification URI and
|
|
6
|
+
* enters a short code; meanwhile, the client polls the token endpoint until
|
|
7
|
+
* authorization completes.
|
|
8
|
+
*
|
|
9
|
+
* This is intentionally separate from the PKCE authorization code flow in
|
|
10
|
+
* oauth2.ts — different grant type, different UX (no localhost server), and
|
|
11
|
+
* different polling lifecycle.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { getLogger } from "../util/logger.js";
|
|
15
|
+
|
|
16
|
+
const log = getLogger("oauth2-device-code");
|
|
17
|
+
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Types
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
export interface DeviceCodeConfig {
|
|
23
|
+
deviceCodeUrl: string;
|
|
24
|
+
tokenUrl: string;
|
|
25
|
+
clientId: string;
|
|
26
|
+
scopes: string[];
|
|
27
|
+
audience?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface DeviceCodeInitResult {
|
|
31
|
+
deviceCode: string;
|
|
32
|
+
userCode: string;
|
|
33
|
+
verificationUri: string;
|
|
34
|
+
verificationUriComplete?: string;
|
|
35
|
+
expiresIn: number;
|
|
36
|
+
interval: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface DeviceCodeTokenResult {
|
|
40
|
+
accessToken: string;
|
|
41
|
+
refreshToken?: string;
|
|
42
|
+
expiresIn?: number;
|
|
43
|
+
tokenType?: string;
|
|
44
|
+
scope?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export class DeviceCodeError extends Error {
|
|
48
|
+
constructor(
|
|
49
|
+
message: string,
|
|
50
|
+
public readonly code:
|
|
51
|
+
| "expired_token"
|
|
52
|
+
| "access_denied"
|
|
53
|
+
| "request_failed"
|
|
54
|
+
| "aborted",
|
|
55
|
+
) {
|
|
56
|
+
super(message);
|
|
57
|
+
this.name = "DeviceCodeError";
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
// Well-known provider configs
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
export const OPENAI_DEVICE_CODE_CONFIG: DeviceCodeConfig = {
|
|
66
|
+
deviceCodeUrl: "https://auth.openai.com/oauth/device/code",
|
|
67
|
+
tokenUrl: "https://auth.openai.com/oauth/token",
|
|
68
|
+
clientId: "app_EMoamEEZ73f0CkXaXp7hrann",
|
|
69
|
+
scopes: ["openid", "profile", "email", "offline_access"],
|
|
70
|
+
audience: "https://chatgpt.com",
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
// Device code request
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
export async function requestDeviceCode(
|
|
78
|
+
config: DeviceCodeConfig,
|
|
79
|
+
): Promise<DeviceCodeInitResult> {
|
|
80
|
+
const body: Record<string, string> = {
|
|
81
|
+
client_id: config.clientId,
|
|
82
|
+
scope: config.scopes.join(" "),
|
|
83
|
+
};
|
|
84
|
+
if (config.audience) {
|
|
85
|
+
body.audience = config.audience;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const resp = await fetch(config.deviceCodeUrl, {
|
|
89
|
+
method: "POST",
|
|
90
|
+
headers: {
|
|
91
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
92
|
+
Accept: "application/json",
|
|
93
|
+
},
|
|
94
|
+
body: new URLSearchParams(body),
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
if (!resp.ok) {
|
|
98
|
+
const rawBody = await resp.text().catch(() => "");
|
|
99
|
+
log.error(
|
|
100
|
+
{ status: resp.status, body: rawBody },
|
|
101
|
+
"Device code request failed",
|
|
102
|
+
);
|
|
103
|
+
throw new DeviceCodeError(
|
|
104
|
+
`Device code request failed (HTTP ${resp.status})`,
|
|
105
|
+
"request_failed",
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const data = (await resp.json()) as Record<string, unknown>;
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
deviceCode: data.device_code as string,
|
|
113
|
+
userCode: data.user_code as string,
|
|
114
|
+
verificationUri: data.verification_uri as string,
|
|
115
|
+
verificationUriComplete: data.verification_uri_complete as
|
|
116
|
+
| string
|
|
117
|
+
| undefined,
|
|
118
|
+
expiresIn: data.expires_in as number,
|
|
119
|
+
interval: (data.interval as number | undefined) ?? 5,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
// Token polling
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Poll the token endpoint until the user completes authorization or the
|
|
129
|
+
* device code expires.
|
|
130
|
+
*
|
|
131
|
+
* Handles RFC 8628 error codes:
|
|
132
|
+
* - `authorization_pending` — keep polling
|
|
133
|
+
* - `slow_down` — increase interval by 5 seconds (per spec)
|
|
134
|
+
* - `expired_token` — abort with error
|
|
135
|
+
* - `access_denied` — abort with error
|
|
136
|
+
*/
|
|
137
|
+
export async function pollForToken(
|
|
138
|
+
config: DeviceCodeConfig,
|
|
139
|
+
deviceCode: string,
|
|
140
|
+
intervalSeconds: number,
|
|
141
|
+
expiresIn: number,
|
|
142
|
+
signal?: AbortSignal,
|
|
143
|
+
/** @internal Test-only: override the sleep function to avoid real delays. */
|
|
144
|
+
_sleepFn?: (ms: number, signal?: AbortSignal) => Promise<void>,
|
|
145
|
+
): Promise<DeviceCodeTokenResult> {
|
|
146
|
+
const doSleep = _sleepFn ?? sleep;
|
|
147
|
+
let interval = intervalSeconds;
|
|
148
|
+
const deadline = Date.now() + expiresIn * 1000;
|
|
149
|
+
|
|
150
|
+
while (Date.now() < deadline) {
|
|
151
|
+
if (signal?.aborted) {
|
|
152
|
+
throw new DeviceCodeError("Device code flow aborted", "aborted");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
await doSleep(interval * 1000, signal);
|
|
156
|
+
|
|
157
|
+
if (signal?.aborted) {
|
|
158
|
+
throw new DeviceCodeError("Device code flow aborted", "aborted");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const body = new URLSearchParams({
|
|
162
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
163
|
+
device_code: deviceCode,
|
|
164
|
+
client_id: config.clientId,
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
let resp: Response;
|
|
168
|
+
try {
|
|
169
|
+
resp = await fetch(config.tokenUrl, {
|
|
170
|
+
method: "POST",
|
|
171
|
+
headers: {
|
|
172
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
173
|
+
Accept: "application/json",
|
|
174
|
+
},
|
|
175
|
+
body,
|
|
176
|
+
signal,
|
|
177
|
+
});
|
|
178
|
+
} catch (err) {
|
|
179
|
+
if (signal?.aborted) {
|
|
180
|
+
throw new DeviceCodeError("Device code flow aborted", "aborted");
|
|
181
|
+
}
|
|
182
|
+
log.warn({ err }, "Token poll request failed, will retry");
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const data = (await resp.json()) as Record<string, unknown>;
|
|
187
|
+
|
|
188
|
+
if (resp.ok) {
|
|
189
|
+
log.info("Device code authorization completed");
|
|
190
|
+
return {
|
|
191
|
+
accessToken: data.access_token as string,
|
|
192
|
+
refreshToken: data.refresh_token as string | undefined,
|
|
193
|
+
expiresIn: data.expires_in as number | undefined,
|
|
194
|
+
tokenType: data.token_type as string | undefined,
|
|
195
|
+
scope: data.scope as string | undefined,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const errorCode = data.error as string | undefined;
|
|
200
|
+
|
|
201
|
+
if (errorCode === "authorization_pending") {
|
|
202
|
+
log.debug("Authorization pending, continuing to poll");
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (errorCode === "slow_down") {
|
|
207
|
+
interval += 5;
|
|
208
|
+
log.info({ newInterval: interval }, "Received slow_down, increasing poll interval");
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (errorCode === "expired_token") {
|
|
213
|
+
throw new DeviceCodeError(
|
|
214
|
+
"Device code expired before user completed authorization",
|
|
215
|
+
"expired_token",
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (errorCode === "access_denied") {
|
|
220
|
+
throw new DeviceCodeError(
|
|
221
|
+
"User denied the authorization request",
|
|
222
|
+
"access_denied",
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
log.error(
|
|
227
|
+
{ status: resp.status, error: errorCode },
|
|
228
|
+
"Unexpected token poll error",
|
|
229
|
+
);
|
|
230
|
+
throw new DeviceCodeError(
|
|
231
|
+
`Token poll failed: ${errorCode ?? `HTTP ${resp.status}`}`,
|
|
232
|
+
"request_failed",
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
throw new DeviceCodeError(
|
|
237
|
+
"Device code expired before user completed authorization",
|
|
238
|
+
"expired_token",
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// ---------------------------------------------------------------------------
|
|
243
|
+
// Combined flow
|
|
244
|
+
// ---------------------------------------------------------------------------
|
|
245
|
+
|
|
246
|
+
export interface DeviceCodeFlowResult {
|
|
247
|
+
tokens: DeviceCodeTokenResult;
|
|
248
|
+
init: DeviceCodeInitResult;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Run the full device-code flow:
|
|
253
|
+
* 1. Request a device code + user code
|
|
254
|
+
* 2. Return the user code and verification URI (caller shows these to the user)
|
|
255
|
+
* 3. Poll for the token
|
|
256
|
+
*
|
|
257
|
+
* The returned `init` contains the user code and verification URI that the
|
|
258
|
+
* caller should present to the user before awaiting `tokens`.
|
|
259
|
+
*/
|
|
260
|
+
export async function startDeviceCodeFlow(
|
|
261
|
+
config: DeviceCodeConfig,
|
|
262
|
+
signal?: AbortSignal,
|
|
263
|
+
): Promise<DeviceCodeFlowResult> {
|
|
264
|
+
const init = await requestDeviceCode(config);
|
|
265
|
+
|
|
266
|
+
log.info(
|
|
267
|
+
{
|
|
268
|
+
verificationUri: init.verificationUri,
|
|
269
|
+
expiresIn: init.expiresIn,
|
|
270
|
+
interval: init.interval,
|
|
271
|
+
},
|
|
272
|
+
"Device code flow started",
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
const tokens = await pollForToken(
|
|
276
|
+
config,
|
|
277
|
+
init.deviceCode,
|
|
278
|
+
init.interval,
|
|
279
|
+
init.expiresIn,
|
|
280
|
+
signal,
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
return { tokens, init };
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// ---------------------------------------------------------------------------
|
|
287
|
+
// Helpers
|
|
288
|
+
// ---------------------------------------------------------------------------
|
|
289
|
+
|
|
290
|
+
function sleep(ms: number, signal?: AbortSignal): Promise<void> {
|
|
291
|
+
return new Promise((resolve, reject) => {
|
|
292
|
+
if (signal?.aborted) {
|
|
293
|
+
reject(new DeviceCodeError("Device code flow aborted", "aborted"));
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
const timer = setTimeout(resolve, ms);
|
|
297
|
+
if (typeof timer === "object" && "unref" in timer) timer.unref();
|
|
298
|
+
signal?.addEventListener(
|
|
299
|
+
"abort",
|
|
300
|
+
() => {
|
|
301
|
+
clearTimeout(timer);
|
|
302
|
+
reject(new DeviceCodeError("Device code flow aborted", "aborted"));
|
|
303
|
+
},
|
|
304
|
+
{ once: true },
|
|
305
|
+
);
|
|
306
|
+
});
|
|
307
|
+
}
|
package/src/security/oauth2.ts
CHANGED
|
@@ -86,6 +86,10 @@ export interface OAuth2FlowOptions {
|
|
|
86
86
|
* instead of an OS-assigned random port. Required for providers like Slack that
|
|
87
87
|
* need pre-registered redirect URIs. */
|
|
88
88
|
loopbackPort?: number;
|
|
89
|
+
/** Override the loopback callback path. Defaults to `/oauth/callback`.
|
|
90
|
+
* Required for providers with pre-registered redirect URIs that use a
|
|
91
|
+
* different path (e.g. OpenAI Codex uses `/auth/callback`). */
|
|
92
|
+
loopbackCallbackPath?: string;
|
|
89
93
|
}
|
|
90
94
|
|
|
91
95
|
export interface OAuth2FlowResult {
|
|
@@ -98,15 +102,15 @@ export interface OAuth2FlowResult {
|
|
|
98
102
|
// PKCE helpers
|
|
99
103
|
// ---------------------------------------------------------------------------
|
|
100
104
|
|
|
101
|
-
function generateCodeVerifier(): string {
|
|
105
|
+
export function generateCodeVerifier(): string {
|
|
102
106
|
return randomBytes(32).toString("base64url");
|
|
103
107
|
}
|
|
104
108
|
|
|
105
|
-
function generateCodeChallenge(verifier: string): string {
|
|
109
|
+
export function generateCodeChallenge(verifier: string): string {
|
|
106
110
|
return createHash("sha256").update(verifier).digest("base64url");
|
|
107
111
|
}
|
|
108
112
|
|
|
109
|
-
function generateState(): string {
|
|
113
|
+
export function generateState(): string {
|
|
110
114
|
return randomBytes(16).toString("hex");
|
|
111
115
|
}
|
|
112
116
|
|
|
@@ -114,7 +118,7 @@ function generateState(): string {
|
|
|
114
118
|
// Token exchange (shared between transports)
|
|
115
119
|
// ---------------------------------------------------------------------------
|
|
116
120
|
|
|
117
|
-
async function exchangeCodeForTokens(
|
|
121
|
+
export async function exchangeCodeForTokens(
|
|
118
122
|
config: OAuth2Config,
|
|
119
123
|
code: string,
|
|
120
124
|
redirectUri: string,
|
|
@@ -293,6 +297,7 @@ async function runLoopbackFlow(
|
|
|
293
297
|
codeChallenge: string,
|
|
294
298
|
state: string,
|
|
295
299
|
loopbackPort?: number,
|
|
300
|
+
callbackPath?: string,
|
|
296
301
|
): Promise<OAuth2FlowResult> {
|
|
297
302
|
const { code, redirectUri } = await startLoopbackServerAndWaitForCode(
|
|
298
303
|
config,
|
|
@@ -300,6 +305,7 @@ async function runLoopbackFlow(
|
|
|
300
305
|
codeChallenge,
|
|
301
306
|
state,
|
|
302
307
|
loopbackPort,
|
|
308
|
+
callbackPath,
|
|
303
309
|
);
|
|
304
310
|
|
|
305
311
|
return await exchangeCodeForTokens(config, code, redirectUri, codeVerifier);
|
|
@@ -317,7 +323,9 @@ function startLoopbackServerAndWaitForCode(
|
|
|
317
323
|
codeChallenge: string,
|
|
318
324
|
state: string,
|
|
319
325
|
loopbackPort?: number,
|
|
326
|
+
callbackPath?: string,
|
|
320
327
|
): Promise<{ code: string; redirectUri: string }> {
|
|
328
|
+
const effectiveCallbackPath = callbackPath ?? LOOPBACK_CALLBACK_PATH;
|
|
321
329
|
return new Promise((resolve, reject) => {
|
|
322
330
|
let settled = false;
|
|
323
331
|
let boundRedirectUri = "";
|
|
@@ -340,7 +348,7 @@ function startLoopbackServerAndWaitForCode(
|
|
|
340
348
|
|
|
341
349
|
const url = new URL(req.url ?? "/", `http://127.0.0.1`);
|
|
342
350
|
|
|
343
|
-
if (url.pathname !==
|
|
351
|
+
if (url.pathname !== effectiveCallbackPath) {
|
|
344
352
|
log.info(
|
|
345
353
|
{ pathname: url.pathname },
|
|
346
354
|
"oauth2 loopback: non-callback path, returning 404",
|
|
@@ -426,7 +434,7 @@ function startLoopbackServerAndWaitForCode(
|
|
|
426
434
|
|
|
427
435
|
server.listen(loopbackPort ?? 0, "localhost", () => {
|
|
428
436
|
const addr = server.address() as { port: number };
|
|
429
|
-
boundRedirectUri = `http://localhost:${addr.port}${
|
|
437
|
+
boundRedirectUri = `http://localhost:${addr.port}${effectiveCallbackPath}`;
|
|
430
438
|
|
|
431
439
|
log.info(
|
|
432
440
|
{ port: addr.port, redirectUri: boundRedirectUri },
|
|
@@ -497,7 +505,11 @@ export async function prepareOAuth2Flow(
|
|
|
497
505
|
const transport = options?.callbackTransport ?? "loopback";
|
|
498
506
|
|
|
499
507
|
if (transport === "loopback") {
|
|
500
|
-
return prepareLoopbackFlow(
|
|
508
|
+
return prepareLoopbackFlow(
|
|
509
|
+
config,
|
|
510
|
+
options?.loopbackPort,
|
|
511
|
+
options?.loopbackCallbackPath,
|
|
512
|
+
);
|
|
501
513
|
}
|
|
502
514
|
|
|
503
515
|
// Dynamic imports required here to avoid circular dependencies with
|
|
@@ -555,6 +567,7 @@ export async function prepareOAuth2Flow(
|
|
|
555
567
|
async function prepareLoopbackFlow(
|
|
556
568
|
config: OAuth2Config,
|
|
557
569
|
loopbackPort?: number,
|
|
570
|
+
callbackPath?: string,
|
|
558
571
|
): Promise<OAuth2PreparedFlow> {
|
|
559
572
|
const codeVerifier = generateCodeVerifier();
|
|
560
573
|
const codeChallenge = generateCodeChallenge(codeVerifier);
|
|
@@ -563,6 +576,7 @@ async function prepareLoopbackFlow(
|
|
|
563
576
|
const { redirectUri, codePromise } = await startLoopbackServerForPreparedFlow(
|
|
564
577
|
state,
|
|
565
578
|
loopbackPort,
|
|
579
|
+
callbackPath,
|
|
566
580
|
);
|
|
567
581
|
|
|
568
582
|
const authParams = new URLSearchParams({
|
|
@@ -599,7 +613,9 @@ async function prepareLoopbackFlow(
|
|
|
599
613
|
function startLoopbackServerForPreparedFlow(
|
|
600
614
|
state: string,
|
|
601
615
|
loopbackPort?: number,
|
|
616
|
+
callbackPath?: string,
|
|
602
617
|
): Promise<{ redirectUri: string; codePromise: Promise<string> }> {
|
|
618
|
+
const effectiveCallbackPath = callbackPath ?? LOOPBACK_CALLBACK_PATH;
|
|
603
619
|
return new Promise((resolveSetup, rejectSetup) => {
|
|
604
620
|
let settled = false;
|
|
605
621
|
let listening = false;
|
|
@@ -619,7 +635,7 @@ function startLoopbackServerForPreparedFlow(
|
|
|
619
635
|
|
|
620
636
|
const url = new URL(req.url ?? "/", `http://127.0.0.1`);
|
|
621
637
|
|
|
622
|
-
if (url.pathname !==
|
|
638
|
+
if (url.pathname !== effectiveCallbackPath) {
|
|
623
639
|
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
624
640
|
res.end("Not found");
|
|
625
641
|
return;
|
|
@@ -683,7 +699,7 @@ function startLoopbackServerForPreparedFlow(
|
|
|
683
699
|
|
|
684
700
|
server.listen(loopbackPort ?? 0, "localhost", () => {
|
|
685
701
|
const addr = server.address() as { port: number };
|
|
686
|
-
const redirectUri = `http://localhost:${addr.port}${
|
|
702
|
+
const redirectUri = `http://localhost:${addr.port}${effectiveCallbackPath}`;
|
|
687
703
|
listening = true;
|
|
688
704
|
resolveSetup({ redirectUri, codePromise });
|
|
689
705
|
});
|
|
@@ -784,6 +800,7 @@ export async function startOAuth2Flow(
|
|
|
784
800
|
codeChallenge,
|
|
785
801
|
state,
|
|
786
802
|
options?.loopbackPort,
|
|
803
|
+
options?.loopbackCallbackPath,
|
|
787
804
|
);
|
|
788
805
|
}
|
|
789
806
|
|
|
@@ -113,6 +113,11 @@ const RECONNECT_COOLDOWN_MS = 3_000;
|
|
|
113
113
|
*/
|
|
114
114
|
const CREDENTIAL_OP_TIMEOUT_MS = 45_000;
|
|
115
115
|
|
|
116
|
+
/** Returns the current CES RPC client if one has been injected. */
|
|
117
|
+
export function getCesClient(): CesClient | undefined {
|
|
118
|
+
return _cesClient;
|
|
119
|
+
}
|
|
120
|
+
|
|
116
121
|
/** Inject a CES RPC client for credential routing. Resets the resolved backend. */
|
|
117
122
|
export function setCesClient(client: CesClient | undefined): void {
|
|
118
123
|
_cesClient = client;
|
|
@@ -111,7 +111,9 @@ export async function fetchCatalog(): Promise<CatalogSkill[]> {
|
|
|
111
111
|
if (!Array.isArray(manifest.skills)) {
|
|
112
112
|
throw new Error("Platform catalog has invalid skills array");
|
|
113
113
|
}
|
|
114
|
-
return manifest.skills
|
|
114
|
+
return manifest.skills.filter(
|
|
115
|
+
(s): s is CatalogSkill => !!s && typeof s.id === "string",
|
|
116
|
+
);
|
|
115
117
|
}
|
|
116
118
|
|
|
117
119
|
export function readLocalCatalog(repoSkillsDir: string): CatalogSkill[] {
|
|
@@ -119,7 +121,9 @@ export function readLocalCatalog(repoSkillsDir: string): CatalogSkill[] {
|
|
|
119
121
|
const raw = readFileSync(join(repoSkillsDir, "catalog.json"), "utf-8");
|
|
120
122
|
const manifest = JSON.parse(raw) as CatalogManifest;
|
|
121
123
|
if (!Array.isArray(manifest.skills)) return [];
|
|
122
|
-
return manifest.skills
|
|
124
|
+
return manifest.skills.filter(
|
|
125
|
+
(s): s is CatalogSkill => !!s && typeof s.id === "string",
|
|
126
|
+
);
|
|
123
127
|
} catch {
|
|
124
128
|
return [];
|
|
125
129
|
}
|
package/src/subagent/manager.ts
CHANGED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
__resetPinnedTabsForTests,
|
|
5
|
+
clearPinnedTab,
|
|
6
|
+
clearPinnedTabByTabId,
|
|
7
|
+
getPinnedTab,
|
|
8
|
+
setPinnedTab,
|
|
9
|
+
} from "../pinned-tabs.js";
|
|
10
|
+
|
|
11
|
+
describe("pinned-tabs", () => {
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
__resetPinnedTabsForTests();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("setPinnedTab / getPinnedTab round-trips", () => {
|
|
17
|
+
setPinnedTab("conv-a", "42");
|
|
18
|
+
expect(getPinnedTab("conv-a")).toBe("42");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("getPinnedTab returns undefined for unset conversation", () => {
|
|
22
|
+
expect(getPinnedTab("unset-conv")).toBeUndefined();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("conversations have independent pins", () => {
|
|
26
|
+
setPinnedTab("conv-a", "42");
|
|
27
|
+
setPinnedTab("conv-b", "99");
|
|
28
|
+
expect(getPinnedTab("conv-a")).toBe("42");
|
|
29
|
+
expect(getPinnedTab("conv-b")).toBe("99");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("setPinnedTab overwrites a prior pin for the same conversation", () => {
|
|
33
|
+
setPinnedTab("conv-a", "42");
|
|
34
|
+
setPinnedTab("conv-a", "100");
|
|
35
|
+
expect(getPinnedTab("conv-a")).toBe("100");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("clearPinnedTab removes the pin", () => {
|
|
39
|
+
setPinnedTab("conv-a", "42");
|
|
40
|
+
clearPinnedTab("conv-a");
|
|
41
|
+
expect(getPinnedTab("conv-a")).toBeUndefined();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("clearPinnedTab on a non-existent conversation is a no-op", () => {
|
|
45
|
+
// Should not throw or affect other conversations.
|
|
46
|
+
setPinnedTab("conv-a", "42");
|
|
47
|
+
clearPinnedTab("nonexistent");
|
|
48
|
+
expect(getPinnedTab("conv-a")).toBe("42");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("clearPinnedTabByTabId clears every matching conversation", () => {
|
|
52
|
+
setPinnedTab("conv-a", "42");
|
|
53
|
+
setPinnedTab("conv-b", "42");
|
|
54
|
+
setPinnedTab("conv-c", "99");
|
|
55
|
+
|
|
56
|
+
const cleared = clearPinnedTabByTabId("42");
|
|
57
|
+
|
|
58
|
+
expect(cleared).toBe(2);
|
|
59
|
+
expect(getPinnedTab("conv-a")).toBeUndefined();
|
|
60
|
+
expect(getPinnedTab("conv-b")).toBeUndefined();
|
|
61
|
+
expect(getPinnedTab("conv-c")).toBe("99"); // unchanged
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("clearPinnedTabByTabId on a non-matching tab is a no-op returning 0", () => {
|
|
65
|
+
setPinnedTab("conv-a", "42");
|
|
66
|
+
const cleared = clearPinnedTabByTabId("999");
|
|
67
|
+
expect(cleared).toBe(0);
|
|
68
|
+
expect(getPinnedTab("conv-a")).toBe("42");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("empty conversationId is rejected on set", () => {
|
|
72
|
+
setPinnedTab("", "42");
|
|
73
|
+
expect(getPinnedTab("")).toBeUndefined();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("empty tabId is rejected on set", () => {
|
|
77
|
+
setPinnedTab("conv-a", "");
|
|
78
|
+
expect(getPinnedTab("conv-a")).toBeUndefined();
|
|
79
|
+
});
|
|
80
|
+
});
|
|
@@ -70,6 +70,7 @@ import type {
|
|
|
70
70
|
CdpClient,
|
|
71
71
|
CdpClientKind,
|
|
72
72
|
} from "./cdp-client/types.js";
|
|
73
|
+
import { clearPinnedTab, setPinnedTab } from "./pinned-tabs.js";
|
|
73
74
|
import { checkBrowserRuntime } from "./runtime-check.js";
|
|
74
75
|
|
|
75
76
|
const log = getLogger("headless-browser");
|
|
@@ -471,6 +472,15 @@ function wrapWithKindMemo(
|
|
|
471
472
|
dispose(): void {
|
|
472
473
|
inner.dispose();
|
|
473
474
|
},
|
|
475
|
+
// Proxy the optional setCdpSessionId through to the underlying
|
|
476
|
+
// client so the navigate executor's --new-tab path can pin the
|
|
477
|
+
// freshly-created tab onto the extension CDP client. We define
|
|
478
|
+
// the method unconditionally here (rather than only when the
|
|
479
|
+
// inner client implements it) so callers can use a simple optional
|
|
480
|
+
// chain on this wrapper without re-walking the inner reference.
|
|
481
|
+
setCdpSessionId(cdpSessionId: string): void {
|
|
482
|
+
inner.setCdpSessionId?.(cdpSessionId);
|
|
483
|
+
},
|
|
474
484
|
};
|
|
475
485
|
}
|
|
476
486
|
|
|
@@ -624,6 +634,89 @@ export async function executeBrowserNavigate(
|
|
|
624
634
|
if (acquired.errorResult) return acquired.errorResult;
|
|
625
635
|
const { cdp, browserMode } = acquired;
|
|
626
636
|
|
|
637
|
+
// --new-tab: open a fresh tab via the extension's Vellum.createTab
|
|
638
|
+
// pseudo-CDP method, then pin this client (and the conversation) to
|
|
639
|
+
// the returned tabId so this Page.navigate and every subsequent
|
|
640
|
+
// command on the same conversation routes to the new tab instead of
|
|
641
|
+
// the user's currently-active tab. Extension backend only; the local
|
|
642
|
+
// (Playwright) backend manages its own isolated browser and the
|
|
643
|
+
// cdp-inspect backend connects to a single tab by URL pattern.
|
|
644
|
+
const newTab = input.new_tab === true;
|
|
645
|
+
if (newTab && cdp.kind === "extension") {
|
|
646
|
+
try {
|
|
647
|
+
const result = await cdp.send<{ tabId?: number | string }>(
|
|
648
|
+
"Vellum.createTab",
|
|
649
|
+
{},
|
|
650
|
+
context.signal,
|
|
651
|
+
);
|
|
652
|
+
const tabId =
|
|
653
|
+
typeof result?.tabId === "number"
|
|
654
|
+
? String(result.tabId)
|
|
655
|
+
: typeof result?.tabId === "string"
|
|
656
|
+
? result.tabId
|
|
657
|
+
: undefined;
|
|
658
|
+
if (!tabId) {
|
|
659
|
+
// Malformed createTab response (no tabId). We're nominally falling
|
|
660
|
+
// back to active-tab routing — but the live `cdp` instance was
|
|
661
|
+
// already constructed with whatever pin was in scope for this
|
|
662
|
+
// conversation, AND the pin store still holds it for future
|
|
663
|
+
// client construction. Clear both: the pin store (so the next
|
|
664
|
+
// executeBrowserNavigate builds a clean client) AND the current
|
|
665
|
+
// cdp instance's session (so the Page.navigate that runs in a
|
|
666
|
+
// few lines targets the active tab rather than the stale pin).
|
|
667
|
+
// Without the setCdpSessionId(undefined) call, the warn message
|
|
668
|
+
// is a lie: navigation would still route to the dead tab via the
|
|
669
|
+
// already-injected cdpSessionId and likely fail with
|
|
670
|
+
// cdp_session_not_found.
|
|
671
|
+
clearPinnedTab(context.conversationId);
|
|
672
|
+
cdp.setCdpSessionId?.(undefined);
|
|
673
|
+
log.warn(
|
|
674
|
+
{ conversationId: context.conversationId, result },
|
|
675
|
+
"Vellum.createTab returned no tabId; cleared stale pin and live session, falling back to active-tab routing",
|
|
676
|
+
);
|
|
677
|
+
} else {
|
|
678
|
+
cdp.setCdpSessionId?.(tabId);
|
|
679
|
+
setPinnedTab(context.conversationId, tabId);
|
|
680
|
+
log.debug(
|
|
681
|
+
{ conversationId: context.conversationId, tabId },
|
|
682
|
+
"Opened new tab via --new-tab; pinned subsequent ops to it",
|
|
683
|
+
);
|
|
684
|
+
}
|
|
685
|
+
} catch (err) {
|
|
686
|
+
// Surface the failure rather than silently clobbering the active
|
|
687
|
+
// tab — that's exactly the behavior --new-tab is supposed to
|
|
688
|
+
// avoid. Clear any stale pin so subsequent ops don't route to a
|
|
689
|
+
// dead tab. Note: an old extension build without Vellum.createTab
|
|
690
|
+
// support will land here (CDP returns an "unknown method" error).
|
|
691
|
+
// We're early-returning before the main try/finally block below,
|
|
692
|
+
// so we must dispose the cdp client manually to avoid leaking it.
|
|
693
|
+
clearPinnedTab(context.conversationId);
|
|
694
|
+
const message =
|
|
695
|
+
err instanceof Error ? err.message : String(err);
|
|
696
|
+
log.warn(
|
|
697
|
+
{ conversationId: context.conversationId, err },
|
|
698
|
+
"Vellum.createTab failed; aborting --new-tab navigate",
|
|
699
|
+
);
|
|
700
|
+
try {
|
|
701
|
+
cdp.dispose();
|
|
702
|
+
} catch (disposeErr) {
|
|
703
|
+
log.warn(
|
|
704
|
+
{ conversationId: context.conversationId, err: disposeErr },
|
|
705
|
+
"Failed to dispose CDP client after Vellum.createTab failure",
|
|
706
|
+
);
|
|
707
|
+
}
|
|
708
|
+
return {
|
|
709
|
+
content: `Error: Failed to open a new tab for navigation: ${message}. The Chrome extension may need an update to support --new-tab.`,
|
|
710
|
+
isError: true,
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
} else if (newTab && cdp.kind !== "extension") {
|
|
714
|
+
log.debug(
|
|
715
|
+
{ conversationId: context.conversationId, backendKind: cdp.kind },
|
|
716
|
+
"--new-tab requested but backend does not support it; ignoring",
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
|
|
627
720
|
// Screencast + handoff are Playwright-backed and only meaningful
|
|
628
721
|
// for the local sacrificial-profile path. On the extension path the
|
|
629
722
|
// user already has their own Chrome window, so both are no-ops.
|