@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,7 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Tests for host-tool
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Tests for `isToolActiveForContext` host-tool capability gating.
|
|
3
|
+
*
|
|
4
|
+
* Scenarios verified:
|
|
5
|
+
* - chrome-extension is its own executor and is exempt from the hasNoClient
|
|
6
|
+
* gate (the extension's own popup UI gates commands; there is no SSE
|
|
7
|
+
* interactive approval channel, and chrome-extension turns intentionally
|
|
8
|
+
* run with `hasNoClient: true` because chrome-extension is not in
|
|
9
|
+
* `INTERACTIVE_INTERFACES`).
|
|
10
|
+
* - macos requires a connected SSE client for host tools that flow through
|
|
11
|
+
* the proxy (e.g. host_bash, host_file_*, host_browser), so
|
|
12
|
+
* `hasNoClient: true` denies those on macos.
|
|
13
|
+
* - host_browser IS in the macos capability set — the proxy routes
|
|
14
|
+
* host_browser_request frames to the desktop client via SSE (or via the
|
|
15
|
+
* Chrome extension registry when an extension connection is present).
|
|
16
|
+
*
|
|
17
|
+
* The per-capability check (`supportsHostProxy(transport, capability)`) runs
|
|
18
|
+
* first and is authoritative for structural support, so host_bash and
|
|
19
|
+
* host_file_* are filtered out for chrome-extension regardless of the
|
|
20
|
+
* hasNoClient flag.
|
|
21
|
+
*
|
|
22
|
+
* Cross-client exception: tools whose capabilities are in
|
|
23
|
+
* CROSS_CLIENT_EXPOSED_CAPABILITIES (host_bash, host_file, host_browser)
|
|
24
|
+
* are allowed for non-host-proxy interactive interfaces ("web", "ios")
|
|
25
|
+
* when at least one capable client is connected via the event hub.
|
|
26
|
+
* chrome-extension is excluded as a security boundary, regardless of
|
|
27
|
+
* whether the capability is technically supported elsewhere.
|
|
5
28
|
*/
|
|
6
29
|
|
|
7
30
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
@@ -237,7 +260,7 @@ describe("isToolActiveForContext - Slack task_progress UI exception", () => {
|
|
|
237
260
|
expect(isToolActiveForContext("ui_update", ctx)).toBe(true);
|
|
238
261
|
});
|
|
239
262
|
|
|
240
|
-
test("ui_dismiss
|
|
263
|
+
test("ui_dismiss remains hidden for Slack without dynamic UI support", () => {
|
|
241
264
|
expect(
|
|
242
265
|
isToolActiveForContext(
|
|
243
266
|
"ui_dismiss",
|
|
@@ -249,45 +272,42 @@ describe("isToolActiveForContext - Slack task_progress UI exception", () => {
|
|
|
249
272
|
},
|
|
250
273
|
}),
|
|
251
274
|
),
|
|
252
|
-
).toBe(
|
|
275
|
+
).toBe(false);
|
|
253
276
|
});
|
|
254
277
|
|
|
255
|
-
test("Slack UI tools
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
).toBe(true);
|
|
269
|
-
}
|
|
278
|
+
test("Slack task_progress UI tools still require a connected client path", () => {
|
|
279
|
+
expect(
|
|
280
|
+
isToolActiveForContext(
|
|
281
|
+
"ui_show",
|
|
282
|
+
makeCtx({
|
|
283
|
+
hasNoClient: true,
|
|
284
|
+
channelCapabilities: {
|
|
285
|
+
channel: "slack",
|
|
286
|
+
supportsDynamicUi: false,
|
|
287
|
+
},
|
|
288
|
+
}),
|
|
289
|
+
),
|
|
290
|
+
).toBe(false);
|
|
270
291
|
});
|
|
271
292
|
|
|
272
|
-
test("other non-dynamic channels
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
).toBe(true);
|
|
286
|
-
}
|
|
293
|
+
test("other non-dynamic channels still hide UI surface tools", () => {
|
|
294
|
+
expect(
|
|
295
|
+
isToolActiveForContext(
|
|
296
|
+
"ui_show",
|
|
297
|
+
makeCtx({
|
|
298
|
+
hasNoClient: false,
|
|
299
|
+
channelCapabilities: {
|
|
300
|
+
channel: "telegram",
|
|
301
|
+
supportsDynamicUi: false,
|
|
302
|
+
},
|
|
303
|
+
}),
|
|
304
|
+
),
|
|
305
|
+
).toBe(false);
|
|
287
306
|
});
|
|
288
307
|
});
|
|
289
308
|
|
|
290
309
|
describe("isToolActiveForContext — host tool capability gating", () => {
|
|
310
|
+
// macOS transport: SSE-based interactive approval required.
|
|
291
311
|
test("host_bash is active for macOS with a connected client", () => {
|
|
292
312
|
expect(
|
|
293
313
|
isToolActiveForContext(
|
|
@@ -297,22 +317,27 @@ describe("isToolActiveForContext — host tool capability gating", () => {
|
|
|
297
317
|
).toBe(true);
|
|
298
318
|
});
|
|
299
319
|
|
|
300
|
-
test("host_bash
|
|
320
|
+
test("host_bash is NOT active for macOS when hasNoClient is true (security invariant)", () => {
|
|
321
|
+
// macOS uses an SSE-based interactive approval channel. Without a
|
|
322
|
+
// connected client the guardian auto-approve path could execute host
|
|
323
|
+
// commands unattended, so host tools must be denied.
|
|
301
324
|
expect(
|
|
302
325
|
isToolActiveForContext(
|
|
303
326
|
"host_bash",
|
|
304
327
|
makeCtx({ hasNoClient: true, transportInterface: "macos" }),
|
|
305
328
|
),
|
|
306
|
-
).toBe(
|
|
329
|
+
).toBe(false);
|
|
307
330
|
});
|
|
308
|
-
|
|
331
|
+
|
|
332
|
+
test("host_file_read is NOT active for macOS when hasNoClient is true", () => {
|
|
309
333
|
expect(
|
|
310
334
|
isToolActiveForContext(
|
|
311
335
|
"host_file_read",
|
|
312
336
|
makeCtx({ hasNoClient: true, transportInterface: "macos" }),
|
|
313
337
|
),
|
|
314
|
-
).toBe(
|
|
338
|
+
).toBe(false);
|
|
315
339
|
});
|
|
340
|
+
|
|
316
341
|
test("host_browser is active for macOS with a connected client", () => {
|
|
317
342
|
// macOS supports host_browser — the proxy routes host_browser_request
|
|
318
343
|
// frames to the desktop client via SSE (or via the Chrome extension
|
|
@@ -325,14 +350,19 @@ describe("isToolActiveForContext — host tool capability gating", () => {
|
|
|
325
350
|
).toBe(true);
|
|
326
351
|
});
|
|
327
352
|
|
|
328
|
-
test("host_browser
|
|
353
|
+
test("host_browser is NOT active for macOS when hasNoClient is true", () => {
|
|
354
|
+
// macOS supports host_browser structurally, but without a connected
|
|
355
|
+
// client the host_browser_request frames have no consumer, so the tool
|
|
356
|
+
// is denied.
|
|
329
357
|
expect(
|
|
330
358
|
isToolActiveForContext(
|
|
331
359
|
"host_browser",
|
|
332
360
|
makeCtx({ hasNoClient: true, transportInterface: "macos" }),
|
|
333
361
|
),
|
|
334
|
-
).toBe(
|
|
362
|
+
).toBe(false);
|
|
335
363
|
});
|
|
364
|
+
|
|
365
|
+
// chrome-extension transport: the extension is its own executor.
|
|
336
366
|
test("host_browser is active for chrome-extension even when hasNoClient is true", () => {
|
|
337
367
|
// chrome-extension turns run with `hasNoClient: true` by design because
|
|
338
368
|
// chrome-extension is not in `INTERACTIVE_INTERFACES` — it is not an
|
|
@@ -388,7 +418,10 @@ describe("isToolActiveForContext — host tool capability gating", () => {
|
|
|
388
418
|
).toBe(false);
|
|
389
419
|
});
|
|
390
420
|
|
|
391
|
-
|
|
421
|
+
// Backwards-compat fallback: no transport plumbed through.
|
|
422
|
+
test("host_bash falls back to hasNoClient gate when transport is undefined (client connected)", () => {
|
|
423
|
+
// Without a transport interface we cannot run the per-capability check,
|
|
424
|
+
// so we fall back to the coarse-grained `hasNoClient` behavior.
|
|
392
425
|
expect(
|
|
393
426
|
isToolActiveForContext(
|
|
394
427
|
"host_bash",
|
|
@@ -397,18 +430,20 @@ describe("isToolActiveForContext — host tool capability gating", () => {
|
|
|
397
430
|
).toBe(true);
|
|
398
431
|
});
|
|
399
432
|
|
|
400
|
-
test("host_bash
|
|
433
|
+
test("host_bash falls back to hasNoClient gate when transport is undefined (no client)", () => {
|
|
401
434
|
expect(
|
|
402
435
|
isToolActiveForContext(
|
|
403
436
|
"host_bash",
|
|
404
437
|
makeCtx({ hasNoClient: true, transportInterface: undefined }),
|
|
405
438
|
),
|
|
406
|
-
).toBe(
|
|
439
|
+
).toBe(false);
|
|
407
440
|
});
|
|
408
441
|
});
|
|
409
442
|
|
|
410
443
|
describe("isToolActiveForContext — cross-client exception (Phase 1: host_bash)", () => {
|
|
411
444
|
test("host_bash is active for web transport when a host_bash-capable client is connected", () => {
|
|
445
|
+
// Cross-client path: a web turn should see host_bash when a macOS client
|
|
446
|
+
// with host_bash capability is connected via the event hub.
|
|
412
447
|
mockClientCountByCapability.set("host_bash", 1);
|
|
413
448
|
expect(
|
|
414
449
|
isToolActiveForContext(
|
|
@@ -418,24 +453,30 @@ describe("isToolActiveForContext — cross-client exception (Phase 1: host_bash)
|
|
|
418
453
|
).toBe(true);
|
|
419
454
|
});
|
|
420
455
|
|
|
421
|
-
test("host_bash
|
|
456
|
+
test("host_bash is NOT active for web transport when no capable client is connected", () => {
|
|
457
|
+
// No cross-client fallback: hub has no host_bash-capable subscribers.
|
|
422
458
|
mockClientCountByCapability.set("host_bash", 0);
|
|
423
459
|
expect(
|
|
424
460
|
isToolActiveForContext(
|
|
425
461
|
"host_bash",
|
|
426
462
|
makeCtx({ hasNoClient: false, transportInterface: "web" }),
|
|
427
463
|
),
|
|
428
|
-
).toBe(
|
|
464
|
+
).toBe(false);
|
|
429
465
|
});
|
|
430
|
-
|
|
466
|
+
|
|
467
|
+
test("host_file_read is NOT active for web transport when only a host_bash client is connected", () => {
|
|
468
|
+
// The cross-client exception is per-capability: a host_bash-capable
|
|
469
|
+
// client in the hub does not satisfy host_file's exposure check, since
|
|
470
|
+
// listClientsByCapability is queried with the tool's actual capability.
|
|
431
471
|
mockClientCountByCapability.set("host_bash", 1);
|
|
432
472
|
expect(
|
|
433
473
|
isToolActiveForContext(
|
|
434
474
|
"host_file_read",
|
|
435
475
|
makeCtx({ hasNoClient: false, transportInterface: "web" }),
|
|
436
476
|
),
|
|
437
|
-
).toBe(
|
|
477
|
+
).toBe(false);
|
|
438
478
|
});
|
|
479
|
+
|
|
439
480
|
test("host_bash for macos transport is unaffected by the cross-client exception", () => {
|
|
440
481
|
// macos natively supports host_bash via host proxy — the supportsHostProxy
|
|
441
482
|
// check passes, so the cross-client branch is never reached.
|
|
@@ -448,15 +489,19 @@ describe("isToolActiveForContext — cross-client exception (Phase 1: host_bash)
|
|
|
448
489
|
).toBe(true);
|
|
449
490
|
});
|
|
450
491
|
|
|
451
|
-
test("host_bash
|
|
492
|
+
test("host_bash for macos with no client is still denied (security invariant unaffected)", () => {
|
|
493
|
+
// Even with a capable client in the hub, the macos SSE path takes
|
|
494
|
+
// precedence — it passes the supportsHostProxy check, bypasses the
|
|
495
|
+
// cross-client branch, and reaches the hasNoClient gate.
|
|
452
496
|
mockClientCountByCapability.set("host_bash", 1);
|
|
453
497
|
expect(
|
|
454
498
|
isToolActiveForContext(
|
|
455
499
|
"host_bash",
|
|
456
500
|
makeCtx({ hasNoClient: true, transportInterface: "macos" }),
|
|
457
501
|
),
|
|
458
|
-
).toBe(
|
|
502
|
+
).toBe(false);
|
|
459
503
|
});
|
|
504
|
+
|
|
460
505
|
test("host_bash is NOT active for chrome-extension even when a capable client is connected", () => {
|
|
461
506
|
// Security boundary: chrome-extension only gets host_browser. The
|
|
462
507
|
// cross-client exception explicitly excludes chrome-extension transport
|
|
@@ -470,14 +515,16 @@ describe("isToolActiveForContext — cross-client exception (Phase 1: host_bash)
|
|
|
470
515
|
).toBe(false);
|
|
471
516
|
});
|
|
472
517
|
|
|
473
|
-
test("host_bash
|
|
518
|
+
test("host_bash is NOT active for web transport when hasNoClient is true (no approval UI)", () => {
|
|
519
|
+
// hasNoClient gate: no interactive approval UI available for this turn.
|
|
520
|
+
// Cross-client exception must not bypass this gate.
|
|
474
521
|
mockClientCountByCapability.set("host_bash", 1);
|
|
475
522
|
expect(
|
|
476
523
|
isToolActiveForContext(
|
|
477
524
|
"host_bash",
|
|
478
525
|
makeCtx({ hasNoClient: true, transportInterface: "web" }),
|
|
479
526
|
),
|
|
480
|
-
).toBe(
|
|
527
|
+
).toBe(false);
|
|
481
528
|
});
|
|
482
529
|
});
|
|
483
530
|
|
|
@@ -500,14 +547,14 @@ describe("isToolActiveForContext — cross-client exposure for host_file_*", ()
|
|
|
500
547
|
).toBe(true);
|
|
501
548
|
});
|
|
502
549
|
|
|
503
|
-
test(`${tool}
|
|
550
|
+
test(`${tool} is NOT exposed for web when no host_file client is connected`, () => {
|
|
504
551
|
mockClientCountByCapability.set("host_file", 0);
|
|
505
552
|
expect(
|
|
506
553
|
isToolActiveForContext(
|
|
507
554
|
tool,
|
|
508
555
|
makeCtx({ hasNoClient: false, transportInterface: "web" }),
|
|
509
556
|
),
|
|
510
|
-
).toBe(
|
|
557
|
+
).toBe(false);
|
|
511
558
|
});
|
|
512
559
|
|
|
513
560
|
test(`${tool} is NOT exposed for chrome-extension (security boundary)`, () => {
|
|
@@ -523,19 +570,20 @@ describe("isToolActiveForContext — cross-client exposure for host_file_*", ()
|
|
|
523
570
|
).toBe(false);
|
|
524
571
|
});
|
|
525
572
|
|
|
526
|
-
test(`${tool}
|
|
573
|
+
test(`${tool} is NOT exposed when hasNoClient is true (no approval UI)`, () => {
|
|
527
574
|
mockClientCountByCapability.set("host_file", 1);
|
|
528
575
|
expect(
|
|
529
576
|
isToolActiveForContext(
|
|
530
577
|
tool,
|
|
531
578
|
makeCtx({ hasNoClient: true, transportInterface: "web" }),
|
|
532
579
|
),
|
|
533
|
-
).toBe(
|
|
580
|
+
).toBe(false);
|
|
534
581
|
});
|
|
535
582
|
}
|
|
536
583
|
|
|
537
|
-
test("
|
|
538
|
-
mockClientCountByCapability.set("
|
|
584
|
+
test("listClientsByCapability is queried with the actual capability, not host_bash (regression guard for D5 latent bug)", () => {
|
|
585
|
+
mockClientCountByCapability.set("host_bash", 0);
|
|
586
|
+
mockClientCountByCapability.set("host_file", 1);
|
|
539
587
|
expect(
|
|
540
588
|
isToolActiveForContext(
|
|
541
589
|
"host_file_transfer",
|
|
@@ -574,33 +622,37 @@ describe("isToolActiveForContext — cross-client exposure for host_browser", ()
|
|
|
574
622
|
).toBe(true);
|
|
575
623
|
});
|
|
576
624
|
|
|
577
|
-
test("host_browser
|
|
625
|
+
test("host_browser is NOT exposed for web when no host_browser client is connected", () => {
|
|
578
626
|
mockClientCountByCapability.set("host_browser", 0);
|
|
579
627
|
expect(
|
|
580
628
|
isToolActiveForContext(
|
|
581
629
|
"host_browser",
|
|
582
630
|
makeCtx({ hasNoClient: false, transportInterface: "web" }),
|
|
583
631
|
),
|
|
584
|
-
).toBe(
|
|
632
|
+
).toBe(false);
|
|
585
633
|
});
|
|
586
|
-
|
|
634
|
+
|
|
635
|
+
test("host_browser is NOT exposed for ios when no host_browser client is connected", () => {
|
|
587
636
|
mockClientCountByCapability.set("host_browser", 0);
|
|
588
637
|
expect(
|
|
589
638
|
isToolActiveForContext(
|
|
590
639
|
"host_browser",
|
|
591
640
|
makeCtx({ hasNoClient: false, transportInterface: "ios" }),
|
|
592
641
|
),
|
|
593
|
-
).toBe(
|
|
642
|
+
).toBe(false);
|
|
594
643
|
});
|
|
595
|
-
|
|
644
|
+
|
|
645
|
+
test("host_browser is NOT exposed when hasNoClient is true (no approval UI)", () => {
|
|
646
|
+
// hasNoClient gate: cross-client exception must not bypass this.
|
|
596
647
|
mockClientCountByCapability.set("host_browser", 1);
|
|
597
648
|
expect(
|
|
598
649
|
isToolActiveForContext(
|
|
599
650
|
"host_browser",
|
|
600
651
|
makeCtx({ hasNoClient: true, transportInterface: "web" }),
|
|
601
652
|
),
|
|
602
|
-
).toBe(
|
|
653
|
+
).toBe(false);
|
|
603
654
|
});
|
|
655
|
+
|
|
604
656
|
test("host_browser for macos transport is unaffected by the cross-client exception", () => {
|
|
605
657
|
// macos natively supports host_browser via host proxy — the
|
|
606
658
|
// supportsHostProxy check passes, so the cross-client branch is never
|
|
@@ -629,7 +681,10 @@ describe("isToolActiveForContext — cross-client exposure for host_browser", ()
|
|
|
629
681
|
).toBe(true);
|
|
630
682
|
});
|
|
631
683
|
|
|
632
|
-
test("host_browser
|
|
684
|
+
test("listClientsByCapability is queried with host_browser, not host_bash or host_file (per-capability invariant)", () => {
|
|
685
|
+
// Defense against any future regression that hardcodes a different
|
|
686
|
+
// capability in the cross-client check. Only host_browser-capable
|
|
687
|
+
// clients should satisfy host_browser exposure.
|
|
633
688
|
mockClientCountByCapability.set("host_bash", 1);
|
|
634
689
|
mockClientCountByCapability.set("host_file", 1);
|
|
635
690
|
mockClientCountByCapability.set("host_browser", 0);
|
|
@@ -638,7 +693,7 @@ describe("isToolActiveForContext — cross-client exposure for host_browser", ()
|
|
|
638
693
|
"host_browser",
|
|
639
694
|
makeCtx({ hasNoClient: false, transportInterface: "web" }),
|
|
640
695
|
),
|
|
641
|
-
).toBe(
|
|
696
|
+
).toBe(false);
|
|
642
697
|
});
|
|
643
698
|
});
|
|
644
699
|
|
|
@@ -41,13 +41,10 @@ mock.module("../../mcp/mcp-header-store.js", () => ({
|
|
|
41
41
|
}));
|
|
42
42
|
|
|
43
43
|
mock.module("../../config/loader.js", () => ({
|
|
44
|
+
getConfig: () => ({ mcp: { servers: {} } }),
|
|
44
45
|
invalidateConfigCache: () => {},
|
|
45
46
|
}));
|
|
46
47
|
|
|
47
|
-
mock.module("../../mcp/workspace-mcp-config.js", () => ({
|
|
48
|
-
loadWorkspaceMcpConfig: () => ({ servers: {} }),
|
|
49
|
-
}));
|
|
50
|
-
|
|
51
48
|
mock.module("../../tools/registry.js", () => ({
|
|
52
49
|
registerMcpTools: () => [],
|
|
53
50
|
unregisterAllMcpTools: () => {},
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filesystem watcher for app source directories.
|
|
3
|
+
*
|
|
4
|
+
* Watches the apps root directory recursively using fs.watch (FSEvents on
|
|
5
|
+
* macOS). When a source file changes, debounces per app ID and calls the
|
|
6
|
+
* provided callback so the server can recompile + refresh surfaces.
|
|
7
|
+
*
|
|
8
|
+
* This catches ALL modification sources (file_edit, file_write, bash, etc.)
|
|
9
|
+
* without relying on individual tool hooks.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { existsSync, type FSWatcher, watch } from "node:fs";
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
getApp,
|
|
16
|
+
getAppDirPath,
|
|
17
|
+
getAppsDir,
|
|
18
|
+
resolveAppIdByDirName,
|
|
19
|
+
} from "../apps/app-store.js";
|
|
20
|
+
import { compileApp } from "../bundler/app-compiler.js";
|
|
21
|
+
import { broadcastMessage } from "../runtime/assistant-event-hub.js";
|
|
22
|
+
import { publishAppsChanged } from "../runtime/sync/resource-sync-events.js";
|
|
23
|
+
import { updatePublishedAppDeployment } from "../services/published-app-updater.js";
|
|
24
|
+
import { DebouncerMap } from "../util/debounce.js";
|
|
25
|
+
import { attachFsWatcherErrorHandler } from "../util/fs-watcher-error.js";
|
|
26
|
+
import { getLogger } from "../util/logger.js";
|
|
27
|
+
import { allConversations } from "./conversation-registry.js";
|
|
28
|
+
import { refreshSurfacesForApp } from "./conversation-surfaces.js";
|
|
29
|
+
|
|
30
|
+
const log = getLogger("app-source-watcher");
|
|
31
|
+
|
|
32
|
+
const APP_REFRESH_DEBOUNCE_MS = 500;
|
|
33
|
+
|
|
34
|
+
export type AppSourceChangeCallback = (appId: string) => void;
|
|
35
|
+
|
|
36
|
+
/** Process-level singleton; created by {@link startAppSourceWatcher}. */
|
|
37
|
+
let instance: AppSourceWatcher | null = null;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Ensure the watcher is running. Called by tool-side-effects after the apps
|
|
41
|
+
* directory may have been created (e.g. on first app_create), since the
|
|
42
|
+
* watcher is skipped at startup when the directory doesn't exist yet.
|
|
43
|
+
*/
|
|
44
|
+
export function ensureAppSourceWatcher(): void {
|
|
45
|
+
instance?.ensureStarted();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Resolve app ID from a relative path within the apps directory.
|
|
50
|
+
* Returns null if the path is not an app source file (e.g. dist/, records/).
|
|
51
|
+
*/
|
|
52
|
+
function resolveAppIdFromRelPath(relPath: string): string | null {
|
|
53
|
+
const slashIdx = relPath.indexOf("/");
|
|
54
|
+
if (slashIdx === -1) {
|
|
55
|
+
return null;
|
|
56
|
+
} // file directly in apps/ (e.g. .json definition)
|
|
57
|
+
|
|
58
|
+
const dirName = relPath.slice(0, slashIdx);
|
|
59
|
+
const innerPath = relPath.slice(slashIdx + 1);
|
|
60
|
+
|
|
61
|
+
// Skip a git repo at the root of the apps directory. Older installs may
|
|
62
|
+
// still carry a legacy .git there; its objects/index/refs churn constantly,
|
|
63
|
+
// and each event would otherwise hit resolveAppIdByDirName -> existsSync,
|
|
64
|
+
// which is needless work on a path that can never be an app.
|
|
65
|
+
if (dirName === ".git") {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Skip non-source directories (include bare directory names for fs.watch events)
|
|
70
|
+
if (
|
|
71
|
+
innerPath === "records" ||
|
|
72
|
+
innerPath.startsWith("records/") ||
|
|
73
|
+
innerPath === "dist" ||
|
|
74
|
+
innerPath.startsWith("dist/")
|
|
75
|
+
) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return resolveAppIdByDirName(dirName);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export class AppSourceWatcher {
|
|
83
|
+
private watcher: FSWatcher | null = null;
|
|
84
|
+
private onChange: AppSourceChangeCallback | null = null;
|
|
85
|
+
private debouncer = new DebouncerMap({
|
|
86
|
+
defaultDelayMs: APP_REFRESH_DEBOUNCE_MS,
|
|
87
|
+
maxEntries: 50,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
start(onChange: AppSourceChangeCallback): void {
|
|
91
|
+
this.onChange = onChange;
|
|
92
|
+
this.tryWatch();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Ensure the watcher is running. Call after app creation so the watcher
|
|
97
|
+
* starts if the apps directory was created after daemon startup.
|
|
98
|
+
*/
|
|
99
|
+
ensureStarted(): void {
|
|
100
|
+
if (this.watcher || !this.onChange) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
this.tryWatch();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private tryWatch(): void {
|
|
107
|
+
if (this.watcher) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
let appsDir: string;
|
|
112
|
+
try {
|
|
113
|
+
appsDir = getAppsDir();
|
|
114
|
+
} catch {
|
|
115
|
+
log.warn(
|
|
116
|
+
"Could not resolve apps directory; app source watching disabled",
|
|
117
|
+
);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (!existsSync(appsDir)) {
|
|
122
|
+
log.info("Apps directory does not exist yet; skipping source watcher");
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const onChange = this.onChange;
|
|
127
|
+
if (!onChange) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
this.watcher = watch(
|
|
133
|
+
appsDir,
|
|
134
|
+
{ recursive: true },
|
|
135
|
+
(_eventType, filename) => {
|
|
136
|
+
if (!filename) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const appId = resolveAppIdFromRelPath(filename);
|
|
141
|
+
if (!appId) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
this.debouncer.schedule(`app:${appId}`, () => {
|
|
146
|
+
onChange(appId);
|
|
147
|
+
});
|
|
148
|
+
},
|
|
149
|
+
);
|
|
150
|
+
// Recursive watches over app trees (incl. node_modules) can exhaust the
|
|
151
|
+
// inotify watch limit and emit ENOSPC asynchronously. Without an 'error'
|
|
152
|
+
// listener that unhandled emitter error crashes the daemon.
|
|
153
|
+
attachFsWatcherErrorHandler(this.watcher, log, appsDir);
|
|
154
|
+
log.info("App source watcher started");
|
|
155
|
+
} catch (err) {
|
|
156
|
+
log.warn(
|
|
157
|
+
{ err },
|
|
158
|
+
"Failed to watch apps directory; source watching disabled",
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
stop(): void {
|
|
164
|
+
this.debouncer.cancelAll();
|
|
165
|
+
if (this.watcher) {
|
|
166
|
+
this.watcher.close();
|
|
167
|
+
this.watcher = null;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Handle a detected app source file change. Recompiles the app and refreshes
|
|
174
|
+
* surfaces across ALL conversations.
|
|
175
|
+
*/
|
|
176
|
+
function handleAppSourceChange(appId: string): void {
|
|
177
|
+
const app = getApp(appId);
|
|
178
|
+
if (!app) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const doRefresh = () => {
|
|
183
|
+
for (const conversation of allConversations()) {
|
|
184
|
+
refreshSurfacesForApp(conversation, appId, { fileChange: true });
|
|
185
|
+
}
|
|
186
|
+
broadcastMessage({ type: "app_files_changed", appId });
|
|
187
|
+
publishAppsChanged();
|
|
188
|
+
void updatePublishedAppDeployment(appId);
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const appDir = getAppDirPath(appId);
|
|
192
|
+
void compileApp(appDir)
|
|
193
|
+
.then((result) => {
|
|
194
|
+
if (!result.ok) {
|
|
195
|
+
log.warn(
|
|
196
|
+
{ appId, errors: result.errors },
|
|
197
|
+
"Recompile failed on app source change",
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
doRefresh();
|
|
201
|
+
})
|
|
202
|
+
.catch((err) => {
|
|
203
|
+
log.warn({ appId, err }, "Recompile threw on app source change");
|
|
204
|
+
doRefresh();
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** Start watching app source directories for the life of the daemon. */
|
|
209
|
+
export function startAppSourceWatcher(): void {
|
|
210
|
+
instance = new AppSourceWatcher();
|
|
211
|
+
instance.start(handleAppSourceChange);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** Stop the app source watcher during daemon shutdown. */
|
|
215
|
+
export function stopAppSourceWatcher(): void {
|
|
216
|
+
instance?.stop();
|
|
217
|
+
instance = null;
|
|
218
|
+
}
|