@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
|
@@ -78,7 +78,7 @@ describe("handleListMessages attachments", () => {
|
|
|
78
78
|
const stored = uploadAttachment("photo.png", "image/png", IMAGE_BASE64);
|
|
79
79
|
linkAttachmentToMessage(msg.id, stored.id, 0);
|
|
80
80
|
|
|
81
|
-
const response = handleListMessages(createTestArgs(conv.id)
|
|
81
|
+
const response = handleListMessages(createTestArgs(conv.id));
|
|
82
82
|
const body = response as { messages: MessagePayload[] };
|
|
83
83
|
|
|
84
84
|
expect(body.messages).toHaveLength(1);
|
|
@@ -103,7 +103,7 @@ describe("handleListMessages attachments", () => {
|
|
|
103
103
|
);
|
|
104
104
|
linkAttachmentToMessage(msg.id, stored.id, 0);
|
|
105
105
|
|
|
106
|
-
const response = handleListMessages(createTestArgs(conv.id)
|
|
106
|
+
const response = handleListMessages(createTestArgs(conv.id));
|
|
107
107
|
const body = response as { messages: MessagePayload[] };
|
|
108
108
|
|
|
109
109
|
expect(body.messages).toHaveLength(1);
|
|
@@ -125,7 +125,7 @@ describe("handleListMessages attachments", () => {
|
|
|
125
125
|
const stored = uploadAttachment("result.png", "image/png", IMAGE_BASE64);
|
|
126
126
|
linkAttachmentToMessage(msg.id, stored.id, 0);
|
|
127
127
|
|
|
128
|
-
const response = handleListMessages(createTestArgs(conv.id)
|
|
128
|
+
const response = handleListMessages(createTestArgs(conv.id));
|
|
129
129
|
const body = response as { messages: MessagePayload[] };
|
|
130
130
|
|
|
131
131
|
expect(body.messages).toHaveLength(1);
|
|
@@ -153,7 +153,7 @@ describe("handleListMessages attachments", () => {
|
|
|
153
153
|
linkAttachmentToMessage(msg.id, imgStored.id, 0);
|
|
154
154
|
linkAttachmentToMessage(msg.id, docStored.id, 1);
|
|
155
155
|
|
|
156
|
-
const response = handleListMessages(createTestArgs(conv.id)
|
|
156
|
+
const response = handleListMessages(createTestArgs(conv.id));
|
|
157
157
|
const body = response as { messages: MessagePayload[] };
|
|
158
158
|
|
|
159
159
|
const attachments = body.messages[0].attachments!;
|
|
@@ -177,7 +177,7 @@ describe("handleListMessages no_response filtering", () => {
|
|
|
177
177
|
JSON.stringify([{ type: "text", text: "<no_response/>" }]),
|
|
178
178
|
);
|
|
179
179
|
|
|
180
|
-
const response = handleListMessages(createTestArgs(conv.id)
|
|
180
|
+
const response = handleListMessages(createTestArgs(conv.id));
|
|
181
181
|
const body = response as {
|
|
182
182
|
messages: { content: string; textSegments: string[] }[];
|
|
183
183
|
};
|
|
@@ -199,7 +199,7 @@ describe("handleListMessages no_response filtering", () => {
|
|
|
199
199
|
]),
|
|
200
200
|
);
|
|
201
201
|
|
|
202
|
-
const response = handleListMessages(createTestArgs(conv.id)
|
|
202
|
+
const response = handleListMessages(createTestArgs(conv.id));
|
|
203
203
|
const body = response as {
|
|
204
204
|
messages: { content: string; textSegments: string[] }[];
|
|
205
205
|
};
|
|
@@ -228,7 +228,7 @@ describe("handleListMessages no_response filtering", () => {
|
|
|
228
228
|
]),
|
|
229
229
|
);
|
|
230
230
|
|
|
231
|
-
const response = handleListMessages(createTestArgs(conv.id)
|
|
231
|
+
const response = handleListMessages(createTestArgs(conv.id));
|
|
232
232
|
const body = response as {
|
|
233
233
|
messages: {
|
|
234
234
|
content: string;
|
|
@@ -252,7 +252,7 @@ describe("handleListMessages no_response filtering", () => {
|
|
|
252
252
|
JSON.stringify([{ type: "text", text: "What does <no_response/> do?" }]),
|
|
253
253
|
);
|
|
254
254
|
|
|
255
|
-
const response = handleListMessages(createTestArgs(conv.id)
|
|
255
|
+
const response = handleListMessages(createTestArgs(conv.id));
|
|
256
256
|
const body = response as {
|
|
257
257
|
messages: { content: string }[];
|
|
258
258
|
};
|
|
@@ -308,7 +308,7 @@ describe("handleListMessages pagination", () => {
|
|
|
308
308
|
const conv = createConversation();
|
|
309
309
|
await insertMessages(conv.id, 5);
|
|
310
310
|
|
|
311
|
-
const response = handleListMessages(createTestArgs(conv.id)
|
|
311
|
+
const response = handleListMessages(createTestArgs(conv.id));
|
|
312
312
|
const body = response as unknown as PaginatedResponse;
|
|
313
313
|
|
|
314
314
|
expect(body.messages).toHaveLength(5);
|
|
@@ -322,7 +322,7 @@ describe("handleListMessages pagination", () => {
|
|
|
322
322
|
await insertMessages(conv.id, 5);
|
|
323
323
|
|
|
324
324
|
const args = createPaginatedArgs(conv.id, { limit: "3" });
|
|
325
|
-
const response = handleListMessages(args
|
|
325
|
+
const response = handleListMessages(args);
|
|
326
326
|
const body = response as unknown as PaginatedResponse;
|
|
327
327
|
|
|
328
328
|
// Option A: without beforeTimestamp, all messages are returned regardless of limit
|
|
@@ -339,7 +339,7 @@ describe("handleListMessages pagination", () => {
|
|
|
339
339
|
beforeTimestamp: String(msgs[7].createdAt),
|
|
340
340
|
limit: "3",
|
|
341
341
|
});
|
|
342
|
-
const response = handleListMessages(args
|
|
342
|
+
const response = handleListMessages(args);
|
|
343
343
|
const body = response as unknown as PaginatedResponse;
|
|
344
344
|
|
|
345
345
|
expect(body.messages).toHaveLength(3);
|
|
@@ -360,7 +360,7 @@ describe("handleListMessages pagination", () => {
|
|
|
360
360
|
beforeTimestamp: String(msgs[1].createdAt),
|
|
361
361
|
limit: "10",
|
|
362
362
|
});
|
|
363
|
-
const response = handleListMessages(args
|
|
363
|
+
const response = handleListMessages(args);
|
|
364
364
|
const body = response as unknown as PaginatedResponse;
|
|
365
365
|
|
|
366
366
|
const ids = body.messages.map((m) => m.id);
|
|
@@ -378,7 +378,7 @@ describe("handleListMessages pagination", () => {
|
|
|
378
378
|
beforeTimestamp: String(msgs[4].createdAt + 1),
|
|
379
379
|
limit: "10",
|
|
380
380
|
});
|
|
381
|
-
const response = handleListMessages(args
|
|
381
|
+
const response = handleListMessages(args);
|
|
382
382
|
const body = response as unknown as PaginatedResponse;
|
|
383
383
|
|
|
384
384
|
expect(body.messages).toHaveLength(5);
|
|
@@ -394,7 +394,7 @@ describe("handleListMessages pagination", () => {
|
|
|
394
394
|
beforeTimestamp: String(msgs[4].createdAt + 1),
|
|
395
395
|
limit: "3",
|
|
396
396
|
});
|
|
397
|
-
const response = handleListMessages(args
|
|
397
|
+
const response = handleListMessages(args);
|
|
398
398
|
const body = response as unknown as PaginatedResponse;
|
|
399
399
|
|
|
400
400
|
expect(body.messages).toHaveLength(3);
|
|
@@ -405,7 +405,7 @@ describe("handleListMessages pagination", () => {
|
|
|
405
405
|
|
|
406
406
|
test("empty / nonexistent conversation → empty messages, no pagination metadata", async () => {
|
|
407
407
|
const args = createPaginatedArgs("nonexistent-conv-id");
|
|
408
|
-
const response = handleListMessages(args
|
|
408
|
+
const response = handleListMessages(args);
|
|
409
409
|
const body = response as unknown as PaginatedResponse;
|
|
410
410
|
|
|
411
411
|
expect(body.messages).toEqual([]);
|
|
@@ -418,13 +418,17 @@ describe("handleListMessages pagination", () => {
|
|
|
418
418
|
const conv = createConversation();
|
|
419
419
|
const args = createPaginatedArgs(conv.id, { limit: "abc" });
|
|
420
420
|
|
|
421
|
-
expect(() => handleListMessages(args
|
|
421
|
+
expect(() => handleListMessages(args)).toThrow(
|
|
422
|
+
"limit must be a valid number",
|
|
423
|
+
);
|
|
422
424
|
});
|
|
423
425
|
|
|
424
426
|
test("invalid beforeTimestamp (NaN) → 400", async () => {
|
|
425
427
|
const conv = createConversation();
|
|
426
428
|
const args = createPaginatedArgs(conv.id, { beforeTimestamp: "abc" });
|
|
427
429
|
|
|
428
|
-
expect(() => handleListMessages(args
|
|
430
|
+
expect(() => handleListMessages(args)).toThrow(
|
|
431
|
+
"beforeTimestamp must be a valid number",
|
|
432
|
+
);
|
|
429
433
|
});
|
|
430
434
|
});
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for handleListMessages metadata.hidden filtering.
|
|
3
|
+
*
|
|
4
|
+
* Messages persisted with `metadata: { hidden: true }` (e.g. internal
|
|
5
|
+
* scaffolding like retrospective instructions) must be omitted from the
|
|
6
|
+
* UI history list while remaining visible to the LLM-side history loader.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
10
|
+
|
|
11
|
+
mock.module("../util/logger.js", () => ({
|
|
12
|
+
getLogger: () =>
|
|
13
|
+
new Proxy({} as Record<string, unknown>, {
|
|
14
|
+
get: () => () => {},
|
|
15
|
+
}),
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
mock.module("../config/loader.js", () => ({
|
|
19
|
+
getConfig: () => ({
|
|
20
|
+
ui: {},
|
|
21
|
+
model: "test",
|
|
22
|
+
provider: "test",
|
|
23
|
+
memory: { enabled: false },
|
|
24
|
+
rateLimit: { maxRequestsPerMinute: 0 },
|
|
25
|
+
}),
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
import {
|
|
29
|
+
addMessage,
|
|
30
|
+
createConversation,
|
|
31
|
+
getMessages,
|
|
32
|
+
} from "../memory/conversation-crud.js";
|
|
33
|
+
import { getDb } from "../memory/db-connection.js";
|
|
34
|
+
import { initializeDb } from "../memory/db-init.js";
|
|
35
|
+
import { handleListMessages } from "../runtime/routes/conversation-routes.js";
|
|
36
|
+
|
|
37
|
+
initializeDb();
|
|
38
|
+
|
|
39
|
+
function resetTables() {
|
|
40
|
+
const db = getDb();
|
|
41
|
+
db.run("DELETE FROM message_attachments");
|
|
42
|
+
db.run("DELETE FROM attachments");
|
|
43
|
+
db.run("DELETE FROM messages");
|
|
44
|
+
db.run("DELETE FROM conversations");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface MessagePayload {
|
|
48
|
+
role: string;
|
|
49
|
+
content: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
describe("handleListMessages metadata.hidden filtering", () => {
|
|
53
|
+
beforeEach(resetTables);
|
|
54
|
+
|
|
55
|
+
test("UI serializer omits hidden messages but LLM-side getMessages includes them", async () => {
|
|
56
|
+
const conv = createConversation();
|
|
57
|
+
await addMessage(
|
|
58
|
+
conv.id,
|
|
59
|
+
"user",
|
|
60
|
+
JSON.stringify([{ type: "text", text: "first visible" }]),
|
|
61
|
+
);
|
|
62
|
+
await addMessage(
|
|
63
|
+
conv.id,
|
|
64
|
+
"assistant",
|
|
65
|
+
JSON.stringify([{ type: "text", text: "internal scaffolding" }]),
|
|
66
|
+
{ hidden: true },
|
|
67
|
+
);
|
|
68
|
+
await addMessage(
|
|
69
|
+
conv.id,
|
|
70
|
+
"user",
|
|
71
|
+
JSON.stringify([{ type: "text", text: "second visible" }]),
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const response = handleListMessages({
|
|
75
|
+
queryParams: { conversationId: conv.id },
|
|
76
|
+
});
|
|
77
|
+
const body = response as { messages: MessagePayload[] };
|
|
78
|
+
|
|
79
|
+
expect(body.messages).toHaveLength(2);
|
|
80
|
+
expect(body.messages[0].content).toBe("first visible");
|
|
81
|
+
expect(body.messages[1].content).toBe("second visible");
|
|
82
|
+
expect(
|
|
83
|
+
body.messages.some((m) => m.content.includes("internal scaffolding")),
|
|
84
|
+
).toBe(false);
|
|
85
|
+
|
|
86
|
+
// LLM-side loader must include the hidden row so agent context is intact.
|
|
87
|
+
const llmRows = getMessages(conv.id);
|
|
88
|
+
expect(llmRows).toHaveLength(3);
|
|
89
|
+
expect(llmRows[1].metadata).toContain('"hidden":true');
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("messages without metadata or with hidden=false are returned", async () => {
|
|
93
|
+
const conv = createConversation();
|
|
94
|
+
await addMessage(
|
|
95
|
+
conv.id,
|
|
96
|
+
"user",
|
|
97
|
+
JSON.stringify([{ type: "text", text: "no metadata" }]),
|
|
98
|
+
);
|
|
99
|
+
await addMessage(
|
|
100
|
+
conv.id,
|
|
101
|
+
"assistant",
|
|
102
|
+
JSON.stringify([{ type: "text", text: "hidden false" }]),
|
|
103
|
+
{ hidden: false },
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
const response = handleListMessages({
|
|
107
|
+
queryParams: { conversationId: conv.id },
|
|
108
|
+
});
|
|
109
|
+
const body = response as { messages: MessagePayload[] };
|
|
110
|
+
|
|
111
|
+
expect(body.messages).toHaveLength(2);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("pagination skips hidden rows so hasMore and oldest cursor reflect visible rows", async () => {
|
|
115
|
+
const conv = createConversation();
|
|
116
|
+
// 4 visible older rows, then a block of 3 hidden rows, then 2 visible newer.
|
|
117
|
+
// With limit=2 and page=latest we should get the 2 newest visible rows,
|
|
118
|
+
// hasMore=true (older visible rows exist), and a cursor pointing at the
|
|
119
|
+
// oldest visible row in the page rather than null.
|
|
120
|
+
for (let i = 0; i < 4; i++) {
|
|
121
|
+
await addMessage(
|
|
122
|
+
conv.id,
|
|
123
|
+
"user",
|
|
124
|
+
JSON.stringify([{ type: "text", text: `old visible ${i}` }]),
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
for (let i = 0; i < 3; i++) {
|
|
128
|
+
await addMessage(
|
|
129
|
+
conv.id,
|
|
130
|
+
"assistant",
|
|
131
|
+
JSON.stringify([{ type: "text", text: `hidden ${i}` }]),
|
|
132
|
+
{ hidden: true },
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
for (let i = 0; i < 2; i++) {
|
|
136
|
+
await addMessage(
|
|
137
|
+
conv.id,
|
|
138
|
+
"user",
|
|
139
|
+
JSON.stringify([{ type: "text", text: `new visible ${i}` }]),
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const latest = handleListMessages({
|
|
144
|
+
queryParams: { conversationId: conv.id, page: "latest", limit: "2" },
|
|
145
|
+
}) as {
|
|
146
|
+
messages: MessagePayload[];
|
|
147
|
+
hasMore: boolean;
|
|
148
|
+
oldestTimestamp: number | null;
|
|
149
|
+
oldestMessageId: string | null;
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
expect(latest.messages.map((m) => m.content)).toEqual([
|
|
153
|
+
"new visible 0",
|
|
154
|
+
"new visible 1",
|
|
155
|
+
]);
|
|
156
|
+
expect(latest.hasMore).toBe(true);
|
|
157
|
+
expect(latest.oldestTimestamp).not.toBeNull();
|
|
158
|
+
expect(latest.oldestMessageId).not.toBeNull();
|
|
159
|
+
|
|
160
|
+
// Older page request — anchored before the latest page's oldest row —
|
|
161
|
+
// should skip the hidden block entirely and return the next 2 visible rows.
|
|
162
|
+
const older = handleListMessages({
|
|
163
|
+
queryParams: {
|
|
164
|
+
conversationId: conv.id,
|
|
165
|
+
beforeTimestamp: String(latest.oldestTimestamp),
|
|
166
|
+
limit: "2",
|
|
167
|
+
},
|
|
168
|
+
}) as {
|
|
169
|
+
messages: MessagePayload[];
|
|
170
|
+
hasMore: boolean;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
expect(older.messages.map((m) => m.content)).toEqual([
|
|
174
|
+
"old visible 2",
|
|
175
|
+
"old visible 3",
|
|
176
|
+
]);
|
|
177
|
+
expect(older.hasMore).toBe(true);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("pagination drains DB when every row in a page is hidden", async () => {
|
|
181
|
+
const conv = createConversation();
|
|
182
|
+
// 5 hidden rows then 2 visible older rows. With limit=2, the naive
|
|
183
|
+
// implementation fetches 3 newest (all hidden), filters to 0 visible, and
|
|
184
|
+
// returns hasMore=true with no cursor. We expect the loop to keep going
|
|
185
|
+
// and surface the visible rows instead.
|
|
186
|
+
for (let i = 0; i < 2; i++) {
|
|
187
|
+
await addMessage(
|
|
188
|
+
conv.id,
|
|
189
|
+
"user",
|
|
190
|
+
JSON.stringify([{ type: "text", text: `old visible ${i}` }]),
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
for (let i = 0; i < 5; i++) {
|
|
194
|
+
await addMessage(
|
|
195
|
+
conv.id,
|
|
196
|
+
"assistant",
|
|
197
|
+
JSON.stringify([{ type: "text", text: `hidden ${i}` }]),
|
|
198
|
+
{ hidden: true },
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const latest = handleListMessages({
|
|
203
|
+
queryParams: { conversationId: conv.id, page: "latest", limit: "2" },
|
|
204
|
+
}) as {
|
|
205
|
+
messages: MessagePayload[];
|
|
206
|
+
hasMore: boolean;
|
|
207
|
+
oldestTimestamp: number | null;
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
expect(latest.messages.map((m) => m.content)).toEqual([
|
|
211
|
+
"old visible 0",
|
|
212
|
+
"old visible 1",
|
|
213
|
+
]);
|
|
214
|
+
expect(latest.hasMore).toBe(false);
|
|
215
|
+
expect(latest.oldestTimestamp).not.toBeNull();
|
|
216
|
+
});
|
|
217
|
+
});
|
|
@@ -32,6 +32,11 @@ mock.module("../config/loader.js", () => ({
|
|
|
32
32
|
}),
|
|
33
33
|
}));
|
|
34
34
|
|
|
35
|
+
let mockAssistantName: string | null = null;
|
|
36
|
+
mock.module("../daemon/identity-helpers.js", () => ({
|
|
37
|
+
getAssistantName: () => mockAssistantName,
|
|
38
|
+
}));
|
|
39
|
+
|
|
35
40
|
import { createConversation } from "../memory/conversation-crud.js";
|
|
36
41
|
import { getDb } from "../memory/db-connection.js";
|
|
37
42
|
import { initializeDb } from "../memory/db-init.js";
|
|
@@ -89,8 +94,10 @@ interface MessagePayload {
|
|
|
89
94
|
timestamp: string;
|
|
90
95
|
slackMessage?: {
|
|
91
96
|
channelId: string;
|
|
97
|
+
channelName?: string;
|
|
92
98
|
channelTs: string;
|
|
93
99
|
threadTs?: string;
|
|
100
|
+
sender?: { displayName?: string; externalUserId?: string };
|
|
94
101
|
messageLink?: { appUrl?: string; webUrl?: string };
|
|
95
102
|
threadLink?: { appUrl?: string; webUrl?: string };
|
|
96
103
|
};
|
|
@@ -104,14 +111,16 @@ interface ListResponse {
|
|
|
104
111
|
}
|
|
105
112
|
|
|
106
113
|
function callList(query: Record<string, string>): ListResponse {
|
|
107
|
-
return handleListMessages(
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
) as unknown as ListResponse;
|
|
114
|
+
return handleListMessages({
|
|
115
|
+
queryParams: query,
|
|
116
|
+
}) as unknown as ListResponse;
|
|
111
117
|
}
|
|
112
118
|
|
|
113
119
|
describe("handleListMessages page=latest", () => {
|
|
114
|
-
beforeEach(
|
|
120
|
+
beforeEach(() => {
|
|
121
|
+
resetTables();
|
|
122
|
+
mockAssistantName = null;
|
|
123
|
+
});
|
|
115
124
|
|
|
116
125
|
test("page=latest with no limit returns all messages chronologically", () => {
|
|
117
126
|
const conv = createConversation();
|
|
@@ -219,8 +228,11 @@ describe("handleListMessages page=latest", () => {
|
|
|
219
228
|
slackMeta: writeSlackMetadata({
|
|
220
229
|
source: "slack",
|
|
221
230
|
channelId: "C123ABCDEF",
|
|
231
|
+
channelName: "engineering",
|
|
222
232
|
channelTs: "1710000000.000200",
|
|
223
233
|
threadTs: "1710000000.000100",
|
|
234
|
+
displayName: "Alice",
|
|
235
|
+
actorExternalUserId: "U_ALICE",
|
|
224
236
|
eventKind: "message",
|
|
225
237
|
}),
|
|
226
238
|
}),
|
|
@@ -232,13 +244,18 @@ describe("handleListMessages page=latest", () => {
|
|
|
232
244
|
|
|
233
245
|
expect(body.messages[0].slackMessage).toEqual({
|
|
234
246
|
channelId: "C123ABCDEF",
|
|
247
|
+
channelName: "engineering",
|
|
235
248
|
channelTs: "1710000000.000200",
|
|
236
249
|
threadTs: "1710000000.000100",
|
|
250
|
+
sender: {
|
|
251
|
+
displayName: "Alice",
|
|
252
|
+
externalUserId: "U_ALICE",
|
|
253
|
+
},
|
|
237
254
|
messageLink: {
|
|
238
255
|
appUrl:
|
|
239
256
|
"slack://channel?team=T123&id=C123ABCDEF&message=1710000000.000200",
|
|
240
257
|
webUrl:
|
|
241
|
-
"https://example.slack.com/archives/C123ABCDEF/p1710000000000200",
|
|
258
|
+
"https://example.slack.com/archives/C123ABCDEF/p1710000000000200?thread_ts=1710000000.000100&cid=C123ABCDEF",
|
|
242
259
|
},
|
|
243
260
|
threadLink: {
|
|
244
261
|
appUrl:
|
|
@@ -249,6 +266,107 @@ describe("handleListMessages page=latest", () => {
|
|
|
249
266
|
});
|
|
250
267
|
});
|
|
251
268
|
|
|
269
|
+
test("top-level Slack messages with matching threadTs use plain permalinks", () => {
|
|
270
|
+
const conv = createConversation();
|
|
271
|
+
const db = getDb();
|
|
272
|
+
db.insert(messages)
|
|
273
|
+
.values({
|
|
274
|
+
id: "msg-slack-top-level",
|
|
275
|
+
conversationId: conv.id,
|
|
276
|
+
role: "user",
|
|
277
|
+
content: JSON.stringify([{ type: "text", text: "Slack top-level" }]),
|
|
278
|
+
metadata: JSON.stringify({
|
|
279
|
+
slackMeta: writeSlackMetadata({
|
|
280
|
+
source: "slack",
|
|
281
|
+
channelId: "C123ABCDEF",
|
|
282
|
+
channelName: "engineering",
|
|
283
|
+
channelTs: "1710000000.000200",
|
|
284
|
+
threadTs: "1710000000.000200",
|
|
285
|
+
displayName: "Alice",
|
|
286
|
+
actorExternalUserId: "U_ALICE",
|
|
287
|
+
eventKind: "message",
|
|
288
|
+
}),
|
|
289
|
+
}),
|
|
290
|
+
createdAt: 1,
|
|
291
|
+
})
|
|
292
|
+
.run();
|
|
293
|
+
|
|
294
|
+
const body = callList({ conversationId: conv.id, page: "latest" });
|
|
295
|
+
|
|
296
|
+
expect(body.messages[0].slackMessage).toEqual({
|
|
297
|
+
channelId: "C123ABCDEF",
|
|
298
|
+
channelName: "engineering",
|
|
299
|
+
channelTs: "1710000000.000200",
|
|
300
|
+
threadTs: "1710000000.000200",
|
|
301
|
+
sender: {
|
|
302
|
+
displayName: "Alice",
|
|
303
|
+
externalUserId: "U_ALICE",
|
|
304
|
+
},
|
|
305
|
+
messageLink: {
|
|
306
|
+
appUrl:
|
|
307
|
+
"slack://channel?team=T123&id=C123ABCDEF&message=1710000000.000200",
|
|
308
|
+
webUrl:
|
|
309
|
+
"https://example.slack.com/archives/C123ABCDEF/p1710000000000200",
|
|
310
|
+
},
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
test("assistant Slack messages without stored sender use the assistant name", () => {
|
|
315
|
+
mockAssistantName = "Nova";
|
|
316
|
+
const conv = createConversation();
|
|
317
|
+
const db = getDb();
|
|
318
|
+
db.insert(messages)
|
|
319
|
+
.values({
|
|
320
|
+
id: "msg-slack-assistant",
|
|
321
|
+
conversationId: conv.id,
|
|
322
|
+
role: "assistant",
|
|
323
|
+
content: JSON.stringify([{ type: "text", text: "Slack response" }]),
|
|
324
|
+
metadata: JSON.stringify({
|
|
325
|
+
slackMeta: writeSlackMetadata({
|
|
326
|
+
source: "slack",
|
|
327
|
+
channelId: "C123ABCDEF",
|
|
328
|
+
channelTs: "1710000000.000300",
|
|
329
|
+
eventKind: "message",
|
|
330
|
+
}),
|
|
331
|
+
}),
|
|
332
|
+
createdAt: 1,
|
|
333
|
+
})
|
|
334
|
+
.run();
|
|
335
|
+
|
|
336
|
+
const body = callList({ conversationId: conv.id, page: "latest" });
|
|
337
|
+
|
|
338
|
+
expect(body.messages[0].slackMessage?.sender).toEqual({
|
|
339
|
+
displayName: "Nova",
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
test("user Slack messages without stored sender do not use the assistant name", () => {
|
|
344
|
+
mockAssistantName = "Nova";
|
|
345
|
+
const conv = createConversation();
|
|
346
|
+
const db = getDb();
|
|
347
|
+
db.insert(messages)
|
|
348
|
+
.values({
|
|
349
|
+
id: "msg-slack-user",
|
|
350
|
+
conversationId: conv.id,
|
|
351
|
+
role: "user",
|
|
352
|
+
content: JSON.stringify([{ type: "text", text: "Slack request" }]),
|
|
353
|
+
metadata: JSON.stringify({
|
|
354
|
+
slackMeta: writeSlackMetadata({
|
|
355
|
+
source: "slack",
|
|
356
|
+
channelId: "C123ABCDEF",
|
|
357
|
+
channelTs: "1710000000.000300",
|
|
358
|
+
eventKind: "message",
|
|
359
|
+
}),
|
|
360
|
+
}),
|
|
361
|
+
createdAt: 1,
|
|
362
|
+
})
|
|
363
|
+
.run();
|
|
364
|
+
|
|
365
|
+
const body = callList({ conversationId: conv.id, page: "latest" });
|
|
366
|
+
|
|
367
|
+
expect(body.messages[0].slackMessage?.sender).toBeUndefined();
|
|
368
|
+
});
|
|
369
|
+
|
|
252
370
|
test("page=latest on unresolved conversationKey returns null metadata contract", () => {
|
|
253
371
|
const body = callList({
|
|
254
372
|
conversationKey: "no-such-key",
|
|
@@ -274,16 +392,14 @@ describe("handleListMessages page=latest", () => {
|
|
|
274
392
|
const conv = createConversation();
|
|
275
393
|
|
|
276
394
|
expect(() =>
|
|
277
|
-
handleListMessages(
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
),
|
|
395
|
+
handleListMessages({
|
|
396
|
+
queryParams: { conversationId: conv.id, page: "invalid" },
|
|
397
|
+
}),
|
|
281
398
|
).toThrow(BadRequestError);
|
|
282
399
|
expect(() =>
|
|
283
|
-
handleListMessages(
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
),
|
|
400
|
+
handleListMessages({
|
|
401
|
+
queryParams: { conversationId: conv.id, page: "invalid" },
|
|
402
|
+
}),
|
|
287
403
|
).toThrow("page must be 'latest' when provided");
|
|
288
404
|
});
|
|
289
405
|
|
|
@@ -91,7 +91,7 @@ describe("handleListMessages tool_result merging", () => {
|
|
|
91
91
|
]),
|
|
92
92
|
);
|
|
93
93
|
|
|
94
|
-
const response = handleListMessages(createTestArgs(conv.id)
|
|
94
|
+
const response = handleListMessages(createTestArgs(conv.id));
|
|
95
95
|
const body = response as { messages: MessagePayload[] };
|
|
96
96
|
|
|
97
97
|
// Should be 2 messages: user prompt + assistant (tool_result user msg suppressed)
|
|
@@ -137,7 +137,7 @@ describe("handleListMessages tool_result merging", () => {
|
|
|
137
137
|
]),
|
|
138
138
|
);
|
|
139
139
|
|
|
140
|
-
const response = handleListMessages(createTestArgs(conv.id)
|
|
140
|
+
const response = handleListMessages(createTestArgs(conv.id));
|
|
141
141
|
const body = response as { messages: MessagePayload[] };
|
|
142
142
|
|
|
143
143
|
expect(body.messages).toHaveLength(2);
|
|
@@ -167,7 +167,7 @@ describe("handleListMessages tool_result merging", () => {
|
|
|
167
167
|
JSON.stringify([{ type: "text", text: "how are you?" }]),
|
|
168
168
|
);
|
|
169
169
|
|
|
170
|
-
const response = handleListMessages(createTestArgs(conv.id)
|
|
170
|
+
const response = handleListMessages(createTestArgs(conv.id));
|
|
171
171
|
const body = response as { messages: MessagePayload[] };
|
|
172
172
|
|
|
173
173
|
expect(body.messages).toHaveLength(3);
|
|
@@ -175,11 +175,15 @@ describe("handleListMessages tool_result merging", () => {
|
|
|
175
175
|
expect(body.messages[2].content).toBe("how are you?");
|
|
176
176
|
});
|
|
177
177
|
|
|
178
|
-
test("tool_result at start of array (no preceding assistant) is
|
|
178
|
+
test("tool_result at start of array (no preceding assistant) is dropped", async () => {
|
|
179
179
|
const conv = createConversation();
|
|
180
|
-
// Orphan tool_result with no preceding assistant
|
|
181
|
-
//
|
|
182
|
-
//
|
|
180
|
+
// Orphan tool_result with no preceding assistant. Without the parent
|
|
181
|
+
// tool_use we can't tell the user what tool ran, so the result is
|
|
182
|
+
// meaningless — renderHistoryContent drops it rather than synthesizing
|
|
183
|
+
// a phantom "unknown" tool call. See shared.ts comment.
|
|
184
|
+
// The user message itself is preserved at the pagination boundary
|
|
185
|
+
// (mergeToolResultsIntoAssistantMessages keeps it to avoid data loss
|
|
186
|
+
// in case the matching tool_use lives on the previous page).
|
|
183
187
|
await addMessage(
|
|
184
188
|
conv.id,
|
|
185
189
|
"user",
|
|
@@ -197,17 +201,14 @@ describe("handleListMessages tool_result merging", () => {
|
|
|
197
201
|
JSON.stringify([{ type: "text", text: "response" }]),
|
|
198
202
|
);
|
|
199
203
|
|
|
200
|
-
const response = handleListMessages(createTestArgs(conv.id)
|
|
204
|
+
const response = handleListMessages(createTestArgs(conv.id));
|
|
201
205
|
const body = response as { messages: MessagePayload[] };
|
|
202
206
|
|
|
203
|
-
//
|
|
207
|
+
// Both messages exist — user message preserved at pagination boundary,
|
|
208
|
+
// but the orphan tool_result is dropped (no phantom toolCalls)
|
|
204
209
|
expect(body.messages).toHaveLength(2);
|
|
205
210
|
expect(body.messages[0].role).toBe("user");
|
|
206
|
-
|
|
207
|
-
const orphanToolCalls = body.messages[0].toolCalls;
|
|
208
|
-
expect(orphanToolCalls).toBeDefined();
|
|
209
|
-
expect(orphanToolCalls).toHaveLength(1);
|
|
210
|
-
expect(orphanToolCalls![0].result).toBe("stale result");
|
|
211
|
+
expect(body.messages[0].toolCalls).toBeUndefined();
|
|
211
212
|
expect(body.messages[1].role).toBe("assistant");
|
|
212
213
|
expect(body.messages[1].content).toBe("response");
|
|
213
214
|
});
|
|
@@ -262,7 +263,7 @@ describe("handleListMessages tool_result merging", () => {
|
|
|
262
263
|
JSON.stringify([{ type: "text", text: "thanks" }]),
|
|
263
264
|
);
|
|
264
265
|
|
|
265
|
-
const response = handleListMessages(createTestArgs(conv.id)
|
|
266
|
+
const response = handleListMessages(createTestArgs(conv.id));
|
|
266
267
|
const body = response as { messages: MessagePayload[] };
|
|
267
268
|
|
|
268
269
|
// Consecutive assistant messages are merged at query time so the client
|
|
@@ -312,7 +313,7 @@ describe("handleListMessages tool_result merging", () => {
|
|
|
312
313
|
]),
|
|
313
314
|
);
|
|
314
315
|
|
|
315
|
-
const response = handleListMessages(createTestArgs(conv.id)
|
|
316
|
+
const response = handleListMessages(createTestArgs(conv.id));
|
|
316
317
|
const body = response as { messages: MessagePayload[] };
|
|
317
318
|
|
|
318
319
|
expect(body.messages).toHaveLength(2);
|