@vellumai/assistant 0.6.3 → 0.6.5
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/.prettierignore +5 -0
- package/ARCHITECTURE.md +298 -39
- package/Dockerfile +14 -3
- package/README.md +3 -4
- package/bun.lock +13 -16
- package/docs/architecture/integrations.md +1 -20
- package/docs/architecture/security.md +16 -16
- package/docs/backup-troubleshooting.md +52 -0
- package/docs/browser-use-architecture-phase2.md +174 -0
- package/docs/error-handling.md +111 -0
- package/docs/skills.md +10 -10
- package/docs/stt-provider-onboarding.md +121 -0
- package/knip.json +20 -3
- package/node_modules/@vellumai/ces-contracts/bun.lock +8 -6
- package/node_modules/@vellumai/ces-contracts/package.json +5 -4
- package/node_modules/@vellumai/ces-contracts/src/__tests__/trust-rules.test.ts +471 -0
- package/node_modules/@vellumai/ces-contracts/src/trust-rules.ts +398 -4
- package/node_modules/@vellumai/credential-storage/bun.lock +2 -2
- package/node_modules/@vellumai/credential-storage/package.json +2 -2
- package/node_modules/@vellumai/credential-storage/src/oauth-runtime.ts +20 -2
- package/node_modules/@vellumai/egress-proxy/bun.lock +2 -2
- package/node_modules/@vellumai/egress-proxy/package.json +2 -2
- package/openapi.yaml +1094 -72
- package/package.json +9 -8
- package/scripts/generate-openapi.ts +50 -12
- package/scripts/test.sh +73 -18
- package/src/__tests__/agent-image-optimize.test.ts +28 -0
- package/src/__tests__/agent-loop-callsite-precedence.test.ts +318 -0
- package/src/__tests__/agent-loop-sentry-hygiene.test.ts +137 -0
- package/src/__tests__/agent-loop.test.ts +235 -1
- package/src/__tests__/anthropic-error-formatting.test.ts +98 -0
- package/src/__tests__/anthropic-provider.test.ts +434 -12
- package/src/__tests__/approval-cascade.test.ts +31 -10
- package/src/__tests__/approval-routes-http.test.ts +134 -10
- package/src/__tests__/assistant-attachments.test.ts +44 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +29 -0
- 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 +12 -1
- package/src/__tests__/browser-identifier-parity-guard.test.ts +53 -0
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +23 -33
- package/src/__tests__/browser-skill-endstate.test.ts +52 -159
- package/src/__tests__/btw-routes.test.ts +54 -1
- package/src/__tests__/call-controller.test.ts +582 -22
- package/src/__tests__/call-site-routing-provider.test.ts +214 -0
- package/src/__tests__/catalog-cache.test.ts +27 -4
- package/src/__tests__/catalog-files.test.ts +138 -0
- package/src/__tests__/channel-approval-routes.test.ts +4 -4
- 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__/channel-reply-delivery.test.ts +300 -2
- package/src/__tests__/checker.test.ts +576 -502
- package/src/__tests__/clawhub-files.test.ts +347 -0
- package/src/__tests__/cli-command-risk-guard.test.ts +30 -33
- package/src/__tests__/commit-message-enrichment-service.test.ts +36 -19
- package/src/__tests__/compaction-circuit-breaker.test.ts +336 -0
- package/src/__tests__/compaction.benchmark.test.ts +1 -1
- package/src/__tests__/config-analysis.test.ts +83 -0
- package/src/__tests__/config-loader-backfill.test.ts +174 -0
- package/src/__tests__/config-loader-corrupt.test.ts +183 -0
- package/src/__tests__/config-loader-quarantine-bulletin.test.ts +202 -0
- package/src/__tests__/config-schema-cmd.test.ts +11 -5
- package/src/__tests__/config-schema.test.ts +1458 -198
- package/src/__tests__/config-watcher-cleanup-throttle.test.ts +339 -0
- package/src/__tests__/config-watcher.test.ts +45 -10
- package/src/__tests__/contact-store-user-file.test.ts +511 -0
- package/src/__tests__/contacts-write.test.ts +197 -0
- package/src/__tests__/context-token-estimator.test.ts +191 -1
- package/src/__tests__/context-window-manager.test.ts +618 -2
- package/src/__tests__/conversation-abort-tool-results.test.ts +32 -16
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +62 -17
- package/src/__tests__/conversation-agent-loop.test.ts +510 -84
- package/src/__tests__/conversation-attachments.test.ts +1 -1
- package/src/__tests__/conversation-confirmation-signals.test.ts +165 -9
- package/src/__tests__/conversation-error.test.ts +102 -1
- package/src/__tests__/conversation-history-web-search.test.ts +17 -4
- package/src/__tests__/conversation-init.benchmark.test.ts +42 -1
- package/src/__tests__/conversation-launcher-skill-regression.test.ts +51 -0
- package/src/__tests__/conversation-lifecycle.test.ts +336 -0
- package/src/__tests__/conversation-list-source.test.ts +145 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +27 -10
- package/src/__tests__/conversation-pre-run-repair.test.ts +32 -16
- package/src/__tests__/conversation-process-callsite.test.ts +306 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +32 -16
- package/src/__tests__/conversation-queue.test.ts +932 -76
- package/src/__tests__/conversation-routes-disk-view.test.ts +299 -1
- package/src/__tests__/conversation-routes-slash-commands.test.ts +31 -3
- package/src/__tests__/conversation-runtime-assembly.test.ts +2790 -55
- package/src/__tests__/conversation-runtime-workspace.test.ts +12 -12
- package/src/__tests__/conversation-skill-tools.test.ts +12 -143
- package/src/__tests__/conversation-slash-commands.test.ts +33 -0
- package/src/__tests__/conversation-slash-queue.test.ts +120 -34
- package/src/__tests__/conversation-slash-unknown.test.ts +32 -16
- package/src/__tests__/conversation-speed-override.test.ts +30 -11
- package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +1035 -0
- package/src/__tests__/conversation-surfaces-standalone.test.ts +630 -0
- package/src/__tests__/conversation-title-service.test.ts +2 -2
- package/src/__tests__/conversation-tool-setup-batch-authorized.test.ts +226 -0
- package/src/__tests__/conversation-unread-route.test.ts +2 -2
- package/src/__tests__/conversation-usage.test.ts +3 -1
- package/src/__tests__/conversation-workspace-cache-state.test.ts +31 -10
- package/src/__tests__/conversation-workspace-injection.test.ts +45 -15
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +46 -16
- package/src/__tests__/credential-broker-browser-fill.test.ts +110 -0
- package/src/__tests__/credential-health-service.test.ts +352 -0
- package/src/__tests__/credential-security-invariants.test.ts +8 -3
- package/src/__tests__/credential-storage-oauth-compat.test.ts +18 -0
- package/src/__tests__/credential-storage-static-compat.test.ts +28 -0
- package/src/__tests__/credential-vault-unit.test.ts +495 -3
- package/src/__tests__/credentials-cli.test.ts +32 -16
- package/src/__tests__/cross-provider-web-search.test.ts +230 -35
- package/src/__tests__/daemon-server-persist-and-process-callsite.test.ts +92 -0
- package/src/__tests__/delete-propagation.test.ts +437 -0
- package/src/__tests__/deterministic-verification-control-plane.test.ts +10 -1
- package/src/__tests__/device-id.test.ts +112 -0
- package/src/__tests__/dm-backfill.test.ts +417 -0
- package/src/__tests__/dm-persistence.test.ts +227 -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__/edit-propagation.test.ts +280 -0
- package/src/__tests__/email-html-renderer.test.ts +71 -0
- package/src/__tests__/email-invite-adapter.test.ts +36 -32
- package/src/__tests__/emit-event-signal.test.ts +71 -0
- package/src/__tests__/ephemeral-permissions.test.ts +93 -3
- package/src/__tests__/estimator-calibration-integration.test.ts +208 -0
- package/src/__tests__/estimator-calibration.test.ts +213 -0
- package/src/__tests__/extension-id-sync-guard.test.ts +101 -15
- package/src/__tests__/file-write-tool.test.ts +151 -1
- package/src/__tests__/filing-service.test.ts +255 -0
- package/src/__tests__/fixtures/mock-chrome-extension.ts +11 -0
- package/src/__tests__/gateway-only-enforcement.test.ts +206 -1
- package/src/__tests__/gateway-only-guard.test.ts +0 -1
- package/src/__tests__/gemini-provider.test.ts +64 -3
- package/src/__tests__/get-skill-detail-audit.test.ts +325 -0
- package/src/__tests__/guardian-grant-minting.test.ts +8 -0
- package/src/__tests__/headless-browser-interactions.test.ts +44 -1
- package/src/__tests__/headless-browser-mode.test.ts +614 -0
- package/src/__tests__/headless-browser-navigate.test.ts +142 -5
- package/src/__tests__/headless-browser-read-tools.test.ts +11 -0
- package/src/__tests__/headless-browser-snapshot.test.ts +10 -0
- package/src/__tests__/heartbeat-service.test.ts +166 -32
- package/src/__tests__/home-state-routes.test.ts +162 -0
- package/src/__tests__/host-bash-proxy.test.ts +0 -5
- package/src/__tests__/host-browser-e2e-cloud.test.ts +138 -4
- package/src/__tests__/host-browser-e2e-self-hosted.test.ts +4 -4
- package/src/__tests__/host-browser-ws-events-e2e.test.ts +103 -0
- package/src/__tests__/host-cu-proxy.test.ts +0 -5
- package/src/__tests__/host-shell-tool.test.ts +124 -18
- package/src/__tests__/http-user-message-parity.test.ts +29 -1
- package/src/__tests__/identity-intro-cache.test.ts +40 -10
- package/src/__tests__/inbound-slack-persistence.test.ts +340 -0
- package/src/__tests__/init-feature-flag-overrides.test.ts +38 -112
- package/src/__tests__/intent-routing.test.ts +1 -40
- package/src/__tests__/jobs-store-upsert-debounced.test.ts +141 -0
- package/src/__tests__/llm-catalog-parity.test.ts +174 -0
- package/src/__tests__/llm-context-normalization.test.ts +609 -0
- package/src/__tests__/llm-context-route-provider.test.ts +86 -5
- package/src/__tests__/llm-resolver.test.ts +214 -0
- package/src/__tests__/llm-schema.test.ts +223 -0
- package/src/__tests__/llm-usage-store.test.ts +363 -0
- package/src/__tests__/managed-proxy-context.test.ts +6 -2
- 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__/messaging-skill-split.test.ts +3 -34
- package/src/__tests__/migration-export-http.test.ts +6 -6
- package/src/__tests__/migration-import-commit-http.test.ts +8 -6
- package/src/__tests__/migration-import-from-url.test.ts +684 -0
- 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 +10 -84
- package/src/__tests__/notification-decision-fallback.test.ts +0 -10
- package/src/__tests__/notification-decision-identity.test.ts +0 -9
- package/src/__tests__/notification-decision-recipient-context.test.ts +0 -9
- package/src/__tests__/oauth-apps-routes.test.ts +1 -0
- package/src/__tests__/oauth-cli.test.ts +2 -0
- package/src/__tests__/oauth-connect-orchestrator.test.ts +2 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +1 -0
- package/src/__tests__/oauth-providers-routes.test.ts +2 -0
- package/src/__tests__/oauth-store.test.ts +95 -7
- package/src/__tests__/oauth2-gateway-transport.test.ts +257 -9
- package/src/__tests__/oauth2-refresh-retry.test.ts +279 -0
- package/src/__tests__/onboarding-template-contract.test.ts +6 -13
- package/src/__tests__/openai-provider.test.ts +183 -0
- package/src/__tests__/openai-responses-cutover-guard.test.ts +184 -0
- package/src/__tests__/openai-responses-provider.test.ts +1501 -0
- package/src/__tests__/openrouter-provider-only.test.ts +135 -0
- package/src/__tests__/openrouter-token-estimation.test.ts +100 -0
- package/src/__tests__/outbound-slack-persistence.test.ts +293 -0
- package/src/__tests__/permission-checker-host-gate.test.ts +1 -1
- package/src/__tests__/permission-mode.test.ts +16 -0
- package/src/__tests__/permission-types.test.ts +0 -1
- package/src/__tests__/persona-resolver.test.ts +251 -0
- package/src/__tests__/pkb-autoinject.test.ts +37 -1
- package/src/__tests__/platform-bash-auto-approve.test.ts +5 -1
- package/src/__tests__/platform.test.ts +92 -1
- package/src/__tests__/post-turn-tool-result-truncation.test.ts +47 -0
- package/src/__tests__/prechat-onboarding-contract.test.ts +267 -0
- package/src/__tests__/pricing.test.ts +224 -3
- package/src/__tests__/profiler-routes.test.ts +1 -1
- package/src/__tests__/provider-commit-message-generator.test.ts +14 -84
- package/src/__tests__/provider-env-vars-scope.test.ts +52 -0
- package/src/__tests__/provider-error-scenarios.test.ts +135 -6
- package/src/__tests__/provider-managed-proxy-integration.test.ts +42 -11
- package/src/__tests__/provider-registry-ollama.test.ts +1 -2
- package/src/__tests__/proxy-approval-callback.test.ts +0 -1
- package/src/__tests__/qdrant-manager.test.ts +29 -8
- package/src/__tests__/reaction-persistence.test.ts +560 -0
- 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 +424 -6
- package/src/__tests__/require-fresh-approval.test.ts +1 -1
- package/src/__tests__/retry-openrouter-only-normalization.test.ts +136 -0
- package/src/__tests__/retry-thinking-tool-choice.test.ts +226 -0
- package/src/__tests__/risk-classifier-parity.test.ts +230 -0
- package/src/__tests__/sanitize-config-for-transfer.test.ts +78 -1
- package/src/__tests__/search-skills-unified.test.ts +118 -0
- package/src/__tests__/secret-ingress-http.test.ts +28 -0
- package/src/__tests__/secret-prompter-channel-fallback.test.ts +125 -0
- package/src/__tests__/secret-routes-managed-proxy.test.ts +2 -3
- package/src/__tests__/secret-scanner-executor.test.ts +5 -1
- package/src/__tests__/secure-keys.test.ts +107 -0
- package/src/__tests__/send-endpoint-busy.test.ts +34 -2
- package/src/__tests__/sequence-store.test.ts +1 -1
- package/src/__tests__/server-history-render.test.ts +80 -0
- package/src/__tests__/settings-routes.test.ts +201 -0
- package/src/__tests__/shell-parser-property.test.ts +13 -13
- package/src/__tests__/skill-cache-store.test.ts +182 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +1 -0
- package/src/__tests__/skills-file-content-endpoint.test.ts +276 -145
- package/src/__tests__/skills-files-catalog-fallback.test.ts +381 -93
- package/src/__tests__/skills.test.ts +19 -30
- package/src/__tests__/skillssh-files.test.ts +446 -0
- package/src/__tests__/slack-app-setup-skill-regression.test.ts +3 -1
- package/src/__tests__/slack-block-formatting.test.ts +110 -0
- package/src/__tests__/slack-channel-config.test.ts +564 -1
- package/src/__tests__/slack-skill.test.ts +3 -8
- package/src/__tests__/starter-bundle.test.ts +35 -0
- package/src/__tests__/stt-catalog-parity.test.ts +282 -0
- package/src/__tests__/stt-stream-session.test.ts +535 -0
- package/src/__tests__/subagent-call-site-routing.test.ts +280 -0
- package/src/__tests__/suggestion-routes.test.ts +160 -3
- package/src/__tests__/system-prompt.test.ts +126 -53
- package/src/__tests__/task-runner.test.ts +3 -1
- package/src/__tests__/tcc-sandbox-deny.test.ts +198 -0
- package/src/__tests__/telephony-stt-routing.test.ts +329 -0
- package/src/__tests__/terminal-tools.test.ts +26 -7
- package/src/__tests__/test-preload.ts +18 -0
- package/src/__tests__/test-support/browser-skill-harness.ts +2 -49
- package/src/__tests__/thread-backfill.test.ts +941 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -2
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +10 -6
- package/src/__tests__/tool-executor-shell-integration.test.ts +4 -0
- package/src/__tests__/tool-executor.test.ts +88 -113
- package/src/__tests__/tool-result-truncation.test.ts +36 -0
- package/src/__tests__/trust-store.test.ts +442 -103
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +1 -1
- 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-job.test.ts +389 -0
- package/src/__tests__/usage-cache-backfill-migration.test.ts +3 -1
- package/src/__tests__/usage-routes.test.ts +25 -4
- package/src/__tests__/user-reference.test.ts +46 -61
- package/src/__tests__/verification-control-plane-policy.test.ts +5 -22
- package/src/__tests__/voice-config-update.test.ts +403 -0
- package/src/__tests__/voice-quality.test.ts +434 -19
- package/src/__tests__/voice-session-bridge.test.ts +39 -0
- package/src/__tests__/volume-security-guard.test.ts +3 -2
- package/src/__tests__/web-search-history.test.ts +337 -0
- 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-039-drop-legacy-llm-keys.test.ts +343 -0
- package/src/__tests__/workspace-migration-043-release-notes-latex-rendering.test.ts +202 -0
- package/src/__tests__/workspace-migration-045-release-notes-meet-avatar.test.ts +210 -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-migration-unify-llm-callsite-configs.test.ts +841 -0
- package/src/__tests__/workspace-policy.test.ts +1 -11
- package/src/acp/client-handler.ts +1 -2
- package/src/agent/image-optimize.ts +24 -12
- package/src/agent/loop.ts +251 -19
- package/src/avatar/resvg-lazy.test.ts +136 -0
- package/src/avatar/resvg-lazy.ts +82 -9
- package/src/avatar/traits-png-sync.ts +21 -1
- 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/__tests__/operations.test.ts +163 -0
- package/src/browser/identifiers.ts +51 -0
- package/src/browser/operations.ts +660 -0
- package/src/browser/types.ts +81 -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/guardian-question-copy.ts +2 -2
- 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 +9 -1
- package/src/channels/types.ts +16 -0
- package/src/cli/AGENTS.md +1 -1
- package/src/cli/__tests__/run-assistant-command.ts +11 -1
- package/src/cli/commands/__tests__/attachment.test.ts +438 -0
- package/src/cli/commands/__tests__/backup.test.ts +1165 -0
- package/src/cli/commands/__tests__/browser.test.ts +554 -0
- package/src/cli/commands/__tests__/cache.test.ts +623 -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 +28 -4
- package/src/cli/commands/__tests__/email-register.test.ts +4 -4
- package/src/cli/commands/__tests__/email-send.test.ts +130 -5
- 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/__tests__/image-generation.test.ts +666 -0
- package/src/cli/commands/__tests__/inference-send.test.ts +451 -0
- package/src/cli/commands/__tests__/stt-transcribe.test.ts +454 -0
- package/src/cli/commands/__tests__/task.test.ts +913 -0
- package/src/cli/commands/__tests__/tts-synthesize.test.ts +594 -0
- package/src/cli/commands/__tests__/ui-confirm.test.ts +650 -0
- package/src/cli/commands/__tests__/ui.test.ts +1215 -0
- package/src/cli/commands/__tests__/watchers.test.ts +716 -0
- package/src/cli/commands/attachment.ts +182 -0
- package/src/cli/commands/backup.ts +993 -0
- package/src/cli/commands/browser.ts +350 -0
- package/src/cli/commands/cache.ts +341 -0
- package/src/cli/commands/completions.ts +0 -3
- package/src/cli/commands/config.ts +6 -6
- package/src/cli/commands/conversations-import.ts +347 -0
- package/src/cli/commands/conversations.ts +90 -0
- package/src/cli/commands/credentials.ts +0 -1
- package/src/cli/commands/domain.ts +210 -0
- package/src/cli/commands/email.ts +308 -16
- package/src/cli/commands/image-generation.ts +300 -0
- package/src/cli/commands/inference.ts +200 -0
- package/src/cli/commands/memory.ts +127 -17
- package/src/cli/commands/oauth/__tests__/connect.test.ts +12 -0
- package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +1 -0
- package/src/cli/commands/oauth/__tests__/providers-register.test.ts +1 -0
- package/src/cli/commands/oauth/__tests__/providers-update.test.ts +1 -0
- package/src/cli/commands/oauth/mode.ts +12 -3
- package/src/cli/commands/oauth/providers.ts +15 -0
- package/src/cli/commands/oauth/shared.ts +2 -1
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +4 -10
- package/src/cli/commands/platform/__tests__/connect.test.ts +6 -1
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -2
- package/src/cli/commands/platform/__tests__/status.test.ts +6 -1
- package/src/cli/commands/stt.ts +339 -0
- package/src/cli/commands/task.ts +795 -0
- package/src/cli/commands/trust.ts +50 -19
- package/src/cli/commands/tts.ts +273 -0
- package/src/cli/commands/ui.ts +670 -0
- package/src/cli/commands/watchers.ts +509 -0
- package/src/cli/lib/daemon-credential-client.ts +0 -19
- package/src/cli/program.ts +53 -8
- package/src/cli.ts +0 -37
- 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/references/CUSTOM_ROUTES.md +37 -1
- package/src/config/bundled-skills/contacts/SKILL.md +2 -2
- package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +23 -1
- 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/services/reduce.ts +1 -1
- package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +0 -10
- package/src/config/bundled-skills/messaging/SKILL.md +5 -5
- package/src/config/bundled-skills/messaging/TOOLS.json +4 -0
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +9 -2
- package/src/config/bundled-skills/messaging/tools/messaging-read.ts +15 -1
- package/src/config/bundled-skills/messaging/tools/messaging-search.ts +21 -1
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +11 -12
- package/src/config/bundled-skills/phone-calls/SKILL.md +2 -2
- package/src/config/bundled-skills/phone-calls/references/CONFIG.md +28 -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/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 +0 -167
- package/src/config/env-registry.ts +24 -0
- package/src/config/env.ts +39 -10
- package/src/config/feature-flag-registry.json +63 -15
- package/src/config/llm-resolver.ts +128 -0
- package/src/config/loader.ts +220 -22
- package/src/config/raw-config-utils.ts +30 -2
- package/src/config/sanitize-for-transfer.ts +35 -0
- package/src/config/schema.ts +65 -51
- package/src/config/schemas/__tests__/stt.test.ts +43 -0
- package/src/config/schemas/analysis.ts +32 -0
- package/src/config/schemas/backup.ts +72 -0
- package/src/config/schemas/calls.ts +1 -30
- package/src/config/schemas/elevenlabs.ts +0 -59
- package/src/config/schemas/filing.ts +49 -14
- package/src/config/schemas/heartbeat.ts +27 -10
- package/src/config/schemas/host-browser.ts +47 -1
- package/src/config/schemas/inference.ts +3 -23
- package/src/config/schemas/llm.ts +318 -0
- package/src/config/schemas/memory-lifecycle.ts +14 -2
- package/src/config/schemas/memory-processing.ts +1 -9
- package/src/config/schemas/notifications.ts +4 -11
- package/src/config/schemas/platform.ts +3 -9
- package/src/config/schemas/security.ts +33 -0
- package/src/config/schemas/services.ts +53 -4
- package/src/config/schemas/stt.ts +60 -0
- package/src/config/schemas/tts.ts +283 -0
- package/src/config/schemas/updates.ts +14 -0
- package/src/config/schemas/workspace-git.ts +3 -40
- package/src/config/skills.ts +6 -2
- package/src/config/types.ts +4 -0
- package/src/contacts/contact-store.ts +56 -11
- package/src/contacts/contacts-write.ts +38 -1
- package/src/context/__tests__/compact-prompt.test.ts +45 -0
- package/src/context/__tests__/microcompact.test.ts +805 -0
- package/src/context/estimator-calibration.ts +136 -0
- package/src/context/microcompact.ts +443 -0
- package/src/context/post-turn-tool-result-truncation.ts +3 -2
- package/src/context/prompts/compact.md +12 -0
- package/src/context/token-estimator.ts +61 -3
- package/src/context/tool-result-truncation.ts +2 -1
- package/src/context/window-manager.ts +272 -35
- package/src/credential-execution/approval-bridge.ts +0 -1
- package/src/credential-execution/executable-discovery.ts +23 -2
- package/src/credential-execution/process-manager.test.ts +109 -0
- package/src/credential-execution/process-manager.ts +96 -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 +17 -8
- package/src/daemon/__tests__/lifecycle-startup-ordering.test.ts +127 -0
- package/src/daemon/approval-generators.ts +29 -4
- package/src/daemon/assistant-attachments.ts +24 -13
- package/src/daemon/classifier.ts +2 -2
- package/src/daemon/config-watcher.ts +99 -6
- package/src/daemon/context-overflow-reducer.ts +4 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +85 -12
- package/src/daemon/conversation-agent-loop.ts +563 -104
- package/src/daemon/conversation-attachments.ts +2 -6
- package/src/daemon/conversation-error.ts +46 -0
- package/src/daemon/conversation-history.ts +40 -6
- package/src/daemon/conversation-launch.ts +220 -0
- package/src/daemon/conversation-lifecycle.ts +85 -11
- package/src/daemon/conversation-messaging.ts +110 -7
- package/src/daemon/conversation-notifiers.ts +5 -0
- package/src/daemon/conversation-process.ts +591 -23
- package/src/daemon/conversation-queue-manager.ts +27 -0
- package/src/daemon/conversation-runtime-assembly.ts +769 -28
- package/src/daemon/conversation-slash.ts +38 -2
- package/src/daemon/conversation-surfaces.ts +483 -5
- package/src/daemon/conversation-tool-setup.ts +35 -5
- package/src/daemon/conversation-usage.ts +8 -5
- package/src/daemon/conversation.ts +193 -47
- package/src/daemon/external-skills-bootstrap.ts +41 -0
- package/src/daemon/guardian-action-generators.ts +34 -14
- package/src/daemon/handlers/config-model.test.ts +86 -0
- package/src/daemon/handlers/config-model.ts +54 -12
- package/src/daemon/handlers/config-slack-channel.ts +269 -94
- package/src/daemon/handlers/conversations.ts +13 -3
- package/src/daemon/handlers/shared.ts +51 -1
- package/src/daemon/handlers/skills.ts +323 -79
- package/src/daemon/handlers/slack-channel-oauth-install.ts +197 -0
- package/src/daemon/host-browser-proxy.ts +2 -1
- package/src/daemon/lifecycle.ts +185 -26
- package/src/daemon/message-protocol.ts +6 -0
- package/src/daemon/message-types/conversations.ts +48 -1
- package/src/daemon/message-types/home.ts +40 -0
- package/src/daemon/message-types/meet.ts +143 -0
- package/src/daemon/message-types/messages.ts +23 -1
- package/src/daemon/message-types/schedules.ts +34 -2
- package/src/daemon/message-types/skills.ts +16 -0
- package/src/daemon/message-types/surfaces.ts +2 -0
- package/src/daemon/message-types/trust.ts +0 -2
- package/src/daemon/parse-actual-tokens-from-error.test.ts +57 -1
- package/src/daemon/parse-actual-tokens-from-error.ts +66 -0
- package/src/daemon/pkb-context-tracker.test.ts +169 -0
- package/src/daemon/pkb-context-tracker.ts +125 -0
- package/src/daemon/pkb-reminder-builder.test.ts +70 -0
- package/src/daemon/pkb-reminder-builder.ts +31 -0
- package/src/daemon/providers-setup.ts +6 -0
- package/src/daemon/server.ts +463 -10
- package/src/daemon/shutdown-handlers.ts +32 -4
- package/src/daemon/shutdown-registry.ts +40 -0
- package/src/daemon/tool-side-effects.ts +9 -9
- package/src/daemon/watch-handler.ts +4 -4
- package/src/daemon/web-search-history.ts +126 -0
- package/src/email/html-renderer.ts +76 -0
- package/src/events/domain-events.ts +0 -1
- package/src/filing/filing-service.ts +9 -10
- package/src/heartbeat/heartbeat-service.ts +156 -22
- 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 +222 -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 +442 -0
- package/src/home/assistant-feed-authoring.ts +128 -0
- package/src/home/emit-feed-event.ts +162 -0
- package/src/home/feed-scheduler.ts +263 -0
- package/src/home/feed-types.ts +235 -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 +413 -0
- package/src/home/suggested-prompts.ts +101 -0
- package/src/hooks/runner.ts +7 -0
- package/src/inbound/platform-callback-registration.ts +12 -3
- package/src/inbound/public-ingress-urls.ts +12 -0
- package/src/instrument.ts +1 -1
- package/src/ipc/__tests__/attachment-ipc.test.ts +213 -0
- package/src/ipc/__tests__/browser-ipc.test.ts +339 -0
- package/src/ipc/__tests__/cache-ipc.test.ts +266 -0
- package/src/ipc/__tests__/cli-ipc.test.ts +200 -0
- package/src/ipc/__tests__/socket-path.test.ts +73 -0
- package/src/ipc/__tests__/task-ipc.test.ts +577 -0
- package/src/ipc/__tests__/ui-request-route.test.ts +495 -0
- package/src/ipc/__tests__/watcher-ipc.test.ts +295 -0
- package/src/ipc/cli-client.ts +152 -0
- package/src/ipc/cli-server.ts +252 -0
- package/src/ipc/gateway-client.ts +180 -0
- package/src/ipc/routes/attachment.ts +114 -0
- package/src/ipc/routes/browser-context.ts +61 -0
- package/src/ipc/routes/browser.ts +96 -0
- package/src/ipc/routes/cache.ts +96 -0
- package/src/ipc/routes/index.ts +21 -0
- package/src/ipc/routes/task-queue.ts +226 -0
- package/src/ipc/routes/task.ts +173 -0
- package/src/ipc/routes/ui-request.ts +50 -0
- package/src/ipc/routes/wake-conversation.ts +19 -0
- package/src/ipc/routes/watcher.ts +203 -0
- package/src/ipc/socket-path.ts +100 -0
- 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 +233 -0
- package/src/memory/__tests__/conversation-group-migration.test.ts +99 -0
- package/src/memory/__tests__/find-analysis-conversation.test.ts +196 -0
- package/src/memory/admin.ts +18 -0
- package/src/memory/app-store.ts +1 -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 +74 -0
- package/src/memory/conversation-attention-store.ts +13 -6
- package/src/memory/conversation-crud.ts +199 -0
- package/src/memory/conversation-disk-view.ts +7 -0
- package/src/memory/conversation-group-migration.ts +65 -1
- package/src/memory/conversation-queries.ts +6 -5
- package/src/memory/conversation-title-service.ts +7 -4
- package/src/memory/db-init.ts +8 -0
- package/src/memory/db-maintenance.ts +108 -0
- package/src/memory/db.ts +1 -0
- package/src/memory/embedding-backend.ts +1 -1
- package/src/memory/graph/compaction.ts +299 -0
- package/src/memory/graph/consolidation.ts +4 -4
- package/src/memory/graph/conversation-graph-memory.ts +104 -29
- package/src/memory/graph/extraction.test.ts +295 -2
- package/src/memory/graph/extraction.ts +181 -51
- package/src/memory/graph/graph-search.test.ts +92 -0
- package/src/memory/graph/graph-search.ts +4 -1
- package/src/memory/graph/narrative.ts +2 -2
- package/src/memory/graph/pattern-scan.ts +2 -2
- package/src/memory/graph/retriever.test.ts +459 -0
- package/src/memory/graph/retriever.ts +257 -66
- package/src/memory/graph/scoring.test.ts +186 -0
- package/src/memory/graph/scoring.ts +31 -1
- package/src/memory/graph/store.ts +41 -0
- package/src/memory/graph/tool-handlers.ts +27 -0
- package/src/memory/graph/tools.ts +6 -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 +39 -30
- package/src/memory/job-handlers/summarization.ts +2 -2
- package/src/memory/job-utils.ts +7 -1
- package/src/memory/jobs/embed-pkb-file.test.ts +168 -0
- package/src/memory/jobs/embed-pkb-file.ts +54 -0
- package/src/memory/jobs-store.ts +106 -5
- package/src/memory/jobs-worker.ts +26 -9
- package/src/memory/llm-usage-store.ts +92 -56
- package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +1 -1
- 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/222-strip-placeholder-sentinels-from-messages.ts +82 -0
- package/src/memory/migrations/index.ts +7 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/pkb/pkb-index.test.ts +368 -0
- package/src/memory/pkb/pkb-index.ts +255 -0
- package/src/memory/pkb/pkb-reconcile.test.ts +251 -0
- package/src/memory/pkb/pkb-reconcile.ts +148 -0
- package/src/memory/pkb/pkb-search.test.ts +438 -0
- package/src/memory/pkb/pkb-search.ts +137 -0
- package/src/memory/pkb/types.ts +53 -0
- package/src/memory/qdrant-client.ts +122 -1
- package/src/memory/qdrant-manager.ts +43 -16
- package/src/memory/schema/conversations.ts +2 -0
- package/src/memory/schema/oauth.ts +3 -0
- package/src/memory/slack-thread-store.ts +37 -0
- package/src/memory/usage-buckets.ts +396 -0
- package/src/messaging/providers/gmail/adapter.ts +6 -16
- package/src/messaging/providers/gmail/client.ts +79 -6
- package/src/messaging/providers/gmail/types.ts +7 -0
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +282 -0
- package/src/messaging/providers/slack/adapter.ts +155 -38
- package/src/messaging/providers/slack/backfill.test.ts +257 -0
- package/src/messaging/providers/slack/backfill.ts +101 -0
- package/src/messaging/providers/slack/client.ts +16 -0
- package/src/messaging/providers/slack/message-metadata.test.ts +316 -0
- package/src/messaging/providers/slack/message-metadata.ts +123 -0
- package/src/messaging/providers/slack/render-transcript.test.ts +1373 -0
- package/src/messaging/providers/slack/render-transcript.ts +443 -0
- package/src/messaging/providers/slack/types.ts +4 -0
- package/src/messaging/style-analyzer.ts +5 -2
- package/src/notifications/README.md +9 -5
- package/src/notifications/decision-engine.ts +6 -12
- package/src/notifications/preference-extractor.ts +2 -6
- package/src/notifications/signal.ts +5 -0
- package/src/oauth/__tests__/identity-verifier.test.ts +1 -0
- package/src/oauth/byo-connection.test.ts +18 -1
- package/src/oauth/byo-connection.ts +3 -1
- package/src/oauth/connect-orchestrator.ts +2 -0
- package/src/oauth/connection-resolver.ts +6 -2
- package/src/oauth/connection.ts +2 -0
- package/src/oauth/oauth-store.ts +10 -0
- package/src/oauth/platform-connection.test.ts +145 -0
- package/src/oauth/platform-connection.ts +62 -31
- package/src/oauth/seed-providers.ts +10 -1
- package/src/permissions/approval-policy.test.ts +948 -0
- package/src/permissions/approval-policy.ts +257 -0
- package/src/permissions/bash-risk-classifier.test.ts +1208 -0
- package/src/permissions/bash-risk-classifier.ts +707 -0
- package/src/permissions/checker.ts +218 -699
- package/src/permissions/command-registry.test.ts +535 -0
- package/src/permissions/command-registry.ts +825 -0
- package/src/permissions/defaults.ts +71 -75
- package/src/permissions/file-risk-classifier.test.ts +535 -0
- package/src/permissions/file-risk-classifier.ts +274 -0
- package/src/permissions/risk-types.ts +205 -0
- package/src/permissions/secret-prompter.ts +53 -2
- package/src/permissions/skill-risk-classifier.test.ts +311 -0
- package/src/permissions/skill-risk-classifier.ts +214 -0
- package/src/permissions/trust-client.ts +52 -25
- package/src/permissions/trust-store-interface.ts +1 -6
- package/src/permissions/trust-store.ts +164 -65
- package/src/permissions/types.ts +23 -14
- package/src/permissions/web-risk-classifier.test.ts +170 -0
- package/src/permissions/web-risk-classifier.ts +89 -0
- package/src/permissions/workspace-policy.ts +1 -13
- package/src/platform/client.test.ts +10 -0
- package/src/platform/client.ts +19 -1
- package/src/platform/sync-identity.ts +129 -0
- package/src/prompts/persona-resolver.ts +127 -3
- package/src/prompts/system-prompt.ts +78 -38
- package/src/prompts/templates/BOOTSTRAP.md +5 -5
- package/src/prompts/templates/SOUL.md +5 -3
- package/src/prompts/templates/channels/slack.md +20 -0
- package/src/prompts/update-bulletin-job.ts +190 -0
- package/src/prompts/user-reference.ts +20 -17
- package/src/providers/__tests__/context-overflow-error.test.ts +328 -0
- package/src/providers/__tests__/provider-env-vars.test.ts +102 -0
- package/src/providers/__tests__/provider-secret-catalog.test.ts +42 -0
- package/src/providers/__tests__/retry-callsite.test.ts +424 -0
- package/src/providers/anthropic/client.ts +335 -70
- package/src/providers/call-site-routing.ts +71 -0
- package/src/providers/fireworks/client.ts +2 -2
- package/src/providers/gemini/client.ts +74 -3
- package/src/providers/managed-proxy/constants.ts +2 -1
- package/src/providers/model-catalog.ts +502 -28
- package/src/providers/model-intents.ts +8 -8
- package/src/providers/ollama/client.ts +2 -2
- package/src/providers/openai/chat-completions-provider.ts +530 -0
- package/src/providers/openai/client.ts +25 -440
- package/src/providers/openai/responses-provider.ts +579 -0
- package/src/providers/openrouter/client.ts +168 -4
- package/src/providers/provider-env-vars.ts +56 -0
- package/src/providers/provider-secret-catalog.ts +139 -0
- package/src/providers/provider-send-message.ts +22 -5
- package/src/providers/ratelimit.ts +4 -0
- package/src/providers/registry.ts +21 -10
- package/src/providers/retry.ts +185 -39
- package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +251 -0
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +883 -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 +323 -0
- package/src/providers/speech-to-text/resolve.ts +393 -6
- package/src/providers/speech-to-text/xai-realtime.test.ts +578 -0
- package/src/providers/speech-to-text/xai-realtime.ts +796 -0
- package/src/providers/speech-to-text/xai.test.ts +155 -0
- package/src/providers/speech-to-text/xai.ts +97 -0
- package/src/providers/types.ts +102 -3
- package/src/runtime/AGENTS.md +45 -3
- package/src/runtime/__tests__/agent-wake.test.ts +872 -0
- package/src/runtime/__tests__/interactive-ui.test.ts +673 -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 +553 -0
- package/src/runtime/auth/__tests__/route-policy.test.ts +40 -0
- package/src/runtime/auth/route-policy.ts +34 -5
- package/src/runtime/auth/token-service.ts +56 -1
- package/src/runtime/btw-sidechain.ts +15 -3
- package/src/runtime/capability-tokens.ts +10 -10
- 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/channel-reply-delivery.ts +106 -2
- package/src/runtime/chrome-extension-registry.ts +38 -2
- package/src/runtime/decision-token.ts +116 -0
- package/src/runtime/gateway-client.ts +2 -2
- package/src/runtime/http-router.ts +32 -0
- package/src/runtime/http-server.ts +447 -11
- package/src/runtime/http-types.ts +29 -3
- package/src/runtime/interactive-ui.ts +362 -0
- package/src/runtime/invite-instruction-generator.ts +2 -2
- package/src/runtime/migrations/__tests__/gcs-signed-url.test.ts +176 -0
- package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +36 -0
- package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +360 -0
- package/src/runtime/migrations/__tests__/vbundle-metadata-merge-integration.test.ts +390 -0
- package/src/runtime/migrations/__tests__/vbundle-metadata-merge.test.ts +221 -0
- package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +1540 -0
- package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +453 -0
- package/src/runtime/migrations/__tests__/vbundle-tar-stream.test.ts +222 -0
- package/src/runtime/migrations/gcs-signed-url.ts +162 -0
- package/src/runtime/migrations/migration-transport.ts +1 -0
- package/src/runtime/migrations/migration-wizard.ts +1 -0
- package/src/runtime/migrations/vbundle-import-analyzer.ts +77 -1
- package/src/runtime/migrations/vbundle-importer.ts +187 -8
- package/src/runtime/migrations/vbundle-metadata-merge.ts +124 -0
- package/src/runtime/migrations/vbundle-streaming-importer.ts +2522 -0
- package/src/runtime/migrations/vbundle-streaming-validator.ts +244 -0
- package/src/runtime/migrations/vbundle-tar-stream.ts +217 -0
- package/src/runtime/migrations/vbundle-validator.ts +15 -6
- package/src/runtime/pending-interactions.ts +0 -11
- package/src/runtime/routes/__tests__/backup-routes.test.ts +967 -0
- package/src/runtime/routes/__tests__/home-feed-routes.test.ts +618 -0
- package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +247 -0
- package/src/runtime/routes/__tests__/migration-vellum-metadata-reconcile.test.ts +246 -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-prompt-ts-tracker.ts +58 -0
- package/src/runtime/routes/approval-routes.ts +12 -17
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +9 -0
- package/src/runtime/routes/attachment-routes.test.ts +9 -3
- package/src/runtime/routes/attachment-routes.ts +216 -17
- package/src/runtime/routes/avatar-routes.ts +20 -4
- package/src/runtime/routes/backup-routes.ts +519 -0
- package/src/runtime/routes/browser-extension-pair-routes.ts +82 -23
- package/src/runtime/routes/btw-routes.ts +9 -10
- 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 -142
- package/src/runtime/routes/conversation-management-routes.ts +133 -0
- package/src/runtime/routes/conversation-routes.ts +487 -160
- package/src/runtime/routes/debug-routes.ts +1 -1
- package/src/runtime/routes/diagnostics-routes.ts +6 -4
- package/src/runtime/routes/events-routes.ts +16 -0
- package/src/runtime/routes/filing-routes.ts +93 -0
- package/src/runtime/routes/guardian-approval-interception.ts +33 -3
- package/src/runtime/routes/guardian-approval-prompt.ts +13 -3
- package/src/runtime/routes/home-feed-routes.ts +452 -0
- package/src/runtime/routes/home-state-routes.ts +138 -0
- package/src/runtime/routes/host-browser-routes.ts +3 -14
- package/src/runtime/routes/identity-intro-cache.ts +7 -3
- package/src/runtime/routes/identity-routes.ts +3 -17
- package/src/runtime/routes/inbound-message-handler.ts +912 -2
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +113 -2
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +61 -3
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +129 -6
- 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 +36 -6
- package/src/runtime/routes/integrations/slack/share.ts +45 -7
- package/src/runtime/routes/llm-context-normalization.ts +325 -0
- package/src/runtime/routes/memory-item-routes.test.ts +3 -2
- package/src/runtime/routes/migration-routes.ts +722 -91
- package/src/runtime/routes/settings-routes.ts +26 -7
- package/src/runtime/routes/skills-routes.ts +76 -7
- package/src/runtime/routes/stt-routes.ts +233 -0
- package/src/runtime/routes/surface-action-routes.ts +41 -2
- package/src/runtime/routes/trust-rules-routes.ts +30 -14
- package/src/runtime/routes/tts-routes.ts +108 -24
- package/src/runtime/routes/usage-routes.ts +30 -2
- 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.test.ts +1 -1
- package/src/runtime/routes/work-items-routes.ts +11 -3
- package/src/runtime/runtime-mode.ts +33 -0
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +426 -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 +340 -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 +71 -0
- package/src/runtime/slack-block-formatting.ts +437 -10
- package/src/schedule/scheduler.ts +58 -0
- package/src/security/__tests__/provider-key-env-fallback.test.ts +119 -0
- package/src/security/__tests__/untrusted-content.test.ts +109 -0
- package/src/security/oauth2.ts +122 -37
- package/src/security/secure-keys.ts +32 -10
- package/src/security/token-manager.ts +35 -13
- package/src/security/untrusted-content.ts +102 -0
- package/src/sequence/engine.ts +23 -0
- package/src/sequence/types.ts +1 -1
- package/src/skills/catalog-cache.ts +26 -7
- package/src/skills/catalog-files.ts +64 -2
- package/src/skills/catalog-install.ts +31 -3
- 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-cache-store.ts +97 -0
- 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 +468 -0
- package/src/stt/__tests__/types.test.ts +89 -0
- package/src/stt/daemon-batch-transcriber.ts +228 -0
- package/src/stt/stt-stream-session.ts +506 -0
- package/src/stt/types.ts +334 -0
- package/src/stt/wav-encoder.test.ts +373 -0
- package/src/stt/wav-encoder.ts +175 -0
- package/src/subagent/manager.ts +79 -27
- package/src/tasks/ephemeral-permissions.ts +9 -4
- package/src/telemetry/usage-telemetry-reporter.ts +27 -5
- package/src/tools/browser/__tests__/browser-mode.test.ts +119 -0
- package/src/tools/browser/__tests__/browser-status.test.ts +166 -0
- package/src/tools/browser/browser-execution.ts +1208 -41
- package/src/tools/browser/browser-manager.ts +45 -0
- 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__/cdp-inspect-client.test.ts +393 -0
- package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +29 -0
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +1648 -32
- package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +264 -0
- package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +205 -17
- package/src/tools/browser/cdp-client/cdp-inspect-client.ts +254 -21
- package/src/tools/browser/cdp-client/errors.ts +15 -0
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +39 -16
- package/src/tools/browser/cdp-client/factory.ts +797 -87
- package/src/tools/browser/cdp-client/index.ts +16 -2
- package/src/tools/browser/cdp-client/types.ts +68 -0
- package/src/tools/credentials/tool-policy.ts +39 -5
- package/src/tools/credentials/vault.ts +41 -7
- package/src/tools/executor.ts +4 -0
- package/src/tools/filesystem/write.ts +52 -0
- package/src/tools/host-terminal/host-shell.ts +45 -5
- package/src/tools/memory/register.test.ts +185 -0
- package/src/tools/memory/register.ts +3 -1
- package/src/tools/network/web-fetch.ts +25 -12
- package/src/tools/network/web-search.ts +20 -2
- package/src/tools/permission-checker.ts +36 -15
- package/src/tools/policy-context.ts +25 -8
- package/src/tools/registry.ts +55 -3
- package/src/tools/shared/shell-output.ts +3 -1
- package/src/tools/side-effects.ts +0 -9
- package/src/tools/skills/execute.ts +2 -2
- package/src/tools/skills/sandbox-runner.ts +6 -2
- package/src/tools/terminal/backends/native.ts +51 -2
- package/src/tools/terminal/safe-env.ts +11 -2
- package/src/tools/terminal/shell.ts +16 -4
- package/src/tools/tool-manifest.ts +6 -0
- package/src/tools/types.ts +29 -3
- package/src/tools/ui-surface/definitions.ts +6 -1
- package/src/tools/verification-control-plane-policy.ts +1 -1
- package/src/tts/__tests__/provider-adapters.test.ts +1061 -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 +219 -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 +44 -0
- package/src/tts/providers/register-builtins.ts +130 -0
- package/src/tts/providers/xai-provider.ts +224 -0
- package/src/tts/synthesize-text.ts +110 -0
- package/src/tts/tts-config-resolver.ts +78 -0
- package/src/tts/types.ts +199 -0
- package/src/types/onboarding-context.ts +7 -0
- package/src/types/tar-stream.d.ts +66 -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/json.ts +17 -0
- package/src/util/platform.ts +56 -12
- package/src/util/pricing.ts +78 -5
- 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 +24 -1
- package/src/watcher/providers/google-calendar.ts +134 -8
- package/src/watcher/providers/outlook-calendar.ts +42 -2
- package/src/watcher/watcher-store.ts +31 -0
- package/src/workspace/git-service.ts +23 -4
- 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/038-unify-llm-callsite-configs.ts +516 -0
- package/src/workspace/migrations/039-drop-legacy-llm-keys.ts +171 -0
- package/src/workspace/migrations/040-seed-latency-callsite-defaults.ts +154 -0
- package/src/workspace/migrations/041-backfill-google-gmail-settings-scope.ts +57 -0
- package/src/workspace/migrations/042-fix-backfill-google-gmail-settings-scope.ts +70 -0
- package/src/workspace/migrations/043-release-notes-latex-rendering.ts +75 -0
- package/src/workspace/migrations/044-bump-stale-provider-stream-timeout.ts +51 -0
- package/src/workspace/migrations/045-release-notes-meet-avatar.ts +130 -0
- package/src/workspace/migrations/AGENTS.md +1 -1
- package/src/workspace/migrations/registry.ts +32 -0
- package/src/workspace/provider-commit-message-generator.ts +19 -38
- package/src/workspace/top-level-renderer.ts +13 -1
- package/src/workspace/turn-commit.ts +31 -0
- package/src/__tests__/email-cli.test.ts +0 -297
- package/src/__tests__/email-service-config-fallback.test.ts +0 -102
- package/src/__tests__/outlook-attachments.test.ts +0 -301
- package/src/__tests__/outlook-automation-tools.test.ts +0 -425
- package/src/__tests__/outlook-categories.test.ts +0 -212
- package/src/__tests__/outlook-compose-tools.test.ts +0 -325
- package/src/__tests__/outlook-declutter-tools.test.ts +0 -585
- package/src/__tests__/outlook-follow-up.test.ts +0 -196
- package/src/__tests__/outlook-trash.test.ts +0 -77
- package/src/__tests__/outlook-unsubscribe.test.ts +0 -250
- package/src/__tests__/update-bulletin-format.test.ts +0 -122
- package/src/__tests__/update-bulletin-state.test.ts +0 -135
- package/src/__tests__/update-bulletin.test.ts +0 -277
- package/src/__tests__/update-template-contract.test.ts +0 -29
- package/src/cli/commands/browser-relay.ts +0 -466
- package/src/cli/commands/doctor.ts +0 -341
- package/src/config/bundled-skills/browser/SKILL.md +0 -63
- package/src/config/bundled-skills/browser/TOOLS.json +0 -393
- package/src/config/bundled-skills/browser/tools/browser-click.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-close.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-extract.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-fill-credential.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-hover.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-navigate.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-press-key.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-screenshot.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-scroll.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-select-option.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-snapshot.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-type.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +0 -32
- package/src/config/bundled-skills/browser/tools/browser-wait-for.ts +0 -12
- package/src/config/bundled-skills/chatgpt-import/SKILL.md +0 -27
- package/src/config/bundled-skills/chatgpt-import/TOOLS.json +0 -27
- package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +0 -378
- package/src/config/bundled-skills/gmail/SKILL.md +0 -175
- package/src/config/bundled-skills/gmail/TOOLS.json +0 -558
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +0 -149
- package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +0 -112
- package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +0 -44
- package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +0 -81
- package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +0 -108
- package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +0 -146
- package/src/config/bundled-skills/gmail/tools/gmail-label.ts +0 -53
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +0 -220
- package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +0 -26
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +0 -251
- package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +0 -29
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +0 -122
- package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +0 -67
- package/src/config/bundled-skills/gmail/tools/scan-result-store.ts +0 -100
- package/src/config/bundled-skills/gmail/tools/shared.ts +0 -47
- package/src/config/bundled-skills/google-calendar/SKILL.md +0 -51
- package/src/config/bundled-skills/google-calendar/TOOLS.json +0 -226
- package/src/config/bundled-skills/google-calendar/calendar-client.ts +0 -223
- package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +0 -27
- package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +0 -48
- package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +0 -19
- package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +0 -36
- package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +0 -58
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +0 -17
- package/src/config/bundled-skills/google-calendar/types.ts +0 -97
- package/src/config/bundled-skills/outlook/SKILL.md +0 -196
- package/src/config/bundled-skills/outlook/TOOLS.json +0 -530
- package/src/config/bundled-skills/outlook/tools/outlook-attachments.ts +0 -85
- package/src/config/bundled-skills/outlook/tools/outlook-categories.ts +0 -77
- package/src/config/bundled-skills/outlook/tools/outlook-draft.ts +0 -84
- package/src/config/bundled-skills/outlook/tools/outlook-follow-up.ts +0 -94
- package/src/config/bundled-skills/outlook/tools/outlook-forward.ts +0 -49
- package/src/config/bundled-skills/outlook/tools/outlook-outreach-scan.ts +0 -237
- package/src/config/bundled-skills/outlook/tools/outlook-rules.ts +0 -161
- package/src/config/bundled-skills/outlook/tools/outlook-send-draft.ts +0 -32
- package/src/config/bundled-skills/outlook/tools/outlook-sender-digest.ts +0 -272
- package/src/config/bundled-skills/outlook/tools/outlook-trash.ts +0 -29
- package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +0 -129
- package/src/config/bundled-skills/outlook/tools/outlook-vacation.ts +0 -87
- package/src/config/bundled-skills/outlook/tools/shared.ts +0 -20
- package/src/config/bundled-skills/outlook-calendar/SKILL.md +0 -51
- package/src/config/bundled-skills/outlook-calendar/TOOLS.json +0 -221
- package/src/config/bundled-skills/outlook-calendar/calendar-client.ts +0 -252
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-check-availability.ts +0 -53
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-create-event.ts +0 -74
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-get-event.ts +0 -18
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-list-events.ts +0 -46
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-rsvp.ts +0 -36
- package/src/config/bundled-skills/outlook-calendar/tools/shared.ts +0 -17
- package/src/config/bundled-skills/outlook-calendar/types.ts +0 -120
- package/src/config/bundled-skills/slack/SKILL.md +0 -107
- package/src/config/bundled-skills/tasks/SKILL.md +0 -37
- package/src/config/bundled-skills/tasks/TOOLS.json +0 -353
- package/src/config/bundled-skills/tasks/icon.svg +0 -34
- package/src/config/bundled-skills/tasks/tools/task-delete.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list-add.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list-show.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list-update.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-queue-run.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-run.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-save.ts +0 -12
- package/src/config/bundled-skills/watcher/SKILL.md +0 -31
- package/src/config/bundled-skills/watcher/TOOLS.json +0 -167
- package/src/config/bundled-skills/watcher/tools/watcher-create.ts +0 -12
- package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +0 -12
- package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +0 -12
- package/src/config/bundled-skills/watcher/tools/watcher-list.ts +0 -12
- package/src/config/bundled-skills/watcher/tools/watcher-update.ts +0 -12
- 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/prompts/templates/UPDATES.md +0 -38
- package/src/prompts/templates/USER.md +0 -13
- package/src/prompts/update-bulletin-format.ts +0 -68
- package/src/prompts/update-bulletin-state.ts +0 -58
- package/src/prompts/update-bulletin-template-path.ts +0 -13
- package/src/prompts/update-bulletin.ts +0 -128
- package/src/providers/speech-to-text/types.ts +0 -17
- package/src/runtime/routes/browser-cdp-routes.ts +0 -229
- package/src/shared/provider-env-vars.ts +0 -19
- package/src/tools/watcher/create.ts +0 -86
- package/src/tools/watcher/delete.ts +0 -36
- package/src/tools/watcher/digest.ts +0 -54
- package/src/tools/watcher/list.ts +0 -83
- package/src/tools/watcher/update.ts +0 -71
|
@@ -55,23 +55,34 @@ mock.module("../providers/registry.js", () => ({
|
|
|
55
55
|
mock.module("../config/loader.js", () => ({
|
|
56
56
|
getConfig: () => ({
|
|
57
57
|
ui: {},
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
58
|
+
|
|
59
|
+
llm: {
|
|
60
|
+
default: {
|
|
61
|
+
provider: "mock-provider",
|
|
62
|
+
model: "mock-model",
|
|
63
|
+
maxTokens: 4096,
|
|
64
|
+
effort: "max" as const,
|
|
65
|
+
speed: "standard" as const,
|
|
66
|
+
temperature: null,
|
|
67
|
+
thinking: { enabled: false, streamThinking: true },
|
|
68
|
+
contextWindow: {
|
|
69
|
+
enabled: true,
|
|
70
|
+
maxInputTokens: 100000,
|
|
71
|
+
targetBudgetRatio: 0.3,
|
|
72
|
+
compactThreshold: 0.8,
|
|
73
|
+
summaryBudgetRatio: 0.05,
|
|
74
|
+
overflowRecovery: {
|
|
75
|
+
enabled: true,
|
|
76
|
+
safetyMarginRatio: 0.05,
|
|
77
|
+
maxAttempts: 3,
|
|
78
|
+
interactiveLatestTurnCompression: "summarize",
|
|
79
|
+
nonInteractiveLatestTurnCompression: "truncate",
|
|
80
|
+
},
|
|
81
|
+
},
|
|
74
82
|
},
|
|
83
|
+
profiles: {},
|
|
84
|
+
callSites: {},
|
|
85
|
+
pricingOverrides: [],
|
|
75
86
|
},
|
|
76
87
|
rateLimit: { maxRequestsPerMinute: 0 },
|
|
77
88
|
timeouts: { permissionTimeoutSec: 1 },
|
|
@@ -105,6 +116,20 @@ mock.module("../config/loader.js", () => ({
|
|
|
105
116
|
|
|
106
117
|
const mockedConversationHostAccess = new Map<string, boolean>();
|
|
107
118
|
|
|
119
|
+
const capturedAddMessages: Array<{
|
|
120
|
+
id: string;
|
|
121
|
+
role: string;
|
|
122
|
+
content: string;
|
|
123
|
+
metadata?: Record<string, unknown>;
|
|
124
|
+
}> = [];
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Content substrings that should cause `addMessage` to throw — used to
|
|
128
|
+
* simulate a mid-batch persist failure (e.g. a DB error on a specific
|
|
129
|
+
* tail message while its siblings succeed).
|
|
130
|
+
*/
|
|
131
|
+
const addMessageShouldThrowForContent = new Set<string>();
|
|
132
|
+
|
|
108
133
|
mock.module("../prompts/system-prompt.js", () => ({
|
|
109
134
|
buildSystemPrompt: () => "system prompt",
|
|
110
135
|
}));
|
|
@@ -133,6 +158,7 @@ mock.module("../security/secret-allowlist.js", () => ({
|
|
|
133
158
|
mock.module("../memory/conversation-crud.js", () => ({
|
|
134
159
|
getConversationType: () => "default",
|
|
135
160
|
setConversationOriginChannelIfUnset: () => {},
|
|
161
|
+
setConversationOriginInterfaceIfUnset: () => {},
|
|
136
162
|
updateConversationContextWindow: () => {},
|
|
137
163
|
getConversationHostAccess: (conversationId: string) =>
|
|
138
164
|
mockedConversationHostAccess.get(conversationId) ?? false,
|
|
@@ -159,11 +185,28 @@ mock.module("../memory/conversation-crud.js", () => ({
|
|
|
159
185
|
totalEstimatedCost: 0,
|
|
160
186
|
}),
|
|
161
187
|
createConversation: () => ({ id: "conv-1" }),
|
|
162
|
-
addMessage: (
|
|
163
|
-
|
|
188
|
+
addMessage: (
|
|
189
|
+
_convId: string,
|
|
190
|
+
role: string,
|
|
191
|
+
content: string,
|
|
192
|
+
metadata?: Record<string, unknown>,
|
|
193
|
+
) => {
|
|
194
|
+
// Simulate a persist failure for tests that need to exercise the
|
|
195
|
+
// tail-persist-failed path in drainBatch. Triggered by matching any
|
|
196
|
+
// registered substring against the serialized content payload.
|
|
197
|
+
for (const needle of addMessageShouldThrowForContent) {
|
|
198
|
+
if (content.includes(needle)) {
|
|
199
|
+
throw new Error(`Simulated addMessage failure for content: ${needle}`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
const id = `msg-${Date.now()}-${capturedAddMessages.length}`;
|
|
203
|
+
capturedAddMessages.push({ id, role, content, metadata });
|
|
204
|
+
return { id };
|
|
164
205
|
},
|
|
165
206
|
updateConversationUsage: () => {},
|
|
166
207
|
updateConversationTitle: () => {},
|
|
208
|
+
getMessageById: () => null,
|
|
209
|
+
getLastUserTimestampBefore: () => 0,
|
|
167
210
|
}));
|
|
168
211
|
|
|
169
212
|
mock.module("../memory/conversation-queries.js", () => ({
|
|
@@ -298,6 +341,9 @@ mock.module("../agent/loop.js", () => ({
|
|
|
298
341
|
getToolTokenBudget() {
|
|
299
342
|
return 0;
|
|
300
343
|
}
|
|
344
|
+
getActiveModel() {
|
|
345
|
+
return undefined;
|
|
346
|
+
}
|
|
301
347
|
async run(
|
|
302
348
|
messages: Message[],
|
|
303
349
|
onEvent: (event: AgentEvent) => void,
|
|
@@ -456,6 +502,7 @@ beforeEach(() => {
|
|
|
456
502
|
turnCommitCalls.length = 0;
|
|
457
503
|
turnCommitHangForever = false;
|
|
458
504
|
linkAttachmentShouldThrow = false;
|
|
505
|
+
addMessageShouldThrowForContent.clear();
|
|
459
506
|
});
|
|
460
507
|
|
|
461
508
|
afterAll(() => {
|
|
@@ -521,44 +568,73 @@ describe("Conversation message queue", () => {
|
|
|
521
568
|
await new Promise((r) => setTimeout(r, 10));
|
|
522
569
|
});
|
|
523
570
|
|
|
524
|
-
test("[experimental] queued
|
|
571
|
+
test("[experimental] queued passthrough siblings drain as a single batched run", async () => {
|
|
525
572
|
const conversation = makeConversation();
|
|
526
573
|
await conversation.loadFromDb();
|
|
527
574
|
|
|
528
|
-
const
|
|
529
|
-
|
|
530
|
-
const
|
|
531
|
-
if (e.type === "message_complete") processedOrder.push(label);
|
|
532
|
-
};
|
|
575
|
+
const events1: ServerMessage[] = [];
|
|
576
|
+
const events2: ServerMessage[] = [];
|
|
577
|
+
const events3: ServerMessage[] = [];
|
|
533
578
|
|
|
534
579
|
// Start first message
|
|
535
580
|
const p1 = conversation.processMessage(
|
|
536
581
|
"msg-1",
|
|
537
582
|
[],
|
|
538
|
-
|
|
583
|
+
(e) => events1.push(e),
|
|
539
584
|
"req-1",
|
|
540
585
|
);
|
|
541
586
|
await waitForPendingRun(1);
|
|
542
587
|
|
|
543
|
-
// Enqueue two more
|
|
544
|
-
conversation.enqueueMessage("msg-2", [],
|
|
545
|
-
conversation.enqueueMessage("msg-3", [],
|
|
588
|
+
// Enqueue two more sibling passthrough messages
|
|
589
|
+
conversation.enqueueMessage("msg-2", [], (e) => events2.push(e), "req-2");
|
|
590
|
+
conversation.enqueueMessage("msg-3", [], (e) => events3.push(e), "req-3");
|
|
546
591
|
expect(conversation.getQueueDepth()).toBe(2);
|
|
547
592
|
|
|
548
|
-
// Complete
|
|
593
|
+
// Complete run 0 → drain pulls msg-2 and msg-3 into ONE batched run.
|
|
549
594
|
resolveRun(0);
|
|
550
595
|
await p1;
|
|
551
596
|
await waitForPendingRun(2);
|
|
552
597
|
|
|
553
|
-
//
|
|
554
|
-
|
|
555
|
-
await waitForPendingRun(3);
|
|
598
|
+
// Exactly two runs total (not three): run 0 = msg-1, run 1 = batched [msg-2, msg-3]
|
|
599
|
+
expect(pendingRuns.length).toBe(2);
|
|
556
600
|
|
|
557
|
-
//
|
|
558
|
-
|
|
601
|
+
// Each batched client saw its own message_dequeued tagged with its own requestId.
|
|
602
|
+
const dequeued2 = events2.filter((e) => e.type === "message_dequeued");
|
|
603
|
+
expect(dequeued2).toHaveLength(1);
|
|
604
|
+
expect(dequeued2[0]).toEqual({
|
|
605
|
+
type: "message_dequeued",
|
|
606
|
+
conversationId: "conv-1",
|
|
607
|
+
requestId: "req-2",
|
|
608
|
+
});
|
|
609
|
+
const dequeued3 = events3.filter((e) => e.type === "message_dequeued");
|
|
610
|
+
expect(dequeued3).toHaveLength(1);
|
|
611
|
+
expect(dequeued3[0]).toEqual({
|
|
612
|
+
type: "message_dequeued",
|
|
613
|
+
conversationId: "conv-1",
|
|
614
|
+
requestId: "req-3",
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
// The batched run's captured history carries both siblings. Either as
|
|
618
|
+
// separate user entries (raw history) or merged into one user entry
|
|
619
|
+
// (after history-repair's alternation enforcement — required by the
|
|
620
|
+
// Anthropic API). Either way, both msg-2 and msg-3 text must appear.
|
|
621
|
+
const batchedHistory = pendingRuns[1].messages;
|
|
622
|
+
const userMessages = batchedHistory.filter((m) => m.role === "user");
|
|
623
|
+
const textOf = (m: Message) =>
|
|
624
|
+
(Array.isArray(m.content) ? m.content : [])
|
|
625
|
+
.filter((b) => b.type === "text")
|
|
626
|
+
.map((b) => (b as { text: string }).text)
|
|
627
|
+
.join("\n");
|
|
628
|
+
const combinedUserText = userMessages.map(textOf).join("\n");
|
|
629
|
+
expect(combinedUserText).toContain("msg-2");
|
|
630
|
+
expect(combinedUserText).toContain("msg-3");
|
|
631
|
+
|
|
632
|
+
// Resolve the batched run; message_complete must fan out to both clients.
|
|
633
|
+
resolveRun(1);
|
|
559
634
|
await new Promise((r) => setTimeout(r, 10));
|
|
560
635
|
|
|
561
|
-
expect(
|
|
636
|
+
expect(events2.some((e) => e.type === "message_complete")).toBe(true);
|
|
637
|
+
expect(events3.some((e) => e.type === "message_complete")).toBe(true);
|
|
562
638
|
});
|
|
563
639
|
|
|
564
640
|
test("message_queued and message_dequeued events are emitted", async () => {
|
|
@@ -699,27 +775,17 @@ describe("Conversation message queue", () => {
|
|
|
699
775
|
conversation.enqueueMessage("msg-4", [], () => {}, "req-4");
|
|
700
776
|
expect(conversation.getQueueDepth()).toBe(3);
|
|
701
777
|
|
|
702
|
-
// Complete first →
|
|
778
|
+
// Complete first → drain pulls all three same-interface passthroughs
|
|
779
|
+
// into a single batched run (depth → 0, runs → 2 total).
|
|
703
780
|
resolveRun(0);
|
|
704
781
|
await p1;
|
|
705
782
|
await waitForPendingRun(2);
|
|
706
783
|
|
|
707
|
-
expect(conversation.getQueueDepth()).toBe(2);
|
|
708
|
-
|
|
709
|
-
// Complete second → drains another
|
|
710
|
-
resolveRun(1);
|
|
711
|
-
await waitForPendingRun(3);
|
|
712
|
-
|
|
713
|
-
expect(conversation.getQueueDepth()).toBe(1);
|
|
714
|
-
|
|
715
|
-
// Complete third → drains last
|
|
716
|
-
resolveRun(2);
|
|
717
|
-
await waitForPendingRun(4);
|
|
718
|
-
|
|
719
784
|
expect(conversation.getQueueDepth()).toBe(0);
|
|
785
|
+
expect(pendingRuns.length).toBe(2);
|
|
720
786
|
|
|
721
|
-
// Complete
|
|
722
|
-
resolveRun(
|
|
787
|
+
// Complete the batched run; conversation finishes cleanly.
|
|
788
|
+
resolveRun(1);
|
|
723
789
|
await new Promise((r) => setTimeout(r, 10));
|
|
724
790
|
});
|
|
725
791
|
|
|
@@ -773,6 +839,764 @@ describe("Conversation message queue", () => {
|
|
|
773
839
|
});
|
|
774
840
|
});
|
|
775
841
|
|
|
842
|
+
// ---------------------------------------------------------------------------
|
|
843
|
+
// Batched drain — mixed-interface, slash-in-middle, attachments, byte budget
|
|
844
|
+
// ---------------------------------------------------------------------------
|
|
845
|
+
|
|
846
|
+
describe("Batched drain", () => {
|
|
847
|
+
beforeEach(() => {
|
|
848
|
+
pendingRuns = [];
|
|
849
|
+
});
|
|
850
|
+
|
|
851
|
+
test("mixed-interface queue splits into multiple batches at each interface boundary", async () => {
|
|
852
|
+
const conversation = makeConversation();
|
|
853
|
+
await conversation.loadFromDb();
|
|
854
|
+
|
|
855
|
+
const events2: ServerMessage[] = [];
|
|
856
|
+
const events3: ServerMessage[] = [];
|
|
857
|
+
const events4: ServerMessage[] = [];
|
|
858
|
+
const events5: ServerMessage[] = [];
|
|
859
|
+
|
|
860
|
+
// Start in-flight message (msg-1)
|
|
861
|
+
const p1 = conversation.processMessage("msg-1", [], () => {}, "req-1");
|
|
862
|
+
await waitForPendingRun(1);
|
|
863
|
+
|
|
864
|
+
// Enqueue 4 messages with interfaces [macos, macos, cli, macos].
|
|
865
|
+
// Expected drain: [macos batch of 2] → [cli single] → [macos single].
|
|
866
|
+
const meta = (iface: string) => ({
|
|
867
|
+
userMessageInterface: iface,
|
|
868
|
+
assistantMessageInterface: iface,
|
|
869
|
+
});
|
|
870
|
+
conversation.enqueueMessage(
|
|
871
|
+
"msg-2",
|
|
872
|
+
[],
|
|
873
|
+
(e) => events2.push(e),
|
|
874
|
+
"req-2",
|
|
875
|
+
undefined,
|
|
876
|
+
undefined,
|
|
877
|
+
meta("macos"),
|
|
878
|
+
);
|
|
879
|
+
conversation.enqueueMessage(
|
|
880
|
+
"msg-3",
|
|
881
|
+
[],
|
|
882
|
+
(e) => events3.push(e),
|
|
883
|
+
"req-3",
|
|
884
|
+
undefined,
|
|
885
|
+
undefined,
|
|
886
|
+
meta("macos"),
|
|
887
|
+
);
|
|
888
|
+
conversation.enqueueMessage(
|
|
889
|
+
"msg-4",
|
|
890
|
+
[],
|
|
891
|
+
(e) => events4.push(e),
|
|
892
|
+
"req-4",
|
|
893
|
+
undefined,
|
|
894
|
+
undefined,
|
|
895
|
+
meta("cli"),
|
|
896
|
+
);
|
|
897
|
+
conversation.enqueueMessage(
|
|
898
|
+
"msg-5",
|
|
899
|
+
[],
|
|
900
|
+
(e) => events5.push(e),
|
|
901
|
+
"req-5",
|
|
902
|
+
undefined,
|
|
903
|
+
undefined,
|
|
904
|
+
meta("macos"),
|
|
905
|
+
);
|
|
906
|
+
expect(conversation.getQueueDepth()).toBe(4);
|
|
907
|
+
|
|
908
|
+
// Resolve msg-1 → batched run pulls macos msg-2 + msg-3.
|
|
909
|
+
resolveRun(0);
|
|
910
|
+
await p1;
|
|
911
|
+
await waitForPendingRun(2);
|
|
912
|
+
|
|
913
|
+
// Batched run's history must contain both macos messages (either as
|
|
914
|
+
// separate user entries or merged into one after history-repair).
|
|
915
|
+
const macosBatchedHistory = pendingRuns[1].messages;
|
|
916
|
+
const macosUserMessages = macosBatchedHistory.filter(
|
|
917
|
+
(m) => m.role === "user",
|
|
918
|
+
);
|
|
919
|
+
const textOf = (m: Message) =>
|
|
920
|
+
(Array.isArray(m.content) ? m.content : [])
|
|
921
|
+
.filter((b) => b.type === "text")
|
|
922
|
+
.map((b) => (b as { text: string }).text)
|
|
923
|
+
.join("\n");
|
|
924
|
+
const combinedMacosText = macosUserMessages.map(textOf).join("\n");
|
|
925
|
+
expect(combinedMacosText).toContain("msg-2");
|
|
926
|
+
expect(combinedMacosText).toContain("msg-3");
|
|
927
|
+
|
|
928
|
+
// Both msg-2 and msg-3 received their own dequeue event.
|
|
929
|
+
expect(events2.filter((e) => e.type === "message_dequeued")).toHaveLength(
|
|
930
|
+
1,
|
|
931
|
+
);
|
|
932
|
+
expect(events3.filter((e) => e.type === "message_dequeued")).toHaveLength(
|
|
933
|
+
1,
|
|
934
|
+
);
|
|
935
|
+
|
|
936
|
+
// Resolve the batched run → drain pulls the cli single-message run.
|
|
937
|
+
resolveRun(1);
|
|
938
|
+
await waitForPendingRun(3);
|
|
939
|
+
|
|
940
|
+
// cli run contains msg-4 as a single-message run.
|
|
941
|
+
const cliHistory = pendingRuns[2].messages;
|
|
942
|
+
const cliUserText = cliHistory
|
|
943
|
+
.filter((m) => m.role === "user")
|
|
944
|
+
.map(textOf)
|
|
945
|
+
.join("\n");
|
|
946
|
+
expect(cliUserText).toContain("msg-4");
|
|
947
|
+
expect(events4.filter((e) => e.type === "message_dequeued")).toHaveLength(
|
|
948
|
+
1,
|
|
949
|
+
);
|
|
950
|
+
|
|
951
|
+
// Resolve the cli run → drain pulls the final macos single-message run.
|
|
952
|
+
resolveRun(2);
|
|
953
|
+
await waitForPendingRun(4);
|
|
954
|
+
const finalHistory = pendingRuns[3].messages;
|
|
955
|
+
const finalUserText = finalHistory
|
|
956
|
+
.filter((m) => m.role === "user")
|
|
957
|
+
.map(textOf)
|
|
958
|
+
.join("\n");
|
|
959
|
+
expect(finalUserText).toContain("msg-5");
|
|
960
|
+
expect(events5.filter((e) => e.type === "message_dequeued")).toHaveLength(
|
|
961
|
+
1,
|
|
962
|
+
);
|
|
963
|
+
|
|
964
|
+
// Four total runs: msg-1, batched [msg-2, msg-3], msg-4, msg-5.
|
|
965
|
+
expect(pendingRuns.length).toBe(4);
|
|
966
|
+
|
|
967
|
+
resolveRun(3);
|
|
968
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
969
|
+
});
|
|
970
|
+
|
|
971
|
+
test("slash-in-middle splits the queue at the slash boundary", async () => {
|
|
972
|
+
const conversation = makeConversation();
|
|
973
|
+
await conversation.loadFromDb();
|
|
974
|
+
|
|
975
|
+
const eventsHello: ServerMessage[] = [];
|
|
976
|
+
const eventsSlash: ServerMessage[] = [];
|
|
977
|
+
const eventsWorld: ServerMessage[] = [];
|
|
978
|
+
|
|
979
|
+
// Start in-flight message
|
|
980
|
+
const p1 = conversation.processMessage("msg-1", [], () => {}, "req-1");
|
|
981
|
+
await waitForPendingRun(1);
|
|
982
|
+
|
|
983
|
+
// Enqueue ["hello", "/compact", "world"]. /compact resolves to a non-
|
|
984
|
+
// passthrough slash, so the batch builder stops at "hello" (length 1),
|
|
985
|
+
// then /compact takes the single-message /compact short-circuit path
|
|
986
|
+
// (no new runAgentLoop invocation), then "world" drains as its own run.
|
|
987
|
+
conversation.enqueueMessage(
|
|
988
|
+
"hello",
|
|
989
|
+
[],
|
|
990
|
+
(e) => eventsHello.push(e),
|
|
991
|
+
"req-hello",
|
|
992
|
+
);
|
|
993
|
+
conversation.enqueueMessage(
|
|
994
|
+
"/compact",
|
|
995
|
+
[],
|
|
996
|
+
(e) => eventsSlash.push(e),
|
|
997
|
+
"req-slash",
|
|
998
|
+
);
|
|
999
|
+
conversation.enqueueMessage(
|
|
1000
|
+
"world",
|
|
1001
|
+
[],
|
|
1002
|
+
(e) => eventsWorld.push(e),
|
|
1003
|
+
"req-world",
|
|
1004
|
+
);
|
|
1005
|
+
expect(conversation.getQueueDepth()).toBe(3);
|
|
1006
|
+
|
|
1007
|
+
// Resolve msg-1 → drain pulls "hello" as its own run (batch stops at
|
|
1008
|
+
// /compact boundary).
|
|
1009
|
+
resolveRun(0);
|
|
1010
|
+
await p1;
|
|
1011
|
+
await waitForPendingRun(2);
|
|
1012
|
+
|
|
1013
|
+
expect(pendingRuns.length).toBe(2);
|
|
1014
|
+
expect(eventsHello.some((e) => e.type === "message_dequeued")).toBe(true);
|
|
1015
|
+
// /compact and "world" are still queued.
|
|
1016
|
+
expect(conversation.getQueueDepth()).toBe(2);
|
|
1017
|
+
|
|
1018
|
+
// Resolve "hello" → drain pops /compact via the builder-rejected path,
|
|
1019
|
+
// runs its short-circuit (no new runAgentLoop), then drains "world".
|
|
1020
|
+
resolveRun(1);
|
|
1021
|
+
await waitForPendingRun(3);
|
|
1022
|
+
|
|
1023
|
+
// /compact should have emitted its own message_complete via the short-
|
|
1024
|
+
// circuit path (not via a runAgentLoop run).
|
|
1025
|
+
expect(eventsSlash.some((e) => e.type === "message_complete")).toBe(true);
|
|
1026
|
+
expect(eventsWorld.some((e) => e.type === "message_dequeued")).toBe(true);
|
|
1027
|
+
expect(pendingRuns.length).toBe(3);
|
|
1028
|
+
|
|
1029
|
+
resolveRun(2);
|
|
1030
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
1031
|
+
});
|
|
1032
|
+
|
|
1033
|
+
test("unknown-slash in middle splits the queue at the unknown-slash boundary", async () => {
|
|
1034
|
+
// Covers the `kind: "unknown"` short-circuit branch in drainSingleMessage
|
|
1035
|
+
// specifically. The sibling /compact-in-middle test covers the `kind:
|
|
1036
|
+
// "compact"` short-circuit (via a different code path), so this test
|
|
1037
|
+
// exists to guarantee the batch builder also stops at unknown-kind
|
|
1038
|
+
// boundaries and that the unknown-slash drain path does NOT invoke a new
|
|
1039
|
+
// runAgentLoop run.
|
|
1040
|
+
//
|
|
1041
|
+
// We use `/status`, which the real `resolveSlash` returns as
|
|
1042
|
+
// `{ kind: "unknown", message: <status report> }` when a SlashContext is
|
|
1043
|
+
// present (always true for queued drains via buildSlashContext).
|
|
1044
|
+
const conversation = makeConversation();
|
|
1045
|
+
await conversation.loadFromDb();
|
|
1046
|
+
|
|
1047
|
+
const eventsPlainA: ServerMessage[] = [];
|
|
1048
|
+
const eventsSlash: ServerMessage[] = [];
|
|
1049
|
+
const eventsPlainB: ServerMessage[] = [];
|
|
1050
|
+
|
|
1051
|
+
// Start in-flight message
|
|
1052
|
+
const p1 = conversation.processMessage("msg-1", [], () => {}, "req-1");
|
|
1053
|
+
await waitForPendingRun(1);
|
|
1054
|
+
|
|
1055
|
+
// Enqueue ["plain-a", "/status", "plain-b"]. /status resolves to a non-
|
|
1056
|
+
// passthrough slash (kind: "unknown"), so the batch builder stops at
|
|
1057
|
+
// "plain-a" (length-1 batch → drainSingleMessage), then /status takes the
|
|
1058
|
+
// unknown-slash short-circuit path (no new runAgentLoop invocation — it
|
|
1059
|
+
// emits assistant_text_delta + message_complete inline), then "plain-b"
|
|
1060
|
+
// drains as its own run.
|
|
1061
|
+
conversation.enqueueMessage(
|
|
1062
|
+
"plain-a",
|
|
1063
|
+
[],
|
|
1064
|
+
(e) => eventsPlainA.push(e),
|
|
1065
|
+
"req-plain-a",
|
|
1066
|
+
);
|
|
1067
|
+
conversation.enqueueMessage(
|
|
1068
|
+
"/status",
|
|
1069
|
+
[],
|
|
1070
|
+
(e) => eventsSlash.push(e),
|
|
1071
|
+
"req-slash",
|
|
1072
|
+
);
|
|
1073
|
+
conversation.enqueueMessage(
|
|
1074
|
+
"plain-b",
|
|
1075
|
+
[],
|
|
1076
|
+
(e) => eventsPlainB.push(e),
|
|
1077
|
+
"req-plain-b",
|
|
1078
|
+
);
|
|
1079
|
+
expect(conversation.getQueueDepth()).toBe(3);
|
|
1080
|
+
|
|
1081
|
+
// Resolve msg-1 → drain pulls "plain-a" as its own run (batch stops at
|
|
1082
|
+
// the /status boundary).
|
|
1083
|
+
resolveRun(0);
|
|
1084
|
+
await p1;
|
|
1085
|
+
await waitForPendingRun(2);
|
|
1086
|
+
|
|
1087
|
+
expect(pendingRuns.length).toBe(2);
|
|
1088
|
+
expect(eventsPlainA.some((e) => e.type === "message_dequeued")).toBe(true);
|
|
1089
|
+
// /status and "plain-b" are still queued.
|
|
1090
|
+
expect(conversation.getQueueDepth()).toBe(2);
|
|
1091
|
+
|
|
1092
|
+
// Resolve "plain-a" → drain pops /status via the builder-rejected path,
|
|
1093
|
+
// runs its unknown-slash short-circuit (no new runAgentLoop, emits
|
|
1094
|
+
// assistant_text_delta + message_complete inline), then drains "plain-b"
|
|
1095
|
+
// as its own run.
|
|
1096
|
+
resolveRun(1);
|
|
1097
|
+
await waitForPendingRun(3);
|
|
1098
|
+
|
|
1099
|
+
// /status should have emitted its own assistant_text_delta + message_complete
|
|
1100
|
+
// via the unknown-slash short-circuit path (not via a runAgentLoop run).
|
|
1101
|
+
expect(eventsSlash.some((e) => e.type === "assistant_text_delta")).toBe(
|
|
1102
|
+
true,
|
|
1103
|
+
);
|
|
1104
|
+
expect(eventsSlash.some((e) => e.type === "message_complete")).toBe(true);
|
|
1105
|
+
expect(eventsPlainB.some((e) => e.type === "message_dequeued")).toBe(true);
|
|
1106
|
+
// Only three runs total: msg-1, "plain-a", "plain-b". /status short-circuits
|
|
1107
|
+
// without a runAgentLoop invocation.
|
|
1108
|
+
expect(pendingRuns.length).toBe(3);
|
|
1109
|
+
|
|
1110
|
+
resolveRun(2);
|
|
1111
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
1112
|
+
});
|
|
1113
|
+
|
|
1114
|
+
test("attachments are preserved across a batched drain", async () => {
|
|
1115
|
+
capturedAddMessages.length = 0;
|
|
1116
|
+
const conversation = makeConversation();
|
|
1117
|
+
await conversation.loadFromDb();
|
|
1118
|
+
|
|
1119
|
+
// Start in-flight message
|
|
1120
|
+
const p1 = conversation.processMessage("msg-1", [], () => {}, "req-1");
|
|
1121
|
+
await waitForPendingRun(1);
|
|
1122
|
+
|
|
1123
|
+
// Two sibling messages, each with a distinct image attachment.
|
|
1124
|
+
const attachA = [
|
|
1125
|
+
{
|
|
1126
|
+
id: "att-a",
|
|
1127
|
+
filename: "a.png",
|
|
1128
|
+
mimeType: "image/png",
|
|
1129
|
+
data: Buffer.from("imageA").toString("base64"),
|
|
1130
|
+
filePath: "/tmp/a.png",
|
|
1131
|
+
},
|
|
1132
|
+
];
|
|
1133
|
+
const attachB = [
|
|
1134
|
+
{
|
|
1135
|
+
id: "att-b",
|
|
1136
|
+
filename: "b.png",
|
|
1137
|
+
mimeType: "image/png",
|
|
1138
|
+
data: Buffer.from("imageB").toString("base64"),
|
|
1139
|
+
filePath: "/tmp/b.png",
|
|
1140
|
+
},
|
|
1141
|
+
];
|
|
1142
|
+
conversation.enqueueMessage("with-A", attachA, () => {}, "req-A");
|
|
1143
|
+
conversation.enqueueMessage("with-B", attachB, () => {}, "req-B");
|
|
1144
|
+
expect(conversation.getQueueDepth()).toBe(2);
|
|
1145
|
+
|
|
1146
|
+
resolveRun(0);
|
|
1147
|
+
await p1;
|
|
1148
|
+
await waitForPendingRun(2);
|
|
1149
|
+
|
|
1150
|
+
// Two persisted user rows in the DB (one per batched message), each with
|
|
1151
|
+
// its own imageSourcePaths metadata keyed by the right filename.
|
|
1152
|
+
const userRows = capturedAddMessages.filter(
|
|
1153
|
+
(m) => m.role === "user" && m.content.includes('"image"'),
|
|
1154
|
+
);
|
|
1155
|
+
expect(userRows).toHaveLength(2);
|
|
1156
|
+
const pathsA = (userRows[0].metadata as Record<string, unknown>)
|
|
1157
|
+
?.imageSourcePaths as Record<string, string> | undefined;
|
|
1158
|
+
expect(pathsA).toBeDefined();
|
|
1159
|
+
expect(pathsA!["0:a.png"]).toBe("/tmp/a.png");
|
|
1160
|
+
const pathsB = (userRows[1].metadata as Record<string, unknown>)
|
|
1161
|
+
?.imageSourcePaths as Record<string, string> | undefined;
|
|
1162
|
+
expect(pathsB).toBeDefined();
|
|
1163
|
+
expect(pathsB!["0:b.png"]).toBe("/tmp/b.png");
|
|
1164
|
+
|
|
1165
|
+
// The batched run's in-memory history also reflects both image sources
|
|
1166
|
+
// (enrichMessageWithSourcePaths injects file:// references for images).
|
|
1167
|
+
const batchedHistory = pendingRuns[1].messages;
|
|
1168
|
+
const userMessages = batchedHistory.filter((m) => m.role === "user");
|
|
1169
|
+
const allText = userMessages
|
|
1170
|
+
.map((m) =>
|
|
1171
|
+
(Array.isArray(m.content) ? m.content : [])
|
|
1172
|
+
.filter((b) => b.type === "text")
|
|
1173
|
+
.map((b) => (b as { text: string }).text)
|
|
1174
|
+
.join("\n"),
|
|
1175
|
+
)
|
|
1176
|
+
.join("\n");
|
|
1177
|
+
expect(allText).toContain("a.png");
|
|
1178
|
+
expect(allText).toContain("b.png");
|
|
1179
|
+
|
|
1180
|
+
resolveRun(1);
|
|
1181
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
1182
|
+
});
|
|
1183
|
+
|
|
1184
|
+
test("byte-budget accounting is unchanged by shiftN-based batching", async () => {
|
|
1185
|
+
// Uses a small budget so we can observe reclamation after drain.
|
|
1186
|
+
// Each ~500-char message ≈ 1512 bytes.
|
|
1187
|
+
const conversation = makeConversation();
|
|
1188
|
+
await conversation.loadFromDb();
|
|
1189
|
+
|
|
1190
|
+
const budget = 4000;
|
|
1191
|
+
(conversation as unknown as { queue: MessageQueue }).queue =
|
|
1192
|
+
new MessageQueue(budget);
|
|
1193
|
+
|
|
1194
|
+
// Start in-flight so subsequent enqueues are queued (not processed).
|
|
1195
|
+
const p1 = conversation.processMessage("msg-1", [], () => {}, "req-1");
|
|
1196
|
+
await waitForPendingRun(1);
|
|
1197
|
+
|
|
1198
|
+
// Fill to just-under budget: two ~500-char messages (1512+1512 = 3024 bytes).
|
|
1199
|
+
const accepted1 = conversation.enqueueMessage(
|
|
1200
|
+
"x".repeat(500),
|
|
1201
|
+
[],
|
|
1202
|
+
() => {},
|
|
1203
|
+
"req-big-1",
|
|
1204
|
+
);
|
|
1205
|
+
const accepted2 = conversation.enqueueMessage(
|
|
1206
|
+
"y".repeat(500),
|
|
1207
|
+
[],
|
|
1208
|
+
() => {},
|
|
1209
|
+
"req-big-2",
|
|
1210
|
+
);
|
|
1211
|
+
expect(accepted1.queued).toBe(true);
|
|
1212
|
+
expect(accepted2.queued).toBe(true);
|
|
1213
|
+
// A third would push the queue over budget → rejected. Capture its
|
|
1214
|
+
// onEvent callback so we can verify the queue_full error event reaches
|
|
1215
|
+
// the rejected caller (not just the synchronous return value).
|
|
1216
|
+
const rejectedEvents: ServerMessage[] = [];
|
|
1217
|
+
const rejected = conversation.enqueueMessage(
|
|
1218
|
+
"z".repeat(500),
|
|
1219
|
+
[],
|
|
1220
|
+
(e) => rejectedEvents.push(e),
|
|
1221
|
+
"req-over",
|
|
1222
|
+
);
|
|
1223
|
+
expect(rejected.queued).toBe(false);
|
|
1224
|
+
expect(rejected.rejected).toBe(true);
|
|
1225
|
+
expect(conversation.getQueueDepth()).toBe(2);
|
|
1226
|
+
|
|
1227
|
+
// The rejected caller must have received a `queue_full` error event on
|
|
1228
|
+
// its own onEvent callback — event emission is part of the public
|
|
1229
|
+
// contract, not just the return value.
|
|
1230
|
+
const queueFullErr = rejectedEvents.find(
|
|
1231
|
+
(e) => e.type === "error" && e.category === "queue_full",
|
|
1232
|
+
);
|
|
1233
|
+
expect(queueFullErr).toBeDefined();
|
|
1234
|
+
if (queueFullErr && queueFullErr.type === "error") {
|
|
1235
|
+
expect(queueFullErr.category).toBe("queue_full");
|
|
1236
|
+
expect(typeof queueFullErr.message).toBe("string");
|
|
1237
|
+
expect(queueFullErr.message.length).toBeGreaterThan(0);
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
// Complete in-flight → drain pulls both queued passthroughs as ONE batched run.
|
|
1241
|
+
resolveRun(0);
|
|
1242
|
+
await p1;
|
|
1243
|
+
await waitForPendingRun(2);
|
|
1244
|
+
expect(conversation.getQueueDepth()).toBe(0);
|
|
1245
|
+
|
|
1246
|
+
// Resolve the batched run.
|
|
1247
|
+
resolveRun(1);
|
|
1248
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
1249
|
+
|
|
1250
|
+
// After the full drain, the byte budget must be fully reclaimed — a fresh
|
|
1251
|
+
// round of enqueues up to the budget should succeed again. Spin up another
|
|
1252
|
+
// in-flight message to reach the queueing state.
|
|
1253
|
+
const p2 = conversation.processMessage("msg-2", [], () => {}, "req-2");
|
|
1254
|
+
await waitForPendingRun(3);
|
|
1255
|
+
expect(
|
|
1256
|
+
conversation.enqueueMessage("a".repeat(500), [], () => {}, "req-a")
|
|
1257
|
+
.queued,
|
|
1258
|
+
).toBe(true);
|
|
1259
|
+
expect(
|
|
1260
|
+
conversation.enqueueMessage("b".repeat(500), [], () => {}, "req-b")
|
|
1261
|
+
.queued,
|
|
1262
|
+
).toBe(true);
|
|
1263
|
+
|
|
1264
|
+
resolveRun(2);
|
|
1265
|
+
await p2;
|
|
1266
|
+
await waitForPendingRun(4);
|
|
1267
|
+
resolveRun(3);
|
|
1268
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
1269
|
+
});
|
|
1270
|
+
});
|
|
1271
|
+
|
|
1272
|
+
// ---------------------------------------------------------------------------
|
|
1273
|
+
// Batched drain — correctness fixes (surface exclusion, abort, last-successful
|
|
1274
|
+
// tracking, single activity-state emission)
|
|
1275
|
+
// ---------------------------------------------------------------------------
|
|
1276
|
+
|
|
1277
|
+
describe("Batched drain correctness fixes", () => {
|
|
1278
|
+
beforeEach(() => {
|
|
1279
|
+
pendingRuns = [];
|
|
1280
|
+
capturedAddMessages.length = 0;
|
|
1281
|
+
});
|
|
1282
|
+
|
|
1283
|
+
test("surface-action messages are not batched with regular passthroughs", async () => {
|
|
1284
|
+
const conversation = makeConversation();
|
|
1285
|
+
await conversation.loadFromDb();
|
|
1286
|
+
|
|
1287
|
+
const eventsSurface: ServerMessage[] = [];
|
|
1288
|
+
const eventsRegular: ServerMessage[] = [];
|
|
1289
|
+
|
|
1290
|
+
// Start in-flight message
|
|
1291
|
+
const p1 = conversation.processMessage("msg-1", [], () => {}, "req-1");
|
|
1292
|
+
await waitForPendingRun(1);
|
|
1293
|
+
|
|
1294
|
+
// Enqueue a surface-action message (activeSurfaceId set + tracked in
|
|
1295
|
+
// surfaceActionRequestIds) followed by a regular passthrough from the
|
|
1296
|
+
// same interface. The batch builder must reject the surface-action head
|
|
1297
|
+
// so each drains as its own run.
|
|
1298
|
+
conversation.surfaceActionRequestIds.add("req-surface");
|
|
1299
|
+
conversation.enqueueMessage(
|
|
1300
|
+
"surface action response",
|
|
1301
|
+
[],
|
|
1302
|
+
(e) => eventsSurface.push(e),
|
|
1303
|
+
"req-surface",
|
|
1304
|
+
"surface-1", // activeSurfaceId
|
|
1305
|
+
);
|
|
1306
|
+
conversation.enqueueMessage(
|
|
1307
|
+
"regular follow-up",
|
|
1308
|
+
[],
|
|
1309
|
+
(e) => eventsRegular.push(e),
|
|
1310
|
+
"req-regular",
|
|
1311
|
+
);
|
|
1312
|
+
expect(conversation.getQueueDepth()).toBe(2);
|
|
1313
|
+
|
|
1314
|
+
// Complete run 0 → drain must NOT batch the surface-action with the
|
|
1315
|
+
// regular passthrough. Expect the surface-action to drain as a single
|
|
1316
|
+
// run first.
|
|
1317
|
+
resolveRun(0);
|
|
1318
|
+
await p1;
|
|
1319
|
+
await waitForPendingRun(2);
|
|
1320
|
+
|
|
1321
|
+
// The second run is the surface-action single-message run.
|
|
1322
|
+
const surfaceUserRowsAfterRun2 = capturedAddMessages.filter(
|
|
1323
|
+
(m) => m.role === "user" && m.content.includes("surface action response"),
|
|
1324
|
+
);
|
|
1325
|
+
expect(surfaceUserRowsAfterRun2).toHaveLength(1);
|
|
1326
|
+
expect(
|
|
1327
|
+
eventsSurface.filter((e) => e.type === "message_dequeued"),
|
|
1328
|
+
).toHaveLength(1);
|
|
1329
|
+
|
|
1330
|
+
// Complete the surface-action run; drain pulls the regular passthrough
|
|
1331
|
+
// as its own separate run.
|
|
1332
|
+
resolveRun(1);
|
|
1333
|
+
await waitForPendingRun(3);
|
|
1334
|
+
expect(pendingRuns.length).toBe(3);
|
|
1335
|
+
expect(
|
|
1336
|
+
eventsRegular.filter((e) => e.type === "message_dequeued"),
|
|
1337
|
+
).toHaveLength(1);
|
|
1338
|
+
|
|
1339
|
+
// Total runs = 3: msg-1, surface-action, regular — NOT 2 (would mean
|
|
1340
|
+
// they were batched).
|
|
1341
|
+
resolveRun(2);
|
|
1342
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
1343
|
+
});
|
|
1344
|
+
|
|
1345
|
+
test("abort mid-batch stops tail persists", async () => {
|
|
1346
|
+
const conversation = makeConversation();
|
|
1347
|
+
await conversation.loadFromDb();
|
|
1348
|
+
|
|
1349
|
+
const events1: ServerMessage[] = [];
|
|
1350
|
+
const events2: ServerMessage[] = [];
|
|
1351
|
+
const events3: ServerMessage[] = [];
|
|
1352
|
+
const events4: ServerMessage[] = [];
|
|
1353
|
+
|
|
1354
|
+
// Start in-flight message
|
|
1355
|
+
const p1 = conversation.processMessage(
|
|
1356
|
+
"msg-1",
|
|
1357
|
+
[],
|
|
1358
|
+
(e) => events1.push(e),
|
|
1359
|
+
"req-1",
|
|
1360
|
+
);
|
|
1361
|
+
await waitForPendingRun(1);
|
|
1362
|
+
|
|
1363
|
+
// Enqueue three sibling passthroughs (msg-2 = head, msg-3 = mid,
|
|
1364
|
+
// msg-4 = tail). We trigger abort from msg-3's dequeue callback —
|
|
1365
|
+
// by the time that fires, msg-2 has already been persisted (which
|
|
1366
|
+
// REPLACED the abortController, since persistUserMessage creates a
|
|
1367
|
+
// fresh one). Calling abort() now aborts that fresh controller, and
|
|
1368
|
+
// the drainBatch loop's abort check after msg-3's persist will break,
|
|
1369
|
+
// so msg-4 never persists.
|
|
1370
|
+
conversation.enqueueMessage("msg-2", [], (e) => events2.push(e), "req-2");
|
|
1371
|
+
|
|
1372
|
+
// Install a one-shot abort trigger on msg-3's dequeue event. We do
|
|
1373
|
+
// this before enqueueing so the wrapped callback is what drainBatch
|
|
1374
|
+
// invokes.
|
|
1375
|
+
let aborted = false;
|
|
1376
|
+
const onMsg3Event = (e: ServerMessage) => {
|
|
1377
|
+
events3.push(e);
|
|
1378
|
+
if (!aborted && e.type === "message_dequeued") {
|
|
1379
|
+
aborted = true;
|
|
1380
|
+
conversation.abort();
|
|
1381
|
+
}
|
|
1382
|
+
};
|
|
1383
|
+
conversation.enqueueMessage("msg-3", [], onMsg3Event, "req-3");
|
|
1384
|
+
conversation.enqueueMessage("msg-4", [], (e) => events4.push(e), "req-4");
|
|
1385
|
+
expect(conversation.getQueueDepth()).toBe(3);
|
|
1386
|
+
|
|
1387
|
+
const persistedUserRowCountBefore = capturedAddMessages.filter(
|
|
1388
|
+
(m) => m.role === "user",
|
|
1389
|
+
).length;
|
|
1390
|
+
|
|
1391
|
+
// Complete run 0 → drain pulls the sibling batch.
|
|
1392
|
+
resolveRun(0);
|
|
1393
|
+
await p1;
|
|
1394
|
+
|
|
1395
|
+
// Give the drain loop a chance to iterate. Abort happens on msg-3's
|
|
1396
|
+
// dequeue (between msg-2's persist and msg-3's persist), so msg-3 may
|
|
1397
|
+
// still persist before the abort check at the end of its iteration.
|
|
1398
|
+
// Either way, msg-4 must NOT persist.
|
|
1399
|
+
await new Promise((r) => setTimeout(r, 30));
|
|
1400
|
+
|
|
1401
|
+
const userRowsAfter = capturedAddMessages
|
|
1402
|
+
.slice(persistedUserRowCountBefore)
|
|
1403
|
+
.filter((m) => m.role === "user");
|
|
1404
|
+
const contents = userRowsAfter.map((r) => r.content).join("||");
|
|
1405
|
+
expect(contents).toContain("msg-2");
|
|
1406
|
+
expect(contents).not.toContain("msg-4");
|
|
1407
|
+
expect(events4.filter((e) => e.type === "message_dequeued")).toHaveLength(
|
|
1408
|
+
0,
|
|
1409
|
+
);
|
|
1410
|
+
});
|
|
1411
|
+
|
|
1412
|
+
test("failed tail persist uses last-successful requestId", async () => {
|
|
1413
|
+
const conversation = makeConversation();
|
|
1414
|
+
await conversation.loadFromDb();
|
|
1415
|
+
|
|
1416
|
+
const events1: ServerMessage[] = [];
|
|
1417
|
+
const events2: ServerMessage[] = [];
|
|
1418
|
+
const events3: ServerMessage[] = [];
|
|
1419
|
+
const events4: ServerMessage[] = [];
|
|
1420
|
+
|
|
1421
|
+
// Start in-flight message
|
|
1422
|
+
const p1 = conversation.processMessage(
|
|
1423
|
+
"msg-1",
|
|
1424
|
+
[],
|
|
1425
|
+
(e) => events1.push(e),
|
|
1426
|
+
"req-1",
|
|
1427
|
+
);
|
|
1428
|
+
await waitForPendingRun(1);
|
|
1429
|
+
|
|
1430
|
+
// Enqueue three siblings. Configure addMessage to throw for the second
|
|
1431
|
+
// tail (msg-mid) but succeed for msg-head and msg-tail. This simulates
|
|
1432
|
+
// a middle tail persist failure — currentRequestId should end up as
|
|
1433
|
+
// msg-tail's requestId (the LAST successful persist), not msg-mid's.
|
|
1434
|
+
addMessageShouldThrowForContent.add("msg-mid-unique-marker");
|
|
1435
|
+
|
|
1436
|
+
conversation.enqueueMessage(
|
|
1437
|
+
"msg-head",
|
|
1438
|
+
[],
|
|
1439
|
+
(e) => events2.push(e),
|
|
1440
|
+
"req-head",
|
|
1441
|
+
);
|
|
1442
|
+
conversation.enqueueMessage(
|
|
1443
|
+
"msg-mid-unique-marker",
|
|
1444
|
+
[],
|
|
1445
|
+
(e) => events3.push(e),
|
|
1446
|
+
"req-mid",
|
|
1447
|
+
);
|
|
1448
|
+
conversation.enqueueMessage(
|
|
1449
|
+
"msg-tail",
|
|
1450
|
+
[],
|
|
1451
|
+
(e) => events4.push(e),
|
|
1452
|
+
"req-tail",
|
|
1453
|
+
);
|
|
1454
|
+
expect(conversation.getQueueDepth()).toBe(3);
|
|
1455
|
+
|
|
1456
|
+
// Complete run 0 → batched drain.
|
|
1457
|
+
resolveRun(0);
|
|
1458
|
+
await p1;
|
|
1459
|
+
await waitForPendingRun(2);
|
|
1460
|
+
|
|
1461
|
+
// mid should have emitted an error event via persist failure.
|
|
1462
|
+
const errMid = events3.find((e) => e.type === "error");
|
|
1463
|
+
expect(errMid).toBeDefined();
|
|
1464
|
+
|
|
1465
|
+
// The agent loop should have been invoked with the tail's userMessageId
|
|
1466
|
+
// (last SUCCESSFUL persist), not the mid's. We check via currentRequestId
|
|
1467
|
+
// on the conversation which drainBatch assigns after the loop.
|
|
1468
|
+
expect(
|
|
1469
|
+
(conversation as unknown as { currentRequestId?: string })
|
|
1470
|
+
.currentRequestId,
|
|
1471
|
+
).toBe("req-tail");
|
|
1472
|
+
|
|
1473
|
+
// Cleanup: resolve the batched run.
|
|
1474
|
+
resolveRun(1);
|
|
1475
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
1476
|
+
});
|
|
1477
|
+
|
|
1478
|
+
test("failed tail persist is excluded from fanOutOnEvent agent events", async () => {
|
|
1479
|
+
const conversation = makeConversation();
|
|
1480
|
+
await conversation.loadFromDb();
|
|
1481
|
+
|
|
1482
|
+
const events1: ServerMessage[] = [];
|
|
1483
|
+
const events2: ServerMessage[] = [];
|
|
1484
|
+
const events3: ServerMessage[] = [];
|
|
1485
|
+
const events4: ServerMessage[] = [];
|
|
1486
|
+
|
|
1487
|
+
const p1 = conversation.processMessage(
|
|
1488
|
+
"msg-1",
|
|
1489
|
+
[],
|
|
1490
|
+
(e) => events1.push(e),
|
|
1491
|
+
"req-1",
|
|
1492
|
+
);
|
|
1493
|
+
await waitForPendingRun(1);
|
|
1494
|
+
|
|
1495
|
+
// Mid tail will fail to persist. After the batched run resolves,
|
|
1496
|
+
// message_complete (broadcast via fanOutOnEvent) must NOT land on the
|
|
1497
|
+
// failed mid tail — it already received an error event and persisting
|
|
1498
|
+
// the assistant reply for a user message that has no DB row would
|
|
1499
|
+
// desync the client.
|
|
1500
|
+
addMessageShouldThrowForContent.add("fanout-mid-marker");
|
|
1501
|
+
|
|
1502
|
+
conversation.enqueueMessage(
|
|
1503
|
+
"fanout-head",
|
|
1504
|
+
[],
|
|
1505
|
+
(e) => events2.push(e),
|
|
1506
|
+
"req-fanout-head",
|
|
1507
|
+
);
|
|
1508
|
+
conversation.enqueueMessage(
|
|
1509
|
+
"fanout-mid-marker",
|
|
1510
|
+
[],
|
|
1511
|
+
(e) => events3.push(e),
|
|
1512
|
+
"req-fanout-mid",
|
|
1513
|
+
);
|
|
1514
|
+
conversation.enqueueMessage(
|
|
1515
|
+
"fanout-tail",
|
|
1516
|
+
[],
|
|
1517
|
+
(e) => events4.push(e),
|
|
1518
|
+
"req-fanout-tail",
|
|
1519
|
+
);
|
|
1520
|
+
|
|
1521
|
+
resolveRun(0);
|
|
1522
|
+
await p1;
|
|
1523
|
+
await waitForPendingRun(2);
|
|
1524
|
+
|
|
1525
|
+
// Drive the batched run to emit message_complete via fanOutOnEvent.
|
|
1526
|
+
resolveRun(1);
|
|
1527
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
1528
|
+
|
|
1529
|
+
expect(events3.find((e) => e.type === "error")).toBeDefined();
|
|
1530
|
+
expect(events3.find((e) => e.type === "message_complete")).toBeUndefined();
|
|
1531
|
+
|
|
1532
|
+
expect(events2.find((e) => e.type === "message_complete")).toBeDefined();
|
|
1533
|
+
expect(events4.find((e) => e.type === "message_complete")).toBeDefined();
|
|
1534
|
+
});
|
|
1535
|
+
|
|
1536
|
+
test("drainBatch emits exactly one activity-state event for the whole batch", async () => {
|
|
1537
|
+
const activityStates: ServerMessage[] = [];
|
|
1538
|
+
const conversation = makeConversation((msg) => {
|
|
1539
|
+
if ("type" in msg && msg.type === "assistant_activity_state") {
|
|
1540
|
+
activityStates.push(msg);
|
|
1541
|
+
}
|
|
1542
|
+
});
|
|
1543
|
+
await conversation.loadFromDb();
|
|
1544
|
+
|
|
1545
|
+
// Start in-flight message
|
|
1546
|
+
const p1 = conversation.processMessage("msg-1", [], () => {}, "req-1");
|
|
1547
|
+
await waitForPendingRun(1);
|
|
1548
|
+
|
|
1549
|
+
// Snapshot the count before drain so we only compare batch-emitted
|
|
1550
|
+
// transitions (msg-1's processMessage already fired one).
|
|
1551
|
+
const baseline = activityStates.length;
|
|
1552
|
+
|
|
1553
|
+
// Enqueue three sibling passthroughs.
|
|
1554
|
+
conversation.enqueueMessage("msg-2", [], () => {}, "req-2");
|
|
1555
|
+
conversation.enqueueMessage("msg-3", [], () => {}, "req-3");
|
|
1556
|
+
conversation.enqueueMessage("msg-4", [], () => {}, "req-4");
|
|
1557
|
+
|
|
1558
|
+
// Complete run 0 → drain pulls the batched siblings as ONE run.
|
|
1559
|
+
resolveRun(0);
|
|
1560
|
+
await p1;
|
|
1561
|
+
await waitForPendingRun(2);
|
|
1562
|
+
|
|
1563
|
+
// Filter for "message_dequeued" reasons emitted by the batched drain.
|
|
1564
|
+
const batchEmissions = activityStates
|
|
1565
|
+
.slice(baseline)
|
|
1566
|
+
.filter(
|
|
1567
|
+
(m) =>
|
|
1568
|
+
"type" in m &&
|
|
1569
|
+
m.type === "assistant_activity_state" &&
|
|
1570
|
+
(m as { reason?: string }).reason === "message_dequeued",
|
|
1571
|
+
);
|
|
1572
|
+
expect(batchEmissions).toHaveLength(1);
|
|
1573
|
+
expect(batchEmissions[0]).toMatchObject({
|
|
1574
|
+
type: "assistant_activity_state",
|
|
1575
|
+
reason: "message_dequeued",
|
|
1576
|
+
requestId: "req-2", // head's requestId, per the fix
|
|
1577
|
+
});
|
|
1578
|
+
|
|
1579
|
+
resolveRun(1);
|
|
1580
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
1581
|
+
});
|
|
1582
|
+
|
|
1583
|
+
// Defensive recovery path: buildPassthroughBatch is designed to make
|
|
1584
|
+
// the invariant throw unreachable in practice, so neither the head
|
|
1585
|
+
// branch (re-dispatch batch.slice(1) to drainBatch/drainSingleMessage/
|
|
1586
|
+
// drainQueue) nor the tail branch (skip + continue) can fire in normal
|
|
1587
|
+
// operation. Left as a todo so the harness contract is documented
|
|
1588
|
+
// without wedging mainline CI. Covering this would require either
|
|
1589
|
+
// (a) reflecting into drainBatch to short-circuit resolveSlash for a
|
|
1590
|
+
// specific batch entry, or (b) exposing a seam on SlashContext — both
|
|
1591
|
+
// are more invasive than the safety-net value justifies.
|
|
1592
|
+
test.todo(
|
|
1593
|
+
"invariant violation in persist loop triggers error event + recovery, not stranded state",
|
|
1594
|
+
async () => {
|
|
1595
|
+
// no-op: see comment above.
|
|
1596
|
+
},
|
|
1597
|
+
);
|
|
1598
|
+
});
|
|
1599
|
+
|
|
776
1600
|
// ---------------------------------------------------------------------------
|
|
777
1601
|
// Queue policy primitives
|
|
778
1602
|
// ---------------------------------------------------------------------------
|
|
@@ -962,32 +1786,31 @@ describe("Conversation checkpoint handoff", () => {
|
|
|
962
1786
|
await p1;
|
|
963
1787
|
});
|
|
964
1788
|
|
|
965
|
-
test("[experimental]
|
|
1789
|
+
test("[experimental] checkpoint handoff pulls a batched run for all queued siblings", async () => {
|
|
966
1790
|
const conversation = makeConversation();
|
|
967
1791
|
await conversation.loadFromDb();
|
|
968
1792
|
|
|
969
|
-
const
|
|
970
|
-
|
|
971
|
-
const
|
|
972
|
-
|
|
973
|
-
processedOrder.push(label);
|
|
974
|
-
};
|
|
1793
|
+
const events1: ServerMessage[] = [];
|
|
1794
|
+
const events2: ServerMessage[] = [];
|
|
1795
|
+
const events3: ServerMessage[] = [];
|
|
1796
|
+
const events4: ServerMessage[] = [];
|
|
975
1797
|
|
|
976
|
-
// Start first message
|
|
1798
|
+
// Start first message (mid-tool-use — will yield at the next checkpoint)
|
|
977
1799
|
const p1 = conversation.processMessage(
|
|
978
1800
|
"msg-1",
|
|
979
1801
|
[],
|
|
980
|
-
|
|
1802
|
+
(e) => events1.push(e),
|
|
981
1803
|
"req-1",
|
|
982
1804
|
);
|
|
983
1805
|
await waitForPendingRun(1);
|
|
984
1806
|
|
|
985
|
-
// Enqueue
|
|
986
|
-
conversation.enqueueMessage("msg-2", [],
|
|
987
|
-
conversation.enqueueMessage("msg-3", [],
|
|
988
|
-
|
|
1807
|
+
// Enqueue three sibling passthroughs while msg-1 is mid-turn
|
|
1808
|
+
conversation.enqueueMessage("msg-2", [], (e) => events2.push(e), "req-2");
|
|
1809
|
+
conversation.enqueueMessage("msg-3", [], (e) => events3.push(e), "req-3");
|
|
1810
|
+
conversation.enqueueMessage("msg-4", [], (e) => events4.push(e), "req-4");
|
|
1811
|
+
expect(conversation.getQueueDepth()).toBe(3);
|
|
989
1812
|
|
|
990
|
-
// Simulate the agent loop yielding at the checkpoint (first run)
|
|
1813
|
+
// Simulate the agent loop yielding at the checkpoint (first run is mid-tool-use)
|
|
991
1814
|
const run0 = pendingRuns[0];
|
|
992
1815
|
expect(run0.onCheckpoint).toBeDefined();
|
|
993
1816
|
const decision = run0.onCheckpoint!({
|
|
@@ -1002,19 +1825,23 @@ describe("Conversation checkpoint handoff", () => {
|
|
|
1002
1825
|
resolveRun(0);
|
|
1003
1826
|
await p1;
|
|
1004
1827
|
|
|
1005
|
-
//
|
|
1828
|
+
// The yielded drain pulls ALL THREE queued siblings as ONE batched run —
|
|
1829
|
+
// not three separate runs.
|
|
1006
1830
|
await waitForPendingRun(2);
|
|
1831
|
+
expect(pendingRuns.length).toBe(2);
|
|
1007
1832
|
|
|
1008
|
-
//
|
|
1009
|
-
|
|
1010
|
-
|
|
1833
|
+
// Each client saw its own message_dequeued tagged with its own requestId.
|
|
1834
|
+
expect(events2.some((e) => e.type === "message_dequeued")).toBe(true);
|
|
1835
|
+
expect(events3.some((e) => e.type === "message_dequeued")).toBe(true);
|
|
1836
|
+
expect(events4.some((e) => e.type === "message_dequeued")).toBe(true);
|
|
1011
1837
|
|
|
1012
|
-
//
|
|
1013
|
-
resolveRun(
|
|
1838
|
+
// Resolve the batched run — message_complete fans out to all three clients.
|
|
1839
|
+
resolveRun(1);
|
|
1014
1840
|
await new Promise((r) => setTimeout(r, 10));
|
|
1015
1841
|
|
|
1016
|
-
|
|
1017
|
-
expect(
|
|
1842
|
+
expect(events2.some((e) => e.type === "message_complete")).toBe(true);
|
|
1843
|
+
expect(events3.some((e) => e.type === "message_complete")).toBe(true);
|
|
1844
|
+
expect(events4.some((e) => e.type === "message_complete")).toBe(true);
|
|
1018
1845
|
});
|
|
1019
1846
|
|
|
1020
1847
|
test("[experimental] active run with repeated tool turns + queued message triggers checkpoint handoff", async () => {
|
|
@@ -1100,10 +1927,39 @@ describe("Conversation checkpoint handoff", () => {
|
|
|
1100
1927
|
);
|
|
1101
1928
|
await waitForPendingRun(1);
|
|
1102
1929
|
|
|
1103
|
-
// Enqueue messages B, C, D
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1930
|
+
// Enqueue messages B, C, D — each on a distinct userMessageInterface so the
|
|
1931
|
+
// batch builder stops at each boundary and we see one run per message.
|
|
1932
|
+
const meta = (iface: string) => ({
|
|
1933
|
+
userMessageInterface: iface,
|
|
1934
|
+
assistantMessageInterface: iface,
|
|
1935
|
+
});
|
|
1936
|
+
conversation.enqueueMessage(
|
|
1937
|
+
"msg-B",
|
|
1938
|
+
[],
|
|
1939
|
+
makeHandler("B"),
|
|
1940
|
+
"req-B",
|
|
1941
|
+
undefined,
|
|
1942
|
+
undefined,
|
|
1943
|
+
meta("macos"),
|
|
1944
|
+
);
|
|
1945
|
+
conversation.enqueueMessage(
|
|
1946
|
+
"msg-C",
|
|
1947
|
+
[],
|
|
1948
|
+
makeHandler("C"),
|
|
1949
|
+
"req-C",
|
|
1950
|
+
undefined,
|
|
1951
|
+
undefined,
|
|
1952
|
+
meta("cli"),
|
|
1953
|
+
);
|
|
1954
|
+
conversation.enqueueMessage(
|
|
1955
|
+
"msg-D",
|
|
1956
|
+
[],
|
|
1957
|
+
makeHandler("D"),
|
|
1958
|
+
"req-D",
|
|
1959
|
+
undefined,
|
|
1960
|
+
undefined,
|
|
1961
|
+
meta("vellum"),
|
|
1962
|
+
);
|
|
1107
1963
|
expect(conversation.getQueueDepth()).toBe(3);
|
|
1108
1964
|
|
|
1109
1965
|
// Handoff from A -> B
|