@vellumai/assistant 0.11.2 → 0.11.3-staging.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +58 -3
- package/docs/trusted-contact-access.md +9 -5
- package/docs/vellum-doctor.md +195 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +39 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/ingress.ts +10 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +6 -0
- package/node_modules/@vellumai/ces-client/src/index.ts +1 -0
- package/node_modules/@vellumai/ces-client/src/rpc-client.ts +19 -2
- package/node_modules/@vellumai/environments/package.json +2 -1
- package/node_modules/@vellumai/environments/src/__tests__/cloud-assistant-hub-url.test.ts +38 -0
- package/node_modules/@vellumai/environments/src/__tests__/install-layout.test.ts +98 -0
- package/node_modules/@vellumai/environments/src/__tests__/package-boundary.test.ts +5 -5
- package/node_modules/@vellumai/environments/src/index.ts +17 -6
- package/node_modules/@vellumai/environments/src/install-layout.ts +49 -0
- package/node_modules/@vellumai/environments/src/seeds.ts +29 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +39 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/ingress.ts +10 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +6 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +4 -0
- package/node_modules/@vellumai/gateway-client/src/verification-session-contract.ts +91 -6
- package/node_modules/@vellumai/ipc-server-utils/src/endpoint.test.ts +36 -0
- package/node_modules/@vellumai/ipc-server-utils/src/endpoint.ts +142 -0
- package/node_modules/@vellumai/ipc-server-utils/src/index.ts +2 -0
- package/node_modules/@vellumai/ipc-server-utils/src/listen-options.ts +3 -0
- package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.test.ts +15 -1
- package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.ts +17 -2
- package/node_modules/@vellumai/service-contracts/src/channels.ts +39 -0
- package/node_modules/@vellumai/service-contracts/src/ingress.ts +10 -0
- package/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +6 -0
- package/node_modules/@vellumai/slack-text/src/index.ts +16 -2
- package/openapi.yaml +589 -30
- package/package.json +1 -1
- package/src/__tests__/agent-image-optimize.test.ts +4 -4
- package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +113 -66
- package/src/__tests__/agent-loop-override-profile.test.ts +38 -48
- package/src/__tests__/agent-loop-subagent-billing-headers.test.ts +166 -0
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +1 -0
- package/src/__tests__/agent-wake-override-profile.test.ts +1 -0
- package/src/__tests__/answered-question-persistence.test.ts +240 -0
- package/src/__tests__/anthropic-provider.test.ts +165 -30
- package/src/__tests__/app-control-flow.test.ts +9 -8
- package/src/__tests__/assistant-event-hub.test.ts +48 -0
- package/src/__tests__/attachments-store-heic-normalize.test.ts +16 -12
- package/src/__tests__/attachments-store.test.ts +130 -68
- package/src/__tests__/attachments.test.ts +36 -36
- package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
- package/src/__tests__/call-start-guardian-guard.test.ts +18 -4
- package/src/__tests__/canned-reply-release.test.ts +86 -0
- package/src/__tests__/channel-guardian.test.ts +59 -29
- package/src/__tests__/channel-readiness-telegram-remote.test.ts +154 -0
- package/src/__tests__/channel-setup-panel-ack.test.ts +6 -5
- package/src/__tests__/client-os-metadata-persistence.test.ts +103 -4
- package/src/__tests__/compactor-image-manifest-trust.test.ts +1 -1
- package/src/__tests__/consult-deadline.test.ts +65 -0
- package/src/__tests__/conversation-agent-loop.test.ts +438 -2
- package/src/__tests__/conversation-disk-view-integration.test.ts +5 -1
- package/src/__tests__/conversation-disk-view.test.ts +4 -4
- package/src/__tests__/conversation-error.test.ts +22 -3
- package/src/__tests__/conversation-fork-crud.test.ts +5 -1
- package/src/__tests__/conversation-fork-referential.test.ts +352 -0
- package/src/__tests__/conversation-fork-retrospective.test.ts +5 -1
- package/src/__tests__/conversation-group-tools.test.ts +32 -2
- package/src/__tests__/conversation-inference-profile-route.test.ts +27 -1
- package/src/__tests__/conversation-initial-prompt.test.ts +51 -22
- package/src/__tests__/conversation-key-store-bootstrap-cleanup.test.ts +119 -0
- package/src/__tests__/conversation-key-store-origin.test.ts +105 -0
- package/src/__tests__/conversation-list-recency-ordering.test.ts +104 -0
- package/src/__tests__/conversation-origin-at-creation.test.ts +153 -0
- package/src/__tests__/conversation-origin-channel-filter.test.ts +95 -0
- package/src/__tests__/conversation-placement-promotion.test.ts +172 -0
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +123 -0
- package/src/__tests__/conversation-queue.test.ts +433 -1
- package/src/__tests__/conversation-retry-route.test.ts +3 -1
- package/src/__tests__/conversation-routes-hidden-queue.test.ts +64 -1
- package/src/__tests__/conversation-store-ephemeral.test.ts +4 -0
- package/src/__tests__/conversation-store.test.ts +26 -10
- package/src/__tests__/conversation-summarize-route.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +15 -6
- package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -5
- package/src/__tests__/conversation-surfaces-app-control.test.ts +12 -11
- package/src/__tests__/conversation-surfaces-app-open.test.ts +11 -5
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +5 -4
- package/src/__tests__/conversation-surfaces-history-restored-completion.test.ts +967 -0
- package/src/__tests__/conversation-surfaces-persisted-info.test.ts +312 -0
- package/src/__tests__/conversation-surfaces-queued-emit.test.ts +6 -5
- package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +12 -8
- package/src/__tests__/conversation-surfaces-standalone.test.ts +21 -11
- package/src/__tests__/conversation-surfaces-state-update.test.ts +7 -11
- package/src/__tests__/conversation-surfaces-table-action.test.ts +7 -6
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +11 -6
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +9 -6
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +70 -7
- package/src/__tests__/conversation-wait-for-idle.test.ts +6 -11
- package/src/__tests__/credential-prompt-route.test.ts +57 -7
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/cu-unified-flow.test.ts +89 -28
- package/src/__tests__/custom-group-listing-visibility.test.ts +1 -1
- package/src/__tests__/db-schedule-syntax-migration.test.ts +4 -1
- package/src/__tests__/discord-access-request-privacy.test.ts +366 -0
- package/src/__tests__/discord-requester-notice-privacy.test.ts +87 -0
- package/src/__tests__/disk-pressure-guard.test.ts +55 -55
- package/src/__tests__/disk-pressure-routes.test.ts +10 -10
- package/src/__tests__/disk-pressure-tools.test.ts +2 -2
- package/src/__tests__/disk-usage.test.ts +35 -20
- package/src/__tests__/document-sync-tags.test.ts +379 -0
- package/src/__tests__/document-update-default-surface.test.ts +28 -0
- package/src/__tests__/dynamic-skill-background-guard.test.ts +2 -2
- package/src/__tests__/emit-signal-routing-intent.test.ts +3 -4
- package/src/__tests__/ensure-conversation-exists.test.ts +8 -5
- package/src/__tests__/events-dev-bypass-actor.test.ts +183 -1
- package/src/__tests__/evict-conversations-for-reload.test.ts +90 -0
- package/src/__tests__/file-list-tool.test.ts +12 -10
- package/src/__tests__/file-ops-service.test.ts +151 -44
- package/src/__tests__/file-read-container-boundary.test.ts +137 -0
- package/src/__tests__/file-read-tool.test.ts +21 -9
- package/src/__tests__/file-url-path-guard.test.ts +83 -0
- package/src/__tests__/filesystem-tools.test.ts +82 -66
- package/src/__tests__/guardian-outbound-http.test.ts +8 -8
- package/src/__tests__/helpers/gateway-verification-sessions-stub.ts +14 -1
- package/src/__tests__/helpers/hub-clients.ts +42 -0
- package/src/__tests__/helpers/mock-conversation.ts +46 -0
- package/src/__tests__/helpers/verification-sessions-ipc-sim.ts +61 -57
- package/src/__tests__/history-repair.test.ts +112 -12
- package/src/__tests__/host-bash-proxy.test.ts +10 -5
- package/src/__tests__/host-bash-routes.test.ts +67 -0
- package/src/__tests__/host-browser-proxy.test.ts +2 -2
- package/src/__tests__/host-cu-proxy.test.ts +3 -7
- package/src/__tests__/host-file-proxy.test.ts +3 -7
- package/src/__tests__/host-file-read-tool.test.ts +5 -1
- package/src/__tests__/host-transfer-proxy-targeted.test.ts +6 -4
- package/src/__tests__/http-conversation-lineage.test.ts +2 -5
- package/src/__tests__/http-user-message-parity.test.ts +44 -0
- package/src/__tests__/identity-routes.test.ts +13 -13
- package/src/__tests__/image-conversion.test.ts +29 -26
- package/src/__tests__/image-source-path-reinject.test.ts +2 -2
- package/src/__tests__/inference-profile-session-handler.test.ts +95 -0
- package/src/__tests__/inference-profile-session-ipc.test.ts +25 -0
- package/src/__tests__/introduction-card-resolver.test.ts +6 -1
- package/src/__tests__/list-messages-attachments.test.ts +56 -32
- package/src/__tests__/list-messages-background-tool-completion.test.ts +4 -4
- package/src/__tests__/list-messages-client-message-id.test.ts +2 -2
- package/src/__tests__/list-messages-hidden-metadata.test.ts +16 -16
- package/src/__tests__/list-messages-page-latest.test.ts +61 -61
- package/src/__tests__/list-messages-provider-error.test.ts +6 -6
- package/src/__tests__/list-messages-queued.test.ts +49 -6
- package/src/__tests__/list-messages-system-card.test.ts +4 -4
- package/src/__tests__/list-messages-tool-merge.test.ts +9 -9
- package/src/__tests__/llm-resolver-override-or-default.test.ts +141 -0
- package/src/__tests__/llm-usage-store.test.ts +55 -0
- package/src/__tests__/manual-token-reconciliation.test.ts +45 -0
- package/src/__tests__/mcp-tool-annotations-risk.test.ts +133 -0
- package/src/__tests__/media-stream-server-integration.test.ts +36 -0
- package/src/__tests__/migration-export-http.test.ts +1 -1
- package/src/__tests__/migration-import-commit-http.test.ts +1 -1
- package/src/__tests__/migration-import-preflight-http.test.ts +1 -1
- package/src/__tests__/migration-validate-http.test.ts +1 -1
- package/src/__tests__/mtime-cache.test.ts +65 -3
- package/src/__tests__/notification-platform-adapter.test.ts +104 -0
- package/src/__tests__/notification-schedule-notify-dedup.test.ts +1 -1
- package/src/__tests__/oauth-provider-profiles.test.ts +4 -2
- package/src/__tests__/oauth-provider-seed-logos.test.ts +65 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +17 -0
- package/src/__tests__/oauth-providers-routes.test.ts +1 -0
- package/src/__tests__/openai-responses-prompt-cache.test.ts +5 -2
- package/src/__tests__/path-policy.test.ts +164 -1
- package/src/__tests__/persist-media-references.test.ts +2 -2
- package/src/__tests__/persist-unsendable-image-downscale.test.ts +5 -5
- package/src/__tests__/persist-unsendable-image.test.ts +23 -23
- package/src/__tests__/persona-resolver.test.ts +154 -0
- package/src/__tests__/plugin-api-webhook-url.test.ts +170 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +6 -0
- package/src/__tests__/processing-flag-persist-failure.test.ts +15 -10
- package/src/__tests__/profile-availability-incomplete.test.ts +113 -0
- package/src/__tests__/profiler-routes.test.ts +13 -13
- package/src/__tests__/prompt-cache-cross-turn-stability.test.ts +593 -0
- package/src/__tests__/proxy-approval-callback.test.ts +3 -3
- package/src/__tests__/queued-message-cancel-and-steer.test.ts +189 -0
- package/src/__tests__/queued-message-steer-actor-scoping.test.ts +164 -0
- package/src/__tests__/resolve-trust-class.test.ts +20 -0
- package/src/__tests__/run-conversation-turn-persistence.test.ts +1 -1
- package/src/__tests__/run-due-schedules.test.ts +174 -0
- package/src/__tests__/runtime-attachment-metadata.test.ts +15 -7
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +166 -0
- package/src/__tests__/schedule-routes.test.ts +730 -8
- package/src/__tests__/schedule-store.test.ts +540 -0
- package/src/__tests__/schedule-tools.test.ts +103 -0
- package/src/__tests__/scheduler-wake.test.ts +5 -0
- package/src/__tests__/secret-routes-platform-proxy.test.ts +29 -1
- package/src/__tests__/secure-keys.test.ts +91 -0
- package/src/__tests__/server-history-render.test.ts +97 -7
- package/src/__tests__/size-guard.test.ts +8 -8
- package/src/__tests__/skill-docs-sync-guard.test.ts +3 -2
- package/src/__tests__/skill-tool-factory.test.ts +247 -0
- package/src/__tests__/skills-install-staging.test.ts +15 -6
- package/src/__tests__/slack-edit-ordering-characterization.test.ts +103 -0
- package/src/__tests__/slack-inbound-verification.test.ts +4 -1
- package/src/__tests__/slack-mention-binding-invariance.test.ts +103 -0
- package/src/__tests__/slack-mention-provider-leak-audit.test.ts +135 -0
- package/src/__tests__/starter-task-flow.test.ts +5 -4
- package/src/__tests__/steer-on-enqueue-question.test.ts +3 -2
- package/src/__tests__/stt-language-catalog-parity.test.ts +38 -0
- package/src/__tests__/subagent-allowlist-validation.test.ts +26 -4
- package/src/__tests__/subagent-cron-run-attribution.test.ts +301 -0
- package/src/__tests__/subagent-disposal.test.ts +10 -0
- package/src/__tests__/subagent-fork-notifications.test.ts +10 -0
- package/src/__tests__/subagent-fork-prompt-role.test.ts +85 -11
- package/src/__tests__/subagent-fork-spawn.test.ts +15 -7
- package/src/__tests__/subagent-manager-notify.test.ts +148 -0
- package/src/__tests__/subagent-notify-parent.test.ts +1 -1
- package/src/__tests__/subagent-persistence.test.ts +141 -0
- package/src/__tests__/subagent-role-registry.test.ts +172 -63
- package/src/__tests__/subagent-role-resolution.test.ts +88 -0
- package/src/__tests__/subagent-spawn-and-await.test.ts +48 -0
- package/src/__tests__/subagent-spawn-tool-fork.test.ts +111 -10
- package/src/__tests__/subagent-terminal-message.test.ts +88 -1
- package/src/__tests__/subagent-tool-gate-mode.test.ts +225 -10
- package/src/__tests__/subagent-tools.test.ts +1901 -108
- package/src/__tests__/surface-completion-compaction-boundary.test.ts +124 -0
- package/src/__tests__/surface-completion-in-flight-snapshot.test.ts +298 -0
- package/src/__tests__/system-prompt.test.ts +82 -7
- package/src/__tests__/tool-grant-request-escalation.test.ts +40 -1
- package/src/__tests__/tool-result-spool.test.ts +74 -18
- package/src/__tests__/tool-side-effects-documents.test.ts +174 -0
- package/src/__tests__/tool-side-effects-hook-failures.test.ts +129 -0
- package/src/__tests__/tools-audio-read.test.ts +10 -10
- package/src/__tests__/tools-get-route.test.ts +85 -12
- package/src/__tests__/tools-list-cli.test.ts +171 -0
- package/src/__tests__/top-level-renderer.test.ts +54 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +68 -1
- package/src/__tests__/trusted-contact-multichannel.test.ts +4 -1
- package/src/__tests__/turn-tail-chain.test.ts +135 -0
- package/src/__tests__/ui-choice-copy-surfaces.test.ts +6 -4
- package/src/__tests__/ui-visual-surface.test.ts +5 -4
- package/src/__tests__/ui-work-result-surface.test.ts +5 -4
- package/src/__tests__/unified-turn-context-visible-app.test.ts +7 -8
- package/src/__tests__/usage-routes.test.ts +62 -0
- package/src/__tests__/verification-outbound-delivery.test.ts +176 -0
- package/src/__tests__/visible-app-context.test.ts +20 -4
- package/src/__tests__/web-search-history.test.ts +96 -0
- package/src/__tests__/workspace-migration-139-clear-renamed-cost-profile-label.test.ts +6 -6
- package/src/__tests__/workspace-migration-140-repair-seed-pinned-memory-v3-live.test.ts +330 -0
- package/src/acp/failure-error.ts +3 -5
- package/src/agent/attachments.ts +26 -24
- package/src/agent/history-repair/history-repair.ts +27 -26
- package/src/agent/image-optimize.ts +6 -6
- package/src/agent/loop.ts +40 -22
- package/src/agent/message-types.ts +3 -3
- package/src/api/constants/document-tools.ts +55 -0
- package/src/api/events/question-answered.ts +53 -0
- package/src/api/events/tool-result.ts +7 -0
- package/src/api/index.ts +13 -0
- package/src/api/responses/conversation-message.ts +11 -0
- package/src/api/surfaces-oauth-connect.test.ts +96 -0
- package/src/api/surfaces.ts +36 -8
- package/src/approvals/guardian-channel-delivery.ts +83 -0
- package/src/approvals/guardian-expiry-notifier.ts +13 -11
- package/src/approvals/guardian-request-resolvers.ts +222 -135
- package/src/calls/__tests__/voice-session-bridge.test.ts +50 -0
- package/src/calls/media-stream-server.ts +9 -1
- package/src/calls/voice-session-bridge.ts +38 -0
- package/src/channels/__tests__/gateway-verification-sessions.test.ts +3 -3
- package/src/channels/config.ts +7 -2
- package/src/channels/gateway-verification-sessions.ts +7 -2
- package/src/channels/types.ts +7 -1
- package/src/cli/commands/__tests__/cache.test.ts +7 -4
- package/src/cli/commands/__tests__/cli-test-harness.ts +82 -0
- package/src/cli/commands/__tests__/conversations-wake.test.ts +51 -6
- package/src/cli/commands/__tests__/inference-models.test.ts +14 -0
- package/src/cli/commands/__tests__/inference-profiles.test.ts +44 -1
- package/src/cli/commands/__tests__/inference-providers.test.ts +7 -0
- package/src/cli/commands/__tests__/plugins.test.ts +692 -0
- package/src/cli/commands/__tests__/schedules.test.ts +136 -37
- package/src/cli/commands/conversations.ts +7 -3
- package/src/cli/commands/credentials.help.ts +7 -0
- package/src/cli/commands/inference-profiles.ts +9 -3
- package/src/cli/commands/inference.help.ts +16 -2
- package/src/cli/commands/mcp.help.ts +4 -2
- package/src/cli/commands/memory/memory-retrospective.ts +18 -0
- package/src/cli/commands/notifications.ts +3 -6
- package/src/cli/commands/oauth/connect-surface-guidance.test.ts +16 -0
- package/src/cli/commands/oauth/connect-surface-guidance.ts +25 -7
- package/src/cli/commands/oauth/connect.test.ts +26 -1
- package/src/cli/commands/oauth/connect.ts +4 -1
- package/src/cli/commands/oauth/index.help.ts +3 -1
- package/src/cli/commands/oauth/providers.ts +3 -0
- package/src/cli/commands/plugins.help.ts +25 -2
- package/src/cli/commands/plugins.ts +289 -10
- package/src/cli/commands/schedules.help.ts +14 -12
- package/src/cli/commands/schedules.ts +19 -5
- package/src/cli/commands/tools.help.ts +4 -2
- package/src/cli/commands/tools.ts +38 -0
- package/src/cli/lib/__tests__/inspect-plugin.test.ts +1 -0
- package/src/cli/lib/__tests__/install-from-github.test.ts +79 -0
- package/src/cli/lib/__tests__/install-from-platform.test.ts +47 -1
- package/src/cli/lib/__tests__/plugin-surfaces.test.ts +86 -7
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +122 -0
- package/src/cli/lib/install-from-github.ts +61 -0
- package/src/cli/lib/install-from-platform.ts +13 -2
- package/src/cli/lib/plugin-surfaces.ts +124 -4
- package/src/cli/lib/upgrade-plugin.ts +18 -0
- package/src/cli.ts +1 -1
- package/src/config/__tests__/deployment-context-defaults.test.ts +73 -0
- package/src/config/__tests__/memory-retrospective-schema.test.ts +40 -0
- package/src/config/__tests__/plugin-updates-schema.test.ts +43 -0
- package/src/config/__tests__/profile-tool-support.test.ts +131 -0
- package/src/config/bundled-skills/AGENTS.md +1 -1
- package/src/config/bundled-skills/app-builder/SKILL.md +1 -1
- package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +3 -3
- package/src/config/bundled-skills/schedule/SKILL.md +5 -1
- package/src/config/bundled-skills/schedule/TOOLS.json +2 -2
- package/src/config/bundled-skills/settings/TOOLS.json +2 -2
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +1 -0
- package/src/config/bundled-skills/subagent/SKILL.md +53 -22
- package/src/config/bundled-skills/subagent/TOOLS.json +14 -13
- package/src/config/bundled-skills/visualize/SKILL.md +1 -1
- package/src/config/default-profile-catalog.ts +6 -2
- package/src/config/feature-flag-registry.json +25 -17
- package/src/config/llm-resolver.ts +45 -15
- package/src/config/loader.ts +70 -1
- package/src/config/profile-tool-support.ts +110 -0
- package/src/config/sanitize-for-transfer.ts +11 -0
- package/src/config/schema.ts +4 -0
- package/src/config/schemas/__tests__/stt.test.ts +20 -5
- package/src/config/schemas/channels.ts +13 -0
- package/src/config/schemas/memory-retrospective.ts +25 -0
- package/src/config/schemas/plugin-updates.ts +55 -0
- package/src/config/schemas/services.ts +8 -4
- package/src/config/schemas/stt.ts +17 -5
- package/src/config/schemas/timeouts.ts +1 -1
- package/src/contacts/__tests__/guardian-delivery-reader.test.ts +16 -0
- package/src/contacts/guardian-delivery-reader.ts +29 -2
- package/src/context/compactor.ts +4 -4
- package/src/context/post-turn-tool-result-truncation.ts +32 -16
- package/src/context/strip-injections.ts +42 -0
- package/src/context/tool-result-spool.ts +32 -18
- package/src/credential-execution/ces-runtime.ts +3 -0
- package/src/credential-execution/client.ts +22 -1
- package/src/credential-execution/process-manager.test.ts +99 -0
- package/src/credential-execution/process-manager.ts +62 -16
- package/src/daemon/__tests__/abort-null-controller.test.ts +141 -5
- package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +8 -12
- package/src/daemon/__tests__/install-assistant-command.test.ts +234 -0
- package/src/daemon/__tests__/turn-tail-assistant-reply-notify.test.ts +2 -4
- package/src/daemon/__tests__/turn-tail-deleted-conversation.test.ts +20 -29
- package/src/daemon/channel-ui-capability.ts +4 -4
- package/src/daemon/conversation-agent-loop-handlers.ts +117 -61
- package/src/daemon/conversation-agent-loop.ts +318 -148
- package/src/daemon/conversation-attachments.ts +1 -1
- package/src/daemon/conversation-error.ts +8 -1
- package/src/daemon/conversation-initial-prompt.ts +16 -38
- package/src/daemon/conversation-lifecycle.ts +154 -32
- package/src/daemon/conversation-messaging.ts +106 -43
- package/src/daemon/conversation-process.ts +131 -33
- package/src/daemon/conversation-queue-manager.ts +10 -0
- package/src/daemon/conversation-runtime-assembly.ts +9 -4
- package/src/daemon/conversation-store.ts +52 -3
- package/src/daemon/conversation-surface-state.ts +9 -0
- package/src/daemon/conversation-surfaces.ts +403 -210
- package/src/daemon/conversation-tool-setup.ts +56 -7
- package/src/daemon/conversation-turn-finalize.ts +134 -89
- package/src/daemon/conversation.ts +111 -27
- package/src/daemon/disk-pressure-guard-lifecycle.ts +2 -2
- package/src/daemon/disk-pressure-guard.ts +3 -3
- package/src/daemon/doordash-steps.ts +5 -5
- package/src/daemon/external-plugins-bootstrap.ts +7 -1
- package/src/daemon/handlers/config-channels.ts +48 -5
- package/src/daemon/handlers/conversation-history.ts +1 -1
- package/src/daemon/handlers/conversations.ts +62 -17
- package/src/daemon/handlers/shared.ts +12 -0
- package/src/daemon/handlers/skills.ts +11 -1
- package/src/daemon/identity-helpers.ts +38 -10
- package/src/daemon/install-assistant-command.ts +322 -0
- package/src/daemon/lifecycle.ts +14 -3
- package/src/daemon/message-types/conversations.ts +6 -7
- package/src/daemon/message-types/sync.ts +1 -0
- package/src/daemon/persist-media-references.ts +23 -14
- package/src/daemon/process-message.ts +6 -6
- package/src/daemon/tool-setup-types.ts +19 -122
- package/src/daemon/tool-side-effects.ts +17 -94
- package/src/daemon/trust-context-types.ts +39 -0
- package/src/daemon/trust-context.ts +14 -6
- package/src/daemon/turn-tail-chain.ts +73 -0
- package/src/daemon/web-search-history.ts +47 -33
- package/src/heartbeat/heartbeat-service.ts +6 -6
- package/src/ipc/__tests__/socket-path.test.ts +21 -0
- package/src/ipc/assistant-server.ts +17 -10
- package/src/ipc/cli-client.ts +10 -1
- package/src/ipc/routes/__tests__/documents-sync-ipc-routes.test.ts +56 -0
- package/src/ipc/routes/documents-sync-ipc-routes.ts +42 -0
- package/src/ipc/socket-cleanup.ts +9 -7
- package/src/ipc/socket-path.ts +4 -96
- package/src/live-voice/__tests__/live-voice-archive.test.ts +16 -16
- package/src/live-voice/__tests__/live-voice-attach-image.test.ts +209 -0
- package/src/live-voice/__tests__/live-voice-connection.test.ts +20 -0
- package/src/live-voice/__tests__/live-voice-integration.test.ts +7 -5
- package/src/live-voice/__tests__/live-voice-session-telemetry.test.ts +265 -0
- package/src/live-voice/__tests__/live-voice-stt.test.ts +6 -1
- package/src/live-voice/__tests__/live-voice-vad.test.ts +35 -15
- package/src/live-voice/__tests__/protocol.test.ts +52 -0
- package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +3 -0
- package/src/live-voice/live-voice-archive.ts +13 -13
- package/src/live-voice/live-voice-connection.ts +7 -1
- package/src/live-voice/live-voice-photo.ts +169 -0
- package/src/live-voice/live-voice-session.ts +108 -4
- package/src/live-voice/protocol.ts +89 -1
- package/src/mcp/client.ts +10 -0
- package/src/messaging/providers/__tests__/callback-routing.test.ts +5 -1
- package/src/messaging/providers/__tests__/transport-dispatch.test.ts +66 -4
- package/src/messaging/providers/callback-routing.ts +1 -0
- package/src/messaging/providers/discord/api.ts +312 -0
- package/src/messaging/providers/discord/dm-delivery.test.ts +204 -0
- package/src/messaging/providers/discord/render.test.ts +204 -0
- package/src/messaging/providers/discord/render.ts +195 -0
- package/src/messaging/providers/discord/send.test.ts +179 -0
- package/src/messaging/providers/discord/send.ts +212 -0
- package/src/messaging/providers/discord/transport.ts +81 -0
- package/src/messaging/providers/index.ts +3 -1
- package/src/messaging/providers/retry-policy.test.ts +247 -0
- package/src/messaging/providers/retry-policy.ts +206 -0
- package/src/messaging/providers/slack/mention-source.test.ts +285 -0
- package/src/messaging/providers/slack/mention-source.ts +287 -0
- package/src/messaging/providers/telegram-bot/api.ts +47 -130
- package/src/messaging/providers/whatsapp/api.ts +47 -136
- package/src/monitoring/__tests__/db-integrity-sample.test.ts +4 -1
- package/src/monitoring/__tests__/plugin-auto-update.test.ts +332 -0
- package/src/monitoring/db-integrity-sample.ts +6 -1
- package/src/monitoring/plugin-auto-update.ts +359 -0
- package/src/monitoring/resource-sampler.ts +21 -5
- package/src/monitoring/worker.ts +13 -0
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +375 -2
- package/src/notifications/__tests__/broadcaster.test.ts +37 -1
- package/src/notifications/__tests__/copy-composer.test.ts +165 -0
- package/src/notifications/__tests__/emit-signal-pipeline-failure.test.ts +205 -0
- package/src/notifications/__tests__/notification-utils.test.ts +249 -0
- package/src/notifications/adapters/platform.ts +31 -2
- package/src/notifications/assistant-reply-producer.ts +79 -32
- package/src/notifications/broadcaster.ts +30 -10
- package/src/notifications/copy-composer.ts +69 -0
- package/src/notifications/emit-signal.ts +82 -15
- package/src/notifications/events-store.ts +10 -2
- package/src/notifications/home-feed-side-effect.ts +6 -3
- package/src/notifications/notification-utils.ts +173 -5
- package/src/notifications/signal.ts +12 -0
- package/src/oauth/AGENTS.md +4 -2
- package/src/oauth/byo-connection.ts +6 -1
- package/src/oauth/credential-token-resolver.ts +3 -10
- package/src/oauth/manual-token-connection.ts +29 -64
- package/src/oauth/manual-token-providers.ts +62 -0
- package/src/oauth/provider-serializer.ts +13 -0
- package/src/oauth/seed-providers.ts +62 -11
- package/src/onboarding/onboarding-events-store.ts +91 -1
- package/src/permissions/question-prompter.test.ts +4 -0
- package/src/permissions/question-prompter.ts +21 -4
- package/src/persistence/__tests__/conversation-lineage.test.ts +248 -0
- package/src/persistence/attachments-store.ts +15 -15
- package/src/persistence/conversation-attention-store.ts +30 -10
- package/src/persistence/conversation-bootstrap.ts +17 -0
- package/src/persistence/conversation-crud.ts +363 -97
- package/src/persistence/conversation-group-migration.ts +1 -1
- package/src/persistence/conversation-key-store.ts +38 -10
- package/src/persistence/conversation-lineage.ts +226 -0
- package/src/persistence/conversation-plugin-facade.ts +28 -3
- package/src/persistence/conversation-queries.ts +207 -33
- package/src/persistence/conversation-types.ts +107 -1
- package/src/persistence/jobs-store.ts +32 -8
- package/src/persistence/llm-usage-store.ts +22 -0
- package/src/persistence/migrations/361-normalize-managed-connection-rows.test.ts +168 -0
- package/src/persistence/migrations/361-normalize-managed-connection-rows.ts +76 -0
- package/src/persistence/migrations/362-add-conversation-subagent-kind.test.ts +187 -0
- package/src/persistence/migrations/362-add-conversation-subagent-kind.ts +93 -0
- package/src/persistence/migrations/363-backfill-schedule-inference-profile.test.ts +229 -0
- package/src/persistence/migrations/363-backfill-schedule-inference-profile.ts +127 -0
- package/src/persistence/migrations/364-add-schedule-source-key.test.ts +120 -0
- package/src/persistence/migrations/364-add-schedule-source-key.ts +43 -0
- package/src/persistence/migrations/365-add-conversation-fork-strategy.ts +27 -0
- package/src/persistence/schema/conversations.ts +32 -0
- package/src/persistence/schema/infrastructure.ts +3 -0
- package/src/persistence/steps.ts +30 -0
- package/src/persistence/subagent-store.ts +148 -6
- package/src/platform/managed-speech.test.ts +20 -0
- package/src/platform/managed-speech.ts +9 -0
- package/src/plugin-api/index.ts +8 -0
- package/src/plugin-api/webhook-url.ts +151 -0
- package/src/plugins/__tests__/installed-plugin-dirs.test.ts +82 -0
- package/src/plugins/__tests__/source-fingerprint.test.ts +5 -1
- package/src/plugins/collect-source-versions.ts +4 -27
- package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +5 -5
- package/src/plugins/defaults/exploration-drift/package.json +1 -1
- package/src/plugins/defaults/image-recovery/hooks/post-model-call.ts +4 -2
- package/src/plugins/defaults/image-recovery/recover.ts +67 -57
- package/src/plugins/defaults/index.ts +1 -1
- package/src/plugins/defaults/memory/__tests__/conversation-queries.test.ts +64 -18
- package/src/plugins/defaults/memory/__tests__/jobs-worker-outcome-contract.test.ts +242 -0
- package/src/plugins/defaults/memory/__tests__/jobs-worker-retrospective-sweep.test.ts +24 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +48 -3
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +511 -19
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +39 -3
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +493 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-wake-chain.test.ts +5 -4
- package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +5 -1
- package/src/plugins/defaults/memory/job-handlers.ts +87 -5
- package/src/plugins/defaults/memory/jobs-worker.ts +60 -6
- package/src/plugins/defaults/memory/memory-retrospective-constants.ts +11 -0
- package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +29 -0
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +342 -60
- package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +23 -3
- package/src/plugins/defaults/memory/substrate/consolidation-lock.ts +9 -4
- package/src/plugins/defaults/memory/substrate/sweep-job.ts +7 -0
- package/src/plugins/defaults/memory/v1/graph/injection.ts +1 -1
- package/src/plugins/defaults/memory/v2/router.ts +8 -0
- package/src/plugins/defaults/turn-context/unified-turn-context.ts +1 -2
- package/src/plugins/external-plugin-loader.ts +17 -8
- package/src/plugins/installed-plugin-dirs.ts +101 -0
- package/src/plugins/mtime-cache.ts +34 -22
- package/src/plugins/types.ts +69 -8
- package/src/prompts/persona-resolver.ts +6 -2
- package/src/prompts/system-prompt.ts +72 -17
- package/src/providers/__tests__/inference.test.ts +40 -0
- package/src/providers/__tests__/preflight-resolved-config.test.ts +58 -1
- package/src/providers/__tests__/retry-callsite.test.ts +67 -0
- package/src/providers/__tests__/vellum-connection-routing.test.ts +42 -11
- package/src/providers/anthropic/client.ts +112 -95
- package/src/providers/connection-resolution.ts +35 -1
- package/src/providers/gemini/client.ts +5 -5
- package/src/providers/inference/adapter-factory.ts +16 -10
- package/src/providers/inference/auth.ts +16 -0
- package/src/providers/inference/connection-availability.ts +164 -12
- package/src/providers/inference/connections.ts +25 -1
- package/src/providers/media-resolve.ts +21 -11
- package/src/providers/model-catalog.ts +15 -0
- package/src/providers/openai/chat-completions-provider.ts +4 -4
- package/src/providers/openai/responses-provider.ts +12 -25
- package/src/providers/registry.ts +9 -4
- package/src/providers/retry.ts +25 -0
- package/src/providers/server-tool-pairing.ts +134 -0
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +187 -6
- package/src/providers/speech-to-text/deepgram.ts +1 -0
- package/src/providers/speech-to-text/resolve.ts +80 -10
- package/src/providers/speech-to-text/vellum-managed.ts +7 -1
- package/src/providers/speech-to-text/vellum-speech-relay-connection.ts +3 -1
- package/src/providers/types.ts +26 -6
- package/src/providers/vellum-model-routing.ts +6 -7
- package/src/runtime/AGENTS.md +2 -0
- package/src/runtime/__tests__/agent-wake.test.ts +16 -16
- package/src/runtime/__tests__/background-job-runner.test.ts +0 -144
- package/src/runtime/__tests__/desktop-presence.test.ts +162 -0
- package/src/runtime/agent-wake.ts +63 -24
- package/src/runtime/assistant-event-hub.ts +87 -12
- package/src/runtime/auth/__tests__/guard-tests.test.ts +2 -0
- package/src/runtime/auth/__tests__/scopes.test.ts +20 -0
- package/src/runtime/auth/same-actor.ts +56 -8
- package/src/runtime/auth/scopes.ts +12 -0
- package/src/runtime/auth/types.ts +2 -0
- package/src/runtime/background-job-runner.ts +5 -27
- package/src/runtime/channel-readiness-service.ts +59 -3
- package/src/runtime/channel-readiness-types.ts +18 -0
- package/src/runtime/desktop-presence.ts +69 -0
- package/src/runtime/http-server.ts +12 -0
- package/src/runtime/local-actor-identity.ts +11 -9
- package/src/runtime/migrations/__tests__/vbundle-symlink-streaming-importer.test.ts +0 -0
- package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/client-routes.test.ts +186 -14
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +547 -1
- package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +162 -7
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +14 -10
- package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +340 -3
- package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +28 -1
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +173 -1
- package/src/runtime/routes/__tests__/question-routes.test.ts +64 -0
- package/src/runtime/routes/__tests__/retrospective-routes.test.ts +20 -0
- package/src/runtime/routes/__tests__/skill-history-route.test.ts +116 -0
- package/src/runtime/routes/__tests__/wake-conversation-routes.test.ts +60 -2
- package/src/runtime/routes/archive-utils.ts +16 -12
- package/src/runtime/routes/attachment-routes.ts +13 -6
- package/src/runtime/routes/canned-reply-release.ts +54 -0
- package/src/runtime/routes/channel-verification-routes.ts +2 -27
- package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +5 -0
- package/src/runtime/routes/client-routes.ts +114 -2
- package/src/runtime/routes/conversation-cli-routes.ts +10 -12
- package/src/runtime/routes/conversation-list-routes.ts +59 -10
- package/src/runtime/routes/conversation-management-routes.ts +47 -18
- package/src/runtime/routes/conversation-query-routes.ts +32 -53
- package/src/runtime/routes/conversation-routes.ts +360 -293
- package/src/runtime/routes/credential-prompt-routes.ts +23 -2
- package/src/runtime/routes/debug-routes.ts +4 -1
- package/src/runtime/routes/default-provider-routes.ts +7 -11
- package/src/runtime/routes/documents-routes.ts +44 -1
- package/src/runtime/routes/events-routes.ts +28 -1
- package/src/runtime/routes/host-app-control-routes.ts +2 -0
- package/src/runtime/routes/host-bash-routes.ts +2 -0
- package/src/runtime/routes/host-browser-routes.ts +2 -0
- package/src/runtime/routes/host-cu-routes.ts +2 -0
- package/src/runtime/routes/host-file-routes.ts +2 -0
- package/src/runtime/routes/host-transfer-routes.ts +4 -0
- package/src/runtime/routes/identity-routes.ts +6 -6
- package/src/runtime/routes/inbound-message-handler.ts +5 -5
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +7 -3
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +14 -0
- package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +2 -2
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +14 -11
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +14 -17
- package/src/runtime/routes/inference-profile-availability-guard.ts +171 -0
- package/src/runtime/routes/inference-profile-session-handler.ts +26 -2
- package/src/runtime/routes/inference-profile-session-routes.ts +2 -1
- package/src/runtime/routes/inference-profiles-routes.ts +152 -73
- package/src/runtime/routes/inference-provider-connection-routes.ts +0 -16
- package/src/runtime/routes/llm-call-sites-routes.ts +13 -0
- package/src/runtime/routes/log-export-routes.ts +1 -1
- package/src/runtime/routes/notification-routes.ts +0 -14
- package/src/runtime/routes/oauth-providers.ts +1 -0
- package/src/runtime/routes/plugins-routes.ts +106 -1
- package/src/runtime/routes/profiler-routes.ts +4 -2
- package/src/runtime/routes/question-routes.ts +28 -10
- package/src/runtime/routes/retrospective-routes.ts +9 -3
- package/src/runtime/routes/schedule-routes.ts +210 -12
- package/src/runtime/routes/secret-routes.ts +40 -23
- package/src/runtime/routes/settings-routes.ts +41 -13
- package/src/runtime/routes/skills-routes.ts +79 -0
- package/src/runtime/routes/ui-snapshot-routes.ts +1 -0
- package/src/runtime/routes/wake-conversation-routes.ts +23 -0
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +1 -0
- package/src/runtime/services/conversation-serializer.ts +24 -9
- package/src/runtime/sync/documents-sidecar-publish.test.ts +101 -0
- package/src/runtime/sync/resource-sync-events.ts +79 -1
- package/src/runtime/sync/worker-daemon-notify.test.ts +34 -0
- package/src/runtime/sync/worker-daemon-notify.ts +44 -6
- package/src/runtime/verification-outbound-actions.ts +250 -222
- package/src/runtime/verification-templates.ts +41 -30
- package/src/schedule/__tests__/plugin-schedule-declarations.test.ts +614 -0
- package/src/schedule/__tests__/plugin-schedule-reconciler.test.ts +1066 -0
- package/src/schedule/inference-profile.ts +64 -3
- package/src/schedule/plugin-schedule-declarations.ts +518 -0
- package/src/schedule/plugin-schedule-reconciler.ts +605 -0
- package/src/schedule/plugin-schedules-gate.ts +23 -0
- package/src/schedule/schedule-store.ts +520 -26
- package/src/schedule/scheduler.ts +38 -0
- package/src/security/credential-backend.ts +51 -4
- package/src/security/encrypted-store.ts +64 -42
- package/src/security/secure-keys.ts +55 -7
- package/src/skills/catalog-install.ts +20 -7
- package/src/skills/inline-command-runner.ts +2 -7
- package/src/skills/skill-history.test.ts +269 -0
- package/src/skills/skill-history.ts +250 -0
- package/src/skills/skillssh-registry.ts +1 -1
- package/src/stt/daemon-batch-transcriber.ts +8 -6
- package/src/subagent/__tests__/consult-prompt.test.ts +25 -0
- package/src/subagent/consult-prompt.ts +3 -1
- package/src/subagent/consult-transcript.ts +1 -1
- package/src/subagent/manager.ts +304 -44
- package/src/subagent/progress-events.ts +35 -0
- package/src/subagent/role-resolution.ts +88 -0
- package/src/subagent/types.ts +302 -67
- package/src/subagent/validate-allowlists.ts +3 -4
- package/src/telegram/__tests__/webhook-health.test.ts +73 -1
- package/src/telegram/webhook-health.ts +101 -3
- package/src/telemetry/__tests__/telemetry-event-fixtures.ts +2 -0
- package/src/telemetry/activation-funnel.ts +6 -1
- package/src/telemetry/live-voice-funnel.ts +75 -0
- package/src/telemetry/telemetry-event-sources.ts +6 -0
- package/src/telemetry/telemetry-wire-source.json +1 -1
- package/src/telemetry/telemetry-wire.generated.ts +2 -0
- package/src/telemetry/types.ts +23 -4
- package/src/telemetry/usage-telemetry-reporter.test.ts +4 -0
- package/src/tools/__tests__/subagent-workflow-tool-input-schemas.test.ts +1 -1
- package/src/tools/ask-question/ask-question-tool.test.ts +118 -3
- package/src/tools/ask-question/ask-question-tool.ts +56 -2
- package/src/tools/browser/browser-execution.ts +1 -1
- package/src/tools/calls/call-start.ts +8 -5
- package/src/tools/conversation-groups/move_to_group.ts +10 -12
- package/src/tools/document/document-tool.ts +11 -4
- package/src/tools/execution-timeout.ts +9 -2
- package/src/tools/executor.ts +7 -6
- package/src/tools/filesystem/edit.ts +1 -1
- package/src/tools/filesystem/list.ts +1 -1
- package/src/tools/filesystem/read.ts +7 -13
- package/src/tools/filesystem/search.ts +23 -21
- package/src/tools/filesystem/write.ts +4 -1
- package/src/tools/host-filesystem/edit.ts +7 -2
- package/src/tools/host-filesystem/read.ts +21 -8
- package/src/tools/host-filesystem/write.ts +10 -2
- package/src/tools/host-terminal/host-shell.ts +2 -2
- package/src/tools/mcp/mcp-tool-factory.ts +25 -1
- package/src/tools/permission-checker.ts +8 -3
- package/src/tools/schedule/delete.ts +13 -1
- package/src/tools/schedule/list.ts +14 -3
- package/src/tools/schedule/update.ts +56 -33
- package/src/tools/shared/filesystem/audio-read.ts +6 -4
- package/src/tools/shared/filesystem/file-ops-service.ts +176 -116
- package/src/tools/shared/filesystem/image-read.ts +12 -12
- package/src/tools/shared/filesystem/path-policy.ts +70 -5
- package/src/tools/shared/filesystem/size-guard.ts +6 -6
- package/src/tools/shared/input-misuse.ts +72 -0
- package/src/tools/skills/scaffold-managed.ts +82 -0
- package/src/tools/skills/skill-tool-factory.ts +14 -1
- package/src/tools/subagent/consult-deadline.ts +12 -7
- package/src/tools/subagent/message.ts +3 -1
- package/src/tools/subagent/read.ts +66 -4
- package/src/tools/subagent/spawn.ts +500 -78
- package/src/tools/tool-approval-handler.ts +45 -4
- package/src/tools/types.ts +22 -3
- package/src/tools/ui-surface/surface-shape-docs.ts +1 -1
- package/src/tools/ui-surface/visual-validation.ts +2 -0
- package/src/usage/__tests__/subagent-attribution.test.ts +109 -0
- package/src/usage/subagent-attribution.ts +108 -0
- package/src/util/__tests__/sqlite-retry.test.ts +112 -0
- package/src/util/abort-reasons.ts +16 -0
- package/src/util/ansi.ts +44 -0
- package/src/util/disk-usage.ts +17 -13
- package/src/util/image-conversion.ts +20 -13
- package/src/util/schedule-source-key.ts +23 -0
- package/src/util/worker-process.ts +7 -1
- package/src/workspace/git-service.ts +33 -9
- package/src/workspace/migrations/140-repair-seed-pinned-memory-v3-live.ts +260 -0
- package/src/workspace/migrations/141-stt-english-default-to-multilingual.ts +111 -0
- package/src/workspace/migrations/__tests__/141-stt-english-default-to-multilingual.test.ts +126 -0
- package/src/workspace/migrations/registry.ts +4 -0
- package/src/workspace/top-level-renderer.ts +15 -2
- package/src/__tests__/tool-side-effects-slack-dm.test.ts +0 -357
- package/src/daemon/install-symlink.ts +0 -207
- package/src/notifications/deferred-emit.ts +0 -147
|
@@ -86,6 +86,7 @@ import {
|
|
|
86
86
|
MessageQueue,
|
|
87
87
|
type QueuedMessage,
|
|
88
88
|
} from "../daemon/conversation-queue-manager.js";
|
|
89
|
+
import type { TrustContext } from "../daemon/trust-context-types.js";
|
|
89
90
|
|
|
90
91
|
// ---------------------------------------------------------------------------
|
|
91
92
|
// Fake context — captures preactivation calls, satisfies the bare minimum
|
|
@@ -172,6 +173,7 @@ function makeQueuedMessage(opts: {
|
|
|
172
173
|
content?: string;
|
|
173
174
|
turnInterfaceContext?: TurnInterfaceContext;
|
|
174
175
|
sourceActorPrincipalId?: string;
|
|
176
|
+
trustContext?: TrustContext;
|
|
175
177
|
}): QueuedMessage {
|
|
176
178
|
return {
|
|
177
179
|
content: opts.content ?? "follow up",
|
|
@@ -185,6 +187,7 @@ function makeQueuedMessage(opts: {
|
|
|
185
187
|
authContext: opts.sourceActorPrincipalId
|
|
186
188
|
? ({ actorPrincipalId: opts.sourceActorPrincipalId } as never)
|
|
187
189
|
: undefined,
|
|
190
|
+
trustContext: opts.trustContext,
|
|
188
191
|
};
|
|
189
192
|
}
|
|
190
193
|
|
|
@@ -371,6 +374,126 @@ describe("drainQueue preactivation re-add for host-proxy interfaces", () => {
|
|
|
371
374
|
expect(ctx.currentTurnSourceActorPrincipalId).toBe("trusted-contact-user");
|
|
372
375
|
});
|
|
373
376
|
|
|
377
|
+
test("drainSingleMessage runs the turn under the queued sender's trust, not the live slot", async () => {
|
|
378
|
+
// The conversation-level slot holds whichever actor sent most recently.
|
|
379
|
+
// A message that waited while someone else sent must still run as its own
|
|
380
|
+
// sender: trust decides `trustClass`, `executionChannel`, and
|
|
381
|
+
// `requesterExternalUserId`, so reading the slot hands the whole
|
|
382
|
+
// tool-approval path the wrong identity in both directions -- a contact
|
|
383
|
+
// inheriting guardian self-approval, or a guardian's own call escalating
|
|
384
|
+
// back to her as a contact's grant request.
|
|
385
|
+
const contactTrust: TrustContext = {
|
|
386
|
+
trustClass: "trusted_contact",
|
|
387
|
+
sourceChannel: "slack",
|
|
388
|
+
requesterExternalUserId: "U-contact",
|
|
389
|
+
};
|
|
390
|
+
const queue = new MessageQueue();
|
|
391
|
+
queue.push(
|
|
392
|
+
makeQueuedMessage({
|
|
393
|
+
requestId: "req-contact",
|
|
394
|
+
trustContext: contactTrust,
|
|
395
|
+
}),
|
|
396
|
+
);
|
|
397
|
+
const ctx = makeFakeContext({ queue });
|
|
398
|
+
// Someone else sent after this message was queued, moving the slot.
|
|
399
|
+
ctx.trustContext = {
|
|
400
|
+
trustClass: "guardian",
|
|
401
|
+
sourceChannel: "vellum",
|
|
402
|
+
requesterExternalUserId: "guardian-principal",
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
await drainQueue(ctx);
|
|
406
|
+
|
|
407
|
+
expect(ctx.currentTurnTrustContext?.trustClass).toBe("trusted_contact");
|
|
408
|
+
expect(ctx.currentTurnTrustContext?.requesterExternalUserId).toBe(
|
|
409
|
+
"U-contact",
|
|
410
|
+
);
|
|
411
|
+
expect(ctx.currentTurnTrustContext?.sourceChannel).toBe("slack");
|
|
412
|
+
// The slot itself is left alone; only the turn's view is corrected.
|
|
413
|
+
expect(ctx.trustContext?.trustClass).toBe("guardian");
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
test("buildPassthroughBatch refuses to coalesce two channel senders", async () => {
|
|
417
|
+
// Channel senders carry no principal, so the `sourceActorPrincipalId`
|
|
418
|
+
// boundary sees `undefined === undefined` and would batch two different
|
|
419
|
+
// Slack contacts into one turn running under the head's trust. The batch
|
|
420
|
+
// must split on the sender's trust identity instead.
|
|
421
|
+
const ifCtx: TurnInterfaceContext = {
|
|
422
|
+
userMessageInterface: "web",
|
|
423
|
+
assistantMessageInterface: "web",
|
|
424
|
+
};
|
|
425
|
+
const queue = new MessageQueue();
|
|
426
|
+
queue.push(
|
|
427
|
+
makeQueuedMessage({
|
|
428
|
+
requestId: "req-contact-a",
|
|
429
|
+
content: "from A",
|
|
430
|
+
turnInterfaceContext: ifCtx,
|
|
431
|
+
trustContext: {
|
|
432
|
+
trustClass: "trusted_contact",
|
|
433
|
+
sourceChannel: "slack",
|
|
434
|
+
requesterExternalUserId: "U-alex",
|
|
435
|
+
},
|
|
436
|
+
}),
|
|
437
|
+
);
|
|
438
|
+
queue.push(
|
|
439
|
+
makeQueuedMessage({
|
|
440
|
+
requestId: "req-contact-b",
|
|
441
|
+
content: "from B",
|
|
442
|
+
turnInterfaceContext: ifCtx,
|
|
443
|
+
trustContext: {
|
|
444
|
+
trustClass: "trusted_contact",
|
|
445
|
+
sourceChannel: "slack",
|
|
446
|
+
requesterExternalUserId: "U-blake",
|
|
447
|
+
},
|
|
448
|
+
}),
|
|
449
|
+
);
|
|
450
|
+
const ctx = makeFakeContext({ queue, turnInterfaceContext: ifCtx });
|
|
451
|
+
|
|
452
|
+
await drainQueue(ctx);
|
|
453
|
+
|
|
454
|
+
// B stays queued: it gets its own turn under its own trust.
|
|
455
|
+
expect(queue.length).toBe(1);
|
|
456
|
+
expect(queue.peek(0)?.requestId).toBe("req-contact-b");
|
|
457
|
+
expect(ctx.currentTurnTrustContext?.requesterExternalUserId).toBe("U-alex");
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
test("buildPassthroughBatch still coalesces two messages from the same sender", async () => {
|
|
461
|
+
// Sensitivity check on the split above: identical trust must still batch,
|
|
462
|
+
// otherwise the boundary would be splitting on something incidental and
|
|
463
|
+
// the test above would pass for the wrong reason.
|
|
464
|
+
const ifCtx: TurnInterfaceContext = {
|
|
465
|
+
userMessageInterface: "web",
|
|
466
|
+
assistantMessageInterface: "web",
|
|
467
|
+
};
|
|
468
|
+
const sameTrust: TrustContext = {
|
|
469
|
+
trustClass: "trusted_contact",
|
|
470
|
+
sourceChannel: "slack",
|
|
471
|
+
requesterExternalUserId: "U-alex",
|
|
472
|
+
};
|
|
473
|
+
const queue = new MessageQueue();
|
|
474
|
+
queue.push(
|
|
475
|
+
makeQueuedMessage({
|
|
476
|
+
requestId: "req-a1",
|
|
477
|
+
content: "first",
|
|
478
|
+
turnInterfaceContext: ifCtx,
|
|
479
|
+
trustContext: { ...sameTrust },
|
|
480
|
+
}),
|
|
481
|
+
);
|
|
482
|
+
queue.push(
|
|
483
|
+
makeQueuedMessage({
|
|
484
|
+
requestId: "req-a2",
|
|
485
|
+
content: "second",
|
|
486
|
+
turnInterfaceContext: ifCtx,
|
|
487
|
+
trustContext: { ...sameTrust },
|
|
488
|
+
}),
|
|
489
|
+
);
|
|
490
|
+
const ctx = makeFakeContext({ queue, turnInterfaceContext: ifCtx });
|
|
491
|
+
|
|
492
|
+
await drainQueue(ctx);
|
|
493
|
+
|
|
494
|
+
expect(queue.length).toBe(0);
|
|
495
|
+
});
|
|
496
|
+
|
|
374
497
|
test("drainSingleMessage does NOT re-add 'app-control' for web-sourced message when no capable client is connected", async () => {
|
|
375
498
|
// mockCapabilityClients remains [] (reset by afterEach from prior test)
|
|
376
499
|
const queue = new MessageQueue();
|
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
import type { AssistantEvent } from "../api/index.js";
|
|
13
13
|
import type { Message, ProviderResponse } from "../providers/types.js";
|
|
14
14
|
import { stampAndBuffer } from "../runtime/assistant-stream-state.js";
|
|
15
|
+
import { createAbortReason } from "../util/abort-reasons.js";
|
|
15
16
|
import { setConfig } from "./helpers/set-config.js";
|
|
16
17
|
|
|
17
18
|
// ---------------------------------------------------------------------------
|
|
@@ -555,6 +556,174 @@ describe("Conversation message queue", () => {
|
|
|
555
556
|
await new Promise((r) => setTimeout(r, 10));
|
|
556
557
|
});
|
|
557
558
|
|
|
559
|
+
test("enqueueMessage captures the sender's trust, immune to a later slot change", async () => {
|
|
560
|
+
// Trust must ride with the queued message. The conversation-level slot is
|
|
561
|
+
// rewritten by whoever sends next, so a message that reads it at drain time
|
|
562
|
+
// would run as the wrong actor. Capturing at enqueue is what makes the
|
|
563
|
+
// drain's identity independent of who sent afterwards.
|
|
564
|
+
const conversation = makeConversation();
|
|
565
|
+
await conversation.loadFromDb();
|
|
566
|
+
|
|
567
|
+
conversation.setTrustContext({
|
|
568
|
+
trustClass: "trusted_contact",
|
|
569
|
+
sourceChannel: "slack",
|
|
570
|
+
requesterExternalUserId: "U-contact",
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
const p1 = conversation.processMessage({
|
|
574
|
+
content: "msg-1",
|
|
575
|
+
attachments: [],
|
|
576
|
+
onEvent: () => {},
|
|
577
|
+
requestId: "req-1",
|
|
578
|
+
});
|
|
579
|
+
await waitForPendingRun(1);
|
|
580
|
+
|
|
581
|
+
conversation.enqueueMessage({ content: "msg-2", requestId: "req-2" });
|
|
582
|
+
|
|
583
|
+
// A different actor sends while the message waits, moving the slot.
|
|
584
|
+
conversation.setTrustContext({
|
|
585
|
+
trustClass: "guardian",
|
|
586
|
+
sourceChannel: "vellum",
|
|
587
|
+
requesterExternalUserId: "guardian-principal",
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
const queued = conversation.queue.peek(0);
|
|
591
|
+
expect(queued?.requestId).toBe("req-2");
|
|
592
|
+
expect(queued?.trustContext?.trustClass).toBe("trusted_contact");
|
|
593
|
+
expect(queued?.trustContext?.requesterExternalUserId).toBe("U-contact");
|
|
594
|
+
|
|
595
|
+
await resolveRun(0);
|
|
596
|
+
await p1;
|
|
597
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
test("a drained turn keeps its sender's trust once the agent loop is running", async () => {
|
|
601
|
+
// Exercises the real drain -> runAgentLoop ordering against a live
|
|
602
|
+
// Conversation. The loop re-initializes the per-turn trust snapshot on
|
|
603
|
+
// entry, so a drain that only stamped the field before calling it would
|
|
604
|
+
// have that stamp silently undone and the turn would execute as whoever
|
|
605
|
+
// sent most recently. Asserting inside the run is the point: checking
|
|
606
|
+
// before the loop starts passes either way.
|
|
607
|
+
const conversation = makeConversation();
|
|
608
|
+
await conversation.loadFromDb();
|
|
609
|
+
|
|
610
|
+
conversation.setTrustContext({
|
|
611
|
+
trustClass: "trusted_contact",
|
|
612
|
+
sourceChannel: "slack",
|
|
613
|
+
requesterExternalUserId: "U-contact",
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
const p1 = conversation.processMessage({
|
|
617
|
+
content: "msg-1",
|
|
618
|
+
attachments: [],
|
|
619
|
+
onEvent: () => {},
|
|
620
|
+
requestId: "req-1",
|
|
621
|
+
});
|
|
622
|
+
await waitForPendingRun(1);
|
|
623
|
+
|
|
624
|
+
conversation.enqueueMessage({ content: "msg-2", requestId: "req-2" });
|
|
625
|
+
|
|
626
|
+
// The guardian sends while the contact's message waits, moving the slot.
|
|
627
|
+
conversation.setTrustContext({
|
|
628
|
+
trustClass: "guardian",
|
|
629
|
+
sourceChannel: "vellum",
|
|
630
|
+
requesterExternalUserId: "guardian-principal",
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
// Finish the first turn so the queue drains into a second run.
|
|
634
|
+
await resolveRun(0);
|
|
635
|
+
await p1;
|
|
636
|
+
await waitForPendingRun(2);
|
|
637
|
+
|
|
638
|
+
// Read while run 2 is in flight: this is what tool setup and the
|
|
639
|
+
// guardian-request producers resolve against.
|
|
640
|
+
expect(conversation.currentTurnTrustContext?.trustClass).toBe(
|
|
641
|
+
"trusted_contact",
|
|
642
|
+
);
|
|
643
|
+
expect(conversation.currentTurnTrustContext?.requesterExternalUserId).toBe(
|
|
644
|
+
"U-contact",
|
|
645
|
+
);
|
|
646
|
+
|
|
647
|
+
await resolveRun(1);
|
|
648
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
test("a processMessage turn keeps its turn-start trust when the slot moves before the loop opens", async () => {
|
|
652
|
+
// processMessage is the third turn entry point, and the one a web turn
|
|
653
|
+
// takes on an idle conversation (the route only enqueues while
|
|
654
|
+
// isProcessing). It captures trust at turn start, then awaits several
|
|
655
|
+
// times before the agent loop opens. The conversation slot is writable
|
|
656
|
+
// throughout that window by paths that do not own this turn: live-voice
|
|
657
|
+
// hydration stamp-and-restore, pointer elevation, the voice bridge.
|
|
658
|
+
//
|
|
659
|
+
// Driving a real Conversation with only AgentLoop.run mocked is what makes
|
|
660
|
+
// this observable. A double that stubs runAgentLoop itself would skip the
|
|
661
|
+
// re-read entirely and pass either way.
|
|
662
|
+
const conversation = makeConversation();
|
|
663
|
+
await conversation.loadFromDb();
|
|
664
|
+
|
|
665
|
+
conversation.setTrustContext({
|
|
666
|
+
trustClass: "trusted_contact",
|
|
667
|
+
sourceChannel: "slack",
|
|
668
|
+
requesterExternalUserId: "U-contact",
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
// Land another actor's slot write inside the window, after the turn-start
|
|
672
|
+
// capture and before the loop opens. persistUserMessage is awaited there.
|
|
673
|
+
const originalPersist = conversation.persistUserMessage.bind(conversation);
|
|
674
|
+
let slotMoved = false;
|
|
675
|
+
(
|
|
676
|
+
conversation as unknown as {
|
|
677
|
+
persistUserMessage: typeof conversation.persistUserMessage;
|
|
678
|
+
}
|
|
679
|
+
).persistUserMessage = async (opts) => {
|
|
680
|
+
const result = await originalPersist(opts);
|
|
681
|
+
conversation.setTrustContext({
|
|
682
|
+
trustClass: "guardian",
|
|
683
|
+
sourceChannel: "vellum",
|
|
684
|
+
requesterExternalUserId: "guardian-principal",
|
|
685
|
+
});
|
|
686
|
+
// Also move the per-turn field, which is writable out-of-band: a wake
|
|
687
|
+
// that settles inside this window restores its prior value there
|
|
688
|
+
// (agent-wake stamps at :1470 and restores in a `finally` at :1554).
|
|
689
|
+
// Covers both writers, so a fix that reads either one back at the agent
|
|
690
|
+
// loop call still fails this test.
|
|
691
|
+
conversation.currentTurnTrustContext = {
|
|
692
|
+
trustClass: "guardian",
|
|
693
|
+
sourceChannel: "vellum",
|
|
694
|
+
requesterExternalUserId: "guardian-principal",
|
|
695
|
+
};
|
|
696
|
+
slotMoved = true;
|
|
697
|
+
return result;
|
|
698
|
+
};
|
|
699
|
+
|
|
700
|
+
const p1 = conversation.processMessage({
|
|
701
|
+
content: "msg-1",
|
|
702
|
+
attachments: [],
|
|
703
|
+
onEvent: () => {},
|
|
704
|
+
requestId: "req-1",
|
|
705
|
+
});
|
|
706
|
+
await waitForPendingRun(1);
|
|
707
|
+
|
|
708
|
+
// Guard the test itself: if the injection stopped running, the assertions
|
|
709
|
+
// below would pass for the wrong reason.
|
|
710
|
+
expect(slotMoved).toBe(true);
|
|
711
|
+
expect(conversation.trustContext?.trustClass).toBe("guardian");
|
|
712
|
+
|
|
713
|
+
// Read mid-run: this is what tool setup and the guardian-request producers
|
|
714
|
+
// resolve against while the turn executes.
|
|
715
|
+
expect(conversation.currentTurnTrustContext?.trustClass).toBe(
|
|
716
|
+
"trusted_contact",
|
|
717
|
+
);
|
|
718
|
+
expect(conversation.currentTurnTrustContext?.requesterExternalUserId).toBe(
|
|
719
|
+
"U-contact",
|
|
720
|
+
);
|
|
721
|
+
|
|
722
|
+
await resolveRun(0);
|
|
723
|
+
await p1;
|
|
724
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
725
|
+
});
|
|
726
|
+
|
|
558
727
|
test("[experimental] queued passthrough siblings drain as a single batched run", async () => {
|
|
559
728
|
const conversation = makeConversation();
|
|
560
729
|
await conversation.loadFromDb();
|
|
@@ -675,6 +844,62 @@ describe("Conversation message queue", () => {
|
|
|
675
844
|
expect(pendingRuns.length).toBe(3);
|
|
676
845
|
});
|
|
677
846
|
|
|
847
|
+
// `Conversation.clientOs` is a live field that only a transport-carrying
|
|
848
|
+
// message refreshes, so a transport-less drain (a surface action, a signal)
|
|
849
|
+
// persists the OS of an earlier send. Both rows keep that OS for telemetry;
|
|
850
|
+
// only the row that reported it claims the surface, which is what stops an
|
|
851
|
+
// attended Mac from suppressing the push for a button tapped on the phone.
|
|
852
|
+
test("[experimental] only a queued send that reported its own OS claims that surface", async () => {
|
|
853
|
+
capturedAddMessages.length = 0;
|
|
854
|
+
const conversation = makeConversation();
|
|
855
|
+
await conversation.loadFromDb();
|
|
856
|
+
|
|
857
|
+
const p1 = conversation.processMessage({
|
|
858
|
+
content: "msg-1",
|
|
859
|
+
attachments: [],
|
|
860
|
+
onEvent: () => {},
|
|
861
|
+
requestId: "req-1",
|
|
862
|
+
});
|
|
863
|
+
await waitForPendingRun(1);
|
|
864
|
+
|
|
865
|
+
conversation.enqueueMessage({
|
|
866
|
+
content: "from-the-mac",
|
|
867
|
+
onEvent: () => {},
|
|
868
|
+
requestId: "req-mac",
|
|
869
|
+
transport: { channelId: "vellum", interfaceId: "web", clientOs: "macos" },
|
|
870
|
+
});
|
|
871
|
+
// A surface action: no transport, so the drain leaves the conversation's
|
|
872
|
+
// `clientOs` on the macOS value the send above applied.
|
|
873
|
+
conversation.enqueueMessage({
|
|
874
|
+
content: "[User action on card surface: submit]",
|
|
875
|
+
onEvent: () => {},
|
|
876
|
+
requestId: "req-surface-action",
|
|
877
|
+
});
|
|
878
|
+
|
|
879
|
+
await resolveRun(0);
|
|
880
|
+
await p1;
|
|
881
|
+
await waitForPendingRun(2);
|
|
882
|
+
await resolveRun(1);
|
|
883
|
+
await waitForPendingRun(3);
|
|
884
|
+
|
|
885
|
+
const macRow = capturedAddMessages.find(
|
|
886
|
+
(m) => m.role === "user" && m.content.includes("from-the-mac"),
|
|
887
|
+
);
|
|
888
|
+
expect(macRow).toBeDefined();
|
|
889
|
+
expect(macRow!.metadata?.client).toEqual({ os: "macos" });
|
|
890
|
+
expect(macRow!.metadata?.clientOsFromRequest).toBe(true);
|
|
891
|
+
|
|
892
|
+
const surfaceActionRow = capturedAddMessages.find(
|
|
893
|
+
(m) => m.role === "user" && m.content.includes("User action on card"),
|
|
894
|
+
);
|
|
895
|
+
expect(surfaceActionRow).toBeDefined();
|
|
896
|
+
expect(surfaceActionRow!.metadata?.client).toEqual({ os: "macos" });
|
|
897
|
+
expect(surfaceActionRow!.metadata?.clientOsFromRequest).toBeUndefined();
|
|
898
|
+
|
|
899
|
+
await resolveRun(2);
|
|
900
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
901
|
+
});
|
|
902
|
+
|
|
678
903
|
test("[experimental] queued passthrough siblings viewing different apps do NOT batch", async () => {
|
|
679
904
|
// A batched turn applies only the head's `visibleAppId`, which drives the
|
|
680
905
|
// `visible_app:` context line. Coalescing messages sent while different
|
|
@@ -826,7 +1051,87 @@ describe("Conversation message queue", () => {
|
|
|
826
1051
|
await new Promise((r) => setTimeout(r, 10));
|
|
827
1052
|
});
|
|
828
1053
|
|
|
829
|
-
test("
|
|
1054
|
+
test("subagent notifications are never acked with message_queued and don't shift visible positions", async () => {
|
|
1055
|
+
const conversation = makeConversation();
|
|
1056
|
+
await conversation.loadFromDb();
|
|
1057
|
+
|
|
1058
|
+
const notificationEvents: AssistantEvent[] = [];
|
|
1059
|
+
const visibleEvents: AssistantEvent[] = [];
|
|
1060
|
+
|
|
1061
|
+
const p1 = conversation.processMessage({
|
|
1062
|
+
content: "msg-1",
|
|
1063
|
+
attachments: [],
|
|
1064
|
+
requestId: "req-1",
|
|
1065
|
+
});
|
|
1066
|
+
await waitForPendingRun(1);
|
|
1067
|
+
|
|
1068
|
+
// A subagent's completion summary injected into a busy parent is daemon
|
|
1069
|
+
// scaffolding: the user follows that run through its inline card, so the
|
|
1070
|
+
// row gets no queued ack and no queue-drawer bubble.
|
|
1071
|
+
const notification = conversation.enqueueMessage({
|
|
1072
|
+
content: '[Subagent "hermes-local-review" — important] review finished',
|
|
1073
|
+
onEvent: (e) => notificationEvents.push(e),
|
|
1074
|
+
requestId: "req-subagent",
|
|
1075
|
+
metadata: {
|
|
1076
|
+
subagentNotification: {
|
|
1077
|
+
subagentId: "sub-1",
|
|
1078
|
+
label: "hermes-local-review",
|
|
1079
|
+
status: "completed",
|
|
1080
|
+
conversationId: "conv-sub-1",
|
|
1081
|
+
objective: "review the diff",
|
|
1082
|
+
},
|
|
1083
|
+
},
|
|
1084
|
+
});
|
|
1085
|
+
expect(notification.queued).toBe(true);
|
|
1086
|
+
expect(
|
|
1087
|
+
notificationEvents.filter((e) => e.type === "message_queued"),
|
|
1088
|
+
).toEqual([]);
|
|
1089
|
+
|
|
1090
|
+
// The user's own send queued behind it is position 1: the notification
|
|
1091
|
+
// does not consume a visible slot, matching the cold-load snapshot.
|
|
1092
|
+
const visible = conversation.enqueueMessage({
|
|
1093
|
+
content: "visible-msg",
|
|
1094
|
+
onEvent: (e) => visibleEvents.push(e),
|
|
1095
|
+
requestId: "req-visible",
|
|
1096
|
+
});
|
|
1097
|
+
expect(visible.queued).toBe(true);
|
|
1098
|
+
expect(conversation.getQueueDepth()).toBe(2);
|
|
1099
|
+
expect(
|
|
1100
|
+
visibleEvents.find((e) => e.type === "message_queued"),
|
|
1101
|
+
).toMatchObject({
|
|
1102
|
+
requestId: "req-visible",
|
|
1103
|
+
position: 1,
|
|
1104
|
+
});
|
|
1105
|
+
|
|
1106
|
+
// Suppressing the ack changes only what clients are told: the row is
|
|
1107
|
+
// still queued, still drains, and still wakes the parent on the notification
|
|
1108
|
+
// text (delivery is asserted end-to-end in "drained subagent-notification
|
|
1109
|
+
// message persists and wakes the agent but emits no user_message_echo").
|
|
1110
|
+
await resolveRun(0);
|
|
1111
|
+
await p1;
|
|
1112
|
+
await waitForPendingRun(2);
|
|
1113
|
+
await waitForCondition(() => conversation.getQueueDepth() === 0);
|
|
1114
|
+
expect(
|
|
1115
|
+
capturedAddMessages.some((m) =>
|
|
1116
|
+
m.content.includes("hermes-local-review"),
|
|
1117
|
+
),
|
|
1118
|
+
).toBe(true);
|
|
1119
|
+
|
|
1120
|
+
for (let i = 1; i < pendingRuns.length; i++) {
|
|
1121
|
+
await resolveRun(i);
|
|
1122
|
+
}
|
|
1123
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
1124
|
+
|
|
1125
|
+
// Queue events stay paired: an enqueue that was never acked is never
|
|
1126
|
+
// dequeued either, so the client counter can't retire an entry it never
|
|
1127
|
+
// took. The visible send keeps both halves.
|
|
1128
|
+
expect(
|
|
1129
|
+
notificationEvents.filter((e) => e.type === "message_dequeued"),
|
|
1130
|
+
).toEqual([]);
|
|
1131
|
+
expect(visibleEvents.some((e) => e.type === "message_dequeued")).toBe(true);
|
|
1132
|
+
});
|
|
1133
|
+
|
|
1134
|
+
test("abort() clears the queue and closes out each queued message", async () => {
|
|
830
1135
|
const conversation = makeConversation();
|
|
831
1136
|
await conversation.loadFromDb();
|
|
832
1137
|
|
|
@@ -873,6 +1178,17 @@ describe("Conversation message queue", () => {
|
|
|
873
1178
|
conversationId: "conv-1",
|
|
874
1179
|
});
|
|
875
1180
|
|
|
1181
|
+
// Each discarded row also gets its own terminal event. `generation_cancelled`
|
|
1182
|
+
// closes out the turn they were waiting on; only `message_queued_deleted`
|
|
1183
|
+
// closes out the queued rows themselves, and without it clients keep the
|
|
1184
|
+
// pending indicator up forever, since no `message_dequeued` is coming.
|
|
1185
|
+
expect(
|
|
1186
|
+
events2.find((e) => e.type === "message_queued_deleted"),
|
|
1187
|
+
).toMatchObject({ conversationId: "conv-1", requestId: "req-2" });
|
|
1188
|
+
expect(
|
|
1189
|
+
events3.find((e) => e.type === "message_queued_deleted"),
|
|
1190
|
+
).toMatchObject({ conversationId: "conv-1", requestId: "req-3" });
|
|
1191
|
+
|
|
876
1192
|
// abort() must NOT emit conversation_error or generic error for queued discards.
|
|
877
1193
|
const err2 = events2.find((e) => e.type === "error");
|
|
878
1194
|
expect(err2).toBeUndefined();
|
|
@@ -897,6 +1213,122 @@ describe("Conversation message queue", () => {
|
|
|
897
1213
|
await new Promise((r) => setTimeout(r, 10));
|
|
898
1214
|
});
|
|
899
1215
|
|
|
1216
|
+
test("a user interrupt keeps the queue and the stopped turn's drain sends it", async () => {
|
|
1217
|
+
// GIVEN a turn in flight with a message queued behind it
|
|
1218
|
+
const conversation = makeConversation();
|
|
1219
|
+
await conversation.loadFromDb();
|
|
1220
|
+
|
|
1221
|
+
const events2: AssistantEvent[] = [];
|
|
1222
|
+
conversation.processMessage({
|
|
1223
|
+
content: "msg-1",
|
|
1224
|
+
attachments: [],
|
|
1225
|
+
requestId: "req-1",
|
|
1226
|
+
});
|
|
1227
|
+
await waitForPendingRun(1);
|
|
1228
|
+
|
|
1229
|
+
conversation.enqueueMessage({
|
|
1230
|
+
content: "msg-2",
|
|
1231
|
+
onEvent: (e) => events2.push(e),
|
|
1232
|
+
requestId: "req-2",
|
|
1233
|
+
});
|
|
1234
|
+
expect(conversation.getQueueDepth()).toBe(1);
|
|
1235
|
+
|
|
1236
|
+
// WHEN the user hits Stop
|
|
1237
|
+
conversation.abort(
|
|
1238
|
+
createAbortReason("user_cancel", "conversation-queue.test", "conv-1"),
|
|
1239
|
+
);
|
|
1240
|
+
|
|
1241
|
+
// THEN the queued message survives the abort. Stop ends the turn the user
|
|
1242
|
+
// is watching, not the message they queued behind it, and a discard here
|
|
1243
|
+
// reaches no client: it emits no per-row terminal event.
|
|
1244
|
+
expect(conversation.getQueueDepth()).toBe(1);
|
|
1245
|
+
expect(
|
|
1246
|
+
events2.find((e) => e.type === "generation_cancelled"),
|
|
1247
|
+
).toBeUndefined();
|
|
1248
|
+
expect(
|
|
1249
|
+
events2.find((e) => e.type === "message_queued_deleted"),
|
|
1250
|
+
).toBeUndefined();
|
|
1251
|
+
|
|
1252
|
+
// AND once the stopped turn unwinds, its `finally` drains the queue, so the
|
|
1253
|
+
// message is sent instead of needing to be retyped.
|
|
1254
|
+
await resolveRun(0);
|
|
1255
|
+
await waitForPendingRun(2);
|
|
1256
|
+
expect(events2.find((e) => e.type === "message_dequeued")).toMatchObject({
|
|
1257
|
+
conversationId: "conv-1",
|
|
1258
|
+
requestId: "req-2",
|
|
1259
|
+
});
|
|
1260
|
+
expect(conversation.getQueueDepth()).toBe(0);
|
|
1261
|
+
expect(
|
|
1262
|
+
JSON.stringify(
|
|
1263
|
+
pendingRuns[1].messages[pendingRuns[1].messages.length - 1],
|
|
1264
|
+
),
|
|
1265
|
+
).toContain("msg-2");
|
|
1266
|
+
|
|
1267
|
+
await resolveRun(1);
|
|
1268
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
1269
|
+
});
|
|
1270
|
+
|
|
1271
|
+
test("a user interrupt mid-tool repairs the abandoned tool_use before the queued message runs", async () => {
|
|
1272
|
+
// GIVEN a turn whose history ends with an unanswered tool_use, the shape a
|
|
1273
|
+
// Stop mid-tool-call leaves behind and which providers reject outright
|
|
1274
|
+
const conversation = makeConversation();
|
|
1275
|
+
await conversation.loadFromDb();
|
|
1276
|
+
|
|
1277
|
+
const events2: AssistantEvent[] = [];
|
|
1278
|
+
conversation.processMessage({
|
|
1279
|
+
content: "msg-1",
|
|
1280
|
+
attachments: [],
|
|
1281
|
+
requestId: "req-1",
|
|
1282
|
+
});
|
|
1283
|
+
await waitForPendingRun(1);
|
|
1284
|
+
|
|
1285
|
+
conversation.enqueueMessage({
|
|
1286
|
+
content: "msg-2",
|
|
1287
|
+
onEvent: (e) => events2.push(e),
|
|
1288
|
+
requestId: "req-2",
|
|
1289
|
+
});
|
|
1290
|
+
|
|
1291
|
+
// WHEN the user interrupts and the stopped turn unwinds with the tool call
|
|
1292
|
+
// still unanswered, then drains the queue
|
|
1293
|
+
conversation.abort(
|
|
1294
|
+
createAbortReason("user_cancel", "conversation-queue.test", "conv-1"),
|
|
1295
|
+
);
|
|
1296
|
+
const interrupted = pendingRuns[0];
|
|
1297
|
+
await interrupted.onEvent({ type: "llm_call_started" });
|
|
1298
|
+
interrupted.resolve([
|
|
1299
|
+
...interrupted.messages,
|
|
1300
|
+
{
|
|
1301
|
+
role: "assistant",
|
|
1302
|
+
content: [
|
|
1303
|
+
{
|
|
1304
|
+
type: "tool_use",
|
|
1305
|
+
id: "toolu-interrupted",
|
|
1306
|
+
name: "bash",
|
|
1307
|
+
input: {},
|
|
1308
|
+
},
|
|
1309
|
+
],
|
|
1310
|
+
},
|
|
1311
|
+
]);
|
|
1312
|
+
await waitForPendingRun(2);
|
|
1313
|
+
|
|
1314
|
+
// THEN the drained turn opens with a synthetic result for the abandoned
|
|
1315
|
+
// call rather than a dangling tool_use.
|
|
1316
|
+
const repaired = pendingRuns[1].messages.find(
|
|
1317
|
+
(m) =>
|
|
1318
|
+
m.role === "user" &&
|
|
1319
|
+
m.content.some(
|
|
1320
|
+
(block) =>
|
|
1321
|
+
block.type === "tool_result" &&
|
|
1322
|
+
block.tool_use_id === "toolu-interrupted",
|
|
1323
|
+
),
|
|
1324
|
+
);
|
|
1325
|
+
expect(repaired).toBeDefined();
|
|
1326
|
+
expect(conversation.pendingInterruptRepair).toBe(false);
|
|
1327
|
+
|
|
1328
|
+
await resolveRun(1);
|
|
1329
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
1330
|
+
});
|
|
1331
|
+
|
|
900
1332
|
test("conversation-scoped errors emit both conversation_error and generic error", async () => {
|
|
901
1333
|
const conversation = makeConversation();
|
|
902
1334
|
await conversation.loadFromDb();
|
|
@@ -24,6 +24,7 @@ import { z } from "zod";
|
|
|
24
24
|
import {
|
|
25
25
|
isBackgroundEventMetadata,
|
|
26
26
|
isEchoSuppressedUserMessage,
|
|
27
|
+
isHiddenMessageMetadata,
|
|
27
28
|
} from "../persistence/conversation-types.js";
|
|
28
29
|
|
|
29
30
|
mock.module("../config/env.js", () => ({
|
|
@@ -73,7 +74,7 @@ const getConversationMock = mock((id: string) =>
|
|
|
73
74
|
mock.module("../persistence/conversation-crud.js", () => ({
|
|
74
75
|
addMessage: async () => ({ id: "unused", deduplicated: false }),
|
|
75
76
|
archiveConversation: () => true,
|
|
76
|
-
|
|
77
|
+
batchSetConversationPlacement: () => {},
|
|
77
78
|
countConversationsByScheduleJobId: () => 0,
|
|
78
79
|
deleteConversation: () => ({ segmentIds: [], deletedSummaryIds: [] }),
|
|
79
80
|
extractImageSourcePaths: () => undefined,
|
|
@@ -81,6 +82,7 @@ mock.module("../persistence/conversation-crud.js", () => ({
|
|
|
81
82
|
getConversation: getConversationMock,
|
|
82
83
|
isBackgroundEventMetadata,
|
|
83
84
|
isEchoSuppressedUserMessage,
|
|
85
|
+
isHiddenMessageMetadata,
|
|
84
86
|
provenanceFromTrustContext: () => ({ provenanceTrustClass: "unknown" }),
|
|
85
87
|
setConversationSurfaced: () => null,
|
|
86
88
|
unarchiveConversation: () => true,
|