@vellumai/assistant 0.11.7 → 0.11.8-staging.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/ARCHITECTURE.md +10 -10
- package/Dockerfile +1 -0
- package/docs/architecture/security.md +1 -1
- package/docs/error-handling.md +1 -2
- package/docs/guardian-request-flow.md +51 -5
- package/docs/trusted-contact-access.md +13 -12
- package/knip.json +1 -0
- package/node_modules/@vellumai/avatar-catalog/package.json +19 -0
- package/{src/avatar/character-components.ts → node_modules/@vellumai/avatar-catalog/src/catalog.ts} +12 -44
- package/node_modules/@vellumai/avatar-catalog/src/colors.ts +19 -0
- package/node_modules/@vellumai/avatar-catalog/src/index.ts +25 -0
- package/node_modules/@vellumai/avatar-catalog/src/types.ts +47 -0
- package/node_modules/@vellumai/avatar-catalog/tsconfig.json +20 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +7 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/no-response.ts +84 -0
- package/node_modules/@vellumai/credential-storage/src/static-credentials.ts +43 -12
- package/node_modules/@vellumai/environments/package.json +2 -1
- package/node_modules/@vellumai/environments/src/__tests__/test-path-guard.test.ts +89 -0
- package/node_modules/@vellumai/environments/src/test-path-guard.ts +113 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +7 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/no-response.ts +84 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-request-contract.test.ts +8 -18
- package/node_modules/@vellumai/gateway-client/src/__tests__/inbound-event-kind.test.ts +46 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/invite-contract.test.ts +1 -0
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +46 -0
- package/node_modules/@vellumai/gateway-client/src/guardian-request-contract.ts +62 -21
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +14 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-event-kind.ts +53 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +11 -5
- package/node_modules/@vellumai/gateway-client/src/invite-contract.ts +1 -0
- package/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/service-contracts/src/channels.ts +7 -0
- package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/no-response.ts +84 -0
- package/openapi.yaml +481 -9
- package/package.json +3 -1
- package/scripts/sync-llm-catalog.ts +3 -0
- package/src/__tests__/agent-loop-callsite-precedence.test.ts +16 -0
- package/src/__tests__/agent-loop-compacted-history-transform.test.ts +304 -0
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +3 -0
- package/src/__tests__/agent-wake-override-profile.test.ts +3 -0
- package/src/__tests__/anthropic-provider.test.ts +56 -0
- package/src/__tests__/approval-cascade.test.ts +0 -13
- package/src/__tests__/attachments-store.test.ts +117 -6
- package/src/__tests__/attachments.test.ts +122 -0
- package/src/__tests__/bundled-asset.test.ts +22 -1
- package/src/__tests__/channel-approval.test.ts +14 -7
- package/src/__tests__/channel-approvals.test.ts +3 -3
- package/src/__tests__/channel-delivery-store.test.ts +58 -0
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +1 -0
- package/src/__tests__/channel-invite-transport.test.ts +1 -0
- package/src/__tests__/channel-readiness-slack-remote.test.ts +3 -3
- package/src/__tests__/compaction-events.test.ts +0 -12
- package/src/__tests__/compactor-call-site-logging.test.ts +7 -1
- package/src/__tests__/compactor-retained-image-validation.test.ts +15 -0
- package/src/__tests__/config-schema.test.ts +35 -0
- package/src/__tests__/contacts-write.test.ts +141 -3
- package/src/__tests__/conversation-abort-tool-results.test.ts +0 -13
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +0 -12
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +0 -13
- package/src/__tests__/conversation-agent-loop.test.ts +95 -13
- package/src/__tests__/conversation-confirmation-signals.test.ts +0 -13
- package/src/__tests__/conversation-load-history-repair.test.ts +418 -0
- package/src/__tests__/conversation-media-retry.test.ts +33 -2
- package/src/__tests__/conversation-pairing.test.ts +54 -0
- package/src/__tests__/conversation-process-callsite.test.ts +0 -12
- package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -13
- package/src/__tests__/conversation-queue.test.ts +4 -13
- package/src/__tests__/conversation-retry-route.test.ts +5 -0
- package/src/__tests__/conversation-routes-disk-view.test.ts +1 -1
- package/src/__tests__/conversation-routes-enabled-plugins.test.ts +1 -1
- package/src/__tests__/conversation-routes-hidden-queue.test.ts +1 -1
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -1
- package/src/__tests__/conversation-sight-frames.test.ts +615 -0
- package/src/__tests__/conversation-slash-queue.test.ts +0 -13
- package/src/__tests__/conversation-slash-unknown.test.ts +0 -13
- package/src/__tests__/conversation-speed-override.test.ts +0 -12
- package/src/__tests__/conversation-store-ephemeral.test.ts +0 -12
- package/src/__tests__/conversation-summarize-route.test.ts +5 -0
- package/src/__tests__/conversation-summarize-up-to.test.ts +102 -0
- package/src/__tests__/conversation-wait-for-idle.test.ts +0 -12
- package/src/__tests__/conversation-workspace-cache-state.test.ts +0 -5
- package/src/__tests__/conversation-workspace-injection.test.ts +0 -17
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -17
- package/src/__tests__/credential-health-service.test.ts +45 -0
- package/src/__tests__/credential-record-retire.test.ts +256 -0
- package/src/__tests__/credential-record-write-through.test.ts +8 -4
- package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +0 -4
- package/src/__tests__/db-conversation-inference-profile-migration.test.ts +0 -4
- package/src/__tests__/db-llm-request-log-provider-migration.test.ts +0 -8
- package/src/__tests__/delete-propagation.test.ts +121 -3
- package/src/__tests__/evict-conversations-for-reload.test.ts +46 -6
- package/src/__tests__/file-ops-service.test.ts +8 -3
- package/src/__tests__/gemini-provider.test.ts +64 -0
- package/src/__tests__/guardian-action-service-scope.test.ts +118 -0
- package/src/__tests__/guardian-card-withdrawal.test.ts +299 -5
- package/src/__tests__/guardian-expiry-notifier.test.ts +117 -13
- package/src/__tests__/guardian-gateway-sim.test.ts +119 -0
- package/src/__tests__/guardian-gateway-sim.ts +56 -20
- package/src/__tests__/guardian-reply-router-answer-mode.test.ts +6 -0
- package/src/__tests__/guardian-request-status-sync.test.ts +162 -0
- package/src/__tests__/guardian-wait-controller.test.ts +6 -0
- package/src/__tests__/headless-browser-mode.test.ts +27 -0
- package/src/__tests__/helpers/mock-conversation.ts +2 -0
- package/src/__tests__/list-messages-system-card.test.ts +70 -0
- package/src/__tests__/llm-catalog-parity.test.ts +2 -0
- package/src/__tests__/managed-profile-guard.test.ts +2 -2
- package/src/__tests__/media-resolve-image-validation.test.ts +106 -0
- package/src/__tests__/media-stream-server-integration.test.ts +6 -0
- package/src/__tests__/non-member-access-request.test.ts +12 -0
- package/src/__tests__/notification-discord-adapter.test.ts +76 -4
- package/src/__tests__/oauth-commands-routes.test.ts +21 -0
- package/src/__tests__/openai-provider.test.ts +63 -0
- package/src/__tests__/outbound-slack-persistence.test.ts +114 -2
- package/src/__tests__/persist-unsendable-image-downscale.test.ts +63 -1
- package/src/__tests__/persist-unsendable-image.test.ts +19 -0
- package/src/__tests__/plugin-app-serve-routes.test.ts +2 -2
- package/src/__tests__/plugin-import-boundary-guard.test.ts +1 -0
- package/src/__tests__/process-message-background-slack.test.ts +1 -1
- package/src/__tests__/process-message-display-content.test.ts +1 -1
- package/src/__tests__/processing-flag-persist-failure.test.ts +0 -5
- package/src/__tests__/prompt-cache-cross-turn-stability.test.ts +0 -17
- package/src/__tests__/qdrant-manager.test.ts +6 -0
- package/src/__tests__/reaction-persistence.test.ts +58 -62
- package/src/__tests__/retry-thinking-adaptive-only.test.ts +67 -0
- package/src/__tests__/server-history-render.test.ts +22 -0
- package/src/__tests__/sight-frame-retention.test.ts +362 -0
- package/src/__tests__/slack-reaction-approvals.test.ts +44 -24
- package/src/__tests__/slack-reaction-guardian-approval.test.ts +29 -1
- package/src/__tests__/subagent-fork-notifications.test.ts +2 -0
- package/src/__tests__/subagent-manager-notify.test.ts +2 -0
- package/src/__tests__/subagent-notify-parent.test.ts +2 -0
- package/src/__tests__/subagent-notify-stale-rebuild.test.ts +85 -0
- package/src/__tests__/subagent-tool-gate-mode.test.ts +4 -4
- package/src/__tests__/terminal-tools.test.ts +9 -9
- package/src/__tests__/tool-result-spool.test.ts +5 -8
- package/src/__tests__/usage-attribution.test.ts +26 -0
- package/src/acp/__tests__/acp-auth-marker-store.test.ts +191 -0
- package/src/acp/__tests__/acp-claude-oauth.test.ts +262 -0
- package/src/acp/__tests__/acp-connect-card-state.test.ts +17 -0
- package/src/acp/__tests__/helpers/acp-history-db.ts +10 -3
- package/src/acp/acp-auth-marker-store.ts +196 -0
- package/src/acp/acp-claude-oauth.ts +154 -4
- package/src/acp/acp-connect-card-state.ts +49 -8
- package/src/acp/agent-process.ts +1 -0
- package/src/acp/auto-install.ts +1 -0
- package/src/acp/client-handler.ts +1 -0
- package/src/acp/prepare-agent-env.ts +79 -1
- package/src/acp/resolve-agent.ts +12 -0
- package/src/acp/session-manager.test.ts +183 -0
- package/src/acp/session-manager.ts +110 -6
- package/src/acp/types.ts +17 -0
- package/src/agent/attachments.ts +7 -2
- package/src/agent/loop.ts +59 -1
- package/src/api/events/contact-form-closed.ts +32 -0
- package/src/api/events/contact-record-request.ts +63 -0
- package/src/api/events/contact-request.ts +10 -3
- package/src/api/index.ts +20 -0
- package/src/api/responses/conversation-message.ts +20 -0
- package/src/api/responses/home.ts +101 -0
- package/src/approvals/AGENTS.md +1 -1
- package/src/approvals/guardian-card-withdrawal.ts +127 -30
- package/src/approvals/guardian-decision-primitive.ts +9 -0
- package/src/approvals/guardian-expiry-notifier.ts +22 -16
- package/src/approvals/guardian-request-status-sync.ts +98 -0
- package/src/avatar/svg-compositor.ts +1 -1
- package/src/avatar/traits-png-sync.ts +1 -1
- package/src/bundler/app-compiler.ts +1 -0
- package/src/bundler/compiler-tools.ts +1 -0
- package/src/bundler/package-resolver.ts +1 -0
- package/src/calls/__tests__/voice-session-bridge.test.ts +183 -3
- package/src/calls/guardian-action-sweep.ts +6 -1
- package/src/calls/voice-session-bridge.ts +46 -1
- package/src/channels/__tests__/gateway-guardian-requests.test.ts +13 -18
- package/src/channels/gateway-guardian-requests.ts +37 -18
- package/src/cli/__tests__/contacts-record-prompt-cli.test.ts +329 -0
- package/src/cli/commands/__tests__/browser.test.ts +65 -1
- package/src/cli/commands/browser.ts +30 -10
- package/src/cli/commands/contacts.help.ts +173 -11
- package/src/cli/commands/contacts.ts +343 -4
- package/src/cli/commands/db/refresh.ts +1 -0
- package/src/cli/commands/oauth/index.help.ts +6 -3
- package/src/cli/commands/oauth/request.test.ts +37 -0
- package/src/cli/commands/oauth/request.ts +7 -6
- package/src/cli/lib/bundled-marketplace.json +1 -1
- package/src/cli/lib/install-from-github.ts +20 -19
- package/src/cli/lib/install-plugin-dependencies.ts +3 -5
- package/src/cli/lib/merge-plugin-tree.ts +1 -0
- package/src/cli/lib/publish-plugin.ts +4 -1
- package/src/config/__tests__/balanced-model-experiment.test.ts +63 -31
- package/src/config/__tests__/default-profile-catalog.test.ts +2 -2
- package/src/config/balanced-model-experiment.ts +15 -9
- package/src/config/bundled-skills/contacts/SKILL.md +61 -18
- package/src/config/default-profile-catalog.ts +12 -11
- package/src/config/feature-flag-registry.json +22 -13
- package/src/config/schema.ts +2 -0
- package/src/config/schemas/__tests__/live-voice.test.ts +13 -0
- package/src/config/schemas/live-voice.ts +7 -0
- package/src/config/schemas/sight.ts +45 -0
- package/src/config/skills.ts +2 -1
- package/src/contacts/__tests__/mirror-reconciler.test.ts +352 -0
- package/src/contacts/contact-store.ts +47 -4
- package/src/contacts/contacts-write.ts +5 -2
- package/src/contacts/mirror-reconciler.ts +210 -0
- package/src/contacts/mirror-tombstones.ts +42 -0
- package/src/contacts/notify-contacts-changed.ts +11 -2
- package/src/context/compactor.ts +47 -19
- package/src/context/tool-result-spool.ts +21 -14
- package/src/conversations/message-consolidation.ts +2 -2
- package/src/credential-health/credential-health-service.ts +37 -6
- package/src/daemon/conversation-agent-loop-handlers.ts +19 -2
- package/src/daemon/conversation-agent-loop.ts +100 -1
- package/src/daemon/conversation-media-retry.ts +11 -6
- package/src/daemon/conversation-messaging.ts +293 -30
- package/src/daemon/conversation-runtime-assembly.ts +67 -0
- package/src/daemon/conversation-sight-frames.ts +187 -0
- package/src/daemon/conversation-store.ts +18 -28
- package/src/daemon/conversation-surfaces.ts +12 -2
- package/src/daemon/conversation-tool-setup.ts +39 -10
- package/src/daemon/conversation.ts +159 -16
- package/src/daemon/daemon-control.ts +6 -17
- package/src/daemon/handlers/config-model.test.ts +1 -0
- package/src/daemon/handlers/conversations.ts +10 -11
- package/src/daemon/install-assistant-command.ts +1 -0
- package/src/daemon/lifecycle.ts +10 -13
- package/src/daemon/message-types/sync.ts +4 -0
- package/src/daemon/process-message.ts +2 -17
- package/src/daemon/reaction-history-render.test.ts +162 -0
- package/src/daemon/reaction-history-render.ts +115 -0
- package/src/daemon/reaction-record.test.ts +162 -0
- package/src/daemon/reaction-record.ts +83 -0
- package/src/daemon/shutdown-handlers.ts +0 -2
- package/src/daemon/summarize-boundary.ts +12 -0
- package/src/daemon/video-thumbnail.ts +1 -1
- package/src/home/feed-types.ts +4 -0
- package/src/home/feed-writer.ts +25 -0
- package/src/live-voice/__tests__/live-voice-attach-frame.test.ts +679 -0
- package/src/live-voice/__tests__/live-voice-attach-image.test.ts +8 -5
- package/src/live-voice/__tests__/live-voice-photo.test.ts +637 -1
- package/src/live-voice/__tests__/live-voice-sight-frame-inline.test.ts +787 -0
- package/src/live-voice/__tests__/live-voice-sight-frame.test.ts +347 -0
- package/src/live-voice/__tests__/live-voice-vad.test.ts +109 -0
- package/src/live-voice/live-voice-photo.ts +420 -65
- package/src/live-voice/live-voice-session.ts +311 -27
- package/src/live-voice/protocol.ts +147 -11
- package/src/messaging/provider-message-metadata.ts +7 -0
- package/src/messaging/providers/__tests__/transport-dispatch.test.ts +113 -1
- package/src/messaging/providers/channel-transport.ts +37 -0
- package/src/messaging/providers/discord/api.ts +4 -4
- package/src/messaging/providers/discord/send.test.ts +177 -2
- package/src/messaging/providers/discord/send.ts +197 -10
- package/src/messaging/providers/discord/transport.ts +10 -0
- package/src/messaging/providers/index.ts +42 -0
- package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +129 -3
- package/src/messaging/providers/slack/__tests__/auth.test.ts +35 -0
- package/src/messaging/providers/slack/__tests__/message-content.test.ts +152 -0
- package/src/messaging/providers/slack/adapter.ts +112 -27
- package/src/messaging/providers/slack/api.test.ts +10 -0
- package/src/messaging/providers/slack/auth.ts +32 -6
- package/src/messaging/providers/slack/client.ts +0 -15
- package/src/messaging/providers/slack/message-content.ts +137 -0
- package/src/messaging/providers/slack/send.test.ts +43 -2
- package/src/messaging/providers/slack/send.ts +18 -5
- package/src/messaging/providers/slack/transport.ts +10 -0
- package/src/messaging/providers/slack/types.ts +21 -2
- package/src/messaging/providers/telegram-bot/adapter.ts +5 -1
- package/src/messaging/providers/telegram-bot/send.test.ts +49 -2
- package/src/messaging/providers/telegram-bot/send.ts +41 -1
- package/src/messaging/providers/telegram-bot/transport.ts +10 -0
- package/src/messaging/providers/telegram-bot/withdraw.ts +13 -5
- package/src/messaging/reaction-envelopes.ts +79 -0
- package/src/monitoring/control.ts +2 -2
- package/src/monitoring/page-cache.ts +5 -1
- package/src/notifications/AGENTS.md +1 -1
- package/src/notifications/__tests__/guardian-feed-projection.test.ts +360 -0
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +8 -0
- package/src/notifications/adapters/discord.ts +62 -9
- package/src/notifications/conversation-pairing.ts +31 -4
- package/src/notifications/emit-signal.ts +8 -5
- package/src/notifications/guardian-delivery-recorder.ts +47 -6
- package/src/notifications/guardian-feed-projection.ts +492 -0
- package/src/notifications/home-feed-side-effect.ts +59 -11
- package/src/oauth/byo-connection.test.ts +22 -0
- package/src/oauth/byo-connection.ts +11 -5
- package/src/oauth/connection.ts +3 -2
- package/src/oauth/platform-connection.test.ts +35 -0
- package/src/oauth/platform-connection.ts +18 -13
- package/src/oauth/scope-utils.ts +48 -0
- package/src/oauth/seed-providers.ts +12 -0
- package/src/outbound-proxy/certs.ts +5 -4
- package/src/permissions/question-guardian-request.test.ts +7 -1
- package/src/persistence/attachments-store.ts +134 -31
- package/src/persistence/conversation-crud.ts +137 -4
- package/src/persistence/conversation-types.ts +109 -0
- package/src/persistence/db-connection.ts +3 -21
- package/src/persistence/delivery-crud.ts +42 -12
- package/src/persistence/embeddings/__tests__/embedding-local-lifecycle.test.ts +15 -2
- package/src/persistence/embeddings/embedding-local.ts +7 -112
- package/src/persistence/embeddings/qdrant-manager.ts +4 -1
- package/src/persistence/llm-request-log-store.ts +1 -1
- package/src/persistence/migrations/370-acp-session-history-auth-error-code.ts +36 -0
- package/src/persistence/migrations/371-acp-session-history-auth-error-credential.ts +40 -0
- package/src/persistence/migrations/372-create-acp-refused-credentials.ts +34 -0
- package/src/persistence/migrations/373-acp-auth-marker-index.ts +27 -0
- package/src/persistence/migrations/__tests__/370-acp-session-history-auth-error-code.test.ts +65 -0
- package/src/persistence/migrations/__tests__/371-acp-session-history-auth-error-credential.test.ts +68 -0
- package/src/persistence/migrations/__tests__/372-create-acp-refused-credentials.test.ts +64 -0
- package/src/persistence/migrations/__tests__/373-acp-auth-marker-index.test.ts +82 -0
- package/src/persistence/schema/acp.ts +25 -0
- package/src/persistence/steps.ts +8 -0
- package/src/platform/platform-patch-queue.test.ts +43 -41
- package/src/plugin-api/index.ts +12 -0
- package/src/plugins/defaults/compaction/window-manager.ts +2 -0
- package/src/plugins/defaults/image-recovery/recover.ts +12 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +166 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +3 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-wake-chain.test.ts +3 -0
- package/src/plugins/defaults/memory/jobs-worker.ts +1 -2
- package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +53 -9
- package/src/plugins/defaults/memory/src/__tests__/memory-worker-routes.test.ts +4 -7
- package/src/plugins/defaults/memory/src/memory-worker-routes.ts +1 -1
- package/src/plugins/defaults/memory/v1/job-handlers/index-maintenance.ts +15 -1
- package/src/plugins/defaults/memory/worker-control.ts +1 -2
- package/src/providers/content-block-schema.ts +1 -0
- package/src/providers/fireworks/client.ts +9 -1
- package/src/providers/gemini/client.ts +22 -15
- package/src/providers/media-resolve.ts +24 -10
- package/src/providers/model-catalog.ts +91 -0
- package/src/providers/openai/chat-completions-provider.ts +44 -5
- package/src/providers/retry.ts +16 -11
- package/src/providers/types.ts +56 -0
- package/src/routes/__tests__/control.test.ts +4 -3
- package/src/routes/control.ts +1 -1
- package/src/routes/route-host-client.ts +1 -1
- package/src/runtime/AGENTS.md +30 -12
- package/src/runtime/__tests__/agent-wake.test.ts +71 -6
- package/src/runtime/__tests__/guardian-form-registry.test.ts +242 -0
- package/src/runtime/access-request-helper.ts +15 -7
- package/src/runtime/agent-wake.ts +16 -4
- package/src/runtime/channel-approval-types.ts +8 -5
- package/src/runtime/channel-invite-transport.ts +2 -0
- package/src/runtime/channel-invite-transports/discord.ts +27 -0
- package/src/runtime/channel-readiness-service.ts +4 -4
- package/src/runtime/channel-reply-delivery.ts +72 -28
- package/src/runtime/guardian-action-service.ts +9 -5
- package/src/runtime/guardian-form-registry.ts +256 -0
- package/src/runtime/guardian-reply-router.ts +33 -14
- package/src/runtime/invite-instruction-generator.ts +14 -2
- package/src/runtime/migrations/vbundle-metadata-merge.ts +4 -7
- package/src/runtime/no-response.ts +13 -74
- package/src/runtime/routes/__tests__/acp-routes.test.ts +527 -0
- package/src/runtime/routes/__tests__/app-management-shared-apps.test.ts +74 -0
- package/src/runtime/routes/__tests__/contact-form-kinds.test.ts +22 -0
- package/src/runtime/routes/__tests__/contact-record-prompt-route.test.ts +303 -0
- package/src/runtime/routes/__tests__/monitoring-routes.test.ts +3 -4
- package/src/runtime/routes/__tests__/pending-interactions-route.test.ts +6 -0
- package/src/runtime/routes/__tests__/route-host-worker-routes.test.ts +3 -4
- package/src/runtime/routes/__tests__/schedule-worker-routes.test.ts +5 -6
- package/src/runtime/routes/acp-routes-list.test.ts +58 -0
- package/src/runtime/routes/acp-routes.ts +253 -47
- package/src/runtime/routes/app-management-routes.ts +80 -47
- package/src/runtime/routes/approval-routes.ts +9 -0
- package/src/runtime/routes/archive-utils.ts +6 -1
- package/src/runtime/routes/avatar-routes.ts +1 -1
- package/src/runtime/routes/channel-route-shared.ts +18 -20
- package/src/runtime/routes/contact-prompt-routes.ts +271 -105
- package/src/runtime/routes/conversation-routes.ts +41 -32
- package/src/runtime/routes/credential-prompt-routes.ts +10 -1
- package/src/runtime/routes/debug-bash-routes.ts +1 -0
- package/src/runtime/routes/guardian-expiry-sweep.ts +89 -31
- package/src/runtime/routes/guardian-form-routes.ts +115 -0
- package/src/runtime/routes/inbound-message-handler.ts +82 -29
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +16 -3
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +6 -0
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +6 -0
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +6 -1
- package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +50 -17
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +97 -94
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/integrations/slack/channel.ts +39 -18
- package/src/runtime/routes/migration-routes.ts +1 -3
- package/src/runtime/routes/monitoring-routes.ts +0 -3
- package/src/runtime/routes/oauth-commands-routes.ts +18 -5
- package/src/runtime/routes/route-host-worker-routes.ts +1 -1
- package/src/runtime/routes/schedule-worker-routes.ts +1 -1
- package/src/runtime/tool-grant-request-helper.ts +4 -0
- package/src/schedule/run-script.ts +1 -0
- package/src/schedule/scheduler.ts +1 -1
- package/src/schedule/worker-control.ts +2 -2
- package/src/security/oauth2.ts +2 -11
- package/src/security/secure-keys.ts +55 -8
- package/src/skills/catalog-install.ts +7 -4
- package/src/skills/clawhub.ts +1 -0
- package/src/skills/inline-command-runner.ts +1 -0
- package/src/stt/__tests__/room-noise-floor.test.ts +108 -0
- package/src/stt/room-noise-floor.ts +140 -0
- package/src/subagent/notify.ts +49 -3
- package/src/tools/__tests__/tool-input-schemas.test.ts +2 -0
- package/src/tools/acp/spawn.ts +2 -2
- package/src/tools/browser/__tests__/browser-status.test.ts +9 -0
- package/src/tools/browser/browser-execution.ts +10 -43
- package/src/tools/browser/browser-manager.ts +1 -0
- package/src/tools/browser/browser-status-constants.ts +6 -5
- package/src/tools/browser/runtime-check.ts +2 -0
- package/src/tools/channel/react-to-message.test.ts +193 -0
- package/src/tools/channel/react-to-message.ts +159 -0
- package/src/tools/credentials/metadata-store.ts +115 -19
- package/src/tools/credentials/store.ts +0 -2
- package/src/tools/filesystem/read.ts +2 -2
- package/src/tools/host-filesystem/read.ts +2 -2
- package/src/tools/host-terminal/host-shell.test.ts +24 -0
- package/src/tools/host-terminal/host-shell.ts +11 -38
- package/src/tools/shared/filesystem/file-ops-service.ts +10 -7
- package/src/tools/shared/shell-output.test.ts +104 -0
- package/src/tools/shared/shell-output.ts +112 -39
- package/src/tools/skills/sandbox-runner.ts +1 -0
- package/src/tools/system/request-permission.test.ts +34 -0
- package/src/tools/system/request-permission.ts +29 -5
- package/src/tools/terminal/shell.test.ts +63 -0
- package/src/tools/terminal/shell.ts +12 -44
- package/src/tools/tool-input-schemas.ts +2 -0
- package/src/tools/tool-manifest.ts +2 -0
- package/src/usage/attribution.ts +12 -4
- package/src/util/__tests__/worker-ownership.test.ts +77 -0
- package/src/util/__tests__/worker-process-command.test.ts +246 -1
- package/src/util/bun-runtime.ts +44 -13
- package/src/util/bundled-asset.ts +9 -1
- package/src/util/disk-usage.ts +1 -0
- package/src/util/file-use.ts +4 -1
- package/src/util/guardian-form-timeouts.ts +50 -0
- package/src/util/image-conversion.ts +1 -0
- package/src/util/oauth-request-body.test.ts +59 -0
- package/src/util/oauth-request-body.ts +56 -5
- package/src/util/platform.ts +69 -73
- package/src/util/process-table.ts +16 -5
- package/src/util/read-stdin.ts +5 -0
- package/src/util/spawn.ts +1 -0
- package/src/util/sqlite3-runtime.ts +25 -5
- package/src/util/worker-ownership.ts +193 -0
- package/src/util/worker-process.ts +375 -37
- package/src/workspace/git-service.ts +4 -12
- package/src/workspace/migrations/010-app-dir-rename.ts +1 -0
package/src/tools/acp/spawn.ts
CHANGED
|
@@ -111,7 +111,7 @@ export async function executeAcpSpawn(
|
|
|
111
111
|
// This failure raises the inline Connect card. Record it so the
|
|
112
112
|
// credential-prompt route only redirects a redundant secure-prompt when a
|
|
113
113
|
// card actually exists (not for a proactive prompt before any failure).
|
|
114
|
-
markAcpConnectCardRaised(context.conversationId);
|
|
114
|
+
markAcpConnectCardRaised(context.conversationId, agent);
|
|
115
115
|
}
|
|
116
116
|
return { content: msg, isError: true, ...(errorCode ? { errorCode } : {}) };
|
|
117
117
|
}
|
|
@@ -174,7 +174,7 @@ export async function executeAcpSpawn(
|
|
|
174
174
|
err instanceof Error
|
|
175
175
|
? err.message
|
|
176
176
|
: String((err as { message?: unknown }).message ?? err);
|
|
177
|
-
markAcpConnectCardRaised(context.conversationId);
|
|
177
|
+
markAcpConnectCardRaised(context.conversationId, agent);
|
|
178
178
|
return {
|
|
179
179
|
content: `${message}\n\n${ACP_AUTH_RECOVERY_GUIDANCE}`,
|
|
180
180
|
isError: true,
|
|
@@ -5,6 +5,7 @@ import type { ToolContext } from "../../types.js";
|
|
|
5
5
|
import {
|
|
6
6
|
BROWSER_STATUS_INPUT_FIELD,
|
|
7
7
|
BROWSER_STATUS_MODE,
|
|
8
|
+
CHROME_EXTENSION_INSTALL_HINT,
|
|
8
9
|
} from "../browser-status-constants.js";
|
|
9
10
|
import { CdpError } from "../cdp-client/errors.js";
|
|
10
11
|
|
|
@@ -214,6 +215,10 @@ describe("executeBrowserStatus", () => {
|
|
|
214
215
|
expect(extension.summary).toContain("no Chrome Extension is connected");
|
|
215
216
|
expect(extension.verified).toBe("preflight");
|
|
216
217
|
expect(extension.details.transport).toBe("extension-ws");
|
|
218
|
+
expect(extension.userActions).toEqual([
|
|
219
|
+
CHROME_EXTENSION_INSTALL_HINT,
|
|
220
|
+
"Tell the user to make sure a browser is open with the Vellum Chrome extension on.",
|
|
221
|
+
]);
|
|
217
222
|
});
|
|
218
223
|
|
|
219
224
|
test("probe failure diagnostics include remediation actions", async () => {
|
|
@@ -237,6 +242,10 @@ describe("executeBrowserStatus", () => {
|
|
|
237
242
|
expect(extension).toBeDefined();
|
|
238
243
|
expect(extension.available).toBe(false);
|
|
239
244
|
expect(extension.summary).toContain("probe failed");
|
|
245
|
+
expect(extension.userActions).toEqual([
|
|
246
|
+
CHROME_EXTENSION_INSTALL_HINT,
|
|
247
|
+
"Tell the user to make sure a browser is open with the Vellum Chrome extension on.",
|
|
248
|
+
]);
|
|
240
249
|
});
|
|
241
250
|
|
|
242
251
|
test("recommendation order follows auto candidate precedence with available extension", async () => {
|
|
@@ -39,7 +39,8 @@ import {
|
|
|
39
39
|
BROWSER_STATUS_MODES,
|
|
40
40
|
type BrowserStatusMode,
|
|
41
41
|
CDP_INSPECT_STATUS_DISCOVERY_CODE,
|
|
42
|
-
|
|
42
|
+
CHROME_EXTENSION_INSTALL_HINT,
|
|
43
|
+
CHROME_WEB_STORE_INSTALL_URL,
|
|
43
44
|
} from "./browser-status-constants.js";
|
|
44
45
|
import {
|
|
45
46
|
formatAxSnapshot,
|
|
@@ -296,6 +297,7 @@ export function parseBrowserMode(
|
|
|
296
297
|
const REMEDIATION_HINTS: Record<string, string[]> = {
|
|
297
298
|
// Extension backend
|
|
298
299
|
"extension:transport_error": [
|
|
300
|
+
CHROME_EXTENSION_INSTALL_HINT,
|
|
299
301
|
"Ensure the Vellum browser extension is installed and enabled, or that the macOS desktop client is running for host browser proxy mode.",
|
|
300
302
|
"For extension mode: check that the extension WebSocket connection is active (extension popup → status).",
|
|
301
303
|
"For macOS host browser proxy: verify the desktop client is running and has an active SSE connection to the assistant.",
|
|
@@ -335,11 +337,11 @@ const REMEDIATION_HINTS: Record<string, string[]> = {
|
|
|
335
337
|
"Ensure Chrome on the user's machine is on version 146 or higher (chrome://settings/help).",
|
|
336
338
|
'Ensure "Allow remote debugging for this browser instance" is toggled on at chrome://inspect/#remote-debugging.',
|
|
337
339
|
"Verify the desktop client is running and has an active SSE connection to the assistant.",
|
|
338
|
-
|
|
340
|
+
`Installing the Vellum Chrome extension is the preferred path and avoids the debug-port requirement: ${CHROME_WEB_STORE_INSTALL_URL}`,
|
|
339
341
|
],
|
|
340
342
|
"host-bridge:transport_error": [
|
|
341
343
|
"The desktop client could not reach Chrome's remote-debugging endpoint on the user's machine.",
|
|
342
|
-
|
|
344
|
+
`Ensure Chrome is running with remote debugging enabled, or install the Vellum Chrome extension (preferred): ${CHROME_WEB_STORE_INSTALL_URL}`,
|
|
343
345
|
"Verify the desktop client is running and connected.",
|
|
344
346
|
],
|
|
345
347
|
// Local/Playwright backend
|
|
@@ -2485,10 +2487,10 @@ function modeTradeoffs(mode: StatusCheckMode): string[] {
|
|
|
2485
2487
|
return MODE_TRADEOFFS[mode];
|
|
2486
2488
|
}
|
|
2487
2489
|
|
|
2488
|
-
function
|
|
2490
|
+
function extensionConnectionActions(): string[] {
|
|
2489
2491
|
return [
|
|
2490
|
-
|
|
2491
|
-
"
|
|
2492
|
+
CHROME_EXTENSION_INSTALL_HINT,
|
|
2493
|
+
"Tell the user to make sure a browser is open with the Vellum Chrome extension on.",
|
|
2492
2494
|
];
|
|
2493
2495
|
}
|
|
2494
2496
|
|
|
@@ -2517,49 +2519,14 @@ function extractDiscoveryCodes(error: CdpError): string[] {
|
|
|
2517
2519
|
return dedupeStrings(codes);
|
|
2518
2520
|
}
|
|
2519
2521
|
|
|
2520
|
-
function containsTokenCaseInsensitive(text: string, token: string): boolean {
|
|
2521
|
-
return text.toLowerCase().includes(token.toLowerCase());
|
|
2522
|
-
}
|
|
2523
|
-
|
|
2524
2522
|
function probeFailureActions(mode: StatusCheckMode, error: CdpError): string[] {
|
|
2525
2523
|
const actions: string[] = [];
|
|
2526
|
-
const message = error.message.toLowerCase();
|
|
2527
2524
|
const discoveryCodes = extractDiscoveryCodes(error).map((c) =>
|
|
2528
2525
|
c.toLowerCase(),
|
|
2529
2526
|
);
|
|
2530
2527
|
|
|
2531
2528
|
if (mode === BROWSER_STATUS_MODE.EXTENSION) {
|
|
2532
|
-
actions.push(...
|
|
2533
|
-
if (
|
|
2534
|
-
containsTokenCaseInsensitive(
|
|
2535
|
-
message,
|
|
2536
|
-
EXTENSION_STATUS_ERROR_MARKER.UNAUTHORIZED_ORIGIN,
|
|
2537
|
-
)
|
|
2538
|
-
) {
|
|
2539
|
-
actions.push(
|
|
2540
|
-
"Ensure this extension ID is present in chrome-extension-allowlist.local.json in $GATEWAY_SECURITY_DIR and restart the assistant.",
|
|
2541
|
-
);
|
|
2542
|
-
}
|
|
2543
|
-
if (
|
|
2544
|
-
containsTokenCaseInsensitive(
|
|
2545
|
-
message,
|
|
2546
|
-
EXTENSION_STATUS_ERROR_MARKER.NATIVE_MESSAGING_HOST,
|
|
2547
|
-
)
|
|
2548
|
-
) {
|
|
2549
|
-
actions.push(
|
|
2550
|
-
"Reinstall the native messaging host manifest and confirm it allows this extension ID.",
|
|
2551
|
-
);
|
|
2552
|
-
}
|
|
2553
|
-
if (
|
|
2554
|
-
containsTokenCaseInsensitive(
|
|
2555
|
-
message,
|
|
2556
|
-
EXTENSION_STATUS_ERROR_MARKER.HTTP_401,
|
|
2557
|
-
)
|
|
2558
|
-
) {
|
|
2559
|
-
actions.push(
|
|
2560
|
-
"Re-pair the extension so it refreshes its local relay credential.",
|
|
2561
|
-
);
|
|
2562
|
-
}
|
|
2529
|
+
actions.push(...extensionConnectionActions());
|
|
2563
2530
|
}
|
|
2564
2531
|
|
|
2565
2532
|
if (mode === BROWSER_STATUS_MODE.CDP_INSPECT) {
|
|
@@ -2651,7 +2618,7 @@ async function checkExtensionModeStatus(
|
|
|
2651
2618
|
autoCandidate,
|
|
2652
2619
|
summary:
|
|
2653
2620
|
"Extension mode is unavailable: no Chrome Extension is connected.",
|
|
2654
|
-
userActions:
|
|
2621
|
+
userActions: extensionConnectionActions(),
|
|
2655
2622
|
tradeoffs: modeTradeoffs(BROWSER_STATUS_MODE.EXTENSION),
|
|
2656
2623
|
details: { transport: "extension-ws" },
|
|
2657
2624
|
};
|
|
@@ -20,11 +20,12 @@ export const BROWSER_STATUS_INPUT_FIELD = {
|
|
|
20
20
|
CHECK_LOCAL_LAUNCH: "check_local_launch",
|
|
21
21
|
} as const;
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
/** Chrome Web Store listing for the Vellum Assistant browser extension. */
|
|
24
|
+
export const CHROME_WEB_STORE_INSTALL_URL =
|
|
25
|
+
"https://chromewebstore.google.com/detail/vellum-assistant-browser/hphbdmpffeigpcdjkckleobjmhhokpne";
|
|
26
|
+
|
|
27
|
+
/** User-facing install step for status userActions and command-failure hints. */
|
|
28
|
+
export const CHROME_EXTENSION_INSTALL_HINT = `Install the Vellum Assistant Chrome extension from the Chrome Web Store: ${CHROME_WEB_STORE_INSTALL_URL}`;
|
|
28
29
|
|
|
29
30
|
export const CDP_INSPECT_STATUS_DISCOVERY_CODE = {
|
|
30
31
|
NO_TARGETS: DEVTOOLS_DISCOVERY_CODE.NO_TARGETS,
|
|
@@ -104,6 +104,7 @@ export async function importPlaywright(): Promise<typeof import("playwright")> {
|
|
|
104
104
|
cwd: externalDir,
|
|
105
105
|
stdout: "pipe",
|
|
106
106
|
stderr: "pipe",
|
|
107
|
+
windowsHide: true,
|
|
107
108
|
});
|
|
108
109
|
const exitCode = await proc.exited;
|
|
109
110
|
if (exitCode !== 0) {
|
|
@@ -168,6 +169,7 @@ export async function ensureChromiumHeadlessShell(
|
|
|
168
169
|
stdout: "pipe",
|
|
169
170
|
stderr: "pipe",
|
|
170
171
|
cwd: externalPwExists ? externalDir : undefined,
|
|
172
|
+
windowsHide: true,
|
|
171
173
|
},
|
|
172
174
|
);
|
|
173
175
|
const timeoutMs = 120_000;
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import type { ReactionTarget } from "../../messaging/providers/channel-transport.js";
|
|
4
|
+
import type { ToolContext } from "../types.js";
|
|
5
|
+
|
|
6
|
+
let reactCalls: Array<{ channel: string; target: ReactionTarget }> = [];
|
|
7
|
+
let reactResultOk = true;
|
|
8
|
+
let channelSupported = true;
|
|
9
|
+
|
|
10
|
+
mock.module("../../messaging/providers/index.js", () => ({
|
|
11
|
+
supportsChannelReaction: () => channelSupported,
|
|
12
|
+
sendChannelReaction: async (channel: string, target: ReactionTarget) => {
|
|
13
|
+
reactCalls.push({ channel, target });
|
|
14
|
+
return { ok: reactResultOk };
|
|
15
|
+
},
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
import type { QueuedReactionRecord } from "../../daemon/reaction-record.js";
|
|
19
|
+
|
|
20
|
+
let queued: Array<{ conversationId: string; record: QueuedReactionRecord }> =
|
|
21
|
+
[];
|
|
22
|
+
let conversationResident = true;
|
|
23
|
+
mock.module("../../daemon/conversation-registry.js", () => ({
|
|
24
|
+
findConversationOrSubagent: (id: string) =>
|
|
25
|
+
conversationResident
|
|
26
|
+
? {
|
|
27
|
+
queueReactionRecord: (record: QueuedReactionRecord) =>
|
|
28
|
+
queued.push({ conversationId: id, record }),
|
|
29
|
+
}
|
|
30
|
+
: undefined,
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
let directPersists: Array<{
|
|
34
|
+
conversationId: string;
|
|
35
|
+
records: readonly QueuedReactionRecord[];
|
|
36
|
+
}> = [];
|
|
37
|
+
mock.module("../../daemon/reaction-record.js", () => ({
|
|
38
|
+
persistReactionRecords: async (
|
|
39
|
+
conversationId: string,
|
|
40
|
+
records: readonly QueuedReactionRecord[],
|
|
41
|
+
) => {
|
|
42
|
+
directPersists.push({ conversationId, records });
|
|
43
|
+
},
|
|
44
|
+
}));
|
|
45
|
+
|
|
46
|
+
const { reactToMessageTool } = await import("./react-to-message.js");
|
|
47
|
+
|
|
48
|
+
function channelContext(overrides: Partial<ToolContext> = {}): ToolContext {
|
|
49
|
+
return {
|
|
50
|
+
workingDir: "/tmp",
|
|
51
|
+
conversationId: "conv-1",
|
|
52
|
+
trustClass: "guardian",
|
|
53
|
+
executionChannel: "slack",
|
|
54
|
+
requesterChatId: "C123",
|
|
55
|
+
sourceMessageId: "1700000000.111111",
|
|
56
|
+
...overrides,
|
|
57
|
+
} as ToolContext;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
describe("react_to_message", () => {
|
|
61
|
+
beforeEach(() => {
|
|
62
|
+
reactCalls = [];
|
|
63
|
+
reactResultOk = true;
|
|
64
|
+
channelSupported = true;
|
|
65
|
+
queued = [];
|
|
66
|
+
directPersists = [];
|
|
67
|
+
conversationResident = true;
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("reacts to the triggering message by default", async () => {
|
|
71
|
+
const result = await reactToMessageTool.execute(
|
|
72
|
+
{ emoji: "thumbsup" },
|
|
73
|
+
channelContext(),
|
|
74
|
+
);
|
|
75
|
+
expect(result.isError).toBe(false);
|
|
76
|
+
expect(reactCalls).toHaveLength(1);
|
|
77
|
+
expect(reactCalls[0]).toEqual({
|
|
78
|
+
channel: "slack",
|
|
79
|
+
target: {
|
|
80
|
+
chatId: "C123",
|
|
81
|
+
messageId: "1700000000.111111",
|
|
82
|
+
emoji: "thumbsup",
|
|
83
|
+
action: "add",
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("an explicit messageId overrides the default target", async () => {
|
|
89
|
+
await reactToMessageTool.execute(
|
|
90
|
+
{ emoji: "tada", messageId: "1700000000.222222" },
|
|
91
|
+
channelContext(),
|
|
92
|
+
);
|
|
93
|
+
expect(reactCalls[0]?.target.messageId).toBe("1700000000.222222");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("the turn's thread coordinate rides only the default target", async () => {
|
|
97
|
+
await reactToMessageTool.execute(
|
|
98
|
+
{ emoji: "tada" },
|
|
99
|
+
channelContext({ sourceThreadId: "THREAD-1" }),
|
|
100
|
+
);
|
|
101
|
+
expect(reactCalls[0]?.target.threadId).toBe("THREAD-1");
|
|
102
|
+
|
|
103
|
+
await reactToMessageTool.execute(
|
|
104
|
+
{ emoji: "tada", messageId: "other-msg" },
|
|
105
|
+
channelContext({ sourceThreadId: "THREAD-1" }),
|
|
106
|
+
);
|
|
107
|
+
expect(reactCalls[1]?.target.threadId).toBeUndefined();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("remove action passes through and reports removal", async () => {
|
|
111
|
+
const result = await reactToMessageTool.execute(
|
|
112
|
+
{ emoji: "tada", action: "remove" },
|
|
113
|
+
channelContext(),
|
|
114
|
+
);
|
|
115
|
+
expect(reactCalls[0]?.target.action).toBe("remove");
|
|
116
|
+
expect(result.content).toContain("Removed");
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("errors without channel message context", async () => {
|
|
120
|
+
const result = await reactToMessageTool.execute(
|
|
121
|
+
{ emoji: "thumbsup" },
|
|
122
|
+
channelContext({ executionChannel: undefined }),
|
|
123
|
+
);
|
|
124
|
+
expect(result.isError).toBe(true);
|
|
125
|
+
expect(reactCalls).toHaveLength(0);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("errors on a channel without the capability", async () => {
|
|
129
|
+
channelSupported = false;
|
|
130
|
+
const result = await reactToMessageTool.execute(
|
|
131
|
+
{ emoji: "thumbsup" },
|
|
132
|
+
channelContext(),
|
|
133
|
+
);
|
|
134
|
+
expect(result.isError).toBe(true);
|
|
135
|
+
expect(result.content).toContain("does not support reactions");
|
|
136
|
+
expect(reactCalls).toHaveLength(0);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("a channel rejection reaches the model as an error", async () => {
|
|
140
|
+
reactResultOk = false;
|
|
141
|
+
const result = await reactToMessageTool.execute(
|
|
142
|
+
{ emoji: "thumbsup" },
|
|
143
|
+
channelContext(),
|
|
144
|
+
);
|
|
145
|
+
expect(result.isError).toBe(true);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("a delivered reaction queues its durable record on the conversation", async () => {
|
|
149
|
+
await reactToMessageTool.execute({ emoji: "thumbsup" }, channelContext());
|
|
150
|
+
expect(queued).toHaveLength(1);
|
|
151
|
+
expect(queued[0]).toEqual({
|
|
152
|
+
conversationId: "conv-1",
|
|
153
|
+
record: {
|
|
154
|
+
channel: "slack",
|
|
155
|
+
chatId: "C123",
|
|
156
|
+
messageId: "1700000000.111111",
|
|
157
|
+
emoji: "thumbsup",
|
|
158
|
+
op: "added",
|
|
159
|
+
provenanceTrustClass: "guardian",
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
expect(directPersists).toHaveLength(0);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("a removed reaction queues op removed", async () => {
|
|
166
|
+
await reactToMessageTool.execute(
|
|
167
|
+
{ emoji: "thumbsup", action: "remove" },
|
|
168
|
+
channelContext(),
|
|
169
|
+
);
|
|
170
|
+
expect(queued[0]?.record.op).toBe("removed");
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test("with no resident conversation the record persists directly", async () => {
|
|
174
|
+
conversationResident = false;
|
|
175
|
+
await reactToMessageTool.execute({ emoji: "thumbsup" }, channelContext());
|
|
176
|
+
expect(queued).toHaveLength(0);
|
|
177
|
+
expect(directPersists).toHaveLength(1);
|
|
178
|
+
expect(directPersists[0]?.records[0]?.emoji).toBe("thumbsup");
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test("a rejected reaction records nothing", async () => {
|
|
182
|
+
reactResultOk = false;
|
|
183
|
+
await reactToMessageTool.execute({ emoji: "thumbsup" }, channelContext());
|
|
184
|
+
expect(queued).toHaveLength(0);
|
|
185
|
+
expect(directPersists).toHaveLength(0);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("rejects input with no emoji", async () => {
|
|
189
|
+
const result = await reactToMessageTool.execute({}, channelContext());
|
|
190
|
+
expect(result.isError).toBe(true);
|
|
191
|
+
expect(reactCalls).toHaveLength(0);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `react_to_message`: the assistant reacts with an emoji on the current
|
|
3
|
+
* channel conversation.
|
|
4
|
+
*
|
|
5
|
+
* The producer half of the transport's `react` capability: the tool surface
|
|
6
|
+
* only offers this when the turn's channel transport implements `react`
|
|
7
|
+
* (`isToolActiveForContext` gates on `supportsChannelReaction`), so the
|
|
8
|
+
* model never sees the option on a channel that cannot honor it.
|
|
9
|
+
*
|
|
10
|
+
* The default target is the message that triggered the turn, which is the
|
|
11
|
+
* one id every channel turn carries (`ToolContext.sourceMessageId`); an
|
|
12
|
+
* explicit `messageId` in the channel's own id space overrides it for
|
|
13
|
+
* callers that hold one (a skill working from platform APIs).
|
|
14
|
+
*/
|
|
15
|
+
import { z } from "zod";
|
|
16
|
+
|
|
17
|
+
import { parseChannelId } from "../../channels/types.js";
|
|
18
|
+
import { findConversationOrSubagent } from "../../daemon/conversation-registry.js";
|
|
19
|
+
import { persistReactionRecords } from "../../daemon/reaction-record.js";
|
|
20
|
+
import {
|
|
21
|
+
sendChannelReaction,
|
|
22
|
+
supportsChannelReaction,
|
|
23
|
+
} from "../../messaging/providers/index.js";
|
|
24
|
+
import { RiskLevel } from "../../permissions/types.js";
|
|
25
|
+
import {
|
|
26
|
+
invalidToolInputResult,
|
|
27
|
+
toToolInputSchema,
|
|
28
|
+
} from "../shared/zod-tool-schema.js";
|
|
29
|
+
import type {
|
|
30
|
+
ToolContext,
|
|
31
|
+
ToolDefinition,
|
|
32
|
+
ToolExecutionResult,
|
|
33
|
+
} from "../types.js";
|
|
34
|
+
|
|
35
|
+
export const reactToMessageInputSchema = z.looseObject({
|
|
36
|
+
emoji: z
|
|
37
|
+
.string()
|
|
38
|
+
.min(1)
|
|
39
|
+
.describe(
|
|
40
|
+
"The emoji, in this channel's own form: a Slack emoji name like " +
|
|
41
|
+
"thumbsup (colons optional), a unicode emoji on other channels.",
|
|
42
|
+
),
|
|
43
|
+
action: z
|
|
44
|
+
.enum(["add", "remove"])
|
|
45
|
+
.describe("Add the reaction (default) or remove one you added earlier.")
|
|
46
|
+
.optional()
|
|
47
|
+
.catch(undefined),
|
|
48
|
+
messageId: z
|
|
49
|
+
.string()
|
|
50
|
+
.min(1)
|
|
51
|
+
.describe(
|
|
52
|
+
"Target message id in the channel's own id space. Omit to react to " +
|
|
53
|
+
"the message that started this turn, which is the usual case.",
|
|
54
|
+
)
|
|
55
|
+
.optional()
|
|
56
|
+
.catch(undefined),
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const DESCRIPTION =
|
|
60
|
+
"React with an emoji on the current channel conversation, the way a " +
|
|
61
|
+
"person would tap a reaction instead of writing a reply. By default the " +
|
|
62
|
+
"reaction lands on the message that started this turn. Use it to " +
|
|
63
|
+
"acknowledge without adding a message; it does not replace answering " +
|
|
64
|
+
"when an answer is called for. When the reaction is your whole reply, " +
|
|
65
|
+
"output exactly <no_response/> afterward so no message is posted.";
|
|
66
|
+
|
|
67
|
+
export const reactToMessageTool = {
|
|
68
|
+
name: "react_to_message",
|
|
69
|
+
description: DESCRIPTION,
|
|
70
|
+
category: "interaction",
|
|
71
|
+
executionTarget: "sandbox",
|
|
72
|
+
defaultRiskLevel: RiskLevel.Low,
|
|
73
|
+
input_schema: toToolInputSchema(reactToMessageInputSchema),
|
|
74
|
+
|
|
75
|
+
async execute(
|
|
76
|
+
input: Record<string, unknown>,
|
|
77
|
+
context: ToolContext,
|
|
78
|
+
): Promise<ToolExecutionResult> {
|
|
79
|
+
const parsed = reactToMessageInputSchema.safeParse(input);
|
|
80
|
+
if (!parsed.success) {
|
|
81
|
+
return invalidToolInputResult("react_to_message", parsed.error);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const channel = context.executionChannel;
|
|
85
|
+
const chatId = context.requesterChatId;
|
|
86
|
+
const messageId = parsed.data.messageId ?? context.sourceMessageId;
|
|
87
|
+
if (!channel || !chatId || !messageId) {
|
|
88
|
+
return {
|
|
89
|
+
content:
|
|
90
|
+
"Reactions are only available in a channel conversation with a " +
|
|
91
|
+
"message to react to; this turn has no channel message context.",
|
|
92
|
+
isError: true,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
if (!supportsChannelReaction(channel)) {
|
|
96
|
+
return {
|
|
97
|
+
content: `The ${channel} channel does not support reactions.`,
|
|
98
|
+
isError: true,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const action = parsed.data.action ?? "add";
|
|
103
|
+
// The turn's thread coordinate belongs only to the turn's own message;
|
|
104
|
+
// an explicit messageId may live elsewhere, so it travels bare.
|
|
105
|
+
const threadId =
|
|
106
|
+
parsed.data.messageId === undefined ? context.sourceThreadId : undefined;
|
|
107
|
+
const result = await sendChannelReaction(channel, {
|
|
108
|
+
chatId,
|
|
109
|
+
messageId,
|
|
110
|
+
...(threadId ? { threadId } : {}),
|
|
111
|
+
emoji: parsed.data.emoji,
|
|
112
|
+
action,
|
|
113
|
+
});
|
|
114
|
+
if (!result.ok) {
|
|
115
|
+
return {
|
|
116
|
+
content:
|
|
117
|
+
`Could not ${action} the ${parsed.data.emoji} reaction; the ` +
|
|
118
|
+
"channel rejected it.",
|
|
119
|
+
isError: true,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// The delivered reaction becomes a durable row, the same canonical
|
|
124
|
+
// fact inbound reactions store. Queued on the live conversation and
|
|
125
|
+
// drained by the agent loop at the turn boundary, never written here:
|
|
126
|
+
// an assistant row inserted between this call's tool_use and its
|
|
127
|
+
// tool_result would break the pairing history repair enforces, and a
|
|
128
|
+
// reload would read the reaction as having failed. The rare turn with
|
|
129
|
+
// no resident conversation persists directly, trading perfect row
|
|
130
|
+
// ordering for durability.
|
|
131
|
+
const sourceChannel = parseChannelId(channel);
|
|
132
|
+
if (sourceChannel) {
|
|
133
|
+
const record = {
|
|
134
|
+
channel: sourceChannel,
|
|
135
|
+
chatId,
|
|
136
|
+
messageId,
|
|
137
|
+
emoji: parsed.data.emoji,
|
|
138
|
+
op: action === "remove" ? ("removed" as const) : ("added" as const),
|
|
139
|
+
...(context.trustClass
|
|
140
|
+
? { provenanceTrustClass: context.trustClass }
|
|
141
|
+
: {}),
|
|
142
|
+
};
|
|
143
|
+
const conversation = findConversationOrSubagent(context.conversationId);
|
|
144
|
+
if (conversation) {
|
|
145
|
+
conversation.queueReactionRecord(record);
|
|
146
|
+
} else {
|
|
147
|
+
await persistReactionRecords(context.conversationId, [record]);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
content:
|
|
153
|
+
action === "remove"
|
|
154
|
+
? `Removed the ${parsed.data.emoji} reaction.`
|
|
155
|
+
: `Reacted with ${parsed.data.emoji}.`,
|
|
156
|
+
isError: false,
|
|
157
|
+
};
|
|
158
|
+
},
|
|
159
|
+
} satisfies ToolDefinition;
|