@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
|
@@ -72,6 +72,7 @@ mock.module("../../../contacts/contact-store.js", () => ({
|
|
|
72
72
|
let recordInboundCalls: Array<{ conversationId?: string }> = [];
|
|
73
73
|
let recordedEvent: { eventId: string; conversationId: string } | null = null;
|
|
74
74
|
let outboundTargetConversationId: string | null = null;
|
|
75
|
+
let outboundLookupChannels: string[] = [];
|
|
75
76
|
let storedTarget: { messageId: string; conversationId: string } | null = null;
|
|
76
77
|
mock.module("../../../persistence/delivery-crud.js", () => ({
|
|
77
78
|
recordInbound: (
|
|
@@ -89,7 +90,10 @@ mock.module("../../../persistence/delivery-crud.js", () => ({
|
|
|
89
90
|
};
|
|
90
91
|
},
|
|
91
92
|
findMessageBySourceId: () => storedTarget,
|
|
92
|
-
|
|
93
|
+
findConversationByProviderMessageId: (sourceChannel: string) => {
|
|
94
|
+
outboundLookupChannels.push(sourceChannel);
|
|
95
|
+
return outboundTargetConversationId;
|
|
96
|
+
},
|
|
93
97
|
findInboundEvent: () => recordedEvent,
|
|
94
98
|
clearPayload: () => {},
|
|
95
99
|
linkMessage: () => {},
|
|
@@ -134,7 +138,7 @@ mock.module("./guardian-reply-intercept.js", () => ({
|
|
|
134
138
|
},
|
|
135
139
|
}));
|
|
136
140
|
|
|
137
|
-
import {
|
|
141
|
+
import { handleReactionIntercept } from "./reaction-intercept.js";
|
|
138
142
|
|
|
139
143
|
// ---------------------------------------------------------------------------
|
|
140
144
|
// Fixtures
|
|
@@ -178,7 +182,11 @@ function buildParams(overrides: {
|
|
|
178
182
|
}) {
|
|
179
183
|
msgCounter++;
|
|
180
184
|
return {
|
|
181
|
-
|
|
185
|
+
reaction: {
|
|
186
|
+
op: "added" as const,
|
|
187
|
+
emoji: "white_check_mark",
|
|
188
|
+
targetMessageId: "1700000000.1",
|
|
189
|
+
},
|
|
182
190
|
sourceChannel: "slack" as const,
|
|
183
191
|
sourceInterface: "slack" as const,
|
|
184
192
|
conversationExternalId: SLACK_CHANNEL_ID,
|
|
@@ -217,6 +225,7 @@ describe("reaction intercept consumes the stamped verdict directly", () => {
|
|
|
217
225
|
recordInboundCalls = [];
|
|
218
226
|
recordedEvent = null;
|
|
219
227
|
outboundTargetConversationId = null;
|
|
228
|
+
outboundLookupChannels = [];
|
|
220
229
|
addMessageCalls = 0;
|
|
221
230
|
guardianReplyCalls = [];
|
|
222
231
|
guardianReplyResponse = undefined;
|
|
@@ -226,7 +235,7 @@ describe("reaction intercept consumes the stamped verdict directly", () => {
|
|
|
226
235
|
test("guardian verdict routes the reaction into the approval decision pipeline", async () => {
|
|
227
236
|
guardianReplyResponse = { accepted: true, canonicalRouter: "applied" };
|
|
228
237
|
|
|
229
|
-
const result = await
|
|
238
|
+
const result = await handleReactionIntercept(
|
|
230
239
|
buildParams({
|
|
231
240
|
rawSenderId: GUARDIAN_USER_ID,
|
|
232
241
|
trustVerdict: GUARDIAN_VERDICT,
|
|
@@ -242,7 +251,7 @@ describe("reaction intercept consumes the stamped verdict directly", () => {
|
|
|
242
251
|
});
|
|
243
252
|
|
|
244
253
|
test("contact verdict records the reaction; the decision pipeline self-gate ignores it", async () => {
|
|
245
|
-
const result = await
|
|
254
|
+
const result = await handleReactionIntercept(
|
|
246
255
|
buildParams({
|
|
247
256
|
rawSenderId: MEMBER_USER_ID,
|
|
248
257
|
trustVerdict: MEMBER_VERDICT,
|
|
@@ -261,12 +270,12 @@ describe("reaction intercept consumes the stamped verdict directly", () => {
|
|
|
261
270
|
expect(recordInboundCalls).toEqual([{ conversationId: "conv-target" }]);
|
|
262
271
|
});
|
|
263
272
|
|
|
264
|
-
test("a reaction on the assistant's own post resolves through
|
|
265
|
-
// Outbound posts open no inbound event, so the
|
|
273
|
+
test("a reaction on the assistant's own post resolves through its stored envelope", async () => {
|
|
274
|
+
// Outbound posts open no inbound event, so the id is only on the row.
|
|
266
275
|
storedTarget = null;
|
|
267
276
|
outboundTargetConversationId = "conv-assistant-post";
|
|
268
277
|
|
|
269
|
-
const result = await
|
|
278
|
+
const result = await handleReactionIntercept(
|
|
270
279
|
buildParams({
|
|
271
280
|
rawSenderId: MEMBER_USER_ID,
|
|
272
281
|
trustVerdict: MEMBER_VERDICT,
|
|
@@ -278,12 +287,36 @@ describe("reaction intercept consumes the stamped verdict directly", () => {
|
|
|
278
287
|
]);
|
|
279
288
|
expect(addMessageCalls).toBe(1);
|
|
280
289
|
expect(result).toMatchObject({ accepted: true, duplicate: false });
|
|
290
|
+
// The outbound-row lookup is scoped to the reaction's own channel: a
|
|
291
|
+
// Slack reaction never scans another channel's conversations.
|
|
292
|
+
expect(outboundLookupChannels).toEqual(["slack"]);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
test("a non-Slack reaction resolves the assistant's post on its own channel", async () => {
|
|
296
|
+
storedTarget = null;
|
|
297
|
+
outboundTargetConversationId = "conv-discord-post";
|
|
298
|
+
|
|
299
|
+
const params = {
|
|
300
|
+
...buildParams({
|
|
301
|
+
rawSenderId: MEMBER_USER_ID,
|
|
302
|
+
trustVerdict: MEMBER_VERDICT,
|
|
303
|
+
}),
|
|
304
|
+
sourceChannel: "discord" as const,
|
|
305
|
+
sourceInterface: undefined,
|
|
306
|
+
};
|
|
307
|
+
const result = await handleReactionIntercept(params);
|
|
308
|
+
|
|
309
|
+
expect(outboundLookupChannels).toEqual(["discord"]);
|
|
310
|
+
expect(recordInboundCalls).toEqual([
|
|
311
|
+
{ conversationId: "conv-discord-post" },
|
|
312
|
+
]);
|
|
313
|
+
expect(result).toMatchObject({ accepted: true, duplicate: false });
|
|
281
314
|
});
|
|
282
315
|
|
|
283
316
|
test("a reaction on a message that is not stored is dropped without minting", async () => {
|
|
284
317
|
storedTarget = null;
|
|
285
318
|
|
|
286
|
-
const result = await
|
|
319
|
+
const result = await handleReactionIntercept(
|
|
287
320
|
buildParams({
|
|
288
321
|
rawSenderId: MEMBER_USER_ID,
|
|
289
322
|
trustVerdict: MEMBER_VERDICT,
|
|
@@ -298,7 +331,7 @@ describe("reaction intercept consumes the stamped verdict directly", () => {
|
|
|
298
331
|
test("a redelivered reaction never reaches the guardian rail twice", async () => {
|
|
299
332
|
recordedEvent = { eventId: "evt-1", conversationId: "conv-target" };
|
|
300
333
|
|
|
301
|
-
const result = await
|
|
334
|
+
const result = await handleReactionIntercept(
|
|
302
335
|
buildParams({
|
|
303
336
|
rawSenderId: GUARDIAN_USER_ID,
|
|
304
337
|
trustVerdict: GUARDIAN_VERDICT,
|
|
@@ -319,7 +352,7 @@ describe("reaction intercept consumes the stamped verdict directly", () => {
|
|
|
319
352
|
storedTarget = null;
|
|
320
353
|
guardianReplyResponse = { accepted: true, canonicalRouter: "applied" };
|
|
321
354
|
|
|
322
|
-
const result = await
|
|
355
|
+
const result = await handleReactionIntercept(
|
|
323
356
|
buildParams({
|
|
324
357
|
rawSenderId: GUARDIAN_USER_ID,
|
|
325
358
|
trustVerdict: GUARDIAN_VERDICT,
|
|
@@ -331,21 +364,21 @@ describe("reaction intercept consumes the stamped verdict directly", () => {
|
|
|
331
364
|
});
|
|
332
365
|
|
|
333
366
|
test("unknown verdict is dropped before any write", async () => {
|
|
334
|
-
const result = await
|
|
367
|
+
const result = await handleReactionIntercept(
|
|
335
368
|
buildParams({ rawSenderId: "U_STRANGER", trustVerdict: UNKNOWN_VERDICT }),
|
|
336
369
|
);
|
|
337
370
|
expectDropped(result);
|
|
338
371
|
});
|
|
339
372
|
|
|
340
373
|
test("missing verdict is dropped fail-closed", async () => {
|
|
341
|
-
const result = await
|
|
374
|
+
const result = await handleReactionIntercept(
|
|
342
375
|
buildParams({ rawSenderId: MEMBER_USER_ID }),
|
|
343
376
|
);
|
|
344
377
|
expectDropped(result);
|
|
345
378
|
});
|
|
346
379
|
|
|
347
380
|
test("resolutionFailed verdict is dropped fail-closed even with a guardian shape", async () => {
|
|
348
|
-
const result = await
|
|
381
|
+
const result = await handleReactionIntercept(
|
|
349
382
|
buildParams({
|
|
350
383
|
rawSenderId: GUARDIAN_USER_ID,
|
|
351
384
|
trustVerdict: { ...GUARDIAN_VERDICT, resolutionFailed: true },
|
|
@@ -355,7 +388,7 @@ describe("reaction intercept consumes the stamped verdict directly", () => {
|
|
|
355
388
|
});
|
|
356
389
|
|
|
357
390
|
test("memberless guardian verdict is contradictory and dropped fail-closed", async () => {
|
|
358
|
-
const result = await
|
|
391
|
+
const result = await handleReactionIntercept(
|
|
359
392
|
buildParams({
|
|
360
393
|
rawSenderId: GUARDIAN_USER_ID,
|
|
361
394
|
trustVerdict: {
|
|
@@ -370,13 +403,13 @@ describe("reaction intercept consumes the stamped verdict directly", () => {
|
|
|
370
403
|
|
|
371
404
|
test("classification is verdict-only: no IPC, cache, or local-store reads", async () => {
|
|
372
405
|
guardianReplyResponse = { accepted: true, canonicalRouter: "applied" };
|
|
373
|
-
await
|
|
406
|
+
await handleReactionIntercept(
|
|
374
407
|
buildParams({
|
|
375
408
|
rawSenderId: GUARDIAN_USER_ID,
|
|
376
409
|
trustVerdict: GUARDIAN_VERDICT,
|
|
377
410
|
}),
|
|
378
411
|
);
|
|
379
|
-
await
|
|
412
|
+
await handleReactionIntercept(
|
|
380
413
|
buildParams({
|
|
381
414
|
rawSenderId: MEMBER_USER_ID,
|
|
382
415
|
trustVerdict: MEMBER_VERDICT,
|
|
@@ -20,25 +20,35 @@
|
|
|
20
20
|
* `unknown` (drop). Reactions never drive an agent turn.
|
|
21
21
|
*/
|
|
22
22
|
import type { SourceMetadata } from "@vellumai/gateway-client";
|
|
23
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
type InboundReactionPayload,
|
|
25
|
+
resolveInboundEventKind,
|
|
26
|
+
} from "@vellumai/gateway-client";
|
|
24
27
|
|
|
25
28
|
import type { ChannelId, InterfaceId } from "../../../channels/types.js";
|
|
29
|
+
import { findConversation } from "../../../daemon/conversation-registry.js";
|
|
26
30
|
import { getDiskPressureStatus } from "../../../daemon/disk-pressure-guard.js";
|
|
27
31
|
import { classifyDiskPressureTurnPolicy } from "../../../daemon/disk-pressure-policy.js";
|
|
32
|
+
import type { TrustContext } from "../../../daemon/trust-context-types.js";
|
|
33
|
+
import { writeSlackMetadata } from "../../../messaging/providers/slack/message-metadata.js";
|
|
34
|
+
import {
|
|
35
|
+
buildNeutralReactionMeta,
|
|
36
|
+
buildSlackReactionMeta,
|
|
37
|
+
} from "../../../messaging/reaction-envelopes.js";
|
|
28
38
|
import {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
} from "../../../
|
|
32
|
-
import { addMessage } from "../../../persistence/conversation-crud.js";
|
|
39
|
+
addMessage,
|
|
40
|
+
provenanceFromTrustContext,
|
|
41
|
+
} from "../../../persistence/conversation-crud.js";
|
|
33
42
|
import {
|
|
43
|
+
findConversationByProviderMessageId,
|
|
34
44
|
findInboundEvent,
|
|
35
45
|
findMessageBySourceId,
|
|
36
|
-
findSlackConversationByMessageTs,
|
|
37
46
|
linkMessage,
|
|
38
47
|
recordInbound,
|
|
39
48
|
} from "../../../persistence/delivery-crud.js";
|
|
40
49
|
import { markProcessed } from "../../../persistence/delivery-status.js";
|
|
41
50
|
import { getLogger } from "../../../util/logger.js";
|
|
51
|
+
import { toTrustContext } from "../../actor-trust-resolver.js";
|
|
42
52
|
import type { ApprovalConversationGenerator } from "../../http-types.js";
|
|
43
53
|
import {
|
|
44
54
|
actorTrustContextFromVerdict,
|
|
@@ -49,56 +59,24 @@ import { handleGuardianReplyIntercept } from "./guardian-reply-intercept.js";
|
|
|
49
59
|
const log = getLogger("runtime-http");
|
|
50
60
|
|
|
51
61
|
/**
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* so the daemon can route reactions to this dedicated stage instead of the
|
|
58
|
-
* agent-response pipeline.
|
|
62
|
+
* Whether the inbound payload belongs to the reaction event family, on any
|
|
63
|
+
* channel. Family membership is the kind alone; whether the payload can be
|
|
64
|
+
* acted on is `resolveInboundReactionPayload`'s answer, and the dispatch
|
|
65
|
+
* drops a family member whose payload does not resolve rather than letting
|
|
66
|
+
* it be reinterpreted as a message.
|
|
59
67
|
*/
|
|
60
|
-
export function
|
|
61
|
-
sourceChannel?: string;
|
|
68
|
+
export function isReactionEvent(body: {
|
|
62
69
|
eventKind?: string;
|
|
70
|
+
isEdit?: boolean;
|
|
63
71
|
callbackData?: string;
|
|
72
|
+
callbackQueryId?: string;
|
|
64
73
|
}): boolean {
|
|
65
|
-
|
|
66
|
-
// whose reactions are ingested today, and widening this is a per-channel
|
|
67
|
-
// decision made with that channel's reaction wire shape, not a fallthrough.
|
|
68
|
-
return (
|
|
69
|
-
body.sourceChannel === "slack" &&
|
|
70
|
-
resolveInboundEventKind(body) === "reaction"
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Parse a reaction `callbackData` string into its op (added/removed) and
|
|
76
|
-
* emoji name. Returns `null` when the input is not a reaction prefix or
|
|
77
|
-
* when the emoji portion is empty.
|
|
78
|
-
*/
|
|
79
|
-
export function parseSlackReactionCallbackData(
|
|
80
|
-
callbackData: string,
|
|
81
|
-
): { op: "added" | "removed"; emoji: string } | null {
|
|
82
|
-
let op: "added" | "removed";
|
|
83
|
-
let emoji: string;
|
|
84
|
-
if (callbackData.startsWith("reaction_removed:")) {
|
|
85
|
-
op = "removed";
|
|
86
|
-
emoji = callbackData.slice("reaction_removed:".length);
|
|
87
|
-
} else if (callbackData.startsWith("reaction:")) {
|
|
88
|
-
op = "added";
|
|
89
|
-
emoji = callbackData.slice("reaction:".length);
|
|
90
|
-
} else {
|
|
91
|
-
return null;
|
|
92
|
-
}
|
|
93
|
-
if (emoji.length === 0) {
|
|
94
|
-
return null;
|
|
95
|
-
}
|
|
96
|
-
return { op, emoji };
|
|
74
|
+
return resolveInboundEventKind(body) === "reaction";
|
|
97
75
|
}
|
|
98
76
|
|
|
99
77
|
export interface ReactionInterceptParams {
|
|
100
|
-
/** The
|
|
101
|
-
|
|
78
|
+
/** The resolved structured payload: op, emoji, and target message id. */
|
|
79
|
+
reaction: InboundReactionPayload;
|
|
102
80
|
sourceChannel: ChannelId;
|
|
103
81
|
sourceInterface: InterfaceId | undefined;
|
|
104
82
|
conversationExternalId: string;
|
|
@@ -113,15 +91,15 @@ export interface ReactionInterceptParams {
|
|
|
113
91
|
}
|
|
114
92
|
|
|
115
93
|
/**
|
|
116
|
-
* Handle a
|
|
117
|
-
* caller dispatches here only for `
|
|
94
|
+
* Handle a reaction event end to end, on any channel. Always consumes the event (the
|
|
95
|
+
* caller dispatches here only for `isReactionEvent`), returning the
|
|
118
96
|
* response the top-level handler should short-circuit with.
|
|
119
97
|
*/
|
|
120
|
-
export async function
|
|
98
|
+
export async function handleReactionIntercept(
|
|
121
99
|
params: ReactionInterceptParams,
|
|
122
100
|
): Promise<Record<string, unknown>> {
|
|
123
101
|
const {
|
|
124
|
-
|
|
102
|
+
reaction,
|
|
125
103
|
sourceChannel,
|
|
126
104
|
sourceInterface,
|
|
127
105
|
conversationExternalId,
|
|
@@ -162,10 +140,7 @@ export async function handleSlackReactionIntercept(
|
|
|
162
140
|
return { accepted: true, reaction: "dropped_unknown_actor" };
|
|
163
141
|
}
|
|
164
142
|
|
|
165
|
-
const reactedMessageTs =
|
|
166
|
-
typeof sourceMetadata?.messageId === "string"
|
|
167
|
-
? sourceMetadata.messageId
|
|
168
|
-
: undefined;
|
|
143
|
+
const reactedMessageTs = reaction.targetMessageId;
|
|
169
144
|
// Respect disk-pressure cleanup so reactions don't bypass storage
|
|
170
145
|
// protection. Guardians resolve to `allow-cleanup-mode` (not `block`), so a
|
|
171
146
|
// guardian's approval-by-reaction still flows. Blocked silently and before
|
|
@@ -210,12 +185,13 @@ export async function handleSlackReactionIntercept(
|
|
|
210
185
|
// `reaction_removed:` never does. `handleGuardianReplyIntercept` self-gates
|
|
211
186
|
// on `trustClass === "guardian"`, so a contact's reaction returns no
|
|
212
187
|
// response and falls through to persistence.
|
|
213
|
-
if (
|
|
188
|
+
if (reaction.op === "added" && replyCallbackUrl) {
|
|
214
189
|
const reactionIntercept = await handleGuardianReplyIntercept({
|
|
215
190
|
isDuplicate: false,
|
|
216
191
|
trimmedContent: "",
|
|
217
192
|
hasCallbackData: true,
|
|
218
|
-
callbackData,
|
|
193
|
+
callbackData: undefined,
|
|
194
|
+
reaction,
|
|
219
195
|
reactedMessageTs,
|
|
220
196
|
rawSenderId,
|
|
221
197
|
canonicalSenderId,
|
|
@@ -239,14 +215,16 @@ export async function handleSlackReactionIntercept(
|
|
|
239
215
|
// reaction is dropped rather than given a conversation of its own.
|
|
240
216
|
// Inbound messages carry their provider id on the event that delivered
|
|
241
217
|
// them. The assistant's own posts open no inbound event, so a reaction on
|
|
242
|
-
// one is resolved through the
|
|
218
|
+
// one is resolved through the envelope those rows carry (`slackMeta` on
|
|
219
|
+
// Slack, the neutral `providerMeta` everywhere else).
|
|
243
220
|
const targetConversationId = reactedMessageTs
|
|
244
221
|
? (findMessageBySourceId(
|
|
245
222
|
sourceChannel,
|
|
246
223
|
conversationExternalId,
|
|
247
224
|
reactedMessageTs,
|
|
248
225
|
)?.conversationId ??
|
|
249
|
-
|
|
226
|
+
findConversationByProviderMessageId(
|
|
227
|
+
sourceChannel,
|
|
250
228
|
conversationExternalId,
|
|
251
229
|
reactedMessageTs,
|
|
252
230
|
))
|
|
@@ -272,15 +250,24 @@ export async function handleSlackReactionIntercept(
|
|
|
272
250
|
|
|
273
251
|
// Record the reaction as an inline transcript signal.
|
|
274
252
|
try {
|
|
275
|
-
await
|
|
253
|
+
await persistReactionAsMessage({
|
|
276
254
|
conversationId: result.conversationId,
|
|
277
255
|
conversationExternalId,
|
|
278
256
|
eventId: result.eventId,
|
|
279
|
-
|
|
257
|
+
sourceChannel,
|
|
258
|
+
reaction,
|
|
280
259
|
actorDisplayName,
|
|
260
|
+
actorExternalId: canonicalSenderId ?? rawSenderId ?? undefined,
|
|
281
261
|
reactedMessageTs,
|
|
282
262
|
duplicate: result.duplicate,
|
|
263
|
+
trustCtx: toTrustContext(trustCtx, conversationExternalId),
|
|
283
264
|
});
|
|
265
|
+
if (!result.duplicate) {
|
|
266
|
+
// Reactions never drive a turn, so a resident conversation would
|
|
267
|
+
// otherwise carry the new row only after eviction. Marking it stale
|
|
268
|
+
// makes the next turn's history reload pick the reaction up.
|
|
269
|
+
findConversation(result.conversationId)?.markHistoryStale();
|
|
270
|
+
}
|
|
284
271
|
} catch (err) {
|
|
285
272
|
log.error(
|
|
286
273
|
{ err, conversationId: result.conversationId, eventId: result.eventId },
|
|
@@ -305,53 +292,66 @@ export async function handleSlackReactionIntercept(
|
|
|
305
292
|
* deduplication and conversation resolution have happened. Duplicate inbound
|
|
306
293
|
* events are skipped here to keep persistence idempotent.
|
|
307
294
|
*/
|
|
308
|
-
async function
|
|
295
|
+
async function persistReactionAsMessage(params: {
|
|
309
296
|
conversationId: string;
|
|
310
297
|
conversationExternalId: string;
|
|
311
298
|
eventId: string;
|
|
312
|
-
|
|
299
|
+
sourceChannel: ChannelId;
|
|
300
|
+
reaction: InboundReactionPayload;
|
|
313
301
|
actorDisplayName?: string;
|
|
302
|
+
/**
|
|
303
|
+
* The reactor's stable channel identity, canonical when resolution
|
|
304
|
+
* succeeded. Persisted as the envelope's actor id: a display name is
|
|
305
|
+
* sender-controlled labeling, and the rendered history line attributes
|
|
306
|
+
* its fenced content by this id (`origin`), never by the label.
|
|
307
|
+
*/
|
|
308
|
+
actorExternalId?: string;
|
|
314
309
|
reactedMessageTs: string;
|
|
315
310
|
duplicate: boolean;
|
|
311
|
+
/**
|
|
312
|
+
* The reactor's trust context. Persisted as row provenance so actor-scoped
|
|
313
|
+
* history loads keep the row: `filterMessagesForUntrustedActor` drops any
|
|
314
|
+
* row with no `provenanceTrustClass`, which would hide every reaction from
|
|
315
|
+
* non-guardian turns.
|
|
316
|
+
*/
|
|
317
|
+
trustCtx: TrustContext;
|
|
316
318
|
}): Promise<void> {
|
|
317
319
|
if (params.duplicate) {
|
|
318
320
|
return;
|
|
319
321
|
}
|
|
322
|
+
const facts = {
|
|
323
|
+
channel: params.sourceChannel,
|
|
324
|
+
chatId: params.conversationExternalId,
|
|
325
|
+
targetMessageId: params.reactedMessageTs,
|
|
326
|
+
emoji: params.reaction.emoji,
|
|
327
|
+
op: params.reaction.op,
|
|
328
|
+
...(params.actorExternalId
|
|
329
|
+
? { actorExternalId: params.actorExternalId }
|
|
330
|
+
: {}),
|
|
331
|
+
...(params.actorDisplayName
|
|
332
|
+
? { actorDisplayName: params.actorDisplayName }
|
|
333
|
+
: {}),
|
|
334
|
+
};
|
|
320
335
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
336
|
+
if (params.sourceChannel !== "slack") {
|
|
337
|
+
const persisted = await addMessage(
|
|
338
|
+
params.conversationId,
|
|
339
|
+
"user",
|
|
340
|
+
"[reaction]",
|
|
324
341
|
{
|
|
325
|
-
|
|
326
|
-
|
|
342
|
+
metadata: {
|
|
343
|
+
...provenanceFromTrustContext(params.trustCtx),
|
|
344
|
+
providerMeta: JSON.stringify(buildNeutralReactionMeta(facts)),
|
|
345
|
+
},
|
|
346
|
+
skipIndexing: true,
|
|
327
347
|
},
|
|
328
|
-
"Skipping reaction persistence: unparseable callbackData",
|
|
329
348
|
);
|
|
349
|
+
linkMessage(params.eventId, persisted.id);
|
|
350
|
+
markProcessed(params.eventId);
|
|
330
351
|
return;
|
|
331
352
|
}
|
|
332
353
|
|
|
333
|
-
const slackMeta
|
|
334
|
-
source: "slack",
|
|
335
|
-
channelId: params.conversationExternalId,
|
|
336
|
-
channelTs: params.reactedMessageTs,
|
|
337
|
-
eventKind: "reaction",
|
|
338
|
-
// No `threadTs`: Slack sends none on a reaction, so the gateway's thread
|
|
339
|
-
// id here is the reacted message's own ts. It equals `channelTs`, which
|
|
340
|
-
// every reader treats as "not in a thread" anyway, and storing it makes
|
|
341
|
-
// the row false evidence that a thread belongs to this conversation
|
|
342
|
-
// (`legacySlackConversationHasThreadEvidence`).
|
|
343
|
-
...(params.actorDisplayName
|
|
344
|
-
? { displayName: params.actorDisplayName }
|
|
345
|
-
: {}),
|
|
346
|
-
reaction: {
|
|
347
|
-
emoji: parsed.emoji,
|
|
348
|
-
targetChannelTs: params.reactedMessageTs,
|
|
349
|
-
op: parsed.op,
|
|
350
|
-
...(params.actorDisplayName
|
|
351
|
-
? { actorDisplayName: params.actorDisplayName }
|
|
352
|
-
: {}),
|
|
353
|
-
},
|
|
354
|
-
};
|
|
354
|
+
const slackMeta = buildSlackReactionMeta(facts);
|
|
355
355
|
|
|
356
356
|
// Sentinel content — Slack transcript renderers read `slackMeta` to format
|
|
357
357
|
// the reaction line; the literal text is never displayed to the model.
|
|
@@ -360,7 +360,10 @@ async function persistSlackReactionAsMessage(params: {
|
|
|
360
360
|
"user",
|
|
361
361
|
"[reaction]",
|
|
362
362
|
{
|
|
363
|
-
metadata: {
|
|
363
|
+
metadata: {
|
|
364
|
+
...provenanceFromTrustContext(params.trustCtx),
|
|
365
|
+
slackMeta: writeSlackMetadata(slackMeta),
|
|
366
|
+
},
|
|
364
367
|
skipIndexing: true,
|
|
365
368
|
},
|
|
366
369
|
);
|
|
@@ -74,6 +74,7 @@ import { ROUTES as GATEWAY_STATUS_ROUTES } from "./gateway-status-routes.js";
|
|
|
74
74
|
import { ROUTES as GLOBAL_SEARCH_ROUTES } from "./global-search-routes.js";
|
|
75
75
|
import { ROUTES as GROUP_ROUTES } from "./group-routes.js";
|
|
76
76
|
import { ROUTES as GUARDIAN_ACTION_ROUTES } from "./guardian-action-routes.js";
|
|
77
|
+
import { GUARDIAN_FORM_ROUTES } from "./guardian-form-routes.js";
|
|
77
78
|
import { ROUTES as HEARTBEAT_ROUTES } from "./heartbeat-routes.js";
|
|
78
79
|
import { ROUTES as HOME_FEED_ROUTES } from "./home-feed-routes.js";
|
|
79
80
|
import { ROUTES as HOME_STATE_ROUTES } from "./home-state-routes.js";
|
|
@@ -193,6 +194,7 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
193
194
|
...CLIENT_ROUTES,
|
|
194
195
|
...CONTENT_SOURCE_ROUTES,
|
|
195
196
|
...CONTACT_PROMPT_ROUTES,
|
|
197
|
+
...GUARDIAN_FORM_ROUTES,
|
|
196
198
|
...CONTACT_ROUTES,
|
|
197
199
|
...CONVERSATION_ATTENTION_ROUTES,
|
|
198
200
|
...CONVERSATION_CLI_ROUTES,
|
|
@@ -19,8 +19,39 @@ import {
|
|
|
19
19
|
} from "../../../../daemon/handlers/config-slack-channel.js";
|
|
20
20
|
import { ACTOR_PRINCIPALS } from "../../../auth/route-policy.js";
|
|
21
21
|
import { BadRequestError } from "../../errors.js";
|
|
22
|
+
import { parseBody } from "../../parse-body.js";
|
|
22
23
|
import type { RouteDefinition, RouteHandlerArgs } from "../../types.js";
|
|
23
24
|
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Body schemas
|
|
27
|
+
//
|
|
28
|
+
// Each is named once and used twice: as the route's `requestBody`, a codegen
|
|
29
|
+
// signal only, and by `parseBody` in the handler, which is what rejects a
|
|
30
|
+
// malformed request. One declaration keeps the wire contract and the runtime
|
|
31
|
+
// check in agreement.
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Every field is optional because `setSlackChannelConfig` accepts any subset:
|
|
36
|
+
* a user token alone is a valid update that leaves the bot and app tokens in
|
|
37
|
+
* place. The wizard requires both bot and app tokens, and enforces that in
|
|
38
|
+
* `use-save-slack-config.ts` where that requirement belongs.
|
|
39
|
+
*/
|
|
40
|
+
const SetSlackChannelConfigBody = z.object({
|
|
41
|
+
botToken: z.string().optional().describe("Slack bot token"),
|
|
42
|
+
appToken: z.string().optional().describe("Slack app-level token"),
|
|
43
|
+
userToken: z
|
|
44
|
+
.string()
|
|
45
|
+
.optional()
|
|
46
|
+
.describe("Slack user token, for reading channels the bot is not in"),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const PatchSlackChannelConfigBody = z.object({
|
|
50
|
+
threadMode: SlackThreadMode.describe(
|
|
51
|
+
"Controls whether the bot follows threads after an initial @mention",
|
|
52
|
+
).optional(),
|
|
53
|
+
});
|
|
54
|
+
|
|
24
55
|
// ---------------------------------------------------------------------------
|
|
25
56
|
// Handlers
|
|
26
57
|
// ---------------------------------------------------------------------------
|
|
@@ -32,22 +63,19 @@ async function handleGetSlackChannelConfig() {
|
|
|
32
63
|
export async function handleSetSlackChannelConfig({
|
|
33
64
|
body = {},
|
|
34
65
|
}: RouteHandlerArgs) {
|
|
35
|
-
const { botToken, appToken, userToken } =
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
};
|
|
66
|
+
const { botToken, appToken, userToken } = parseBody(
|
|
67
|
+
SetSlackChannelConfigBody,
|
|
68
|
+
body,
|
|
69
|
+
);
|
|
40
70
|
const result = await setSlackChannelConfig(botToken, appToken, userToken);
|
|
41
71
|
if (!result.success) {
|
|
42
|
-
throw new BadRequestError(
|
|
43
|
-
(result as { error?: string }).error ?? "Failed to set Slack config",
|
|
44
|
-
);
|
|
72
|
+
throw new BadRequestError(result.error ?? "Failed to set Slack config");
|
|
45
73
|
}
|
|
46
74
|
return result;
|
|
47
75
|
}
|
|
48
76
|
|
|
49
77
|
async function handlePatchSlackChannelConfig({ body = {} }: RouteHandlerArgs) {
|
|
50
|
-
const { threadMode } = body
|
|
78
|
+
const { threadMode } = parseBody(PatchSlackChannelConfigBody, body);
|
|
51
79
|
if (threadMode !== undefined) {
|
|
52
80
|
const parsed = SlackThreadMode.safeParse(threadMode);
|
|
53
81
|
if (!parsed.success) {
|
|
@@ -95,10 +123,7 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
95
123
|
description: "Validate and store Slack channel credentials.",
|
|
96
124
|
tags: ["integrations"],
|
|
97
125
|
handler: handleSetSlackChannelConfig,
|
|
98
|
-
requestBody:
|
|
99
|
-
botToken: z.string().describe("Slack bot token"),
|
|
100
|
-
appToken: z.string().describe("Slack app-level token"),
|
|
101
|
-
}),
|
|
126
|
+
requestBody: SetSlackChannelConfigBody,
|
|
102
127
|
responseBody: SlackChannelConfigResultSchema,
|
|
103
128
|
},
|
|
104
129
|
{
|
|
@@ -113,11 +138,7 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
113
138
|
description: "Update Slack channel behavior settings (e.g. thread mode).",
|
|
114
139
|
tags: ["integrations"],
|
|
115
140
|
handler: handlePatchSlackChannelConfig,
|
|
116
|
-
requestBody:
|
|
117
|
-
threadMode: SlackThreadMode.describe(
|
|
118
|
-
"Controls whether the bot follows threads after an initial @mention",
|
|
119
|
-
).optional(),
|
|
120
|
-
}),
|
|
141
|
+
requestBody: PatchSlackChannelConfigBody,
|
|
121
142
|
responseBody: SlackChannelConfigResultSchema,
|
|
122
143
|
},
|
|
123
144
|
{
|
|
@@ -44,7 +44,6 @@ import {
|
|
|
44
44
|
} from "../../security/secure-keys.js";
|
|
45
45
|
import {
|
|
46
46
|
getCredentialMetadata,
|
|
47
|
-
persistCredentialMetadata,
|
|
48
47
|
upsertCredentialMetadata,
|
|
49
48
|
} from "../../tools/credentials/metadata-store.js";
|
|
50
49
|
import { getLogger } from "../../util/logger.js";
|
|
@@ -157,8 +156,7 @@ export async function reconcileVellumMetadataFromCes(warningSink: {
|
|
|
157
156
|
if (getCredentialMetadata("vellum", field)) {
|
|
158
157
|
continue;
|
|
159
158
|
}
|
|
160
|
-
|
|
161
|
-
await persistCredentialMetadata(metadata);
|
|
159
|
+
upsertCredentialMetadata("vellum", field, {});
|
|
162
160
|
log.info(
|
|
163
161
|
{ field },
|
|
164
162
|
"Reconciled vellum:* metadata entry from CES after import",
|
|
@@ -139,10 +139,7 @@ const statusResponseSchema = z.object({
|
|
|
139
139
|
async function handleMonitoringStart() {
|
|
140
140
|
let result: { pid: number; alreadyRunning: boolean };
|
|
141
141
|
try {
|
|
142
|
-
// `detached: false` parents the monitor to the daemon so it appears in
|
|
143
|
-
// `assistant ps` and is torn down on shutdown.
|
|
144
142
|
result = await spawnMonitoringWorkerProcess({
|
|
145
|
-
detached: false,
|
|
146
143
|
terminateOnTimeout: true,
|
|
147
144
|
});
|
|
148
145
|
} catch (err) {
|