@vellumai/assistant 0.12.1-staging.1 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +0 -4
- package/ARCHITECTURE.md +12 -11
- package/docs/architecture/turn-actor.md +8 -15
- package/openapi.yaml +1 -291
- package/package.json +1 -1
- package/src/__tests__/always-loaded-tools-guard.test.ts +10 -18
- package/src/__tests__/app-compiler.test.ts +0 -6
- package/src/__tests__/app-control-tool-schemas.test.ts +2 -5
- package/src/__tests__/app-dir-path-guard.test.ts +3 -1
- package/src/__tests__/app-source-watcher.test.ts +218 -0
- package/src/{calls/__tests__ → __tests__}/call-controller.test.ts +82 -503
- package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +2 -5
- package/src/__tests__/computer-use-tools.test.ts +5 -17
- package/src/__tests__/config-schema-cmd.test.ts +2 -2
- package/src/__tests__/config-schema.test.ts +29 -28
- package/src/__tests__/config-watcher-skill-reseed.test.ts +359 -0
- package/src/__tests__/config-watcher.test.ts +8 -23
- package/src/__tests__/connection-model-compat.test.ts +2 -2
- package/src/__tests__/conversation-key-store-disk-view.test.ts +1 -1
- package/src/__tests__/conversation-pairing.test.ts +0 -26
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +10 -4
- package/src/__tests__/conversation-queue.test.ts +207 -48
- package/src/__tests__/conversation-retry-discard.test.ts +1 -54
- package/src/__tests__/conversation-skill-tools.test.ts +0 -27
- package/src/__tests__/conversation-surfaces-app-control.test.ts +3 -8
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +0 -67
- package/src/__tests__/conversation-title-service.test.ts +10 -14
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +5 -3
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +0 -51
- package/src/__tests__/credential-record-retire.test.ts +1 -171
- package/src/__tests__/credential-routes.test.ts +3 -212
- package/src/__tests__/credential-security-invariants.test.ts +1 -2
- package/src/__tests__/cu-unified-flow.test.ts +2 -8
- package/src/__tests__/drain-kick-guard.test.ts +2 -0
- package/src/__tests__/drain-requeue-on-contention.test.ts +6 -0
- package/src/__tests__/evict-conversations-for-reload.test.ts +1 -1
- package/src/__tests__/host-bash-proxy.test.ts +45 -26
- package/src/__tests__/host-browser-proxy.test.ts +17 -6
- package/src/__tests__/host-file-proxy-targeted.test.ts +0 -23
- package/src/__tests__/host-file-proxy.test.ts +23 -11
- package/src/__tests__/host-transfer-proxy-targeted.test.ts +9 -30
- package/src/__tests__/llm-catalog-parity.test.ts +0 -25
- package/src/__tests__/mcp-auth-routes.test.ts +9 -6
- package/src/__tests__/mcp-config-secret-boundary.test.ts +136 -78
- package/src/__tests__/mcp-health-check.test.ts +11 -8
- package/src/__tests__/mcp-list-plugin-servers.test.ts +10 -8
- package/src/{calls/__tests__ → __tests__}/media-stream-audio-transcode.test.ts +1 -1
- package/src/{calls/__tests__ → __tests__}/media-stream-output.test.ts +8 -37
- package/src/{calls/__tests__ → __tests__}/media-stream-parser.test.ts +1 -1
- package/src/{calls/__tests__ → __tests__}/media-stream-server-integration.test.ts +57 -150
- package/src/{calls/__tests__ → __tests__}/media-stream-stt-session.test.ts +5 -5
- package/src/{calls/__tests__ → __tests__}/media-turn-detector.test.ts +1 -1
- package/src/__tests__/messages-after-tiebreaker.test.ts +0 -91
- package/src/__tests__/messages-read-boundary-guard.test.ts +1 -1
- package/src/__tests__/messaging-send-tool.test.ts +548 -355
- package/src/__tests__/model-intents.test.ts +0 -3
- package/src/__tests__/outlook-messaging-provider.test.ts +6 -6
- package/src/__tests__/platform-client-verify-credential.test.ts +0 -2
- package/src/__tests__/plugin-import-boundary-guard.test.ts +0 -13
- package/src/__tests__/script-proxy-certs.test.ts +0 -35
- package/src/__tests__/skill-feature-flags-integration.test.ts +1 -1
- package/src/__tests__/skill-feature-flags.test.ts +2 -2
- package/src/__tests__/speaker-identification.test.ts +56 -0
- package/src/__tests__/subagent-tool-gate-mode.test.ts +3 -19
- package/src/__tests__/subagent-tools.test.ts +2 -3
- package/src/__tests__/telephony-tts-guard.test.ts +3 -38
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +0 -4
- package/src/{calls/__tests__/voice-session-bridge-turn-flow.test.ts → __tests__/voice-session-bridge.test.ts} +12 -20
- package/src/__tests__/watch-retro-tool-availability.test.ts +17 -8
- package/src/__tests__/workspace-git-service.test.ts +0 -26
- package/src/acp/__tests__/agent-process.test.ts +4 -258
- package/src/acp/__tests__/auth-required.test.ts +0 -96
- package/src/acp/__tests__/client-handler.test.ts +2 -90
- package/src/acp/__tests__/session-manager-persistence.test.ts +1 -5
- package/src/acp/__tests__/session-manager-resume.test.ts +6 -270
- package/src/acp/__tests__/session-manager-usage.test.ts +2 -4
- package/src/acp/__tests__/session-manager.test.ts +4 -895
- package/src/acp/agent-process.test.ts +1 -4
- package/src/acp/agent-process.ts +9 -94
- package/src/acp/auth-required.ts +1 -50
- package/src/acp/auto-install.test.ts +7 -9
- package/src/acp/client-handler.ts +3 -16
- package/src/acp/resolve-agent.test.ts +6 -220
- package/src/acp/resolve-agent.ts +23 -88
- package/src/acp/session-manager.test.ts +3 -196
- package/src/acp/session-manager.ts +18 -441
- package/src/acp/types.ts +5 -50
- package/src/api/events/app-files-changed.ts +3 -4
- package/src/api/index.ts +0 -6
- package/src/apps/app-store.ts +1 -1
- package/src/bundler/app-compiler.ts +0 -15
- package/src/calls/__tests__/voice-control-protocol.test.ts +25 -58
- package/src/calls/__tests__/voice-session-bridge.test.ts +1 -37
- package/src/calls/__tests__/voice-triage-escalate.test.ts +0 -105
- package/src/calls/call-constants.ts +4 -24
- package/src/calls/call-controller.ts +199 -499
- package/src/calls/call-transport.ts +0 -16
- package/src/calls/media-stream-output.ts +0 -32
- package/src/calls/media-stream-server.ts +30 -82
- package/src/calls/media-stream-stt-session.ts +0 -15
- package/src/calls/resolve-call-tts-provider.ts +2 -33
- package/src/calls/speaker-identification.ts +233 -0
- package/src/calls/tts-call-strategy.ts +5 -9
- package/src/calls/types.ts +0 -2
- package/src/calls/voice-control-protocol.ts +21 -53
- package/src/calls/voice-session-bridge.ts +46 -59
- package/src/calls/voice-triage-escalate.ts +56 -175
- package/src/cli/commands/apps.help.ts +13 -67
- package/src/cli/commands/apps.ts +0 -183
- package/src/cli/commands/channels/index.help.ts +2 -70
- package/src/cli/commands/channels/index.ts +3 -16
- package/src/cli/commands/credentials.help.ts +2 -6
- package/src/cli/commands/credentials.ts +2 -2
- package/src/cli/commands/mcp.help.ts +8 -8
- package/src/cli/commands/mcp.ts +1 -1
- package/src/cli/commands/roadmap.help.ts +1 -5
- package/src/cli/commands/roadmap.ts +1 -1
- package/src/cli/lib/bundled-marketplace.json +90 -103
- package/src/config/__tests__/default-profile-catalog.test.ts +0 -22
- package/src/config/__tests__/memory-retrospective-schema.test.ts +1 -15
- package/src/config/__tests__/platform-rehydration.test.ts +0 -93
- package/src/config/acp-defaults.test.ts +3 -13
- package/src/config/acp-defaults.ts +6 -23
- package/src/config/acp-schema.ts +2 -30
- package/src/config/bundled-skills/acp/SKILL.md +14 -22
- package/src/config/bundled-skills/acp/TOOLS.json +1 -5
- package/src/config/bundled-skills/app-builder/SKILL.md +9 -12
- package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +1 -1
- package/src/config/bundled-skills/messaging/TOOLS.json +3 -3
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +139 -67
- package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +1 -1
- package/src/config/bundled-skills/screen-annotation/SKILL.md +1 -8
- package/src/config/bundled-skills/screen-annotation/tools/screen-annotation.test.ts +1 -4
- package/src/config/bundled-skills/skill-management/TOOLS.json +2 -2
- package/src/config/default-profile-catalog.ts +1 -1
- package/src/config/env.ts +2 -2
- package/src/config/feature-flag-registry.json +12 -8
- package/src/config/memory-v3-gate.test.ts +0 -31
- package/src/config/memory-v3-gate.ts +16 -20
- package/src/config/platform-rehydration.ts +21 -75
- package/src/config/schema.ts +2 -2
- package/src/config/schemas/__tests__/live-voice.test.ts +158 -8
- package/src/config/schemas/calls.ts +28 -1
- package/src/config/schemas/live-voice.ts +173 -1
- package/src/config/schemas/mcp.ts +1 -1
- package/src/config/schemas/memory-retrospective.ts +0 -10
- package/src/config/schemas/skills.ts +14 -0
- package/src/credential-execution/process-manager.test.ts +19 -18
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +121 -66
- package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +1 -4
- package/src/daemon/app-source-watcher.ts +218 -0
- package/src/daemon/config-watcher.ts +194 -13
- package/src/daemon/conversation-process.ts +64 -35
- package/src/daemon/conversation-skill-tools.ts +31 -13
- package/src/daemon/conversation-store.ts +1 -1
- package/src/daemon/conversation-surfaces.ts +8 -10
- package/src/daemon/conversation-tool-setup.ts +43 -37
- package/src/daemon/conversation.ts +3 -0
- package/src/daemon/host-bash-proxy.ts +12 -8
- package/src/daemon/host-browser-proxy.ts +1 -15
- package/src/daemon/host-file-proxy.ts +10 -8
- package/src/daemon/host-transfer-proxy.ts +18 -14
- package/src/daemon/lifecycle.ts +5 -0
- package/src/daemon/mcp-reload-service.ts +5 -5
- package/src/daemon/message-types/sync.ts +0 -1
- package/src/daemon/providers-setup.ts +1 -1
- package/src/daemon/shutdown-handlers.ts +9 -1
- package/src/daemon/skill-memory-refresh.ts +0 -64
- package/src/daemon/tool-side-effects.ts +18 -7
- package/src/hooks/types.ts +0 -33
- package/src/inbound/platform-callback-registration.ts +5 -4
- package/src/live-voice/__tests__/live-voice-metrics.test.ts +3 -0
- package/src/live-voice/__tests__/live-voice-progress.test.ts +15 -15
- package/src/live-voice/__tests__/live-voice-vad.test.ts +2 -2
- package/src/{calls → live-voice}/__tests__/progress-narration.test.ts +4 -4
- package/src/{calls → live-voice}/__tests__/progress-phrases.test.ts +1 -1
- package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +69 -13
- package/src/live-voice/live-voice-session.ts +693 -213
- package/src/{calls → live-voice}/progress-narration.ts +2 -2
- package/src/live-voice/protocol.ts +2 -0
- package/src/mcp/__tests__/effective-config.test.ts +2 -2
- package/src/mcp/effective-config.ts +3 -3
- package/src/mcp/mcp-header-store.ts +45 -29
- package/src/mcp/startup.ts +4 -8
- package/src/messaging/provider.ts +3 -10
- package/src/messaging/providers/callback-routing.ts +0 -18
- package/src/messaging/providers/channel-transport.ts +0 -56
- package/src/messaging/providers/discord/transport.ts +0 -17
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +17 -6
- package/src/messaging/providers/slack/adapter.ts +44 -7
- package/src/messaging/providers/slack/transport.ts +0 -16
- package/src/messaging/providers/telegram-bot/adapter.ts +48 -1
- package/src/messaging/providers/telegram-bot/client.ts +26 -0
- package/src/messaging/providers/telegram-bot/transport.ts +0 -20
- package/src/messaging/providers/whatsapp/adapter.ts +48 -1
- package/src/messaging/providers/whatsapp/client.ts +29 -0
- package/src/messaging/providers/whatsapp/transport.ts +0 -18
- package/src/monitoring/__tests__/workspace-git-heartbeat.test.ts +0 -8
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +0 -55
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +3 -153
- package/src/notifications/assistant-reply-producer.ts +3 -8
- package/src/notifications/conversation-pairing.ts +16 -36
- package/src/notifications/delivered-post-record.ts +1 -11
- package/src/notifications/home-feed-side-effect.ts +7 -55
- package/src/outbound-proxy/certs.ts +5 -3
- package/src/permissions/checker.test.ts +6 -11
- package/src/persistence/compaction-log-store-clickhouse.ts +3 -3
- package/src/persistence/conversation-crud.ts +63 -71
- package/src/persistence/conversation-lineage.ts +4 -12
- package/src/persistence/conversation-title-service.ts +26 -9
- package/src/persistence/llm-request-log-sink-clickhouse.ts +3 -3
- package/src/persistence/llm-request-log-source-clickhouse.ts +3 -3
- package/src/persistence/message-reads.ts +0 -30
- package/src/persistence/migrations/157-invite-contact-id.ts +6 -18
- package/src/persistence/schema/memory-core.ts +0 -5
- package/src/platform/client.test.ts +0 -2
- package/src/platform/client.ts +10 -2
- package/src/plugin-api/constants.ts +0 -2
- package/src/plugin-api/index.ts +0 -1
- package/src/plugin-api/types.ts +0 -2
- package/src/plugins/defaults/index.ts +0 -2
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +0 -46
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +5 -9
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +10 -20
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-state.test.ts +0 -219
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +0 -31
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-trigger-check.test.ts +0 -63
- package/src/plugins/defaults/memory/__tests__/shutdown-hook.test.ts +0 -4
- package/src/plugins/defaults/memory/graph/capability-seed.ts +6 -4
- package/src/plugins/defaults/memory/hooks/init.ts +0 -10
- package/src/plugins/defaults/memory/hooks/shutdown.ts +0 -2
- package/src/plugins/defaults/memory/jobs-worker.ts +0 -13
- package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +60 -34
- package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +5 -3
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +3 -12
- package/src/plugins/defaults/memory/memory-retrospective-state.ts +71 -312
- package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +1 -2
- package/src/plugins/defaults/memory/memory-retrospective-trigger-check.ts +3 -5
- package/src/plugins/defaults/memory/startup.ts +1 -6
- package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +9 -7
- package/src/plugins/defaults/memory/substrate/skill-content.ts +2 -2
- package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +0 -89
- package/src/plugins/defaults/memory/worker.ts +0 -6
- package/src/plugins/mcp-servers.ts +75 -15
- package/src/providers/__tests__/connection-model-compat.test.ts +3 -3
- package/src/providers/gemini/client.ts +23 -9
- package/src/providers/model-catalog.ts +0 -34
- package/src/providers/model-intents.ts +5 -10
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +18 -146
- package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +1 -83
- package/src/providers/openai/chat-completions-provider.ts +78 -192
- package/src/providers/openai/codex-models.ts +4 -0
- package/src/runtime/AGENTS.md +0 -2
- package/src/runtime/auth/same-actor.ts +3 -38
- package/src/runtime/http-server.ts +151 -160
- package/src/runtime/routes/__tests__/acp-routes.test.ts +4 -159
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +1 -108
- package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +12 -16
- package/src/runtime/routes/acp-routes.test.ts +0 -15
- package/src/runtime/routes/acp-routes.ts +9 -72
- package/src/runtime/routes/app-management-routes.ts +0 -73
- package/src/runtime/routes/app-routes.ts +1 -1
- package/src/runtime/routes/conversation-list-routes.ts +3 -14
- package/src/runtime/routes/conversation-query-routes.ts +0 -40
- package/src/runtime/routes/credential-routes.ts +10 -19
- package/src/runtime/routes/diagnostics-routes.ts +3 -55
- package/src/runtime/routes/index.ts +0 -4
- package/src/runtime/routes/log-export-routes.ts +0 -2
- package/src/runtime/routes/mcp-auth-routes.ts +47 -39
- package/src/runtime/routes/migration-routes.ts +21 -8
- package/src/runtime/routes/platform-routes.ts +15 -18
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +0 -28
- package/src/runtime/services/conversation-serializer.ts +3 -16
- package/src/runtime/sync/resource-sync-events.ts +1 -2
- package/src/schedule/worker.ts +1 -1
- package/src/security/ces-rpc-record-backend.ts +0 -7
- package/src/subagent/consult-context.ts +25 -12
- package/src/tools/acp/list-agents.test.ts +1 -3
- package/src/tools/acp/spawn.test.ts +11 -146
- package/src/tools/acp/spawn.ts +3 -21
- package/src/tools/acp/status.ts +3 -40
- package/src/tools/app-control/skill-proxy-bridge.ts +1 -2
- package/src/tools/capability-offer.test.ts +2 -22
- package/src/tools/capability-offer.ts +1 -16
- package/src/tools/computer-use/definitions.ts +2 -4
- package/src/tools/computer-use/skill-proxy-bridge.ts +1 -2
- package/src/tools/credentials/metadata-store.ts +0 -100
- package/src/tools/host-filesystem/edit.test.ts +1 -2
- package/src/tools/host-filesystem/edit.ts +2 -2
- package/src/tools/host-filesystem/read.test.ts +1 -2
- package/src/tools/host-filesystem/read.ts +2 -2
- package/src/tools/host-filesystem/transfer.test.ts +1 -2
- package/src/tools/host-filesystem/transfer.ts +2 -2
- package/src/tools/host-filesystem/write.test.ts +1 -2
- package/src/tools/host-filesystem/write.ts +2 -2
- package/src/tools/host-terminal/host-shell.test.ts +0 -16
- package/src/tools/host-terminal/host-shell.ts +20 -4
- package/src/tools/policy-context.ts +7 -6
- package/src/tools/subagent/spawn.ts +8 -5
- package/src/tools/watch/watch-retro-report.ts +8 -0
- package/src/util/__tests__/language-subtag.test.ts +1 -23
- package/src/util/fs-watcher-error.ts +36 -0
- package/src/util/language-subtag.ts +0 -16
- package/src/util/platform.ts +0 -8
- package/src/workspace/git-service.ts +54 -5
- package/src/workspace/migrations/registry.ts +0 -6
- package/scripts/smoke-desktop-window-theme.ts +0 -186
- package/src/__tests__/git-porcelain.test.ts +0 -47
- package/src/__tests__/helpers/set-workspace-mcp.ts +0 -11
- package/src/__tests__/message-deleted-hook-dispatch.test.ts +0 -119
- package/src/__tests__/resolve-app.test.ts +0 -135
- package/src/__tests__/source-fingerprint.test.ts +0 -184
- package/src/__tests__/workspace-migration-154-repair-retired-codex-gpt-5-4-model-ids.test.ts +0 -336
- package/src/acp/__tests__/helpers/acp-model-option.ts +0 -51
- package/src/acp/model-config.test.ts +0 -166
- package/src/acp/model-config.ts +0 -138
- package/src/api/events/acp-session-model-update.test.ts +0 -88
- package/src/api/events/acp-session-model-update.ts +0 -46
- package/src/apps/resolve-app.ts +0 -81
- package/src/apps/source-fingerprint.ts +0 -266
- package/src/calls/__tests__/barge-in-guard.test.ts +0 -81
- package/src/calls/__tests__/voice-leg-coordinator.test.ts +0 -229
- package/src/calls/__tests__/voice-progress-cadence.test.ts +0 -274
- package/src/calls/barge-in-guard.ts +0 -118
- package/src/calls/voice-leg-coordinator.ts +0 -189
- package/src/calls/voice-progress-cadence.ts +0 -423
- package/src/cli/commands/__tests__/apps.test.ts +0 -245
- package/src/cli/commands/__tests__/roadmap.help.test.ts +0 -19
- package/src/cli/commands/channels/__tests__/send.test.ts +0 -218
- package/src/cli/commands/channels/send.ts +0 -128
- package/src/config/__tests__/platform-identity.test.ts +0 -117
- package/src/config/acp-agent-write.ts +0 -43
- package/src/config/acp-schema.test.ts +0 -81
- package/src/config/platform-identity.ts +0 -144
- package/src/config/schemas/__tests__/voice.test.ts +0 -148
- package/src/config/schemas/voice.ts +0 -150
- package/src/daemon/__tests__/skill-memory-refresh-mtime.test.ts +0 -133
- package/src/daemon/app-change-notify.ts +0 -45
- package/src/desktop/__tests__/fake-desktop.ts +0 -119
- package/src/desktop/desktop-chrome-policy.test.ts +0 -71
- package/src/desktop/desktop-chrome-policy.ts +0 -42
- package/src/desktop/desktop-dependencies.test.ts +0 -102
- package/src/desktop/desktop-dependencies.ts +0 -306
- package/src/desktop/desktop-feature.test.ts +0 -18
- package/src/desktop/desktop-feature.ts +0 -19
- package/src/desktop/desktop-panel-config.test.ts +0 -135
- package/src/desktop/desktop-panel-config.ts +0 -149
- package/src/desktop/desktop-session-manager.test.ts +0 -627
- package/src/desktop/desktop-session-manager.ts +0 -814
- package/src/desktop/desktop-stream-bridge.test.ts +0 -395
- package/src/desktop/desktop-stream-bridge.ts +0 -214
- package/src/desktop/desktop-wallpaper.test.ts +0 -191
- package/src/desktop/desktop-wallpaper.ts +0 -113
- package/src/desktop/desktop-window-theme.ts +0 -170
- package/src/i18n/AGENTS.md +0 -22
- package/src/i18n/__tests__/i18n.test.ts +0 -108
- package/src/i18n/index.ts +0 -16
- package/src/i18n/locales.ts +0 -93
- package/src/i18n/messages.ts +0 -61
- package/src/i18n/title.ts +0 -25
- package/src/mcp/__tests__/workspace-mcp-config.test.ts +0 -135
- package/src/mcp/spec-schema.ts +0 -111
- package/src/mcp/workspace-mcp-config.ts +0 -188
- package/src/messaging/providers/__tests__/proactive-address.test.ts +0 -95
- package/src/persistence/conversation-title-placeholders.ts +0 -23
- package/src/persistence/message-cursor.ts +0 -78
- package/src/persistence/migrations/__tests__/157-invite-contact-id.test.ts +0 -149
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-backfill.test.ts +0 -118
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-preserve.test.ts +0 -83
- package/src/plugins/defaults/memory/hooks/message-deleted.ts +0 -21
- package/src/plugins/defaults/memory/memory-retrospective-cursor-backfill.ts +0 -126
- package/src/plugins/defaults/memory/memory-retrospective-cursor-preserve.ts +0 -79
- package/src/plugins/defaults/memory/memory-retrospective-cursor.ts +0 -28
- package/src/plugins/defaults/memory/skill-md-mtime-poll.ts +0 -9
- package/src/providers/gemini-thought-signature.test.ts +0 -63
- package/src/providers/gemini-thought-signature.ts +0 -44
- package/src/providers/openai/__tests__/chat-completions-provider-thought-signature.test.ts +0 -613
- package/src/providers/openai/__tests__/google-thought-signature.test.ts +0 -235
- package/src/providers/openai/google-thought-signature.ts +0 -320
- package/src/runtime/__tests__/channel-send.test.ts +0 -443
- package/src/runtime/__tests__/desktop-stream-upgrade.test.ts +0 -89
- package/src/runtime/__tests__/live-voice-upgrade.test.ts +0 -58
- package/src/runtime/__tests__/runtime-ws-test-utils.ts +0 -72
- package/src/runtime/__tests__/watch-stream-upgrade.test.ts +0 -59
- package/src/runtime/channel-send.ts +0 -316
- package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +0 -205
- package/src/runtime/routes/__tests__/channel-send-routes.test.ts +0 -127
- package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +0 -107
- package/src/runtime/routes/channel-send-routes.ts +0 -85
- package/src/runtime/routes/desktop-setup-routes.test.ts +0 -74
- package/src/runtime/routes/desktop-setup-routes.ts +0 -35
- package/src/security/__tests__/ces-http-record-backend.test.ts +0 -137
- package/src/security/ces-http-record-backend.ts +0 -120
- package/src/skills/__tests__/workspace-skill-md-signature.test.ts +0 -60
- package/src/skills/workspace-skill-md-signature.ts +0 -50
- package/src/tools/acp/status.test.ts +0 -94
- package/src/util/ca-bundle.ts +0 -13
- package/src/workspace/git-porcelain.ts +0 -66
- package/src/workspace/migrations/154-repair-retired-codex-gpt-5-4-model-ids.ts +0 -258
- package/src/workspace/migrations/155-move-front-model-config-to-voice.ts +0 -78
- package/src/workspace/migrations/156-extract-workspace-mcp-json.ts +0 -214
- package/src/workspace/migrations/__tests__/155-move-front-model-config-to-voice.test.ts +0 -102
- package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +0 -199
- /package/src/{calls → live-voice}/progress-phrases.ts +0 -0
|
@@ -13,17 +13,15 @@ describe("DEFAULT_ACP_AGENT_PROFILES", () => {
|
|
|
13
13
|
]);
|
|
14
14
|
});
|
|
15
15
|
|
|
16
|
-
test("claude profile uses the @agentclientprotocol adapter binary
|
|
16
|
+
test("claude profile uses the @agentclientprotocol adapter binary", () => {
|
|
17
17
|
expect(DEFAULT_ACP_AGENT_PROFILES.claude).toEqual({
|
|
18
18
|
command: "claude-agent-acp",
|
|
19
19
|
args: [],
|
|
20
20
|
description: "Claude Code (via @agentclientprotocol/claude-agent-acp)",
|
|
21
|
-
model: "opus",
|
|
22
21
|
});
|
|
23
22
|
});
|
|
24
23
|
|
|
25
24
|
test("codex profile uses the @agentclientprotocol adapter binary", () => {
|
|
26
|
-
// No model: Codex aliases are its own, so the adapter's default stands.
|
|
27
25
|
expect(DEFAULT_ACP_AGENT_PROFILES.codex).toEqual({
|
|
28
26
|
command: "codex-acp",
|
|
29
27
|
args: [],
|
|
@@ -46,19 +44,11 @@ describe("DEFAULT_ACP_AGENT_PROFILES", () => {
|
|
|
46
44
|
describe("DEFAULT_AGENT_NPM_PACKAGES", () => {
|
|
47
45
|
test("is keyed by command name with the canonical npm package", () => {
|
|
48
46
|
expect(DEFAULT_AGENT_NPM_PACKAGES).toEqual({
|
|
49
|
-
"claude-agent-acp": "@agentclientprotocol/claude-agent-acp
|
|
50
|
-
"codex-acp": "@agentclientprotocol/codex-acp
|
|
47
|
+
"claude-agent-acp": "@agentclientprotocol/claude-agent-acp",
|
|
48
|
+
"codex-acp": "@agentclientprotocol/codex-acp",
|
|
51
49
|
});
|
|
52
50
|
});
|
|
53
51
|
|
|
54
|
-
test("every package spec pins an exact version", () => {
|
|
55
|
-
// A bare name would let a clean install resolve whatever npm calls
|
|
56
|
-
// latest, which is what the repo's pinning rule exists to prevent.
|
|
57
|
-
for (const spec of Object.values(DEFAULT_AGENT_NPM_PACKAGES)) {
|
|
58
|
-
expect(spec).toMatch(/^@?[^@]+@\d+\.\d+\.\d+$/);
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
|
|
62
52
|
test("every default profile's command has a matching npm package", () => {
|
|
63
53
|
for (const profile of Object.values(DEFAULT_ACP_AGENT_PROFILES)) {
|
|
64
54
|
expect(DEFAULT_AGENT_NPM_PACKAGES[profile.command]).toBeDefined();
|
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
import type { AcpAgentConfig } from "./acp-schema.js";
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* A bundled profile always names its command; a user entry for the same id
|
|
5
|
-
* may omit `command` and inherit this one.
|
|
6
|
-
*/
|
|
7
|
-
export type AcpAgentProfile = AcpAgentConfig & { command: string };
|
|
8
|
-
|
|
9
3
|
// Shared frozen empty args array — these defaults are read by every ACP spawn,
|
|
10
4
|
// so a single accidental `args.push(...)` from one caller would corrupt every
|
|
11
5
|
// subsequent read. Cast through `unknown` because `AcpAgentConfig.args` is
|
|
@@ -19,22 +13,17 @@ const FROZEN_EMPTY_ARGS = Object.freeze([] as string[]) as unknown as string[];
|
|
|
19
13
|
* falls back to this map so common agents like `claude` and `codex` Just Work
|
|
20
14
|
* without requiring per-user config.
|
|
21
15
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* entry that names a model overrides it, one that omits it inherits it.
|
|
25
|
-
*
|
|
26
|
-
* Keyed by agent id. Deeply frozen: the outer object, each profile, and the
|
|
27
|
-
* `args` arrays, so mutation throws in strict mode rather than silently
|
|
16
|
+
* Keyed by agent id. Deeply frozen — the outer object, each profile, and the
|
|
17
|
+
* `args` arrays — so mutation throws in strict mode rather than silently
|
|
28
18
|
* corrupting the shared defaults.
|
|
29
19
|
*/
|
|
30
20
|
export const DEFAULT_ACP_AGENT_PROFILES: Readonly<
|
|
31
|
-
Record<string,
|
|
21
|
+
Record<string, AcpAgentConfig>
|
|
32
22
|
> = Object.freeze({
|
|
33
23
|
claude: Object.freeze({
|
|
34
24
|
command: "claude-agent-acp",
|
|
35
25
|
args: FROZEN_EMPTY_ARGS,
|
|
36
26
|
description: "Claude Code (via @agentclientprotocol/claude-agent-acp)",
|
|
37
|
-
model: "opus",
|
|
38
27
|
}),
|
|
39
28
|
codex: Object.freeze({
|
|
40
29
|
command: "codex-acp",
|
|
@@ -44,21 +33,15 @@ export const DEFAULT_ACP_AGENT_PROFILES: Readonly<
|
|
|
44
33
|
});
|
|
45
34
|
|
|
46
35
|
/**
|
|
47
|
-
* Single source of truth for adapter binary → npm package
|
|
36
|
+
* Single source of truth for adapter binary → npm package name. Automatic
|
|
48
37
|
* installation and the resolver's install hints use this map, so a new
|
|
49
38
|
* adapter only needs one entry here.
|
|
50
39
|
*
|
|
51
40
|
* Keyed by command name (not agent id) so the mapping follows the binary
|
|
52
41
|
* regardless of how a user's config aliases an agent.
|
|
53
|
-
*
|
|
54
|
-
* Values carry an exact version, so a clean install lands the adapter version
|
|
55
|
-
* the daemon was tested against instead of whatever npm calls latest today.
|
|
56
|
-
* An adapter already on PATH is left alone: installation runs only when
|
|
57
|
-
* preflight found no binary at all, so this never moves a version the user
|
|
58
|
-
* installed themselves.
|
|
59
42
|
*/
|
|
60
43
|
export const DEFAULT_AGENT_NPM_PACKAGES: Readonly<Record<string, string>> =
|
|
61
44
|
Object.freeze({
|
|
62
|
-
"claude-agent-acp": "@agentclientprotocol/claude-agent-acp
|
|
63
|
-
"codex-acp": "@agentclientprotocol/codex-acp
|
|
45
|
+
"claude-agent-acp": "@agentclientprotocol/claude-agent-acp",
|
|
46
|
+
"codex-acp": "@agentclientprotocol/codex-acp",
|
|
64
47
|
});
|
package/src/config/acp-schema.ts
CHANGED
|
@@ -1,15 +1,8 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export const AcpAgentConfigSchema = z
|
|
3
|
+
const AcpAgentConfigSchema = z
|
|
6
4
|
.object({
|
|
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
|
-
),
|
|
5
|
+
command: z.string().describe("Command to spawn the ACP agent process"),
|
|
13
6
|
args: z
|
|
14
7
|
.array(z.string())
|
|
15
8
|
.default([])
|
|
@@ -22,12 +15,6 @@ export const AcpAgentConfigSchema = z
|
|
|
22
15
|
.record(z.string(), z.string())
|
|
23
16
|
.optional()
|
|
24
17
|
.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
|
-
),
|
|
31
18
|
})
|
|
32
19
|
.describe("Configuration for an individual ACP agent");
|
|
33
20
|
|
|
@@ -43,21 +30,6 @@ export const AcpConfigSchema = z
|
|
|
43
30
|
),
|
|
44
31
|
agents: z
|
|
45
32
|
.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
|
-
})
|
|
61
33
|
.default({})
|
|
62
34
|
.describe("Map of agent names to their configurations"),
|
|
63
35
|
})
|
|
@@ -23,18 +23,6 @@ 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
|
-
|
|
38
26
|
## When the user names Claude Code or Codex
|
|
39
27
|
|
|
40
28
|
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.
|
|
@@ -47,23 +35,21 @@ If they name Claude Code or Codex without asking you to run it here (for example
|
|
|
47
35
|
|
|
48
36
|
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:
|
|
49
37
|
|
|
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
|
|
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 it once via a sandboxed bun global install and proceeds in the same call (see "Automatic adapter availability" below).
|
|
51
39
|
|
|
52
40
|
2. Call `acp_spawn`. Do NOT run `vellum sleep && vellum wake` - that kills the conversation.
|
|
53
41
|
|
|
54
42
|
## Automatic adapter availability
|
|
55
43
|
|
|
56
|
-
When
|
|
57
|
-
|
|
58
|
-
An adapter already on PATH is left alone, whoever installed it: the install runs only when preflight found no binary at all.
|
|
44
|
+
When `acp_spawn` finds the agent's binary missing from PATH, the assistant installs it once 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. After this one-time install, the adapter is a normal trusted binary on PATH and every later spawn (and resume) uses it directly.
|
|
59
45
|
|
|
60
46
|
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.
|
|
61
47
|
|
|
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).
|
|
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).
|
|
63
49
|
|
|
64
50
|
```bash
|
|
65
|
-
bun add -g @agentclientprotocol/claude-agent-acp
|
|
66
|
-
bun add -g @agentclientprotocol/codex-acp
|
|
51
|
+
bun add -g @agentclientprotocol/claude-agent-acp # claude
|
|
52
|
+
bun add -g @agentclientprotocol/codex-acp # codex
|
|
67
53
|
```
|
|
68
54
|
|
|
69
55
|
## Claude setup
|
|
@@ -98,14 +84,20 @@ Do NOT put API keys (or any secret) in the workspace config file - secrets never
|
|
|
98
84
|
|
|
99
85
|
- Two agents are supported out-of-box: `claude` (via the `claude-agent-acp` adapter) and `codex` (via the `codex-acp` adapter).
|
|
100
86
|
- 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.
|
|
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
|
|
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 `acp.agents.<id>` entry replaces the bundled default entirely (no field merge), so any override must spell out the full `command` and `args`, not just the field being changed.
|
|
102
88
|
- 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.
|
|
103
89
|
|
|
104
90
|
## Updating an adapter
|
|
105
91
|
|
|
106
|
-
|
|
92
|
+
Adapters are installed automatically when missing. To update an installed adapter, ask the user first and use its owning package manager. For bun installations:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
bun add -g @agentclientprotocol/claude-agent-acp@latest
|
|
96
|
+
# or
|
|
97
|
+
bun add -g @agentclientprotocol/codex-acp@latest
|
|
98
|
+
```
|
|
107
99
|
|
|
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.
|
|
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. Verify the required model with a fresh `acp_spawn` call after updating.
|
|
109
101
|
|
|
110
102
|
## When to use acp_steer vs acp_spawn
|
|
111
103
|
|
|
@@ -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
|
|
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.",
|
|
7
7
|
"category": "orchestration",
|
|
8
8
|
"risk": "high",
|
|
9
9
|
"input_schema": {
|
|
@@ -20,10 +20,6 @@
|
|
|
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."
|
|
27
23
|
}
|
|
28
24
|
},
|
|
29
25
|
"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 — auto-generated by app_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` + `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 automatically. 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. **`app_refresh`** ONCE at the end to compile.
|
|
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 `app_refresh` 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,14 +221,11 @@ 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
|
-
|
|
226
224
|
```
|
|
227
|
-
|
|
228
|
-
assistant apps refresh <name>
|
|
225
|
+
app_refresh(app_id)
|
|
229
226
|
```
|
|
230
227
|
|
|
231
|
-
|
|
228
|
+
Call it ONCE, after ALL file writes — batching is required. If it fails, the response has error details; fix with `file_edit`, then `app_refresh` again.
|
|
232
229
|
|
|
233
230
|
### 5 — Show the preview card
|
|
234
231
|
|
|
@@ -247,7 +244,7 @@ Editing an existing app means reusing its `app_id` — never `app_create`. Resol
|
|
|
247
244
|
- **`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.
|
|
248
245
|
- **Full rebrand** — still iteration, edit the existing files.
|
|
249
246
|
|
|
250
|
-
Then
|
|
247
|
+
Then `app_refresh(app_id)` ONCE (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.
|
|
251
248
|
|
|
252
249
|
> ⚠️ **`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.
|
|
253
250
|
|
|
@@ -353,7 +350,7 @@ Slide decks are a different domain — skip app patterns (contextual headers, se
|
|
|
353
350
|
## SKILL COMPLETE WHEN
|
|
354
351
|
|
|
355
352
|
- [ ] Request was scoped: personal build (sandbox) or complex/shippable (handed off to a project folder + coding agent)
|
|
356
|
-
- [ ] **Sandbox path:** `app_create` returned an `app_id`; all files written via `file_write`;
|
|
353
|
+
- [ ] **Sandbox path:** `app_create` returned an `app_id`; all files written via `file_write`; `app_refresh` ran ONCE clean; `app_open(open_mode: "preview")` rendered the card; user told what was built (3-6 bullets); iterations reflected live
|
|
357
354
|
- [ ] **Handoff path:** project folder established; coding agent spawned via `acp_spawn({ task, cwd })`; user told work continues in the folder
|
|
358
355
|
|
|
359
356
|
---
|
|
@@ -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 calling `app_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
|
|
@@ -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).
|
|
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), attachments (via attachment_paths, Gmail and Outlook), and all messaging platforms.",
|
|
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\"
|
|
136
|
+
"description": "Platform (e.g. \"gmail\"). Auto-detected if only one is connected. Slack is not supported — use the Slack Web API directly."
|
|
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
|
|
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."
|
|
141
141
|
},
|
|
142
142
|
"conversation_id": {
|
|
143
143
|
"type": "string",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { basename } from "node:path";
|
|
3
3
|
|
|
4
|
+
import { isChannelId } from "../../../../channels/types.js";
|
|
4
5
|
import type { OutboundAttachment } from "../../../../messaging/provider-types.js";
|
|
5
6
|
import {
|
|
6
7
|
createDraft,
|
|
@@ -15,10 +16,11 @@ import {
|
|
|
15
16
|
toOutlookFileAttachments,
|
|
16
17
|
} from "../../../../messaging/providers/outlook/client.js";
|
|
17
18
|
import type { OutlookDraftMessage } from "../../../../messaging/providers/outlook/types.js";
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
} from "../../../../
|
|
19
|
+
import { resolveProactiveHomeConversation } from "../../../../notifications/conversation-pairing.js";
|
|
20
|
+
import { recordDeliveredChannelPost } from "../../../../notifications/delivered-post-record.js";
|
|
21
|
+
import { getConversation } from "../../../../persistence/conversation-crud.js";
|
|
22
|
+
import { syncMessageToDisk } from "../../../../persistence/conversation-disk-view.js";
|
|
23
|
+
import { normalizeExternalThreadId } from "../../../../persistence/external-conversation-store.js";
|
|
22
24
|
import {
|
|
23
25
|
isAbortLikeError,
|
|
24
26
|
throwIfCancelled,
|
|
@@ -27,6 +29,7 @@ import type {
|
|
|
27
29
|
ToolContext,
|
|
28
30
|
ToolExecutionResult,
|
|
29
31
|
} from "../../../../tools/types.js";
|
|
32
|
+
import { getLogger } from "../../../../util/logger.js";
|
|
30
33
|
import { guessMimeType } from "../../../../util/mime-type.js";
|
|
31
34
|
import {
|
|
32
35
|
err,
|
|
@@ -39,6 +42,8 @@ import {
|
|
|
39
42
|
resolveProvider,
|
|
40
43
|
} from "./shared.js";
|
|
41
44
|
|
|
45
|
+
const log = getLogger("messaging-send");
|
|
46
|
+
|
|
42
47
|
/** Read attachment files from disk into in-memory parts for outbound sending. */
|
|
43
48
|
async function readAttachments(paths: string[]): Promise<OutboundAttachment[]> {
|
|
44
49
|
return Promise.all(
|
|
@@ -53,6 +58,123 @@ async function readAttachments(paths: string[]): Promise<OutboundAttachment[]> {
|
|
|
53
58
|
/** Email providers that accept file attachments on outbound sends. */
|
|
54
59
|
const ATTACHMENT_CAPABLE_PLATFORMS = new Set(["gmail", "outlook"]);
|
|
55
60
|
|
|
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
|
+
|
|
56
178
|
export async function run(
|
|
57
179
|
input: Record<string, unknown>,
|
|
58
180
|
context: ToolContext,
|
|
@@ -75,62 +197,9 @@ export async function run(
|
|
|
75
197
|
throwIfCancelled(context);
|
|
76
198
|
|
|
77
199
|
try {
|
|
78
|
-
|
|
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
|
-
}
|
|
200
|
+
const provider = await resolveProvider(platform);
|
|
132
201
|
|
|
133
|
-
// Reject attachments on platforms that can't carry them.
|
|
202
|
+
// Reject attachments on platforms that can't carry them (e.g. Telegram, WhatsApp).
|
|
134
203
|
if (
|
|
135
204
|
attachmentPaths?.length &&
|
|
136
205
|
!ATTACHMENT_CAPABLE_PLATFORMS.has(provider.id)
|
|
@@ -357,14 +426,7 @@ export async function run(
|
|
|
357
426
|
);
|
|
358
427
|
}
|
|
359
428
|
|
|
360
|
-
//
|
|
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
|
-
}
|
|
429
|
+
// Non-email platforms
|
|
368
430
|
const attachments = attachmentPaths?.length
|
|
369
431
|
? await readAttachments(attachmentPaths)
|
|
370
432
|
: undefined;
|
|
@@ -380,6 +442,16 @@ export async function run(
|
|
|
380
442
|
const threadSuffix = result.threadId
|
|
381
443
|
? `, "thread_id": "${result.threadId}"`
|
|
382
444
|
: "";
|
|
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
|
+
|
|
383
455
|
return ok(`Message sent (ID: ${result.id}${threadSuffix}).`);
|
|
384
456
|
} catch (e) {
|
|
385
457
|
// 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 and system events
|
|
26
|
+
- `role: "user"` - caller speech (prefixed with `[SPEAKER]` tags) 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
|
|
@@ -124,14 +124,7 @@ 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.
|
|
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
|
|
127
|
+
because a plausible amount of time has passed. If the next picture shows the
|
|
135
128
|
step not done, or done to the wrong thing, point at the same place again with
|
|
136
129
|
a shorter caption and say what you saw. Pointing at the next step while the
|
|
137
130
|
previous one is still open is how someone ends up two steps behind a mark.
|