@vellumai/assistant 0.12.0 → 0.12.1-dev.202609141913.5e16607
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/AGENTS.md +5 -1
- package/ARCHITECTURE.md +11 -12
- package/docs/architecture/turn-actor.md +15 -8
- package/docs/guardian-request-flow.md +16 -0
- package/openapi.yaml +292 -50
- package/package.json +1 -1
- package/scripts/smoke-desktop-window-theme.ts +186 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +18 -10
- package/src/__tests__/app-compiler.test.ts +6 -0
- package/src/__tests__/app-control-tool-schemas.test.ts +5 -2
- package/src/__tests__/app-dir-path-guard.test.ts +1 -3
- package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +5 -2
- package/src/__tests__/computer-use-tools.test.ts +17 -5
- package/src/__tests__/config-schema-cmd.test.ts +2 -2
- package/src/__tests__/config-schema.test.ts +28 -29
- package/src/__tests__/config-watcher.test.ts +23 -8
- package/src/__tests__/connection-model-compat.test.ts +2 -2
- package/src/__tests__/conversation-key-store-disk-view.test.ts +1 -1
- package/src/__tests__/conversation-pairing.test.ts +26 -0
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +4 -10
- package/src/__tests__/conversation-queue.test.ts +48 -207
- package/src/__tests__/conversation-retry-discard.test.ts +54 -1
- package/src/__tests__/conversation-skill-tools.test.ts +27 -0
- package/src/__tests__/conversation-surfaces-app-control.test.ts +8 -3
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
- package/src/__tests__/conversation-title-service.test.ts +14 -10
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +3 -5
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +51 -0
- package/src/__tests__/credential-record-retire.test.ts +171 -1
- package/src/__tests__/credential-routes.test.ts +212 -3
- package/src/__tests__/credential-security-invariants.test.ts +4 -2
- package/src/__tests__/cu-unified-flow.test.ts +8 -2
- package/src/__tests__/document-sync-tags.test.ts +25 -0
- package/src/__tests__/drain-kick-guard.test.ts +0 -2
- package/src/__tests__/drain-requeue-on-contention.test.ts +0 -6
- package/src/__tests__/evict-conversations-for-reload.test.ts +1 -1
- package/src/__tests__/git-porcelain.test.ts +47 -0
- package/src/__tests__/helpers/set-workspace-mcp.ts +11 -0
- package/src/__tests__/host-bash-proxy.test.ts +26 -45
- package/src/__tests__/host-browser-proxy.test.ts +6 -17
- package/src/__tests__/host-file-proxy-targeted.test.ts +23 -0
- package/src/__tests__/host-file-proxy.test.ts +11 -23
- package/src/__tests__/host-transfer-proxy-targeted.test.ts +30 -9
- package/src/__tests__/llm-catalog-parity.test.ts +25 -0
- package/src/__tests__/mcp-auth-routes.test.ts +6 -9
- package/src/__tests__/mcp-config-secret-boundary.test.ts +78 -136
- package/src/__tests__/mcp-health-check.test.ts +8 -11
- package/src/__tests__/mcp-list-plugin-servers.test.ts +8 -10
- package/src/__tests__/message-deleted-hook-dispatch.test.ts +119 -0
- package/src/__tests__/messages-after-tiebreaker.test.ts +91 -0
- package/src/__tests__/messages-read-boundary-guard.test.ts +1 -1
- package/src/__tests__/messaging-send-tool.test.ts +355 -548
- package/src/__tests__/model-intents.test.ts +3 -0
- package/src/__tests__/notification-vellum-adapter.test.ts +85 -2
- package/src/__tests__/outlook-messaging-provider.test.ts +6 -6
- package/src/__tests__/platform-callback-registration.test.ts +8 -16
- package/src/__tests__/platform-client-verify-credential.test.ts +2 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +13 -0
- package/src/__tests__/resolve-app.test.ts +135 -0
- package/src/__tests__/script-proxy-certs.test.ts +35 -0
- package/src/__tests__/service-account-token.test.ts +247 -0
- package/src/__tests__/skill-feature-flags-integration.test.ts +1 -1
- package/src/__tests__/skill-feature-flags.test.ts +2 -2
- package/src/__tests__/source-fingerprint.test.ts +184 -0
- package/src/__tests__/subagent-tool-gate-mode.test.ts +19 -3
- package/src/__tests__/subagent-tools.test.ts +3 -2
- package/src/__tests__/telephony-tts-guard.test.ts +38 -3
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +4 -0
- package/src/__tests__/watch-retro-tool-availability.test.ts +8 -17
- package/src/__tests__/workspace-git-service.test.ts +26 -0
- package/src/__tests__/workspace-migration-154-repair-retired-codex-gpt-5-4-model-ids.test.ts +336 -0
- package/src/acp/__tests__/agent-process.test.ts +258 -4
- package/src/acp/__tests__/auth-required.test.ts +96 -0
- package/src/acp/__tests__/client-handler.test.ts +90 -2
- package/src/acp/__tests__/helpers/acp-model-option.ts +51 -0
- package/src/acp/__tests__/session-manager-persistence.test.ts +5 -1
- package/src/acp/__tests__/session-manager-resume.test.ts +270 -6
- package/src/acp/__tests__/session-manager-usage.test.ts +4 -2
- package/src/acp/__tests__/session-manager.test.ts +895 -4
- package/src/acp/agent-process.test.ts +4 -1
- package/src/acp/agent-process.ts +94 -9
- package/src/acp/auth-required.ts +50 -1
- package/src/acp/auto-install.test.ts +9 -7
- package/src/acp/client-handler.ts +16 -3
- package/src/acp/model-config.test.ts +166 -0
- package/src/acp/model-config.ts +138 -0
- package/src/acp/resolve-agent.test.ts +220 -6
- package/src/acp/resolve-agent.ts +88 -23
- package/src/acp/session-manager.test.ts +196 -3
- package/src/acp/session-manager.ts +441 -18
- package/src/acp/types.ts +50 -5
- package/src/api/events/acp-session-model-update.test.ts +88 -0
- package/src/api/events/acp-session-model-update.ts +46 -0
- package/src/api/events/app-files-changed.ts +4 -3
- package/src/api/events/notification-intent.ts +2 -0
- package/src/api/index.ts +6 -0
- package/src/apps/app-store.ts +1 -1
- package/src/apps/resolve-app.ts +81 -0
- package/src/apps/source-fingerprint.ts +266 -0
- package/src/bundler/app-compiler.ts +15 -0
- package/src/calls/__tests__/barge-in-guard.test.ts +81 -0
- package/src/{__tests__ → calls/__tests__}/call-controller.test.ts +503 -82
- package/src/{__tests__ → calls/__tests__}/media-stream-audio-transcode.test.ts +1 -1
- package/src/{__tests__ → calls/__tests__}/media-stream-output.test.ts +37 -8
- package/src/{__tests__ → calls/__tests__}/media-stream-parser.test.ts +1 -1
- package/src/{__tests__ → calls/__tests__}/media-stream-server-integration.test.ts +150 -57
- package/src/{__tests__ → calls/__tests__}/media-stream-stt-session.test.ts +5 -5
- package/src/{__tests__ → calls/__tests__}/media-turn-detector.test.ts +1 -1
- package/src/{live-voice → calls}/__tests__/progress-narration.test.ts +4 -4
- package/src/{live-voice → calls}/__tests__/progress-phrases.test.ts +1 -1
- package/src/calls/__tests__/voice-control-protocol.test.ts +58 -25
- package/src/calls/__tests__/voice-leg-coordinator.test.ts +229 -0
- package/src/calls/__tests__/voice-progress-cadence.test.ts +274 -0
- package/src/{__tests__/voice-session-bridge.test.ts → calls/__tests__/voice-session-bridge-turn-flow.test.ts} +20 -12
- package/src/calls/__tests__/voice-session-bridge.test.ts +37 -1
- package/src/calls/__tests__/voice-triage-escalate.test.ts +105 -0
- package/src/calls/barge-in-guard.ts +118 -0
- package/src/calls/call-constants.ts +24 -4
- package/src/calls/call-controller.ts +499 -199
- package/src/calls/call-transport.ts +16 -0
- package/src/calls/media-stream-output.ts +32 -0
- package/src/calls/media-stream-server.ts +82 -30
- package/src/calls/media-stream-stt-session.ts +15 -0
- package/src/{live-voice → calls}/progress-narration.ts +2 -2
- package/src/calls/resolve-call-tts-provider.ts +33 -2
- package/src/calls/tts-call-strategy.ts +9 -5
- package/src/calls/types.ts +2 -0
- package/src/calls/voice-control-protocol.ts +53 -21
- package/src/calls/voice-leg-coordinator.ts +189 -0
- package/src/calls/voice-progress-cadence.ts +423 -0
- package/src/calls/voice-session-bridge.ts +59 -46
- package/src/calls/voice-triage-escalate.ts +175 -56
- package/src/cli/commands/__tests__/apps.test.ts +245 -0
- package/src/cli/commands/__tests__/bash.test.ts +53 -0
- package/src/cli/commands/__tests__/roadmap.help.test.ts +19 -0
- package/src/cli/commands/apps.help.ts +67 -13
- package/src/cli/commands/apps.ts +183 -0
- package/src/cli/commands/bash.help.ts +8 -13
- package/src/cli/commands/bash.ts +7 -25
- package/src/cli/commands/channels/__tests__/send.test.ts +218 -0
- package/src/cli/commands/channels/index.help.ts +70 -2
- package/src/cli/commands/channels/index.ts +16 -3
- package/src/cli/commands/channels/send.ts +128 -0
- package/src/cli/commands/credentials.help.ts +6 -2
- package/src/cli/commands/credentials.ts +2 -2
- package/src/cli/commands/mcp.help.ts +8 -8
- package/src/cli/commands/mcp.ts +1 -1
- package/src/cli/commands/roadmap.help.ts +5 -1
- package/src/cli/commands/roadmap.ts +1 -1
- package/src/cli/lib/bundled-marketplace.json +103 -90
- package/src/config/__tests__/default-profile-catalog.test.ts +22 -0
- package/src/config/__tests__/memory-retrospective-schema.test.ts +15 -1
- package/src/config/__tests__/platform-identity.test.ts +255 -0
- package/src/config/__tests__/platform-rehydration.test.ts +99 -9
- package/src/config/acp-agent-write.ts +43 -0
- package/src/config/acp-defaults.test.ts +13 -3
- package/src/config/acp-defaults.ts +23 -6
- package/src/config/acp-schema.test.ts +81 -0
- package/src/config/acp-schema.ts +30 -2
- package/src/config/bundled-skills/acp/SKILL.md +22 -14
- package/src/config/bundled-skills/acp/TOOLS.json +5 -1
- package/src/config/bundled-skills/app-builder/SKILL.md +12 -9
- package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +1 -1
- package/src/config/bundled-skills/messaging/SKILL.md +3 -3
- package/src/config/bundled-skills/messaging/TOOLS.json +3 -3
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +67 -139
- package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +1 -1
- package/src/config/bundled-skills/schedule/SKILL.md +3 -4
- package/src/config/bundled-skills/screen-annotation/SKILL.md +8 -1
- package/src/config/bundled-skills/screen-annotation/tools/screen-annotation.test.ts +4 -1
- package/src/config/bundled-skills/skill-management/TOOLS.json +2 -2
- package/src/config/default-profile-catalog.ts +1 -1
- package/src/config/env.ts +2 -2
- package/src/config/feature-flag-registry.json +24 -12
- package/src/config/loader.ts +1 -0
- package/src/config/memory-v3-gate.test.ts +31 -0
- package/src/config/memory-v3-gate.ts +20 -16
- package/src/config/platform-identity.ts +201 -0
- package/src/config/platform-rehydration.ts +11 -27
- package/src/config/schema.ts +2 -2
- package/src/config/schemas/__tests__/live-voice.test.ts +8 -158
- package/src/config/schemas/__tests__/voice.test.ts +148 -0
- package/src/config/schemas/calls.ts +1 -28
- package/src/config/schemas/live-voice.ts +1 -173
- package/src/config/schemas/mcp.ts +1 -1
- package/src/config/schemas/memory-retrospective.ts +10 -0
- package/src/config/schemas/services.ts +8 -0
- package/src/config/schemas/skills.ts +0 -14
- package/src/config/schemas/voice.ts +150 -0
- package/src/credential-execution/process-manager.test.ts +18 -19
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -121
- package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +4 -1
- package/src/daemon/__tests__/skill-memory-refresh-mtime.test.ts +133 -0
- package/src/daemon/app-change-notify.ts +45 -0
- package/src/daemon/config-watcher.ts +13 -194
- package/src/daemon/conversation-process.ts +35 -64
- package/src/daemon/conversation-skill-tools.ts +13 -31
- package/src/daemon/conversation-store.ts +1 -1
- package/src/daemon/conversation-surfaces.ts +10 -8
- package/src/daemon/conversation-tool-setup.ts +37 -43
- package/src/daemon/conversation.ts +0 -3
- package/src/daemon/host-bash-proxy.ts +8 -12
- package/src/daemon/host-browser-proxy.ts +15 -1
- package/src/daemon/host-file-proxy.ts +8 -10
- package/src/daemon/host-transfer-proxy.ts +14 -18
- package/src/daemon/lifecycle.ts +0 -5
- package/src/daemon/mcp-reload-service.ts +5 -5
- package/src/daemon/message-types/sync.ts +1 -0
- package/src/daemon/orphan-reaper.ts +1 -1
- package/src/daemon/providers-setup.ts +1 -1
- package/src/daemon/shutdown-handlers.ts +1 -9
- package/src/daemon/skill-memory-refresh.ts +64 -0
- package/src/daemon/tool-side-effects.ts +7 -18
- package/src/desktop/__tests__/fake-desktop.ts +119 -0
- package/src/desktop/desktop-chrome-policy.test.ts +71 -0
- package/src/desktop/desktop-chrome-policy.ts +42 -0
- package/src/desktop/desktop-dependencies.test.ts +102 -0
- package/src/desktop/desktop-dependencies.ts +306 -0
- package/src/desktop/desktop-feature.test.ts +18 -0
- package/src/desktop/desktop-feature.ts +19 -0
- package/src/desktop/desktop-panel-config.test.ts +135 -0
- package/src/desktop/desktop-panel-config.ts +149 -0
- package/src/desktop/desktop-session-manager.test.ts +627 -0
- package/src/desktop/desktop-session-manager.ts +814 -0
- package/src/desktop/desktop-stream-bridge.test.ts +395 -0
- package/src/desktop/desktop-stream-bridge.ts +214 -0
- package/src/desktop/desktop-wallpaper.test.ts +191 -0
- package/src/desktop/desktop-wallpaper.ts +113 -0
- package/src/desktop/desktop-window-theme.ts +170 -0
- package/src/home/__tests__/feed-writer.test.ts +22 -0
- package/src/home/feed-writer.ts +11 -2
- package/src/hooks/types.ts +33 -0
- package/src/i18n/AGENTS.md +22 -0
- package/src/i18n/__tests__/i18n.test.ts +108 -0
- package/src/i18n/index.ts +16 -0
- package/src/i18n/locales.ts +93 -0
- package/src/i18n/messages.ts +61 -0
- package/src/i18n/title.ts +25 -0
- package/src/inbound/platform-callback-registration.ts +4 -5
- package/src/live-voice/__tests__/live-voice-metrics.test.ts +0 -3
- package/src/live-voice/__tests__/live-voice-progress.test.ts +15 -15
- package/src/live-voice/__tests__/live-voice-vad.test.ts +2 -2
- package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +13 -69
- package/src/live-voice/live-voice-session.ts +213 -693
- package/src/live-voice/protocol.ts +0 -2
- package/src/mcp/__tests__/effective-config.test.ts +2 -2
- package/src/mcp/__tests__/workspace-mcp-config.test.ts +135 -0
- package/src/mcp/effective-config.ts +3 -3
- package/src/mcp/mcp-header-store.ts +29 -45
- package/src/mcp/spec-schema.ts +111 -0
- package/src/mcp/startup.ts +8 -4
- package/src/mcp/workspace-mcp-config.ts +188 -0
- package/src/messaging/provider.ts +10 -3
- package/src/messaging/providers/__tests__/proactive-address.test.ts +95 -0
- package/src/messaging/providers/callback-routing.ts +18 -0
- package/src/messaging/providers/channel-transport.ts +56 -0
- package/src/messaging/providers/discord/transport.ts +17 -0
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +6 -17
- package/src/messaging/providers/slack/adapter.ts +7 -44
- package/src/messaging/providers/slack/transport.ts +16 -0
- package/src/messaging/providers/telegram-bot/adapter.ts +1 -48
- package/src/messaging/providers/telegram-bot/client.ts +0 -26
- package/src/messaging/providers/telegram-bot/transport.ts +20 -0
- package/src/messaging/providers/whatsapp/adapter.ts +1 -48
- package/src/messaging/providers/whatsapp/transport.ts +18 -0
- package/src/monitoring/__tests__/workspace-git-heartbeat.test.ts +8 -0
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +55 -0
- package/src/notifications/__tests__/guardian-feed-projection.test.ts +158 -2
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +153 -3
- package/src/notifications/__tests__/schedule-result-producer.test.ts +99 -3
- package/src/notifications/adapters/macos.ts +3 -0
- package/src/notifications/assistant-reply-producer.ts +8 -3
- package/src/notifications/conversation-pairing.ts +36 -16
- package/src/notifications/delivered-post-record.ts +11 -1
- package/src/notifications/guardian-feed-projection.ts +125 -5
- package/src/notifications/home-feed-side-effect.ts +55 -7
- package/src/notifications/schedule-result-producer.ts +66 -22
- package/src/oauth/__tests__/seed-providers-managed.test.ts +54 -0
- package/src/oauth/byo-connection.test.ts +45 -0
- package/src/oauth/byo-connection.ts +36 -6
- package/src/oauth/connection-resolver.test.ts +73 -0
- package/src/oauth/connection-resolver.ts +95 -2
- package/src/oauth/seed-providers.ts +165 -0
- package/src/outbound-proxy/certs.ts +3 -5
- package/src/permissions/checker.test.ts +11 -6
- package/src/persistence/compaction-log-store-clickhouse.ts +3 -3
- package/src/persistence/conversation-crud.ts +71 -63
- package/src/persistence/conversation-lineage.ts +12 -4
- package/src/persistence/conversation-title-placeholders.ts +23 -0
- package/src/persistence/conversation-title-service.ts +9 -26
- package/src/persistence/llm-request-log-sink-clickhouse.ts +3 -3
- package/src/persistence/llm-request-log-source-clickhouse.ts +3 -3
- package/src/persistence/message-cursor.ts +78 -0
- package/src/persistence/message-reads.ts +30 -0
- package/src/persistence/migrations/157-invite-contact-id.ts +18 -6
- package/src/persistence/migrations/__tests__/157-invite-contact-id.test.ts +149 -0
- package/src/persistence/schema/memory-core.ts +5 -0
- package/src/platform/client.test.ts +4 -2
- package/src/platform/client.ts +2 -10
- package/src/plugin-api/constants.ts +2 -0
- package/src/plugin-api/index.ts +1 -0
- package/src/plugin-api/types.ts +2 -0
- package/src/plugins/defaults/index.ts +2 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +46 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-backfill.test.ts +118 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-preserve.test.ts +83 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +9 -5
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +20 -10
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-state.test.ts +219 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +31 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-trigger-check.test.ts +63 -0
- package/src/plugins/defaults/memory/__tests__/shutdown-hook.test.ts +4 -0
- package/src/plugins/defaults/memory/graph/capability-seed.ts +4 -6
- package/src/plugins/defaults/memory/hooks/init.ts +10 -0
- package/src/plugins/defaults/memory/hooks/message-deleted.ts +21 -0
- package/src/plugins/defaults/memory/hooks/shutdown.ts +2 -0
- package/src/plugins/defaults/memory/jobs-worker.ts +13 -0
- package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +34 -60
- package/src/plugins/defaults/memory/memory-retrospective-cursor-backfill.ts +126 -0
- package/src/plugins/defaults/memory/memory-retrospective-cursor-preserve.ts +79 -0
- package/src/plugins/defaults/memory/memory-retrospective-cursor.ts +28 -0
- package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +3 -5
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +12 -3
- package/src/plugins/defaults/memory/memory-retrospective-state.ts +312 -71
- package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +2 -1
- package/src/plugins/defaults/memory/memory-retrospective-trigger-check.ts +5 -3
- package/src/plugins/defaults/memory/skill-md-mtime-poll.ts +9 -0
- package/src/plugins/defaults/memory/startup.ts +6 -1
- package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +7 -9
- package/src/plugins/defaults/memory/substrate/skill-content.ts +2 -2
- package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +89 -0
- package/src/plugins/defaults/memory/worker.ts +6 -0
- package/src/plugins/mcp-servers.ts +15 -75
- package/src/providers/__tests__/connection-model-compat.test.ts +3 -3
- package/src/providers/gemini/client.ts +9 -23
- package/src/providers/gemini-thought-signature.test.ts +63 -0
- package/src/providers/gemini-thought-signature.ts +44 -0
- package/src/providers/inference/auth.ts +2 -3
- package/src/providers/inference/connection-availability.ts +27 -8
- package/src/providers/inference/resolve-auth.ts +22 -4
- package/src/providers/inference/service-account-token.ts +248 -0
- package/src/providers/model-catalog.ts +34 -0
- package/src/providers/model-intents.ts +10 -5
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +146 -18
- package/src/providers/openai/__tests__/chat-completions-provider-thought-signature.test.ts +613 -0
- package/src/providers/openai/__tests__/google-thought-signature.test.ts +235 -0
- package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +83 -1
- package/src/providers/openai/chat-completions-provider.ts +192 -78
- package/src/providers/openai/codex-models.ts +0 -4
- package/src/providers/openai/google-thought-signature.ts +320 -0
- package/src/runtime/AGENTS.md +2 -0
- package/src/runtime/__tests__/channel-send.test.ts +443 -0
- package/src/runtime/__tests__/desktop-stream-upgrade.test.ts +89 -0
- package/src/runtime/__tests__/live-voice-upgrade.test.ts +58 -0
- package/src/runtime/__tests__/runtime-ws-test-utils.ts +72 -0
- package/src/runtime/__tests__/watch-stream-upgrade.test.ts +59 -0
- package/src/runtime/auth/same-actor.ts +38 -3
- package/src/runtime/channel-send.ts +316 -0
- package/src/runtime/http-server.ts +160 -151
- package/src/runtime/routes/__tests__/acp-routes.test.ts +159 -4
- package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +205 -0
- package/src/runtime/routes/__tests__/channel-send-routes.test.ts +127 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +108 -1
- package/src/runtime/routes/__tests__/default-provider-routes.test.ts +2 -2
- package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +107 -0
- package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +16 -12
- package/src/runtime/routes/acp-routes.test.ts +15 -0
- package/src/runtime/routes/acp-routes.ts +72 -9
- package/src/runtime/routes/app-management-routes.ts +73 -0
- package/src/runtime/routes/app-routes.ts +1 -1
- package/src/runtime/routes/channel-send-routes.ts +85 -0
- package/src/runtime/routes/conversation-list-routes.ts +14 -3
- package/src/runtime/routes/conversation-query-routes.ts +40 -0
- package/src/runtime/routes/credential-routes.ts +19 -10
- package/src/runtime/routes/desktop-setup-routes.test.ts +74 -0
- package/src/runtime/routes/desktop-setup-routes.ts +35 -0
- package/src/runtime/routes/diagnostics-routes.ts +55 -3
- package/src/runtime/routes/index.ts +4 -2
- package/src/runtime/routes/log-export-routes.ts +2 -0
- package/src/runtime/routes/mcp-auth-routes.ts +39 -47
- package/src/runtime/routes/migration-routes.ts +6 -20
- package/src/runtime/routes/platform-routes.ts +18 -15
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +28 -0
- package/src/runtime/services/conversation-serializer.ts +16 -3
- package/src/runtime/sync/resource-sync-events.ts +2 -1
- package/src/schedule/worker.ts +1 -1
- package/src/security/__tests__/ces-http-record-backend.test.ts +137 -0
- package/src/security/ces-http-record-backend.ts +120 -0
- package/src/security/ces-rpc-record-backend.ts +7 -0
- package/src/skills/__tests__/workspace-skill-md-signature.test.ts +60 -0
- package/src/skills/workspace-skill-md-signature.ts +50 -0
- package/src/subagent/consult-context.ts +12 -25
- package/src/tools/acp/list-agents.test.ts +3 -1
- package/src/tools/acp/spawn.test.ts +146 -11
- package/src/tools/acp/spawn.ts +21 -3
- package/src/tools/acp/status.test.ts +94 -0
- package/src/tools/acp/status.ts +40 -3
- package/src/tools/app-control/skill-proxy-bridge.ts +2 -1
- package/src/tools/capability-offer.test.ts +22 -2
- package/src/tools/capability-offer.ts +16 -1
- package/src/tools/computer-use/definitions.ts +4 -2
- package/src/tools/computer-use/skill-proxy-bridge.ts +2 -1
- package/src/tools/credentials/metadata-store.ts +100 -0
- package/src/tools/host-filesystem/edit.test.ts +2 -1
- package/src/tools/host-filesystem/edit.ts +2 -2
- package/src/tools/host-filesystem/read.test.ts +2 -1
- package/src/tools/host-filesystem/read.ts +2 -2
- package/src/tools/host-filesystem/transfer.test.ts +2 -1
- package/src/tools/host-filesystem/transfer.ts +2 -2
- package/src/tools/host-filesystem/write.test.ts +2 -1
- package/src/tools/host-filesystem/write.ts +2 -2
- package/src/tools/host-terminal/host-shell.test.ts +16 -0
- package/src/tools/host-terminal/host-shell.ts +4 -20
- package/src/tools/policy-context.ts +6 -7
- package/src/tools/subagent/spawn.ts +5 -8
- package/src/tools/terminal/__tests__/safe-env.test.ts +6 -6
- package/src/tools/terminal/__tests__/sanitized-bash.test.ts +57 -0
- package/src/tools/terminal/safe-env.ts +23 -3
- package/src/tools/terminal/sanitized-bash.ts +105 -0
- package/src/tools/watch/watch-retro-report.ts +0 -8
- package/src/util/__tests__/language-subtag.test.ts +23 -1
- package/src/util/ca-bundle.ts +13 -0
- package/src/util/language-subtag.ts +16 -0
- package/src/util/platform.ts +8 -0
- package/src/workspace/git-porcelain.ts +66 -0
- package/src/workspace/git-service.ts +5 -54
- package/src/workspace/migrations/154-repair-retired-codex-gpt-5-4-model-ids.ts +258 -0
- package/src/workspace/migrations/155-move-front-model-config-to-voice.ts +78 -0
- package/src/workspace/migrations/156-extract-workspace-mcp-json.ts +214 -0
- package/src/workspace/migrations/__tests__/155-move-front-model-config-to-voice.test.ts +102 -0
- package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +199 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/src/__tests__/app-source-watcher.test.ts +0 -218
- package/src/__tests__/config-watcher-skill-reseed.test.ts +0 -359
- package/src/__tests__/speaker-identification.test.ts +0 -56
- package/src/calls/speaker-identification.ts +0 -233
- package/src/daemon/app-source-watcher.ts +0 -218
- package/src/messaging/providers/whatsapp/client.ts +0 -29
- package/src/runtime/routes/debug-bash-routes.ts +0 -173
- package/src/util/fs-watcher-error.ts +0 -36
- /package/src/{live-voice → calls}/progress-phrases.ts +0 -0
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
// ── Mutable state for injected vault ────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
const vaultStore = new Map<string, string>();
|
|
6
|
+
const writtenKeys = new Map<string, string>();
|
|
7
|
+
|
|
8
|
+
// ── Import under test ────────────────────────────────────────────────────────
|
|
9
|
+
//
|
|
10
|
+
// No mock.module — prevents all bun worker contamination. Vault access is
|
|
11
|
+
// injected through _injectVaultAccessors / _resetVaultAccessors instead.
|
|
12
|
+
|
|
13
|
+
const {
|
|
14
|
+
getValidServiceAccountToken,
|
|
15
|
+
parseServiceAccountKey,
|
|
16
|
+
_resetServiceAccountMutex,
|
|
17
|
+
_injectVaultAccessors,
|
|
18
|
+
_resetVaultAccessors,
|
|
19
|
+
} = await import("../providers/inference/service-account-token.js");
|
|
20
|
+
|
|
21
|
+
// ── Hooks ────────────────────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
vaultStore.clear();
|
|
25
|
+
writtenKeys.clear();
|
|
26
|
+
_resetServiceAccountMutex();
|
|
27
|
+
_injectVaultAccessors(
|
|
28
|
+
async (key: string) => vaultStore.get(key),
|
|
29
|
+
async (key: string, value: string) => {
|
|
30
|
+
vaultStore.set(key, value);
|
|
31
|
+
writtenKeys.set(key, value);
|
|
32
|
+
return true;
|
|
33
|
+
},
|
|
34
|
+
);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
afterEach(() => {
|
|
38
|
+
_resetVaultAccessors();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// ── Real 2048-bit RSA PKCS#8 key generated offline for unit tests only ───────
|
|
42
|
+
|
|
43
|
+
const TEST_PRIVATE_KEY =
|
|
44
|
+
"-----BEGIN PRIVATE KEY-----\n" +
|
|
45
|
+
"MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDFnEC0wI/cNKyO\n" +
|
|
46
|
+
"XzkqOGAvsTTx4Q+DPjDBPVoy3yHVk7cGYIf5sg3nsrQlE4ARMEQ9MXKUvw+VY59c\n" +
|
|
47
|
+
"zzpy9/JnCN9v5ymZUrowF/87Xn0muklHOhYPFz+73BunncH0Dk0mgCRWEfd/xoGB\n" +
|
|
48
|
+
"bMI40mI2hPMN5aGU4moTmVzTsgvatp0tAk90oaUQBZ/Y3PVjoR+SHbKhQtZW8eiO\n" +
|
|
49
|
+
"zUGyBhzmlmMZcquWxHui8QbO//kEKg7jv2+7npkdW/S0meoDeKZzqRNACKci1CQB\n" +
|
|
50
|
+
"N/skFqy6B8qYekhVYtAaIQOLtuD+VdLL7azMBqIHZSVQQXD6KisN8XSj95k/Fyeb\n" +
|
|
51
|
+
"UEmhvlwrAgMBAAECggEALKr7PWBhpGcLskr4AwEnTOCDlUFIXmRKZQPXRbSGU17G\n" +
|
|
52
|
+
"elyLn/eNiRgt/EuCIWCFrEdnHRRrE1unhDUbgy1kz8GDsCnSj2PRzlk+Sk87fuuQ\n" +
|
|
53
|
+
"8NbCcbrRn0dLwQG5+87XxaIllIcPKQTX9Ud+R5XWuXgn8LY9Zggf7jo2b2vJnjBO\n" +
|
|
54
|
+
"2SIS4M24a8x/1hHLjrTKtyO1yF3TI/9z72z4+yYiXAHxBugCcg6kv27pMEtOl5hv\n" +
|
|
55
|
+
"HW6ogBIDKVIVp3DmjaWxPawy1ytxpI1mR1lfGKoNI3iutLNfuHl3MwinYL7oCiL/\n" +
|
|
56
|
+
"rejuz37rXicFZ40YTKHDqKy+brLS6Ahg0R7XxSNtcQKBgQD7i4oh4stD+geFoPoJ\n" +
|
|
57
|
+
"o/grtOazn2GKuMOrcbBSaTfs4PZfcSRT8Egzhl3Vf/3Z30T3ChugFKkpN9naDEL3\n" +
|
|
58
|
+
"0SEH5sR5oMhGzzE3ncwTPR4fClpiX61JIY6iVL5AQukXIK+v+hCuhRu9YepMbGWq\n" +
|
|
59
|
+
"ndl/uH4LofuEctJYaovnghKedwKBgQDJHC7NiqNhGB6YzwgvYK2GVrFLdPpcfLbl\n" +
|
|
60
|
+
"y5DZKaVYCs3cLFCH7qHEdsmVEWkQ9UkRhxNPGpgQadvYNAd9Bbf2au2lfNuCt0Kn\n" +
|
|
61
|
+
"dYDgkxHAo4PByEznOBgQYoG5IjF8bdubQJbJHQh/bj5EkzeKDggD37ED0fTdWIiJ\n" +
|
|
62
|
+
"Nhh0/q2Y7QKBgGlLMsoWbjGJMqbcCG4KpfJdqghcWe9Agh982mrUwmU0kczHxoYg\n" +
|
|
63
|
+
"WYi/4P1iszcE/5BNOd0baOfpChb89PdZIfN1xJ6JD+ycBCUct3UCK16g7c6p8BZh\n" +
|
|
64
|
+
"ppwKzwyFNZ7JvuDx0xPKgh8XIoVVBoWRx55v8ssZxffePHBoQSHqT7LnAoGAB1vO\n" +
|
|
65
|
+
"opq2C7F0PScmJ4WkE8xMRHSNTKELmDDwpeGOOUB789hzQeYLaULncVrnu0UeXVPR\n" +
|
|
66
|
+
"8w0ID2GwN6L/uyVga9XRyEJfTdMizznPc2guOBxDj+2iUruMtdzE3l9P7Dug03dz\n" +
|
|
67
|
+
"jPxQ+UixYpT4bUbCKFIhCgC+svKwXETsdCt1cqkCgYBVSSabJNSvFfqYz/VJbRK7\n" +
|
|
68
|
+
"cvxEr3mcV/I/+5BoyTmTKQGkIRl6xCMhqZk019hXbdN66jMQ9zg6wni4UKA43cXI\n" +
|
|
69
|
+
"/3NHY86IvdpLORlYqKVHycZsqSVQE6Z3V1G4C2wVKUZtWsoT6Wr+Ind+V+VzbDD2\n" +
|
|
70
|
+
"wHbW4FhxHN9iwdHfmJyq+g==\n" +
|
|
71
|
+
"-----END PRIVATE KEY-----\n";
|
|
72
|
+
|
|
73
|
+
const TEST_SERVICE_ACCOUNT = JSON.stringify({
|
|
74
|
+
type: "service_account",
|
|
75
|
+
client_email: "test@example.com",
|
|
76
|
+
private_key: TEST_PRIVATE_KEY,
|
|
77
|
+
token_uri: "https://oauth2.googleapis.com/token",
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// ── Tests ────────────────────────────────────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
describe("service-account-token", () => {
|
|
83
|
+
// -- parseServiceAccountKey ------------------------------------------------
|
|
84
|
+
|
|
85
|
+
describe("parseServiceAccountKey", () => {
|
|
86
|
+
test("returns null for empty string", () => {
|
|
87
|
+
expect(parseServiceAccountKey("")).toBeNull();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("returns null for invalid JSON", () => {
|
|
91
|
+
expect(parseServiceAccountKey("not-json")).toBeNull();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("returns null when required fields are missing", () => {
|
|
95
|
+
expect(
|
|
96
|
+
parseServiceAccountKey(
|
|
97
|
+
JSON.stringify({ client_email: "test@example.com" }),
|
|
98
|
+
),
|
|
99
|
+
).toBeNull();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("returns the key when all required fields are present", () => {
|
|
103
|
+
const key = parseServiceAccountKey(TEST_SERVICE_ACCOUNT);
|
|
104
|
+
expect(key).not.toBeNull();
|
|
105
|
+
expect(key?.client_email).toBe("test@example.com");
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// -- getValidServiceAccountToken -------------------------------------------
|
|
110
|
+
|
|
111
|
+
test("returns not_found when no credential is stored", async () => {
|
|
112
|
+
const result = await getValidServiceAccountToken("credential/vertex-ai");
|
|
113
|
+
expect(result).toEqual({ ok: false, reason: "not_found" });
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("returns invalid_config when credential is not valid JSON", async () => {
|
|
117
|
+
vaultStore.set("credential/vertex-ai", "not-json");
|
|
118
|
+
const result = await getValidServiceAccountToken("credential/vertex-ai");
|
|
119
|
+
expect(result).toEqual({ ok: false, reason: "invalid_config" });
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("returns invalid_config when credential JSON is missing required fields", async () => {
|
|
123
|
+
vaultStore.set(
|
|
124
|
+
"credential/vertex-ai",
|
|
125
|
+
JSON.stringify({ client_email: "missing-other-fields@example.com" }),
|
|
126
|
+
);
|
|
127
|
+
const result = await getValidServiceAccountToken("credential/vertex-ai");
|
|
128
|
+
expect(result).toEqual({ ok: false, reason: "invalid_config" });
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("returns cached token when the cache blob is still fresh", async () => {
|
|
132
|
+
const futureExpiry = Math.floor(Date.now() / 1000) + 7200;
|
|
133
|
+
vaultStore.set("credential/vertex-ai", TEST_SERVICE_ACCOUNT);
|
|
134
|
+
vaultStore.set(
|
|
135
|
+
"credential/vertex-ai/token_cache",
|
|
136
|
+
JSON.stringify({
|
|
137
|
+
access_token: "cached-token",
|
|
138
|
+
expires_at: futureExpiry,
|
|
139
|
+
}),
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
const result = await getValidServiceAccountToken("credential/vertex-ai");
|
|
143
|
+
expect(result).toEqual({ ok: true, token: "cached-token" });
|
|
144
|
+
// Nothing new should have been written (no exchange performed).
|
|
145
|
+
expect(writtenKeys.size).toBe(0);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("returns cached token when cache blob has no expiry", async () => {
|
|
149
|
+
vaultStore.set("credential/vertex-ai", TEST_SERVICE_ACCOUNT);
|
|
150
|
+
vaultStore.set(
|
|
151
|
+
"credential/vertex-ai/token_cache",
|
|
152
|
+
JSON.stringify({ access_token: "no-expiry-token" }),
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
const result = await getValidServiceAccountToken("credential/vertex-ai");
|
|
156
|
+
expect(result).toEqual({ ok: true, token: "no-expiry-token" });
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("coalesces concurrent callers for the same credential onto one exchange", async () => {
|
|
160
|
+
const pastExpiry = Math.floor(Date.now() / 1000) - 600;
|
|
161
|
+
vaultStore.set("credential/vertex-ai", TEST_SERVICE_ACCOUNT);
|
|
162
|
+
vaultStore.set(
|
|
163
|
+
"credential/vertex-ai/token_cache",
|
|
164
|
+
JSON.stringify({ access_token: "stale-token", expires_at: pastExpiry }),
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
let fetchCallCount = 0;
|
|
168
|
+
const originalFetch = globalThis.fetch;
|
|
169
|
+
globalThis.fetch = (async () => {
|
|
170
|
+
fetchCallCount += 1;
|
|
171
|
+
return {
|
|
172
|
+
ok: true,
|
|
173
|
+
json: async () => ({ access_token: "new-token", expires_in: 3600 }),
|
|
174
|
+
} as Response;
|
|
175
|
+
}) as unknown as typeof fetch;
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
const [r1, r2] = await Promise.all([
|
|
179
|
+
getValidServiceAccountToken("credential/vertex-ai"),
|
|
180
|
+
getValidServiceAccountToken("credential/vertex-ai"),
|
|
181
|
+
]);
|
|
182
|
+
|
|
183
|
+
expect(r1).toEqual({ ok: true, token: "new-token" });
|
|
184
|
+
expect(r2).toEqual({ ok: true, token: "new-token" });
|
|
185
|
+
expect(fetchCallCount).toBe(1);
|
|
186
|
+
} finally {
|
|
187
|
+
globalThis.fetch = originalFetch;
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test("concurrent callers for different credentials exchange independently", async () => {
|
|
192
|
+
vaultStore.set("credential/vertex-a", TEST_SERVICE_ACCOUNT);
|
|
193
|
+
vaultStore.set("credential/vertex-b", TEST_SERVICE_ACCOUNT);
|
|
194
|
+
|
|
195
|
+
let fetchCallCount = 0;
|
|
196
|
+
const originalFetch = globalThis.fetch;
|
|
197
|
+
globalThis.fetch = (async () => {
|
|
198
|
+
fetchCallCount += 1;
|
|
199
|
+
return {
|
|
200
|
+
ok: true,
|
|
201
|
+
json: async () => ({
|
|
202
|
+
access_token: `token-${fetchCallCount}`,
|
|
203
|
+
expires_in: 3600,
|
|
204
|
+
}),
|
|
205
|
+
} as Response;
|
|
206
|
+
}) as unknown as typeof fetch;
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
const [r1, r2] = await Promise.all([
|
|
210
|
+
getValidServiceAccountToken("credential/vertex-a"),
|
|
211
|
+
getValidServiceAccountToken("credential/vertex-b"),
|
|
212
|
+
]);
|
|
213
|
+
|
|
214
|
+
expect(r1.ok).toBe(true);
|
|
215
|
+
expect(r2.ok).toBe(true);
|
|
216
|
+
// Each credential triggers its own exchange.
|
|
217
|
+
expect(fetchCallCount).toBe(2);
|
|
218
|
+
} finally {
|
|
219
|
+
globalThis.fetch = originalFetch;
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test("caches token+expiry as a single JSON blob", async () => {
|
|
224
|
+
vaultStore.set("credential/vertex-ai", TEST_SERVICE_ACCOUNT);
|
|
225
|
+
|
|
226
|
+
const originalFetch = globalThis.fetch;
|
|
227
|
+
globalThis.fetch = (async () => ({
|
|
228
|
+
ok: true,
|
|
229
|
+
json: async () => ({ access_token: "fresh-token", expires_in: 3600 }),
|
|
230
|
+
})) as unknown as typeof fetch;
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
await getValidServiceAccountToken("credential/vertex-ai");
|
|
234
|
+
|
|
235
|
+
const blob = vaultStore.get("credential/vertex-ai/token_cache");
|
|
236
|
+
expect(blob).toBeDefined();
|
|
237
|
+
const parsed = JSON.parse(blob!) as {
|
|
238
|
+
access_token: string;
|
|
239
|
+
expires_at: number;
|
|
240
|
+
};
|
|
241
|
+
expect(parsed.access_token).toBe("fresh-token");
|
|
242
|
+
expect(typeof parsed.expires_at).toBe("number");
|
|
243
|
+
} finally {
|
|
244
|
+
globalThis.fetch = originalFetch;
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
});
|
|
@@ -60,7 +60,7 @@ function makeConfig(overrides: Partial<AssistantConfig> = {}): AssistantConfig {
|
|
|
60
60
|
return {
|
|
61
61
|
skills: {
|
|
62
62
|
entries: {},
|
|
63
|
-
load: { extraDirs: []
|
|
63
|
+
load: { extraDirs: [] },
|
|
64
64
|
install: { nodeManager: "npm" },
|
|
65
65
|
allowBundled: null,
|
|
66
66
|
remoteProviders: {
|
|
@@ -28,7 +28,7 @@ function makeConfig(overrides: Partial<AssistantConfig> = {}): AssistantConfig {
|
|
|
28
28
|
return {
|
|
29
29
|
skills: {
|
|
30
30
|
entries: {},
|
|
31
|
-
load: { extraDirs: []
|
|
31
|
+
load: { extraDirs: [] },
|
|
32
32
|
install: { nodeManager: "npm" },
|
|
33
33
|
allowBundled: null,
|
|
34
34
|
remoteProviders: {
|
|
@@ -233,7 +233,7 @@ describe("resolveSkillStates with feature flags", () => {
|
|
|
233
233
|
const config = makeConfig({
|
|
234
234
|
skills: {
|
|
235
235
|
entries: { [DECLARED_SKILL_ID]: { enabled: true } },
|
|
236
|
-
load: { extraDirs: []
|
|
236
|
+
load: { extraDirs: [] },
|
|
237
237
|
install: { nodeManager: "npm" },
|
|
238
238
|
allowBundled: null,
|
|
239
239
|
remoteProviders: {
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for app source fingerprinting: walk exclusions, comparison, inspect
|
|
3
|
+
* statuses, and the compile-time sidecar format.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
mkdirSync,
|
|
8
|
+
mkdtempSync,
|
|
9
|
+
readFileSync,
|
|
10
|
+
rmSync,
|
|
11
|
+
symlinkSync,
|
|
12
|
+
writeFileSync,
|
|
13
|
+
} from "node:fs";
|
|
14
|
+
import { tmpdir } from "node:os";
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
17
|
+
|
|
18
|
+
import {
|
|
19
|
+
compareAppSourceFingerprint,
|
|
20
|
+
computeAppSourceFingerprint,
|
|
21
|
+
inspectAppSource,
|
|
22
|
+
parseAppSourceFingerprint,
|
|
23
|
+
SOURCE_FINGERPRINT_FILENAME,
|
|
24
|
+
writeAppSourceFingerprint,
|
|
25
|
+
} from "../apps/source-fingerprint.js";
|
|
26
|
+
|
|
27
|
+
let root: string;
|
|
28
|
+
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
root = mkdtempSync(join(tmpdir(), "app-source-fingerprint-"));
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
afterEach(() => {
|
|
34
|
+
rmSync(root, { recursive: true, force: true });
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
function write(rel: string, contents: string): void {
|
|
38
|
+
const abs = join(root, rel);
|
|
39
|
+
mkdirSync(join(abs, ".."), { recursive: true });
|
|
40
|
+
writeFileSync(abs, contents);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
describe("computeAppSourceFingerprint", () => {
|
|
44
|
+
test("digests source files with POSIX-relative keys", () => {
|
|
45
|
+
write("src/main.tsx", "export const x = 1;");
|
|
46
|
+
write("src/index.html", "<html></html>");
|
|
47
|
+
|
|
48
|
+
const fp = computeAppSourceFingerprint(root);
|
|
49
|
+
|
|
50
|
+
expect(Object.keys(fp.files).sort()).toEqual([
|
|
51
|
+
"src/index.html",
|
|
52
|
+
"src/main.tsx",
|
|
53
|
+
]);
|
|
54
|
+
expect(fp.algorithm).toBe("sha256");
|
|
55
|
+
expect(fp.files["src/main.tsx"]).toMatch(/^[0-9a-f]{64}$/);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("skips dist, records, node_modules, and .git", () => {
|
|
59
|
+
write("src/main.tsx", "src");
|
|
60
|
+
write("dist/main.js", "compiled");
|
|
61
|
+
write("records/rec-1.json", "{}");
|
|
62
|
+
write("node_modules/pkg/index.js", "dep");
|
|
63
|
+
write("src/node_modules/nested/index.js", "nested");
|
|
64
|
+
write(".git/HEAD", "ref");
|
|
65
|
+
write("src/.git/config", "nope");
|
|
66
|
+
|
|
67
|
+
const fp = computeAppSourceFingerprint(root);
|
|
68
|
+
|
|
69
|
+
expect(Object.keys(fp.files)).toEqual(["src/main.tsx"]);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("skips symlinks", () => {
|
|
73
|
+
write("src/real.tsx", "hello");
|
|
74
|
+
mkdirSync(join(root, "src"), { recursive: true });
|
|
75
|
+
symlinkSync(join(root, "src/real.tsx"), join(root, "src/link.tsx"));
|
|
76
|
+
|
|
77
|
+
const fp = computeAppSourceFingerprint(root);
|
|
78
|
+
|
|
79
|
+
expect(Object.keys(fp.files)).toEqual(["src/real.tsx"]);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe("compareAppSourceFingerprint", () => {
|
|
84
|
+
test("reports modified, added, and removed paths", () => {
|
|
85
|
+
write("src/main.tsx", "one");
|
|
86
|
+
write("src/keep.tsx", "keep");
|
|
87
|
+
const baseline = computeAppSourceFingerprint(root);
|
|
88
|
+
|
|
89
|
+
write("src/main.tsx", "two");
|
|
90
|
+
write("src/new.tsx", "new");
|
|
91
|
+
rmSync(join(root, "src/keep.tsx"));
|
|
92
|
+
|
|
93
|
+
const diff = compareAppSourceFingerprint(root, baseline);
|
|
94
|
+
expect(diff.clean).toBe(false);
|
|
95
|
+
expect(diff.modified).toEqual(["src/main.tsx"]);
|
|
96
|
+
expect(diff.added).toEqual(["src/new.tsx"]);
|
|
97
|
+
expect(diff.removed).toEqual(["src/keep.tsx"]);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("is clean when source matches the baseline", () => {
|
|
101
|
+
write("src/main.tsx", "same");
|
|
102
|
+
const baseline = computeAppSourceFingerprint(root);
|
|
103
|
+
const diff = compareAppSourceFingerprint(root, baseline);
|
|
104
|
+
expect(diff.clean).toBe(true);
|
|
105
|
+
expect(diff.modified).toEqual([]);
|
|
106
|
+
expect(diff.added).toEqual([]);
|
|
107
|
+
expect(diff.removed).toEqual([]);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe("inspectAppSource", () => {
|
|
112
|
+
test("reports never_compiled when dist index is missing even if a fingerprint exists", () => {
|
|
113
|
+
write("src/main.tsx", "src");
|
|
114
|
+
mkdirSync(join(root, "dist"), { recursive: true });
|
|
115
|
+
writeAppSourceFingerprint(root, join(root, "dist"));
|
|
116
|
+
|
|
117
|
+
const result = inspectAppSource(root);
|
|
118
|
+
expect(result.status).toBe("never_compiled");
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("reports unknown_baseline when dist exists without a fingerprint", () => {
|
|
122
|
+
write("src/main.tsx", "src");
|
|
123
|
+
write("dist/index.html", "<html></html>");
|
|
124
|
+
const result = inspectAppSource(root);
|
|
125
|
+
expect(result.status).toBe("unknown_baseline");
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("reports clean after writing a fingerprint that matches source", () => {
|
|
129
|
+
write("src/main.tsx", "src");
|
|
130
|
+
mkdirSync(join(root, "dist"), { recursive: true });
|
|
131
|
+
writeFileSync(join(root, "dist", "index.html"), "<html></html>");
|
|
132
|
+
writeAppSourceFingerprint(root, join(root, "dist"));
|
|
133
|
+
|
|
134
|
+
const result = inspectAppSource(root);
|
|
135
|
+
expect(result.status).toBe("clean");
|
|
136
|
+
expect(result.compiledAt).toMatch(/^\d{4}-\d{2}-\d{2}T/);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("reports stale after a source edit", () => {
|
|
140
|
+
write("src/main.tsx", "before");
|
|
141
|
+
mkdirSync(join(root, "dist"), { recursive: true });
|
|
142
|
+
writeFileSync(join(root, "dist", "index.html"), "<html></html>");
|
|
143
|
+
writeAppSourceFingerprint(root, join(root, "dist"));
|
|
144
|
+
write("src/main.tsx", "after");
|
|
145
|
+
|
|
146
|
+
const result = inspectAppSource(root);
|
|
147
|
+
expect(result.status).toBe("stale");
|
|
148
|
+
expect(result.modified).toEqual(["src/main.tsx"]);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe("parseAppSourceFingerprint", () => {
|
|
153
|
+
test("accepts a compile sidecar with compiledAt", () => {
|
|
154
|
+
const parsed = parseAppSourceFingerprint({
|
|
155
|
+
algorithm: "sha256",
|
|
156
|
+
compiledAt: "2026-09-09T00:00:00.000Z",
|
|
157
|
+
files: { "src/main.tsx": "abc" },
|
|
158
|
+
});
|
|
159
|
+
expect(parsed).toEqual({
|
|
160
|
+
algorithm: "sha256",
|
|
161
|
+
compiledAt: "2026-09-09T00:00:00.000Z",
|
|
162
|
+
files: { "src/main.tsx": "abc" },
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("returns null for an invalid sidecar", () => {
|
|
167
|
+
expect(parseAppSourceFingerprint({ algorithm: "md5", files: {} })).toBe(
|
|
168
|
+
null,
|
|
169
|
+
);
|
|
170
|
+
expect(parseAppSourceFingerprint(null)).toBeNull();
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test("writeAppSourceFingerprint records a sidecar the parser accepts", () => {
|
|
174
|
+
write("src/main.tsx", "src");
|
|
175
|
+
mkdirSync(join(root, "dist"), { recursive: true });
|
|
176
|
+
writeAppSourceFingerprint(root, join(root, "dist"));
|
|
177
|
+
const raw = JSON.parse(
|
|
178
|
+
readFileSync(join(root, "dist", SOURCE_FINGERPRINT_FILENAME), "utf-8"),
|
|
179
|
+
);
|
|
180
|
+
expect(parseAppSourceFingerprint(raw)?.files["src/main.tsx"]).toMatch(
|
|
181
|
+
/^[0-9a-f]{64}$/,
|
|
182
|
+
);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
@@ -249,14 +249,23 @@ describe("createResolveToolsCallback — toolContextPin", () => {
|
|
|
249
249
|
});
|
|
250
250
|
}
|
|
251
251
|
|
|
252
|
-
test("control: without a pin, a clientless fork drops
|
|
252
|
+
test("control: without a pin, a clientless fork drops client-gated tools but keeps host and UI tools", () => {
|
|
253
253
|
projectedSkillToolNames = [];
|
|
254
254
|
const resolve = createResolveToolsCallback(
|
|
255
255
|
CLIENT_GATED_DEFS,
|
|
256
256
|
clientlessExecutionCtx(),
|
|
257
257
|
)!;
|
|
258
258
|
|
|
259
|
-
|
|
259
|
+
// Host tool definitions stay on the wire for background turns, and
|
|
260
|
+
// ui_show stays because background UI surfaces persist and return. The
|
|
261
|
+
// executor rejects host tools on explicitly non-interactive turns before
|
|
262
|
+
// dispatch; ask_question and request_system_permission remain
|
|
263
|
+
// client-gated at resolution.
|
|
264
|
+
expect(resolve(EMPTY_HISTORY).map((t) => t.name)).toEqual([
|
|
265
|
+
"remember",
|
|
266
|
+
"host_bash",
|
|
267
|
+
"ui_show",
|
|
268
|
+
]);
|
|
260
269
|
});
|
|
261
270
|
|
|
262
271
|
test("a desktop-source pin restores the host/UI/client tool defs on the wire", () => {
|
|
@@ -291,7 +300,14 @@ describe("createResolveToolsCallback — toolContextPin", () => {
|
|
|
291
300
|
}),
|
|
292
301
|
)!;
|
|
293
302
|
|
|
294
|
-
|
|
303
|
+
// Host tool definitions stay on the wire for background turns, and
|
|
304
|
+
// ui_show survives the clientless pin: it persists and returns. The
|
|
305
|
+
// executor rejects host tools on explicitly non-interactive turns.
|
|
306
|
+
expect(resolve(EMPTY_HISTORY).map((t) => t.name)).toEqual([
|
|
307
|
+
"remember",
|
|
308
|
+
"host_bash",
|
|
309
|
+
"ui_show",
|
|
310
|
+
]);
|
|
295
311
|
});
|
|
296
312
|
|
|
297
313
|
test("invariant: a pinned-in tool is on the wire but can never execute", async () => {
|
|
@@ -3447,8 +3447,9 @@ describe("Subagent advisor-role consult", () => {
|
|
|
3447
3447
|
});
|
|
3448
3448
|
|
|
3449
3449
|
test("advisor consults still run when the parent conversation is gone", async () => {
|
|
3450
|
-
//
|
|
3451
|
-
//
|
|
3450
|
+
// Advisor consults do not look up the parent conversation for a skill
|
|
3451
|
+
// catalog snapshot. An evicted parent still gets a consult; the context
|
|
3452
|
+
// pack loads the catalog from disk independently.
|
|
3452
3453
|
mockFindConversation = () => undefined;
|
|
3453
3454
|
const { captured, restore } = stubSpawn();
|
|
3454
3455
|
try {
|
|
@@ -455,10 +455,11 @@ describe("resolveCallTtsProvider with requiresPcmAudio", () => {
|
|
|
455
455
|
expect(result.provider?.id).toBe("elevenlabs");
|
|
456
456
|
});
|
|
457
457
|
|
|
458
|
-
test("without requiresPcmAudio the configured provider is not swapped", async () => {
|
|
458
|
+
test("without requiresPcmAudio the configured provider is not swapped for playability", async () => {
|
|
459
459
|
testConfig.services.tts.provider = "deepgram";
|
|
460
|
-
|
|
461
|
-
// media-stream playability
|
|
460
|
+
storedKeys["deepgram"] = "dg-key";
|
|
461
|
+
// The CR transport path does not consult the media-stream playability
|
|
462
|
+
// capability.
|
|
462
463
|
registerStubProvider("deepgram", async () => {
|
|
463
464
|
return { audio: Buffer.from("x"), contentType: "audio/mpeg" };
|
|
464
465
|
});
|
|
@@ -468,6 +469,37 @@ describe("resolveCallTtsProvider with requiresPcmAudio", () => {
|
|
|
468
469
|
expect(result.useSynthesizedPath).toBe(true);
|
|
469
470
|
expect(result.audioFormat).toBe("mp3");
|
|
470
471
|
});
|
|
472
|
+
|
|
473
|
+
test("managed speech stands in for a configured provider whose credential is missing", async () => {
|
|
474
|
+
testConfig.services.tts.provider = "deepgram";
|
|
475
|
+
// No deepgram key, managed speech available: the phone speaks through
|
|
476
|
+
// managed speech, the same substitution live voice makes.
|
|
477
|
+
registerStubProvider("deepgram", async () => {
|
|
478
|
+
throw new Error("should not be used");
|
|
479
|
+
});
|
|
480
|
+
registerStubProvider("vellum", async () => {
|
|
481
|
+
return { audio: Buffer.from("x"), contentType: "audio/mpeg" };
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
const result = await resolveCallTtsProvider();
|
|
485
|
+
expect(result.provider?.id).toBe("vellum");
|
|
486
|
+
expect(result.useSynthesizedPath).toBe(true);
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
test("without managed speech a credential-less configured provider is kept", async () => {
|
|
490
|
+
mockManagedSpeechAvailable = false;
|
|
491
|
+
try {
|
|
492
|
+
testConfig.services.tts.provider = "deepgram";
|
|
493
|
+
registerStubProvider("deepgram", async () => {
|
|
494
|
+
return { audio: Buffer.from("x"), contentType: "audio/mpeg" };
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
const result = await resolveCallTtsProvider();
|
|
498
|
+
expect(result.provider?.id).toBe("deepgram");
|
|
499
|
+
} finally {
|
|
500
|
+
mockManagedSpeechAvailable = true;
|
|
501
|
+
}
|
|
502
|
+
});
|
|
471
503
|
});
|
|
472
504
|
|
|
473
505
|
// ---------------------------------------------------------------------------
|
|
@@ -584,6 +616,7 @@ describe("speakSystemPrompt mid-stream failure after audio started", () => {
|
|
|
584
616
|
test("non-PCM transport: skips the native text fallback — no full-prompt re-speak", async () => {
|
|
585
617
|
testConfig.services.tts.provider = "fish-audio";
|
|
586
618
|
testConfig.services.tts.providers["fish-audio"].referenceId = "ref-123";
|
|
619
|
+
storedKeys["fish-audio"] = "fa-key";
|
|
587
620
|
|
|
588
621
|
const fishStream = jest.fn(failAfterFirstChunk());
|
|
589
622
|
registerStreamingStubProvider("fish-audio", fishStream);
|
|
@@ -657,6 +690,7 @@ describe("speakSystemPrompt aborted synthesis", () => {
|
|
|
657
690
|
test("non-PCM transport with native-fallback provider: abort skips the text fallback and end-of-turn", async () => {
|
|
658
691
|
testConfig.services.tts.provider = "fish-audio";
|
|
659
692
|
testConfig.services.tts.providers["fish-audio"].referenceId = "ref-123";
|
|
693
|
+
storedKeys["fish-audio"] = "fa-key";
|
|
660
694
|
|
|
661
695
|
const controller = new AbortController();
|
|
662
696
|
controller.abort();
|
|
@@ -742,6 +776,7 @@ describe("speakSystemPrompt aborted synthesis", () => {
|
|
|
742
776
|
test("provider AbortError without any signal is a failure — non-PCM native fallback still runs", async () => {
|
|
743
777
|
testConfig.services.tts.provider = "fish-audio";
|
|
744
778
|
testConfig.services.tts.providers["fish-audio"].referenceId = "ref-123";
|
|
779
|
+
storedKeys["fish-audio"] = "fa-key";
|
|
745
780
|
|
|
746
781
|
const fishSynthesize = jest.fn(async () => {
|
|
747
782
|
throw new DOMException("socket torn down", "AbortError");
|
|
@@ -136,6 +136,10 @@ describe("vellum-self-knowledge skill", () => {
|
|
|
136
136
|
const result = await executeSkillLoad({ skill: "vellum-self-knowledge" });
|
|
137
137
|
expect(result.content).toContain("## Critical Rule");
|
|
138
138
|
expect(result.content).toContain("Never answer from memory");
|
|
139
|
+
expect(result.content).toContain("distinct, legacy Vellum product");
|
|
140
|
+
expect(result.content).toContain(
|
|
141
|
+
"Never use it to answer questions about this Vellum",
|
|
142
|
+
);
|
|
139
143
|
});
|
|
140
144
|
|
|
141
145
|
// ── Resolution order ─────────────────────────────────────────────────
|
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The gate that decides whether a watch retrospective can report at all.
|
|
3
3
|
*
|
|
4
|
-
* A retrospective runs as a `clientless` wake,
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* cannot. Nothing about the card's schema, its renderer, or the prompt's
|
|
9
|
-
* wording reveals that, which is why it is asserted here against the real
|
|
10
|
-
* registry rather than assumed anywhere else.
|
|
4
|
+
* A retrospective runs as a `clientless` wake, but its report continues to
|
|
5
|
+
* use the post-turn renderer so the report lands after its tool call has been
|
|
6
|
+
* persisted. The core UI tools remain available on that turn for other
|
|
7
|
+
* background work, so this file pins both contracts against the real registry.
|
|
11
8
|
*/
|
|
12
9
|
|
|
13
10
|
import { afterAll, describe, expect, test } from "bun:test";
|
|
@@ -54,20 +51,14 @@ describe("watch retrospective tool availability", () => {
|
|
|
54
51
|
).toBe(true);
|
|
55
52
|
});
|
|
56
53
|
|
|
57
|
-
test("
|
|
54
|
+
test("core UI tools stay available on a clientless turn", async () => {
|
|
58
55
|
await initializeTools();
|
|
59
56
|
const ctx = clientlessContext();
|
|
60
57
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
// cannot report at all.
|
|
65
|
-
expect(isToolActiveForContext("ui_show", ctx)).toBe(false);
|
|
66
|
-
expect(isToolActiveForContext("ui_update", ctx)).toBe(false);
|
|
67
|
-
expect(isToolActiveForContext("ui_dismiss", ctx)).toBe(false);
|
|
58
|
+
for (const name of ["ui_show", "ui_update", "ui_dismiss"]) {
|
|
59
|
+
expect(isToolActiveForContext(name, ctx)).toBe(true);
|
|
60
|
+
}
|
|
68
61
|
|
|
69
|
-
// And the gate really is about the client, not about the tools being
|
|
70
|
-
// unregistered: with one attached, the same names are active.
|
|
71
62
|
const withClient = clientfulContext();
|
|
72
63
|
expect(isToolActiveForContext("ui_show", withClient)).toBe(true);
|
|
73
64
|
});
|