@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
|
@@ -206,19 +206,26 @@ describe("spawnScheduleWorkerProcess", () => {
|
|
|
206
206
|
}
|
|
207
207
|
});
|
|
208
208
|
|
|
209
|
-
test("
|
|
209
|
+
test("spawns when the PID file names an unrelated live process", async () => {
|
|
210
210
|
writeFileSync(pidPath, String(process.pid));
|
|
211
211
|
let spawned = false;
|
|
212
212
|
const restore = stubBunSpawn(() => {
|
|
213
213
|
spawned = true;
|
|
214
|
-
|
|
214
|
+
writeFileSync(pidPath, "4242");
|
|
215
|
+
return {
|
|
216
|
+
unref: () => {},
|
|
217
|
+
kill: () => {},
|
|
218
|
+
pid: 4242,
|
|
219
|
+
exited: neverExits(),
|
|
220
|
+
};
|
|
215
221
|
});
|
|
216
222
|
try {
|
|
217
223
|
const result = await spawnScheduleWorkerProcess({
|
|
218
|
-
pidWaitTimeoutMs:
|
|
224
|
+
pidWaitTimeoutMs: 1_000,
|
|
225
|
+
pidPollIntervalMs: 10,
|
|
219
226
|
});
|
|
220
|
-
expect(result).toEqual({ pid:
|
|
221
|
-
expect(spawned).toBe(
|
|
227
|
+
expect(result).toEqual({ pid: 4242, alreadyRunning: false });
|
|
228
|
+
expect(spawned).toBe(true);
|
|
222
229
|
} finally {
|
|
223
230
|
restore();
|
|
224
231
|
}
|
|
@@ -240,6 +247,11 @@ describe("probeScheduleWorker", () => {
|
|
|
240
247
|
}
|
|
241
248
|
});
|
|
242
249
|
|
|
250
|
+
test("reports not_running when the PID file names a live process that is not this worker", () => {
|
|
251
|
+
writeFileSync(pidPath, String(process.pid));
|
|
252
|
+
expect(probeScheduleWorker()).toEqual({ status: "not_running" });
|
|
253
|
+
});
|
|
254
|
+
|
|
243
255
|
test("reports running (not throws) when the process exists but is not signalable (EPERM)", () => {
|
|
244
256
|
writeFileSync(pidPath, "4321");
|
|
245
257
|
const restore = stubProcessKill(new Set(), new Set([4321]));
|
|
@@ -256,6 +268,25 @@ describe("stopScheduleWorkerProcess", () => {
|
|
|
256
268
|
expect(stopScheduleWorkerProcess()).toEqual({ status: "not_running" });
|
|
257
269
|
});
|
|
258
270
|
|
|
271
|
+
test("does not signal a recycled PID that is not this worker", () => {
|
|
272
|
+
writeFileSync(pidPath, String(process.pid));
|
|
273
|
+
const signalled: Array<[number, string | number | undefined]> = [];
|
|
274
|
+
const original = process.kill.bind(process);
|
|
275
|
+
process.kill = ((pid: number, signal?: string | number) => {
|
|
276
|
+
if ((signal ?? 0) === 0) {
|
|
277
|
+
return original(pid, signal);
|
|
278
|
+
}
|
|
279
|
+
signalled.push([pid, signal]);
|
|
280
|
+
return true;
|
|
281
|
+
}) as typeof process.kill;
|
|
282
|
+
try {
|
|
283
|
+
expect(stopScheduleWorkerProcess()).toEqual({ status: "not_running" });
|
|
284
|
+
expect(signalled).toEqual([]);
|
|
285
|
+
} finally {
|
|
286
|
+
process.kill = original;
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
|
|
259
290
|
test("signals a running worker and reports its prior state", () => {
|
|
260
291
|
writeFileSync(pidPath, "4321");
|
|
261
292
|
const signalled: Array<[number, string | number | undefined]> = [];
|
|
@@ -19,6 +19,8 @@ import {
|
|
|
19
19
|
type WorkerProcessStatus,
|
|
20
20
|
} from "../util/worker-process.js";
|
|
21
21
|
|
|
22
|
+
const SCHEDULE_WORKER_ENTRY = new URL("./worker.ts", import.meta.url);
|
|
23
|
+
|
|
22
24
|
const log = getLogger("schedule-worker-control");
|
|
23
25
|
|
|
24
26
|
/**
|
|
@@ -42,11 +44,15 @@ export function setScheduleWorkerAdministrativelyStopped(value: boolean): void {
|
|
|
42
44
|
|
|
43
45
|
/**
|
|
44
46
|
* Inspect the PID file to determine whether the schedule worker process is
|
|
45
|
-
* alive. A stale PID file (pointing at a dead process
|
|
46
|
-
* reported as not_running.
|
|
47
|
+
* alive. A stale PID file (pointing at a dead process, or at a live process
|
|
48
|
+
* that is not this worker) is cleaned up and reported as not_running.
|
|
47
49
|
*/
|
|
48
50
|
export function probeScheduleWorker(): WorkerProcessStatus {
|
|
49
|
-
return probeWorkerPidFile(
|
|
51
|
+
return probeWorkerPidFile(
|
|
52
|
+
getScheduleWorkerPidPath(),
|
|
53
|
+
SCHEDULE_WORKER_ENTRY,
|
|
54
|
+
"schedule",
|
|
55
|
+
);
|
|
50
56
|
}
|
|
51
57
|
|
|
52
58
|
export class ScheduleWorkerSpawnError extends WorkerProcessSpawnError {}
|
|
@@ -95,7 +101,7 @@ async function spawnScheduleWorkerProcessUncoalesced(
|
|
|
95
101
|
try {
|
|
96
102
|
return await spawnWorkerProcess({
|
|
97
103
|
pidPath: getScheduleWorkerPidPath(),
|
|
98
|
-
entry:
|
|
104
|
+
entry: SCHEDULE_WORKER_ENTRY,
|
|
99
105
|
packagedEntry: "schedule",
|
|
100
106
|
workerLabel: "Schedule worker",
|
|
101
107
|
options: opts,
|
|
@@ -114,7 +120,11 @@ async function spawnScheduleWorkerProcessUncoalesced(
|
|
|
114
120
|
* `process.kill` itself fails (e.g. EPERM) — a not-running worker is a no-op.
|
|
115
121
|
*/
|
|
116
122
|
export function stopScheduleWorkerProcess(): WorkerProcessStatus {
|
|
117
|
-
return stopWorkerProcess(
|
|
123
|
+
return stopWorkerProcess(
|
|
124
|
+
getScheduleWorkerPidPath(),
|
|
125
|
+
SCHEDULE_WORKER_ENTRY,
|
|
126
|
+
"schedule",
|
|
127
|
+
);
|
|
118
128
|
}
|
|
119
129
|
|
|
120
130
|
/**
|
|
@@ -1,22 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Unified secure key storage
|
|
2
|
+
* Unified secure key storage: single-backend routing through CredentialBackend
|
|
3
3
|
* adapters.
|
|
4
4
|
*
|
|
5
5
|
* Backend selection (`resolveBackendAsync`) is the single async decision point:
|
|
6
|
-
* 1. CES RPC (primary) -
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* resolution they discover the CES bootstrap socket
|
|
13
|
-
* (`CES_BOOTSTRAP_SOCKET_DIR`) and cache
|
|
14
|
-
* the connection.
|
|
15
|
-
* 3. CES HTTP - containerized failover when IPC is unavailable
|
|
6
|
+
* 1. CES RPC (primary) - connect to the CES bootstrap socket and talk RPC.
|
|
7
|
+
* `openCesRpcSession` is the one client constructor. The assistant
|
|
8
|
+
* claims session ownership at boot (`startCes`) and hands CES the
|
|
9
|
+
* assistant API key. Child processes open a session on first credential
|
|
10
|
+
* read only when this process has no client and no reconnect owner.
|
|
11
|
+
* 2. CES HTTP - containerized failover when IPC is unavailable
|
|
16
12
|
* (`IS_CONTAINERIZED` + `CES_CREDENTIAL_URL`). Used if the assistant's
|
|
17
13
|
* bootstrap RPC transport is down, or if a process with HTTP env could
|
|
18
14
|
* not open the socket.
|
|
19
|
-
*
|
|
15
|
+
* 3. Encrypted file store (fallback) - used when CES is unavailable locally.
|
|
20
16
|
*
|
|
21
17
|
* All operations (reads, writes, lists, deletes) go to exactly one backend.
|
|
22
18
|
* There are no cross-store fallbacks or merges. The only transport failover is
|
|
@@ -35,14 +31,11 @@ import type {
|
|
|
35
31
|
|
|
36
32
|
import { getIsContainerized } from "../config/env-registry.js";
|
|
37
33
|
import {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
} from "../credential-execution/
|
|
34
|
+
openCesRpcSession,
|
|
35
|
+
reconnectCesRpcSession,
|
|
36
|
+
} from "../credential-execution/ces-connect.js";
|
|
37
|
+
import { type CesClient } from "../credential-execution/client.js";
|
|
41
38
|
import { discoverCes } from "../credential-execution/executable-discovery.js";
|
|
42
|
-
import {
|
|
43
|
-
CesUnavailableError,
|
|
44
|
-
createCesProcessManager,
|
|
45
|
-
} from "../credential-execution/process-manager.js";
|
|
46
39
|
import { getAnyProviderEnvVar } from "../providers/provider-env-vars.js";
|
|
47
40
|
import { getLogger } from "../util/logger.js";
|
|
48
41
|
import { getProtectedDir } from "../util/platform.js";
|
|
@@ -85,11 +78,11 @@ let _resolvedBackend: CredentialBackend | undefined;
|
|
|
85
78
|
let _resolvePromise: Promise<CredentialBackend> | undefined;
|
|
86
79
|
|
|
87
80
|
/**
|
|
88
|
-
* In-flight
|
|
81
|
+
* In-flight CES RPC session promise for processes that did not call startCes().
|
|
89
82
|
*
|
|
90
83
|
* Workers and CLI subprocesses never call startCes(). When they hit
|
|
91
84
|
* resolveBackendAsync() with no _cesClient and no _cesReconnect
|
|
92
|
-
* (
|
|
85
|
+
* (assistant-boot only), this promise memoizes `openCesRpcSession` so
|
|
93
86
|
* concurrent credential reads in the same process share a single
|
|
94
87
|
* connect+handshake rather than racing.
|
|
95
88
|
*/
|
|
@@ -206,10 +199,9 @@ function getEncryptedStoreBackend(): CredentialBackend {
|
|
|
206
199
|
* Resolve the primary credential backend for this process (async).
|
|
207
200
|
*
|
|
208
201
|
* Priority:
|
|
209
|
-
* 1. CES RPC client
|
|
210
|
-
* 2.
|
|
211
|
-
* 3.
|
|
212
|
-
* 4. Encrypted file store: local fallback when CES is unavailable.
|
|
202
|
+
* 1. CES RPC: live client, or open one via `openCesRpcSession`.
|
|
203
|
+
* 2. Containerized + CES_CREDENTIAL_URL: CES HTTP, only if IPC is down.
|
|
204
|
+
* 3. Encrypted file store: local fallback when CES is unavailable.
|
|
213
205
|
*
|
|
214
206
|
* Once resolved, the backend is cached. If it becomes unavailable (e.g. the
|
|
215
207
|
* CES transport dies), we attempt to reconnect via `_cesReconnect` rather
|
|
@@ -415,19 +407,19 @@ export async function attemptCesReconnection(
|
|
|
415
407
|
}
|
|
416
408
|
|
|
417
409
|
/**
|
|
418
|
-
*
|
|
410
|
+
* Open a CES RPC session from a process that did not call startCes().
|
|
419
411
|
*
|
|
420
412
|
* Workers and CLI subprocesses never call startCes(). This function
|
|
421
|
-
*
|
|
413
|
+
* opens the same `openCesRpcSession` path the assistant uses at boot,
|
|
422
414
|
* memoizing the in-flight promise so concurrent callers share a single
|
|
423
415
|
* connect+handshake. Discovery uses the shared CES bootstrap socket; a
|
|
424
416
|
* missing socket fails immediately so callers can fall through without
|
|
425
417
|
* polling.
|
|
426
418
|
*
|
|
427
419
|
* On success, the client is injected via setCesClient() so subsequent
|
|
428
|
-
* resolveBackendAsync() calls take the
|
|
429
|
-
*
|
|
430
|
-
*
|
|
420
|
+
* resolveBackendAsync() calls take the live CES RPC path. A reconnect
|
|
421
|
+
* callback is also registered so the session can heal if the transport
|
|
422
|
+
* drops mid-process.
|
|
431
423
|
*
|
|
432
424
|
* Returns undefined on any failure (socket not found, handshake rejected,
|
|
433
425
|
* timeout) so the caller falls through to the encrypted file store.
|
|
@@ -438,70 +430,30 @@ async function tryLazyCesConnect(): Promise<CesClient | undefined> {
|
|
|
438
430
|
}
|
|
439
431
|
|
|
440
432
|
_lazyConnectPromise = (async () => {
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
if (discovery.mode === "unavailable") {
|
|
444
|
-
log.info(
|
|
445
|
-
{ reason: discovery.reason },
|
|
446
|
-
"CES socket not reachable for lazy connect, falling back to encrypted file store",
|
|
447
|
-
);
|
|
448
|
-
return undefined;
|
|
449
|
-
}
|
|
450
|
-
const pm = createCesProcessManager({});
|
|
451
|
-
const transport = await pm.start();
|
|
452
|
-
const client = createCesClient(transport);
|
|
453
|
-
const { accepted, reason } = await client.handshake();
|
|
454
|
-
if (!accepted) {
|
|
455
|
-
log.warn(
|
|
456
|
-
{ reason },
|
|
457
|
-
"Lazy CES connection handshake rejected — falling back to encrypted file store",
|
|
458
|
-
);
|
|
459
|
-
client.close();
|
|
460
|
-
await pm.stop().catch(() => {});
|
|
461
|
-
return undefined;
|
|
462
|
-
}
|
|
433
|
+
const discovery = discoverCes();
|
|
434
|
+
if (discovery.mode === "unavailable") {
|
|
463
435
|
log.info(
|
|
464
|
-
|
|
436
|
+
{ reason: discovery.reason },
|
|
437
|
+
"CES socket not reachable for lazy connect, falling back to encrypted file store",
|
|
465
438
|
);
|
|
466
|
-
setCesClient(client);
|
|
467
|
-
// Register a reconnect callback so the lazy connection self-heals
|
|
468
|
-
// if the transport drops, mirroring the daemon's proactive reconnect.
|
|
469
|
-
setCesReconnect(async () => {
|
|
470
|
-
try {
|
|
471
|
-
await pm.stop();
|
|
472
|
-
const newTransport = await pm.start();
|
|
473
|
-
const newClient = createCesClient(newTransport);
|
|
474
|
-
const { accepted: ok } = await newClient.handshake();
|
|
475
|
-
if (ok) {
|
|
476
|
-
log.info("Lazy CES reconnection successful");
|
|
477
|
-
return newClient;
|
|
478
|
-
}
|
|
479
|
-
newClient.close();
|
|
480
|
-
await pm.stop().catch(() => {});
|
|
481
|
-
return undefined;
|
|
482
|
-
} catch (err) {
|
|
483
|
-
log.warn(
|
|
484
|
-
{ error: err instanceof Error ? err.message : String(err) },
|
|
485
|
-
"Lazy CES reconnection failed",
|
|
486
|
-
);
|
|
487
|
-
return undefined;
|
|
488
|
-
}
|
|
489
|
-
});
|
|
490
|
-
return client;
|
|
491
|
-
} catch (err) {
|
|
492
|
-
if (err instanceof CesUnavailableError) {
|
|
493
|
-
log.info(
|
|
494
|
-
{ reason: err.message },
|
|
495
|
-
"CES socket not reachable for lazy connect — falling back to encrypted file store",
|
|
496
|
-
);
|
|
497
|
-
} else {
|
|
498
|
-
log.warn(
|
|
499
|
-
{ error: err instanceof Error ? err.message : String(err) },
|
|
500
|
-
"Lazy CES connection failed — falling back to encrypted file store",
|
|
501
|
-
);
|
|
502
|
-
}
|
|
503
439
|
return undefined;
|
|
504
440
|
}
|
|
441
|
+
const session = await openCesRpcSession();
|
|
442
|
+
if (!session) {
|
|
443
|
+
return undefined;
|
|
444
|
+
}
|
|
445
|
+
log.info(
|
|
446
|
+
"CES RPC session established; credential operations route through CES RPC",
|
|
447
|
+
);
|
|
448
|
+
setCesClient(session.client);
|
|
449
|
+
setCesReconnect(async () => {
|
|
450
|
+
const client = await reconnectCesRpcSession(session.processManager);
|
|
451
|
+
if (client) {
|
|
452
|
+
log.info("CES RPC reconnection successful");
|
|
453
|
+
}
|
|
454
|
+
return client;
|
|
455
|
+
});
|
|
456
|
+
return session.client;
|
|
505
457
|
})();
|
|
506
458
|
|
|
507
459
|
try {
|
|
@@ -512,7 +464,10 @@ async function tryLazyCesConnect(): Promise<CesClient | undefined> {
|
|
|
512
464
|
}
|
|
513
465
|
|
|
514
466
|
async function doResolveBackend(): Promise<CredentialBackend> {
|
|
515
|
-
// 1. CES RPC. Primary credential backend in every environment.
|
|
467
|
+
// 1. CES RPC. Primary credential backend in every environment. Boot
|
|
468
|
+
// claims reconnect ownership before it reads handshake identity, so
|
|
469
|
+
// that read cannot open a second session. Children open here only
|
|
470
|
+
// when this process has no client and no reconnect owner.
|
|
516
471
|
if (_cesClient) {
|
|
517
472
|
const cesRpc = new CesRpcCredentialBackend(_cesClient);
|
|
518
473
|
if (cesRpc.isAvailable()) {
|
|
@@ -525,24 +480,19 @@ async function doResolveBackend(): Promise<CredentialBackend> {
|
|
|
525
480
|
);
|
|
526
481
|
}
|
|
527
482
|
|
|
528
|
-
// 2. Lazy CES RPC connect. Child processes never call startCes(). When
|
|
529
|
-
// the assistant's setCesReconnect() is NOT registered, attempt a
|
|
530
|
-
// direct connection to the CES bootstrap socket. On success, inject
|
|
531
|
-
// the client via setCesClient()
|
|
532
|
-
// and re-resolve through the CES RPC path. On failure, fall through.
|
|
533
483
|
if (!_cesClient && !_cesReconnect) {
|
|
534
|
-
const
|
|
535
|
-
if (
|
|
536
|
-
const cesRpc = new CesRpcCredentialBackend(
|
|
484
|
+
const client = await tryLazyCesConnect();
|
|
485
|
+
if (client) {
|
|
486
|
+
const cesRpc = new CesRpcCredentialBackend(client);
|
|
537
487
|
if (cesRpc.isAvailable()) {
|
|
538
488
|
_resolvedBackend = cesRpc;
|
|
539
|
-
log.info("Resolved credential backend: ces-rpc
|
|
489
|
+
log.info("Resolved credential backend: ces-rpc");
|
|
540
490
|
return cesRpc;
|
|
541
491
|
}
|
|
542
492
|
}
|
|
543
493
|
}
|
|
544
494
|
|
|
545
|
-
//
|
|
495
|
+
// 2. CES HTTP. Managed failover when IPC is unavailable.
|
|
546
496
|
if (getIsContainerized() && process.env.CES_CREDENTIAL_URL) {
|
|
547
497
|
const ces = createCesCredentialBackend();
|
|
548
498
|
if (ces.isAvailable()) {
|
|
@@ -556,7 +506,7 @@ async function doResolveBackend(): Promise<CredentialBackend> {
|
|
|
556
506
|
);
|
|
557
507
|
}
|
|
558
508
|
|
|
559
|
-
//
|
|
509
|
+
// 3. On a containerized pod the local encrypted store does not exist and CES
|
|
560
510
|
// owns credentials. Never resolve to the encrypted store here; it would
|
|
561
511
|
// report a provisioned credential as absent. Return an unreachable backend
|
|
562
512
|
// (presence indeterminate, which callers retry) WITHOUT caching it, so the
|
|
@@ -568,7 +518,7 @@ async function doResolveBackend(): Promise<CredentialBackend> {
|
|
|
568
518
|
return createUnavailableBackend();
|
|
569
519
|
}
|
|
570
520
|
|
|
571
|
-
//
|
|
521
|
+
// 4. Encrypted file store: the legitimate backend for local / self-hosted
|
|
572
522
|
// mode when CES is unavailable.
|
|
573
523
|
_resolvedBackend = getEncryptedStoreBackend();
|
|
574
524
|
log.info("Resolved credential backend: encrypted-store (local mode)");
|
package/src/skills/clawhub.ts
CHANGED
|
@@ -11,11 +11,6 @@ import {
|
|
|
11
11
|
|
|
12
12
|
const log = getLogger("clawhub");
|
|
13
13
|
|
|
14
|
-
// Managed skills directory — where installed skill folders live
|
|
15
|
-
function getManagedSkillsDir(): string {
|
|
16
|
-
return getWorkspaceSkillsDir();
|
|
17
|
-
}
|
|
18
|
-
|
|
19
14
|
// ClaWHub project root — clawhub creates a `skills/` subdir inside its cwd,
|
|
20
15
|
// so we use the parent of the managed skills dir as the project root.
|
|
21
16
|
function getClawhubProjectRoot(): string {
|
|
@@ -39,7 +34,7 @@ interface IntegrityRecord {
|
|
|
39
34
|
type IntegrityManifest = Record<string, IntegrityRecord>;
|
|
40
35
|
|
|
41
36
|
function getIntegrityPath(): string {
|
|
42
|
-
return join(
|
|
37
|
+
return join(getWorkspaceSkillsDir(), ".integrity.json");
|
|
43
38
|
}
|
|
44
39
|
|
|
45
40
|
function loadIntegrityManifest(): IntegrityManifest {
|
|
@@ -66,7 +61,7 @@ function loadIntegrityManifest(): IntegrityManifest {
|
|
|
66
61
|
* from skills that haven't been migrated yet.
|
|
67
62
|
*/
|
|
68
63
|
export function verifyAndRecordSkillHash(slug: string): void {
|
|
69
|
-
const skillDir = join(
|
|
64
|
+
const skillDir = join(getWorkspaceSkillsDir(), slug);
|
|
70
65
|
const hash = computeSkillHash(skillDir);
|
|
71
66
|
if (!hash) {
|
|
72
67
|
log.warn({ slug }, "Could not compute content hash for installed skill");
|
|
@@ -232,7 +227,7 @@ function getSkillNameFromSlug(slug: string): string {
|
|
|
232
227
|
}
|
|
233
228
|
|
|
234
229
|
function getClawhubSkillsDir(projectRoot?: string): string {
|
|
235
|
-
return projectRoot ? join(projectRoot, "skills") :
|
|
230
|
+
return projectRoot ? join(projectRoot, "skills") : getWorkspaceSkillsDir();
|
|
236
231
|
}
|
|
237
232
|
|
|
238
233
|
function hasRootSkillFile(skillDir: string): boolean {
|
|
@@ -44,13 +44,9 @@ export function validateManagedSkillId(id: string): string | null {
|
|
|
44
44
|
|
|
45
45
|
// ─── Path helpers ────────────────────────────────────────────────────────────
|
|
46
46
|
|
|
47
|
-
function getManagedSkillsDir(): string {
|
|
48
|
-
return getWorkspaceSkillsDir();
|
|
49
|
-
}
|
|
50
|
-
|
|
51
47
|
/** Absolute path of a managed skill's directory (whether or not it exists). */
|
|
52
48
|
export function getManagedSkillDir(id: string): string {
|
|
53
|
-
return join(
|
|
49
|
+
return join(getWorkspaceSkillsDir(), id);
|
|
54
50
|
}
|
|
55
51
|
|
|
56
52
|
interface ResolvedCompanionPath {
|
package/src/stt/types.ts
CHANGED
|
@@ -436,6 +436,10 @@ export interface SttStreamServerTurnEndEvent {
|
|
|
436
436
|
* provider does not surface a score.
|
|
437
437
|
*/
|
|
438
438
|
readonly confidence?: number;
|
|
439
|
+
/** Provider-reported cause of the turn boundary, when available. */
|
|
440
|
+
readonly trigger?: "model" | "manual" | "timeout";
|
|
441
|
+
/** End of the decoded audio window, in seconds from stream start. */
|
|
442
|
+
readonly audioWindowEndSeconds?: number;
|
|
439
443
|
/**
|
|
440
444
|
* Provider's index for the turn being closed, matching the
|
|
441
445
|
* {@link SttStreamServerTurnStartEvent} that opened it. A consumer can
|
package/src/subagent/notify.ts
CHANGED
|
@@ -8,16 +8,20 @@
|
|
|
8
8
|
* manager out of the import graph lets tool modules import these helpers without
|
|
9
9
|
* pulling in the conversation/agent-loop core.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* stale idle parent is rebuilt through `getOrCreateConversation` first so
|
|
14
|
-
* injected turn uses the current provider, prompt, and credentials.
|
|
11
|
+
* An active live-voice session owns delivery for its conversation. Otherwise,
|
|
12
|
+
* delivery targets the parent's in-process `Conversation` via the registry.
|
|
13
|
+
* A stale idle parent is rebuilt through `getOrCreateConversation` first so
|
|
14
|
+
* the injected turn uses the current provider, prompt, and credentials.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
+
import { getConfig } from "../config/loader.js";
|
|
18
|
+
import { resolveTurnCommitWaitMs } from "../daemon/abort-watchdog.js";
|
|
17
19
|
import {
|
|
18
20
|
findConversation,
|
|
19
21
|
findConversationOrSubagent,
|
|
20
22
|
} from "../daemon/conversation-registry.js";
|
|
23
|
+
import { startAfterTurnFinalization } from "../daemon/turn-finalization.js";
|
|
24
|
+
import { deliverSubagentNotificationToLiveVoice } from "../live-voice/live-voice-manager.js";
|
|
21
25
|
import { getSubagentRecordByConversationId } from "../persistence/subagent-store.js";
|
|
22
26
|
import { getLogger } from "../util/logger.js";
|
|
23
27
|
import { type SubagentStatus, TERMINAL_STATUSES } from "./types.js";
|
|
@@ -25,9 +29,9 @@ import { type SubagentStatus, TERMINAL_STATUSES } from "./types.js";
|
|
|
25
29
|
const log = getLogger("subagent-notify");
|
|
26
30
|
|
|
27
31
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
32
|
+
* Deliver task updates through the parent's live voice session when present,
|
|
33
|
+
* otherwise enqueue a parent turn. No-op with a warning
|
|
34
|
+
* when neither the session nor the parent conversation is live here.
|
|
31
35
|
*
|
|
32
36
|
* Shared by the child-triggered {@link notifyParentFromChild} and the
|
|
33
37
|
* manager's terminal/abort injections so every subagent → parent turn lands
|
|
@@ -37,8 +41,30 @@ export function injectMessageIntoParent(
|
|
|
37
41
|
parentConversationId: string,
|
|
38
42
|
message: string,
|
|
39
43
|
metadata?: Record<string, unknown>,
|
|
40
|
-
opts?: { cronRunId?: string | null },
|
|
44
|
+
opts?: { cronRunId?: string | null; bypassLiveVoice?: boolean },
|
|
41
45
|
): void {
|
|
46
|
+
const notification = metadata?.subagentNotification;
|
|
47
|
+
// The live child's conversation ID is stable even if its cosmetic record changes.
|
|
48
|
+
if (
|
|
49
|
+
!opts?.bypassLiveVoice &&
|
|
50
|
+
notification !== null &&
|
|
51
|
+
typeof notification === "object" &&
|
|
52
|
+
"subagentId" in notification &&
|
|
53
|
+
typeof notification.subagentId === "string" &&
|
|
54
|
+
metadata !== undefined &&
|
|
55
|
+
deliverSubagentNotificationToLiveVoice(parentConversationId, {
|
|
56
|
+
taskId:
|
|
57
|
+
"conversationId" in notification &&
|
|
58
|
+
typeof notification.conversationId === "string"
|
|
59
|
+
? notification.conversationId
|
|
60
|
+
: notification.subagentId,
|
|
61
|
+
message,
|
|
62
|
+
metadata,
|
|
63
|
+
cronRunId: opts?.cronRunId,
|
|
64
|
+
})
|
|
65
|
+
) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
42
68
|
const existing = findConversation(parentConversationId);
|
|
43
69
|
if (!existing) {
|
|
44
70
|
log.warn(
|
|
@@ -76,17 +102,10 @@ function deliverToParent(
|
|
|
76
102
|
content: string;
|
|
77
103
|
metadata?: Record<string, unknown>;
|
|
78
104
|
isInteractive: boolean;
|
|
105
|
+
queueWhenIdle: boolean;
|
|
79
106
|
cronRunId?: string | null;
|
|
80
107
|
}) => { queued: boolean; rejected?: boolean };
|
|
81
|
-
|
|
82
|
-
content: string;
|
|
83
|
-
metadata?: Record<string, unknown>;
|
|
84
|
-
}) => Promise<{ id: string }>;
|
|
85
|
-
runAgentLoop: (
|
|
86
|
-
message: string,
|
|
87
|
-
messageId: string,
|
|
88
|
-
options: { isInteractive: boolean; cronRunId?: string | null },
|
|
89
|
-
) => Promise<unknown>;
|
|
108
|
+
kickDrainQueue: (reason: "loop_complete", origin: string) => Promise<void>;
|
|
90
109
|
},
|
|
91
110
|
parentConversationId: string,
|
|
92
111
|
message: string,
|
|
@@ -95,33 +114,34 @@ function deliverToParent(
|
|
|
95
114
|
): void {
|
|
96
115
|
// The continuation this notification starts is still the scheduled firing's
|
|
97
116
|
// work, so it carries the same run id as the child whose result triggered it.
|
|
98
|
-
//
|
|
99
|
-
// has ended, so the id has to travel on the message.
|
|
117
|
+
// The queue drains after the enqueuing turn, so the id travels on the message.
|
|
100
118
|
const cronRunId = opts?.cronRunId ?? null;
|
|
101
119
|
// Machine-injected with no human asserted present, so the notification
|
|
102
120
|
// turn runs non-interactive; it still streams to whoever is watching
|
|
103
121
|
// through the parent's sink.
|
|
104
122
|
const enqueueResult = parentConversation.enqueueMessage({
|
|
105
123
|
content: message,
|
|
106
|
-
metadata,
|
|
124
|
+
metadata: { ...metadata, scripted: true },
|
|
107
125
|
isInteractive: false,
|
|
126
|
+
queueWhenIdle: true,
|
|
108
127
|
...(cronRunId ? { cronRunId } : {}),
|
|
109
128
|
});
|
|
110
|
-
if (
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
)
|
|
119
|
-
.catch((err) => {
|
|
120
|
-
log.error(
|
|
121
|
-
{ parentConversationId, err },
|
|
122
|
-
"Failed to process subagent notification in parent",
|
|
129
|
+
if (enqueueResult.queued) {
|
|
130
|
+
startAfterTurnFinalization(
|
|
131
|
+
parentConversationId,
|
|
132
|
+
resolveTurnCommitWaitMs(getConfig().workspaceGit?.turnCommitMaxWaitMs),
|
|
133
|
+
() => {
|
|
134
|
+
void parentConversation.kickDrainQueue(
|
|
135
|
+
"loop_complete",
|
|
136
|
+
"subagent_notification",
|
|
123
137
|
);
|
|
124
|
-
}
|
|
138
|
+
},
|
|
139
|
+
);
|
|
140
|
+
} else {
|
|
141
|
+
log.error(
|
|
142
|
+
{ parentConversationId },
|
|
143
|
+
"Parent queue rejected subagent notification",
|
|
144
|
+
);
|
|
125
145
|
}
|
|
126
146
|
}
|
|
127
147
|
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
PHONE_CALL_STEPS,
|
|
5
|
+
phoneCallEndScreen,
|
|
6
|
+
phoneCallSilenceReason,
|
|
7
|
+
phoneCallStartScreen,
|
|
8
|
+
} from "../phone-call-funnel.js";
|
|
9
|
+
|
|
10
|
+
describe("phoneCallStartScreen", () => {
|
|
11
|
+
it("stamps the direction and the call mode", () => {
|
|
12
|
+
expect(phoneCallStartScreen("inbound", "normal")).toBe(
|
|
13
|
+
"started_inbound:normal",
|
|
14
|
+
);
|
|
15
|
+
expect(phoneCallStartScreen("outbound", "verification")).toBe(
|
|
16
|
+
"started_outbound:verification",
|
|
17
|
+
);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("reads an unwritten mode as the ordinary call it is", () => {
|
|
21
|
+
// Only verification and invite calls write a mode, so a null there is a
|
|
22
|
+
// normal conversation rather than a dimension nobody filled in.
|
|
23
|
+
expect(phoneCallStartScreen("inbound", null)).toBe(
|
|
24
|
+
"started_inbound:normal",
|
|
25
|
+
);
|
|
26
|
+
expect(phoneCallStartScreen("inbound")).toBe("started_inbound:normal");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("keeps the longest stamp inside the wire field's 64-char bound", () => {
|
|
30
|
+
const longest = phoneCallStartScreen("outbound", "verification");
|
|
31
|
+
expect(longest.length).toBeLessThanOrEqual(64);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe("phoneCallSilenceReason", () => {
|
|
36
|
+
it("blames the connection when the call never reached the caller", () => {
|
|
37
|
+
// Nothing that happens on a live call can be held against one that never
|
|
38
|
+
// connected: nobody was there to speak.
|
|
39
|
+
expect(phoneCallSilenceReason({ connected: false })).toBe("no_connect");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("reports a connected call that took no turn as silence on the line", () => {
|
|
43
|
+
expect(phoneCallSilenceReason({ connected: true })).toBe("no_turn");
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe("phoneCallEndScreen", () => {
|
|
48
|
+
it("stamps the terminal status on a call that took a turn", () => {
|
|
49
|
+
expect(phoneCallEndScreen("completed")).toBe("ended_completed");
|
|
50
|
+
expect(phoneCallEndScreen("failed", null)).toBe("ended_failed");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("carries the silence classification when no turn was taken", () => {
|
|
54
|
+
expect(phoneCallEndScreen("failed", "no_connect")).toBe(
|
|
55
|
+
"ended_failed:silent_no_connect",
|
|
56
|
+
);
|
|
57
|
+
expect(phoneCallEndScreen("completed", "no_turn")).toBe(
|
|
58
|
+
"ended_completed:silent_no_turn",
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe("PHONE_CALL_STEPS", () => {
|
|
64
|
+
it("orders the pair the warehouse subtracts", () => {
|
|
65
|
+
// Duration is the gap between these two rows, so their order is part of
|
|
66
|
+
// the contract rather than a label.
|
|
67
|
+
expect(PHONE_CALL_STEPS.callStarted.stepIndex).toBe(0);
|
|
68
|
+
expect(PHONE_CALL_STEPS.callEnded.stepIndex).toBe(1);
|
|
69
|
+
expect(PHONE_CALL_STEPS.callStarted.stepName).toBe("phone_call_started");
|
|
70
|
+
expect(PHONE_CALL_STEPS.callEnded.stepName).toBe("phone_call_ended");
|
|
71
|
+
});
|
|
72
|
+
});
|