@vellumai/assistant 0.12.0 → 0.12.1-dev.202609141913.5e16607
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +5 -1
- package/ARCHITECTURE.md +11 -12
- package/docs/architecture/turn-actor.md +15 -8
- package/docs/guardian-request-flow.md +16 -0
- package/openapi.yaml +292 -50
- package/package.json +1 -1
- package/scripts/smoke-desktop-window-theme.ts +186 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +18 -10
- package/src/__tests__/app-compiler.test.ts +6 -0
- package/src/__tests__/app-control-tool-schemas.test.ts +5 -2
- package/src/__tests__/app-dir-path-guard.test.ts +1 -3
- package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +5 -2
- package/src/__tests__/computer-use-tools.test.ts +17 -5
- package/src/__tests__/config-schema-cmd.test.ts +2 -2
- package/src/__tests__/config-schema.test.ts +28 -29
- package/src/__tests__/config-watcher.test.ts +23 -8
- package/src/__tests__/connection-model-compat.test.ts +2 -2
- package/src/__tests__/conversation-key-store-disk-view.test.ts +1 -1
- package/src/__tests__/conversation-pairing.test.ts +26 -0
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +4 -10
- package/src/__tests__/conversation-queue.test.ts +48 -207
- package/src/__tests__/conversation-retry-discard.test.ts +54 -1
- package/src/__tests__/conversation-skill-tools.test.ts +27 -0
- package/src/__tests__/conversation-surfaces-app-control.test.ts +8 -3
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
- package/src/__tests__/conversation-title-service.test.ts +14 -10
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +3 -5
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +51 -0
- package/src/__tests__/credential-record-retire.test.ts +171 -1
- package/src/__tests__/credential-routes.test.ts +212 -3
- package/src/__tests__/credential-security-invariants.test.ts +4 -2
- package/src/__tests__/cu-unified-flow.test.ts +8 -2
- package/src/__tests__/document-sync-tags.test.ts +25 -0
- package/src/__tests__/drain-kick-guard.test.ts +0 -2
- package/src/__tests__/drain-requeue-on-contention.test.ts +0 -6
- package/src/__tests__/evict-conversations-for-reload.test.ts +1 -1
- package/src/__tests__/git-porcelain.test.ts +47 -0
- package/src/__tests__/helpers/set-workspace-mcp.ts +11 -0
- package/src/__tests__/host-bash-proxy.test.ts +26 -45
- package/src/__tests__/host-browser-proxy.test.ts +6 -17
- package/src/__tests__/host-file-proxy-targeted.test.ts +23 -0
- package/src/__tests__/host-file-proxy.test.ts +11 -23
- package/src/__tests__/host-transfer-proxy-targeted.test.ts +30 -9
- package/src/__tests__/llm-catalog-parity.test.ts +25 -0
- package/src/__tests__/mcp-auth-routes.test.ts +6 -9
- package/src/__tests__/mcp-config-secret-boundary.test.ts +78 -136
- package/src/__tests__/mcp-health-check.test.ts +8 -11
- package/src/__tests__/mcp-list-plugin-servers.test.ts +8 -10
- package/src/__tests__/message-deleted-hook-dispatch.test.ts +119 -0
- package/src/__tests__/messages-after-tiebreaker.test.ts +91 -0
- package/src/__tests__/messages-read-boundary-guard.test.ts +1 -1
- package/src/__tests__/messaging-send-tool.test.ts +355 -548
- package/src/__tests__/model-intents.test.ts +3 -0
- package/src/__tests__/notification-vellum-adapter.test.ts +85 -2
- package/src/__tests__/outlook-messaging-provider.test.ts +6 -6
- package/src/__tests__/platform-callback-registration.test.ts +8 -16
- package/src/__tests__/platform-client-verify-credential.test.ts +2 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +13 -0
- package/src/__tests__/resolve-app.test.ts +135 -0
- package/src/__tests__/script-proxy-certs.test.ts +35 -0
- package/src/__tests__/service-account-token.test.ts +247 -0
- package/src/__tests__/skill-feature-flags-integration.test.ts +1 -1
- package/src/__tests__/skill-feature-flags.test.ts +2 -2
- package/src/__tests__/source-fingerprint.test.ts +184 -0
- package/src/__tests__/subagent-tool-gate-mode.test.ts +19 -3
- package/src/__tests__/subagent-tools.test.ts +3 -2
- package/src/__tests__/telephony-tts-guard.test.ts +38 -3
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +4 -0
- package/src/__tests__/watch-retro-tool-availability.test.ts +8 -17
- package/src/__tests__/workspace-git-service.test.ts +26 -0
- package/src/__tests__/workspace-migration-154-repair-retired-codex-gpt-5-4-model-ids.test.ts +336 -0
- package/src/acp/__tests__/agent-process.test.ts +258 -4
- package/src/acp/__tests__/auth-required.test.ts +96 -0
- package/src/acp/__tests__/client-handler.test.ts +90 -2
- package/src/acp/__tests__/helpers/acp-model-option.ts +51 -0
- package/src/acp/__tests__/session-manager-persistence.test.ts +5 -1
- package/src/acp/__tests__/session-manager-resume.test.ts +270 -6
- package/src/acp/__tests__/session-manager-usage.test.ts +4 -2
- package/src/acp/__tests__/session-manager.test.ts +895 -4
- package/src/acp/agent-process.test.ts +4 -1
- package/src/acp/agent-process.ts +94 -9
- package/src/acp/auth-required.ts +50 -1
- package/src/acp/auto-install.test.ts +9 -7
- package/src/acp/client-handler.ts +16 -3
- package/src/acp/model-config.test.ts +166 -0
- package/src/acp/model-config.ts +138 -0
- package/src/acp/resolve-agent.test.ts +220 -6
- package/src/acp/resolve-agent.ts +88 -23
- package/src/acp/session-manager.test.ts +196 -3
- package/src/acp/session-manager.ts +441 -18
- package/src/acp/types.ts +50 -5
- package/src/api/events/acp-session-model-update.test.ts +88 -0
- package/src/api/events/acp-session-model-update.ts +46 -0
- package/src/api/events/app-files-changed.ts +4 -3
- package/src/api/events/notification-intent.ts +2 -0
- package/src/api/index.ts +6 -0
- package/src/apps/app-store.ts +1 -1
- package/src/apps/resolve-app.ts +81 -0
- package/src/apps/source-fingerprint.ts +266 -0
- package/src/bundler/app-compiler.ts +15 -0
- package/src/calls/__tests__/barge-in-guard.test.ts +81 -0
- package/src/{__tests__ → calls/__tests__}/call-controller.test.ts +503 -82
- package/src/{__tests__ → calls/__tests__}/media-stream-audio-transcode.test.ts +1 -1
- package/src/{__tests__ → calls/__tests__}/media-stream-output.test.ts +37 -8
- package/src/{__tests__ → calls/__tests__}/media-stream-parser.test.ts +1 -1
- package/src/{__tests__ → calls/__tests__}/media-stream-server-integration.test.ts +150 -57
- package/src/{__tests__ → calls/__tests__}/media-stream-stt-session.test.ts +5 -5
- package/src/{__tests__ → calls/__tests__}/media-turn-detector.test.ts +1 -1
- package/src/{live-voice → calls}/__tests__/progress-narration.test.ts +4 -4
- package/src/{live-voice → calls}/__tests__/progress-phrases.test.ts +1 -1
- package/src/calls/__tests__/voice-control-protocol.test.ts +58 -25
- package/src/calls/__tests__/voice-leg-coordinator.test.ts +229 -0
- package/src/calls/__tests__/voice-progress-cadence.test.ts +274 -0
- package/src/{__tests__/voice-session-bridge.test.ts → calls/__tests__/voice-session-bridge-turn-flow.test.ts} +20 -12
- package/src/calls/__tests__/voice-session-bridge.test.ts +37 -1
- package/src/calls/__tests__/voice-triage-escalate.test.ts +105 -0
- package/src/calls/barge-in-guard.ts +118 -0
- package/src/calls/call-constants.ts +24 -4
- package/src/calls/call-controller.ts +499 -199
- package/src/calls/call-transport.ts +16 -0
- package/src/calls/media-stream-output.ts +32 -0
- package/src/calls/media-stream-server.ts +82 -30
- package/src/calls/media-stream-stt-session.ts +15 -0
- package/src/{live-voice → calls}/progress-narration.ts +2 -2
- package/src/calls/resolve-call-tts-provider.ts +33 -2
- package/src/calls/tts-call-strategy.ts +9 -5
- package/src/calls/types.ts +2 -0
- package/src/calls/voice-control-protocol.ts +53 -21
- package/src/calls/voice-leg-coordinator.ts +189 -0
- package/src/calls/voice-progress-cadence.ts +423 -0
- package/src/calls/voice-session-bridge.ts +59 -46
- package/src/calls/voice-triage-escalate.ts +175 -56
- package/src/cli/commands/__tests__/apps.test.ts +245 -0
- package/src/cli/commands/__tests__/bash.test.ts +53 -0
- package/src/cli/commands/__tests__/roadmap.help.test.ts +19 -0
- package/src/cli/commands/apps.help.ts +67 -13
- package/src/cli/commands/apps.ts +183 -0
- package/src/cli/commands/bash.help.ts +8 -13
- package/src/cli/commands/bash.ts +7 -25
- package/src/cli/commands/channels/__tests__/send.test.ts +218 -0
- package/src/cli/commands/channels/index.help.ts +70 -2
- package/src/cli/commands/channels/index.ts +16 -3
- package/src/cli/commands/channels/send.ts +128 -0
- package/src/cli/commands/credentials.help.ts +6 -2
- package/src/cli/commands/credentials.ts +2 -2
- package/src/cli/commands/mcp.help.ts +8 -8
- package/src/cli/commands/mcp.ts +1 -1
- package/src/cli/commands/roadmap.help.ts +5 -1
- package/src/cli/commands/roadmap.ts +1 -1
- package/src/cli/lib/bundled-marketplace.json +103 -90
- package/src/config/__tests__/default-profile-catalog.test.ts +22 -0
- package/src/config/__tests__/memory-retrospective-schema.test.ts +15 -1
- package/src/config/__tests__/platform-identity.test.ts +255 -0
- package/src/config/__tests__/platform-rehydration.test.ts +99 -9
- package/src/config/acp-agent-write.ts +43 -0
- package/src/config/acp-defaults.test.ts +13 -3
- package/src/config/acp-defaults.ts +23 -6
- package/src/config/acp-schema.test.ts +81 -0
- package/src/config/acp-schema.ts +30 -2
- package/src/config/bundled-skills/acp/SKILL.md +22 -14
- package/src/config/bundled-skills/acp/TOOLS.json +5 -1
- package/src/config/bundled-skills/app-builder/SKILL.md +12 -9
- package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +1 -1
- package/src/config/bundled-skills/messaging/SKILL.md +3 -3
- package/src/config/bundled-skills/messaging/TOOLS.json +3 -3
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +67 -139
- package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +1 -1
- package/src/config/bundled-skills/schedule/SKILL.md +3 -4
- package/src/config/bundled-skills/screen-annotation/SKILL.md +8 -1
- package/src/config/bundled-skills/screen-annotation/tools/screen-annotation.test.ts +4 -1
- package/src/config/bundled-skills/skill-management/TOOLS.json +2 -2
- package/src/config/default-profile-catalog.ts +1 -1
- package/src/config/env.ts +2 -2
- package/src/config/feature-flag-registry.json +24 -12
- package/src/config/loader.ts +1 -0
- package/src/config/memory-v3-gate.test.ts +31 -0
- package/src/config/memory-v3-gate.ts +20 -16
- package/src/config/platform-identity.ts +201 -0
- package/src/config/platform-rehydration.ts +11 -27
- package/src/config/schema.ts +2 -2
- package/src/config/schemas/__tests__/live-voice.test.ts +8 -158
- package/src/config/schemas/__tests__/voice.test.ts +148 -0
- package/src/config/schemas/calls.ts +1 -28
- package/src/config/schemas/live-voice.ts +1 -173
- package/src/config/schemas/mcp.ts +1 -1
- package/src/config/schemas/memory-retrospective.ts +10 -0
- package/src/config/schemas/services.ts +8 -0
- package/src/config/schemas/skills.ts +0 -14
- package/src/config/schemas/voice.ts +150 -0
- package/src/credential-execution/process-manager.test.ts +18 -19
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -121
- package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +4 -1
- package/src/daemon/__tests__/skill-memory-refresh-mtime.test.ts +133 -0
- package/src/daemon/app-change-notify.ts +45 -0
- package/src/daemon/config-watcher.ts +13 -194
- package/src/daemon/conversation-process.ts +35 -64
- package/src/daemon/conversation-skill-tools.ts +13 -31
- package/src/daemon/conversation-store.ts +1 -1
- package/src/daemon/conversation-surfaces.ts +10 -8
- package/src/daemon/conversation-tool-setup.ts +37 -43
- package/src/daemon/conversation.ts +0 -3
- package/src/daemon/host-bash-proxy.ts +8 -12
- package/src/daemon/host-browser-proxy.ts +15 -1
- package/src/daemon/host-file-proxy.ts +8 -10
- package/src/daemon/host-transfer-proxy.ts +14 -18
- package/src/daemon/lifecycle.ts +0 -5
- package/src/daemon/mcp-reload-service.ts +5 -5
- package/src/daemon/message-types/sync.ts +1 -0
- package/src/daemon/orphan-reaper.ts +1 -1
- package/src/daemon/providers-setup.ts +1 -1
- package/src/daemon/shutdown-handlers.ts +1 -9
- package/src/daemon/skill-memory-refresh.ts +64 -0
- package/src/daemon/tool-side-effects.ts +7 -18
- package/src/desktop/__tests__/fake-desktop.ts +119 -0
- package/src/desktop/desktop-chrome-policy.test.ts +71 -0
- package/src/desktop/desktop-chrome-policy.ts +42 -0
- package/src/desktop/desktop-dependencies.test.ts +102 -0
- package/src/desktop/desktop-dependencies.ts +306 -0
- package/src/desktop/desktop-feature.test.ts +18 -0
- package/src/desktop/desktop-feature.ts +19 -0
- package/src/desktop/desktop-panel-config.test.ts +135 -0
- package/src/desktop/desktop-panel-config.ts +149 -0
- package/src/desktop/desktop-session-manager.test.ts +627 -0
- package/src/desktop/desktop-session-manager.ts +814 -0
- package/src/desktop/desktop-stream-bridge.test.ts +395 -0
- package/src/desktop/desktop-stream-bridge.ts +214 -0
- package/src/desktop/desktop-wallpaper.test.ts +191 -0
- package/src/desktop/desktop-wallpaper.ts +113 -0
- package/src/desktop/desktop-window-theme.ts +170 -0
- package/src/home/__tests__/feed-writer.test.ts +22 -0
- package/src/home/feed-writer.ts +11 -2
- package/src/hooks/types.ts +33 -0
- package/src/i18n/AGENTS.md +22 -0
- package/src/i18n/__tests__/i18n.test.ts +108 -0
- package/src/i18n/index.ts +16 -0
- package/src/i18n/locales.ts +93 -0
- package/src/i18n/messages.ts +61 -0
- package/src/i18n/title.ts +25 -0
- package/src/inbound/platform-callback-registration.ts +4 -5
- package/src/live-voice/__tests__/live-voice-metrics.test.ts +0 -3
- package/src/live-voice/__tests__/live-voice-progress.test.ts +15 -15
- package/src/live-voice/__tests__/live-voice-vad.test.ts +2 -2
- package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +13 -69
- package/src/live-voice/live-voice-session.ts +213 -693
- package/src/live-voice/protocol.ts +0 -2
- package/src/mcp/__tests__/effective-config.test.ts +2 -2
- package/src/mcp/__tests__/workspace-mcp-config.test.ts +135 -0
- package/src/mcp/effective-config.ts +3 -3
- package/src/mcp/mcp-header-store.ts +29 -45
- package/src/mcp/spec-schema.ts +111 -0
- package/src/mcp/startup.ts +8 -4
- package/src/mcp/workspace-mcp-config.ts +188 -0
- package/src/messaging/provider.ts +10 -3
- package/src/messaging/providers/__tests__/proactive-address.test.ts +95 -0
- package/src/messaging/providers/callback-routing.ts +18 -0
- package/src/messaging/providers/channel-transport.ts +56 -0
- package/src/messaging/providers/discord/transport.ts +17 -0
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +6 -17
- package/src/messaging/providers/slack/adapter.ts +7 -44
- package/src/messaging/providers/slack/transport.ts +16 -0
- package/src/messaging/providers/telegram-bot/adapter.ts +1 -48
- package/src/messaging/providers/telegram-bot/client.ts +0 -26
- package/src/messaging/providers/telegram-bot/transport.ts +20 -0
- package/src/messaging/providers/whatsapp/adapter.ts +1 -48
- package/src/messaging/providers/whatsapp/transport.ts +18 -0
- package/src/monitoring/__tests__/workspace-git-heartbeat.test.ts +8 -0
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +55 -0
- package/src/notifications/__tests__/guardian-feed-projection.test.ts +158 -2
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +153 -3
- package/src/notifications/__tests__/schedule-result-producer.test.ts +99 -3
- package/src/notifications/adapters/macos.ts +3 -0
- package/src/notifications/assistant-reply-producer.ts +8 -3
- package/src/notifications/conversation-pairing.ts +36 -16
- package/src/notifications/delivered-post-record.ts +11 -1
- package/src/notifications/guardian-feed-projection.ts +125 -5
- package/src/notifications/home-feed-side-effect.ts +55 -7
- package/src/notifications/schedule-result-producer.ts +66 -22
- package/src/oauth/__tests__/seed-providers-managed.test.ts +54 -0
- package/src/oauth/byo-connection.test.ts +45 -0
- package/src/oauth/byo-connection.ts +36 -6
- package/src/oauth/connection-resolver.test.ts +73 -0
- package/src/oauth/connection-resolver.ts +95 -2
- package/src/oauth/seed-providers.ts +165 -0
- package/src/outbound-proxy/certs.ts +3 -5
- package/src/permissions/checker.test.ts +11 -6
- package/src/persistence/compaction-log-store-clickhouse.ts +3 -3
- package/src/persistence/conversation-crud.ts +71 -63
- package/src/persistence/conversation-lineage.ts +12 -4
- package/src/persistence/conversation-title-placeholders.ts +23 -0
- package/src/persistence/conversation-title-service.ts +9 -26
- package/src/persistence/llm-request-log-sink-clickhouse.ts +3 -3
- package/src/persistence/llm-request-log-source-clickhouse.ts +3 -3
- package/src/persistence/message-cursor.ts +78 -0
- package/src/persistence/message-reads.ts +30 -0
- package/src/persistence/migrations/157-invite-contact-id.ts +18 -6
- package/src/persistence/migrations/__tests__/157-invite-contact-id.test.ts +149 -0
- package/src/persistence/schema/memory-core.ts +5 -0
- package/src/platform/client.test.ts +4 -2
- package/src/platform/client.ts +2 -10
- package/src/plugin-api/constants.ts +2 -0
- package/src/plugin-api/index.ts +1 -0
- package/src/plugin-api/types.ts +2 -0
- package/src/plugins/defaults/index.ts +2 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +46 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-backfill.test.ts +118 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-preserve.test.ts +83 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +9 -5
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +20 -10
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-state.test.ts +219 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +31 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-trigger-check.test.ts +63 -0
- package/src/plugins/defaults/memory/__tests__/shutdown-hook.test.ts +4 -0
- package/src/plugins/defaults/memory/graph/capability-seed.ts +4 -6
- package/src/plugins/defaults/memory/hooks/init.ts +10 -0
- package/src/plugins/defaults/memory/hooks/message-deleted.ts +21 -0
- package/src/plugins/defaults/memory/hooks/shutdown.ts +2 -0
- package/src/plugins/defaults/memory/jobs-worker.ts +13 -0
- package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +34 -60
- package/src/plugins/defaults/memory/memory-retrospective-cursor-backfill.ts +126 -0
- package/src/plugins/defaults/memory/memory-retrospective-cursor-preserve.ts +79 -0
- package/src/plugins/defaults/memory/memory-retrospective-cursor.ts +28 -0
- package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +3 -5
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +12 -3
- package/src/plugins/defaults/memory/memory-retrospective-state.ts +312 -71
- package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +2 -1
- package/src/plugins/defaults/memory/memory-retrospective-trigger-check.ts +5 -3
- package/src/plugins/defaults/memory/skill-md-mtime-poll.ts +9 -0
- package/src/plugins/defaults/memory/startup.ts +6 -1
- package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +7 -9
- package/src/plugins/defaults/memory/substrate/skill-content.ts +2 -2
- package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +89 -0
- package/src/plugins/defaults/memory/worker.ts +6 -0
- package/src/plugins/mcp-servers.ts +15 -75
- package/src/providers/__tests__/connection-model-compat.test.ts +3 -3
- package/src/providers/gemini/client.ts +9 -23
- package/src/providers/gemini-thought-signature.test.ts +63 -0
- package/src/providers/gemini-thought-signature.ts +44 -0
- package/src/providers/inference/auth.ts +2 -3
- package/src/providers/inference/connection-availability.ts +27 -8
- package/src/providers/inference/resolve-auth.ts +22 -4
- package/src/providers/inference/service-account-token.ts +248 -0
- package/src/providers/model-catalog.ts +34 -0
- package/src/providers/model-intents.ts +10 -5
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +146 -18
- package/src/providers/openai/__tests__/chat-completions-provider-thought-signature.test.ts +613 -0
- package/src/providers/openai/__tests__/google-thought-signature.test.ts +235 -0
- package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +83 -1
- package/src/providers/openai/chat-completions-provider.ts +192 -78
- package/src/providers/openai/codex-models.ts +0 -4
- package/src/providers/openai/google-thought-signature.ts +320 -0
- package/src/runtime/AGENTS.md +2 -0
- package/src/runtime/__tests__/channel-send.test.ts +443 -0
- package/src/runtime/__tests__/desktop-stream-upgrade.test.ts +89 -0
- package/src/runtime/__tests__/live-voice-upgrade.test.ts +58 -0
- package/src/runtime/__tests__/runtime-ws-test-utils.ts +72 -0
- package/src/runtime/__tests__/watch-stream-upgrade.test.ts +59 -0
- package/src/runtime/auth/same-actor.ts +38 -3
- package/src/runtime/channel-send.ts +316 -0
- package/src/runtime/http-server.ts +160 -151
- package/src/runtime/routes/__tests__/acp-routes.test.ts +159 -4
- package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +205 -0
- package/src/runtime/routes/__tests__/channel-send-routes.test.ts +127 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +108 -1
- package/src/runtime/routes/__tests__/default-provider-routes.test.ts +2 -2
- package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +107 -0
- package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +16 -12
- package/src/runtime/routes/acp-routes.test.ts +15 -0
- package/src/runtime/routes/acp-routes.ts +72 -9
- package/src/runtime/routes/app-management-routes.ts +73 -0
- package/src/runtime/routes/app-routes.ts +1 -1
- package/src/runtime/routes/channel-send-routes.ts +85 -0
- package/src/runtime/routes/conversation-list-routes.ts +14 -3
- package/src/runtime/routes/conversation-query-routes.ts +40 -0
- package/src/runtime/routes/credential-routes.ts +19 -10
- package/src/runtime/routes/desktop-setup-routes.test.ts +74 -0
- package/src/runtime/routes/desktop-setup-routes.ts +35 -0
- package/src/runtime/routes/diagnostics-routes.ts +55 -3
- package/src/runtime/routes/index.ts +4 -2
- package/src/runtime/routes/log-export-routes.ts +2 -0
- package/src/runtime/routes/mcp-auth-routes.ts +39 -47
- package/src/runtime/routes/migration-routes.ts +6 -20
- package/src/runtime/routes/platform-routes.ts +18 -15
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +28 -0
- package/src/runtime/services/conversation-serializer.ts +16 -3
- package/src/runtime/sync/resource-sync-events.ts +2 -1
- package/src/schedule/worker.ts +1 -1
- package/src/security/__tests__/ces-http-record-backend.test.ts +137 -0
- package/src/security/ces-http-record-backend.ts +120 -0
- package/src/security/ces-rpc-record-backend.ts +7 -0
- package/src/skills/__tests__/workspace-skill-md-signature.test.ts +60 -0
- package/src/skills/workspace-skill-md-signature.ts +50 -0
- package/src/subagent/consult-context.ts +12 -25
- package/src/tools/acp/list-agents.test.ts +3 -1
- package/src/tools/acp/spawn.test.ts +146 -11
- package/src/tools/acp/spawn.ts +21 -3
- package/src/tools/acp/status.test.ts +94 -0
- package/src/tools/acp/status.ts +40 -3
- package/src/tools/app-control/skill-proxy-bridge.ts +2 -1
- package/src/tools/capability-offer.test.ts +22 -2
- package/src/tools/capability-offer.ts +16 -1
- package/src/tools/computer-use/definitions.ts +4 -2
- package/src/tools/computer-use/skill-proxy-bridge.ts +2 -1
- package/src/tools/credentials/metadata-store.ts +100 -0
- package/src/tools/host-filesystem/edit.test.ts +2 -1
- package/src/tools/host-filesystem/edit.ts +2 -2
- package/src/tools/host-filesystem/read.test.ts +2 -1
- package/src/tools/host-filesystem/read.ts +2 -2
- package/src/tools/host-filesystem/transfer.test.ts +2 -1
- package/src/tools/host-filesystem/transfer.ts +2 -2
- package/src/tools/host-filesystem/write.test.ts +2 -1
- package/src/tools/host-filesystem/write.ts +2 -2
- package/src/tools/host-terminal/host-shell.test.ts +16 -0
- package/src/tools/host-terminal/host-shell.ts +4 -20
- package/src/tools/policy-context.ts +6 -7
- package/src/tools/subagent/spawn.ts +5 -8
- package/src/tools/terminal/__tests__/safe-env.test.ts +6 -6
- package/src/tools/terminal/__tests__/sanitized-bash.test.ts +57 -0
- package/src/tools/terminal/safe-env.ts +23 -3
- package/src/tools/terminal/sanitized-bash.ts +105 -0
- package/src/tools/watch/watch-retro-report.ts +0 -8
- package/src/util/__tests__/language-subtag.test.ts +23 -1
- package/src/util/ca-bundle.ts +13 -0
- package/src/util/language-subtag.ts +16 -0
- package/src/util/platform.ts +8 -0
- package/src/workspace/git-porcelain.ts +66 -0
- package/src/workspace/git-service.ts +5 -54
- package/src/workspace/migrations/154-repair-retired-codex-gpt-5-4-model-ids.ts +258 -0
- package/src/workspace/migrations/155-move-front-model-config-to-voice.ts +78 -0
- package/src/workspace/migrations/156-extract-workspace-mcp-json.ts +214 -0
- package/src/workspace/migrations/__tests__/155-move-front-model-config-to-voice.test.ts +102 -0
- package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +199 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/src/__tests__/app-source-watcher.test.ts +0 -218
- package/src/__tests__/config-watcher-skill-reseed.test.ts +0 -359
- package/src/__tests__/speaker-identification.test.ts +0 -56
- package/src/calls/speaker-identification.ts +0 -233
- package/src/daemon/app-source-watcher.ts +0 -218
- package/src/messaging/providers/whatsapp/client.ts +0 -29
- package/src/runtime/routes/debug-bash-routes.ts +0 -173
- package/src/util/fs-watcher-error.ts +0 -36
- /package/src/{live-voice → calls}/progress-phrases.ts +0 -0
|
@@ -18,10 +18,7 @@ import { getConfig } from "../../config/loader.js";
|
|
|
18
18
|
import { HostBashProxy } from "../../daemon/host-bash-proxy.js";
|
|
19
19
|
import { RiskLevel } from "../../permissions/types.js";
|
|
20
20
|
import { wakeAgentForOpportunity } from "../../runtime/agent-wake.js";
|
|
21
|
-
import {
|
|
22
|
-
assistantEventHub,
|
|
23
|
-
broadcastMessage,
|
|
24
|
-
} from "../../runtime/assistant-event-hub.js";
|
|
21
|
+
import { broadcastMessage } from "../../runtime/assistant-event-hub.js";
|
|
25
22
|
import { conversationRevealNonce } from "../../runtime/reveal-nonce.js";
|
|
26
23
|
import { redactSecrets } from "../../security/secret-scanner.js";
|
|
27
24
|
import {
|
|
@@ -39,7 +36,7 @@ import {
|
|
|
39
36
|
registerBackgroundTool,
|
|
40
37
|
removeBackgroundTool,
|
|
41
38
|
} from "../background-tool-registry.js";
|
|
42
|
-
import {
|
|
39
|
+
import { formatDesktopAppRequired } from "../capability-offer.js";
|
|
43
40
|
import {
|
|
44
41
|
attachBoundedStdio,
|
|
45
42
|
MAX_OUTPUT_LENGTH,
|
|
@@ -142,7 +139,7 @@ export const hostShellInputSchema = z.looseObject({
|
|
|
142
139
|
target_client_id: z
|
|
143
140
|
.string()
|
|
144
141
|
.describe(
|
|
145
|
-
"ID of the specific client to execute this command on.
|
|
142
|
+
"Optional ID of the specific client to execute this command on. Without it, the most recently active eligible client is used. Obtain IDs from `assistant clients list --capability host_bash`.",
|
|
146
143
|
)
|
|
147
144
|
.optional()
|
|
148
145
|
.catch(undefined),
|
|
@@ -203,20 +200,7 @@ export const hostShellTool = {
|
|
|
203
200
|
const config = getConfig();
|
|
204
201
|
const { shellDefaultTimeoutSec, shellMaxTimeoutSec } = config.timeouts;
|
|
205
202
|
|
|
206
|
-
// Guard: non-host-proxy interfaces need an explicit target when multiple
|
|
207
|
-
// capable clients are connected to avoid ambiguous untargeted broadcasts.
|
|
208
203
|
const transportInterface = context.transportInterface;
|
|
209
|
-
if (
|
|
210
|
-
targetClientId == null &&
|
|
211
|
-
transportInterface != null &&
|
|
212
|
-
!supportsHostProxy(transportInterface) &&
|
|
213
|
-
assistantEventHub.listClientsByCapability("host_bash").length > 1
|
|
214
|
-
) {
|
|
215
|
-
return {
|
|
216
|
-
content: `Error: multiple clients support host_bash. Specify which client to use with \`target_client_id\`. Run \`assistant clients list --capability host_bash\` to see client IDs and labels.`,
|
|
217
|
-
isError: true,
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
204
|
|
|
221
205
|
// Guard: non-host-proxy interfaces with no capable clients connected.
|
|
222
206
|
if (
|
|
@@ -226,7 +210,7 @@ export const hostShellTool = {
|
|
|
226
210
|
!HostBashProxy.instance.isAvailable()
|
|
227
211
|
) {
|
|
228
212
|
return {
|
|
229
|
-
content:
|
|
213
|
+
content: formatDesktopAppRequired("shell"),
|
|
230
214
|
isError: true,
|
|
231
215
|
};
|
|
232
216
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getConfig } from "../config/loader.js";
|
|
2
|
-
import {
|
|
2
|
+
import { isSkillImprovementActive } from "../config/memory-v3-gate.js";
|
|
3
3
|
import type { ExecutionContext } from "../permissions/approval-policy.js";
|
|
4
4
|
import type { ChannelPermissionCoordinates } from "../permissions/channel-permission-query.js";
|
|
5
5
|
import type { PolicyContext } from "../permissions/types.js";
|
|
@@ -63,12 +63,11 @@ export function buildPolicyContext(
|
|
|
63
63
|
requestOrigin: context?.requestOrigin,
|
|
64
64
|
...channelCoordinatesFromToolContext(context),
|
|
65
65
|
requesterContactId: context?.requesterContactId,
|
|
66
|
-
// Precompute the
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
procToSkillsActive: isV3TierActive(getConfig()),
|
|
66
|
+
// Precompute the retrospective skill-improvement gate here so the
|
|
67
|
+
// permission checker, a leaf module that must not read config, can deny
|
|
68
|
+
// the memory-retrospective skill-authoring grant whenever the feature is
|
|
69
|
+
// inactive just by reading this boolean.
|
|
70
|
+
procToSkillsActive: isSkillImprovementActive(getConfig()),
|
|
72
71
|
};
|
|
73
72
|
|
|
74
73
|
const ownerKind = getToolOwner(tool.name)?.kind;
|
|
@@ -484,9 +484,9 @@ function spawnCancelledResult(label: string): ToolExecutionResult {
|
|
|
484
484
|
* repeat-spawn guard folds, so a consult has to record it the same way every
|
|
485
485
|
* other spawn does or the guard could never match one consult against another.
|
|
486
486
|
*
|
|
487
|
-
* The context pack is best effort:
|
|
488
|
-
*
|
|
489
|
-
*
|
|
487
|
+
* The context pack is best effort: each section independently falls back to
|
|
488
|
+
* null on failure, so a missing workspace or empty catalog costs only that
|
|
489
|
+
* section, never the consult.
|
|
490
490
|
*
|
|
491
491
|
* The budget is the advisor's alone. It is the only spawn the agent may issue
|
|
492
492
|
* unprompted and the only one that runs on the premium profile, so it is the
|
|
@@ -502,21 +502,18 @@ async function buildAdvisorFields(
|
|
|
502
502
|
maxRuntimeMs: number;
|
|
503
503
|
maxToolCalls: number;
|
|
504
504
|
}> {
|
|
505
|
-
const parentConversation = findConversation(context.conversationId);
|
|
506
505
|
// Situational awareness for the advisor: the parent's live tool set, the
|
|
507
506
|
// full skill catalog, and its workspace. Assembled off the per-turn
|
|
508
507
|
// ToolContext snapshot (trust, channel) so the personal-memory sections are
|
|
509
508
|
// gated exactly like the runtime injectors. A null pack just means the
|
|
510
|
-
// consult runs on the brief alone.
|
|
509
|
+
// consult runs on the brief alone. Skills come from a fresh
|
|
510
|
+
// `loadSkillCatalog()` inside the context pack, not a conversation snapshot.
|
|
511
511
|
const situationalContext = await buildAdvisorContext({
|
|
512
512
|
conversationId: context.conversationId,
|
|
513
513
|
workingDir: context.workingDir,
|
|
514
514
|
allowedToolNames: context.allowedToolNames,
|
|
515
515
|
trustClass: context.trustClass,
|
|
516
516
|
enabledPluginSet: context.enabledPluginSet,
|
|
517
|
-
// The parent's warm per-turn catalog keeps the synchronous on-disk catalog
|
|
518
|
-
// scan out of the spawn path.
|
|
519
|
-
skillCatalog: parentConversation?.skillProjectionCache?.catalog,
|
|
520
517
|
});
|
|
521
518
|
return {
|
|
522
519
|
requestText: advisorRequestText(objective, situationalContext),
|
|
@@ -50,10 +50,10 @@ describe("safe-env Qdrant forwarding", () => {
|
|
|
50
50
|
});
|
|
51
51
|
});
|
|
52
52
|
|
|
53
|
-
describe("safe-env CES
|
|
54
|
-
test("
|
|
55
|
-
expect(SAFE_ENV_VARS).
|
|
56
|
-
expect(SAFE_ENV_VARS).
|
|
53
|
+
describe("safe-env CES child forwarding", () => {
|
|
54
|
+
test("forwards CES HTTP credentials so sanitized children can resolve managed connections", () => {
|
|
55
|
+
expect(SAFE_ENV_VARS).toContain("CES_SERVICE_TOKEN");
|
|
56
|
+
expect(SAFE_ENV_VARS).toContain("CES_CREDENTIAL_URL");
|
|
57
57
|
expect(SAFE_ENV_VARS).toContain("CES_LOCAL_SOCKET");
|
|
58
58
|
|
|
59
59
|
const env = buildSanitizedEnv("linux", {
|
|
@@ -61,8 +61,8 @@ describe("safe-env CES vault isolation", () => {
|
|
|
61
61
|
CES_CREDENTIAL_URL: "http://127.0.0.1:8090",
|
|
62
62
|
CES_LOCAL_SOCKET: "/tmp/ces.sock",
|
|
63
63
|
});
|
|
64
|
-
expect(env.CES_SERVICE_TOKEN).
|
|
65
|
-
expect(env.CES_CREDENTIAL_URL).
|
|
64
|
+
expect(env.CES_SERVICE_TOKEN).toBe("vault-bearer");
|
|
65
|
+
expect(env.CES_CREDENTIAL_URL).toBe("http://127.0.0.1:8090");
|
|
66
66
|
expect(env.CES_LOCAL_SOCKET).toBe("/tmp/ces.sock");
|
|
67
67
|
});
|
|
68
68
|
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { runSanitizedBash } from "../sanitized-bash.js";
|
|
4
|
+
|
|
5
|
+
describe("runSanitizedBash", () => {
|
|
6
|
+
const priorToken = process.env.CES_SERVICE_TOKEN;
|
|
7
|
+
const priorSocket = process.env.CES_LOCAL_SOCKET;
|
|
8
|
+
|
|
9
|
+
afterEach(() => {
|
|
10
|
+
if (priorToken == null) {
|
|
11
|
+
delete process.env.CES_SERVICE_TOKEN;
|
|
12
|
+
} else {
|
|
13
|
+
process.env.CES_SERVICE_TOKEN = priorToken;
|
|
14
|
+
}
|
|
15
|
+
if (priorSocket == null) {
|
|
16
|
+
delete process.env.CES_LOCAL_SOCKET;
|
|
17
|
+
} else {
|
|
18
|
+
process.env.CES_LOCAL_SOCKET = priorSocket;
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("runs a command and returns stdout", async () => {
|
|
23
|
+
const result = await runSanitizedBash("echo hello", 5_000);
|
|
24
|
+
expect(result.error).toBeUndefined();
|
|
25
|
+
expect(result.timedOut).toBe(false);
|
|
26
|
+
expect(result.exitCode).toBe(0);
|
|
27
|
+
expect(result.stdout.trim()).toBe("hello");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("forwards CES_SERVICE_TOKEN to the child", async () => {
|
|
31
|
+
process.env.CES_SERVICE_TOKEN = "vault-bearer";
|
|
32
|
+
const result = await runSanitizedBash("printenv CES_SERVICE_TOKEN", 5_000);
|
|
33
|
+
expect(result.error).toBeUndefined();
|
|
34
|
+
expect(result.exitCode).toBe(0);
|
|
35
|
+
expect(result.stdout.trim()).toBe("vault-bearer");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("forwards CES_LOCAL_SOCKET to the child", async () => {
|
|
39
|
+
process.env.CES_LOCAL_SOCKET = "/tmp/ces.sock";
|
|
40
|
+
const result = await runSanitizedBash("printenv CES_LOCAL_SOCKET", 5_000);
|
|
41
|
+
expect(result.error).toBeUndefined();
|
|
42
|
+
expect(result.exitCode).toBe(0);
|
|
43
|
+
expect(result.stdout.trim()).toBe("/tmp/ces.sock");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("times out a hung command", async () => {
|
|
47
|
+
const result = await runSanitizedBash("sleep 5", 200);
|
|
48
|
+
expect(result.error).toBeUndefined();
|
|
49
|
+
expect(result.timedOut).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("rejects an empty command", async () => {
|
|
53
|
+
const result = await runSanitizedBash("");
|
|
54
|
+
expect(result.error).toBe("command is required");
|
|
55
|
+
expect(result.exitCode).toBeNull();
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -55,10 +55,30 @@ export const SAFE_ENV_VARS = [
|
|
|
55
55
|
"VELLUM_MIGRATION_EXPORT_ALLOWED_HOSTS",
|
|
56
56
|
"VELLUM_MIGRATION_IMPORT_ALLOWED_HOSTS",
|
|
57
57
|
"CES_MANAGED_MODE",
|
|
58
|
-
// Socket path only. The CES HTTP bearer (`CES_SERVICE_TOKEN`) and
|
|
59
|
-
// `CES_CREDENTIAL_URL` stay in the assistant process: child shells must
|
|
60
|
-
// not inherit a vault token they can printenv or log.
|
|
61
58
|
"CES_LOCAL_SOCKET",
|
|
59
|
+
// Child processes (bash, skill sandbox, scheduled scripts) run
|
|
60
|
+
// `assistant oauth request` in-process and resolve managed connections
|
|
61
|
+
// through CES HTTP (`getSecureKeyAsync` / platform client). They need
|
|
62
|
+
// both the credential URL and the service token. CES_LOCAL_SOCKET alone
|
|
63
|
+
// is not enough on managed pods: the socket is often absent or does not
|
|
64
|
+
// complete the lazy RPC path those children use, so a connected account
|
|
65
|
+
// looks disconnected from sanitized children while the daemon still
|
|
66
|
+
// reports healthy.
|
|
67
|
+
//
|
|
68
|
+
// To drop these later, children must resolve the same ACTIVE managed
|
|
69
|
+
// connections without inheriting a reusable vault bearer. That means
|
|
70
|
+
// CES RPC over CES_LOCAL_SOCKET works on managed pods (present,
|
|
71
|
+
// reachable, and sufficient for secure-key / platform-client reads), or
|
|
72
|
+
// a narrower grant protocol issues a one-shot provider token.
|
|
73
|
+
//
|
|
74
|
+
// Before removing them again, reproduce from a sanitized child on a
|
|
75
|
+
// platform-managed pod. After `buildSanitizedEnv()`,
|
|
76
|
+
// `assistant oauth request --provider <key> --json <provider-url>` must
|
|
77
|
+
// succeed for an ACTIVE connection and match a direct terminal
|
|
78
|
+
// invocation. Unit tests that only assert the vars are absent are not
|
|
79
|
+
// enough: also cover skill sandbox and scheduled-script children.
|
|
80
|
+
"CES_CREDENTIAL_URL",
|
|
81
|
+
"CES_SERVICE_TOKEN",
|
|
62
82
|
// Per-instance port of the assistant-managed Qdrant sidecar, so skill and
|
|
63
83
|
// bash-tool subprocesses that use the vector helpers (e.g. embed/search over
|
|
64
84
|
// `@vellumai/plugin-api`) resolve the same local sidecar as the daemon
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spawn a shell command with the same sanitized environment the bash tool
|
|
3
|
+
* forwards to tool-call subprocesses.
|
|
4
|
+
*
|
|
5
|
+
* Used by `assistant bash` so a CLI invocation sees the same allowlisted
|
|
6
|
+
* env, workspace cwd, and platform shell as a bash tool call, without
|
|
7
|
+
* round-tripping through the assistant HTTP or IPC API.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { spawn } from "node:child_process";
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
buildShellInvocation,
|
|
14
|
+
terminateProcessTree,
|
|
15
|
+
} from "../../util/host-process.js";
|
|
16
|
+
import { getWorkspaceDir } from "../../util/platform.js";
|
|
17
|
+
import { buildSanitizedEnv } from "./safe-env.js";
|
|
18
|
+
|
|
19
|
+
export const DEFAULT_SANITIZED_BASH_TIMEOUT_MS = 30_000;
|
|
20
|
+
|
|
21
|
+
export interface SanitizedBashResult {
|
|
22
|
+
stdout: string;
|
|
23
|
+
stderr: string;
|
|
24
|
+
exitCode: number | null;
|
|
25
|
+
timedOut: boolean;
|
|
26
|
+
error?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function runSanitizedBash(
|
|
30
|
+
command: string,
|
|
31
|
+
timeoutMs: number = DEFAULT_SANITIZED_BASH_TIMEOUT_MS,
|
|
32
|
+
): Promise<SanitizedBashResult> {
|
|
33
|
+
if (!command) {
|
|
34
|
+
return Promise.resolve({
|
|
35
|
+
stdout: "",
|
|
36
|
+
stderr: "",
|
|
37
|
+
exitCode: null,
|
|
38
|
+
timedOut: false,
|
|
39
|
+
error: "command is required",
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const effectiveTimeout =
|
|
44
|
+
typeof timeoutMs === "number" && timeoutMs > 0
|
|
45
|
+
? timeoutMs
|
|
46
|
+
: DEFAULT_SANITIZED_BASH_TIMEOUT_MS;
|
|
47
|
+
|
|
48
|
+
return new Promise<SanitizedBashResult>((resolve) => {
|
|
49
|
+
const stdoutChunks: Buffer[] = [];
|
|
50
|
+
const stderrChunks: Buffer[] = [];
|
|
51
|
+
let timedOut = false;
|
|
52
|
+
let settled = false;
|
|
53
|
+
|
|
54
|
+
const finish = (result: SanitizedBashResult) => {
|
|
55
|
+
if (settled) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
settled = true;
|
|
59
|
+
resolve(result);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const wrapped = buildShellInvocation(command);
|
|
63
|
+
const child = spawn(wrapped.command, wrapped.args, {
|
|
64
|
+
cwd: getWorkspaceDir(),
|
|
65
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
66
|
+
detached: true,
|
|
67
|
+
env: buildSanitizedEnv(),
|
|
68
|
+
windowsHide: true,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const timer = setTimeout(() => {
|
|
72
|
+
timedOut = true;
|
|
73
|
+
terminateProcessTree(child);
|
|
74
|
+
}, effectiveTimeout);
|
|
75
|
+
|
|
76
|
+
child.stdout.on("data", (data: Buffer) => {
|
|
77
|
+
stdoutChunks.push(data);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
child.stderr.on("data", (data: Buffer) => {
|
|
81
|
+
stderrChunks.push(data);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
child.on("close", (code) => {
|
|
85
|
+
clearTimeout(timer);
|
|
86
|
+
finish({
|
|
87
|
+
stdout: Buffer.concat(stdoutChunks).toString(),
|
|
88
|
+
stderr: Buffer.concat(stderrChunks).toString(),
|
|
89
|
+
exitCode: code,
|
|
90
|
+
timedOut,
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
child.on("error", (err) => {
|
|
95
|
+
clearTimeout(timer);
|
|
96
|
+
finish({
|
|
97
|
+
stdout: "",
|
|
98
|
+
stderr: "",
|
|
99
|
+
exitCode: null,
|
|
100
|
+
timedOut: false,
|
|
101
|
+
error: err.message,
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
}
|
|
@@ -14,14 +14,6 @@ import type {
|
|
|
14
14
|
/**
|
|
15
15
|
* How a watch retrospective hands its report to the daemon.
|
|
16
16
|
*
|
|
17
|
-
* **The card cannot come from `ui_show`.** A retrospective runs as a
|
|
18
|
-
* `clientless` wake, which pins the turn non-interactive, and
|
|
19
|
-
* `conversation-tool-setup` gates the whole `ui_surface` tool family on a
|
|
20
|
-
* client being present (`return channelCapabilities?.supportsDynamicUi ??
|
|
21
|
-
* !hasNoClient`). So `ui_show` is not merely denied in this turn, it is absent
|
|
22
|
-
* from the tool set, and a retrospective told to call it can only report that
|
|
23
|
-
* it cannot. This tool is an ordinary one and passes that gate untouched.
|
|
24
|
-
*
|
|
25
17
|
* **It records; it does not render.** The executor validates the payload and
|
|
26
18
|
* returns, and nothing here writes to the conversation. What makes the card is
|
|
27
19
|
* `watch-retro.ts` reading this call back out of the turn's own history once
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
baseLanguageSubtag,
|
|
5
|
+
fixedPhraseLanguage,
|
|
6
|
+
localizedOrDefault,
|
|
7
|
+
} from "../language-subtag.js";
|
|
4
8
|
|
|
5
9
|
describe("baseLanguageSubtag", () => {
|
|
6
10
|
test("lowercases a bare tag", () => {
|
|
@@ -52,3 +56,21 @@ describe("localizedOrDefault", () => {
|
|
|
52
56
|
expect(localizedOrDefault(table, "__proto__", "fallback")).toBe("fallback");
|
|
53
57
|
});
|
|
54
58
|
});
|
|
59
|
+
|
|
60
|
+
describe("fixedPhraseLanguage", () => {
|
|
61
|
+
const table = { en: "one", es: "uno" };
|
|
62
|
+
|
|
63
|
+
test("pins English when the speaker's language has no table entry", () => {
|
|
64
|
+
expect(fixedPhraseLanguage(table, "ko")).toBe("en");
|
|
65
|
+
expect(fixedPhraseLanguage(table, "ar-EG")).toBe("en");
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("rides the speaker's language when the table covers it", () => {
|
|
69
|
+
expect(fixedPhraseLanguage(table, "es")).toBeUndefined();
|
|
70
|
+
expect(fixedPhraseLanguage(table, "es-419")).toBeUndefined();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("carries no hint when the language is unknown", () => {
|
|
74
|
+
expect(fixedPhraseLanguage(table, undefined)).toBeUndefined();
|
|
75
|
+
});
|
|
76
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
|
|
3
|
+
export async function writeCombinedCABundle(
|
|
4
|
+
systemBundlePath: string,
|
|
5
|
+
extraCAPath: string,
|
|
6
|
+
outputPath: string,
|
|
7
|
+
): Promise<void> {
|
|
8
|
+
const [systemCAs, extraCAs] = await Promise.all([
|
|
9
|
+
readFile(systemBundlePath, "utf-8"),
|
|
10
|
+
readFile(extraCAPath, "utf-8"),
|
|
11
|
+
]);
|
|
12
|
+
await writeFile(outputPath, `${systemCAs}\n${extraCAs}`, { mode: 0o644 });
|
|
13
|
+
}
|
|
@@ -41,3 +41,19 @@ export function localizedOrDefault<T>(
|
|
|
41
41
|
? (table[baseLanguageSubtag(language)!] as T)
|
|
42
42
|
: fallback;
|
|
43
43
|
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The TTS language hint for a fixed phrase picked from a localized table:
|
|
47
|
+
* "en" when the speaker has a language the table does not cover (the
|
|
48
|
+
* picker fell back to English text, which must not be synthesized under
|
|
49
|
+
* an ar/ko/ta hint), undefined otherwise (the phrase rides the speaker's
|
|
50
|
+
* language, or carries no hint at all when the language is unknown).
|
|
51
|
+
*/
|
|
52
|
+
export function fixedPhraseLanguage(
|
|
53
|
+
table: Readonly<Record<string, unknown>>,
|
|
54
|
+
language: string | undefined,
|
|
55
|
+
): string | undefined {
|
|
56
|
+
return language !== undefined && !hasLocalizedEntry(table, language)
|
|
57
|
+
? "en"
|
|
58
|
+
: undefined;
|
|
59
|
+
}
|
package/src/util/platform.ts
CHANGED
|
@@ -470,6 +470,14 @@ export function getWorkspaceConfigPath(): string {
|
|
|
470
470
|
return join(getWorkspaceDir(), "config.json");
|
|
471
471
|
}
|
|
472
472
|
|
|
473
|
+
/** Filename for the user-owned Agent Plugins MCP document. */
|
|
474
|
+
export const WORKSPACE_MCP_FILENAME = "mcp.json";
|
|
475
|
+
|
|
476
|
+
/** Returns $VELLUM_WORKSPACE_DIR/mcp.json */
|
|
477
|
+
export function getWorkspaceMcpConfigPath(): string {
|
|
478
|
+
return join(getWorkspaceDir(), WORKSPACE_MCP_FILENAME);
|
|
479
|
+
}
|
|
480
|
+
|
|
473
481
|
/** Returns $VELLUM_WORKSPACE_DIR/skills */
|
|
474
482
|
export function getWorkspaceSkillsDir(): string {
|
|
475
483
|
return join(getWorkspaceDir(), "skills");
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NUL-terminated `git status --porcelain -z` parsing.
|
|
3
|
+
*
|
|
4
|
+
* Status reporting and batched staging share this parser so rename/copy
|
|
5
|
+
* origin paths cannot drift between the two readers.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface PorcelainEntry {
|
|
9
|
+
/** Two-character XY status from porcelain v1. */
|
|
10
|
+
status: string;
|
|
11
|
+
/** Path git reports for this record (the destination for rename/copy). */
|
|
12
|
+
path: string;
|
|
13
|
+
/** Pre-rename/copy path. Present when the record is a rename or copy. */
|
|
14
|
+
origin?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Parse NUL-terminated `git status --porcelain -z` output into status/path
|
|
19
|
+
* records. NUL termination is required so paths with special characters
|
|
20
|
+
* (non-ASCII, quotes, newlines) arrive verbatim instead of C-style quoted.
|
|
21
|
+
*
|
|
22
|
+
* A rename/copy record is followed by a bare origin-path entry, captured on
|
|
23
|
+
* {@link PorcelainEntry.origin}.
|
|
24
|
+
*/
|
|
25
|
+
export function parsePorcelainZ(stdout: string): PorcelainEntry[] {
|
|
26
|
+
const entries = stdout.split("\0");
|
|
27
|
+
const parsed: PorcelainEntry[] = [];
|
|
28
|
+
for (let i = 0; i < entries.length; i++) {
|
|
29
|
+
const entry = entries[i] ?? "";
|
|
30
|
+
if (entry.length < 4) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
const status = entry.substring(0, 2);
|
|
34
|
+
const path = entry.substring(3);
|
|
35
|
+
if (status[0] === "R" || status[0] === "C") {
|
|
36
|
+
i++;
|
|
37
|
+
const origin = entries[i];
|
|
38
|
+
if (origin && origin.length > 0) {
|
|
39
|
+
parsed.push({ status, path, origin });
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
parsed.push({ status, path });
|
|
44
|
+
}
|
|
45
|
+
return parsed;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Every path git status knows about, including rename/copy origins.
|
|
50
|
+
* Origins still need to be staged so a deletion is not left behind when a
|
|
51
|
+
* rename is split across add batches.
|
|
52
|
+
*/
|
|
53
|
+
export function collectDirtyPathsFromPorcelain(stdout: string): string[] {
|
|
54
|
+
const paths: string[] = [];
|
|
55
|
+
const seen = new Set<string>();
|
|
56
|
+
for (const entry of parsePorcelainZ(stdout)) {
|
|
57
|
+
for (const path of [entry.path, entry.origin]) {
|
|
58
|
+
if (!path || seen.has(path)) {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
seen.add(path);
|
|
62
|
+
paths.push(path);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return paths;
|
|
66
|
+
}
|
|
@@ -17,6 +17,10 @@ import { getLogger } from "../util/logger.js";
|
|
|
17
17
|
import { Mutex } from "../util/mutex.js";
|
|
18
18
|
import { addToPathEnv, getExtraToolPathDirs } from "../util/platform.js";
|
|
19
19
|
import { PromiseGuard } from "../util/promise-guard.js";
|
|
20
|
+
import {
|
|
21
|
+
collectDirtyPathsFromPorcelain,
|
|
22
|
+
parsePorcelainZ,
|
|
23
|
+
} from "./git-porcelain.js";
|
|
20
24
|
|
|
21
25
|
const execFileAsync = promisify(execFile);
|
|
22
26
|
const log = getLogger("workspace-git");
|
|
@@ -226,59 +230,6 @@ done
|
|
|
226
230
|
exit 0
|
|
227
231
|
`;
|
|
228
232
|
|
|
229
|
-
/**
|
|
230
|
-
* Parse NUL-terminated `git status --porcelain -z` output into status/path
|
|
231
|
-
* pairs. NUL termination is required so paths with special characters
|
|
232
|
-
* (non-ASCII, quotes, newlines) arrive verbatim instead of C-style quoted.
|
|
233
|
-
* A rename/copy record is followed by a bare origin-path entry, which is
|
|
234
|
-
* skipped.
|
|
235
|
-
*/
|
|
236
|
-
function parsePorcelainZ(
|
|
237
|
-
stdout: string,
|
|
238
|
-
): Array<{ status: string; path: string }> {
|
|
239
|
-
const entries = stdout.split("\0");
|
|
240
|
-
const parsed: Array<{ status: string; path: string }> = [];
|
|
241
|
-
for (let i = 0; i < entries.length; i++) {
|
|
242
|
-
const entry = entries[i] ?? "";
|
|
243
|
-
if (entry.length < 4) {
|
|
244
|
-
continue;
|
|
245
|
-
}
|
|
246
|
-
const status = entry.substring(0, 2);
|
|
247
|
-
parsed.push({ status, path: entry.substring(3) });
|
|
248
|
-
if (status[0] === "R" || status[0] === "C") {
|
|
249
|
-
i++;
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
return parsed;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* Every path git status knows about, including rename/copy origins.
|
|
257
|
-
* Origins are skipped by {@link parsePorcelainZ} (they have no status
|
|
258
|
-
* prefix) but still need to be staged so a deletion is not left behind
|
|
259
|
-
* when a rename is split across add batches.
|
|
260
|
-
*/
|
|
261
|
-
function collectDirtyPathsFromPorcelain(stdout: string): string[] {
|
|
262
|
-
const entries = stdout.split("\0");
|
|
263
|
-
const paths: string[] = [];
|
|
264
|
-
for (let i = 0; i < entries.length; i++) {
|
|
265
|
-
const entry = entries[i] ?? "";
|
|
266
|
-
if (entry.length < 4) {
|
|
267
|
-
continue;
|
|
268
|
-
}
|
|
269
|
-
const status = entry.substring(0, 2);
|
|
270
|
-
paths.push(entry.substring(3));
|
|
271
|
-
if (status[0] === "R" || status[0] === "C") {
|
|
272
|
-
i++;
|
|
273
|
-
const origin = entries[i];
|
|
274
|
-
if (origin && origin.length > 0) {
|
|
275
|
-
paths.push(origin);
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
return paths;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
233
|
/** Properties added by Node's child_process errors. */
|
|
283
234
|
interface ExecError extends Error {
|
|
284
235
|
killed?: boolean;
|
|
@@ -433,7 +384,7 @@ export class WorkspaceGitService {
|
|
|
433
384
|
*
|
|
434
385
|
* This method is always called inside the mutex, so no git operation from
|
|
435
386
|
* our code can be concurrently holding the lock. However, an external git
|
|
436
|
-
* process (user running `git add`, IDE tooling, etc.) could legitimately
|
|
387
|
+
* git process (user running `git add`, IDE tooling, etc.) could legitimately
|
|
437
388
|
* hold the lock. We use `lsof` to check — if any process has the file
|
|
438
389
|
* open, we leave it alone. If no process holds it, it's stale (crashed
|
|
439
390
|
* process) and safe to remove.
|