@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
|
@@ -18,7 +18,14 @@
|
|
|
18
18
|
* resolve the interaction.
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
+
import type { InteractionResolutionState } from "../daemon/message-types/messages.js";
|
|
21
22
|
import type { UserDecision } from "../permissions/types.js";
|
|
23
|
+
import { getLogger } from "../util/logger.js";
|
|
24
|
+
import { broadcastMessage } from "./assistant-event-hub.js";
|
|
25
|
+
|
|
26
|
+
const log = getLogger("pending-interactions");
|
|
27
|
+
|
|
28
|
+
export type { InteractionResolutionState } from "../daemon/message-types/messages.js";
|
|
22
29
|
|
|
23
30
|
export interface ConfirmationDetails {
|
|
24
31
|
toolName: string;
|
|
@@ -98,17 +105,50 @@ export function register(
|
|
|
98
105
|
* Remove and return the pending interaction for the given requestId.
|
|
99
106
|
* Auto-clears the proxy timer and detaches the abort listener if present.
|
|
100
107
|
* Returns undefined if no interaction is registered.
|
|
108
|
+
*
|
|
109
|
+
* Emits `interaction_resolved` on the event hub when an interaction is
|
|
110
|
+
* actually removed (no-op when the entry was already consumed by another
|
|
111
|
+
* path). Callers pass `state` to communicate the lifecycle outcome
|
|
112
|
+
* — defaults to `"cancelled"`, the safest value when the call site has
|
|
113
|
+
* no extra context.
|
|
101
114
|
*/
|
|
102
|
-
export function resolve(
|
|
115
|
+
export function resolve(
|
|
116
|
+
requestId: string,
|
|
117
|
+
state: InteractionResolutionState = "cancelled",
|
|
118
|
+
): PendingInteraction | undefined {
|
|
103
119
|
const interaction = pending.get(requestId);
|
|
104
|
-
if (interaction)
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
120
|
+
if (!interaction) return undefined;
|
|
121
|
+
pending.delete(requestId);
|
|
122
|
+
if (interaction.timer != null) clearTimeout(interaction.timer);
|
|
123
|
+
interaction.detachAbort?.();
|
|
124
|
+
emitResolved(requestId, interaction, state);
|
|
109
125
|
return interaction;
|
|
110
126
|
}
|
|
111
127
|
|
|
128
|
+
function emitResolved(
|
|
129
|
+
requestId: string,
|
|
130
|
+
interaction: PendingInteraction,
|
|
131
|
+
state: InteractionResolutionState,
|
|
132
|
+
): void {
|
|
133
|
+
log.info(
|
|
134
|
+
{
|
|
135
|
+
requestId,
|
|
136
|
+
conversationId: interaction.conversationId,
|
|
137
|
+
kind: interaction.kind,
|
|
138
|
+
state,
|
|
139
|
+
},
|
|
140
|
+
"Pending interaction resolved",
|
|
141
|
+
);
|
|
142
|
+
broadcastMessage({
|
|
143
|
+
type: "interaction_resolved",
|
|
144
|
+
requestId,
|
|
145
|
+
conversationId: interaction.conversationId,
|
|
146
|
+
conversationKey: interaction.conversationId,
|
|
147
|
+
kind: interaction.kind,
|
|
148
|
+
state,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
112
152
|
/**
|
|
113
153
|
* Return the pending interaction without removing it.
|
|
114
154
|
* Used by trust-rule endpoint which doesn't resolve the confirmation itself.
|
|
@@ -146,7 +186,10 @@ export function getByConversation(
|
|
|
146
186
|
* /v1/host-transfer-result after completing the operation, get a 404, and the
|
|
147
187
|
* proxy timer would fire with a spurious timeout error.
|
|
148
188
|
*/
|
|
149
|
-
export function removeByConversation(
|
|
189
|
+
export function removeByConversation(
|
|
190
|
+
conversationId: string,
|
|
191
|
+
state: InteractionResolutionState = "superseded",
|
|
192
|
+
): void {
|
|
150
193
|
// Snapshot keys to avoid mutation-during-iteration.
|
|
151
194
|
for (const [requestId, interaction] of [...pending]) {
|
|
152
195
|
if (
|
|
@@ -160,7 +203,7 @@ export function removeByConversation(conversationId: string): void {
|
|
|
160
203
|
interaction.kind !== "acp_confirmation"
|
|
161
204
|
) {
|
|
162
205
|
// resolve() clears the stored timer and detaches abort listeners.
|
|
163
|
-
resolve(requestId);
|
|
206
|
+
resolve(requestId, state);
|
|
164
207
|
}
|
|
165
208
|
}
|
|
166
209
|
}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Asserts `listConsolidationRuns` maps background-conversation rows tagged
|
|
3
|
+
* with `source = MEMORY_V2_CONSOLIDATION_SOURCE` into the heartbeat-runs
|
|
4
|
+
* response shape, derives `status` / `finishedAt` / `durationMs` from
|
|
5
|
+
* **assistant-message presence** (not `lastMessageAt`), and clamps the
|
|
6
|
+
* `limit` query param.
|
|
7
|
+
*
|
|
8
|
+
* Synthetic-field semantics covered here:
|
|
9
|
+
* - `id` and `conversationId` both equal the conversation row's id.
|
|
10
|
+
* - `scheduledFor` and `startedAt` both equal `conversation.createdAt`
|
|
11
|
+
* (no separate schedule timestamp on the row).
|
|
12
|
+
* - `finishedAt` is the `createdAt` of the LATEST assistant message,
|
|
13
|
+
* NOT `conversation.lastMessageAt` — the kickoff user prompt bumps
|
|
14
|
+
* `lastMessageAt` before the agent runs, so it cannot be used as a
|
|
15
|
+
* completion signal.
|
|
16
|
+
* - `durationMs` is `finishedAt − startedAt` when both are present, else
|
|
17
|
+
* null.
|
|
18
|
+
* - `status` is `"ok"` when the conversation has at least one assistant
|
|
19
|
+
* message (positive evidence the agent emitted output) and `"running"`
|
|
20
|
+
* otherwise — including the case where only the kickoff user prompt
|
|
21
|
+
* has been persisted.
|
|
22
|
+
* - `skipReason` and `error` are always null — the conversation row
|
|
23
|
+
* alone cannot distinguish a clean run from a mid-flight crash even
|
|
24
|
+
* once assistant output exists.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
28
|
+
|
|
29
|
+
mock.module("../../../util/logger.js", () => ({
|
|
30
|
+
getLogger: () =>
|
|
31
|
+
new Proxy({} as Record<string, unknown>, {
|
|
32
|
+
get: () => () => {},
|
|
33
|
+
}),
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
import { createConversation } from "../../../memory/conversation-crud.js";
|
|
37
|
+
import { getDb } from "../../../memory/db-connection.js";
|
|
38
|
+
import { initializeDb } from "../../../memory/db-init.js";
|
|
39
|
+
import { rawRun } from "../../../memory/raw-query.js";
|
|
40
|
+
import { ROUTES } from "../consolidation-routes.js";
|
|
41
|
+
import type { RouteDefinition } from "../types.js";
|
|
42
|
+
|
|
43
|
+
initializeDb();
|
|
44
|
+
|
|
45
|
+
function resetTables(): void {
|
|
46
|
+
const db = getDb();
|
|
47
|
+
db.run(`DELETE FROM messages`);
|
|
48
|
+
db.run(`DELETE FROM conversations`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function findHandler(operationId: string): RouteDefinition["handler"] {
|
|
52
|
+
const route = ROUTES.find((r) => r.operationId === operationId);
|
|
53
|
+
if (!route) throw new Error(`Route ${operationId} not found`);
|
|
54
|
+
return route.handler;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function insertMessage(
|
|
58
|
+
conversationId: string,
|
|
59
|
+
role: string,
|
|
60
|
+
createdAt: number,
|
|
61
|
+
): void {
|
|
62
|
+
rawRun(
|
|
63
|
+
"INSERT INTO messages (id, conversation_id, role, content, created_at) VALUES (?, ?, ?, ?, ?)",
|
|
64
|
+
`msg-${conversationId}-${role}-${createdAt}`,
|
|
65
|
+
conversationId,
|
|
66
|
+
role,
|
|
67
|
+
"x",
|
|
68
|
+
createdAt,
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
interface RunRecord {
|
|
73
|
+
id: string;
|
|
74
|
+
scheduledFor: number;
|
|
75
|
+
startedAt: number | null;
|
|
76
|
+
finishedAt: number | null;
|
|
77
|
+
durationMs: number | null;
|
|
78
|
+
status: "ok" | "running";
|
|
79
|
+
skipReason: string | null;
|
|
80
|
+
error: string | null;
|
|
81
|
+
conversationId: string | null;
|
|
82
|
+
createdAt: number;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
interface ListRunsResponse {
|
|
86
|
+
runs: RunRecord[];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
describe("listConsolidationRuns handler", () => {
|
|
90
|
+
beforeEach(() => {
|
|
91
|
+
resetTables();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("returns only conversations sourced from memory_v2_consolidation", async () => {
|
|
95
|
+
createConversation({ title: "c1", source: "memory_v2_consolidation" });
|
|
96
|
+
createConversation({ title: "h1", source: "heartbeat" });
|
|
97
|
+
createConversation({ title: "u1", source: "user" });
|
|
98
|
+
|
|
99
|
+
const handler = findHandler("listConsolidationRuns");
|
|
100
|
+
const result = (await handler({})) as ListRunsResponse;
|
|
101
|
+
|
|
102
|
+
expect(result.runs).toHaveLength(1);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("synthesizes status='ok' with finishedAt from latest assistant message", async () => {
|
|
106
|
+
const conv = createConversation({
|
|
107
|
+
title: "c1",
|
|
108
|
+
source: "memory_v2_consolidation",
|
|
109
|
+
});
|
|
110
|
+
rawRun(
|
|
111
|
+
"UPDATE conversations SET created_at = ? WHERE id = ?",
|
|
112
|
+
1000,
|
|
113
|
+
conv.id,
|
|
114
|
+
);
|
|
115
|
+
// Kickoff user prompt at t=1100 (bumps lastMessageAt — must NOT be
|
|
116
|
+
// mistaken for completion).
|
|
117
|
+
insertMessage(conv.id, "user", 1100);
|
|
118
|
+
// Agent's first assistant turn at t=2000.
|
|
119
|
+
insertMessage(conv.id, "assistant", 2000);
|
|
120
|
+
// Agent's final assistant turn at t=2500.
|
|
121
|
+
insertMessage(conv.id, "assistant", 2500);
|
|
122
|
+
|
|
123
|
+
const handler = findHandler("listConsolidationRuns");
|
|
124
|
+
const result = (await handler({})) as ListRunsResponse;
|
|
125
|
+
|
|
126
|
+
expect(result.runs).toHaveLength(1);
|
|
127
|
+
const run = result.runs[0]!;
|
|
128
|
+
expect(run.id).toBe(conv.id);
|
|
129
|
+
expect(run.conversationId).toBe(conv.id);
|
|
130
|
+
expect(run.status).toBe("ok");
|
|
131
|
+
expect(run.scheduledFor).toBe(1000);
|
|
132
|
+
expect(run.startedAt).toBe(1000);
|
|
133
|
+
// finishedAt = createdAt of LATEST assistant message (2500), NOT
|
|
134
|
+
// the conversation's lastMessageAt (which sqlite triggers may or may
|
|
135
|
+
// not have updated here — irrelevant to this endpoint).
|
|
136
|
+
expect(run.finishedAt).toBe(2500);
|
|
137
|
+
expect(run.durationMs).toBe(1500);
|
|
138
|
+
expect(run.createdAt).toBe(1000);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("synthesizes status='running' when conversation has no assistant message", async () => {
|
|
142
|
+
createConversation({ title: "c1", source: "memory_v2_consolidation" });
|
|
143
|
+
|
|
144
|
+
const handler = findHandler("listConsolidationRuns");
|
|
145
|
+
const result = (await handler({})) as ListRunsResponse;
|
|
146
|
+
|
|
147
|
+
expect(result.runs).toHaveLength(1);
|
|
148
|
+
const run = result.runs[0]!;
|
|
149
|
+
expect(run.status).toBe("running");
|
|
150
|
+
expect(run.finishedAt).toBeNull();
|
|
151
|
+
expect(run.durationMs).toBeNull();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("status stays 'running' when only the kickoff user prompt exists (Codex bug regression guard)", async () => {
|
|
155
|
+
// Regression guard for the original `status from lastMessageAt`
|
|
156
|
+
// heuristic. `processMessage` persists the background kickoff prompt as
|
|
157
|
+
// a user message BEFORE the agent runs, which bumps
|
|
158
|
+
// `conversation.lastMessageAt`. A run that timed out / threw before
|
|
159
|
+
// emitting any assistant turn must still report status='running' (or
|
|
160
|
+
// an explicit failure status once one exists) — never 'ok'.
|
|
161
|
+
const conv = createConversation({
|
|
162
|
+
title: "c1",
|
|
163
|
+
source: "memory_v2_consolidation",
|
|
164
|
+
});
|
|
165
|
+
rawRun(
|
|
166
|
+
"UPDATE conversations SET created_at = ?, last_message_at = ? WHERE id = ?",
|
|
167
|
+
1000,
|
|
168
|
+
1100,
|
|
169
|
+
conv.id,
|
|
170
|
+
);
|
|
171
|
+
insertMessage(conv.id, "user", 1100);
|
|
172
|
+
|
|
173
|
+
const handler = findHandler("listConsolidationRuns");
|
|
174
|
+
const result = (await handler({})) as ListRunsResponse;
|
|
175
|
+
|
|
176
|
+
expect(result.runs).toHaveLength(1);
|
|
177
|
+
const run = result.runs[0]!;
|
|
178
|
+
expect(run.status).toBe("running");
|
|
179
|
+
expect(run.finishedAt).toBeNull();
|
|
180
|
+
expect(run.durationMs).toBeNull();
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test("skipReason and error are always null (not derivable from conversation row)", async () => {
|
|
184
|
+
const conv = createConversation({
|
|
185
|
+
title: "c1",
|
|
186
|
+
source: "memory_v2_consolidation",
|
|
187
|
+
});
|
|
188
|
+
insertMessage(conv.id, "assistant", 2000);
|
|
189
|
+
|
|
190
|
+
const handler = findHandler("listConsolidationRuns");
|
|
191
|
+
const result = (await handler({})) as ListRunsResponse;
|
|
192
|
+
|
|
193
|
+
expect(result.runs[0]!.skipReason).toBeNull();
|
|
194
|
+
expect(result.runs[0]!.error).toBeNull();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test("orders runs by createdAt descending", async () => {
|
|
198
|
+
const a = createConversation({
|
|
199
|
+
title: "a",
|
|
200
|
+
source: "memory_v2_consolidation",
|
|
201
|
+
});
|
|
202
|
+
const b = createConversation({
|
|
203
|
+
title: "b",
|
|
204
|
+
source: "memory_v2_consolidation",
|
|
205
|
+
});
|
|
206
|
+
const c = createConversation({
|
|
207
|
+
title: "c",
|
|
208
|
+
source: "memory_v2_consolidation",
|
|
209
|
+
});
|
|
210
|
+
rawRun("UPDATE conversations SET created_at = ? WHERE id = ?", 1000, a.id);
|
|
211
|
+
rawRun("UPDATE conversations SET created_at = ? WHERE id = ?", 3000, b.id);
|
|
212
|
+
rawRun("UPDATE conversations SET created_at = ? WHERE id = ?", 2000, c.id);
|
|
213
|
+
|
|
214
|
+
const handler = findHandler("listConsolidationRuns");
|
|
215
|
+
const result = (await handler({})) as ListRunsResponse;
|
|
216
|
+
|
|
217
|
+
expect(result.runs.map((r) => r.id)).toEqual([b.id, c.id, a.id]);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test("limit defaults to 20, clamps to [1, 100], and falls back on non-numeric input", async () => {
|
|
221
|
+
for (let i = 0; i < 5; i++) {
|
|
222
|
+
createConversation({
|
|
223
|
+
title: `c${i}`,
|
|
224
|
+
source: "memory_v2_consolidation",
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const handler = findHandler("listConsolidationRuns");
|
|
229
|
+
|
|
230
|
+
// Default — all 5 returned (under the 20 default).
|
|
231
|
+
const def = (await handler({})) as ListRunsResponse;
|
|
232
|
+
expect(def.runs).toHaveLength(5);
|
|
233
|
+
|
|
234
|
+
// Explicit limit honored.
|
|
235
|
+
const lim2 = (await handler({
|
|
236
|
+
queryParams: { limit: "2" },
|
|
237
|
+
})) as ListRunsResponse;
|
|
238
|
+
expect(lim2.runs).toHaveLength(2);
|
|
239
|
+
|
|
240
|
+
// Negative clamps to 1.
|
|
241
|
+
const neg = (await handler({
|
|
242
|
+
queryParams: { limit: "-5" },
|
|
243
|
+
})) as ListRunsResponse;
|
|
244
|
+
expect(neg.runs).toHaveLength(1);
|
|
245
|
+
|
|
246
|
+
// Zero clamps to 1.
|
|
247
|
+
const zero = (await handler({
|
|
248
|
+
queryParams: { limit: "0" },
|
|
249
|
+
})) as ListRunsResponse;
|
|
250
|
+
expect(zero.runs).toHaveLength(1);
|
|
251
|
+
|
|
252
|
+
// Non-numeric falls back to the default (20 → all 5 here).
|
|
253
|
+
const bad = (await handler({
|
|
254
|
+
queryParams: { limit: "garbage" },
|
|
255
|
+
})) as ListRunsResponse;
|
|
256
|
+
expect(bad.runs).toHaveLength(5);
|
|
257
|
+
});
|
|
258
|
+
});
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for content-source-routes.ts.
|
|
3
|
+
*
|
|
4
|
+
* Drives the handler function directly (bypassing the router) and mocks
|
|
5
|
+
* out node:fs writes so no real I/O occurs.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Module mocks — must appear before any imports of the module under test
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
mock.module("../../../util/logger.js", () => ({
|
|
15
|
+
getLogger: () =>
|
|
16
|
+
new Proxy({} as Record<string, unknown>, {
|
|
17
|
+
get: () => () => {},
|
|
18
|
+
}),
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
const FAKE_WORKSPACE = "/tmp/content-source-routes-test-workspace";
|
|
22
|
+
|
|
23
|
+
mock.module("../../../util/platform.js", () => ({
|
|
24
|
+
getWorkspaceDir: () => FAKE_WORKSPACE,
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
const writtenFiles = new Map<string, string>();
|
|
28
|
+
|
|
29
|
+
mock.module("node:fs", () => ({
|
|
30
|
+
mkdirSync: () => {},
|
|
31
|
+
writeFileSync: (path: string, content: string) => {
|
|
32
|
+
writtenFiles.set(path, content);
|
|
33
|
+
},
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Imports after mocks
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
import { ROUTES } from "../content-source-routes.js";
|
|
41
|
+
import type { RouteDefinition, RouteHandlerArgs } from "../types.js";
|
|
42
|
+
|
|
43
|
+
afterAll(() => {
|
|
44
|
+
mock.restore();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// Helpers
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
function findHandler(operationId: string): RouteDefinition["handler"] {
|
|
52
|
+
const route = ROUTES.find((r) => r.operationId === operationId);
|
|
53
|
+
if (!route) throw new Error(`Route ${operationId} not found`);
|
|
54
|
+
return route.handler;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function makeArgs(body?: Record<string, unknown>): RouteHandlerArgs {
|
|
58
|
+
return { body };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const handler = findHandler("content_source_set");
|
|
62
|
+
|
|
63
|
+
beforeEach(() => {
|
|
64
|
+
writtenFiles.clear();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
// URL validation
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
|
|
71
|
+
describe("content_source_set — URL validation", () => {
|
|
72
|
+
test("https URL is accepted and sidecar written", () => {
|
|
73
|
+
const result = handler(makeArgs({ url: "https://myblog.com/posts" }));
|
|
74
|
+
expect(result).toEqual({ ok: true });
|
|
75
|
+
|
|
76
|
+
const expectedPath = `${FAKE_WORKSPACE}/data/content-source.json`;
|
|
77
|
+
expect(writtenFiles.has(expectedPath)).toBe(true);
|
|
78
|
+
const written = JSON.parse(writtenFiles.get(expectedPath)!);
|
|
79
|
+
expect(written.url).toBe("https://myblog.com/posts");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("http URL is accepted and sidecar written", () => {
|
|
83
|
+
const result = handler(makeArgs({ url: "http://intranet.example.com" }));
|
|
84
|
+
expect(result).toEqual({ ok: true });
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("URL with leading/trailing whitespace is trimmed", () => {
|
|
88
|
+
const result = handler(makeArgs({ url: " https://blog.example.com " }));
|
|
89
|
+
expect(result).toEqual({ ok: true });
|
|
90
|
+
|
|
91
|
+
const expectedPath = `${FAKE_WORKSPACE}/data/content-source.json`;
|
|
92
|
+
const written = JSON.parse(writtenFiles.get(expectedPath)!);
|
|
93
|
+
expect(written.url).toBe("https://blog.example.com/");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("bare hostname without protocol returns invalid_url", () => {
|
|
97
|
+
const result = handler(makeArgs({ url: "myblog.com" }));
|
|
98
|
+
expect(result).toEqual({ ok: false, error: "invalid_url" });
|
|
99
|
+
expect(writtenFiles.size).toBe(0);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("ftp:// URL is rejected", () => {
|
|
103
|
+
const result = handler(makeArgs({ url: "ftp://files.example.com" }));
|
|
104
|
+
expect(result).toEqual({ ok: false, error: "invalid_url" });
|
|
105
|
+
expect(writtenFiles.size).toBe(0);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("empty string returns invalid_url", () => {
|
|
109
|
+
const result = handler(makeArgs({ url: "" }));
|
|
110
|
+
expect(result).toEqual({ ok: false, error: "invalid_url" });
|
|
111
|
+
expect(writtenFiles.size).toBe(0);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("whitespace-only string returns invalid_url", () => {
|
|
115
|
+
const result = handler(makeArgs({ url: " " }));
|
|
116
|
+
expect(result).toEqual({ ok: false, error: "invalid_url" });
|
|
117
|
+
expect(writtenFiles.size).toBe(0);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("javascript: URL is rejected", () => {
|
|
121
|
+
const result = handler(makeArgs({ url: "javascript:alert(1)" }));
|
|
122
|
+
expect(result).toEqual({ ok: false, error: "invalid_url" });
|
|
123
|
+
expect(writtenFiles.size).toBe(0);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("missing url field returns invalid_url", () => {
|
|
127
|
+
const result = handler(makeArgs({}));
|
|
128
|
+
expect(result).toEqual({ ok: false, error: "invalid_url" });
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
// Sidecar content verification
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
|
|
136
|
+
describe("content_source_set — sidecar contents", () => {
|
|
137
|
+
test("writes url to data/content-source.json", () => {
|
|
138
|
+
handler(makeArgs({ url: "https://example.com/blog" }));
|
|
139
|
+
|
|
140
|
+
const expectedPath = `${FAKE_WORKSPACE}/data/content-source.json`;
|
|
141
|
+
expect(writtenFiles.has(expectedPath)).toBe(true);
|
|
142
|
+
|
|
143
|
+
const written = JSON.parse(writtenFiles.get(expectedPath)!);
|
|
144
|
+
expect(Object.keys(written)).toEqual(["url"]);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("no sidecar written on invalid URL", () => {
|
|
148
|
+
handler(makeArgs({ url: "not-a-url" }));
|
|
149
|
+
expect(writtenFiles.size).toBe(0);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// ---------------------------------------------------------------------------
|
|
154
|
+
// Policy key verification
|
|
155
|
+
// ---------------------------------------------------------------------------
|
|
156
|
+
|
|
157
|
+
describe("route policy key", () => {
|
|
158
|
+
test("content_source_set uses policyKey: secrets", () => {
|
|
159
|
+
const route = ROUTES.find((r) => r.operationId === "content_source_set");
|
|
160
|
+
expect(route?.policyKey).toBe("secrets");
|
|
161
|
+
});
|
|
162
|
+
});
|
|
@@ -71,6 +71,10 @@ import {
|
|
|
71
71
|
memoryV2ActivationLogs,
|
|
72
72
|
messages,
|
|
73
73
|
} from "../../../memory/schema.js";
|
|
74
|
+
import {
|
|
75
|
+
createConnection,
|
|
76
|
+
getConnection,
|
|
77
|
+
} from "../../../providers/inference/connections.js";
|
|
74
78
|
import { ROUTES } from "../conversation-query-routes.js";
|
|
75
79
|
|
|
76
80
|
// Local subset: this test only exercises a single concept row.
|
|
@@ -137,7 +141,11 @@ function seedConversationAndMessage(args: {
|
|
|
137
141
|
.run();
|
|
138
142
|
}
|
|
139
143
|
|
|
140
|
-
function seedRequestLog(
|
|
144
|
+
function seedRequestLog(
|
|
145
|
+
messageId: string,
|
|
146
|
+
id: string,
|
|
147
|
+
options: { agentLoopExitReason?: string | null } = {},
|
|
148
|
+
): void {
|
|
141
149
|
getDb()
|
|
142
150
|
.insert(llmRequestLogs)
|
|
143
151
|
.values({
|
|
@@ -150,6 +158,9 @@ function seedRequestLog(messageId: string, id: string): void {
|
|
|
150
158
|
choices: [{ message: { content: "hi" } }],
|
|
151
159
|
}),
|
|
152
160
|
createdAt: 1_700_000_000_000,
|
|
161
|
+
...(options.agentLoopExitReason != null
|
|
162
|
+
? { agentLoopExitReason: options.agentLoopExitReason }
|
|
163
|
+
: {}),
|
|
153
164
|
})
|
|
154
165
|
.run();
|
|
155
166
|
}
|
|
@@ -313,6 +324,53 @@ describe("GET /v1/messages/:id/llm-context — conversationTotalEstimatedCostUsd
|
|
|
313
324
|
});
|
|
314
325
|
});
|
|
315
326
|
|
|
327
|
+
describe("GET /v1/messages/:id/llm-context — agentLoopExitReason", () => {
|
|
328
|
+
beforeEach(() => {
|
|
329
|
+
clearTables();
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
test("surfaces the stamped agent_loop_exit_reason on the terminal log", async () => {
|
|
333
|
+
const messageId = "msg-with-exit";
|
|
334
|
+
seedConversationAndMessage({
|
|
335
|
+
conversationId: "conv-1",
|
|
336
|
+
messageId,
|
|
337
|
+
source: "user",
|
|
338
|
+
conversationType: "standard",
|
|
339
|
+
});
|
|
340
|
+
// Two logs in the same turn — only the terminal one is stamped.
|
|
341
|
+
seedRequestLog(messageId, "log-non-terminal");
|
|
342
|
+
seedRequestLog(messageId, "log-terminal", {
|
|
343
|
+
agentLoopExitReason: "no_tool_calls",
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
const body = (await dispatchLlmContext(messageId)) as {
|
|
347
|
+
logs: Array<{ id: string; agentLoopExitReason: string | null }>;
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
const byId = new Map(body.logs.map((l) => [l.id, l.agentLoopExitReason]));
|
|
351
|
+
expect(byId.get("log-non-terminal")).toBeNull();
|
|
352
|
+
expect(byId.get("log-terminal")).toBe("no_tool_calls");
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
test("returns null when no log in the turn has been stamped", async () => {
|
|
356
|
+
const messageId = "msg-no-exit";
|
|
357
|
+
seedConversationAndMessage({
|
|
358
|
+
conversationId: "conv-1",
|
|
359
|
+
messageId,
|
|
360
|
+
source: "user",
|
|
361
|
+
conversationType: "standard",
|
|
362
|
+
});
|
|
363
|
+
seedRequestLog(messageId, "log-unstamped");
|
|
364
|
+
|
|
365
|
+
const body = (await dispatchLlmContext(messageId)) as {
|
|
366
|
+
logs: Array<{ id: string; agentLoopExitReason: string | null }>;
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
expect(body.logs).toHaveLength(1);
|
|
370
|
+
expect(body.logs[0]!.agentLoopExitReason).toBeNull();
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
|
|
316
374
|
describe("PUT /v1/config/llm/profiles/:name", () => {
|
|
317
375
|
beforeEach(() => {
|
|
318
376
|
savedRawConfig = null;
|
|
@@ -427,7 +485,7 @@ describe("PUT /v1/config/llm/profiles/:name", () => {
|
|
|
427
485
|
expect(savedProfile.provider_connection).toBe("personal-openai");
|
|
428
486
|
});
|
|
429
487
|
|
|
430
|
-
test("
|
|
488
|
+
test("auto-derives provider_connection when omitted from body (Any active)", async () => {
|
|
431
489
|
// Seed an existing binding so the test starts from a non-empty state.
|
|
432
490
|
(
|
|
433
491
|
rawConfigFixture.llm as {
|
|
@@ -441,8 +499,37 @@ describe("PUT /v1/config/llm/profiles/:name", () => {
|
|
|
441
499
|
provider: "openai",
|
|
442
500
|
model: "gpt-5.5",
|
|
443
501
|
// provider_connection deliberately omitted — the UI cleared the
|
|
444
|
-
// picker back to "Any active"
|
|
445
|
-
//
|
|
502
|
+
// picker back to "Any active". The route auto-derives an active
|
|
503
|
+
// connection for the provider to prevent stale inheritance during
|
|
504
|
+
// config deep-merge.
|
|
505
|
+
},
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
expect(result).toEqual({ ok: true });
|
|
509
|
+
const savedProfile = (
|
|
510
|
+
savedRawConfig?.llm as {
|
|
511
|
+
profiles: Record<string, Record<string, unknown>>;
|
|
512
|
+
}
|
|
513
|
+
).profiles.custom;
|
|
514
|
+
|
|
515
|
+
// The canonical "openai-managed" connection exists in the test DB;
|
|
516
|
+
// the route auto-derives it when the UI omits provider_connection.
|
|
517
|
+
expect(savedProfile.provider_connection).toBe("openai-managed");
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
test("auto-derives provider_connection for BYOK provider (Any active)", async () => {
|
|
521
|
+
// Seed a fireworks connection in the DB.
|
|
522
|
+
createConnection(getDb(), {
|
|
523
|
+
name: "fireworks",
|
|
524
|
+
provider: "fireworks",
|
|
525
|
+
auth: { type: "api_key", credential: "fireworks:api_key" },
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
const result = await replaceProfileRoute.handler({
|
|
529
|
+
pathParams: { name: "custom" },
|
|
530
|
+
body: {
|
|
531
|
+
provider: "fireworks",
|
|
532
|
+
model: "accounts/fireworks/models/llama-v3p1-8b-instruct",
|
|
446
533
|
},
|
|
447
534
|
});
|
|
448
535
|
|
|
@@ -453,7 +540,36 @@ describe("PUT /v1/config/llm/profiles/:name", () => {
|
|
|
453
540
|
}
|
|
454
541
|
).profiles.custom;
|
|
455
542
|
|
|
456
|
-
expect(savedProfile.
|
|
543
|
+
expect(savedProfile.provider).toBe("fireworks");
|
|
544
|
+
expect(savedProfile.provider_connection).toBe("fireworks");
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
test("auto-creates provider_connection when no connection exists for provider", async () => {
|
|
548
|
+
const result = await replaceProfileRoute.handler({
|
|
549
|
+
pathParams: { name: "custom" },
|
|
550
|
+
body: {
|
|
551
|
+
provider: "openrouter",
|
|
552
|
+
model: "anthropic/claude-sonnet-4-6",
|
|
553
|
+
},
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
expect(result).toEqual({ ok: true });
|
|
557
|
+
const savedProfile = (
|
|
558
|
+
savedRawConfig?.llm as {
|
|
559
|
+
profiles: Record<string, Record<string, unknown>>;
|
|
560
|
+
}
|
|
561
|
+
).profiles.custom;
|
|
562
|
+
|
|
563
|
+
expect(savedProfile.provider).toBe("openrouter");
|
|
564
|
+
expect(savedProfile.provider_connection).toBe("openrouter-personal");
|
|
565
|
+
|
|
566
|
+
const conn = getConnection(getDb(), "openrouter-personal");
|
|
567
|
+
expect(conn).not.toBeNull();
|
|
568
|
+
expect(conn!.provider).toBe("openrouter");
|
|
569
|
+
expect(conn!.auth).toEqual({
|
|
570
|
+
type: "api_key",
|
|
571
|
+
credential: "credential/openrouter/api_key",
|
|
572
|
+
});
|
|
457
573
|
});
|
|
458
574
|
|
|
459
575
|
describe("managed profile guard", () => {
|