@vellumai/assistant 0.6.2 → 0.6.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 +273 -10
- package/Dockerfile +2 -3
- package/bun.lock +41 -49
- package/bunfig.toml +3 -0
- package/docs/architecture/memory.md +1 -1
- package/docs/backup-troubleshooting.md +52 -0
- package/docs/browser-use-architecture-phase2.md +174 -0
- package/docs/stt-provider-onboarding.md +120 -0
- package/knip.json +12 -2
- package/node_modules/@vellumai/ces-contracts/bun.lock +8 -6
- package/node_modules/@vellumai/ces-contracts/package.json +3 -3
- package/node_modules/@vellumai/ces-contracts/src/rpc.ts +42 -0
- package/openapi.yaml +1111 -86
- package/package.json +40 -42
- package/scripts/generate-openapi.ts +0 -2
- package/scripts/test.sh +73 -18
- package/src/__tests__/acp-session.test.ts +43 -0
- package/src/__tests__/agent-image-optimize.test.ts +28 -0
- package/src/__tests__/agent-loop.test.ts +123 -0
- package/src/__tests__/anthropic-provider.test.ts +263 -10
- package/src/__tests__/app-builder-tool-scripts.test.ts +1 -0
- package/src/__tests__/app-executors.test.ts +1 -0
- package/src/__tests__/app-source-watcher.test.ts +37 -11
- package/src/__tests__/approval-routes-http.test.ts +178 -1
- package/src/__tests__/auto-analysis-end-to-end.test.ts +550 -0
- package/src/__tests__/auto-analysis-prompt.test.ts +50 -0
- package/src/__tests__/browser-fill-credential.test.ts +240 -94
- package/src/__tests__/browser-manager.test.ts +40 -27
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
- package/src/__tests__/browser-skill-endstate.test.ts +31 -7
- package/src/__tests__/btw-routes.test.ts +7 -0
- package/src/__tests__/call-controller.test.ts +581 -20
- package/src/__tests__/catalog-files.test.ts +1000 -0
- package/src/__tests__/channel-approvals.test.ts +53 -0
- package/src/__tests__/channel-invite-transport.test.ts +2 -2
- package/src/__tests__/channel-readiness-routes.test.ts +16 -20
- package/src/__tests__/channel-readiness-service.test.ts +12 -7
- package/src/__tests__/checker.test.ts +157 -10
- package/src/__tests__/clawhub-files.test.ts +347 -0
- package/src/__tests__/commit-message-enrichment-service.test.ts +36 -19
- package/src/__tests__/config-analysis.test.ts +100 -0
- package/src/__tests__/config-managed-gemini-defaults.test.ts +326 -0
- package/src/__tests__/config-schema-cmd.test.ts +2 -2
- package/src/__tests__/config-schema.test.ts +1248 -224
- package/src/__tests__/config-watcher-cleanup-throttle.test.ts +339 -0
- package/src/__tests__/config-watcher.test.ts +43 -8
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +23 -0
- package/src/__tests__/contact-store-user-file.test.ts +512 -0
- package/src/__tests__/contacts-write.test.ts +197 -0
- package/src/__tests__/context-overflow-approval.test.ts +16 -1
- package/src/__tests__/context-window-manager.test.ts +88 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +2 -1
- package/src/__tests__/conversation-agent-loop.test.ts +99 -3
- package/src/__tests__/conversation-analysis-routes.test.ts +2 -2
- package/src/__tests__/conversation-attachments.test.ts +80 -4
- package/src/__tests__/conversation-confirmation-signals.test.ts +290 -0
- package/src/__tests__/conversation-error.test.ts +70 -0
- package/src/__tests__/conversation-fork-crud.test.ts +17 -0
- package/src/__tests__/conversation-history-web-search.test.ts +12 -4
- package/src/__tests__/conversation-host-access-routes.test.ts +229 -0
- package/src/__tests__/conversation-init.benchmark.test.ts +6 -1
- package/src/__tests__/conversation-inject-context.test.ts +103 -0
- package/src/__tests__/conversation-launcher-skill-regression.test.ts +51 -0
- package/src/__tests__/conversation-list-source.test.ts +145 -0
- package/src/__tests__/conversation-pre-run-repair.test.ts +2 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -0
- package/src/__tests__/conversation-queue.test.ts +946 -62
- package/src/__tests__/conversation-routes-disk-view.test.ts +275 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +16 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +324 -46
- package/src/__tests__/conversation-skill-tools.test.ts +7 -4
- package/src/__tests__/conversation-slash-commands.test.ts +33 -0
- package/src/__tests__/conversation-slash-queue.test.ts +89 -18
- package/src/__tests__/conversation-slash-unknown.test.ts +2 -0
- package/src/__tests__/conversation-starter-routes.test.ts +126 -0
- package/src/__tests__/conversation-starters-cadence.test.ts +161 -0
- package/src/__tests__/conversation-store.test.ts +195 -0
- package/src/__tests__/conversation-tool-setup-batch-authorized.test.ts +226 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +193 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +2 -0
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +2 -0
- package/src/__tests__/credential-execution-approval-bridge.test.ts +32 -1
- package/src/__tests__/credential-health-service.test.ts +352 -0
- package/src/__tests__/credential-security-invariants.test.ts +6 -3
- package/src/__tests__/credential-vault-unit.test.ts +383 -7
- package/src/__tests__/credential-vault.test.ts +152 -13
- package/src/__tests__/credentials-cli.test.ts +42 -18
- package/src/__tests__/cross-provider-web-search.test.ts +146 -35
- package/src/__tests__/date-context.test.ts +4 -4
- package/src/__tests__/deterministic-verification-control-plane.test.ts +10 -1
- package/src/__tests__/device-id.test.ts +112 -0
- package/src/__tests__/docker-signing-key-bootstrap.test.ts +167 -4
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +1 -3
- package/src/__tests__/email-html-renderer.test.ts +71 -0
- package/src/__tests__/email-invite-adapter.test.ts +36 -32
- package/src/__tests__/embedding-managed-proxy-selection.test.ts +256 -0
- package/src/__tests__/emit-event-signal.test.ts +71 -0
- package/src/__tests__/extension-id-sync-guard.test.ts +222 -0
- package/src/__tests__/fixtures/mock-chrome-extension.ts +386 -0
- package/src/__tests__/gateway-only-enforcement.test.ts +206 -1
- package/src/__tests__/gateway-only-guard.test.ts +2 -0
- package/src/__tests__/gemini-provider.test.ts +66 -2
- package/src/__tests__/get-skill-detail-audit.test.ts +325 -0
- package/src/__tests__/gmail-archive-fallback.test.ts +193 -0
- package/src/__tests__/gmail-archive-gate.test.ts +246 -0
- package/src/__tests__/gmail-preferences.test.ts +117 -0
- package/src/__tests__/guardian-routing-invariants.test.ts +70 -2
- package/src/__tests__/headless-browser-interactions.test.ts +738 -359
- package/src/__tests__/headless-browser-mode.test.ts +614 -0
- package/src/__tests__/headless-browser-navigate.test.ts +528 -49
- package/src/__tests__/headless-browser-read-tools.test.ts +274 -100
- package/src/__tests__/headless-browser-snapshot.test.ts +250 -77
- package/src/__tests__/heartbeat-service.test.ts +70 -17
- package/src/__tests__/home-state-routes.test.ts +162 -0
- package/src/__tests__/host-bash-proxy.test.ts +145 -1
- package/src/__tests__/host-browser-e2e-cloud.test.ts +596 -0
- package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +286 -0
- package/src/__tests__/host-browser-e2e-self-hosted.test.ts +374 -0
- package/src/__tests__/host-browser-event-routes.test.ts +350 -0
- package/src/__tests__/host-browser-proxy.test.ts +444 -0
- package/src/__tests__/host-browser-routes.test.ts +198 -0
- package/src/__tests__/host-browser-ws-events-e2e.test.ts +423 -0
- package/src/__tests__/host-cu-proxy.test.ts +166 -1
- package/src/__tests__/host-file-proxy.test.ts +185 -1
- package/src/__tests__/host-file-read-tool.test.ts +52 -0
- package/src/__tests__/host-proxy-interface.test.ts +165 -0
- package/src/__tests__/host-shell-tool.test.ts +1 -11
- package/src/__tests__/http-user-message-parity.test.ts +1 -0
- package/src/__tests__/identity-intro-cache.test.ts +40 -10
- package/src/__tests__/init-feature-flag-overrides.test.ts +38 -112
- package/src/__tests__/integration-status.test.ts +6 -7
- package/src/__tests__/jobs-store-upsert-debounced.test.ts +141 -0
- package/src/__tests__/list-messages-tool-merge.test.ts +37 -12
- package/src/__tests__/llm-context-normalization.test.ts +488 -0
- package/src/__tests__/llm-context-route-provider.test.ts +86 -5
- package/src/__tests__/llm-usage-store.test.ts +363 -0
- package/src/__tests__/mcp-client-auth.test.ts +40 -4
- package/src/__tests__/mcp-health-check.test.ts +10 -3
- package/src/__tests__/media-stream-output.test.ts +555 -0
- package/src/__tests__/media-stream-parser.test.ts +374 -0
- package/src/__tests__/media-stream-server-integration.test.ts +1234 -0
- package/src/__tests__/media-stream-stt-session.test.ts +588 -0
- package/src/__tests__/media-turn-detector.test.ts +440 -0
- package/src/__tests__/message-queue.test.ts +125 -0
- package/src/__tests__/migration-cross-version-compatibility.test.ts +3 -1
- package/src/__tests__/migration-export-http.test.ts +67 -8
- package/src/__tests__/migration-export-streaming.test.ts +66 -0
- package/src/__tests__/migration-import-commit-http.test.ts +109 -7
- package/src/__tests__/migration-import-preflight-http.test.ts +6 -5
- package/src/__tests__/migration-validate-http.test.ts +3 -3
- package/src/__tests__/mock-gateway-ipc.ts +151 -0
- package/src/__tests__/model-intents.test.ts +2 -2
- package/src/__tests__/native-host-marker-sync-guard.test.ts +157 -0
- package/src/__tests__/oauth-apps-routes.test.ts +18 -12
- package/src/__tests__/oauth-cli.test.ts +709 -60
- package/src/__tests__/oauth-connect-orchestrator.test.ts +118 -24
- package/src/__tests__/oauth-provider-seed-logos.test.ts +23 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +147 -10
- package/src/__tests__/oauth-provider-visibility.test.ts +19 -21
- package/src/__tests__/oauth-providers-routes.test.ts +52 -14
- package/src/__tests__/oauth-store.test.ts +1465 -176
- package/src/__tests__/oauth2-gateway-transport.test.ts +460 -26
- package/src/__tests__/onboarding-template-contract.test.ts +81 -70
- package/src/__tests__/openai-provider.test.ts +178 -2
- package/src/__tests__/openai-responses-cutover-guard.test.ts +184 -0
- package/src/__tests__/openai-responses-provider.test.ts +1105 -0
- package/src/__tests__/openrouter-token-estimation.test.ts +100 -0
- package/src/__tests__/outlook-categories.test.ts +1 -1
- package/src/__tests__/outlook-client-automation.test.ts +1 -1
- package/src/__tests__/outlook-compose-tools.test.ts +1 -1
- package/src/__tests__/outlook-email-watcher.test.ts +1 -1
- package/src/__tests__/outlook-follow-up.test.ts +1 -1
- package/src/__tests__/outlook-messaging-provider.test.ts +2 -2
- package/src/__tests__/outlook-trash.test.ts +1 -1
- package/src/__tests__/outlook-unsubscribe.test.ts +32 -3
- package/src/__tests__/permission-checker-host-gate.test.ts +74 -14
- package/src/__tests__/permission-mode.test.ts +28 -56
- package/src/__tests__/persona-resolver.test.ts +251 -0
- package/src/__tests__/platform-bash-auto-approve.test.ts +4 -0
- package/src/__tests__/platform-callback-registration.test.ts +19 -0
- package/src/__tests__/platform.test.ts +92 -1
- package/src/__tests__/post-turn-tool-result-truncation.test.ts +343 -0
- package/src/__tests__/prechat-onboarding-contract.test.ts +267 -0
- package/src/__tests__/pricing.test.ts +174 -0
- package/src/__tests__/proxy-approval-callback.test.ts +18 -0
- package/src/__tests__/qdrant-manager.test.ts +29 -8
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +194 -0
- package/src/__tests__/relationship-state-contract.test.ts +175 -0
- package/src/__tests__/relay-server.test.ts +423 -5
- package/src/__tests__/require-fresh-approval.test.ts +40 -1
- package/src/__tests__/sanitize-config-for-transfer.test.ts +132 -0
- package/src/__tests__/schedule-routes.test.ts +162 -0
- package/src/__tests__/search-skills-unified.test.ts +118 -0
- package/src/__tests__/secret-detection-handler.test.ts +84 -0
- package/src/__tests__/secret-ingress-http.test.ts +1 -0
- package/src/__tests__/secret-scanner-executor.test.ts +4 -0
- package/src/__tests__/secure-keys.test.ts +107 -0
- package/src/__tests__/send-endpoint-busy.test.ts +8 -1
- package/src/__tests__/sequence-store.test.ts +1 -1
- package/src/__tests__/server-history-render.test.ts +49 -0
- package/src/__tests__/set-permission-mode.test.ts +13 -250
- package/src/__tests__/settings-routes.test.ts +201 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +1 -0
- package/src/__tests__/skills-file-content-endpoint.test.ts +801 -0
- package/src/__tests__/skills-files-catalog-fallback.test.ts +738 -0
- package/src/__tests__/skills.test.ts +5 -2
- package/src/__tests__/skillssh-files.test.ts +446 -0
- package/src/__tests__/slack-block-formatting.test.ts +110 -0
- package/src/__tests__/slack-channel-config.test.ts +576 -16
- package/src/__tests__/stt-catalog-parity.test.ts +282 -0
- package/src/__tests__/stt-stream-session.test.ts +535 -0
- package/src/__tests__/subagent-detail.test.ts +44 -2
- package/src/__tests__/subagent-disposal.test.ts +1 -0
- package/src/__tests__/subagent-fork-notifications.test.ts +291 -0
- package/src/__tests__/subagent-fork-spawn.test.ts +384 -0
- package/src/__tests__/subagent-manager-notify.test.ts +1 -0
- package/src/__tests__/subagent-notify-parent.test.ts +1 -0
- package/src/__tests__/subagent-spawn-tool-fork.test.ts +411 -0
- package/src/__tests__/subagent-tools.test.ts +1 -0
- package/src/__tests__/subagent-types.test.ts +1 -0
- package/src/__tests__/system-prompt-ask-mode.test.ts +27 -71
- package/src/__tests__/system-prompt.test.ts +184 -27
- package/src/__tests__/task-scheduler.test.ts +32 -6
- package/src/__tests__/telegram-config.test.ts +10 -13
- package/src/__tests__/telephony-stt-routing.test.ts +329 -0
- package/src/__tests__/terminal-tools.test.ts +25 -5
- package/src/__tests__/test-preload.ts +18 -0
- package/src/__tests__/test-support/browser-skill-harness.ts +4 -1
- package/src/__tests__/tool-approval-handler.test.ts +73 -0
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +9 -5
- package/src/__tests__/tool-executor-shell-integration.test.ts +4 -0
- package/src/__tests__/tool-executor.test.ts +33 -24
- package/src/__tests__/tool-result-truncation.test.ts +36 -0
- package/src/__tests__/tool-side-effects-slack-dm.test.ts +22 -0
- package/src/__tests__/top-level-renderer.test.ts +73 -1
- package/src/__tests__/transport-hints-queue.test.ts +14 -29
- package/src/__tests__/trust-store.test.ts +7 -1
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +1 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +109 -0
- package/src/__tests__/tts-catalog-parity.test.ts +345 -0
- package/src/__tests__/twilio-routes-twiml.test.ts +512 -114
- package/src/__tests__/twilio-routes.test.ts +376 -0
- package/src/__tests__/unicode.test.ts +293 -0
- package/src/__tests__/update-bulletin-format.test.ts +59 -0
- package/src/__tests__/update-bulletin.test.ts +206 -5
- package/src/__tests__/usage-routes.test.ts +25 -4
- package/src/__tests__/user-reference.test.ts +46 -61
- package/src/__tests__/v2-consent-policy.test.ts +103 -0
- package/src/__tests__/verification-control-plane-policy.test.ts +4 -0
- package/src/__tests__/voice-config-update.test.ts +403 -0
- package/src/__tests__/voice-quality.test.ts +434 -19
- package/src/__tests__/workspace-heartbeat-service.test.ts +7 -0
- package/src/__tests__/workspace-migration-033-stt-service-explicit-config.test.ts +547 -0
- package/src/__tests__/workspace-migration-034-remove-calls-voice-transcription-provider.test.ts +596 -0
- package/src/__tests__/workspace-migration-drop-user-md.test.ts +368 -0
- package/src/__tests__/workspace-migration-meets.test.ts +244 -0
- package/src/__tests__/workspace-migration-seed-device-id.test.ts +14 -20
- package/src/__tests__/workspace-policy.test.ts +2 -0
- package/src/acp/client-handler.ts +30 -4
- package/src/agent/image-optimize.ts +24 -12
- package/src/agent/loop.ts +55 -9
- package/src/approvals/guardian-request-resolvers.ts +21 -15
- package/src/backup/__tests__/backup-key.test.ts +152 -0
- package/src/backup/__tests__/backup-worker.test.ts +767 -0
- package/src/backup/__tests__/list-snapshots.test.ts +87 -0
- package/src/backup/__tests__/local-writer.test.ts +218 -0
- package/src/backup/__tests__/offsite-writer.test.ts +641 -0
- package/src/backup/__tests__/paths.test.ts +300 -0
- package/src/backup/__tests__/restore.test.ts +498 -0
- package/src/backup/__tests__/snapshot-lock.test.ts +352 -0
- package/src/backup/__tests__/stream-crypt.test.ts +228 -0
- package/src/backup/backup-key.ts +137 -0
- package/src/backup/backup-worker.ts +459 -0
- package/src/backup/list-snapshots.ts +147 -0
- package/src/backup/local-writer.ts +133 -0
- package/src/backup/offsite-writer.ts +222 -0
- package/src/backup/paths.ts +226 -0
- package/src/backup/restore.ts +322 -0
- package/src/backup/snapshot-lock.ts +431 -0
- package/src/backup/stream-crypt.ts +263 -0
- package/src/browser-session/__tests__/manager.test.ts +297 -0
- package/src/browser-session/backends/cdp-inspect.ts +30 -0
- package/src/browser-session/backends/extension.ts +26 -0
- package/src/browser-session/backends/local.ts +24 -0
- package/src/browser-session/events.ts +164 -0
- package/src/browser-session/index.ts +27 -0
- package/src/browser-session/manager.ts +159 -0
- package/src/browser-session/types.ts +28 -0
- package/src/bundler/package-resolver.ts +4 -0
- package/src/calls/audio-store.ts +11 -5
- package/src/calls/call-controller.ts +226 -71
- package/src/calls/call-domain.ts +9 -0
- package/src/calls/call-speech-output.ts +190 -0
- package/src/calls/call-transport.ts +77 -0
- package/src/calls/media-stream-audio-transcode.ts +173 -0
- package/src/calls/media-stream-output.ts +660 -0
- package/src/calls/media-stream-parser.ts +300 -0
- package/src/calls/media-stream-protocol.ts +166 -0
- package/src/calls/media-stream-server.ts +592 -0
- package/src/calls/media-stream-stt-session.ts +460 -0
- package/src/calls/media-turn-detector.ts +230 -0
- package/src/calls/relay-server.ts +90 -75
- package/src/calls/resolve-call-tts-provider.ts +136 -0
- package/src/calls/telephony-stt-routing.ts +145 -0
- package/src/calls/tts-call-strategy.ts +161 -0
- package/src/calls/tts-text-sanitizer.ts +32 -16
- package/src/calls/twilio-routes.ts +281 -17
- package/src/calls/voice-quality.ts +78 -35
- package/src/calls/voice-session-bridge.ts +8 -1
- package/src/channels/__tests__/types.test.ts +134 -0
- package/src/channels/types.ts +69 -3
- package/src/cli/__tests__/run-assistant-command.ts +11 -1
- package/src/cli/commands/__tests__/backup.test.ts +1165 -0
- package/src/cli/commands/__tests__/domain-register.test.ts +234 -0
- package/src/cli/commands/__tests__/domain-status.test.ts +132 -0
- package/src/cli/commands/__tests__/email-attachment.test.ts +422 -0
- package/src/cli/commands/__tests__/email-download.test.ts +16 -1
- package/src/cli/commands/__tests__/email-list.test.ts +22 -4
- package/src/cli/commands/__tests__/email-register.test.ts +4 -4
- package/src/cli/commands/__tests__/email-send.test.ts +37 -4
- package/src/cli/commands/__tests__/email-status.test.ts +5 -1
- package/src/cli/commands/__tests__/email-unregister.test.ts +34 -5
- package/src/cli/commands/backup.ts +993 -0
- package/src/cli/commands/conversations.ts +77 -0
- package/src/cli/commands/credentials.ts +3 -4
- package/src/cli/commands/domain.ts +210 -0
- package/src/cli/commands/email.ts +273 -16
- package/src/cli/commands/mcp.ts +16 -4
- package/src/cli/commands/oauth/__tests__/connect.test.ts +56 -44
- package/src/cli/commands/oauth/__tests__/disconnect.test.ts +21 -21
- package/src/cli/commands/oauth/__tests__/mode.test.ts +17 -17
- package/src/cli/commands/oauth/__tests__/ping.test.ts +16 -16
- package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +32 -33
- package/src/cli/commands/oauth/__tests__/providers-register.test.ts +330 -0
- package/src/cli/commands/oauth/__tests__/providers-update.test.ts +117 -12
- package/src/cli/commands/oauth/__tests__/status.test.ts +10 -10
- package/src/cli/commands/oauth/__tests__/token.test.ts +7 -7
- package/src/cli/commands/oauth/apps.ts +7 -4
- package/src/cli/commands/oauth/connect.ts +6 -3
- package/src/cli/commands/oauth/disconnect.ts +1 -1
- package/src/cli/commands/oauth/mode.ts +12 -3
- package/src/cli/commands/oauth/providers.ts +215 -36
- package/src/cli/commands/oauth/shared.ts +7 -6
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +254 -0
- package/src/cli/commands/platform/__tests__/connect.test.ts +6 -0
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +6 -0
- package/src/cli/commands/platform/index.ts +107 -10
- package/src/cli/commands/usage.ts +10 -9
- package/src/cli/lib/daemon-credential-client.ts +4 -0
- package/src/cli/program.ts +30 -4
- package/src/config/__tests__/backup-schema.test.ts +134 -0
- package/src/config/assistant-feature-flags.ts +61 -62
- package/src/config/bundled-skills/app-builder/SKILL.md +26 -249
- package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +141 -0
- package/src/config/bundled-skills/app-builder/references/INTERACTION_HOOKS.md +56 -0
- package/src/config/bundled-skills/app-builder/references/WIDGETS.md +125 -0
- package/src/config/bundled-skills/browser/SKILL.md +30 -5
- package/src/config/bundled-skills/browser/TOOLS.json +123 -0
- package/src/config/bundled-skills/browser/tools/browser-attach.ts +12 -0
- package/src/config/bundled-skills/browser/tools/browser-detach.ts +12 -0
- package/src/config/bundled-skills/browser/tools/browser-status.ts +12 -0
- package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +17 -0
- package/src/config/bundled-skills/contacts/SKILL.md +5 -2
- package/src/config/bundled-skills/document/SKILL.md +4 -0
- package/src/config/bundled-skills/gmail/SKILL.md +54 -8
- package/src/config/bundled-skills/gmail/TOOLS.json +33 -3
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +116 -9
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +138 -11
- package/src/config/bundled-skills/gmail/tools/gmail-preferences-tool.ts +59 -0
- package/src/config/bundled-skills/gmail/tools/gmail-preferences.ts +82 -0
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +113 -17
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +2 -2
- package/src/config/bundled-skills/media-processing/SKILL.md +3 -9
- package/src/config/bundled-skills/media-processing/TOOLS.json +1 -6
- package/src/config/bundled-skills/media-processing/__tests__/audio-transcribe.test.ts +125 -0
- package/src/config/bundled-skills/media-processing/__tests__/extract-keyframes.test.ts +181 -0
- package/src/config/bundled-skills/media-processing/__tests__/preprocess-audio.test.ts +141 -0
- package/src/config/bundled-skills/media-processing/services/audio-transcribe.ts +32 -87
- package/src/config/bundled-skills/media-processing/services/preprocess.ts +8 -4
- package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +0 -10
- package/src/config/bundled-skills/messaging/SKILL.md +3 -3
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +2 -2
- package/src/config/bundled-skills/outlook/SKILL.md +9 -2
- package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +2 -2
- package/src/config/bundled-skills/phone-calls/SKILL.md +2 -2
- package/src/config/bundled-skills/phone-calls/references/CONFIG.md +27 -18
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +3 -3
- package/src/config/bundled-skills/settings/TOOLS.json +3 -3
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +26 -22
- package/src/config/bundled-skills/slack/SKILL.md +1 -0
- package/src/config/bundled-skills/subagent/SKILL.md +21 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +8 -4
- package/src/config/bundled-skills/tasks/SKILL.md +5 -0
- package/src/config/bundled-skills/transcribe/SKILL.md +9 -14
- package/src/config/bundled-skills/transcribe/TOOLS.json +2 -7
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +256 -0
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +38 -188
- package/src/config/bundled-tool-registry.ts +8 -0
- package/src/config/env-registry.ts +38 -0
- package/src/config/env.ts +49 -4
- package/src/config/feature-flag-registry.json +85 -14
- package/src/config/loader.ts +82 -13
- package/src/config/sanitize-for-transfer.ts +47 -0
- package/src/config/schema.ts +81 -15
- package/src/config/schemas/__tests__/stt.test.ts +43 -0
- package/src/config/schemas/analysis.ts +51 -0
- package/src/config/schemas/backup.ts +72 -0
- package/src/config/schemas/calls.ts +1 -26
- package/src/config/schemas/elevenlabs.ts +0 -59
- package/src/config/schemas/filing.ts +47 -7
- package/src/config/schemas/heartbeat.ts +27 -5
- package/src/config/schemas/host-browser.ts +112 -0
- package/src/config/schemas/inference.ts +1 -1
- package/src/config/schemas/memory-lifecycle.ts +14 -2
- package/src/config/schemas/memory-retrieval.ts +103 -0
- package/src/config/schemas/security.ts +0 -6
- package/src/config/schemas/services.ts +52 -0
- package/src/config/schemas/stt.ts +59 -0
- package/src/config/schemas/tts.ts +230 -0
- package/src/config/schemas/updates.ts +14 -0
- package/src/config/skills.ts +4 -0
- package/src/config/types.ts +4 -1
- package/src/contacts/contact-store.ts +56 -11
- package/src/contacts/contacts-write.ts +38 -1
- package/src/context/post-turn-tool-result-truncation.ts +177 -0
- package/src/context/tool-result-truncation.ts +2 -1
- package/src/context/window-manager.ts +61 -10
- package/src/credential-execution/approval-bridge.ts +49 -15
- package/src/credential-execution/executable-discovery.ts +12 -2
- package/src/credential-execution/process-manager.ts +33 -2
- package/src/credential-health/credential-health-service.ts +366 -0
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +324 -0
- package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +497 -0
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +195 -0
- package/src/daemon/__tests__/lifecycle-startup-ordering.test.ts +127 -0
- package/src/daemon/app-source-watcher.ts +35 -0
- package/src/daemon/config-watcher.ts +99 -5
- package/src/daemon/context-overflow-approval.ts +5 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +23 -2
- package/src/daemon/conversation-agent-loop.ts +153 -42
- package/src/daemon/conversation-attachments.ts +40 -0
- package/src/daemon/conversation-error.ts +11 -0
- package/src/daemon/conversation-history.ts +40 -6
- package/src/daemon/conversation-launch.ts +220 -0
- package/src/daemon/conversation-lifecycle.ts +59 -9
- package/src/daemon/conversation-messaging.ts +37 -3
- package/src/daemon/conversation-notifiers.ts +5 -0
- package/src/daemon/conversation-process.ts +622 -13
- package/src/daemon/conversation-queue-manager.ts +24 -0
- package/src/daemon/conversation-runtime-assembly.ts +128 -36
- package/src/daemon/conversation-slash.ts +36 -0
- package/src/daemon/conversation-surfaces.ts +131 -40
- package/src/daemon/conversation-tool-setup.ts +99 -8
- package/src/daemon/conversation-usage.ts +7 -4
- package/src/daemon/conversation-workspace.ts +12 -0
- package/src/daemon/conversation.ts +292 -16
- package/src/daemon/date-context.ts +10 -10
- package/src/daemon/first-greeting.ts +3 -2
- package/src/daemon/handlers/config-slack-channel.ts +269 -94
- package/src/daemon/handlers/conversations.ts +13 -141
- package/src/daemon/handlers/shared.ts +80 -0
- package/src/daemon/handlers/skills.ts +483 -44
- package/src/daemon/host-bash-proxy.ts +48 -13
- package/src/daemon/host-browser-proxy.ts +192 -0
- package/src/daemon/host-cu-proxy.ts +36 -11
- package/src/daemon/host-file-proxy.ts +57 -9
- package/src/daemon/lifecycle.ts +179 -28
- package/src/daemon/message-protocol.ts +13 -0
- package/src/daemon/message-types/conversations.ts +89 -14
- package/src/daemon/message-types/home.ts +40 -0
- package/src/daemon/message-types/host-browser.ts +100 -0
- package/src/daemon/message-types/meet.ts +143 -0
- package/src/daemon/message-types/messages.ts +19 -5
- package/src/daemon/message-types/schedules.ts +34 -2
- package/src/daemon/message-types/skills.ts +26 -0
- package/src/daemon/message-types/subagents.ts +2 -0
- package/src/daemon/message-types/surfaces.ts +2 -0
- package/src/daemon/server.ts +439 -14
- package/src/daemon/shutdown-handlers.ts +32 -4
- package/src/daemon/shutdown-registry.ts +40 -0
- package/src/daemon/tool-side-effects.ts +15 -0
- package/src/daemon/transport-hints.ts +5 -24
- package/src/email/html-renderer.ts +76 -0
- package/src/heartbeat/heartbeat-service.ts +93 -7
- package/src/home/__tests__/assistant-feed-authoring.test.ts +156 -0
- package/src/home/__tests__/emit-feed-event.test.ts +169 -0
- package/src/home/__tests__/feed-scheduler.test.ts +194 -0
- package/src/home/__tests__/feed-types.test.ts +275 -0
- package/src/home/__tests__/feed-writer.test.ts +688 -0
- package/src/home/__tests__/phase5-exit-criteria.test.ts +212 -0
- package/src/home/__tests__/platform-gmail-digest.test.ts +222 -0
- package/src/home/__tests__/progress-formula.test.ts +213 -0
- package/src/home/__tests__/relationship-state-writer.test.ts +740 -0
- package/src/home/__tests__/rollup-producer.test.ts +398 -0
- package/src/home/assistant-feed-authoring.ts +124 -0
- package/src/home/emit-feed-event.ts +158 -0
- package/src/home/feed-scheduler.ts +247 -0
- package/src/home/feed-types.ts +181 -0
- package/src/home/feed-writer.ts +469 -0
- package/src/home/platform-gmail-digest.ts +163 -0
- package/src/home/progress-formula.ts +86 -0
- package/src/home/relationship-state-writer.ts +824 -0
- package/src/home/relationship-state.ts +143 -0
- package/src/home/rollup-producer.ts +384 -0
- package/src/hooks/runner.ts +7 -0
- package/src/inbound/platform-callback-registration.ts +30 -20
- package/src/inbound/public-ingress-urls.ts +12 -0
- package/src/instrument.ts +1 -1
- package/src/ipc/__tests__/cli-ipc.test.ts +200 -0
- package/src/ipc/cli-client.ts +151 -0
- package/src/ipc/cli-server.ts +234 -0
- package/src/ipc/gateway-client.ts +180 -0
- package/src/ipc/routes/index.ts +5 -0
- package/src/ipc/routes/wake-conversation.ts +19 -0
- package/src/mcp/client.ts +59 -24
- package/src/memory/__tests__/auto-analysis-enqueue.test.ts +356 -0
- package/src/memory/__tests__/auto-analysis-guard.test.ts +57 -0
- package/src/memory/__tests__/conversation-analyze-job.test.ts +232 -0
- package/src/memory/__tests__/find-analysis-conversation.test.ts +196 -0
- package/src/memory/app-store.ts +31 -1
- package/src/memory/attachments-store.ts +70 -0
- package/src/memory/auto-analysis-enqueue.ts +127 -0
- package/src/memory/auto-analysis-guard.ts +27 -0
- package/src/memory/cleanup-schedule-state.ts +37 -0
- package/src/memory/conversation-analyze-job.ts +73 -0
- package/src/memory/conversation-crud.ts +122 -0
- package/src/memory/conversation-disk-view.ts +7 -0
- package/src/memory/conversation-group-migration.ts +34 -2
- package/src/memory/conversation-queries.ts +6 -5
- package/src/memory/conversation-starters-cadence.ts +76 -0
- package/src/memory/conversation-title-service.ts +5 -2
- package/src/memory/db-init.ts +18 -0
- package/src/memory/db-maintenance.ts +108 -0
- package/src/memory/db.ts +1 -0
- package/src/memory/embedding-backend.test.ts +75 -0
- package/src/memory/embedding-backend.ts +131 -5
- package/src/memory/embedding-gemini.test.ts +54 -0
- package/src/memory/embedding-gemini.ts +20 -9
- package/src/memory/embedding-local.ts +176 -17
- package/src/memory/graph/consolidation.ts +10 -23
- package/src/memory/graph/conversation-graph-memory.ts +15 -0
- package/src/memory/graph/extraction-job.ts +15 -0
- package/src/memory/graph/extraction.test.ts +23 -0
- package/src/memory/graph/extraction.ts +8 -0
- package/src/memory/graph/retriever.ts +67 -40
- package/src/memory/graph/scoring.test.ts +186 -0
- package/src/memory/graph/scoring.ts +31 -1
- package/src/memory/graph/store.test.ts +7 -3
- package/src/memory/graph/store.ts +47 -12
- package/src/memory/graph/tools.ts +1 -1
- package/src/memory/group-crud.ts +6 -1
- package/src/memory/indexer.ts +95 -16
- package/src/memory/job-handlers/cleanup.ts +11 -8
- package/src/memory/job-handlers/conversation-starters.ts +16 -10
- package/src/memory/jobs-store.ts +64 -4
- package/src/memory/jobs-worker.ts +22 -9
- package/src/memory/llm-usage-store.ts +137 -60
- package/src/memory/migrations/213-oauth-providers-scope-separator.ts +13 -0
- package/src/memory/migrations/214-oauth-providers-refresh-url.ts +11 -0
- package/src/memory/migrations/215-oauth-providers-revoke.ts +14 -0
- package/src/memory/migrations/216-oauth-providers-token-auth-method.ts +30 -0
- package/src/memory/migrations/217-conversation-host-access.ts +40 -0
- package/src/memory/migrations/218-oauth-providers-logo-url.ts +11 -0
- package/src/memory/migrations/219-oauth-providers-token-exchange-body-format.ts +15 -0
- package/src/memory/migrations/220-normalize-user-file-by-principal.ts +190 -0
- package/src/memory/migrations/221-conversations-archived-at.ts +16 -0
- package/src/memory/migrations/index.ts +12 -0
- package/src/memory/migrations/registry.ts +16 -0
- package/src/memory/qdrant-manager.ts +43 -16
- package/src/memory/schema/conversations.ts +3 -0
- package/src/memory/schema/oauth.ts +21 -13
- package/src/memory/usage-buckets.ts +396 -0
- package/src/messaging/providers/gmail/client.ts +57 -6
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +282 -0
- package/src/messaging/providers/slack/adapter.ts +143 -38
- package/src/messaging/providers/slack/client.ts +16 -0
- package/src/messaging/providers/slack/types.ts +4 -0
- package/src/notifications/decision-engine.ts +3 -3
- package/src/notifications/signal.ts +5 -0
- package/src/oauth/AGENTS.md +76 -0
- package/src/oauth/__tests__/identity-verifier.test.ts +25 -19
- package/src/oauth/__tests__/seed-providers-managed.test.ts +32 -0
- package/src/oauth/byo-connection.test.ts +26 -9
- package/src/oauth/byo-connection.ts +10 -8
- package/src/oauth/connect-orchestrator.ts +25 -21
- package/src/oauth/connect-types.ts +3 -3
- package/src/oauth/connection-resolver.test.ts +17 -4
- package/src/oauth/connection-resolver.ts +22 -18
- package/src/oauth/connection.ts +3 -1
- package/src/oauth/manual-token-connection.ts +13 -13
- package/src/oauth/oauth-store.ts +223 -100
- package/src/oauth/platform-connection.test.ts +101 -3
- package/src/oauth/platform-connection.ts +56 -35
- package/src/oauth/provider-serializer.ts +31 -5
- package/src/oauth/revoke.ts +76 -0
- package/src/oauth/seed-providers.ts +133 -87
- package/src/oauth/token-persistence.ts +1 -1
- package/src/permissions/checker.ts +16 -6
- package/src/permissions/defaults.ts +49 -1
- package/src/permissions/permission-mode.ts +4 -11
- package/src/permissions/prompter.ts +13 -1
- package/src/permissions/trust-store.ts +3 -3
- package/src/permissions/v2-consent-policy.ts +87 -0
- package/src/permissions/workspace-policy.ts +3 -0
- package/src/platform/client.test.ts +10 -0
- package/src/platform/sync-identity.ts +129 -0
- package/src/prompts/persona-resolver.ts +126 -2
- package/src/prompts/system-prompt.ts +76 -38
- package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +3 -65
- package/src/prompts/templates/BOOTSTRAP.md +59 -105
- package/src/prompts/templates/SOUL.md +3 -1
- package/src/prompts/templates/UPDATES.md +12 -0
- package/src/prompts/templates/channels/slack.md +20 -0
- package/src/prompts/update-bulletin-format.ts +26 -9
- package/src/prompts/update-bulletin.ts +34 -23
- package/src/prompts/user-reference.ts +20 -17
- package/src/providers/__tests__/provider-secret-catalog.test.ts +42 -0
- package/src/providers/anthropic/client.ts +157 -60
- package/src/providers/fireworks/client.ts +2 -2
- package/src/providers/gemini/client.ts +9 -1
- package/src/providers/model-catalog.ts +6 -0
- package/src/providers/model-intents.ts +4 -4
- package/src/providers/ollama/client.ts +2 -2
- package/src/providers/openai/chat-completions-provider.ts +474 -0
- package/src/providers/openai/client.ts +25 -440
- package/src/providers/openai/responses-provider.ts +502 -0
- package/src/providers/openrouter/client.ts +101 -4
- package/src/providers/provider-secret-catalog.ts +139 -0
- package/src/providers/registry.ts +2 -2
- package/src/providers/retry.ts +14 -3
- package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +251 -0
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +828 -0
- package/src/providers/speech-to-text/deepgram-realtime.test.ts +980 -0
- package/src/providers/speech-to-text/deepgram-realtime.ts +767 -0
- package/src/providers/speech-to-text/deepgram.test.ts +332 -0
- package/src/providers/speech-to-text/deepgram.ts +115 -0
- package/src/providers/speech-to-text/google-gemini-live-stream.test.ts +743 -0
- package/src/providers/speech-to-text/google-gemini-live-stream.ts +625 -0
- package/src/providers/speech-to-text/google-gemini.test.ts +226 -0
- package/src/providers/speech-to-text/google-gemini.ts +101 -0
- package/src/providers/speech-to-text/openai-whisper-stream.test.ts +564 -0
- package/src/providers/speech-to-text/openai-whisper-stream.ts +381 -0
- package/src/providers/speech-to-text/openai-whisper.test.ts +1 -37
- package/src/providers/speech-to-text/openai-whisper.ts +63 -33
- package/src/providers/speech-to-text/provider-catalog.ts +306 -0
- package/src/providers/speech-to-text/resolve.ts +386 -6
- package/src/providers/types.ts +10 -1
- package/src/runtime/AGENTS.md +65 -0
- package/src/runtime/__tests__/agent-wake.test.ts +831 -0
- package/src/runtime/__tests__/browser-extension-pair-routes.test.ts +715 -0
- package/src/runtime/__tests__/capability-tokens.test.ts +258 -0
- package/src/runtime/__tests__/chrome-extension-registry.test.ts +518 -0
- package/src/runtime/__tests__/runtime-mode.test.ts +62 -0
- package/src/runtime/__tests__/slack-block-formatting.test.ts +481 -0
- package/src/runtime/agent-wake.ts +512 -0
- package/src/runtime/assistant-event-hub.ts +2 -2
- package/src/runtime/auth/__tests__/guard-tests.test.ts +1 -0
- package/src/runtime/auth/__tests__/middleware.test.ts +116 -1
- package/src/runtime/auth/__tests__/route-policy.test.ts +48 -0
- package/src/runtime/auth/middleware.ts +98 -0
- package/src/runtime/auth/route-policy.ts +33 -9
- package/src/runtime/auth/token-service.ts +56 -1
- package/src/runtime/btw-sidechain.ts +2 -0
- package/src/runtime/capability-tokens.ts +414 -0
- package/src/runtime/channel-approvals.ts +18 -5
- package/src/runtime/channel-invite-transport.ts +1 -1
- package/src/runtime/channel-invite-transports/email.ts +14 -6
- package/src/runtime/channel-readiness-service.ts +12 -22
- package/src/runtime/chrome-extension-registry.ts +368 -0
- package/src/runtime/confirmation-request-guardian-bridge.ts +6 -0
- package/src/runtime/guardian-decision-types.ts +7 -0
- package/src/runtime/http-server.ts +815 -75
- package/src/runtime/http-types.ts +6 -2
- package/src/runtime/migrations/__tests__/rebind-secrets-credentials.test.ts +172 -0
- package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +276 -0
- package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +198 -0
- package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +360 -0
- package/src/runtime/migrations/migration-transport.ts +7 -0
- package/src/runtime/migrations/migration-wizard.ts +23 -2
- package/src/runtime/migrations/rebind-secrets-screen.ts +76 -15
- package/src/runtime/migrations/vbundle-builder.ts +145 -38
- package/src/runtime/migrations/vbundle-import-analyzer.ts +96 -1
- package/src/runtime/migrations/vbundle-importer.ts +89 -5
- package/src/runtime/pending-interactions.ts +18 -13
- package/src/runtime/routes/__tests__/backup-routes.test.ts +967 -0
- package/src/runtime/routes/__tests__/home-feed-routes.test.ts +507 -0
- package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +208 -0
- package/src/runtime/routes/__tests__/stt-routes.test.ts +406 -0
- package/src/runtime/routes/__tests__/tts-routes.test.ts +474 -0
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +148 -17
- package/src/runtime/routes/app-management-routes.ts +12 -18
- package/src/runtime/routes/approval-routes.ts +90 -16
- package/src/runtime/routes/attachment-routes.test.ts +9 -3
- package/src/runtime/routes/attachment-routes.ts +216 -17
- package/src/runtime/routes/backup-routes.ts +519 -0
- package/src/runtime/routes/browser-extension-pair-routes.ts +556 -0
- package/src/runtime/routes/btw-routes.ts +8 -6
- package/src/runtime/routes/contact-routes.test.ts +298 -0
- package/src/runtime/routes/contact-routes.ts +132 -5
- package/src/runtime/routes/conversation-analysis-routes.ts +22 -141
- package/src/runtime/routes/conversation-management-routes.ts +223 -0
- package/src/runtime/routes/conversation-routes.ts +598 -103
- package/src/runtime/routes/conversation-starter-routes.ts +78 -16
- package/src/runtime/routes/filing-routes.ts +93 -0
- package/src/runtime/routes/guardian-action-routes.ts +24 -13
- package/src/runtime/routes/home-feed-routes.ts +334 -0
- package/src/runtime/routes/home-state-routes.ts +138 -0
- package/src/runtime/routes/host-browser-routes.ts +268 -0
- package/src/runtime/routes/host-file-routes.ts +9 -1
- package/src/runtime/routes/identity-intro-cache.ts +7 -3
- package/src/runtime/routes/identity-routes.ts +262 -33
- package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +46 -39
- package/src/runtime/routes/inbound-stages/transcribe-audio.ts +15 -15
- package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +137 -0
- package/src/runtime/routes/integrations/slack/__tests__/share.test.ts +179 -0
- package/src/runtime/routes/integrations/slack/channel.ts +11 -3
- package/src/runtime/routes/integrations/slack/share.ts +45 -7
- package/src/runtime/routes/llm-context-normalization.ts +303 -0
- package/src/runtime/routes/log-export-routes.ts +42 -22
- package/src/runtime/routes/memory-item-routes.test.ts +3 -2
- package/src/runtime/routes/memory-item-routes.ts +1 -7
- package/src/runtime/routes/migration-routes.ts +122 -2
- package/src/runtime/routes/oauth-apps.ts +15 -17
- package/src/runtime/routes/oauth-providers.ts +4 -0
- package/src/runtime/routes/schedule-routes.ts +24 -11
- package/src/runtime/routes/settings-routes.ts +31 -102
- package/src/runtime/routes/skills-routes.ts +128 -9
- package/src/runtime/routes/stt-routes.ts +233 -0
- package/src/runtime/routes/subagents-routes.ts +14 -10
- package/src/runtime/routes/surface-action-routes.ts +41 -2
- package/src/runtime/routes/tts-routes.ts +108 -24
- package/src/runtime/routes/usage-routes.ts +38 -9
- package/src/runtime/routes/user-route-dispatcher.ts +50 -5
- package/src/runtime/routes/user-routes.ts +13 -1
- package/src/runtime/routes/work-items-routes.ts +8 -1
- package/src/runtime/routes/workspace-routes.test.ts +22 -0
- package/src/runtime/routes/workspace-routes.ts +8 -1
- package/src/runtime/routes/workspace-utils.ts +2 -0
- package/src/runtime/runtime-mode.ts +33 -0
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +444 -0
- package/src/runtime/services/__tests__/analyze-deps-singleton.test.ts +67 -0
- package/src/runtime/services/__tests__/auto-analysis-prompt.test.ts +53 -0
- package/src/runtime/services/__tests__/manual-analysis-prompt.test.ts +41 -0
- package/src/runtime/services/analyze-conversation.ts +344 -0
- package/src/runtime/services/analyze-deps-singleton.ts +32 -0
- package/src/runtime/services/auto-analysis-prompt.ts +55 -0
- package/src/runtime/skill-route-registry.ts +49 -0
- package/src/runtime/slack-block-formatting.ts +437 -10
- package/src/schedule/scheduler.ts +57 -5
- package/src/security/ces-credential-client.ts +20 -0
- package/src/security/ces-rpc-credential-backend.ts +17 -0
- package/src/security/credential-backend.ts +5 -0
- package/src/security/oauth2.ts +68 -29
- package/src/security/secure-keys.ts +143 -27
- package/src/security/token-manager.ts +31 -10
- package/src/sequence/engine.ts +23 -0
- package/src/sequence/types.ts +1 -1
- package/src/skills/catalog-files.ts +554 -0
- package/src/skills/category-inference.ts +122 -0
- package/src/skills/clawhub-files.ts +213 -0
- package/src/skills/clawhub.ts +84 -23
- package/src/skills/skill-file-provider.ts +40 -0
- package/src/skills/skillssh-files.ts +395 -0
- package/src/skills/skillssh-registry.ts +4 -4
- package/src/stt/__tests__/daemon-batch-transcriber.test.ts +392 -0
- package/src/stt/__tests__/types.test.ts +89 -0
- package/src/stt/daemon-batch-transcriber.ts +195 -0
- package/src/stt/stt-stream-session.ts +499 -0
- package/src/stt/types.ts +330 -0
- package/src/stt/wav-encoder.test.ts +373 -0
- package/src/stt/wav-encoder.ts +175 -0
- package/src/subagent/manager.ts +169 -40
- package/src/subagent/types.ts +19 -0
- package/src/tools/apps/executors.ts +11 -2
- package/src/tools/browser/__tests__/auth-detector.test.ts +202 -108
- package/src/tools/browser/__tests__/browser-mode.test.ts +119 -0
- package/src/tools/browser/__tests__/browser-status.test.ts +123 -0
- package/src/tools/browser/auth-detector.ts +43 -12
- package/src/tools/browser/browser-execution.ts +1787 -342
- package/src/tools/browser/browser-manager.ts +81 -12
- package/src/tools/browser/browser-mode-constants.ts +12 -0
- package/src/tools/browser/browser-mode.ts +92 -0
- package/src/tools/browser/browser-status-constants.ts +33 -0
- package/src/tools/browser/cdp-client/__tests__/accessibility-snapshot.test.ts +318 -0
- package/src/tools/browser/cdp-client/__tests__/cdp-dom-helpers.test.ts +1175 -0
- package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +1263 -0
- package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +359 -0
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +1993 -0
- package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-nested-frames.json +64 -0
- package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-simple.json +69 -0
- package/src/tools/browser/cdp-client/__tests__/local-cdp-client.test.ts +310 -0
- package/src/tools/browser/cdp-client/__tests__/types.test.ts +96 -0
- package/src/tools/browser/cdp-client/accessibility-snapshot.ts +387 -0
- package/src/tools/browser/cdp-client/cdp-dom-helpers.ts +695 -0
- package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +1007 -0
- package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +580 -0
- package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +744 -0
- package/src/tools/browser/cdp-client/cdp-inspect/ws-transport.ts +579 -0
- package/src/tools/browser/cdp-client/cdp-inspect-client.ts +868 -0
- package/src/tools/browser/cdp-client/errors.ts +49 -0
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +148 -0
- package/src/tools/browser/cdp-client/factory.ts +914 -0
- package/src/tools/browser/cdp-client/index.ts +28 -0
- package/src/tools/browser/cdp-client/local-cdp-client.ts +187 -0
- package/src/tools/browser/cdp-client/types.ts +120 -0
- package/src/tools/credentials/vault.ts +35 -6
- package/src/tools/filesystem/edit.ts +1 -1
- package/src/tools/filesystem/list.ts +1 -1
- package/src/tools/filesystem/read.ts +1 -1
- package/src/tools/filesystem/write.ts +2 -1
- package/src/tools/host-filesystem/edit.ts +1 -1
- package/src/tools/host-filesystem/read.ts +12 -15
- package/src/tools/host-filesystem/write.ts +1 -1
- package/src/tools/host-terminal/host-shell.ts +21 -16
- package/src/tools/network/web-fetch.ts +5 -2
- package/src/tools/network/web-search.ts +5 -2
- package/src/tools/permission-checker.ts +77 -82
- package/src/tools/registry.ts +0 -2
- package/src/tools/secret-detection-handler.ts +34 -0
- package/src/tools/shared/filesystem/image-read.ts +61 -40
- package/src/tools/shared/shell-output.ts +3 -1
- package/src/tools/side-effects.ts +2 -0
- package/src/tools/skills/sandbox-runner.ts +3 -2
- package/src/tools/subagent/spawn.ts +47 -3
- package/src/tools/subagent/status.ts +2 -0
- package/src/tools/system/register.ts +2 -16
- package/src/tools/terminal/safe-env.ts +15 -0
- package/src/tools/terminal/shell.ts +36 -20
- package/src/tools/tool-approval-handler.ts +48 -2
- package/src/tools/tool-manifest.ts +21 -0
- package/src/tools/types.ts +19 -0
- package/src/tools/ui-surface/definitions.ts +6 -1
- package/src/tts/__tests__/provider-adapters.test.ts +834 -0
- package/src/tts/__tests__/provider-catalog-consistency.test.ts +196 -0
- package/src/tts/__tests__/provider-catalog.test.ts +183 -0
- package/src/tts/__tests__/provider-registry.test.ts +90 -0
- package/src/tts/provider-catalog.ts +201 -0
- package/src/tts/provider-registry.ts +73 -0
- package/src/tts/providers/deepgram-provider.ts +219 -0
- package/src/tts/providers/elevenlabs-provider.ts +211 -0
- package/src/tts/providers/fish-audio-provider.ts +183 -0
- package/src/tts/providers/index.ts +42 -0
- package/src/tts/providers/register-builtins.ts +130 -0
- package/src/tts/synthesize-text.ts +110 -0
- package/src/tts/tts-config-resolver.ts +78 -0
- package/src/tts/types.ts +153 -0
- package/src/types/onboarding-context.ts +7 -0
- package/src/util/abort-reasons.ts +58 -0
- package/src/util/device-id.ts +32 -16
- package/src/util/errors.ts +9 -1
- package/src/util/platform.ts +63 -24
- package/src/util/pricing.ts +66 -3
- package/src/util/spawn.ts +1 -1
- package/src/util/truncate.ts +4 -2
- package/src/util/unicode.ts +201 -0
- package/src/version.ts +19 -24
- package/src/watcher/engine.ts +23 -0
- package/src/watcher/watcher-store.ts +31 -0
- package/src/workspace/migrations/003-seed-device-id.ts +9 -3
- package/src/workspace/migrations/017-seed-persona-dirs.ts +68 -4
- package/src/workspace/migrations/029-seed-pkb.ts +1 -1
- package/src/workspace/migrations/031-drop-user-md.ts +317 -0
- package/src/workspace/migrations/031-llm-log-retention-zero-to-null.ts +73 -0
- package/src/workspace/migrations/032-tts-provider-unification.ts +227 -0
- package/src/workspace/migrations/033-stt-service-explicit-config.ts +122 -0
- package/src/workspace/migrations/034-remove-calls-voice-transcription-provider.ts +215 -0
- package/src/workspace/migrations/035-seed-slack-channel-persona.ts +50 -0
- package/src/workspace/migrations/036-update-pkb-index-bar.ts +37 -0
- package/src/workspace/migrations/037-create-meets-dir.ts +61 -0
- package/src/workspace/migrations/registry.ts +16 -0
- package/src/workspace/top-level-renderer.ts +31 -1
- package/src/workspace/turn-commit.ts +31 -0
- package/src/__tests__/chrome-cdp.test.ts +0 -419
- package/src/__tests__/email-cli.test.ts +0 -297
- package/src/__tests__/email-service-config-fallback.test.ts +0 -102
- package/src/__tests__/permission-mode-sse.test.ts +0 -418
- package/src/__tests__/permission-mode-store.test.ts +0 -277
- package/src/browser-extension-relay/protocol.ts +0 -63
- package/src/browser-extension-relay/server.ts +0 -203
- package/src/cli/commands/browser-relay.ts +0 -536
- package/src/config/schemas/sandbox.ts +0 -14
- package/src/email/guardrails.ts +0 -221
- package/src/email/provider.ts +0 -117
- package/src/email/providers/agentmail.ts +0 -361
- package/src/email/providers/index.ts +0 -65
- package/src/email/service.ts +0 -384
- package/src/email/types.ts +0 -126
- package/src/permissions/permission-mode-store.ts +0 -180
- package/src/prompts/templates/USER.md +0 -13
- package/src/providers/speech-to-text/types.ts +0 -17
- package/src/tools/browser/chrome-cdp.ts +0 -239
- package/src/tools/system/set-permission-mode.ts +0 -103
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { rmSync, writeFileSync } from "node:fs";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
afterAll,
|
|
4
|
+
afterEach,
|
|
5
|
+
beforeEach,
|
|
6
|
+
describe,
|
|
7
|
+
expect,
|
|
8
|
+
mock,
|
|
9
|
+
test,
|
|
10
|
+
} from "bun:test";
|
|
3
11
|
|
|
4
12
|
import type {
|
|
5
13
|
AgentEvent,
|
|
@@ -95,6 +103,22 @@ mock.module("../config/loader.js", () => ({
|
|
|
95
103
|
invalidateConfigCache: () => {},
|
|
96
104
|
}));
|
|
97
105
|
|
|
106
|
+
const mockedConversationHostAccess = new Map<string, boolean>();
|
|
107
|
+
|
|
108
|
+
const capturedAddMessages: Array<{
|
|
109
|
+
id: string;
|
|
110
|
+
role: string;
|
|
111
|
+
content: string;
|
|
112
|
+
metadata?: Record<string, unknown>;
|
|
113
|
+
}> = [];
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Content substrings that should cause `addMessage` to throw — used to
|
|
117
|
+
* simulate a mid-batch persist failure (e.g. a DB error on a specific
|
|
118
|
+
* tail message while its siblings succeed).
|
|
119
|
+
*/
|
|
120
|
+
const addMessageShouldThrowForContent = new Set<string>();
|
|
121
|
+
|
|
98
122
|
mock.module("../prompts/system-prompt.js", () => ({
|
|
99
123
|
buildSystemPrompt: () => "system prompt",
|
|
100
124
|
}));
|
|
@@ -113,6 +137,7 @@ mock.module("../permissions/trust-store.js", () => ({
|
|
|
113
137
|
addRule: () => {},
|
|
114
138
|
findHighestPriorityRule: () => null,
|
|
115
139
|
clearCache: () => {},
|
|
140
|
+
patternMatchesCandidate: () => false,
|
|
116
141
|
}));
|
|
117
142
|
|
|
118
143
|
mock.module("../security/secret-allowlist.js", () => ({
|
|
@@ -122,7 +147,16 @@ mock.module("../security/secret-allowlist.js", () => ({
|
|
|
122
147
|
mock.module("../memory/conversation-crud.js", () => ({
|
|
123
148
|
getConversationType: () => "default",
|
|
124
149
|
setConversationOriginChannelIfUnset: () => {},
|
|
150
|
+
setConversationOriginInterfaceIfUnset: () => {},
|
|
125
151
|
updateConversationContextWindow: () => {},
|
|
152
|
+
getConversationHostAccess: (conversationId: string) =>
|
|
153
|
+
mockedConversationHostAccess.get(conversationId) ?? false,
|
|
154
|
+
updateConversationHostAccess: (
|
|
155
|
+
conversationId: string,
|
|
156
|
+
hostAccess: boolean,
|
|
157
|
+
) => {
|
|
158
|
+
mockedConversationHostAccess.set(conversationId, hostAccess);
|
|
159
|
+
},
|
|
126
160
|
deleteMessageById: () => {},
|
|
127
161
|
provenanceFromTrustContext: () => ({
|
|
128
162
|
source: "user",
|
|
@@ -140,11 +174,28 @@ mock.module("../memory/conversation-crud.js", () => ({
|
|
|
140
174
|
totalEstimatedCost: 0,
|
|
141
175
|
}),
|
|
142
176
|
createConversation: () => ({ id: "conv-1" }),
|
|
143
|
-
addMessage: (
|
|
144
|
-
|
|
177
|
+
addMessage: (
|
|
178
|
+
_convId: string,
|
|
179
|
+
role: string,
|
|
180
|
+
content: string,
|
|
181
|
+
metadata?: Record<string, unknown>,
|
|
182
|
+
) => {
|
|
183
|
+
// Simulate a persist failure for tests that need to exercise the
|
|
184
|
+
// tail-persist-failed path in drainBatch. Triggered by matching any
|
|
185
|
+
// registered substring against the serialized content payload.
|
|
186
|
+
for (const needle of addMessageShouldThrowForContent) {
|
|
187
|
+
if (content.includes(needle)) {
|
|
188
|
+
throw new Error(`Simulated addMessage failure for content: ${needle}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
const id = `msg-${Date.now()}-${capturedAddMessages.length}`;
|
|
192
|
+
capturedAddMessages.push({ id, role, content, metadata });
|
|
193
|
+
return { id };
|
|
145
194
|
},
|
|
146
195
|
updateConversationUsage: () => {},
|
|
147
196
|
updateConversationTitle: () => {},
|
|
197
|
+
getMessageById: () => null,
|
|
198
|
+
getLastUserTimestampBefore: () => 0,
|
|
148
199
|
}));
|
|
149
200
|
|
|
150
201
|
mock.module("../memory/conversation-queries.js", () => ({
|
|
@@ -437,6 +488,7 @@ beforeEach(() => {
|
|
|
437
488
|
turnCommitCalls.length = 0;
|
|
438
489
|
turnCommitHangForever = false;
|
|
439
490
|
linkAttachmentShouldThrow = false;
|
|
491
|
+
addMessageShouldThrowForContent.clear();
|
|
440
492
|
});
|
|
441
493
|
|
|
442
494
|
afterAll(() => {
|
|
@@ -502,44 +554,73 @@ describe("Conversation message queue", () => {
|
|
|
502
554
|
await new Promise((r) => setTimeout(r, 10));
|
|
503
555
|
});
|
|
504
556
|
|
|
505
|
-
test("[experimental] queued
|
|
557
|
+
test("[experimental] queued passthrough siblings drain as a single batched run", async () => {
|
|
506
558
|
const conversation = makeConversation();
|
|
507
559
|
await conversation.loadFromDb();
|
|
508
560
|
|
|
509
|
-
const
|
|
510
|
-
|
|
511
|
-
const
|
|
512
|
-
if (e.type === "message_complete") processedOrder.push(label);
|
|
513
|
-
};
|
|
561
|
+
const events1: ServerMessage[] = [];
|
|
562
|
+
const events2: ServerMessage[] = [];
|
|
563
|
+
const events3: ServerMessage[] = [];
|
|
514
564
|
|
|
515
565
|
// Start first message
|
|
516
566
|
const p1 = conversation.processMessage(
|
|
517
567
|
"msg-1",
|
|
518
568
|
[],
|
|
519
|
-
|
|
569
|
+
(e) => events1.push(e),
|
|
520
570
|
"req-1",
|
|
521
571
|
);
|
|
522
572
|
await waitForPendingRun(1);
|
|
523
573
|
|
|
524
|
-
// Enqueue two more
|
|
525
|
-
conversation.enqueueMessage("msg-2", [],
|
|
526
|
-
conversation.enqueueMessage("msg-3", [],
|
|
574
|
+
// Enqueue two more sibling passthrough messages
|
|
575
|
+
conversation.enqueueMessage("msg-2", [], (e) => events2.push(e), "req-2");
|
|
576
|
+
conversation.enqueueMessage("msg-3", [], (e) => events3.push(e), "req-3");
|
|
527
577
|
expect(conversation.getQueueDepth()).toBe(2);
|
|
528
578
|
|
|
529
|
-
// Complete
|
|
579
|
+
// Complete run 0 → drain pulls msg-2 and msg-3 into ONE batched run.
|
|
530
580
|
resolveRun(0);
|
|
531
581
|
await p1;
|
|
532
582
|
await waitForPendingRun(2);
|
|
533
583
|
|
|
534
|
-
//
|
|
535
|
-
|
|
536
|
-
await waitForPendingRun(3);
|
|
584
|
+
// Exactly two runs total (not three): run 0 = msg-1, run 1 = batched [msg-2, msg-3]
|
|
585
|
+
expect(pendingRuns.length).toBe(2);
|
|
537
586
|
|
|
538
|
-
//
|
|
539
|
-
|
|
587
|
+
// Each batched client saw its own message_dequeued tagged with its own requestId.
|
|
588
|
+
const dequeued2 = events2.filter((e) => e.type === "message_dequeued");
|
|
589
|
+
expect(dequeued2).toHaveLength(1);
|
|
590
|
+
expect(dequeued2[0]).toEqual({
|
|
591
|
+
type: "message_dequeued",
|
|
592
|
+
conversationId: "conv-1",
|
|
593
|
+
requestId: "req-2",
|
|
594
|
+
});
|
|
595
|
+
const dequeued3 = events3.filter((e) => e.type === "message_dequeued");
|
|
596
|
+
expect(dequeued3).toHaveLength(1);
|
|
597
|
+
expect(dequeued3[0]).toEqual({
|
|
598
|
+
type: "message_dequeued",
|
|
599
|
+
conversationId: "conv-1",
|
|
600
|
+
requestId: "req-3",
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
// The batched run's captured history carries both siblings. Either as
|
|
604
|
+
// separate user entries (raw history) or merged into one user entry
|
|
605
|
+
// (after history-repair's alternation enforcement — required by the
|
|
606
|
+
// Anthropic API). Either way, both msg-2 and msg-3 text must appear.
|
|
607
|
+
const batchedHistory = pendingRuns[1].messages;
|
|
608
|
+
const userMessages = batchedHistory.filter((m) => m.role === "user");
|
|
609
|
+
const textOf = (m: Message) =>
|
|
610
|
+
(Array.isArray(m.content) ? m.content : [])
|
|
611
|
+
.filter((b) => b.type === "text")
|
|
612
|
+
.map((b) => (b as { text: string }).text)
|
|
613
|
+
.join("\n");
|
|
614
|
+
const combinedUserText = userMessages.map(textOf).join("\n");
|
|
615
|
+
expect(combinedUserText).toContain("msg-2");
|
|
616
|
+
expect(combinedUserText).toContain("msg-3");
|
|
617
|
+
|
|
618
|
+
// Resolve the batched run; message_complete must fan out to both clients.
|
|
619
|
+
resolveRun(1);
|
|
540
620
|
await new Promise((r) => setTimeout(r, 10));
|
|
541
621
|
|
|
542
|
-
expect(
|
|
622
|
+
expect(events2.some((e) => e.type === "message_complete")).toBe(true);
|
|
623
|
+
expect(events3.some((e) => e.type === "message_complete")).toBe(true);
|
|
543
624
|
});
|
|
544
625
|
|
|
545
626
|
test("message_queued and message_dequeued events are emitted", async () => {
|
|
@@ -680,27 +761,17 @@ describe("Conversation message queue", () => {
|
|
|
680
761
|
conversation.enqueueMessage("msg-4", [], () => {}, "req-4");
|
|
681
762
|
expect(conversation.getQueueDepth()).toBe(3);
|
|
682
763
|
|
|
683
|
-
// Complete first →
|
|
764
|
+
// Complete first → drain pulls all three same-interface passthroughs
|
|
765
|
+
// into a single batched run (depth → 0, runs → 2 total).
|
|
684
766
|
resolveRun(0);
|
|
685
767
|
await p1;
|
|
686
768
|
await waitForPendingRun(2);
|
|
687
769
|
|
|
688
|
-
expect(conversation.getQueueDepth()).toBe(2);
|
|
689
|
-
|
|
690
|
-
// Complete second → drains another
|
|
691
|
-
resolveRun(1);
|
|
692
|
-
await waitForPendingRun(3);
|
|
693
|
-
|
|
694
|
-
expect(conversation.getQueueDepth()).toBe(1);
|
|
695
|
-
|
|
696
|
-
// Complete third → drains last
|
|
697
|
-
resolveRun(2);
|
|
698
|
-
await waitForPendingRun(4);
|
|
699
|
-
|
|
700
770
|
expect(conversation.getQueueDepth()).toBe(0);
|
|
771
|
+
expect(pendingRuns.length).toBe(2);
|
|
701
772
|
|
|
702
|
-
// Complete
|
|
703
|
-
resolveRun(
|
|
773
|
+
// Complete the batched run; conversation finishes cleanly.
|
|
774
|
+
resolveRun(1);
|
|
704
775
|
await new Promise((r) => setTimeout(r, 10));
|
|
705
776
|
});
|
|
706
777
|
|
|
@@ -754,6 +825,763 @@ describe("Conversation message queue", () => {
|
|
|
754
825
|
});
|
|
755
826
|
});
|
|
756
827
|
|
|
828
|
+
// ---------------------------------------------------------------------------
|
|
829
|
+
// Batched drain — mixed-interface, slash-in-middle, attachments, byte budget
|
|
830
|
+
// ---------------------------------------------------------------------------
|
|
831
|
+
|
|
832
|
+
describe("Batched drain", () => {
|
|
833
|
+
beforeEach(() => {
|
|
834
|
+
pendingRuns = [];
|
|
835
|
+
});
|
|
836
|
+
|
|
837
|
+
test("mixed-interface queue splits into multiple batches at each interface boundary", async () => {
|
|
838
|
+
const conversation = makeConversation();
|
|
839
|
+
await conversation.loadFromDb();
|
|
840
|
+
|
|
841
|
+
const events2: ServerMessage[] = [];
|
|
842
|
+
const events3: ServerMessage[] = [];
|
|
843
|
+
const events4: ServerMessage[] = [];
|
|
844
|
+
const events5: ServerMessage[] = [];
|
|
845
|
+
|
|
846
|
+
// Start in-flight message (msg-1)
|
|
847
|
+
const p1 = conversation.processMessage("msg-1", [], () => {}, "req-1");
|
|
848
|
+
await waitForPendingRun(1);
|
|
849
|
+
|
|
850
|
+
// Enqueue 4 messages with interfaces [macos, macos, cli, macos].
|
|
851
|
+
// Expected drain: [macos batch of 2] → [cli single] → [macos single].
|
|
852
|
+
const meta = (iface: string) => ({
|
|
853
|
+
userMessageInterface: iface,
|
|
854
|
+
assistantMessageInterface: iface,
|
|
855
|
+
});
|
|
856
|
+
conversation.enqueueMessage(
|
|
857
|
+
"msg-2",
|
|
858
|
+
[],
|
|
859
|
+
(e) => events2.push(e),
|
|
860
|
+
"req-2",
|
|
861
|
+
undefined,
|
|
862
|
+
undefined,
|
|
863
|
+
meta("macos"),
|
|
864
|
+
);
|
|
865
|
+
conversation.enqueueMessage(
|
|
866
|
+
"msg-3",
|
|
867
|
+
[],
|
|
868
|
+
(e) => events3.push(e),
|
|
869
|
+
"req-3",
|
|
870
|
+
undefined,
|
|
871
|
+
undefined,
|
|
872
|
+
meta("macos"),
|
|
873
|
+
);
|
|
874
|
+
conversation.enqueueMessage(
|
|
875
|
+
"msg-4",
|
|
876
|
+
[],
|
|
877
|
+
(e) => events4.push(e),
|
|
878
|
+
"req-4",
|
|
879
|
+
undefined,
|
|
880
|
+
undefined,
|
|
881
|
+
meta("cli"),
|
|
882
|
+
);
|
|
883
|
+
conversation.enqueueMessage(
|
|
884
|
+
"msg-5",
|
|
885
|
+
[],
|
|
886
|
+
(e) => events5.push(e),
|
|
887
|
+
"req-5",
|
|
888
|
+
undefined,
|
|
889
|
+
undefined,
|
|
890
|
+
meta("macos"),
|
|
891
|
+
);
|
|
892
|
+
expect(conversation.getQueueDepth()).toBe(4);
|
|
893
|
+
|
|
894
|
+
// Resolve msg-1 → batched run pulls macos msg-2 + msg-3.
|
|
895
|
+
resolveRun(0);
|
|
896
|
+
await p1;
|
|
897
|
+
await waitForPendingRun(2);
|
|
898
|
+
|
|
899
|
+
// Batched run's history must contain both macos messages (either as
|
|
900
|
+
// separate user entries or merged into one after history-repair).
|
|
901
|
+
const macosBatchedHistory = pendingRuns[1].messages;
|
|
902
|
+
const macosUserMessages = macosBatchedHistory.filter(
|
|
903
|
+
(m) => m.role === "user",
|
|
904
|
+
);
|
|
905
|
+
const textOf = (m: Message) =>
|
|
906
|
+
(Array.isArray(m.content) ? m.content : [])
|
|
907
|
+
.filter((b) => b.type === "text")
|
|
908
|
+
.map((b) => (b as { text: string }).text)
|
|
909
|
+
.join("\n");
|
|
910
|
+
const combinedMacosText = macosUserMessages.map(textOf).join("\n");
|
|
911
|
+
expect(combinedMacosText).toContain("msg-2");
|
|
912
|
+
expect(combinedMacosText).toContain("msg-3");
|
|
913
|
+
|
|
914
|
+
// Both msg-2 and msg-3 received their own dequeue event.
|
|
915
|
+
expect(events2.filter((e) => e.type === "message_dequeued")).toHaveLength(
|
|
916
|
+
1,
|
|
917
|
+
);
|
|
918
|
+
expect(events3.filter((e) => e.type === "message_dequeued")).toHaveLength(
|
|
919
|
+
1,
|
|
920
|
+
);
|
|
921
|
+
|
|
922
|
+
// Resolve the batched run → drain pulls the cli single-message run.
|
|
923
|
+
resolveRun(1);
|
|
924
|
+
await waitForPendingRun(3);
|
|
925
|
+
|
|
926
|
+
// cli run contains msg-4 as a single-message run.
|
|
927
|
+
const cliHistory = pendingRuns[2].messages;
|
|
928
|
+
const cliUserText = cliHistory
|
|
929
|
+
.filter((m) => m.role === "user")
|
|
930
|
+
.map(textOf)
|
|
931
|
+
.join("\n");
|
|
932
|
+
expect(cliUserText).toContain("msg-4");
|
|
933
|
+
expect(events4.filter((e) => e.type === "message_dequeued")).toHaveLength(
|
|
934
|
+
1,
|
|
935
|
+
);
|
|
936
|
+
|
|
937
|
+
// Resolve the cli run → drain pulls the final macos single-message run.
|
|
938
|
+
resolveRun(2);
|
|
939
|
+
await waitForPendingRun(4);
|
|
940
|
+
const finalHistory = pendingRuns[3].messages;
|
|
941
|
+
const finalUserText = finalHistory
|
|
942
|
+
.filter((m) => m.role === "user")
|
|
943
|
+
.map(textOf)
|
|
944
|
+
.join("\n");
|
|
945
|
+
expect(finalUserText).toContain("msg-5");
|
|
946
|
+
expect(events5.filter((e) => e.type === "message_dequeued")).toHaveLength(
|
|
947
|
+
1,
|
|
948
|
+
);
|
|
949
|
+
|
|
950
|
+
// Four total runs: msg-1, batched [msg-2, msg-3], msg-4, msg-5.
|
|
951
|
+
expect(pendingRuns.length).toBe(4);
|
|
952
|
+
|
|
953
|
+
resolveRun(3);
|
|
954
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
955
|
+
});
|
|
956
|
+
|
|
957
|
+
test("slash-in-middle splits the queue at the slash boundary", async () => {
|
|
958
|
+
const conversation = makeConversation();
|
|
959
|
+
await conversation.loadFromDb();
|
|
960
|
+
|
|
961
|
+
const eventsHello: ServerMessage[] = [];
|
|
962
|
+
const eventsSlash: ServerMessage[] = [];
|
|
963
|
+
const eventsWorld: ServerMessage[] = [];
|
|
964
|
+
|
|
965
|
+
// Start in-flight message
|
|
966
|
+
const p1 = conversation.processMessage("msg-1", [], () => {}, "req-1");
|
|
967
|
+
await waitForPendingRun(1);
|
|
968
|
+
|
|
969
|
+
// Enqueue ["hello", "/compact", "world"]. /compact resolves to a non-
|
|
970
|
+
// passthrough slash, so the batch builder stops at "hello" (length 1),
|
|
971
|
+
// then /compact takes the single-message /compact short-circuit path
|
|
972
|
+
// (no new runAgentLoop invocation), then "world" drains as its own run.
|
|
973
|
+
conversation.enqueueMessage(
|
|
974
|
+
"hello",
|
|
975
|
+
[],
|
|
976
|
+
(e) => eventsHello.push(e),
|
|
977
|
+
"req-hello",
|
|
978
|
+
);
|
|
979
|
+
conversation.enqueueMessage(
|
|
980
|
+
"/compact",
|
|
981
|
+
[],
|
|
982
|
+
(e) => eventsSlash.push(e),
|
|
983
|
+
"req-slash",
|
|
984
|
+
);
|
|
985
|
+
conversation.enqueueMessage(
|
|
986
|
+
"world",
|
|
987
|
+
[],
|
|
988
|
+
(e) => eventsWorld.push(e),
|
|
989
|
+
"req-world",
|
|
990
|
+
);
|
|
991
|
+
expect(conversation.getQueueDepth()).toBe(3);
|
|
992
|
+
|
|
993
|
+
// Resolve msg-1 → drain pulls "hello" as its own run (batch stops at
|
|
994
|
+
// /compact boundary).
|
|
995
|
+
resolveRun(0);
|
|
996
|
+
await p1;
|
|
997
|
+
await waitForPendingRun(2);
|
|
998
|
+
|
|
999
|
+
expect(pendingRuns.length).toBe(2);
|
|
1000
|
+
expect(eventsHello.some((e) => e.type === "message_dequeued")).toBe(true);
|
|
1001
|
+
// /compact and "world" are still queued.
|
|
1002
|
+
expect(conversation.getQueueDepth()).toBe(2);
|
|
1003
|
+
|
|
1004
|
+
// Resolve "hello" → drain pops /compact via the builder-rejected path,
|
|
1005
|
+
// runs its short-circuit (no new runAgentLoop), then drains "world".
|
|
1006
|
+
resolveRun(1);
|
|
1007
|
+
await waitForPendingRun(3);
|
|
1008
|
+
|
|
1009
|
+
// /compact should have emitted its own message_complete via the short-
|
|
1010
|
+
// circuit path (not via a runAgentLoop run).
|
|
1011
|
+
expect(eventsSlash.some((e) => e.type === "message_complete")).toBe(true);
|
|
1012
|
+
expect(eventsWorld.some((e) => e.type === "message_dequeued")).toBe(true);
|
|
1013
|
+
expect(pendingRuns.length).toBe(3);
|
|
1014
|
+
|
|
1015
|
+
resolveRun(2);
|
|
1016
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
1017
|
+
});
|
|
1018
|
+
|
|
1019
|
+
test("unknown-slash in middle splits the queue at the unknown-slash boundary", async () => {
|
|
1020
|
+
// Covers the `kind: "unknown"` short-circuit branch in drainSingleMessage
|
|
1021
|
+
// specifically. The sibling /compact-in-middle test covers the `kind:
|
|
1022
|
+
// "compact"` short-circuit (via a different code path), so this test
|
|
1023
|
+
// exists to guarantee the batch builder also stops at unknown-kind
|
|
1024
|
+
// boundaries and that the unknown-slash drain path does NOT invoke a new
|
|
1025
|
+
// runAgentLoop run.
|
|
1026
|
+
//
|
|
1027
|
+
// We use `/status`, which the real `resolveSlash` returns as
|
|
1028
|
+
// `{ kind: "unknown", message: <status report> }` when a SlashContext is
|
|
1029
|
+
// present (always true for queued drains via buildSlashContext).
|
|
1030
|
+
const conversation = makeConversation();
|
|
1031
|
+
await conversation.loadFromDb();
|
|
1032
|
+
|
|
1033
|
+
const eventsPlainA: ServerMessage[] = [];
|
|
1034
|
+
const eventsSlash: ServerMessage[] = [];
|
|
1035
|
+
const eventsPlainB: ServerMessage[] = [];
|
|
1036
|
+
|
|
1037
|
+
// Start in-flight message
|
|
1038
|
+
const p1 = conversation.processMessage("msg-1", [], () => {}, "req-1");
|
|
1039
|
+
await waitForPendingRun(1);
|
|
1040
|
+
|
|
1041
|
+
// Enqueue ["plain-a", "/status", "plain-b"]. /status resolves to a non-
|
|
1042
|
+
// passthrough slash (kind: "unknown"), so the batch builder stops at
|
|
1043
|
+
// "plain-a" (length-1 batch → drainSingleMessage), then /status takes the
|
|
1044
|
+
// unknown-slash short-circuit path (no new runAgentLoop invocation — it
|
|
1045
|
+
// emits assistant_text_delta + message_complete inline), then "plain-b"
|
|
1046
|
+
// drains as its own run.
|
|
1047
|
+
conversation.enqueueMessage(
|
|
1048
|
+
"plain-a",
|
|
1049
|
+
[],
|
|
1050
|
+
(e) => eventsPlainA.push(e),
|
|
1051
|
+
"req-plain-a",
|
|
1052
|
+
);
|
|
1053
|
+
conversation.enqueueMessage(
|
|
1054
|
+
"/status",
|
|
1055
|
+
[],
|
|
1056
|
+
(e) => eventsSlash.push(e),
|
|
1057
|
+
"req-slash",
|
|
1058
|
+
);
|
|
1059
|
+
conversation.enqueueMessage(
|
|
1060
|
+
"plain-b",
|
|
1061
|
+
[],
|
|
1062
|
+
(e) => eventsPlainB.push(e),
|
|
1063
|
+
"req-plain-b",
|
|
1064
|
+
);
|
|
1065
|
+
expect(conversation.getQueueDepth()).toBe(3);
|
|
1066
|
+
|
|
1067
|
+
// Resolve msg-1 → drain pulls "plain-a" as its own run (batch stops at
|
|
1068
|
+
// the /status boundary).
|
|
1069
|
+
resolveRun(0);
|
|
1070
|
+
await p1;
|
|
1071
|
+
await waitForPendingRun(2);
|
|
1072
|
+
|
|
1073
|
+
expect(pendingRuns.length).toBe(2);
|
|
1074
|
+
expect(eventsPlainA.some((e) => e.type === "message_dequeued")).toBe(true);
|
|
1075
|
+
// /status and "plain-b" are still queued.
|
|
1076
|
+
expect(conversation.getQueueDepth()).toBe(2);
|
|
1077
|
+
|
|
1078
|
+
// Resolve "plain-a" → drain pops /status via the builder-rejected path,
|
|
1079
|
+
// runs its unknown-slash short-circuit (no new runAgentLoop, emits
|
|
1080
|
+
// assistant_text_delta + message_complete inline), then drains "plain-b"
|
|
1081
|
+
// as its own run.
|
|
1082
|
+
resolveRun(1);
|
|
1083
|
+
await waitForPendingRun(3);
|
|
1084
|
+
|
|
1085
|
+
// /status should have emitted its own assistant_text_delta + message_complete
|
|
1086
|
+
// via the unknown-slash short-circuit path (not via a runAgentLoop run).
|
|
1087
|
+
expect(eventsSlash.some((e) => e.type === "assistant_text_delta")).toBe(
|
|
1088
|
+
true,
|
|
1089
|
+
);
|
|
1090
|
+
expect(eventsSlash.some((e) => e.type === "message_complete")).toBe(true);
|
|
1091
|
+
expect(eventsPlainB.some((e) => e.type === "message_dequeued")).toBe(true);
|
|
1092
|
+
// Only three runs total: msg-1, "plain-a", "plain-b". /status short-circuits
|
|
1093
|
+
// without a runAgentLoop invocation.
|
|
1094
|
+
expect(pendingRuns.length).toBe(3);
|
|
1095
|
+
|
|
1096
|
+
resolveRun(2);
|
|
1097
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
1098
|
+
});
|
|
1099
|
+
|
|
1100
|
+
test("attachments are preserved across a batched drain", async () => {
|
|
1101
|
+
capturedAddMessages.length = 0;
|
|
1102
|
+
const conversation = makeConversation();
|
|
1103
|
+
await conversation.loadFromDb();
|
|
1104
|
+
|
|
1105
|
+
// Start in-flight message
|
|
1106
|
+
const p1 = conversation.processMessage("msg-1", [], () => {}, "req-1");
|
|
1107
|
+
await waitForPendingRun(1);
|
|
1108
|
+
|
|
1109
|
+
// Two sibling messages, each with a distinct image attachment.
|
|
1110
|
+
const attachA = [
|
|
1111
|
+
{
|
|
1112
|
+
id: "att-a",
|
|
1113
|
+
filename: "a.png",
|
|
1114
|
+
mimeType: "image/png",
|
|
1115
|
+
data: Buffer.from("imageA").toString("base64"),
|
|
1116
|
+
filePath: "/tmp/a.png",
|
|
1117
|
+
},
|
|
1118
|
+
];
|
|
1119
|
+
const attachB = [
|
|
1120
|
+
{
|
|
1121
|
+
id: "att-b",
|
|
1122
|
+
filename: "b.png",
|
|
1123
|
+
mimeType: "image/png",
|
|
1124
|
+
data: Buffer.from("imageB").toString("base64"),
|
|
1125
|
+
filePath: "/tmp/b.png",
|
|
1126
|
+
},
|
|
1127
|
+
];
|
|
1128
|
+
conversation.enqueueMessage("with-A", attachA, () => {}, "req-A");
|
|
1129
|
+
conversation.enqueueMessage("with-B", attachB, () => {}, "req-B");
|
|
1130
|
+
expect(conversation.getQueueDepth()).toBe(2);
|
|
1131
|
+
|
|
1132
|
+
resolveRun(0);
|
|
1133
|
+
await p1;
|
|
1134
|
+
await waitForPendingRun(2);
|
|
1135
|
+
|
|
1136
|
+
// Two persisted user rows in the DB (one per batched message), each with
|
|
1137
|
+
// its own imageSourcePaths metadata keyed by the right filename.
|
|
1138
|
+
const userRows = capturedAddMessages.filter(
|
|
1139
|
+
(m) => m.role === "user" && m.content.includes('"image"'),
|
|
1140
|
+
);
|
|
1141
|
+
expect(userRows).toHaveLength(2);
|
|
1142
|
+
const pathsA = (userRows[0].metadata as Record<string, unknown>)
|
|
1143
|
+
?.imageSourcePaths as Record<string, string> | undefined;
|
|
1144
|
+
expect(pathsA).toBeDefined();
|
|
1145
|
+
expect(pathsA!["0:a.png"]).toBe("/tmp/a.png");
|
|
1146
|
+
const pathsB = (userRows[1].metadata as Record<string, unknown>)
|
|
1147
|
+
?.imageSourcePaths as Record<string, string> | undefined;
|
|
1148
|
+
expect(pathsB).toBeDefined();
|
|
1149
|
+
expect(pathsB!["0:b.png"]).toBe("/tmp/b.png");
|
|
1150
|
+
|
|
1151
|
+
// The batched run's in-memory history also reflects both image sources
|
|
1152
|
+
// (enrichMessageWithSourcePaths injects file:// references for images).
|
|
1153
|
+
const batchedHistory = pendingRuns[1].messages;
|
|
1154
|
+
const userMessages = batchedHistory.filter((m) => m.role === "user");
|
|
1155
|
+
const allText = userMessages
|
|
1156
|
+
.map((m) =>
|
|
1157
|
+
(Array.isArray(m.content) ? m.content : [])
|
|
1158
|
+
.filter((b) => b.type === "text")
|
|
1159
|
+
.map((b) => (b as { text: string }).text)
|
|
1160
|
+
.join("\n"),
|
|
1161
|
+
)
|
|
1162
|
+
.join("\n");
|
|
1163
|
+
expect(allText).toContain("a.png");
|
|
1164
|
+
expect(allText).toContain("b.png");
|
|
1165
|
+
|
|
1166
|
+
resolveRun(1);
|
|
1167
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
1168
|
+
});
|
|
1169
|
+
|
|
1170
|
+
test("byte-budget accounting is unchanged by shiftN-based batching", async () => {
|
|
1171
|
+
// Uses a small budget so we can observe reclamation after drain.
|
|
1172
|
+
// Each ~500-char message ≈ 1512 bytes.
|
|
1173
|
+
const conversation = makeConversation();
|
|
1174
|
+
await conversation.loadFromDb();
|
|
1175
|
+
|
|
1176
|
+
const budget = 4000;
|
|
1177
|
+
(conversation as unknown as { queue: MessageQueue }).queue =
|
|
1178
|
+
new MessageQueue(budget);
|
|
1179
|
+
|
|
1180
|
+
// Start in-flight so subsequent enqueues are queued (not processed).
|
|
1181
|
+
const p1 = conversation.processMessage("msg-1", [], () => {}, "req-1");
|
|
1182
|
+
await waitForPendingRun(1);
|
|
1183
|
+
|
|
1184
|
+
// Fill to just-under budget: two ~500-char messages (1512+1512 = 3024 bytes).
|
|
1185
|
+
const accepted1 = conversation.enqueueMessage(
|
|
1186
|
+
"x".repeat(500),
|
|
1187
|
+
[],
|
|
1188
|
+
() => {},
|
|
1189
|
+
"req-big-1",
|
|
1190
|
+
);
|
|
1191
|
+
const accepted2 = conversation.enqueueMessage(
|
|
1192
|
+
"y".repeat(500),
|
|
1193
|
+
[],
|
|
1194
|
+
() => {},
|
|
1195
|
+
"req-big-2",
|
|
1196
|
+
);
|
|
1197
|
+
expect(accepted1.queued).toBe(true);
|
|
1198
|
+
expect(accepted2.queued).toBe(true);
|
|
1199
|
+
// A third would push the queue over budget → rejected. Capture its
|
|
1200
|
+
// onEvent callback so we can verify the queue_full error event reaches
|
|
1201
|
+
// the rejected caller (not just the synchronous return value).
|
|
1202
|
+
const rejectedEvents: ServerMessage[] = [];
|
|
1203
|
+
const rejected = conversation.enqueueMessage(
|
|
1204
|
+
"z".repeat(500),
|
|
1205
|
+
[],
|
|
1206
|
+
(e) => rejectedEvents.push(e),
|
|
1207
|
+
"req-over",
|
|
1208
|
+
);
|
|
1209
|
+
expect(rejected.queued).toBe(false);
|
|
1210
|
+
expect(rejected.rejected).toBe(true);
|
|
1211
|
+
expect(conversation.getQueueDepth()).toBe(2);
|
|
1212
|
+
|
|
1213
|
+
// The rejected caller must have received a `queue_full` error event on
|
|
1214
|
+
// its own onEvent callback — event emission is part of the public
|
|
1215
|
+
// contract, not just the return value.
|
|
1216
|
+
const queueFullErr = rejectedEvents.find(
|
|
1217
|
+
(e) => e.type === "error" && e.category === "queue_full",
|
|
1218
|
+
);
|
|
1219
|
+
expect(queueFullErr).toBeDefined();
|
|
1220
|
+
if (queueFullErr && queueFullErr.type === "error") {
|
|
1221
|
+
expect(queueFullErr.category).toBe("queue_full");
|
|
1222
|
+
expect(typeof queueFullErr.message).toBe("string");
|
|
1223
|
+
expect(queueFullErr.message.length).toBeGreaterThan(0);
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
// Complete in-flight → drain pulls both queued passthroughs as ONE batched run.
|
|
1227
|
+
resolveRun(0);
|
|
1228
|
+
await p1;
|
|
1229
|
+
await waitForPendingRun(2);
|
|
1230
|
+
expect(conversation.getQueueDepth()).toBe(0);
|
|
1231
|
+
|
|
1232
|
+
// Resolve the batched run.
|
|
1233
|
+
resolveRun(1);
|
|
1234
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
1235
|
+
|
|
1236
|
+
// After the full drain, the byte budget must be fully reclaimed — a fresh
|
|
1237
|
+
// round of enqueues up to the budget should succeed again. Spin up another
|
|
1238
|
+
// in-flight message to reach the queueing state.
|
|
1239
|
+
const p2 = conversation.processMessage("msg-2", [], () => {}, "req-2");
|
|
1240
|
+
await waitForPendingRun(3);
|
|
1241
|
+
expect(
|
|
1242
|
+
conversation.enqueueMessage("a".repeat(500), [], () => {}, "req-a")
|
|
1243
|
+
.queued,
|
|
1244
|
+
).toBe(true);
|
|
1245
|
+
expect(
|
|
1246
|
+
conversation.enqueueMessage("b".repeat(500), [], () => {}, "req-b")
|
|
1247
|
+
.queued,
|
|
1248
|
+
).toBe(true);
|
|
1249
|
+
|
|
1250
|
+
resolveRun(2);
|
|
1251
|
+
await p2;
|
|
1252
|
+
await waitForPendingRun(4);
|
|
1253
|
+
resolveRun(3);
|
|
1254
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
1255
|
+
});
|
|
1256
|
+
});
|
|
1257
|
+
|
|
1258
|
+
// ---------------------------------------------------------------------------
|
|
1259
|
+
// Batched drain — correctness fixes (surface exclusion, abort, last-successful
|
|
1260
|
+
// tracking, single activity-state emission)
|
|
1261
|
+
// ---------------------------------------------------------------------------
|
|
1262
|
+
|
|
1263
|
+
describe("Batched drain correctness fixes", () => {
|
|
1264
|
+
beforeEach(() => {
|
|
1265
|
+
pendingRuns = [];
|
|
1266
|
+
capturedAddMessages.length = 0;
|
|
1267
|
+
});
|
|
1268
|
+
|
|
1269
|
+
test("surface-action messages are not batched with regular passthroughs", async () => {
|
|
1270
|
+
const conversation = makeConversation();
|
|
1271
|
+
await conversation.loadFromDb();
|
|
1272
|
+
|
|
1273
|
+
const eventsSurface: ServerMessage[] = [];
|
|
1274
|
+
const eventsRegular: ServerMessage[] = [];
|
|
1275
|
+
|
|
1276
|
+
// Start in-flight message
|
|
1277
|
+
const p1 = conversation.processMessage("msg-1", [], () => {}, "req-1");
|
|
1278
|
+
await waitForPendingRun(1);
|
|
1279
|
+
|
|
1280
|
+
// Enqueue a surface-action message (activeSurfaceId set + tracked in
|
|
1281
|
+
// surfaceActionRequestIds) followed by a regular passthrough from the
|
|
1282
|
+
// same interface. The batch builder must reject the surface-action head
|
|
1283
|
+
// so each drains as its own run.
|
|
1284
|
+
conversation.surfaceActionRequestIds.add("req-surface");
|
|
1285
|
+
conversation.enqueueMessage(
|
|
1286
|
+
"surface action response",
|
|
1287
|
+
[],
|
|
1288
|
+
(e) => eventsSurface.push(e),
|
|
1289
|
+
"req-surface",
|
|
1290
|
+
"surface-1", // activeSurfaceId
|
|
1291
|
+
);
|
|
1292
|
+
conversation.enqueueMessage(
|
|
1293
|
+
"regular follow-up",
|
|
1294
|
+
[],
|
|
1295
|
+
(e) => eventsRegular.push(e),
|
|
1296
|
+
"req-regular",
|
|
1297
|
+
);
|
|
1298
|
+
expect(conversation.getQueueDepth()).toBe(2);
|
|
1299
|
+
|
|
1300
|
+
// Complete run 0 → drain must NOT batch the surface-action with the
|
|
1301
|
+
// regular passthrough. Expect the surface-action to drain as a single
|
|
1302
|
+
// run first.
|
|
1303
|
+
resolveRun(0);
|
|
1304
|
+
await p1;
|
|
1305
|
+
await waitForPendingRun(2);
|
|
1306
|
+
|
|
1307
|
+
// The second run is the surface-action single-message run.
|
|
1308
|
+
const surfaceUserRowsAfterRun2 = capturedAddMessages.filter(
|
|
1309
|
+
(m) => m.role === "user" && m.content.includes("surface action response"),
|
|
1310
|
+
);
|
|
1311
|
+
expect(surfaceUserRowsAfterRun2).toHaveLength(1);
|
|
1312
|
+
expect(eventsSurface.filter((e) => e.type === "message_dequeued")).toHaveLength(
|
|
1313
|
+
1,
|
|
1314
|
+
);
|
|
1315
|
+
|
|
1316
|
+
// Complete the surface-action run; drain pulls the regular passthrough
|
|
1317
|
+
// as its own separate run.
|
|
1318
|
+
resolveRun(1);
|
|
1319
|
+
await waitForPendingRun(3);
|
|
1320
|
+
expect(pendingRuns.length).toBe(3);
|
|
1321
|
+
expect(eventsRegular.filter((e) => e.type === "message_dequeued")).toHaveLength(
|
|
1322
|
+
1,
|
|
1323
|
+
);
|
|
1324
|
+
|
|
1325
|
+
// Total runs = 3: msg-1, surface-action, regular — NOT 2 (would mean
|
|
1326
|
+
// they were batched).
|
|
1327
|
+
resolveRun(2);
|
|
1328
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
1329
|
+
});
|
|
1330
|
+
|
|
1331
|
+
test("abort mid-batch stops tail persists", async () => {
|
|
1332
|
+
const conversation = makeConversation();
|
|
1333
|
+
await conversation.loadFromDb();
|
|
1334
|
+
|
|
1335
|
+
const events1: ServerMessage[] = [];
|
|
1336
|
+
const events2: ServerMessage[] = [];
|
|
1337
|
+
const events3: ServerMessage[] = [];
|
|
1338
|
+
const events4: ServerMessage[] = [];
|
|
1339
|
+
|
|
1340
|
+
// Start in-flight message
|
|
1341
|
+
const p1 = conversation.processMessage(
|
|
1342
|
+
"msg-1",
|
|
1343
|
+
[],
|
|
1344
|
+
(e) => events1.push(e),
|
|
1345
|
+
"req-1",
|
|
1346
|
+
);
|
|
1347
|
+
await waitForPendingRun(1);
|
|
1348
|
+
|
|
1349
|
+
// Enqueue three sibling passthroughs (msg-2 = head, msg-3 = mid,
|
|
1350
|
+
// msg-4 = tail). We trigger abort from msg-3's dequeue callback —
|
|
1351
|
+
// by the time that fires, msg-2 has already been persisted (which
|
|
1352
|
+
// REPLACED the abortController, since persistUserMessage creates a
|
|
1353
|
+
// fresh one). Calling abort() now aborts that fresh controller, and
|
|
1354
|
+
// the drainBatch loop's abort check after msg-3's persist will break,
|
|
1355
|
+
// so msg-4 never persists.
|
|
1356
|
+
conversation.enqueueMessage("msg-2", [], (e) => events2.push(e), "req-2");
|
|
1357
|
+
|
|
1358
|
+
// Install a one-shot abort trigger on msg-3's dequeue event. We do
|
|
1359
|
+
// this before enqueueing so the wrapped callback is what drainBatch
|
|
1360
|
+
// invokes.
|
|
1361
|
+
let aborted = false;
|
|
1362
|
+
const onMsg3Event = (e: ServerMessage) => {
|
|
1363
|
+
events3.push(e);
|
|
1364
|
+
if (!aborted && e.type === "message_dequeued") {
|
|
1365
|
+
aborted = true;
|
|
1366
|
+
conversation.abort();
|
|
1367
|
+
}
|
|
1368
|
+
};
|
|
1369
|
+
conversation.enqueueMessage("msg-3", [], onMsg3Event, "req-3");
|
|
1370
|
+
conversation.enqueueMessage("msg-4", [], (e) => events4.push(e), "req-4");
|
|
1371
|
+
expect(conversation.getQueueDepth()).toBe(3);
|
|
1372
|
+
|
|
1373
|
+
const persistedUserRowCountBefore = capturedAddMessages.filter(
|
|
1374
|
+
(m) => m.role === "user",
|
|
1375
|
+
).length;
|
|
1376
|
+
|
|
1377
|
+
// Complete run 0 → drain pulls the sibling batch.
|
|
1378
|
+
resolveRun(0);
|
|
1379
|
+
await p1;
|
|
1380
|
+
|
|
1381
|
+
// Give the drain loop a chance to iterate. Abort happens on msg-3's
|
|
1382
|
+
// dequeue (between msg-2's persist and msg-3's persist), so msg-3 may
|
|
1383
|
+
// still persist before the abort check at the end of its iteration.
|
|
1384
|
+
// Either way, msg-4 must NOT persist.
|
|
1385
|
+
await new Promise((r) => setTimeout(r, 30));
|
|
1386
|
+
|
|
1387
|
+
const userRowsAfter = capturedAddMessages
|
|
1388
|
+
.slice(persistedUserRowCountBefore)
|
|
1389
|
+
.filter((m) => m.role === "user");
|
|
1390
|
+
const contents = userRowsAfter.map((r) => r.content).join("||");
|
|
1391
|
+
expect(contents).toContain("msg-2");
|
|
1392
|
+
expect(contents).not.toContain("msg-4");
|
|
1393
|
+
expect(
|
|
1394
|
+
events4.filter((e) => e.type === "message_dequeued"),
|
|
1395
|
+
).toHaveLength(0);
|
|
1396
|
+
});
|
|
1397
|
+
|
|
1398
|
+
test("failed tail persist uses last-successful requestId", async () => {
|
|
1399
|
+
const conversation = makeConversation();
|
|
1400
|
+
await conversation.loadFromDb();
|
|
1401
|
+
|
|
1402
|
+
const events1: ServerMessage[] = [];
|
|
1403
|
+
const events2: ServerMessage[] = [];
|
|
1404
|
+
const events3: ServerMessage[] = [];
|
|
1405
|
+
const events4: ServerMessage[] = [];
|
|
1406
|
+
|
|
1407
|
+
// Start in-flight message
|
|
1408
|
+
const p1 = conversation.processMessage(
|
|
1409
|
+
"msg-1",
|
|
1410
|
+
[],
|
|
1411
|
+
(e) => events1.push(e),
|
|
1412
|
+
"req-1",
|
|
1413
|
+
);
|
|
1414
|
+
await waitForPendingRun(1);
|
|
1415
|
+
|
|
1416
|
+
// Enqueue three siblings. Configure addMessage to throw for the second
|
|
1417
|
+
// tail (msg-mid) but succeed for msg-head and msg-tail. This simulates
|
|
1418
|
+
// a middle tail persist failure — currentRequestId should end up as
|
|
1419
|
+
// msg-tail's requestId (the LAST successful persist), not msg-mid's.
|
|
1420
|
+
addMessageShouldThrowForContent.add("msg-mid-unique-marker");
|
|
1421
|
+
|
|
1422
|
+
conversation.enqueueMessage(
|
|
1423
|
+
"msg-head",
|
|
1424
|
+
[],
|
|
1425
|
+
(e) => events2.push(e),
|
|
1426
|
+
"req-head",
|
|
1427
|
+
);
|
|
1428
|
+
conversation.enqueueMessage(
|
|
1429
|
+
"msg-mid-unique-marker",
|
|
1430
|
+
[],
|
|
1431
|
+
(e) => events3.push(e),
|
|
1432
|
+
"req-mid",
|
|
1433
|
+
);
|
|
1434
|
+
conversation.enqueueMessage(
|
|
1435
|
+
"msg-tail",
|
|
1436
|
+
[],
|
|
1437
|
+
(e) => events4.push(e),
|
|
1438
|
+
"req-tail",
|
|
1439
|
+
);
|
|
1440
|
+
expect(conversation.getQueueDepth()).toBe(3);
|
|
1441
|
+
|
|
1442
|
+
// Complete run 0 → batched drain.
|
|
1443
|
+
resolveRun(0);
|
|
1444
|
+
await p1;
|
|
1445
|
+
await waitForPendingRun(2);
|
|
1446
|
+
|
|
1447
|
+
// mid should have emitted an error event via persist failure.
|
|
1448
|
+
const errMid = events3.find((e) => e.type === "error");
|
|
1449
|
+
expect(errMid).toBeDefined();
|
|
1450
|
+
|
|
1451
|
+
// The agent loop should have been invoked with the tail's userMessageId
|
|
1452
|
+
// (last SUCCESSFUL persist), not the mid's. We check via currentRequestId
|
|
1453
|
+
// on the conversation which drainBatch assigns after the loop.
|
|
1454
|
+
expect(
|
|
1455
|
+
(conversation as unknown as { currentRequestId?: string }).currentRequestId,
|
|
1456
|
+
).toBe("req-tail");
|
|
1457
|
+
|
|
1458
|
+
// Cleanup: resolve the batched run.
|
|
1459
|
+
resolveRun(1);
|
|
1460
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
1461
|
+
});
|
|
1462
|
+
|
|
1463
|
+
test("failed tail persist is excluded from fanOutOnEvent agent events", async () => {
|
|
1464
|
+
const conversation = makeConversation();
|
|
1465
|
+
await conversation.loadFromDb();
|
|
1466
|
+
|
|
1467
|
+
const events1: ServerMessage[] = [];
|
|
1468
|
+
const events2: ServerMessage[] = [];
|
|
1469
|
+
const events3: ServerMessage[] = [];
|
|
1470
|
+
const events4: ServerMessage[] = [];
|
|
1471
|
+
|
|
1472
|
+
const p1 = conversation.processMessage(
|
|
1473
|
+
"msg-1",
|
|
1474
|
+
[],
|
|
1475
|
+
(e) => events1.push(e),
|
|
1476
|
+
"req-1",
|
|
1477
|
+
);
|
|
1478
|
+
await waitForPendingRun(1);
|
|
1479
|
+
|
|
1480
|
+
// Mid tail will fail to persist. After the batched run resolves,
|
|
1481
|
+
// message_complete (broadcast via fanOutOnEvent) must NOT land on the
|
|
1482
|
+
// failed mid tail — it already received an error event and persisting
|
|
1483
|
+
// the assistant reply for a user message that has no DB row would
|
|
1484
|
+
// desync the client.
|
|
1485
|
+
addMessageShouldThrowForContent.add("fanout-mid-marker");
|
|
1486
|
+
|
|
1487
|
+
conversation.enqueueMessage(
|
|
1488
|
+
"fanout-head",
|
|
1489
|
+
[],
|
|
1490
|
+
(e) => events2.push(e),
|
|
1491
|
+
"req-fanout-head",
|
|
1492
|
+
);
|
|
1493
|
+
conversation.enqueueMessage(
|
|
1494
|
+
"fanout-mid-marker",
|
|
1495
|
+
[],
|
|
1496
|
+
(e) => events3.push(e),
|
|
1497
|
+
"req-fanout-mid",
|
|
1498
|
+
);
|
|
1499
|
+
conversation.enqueueMessage(
|
|
1500
|
+
"fanout-tail",
|
|
1501
|
+
[],
|
|
1502
|
+
(e) => events4.push(e),
|
|
1503
|
+
"req-fanout-tail",
|
|
1504
|
+
);
|
|
1505
|
+
|
|
1506
|
+
resolveRun(0);
|
|
1507
|
+
await p1;
|
|
1508
|
+
await waitForPendingRun(2);
|
|
1509
|
+
|
|
1510
|
+
// Drive the batched run to emit message_complete via fanOutOnEvent.
|
|
1511
|
+
resolveRun(1);
|
|
1512
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
1513
|
+
|
|
1514
|
+
expect(events3.find((e) => e.type === "error")).toBeDefined();
|
|
1515
|
+
expect(events3.find((e) => e.type === "message_complete")).toBeUndefined();
|
|
1516
|
+
|
|
1517
|
+
expect(events2.find((e) => e.type === "message_complete")).toBeDefined();
|
|
1518
|
+
expect(events4.find((e) => e.type === "message_complete")).toBeDefined();
|
|
1519
|
+
});
|
|
1520
|
+
|
|
1521
|
+
test("drainBatch emits exactly one activity-state event for the whole batch", async () => {
|
|
1522
|
+
const activityStates: ServerMessage[] = [];
|
|
1523
|
+
const conversation = makeConversation((msg) => {
|
|
1524
|
+
if ("type" in msg && msg.type === "assistant_activity_state") {
|
|
1525
|
+
activityStates.push(msg);
|
|
1526
|
+
}
|
|
1527
|
+
});
|
|
1528
|
+
await conversation.loadFromDb();
|
|
1529
|
+
|
|
1530
|
+
// Start in-flight message
|
|
1531
|
+
const p1 = conversation.processMessage("msg-1", [], () => {}, "req-1");
|
|
1532
|
+
await waitForPendingRun(1);
|
|
1533
|
+
|
|
1534
|
+
// Snapshot the count before drain so we only compare batch-emitted
|
|
1535
|
+
// transitions (msg-1's processMessage already fired one).
|
|
1536
|
+
const baseline = activityStates.length;
|
|
1537
|
+
|
|
1538
|
+
// Enqueue three sibling passthroughs.
|
|
1539
|
+
conversation.enqueueMessage("msg-2", [], () => {}, "req-2");
|
|
1540
|
+
conversation.enqueueMessage("msg-3", [], () => {}, "req-3");
|
|
1541
|
+
conversation.enqueueMessage("msg-4", [], () => {}, "req-4");
|
|
1542
|
+
|
|
1543
|
+
// Complete run 0 → drain pulls the batched siblings as ONE run.
|
|
1544
|
+
resolveRun(0);
|
|
1545
|
+
await p1;
|
|
1546
|
+
await waitForPendingRun(2);
|
|
1547
|
+
|
|
1548
|
+
// Filter for "message_dequeued" reasons emitted by the batched drain.
|
|
1549
|
+
const batchEmissions = activityStates
|
|
1550
|
+
.slice(baseline)
|
|
1551
|
+
.filter(
|
|
1552
|
+
(m) =>
|
|
1553
|
+
"type" in m &&
|
|
1554
|
+
m.type === "assistant_activity_state" &&
|
|
1555
|
+
(m as { reason?: string }).reason === "message_dequeued",
|
|
1556
|
+
);
|
|
1557
|
+
expect(batchEmissions).toHaveLength(1);
|
|
1558
|
+
expect(batchEmissions[0]).toMatchObject({
|
|
1559
|
+
type: "assistant_activity_state",
|
|
1560
|
+
reason: "message_dequeued",
|
|
1561
|
+
requestId: "req-2", // head's requestId, per the fix
|
|
1562
|
+
});
|
|
1563
|
+
|
|
1564
|
+
resolveRun(1);
|
|
1565
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
1566
|
+
});
|
|
1567
|
+
|
|
1568
|
+
// Defensive recovery path: buildPassthroughBatch is designed to make
|
|
1569
|
+
// the invariant throw unreachable in practice, so neither the head
|
|
1570
|
+
// branch (re-dispatch batch.slice(1) to drainBatch/drainSingleMessage/
|
|
1571
|
+
// drainQueue) nor the tail branch (skip + continue) can fire in normal
|
|
1572
|
+
// operation. Left as a todo so the harness contract is documented
|
|
1573
|
+
// without wedging mainline CI. Covering this would require either
|
|
1574
|
+
// (a) reflecting into drainBatch to short-circuit resolveSlash for a
|
|
1575
|
+
// specific batch entry, or (b) exposing a seam on SlashContext — both
|
|
1576
|
+
// are more invasive than the safety-net value justifies.
|
|
1577
|
+
test.todo(
|
|
1578
|
+
"invariant violation in persist loop triggers error event + recovery, not stranded state",
|
|
1579
|
+
async () => {
|
|
1580
|
+
// no-op: see comment above.
|
|
1581
|
+
},
|
|
1582
|
+
);
|
|
1583
|
+
});
|
|
1584
|
+
|
|
757
1585
|
// ---------------------------------------------------------------------------
|
|
758
1586
|
// Queue policy primitives
|
|
759
1587
|
// ---------------------------------------------------------------------------
|
|
@@ -943,32 +1771,31 @@ describe("Conversation checkpoint handoff", () => {
|
|
|
943
1771
|
await p1;
|
|
944
1772
|
});
|
|
945
1773
|
|
|
946
|
-
test("[experimental]
|
|
1774
|
+
test("[experimental] checkpoint handoff pulls a batched run for all queued siblings", async () => {
|
|
947
1775
|
const conversation = makeConversation();
|
|
948
1776
|
await conversation.loadFromDb();
|
|
949
1777
|
|
|
950
|
-
const
|
|
951
|
-
|
|
952
|
-
const
|
|
953
|
-
|
|
954
|
-
processedOrder.push(label);
|
|
955
|
-
};
|
|
1778
|
+
const events1: ServerMessage[] = [];
|
|
1779
|
+
const events2: ServerMessage[] = [];
|
|
1780
|
+
const events3: ServerMessage[] = [];
|
|
1781
|
+
const events4: ServerMessage[] = [];
|
|
956
1782
|
|
|
957
|
-
// Start first message
|
|
1783
|
+
// Start first message (mid-tool-use — will yield at the next checkpoint)
|
|
958
1784
|
const p1 = conversation.processMessage(
|
|
959
1785
|
"msg-1",
|
|
960
1786
|
[],
|
|
961
|
-
|
|
1787
|
+
(e) => events1.push(e),
|
|
962
1788
|
"req-1",
|
|
963
1789
|
);
|
|
964
1790
|
await waitForPendingRun(1);
|
|
965
1791
|
|
|
966
|
-
// Enqueue
|
|
967
|
-
conversation.enqueueMessage("msg-2", [],
|
|
968
|
-
conversation.enqueueMessage("msg-3", [],
|
|
969
|
-
|
|
1792
|
+
// Enqueue three sibling passthroughs while msg-1 is mid-turn
|
|
1793
|
+
conversation.enqueueMessage("msg-2", [], (e) => events2.push(e), "req-2");
|
|
1794
|
+
conversation.enqueueMessage("msg-3", [], (e) => events3.push(e), "req-3");
|
|
1795
|
+
conversation.enqueueMessage("msg-4", [], (e) => events4.push(e), "req-4");
|
|
1796
|
+
expect(conversation.getQueueDepth()).toBe(3);
|
|
970
1797
|
|
|
971
|
-
// Simulate the agent loop yielding at the checkpoint (first run)
|
|
1798
|
+
// Simulate the agent loop yielding at the checkpoint (first run is mid-tool-use)
|
|
972
1799
|
const run0 = pendingRuns[0];
|
|
973
1800
|
expect(run0.onCheckpoint).toBeDefined();
|
|
974
1801
|
const decision = run0.onCheckpoint!({
|
|
@@ -983,19 +1810,23 @@ describe("Conversation checkpoint handoff", () => {
|
|
|
983
1810
|
resolveRun(0);
|
|
984
1811
|
await p1;
|
|
985
1812
|
|
|
986
|
-
//
|
|
1813
|
+
// The yielded drain pulls ALL THREE queued siblings as ONE batched run —
|
|
1814
|
+
// not three separate runs.
|
|
987
1815
|
await waitForPendingRun(2);
|
|
1816
|
+
expect(pendingRuns.length).toBe(2);
|
|
988
1817
|
|
|
989
|
-
//
|
|
990
|
-
|
|
991
|
-
|
|
1818
|
+
// Each client saw its own message_dequeued tagged with its own requestId.
|
|
1819
|
+
expect(events2.some((e) => e.type === "message_dequeued")).toBe(true);
|
|
1820
|
+
expect(events3.some((e) => e.type === "message_dequeued")).toBe(true);
|
|
1821
|
+
expect(events4.some((e) => e.type === "message_dequeued")).toBe(true);
|
|
992
1822
|
|
|
993
|
-
//
|
|
994
|
-
resolveRun(
|
|
1823
|
+
// Resolve the batched run — message_complete fans out to all three clients.
|
|
1824
|
+
resolveRun(1);
|
|
995
1825
|
await new Promise((r) => setTimeout(r, 10));
|
|
996
1826
|
|
|
997
|
-
|
|
998
|
-
expect(
|
|
1827
|
+
expect(events2.some((e) => e.type === "message_complete")).toBe(true);
|
|
1828
|
+
expect(events3.some((e) => e.type === "message_complete")).toBe(true);
|
|
1829
|
+
expect(events4.some((e) => e.type === "message_complete")).toBe(true);
|
|
999
1830
|
});
|
|
1000
1831
|
|
|
1001
1832
|
test("[experimental] active run with repeated tool turns + queued message triggers checkpoint handoff", async () => {
|
|
@@ -1081,10 +1912,39 @@ describe("Conversation checkpoint handoff", () => {
|
|
|
1081
1912
|
);
|
|
1082
1913
|
await waitForPendingRun(1);
|
|
1083
1914
|
|
|
1084
|
-
// Enqueue messages B, C, D
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1915
|
+
// Enqueue messages B, C, D — each on a distinct userMessageInterface so the
|
|
1916
|
+
// batch builder stops at each boundary and we see one run per message.
|
|
1917
|
+
const meta = (iface: string) => ({
|
|
1918
|
+
userMessageInterface: iface,
|
|
1919
|
+
assistantMessageInterface: iface,
|
|
1920
|
+
});
|
|
1921
|
+
conversation.enqueueMessage(
|
|
1922
|
+
"msg-B",
|
|
1923
|
+
[],
|
|
1924
|
+
makeHandler("B"),
|
|
1925
|
+
"req-B",
|
|
1926
|
+
undefined,
|
|
1927
|
+
undefined,
|
|
1928
|
+
meta("macos"),
|
|
1929
|
+
);
|
|
1930
|
+
conversation.enqueueMessage(
|
|
1931
|
+
"msg-C",
|
|
1932
|
+
[],
|
|
1933
|
+
makeHandler("C"),
|
|
1934
|
+
"req-C",
|
|
1935
|
+
undefined,
|
|
1936
|
+
undefined,
|
|
1937
|
+
meta("cli"),
|
|
1938
|
+
);
|
|
1939
|
+
conversation.enqueueMessage(
|
|
1940
|
+
"msg-D",
|
|
1941
|
+
[],
|
|
1942
|
+
makeHandler("D"),
|
|
1943
|
+
"req-D",
|
|
1944
|
+
undefined,
|
|
1945
|
+
undefined,
|
|
1946
|
+
meta("vellum"),
|
|
1947
|
+
);
|
|
1088
1948
|
expect(conversation.getQueueDepth()).toBe(3);
|
|
1089
1949
|
|
|
1090
1950
|
// Handoff from A -> B
|
|
@@ -1315,8 +2175,18 @@ describe("Terminal trace events on rejection/failure", () => {
|
|
|
1315
2175
|
// ---------------------------------------------------------------------------
|
|
1316
2176
|
|
|
1317
2177
|
describe("Conversation host attachment directives", () => {
|
|
1318
|
-
beforeEach(() => {
|
|
2178
|
+
beforeEach(async () => {
|
|
1319
2179
|
pendingRuns = [];
|
|
2180
|
+
mockedConversationHostAccess.clear();
|
|
2181
|
+
const { _setOverridesForTesting } =
|
|
2182
|
+
await import("../config/assistant-feature-flags.js");
|
|
2183
|
+
_setOverridesForTesting({ "permission-controls-v2": true });
|
|
2184
|
+
});
|
|
2185
|
+
|
|
2186
|
+
afterEach(async () => {
|
|
2187
|
+
const { _setOverridesForTesting } =
|
|
2188
|
+
await import("../config/assistant-feature-flags.js");
|
|
2189
|
+
_setOverridesForTesting({});
|
|
1320
2190
|
});
|
|
1321
2191
|
|
|
1322
2192
|
test("host attachment prompts and resolves when user allows", async () => {
|
|
@@ -1364,6 +2234,19 @@ describe("Conversation host attachment directives", () => {
|
|
|
1364
2234
|
(e) => e.type === "confirmation_request",
|
|
1365
2235
|
);
|
|
1366
2236
|
expect(confirmation).toBeDefined();
|
|
2237
|
+
expect(
|
|
2238
|
+
(confirmation as { persistentDecisionsAllowed?: boolean })
|
|
2239
|
+
.persistentDecisionsAllowed,
|
|
2240
|
+
).toBe(false);
|
|
2241
|
+
expect(
|
|
2242
|
+
(
|
|
2243
|
+
confirmation as {
|
|
2244
|
+
temporaryOptionsAvailable?: Array<
|
|
2245
|
+
"allow_10m" | "allow_conversation"
|
|
2246
|
+
>;
|
|
2247
|
+
}
|
|
2248
|
+
).temporaryOptionsAvailable ?? [],
|
|
2249
|
+
).toEqual([]);
|
|
1367
2250
|
conversation.handleConfirmationResponse(
|
|
1368
2251
|
(confirmation as { requestId: string }).requestId,
|
|
1369
2252
|
"allow",
|
|
@@ -1371,6 +2254,7 @@ describe("Conversation host attachment directives", () => {
|
|
|
1371
2254
|
|
|
1372
2255
|
await p1;
|
|
1373
2256
|
|
|
2257
|
+
expect(mockedConversationHostAccess.get("conv-1")).toBe(true);
|
|
1374
2258
|
expect(conversation.lastAssistantAttachments).toHaveLength(1);
|
|
1375
2259
|
expect(conversation.lastAssistantAttachments[0].sourceType).toBe(
|
|
1376
2260
|
"host_file",
|