@vellumai/assistant 0.12.3-staging.1 → 0.12.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 +43 -29
- package/Dockerfile +63 -0
- package/docs/architecture/security.md +10 -5
- package/docs/architecture/turn-actor.md +2 -0
- package/docs/desktop-browser-cli.md +45 -4
- package/docs/live-voice-task-outcomes.md +100 -0
- package/docs/mode-sessions.md +183 -0
- package/docs/voice-input-diagnostics.md +58 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +2 -1
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/integration-categories.ts +36 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +2 -1
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/integration-categories.ts +36 -0
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +20 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +4 -1
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +38 -7
- package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +5 -6
- package/node_modules/@vellumai/service-contracts/package.json +2 -1
- package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/integration-categories.ts +36 -0
- package/node_modules/@vellumai/slack-text/src/index.ts +4 -8
- package/openapi.yaml +665 -14
- package/package.json +1 -1
- package/scripts/smoke-desktop-browser-cli.ts +10 -26
- package/scripts/smoke-desktop-window-theme.ts +2 -3
- package/src/__tests__/agent-loop-output-hooks.test.ts +150 -1
- package/src/__tests__/agent-loop-provider-error-recording.test.ts +87 -19
- package/src/__tests__/annotate-activity-metadata.test.ts +3 -1
- package/src/__tests__/annotate-risk-options.test.ts +2 -0
- package/src/__tests__/answered-question-persistence.test.ts +2 -0
- package/src/__tests__/anthropic-gateway-shared.test.ts +22 -0
- package/src/__tests__/approval-message-composer.test.ts +1 -118
- package/src/__tests__/approval-routes-http.test.ts +3 -0
- package/src/__tests__/assistant-event-hub-connection-history.test.ts +111 -0
- package/src/__tests__/assistant-event-hub-targeted.test.ts +50 -0
- package/src/__tests__/assistant-event-targeting.test.ts +186 -0
- package/src/__tests__/assistant-stream-state.test.ts +29 -9
- package/src/__tests__/browser-fill-credential.test.ts +11 -22
- package/src/__tests__/btw-routes.test.ts +4 -1
- package/src/__tests__/build-persisted-content.test.ts +1 -1
- package/src/__tests__/call-conversation-messages.test.ts +5 -0
- package/src/__tests__/call-recovery.test.ts +7 -2
- package/src/__tests__/call-routes-http.test.ts +12 -0
- package/src/__tests__/call-store.test.ts +7 -2
- package/src/__tests__/channel-retry-sweep.test.ts +9 -0
- package/src/__tests__/chat-reveal-guard-priming.test.ts +2 -0
- package/src/__tests__/checker.test.ts +11 -3
- package/src/__tests__/client-os-metadata-persistence.test.ts +35 -1
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +0 -25
- package/src/__tests__/computer-use-tools.test.ts +1 -233
- package/src/__tests__/config-model-image-provider.test.ts +39 -0
- package/src/__tests__/config-schema.test.ts +16 -0
- package/src/__tests__/config-watcher.test.ts +24 -0
- package/src/__tests__/context-search-agent-runner.test.ts +260 -1
- package/src/__tests__/context-search-fanout.test.ts +1 -1
- package/src/__tests__/context-search-types.test.ts +7 -7
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +8 -2
- package/src/__tests__/conversation-agent-loop-fatal-cleanup.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop.test.ts +546 -16
- package/src/__tests__/conversation-app-control-lifecycle.test.ts +53 -1
- package/src/__tests__/conversation-evictor.test.ts +62 -37
- package/src/__tests__/conversation-history-boundary-repair-failure.test.ts +174 -0
- package/src/__tests__/conversation-loop-db-lock-resilience.test.ts +2 -0
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +2 -0
- package/src/__tests__/conversation-retry-route.test.ts +22 -0
- package/src/__tests__/conversation-routes-disk-view.test.ts +2 -0
- package/src/__tests__/conversation-routes-enabled-plugins.test.ts +2 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +10 -0
- package/src/__tests__/conversation-routes-hidden-queue.test.ts +2 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +2 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +29 -0
- package/src/__tests__/conversation-surface-snapshots.test.ts +543 -0
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +151 -0
- package/src/__tests__/conversation-surfaces-point-at-budget.test.ts +63 -1
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +23 -0
- package/src/__tests__/conversation-undo.test.ts +61 -0
- package/src/__tests__/credential-execution-client.test.ts +6 -6
- package/src/__tests__/credential-prompt-route.test.ts +35 -3
- package/src/__tests__/cu-unified-flow.test.ts +82 -1
- package/src/__tests__/db-mode-sessions-migration.test.ts +175 -0
- package/src/__tests__/deterministic-verification-control-plane.test.ts +2 -0
- package/src/__tests__/dm-backfill.test.ts +91 -1
- package/src/__tests__/document-create-route.test.ts +88 -0
- package/src/__tests__/document-sync-tags.test.ts +27 -0
- package/src/__tests__/emit-signal-routing-intent.test.ts +142 -2
- package/src/__tests__/events-tail-route.test.ts +25 -0
- package/src/__tests__/evict-conversations-for-reload.test.ts +57 -8
- package/src/__tests__/guardian-action-sweep.test.ts +16 -0
- package/src/__tests__/guardian-dispatch.test.ts +13 -0
- package/src/__tests__/headless-browser-interactions.test.ts +64 -20
- package/src/__tests__/heartbeat-service.test.ts +2 -59
- package/src/__tests__/helpers/mock-conversation.ts +21 -0
- package/src/__tests__/helpers/native-web-search-harness.ts +2 -0
- package/src/__tests__/helpers/verification-sessions-ipc-sim.ts +5 -0
- package/src/__tests__/host-cu-proxy.test.ts +46 -2
- package/src/__tests__/http-user-message-parity.test.ts +2 -0
- package/src/__tests__/image-credentials.test.ts +49 -0
- package/src/__tests__/image-service-dispatcher.test.ts +45 -0
- package/src/__tests__/inference-profile-session-handler.test.ts +1 -1
- package/src/__tests__/interrupt-on-send.test.ts +57 -43
- package/src/__tests__/interrupt-turn-note.test.ts +5 -6
- package/src/__tests__/llm-catalog-parity.test.ts +2 -2
- package/src/__tests__/media-generate-image.test.ts +49 -1
- package/src/__tests__/message-provenance-contact-id.test.ts +69 -0
- package/src/__tests__/migration-export-to-gcs.test.ts +142 -0
- package/src/__tests__/mode-session-history.test.ts +245 -0
- package/src/__tests__/mode-session-recovery.test.ts +222 -0
- package/src/__tests__/mode-session-wire.test.ts +141 -0
- package/src/__tests__/notification-guardian-path.test.ts +7 -0
- package/src/__tests__/notification-telegram-adapter.test.ts +2 -2
- package/src/__tests__/notification-vellum-adapter.test.ts +34 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +12 -0
- package/src/__tests__/oauth-providers-routes.test.ts +1 -0
- package/src/__tests__/outbound-slack-persistence.test.ts +2 -0
- package/src/__tests__/persistence-secret-redaction.test.ts +2 -0
- package/src/__tests__/plugin-api-provider.test.ts +5 -0
- package/src/__tests__/plugin-api-resolve-credential.test.ts +97 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +9 -0
- package/src/__tests__/process-message-display-content.test.ts +56 -0
- package/src/__tests__/reaction-persistence.test.ts +5 -0
- package/src/__tests__/request-secret-standalone-channel-gate.test.ts +25 -9
- package/src/__tests__/runtime-events-sse-reconnect.test.ts +1 -1
- package/src/__tests__/scripted-turn-metadata-persistence.test.ts +73 -0
- package/src/__tests__/secret-ingress-http.test.ts +2 -0
- package/src/__tests__/secure-keys.test.ts +43 -1
- package/src/__tests__/send-endpoint-busy.test.ts +163 -1
- package/src/__tests__/server-history-render.test.ts +91 -1
- package/src/__tests__/settings-routes.test.ts +50 -1
- package/src/__tests__/skill-script-runner-sandbox.test.ts +32 -0
- package/src/__tests__/subagent-fork-notifications.test.ts +1 -2
- package/src/__tests__/subagent-manager-notify.test.ts +1 -2
- package/src/__tests__/subagent-notify-parent.test.ts +146 -39
- package/src/__tests__/subagent-notify-stale-rebuild.test.ts +2 -4
- package/src/__tests__/surface-completion-in-flight-snapshot.test.ts +2 -0
- package/src/__tests__/thread-backfill.test.ts +53 -0
- package/src/__tests__/tool-preview-lifecycle.test.ts +2 -0
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +3 -1
- package/src/__tests__/tool-start-timestamp.test.ts +2 -0
- package/src/__tests__/twilio-routes.test.ts +1 -0
- package/src/__tests__/web-search-backend-failure.test.ts +1 -1
- package/src/activation/progress-store.test.ts +12 -7
- package/src/agent/loop.ts +99 -41
- package/src/api/events/assistant-turn-start.ts +3 -0
- package/src/api/events/generation-handoff.ts +2 -0
- package/src/api/events/message-complete.ts +2 -0
- package/src/api/events/notification-conversation-created.ts +14 -16
- package/src/api/events/notification-intent.ts +4 -2
- package/src/api/events/tool-result.ts +85 -0
- package/src/api/events/tool-use-start.ts +3 -0
- package/src/api/events/user-message-echo.ts +3 -0
- package/src/api/index.ts +31 -6
- package/src/api/mode-session.test.ts +131 -0
- package/src/api/mode-session.ts +148 -0
- package/src/api/remember-tool.ts +15 -0
- package/src/api/responses/conversation-message.ts +6 -0
- package/src/approvals/AGENTS.md +4 -2
- package/src/browser/__tests__/operations.test.ts +29 -0
- package/src/browser/operation-meta.ts +2 -1
- package/src/browser/operations.ts +29 -0
- package/src/browser/virtual-desktop-target.ts +2 -8
- package/src/bundler/bundle-signer.ts +1 -3
- package/src/calls/__tests__/barge-in-guard.test.ts +3 -0
- package/src/calls/__tests__/call-controller.test.ts +94 -0
- package/src/calls/__tests__/call-funnel-telemetry.test.ts +204 -0
- package/src/calls/__tests__/voice-control-protocol.test.ts +15 -0
- package/src/calls/__tests__/voice-escalation-judge.test.ts +209 -0
- package/src/calls/__tests__/voice-leg-coordinator.test.ts +168 -0
- package/src/{live-voice/__tests__/live-voice-metrics.test.ts → calls/__tests__/voice-metrics.test.ts} +32 -38
- package/src/calls/__tests__/voice-session-bridge.test.ts +576 -122
- package/src/calls/barge-in-guard.ts +5 -0
- package/src/calls/call-controller.ts +196 -1
- package/src/calls/call-domain.ts +4 -0
- package/src/calls/call-store.ts +87 -2
- package/src/calls/guardian-action-sweep.ts +22 -8
- package/src/calls/media-stream-audio-transcode.ts +1 -3
- package/src/calls/typesafe-noul.ts +156 -0
- package/src/calls/voice-control-protocol.ts +6 -0
- package/src/calls/voice-escalation-judge.ts +145 -0
- package/src/calls/voice-escalation-target.ts +10 -0
- package/src/calls/voice-leg-coordinator.ts +121 -3
- package/src/{live-voice/live-voice-metrics.ts → calls/voice-metrics.ts} +94 -87
- package/src/calls/voice-session-bridge.ts +256 -112
- package/src/calls/voice-triage-escalate.ts +7 -0
- package/src/cli/__tests__/roadmap-cli.test.ts +4 -41
- package/src/cli/commands/__tests__/notifications.test.ts +35 -10
- package/src/cli/commands/browser.help.ts +4 -5
- package/src/cli/commands/browser.ts +0 -1
- package/src/cli/commands/clients.help.ts +46 -0
- package/src/cli/commands/clients.ts +116 -0
- package/src/cli/commands/credentials.help.ts +6 -1
- package/src/cli/commands/notifications.help.ts +16 -2
- package/src/cli/commands/notifications.ts +30 -8
- package/src/cli/commands/roadmap.help.ts +18 -21
- package/src/cli/commands/roadmap.ts +4 -21
- package/src/cli/lib/__tests__/list-installed-plugins.test.ts +5 -2
- package/src/cli/lib/bundled-marketplace.json +84 -34
- package/src/cli/lib/plugin-marketplace.ts +8 -0
- package/src/cli/lib/publish-plugin.ts +1 -2
- package/src/config/__tests__/default-provider.test.ts +3 -1
- package/src/config/__tests__/memory-retrospective-schema.test.ts +14 -1
- package/src/config/bundled-skills/computer-use/SKILL.md +1 -1
- package/src/config/bundled-skills/computer-use/TOOLS.json +89 -1
- package/src/config/bundled-skills/image-studio/SKILL.md +17 -2
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +7 -11
- package/src/config/bundled-skills/sequences/TOOLS.json +1 -5
- package/src/config/call-site-defaults.ts +9 -0
- package/src/config/feature-flag-registry.json +16 -0
- package/src/config/profile-text-generation.test.ts +2 -2
- package/src/config/schemas/__tests__/tools.test.ts +31 -0
- package/src/config/schemas/call-site-catalog.ts +14 -0
- package/src/config/schemas/channels.ts +0 -18
- package/src/config/schemas/llm.ts +3 -1
- package/src/config/schemas/mcp.ts +2 -1
- package/src/config/schemas/memory-retrospective.ts +11 -2
- package/src/config/schemas/services.ts +15 -6
- package/src/config/schemas/tools.ts +10 -0
- package/src/config/session-groups-gate.ts +21 -0
- package/src/config/skills.ts +2 -6
- package/src/conversations/__tests__/message-consolidation.test.ts +149 -0
- package/src/conversations/message-consolidation.ts +102 -5
- package/src/credential-execution/ces-connect.test.ts +69 -0
- package/src/credential-execution/ces-connect.ts +115 -0
- package/src/credential-execution/ces-runtime.ts +55 -93
- package/src/credential-execution/executable-discovery.ts +1 -11
- package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +10 -1
- package/src/daemon/__tests__/inference-profile-notification.test.ts +2 -0
- package/src/daemon/browser-mode-session.test.ts +402 -0
- package/src/daemon/browser-mode-session.ts +186 -0
- package/src/daemon/computer-use-mode-session.test.ts +210 -0
- package/src/daemon/computer-use-mode-session.ts +105 -0
- package/src/daemon/config-watcher.ts +10 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +58 -10
- package/src/daemon/conversation-agent-loop.ts +219 -25
- package/src/daemon/conversation-evictor.ts +5 -24
- package/src/daemon/conversation-history.ts +10 -0
- package/src/daemon/conversation-interrupt-repair.ts +4 -43
- package/src/daemon/conversation-interrupt.ts +7 -11
- package/src/daemon/conversation-messaging.ts +58 -5
- package/src/daemon/conversation-mode-session.test.ts +1667 -0
- package/src/daemon/conversation-mode-session.ts +1008 -0
- package/src/daemon/conversation-process.ts +5 -0
- package/src/daemon/conversation-runtime-assembly.ts +10 -20
- package/src/daemon/conversation-skill-tools.ts +1 -0
- package/src/daemon/conversation-store.ts +4 -4
- package/src/daemon/conversation-surface-snapshots.ts +135 -0
- package/src/daemon/conversation-surfaces.ts +113 -5
- package/src/daemon/conversation-tool-setup.ts +8 -3
- package/src/daemon/conversation.ts +143 -41
- package/src/daemon/handlers/config-model.ts +7 -5
- package/src/daemon/handlers/shared.ts +55 -16
- package/src/daemon/host-cu-proxy.ts +51 -22
- package/src/daemon/lifecycle.ts +30 -13
- package/src/daemon/message-protocol.ts +0 -1
- package/src/daemon/message-provenance.ts +28 -4
- package/src/daemon/mode-session-startup-recovery.ts +23 -0
- package/src/daemon/mode-session-tracking.ts +54 -0
- package/src/daemon/process-message.ts +13 -4
- package/src/daemon/virtual-desktop-context.ts +21 -0
- package/src/desktop/__tests__/fake-desktop.ts +1 -0
- package/src/desktop/desktop-accessibility-script.ts +88 -0
- package/src/desktop/desktop-accessibility.test.ts +160 -0
- package/src/desktop/desktop-accessibility.ts +169 -0
- package/src/desktop/desktop-automation-lease.test.ts +76 -68
- package/src/desktop/desktop-automation-lease.ts +41 -50
- package/src/desktop/desktop-browser-endpoint.ts +4 -0
- package/src/desktop/desktop-computer-use-routing.test.ts +330 -0
- package/src/desktop/desktop-computer-use.test.ts +449 -0
- package/src/desktop/desktop-computer-use.ts +502 -0
- package/src/desktop/desktop-dependencies.ts +47 -293
- package/src/desktop/desktop-display.ts +2 -2
- package/src/desktop/desktop-panel-config.test.ts +13 -3
- package/src/desktop/desktop-panel-config.ts +109 -15
- package/src/desktop/desktop-session-manager.test.ts +69 -21
- package/src/desktop/desktop-session-manager.ts +76 -22
- package/src/desktop/desktop-stream-bridge.test.ts +11 -122
- package/src/desktop/desktop-stream-bridge.ts +6 -8
- package/src/desktop/desktop-window-theme.ts +5 -25
- package/src/desktop/virtual-desktop-feature.ts +52 -2
- package/src/desktop/virtual-desktop-platform.test.ts +5 -9
- package/src/documents/document-store.ts +23 -0
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +22 -26
- package/src/heartbeat/heartbeat-service.ts +5 -33
- package/src/ipc/__tests__/browser-ipc.test.ts +215 -6
- package/src/ipc/assistant-server.ts +2 -0
- package/src/ipc/routes/__tests__/channel-reply-ipc-routes.test.ts +111 -0
- package/src/ipc/routes/channel-reply-ipc-routes.ts +73 -0
- package/src/live-voice/__tests__/continuation-judge.test.ts +151 -0
- package/src/live-voice/__tests__/live-activity-reporter.test.ts +154 -15
- package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +743 -31
- package/src/live-voice/__tests__/live-voice-look-follow-up.test.ts +191 -10
- package/src/live-voice/__tests__/live-voice-session-groups-gate.test.ts +87 -0
- package/src/live-voice/__tests__/live-voice-session-manager.test.ts +5 -1
- package/src/live-voice/__tests__/live-voice-sight-frame-echo.test.ts +41 -0
- package/src/live-voice/__tests__/live-voice-sight-frame.test.ts +1017 -9
- package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +224 -1
- package/src/live-voice/__tests__/live-voice-tts-session.test.ts +101 -0
- package/src/live-voice/__tests__/live-voice-tts.test.ts +44 -0
- package/src/live-voice/__tests__/live-voice-vad.test.ts +939 -75
- package/src/live-voice/__tests__/microphone-idle-gate.test.ts +87 -0
- package/src/live-voice/__tests__/session-controls.test.ts +23 -0
- package/src/live-voice/__tests__/task-outcomes.test.ts +117 -0
- package/src/live-voice/__tests__/voice-input-diagnostics.test.ts +117 -0
- package/src/live-voice/camera-mode-session.test.ts +265 -0
- package/src/live-voice/camera-mode-session.ts +277 -0
- package/src/live-voice/continuation-judge.ts +123 -0
- package/src/live-voice/live-activity-reporter.ts +115 -6
- package/src/live-voice/live-voice-manager.ts +36 -1
- package/src/live-voice/live-voice-photo.ts +88 -7
- package/src/live-voice/live-voice-session-manager.ts +15 -0
- package/src/live-voice/live-voice-session.ts +1443 -747
- package/src/live-voice/live-voice-tts.ts +22 -4
- package/src/live-voice/microphone-idle-gate.ts +56 -0
- package/src/live-voice/protocol.ts +171 -4
- package/src/live-voice/session-controls.ts +11 -3
- package/src/live-voice/task-outcomes.ts +131 -0
- package/src/live-voice/voice-input-diagnostics.ts +117 -0
- package/src/mcp/__tests__/manager-tool-caps.test.ts +63 -18
- package/src/mcp/__tests__/tool-caps.test.ts +28 -1
- package/src/mcp/manager.ts +3 -0
- package/src/mcp/tool-caps.ts +12 -4
- package/src/media/__tests__/image-models.test.ts +58 -0
- package/src/media/image-credentials.ts +5 -0
- package/src/media/image-models.ts +52 -0
- package/src/media/image-service.ts +8 -0
- package/src/media/openrouter-image-service.test.ts +133 -0
- package/src/media/openrouter-image-service.ts +153 -0
- package/src/media/types.ts +1 -1
- package/src/messaging/providers/__tests__/transport-dispatch.test.ts +65 -11
- package/src/messaging/providers/discord/send.ts +4 -17
- package/src/messaging/providers/discord/transport.ts +2 -2
- package/src/messaging/providers/outlook/client.ts +16 -22
- package/src/messaging/providers/telegram-bot/send.test.ts +236 -1
- package/src/messaging/providers/telegram-bot/send.ts +10 -7
- package/src/messaging/providers/telegram-bot/transport.ts +3 -10
- package/src/messaging/providers/whatsapp/send.test.ts +169 -4
- package/src/monitoring/__tests__/control.test.ts +17 -5
- package/src/monitoring/control.ts +15 -5
- package/src/notifications/AGENTS.md +1 -1
- package/src/notifications/README.md +83 -99
- package/src/notifications/__tests__/broadcaster.test.ts +1 -1
- package/src/notifications/__tests__/channel-allowlist.test.ts +39 -0
- package/src/notifications/__tests__/copy-composer.test.ts +1 -1
- package/src/notifications/__tests__/decision-engine.test.ts +79 -2
- package/src/notifications/__tests__/deterministic-checks.test.ts +10 -10
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +93 -4
- package/src/notifications/__tests__/notification-utils.test.ts +18 -0
- package/src/notifications/adapters/macos.ts +37 -46
- package/src/notifications/broadcaster.ts +5 -6
- package/src/notifications/channel-allowlist.ts +44 -0
- package/src/notifications/copy-composer.ts +0 -23
- package/src/notifications/decision-engine.ts +24 -14
- package/src/notifications/deterministic-checks.ts +1 -1
- package/src/notifications/emit-signal.ts +123 -37
- package/src/notifications/home-feed-side-effect.ts +9 -7
- package/src/notifications/notification-utils.ts +19 -0
- package/src/notifications/signal.ts +4 -19
- package/src/notifications/types.ts +15 -0
- package/src/oauth/connection-resolver.ts +1 -2
- package/src/oauth/oauth-store.ts +1 -3
- package/src/oauth/provider-serializer.ts +7 -0
- package/src/oauth/seed-providers.ts +30 -0
- package/src/onboarding/onboarding-events-store.ts +99 -12
- package/src/persistence/__tests__/client-connection-events-store.test.ts +322 -0
- package/src/persistence/client-connection-events-store.ts +377 -0
- package/src/persistence/conversation-crud.ts +55 -6
- package/src/persistence/conversation-mode-sessions.test.ts +424 -0
- package/src/persistence/conversation-mode-sessions.ts +460 -0
- package/src/persistence/conversation-queries.ts +6 -19
- package/src/persistence/llm-usage-store.ts +3 -9
- package/src/persistence/message-metadata.ts +25 -0
- package/src/persistence/migrations/381-create-conversation-mode-sessions.ts +41 -0
- package/src/persistence/migrations/382-create-client-connection-events.ts +40 -0
- package/src/persistence/migrations/__tests__/382-create-client-connection-events.test.ts +56 -0
- package/src/persistence/schema/conversations.ts +67 -0
- package/src/persistence/schema/infrastructure.ts +29 -0
- package/src/persistence/steps.ts +17 -0
- package/src/plugin-api/__tests__/plugin-name-env.test.ts +249 -0
- package/src/plugin-api/effective-context-window.ts +33 -0
- package/src/plugin-api/index.ts +24 -5
- package/src/plugin-api/plugin-name-env.ts +125 -0
- package/src/plugin-api/resolve-credential.ts +79 -20
- package/src/plugin-api/vision-support.test.ts +22 -0
- package/src/plugin-api/vision-support.ts +17 -5
- package/src/plugins/__tests__/active-plugin-env.test.ts +136 -0
- package/src/plugins/active-plugin-env.ts +51 -0
- package/src/plugins/defaults/index.ts +24 -0
- package/src/plugins/defaults/memory/__tests__/archive-removal-framing.test.ts +64 -0
- package/src/plugins/defaults/memory/__tests__/worker-control.test.ts +19 -6
- package/src/plugins/defaults/memory/context-search/agent-protocol.ts +9 -12
- package/src/plugins/defaults/memory/context-search/agent-runner.ts +78 -22
- package/src/plugins/defaults/memory/context-search/format.ts +51 -7
- package/src/plugins/defaults/memory/context-search/limits.ts +10 -12
- package/src/plugins/defaults/memory/context-search/recall-input.ts +38 -0
- package/src/plugins/defaults/memory/context-search/search.ts +8 -14
- package/src/plugins/defaults/memory/context-search/sources/conversations.ts +1 -0
- package/src/plugins/defaults/memory/context-search/types.ts +1 -11
- package/src/plugins/defaults/memory/graph/__tests__/handle-remember-v2.test.ts +11 -4
- package/src/plugins/defaults/memory/graph/tool-handlers.ts +12 -21
- package/src/plugins/defaults/memory/graph/tools.ts +35 -64
- package/src/plugins/defaults/memory/src/memory-eval-routes.ts +2 -3
- package/src/plugins/defaults/memory/src/memory-item-routes.test.ts +5 -0
- package/src/plugins/defaults/memory/src/memory-item-routes.ts +2 -1
- package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +2 -2
- package/src/plugins/defaults/memory/tools.test.ts +124 -9
- package/src/plugins/defaults/memory/tools.ts +38 -11
- package/src/plugins/defaults/memory/v3/__tests__/pool-log-store.test.ts +165 -27
- package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +626 -27
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +46 -27
- package/src/plugins/defaults/memory/v3/orchestrate.ts +78 -38
- package/src/plugins/defaults/memory/v3/pool-log-store.ts +65 -79
- package/src/plugins/defaults/memory/v3/pool-select.test.ts +14 -5
- package/src/plugins/defaults/memory/v3/pool-select.ts +683 -44
- package/src/plugins/defaults/memory/worker-control.ts +15 -5
- package/src/plugins/defaults/task-progress/__tests__/registration.test.ts +70 -0
- package/src/plugins/defaults/task-progress/__tests__/task-progress-context.test.ts +380 -0
- package/src/plugins/defaults/task-progress/hooks/post-compact.ts +19 -0
- package/src/plugins/defaults/task-progress/hooks/user-prompt-submit.ts +24 -0
- package/src/plugins/defaults/task-progress/package.json +14 -0
- package/src/plugins/defaults/task-progress/src/apply-task-progress-context.ts +93 -0
- package/src/plugins/defaults/task-progress/src/format-task-progress-context.ts +67 -0
- package/src/providers/__tests__/context-overflow-error.test.ts +10 -0
- package/src/providers/__tests__/search-provider-catalog.test.ts +18 -0
- package/src/providers/anthropic/client.ts +10 -1
- package/src/providers/anthropic-gateway-shared.ts +6 -0
- package/src/providers/fetch-provider-catalog.ts +11 -0
- package/src/providers/gemini/client.ts +25 -8
- package/src/providers/inference/adapter-factory.ts +1 -1
- package/src/providers/jev/client.test.ts +10 -0
- package/src/providers/jev/client.ts +20 -4
- package/src/providers/model-catalog.ts +4 -3
- package/src/providers/openai/__tests__/chat-completions-extra-body.test.ts +94 -1
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +106 -1
- package/src/providers/openai/__tests__/chat-template-flatten.test.ts +24 -0
- package/src/providers/openai/__tests__/google-thought-signature.test.ts +1 -2
- package/src/providers/openai/chat-completions-provider.ts +100 -29
- package/src/providers/openai/google-thought-signature.ts +0 -6
- package/src/providers/openai/responses-provider.ts +23 -1
- package/src/providers/ratelimit.ts +1 -3
- package/src/providers/search-provider-catalog.ts +24 -0
- package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +24 -0
- package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +7 -1
- package/src/providers/speech-to-text/__tests__/vellum-managed-realtime.test.ts +50 -1
- package/src/providers/speech-to-text/deepgram-flux-frames.ts +11 -0
- package/src/providers/speech-to-text/vellum-managed-flux-realtime.ts +13 -0
- package/src/providers/types.ts +5 -0
- package/src/routes/control.ts +7 -5
- package/src/runtime/AGENTS.md +4 -2
- package/src/runtime/actor-trust-resolver.ts +3 -3
- package/src/runtime/agent-wake.ts +1 -1
- package/src/runtime/approval-message-composer.ts +3 -69
- package/src/runtime/assistant-event-hub.ts +73 -75
- package/src/runtime/assistant-event-publish-options.ts +7 -0
- package/src/runtime/assistant-event-targeting.ts +79 -0
- package/src/runtime/assistant-stream-state.ts +13 -100
- package/src/runtime/background-job-runner.ts +5 -5
- package/src/runtime/channel-retry-sweep.ts +1 -0
- package/src/runtime/guardian-action-message-composer.ts +8 -161
- package/src/runtime/guardian-reply-router.ts +28 -32
- package/src/runtime/http-types.ts +6 -0
- package/src/runtime/message-composer-types.ts +3 -61
- package/src/runtime/migrations/__tests__/vbundle-import-parity.test.ts +67 -1
- package/src/runtime/migrations/vbundle-builder.ts +24 -1
- package/src/runtime/migrations/vbundle-import-analyzer.ts +13 -4
- package/src/runtime/migrations/vbundle-import-policy.ts +10 -0
- package/src/runtime/migrations/vbundle-importer.ts +4 -1
- package/src/runtime/migrations/vbundle-streaming-importer.ts +4 -1
- package/src/runtime/migrations/vbundle-validator.ts +1 -0
- package/src/runtime/pending-interactions-mode-session.test.ts +62 -0
- package/src/runtime/pending-interactions.ts +29 -0
- package/src/runtime/routes/__tests__/client-history-routes.test.ts +104 -0
- package/src/runtime/routes/__tests__/image-generation-routes.test.ts +97 -0
- package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/roadmap-routes.test.ts +29 -6
- package/src/runtime/routes/browser-context.ts +31 -11
- package/src/runtime/routes/browser-routes.ts +43 -4
- package/src/runtime/routes/btw-routes.ts +9 -4
- package/src/runtime/routes/client-routes.ts +108 -0
- package/src/runtime/routes/conversation-management-routes.ts +4 -1
- package/src/runtime/routes/conversation-routes.ts +113 -27
- package/src/runtime/routes/credential-prompt-routes.ts +4 -3
- package/src/runtime/routes/credential-routes.ts +1 -2
- package/src/runtime/routes/desktop-setup-routes.test.ts +9 -18
- package/src/runtime/routes/desktop-setup-routes.ts +4 -6
- package/src/runtime/routes/documents-routes.ts +51 -0
- package/src/runtime/routes/email-routes.ts +3 -7
- package/src/runtime/routes/events-routes.ts +75 -54
- package/src/runtime/routes/identity-routes.ts +1 -5
- package/src/runtime/routes/image-generation-routes.ts +8 -14
- package/src/runtime/routes/inbound-message-handler.ts +35 -38
- package/src/runtime/routes/inbound-stages/backfill-sender-provenance.test.ts +168 -0
- package/src/runtime/routes/inbound-stages/backfill-sender-provenance.ts +96 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +38 -9
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +2 -5
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +2 -0
- package/src/runtime/routes/llm-context-normalization.ts +1 -7
- package/src/runtime/routes/migration-routes.ts +73 -2
- package/src/runtime/routes/notification-routes.ts +10 -0
- package/src/runtime/routes/oauth-providers.ts +7 -0
- package/src/runtime/routes/plugins-routes.ts +6 -0
- package/src/runtime/routes/roadmap-routes.ts +47 -8
- package/src/runtime/routes/secret-routes.ts +1 -1
- package/src/runtime/routes/settings-routes.ts +10 -4
- package/src/runtime/routes/watcher-routes.ts +1 -3
- package/src/runtime/trust-class.test.ts +7 -6
- package/src/runtime/trust-class.ts +9 -17
- package/src/schedule/__tests__/worker-control.test.ts +36 -5
- package/src/schedule/worker-control.ts +15 -5
- package/src/security/secure-keys.ts +53 -103
- package/src/skills/clawhub.ts +3 -8
- package/src/skills/managed-store.ts +1 -5
- package/src/stt/types.ts +4 -0
- package/src/subagent/notify.ts +54 -34
- package/src/subagent/parent-notification.ts +6 -0
- package/src/telemetry/__tests__/phone-call-funnel.test.ts +72 -0
- package/src/telemetry/phone-call-funnel.ts +144 -0
- package/src/tools/__tests__/tool-schema-root-combinator-guard.test.ts +81 -0
- package/src/tools/ask-question/ask-question-tool.ts +6 -4
- package/src/tools/browser/browser-execution.ts +22 -59
- package/src/tools/browser/cdp-client/__tests__/cdp-dom-helpers.test.ts +561 -39
- package/src/tools/browser/cdp-client/cdp-dom-helpers.ts +435 -39
- package/src/tools/browser/cdp-client/cdp-inspect-client.ts +1 -8
- package/src/tools/client-os.ts +15 -0
- package/src/tools/computer-use/skill-proxy-bridge.ts +1 -1
- package/src/tools/computer-use/target.ts +42 -0
- package/src/tools/document/document-tool.ts +13 -18
- package/src/tools/execution-target.ts +14 -16
- package/src/tools/host-terminal/host-shell.ts +4 -1
- package/src/tools/network/__tests__/web-fetch-tinyfish.test.ts +250 -0
- package/src/tools/network/__tests__/web-search.test.ts +203 -0
- package/src/tools/network/web-fetch.ts +274 -7
- package/src/tools/network/web-search.ts +468 -6
- package/src/tools/permission-checker.ts +3 -3
- package/src/tools/policy-context.ts +3 -1
- package/src/tools/skills/load.ts +2 -1
- package/src/tools/skills/sandbox-runner.ts +11 -2
- package/src/tools/skills/skill-script-runner.ts +7 -0
- package/src/tools/skills/skill-tool-factory.ts +15 -1
- package/src/tools/terminal/__tests__/safe-env.test.ts +11 -0
- package/src/tools/terminal/safe-env.ts +4 -0
- package/src/tools/terminal/shell.ts +2 -0
- package/src/tools/tool-approval-handler.ts +5 -1
- package/src/tools/tool-types.ts +3 -3
- package/src/tools/types.ts +8 -2
- package/src/util/__tests__/worker-process-command.test.ts +5 -4
- package/src/util/abort-reasons.ts +18 -26
- package/src/util/canonicalize-identity.test.ts +21 -0
- package/src/util/canonicalize-identity.ts +16 -0
- package/src/util/errors.ts +13 -3
- package/src/util/provider-error-patterns.ts +3 -0
- package/src/util/worker-process.ts +77 -35
- package/src/workspace/migrations/157-add-desktop-files-launcher.ts +94 -0
- package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +4 -10
- package/src/workspace/migrations/__tests__/157-add-desktop-files-launcher.test.ts +113 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/__tests__/computer-use-window-schema.test.ts +0 -24
- package/src/api/events/heartbeat-alert.ts +0 -20
- package/src/daemon/message-types/web-activity.ts +0 -65
- package/src/desktop/desktop-dependencies.test.ts +0 -131
- package/src/tools/computer-use/definitions.ts +0 -607
|
@@ -136,6 +136,34 @@ describe("resolveImageGenCredentials", () => {
|
|
|
136
136
|
expect(result.errorHint).toBeDefined();
|
|
137
137
|
expect(result.errorHint).toContain("OpenAI API key");
|
|
138
138
|
});
|
|
139
|
+
|
|
140
|
+
test("returns direct credentials for openrouter when key is present", async () => {
|
|
141
|
+
mockProviderKey = "openrouter-api-key";
|
|
142
|
+
|
|
143
|
+
const result = await resolveImageGenCredentials({
|
|
144
|
+
provider: "openrouter",
|
|
145
|
+
managed: false,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
expect(result.errorHint).toBeUndefined();
|
|
149
|
+
expect(result.credentials).toEqual({
|
|
150
|
+
type: "direct",
|
|
151
|
+
apiKey: "openrouter-api-key",
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("returns errorHint mentioning 'OpenRouter API key' when no key is set", async () => {
|
|
156
|
+
mockProviderKey = undefined;
|
|
157
|
+
|
|
158
|
+
const result = await resolveImageGenCredentials({
|
|
159
|
+
provider: "openrouter",
|
|
160
|
+
managed: false,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
expect(result.credentials).toBeUndefined();
|
|
164
|
+
expect(result.errorHint).toBeDefined();
|
|
165
|
+
expect(result.errorHint).toContain("OpenRouter API key");
|
|
166
|
+
});
|
|
139
167
|
});
|
|
140
168
|
});
|
|
141
169
|
|
|
@@ -173,6 +201,27 @@ describe("resolveImageGenRouting", () => {
|
|
|
173
201
|
).toEqual({ backendProvider: "openai", managed: false });
|
|
174
202
|
});
|
|
175
203
|
|
|
204
|
+
test("OpenRouter keeps ownership for google/ and openai/ slugs", () => {
|
|
205
|
+
expect(
|
|
206
|
+
resolveImageGenRouting({
|
|
207
|
+
provider: "openrouter",
|
|
208
|
+
model: "google/gemini-3.1-flash-image-preview",
|
|
209
|
+
}),
|
|
210
|
+
).toEqual({ backendProvider: "openrouter", managed: false });
|
|
211
|
+
expect(
|
|
212
|
+
resolveImageGenRouting(
|
|
213
|
+
{ provider: "openrouter", model: "google/gemini-3.1-flash-image-preview" },
|
|
214
|
+
"openai/gpt-image-2",
|
|
215
|
+
),
|
|
216
|
+
).toEqual({ backendProvider: "openrouter", managed: false });
|
|
217
|
+
expect(
|
|
218
|
+
resolveImageGenRouting(
|
|
219
|
+
{ provider: "openrouter", model: "google/gemini-3.1-flash-image-preview" },
|
|
220
|
+
"gpt-image-2",
|
|
221
|
+
),
|
|
222
|
+
).toEqual({ backendProvider: "openrouter", managed: false });
|
|
223
|
+
});
|
|
224
|
+
|
|
176
225
|
test("vellum with an unavailable platform is a hard error, not a BYOK fallback", async () => {
|
|
177
226
|
// Billing rule: an explicit vellum choice never silently spends a stored
|
|
178
227
|
// provider key.
|
|
@@ -17,8 +17,10 @@ interface GenerateCall {
|
|
|
17
17
|
|
|
18
18
|
let geminiCalls: GenerateCall[] = [];
|
|
19
19
|
let openaiCalls: GenerateCall[] = [];
|
|
20
|
+
let openrouterCalls: GenerateCall[] = [];
|
|
20
21
|
let geminiErrorCalls: unknown[] = [];
|
|
21
22
|
let openaiErrorCalls: unknown[] = [];
|
|
23
|
+
let openrouterErrorCalls: unknown[] = [];
|
|
22
24
|
|
|
23
25
|
let geminiResult: ImageGenerationResult = {
|
|
24
26
|
images: [],
|
|
@@ -28,6 +30,10 @@ let openaiResult: ImageGenerationResult = {
|
|
|
28
30
|
images: [],
|
|
29
31
|
resolvedModel: "openai-mock",
|
|
30
32
|
};
|
|
33
|
+
let openrouterResult: ImageGenerationResult = {
|
|
34
|
+
images: [],
|
|
35
|
+
resolvedModel: "openrouter-mock",
|
|
36
|
+
};
|
|
31
37
|
|
|
32
38
|
// ---------------------------------------------------------------------------
|
|
33
39
|
// Mock underlying services — must run before the module under test is imported
|
|
@@ -61,6 +67,20 @@ mock.module("../media/openai-image-service.js", () => ({
|
|
|
61
67
|
},
|
|
62
68
|
}));
|
|
63
69
|
|
|
70
|
+
mock.module("../media/openrouter-image-service.js", () => ({
|
|
71
|
+
generateImageOpenRouter: async (
|
|
72
|
+
credentials: ImageGenCredentials,
|
|
73
|
+
request: ImageGenerationRequest,
|
|
74
|
+
): Promise<ImageGenerationResult> => {
|
|
75
|
+
openrouterCalls.push({ credentials, request });
|
|
76
|
+
return openrouterResult;
|
|
77
|
+
},
|
|
78
|
+
mapOpenRouterError: (error: unknown): string => {
|
|
79
|
+
openrouterErrorCalls.push(error);
|
|
80
|
+
return "openrouter-mapped";
|
|
81
|
+
},
|
|
82
|
+
}));
|
|
83
|
+
|
|
64
84
|
// Import after mocking
|
|
65
85
|
import {
|
|
66
86
|
generateImage,
|
|
@@ -90,8 +110,10 @@ describe("image-service dispatcher", () => {
|
|
|
90
110
|
beforeEach(() => {
|
|
91
111
|
geminiCalls = [];
|
|
92
112
|
openaiCalls = [];
|
|
113
|
+
openrouterCalls = [];
|
|
93
114
|
geminiErrorCalls = [];
|
|
94
115
|
openaiErrorCalls = [];
|
|
116
|
+
openrouterErrorCalls = [];
|
|
95
117
|
geminiResult = {
|
|
96
118
|
images: [{ mimeType: "image/png", dataBase64: "gemini-bytes" }],
|
|
97
119
|
resolvedModel: "gemini-3.1-flash-image-preview",
|
|
@@ -100,6 +122,10 @@ describe("image-service dispatcher", () => {
|
|
|
100
122
|
images: [{ mimeType: "image/png", dataBase64: "openai-bytes" }],
|
|
101
123
|
resolvedModel: "gpt-image-2",
|
|
102
124
|
};
|
|
125
|
+
openrouterResult = {
|
|
126
|
+
images: [{ mimeType: "image/png", dataBase64: "openrouter-bytes" }],
|
|
127
|
+
resolvedModel: "google/gemini-3.1-flash-image-preview",
|
|
128
|
+
};
|
|
103
129
|
});
|
|
104
130
|
|
|
105
131
|
test("generateImage('gemini', ...) delegates to the Gemini implementation", async () => {
|
|
@@ -131,6 +157,17 @@ describe("image-service dispatcher", () => {
|
|
|
131
157
|
expect(mapped).toBe("gemini-mapped");
|
|
132
158
|
});
|
|
133
159
|
|
|
160
|
+
test("generateImage('openrouter', ...) delegates to the OpenRouter implementation", async () => {
|
|
161
|
+
const result = await generateImage("openrouter", directCreds, request);
|
|
162
|
+
|
|
163
|
+
expect(openrouterCalls).toHaveLength(1);
|
|
164
|
+
expect(openrouterCalls[0]?.credentials).toBe(directCreds);
|
|
165
|
+
expect(openrouterCalls[0]?.request).toBe(request);
|
|
166
|
+
expect(geminiCalls).toHaveLength(0);
|
|
167
|
+
expect(openaiCalls).toHaveLength(0);
|
|
168
|
+
expect(result).toEqual(openrouterResult);
|
|
169
|
+
});
|
|
170
|
+
|
|
134
171
|
test("mapImageGenError('openai', err) delegates to mapOpenAIError", () => {
|
|
135
172
|
const err = new Error("kapow");
|
|
136
173
|
const mapped = mapImageGenError("openai", err);
|
|
@@ -139,6 +176,14 @@ describe("image-service dispatcher", () => {
|
|
|
139
176
|
expect(geminiErrorCalls).toEqual([]);
|
|
140
177
|
expect(mapped).toBe("openai-mapped");
|
|
141
178
|
});
|
|
179
|
+
|
|
180
|
+
test("mapImageGenError('openrouter', err) delegates to mapOpenRouterError", () => {
|
|
181
|
+
const err = new Error("or-boom");
|
|
182
|
+
const mapped = mapImageGenError("openrouter", err);
|
|
183
|
+
|
|
184
|
+
expect(openrouterErrorCalls).toEqual([err]);
|
|
185
|
+
expect(mapped).toBe("openrouter-mapped");
|
|
186
|
+
});
|
|
142
187
|
});
|
|
143
188
|
|
|
144
189
|
describe("providerForModel", () => {
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
abortedToolResultText,
|
|
22
22
|
CANCELLED_TOOL_RESULT_TEXT,
|
|
23
23
|
createAbortReason,
|
|
24
|
+
INTERRUPTED_TURN_NOTE_TEXT,
|
|
24
25
|
PREEMPTED_TOOL_RESULT_TEXT,
|
|
25
26
|
} from "../util/abort-reasons.js";
|
|
26
27
|
|
|
@@ -378,8 +379,8 @@ describe("interruptRunningTurn", () => {
|
|
|
378
379
|
|
|
379
380
|
test("arms the interrupted-turn note when the abort caught no tool call", async () => {
|
|
380
381
|
// The abort landed during the provider call, so there is no `tool_use` to
|
|
381
|
-
// answer
|
|
382
|
-
//
|
|
382
|
+
// answer. The note on the interrupting user message is the only thing that
|
|
383
|
+
// tells the model what happened.
|
|
383
384
|
const turn = registerBusyTurn({
|
|
384
385
|
messages: [
|
|
385
386
|
{ role: "user", content: [{ type: "text", text: "research this" }] },
|
|
@@ -393,9 +394,9 @@ describe("interruptRunningTurn", () => {
|
|
|
393
394
|
expect(persisted).toEqual([]);
|
|
394
395
|
});
|
|
395
396
|
|
|
396
|
-
test("
|
|
397
|
-
// The
|
|
398
|
-
//
|
|
397
|
+
test("arms the note alongside the fact-only result of a stopped tool call", async () => {
|
|
398
|
+
// The two texts do different jobs: the `tool_result` states what happened
|
|
399
|
+
// to that one call, the note says what to do about the interruption.
|
|
399
400
|
const turn = registerBusyTurn({
|
|
400
401
|
messages: [assistantWithToolUse("tool-1")],
|
|
401
402
|
});
|
|
@@ -404,13 +405,36 @@ describe("interruptRunningTurn", () => {
|
|
|
404
405
|
|
|
405
406
|
expect(persisted).toHaveLength(1);
|
|
406
407
|
expect(persisted[0].content).toContain(PREEMPTED_TOOL_RESULT_TEXT);
|
|
407
|
-
expect(turn.conversation.pendingInterruptNote).toBe(
|
|
408
|
+
expect(turn.conversation.pendingInterruptNote).toBe(true);
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
test("arms exactly one note however many parallel calls were stopped", async () => {
|
|
412
|
+
// Every stopped call gets its own fact-only result; the behavior
|
|
413
|
+
// instruction is read once, off the interrupting message.
|
|
414
|
+
const turn = registerBusyTurn({
|
|
415
|
+
messages: [assistantWithToolUse("tool-1", "tool-2", "tool-3")],
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
await interruptRunningTurn(turn.conversation, { origin: "test" });
|
|
419
|
+
|
|
420
|
+
const results = JSON.parse(persisted[0].content) as Array<{
|
|
421
|
+
tool_use_id: string;
|
|
422
|
+
content: string;
|
|
423
|
+
}>;
|
|
424
|
+
expect(results.map((block) => block.tool_use_id)).toEqual([
|
|
425
|
+
"tool-1",
|
|
426
|
+
"tool-2",
|
|
427
|
+
"tool-3",
|
|
428
|
+
]);
|
|
429
|
+
for (const block of results) {
|
|
430
|
+
expect(block.content).toBe(PREEMPTED_TOOL_RESULT_TEXT);
|
|
431
|
+
}
|
|
432
|
+
expect(turn.conversation.pendingInterruptNote).toBe(true);
|
|
408
433
|
});
|
|
409
434
|
|
|
410
|
-
test("
|
|
435
|
+
test("arms the note when the loop wrote its own preempted result", async () => {
|
|
411
436
|
// The agent loop's abort handler unwound cleanly and answered the batch
|
|
412
|
-
// itself, so the repair finds nothing to do
|
|
413
|
-
// there.
|
|
437
|
+
// itself, so the repair finds nothing to do. The note is owed all the same.
|
|
414
438
|
const turn = registerBusyTurn({
|
|
415
439
|
messages: [
|
|
416
440
|
assistantWithToolUse("tool-1"),
|
|
@@ -421,12 +445,10 @@ describe("interruptRunningTurn", () => {
|
|
|
421
445
|
await interruptRunningTurn(turn.conversation, { origin: "test" });
|
|
422
446
|
|
|
423
447
|
expect(persisted).toEqual([]);
|
|
424
|
-
expect(turn.conversation.pendingInterruptNote).toBe(
|
|
448
|
+
expect(turn.conversation.pendingInterruptNote).toBe(true);
|
|
425
449
|
});
|
|
426
450
|
|
|
427
451
|
test("arms the note when the cut-off tool call was an ordinary cancel", async () => {
|
|
428
|
-
// A tail answered with the plain cancel wording is a stop the user made
|
|
429
|
-
// earlier, not this interrupt's notice, so the note is still owed.
|
|
430
452
|
const turn = registerBusyTurn({
|
|
431
453
|
messages: [
|
|
432
454
|
assistantWithToolUse("tool-1"),
|
|
@@ -633,6 +655,9 @@ describe("interruptRunningTurn", () => {
|
|
|
633
655
|
// Armed instead, so the drain that runs the queued message repairs it.
|
|
634
656
|
expect(turn.conversation.pendingInterruptRepair).toBe(true);
|
|
635
657
|
expect(turn.activityEvents).toEqual([]);
|
|
658
|
+
// The message joins the queue rather than replacing the turn, so it is not
|
|
659
|
+
// the message that interrupted anything and carries no note.
|
|
660
|
+
expect(turn.conversation.pendingInterruptNote).toBe(false);
|
|
636
661
|
});
|
|
637
662
|
|
|
638
663
|
test("a second interrupt stops the turn the first one started", async () => {
|
|
@@ -932,7 +957,7 @@ describe("repairInterruptedToolUseBlocks", () => {
|
|
|
932
957
|
} as unknown as Conversation;
|
|
933
958
|
}
|
|
934
959
|
|
|
935
|
-
test("writes one result per abandoned call
|
|
960
|
+
test("writes one fact-only result per abandoned call", async () => {
|
|
936
961
|
const messages: Message[] = [assistantWithToolUse("tool-1", "tool-2")];
|
|
937
962
|
|
|
938
963
|
await repairInterruptedToolUseBlocks(fakeConversation(messages), {
|
|
@@ -961,31 +986,6 @@ describe("repairInterruptedToolUseBlocks", () => {
|
|
|
961
986
|
);
|
|
962
987
|
});
|
|
963
988
|
|
|
964
|
-
test("reports the preempted result it wrote", async () => {
|
|
965
|
-
const messages: Message[] = [assistantWithToolUse("tool-1")];
|
|
966
|
-
|
|
967
|
-
const result = await repairInterruptedToolUseBlocks(
|
|
968
|
-
fakeConversation(messages),
|
|
969
|
-
{ force: true },
|
|
970
|
-
);
|
|
971
|
-
|
|
972
|
-
expect(result.preemptedToolResultOnTail).toBe(true);
|
|
973
|
-
});
|
|
974
|
-
|
|
975
|
-
test("reports no preempted result on a history that ends on plain text", async () => {
|
|
976
|
-
const messages: Message[] = [
|
|
977
|
-
{ role: "user", content: [{ type: "text", text: "hi" }] },
|
|
978
|
-
{ role: "assistant", content: [{ type: "text", text: "hello" }] },
|
|
979
|
-
];
|
|
980
|
-
|
|
981
|
-
const result = await repairInterruptedToolUseBlocks(
|
|
982
|
-
fakeConversation(messages),
|
|
983
|
-
{ force: true },
|
|
984
|
-
);
|
|
985
|
-
|
|
986
|
-
expect(result.preemptedToolResultOnTail).toBe(false);
|
|
987
|
-
});
|
|
988
|
-
|
|
989
989
|
test("adds nothing when the loop already wrote its own results", async () => {
|
|
990
990
|
// The agent loop's abort handler synthesizes results of its own when it
|
|
991
991
|
// unwinds cleanly, so exactly one result per call exists either way.
|
|
@@ -994,14 +994,12 @@ describe("repairInterruptedToolUseBlocks", () => {
|
|
|
994
994
|
toolResult("tool-1", PREEMPTED_TOOL_RESULT_TEXT),
|
|
995
995
|
];
|
|
996
996
|
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
);
|
|
997
|
+
await repairInterruptedToolUseBlocks(fakeConversation(messages), {
|
|
998
|
+
force: true,
|
|
999
|
+
});
|
|
1001
1000
|
|
|
1002
1001
|
expect(messages).toHaveLength(2);
|
|
1003
1002
|
expect(persisted).toEqual([]);
|
|
1004
|
-
expect(result.preemptedToolResultOnTail).toBe(true);
|
|
1005
1003
|
});
|
|
1006
1004
|
|
|
1007
1005
|
test("does nothing on a history that ends on an ordinary assistant reply", async () => {
|
|
@@ -1083,3 +1081,19 @@ describe("abortedToolResultText", () => {
|
|
|
1083
1081
|
);
|
|
1084
1082
|
});
|
|
1085
1083
|
});
|
|
1084
|
+
|
|
1085
|
+
describe("the interrupt texts", () => {
|
|
1086
|
+
test("the tool result states the fact and nothing more", () => {
|
|
1087
|
+
// Repeated once per stopped call in a parallel batch, so an instruction
|
|
1088
|
+
// here is read as many times as the batch was wide.
|
|
1089
|
+
expect(PREEMPTED_TOOL_RESULT_TEXT).toBe(
|
|
1090
|
+
"Stopped early because the user sent a new message. This call may have completed anyway; check before repeating it.",
|
|
1091
|
+
);
|
|
1092
|
+
});
|
|
1093
|
+
|
|
1094
|
+
test("the note carries the behavior and leads with the reply", () => {
|
|
1095
|
+
expect(INTERRUPTED_TURN_NOTE_TEXT).toBe(
|
|
1096
|
+
"<interrupted_turn>The user sent this while you were still working, so that work stopped. Reply right away, in a line or two, before any more thinking or tool calls, so the user knows you heard them. Then pick the earlier work back up only if it is still wanted. Never apologize for or mention the interruption.</interrupted_turn>",
|
|
1097
|
+
);
|
|
1098
|
+
});
|
|
1099
|
+
});
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* metadata is what rebuilds the identical block on every later load.
|
|
2
|
+
* The message that interrupted a turn carries the note saying what to do about
|
|
3
|
+
* the interruption, on its LLM-facing content only: the persisted row stays
|
|
4
|
+
* exactly what the user typed, so no client renders it, and
|
|
5
|
+
* `interruptedPriorTurn` in the row's metadata is what rebuilds the identical
|
|
6
|
+
* block on every later load.
|
|
8
7
|
*/
|
|
9
8
|
import { beforeEach, describe, expect, test } from "bun:test";
|
|
10
9
|
|
|
@@ -234,12 +234,12 @@ describe("LLM catalog parity: daemon vs client", () => {
|
|
|
234
234
|
});
|
|
235
235
|
|
|
236
236
|
test("jev-latest opts out of chat text generation", () => {
|
|
237
|
-
expect(catalogModelSupportsText("
|
|
237
|
+
expect(catalogModelSupportsText("typesafe", "jev-latest")).toBe(false);
|
|
238
238
|
expect(catalogModelSupportsText("anthropic", "claude-opus-4-8")).toBe(true);
|
|
239
239
|
expect(catalogModelSupportsText("openai-compatible", "local-model")).toBe(
|
|
240
240
|
true,
|
|
241
241
|
);
|
|
242
|
-
expect(DEFAULT_PROVIDER_CHOICES).not.toContain("
|
|
242
|
+
expect(DEFAULT_PROVIDER_CHOICES).not.toContain("typesafe");
|
|
243
243
|
expect(DEFAULT_PROVIDER_CHOICES).toContain("poolside");
|
|
244
244
|
});
|
|
245
245
|
|
|
@@ -21,6 +21,7 @@ import { setConfig } from "./helpers/set-config.js";
|
|
|
21
21
|
|
|
22
22
|
let mockGeminiKey: string | undefined = "test-gemini-key";
|
|
23
23
|
let mockOpenAIKey: string | undefined = "test-openai-key";
|
|
24
|
+
let mockOpenRouterKey: string | undefined = "test-openrouter-key";
|
|
24
25
|
let mockGenerateResult = {
|
|
25
26
|
images: [{ mimeType: "image/png", dataBase64: "generated-data" }],
|
|
26
27
|
text: "A beautiful image",
|
|
@@ -40,7 +41,7 @@ let mockGenerateDelayMs = 0;
|
|
|
40
41
|
*/
|
|
41
42
|
function seedImageGenService(
|
|
42
43
|
overrides: {
|
|
43
|
-
provider?: "vellum" | "gemini" | "openai";
|
|
44
|
+
provider?: "vellum" | "gemini" | "openai" | "openrouter";
|
|
44
45
|
model?: string;
|
|
45
46
|
} = {},
|
|
46
47
|
): void {
|
|
@@ -64,6 +65,9 @@ mock.module("../security/secure-keys.js", () => ({
|
|
|
64
65
|
if (provider === "openai") {
|
|
65
66
|
return mockOpenAIKey;
|
|
66
67
|
}
|
|
68
|
+
if (provider === "openrouter") {
|
|
69
|
+
return mockOpenRouterKey;
|
|
70
|
+
}
|
|
67
71
|
return undefined;
|
|
68
72
|
},
|
|
69
73
|
}));
|
|
@@ -143,6 +147,7 @@ const CONFIG_DIR = join(
|
|
|
143
147
|
beforeEach(() => {
|
|
144
148
|
mockGeminiKey = "test-gemini-key";
|
|
145
149
|
mockOpenAIKey = "test-openai-key";
|
|
150
|
+
mockOpenRouterKey = "test-openrouter-key";
|
|
146
151
|
seedImageGenService();
|
|
147
152
|
mockGenerateResult = {
|
|
148
153
|
images: [{ mimeType: "image/png", dataBase64: "generated-data" }],
|
|
@@ -308,6 +313,49 @@ describe("image-studio skill script wrapper", () => {
|
|
|
308
313
|
expect(result.content).not.toContain("No Gemini API key");
|
|
309
314
|
});
|
|
310
315
|
|
|
316
|
+
test("OpenRouter accepts an arbitrary slug and keeps OpenRouter ownership", async () => {
|
|
317
|
+
seedImageGenService({
|
|
318
|
+
provider: "openrouter",
|
|
319
|
+
model: "google/gemini-3.1-flash-image-preview",
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
const result = await run(
|
|
323
|
+
{ prompt: "a nebula", model: "black-forest-labs/flux" },
|
|
324
|
+
fakeContext,
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
expect(result.isError).toBe(false);
|
|
328
|
+
expect(lastGenerateProvider).toBe("openrouter");
|
|
329
|
+
expect(lastGenerateCredentials).toEqual({
|
|
330
|
+
type: "direct",
|
|
331
|
+
apiKey: "test-openrouter-key",
|
|
332
|
+
});
|
|
333
|
+
expect(lastGenerateRequest?.model).toBe("black-forest-labs/flux");
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
test("OpenRouter qualifies a built-in alias and keeps OpenRouter ownership", async () => {
|
|
337
|
+
seedImageGenService({ provider: "openrouter" });
|
|
338
|
+
|
|
339
|
+
const result = await run({ prompt: "a robot", model: "openai" }, fakeContext);
|
|
340
|
+
|
|
341
|
+
expect(result.isError).toBe(false);
|
|
342
|
+
expect(lastGenerateProvider).toBe("openrouter");
|
|
343
|
+
expect(lastGenerateRequest?.model).toBe("openai/gpt-image-2");
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
test("OpenRouter returns a missing-key hint without falling back to Gemini", async () => {
|
|
347
|
+
seedImageGenService({ provider: "openrouter" });
|
|
348
|
+
mockOpenRouterKey = undefined;
|
|
349
|
+
mockGeminiKey = "gemini-key-should-not-be-used";
|
|
350
|
+
|
|
351
|
+
const result = await run({ prompt: "a cat" }, fakeContext);
|
|
352
|
+
|
|
353
|
+
expect(result.isError).toBe(true);
|
|
354
|
+
expect(result.content).toContain("OpenRouter API key");
|
|
355
|
+
expect(result.content).not.toContain("No Gemini API key");
|
|
356
|
+
expect(lastGenerateProvider).toBeNull();
|
|
357
|
+
});
|
|
358
|
+
|
|
311
359
|
test("explicit model override routes to owning provider (gemini config → openai call)", async () => {
|
|
312
360
|
// Config says the user's default provider is gemini, but the LLM
|
|
313
361
|
// explicitly requests a gpt-* model. The tool must dispatch to OpenAI
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { actorAuthorProvenance } from "../daemon/message-provenance.js";
|
|
4
|
+
import type { TrustContext } from "../daemon/trust-context-types.js";
|
|
5
|
+
import {
|
|
6
|
+
addMessage,
|
|
7
|
+
createConversation,
|
|
8
|
+
getMessageById,
|
|
9
|
+
parseMessageMetadata,
|
|
10
|
+
provenanceFromTrustContext,
|
|
11
|
+
} from "../persistence/conversation-crud.js";
|
|
12
|
+
import { getDb } from "../persistence/db-connection.js";
|
|
13
|
+
import { initializeDb } from "../persistence/db-init.js";
|
|
14
|
+
|
|
15
|
+
await initializeDb();
|
|
16
|
+
|
|
17
|
+
const ALICE: TrustContext = {
|
|
18
|
+
sourceChannel: "slack",
|
|
19
|
+
trustClass: "trusted_contact",
|
|
20
|
+
requesterIdentifier: "@alice",
|
|
21
|
+
requesterContactId: "contact-alice",
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
async function persistWith(
|
|
25
|
+
role: "user" | "assistant",
|
|
26
|
+
metadata: Record<string, unknown>,
|
|
27
|
+
): Promise<ReturnType<typeof parseMessageMetadata>> {
|
|
28
|
+
const conversation = createConversation("provenance-contact-id");
|
|
29
|
+
const row = await addMessage(conversation.id, role, "hello", { metadata });
|
|
30
|
+
const stored = getMessageById(row.id, conversation.id);
|
|
31
|
+
return parseMessageMetadata(stored?.metadata ?? null);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe("message provenance contact id", () => {
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
const db = getDb();
|
|
37
|
+
db.run(`DELETE FROM messages`);
|
|
38
|
+
db.run(`DELETE FROM conversations`);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("a person's own row names them as its author", async () => {
|
|
42
|
+
const metadata = await persistWith("user", {
|
|
43
|
+
...provenanceFromTrustContext(ALICE),
|
|
44
|
+
...actorAuthorProvenance(ALICE),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
expect(metadata?.provenanceTrustClass).toBe("trusted_contact");
|
|
48
|
+
expect(metadata?.provenanceContactId).toBe("contact-alice");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("a row written during their turn carries the turn's trust but no author", async () => {
|
|
52
|
+
// The assistant's replies, tool results, and notices are stamped with the
|
|
53
|
+
// turn's provenance alone.
|
|
54
|
+
const metadata = await persistWith(
|
|
55
|
+
"assistant",
|
|
56
|
+
provenanceFromTrustContext(ALICE),
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
expect(metadata?.provenanceTrustClass).toBe("trusted_contact");
|
|
60
|
+
expect(metadata?.provenanceContactId).toBeUndefined();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("an actor with no resolved contact names no author", () => {
|
|
64
|
+
expect(
|
|
65
|
+
actorAuthorProvenance({ sourceChannel: "slack", trustClass: "unknown" }),
|
|
66
|
+
).toEqual({});
|
|
67
|
+
expect(actorAuthorProvenance(undefined)).toEqual({});
|
|
68
|
+
});
|
|
69
|
+
});
|