@vellumai/assistant 0.12.1-staging.1 → 0.12.1
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 +0 -4
- package/ARCHITECTURE.md +12 -11
- package/docs/architecture/turn-actor.md +8 -15
- package/openapi.yaml +1 -291
- package/package.json +1 -1
- package/src/__tests__/always-loaded-tools-guard.test.ts +10 -18
- package/src/__tests__/app-compiler.test.ts +0 -6
- package/src/__tests__/app-control-tool-schemas.test.ts +2 -5
- package/src/__tests__/app-dir-path-guard.test.ts +3 -1
- package/src/__tests__/app-source-watcher.test.ts +218 -0
- package/src/{calls/__tests__ → __tests__}/call-controller.test.ts +82 -503
- package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +2 -5
- package/src/__tests__/computer-use-tools.test.ts +5 -17
- package/src/__tests__/config-schema-cmd.test.ts +2 -2
- package/src/__tests__/config-schema.test.ts +29 -28
- package/src/__tests__/config-watcher-skill-reseed.test.ts +359 -0
- package/src/__tests__/config-watcher.test.ts +8 -23
- 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 +0 -26
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +10 -4
- package/src/__tests__/conversation-queue.test.ts +207 -48
- package/src/__tests__/conversation-retry-discard.test.ts +1 -54
- package/src/__tests__/conversation-skill-tools.test.ts +0 -27
- package/src/__tests__/conversation-surfaces-app-control.test.ts +3 -8
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +0 -67
- package/src/__tests__/conversation-title-service.test.ts +10 -14
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +5 -3
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +0 -51
- package/src/__tests__/credential-record-retire.test.ts +1 -171
- package/src/__tests__/credential-routes.test.ts +3 -212
- package/src/__tests__/credential-security-invariants.test.ts +1 -2
- package/src/__tests__/cu-unified-flow.test.ts +2 -8
- package/src/__tests__/drain-kick-guard.test.ts +2 -0
- package/src/__tests__/drain-requeue-on-contention.test.ts +6 -0
- package/src/__tests__/evict-conversations-for-reload.test.ts +1 -1
- package/src/__tests__/host-bash-proxy.test.ts +45 -26
- package/src/__tests__/host-browser-proxy.test.ts +17 -6
- package/src/__tests__/host-file-proxy-targeted.test.ts +0 -23
- package/src/__tests__/host-file-proxy.test.ts +23 -11
- package/src/__tests__/host-transfer-proxy-targeted.test.ts +9 -30
- package/src/__tests__/llm-catalog-parity.test.ts +0 -25
- package/src/__tests__/mcp-auth-routes.test.ts +9 -6
- package/src/__tests__/mcp-config-secret-boundary.test.ts +136 -78
- package/src/__tests__/mcp-health-check.test.ts +11 -8
- package/src/__tests__/mcp-list-plugin-servers.test.ts +10 -8
- package/src/{calls/__tests__ → __tests__}/media-stream-audio-transcode.test.ts +1 -1
- package/src/{calls/__tests__ → __tests__}/media-stream-output.test.ts +8 -37
- package/src/{calls/__tests__ → __tests__}/media-stream-parser.test.ts +1 -1
- package/src/{calls/__tests__ → __tests__}/media-stream-server-integration.test.ts +57 -150
- package/src/{calls/__tests__ → __tests__}/media-stream-stt-session.test.ts +5 -5
- package/src/{calls/__tests__ → __tests__}/media-turn-detector.test.ts +1 -1
- package/src/__tests__/messages-after-tiebreaker.test.ts +0 -91
- package/src/__tests__/messages-read-boundary-guard.test.ts +1 -1
- package/src/__tests__/messaging-send-tool.test.ts +548 -355
- package/src/__tests__/model-intents.test.ts +0 -3
- package/src/__tests__/outlook-messaging-provider.test.ts +6 -6
- package/src/__tests__/platform-client-verify-credential.test.ts +0 -2
- package/src/__tests__/plugin-import-boundary-guard.test.ts +0 -13
- package/src/__tests__/script-proxy-certs.test.ts +0 -35
- package/src/__tests__/skill-feature-flags-integration.test.ts +1 -1
- package/src/__tests__/skill-feature-flags.test.ts +2 -2
- package/src/__tests__/speaker-identification.test.ts +56 -0
- package/src/__tests__/subagent-tool-gate-mode.test.ts +3 -19
- package/src/__tests__/subagent-tools.test.ts +2 -3
- package/src/__tests__/telephony-tts-guard.test.ts +3 -38
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +0 -4
- package/src/{calls/__tests__/voice-session-bridge-turn-flow.test.ts → __tests__/voice-session-bridge.test.ts} +12 -20
- package/src/__tests__/watch-retro-tool-availability.test.ts +17 -8
- package/src/__tests__/workspace-git-service.test.ts +0 -26
- package/src/acp/__tests__/agent-process.test.ts +4 -258
- package/src/acp/__tests__/auth-required.test.ts +0 -96
- package/src/acp/__tests__/client-handler.test.ts +2 -90
- package/src/acp/__tests__/session-manager-persistence.test.ts +1 -5
- package/src/acp/__tests__/session-manager-resume.test.ts +6 -270
- package/src/acp/__tests__/session-manager-usage.test.ts +2 -4
- package/src/acp/__tests__/session-manager.test.ts +4 -895
- package/src/acp/agent-process.test.ts +1 -4
- package/src/acp/agent-process.ts +9 -94
- package/src/acp/auth-required.ts +1 -50
- package/src/acp/auto-install.test.ts +7 -9
- package/src/acp/client-handler.ts +3 -16
- package/src/acp/resolve-agent.test.ts +6 -220
- package/src/acp/resolve-agent.ts +23 -88
- package/src/acp/session-manager.test.ts +3 -196
- package/src/acp/session-manager.ts +18 -441
- package/src/acp/types.ts +5 -50
- package/src/api/events/app-files-changed.ts +3 -4
- package/src/api/index.ts +0 -6
- package/src/apps/app-store.ts +1 -1
- package/src/bundler/app-compiler.ts +0 -15
- package/src/calls/__tests__/voice-control-protocol.test.ts +25 -58
- package/src/calls/__tests__/voice-session-bridge.test.ts +1 -37
- package/src/calls/__tests__/voice-triage-escalate.test.ts +0 -105
- package/src/calls/call-constants.ts +4 -24
- package/src/calls/call-controller.ts +199 -499
- package/src/calls/call-transport.ts +0 -16
- package/src/calls/media-stream-output.ts +0 -32
- package/src/calls/media-stream-server.ts +30 -82
- package/src/calls/media-stream-stt-session.ts +0 -15
- package/src/calls/resolve-call-tts-provider.ts +2 -33
- package/src/calls/speaker-identification.ts +233 -0
- package/src/calls/tts-call-strategy.ts +5 -9
- package/src/calls/types.ts +0 -2
- package/src/calls/voice-control-protocol.ts +21 -53
- package/src/calls/voice-session-bridge.ts +46 -59
- package/src/calls/voice-triage-escalate.ts +56 -175
- package/src/cli/commands/apps.help.ts +13 -67
- package/src/cli/commands/apps.ts +0 -183
- package/src/cli/commands/channels/index.help.ts +2 -70
- package/src/cli/commands/channels/index.ts +3 -16
- package/src/cli/commands/credentials.help.ts +2 -6
- 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 +1 -5
- package/src/cli/commands/roadmap.ts +1 -1
- package/src/cli/lib/bundled-marketplace.json +90 -103
- package/src/config/__tests__/default-profile-catalog.test.ts +0 -22
- package/src/config/__tests__/memory-retrospective-schema.test.ts +1 -15
- package/src/config/__tests__/platform-rehydration.test.ts +0 -93
- package/src/config/acp-defaults.test.ts +3 -13
- package/src/config/acp-defaults.ts +6 -23
- package/src/config/acp-schema.ts +2 -30
- package/src/config/bundled-skills/acp/SKILL.md +14 -22
- package/src/config/bundled-skills/acp/TOOLS.json +1 -5
- package/src/config/bundled-skills/app-builder/SKILL.md +9 -12
- package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +1 -1
- package/src/config/bundled-skills/messaging/TOOLS.json +3 -3
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +139 -67
- package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +1 -1
- package/src/config/bundled-skills/screen-annotation/SKILL.md +1 -8
- package/src/config/bundled-skills/screen-annotation/tools/screen-annotation.test.ts +1 -4
- 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 +12 -8
- package/src/config/memory-v3-gate.test.ts +0 -31
- package/src/config/memory-v3-gate.ts +16 -20
- package/src/config/platform-rehydration.ts +21 -75
- package/src/config/schema.ts +2 -2
- package/src/config/schemas/__tests__/live-voice.test.ts +158 -8
- package/src/config/schemas/calls.ts +28 -1
- package/src/config/schemas/live-voice.ts +173 -1
- package/src/config/schemas/mcp.ts +1 -1
- package/src/config/schemas/memory-retrospective.ts +0 -10
- package/src/config/schemas/skills.ts +14 -0
- package/src/credential-execution/process-manager.test.ts +19 -18
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +121 -66
- package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +1 -4
- package/src/daemon/app-source-watcher.ts +218 -0
- package/src/daemon/config-watcher.ts +194 -13
- package/src/daemon/conversation-process.ts +64 -35
- package/src/daemon/conversation-skill-tools.ts +31 -13
- package/src/daemon/conversation-store.ts +1 -1
- package/src/daemon/conversation-surfaces.ts +8 -10
- package/src/daemon/conversation-tool-setup.ts +43 -37
- package/src/daemon/conversation.ts +3 -0
- package/src/daemon/host-bash-proxy.ts +12 -8
- package/src/daemon/host-browser-proxy.ts +1 -15
- package/src/daemon/host-file-proxy.ts +10 -8
- package/src/daemon/host-transfer-proxy.ts +18 -14
- package/src/daemon/lifecycle.ts +5 -0
- package/src/daemon/mcp-reload-service.ts +5 -5
- package/src/daemon/message-types/sync.ts +0 -1
- package/src/daemon/providers-setup.ts +1 -1
- package/src/daemon/shutdown-handlers.ts +9 -1
- package/src/daemon/skill-memory-refresh.ts +0 -64
- package/src/daemon/tool-side-effects.ts +18 -7
- package/src/hooks/types.ts +0 -33
- package/src/inbound/platform-callback-registration.ts +5 -4
- package/src/live-voice/__tests__/live-voice-metrics.test.ts +3 -0
- 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/{calls → live-voice}/__tests__/progress-narration.test.ts +4 -4
- package/src/{calls → live-voice}/__tests__/progress-phrases.test.ts +1 -1
- package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +69 -13
- package/src/live-voice/live-voice-session.ts +693 -213
- package/src/{calls → live-voice}/progress-narration.ts +2 -2
- package/src/live-voice/protocol.ts +2 -0
- package/src/mcp/__tests__/effective-config.test.ts +2 -2
- package/src/mcp/effective-config.ts +3 -3
- package/src/mcp/mcp-header-store.ts +45 -29
- package/src/mcp/startup.ts +4 -8
- package/src/messaging/provider.ts +3 -10
- package/src/messaging/providers/callback-routing.ts +0 -18
- package/src/messaging/providers/channel-transport.ts +0 -56
- package/src/messaging/providers/discord/transport.ts +0 -17
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +17 -6
- package/src/messaging/providers/slack/adapter.ts +44 -7
- package/src/messaging/providers/slack/transport.ts +0 -16
- package/src/messaging/providers/telegram-bot/adapter.ts +48 -1
- package/src/messaging/providers/telegram-bot/client.ts +26 -0
- package/src/messaging/providers/telegram-bot/transport.ts +0 -20
- package/src/messaging/providers/whatsapp/adapter.ts +48 -1
- package/src/messaging/providers/whatsapp/client.ts +29 -0
- package/src/messaging/providers/whatsapp/transport.ts +0 -18
- package/src/monitoring/__tests__/workspace-git-heartbeat.test.ts +0 -8
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +0 -55
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +3 -153
- package/src/notifications/assistant-reply-producer.ts +3 -8
- package/src/notifications/conversation-pairing.ts +16 -36
- package/src/notifications/delivered-post-record.ts +1 -11
- package/src/notifications/home-feed-side-effect.ts +7 -55
- package/src/outbound-proxy/certs.ts +5 -3
- package/src/permissions/checker.test.ts +6 -11
- package/src/persistence/compaction-log-store-clickhouse.ts +3 -3
- package/src/persistence/conversation-crud.ts +63 -71
- package/src/persistence/conversation-lineage.ts +4 -12
- package/src/persistence/conversation-title-service.ts +26 -9
- 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-reads.ts +0 -30
- package/src/persistence/migrations/157-invite-contact-id.ts +6 -18
- package/src/persistence/schema/memory-core.ts +0 -5
- package/src/platform/client.test.ts +0 -2
- package/src/platform/client.ts +10 -2
- package/src/plugin-api/constants.ts +0 -2
- package/src/plugin-api/index.ts +0 -1
- package/src/plugin-api/types.ts +0 -2
- package/src/plugins/defaults/index.ts +0 -2
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +0 -46
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +5 -9
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +10 -20
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-state.test.ts +0 -219
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +0 -31
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-trigger-check.test.ts +0 -63
- package/src/plugins/defaults/memory/__tests__/shutdown-hook.test.ts +0 -4
- package/src/plugins/defaults/memory/graph/capability-seed.ts +6 -4
- package/src/plugins/defaults/memory/hooks/init.ts +0 -10
- package/src/plugins/defaults/memory/hooks/shutdown.ts +0 -2
- package/src/plugins/defaults/memory/jobs-worker.ts +0 -13
- package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +60 -34
- package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +5 -3
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +3 -12
- package/src/plugins/defaults/memory/memory-retrospective-state.ts +71 -312
- package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +1 -2
- package/src/plugins/defaults/memory/memory-retrospective-trigger-check.ts +3 -5
- package/src/plugins/defaults/memory/startup.ts +1 -6
- package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +9 -7
- package/src/plugins/defaults/memory/substrate/skill-content.ts +2 -2
- package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +0 -89
- package/src/plugins/defaults/memory/worker.ts +0 -6
- package/src/plugins/mcp-servers.ts +75 -15
- package/src/providers/__tests__/connection-model-compat.test.ts +3 -3
- package/src/providers/gemini/client.ts +23 -9
- package/src/providers/model-catalog.ts +0 -34
- package/src/providers/model-intents.ts +5 -10
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +18 -146
- package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +1 -83
- package/src/providers/openai/chat-completions-provider.ts +78 -192
- package/src/providers/openai/codex-models.ts +4 -0
- package/src/runtime/AGENTS.md +0 -2
- package/src/runtime/auth/same-actor.ts +3 -38
- package/src/runtime/http-server.ts +151 -160
- package/src/runtime/routes/__tests__/acp-routes.test.ts +4 -159
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +1 -108
- package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +12 -16
- package/src/runtime/routes/acp-routes.test.ts +0 -15
- package/src/runtime/routes/acp-routes.ts +9 -72
- package/src/runtime/routes/app-management-routes.ts +0 -73
- package/src/runtime/routes/app-routes.ts +1 -1
- package/src/runtime/routes/conversation-list-routes.ts +3 -14
- package/src/runtime/routes/conversation-query-routes.ts +0 -40
- package/src/runtime/routes/credential-routes.ts +10 -19
- package/src/runtime/routes/diagnostics-routes.ts +3 -55
- package/src/runtime/routes/index.ts +0 -4
- package/src/runtime/routes/log-export-routes.ts +0 -2
- package/src/runtime/routes/mcp-auth-routes.ts +47 -39
- package/src/runtime/routes/migration-routes.ts +21 -8
- package/src/runtime/routes/platform-routes.ts +15 -18
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +0 -28
- package/src/runtime/services/conversation-serializer.ts +3 -16
- package/src/runtime/sync/resource-sync-events.ts +1 -2
- package/src/schedule/worker.ts +1 -1
- package/src/security/ces-rpc-record-backend.ts +0 -7
- package/src/subagent/consult-context.ts +25 -12
- package/src/tools/acp/list-agents.test.ts +1 -3
- package/src/tools/acp/spawn.test.ts +11 -146
- package/src/tools/acp/spawn.ts +3 -21
- package/src/tools/acp/status.ts +3 -40
- package/src/tools/app-control/skill-proxy-bridge.ts +1 -2
- package/src/tools/capability-offer.test.ts +2 -22
- package/src/tools/capability-offer.ts +1 -16
- package/src/tools/computer-use/definitions.ts +2 -4
- package/src/tools/computer-use/skill-proxy-bridge.ts +1 -2
- package/src/tools/credentials/metadata-store.ts +0 -100
- package/src/tools/host-filesystem/edit.test.ts +1 -2
- package/src/tools/host-filesystem/edit.ts +2 -2
- package/src/tools/host-filesystem/read.test.ts +1 -2
- package/src/tools/host-filesystem/read.ts +2 -2
- package/src/tools/host-filesystem/transfer.test.ts +1 -2
- package/src/tools/host-filesystem/transfer.ts +2 -2
- package/src/tools/host-filesystem/write.test.ts +1 -2
- package/src/tools/host-filesystem/write.ts +2 -2
- package/src/tools/host-terminal/host-shell.test.ts +0 -16
- package/src/tools/host-terminal/host-shell.ts +20 -4
- package/src/tools/policy-context.ts +7 -6
- package/src/tools/subagent/spawn.ts +8 -5
- package/src/tools/watch/watch-retro-report.ts +8 -0
- package/src/util/__tests__/language-subtag.test.ts +1 -23
- package/src/util/fs-watcher-error.ts +36 -0
- package/src/util/language-subtag.ts +0 -16
- package/src/util/platform.ts +0 -8
- package/src/workspace/git-service.ts +54 -5
- package/src/workspace/migrations/registry.ts +0 -6
- package/scripts/smoke-desktop-window-theme.ts +0 -186
- package/src/__tests__/git-porcelain.test.ts +0 -47
- package/src/__tests__/helpers/set-workspace-mcp.ts +0 -11
- package/src/__tests__/message-deleted-hook-dispatch.test.ts +0 -119
- package/src/__tests__/resolve-app.test.ts +0 -135
- package/src/__tests__/source-fingerprint.test.ts +0 -184
- package/src/__tests__/workspace-migration-154-repair-retired-codex-gpt-5-4-model-ids.test.ts +0 -336
- package/src/acp/__tests__/helpers/acp-model-option.ts +0 -51
- package/src/acp/model-config.test.ts +0 -166
- package/src/acp/model-config.ts +0 -138
- package/src/api/events/acp-session-model-update.test.ts +0 -88
- package/src/api/events/acp-session-model-update.ts +0 -46
- package/src/apps/resolve-app.ts +0 -81
- package/src/apps/source-fingerprint.ts +0 -266
- package/src/calls/__tests__/barge-in-guard.test.ts +0 -81
- package/src/calls/__tests__/voice-leg-coordinator.test.ts +0 -229
- package/src/calls/__tests__/voice-progress-cadence.test.ts +0 -274
- package/src/calls/barge-in-guard.ts +0 -118
- package/src/calls/voice-leg-coordinator.ts +0 -189
- package/src/calls/voice-progress-cadence.ts +0 -423
- package/src/cli/commands/__tests__/apps.test.ts +0 -245
- package/src/cli/commands/__tests__/roadmap.help.test.ts +0 -19
- package/src/cli/commands/channels/__tests__/send.test.ts +0 -218
- package/src/cli/commands/channels/send.ts +0 -128
- package/src/config/__tests__/platform-identity.test.ts +0 -117
- package/src/config/acp-agent-write.ts +0 -43
- package/src/config/acp-schema.test.ts +0 -81
- package/src/config/platform-identity.ts +0 -144
- package/src/config/schemas/__tests__/voice.test.ts +0 -148
- package/src/config/schemas/voice.ts +0 -150
- package/src/daemon/__tests__/skill-memory-refresh-mtime.test.ts +0 -133
- package/src/daemon/app-change-notify.ts +0 -45
- package/src/desktop/__tests__/fake-desktop.ts +0 -119
- package/src/desktop/desktop-chrome-policy.test.ts +0 -71
- package/src/desktop/desktop-chrome-policy.ts +0 -42
- package/src/desktop/desktop-dependencies.test.ts +0 -102
- package/src/desktop/desktop-dependencies.ts +0 -306
- package/src/desktop/desktop-feature.test.ts +0 -18
- package/src/desktop/desktop-feature.ts +0 -19
- package/src/desktop/desktop-panel-config.test.ts +0 -135
- package/src/desktop/desktop-panel-config.ts +0 -149
- package/src/desktop/desktop-session-manager.test.ts +0 -627
- package/src/desktop/desktop-session-manager.ts +0 -814
- package/src/desktop/desktop-stream-bridge.test.ts +0 -395
- package/src/desktop/desktop-stream-bridge.ts +0 -214
- package/src/desktop/desktop-wallpaper.test.ts +0 -191
- package/src/desktop/desktop-wallpaper.ts +0 -113
- package/src/desktop/desktop-window-theme.ts +0 -170
- package/src/i18n/AGENTS.md +0 -22
- package/src/i18n/__tests__/i18n.test.ts +0 -108
- package/src/i18n/index.ts +0 -16
- package/src/i18n/locales.ts +0 -93
- package/src/i18n/messages.ts +0 -61
- package/src/i18n/title.ts +0 -25
- package/src/mcp/__tests__/workspace-mcp-config.test.ts +0 -135
- package/src/mcp/spec-schema.ts +0 -111
- package/src/mcp/workspace-mcp-config.ts +0 -188
- package/src/messaging/providers/__tests__/proactive-address.test.ts +0 -95
- package/src/persistence/conversation-title-placeholders.ts +0 -23
- package/src/persistence/message-cursor.ts +0 -78
- package/src/persistence/migrations/__tests__/157-invite-contact-id.test.ts +0 -149
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-backfill.test.ts +0 -118
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-preserve.test.ts +0 -83
- package/src/plugins/defaults/memory/hooks/message-deleted.ts +0 -21
- package/src/plugins/defaults/memory/memory-retrospective-cursor-backfill.ts +0 -126
- package/src/plugins/defaults/memory/memory-retrospective-cursor-preserve.ts +0 -79
- package/src/plugins/defaults/memory/memory-retrospective-cursor.ts +0 -28
- package/src/plugins/defaults/memory/skill-md-mtime-poll.ts +0 -9
- package/src/providers/gemini-thought-signature.test.ts +0 -63
- package/src/providers/gemini-thought-signature.ts +0 -44
- package/src/providers/openai/__tests__/chat-completions-provider-thought-signature.test.ts +0 -613
- package/src/providers/openai/__tests__/google-thought-signature.test.ts +0 -235
- package/src/providers/openai/google-thought-signature.ts +0 -320
- package/src/runtime/__tests__/channel-send.test.ts +0 -443
- package/src/runtime/__tests__/desktop-stream-upgrade.test.ts +0 -89
- package/src/runtime/__tests__/live-voice-upgrade.test.ts +0 -58
- package/src/runtime/__tests__/runtime-ws-test-utils.ts +0 -72
- package/src/runtime/__tests__/watch-stream-upgrade.test.ts +0 -59
- package/src/runtime/channel-send.ts +0 -316
- package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +0 -205
- package/src/runtime/routes/__tests__/channel-send-routes.test.ts +0 -127
- package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +0 -107
- package/src/runtime/routes/channel-send-routes.ts +0 -85
- package/src/runtime/routes/desktop-setup-routes.test.ts +0 -74
- package/src/runtime/routes/desktop-setup-routes.ts +0 -35
- package/src/security/__tests__/ces-http-record-backend.test.ts +0 -137
- package/src/security/ces-http-record-backend.ts +0 -120
- package/src/skills/__tests__/workspace-skill-md-signature.test.ts +0 -60
- package/src/skills/workspace-skill-md-signature.ts +0 -50
- package/src/tools/acp/status.test.ts +0 -94
- package/src/util/ca-bundle.ts +0 -13
- package/src/workspace/git-porcelain.ts +0 -66
- package/src/workspace/migrations/154-repair-retired-codex-gpt-5-4-model-ids.ts +0 -258
- package/src/workspace/migrations/155-move-front-model-config-to-voice.ts +0 -78
- package/src/workspace/migrations/156-extract-workspace-mcp-json.ts +0 -214
- package/src/workspace/migrations/__tests__/155-move-front-model-config-to-voice.test.ts +0 -102
- package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +0 -199
- /package/src/{calls → live-voice}/progress-phrases.ts +0 -0
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* File watchers and config reload logic extracted from DaemonServer.
|
|
3
|
-
* Watches workspace files (config, prompts
|
|
4
|
-
*
|
|
5
|
-
* and the assistant process, not from this watcher.
|
|
3
|
+
* Watches workspace files (config, prompts) and skills directories
|
|
4
|
+
* for changes.
|
|
6
5
|
*/
|
|
7
6
|
import {
|
|
8
7
|
existsSync,
|
|
9
8
|
type FSWatcher,
|
|
10
9
|
mkdirSync,
|
|
10
|
+
readdirSync,
|
|
11
11
|
readFileSync,
|
|
12
12
|
unwatchFile,
|
|
13
13
|
watch,
|
|
14
14
|
watchFile,
|
|
15
15
|
} from "node:fs";
|
|
16
|
-
import { join } from "node:path";
|
|
16
|
+
import { basename, join } from "node:path";
|
|
17
17
|
|
|
18
18
|
import { AVATAR_IMAGE_FILENAME } from "@vellumai/avatar-manifest";
|
|
19
19
|
|
|
@@ -44,14 +44,50 @@ import {
|
|
|
44
44
|
getSoundsDir,
|
|
45
45
|
getWorkspaceDir,
|
|
46
46
|
getWorkspacePromptPath,
|
|
47
|
-
|
|
47
|
+
getWorkspaceSkillsDir,
|
|
48
48
|
} from "../util/platform.js";
|
|
49
49
|
import { evictConversationsForReload } from "./conversation-store.js";
|
|
50
50
|
import { parseIdentityFields } from "./handlers/identity.js";
|
|
51
51
|
import { reloadMcpServers } from "./mcp-reload-service.js";
|
|
52
|
+
import { refreshSkillCapabilityMemories } from "./skill-memory-refresh.js";
|
|
52
53
|
|
|
53
54
|
const log = getLogger("config-watcher");
|
|
54
55
|
|
|
56
|
+
const SKILL_WATCH_SKIPPED_DIRS = new Set([
|
|
57
|
+
"node_modules",
|
|
58
|
+
".git",
|
|
59
|
+
"__pycache__",
|
|
60
|
+
".install-staging",
|
|
61
|
+
".cache",
|
|
62
|
+
".next",
|
|
63
|
+
".turbo",
|
|
64
|
+
".venv",
|
|
65
|
+
"coverage",
|
|
66
|
+
]);
|
|
67
|
+
|
|
68
|
+
function isSkippedSkillWatchPath(relativePath: string): boolean {
|
|
69
|
+
if (relativePath === "(unknown)") {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const segments = relativePath.split(/[\\/]+/).filter(Boolean);
|
|
74
|
+
return segments.some((segment) => SKILL_WATCH_SKIPPED_DIRS.has(segment));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Files whose contents feed the skill catalog: `SKILL.md` carries a skill's
|
|
79
|
+
* metadata and body, `TOOLS.json` its tool manifest. Both live at the top
|
|
80
|
+
* level of a skill directory. Every other file (scripts, reference docs, build
|
|
81
|
+
* output) is read fresh on demand or irrelevant to the catalog, so changes to
|
|
82
|
+
* them do not require a catalog reload.
|
|
83
|
+
*/
|
|
84
|
+
const SKILL_CATALOG_FILENAMES = new Set(["SKILL.md", "TOOLS.json"]);
|
|
85
|
+
|
|
86
|
+
/** True when a changed path's basename is a skill-catalog file. */
|
|
87
|
+
function isSkillCatalogFile(filename: string): boolean {
|
|
88
|
+
return SKILL_CATALOG_FILENAMES.has(basename(filename));
|
|
89
|
+
}
|
|
90
|
+
|
|
55
91
|
/**
|
|
56
92
|
* Attach a resilient error handler to an FSWatcher so that async errors
|
|
57
93
|
* (e.g. ENXIO when a Unix socket file like `gateway.sock` appears in a
|
|
@@ -191,11 +227,19 @@ export class ConfigWatcher {
|
|
|
191
227
|
return;
|
|
192
228
|
}
|
|
193
229
|
try {
|
|
230
|
+
const prevMcpFingerprint = JSON.stringify(this.lastConfig?.mcp ?? {});
|
|
194
231
|
const changed = await this.refreshConfigFromSources();
|
|
195
232
|
if (changed) {
|
|
196
233
|
evictConversationsForReload();
|
|
197
234
|
refreshAuthenticatedApiRateLimit();
|
|
198
235
|
publishConfigChanged();
|
|
236
|
+
const newConfig = this.lastConfig ?? getConfig();
|
|
237
|
+
const newMcpFingerprint = JSON.stringify(newConfig.mcp ?? {});
|
|
238
|
+
if (newMcpFingerprint !== prevMcpFingerprint) {
|
|
239
|
+
reloadMcpServers().catch((err: unknown) => {
|
|
240
|
+
log.error({ err }, "MCP reload after config change failed");
|
|
241
|
+
});
|
|
242
|
+
}
|
|
199
243
|
}
|
|
200
244
|
} catch (err) {
|
|
201
245
|
log.error(
|
|
@@ -204,14 +248,6 @@ export class ConfigWatcher {
|
|
|
204
248
|
);
|
|
205
249
|
}
|
|
206
250
|
},
|
|
207
|
-
[WORKSPACE_MCP_FILENAME]: () => {
|
|
208
|
-
if (this.suppressReload) {
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
reloadMcpServers().catch((err: unknown) => {
|
|
212
|
-
log.error({ err }, "MCP reload after mcp.json change failed");
|
|
213
|
-
});
|
|
214
|
-
},
|
|
215
251
|
"SOUL.md": () => {
|
|
216
252
|
evictConversationsForReload();
|
|
217
253
|
},
|
|
@@ -234,6 +270,7 @@ export class ConfigWatcher {
|
|
|
234
270
|
this.startAvatarWatcher();
|
|
235
271
|
this.startSignalsWatcher();
|
|
236
272
|
this.startUsersWatcher();
|
|
273
|
+
this.startSkillsWatchers();
|
|
237
274
|
}
|
|
238
275
|
|
|
239
276
|
stop(): void {
|
|
@@ -448,6 +485,150 @@ export class ConfigWatcher {
|
|
|
448
485
|
);
|
|
449
486
|
}
|
|
450
487
|
}
|
|
488
|
+
|
|
489
|
+
private startSkillsWatchers(): void {
|
|
490
|
+
const skillsDir = getWorkspaceSkillsDir();
|
|
491
|
+
if (!existsSync(skillsDir)) {
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
const scheduleSkillsReload = (file: string): void => {
|
|
496
|
+
if (isSkippedSkillWatchPath(file)) {
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
this.debounceTimers.schedule("skills:catalog", () => {
|
|
501
|
+
log.info({ file }, "Skill catalog changed, reloading");
|
|
502
|
+
evictConversationsForReload();
|
|
503
|
+
refreshSkillCapabilityMemories(getConfig());
|
|
504
|
+
});
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
// Only SKILL.md and TOOLS.json feed the skill catalog, and both live at the
|
|
508
|
+
// top level of a skill directory. Changes to any other file (scripts,
|
|
509
|
+
// reference docs, build output) never alter the catalog, so the watchers
|
|
510
|
+
// react only to those two files — plus skill directories appearing or
|
|
511
|
+
// disappearing, which is an install or removal.
|
|
512
|
+
try {
|
|
513
|
+
const recursiveWatcher = watch(
|
|
514
|
+
skillsDir,
|
|
515
|
+
{ recursive: true },
|
|
516
|
+
(_eventType, filename) => {
|
|
517
|
+
if (!filename || isSkillCatalogFile(String(filename))) {
|
|
518
|
+
scheduleSkillsReload(filename ? String(filename) : "(unknown)");
|
|
519
|
+
}
|
|
520
|
+
},
|
|
521
|
+
);
|
|
522
|
+
attachWatcherErrorHandler(recursiveWatcher, skillsDir);
|
|
523
|
+
this.watchers.push(recursiveWatcher);
|
|
524
|
+
log.info({ dir: skillsDir }, "Watching skills catalog recursively");
|
|
525
|
+
return;
|
|
526
|
+
} catch (err) {
|
|
527
|
+
log.info(
|
|
528
|
+
{ err, dir: skillsDir },
|
|
529
|
+
"Recursive skills watch unavailable; using per-skill-directory watchers",
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Fallback when recursive watches are unavailable: watch the skills root
|
|
534
|
+
// (to detect a skill being installed or removed) plus each immediate skill
|
|
535
|
+
// directory (to detect SKILL.md / TOOLS.json edits). Nested subdirectories
|
|
536
|
+
// are deliberately not watched, bounding the descriptor count to one per
|
|
537
|
+
// skill rather than one per subdirectory.
|
|
538
|
+
const skillDirWatchers = new Map<string, FSWatcher>();
|
|
539
|
+
|
|
540
|
+
const watchDir = (
|
|
541
|
+
dirPath: string,
|
|
542
|
+
onChange: (filename: string | null) => void,
|
|
543
|
+
): FSWatcher | null => {
|
|
544
|
+
try {
|
|
545
|
+
const watcher = watch(dirPath, (_eventType, filename) => {
|
|
546
|
+
onChange(filename ? String(filename) : null);
|
|
547
|
+
});
|
|
548
|
+
attachWatcherErrorHandler(watcher, dirPath);
|
|
549
|
+
this.watchers.push(watcher);
|
|
550
|
+
return watcher;
|
|
551
|
+
} catch (err) {
|
|
552
|
+
log.warn({ err, dirPath }, "Failed to watch skill directory");
|
|
553
|
+
return null;
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
|
|
557
|
+
const removeWatcher = (watcher: FSWatcher): void => {
|
|
558
|
+
const idx = this.watchers.indexOf(watcher);
|
|
559
|
+
if (idx !== -1) {
|
|
560
|
+
this.watchers.splice(idx, 1);
|
|
561
|
+
}
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
const listSkillDirs = (): string[] => {
|
|
565
|
+
try {
|
|
566
|
+
return readdirSync(skillsDir, { withFileTypes: true })
|
|
567
|
+
.filter(
|
|
568
|
+
(entry) =>
|
|
569
|
+
entry.isDirectory() && !SKILL_WATCH_SKIPPED_DIRS.has(entry.name),
|
|
570
|
+
)
|
|
571
|
+
.map((entry) => join(skillsDir, entry.name));
|
|
572
|
+
} catch (err) {
|
|
573
|
+
log.warn({ err, skillsDir }, "Failed to enumerate skill directories");
|
|
574
|
+
return [];
|
|
575
|
+
}
|
|
576
|
+
};
|
|
577
|
+
|
|
578
|
+
// Reconcile the per-skill-directory watchers against what is on disk.
|
|
579
|
+
// Returns true when the set of watched directories changed — i.e. a skill
|
|
580
|
+
// was installed or removed — so the caller can reload the catalog.
|
|
581
|
+
const refreshSkillDirWatchers = (): boolean => {
|
|
582
|
+
const nextDirs = new Set(listSkillDirs());
|
|
583
|
+
let changed = false;
|
|
584
|
+
|
|
585
|
+
for (const [dirPath, watcher] of skillDirWatchers.entries()) {
|
|
586
|
+
if (nextDirs.has(dirPath)) {
|
|
587
|
+
continue;
|
|
588
|
+
}
|
|
589
|
+
watcher.close();
|
|
590
|
+
skillDirWatchers.delete(dirPath);
|
|
591
|
+
removeWatcher(watcher);
|
|
592
|
+
changed = true;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
for (const dirPath of nextDirs) {
|
|
596
|
+
if (skillDirWatchers.has(dirPath)) {
|
|
597
|
+
continue;
|
|
598
|
+
}
|
|
599
|
+
const watcher = watchDir(dirPath, (filename) => {
|
|
600
|
+
if (!filename || isSkillCatalogFile(filename)) {
|
|
601
|
+
scheduleSkillsReload(filename ?? "(unknown)");
|
|
602
|
+
}
|
|
603
|
+
});
|
|
604
|
+
if (watcher) {
|
|
605
|
+
skillDirWatchers.set(dirPath, watcher);
|
|
606
|
+
changed = true;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
return changed;
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
const rootWatcher = watchDir(skillsDir, () => {
|
|
614
|
+
// A change directly under the skills root means a skill directory was
|
|
615
|
+
// added, removed, or renamed. Re-sync the per-skill watchers and reload
|
|
616
|
+
// the catalog when the set actually changed.
|
|
617
|
+
if (refreshSkillDirWatchers()) {
|
|
618
|
+
scheduleSkillsReload("(skill installed or removed)");
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
if (!rootWatcher) {
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
refreshSkillDirWatchers();
|
|
627
|
+
log.info(
|
|
628
|
+
{ dir: skillsDir },
|
|
629
|
+
"Watching skills catalog with per-skill-directory fallback",
|
|
630
|
+
);
|
|
631
|
+
}
|
|
451
632
|
}
|
|
452
633
|
|
|
453
634
|
/**
|
|
@@ -646,6 +646,52 @@ export async function kickQueueDrain(
|
|
|
646
646
|
}
|
|
647
647
|
}
|
|
648
648
|
|
|
649
|
+
/**
|
|
650
|
+
* Commit `actor` as the actor the turn about to start runs for: the resting
|
|
651
|
+
* slot names them, so the history is scoped to them and every reader of the
|
|
652
|
+
* slot (the `<turn_context>` actor section, memory retrieval, the Slack
|
|
653
|
+
* transcript filters) describes them rather than whoever sent last, and the
|
|
654
|
+
* per-turn snapshot then reads back that same actor, which is also returned
|
|
655
|
+
* for callers that must hold it in a local. A caller with no actor of its own
|
|
656
|
+
* (internal dispatch, or a queued message whose enqueue found the slot empty)
|
|
657
|
+
* passes `undefined`, and the resting actor stands.
|
|
658
|
+
*
|
|
659
|
+
* `reloadHistory` is off only for a steered drain, which keeps its resident
|
|
660
|
+
* history: a steer comes from the actor whose turn it cut off, and that
|
|
661
|
+
* history may hold the in-memory repair of the abandoned `tool_use`, which a
|
|
662
|
+
* reload would discard.
|
|
663
|
+
*
|
|
664
|
+
* The committed actor is captured and snapshotted before the reload awaits:
|
|
665
|
+
* the slot is writable out-of-band across that await (a wake's stamp, a
|
|
666
|
+
* pointer elevation), and a read after it would hand the turn that writer's
|
|
667
|
+
* actor. A reload that fails starts no turn, so the slot is put back to what
|
|
668
|
+
* it held before, guarded so a writer that legitimately moved it in between
|
|
669
|
+
* is left alone.
|
|
670
|
+
*/
|
|
671
|
+
async function commitTurnActor(
|
|
672
|
+
conversation: Conversation,
|
|
673
|
+
actor: TrustContext | undefined,
|
|
674
|
+
options: { reloadHistory: boolean },
|
|
675
|
+
): Promise<TrustContext | undefined> {
|
|
676
|
+
const prior = restingTrust(conversation);
|
|
677
|
+
if (actor) {
|
|
678
|
+
conversation.setTrustContext(actor);
|
|
679
|
+
}
|
|
680
|
+
const turnTrustContext = restingTrust(conversation);
|
|
681
|
+
conversation.currentTurnTrustContext = turnTrustContext;
|
|
682
|
+
if (options.reloadHistory) {
|
|
683
|
+
try {
|
|
684
|
+
await conversation.ensureActorScopedHistory();
|
|
685
|
+
} catch (err) {
|
|
686
|
+
if (actor && restingTrust(conversation) === actor) {
|
|
687
|
+
conversation.setTrustContext(prior ?? null);
|
|
688
|
+
}
|
|
689
|
+
throw err;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
return turnTrustContext;
|
|
693
|
+
}
|
|
694
|
+
|
|
649
695
|
async function drainSingleMessage(
|
|
650
696
|
conversation: Conversation,
|
|
651
697
|
next: QueuedMessage,
|
|
@@ -748,11 +794,12 @@ async function drainSingleMessage(
|
|
|
748
794
|
// a different actor's context if a concurrent request mutates the live fields.
|
|
749
795
|
// Trust comes from the queued message, not the live slot: the slot holds
|
|
750
796
|
// whichever actor sent most recently, which is this sender only when nobody
|
|
751
|
-
// else sent while this message waited.
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
797
|
+
// else sent while this message waited.
|
|
798
|
+
const turnTrustContext = await commitTurnActor(
|
|
799
|
+
conversation,
|
|
800
|
+
next.trustContext,
|
|
801
|
+
{ reloadHistory: !steered },
|
|
802
|
+
);
|
|
756
803
|
conversation.currentTurnChannelCapabilities =
|
|
757
804
|
conversation.channelCapabilities;
|
|
758
805
|
|
|
@@ -1360,9 +1407,12 @@ async function drainBatch(
|
|
|
1360
1407
|
// The head's trust governs the batch, which is sound only because
|
|
1361
1408
|
// `buildPassthroughBatch` refuses to coalesce messages from different
|
|
1362
1409
|
// actors; without that boundary this would run a tail under the head's
|
|
1363
|
-
// trust.
|
|
1364
|
-
const turnTrustContext =
|
|
1365
|
-
|
|
1410
|
+
// trust.
|
|
1411
|
+
const turnTrustContext = await commitTurnActor(
|
|
1412
|
+
conversation,
|
|
1413
|
+
head.trustContext,
|
|
1414
|
+
{ reloadHistory: true },
|
|
1415
|
+
);
|
|
1366
1416
|
conversation.currentTurnChannelCapabilities =
|
|
1367
1417
|
conversation.channelCapabilities;
|
|
1368
1418
|
|
|
@@ -1840,37 +1890,16 @@ export async function processMessage(
|
|
|
1840
1890
|
metadata: callerMetadata,
|
|
1841
1891
|
trustContext: committingTrustContext,
|
|
1842
1892
|
} = options;
|
|
1843
|
-
const priorRestingTrust = restingTrust(conversation);
|
|
1844
|
-
if (committingTrustContext) {
|
|
1845
|
-
conversation.setTrustContext(committingTrustContext);
|
|
1846
|
-
}
|
|
1847
1893
|
// Held in a local as well as on the conversation: the field is writable
|
|
1848
1894
|
// out-of-band while this turn is in flight (`agent-wake` stamps it and
|
|
1849
1895
|
// restores the prior value in a `finally`), so reading it back at the agent
|
|
1850
1896
|
// loop call below would reintroduce the late read this capture exists to
|
|
1851
|
-
// avoid. The local is what the loop runs under.
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
await conversation.ensureActorScopedHistory();
|
|
1858
|
-
} catch (err) {
|
|
1859
|
-
// This is the commitment point for the turn, so the stamp above is
|
|
1860
|
-
// correct, but a reload that fails starts no turn: the conversation must
|
|
1861
|
-
// not be left attributed to a sender that never ran. Guarded on identity
|
|
1862
|
-
// so a writer that legitimately moved the slot across the await keeps it.
|
|
1863
|
-
// Only the resting slot needs putting back; `runAgentLoopImpl` re-seeds
|
|
1864
|
-
// the per-turn field at the head of every turn, so no later dispatch can
|
|
1865
|
-
// inherit it.
|
|
1866
|
-
if (
|
|
1867
|
-
committingTrustContext &&
|
|
1868
|
-
restingTrust(conversation) === committingTrustContext
|
|
1869
|
-
) {
|
|
1870
|
-
conversation.setTrustContext(priorRestingTrust ?? null);
|
|
1871
|
-
}
|
|
1872
|
-
throw err;
|
|
1873
|
-
}
|
|
1897
|
+
// avoid. The local is what the loop runs under.
|
|
1898
|
+
const turnTrustContext = await commitTurnActor(
|
|
1899
|
+
conversation,
|
|
1900
|
+
committingTrustContext,
|
|
1901
|
+
{ reloadHistory: true },
|
|
1902
|
+
);
|
|
1874
1903
|
conversation.currentTurnAuthContext = conversation.authContext;
|
|
1875
1904
|
conversation.currentTurnSourceActorPrincipalId =
|
|
1876
1905
|
sourceActorPrincipalId ?? conversation.authContext?.actorPrincipalId;
|
|
@@ -75,13 +75,11 @@ export interface SkillToolProjection {
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
/**
|
|
78
|
-
* Conversation-scoped cache for skill projection. Avoids re-scanning the
|
|
79
|
-
* conversation history on every agent turn.
|
|
80
|
-
* here: each projection reads a fresh `loadSkillCatalog()` so newly
|
|
81
|
-
* installed or removed skills are visible on the next turn.
|
|
78
|
+
* Conversation-scoped cache for skill projection. Avoids re-scanning the entire
|
|
79
|
+
* conversation history and re-reading the filesystem on every agent turn.
|
|
82
80
|
*
|
|
83
|
-
* Each conversation should own its own cache instance to prevent
|
|
84
|
-
*
|
|
81
|
+
* Each conversation should own its own cache instance to prevent cross-conversation
|
|
82
|
+
* state bleed.
|
|
85
83
|
*/
|
|
86
84
|
export interface SkillProjectionCache {
|
|
87
85
|
/** Cached deriveActiveSkills result. */
|
|
@@ -97,6 +95,9 @@ export interface SkillProjectionCache {
|
|
|
97
95
|
/** The accumulated active skill entries. */
|
|
98
96
|
entries: ActiveSkillEntry[];
|
|
99
97
|
};
|
|
98
|
+
/** Cached skill catalog. Invalidated when the conversation is marked stale
|
|
99
|
+
* (e.g. skill directories changed on disk while a run is in progress). */
|
|
100
|
+
catalog?: SkillSummary[];
|
|
100
101
|
}
|
|
101
102
|
|
|
102
103
|
export interface ProjectSkillToolsOptions {
|
|
@@ -112,9 +113,9 @@ export interface ProjectSkillToolsOptions {
|
|
|
112
113
|
*/
|
|
113
114
|
previouslyActiveSkillIds?: Map<string, string>;
|
|
114
115
|
/**
|
|
115
|
-
* Conversation-scoped projection cache. When provided, projectSkillTools
|
|
116
|
-
*
|
|
117
|
-
*
|
|
116
|
+
* Conversation-scoped projection cache. When provided, projectSkillTools will
|
|
117
|
+
* avoid redundant deriveActiveSkills scans and loadSkillCatalog filesystem
|
|
118
|
+
* reads across agent turns.
|
|
118
119
|
*/
|
|
119
120
|
cache?: SkillProjectionCache;
|
|
120
121
|
/**
|
|
@@ -309,6 +310,23 @@ function getCachedActiveSkills(
|
|
|
309
310
|
return entries;
|
|
310
311
|
}
|
|
311
312
|
|
|
313
|
+
/**
|
|
314
|
+
* Return the skill catalog, caching it across agent turns.
|
|
315
|
+
*
|
|
316
|
+
* The cache is invalidated when the conversation is marked stale (e.g. skill
|
|
317
|
+
* directories changed on disk while the conversation is still processing).
|
|
318
|
+
*/
|
|
319
|
+
function getCachedCatalog(cache?: SkillProjectionCache): SkillSummary[] {
|
|
320
|
+
if (!cache) {
|
|
321
|
+
return loadSkillCatalog();
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (!cache.catalog) {
|
|
325
|
+
cache.catalog = loadSkillCatalog();
|
|
326
|
+
}
|
|
327
|
+
return cache.catalog;
|
|
328
|
+
}
|
|
329
|
+
|
|
312
330
|
// ---------------------------------------------------------------------------
|
|
313
331
|
// Main export
|
|
314
332
|
// ---------------------------------------------------------------------------
|
|
@@ -345,11 +363,11 @@ export function projectSkillTools(
|
|
|
345
363
|
const contextIds = contextEntries.map((e) => e.id);
|
|
346
364
|
const allCandidateIds = new Set<string>([...contextIds, ...preactivated]);
|
|
347
365
|
|
|
348
|
-
// Load the catalog, then scope it to the
|
|
349
|
-
// selection so plugin-contributed skills from
|
|
350
|
-
// resolvable for this run (null = no restriction).
|
|
366
|
+
// Load the catalog (cached for conversation lifetime), then scope it to the
|
|
367
|
+
// conversation's per-chat plugin selection so plugin-contributed skills from
|
|
368
|
+
// unselected plugins are not resolvable for this run (null = no restriction).
|
|
351
369
|
const catalog = filterSkillsByEnabledPlugins(
|
|
352
|
-
|
|
370
|
+
getCachedCatalog(options?.cache),
|
|
353
371
|
options?.effectiveEnabledPluginSet ?? null,
|
|
354
372
|
);
|
|
355
373
|
const catalogById = new Map<string, SkillSummary>();
|
|
@@ -548,7 +548,7 @@ export function clearAllActiveConversations(): number {
|
|
|
548
548
|
}
|
|
549
549
|
|
|
550
550
|
/**
|
|
551
|
-
* Evict in-memory conversations after a config/prompt reload so the next
|
|
551
|
+
* Evict in-memory conversations after a config/prompt/skills reload so the next
|
|
552
552
|
* turn rebuilds them against the new config. Conversations with in-flight work
|
|
553
553
|
* (a live turn, a queued successor, or an active subagent) are marked stale
|
|
554
554
|
* and rebuilt by `getOrCreateConversation` once that work finishes. Idle
|
|
@@ -55,7 +55,6 @@ import {
|
|
|
55
55
|
isActivationMomentParam,
|
|
56
56
|
} from "../telemetry/activation-funnel.js";
|
|
57
57
|
import { resolveAppId } from "../tools/apps/resolve-app-id.js";
|
|
58
|
-
import { formatDesktopAppRequired } from "../tools/capability-offer.js";
|
|
59
58
|
import { POINT_AT_PROXY_TOOL } from "../tools/computer-use/skill-proxy-bridge.js";
|
|
60
59
|
import type { ToolExecutionResult } from "../tools/types.js";
|
|
61
60
|
import { getLogger } from "../util/logger.js";
|
|
@@ -2937,7 +2936,7 @@ export function buildAppOpenPreview(
|
|
|
2937
2936
|
function describeComputerUseUnavailable(ctx: Conversation): string {
|
|
2938
2937
|
const capable = assistantEventHub.listClientsByCapability("host_cu");
|
|
2939
2938
|
if (capable.length === 0) {
|
|
2940
|
-
return
|
|
2939
|
+
return "Computer use is not available — no desktop client connected. Open the Vellum desktop app on the machine you want to control, then retry.";
|
|
2941
2940
|
}
|
|
2942
2941
|
return `Computer use is not available for this conversation — ${capable.length} desktop client(s) advertise host_cu, but none of them can be driven from this conversation's interface (${ctx.transportInterface ?? "unknown"}) as its current user.`;
|
|
2943
2942
|
}
|
|
@@ -3252,7 +3251,8 @@ export async function surfaceProxyResolver(
|
|
|
3252
3251
|
|
|
3253
3252
|
if (!ctx.hostAppControlProxy || !ctx.hostAppControlProxy.isAvailable()) {
|
|
3254
3253
|
return {
|
|
3255
|
-
content:
|
|
3254
|
+
content:
|
|
3255
|
+
"App control is not available — enable the `app-control` feature flag and connect a macOS client.",
|
|
3256
3256
|
isError: true,
|
|
3257
3257
|
};
|
|
3258
3258
|
}
|
|
@@ -3344,7 +3344,6 @@ export async function surfaceProxyResolver(
|
|
|
3344
3344
|
const caps = ctx.channelCapabilities;
|
|
3345
3345
|
if (
|
|
3346
3346
|
caps &&
|
|
3347
|
-
!ctx.hasNoClient &&
|
|
3348
3347
|
!caps.supportsDynamicUi &&
|
|
3349
3348
|
!isSlackTaskProgressUiException(ctx, toolName, input)
|
|
3350
3349
|
) {
|
|
@@ -3553,12 +3552,11 @@ export async function surfaceProxyResolver(
|
|
|
3553
3552
|
: surfaceType === "table"
|
|
3554
3553
|
? hasActions
|
|
3555
3554
|
: INTERACTIVE_SURFACE_TYPES.includes(surfaceType);
|
|
3556
|
-
//
|
|
3557
|
-
//
|
|
3558
|
-
const awaitAction =
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
((input.await_action as boolean) ?? isInteractive);
|
|
3555
|
+
// An explicit `await_action: true` is honored for every other type; an
|
|
3556
|
+
// actionless surface has nothing to await, so it is forced false.
|
|
3557
|
+
const awaitAction = isActionless
|
|
3558
|
+
? false
|
|
3559
|
+
: ((input.await_action as boolean) ?? isInteractive);
|
|
3562
3560
|
|
|
3563
3561
|
// Only one non-persistent interactive surface at a time. If another
|
|
3564
3562
|
// surface is already awaiting user input, reject this one so the LLM
|
|
@@ -28,6 +28,7 @@ import { getAllDefaultPluginNames } from "../plugins/defaults/main.js";
|
|
|
28
28
|
import { isActivationSession } from "../plugins/defaults/memory/activation-session-store.js";
|
|
29
29
|
import { isPluginDisabled } from "../plugins/disabled-state.js";
|
|
30
30
|
import type { Message, ToolDefinition } from "../providers/types.js";
|
|
31
|
+
import { assistantEventHub } from "../runtime/assistant-event-hub.js";
|
|
31
32
|
import { registerConversationSender } from "../tools/browser/browser-screencast.js";
|
|
32
33
|
import { supportsClientOsForSkillTool } from "../tools/client-os.js";
|
|
33
34
|
import type { ToolExecutor } from "../tools/executor.js";
|
|
@@ -259,24 +260,6 @@ export function createToolExecutor(
|
|
|
259
260
|
// see {@link SubagentToolGateMode}): rejects non-allowlisted calls BEFORE
|
|
260
261
|
// any executor dispatch, so a non-allowlisted tool's executor never runs.
|
|
261
262
|
// The error tool_result lets the model continue or finish.
|
|
262
|
-
const rejectUnattendedHostTool = (
|
|
263
|
-
toolName: string,
|
|
264
|
-
): ToolExecutionResult | null => {
|
|
265
|
-
const { transportInterface } = resolveTurnClientOs(ctx);
|
|
266
|
-
const isUnattended = ctx.currentTurnIsNonInteractive ?? ctx.hasNoClient;
|
|
267
|
-
if (
|
|
268
|
-
HOST_TOOL_NAMES.has(toolName) &&
|
|
269
|
-
isUnattended &&
|
|
270
|
-
transportInterface !== "chrome-extension"
|
|
271
|
-
) {
|
|
272
|
-
return {
|
|
273
|
-
content: `The "${toolName}" tool requires an interactive user turn and cannot run in the background.`,
|
|
274
|
-
isError: true,
|
|
275
|
-
};
|
|
276
|
-
}
|
|
277
|
-
return null;
|
|
278
|
-
};
|
|
279
|
-
|
|
280
263
|
const rejectNonAllowlistedTool = (
|
|
281
264
|
toolName: string,
|
|
282
265
|
): ToolExecutionResult | null => {
|
|
@@ -404,10 +387,6 @@ export function createToolExecutor(
|
|
|
404
387
|
if (rejection) {
|
|
405
388
|
return rejection;
|
|
406
389
|
}
|
|
407
|
-
const unattendedRejection = rejectUnattendedHostTool(executionName);
|
|
408
|
-
if (unattendedRejection) {
|
|
409
|
-
return unattendedRejection;
|
|
410
|
-
}
|
|
411
390
|
}
|
|
412
391
|
|
|
413
392
|
if (isDoordashCommand(executionName, executionInput)) {
|
|
@@ -571,10 +550,6 @@ export function createToolExecutor(
|
|
|
571
550
|
if (innerRejection) {
|
|
572
551
|
return innerRejection;
|
|
573
552
|
}
|
|
574
|
-
const unattendedRejection = rejectUnattendedHostTool(toolName);
|
|
575
|
-
if (unattendedRejection) {
|
|
576
|
-
return unattendedRejection;
|
|
577
|
-
}
|
|
578
553
|
|
|
579
554
|
// Per-chat plugin scope: reject the resolved inner tool when it belongs
|
|
580
555
|
// to a plugin outside the conversation's effective set.
|
|
@@ -653,6 +628,7 @@ export const DEFAULT_PREACTIVATED_SKILL_IDS = ["notifications", "subagent"];
|
|
|
653
628
|
// ── Conditional tool sets ────────────────────────────────────────────
|
|
654
629
|
|
|
655
630
|
const UI_SURFACE_TOOL_NAMES = new Set(["ui_show", "ui_update", "ui_dismiss"]);
|
|
631
|
+
const SLACK_TASK_PROGRESS_UI_TOOL_NAMES = new Set(["ui_show", "ui_update"]);
|
|
656
632
|
/**
|
|
657
633
|
* Single source of truth for which tools are host tools and the capability
|
|
658
634
|
* each one requires from the connected client interface. Adding a tool here
|
|
@@ -880,26 +856,56 @@ export function isToolActiveForContext(
|
|
|
880
856
|
return supportsChannelReaction(turnChannel);
|
|
881
857
|
}
|
|
882
858
|
if (UI_SURFACE_TOOL_NAMES.has(name)) {
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
859
|
+
if (
|
|
860
|
+
channelCapabilities?.channel === "slack" &&
|
|
861
|
+
SLACK_TASK_PROGRESS_UI_TOOL_NAMES.has(name)
|
|
862
|
+
) {
|
|
863
|
+
return !hasNoClient;
|
|
864
|
+
}
|
|
865
|
+
return channelCapabilities?.supportsDynamicUi ?? !hasNoClient;
|
|
887
866
|
}
|
|
888
867
|
if (HOST_TOOL_NAMES.has(name)) {
|
|
889
868
|
const capability = HOST_TOOL_TO_CAPABILITY.get(name);
|
|
890
869
|
const transport = transportInterface;
|
|
891
870
|
|
|
892
|
-
//
|
|
893
|
-
//
|
|
894
|
-
// runs, so the wire schema stays stable across live and background turns.
|
|
871
|
+
// Per-capability check is authoritative for structural support: if the
|
|
872
|
+
// transport cannot service this capability, the tool is filtered out.
|
|
895
873
|
if (transport && capability && !supportsHostProxy(transport, capability)) {
|
|
896
|
-
|
|
874
|
+
// Cross-client exception: allow host tools whose capabilities have
|
|
875
|
+
// cross-client routing infrastructure (Phases 1–3 plus host_browser
|
|
876
|
+
// via PR #27489) to be exposed for non-host-proxy transports (e.g.
|
|
877
|
+
// "web", "ios") when at least one capable client is connected via
|
|
878
|
+
// the event hub. Members of CROSS_CLIENT_EXPOSED_CAPABILITIES
|
|
879
|
+
// (host_bash, host_file, host_browser) qualify.
|
|
880
|
+
// chrome-extension transport is excluded as a security boundary
|
|
881
|
+
// (extension only gets host_browser via its own executor path);
|
|
882
|
+
// hasNoClient turns are excluded (no interactive approval UI
|
|
883
|
+
// available).
|
|
884
|
+
if (
|
|
885
|
+
capability &&
|
|
897
886
|
CROSS_CLIENT_EXPOSED_CAPABILITIES.has(capability) &&
|
|
898
|
-
transport !== "chrome-extension"
|
|
899
|
-
|
|
887
|
+
transport !== "chrome-extension" &&
|
|
888
|
+
!hasNoClient &&
|
|
889
|
+
assistantEventHub.listClientsByCapability(capability).length > 0
|
|
890
|
+
) {
|
|
891
|
+
return true;
|
|
892
|
+
}
|
|
893
|
+
return false;
|
|
900
894
|
}
|
|
901
895
|
|
|
902
|
-
|
|
896
|
+
// chrome-extension is its own executor — the extension's popup gates
|
|
897
|
+
// commands via its own UI, and the transport does not use an SSE-level
|
|
898
|
+
// interactive approval channel. hasNoClient is intentionally `true` for
|
|
899
|
+
// chrome-extension turns (chrome-extension is not in INTERACTIVE_INTERFACES)
|
|
900
|
+
// and must not gate host_browser. Trust the per-capability check.
|
|
901
|
+
if (transport === "chrome-extension") {
|
|
902
|
+
return true;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
// For transports that surface approvals over SSE (macos, backwards-compat
|
|
906
|
+
// fallback), deny when no client is present so the guardian auto-approve
|
|
907
|
+
// path cannot execute host commands unattended.
|
|
908
|
+
return !hasNoClient;
|
|
903
909
|
}
|
|
904
910
|
if (CLIENT_CAPABILITY_TOOL_NAMES.has(name)) {
|
|
905
911
|
if (name === "ask_question" && channelCapabilities?.clientOS === "macos") {
|
|
@@ -2366,6 +2366,9 @@ export class Conversation {
|
|
|
2366
2366
|
|
|
2367
2367
|
markStale(): void {
|
|
2368
2368
|
this.stale = true;
|
|
2369
|
+
// Invalidate the cached skill catalog so the next projection picks up
|
|
2370
|
+
// filesystem changes (e.g. a skill created during this run).
|
|
2371
|
+
this.skillProjectionCache.catalog = undefined;
|
|
2369
2372
|
}
|
|
2370
2373
|
|
|
2371
2374
|
isStale(): boolean {
|