@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
|
@@ -10,10 +10,13 @@
|
|
|
10
10
|
|
|
11
11
|
import { z } from "zod";
|
|
12
12
|
|
|
13
|
+
import { isAssistantFeatureFlagEnabled } from "../../config/assistant-feature-flags.js";
|
|
13
14
|
import { getConfigReadOnly } from "../../config/loader.js";
|
|
14
15
|
import { getDb } from "../../memory/db-connection.js";
|
|
15
16
|
import {
|
|
16
17
|
AuthSchema,
|
|
18
|
+
type ConnectionModel,
|
|
19
|
+
ConnectionModelSchema,
|
|
17
20
|
ConnectionProviderSchema,
|
|
18
21
|
ConnectionStatusSchema,
|
|
19
22
|
ProviderConnectionSchema,
|
|
@@ -25,8 +28,14 @@ import {
|
|
|
25
28
|
getConnection,
|
|
26
29
|
listConnections,
|
|
27
30
|
MANAGED_CONNECTION_NAMES,
|
|
31
|
+
PROVIDERS_REQUIRING_BASE_URL_AND_MODELS,
|
|
28
32
|
updateConnection,
|
|
29
33
|
} from "../../providers/inference/connections.js";
|
|
34
|
+
import {
|
|
35
|
+
isPrivateOrLocalHost,
|
|
36
|
+
resolveHostAddresses,
|
|
37
|
+
resolveRequestAddress,
|
|
38
|
+
} from "../../tools/network/url-safety.js";
|
|
30
39
|
import { BadRequestError, ConflictError, NotFoundError } from "./errors.js";
|
|
31
40
|
import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
|
|
32
41
|
|
|
@@ -35,6 +44,125 @@ import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
|
|
|
35
44
|
// ---------------------------------------------------------------------------
|
|
36
45
|
|
|
37
46
|
const providerConnectionResponseSchema = ProviderConnectionSchema;
|
|
47
|
+
const OPENAI_COMPATIBLE_ENDPOINTS_FLAG = "openai-compatible-endpoints";
|
|
48
|
+
|
|
49
|
+
function openAICompatibleEndpointsEnabled(): boolean {
|
|
50
|
+
return isAssistantFeatureFlagEnabled(
|
|
51
|
+
OPENAI_COMPATIBLE_ENDPOINTS_FLAG,
|
|
52
|
+
getConfigReadOnly(),
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function rejectDisabledOpenAICompatibleProvider(provider: string): void {
|
|
57
|
+
if (provider !== "openai-compatible") return;
|
|
58
|
+
if (openAICompatibleEndpointsEnabled()) return;
|
|
59
|
+
throw new BadRequestError(
|
|
60
|
+
"OpenAI-compatible endpoints are disabled. Enable the openai-compatible-endpoints feature flag to configure this provider.",
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
// Custom provider field parsing (openai-compatible base_url + models)
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Parse and validate `base_url` and `models` from the request body.
|
|
70
|
+
*
|
|
71
|
+
* `base_url` is only accepted for providers in
|
|
72
|
+
* `PROVIDERS_REQUIRING_BASE_URL_AND_MODELS` (currently `openai-compatible`).
|
|
73
|
+
* For all other providers, supplying `base_url` returns a 400. This prevents
|
|
74
|
+
* API-key exfiltration: an attacker cannot create an `anthropic` connection
|
|
75
|
+
* with a `base_url` pointing to their own server, which would redirect all
|
|
76
|
+
* LLM calls (and the API key) to the attacker.
|
|
77
|
+
*
|
|
78
|
+
* Even for `openai-compatible`, the `base_url` must not point to private
|
|
79
|
+
* networks or cloud metadata endpoints (SSRF protection).
|
|
80
|
+
*/
|
|
81
|
+
async function parseCustomProviderFields(
|
|
82
|
+
body: Record<string, unknown>,
|
|
83
|
+
provider: string,
|
|
84
|
+
): Promise<{
|
|
85
|
+
baseUrl?: string | null;
|
|
86
|
+
models?: ConnectionModel[] | null;
|
|
87
|
+
}> {
|
|
88
|
+
const out: {
|
|
89
|
+
baseUrl?: string | null;
|
|
90
|
+
models?: ConnectionModel[] | null;
|
|
91
|
+
} = {};
|
|
92
|
+
|
|
93
|
+
if ("base_url" in body) {
|
|
94
|
+
const raw = body.base_url;
|
|
95
|
+
|
|
96
|
+
// Gate: base_url is only valid for openai-compatible providers.
|
|
97
|
+
if (
|
|
98
|
+
raw !== null &&
|
|
99
|
+
raw !== undefined &&
|
|
100
|
+
!PROVIDERS_REQUIRING_BASE_URL_AND_MODELS.has(provider)
|
|
101
|
+
) {
|
|
102
|
+
throw new BadRequestError(
|
|
103
|
+
`base_url is only valid for openai-compatible providers. Remove base_url or use the openai-compatible provider type.`,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (raw === null) {
|
|
108
|
+
out.baseUrl = null;
|
|
109
|
+
} else if (typeof raw === "string" && raw.length > 0) {
|
|
110
|
+
let parsed: URL;
|
|
111
|
+
try {
|
|
112
|
+
parsed = new URL(raw);
|
|
113
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
114
|
+
throw new BadRequestError(`Invalid base_url: must be an http(s) URL`);
|
|
115
|
+
}
|
|
116
|
+
} catch (err) {
|
|
117
|
+
if (err instanceof BadRequestError) throw err;
|
|
118
|
+
throw new BadRequestError(
|
|
119
|
+
`Invalid base_url: must be a valid http(s) URL`,
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// SSRF protection: reject private IPs, localhost, cloud metadata endpoints.
|
|
124
|
+
const hostname = parsed.hostname;
|
|
125
|
+
if (isPrivateOrLocalHost(hostname)) {
|
|
126
|
+
throw new BadRequestError(
|
|
127
|
+
`Invalid base_url: must not point to a private or local network address.`,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// DNS resolution check: hostname may resolve to a private IP.
|
|
132
|
+
const resolved = await resolveRequestAddress(
|
|
133
|
+
hostname,
|
|
134
|
+
resolveHostAddresses,
|
|
135
|
+
/* allowPrivateNetwork */ false,
|
|
136
|
+
);
|
|
137
|
+
if (resolved.blockedAddress) {
|
|
138
|
+
throw new BadRequestError(
|
|
139
|
+
`Invalid base_url: hostname resolves to a private network address.`,
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
out.baseUrl = raw;
|
|
144
|
+
} else {
|
|
145
|
+
throw new BadRequestError(
|
|
146
|
+
`Invalid base_url: must be a non-empty string or null`,
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if ("models" in body) {
|
|
152
|
+
const raw = body.models;
|
|
153
|
+
if (raw === null) {
|
|
154
|
+
out.models = null;
|
|
155
|
+
} else {
|
|
156
|
+
const parsed = z.array(ConnectionModelSchema).safeParse(raw);
|
|
157
|
+
if (!parsed.success) {
|
|
158
|
+
throw new BadRequestError(`Invalid models: ${parsed.error.message}`);
|
|
159
|
+
}
|
|
160
|
+
out.models = parsed.data;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return out;
|
|
165
|
+
}
|
|
38
166
|
|
|
39
167
|
// ---------------------------------------------------------------------------
|
|
40
168
|
// Handlers
|
|
@@ -42,10 +170,18 @@ const providerConnectionResponseSchema = ProviderConnectionSchema;
|
|
|
42
170
|
|
|
43
171
|
function handleListConnections({ queryParams = {} }: RouteHandlerArgs) {
|
|
44
172
|
const provider = queryParams.provider;
|
|
173
|
+
if (provider) rejectDisabledOpenAICompatibleProvider(provider);
|
|
45
174
|
const connections = listConnections(
|
|
46
175
|
getDb(),
|
|
47
176
|
provider ? { provider } : undefined,
|
|
48
177
|
);
|
|
178
|
+
if (!openAICompatibleEndpointsEnabled()) {
|
|
179
|
+
return {
|
|
180
|
+
connections: connections.filter(
|
|
181
|
+
(conn) => conn.provider !== "openai-compatible",
|
|
182
|
+
),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
49
185
|
return { connections };
|
|
50
186
|
}
|
|
51
187
|
|
|
@@ -55,11 +191,17 @@ function handleGetConnection({ pathParams = {} }: RouteHandlerArgs) {
|
|
|
55
191
|
|
|
56
192
|
const conn = getConnection(getDb(), name);
|
|
57
193
|
if (!conn) throw new NotFoundError(`Connection "${name}" not found.`);
|
|
194
|
+
if (
|
|
195
|
+
conn.provider === "openai-compatible" &&
|
|
196
|
+
!openAICompatibleEndpointsEnabled()
|
|
197
|
+
) {
|
|
198
|
+
throw new NotFoundError(`Connection "${name}" not found.`);
|
|
199
|
+
}
|
|
58
200
|
|
|
59
201
|
return conn;
|
|
60
202
|
}
|
|
61
203
|
|
|
62
|
-
function handleCreateConnection({ body = {} }: RouteHandlerArgs) {
|
|
204
|
+
async function handleCreateConnection({ body = {} }: RouteHandlerArgs) {
|
|
63
205
|
const name = body.name;
|
|
64
206
|
const provider = body.provider;
|
|
65
207
|
const auth = body.auth;
|
|
@@ -74,6 +216,7 @@ function handleCreateConnection({ body = {} }: RouteHandlerArgs) {
|
|
|
74
216
|
`Invalid provider "${String(provider)}". Valid: ${VALID_CONNECTION_PROVIDERS.join(", ")}`,
|
|
75
217
|
);
|
|
76
218
|
}
|
|
219
|
+
rejectDisabledOpenAICompatibleProvider(providerResult.data);
|
|
77
220
|
|
|
78
221
|
const authResult = AuthSchema.safeParse(auth);
|
|
79
222
|
if (!authResult.success) {
|
|
@@ -99,12 +242,15 @@ function handleCreateConnection({ body = {} }: RouteHandlerArgs) {
|
|
|
99
242
|
);
|
|
100
243
|
}
|
|
101
244
|
|
|
245
|
+
const customFields = await parseCustomProviderFields(body, providerResult.data);
|
|
246
|
+
|
|
102
247
|
const result = createConnection(getDb(), {
|
|
103
248
|
name,
|
|
104
249
|
provider: providerResult.data,
|
|
105
250
|
auth: authResult.data,
|
|
106
251
|
...(statusResult ? { status: statusResult.data } : {}),
|
|
107
252
|
...(labelRaw !== undefined ? { label: labelRaw as string | null } : {}),
|
|
253
|
+
...customFields,
|
|
108
254
|
});
|
|
109
255
|
|
|
110
256
|
if (!result.ok) {
|
|
@@ -118,19 +264,38 @@ function handleCreateConnection({ body = {} }: RouteHandlerArgs) {
|
|
|
118
264
|
`Invalid provider "${result.error.provider}". Valid: ${VALID_CONNECTION_PROVIDERS.join(", ")}`,
|
|
119
265
|
);
|
|
120
266
|
}
|
|
267
|
+
if (result.error.code === "base_url_required") {
|
|
268
|
+
throw new BadRequestError(
|
|
269
|
+
"base_url is required for openai-compatible connections.",
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
if (result.error.code === "models_required") {
|
|
273
|
+
throw new BadRequestError(
|
|
274
|
+
"At least one model is required for openai-compatible connections.",
|
|
275
|
+
);
|
|
276
|
+
}
|
|
121
277
|
throw new BadRequestError("Invalid auth configuration.");
|
|
122
278
|
}
|
|
123
279
|
|
|
124
280
|
return result.connection;
|
|
125
281
|
}
|
|
126
282
|
|
|
127
|
-
function handleUpdateConnection({
|
|
283
|
+
async function handleUpdateConnection({
|
|
128
284
|
pathParams = {},
|
|
129
285
|
body = {},
|
|
130
286
|
}: RouteHandlerArgs) {
|
|
131
287
|
const { name } = pathParams;
|
|
132
288
|
if (!name) throw new BadRequestError("name is required");
|
|
133
289
|
|
|
290
|
+
const existing = getConnection(getDb(), name);
|
|
291
|
+
if (!existing) throw new NotFoundError(`Connection "${name}" not found.`);
|
|
292
|
+
if (
|
|
293
|
+
existing.provider === "openai-compatible" &&
|
|
294
|
+
!openAICompatibleEndpointsEnabled()
|
|
295
|
+
) {
|
|
296
|
+
throw new NotFoundError(`Connection "${name}" not found.`);
|
|
297
|
+
}
|
|
298
|
+
|
|
134
299
|
const auth = body.auth;
|
|
135
300
|
const authResult = AuthSchema.safeParse(auth);
|
|
136
301
|
if (!authResult.success) {
|
|
@@ -169,16 +334,29 @@ function handleUpdateConnection({
|
|
|
169
334
|
);
|
|
170
335
|
}
|
|
171
336
|
|
|
337
|
+
const customFields = await parseCustomProviderFields(body, existing.provider);
|
|
338
|
+
|
|
172
339
|
const result = updateConnection(getDb(), name, {
|
|
173
340
|
auth: authResult.data,
|
|
174
341
|
...(statusResult ? { status: statusResult.data } : {}),
|
|
175
342
|
...(labelRaw !== undefined ? { label: labelRaw as string | null } : {}),
|
|
343
|
+
...customFields,
|
|
176
344
|
});
|
|
177
345
|
|
|
178
346
|
if (!result.ok) {
|
|
179
347
|
if (result.error.code === "not_found") {
|
|
180
348
|
throw new NotFoundError(`Connection "${name}" not found.`);
|
|
181
349
|
}
|
|
350
|
+
if (result.error.code === "base_url_required") {
|
|
351
|
+
throw new BadRequestError(
|
|
352
|
+
"base_url is required for openai-compatible connections.",
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
if (result.error.code === "models_required") {
|
|
356
|
+
throw new BadRequestError(
|
|
357
|
+
"At least one model is required for openai-compatible connections.",
|
|
358
|
+
);
|
|
359
|
+
}
|
|
182
360
|
throw new BadRequestError("Invalid auth configuration.");
|
|
183
361
|
}
|
|
184
362
|
|
|
@@ -191,7 +369,14 @@ function handleDeleteConnection({ pathParams = {} }: RouteHandlerArgs) {
|
|
|
191
369
|
|
|
192
370
|
// Existence check first so a stale `llm.default.provider_connection`
|
|
193
371
|
// reference to a missing connection returns 404 (not 409).
|
|
194
|
-
|
|
372
|
+
const existing = getConnection(getDb(), name);
|
|
373
|
+
if (!existing) {
|
|
374
|
+
throw new NotFoundError(`Connection "${name}" not found.`);
|
|
375
|
+
}
|
|
376
|
+
if (
|
|
377
|
+
existing.provider === "openai-compatible" &&
|
|
378
|
+
!openAICompatibleEndpointsEnabled()
|
|
379
|
+
) {
|
|
195
380
|
throw new NotFoundError(`Connection "${name}" not found.`);
|
|
196
381
|
}
|
|
197
382
|
|
|
@@ -301,6 +486,8 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
301
486
|
auth: AuthSchema,
|
|
302
487
|
label: z.string().min(1).optional(),
|
|
303
488
|
status: ConnectionStatusSchema.optional(),
|
|
489
|
+
base_url: z.string().url().nullable().optional(),
|
|
490
|
+
models: z.array(ConnectionModelSchema).nullable().optional(),
|
|
304
491
|
}),
|
|
305
492
|
responseBody: providerConnectionResponseSchema,
|
|
306
493
|
responseStatus: "201",
|
|
@@ -324,6 +511,8 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
324
511
|
auth: AuthSchema,
|
|
325
512
|
status: ConnectionStatusSchema.optional(),
|
|
326
513
|
label: z.string().min(1).nullable().optional(),
|
|
514
|
+
base_url: z.string().url().nullable().optional(),
|
|
515
|
+
models: z.array(ConnectionModelSchema).nullable().optional(),
|
|
327
516
|
}),
|
|
328
517
|
responseBody: providerConnectionResponseSchema,
|
|
329
518
|
additionalResponses: {
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route handlers for A2A integration config endpoints.
|
|
3
|
+
*
|
|
4
|
+
* GET /v1/integrations/a2a/config — get current A2A config status
|
|
5
|
+
* POST /v1/integrations/a2a/config — enable A2A channel
|
|
6
|
+
* DELETE /v1/integrations/a2a/config — disable A2A channel
|
|
7
|
+
* POST /v1/integrations/a2a/invite — create a shareable A2A invite token
|
|
8
|
+
* POST /v1/integrations/a2a/invite/complete — sender-side invite completion
|
|
9
|
+
* POST /v1/integrations/a2a/invite/redeem — receiver-side invite redemption
|
|
10
|
+
* POST /v1/integrations/a2a/invite/accept — self-hosted broker: orchestrate complete + redeem
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { isA2AEnabled } from "../../../a2a/feature-gate.js";
|
|
14
|
+
import { getConfig } from "../../../config/loader.js";
|
|
15
|
+
import {
|
|
16
|
+
acceptA2AInvite,
|
|
17
|
+
clearA2AConfig,
|
|
18
|
+
completeA2AInvite,
|
|
19
|
+
createA2AInvite,
|
|
20
|
+
getA2AConfig,
|
|
21
|
+
redeemA2AInvite,
|
|
22
|
+
setA2AConfig,
|
|
23
|
+
} from "../../../daemon/handlers/config-a2a.js";
|
|
24
|
+
import { BadGatewayError, BadRequestError } from "../errors.js";
|
|
25
|
+
import type { RouteDefinition, RouteHandlerArgs } from "../types.js";
|
|
26
|
+
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Helpers
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
function assertA2AFlag(): void {
|
|
32
|
+
if (!isA2AEnabled(getConfig())) {
|
|
33
|
+
throw new BadRequestError("A2A channel is not available");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Handlers
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
function handleGetA2AConfig() {
|
|
42
|
+
assertA2AFlag();
|
|
43
|
+
return getA2AConfig();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function handleSetA2AConfig() {
|
|
47
|
+
assertA2AFlag();
|
|
48
|
+
const result = setA2AConfig();
|
|
49
|
+
if (!result.success) {
|
|
50
|
+
throw new BadRequestError(result.error ?? "Failed to enable A2A");
|
|
51
|
+
}
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function handleClearA2AConfig() {
|
|
56
|
+
assertA2AFlag();
|
|
57
|
+
return clearA2AConfig();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function handleCreateA2AInvite({ body = {} }: RouteHandlerArgs) {
|
|
61
|
+
assertA2AFlag();
|
|
62
|
+
const { expiresInHours } = body as { expiresInHours?: unknown };
|
|
63
|
+
if (expiresInHours !== undefined) {
|
|
64
|
+
if (
|
|
65
|
+
typeof expiresInHours !== "number" ||
|
|
66
|
+
!Number.isFinite(expiresInHours) ||
|
|
67
|
+
expiresInHours <= 0
|
|
68
|
+
) {
|
|
69
|
+
throw new BadRequestError(
|
|
70
|
+
"expiresInHours must be a positive finite number",
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const result = createA2AInvite({
|
|
75
|
+
expiresInHours: expiresInHours as number | undefined,
|
|
76
|
+
});
|
|
77
|
+
if (!result.success) {
|
|
78
|
+
throw new BadRequestError(result.error ?? "Failed to create A2A invite");
|
|
79
|
+
}
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function handleCompleteA2AInvite({ body = {} }: RouteHandlerArgs) {
|
|
84
|
+
const { token, senderAssistantId, acceptor } = body as {
|
|
85
|
+
token?: unknown;
|
|
86
|
+
senderAssistantId?: unknown;
|
|
87
|
+
acceptor?: {
|
|
88
|
+
assistantId?: unknown;
|
|
89
|
+
displayName?: unknown;
|
|
90
|
+
gatewayUrl?: unknown;
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
if (typeof token !== "string" || !token) {
|
|
95
|
+
throw new BadRequestError(
|
|
96
|
+
"token is required and must be a non-empty string",
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
if (typeof senderAssistantId !== "string" || !senderAssistantId) {
|
|
100
|
+
throw new BadRequestError(
|
|
101
|
+
"senderAssistantId is required and must be a non-empty string",
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
if (
|
|
105
|
+
!acceptor ||
|
|
106
|
+
typeof acceptor.assistantId !== "string" ||
|
|
107
|
+
!acceptor.assistantId ||
|
|
108
|
+
typeof acceptor.displayName !== "string" ||
|
|
109
|
+
!acceptor.displayName ||
|
|
110
|
+
typeof acceptor.gatewayUrl !== "string" ||
|
|
111
|
+
!acceptor.gatewayUrl
|
|
112
|
+
) {
|
|
113
|
+
throw new BadRequestError(
|
|
114
|
+
"acceptor must include non-empty assistantId, displayName, and gatewayUrl",
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const result = completeA2AInvite({
|
|
119
|
+
token,
|
|
120
|
+
senderAssistantId,
|
|
121
|
+
acceptor: {
|
|
122
|
+
assistantId: acceptor.assistantId,
|
|
123
|
+
displayName: acceptor.displayName,
|
|
124
|
+
gatewayUrl: acceptor.gatewayUrl,
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
if (!result.success) {
|
|
128
|
+
throw new BadRequestError(result.error ?? "Failed to complete A2A invite");
|
|
129
|
+
}
|
|
130
|
+
return result;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function handleRedeemA2AInvite({ body = {} }: RouteHandlerArgs) {
|
|
134
|
+
const { sender } = body as {
|
|
135
|
+
sender?: {
|
|
136
|
+
assistantId?: unknown;
|
|
137
|
+
displayName?: unknown;
|
|
138
|
+
gatewayUrl?: unknown;
|
|
139
|
+
};
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
if (
|
|
143
|
+
!sender ||
|
|
144
|
+
typeof sender.assistantId !== "string" ||
|
|
145
|
+
!sender.assistantId ||
|
|
146
|
+
typeof sender.displayName !== "string" ||
|
|
147
|
+
!sender.displayName ||
|
|
148
|
+
typeof sender.gatewayUrl !== "string" ||
|
|
149
|
+
!sender.gatewayUrl
|
|
150
|
+
) {
|
|
151
|
+
throw new BadRequestError(
|
|
152
|
+
"sender must include non-empty assistantId, displayName, and gatewayUrl",
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const result = redeemA2AInvite({
|
|
157
|
+
sender: {
|
|
158
|
+
assistantId: sender.assistantId,
|
|
159
|
+
displayName: sender.displayName,
|
|
160
|
+
gatewayUrl: sender.gatewayUrl,
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
if (!result.success) {
|
|
164
|
+
throw new BadRequestError(result.error ?? "Failed to redeem A2A invite");
|
|
165
|
+
}
|
|
166
|
+
return result;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function handleAcceptA2AInvite({ body = {} }: RouteHandlerArgs) {
|
|
170
|
+
assertA2AFlag();
|
|
171
|
+
const { senderGatewayUrl, senderAssistantId, token } = body as {
|
|
172
|
+
senderGatewayUrl?: unknown;
|
|
173
|
+
senderAssistantId?: unknown;
|
|
174
|
+
token?: unknown;
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
if (typeof senderGatewayUrl !== "string" || !senderGatewayUrl) {
|
|
178
|
+
throw new BadRequestError(
|
|
179
|
+
"senderGatewayUrl is required and must be a non-empty string",
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
try {
|
|
183
|
+
new URL(senderGatewayUrl);
|
|
184
|
+
} catch {
|
|
185
|
+
throw new BadRequestError("senderGatewayUrl must be a valid URL");
|
|
186
|
+
}
|
|
187
|
+
if (typeof senderAssistantId !== "string" || !senderAssistantId) {
|
|
188
|
+
throw new BadRequestError(
|
|
189
|
+
"senderAssistantId is required and must be a non-empty string",
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
if (typeof token !== "string" || !token) {
|
|
193
|
+
throw new BadRequestError(
|
|
194
|
+
"token is required and must be a non-empty string",
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const result = await acceptA2AInvite({
|
|
199
|
+
senderGatewayUrl,
|
|
200
|
+
senderAssistantId,
|
|
201
|
+
token,
|
|
202
|
+
});
|
|
203
|
+
if (!result.success) {
|
|
204
|
+
const isSenderFault =
|
|
205
|
+
result.errorCode === "sender_unreachable" ||
|
|
206
|
+
result.errorCode === "complete_failed";
|
|
207
|
+
if (isSenderFault) {
|
|
208
|
+
throw new BadGatewayError(result.error ?? "Failed to accept A2A invite");
|
|
209
|
+
}
|
|
210
|
+
throw new BadRequestError(result.error ?? "Failed to accept A2A invite");
|
|
211
|
+
}
|
|
212
|
+
return result;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ---------------------------------------------------------------------------
|
|
216
|
+
// Route definitions
|
|
217
|
+
// ---------------------------------------------------------------------------
|
|
218
|
+
|
|
219
|
+
export const ROUTES: RouteDefinition[] = [
|
|
220
|
+
{
|
|
221
|
+
operationId: "integrations_a2a_config_get",
|
|
222
|
+
endpoint: "integrations/a2a/config",
|
|
223
|
+
method: "GET",
|
|
224
|
+
summary: "Get A2A config",
|
|
225
|
+
description: "Check current A2A channel configuration status.",
|
|
226
|
+
tags: ["integrations"],
|
|
227
|
+
requirePolicyEnforcement: true,
|
|
228
|
+
handler: () => handleGetA2AConfig(),
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
operationId: "integrations_a2a_config_post",
|
|
232
|
+
endpoint: "integrations/a2a/config",
|
|
233
|
+
method: "POST",
|
|
234
|
+
summary: "Enable A2A channel",
|
|
235
|
+
description: "Enable the A2A channel for inter-assistant communication.",
|
|
236
|
+
tags: ["integrations"],
|
|
237
|
+
requirePolicyEnforcement: true,
|
|
238
|
+
handler: () => handleSetA2AConfig(),
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
operationId: "integrations_a2a_config_delete",
|
|
242
|
+
endpoint: "integrations/a2a/config",
|
|
243
|
+
method: "DELETE",
|
|
244
|
+
summary: "Disable A2A channel",
|
|
245
|
+
description: "Disable the A2A channel.",
|
|
246
|
+
tags: ["integrations"],
|
|
247
|
+
requirePolicyEnforcement: true,
|
|
248
|
+
handler: () => handleClearA2AConfig(),
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
operationId: "integrations_a2a_invite_post",
|
|
252
|
+
endpoint: "integrations/a2a/invite",
|
|
253
|
+
method: "POST",
|
|
254
|
+
summary: "Create A2A invite",
|
|
255
|
+
description:
|
|
256
|
+
"Create a shareable A2A invite token for link-based contact creation.",
|
|
257
|
+
tags: ["integrations"],
|
|
258
|
+
requirePolicyEnforcement: true,
|
|
259
|
+
handler: handleCreateA2AInvite,
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
operationId: "integrations_a2a_invite_complete_post",
|
|
263
|
+
endpoint: "integrations/a2a/invite/complete",
|
|
264
|
+
method: "POST",
|
|
265
|
+
summary: "Complete A2A invite (sender side)",
|
|
266
|
+
description:
|
|
267
|
+
"Called by the platform to finalize the sender side of a link-based A2A connection.",
|
|
268
|
+
tags: ["integrations"],
|
|
269
|
+
requirePolicyEnforcement: true,
|
|
270
|
+
handler: handleCompleteA2AInvite,
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
operationId: "integrations_a2a_invite_redeem_post",
|
|
274
|
+
endpoint: "integrations/a2a/invite/redeem",
|
|
275
|
+
method: "POST",
|
|
276
|
+
summary: "Redeem A2A invite (receiver side)",
|
|
277
|
+
description:
|
|
278
|
+
"Called by the platform to create a trusted contact on the receiver side of a link-based A2A connection.",
|
|
279
|
+
tags: ["integrations"],
|
|
280
|
+
requirePolicyEnforcement: true,
|
|
281
|
+
handler: handleRedeemA2AInvite,
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
operationId: "integrations_a2a_invite_accept_post",
|
|
285
|
+
endpoint: "integrations/a2a/invite/accept",
|
|
286
|
+
method: "POST",
|
|
287
|
+
summary: "Accept A2A invite (self-hosted broker)",
|
|
288
|
+
description:
|
|
289
|
+
"Orchestrate cross-daemon invite acceptance for self-hosted deployments. Calls the sender's invite/complete, then creates a local contact via invite/redeem.",
|
|
290
|
+
tags: ["integrations"],
|
|
291
|
+
requirePolicyEnforcement: true,
|
|
292
|
+
handler: handleAcceptA2AInvite,
|
|
293
|
+
},
|
|
294
|
+
];
|
|
@@ -1,10 +1,20 @@
|
|
|
1
|
+
import { resolveDefaultProfileKey } from "../../config/llm-resolver.js";
|
|
2
|
+
import { loadConfig } from "../../config/loader.js";
|
|
1
3
|
import { CALL_SITE_CATALOG, CALL_SITE_DOMAINS } from "../../config/schemas/call-site-catalog.js";
|
|
4
|
+
import type { LLMCallSite } from "../../config/schemas/llm.js";
|
|
2
5
|
import type { RouteDefinition } from "./types.js";
|
|
3
6
|
|
|
4
7
|
async function handleGetCallSites() {
|
|
8
|
+
const { llm } = loadConfig();
|
|
5
9
|
return {
|
|
6
10
|
domains: CALL_SITE_DOMAINS,
|
|
7
|
-
callSites: CALL_SITE_CATALOG
|
|
11
|
+
callSites: CALL_SITE_CATALOG.map((entry) => ({
|
|
12
|
+
...entry,
|
|
13
|
+
defaultProfile: resolveDefaultProfileKey(
|
|
14
|
+
entry.id as LLMCallSite,
|
|
15
|
+
llm,
|
|
16
|
+
),
|
|
17
|
+
})),
|
|
8
18
|
};
|
|
9
19
|
}
|
|
10
20
|
|
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
getWorkspaceConfigPath,
|
|
37
37
|
} from "../../util/platform.js";
|
|
38
38
|
import { APP_VERSION, COMMIT_SHA } from "../../version.js";
|
|
39
|
+
import { assistantEventHub } from "../assistant-event-hub.js";
|
|
39
40
|
import { createTarGz } from "./archive-utils.js";
|
|
40
41
|
import { InternalError } from "./errors.js";
|
|
41
42
|
import { collectWorkspaceData } from "./log-export/workspace-allowlist.js";
|
|
@@ -270,6 +271,44 @@ async function handleExport({
|
|
|
270
271
|
);
|
|
271
272
|
}
|
|
272
273
|
|
|
274
|
+
// --- Connected clients (`assistant clients list --json`) ---
|
|
275
|
+
try {
|
|
276
|
+
const clients = assistantEventHub.listClients();
|
|
277
|
+
const clientsList = {
|
|
278
|
+
clients: clients.map((c) => ({
|
|
279
|
+
clientId: c.clientId,
|
|
280
|
+
interfaceId: c.interfaceId,
|
|
281
|
+
capabilities: c.capabilities,
|
|
282
|
+
machineName: c.machineName,
|
|
283
|
+
connectedAt: c.connectedAt.toISOString(),
|
|
284
|
+
lastActiveAt: c.lastActiveAt.toISOString(),
|
|
285
|
+
})),
|
|
286
|
+
};
|
|
287
|
+
writeFileSync(
|
|
288
|
+
join(staging, "clients-list.json"),
|
|
289
|
+
JSON.stringify(clientsList, null, 2),
|
|
290
|
+
"utf-8",
|
|
291
|
+
);
|
|
292
|
+
} catch (err) {
|
|
293
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
294
|
+
writeFileSync(
|
|
295
|
+
join(staging, "clients-list-error.json"),
|
|
296
|
+
JSON.stringify(
|
|
297
|
+
{
|
|
298
|
+
error: message,
|
|
299
|
+
collectedAt: new Date().toISOString(),
|
|
300
|
+
},
|
|
301
|
+
null,
|
|
302
|
+
2,
|
|
303
|
+
),
|
|
304
|
+
"utf-8",
|
|
305
|
+
);
|
|
306
|
+
log.warn(
|
|
307
|
+
{ err },
|
|
308
|
+
"Failed to collect connected clients list, continuing without it",
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
|
|
273
312
|
// --- Export manifest ---
|
|
274
313
|
const manifestType = conversationId
|
|
275
314
|
? ("conversation-export" as const)
|