@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
package/src/config/acp-schema.ts
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import { DEFAULT_ACP_AGENT_PROFILES } from "./acp-defaults.js";
|
|
4
|
+
|
|
5
|
+
export const AcpAgentConfigSchema = z
|
|
4
6
|
.object({
|
|
5
|
-
command: z
|
|
7
|
+
command: z
|
|
8
|
+
.string()
|
|
9
|
+
.optional()
|
|
10
|
+
.describe(
|
|
11
|
+
"Command to spawn the ACP agent process. Required unless the id has a bundled profile, whose command is inherited when this is unset",
|
|
12
|
+
),
|
|
6
13
|
args: z
|
|
7
14
|
.array(z.string())
|
|
8
15
|
.default([])
|
|
@@ -15,6 +22,12 @@ const AcpAgentConfigSchema = z
|
|
|
15
22
|
.record(z.string(), z.string())
|
|
16
23
|
.optional()
|
|
17
24
|
.describe("Environment variables set for the agent process"),
|
|
25
|
+
model: z
|
|
26
|
+
.string()
|
|
27
|
+
.optional()
|
|
28
|
+
.describe(
|
|
29
|
+
"Model sessions of this agent start on when the ask names none (an adapter-reported alias such as 'opus' or 'sonnet' for Claude, not an Assistant catalog id). Overrides the bundled profile's value; unset inherits it.",
|
|
30
|
+
),
|
|
18
31
|
})
|
|
19
32
|
.describe("Configuration for an individual ACP agent");
|
|
20
33
|
|
|
@@ -30,6 +43,21 @@ export const AcpConfigSchema = z
|
|
|
30
43
|
),
|
|
31
44
|
agents: z
|
|
32
45
|
.record(z.string(), AcpAgentConfigSchema)
|
|
46
|
+
.superRefine((agents, ctx) => {
|
|
47
|
+
for (const [id, agent] of Object.entries(agents)) {
|
|
48
|
+
if (
|
|
49
|
+
agent.command !== undefined ||
|
|
50
|
+
Object.hasOwn(DEFAULT_ACP_AGENT_PROFILES, id)
|
|
51
|
+
) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
ctx.addIssue({
|
|
55
|
+
code: "custom",
|
|
56
|
+
path: [id, "command"],
|
|
57
|
+
message: `acp.agents.${id} needs a command: only the bundled ids (${Object.keys(DEFAULT_ACP_AGENT_PROFILES).join(", ")}) inherit one`,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
})
|
|
33
61
|
.default({})
|
|
34
62
|
.describe("Map of agent names to their configurations"),
|
|
35
63
|
})
|
|
@@ -23,6 +23,18 @@ Use `acp_spawn` to delegate a coding task to an external agent. The agent runs a
|
|
|
23
23
|
|
|
24
24
|
Users can refer to agents by natural names: "claude code", "codex cli", and "openai codex" all resolve to the canonical `claude` and `codex` ids (unless the user's config defines an agent literally keyed by that name, which always wins).
|
|
25
25
|
|
|
26
|
+
## Choosing a model
|
|
27
|
+
|
|
28
|
+
Claude runs on Opus unless the user names a model; every other agent starts on its own default.
|
|
29
|
+
|
|
30
|
+
When the user does name one, pass it as `model` on `acp_spawn`. Model names are the agent's own vocabulary, not Assistant model ids: an alias such as `default`, `sonnet`, `opus`, `haiku`, `fable`, or `opusplan` for Claude, or a full model id. Pass what the user said and let the agent resolve it.
|
|
31
|
+
|
|
32
|
+
The spawn result reports `requestedModel` and `effectiveModel`. Treat `effectiveModel` as authoritative for the top-level ACP session. Never infer that model from the task text or from a nested subagent. When `requestedModel` is non-null and differs from `effectiveModel`, state which model the session is actually running on and relay `modelWarning` when present.
|
|
33
|
+
|
|
34
|
+
If the agent refuses the model, or advertises no model selector, the spawn result says so: relay it in one sentence and carry on, because the session is live on the agent's own model.
|
|
35
|
+
|
|
36
|
+
A session runs on the model it started on, so a different model means a new `acp_spawn`. A standing default per agent lives in the config at `acp.agents.<id>.model`.
|
|
37
|
+
|
|
26
38
|
## When the user names Claude Code or Codex
|
|
27
39
|
|
|
28
40
|
If they name Claude Code or Codex without asking you to run it here (for example they say that tool will do the work), offer once, in one short sentence, that you can connect and run it in this conversation. Then continue with whatever they were doing.
|
|
@@ -35,21 +47,23 @@ If they name Claude Code or Codex without asking you to run it here (for example
|
|
|
35
47
|
|
|
36
48
|
ACP is always available - default profiles for `claude` and `codex` ship out-of-box, so no config edit is needed to start. First-time setup is just making the adapter binary available, then spawning:
|
|
37
49
|
|
|
38
|
-
1. Install the adapter binary if it's missing. This happens automatically: when `acp_spawn` finds the agent's binary missing from PATH, the assistant installs
|
|
50
|
+
1. Install the adapter binary if it's missing. This happens automatically: when `acp_spawn` finds the agent's binary missing from PATH, the assistant installs the pinned version via a sandboxed bun global install and proceeds in the same call (see "Automatic adapter availability" below).
|
|
39
51
|
|
|
40
52
|
2. Call `acp_spawn`. Do NOT run `vellum sleep && vellum wake` - that kills the conversation.
|
|
41
53
|
|
|
42
54
|
## Automatic adapter availability
|
|
43
55
|
|
|
44
|
-
When
|
|
56
|
+
When an agent's binary is missing from PATH, the assistant installs the adapter version it was built against via a sandboxed bun global install and then runs the real installed binary. The install runs in a fresh empty temporary directory (never the task's project directory), with known secrets stripped from the installer environment and the registry pinned to the public npm registry, so a malicious project directory cannot hijack package resolution or capture a token.
|
|
57
|
+
|
|
58
|
+
An adapter already on PATH is left alone, whoever installed it: the install runs only when preflight found no binary at all.
|
|
45
59
|
|
|
46
60
|
Only the allowlisted out-of-box packages are ever installed this way (`@agentclientprotocol/claude-agent-acp`, `@agentclientprotocol/codex-acp`); user-configured agents with custom commands are never installed automatically.
|
|
47
61
|
|
|
48
|
-
Manual installation is fallback guidance for unusual setups: bun unavailable, restricted global installs, or an auto-install failure (the failure reason is surfaced in the tool result).
|
|
62
|
+
Manual installation is fallback guidance for unusual setups: bun unavailable, restricted global installs, or an auto-install failure (the failure reason is surfaced in the tool result). Install the pinned version below rather than `@latest`, so the adapter matches what the assistant was built against.
|
|
49
63
|
|
|
50
64
|
```bash
|
|
51
|
-
bun add -g @agentclientprotocol/claude-agent-acp # claude
|
|
52
|
-
bun add -g @agentclientprotocol/codex-acp # codex
|
|
65
|
+
bun add -g @agentclientprotocol/claude-agent-acp@0.75.1 # claude
|
|
66
|
+
bun add -g @agentclientprotocol/codex-acp@1.10.0 # codex
|
|
53
67
|
```
|
|
54
68
|
|
|
55
69
|
## Claude setup
|
|
@@ -84,20 +98,14 @@ Do NOT put API keys (or any secret) in the workspace config file - secrets never
|
|
|
84
98
|
|
|
85
99
|
- Two agents are supported out-of-box: `claude` (via the `claude-agent-acp` adapter) and `codex` (via the `codex-acp` adapter).
|
|
86
100
|
- NEVER use `claude`, `claude -p`, `claude --acp`, or the bare `codex` CLI as the ACP `command`. Claude and Codex only speak the protocol through their dedicated `*-acp` adapters.
|
|
87
|
-
- Default profiles for both ship out-of-box. Users only need an `agents.<id>` entry in config if they want to override the defaults (e.g. point to a custom binary path or pass extra args/env). An
|
|
101
|
+
- Default profiles for both ship out-of-box. Users only need an `agents.<id>` entry in config if they want to override the defaults (e.g. point to a custom binary path or pass extra args/env). An entry that still runs the bundled adapter, whether it omits `command` or names the same binary by name or full path, inherits the `command`, `description` and `model` it leaves out, so a single-field change such as `acp.agents.claude.model` is all it takes. An entry that points the id at a different binary stands on its own, so it must spell out everything it needs, `command` included.
|
|
88
102
|
- NEVER change an existing ACP config to use a different command. If the config already has `claude-agent-acp` or `codex-acp`, leave it alone.
|
|
89
103
|
|
|
90
104
|
## Updating an adapter
|
|
91
105
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
```bash
|
|
95
|
-
bun add -g @agentclientprotocol/claude-agent-acp@latest
|
|
96
|
-
# or
|
|
97
|
-
bun add -g @agentclientprotocol/codex-acp@latest
|
|
98
|
-
```
|
|
106
|
+
Adapter upgrades ship with Assistant releases: the pinned version is what a missing adapter is installed at. An adapter already on PATH is never replaced, so a user who upgrades one themselves keeps that version.
|
|
99
107
|
|
|
100
|
-
Codex uses the adapter's bundled dependency by default, within the version range declared by the adapter. If `CODEX_PATH` selects a separate CLI, update that installation separately.
|
|
108
|
+
Codex uses the adapter's bundled dependency by default, within the version range declared by the adapter. If `CODEX_PATH` selects a separate CLI, update that installation separately.
|
|
101
109
|
|
|
102
110
|
## When to use acp_steer vs acp_spawn
|
|
103
111
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"tools": [
|
|
4
4
|
{
|
|
5
5
|
"name": "acp_spawn",
|
|
6
|
-
"description": "Spawn an external coding agent (e.g. Claude Code, Codex) via ACP to work on a task. Default profiles ship for `claude` (`claude-agent-acp`) and `codex` (`codex-acp`); the assistant resolves the agent id to the right binary. The agent runs as a subprocess and streams results back. Use this when you want to delegate a coding task to an external agent that has its own tools, file editing, and terminal access. If a default agent's binary is missing, the assistant installs it once via a sandboxed bun global install and proceeds in the same call; if that fails (e.g. bun unavailable), an actionable install hint is returned - do NOT alter `agents.<id>.command` to swap binaries.",
|
|
6
|
+
"description": "Spawn an external coding agent (e.g. Claude Code, Codex) via ACP to work on a task. Default profiles ship for `claude` (`claude-agent-acp`) and `codex` (`codex-acp`); the assistant resolves the agent id to the right binary. The result reports `requestedModel` and the adapter-reported `effectiveModel`; treat `effectiveModel` as authoritative for the top-level ACP session. The agent runs as a subprocess and streams results back. Use this when you want to delegate a coding task to an external agent that has its own tools, file editing, and terminal access. If a default agent's binary is missing, the assistant installs it once via a sandboxed bun global install and proceeds in the same call; if that fails (e.g. bun unavailable), an actionable install hint is returned - do NOT alter `agents.<id>.command` to swap binaries.",
|
|
7
7
|
"category": "orchestration",
|
|
8
8
|
"risk": "high",
|
|
9
9
|
"input_schema": {
|
|
@@ -20,6 +20,10 @@
|
|
|
20
20
|
"cwd": {
|
|
21
21
|
"type": "string",
|
|
22
22
|
"description": "Working directory for the agent. This determines where the agent runs and where its session is stored. Set this to the project/repo root the user wants the agent to work in. Defaults to current conversation's working directory."
|
|
23
|
+
},
|
|
24
|
+
"model": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"description": "Model the agent should run on, passed ONLY when the user named one (e.g. 'opus' for Claude). Left off, a Claude session runs on Opus and every other agent on its own default. If the agent refuses the value, or advertises no model selector, the result says so and the session runs on its own model."
|
|
23
27
|
}
|
|
24
28
|
},
|
|
25
29
|
"required": ["task"]
|
|
@@ -60,7 +60,7 @@ Apps live under `/workspace/data/apps/`:
|
|
|
60
60
|
<slug>.json # App metadata
|
|
61
61
|
<slug>/
|
|
62
62
|
src/ # Source files (TSX) — what you write
|
|
63
|
-
dist/ # Compiled output
|
|
63
|
+
dist/ # Compiled output, generated by app_refresh or assistant apps refresh
|
|
64
64
|
records/ # Data records (one JSON file per record)
|
|
65
65
|
<slug>.preview # Preview image (auto-generated)
|
|
66
66
|
```
|
|
@@ -156,9 +156,9 @@ Calculators, single-page tools, landing pages, and slide decks skip this — pas
|
|
|
156
156
|
|
|
157
157
|
### 3 — Create the app (scaffold, then expand)
|
|
158
158
|
|
|
159
|
-
⚠️ **`app_create` is ONE-SHOT per build.** Call it exactly once. After it returns an `app_id`, all further changes go through `file_write` / `file_edit`
|
|
159
|
+
⚠️ **`app_create` is ONE-SHOT per build.** Call it exactly once. After it returns an `app_id`, all further changes go through `file_write` / `file_edit` and a single compile (`assistant apps refresh` or `app_refresh`). To start over: `app_delete(app_id)` first, then a fresh `app_create`.
|
|
160
160
|
|
|
161
|
-
Apps are multi-file Preact + TSX projects; esbuild bundles
|
|
161
|
+
Apps are multi-file Preact + TSX projects; esbuild bundles when you compile. Structure:
|
|
162
162
|
|
|
163
163
|
```
|
|
164
164
|
src/
|
|
@@ -179,9 +179,9 @@ render(<App />, document.getElementById("app")!);
|
|
|
179
179
|
|
|
180
180
|
1. **`app_create`** with a **4-file scaffold**: `src/index.html`, `src/main.tsx`, a **placeholder** `src/components/App.tsx` (`<div>Loading...</div>`), and an **empty** `src/styles.css`. The placeholders make the first compile clean — a 2-file scaffold leaves broken imports.
|
|
181
181
|
2. **`file_write`** each real file, one per tool call, overwriting the placeholders and adding components.
|
|
182
|
-
3.
|
|
182
|
+
3. Compile ONCE at the end. File edits do not compile on their own. Use `assistant apps inspect <name>` to see whether source drifted, then `assistant apps refresh <name>` (or `app_refresh`).
|
|
183
183
|
|
|
184
|
-
**Actually write the files.** Source code in reasoning/thinking is invisible to the app sandbox and is not a file write. Put every real file body in an `app_create.source_files`, `file_write`, `file_edit`, or `app_update.source_files` tool call, then verify the files exist before
|
|
184
|
+
**Actually write the files.** Source code in reasoning/thinking is invisible to the app sandbox and is not a file write. Put every real file body in an `app_create.source_files`, `file_write`, `file_edit`, or `app_update.source_files` tool call, then verify the files exist before compiling when there is any doubt.
|
|
185
185
|
|
|
186
186
|
**Allowed packages** (esbuild-resolved, no CDN): `date-fns`, `chart.js`, `lodash-es`, `zod`, `clsx`. For icons, write inline `<svg>` markup directly — no icon package is bundled.
|
|
187
187
|
|
|
@@ -221,11 +221,14 @@ app_create({ app_create({
|
|
|
221
221
|
|
|
222
222
|
### 4 — Compile
|
|
223
223
|
|
|
224
|
+
File edits do not compile on their own. After ALL file writes, compile once:
|
|
225
|
+
|
|
224
226
|
```
|
|
225
|
-
|
|
227
|
+
assistant apps inspect <name>
|
|
228
|
+
assistant apps refresh <name>
|
|
226
229
|
```
|
|
227
230
|
|
|
228
|
-
|
|
231
|
+
`assistant apps inspect` reports whether source changed since the last compile. `assistant apps refresh` compiles and refreshes open surfaces. `app_refresh(app_id)` also compiles. If compile fails, the response has error details; fix with `file_edit`, then compile again.
|
|
229
232
|
|
|
230
233
|
### 5 — Show the preview card
|
|
231
234
|
|
|
@@ -244,7 +247,7 @@ Editing an existing app means reusing its `app_id` — never `app_create`. Resol
|
|
|
244
247
|
- **`app_update`** — rename or change description/schema, and/or rewrite files via `source_files`, in one call. It recompiles for you, so no separate `app_refresh` is needed. Use this instead of editing `<slug>.json` by hand.
|
|
245
248
|
- **Full rebrand** — still iteration, edit the existing files.
|
|
246
249
|
|
|
247
|
-
Then `app_refresh(app_id)`
|
|
250
|
+
Then compile ONCE (`assistant apps refresh <name>` or `app_refresh(app_id)`) unless you used `app_update`, which already recompiled. If the change is substantial, `app_open(app_id, open_mode: "preview")` for a fresh card; for small tweaks the existing card stays valid.
|
|
248
251
|
|
|
249
252
|
> ⚠️ **`skill_load("app-builder")` is required before every `app_*` call** (including the first `app_create`). The skill can auto-unload between turns; without the reload, `app_refresh` / `app_open` error with "not currently active." It's idempotent — call it every time.
|
|
250
253
|
|
|
@@ -350,7 +353,7 @@ Slide decks are a different domain — skip app patterns (contextual headers, se
|
|
|
350
353
|
## SKILL COMPLETE WHEN
|
|
351
354
|
|
|
352
355
|
- [ ] Request was scoped: personal build (sandbox) or complex/shippable (handed off to a project folder + coding agent)
|
|
353
|
-
- [ ] **Sandbox path:** `app_create` returned an `app_id`; all files written via `file_write`;
|
|
356
|
+
- [ ] **Sandbox path:** `app_create` returned an `app_id`; all files written via `file_write`; compile ran ONCE clean (`assistant apps refresh` or `app_refresh`); `app_open(open_mode: "preview")` rendered the card; user told what was built (3-6 bullets); iterations reflected live
|
|
354
357
|
- [ ] **Handoff path:** project folder established; coding agent spawned via `acp_spawn({ task, cwd })`; user told work continues in the folder
|
|
355
358
|
|
|
356
359
|
---
|
|
@@ -143,7 +143,7 @@ Older routes were written with a second `context` argument (`context.assistantEv
|
|
|
143
143
|
|
|
144
144
|
## Key rules
|
|
145
145
|
|
|
146
|
-
- Always create the route handler files via `file_write` before
|
|
146
|
+
- Always create the route handler files via `file_write` before compiling (`app_refresh` or `assistant apps refresh`)
|
|
147
147
|
- Export an optional `description` string for CLI discoverability (`assistant routes list`)
|
|
148
148
|
- Handlers have full Node.js API access — `fs`, `path`, `crypto`, etc.
|
|
149
149
|
- Handlers get a 30-second timeout per request
|
|
@@ -14,7 +14,7 @@ metadata:
|
|
|
14
14
|
|
|
15
15
|
You are a unified messaging assistant with access to multiple platforms (Gmail, Outlook, Telegram, and more). Use the messaging tools to help users read, search, organize, draft, and send messages across all connected platforms.
|
|
16
16
|
|
|
17
|
-
**
|
|
17
|
+
**On a channel, `messaging_send` posts through the channel's own transport.** Slack, Telegram, Discord, and WhatsApp all go out that way: threaded where you name a `thread_id`, rendered the way a reply is, and recorded in the chat's conversation once the channel acknowledges the post. Reading and searching Slack are not part of this skill; load the **slack** skill, which uses the Slack Web API for those.
|
|
18
18
|
|
|
19
19
|
## External Identity
|
|
20
20
|
|
|
@@ -34,7 +34,7 @@ When a platform is connected (auth test succeeds), always use the messaging API
|
|
|
34
34
|
|
|
35
35
|
On Gmail and Outlook, `messaging_send` creates a real mailbox draft for review. It does not send. Recipients are optional on Outlook: if the user has not named a To address, still create the draft (use a non-email `conversation_id` such as `drafts`) and tell them only after the tool returns a draft ID. If draft creation fails or is interrupted, say so and offer the email copy in chat. Do not claim a draft exists until the tool succeeds.
|
|
36
36
|
|
|
37
|
-
**
|
|
37
|
+
**On a channel, the send goes out as the assistant's own bot**, so `account` does not apply and is refused. It carries text only: attachments stay on Gmail and Outlook. The post is recorded in the chat's conversation, which is what lets `recall` find it later.
|
|
38
38
|
|
|
39
39
|
## Connection Setup
|
|
40
40
|
|
|
@@ -64,7 +64,7 @@ When the user asks to "connect my email", "set up email", "manage my email", or
|
|
|
64
64
|
|
|
65
65
|
### Slack
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
For Slack setup, load the **slack-app-setup** skill directly. `messaging_send` posts to a Slack chat through the channel's transport. Reading, searching, reactions, and file upload live in the **slack** skill, which uses the Slack Web API.
|
|
68
68
|
|
|
69
69
|
### Telegram
|
|
70
70
|
|
|
@@ -125,7 +125,7 @@
|
|
|
125
125
|
},
|
|
126
126
|
{
|
|
127
127
|
"name": "messaging_send",
|
|
128
|
-
"description": "Send a message, reply to a thread, or create a draft. On Gmail and Outlook, always creates a real mailbox draft for review (recipients optional on Outlook). Supports replies (via thread_id)
|
|
128
|
+
"description": "Send a message, reply to a thread, or create a draft. On Gmail and Outlook, always creates a real mailbox draft for review (recipients optional on Outlook). On a channel (Slack, Telegram, Discord, WhatsApp) the message goes out as the channel's own bot, threaded when thread_id names a thread. Supports replies (via thread_id) and attachments (via attachment_paths, Gmail and Outlook).",
|
|
129
129
|
"category": "messaging",
|
|
130
130
|
"risk": "high",
|
|
131
131
|
"input_schema": {
|
|
@@ -133,11 +133,11 @@
|
|
|
133
133
|
"properties": {
|
|
134
134
|
"platform": {
|
|
135
135
|
"type": "string",
|
|
136
|
-
"description": "Platform (e.g. \"gmail\"). Auto-detected if only one is connected.
|
|
136
|
+
"description": "Platform (e.g. \"gmail\", \"slack\", \"telegram\", \"discord\"). Auto-detected if only one is connected."
|
|
137
137
|
},
|
|
138
138
|
"account": {
|
|
139
139
|
"type": "string",
|
|
140
|
-
"description": "Email address of the account to use. Required when multiple accounts are connected for the same platform. If omitted, uses the most recently connected account."
|
|
140
|
+
"description": "Email address of the account to use. Required when multiple accounts are connected for the same email platform. If omitted, uses the most recently connected account. Does not apply to a channel: a channel send goes out as the channel's own bot."
|
|
141
141
|
},
|
|
142
142
|
"conversation_id": {
|
|
143
143
|
"type": "string",
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { basename } from "node:path";
|
|
3
3
|
|
|
4
|
-
import { isChannelId } from "../../../../channels/types.js";
|
|
5
4
|
import type { OutboundAttachment } from "../../../../messaging/provider-types.js";
|
|
6
5
|
import {
|
|
7
6
|
createDraft,
|
|
@@ -16,11 +15,10 @@ import {
|
|
|
16
15
|
toOutlookFileAttachments,
|
|
17
16
|
} from "../../../../messaging/providers/outlook/client.js";
|
|
18
17
|
import type { OutlookDraftMessage } from "../../../../messaging/providers/outlook/types.js";
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
import { normalizeExternalThreadId } from "../../../../persistence/external-conversation-store.js";
|
|
18
|
+
import {
|
|
19
|
+
isProactivelyAddressable,
|
|
20
|
+
sendChannelText,
|
|
21
|
+
} from "../../../../runtime/channel-send.js";
|
|
24
22
|
import {
|
|
25
23
|
isAbortLikeError,
|
|
26
24
|
throwIfCancelled,
|
|
@@ -29,7 +27,6 @@ import type {
|
|
|
29
27
|
ToolContext,
|
|
30
28
|
ToolExecutionResult,
|
|
31
29
|
} from "../../../../tools/types.js";
|
|
32
|
-
import { getLogger } from "../../../../util/logger.js";
|
|
33
30
|
import { guessMimeType } from "../../../../util/mime-type.js";
|
|
34
31
|
import {
|
|
35
32
|
err,
|
|
@@ -42,8 +39,6 @@ import {
|
|
|
42
39
|
resolveProvider,
|
|
43
40
|
} from "./shared.js";
|
|
44
41
|
|
|
45
|
-
const log = getLogger("messaging-send");
|
|
46
|
-
|
|
47
42
|
/** Read attachment files from disk into in-memory parts for outbound sending. */
|
|
48
43
|
async function readAttachments(paths: string[]): Promise<OutboundAttachment[]> {
|
|
49
44
|
return Promise.all(
|
|
@@ -58,123 +53,6 @@ async function readAttachments(paths: string[]): Promise<OutboundAttachment[]> {
|
|
|
58
53
|
/** Email providers that accept file attachments on outbound sends. */
|
|
59
54
|
const ATTACHMENT_CAPABLE_PLATFORMS = new Set(["gmail", "outlook"]);
|
|
60
55
|
|
|
61
|
-
/**
|
|
62
|
-
* Record a message the provider just accepted where the chat's proactive
|
|
63
|
-
* posts live, so the chat's own conversation and `recall` can see what was
|
|
64
|
-
* sent from elsewhere (a scheduled run, another conversation).
|
|
65
|
-
*
|
|
66
|
-
* Only channel providers have such a home; an email send is tool-mediated
|
|
67
|
-
* and has no chat conversation. The row is written after the provider
|
|
68
|
-
* returned the message id, never before, and carries that id on its
|
|
69
|
-
* envelope and in `channel_outbound_posts` like every other post the daemon
|
|
70
|
-
* makes.
|
|
71
|
-
*
|
|
72
|
-
* A send into the sending turn's own chat writes no row: its tool call and
|
|
73
|
-
* result already sit in that conversation's history, and a second assistant
|
|
74
|
-
* row beside the tool pair would break history repair. Whether the send is
|
|
75
|
-
* "its own" is decided by the delivered target against the turn, not by the
|
|
76
|
-
* home: the turn arrived on a channel, in a chat, in a thread (the turn-local
|
|
77
|
-
* snapshot on the tool context), and a post the provider delivered to exactly
|
|
78
|
-
* that place is a same-conversation send even when the chat's home resolves
|
|
79
|
-
* elsewhere. The delivered thread is the one the provider reports, not the
|
|
80
|
-
* one requested: a provider that ignores the request lands the post in the
|
|
81
|
-
* thread-less chat, and the record must say so; a post into a thread is
|
|
82
|
-
* recorded in the thread's own conversation, where its replies arrive. The home
|
|
83
|
-
* comparison stays as the second test, for a sender that arrived through no
|
|
84
|
-
* channel but is the home. The post is then in the outbound index only
|
|
85
|
-
* through no path, which is the same class as a raw API send and is deferred
|
|
86
|
-
* with it.
|
|
87
|
-
*
|
|
88
|
-
* Failures here never fail the send: the message is already out.
|
|
89
|
-
*/
|
|
90
|
-
async function recordSentChannelPost(params: {
|
|
91
|
-
providerId: string;
|
|
92
|
-
externalChatId: string;
|
|
93
|
-
/** The thread the provider delivered into, as it reported it. */
|
|
94
|
-
deliveredThreadId: string | undefined;
|
|
95
|
-
text: string;
|
|
96
|
-
providerMessageId: string;
|
|
97
|
-
sender: ToolContext;
|
|
98
|
-
}): Promise<void> {
|
|
99
|
-
const { providerId, externalChatId, sender } = params;
|
|
100
|
-
if (!isChannelId(providerId) || !params.providerMessageId) {
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
try {
|
|
104
|
-
if (
|
|
105
|
-
isSendIntoOwnChat(
|
|
106
|
-
sender,
|
|
107
|
-
providerId,
|
|
108
|
-
externalChatId,
|
|
109
|
-
params.deliveredThreadId,
|
|
110
|
-
)
|
|
111
|
-
) {
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
const home = await resolveProactiveHomeConversation({
|
|
115
|
-
sourceChannel: providerId,
|
|
116
|
-
externalChatId,
|
|
117
|
-
threadId: params.deliveredThreadId,
|
|
118
|
-
source: "notification",
|
|
119
|
-
conversationType: "background",
|
|
120
|
-
title: `Messages to ${externalChatId}`,
|
|
121
|
-
});
|
|
122
|
-
if (home.conversationId === sender.conversationId) {
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
const recorded = await recordDeliveredChannelPost({
|
|
126
|
-
conversationId: home.conversationId,
|
|
127
|
-
channel: providerId,
|
|
128
|
-
externalChatId,
|
|
129
|
-
...(params.deliveredThreadId
|
|
130
|
-
? { threadId: params.deliveredThreadId }
|
|
131
|
-
: {}),
|
|
132
|
-
text: params.text,
|
|
133
|
-
providerMessageId: params.providerMessageId,
|
|
134
|
-
crossPostedFrom: sender.conversationId,
|
|
135
|
-
});
|
|
136
|
-
const homeConversation = getConversation(home.conversationId);
|
|
137
|
-
if (homeConversation) {
|
|
138
|
-
syncMessageToDisk(
|
|
139
|
-
home.conversationId,
|
|
140
|
-
recorded.messageId,
|
|
141
|
-
homeConversation.createdAt,
|
|
142
|
-
);
|
|
143
|
-
}
|
|
144
|
-
} catch (e) {
|
|
145
|
-
log.warn(
|
|
146
|
-
{ err: e, provider: providerId, externalChatId },
|
|
147
|
-
"Failed to record the sent message in the chat's conversation",
|
|
148
|
-
);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* True when the delivered chat and thread are the ones the sending turn
|
|
154
|
-
* arrived in. Reads the turn-local snapshot the tool context carries
|
|
155
|
-
* (`executionChannel`, `requesterChatId`, `sourceThreadId`), never the live
|
|
156
|
-
* binding, which a concurrent inbound can rewrite while this call runs.
|
|
157
|
-
* Thread ids compare in the binding store's normalized form, so an absent
|
|
158
|
-
* thread on both sides matches and a thread-less delivery never matches a
|
|
159
|
-
* turn that arrived in a thread.
|
|
160
|
-
*/
|
|
161
|
-
function isSendIntoOwnChat(
|
|
162
|
-
sender: ToolContext,
|
|
163
|
-
channel: string,
|
|
164
|
-
externalChatId: string,
|
|
165
|
-
deliveredThreadId: string | undefined,
|
|
166
|
-
): boolean {
|
|
167
|
-
if (!sender.executionChannel || !sender.requesterChatId) {
|
|
168
|
-
return false;
|
|
169
|
-
}
|
|
170
|
-
return (
|
|
171
|
-
sender.executionChannel === channel &&
|
|
172
|
-
sender.requesterChatId === externalChatId &&
|
|
173
|
-
normalizeExternalThreadId(sender.sourceThreadId) ===
|
|
174
|
-
normalizeExternalThreadId(deliveredThreadId)
|
|
175
|
-
);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
56
|
export async function run(
|
|
179
57
|
input: Record<string, unknown>,
|
|
180
58
|
context: ToolContext,
|
|
@@ -197,9 +75,62 @@ export async function run(
|
|
|
197
75
|
throwIfCancelled(context);
|
|
198
76
|
|
|
199
77
|
try {
|
|
200
|
-
|
|
78
|
+
// A channel whose transport can be addressed from a named chat is sent
|
|
79
|
+
// through the transport, which is the one send implementation for that
|
|
80
|
+
// channel, records what it sent, and reaches channels with no messaging
|
|
81
|
+
// provider at all. A platform named outright is checked first; one
|
|
82
|
+
// auto-detected from the connected providers is checked the same way.
|
|
83
|
+
const namedChannel =
|
|
84
|
+
platform && isProactivelyAddressable(platform) ? platform : undefined;
|
|
85
|
+
const provider = namedChannel ? undefined : await resolveProvider(platform);
|
|
86
|
+
const channel =
|
|
87
|
+
namedChannel ??
|
|
88
|
+
(provider && isProactivelyAddressable(provider.id)
|
|
89
|
+
? provider.id
|
|
90
|
+
: undefined);
|
|
91
|
+
|
|
92
|
+
if (channel) {
|
|
93
|
+
if (attachmentPaths?.length) {
|
|
94
|
+
return err("Attachments are only supported on Gmail and Outlook.");
|
|
95
|
+
}
|
|
96
|
+
// A channel's transport sends as the channel's one bot identity, so an
|
|
97
|
+
// account cannot select where the send goes out from. Refusing is
|
|
98
|
+
// safer than silently sending from the default account.
|
|
99
|
+
if (input.account) {
|
|
100
|
+
return err(
|
|
101
|
+
`account does not apply to ${channel}: a channel send goes out as the channel's own bot.`,
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
// Recheck: provider resolution above is an await, and this posts to
|
|
105
|
+
// the channel.
|
|
106
|
+
throwIfCancelled(context);
|
|
107
|
+
const sent = await sendChannelText({
|
|
108
|
+
channel,
|
|
109
|
+
target: {
|
|
110
|
+
kind: "chat",
|
|
111
|
+
chatId: conversationId,
|
|
112
|
+
...(threadId ? { threadId } : {}),
|
|
113
|
+
},
|
|
114
|
+
text,
|
|
115
|
+
renderRichly: true,
|
|
116
|
+
assistantId: context.assistantId,
|
|
117
|
+
sender: {
|
|
118
|
+
conversationId: context.conversationId,
|
|
119
|
+
executionChannel: context.executionChannel,
|
|
120
|
+
requesterChatId: context.requesterChatId,
|
|
121
|
+
sourceThreadId: context.sourceThreadId,
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
const threadSuffix = sent.threadId
|
|
125
|
+
? `, "thread_id": "${sent.threadId}"`
|
|
126
|
+
: "";
|
|
127
|
+
return ok(`Message sent (ID: ${sent.lastMessageId}${threadSuffix}).`);
|
|
128
|
+
}
|
|
129
|
+
if (!provider) {
|
|
130
|
+
throw new Error(`Messaging provider "${platform}" not found.`);
|
|
131
|
+
}
|
|
201
132
|
|
|
202
|
-
// Reject attachments on platforms that can't carry them
|
|
133
|
+
// Reject attachments on platforms that can't carry them.
|
|
203
134
|
if (
|
|
204
135
|
attachmentPaths?.length &&
|
|
205
136
|
!ATTACHMENT_CAPABLE_PLATFORMS.has(provider.id)
|
|
@@ -426,7 +357,14 @@ export async function run(
|
|
|
426
357
|
);
|
|
427
358
|
}
|
|
428
359
|
|
|
429
|
-
//
|
|
360
|
+
// A provider with a send of its own and no direct transport (a plugin's,
|
|
361
|
+
// for instance). Its send is tool-mediated and has no chat conversation
|
|
362
|
+
// to record in.
|
|
363
|
+
if (!provider.sendMessage) {
|
|
364
|
+
return err(
|
|
365
|
+
`${provider.displayName} cannot send from here: it has no send of its own and no channel transport to address.`,
|
|
366
|
+
);
|
|
367
|
+
}
|
|
430
368
|
const attachments = attachmentPaths?.length
|
|
431
369
|
? await readAttachments(attachmentPaths)
|
|
432
370
|
: undefined;
|
|
@@ -442,16 +380,6 @@ export async function run(
|
|
|
442
380
|
const threadSuffix = result.threadId
|
|
443
381
|
? `, "thread_id": "${result.threadId}"`
|
|
444
382
|
: "";
|
|
445
|
-
|
|
446
|
-
await recordSentChannelPost({
|
|
447
|
-
providerId: provider.id,
|
|
448
|
-
externalChatId: conversationId,
|
|
449
|
-
deliveredThreadId: result.threadId,
|
|
450
|
-
text,
|
|
451
|
-
providerMessageId: result.id,
|
|
452
|
-
sender: context,
|
|
453
|
-
});
|
|
454
|
-
|
|
455
383
|
return ok(`Message sent (ID: ${result.id}${threadSuffix}).`);
|
|
456
384
|
} catch (e) {
|
|
457
385
|
// A cancelled turn is not a send failure: let it reach the executor's
|
|
@@ -23,7 +23,7 @@ sqlite3 "$VELLUM_WORKSPACE_DIR/data/db/assistant.db" \
|
|
|
23
23
|
|
|
24
24
|
This returns all messages in chronological order with:
|
|
25
25
|
|
|
26
|
-
- `role: "user"` - caller speech
|
|
26
|
+
- `role: "user"` - caller speech and system events
|
|
27
27
|
- `role: "assistant"` - assistant responses, including `text` content and any `tool_use`/`tool_result` blocks
|
|
28
28
|
|
|
29
29
|
## Quick one-liner for the most recent call
|
|
@@ -209,7 +209,6 @@ When you mention a schedule or a conversation it owns in Vellum chat, write a ma
|
|
|
209
209
|
|
|
210
210
|
Use these paths only in Vellum chat. Do not use them in Slack, Telegram, Discord, email, or notifications. Those surfaces cannot navigate `/assistant/...` routes.
|
|
211
211
|
|
|
212
|
-
|
|
213
212
|
### Anchored & Ambiguous Relative Time
|
|
214
213
|
|
|
215
214
|
Phrases like "at the 45 minute mark", "at the top of the hour", "at noon", or "20 minutes in" are **clock-position or anchored relative time** expressions. Do NOT treat them as offsets from now.
|
|
@@ -256,13 +255,13 @@ Write the delivery step into the `message` when you create the schedule — not
|
|
|
256
255
|
|
|
257
256
|
A schedule whose message has no delivery step is not finished. Before calling `schedule_create` in `execute` mode, read your own message back and check that it says where the output goes.
|
|
258
257
|
|
|
259
|
-
There is a safety net, and it is not a substitute for the above. When an execute-mode run finishes with user-facing output and delivered
|
|
258
|
+
There is a safety net, and it is not a substitute for the above. When an execute-mode run finishes with user-facing output and nothing delivered it, the assistant sends a notification carrying the run's final reply, so a schedule can no longer run and leave no trace. `messaging_send` counts as a delivery only when the call reported success, and `assistant notifications send` once the notification pipeline has taken it: a send the channel refused, or one that never reported at all, leaves the run eligible for the net, so the failure reaches you instead of vanishing into a conversation nobody has open. A Slack `chat.postMessage` never counts, because Slack refuses a call with a successful HTTP status and `ok: false` in the body, so the command succeeding proves nothing; a run that posts Block Kit that way also gets the net's notification. It fires on the raw reply, whatever the run happened to end on, at whatever length. An authored delivery step gets a title and body you chose, sent at the moment you chose. Rely on the net and you get the machine's guess instead.
|
|
260
259
|
|
|
261
260
|
Choose the right delivery tool based on the content:
|
|
262
261
|
|
|
263
|
-
- **Rich content** (digests, summaries, reports):
|
|
262
|
+
- **Rich content** (digests, summaries, reports): Use `messaging_send` with the target platform and conversation ID. It reaches Gmail or Outlook as a draft, and posts to a Slack, Telegram, or Discord chat through that channel's own transport, where the post is recorded and can be found again afterwards. Reach for `chat.postMessage` through `assistant oauth request` only for a Block Kit shape `messaging_send` cannot carry, with the provider the **slack** skill says to pass for posting on this workspace (`slack_channel` when the bot is set up; where only the `slack` integration exists, the skill says to tell the user before posting as them), never through `curl` or a revealed token. Either way the full content posts directly.
|
|
264
263
|
- **Short alerts** (status updates, completion notices): Use `assistant notifications send` via `bash` to let the notification router pick the best channel. Note: the router's decision engine rewrites content into short alerts, so it is not suitable for rich content.
|
|
265
264
|
|
|
266
265
|
Example schedule message for a Slack digest:
|
|
267
266
|
|
|
268
|
-
> "Scan my Slack channels for the last 24 hours using `assistant oauth request` for every Slack Web API
|
|
267
|
+
> "Scan my Slack channels for the last 24 hours using `assistant oauth request` for every Slack Web API read, with the provider the slack skill names for this workspace, then post the summary to the channel the user named with `messaging_send`, passing `platform: \"slack\"` and that channel's ID."
|
|
@@ -124,7 +124,14 @@ their ear. Silence is the cue that it is their turn.
|
|
|
124
124
|
|
|
125
125
|
**Advance on evidence, not on time.** Move to the next step when a fresh
|
|
126
126
|
picture shows this one done, or when they tell you it is. Do not move on
|
|
127
|
-
because a plausible amount of time has passed.
|
|
127
|
+
because a plausible amount of time has passed.
|
|
128
|
+
|
|
129
|
+
The most direct telling is automatic. When the user clicks the control an
|
|
130
|
+
arrow is pointing at, a message arrives as their turn saying they clicked it,
|
|
131
|
+
by name, and that mark comes down on its own. Treat it as the step done: say
|
|
132
|
+
what comes next and point at it. Nothing arrives for a click anywhere else,
|
|
133
|
+
and nothing arrives for a ring, so for those the picture and their words are
|
|
134
|
+
still the evidence. If the next picture shows the
|
|
128
135
|
step not done, or done to the wrong thing, point at the same place again with
|
|
129
136
|
a shorter caption and say what you saw. Pointing at the next step while the
|
|
130
137
|
previous one is still open is how someone ends up two steps behind a mark.
|
|
@@ -53,7 +53,10 @@ describe("screen_point_at", () => {
|
|
|
53
53
|
);
|
|
54
54
|
|
|
55
55
|
expect(result.isError).toBe(true);
|
|
56
|
-
expect(result.content).toContain(
|
|
56
|
+
expect(result.content).toContain(
|
|
57
|
+
"The Vellum desktop app is needed to view or control your screen",
|
|
58
|
+
);
|
|
59
|
+
expect(result.content).toContain("https://www.vellum.ai/downloads");
|
|
57
60
|
});
|
|
58
61
|
|
|
59
62
|
/** Drawing on the user's screen is a side effect a stopped turn must not have. */
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"tools": [
|
|
4
4
|
{
|
|
5
5
|
"name": "scaffold_managed_skill",
|
|
6
|
-
"description": "Create or update a managed skill in {workspaceDir}/skills. The skill becomes available for skill_load when a valid top-level SKILL.md is written under the skill directory. Never persist a skill without explicit user consent. Before persisting, test the snippet: write to a temp file with bash and run with `bun run /tmp/vellum-eval/snippet.ts`. Iterate up to 3 attempts, then ask the user. Clean up temp files after. Do not use file_write for temp files outside the working directory.
|
|
6
|
+
"description": "Create or update a managed skill in {workspaceDir}/skills. The skill becomes available for skill_load when a valid top-level SKILL.md is written under the skill directory. Never persist a skill without explicit user consent. Before persisting, test the snippet: write to a temp file with bash and run with `bun run /tmp/vellum-eval/snippet.ts`. Iterate up to 3 attempts, then ask the user. Clean up temp files after. Do not use file_write for temp files outside the working directory.",
|
|
7
7
|
"category": "skills",
|
|
8
8
|
"risk": "high",
|
|
9
9
|
"input_schema": {
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
},
|
|
93
93
|
{
|
|
94
94
|
"name": "delete_managed_skill",
|
|
95
|
-
"description": "Delete a managed skill directory from {workspaceDir}/skills. Never delete a skill without explicit user confirmation.
|
|
95
|
+
"description": "Delete a managed skill directory from {workspaceDir}/skills. Never delete a skill without explicit user confirmation.",
|
|
96
96
|
"category": "skills",
|
|
97
97
|
"risk": "high",
|
|
98
98
|
"input_schema": {
|