@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
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Tests for
|
|
2
|
+
* Tests for delete propagation into stored messages.
|
|
3
3
|
*
|
|
4
|
-
* The gateway forwards delete events with `
|
|
5
|
-
*
|
|
4
|
+
* The gateway forwards delete events with `eventKind: "delete"` and
|
|
5
|
+
* `sourceMetadata.messageId` set to the deleted message's ts. The daemon
|
|
6
6
|
* marks the corresponding stored row's `slackMeta.deletedAt` while leaving
|
|
7
7
|
* the `content` column untouched (audit retention; the renderer elides based
|
|
8
8
|
* on the deletedAt marker).
|
|
@@ -154,6 +154,104 @@ function buildSlackDeleteRequest(opts: {
|
|
|
154
154
|
});
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
+
describe("Discord delete propagation (unattributed)", () => {
|
|
158
|
+
beforeEach(() => {
|
|
159
|
+
resetState();
|
|
160
|
+
_setDeleteLookupConfigForTests(2, 20);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("an unattributed delete applies only to an ingested row, without ACL identity", async () => {
|
|
164
|
+
// Discord's MESSAGE_DELETE names no actor, so the gateway forwards the
|
|
165
|
+
// synthetic discord-system id with actorUnattributed stated. No member
|
|
166
|
+
// exists for that id and no trust verdict rides the event; the delete
|
|
167
|
+
// still applies, because the original's author cleared the ACL when the
|
|
168
|
+
// message arrived and the stamp touches only that ingested row.
|
|
169
|
+
const chatId = "999888777666555444";
|
|
170
|
+
const originalId = "111222333444555001";
|
|
171
|
+
const inbound = recordInbound("discord", chatId, originalId, {
|
|
172
|
+
sourceMessageId: originalId,
|
|
173
|
+
});
|
|
174
|
+
const messageId = `msg-${originalId}`;
|
|
175
|
+
getDb()
|
|
176
|
+
.insert(messages)
|
|
177
|
+
.values({
|
|
178
|
+
id: messageId,
|
|
179
|
+
conversationId: inbound.conversationId,
|
|
180
|
+
role: "user",
|
|
181
|
+
content: "A message someone later deleted",
|
|
182
|
+
metadata: JSON.stringify({ userMessageChannel: "discord" }),
|
|
183
|
+
createdAt: Date.now(),
|
|
184
|
+
})
|
|
185
|
+
.run();
|
|
186
|
+
linkMessage(inbound.eventId, messageId);
|
|
187
|
+
|
|
188
|
+
const req = new Request("http://localhost:8080/channels/inbound", {
|
|
189
|
+
method: "POST",
|
|
190
|
+
headers: {
|
|
191
|
+
"Content-Type": "application/json",
|
|
192
|
+
"X-Gateway-Origin": TEST_BEARER_TOKEN,
|
|
193
|
+
},
|
|
194
|
+
body: JSON.stringify({
|
|
195
|
+
sourceChannel: "discord",
|
|
196
|
+
interface: "discord",
|
|
197
|
+
conversationExternalId: chatId,
|
|
198
|
+
externalMessageId: `${originalId}:delete`,
|
|
199
|
+
eventKind: "delete",
|
|
200
|
+
content: "",
|
|
201
|
+
actorExternalId: "discord-system",
|
|
202
|
+
sourceMetadata: {
|
|
203
|
+
messageId: originalId,
|
|
204
|
+
actorUnattributed: true,
|
|
205
|
+
},
|
|
206
|
+
}),
|
|
207
|
+
});
|
|
208
|
+
const resp = await handleChannelInbound(req, undefined, TEST_BEARER_TOKEN);
|
|
209
|
+
const json = (await resp.json()) as Record<string, unknown>;
|
|
210
|
+
|
|
211
|
+
expect(json.accepted).toBe(true);
|
|
212
|
+
expect(json.deleted).toBe(true);
|
|
213
|
+
|
|
214
|
+
const row = getDb()
|
|
215
|
+
.select()
|
|
216
|
+
.from(messages)
|
|
217
|
+
.where(eq(messages.id, messageId))
|
|
218
|
+
.get();
|
|
219
|
+
expect(row!.content).toBe("A message someone later deleted");
|
|
220
|
+
const neutral = readProviderMetadata(row!.metadata);
|
|
221
|
+
expect(neutral).not.toBeNull();
|
|
222
|
+
expect(neutral!.source).toBe("discord");
|
|
223
|
+
expect(neutral!.deletedAt).toBeDefined();
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test("an unattributed delete for a never-ingested message is a no-op", async () => {
|
|
227
|
+
const req = new Request("http://localhost:8080/channels/inbound", {
|
|
228
|
+
method: "POST",
|
|
229
|
+
headers: {
|
|
230
|
+
"Content-Type": "application/json",
|
|
231
|
+
"X-Gateway-Origin": TEST_BEARER_TOKEN,
|
|
232
|
+
},
|
|
233
|
+
body: JSON.stringify({
|
|
234
|
+
sourceChannel: "discord",
|
|
235
|
+
interface: "discord",
|
|
236
|
+
conversationExternalId: "999888777666555444",
|
|
237
|
+
externalMessageId: "111222333444555002:delete",
|
|
238
|
+
eventKind: "delete",
|
|
239
|
+
content: "",
|
|
240
|
+
actorExternalId: "discord-system",
|
|
241
|
+
sourceMetadata: {
|
|
242
|
+
messageId: "111222333444555002",
|
|
243
|
+
actorUnattributed: true,
|
|
244
|
+
},
|
|
245
|
+
}),
|
|
246
|
+
});
|
|
247
|
+
const resp = await handleChannelInbound(req, undefined, TEST_BEARER_TOKEN);
|
|
248
|
+
const json = (await resp.json()) as Record<string, unknown>;
|
|
249
|
+
|
|
250
|
+
expect(json.accepted).toBe(true);
|
|
251
|
+
expect(json.deleted).toBe(false);
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
157
255
|
describe("Slack delete propagation", () => {
|
|
158
256
|
beforeEach(() => {
|
|
159
257
|
resetState();
|
|
@@ -251,6 +349,26 @@ describe("Slack delete propagation", () => {
|
|
|
251
349
|
expect(slackMeta!.deletedAt).toBeUndefined();
|
|
252
350
|
});
|
|
253
351
|
|
|
352
|
+
test("a never-ingested delete returns without paying the retry window", async () => {
|
|
353
|
+
// The retry loop exists for one race: an inbound-event row written
|
|
354
|
+
// before its message link lands. No row at all means nothing can appear
|
|
355
|
+
// by waiting, and the wait would hold the conversation's serialized
|
|
356
|
+
// forward lane for every unrelated delete a busy room produces.
|
|
357
|
+
_setDeleteLookupConfigForTests(2, 500);
|
|
358
|
+
const started = Date.now();
|
|
359
|
+
const req = buildSlackDeleteRequest({
|
|
360
|
+
externalChatId: "C0123CHANNEL",
|
|
361
|
+
deletedTs: "0000.0000",
|
|
362
|
+
});
|
|
363
|
+
const resp = await handleChannelInbound(req, undefined, TEST_BEARER_TOKEN);
|
|
364
|
+
const json = (await resp.json()) as Record<string, unknown>;
|
|
365
|
+
|
|
366
|
+
expect(json.accepted).toBe(true);
|
|
367
|
+
expect(json.deleted).toBe(false);
|
|
368
|
+
// Far under a single 500ms retry delay: the loop short-circuited.
|
|
369
|
+
expect(Date.now() - started).toBeLessThan(400);
|
|
370
|
+
});
|
|
371
|
+
|
|
254
372
|
test("delete for row without slackMeta stamps the neutral metadata", async () => {
|
|
255
373
|
// A legacy pre-enrichment row still gets its delete marked: the neutral
|
|
256
374
|
// envelope is synthesized so readProviderMetadata serves the stamp to
|
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
* releasing and its queued successor being dispatched.
|
|
8
8
|
*
|
|
9
9
|
* In-flight subagents are the other non-idle case: an async spawn leaves the
|
|
10
|
-
* parent idle between its own tool calls while children are still running
|
|
11
|
-
*
|
|
10
|
+
* parent idle between its own tool calls while children are still running.
|
|
11
|
+
* Reload marks that parent stale without aborting the children, then
|
|
12
|
+
* `getOrCreateConversation` rebuilds it once every child is terminal.
|
|
12
13
|
*/
|
|
13
14
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
14
15
|
|
|
@@ -32,7 +33,10 @@ import {
|
|
|
32
33
|
findConversation,
|
|
33
34
|
setConversation,
|
|
34
35
|
} from "../daemon/conversation-registry.js";
|
|
35
|
-
import {
|
|
36
|
+
import {
|
|
37
|
+
evictConversationsForReload,
|
|
38
|
+
getOrCreateConversation,
|
|
39
|
+
} from "../daemon/conversation-store.js";
|
|
36
40
|
|
|
37
41
|
interface FakeConversation {
|
|
38
42
|
disposed: boolean;
|
|
@@ -41,7 +45,7 @@ interface FakeConversation {
|
|
|
41
45
|
|
|
42
46
|
function register(
|
|
43
47
|
id: string,
|
|
44
|
-
state: { processing: boolean; queued: boolean },
|
|
48
|
+
state: { processing: boolean; queued: boolean; stale?: boolean },
|
|
45
49
|
): FakeConversation {
|
|
46
50
|
const fake: FakeConversation & Record<string, unknown> = {
|
|
47
51
|
disposed: false,
|
|
@@ -49,6 +53,9 @@ function register(
|
|
|
49
53
|
conversationId: id,
|
|
50
54
|
isProcessing: () => state.processing,
|
|
51
55
|
hasQueuedMessages: () => state.queued,
|
|
56
|
+
isStale: () => state.stale === true || fake.markedStale,
|
|
57
|
+
hasInFlightWork: () =>
|
|
58
|
+
state.processing || state.queued || activeParents.has(id),
|
|
52
59
|
dispose() {
|
|
53
60
|
fake.disposed = true;
|
|
54
61
|
},
|
|
@@ -103,7 +110,7 @@ describe("evictConversationsForReload", () => {
|
|
|
103
110
|
expect(abortedParents).toEqual([]);
|
|
104
111
|
});
|
|
105
112
|
|
|
106
|
-
test("
|
|
113
|
+
test("marks an idle parent with in-flight subagents stale", () => {
|
|
107
114
|
const parent = register("reload-with-children", {
|
|
108
115
|
processing: false,
|
|
109
116
|
queued: false,
|
|
@@ -113,7 +120,7 @@ describe("evictConversationsForReload", () => {
|
|
|
113
120
|
evictConversationsForReload();
|
|
114
121
|
|
|
115
122
|
expect(parent.disposed).toBe(false);
|
|
116
|
-
expect(parent.markedStale).toBe(
|
|
123
|
+
expect(parent.markedStale).toBe(true);
|
|
117
124
|
expect(findConversation("reload-with-children")).toBeDefined();
|
|
118
125
|
expect(abortedParents).toEqual([]);
|
|
119
126
|
});
|
|
@@ -132,9 +139,42 @@ describe("evictConversationsForReload", () => {
|
|
|
132
139
|
evictConversationsForReload();
|
|
133
140
|
|
|
134
141
|
expect(protectedParent.disposed).toBe(false);
|
|
142
|
+
expect(protectedParent.markedStale).toBe(true);
|
|
135
143
|
expect(findConversation("reload-protected")).toBeDefined();
|
|
136
144
|
expect(idle.disposed).toBe(true);
|
|
137
145
|
expect(findConversation("reload-unprotected")).toBeUndefined();
|
|
138
146
|
expect(abortedParents).toEqual(["reload-unprotected"]);
|
|
139
147
|
});
|
|
148
|
+
|
|
149
|
+
test("defers stale rebuild while subagents are in flight", async () => {
|
|
150
|
+
const parent = register("stale-parent", {
|
|
151
|
+
processing: false,
|
|
152
|
+
queued: false,
|
|
153
|
+
stale: true,
|
|
154
|
+
});
|
|
155
|
+
activeParents.add("stale-parent");
|
|
156
|
+
|
|
157
|
+
const result = await getOrCreateConversation("stale-parent");
|
|
158
|
+
|
|
159
|
+
expect(result as unknown).toBe(parent);
|
|
160
|
+
expect(parent.disposed).toBe(false);
|
|
161
|
+
expect(abortedParents).toEqual([]);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test("rebuilds a stale parent once every child is terminal", async () => {
|
|
165
|
+
const parent = register("stale-parent", {
|
|
166
|
+
processing: false,
|
|
167
|
+
queued: false,
|
|
168
|
+
stale: true,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
await getOrCreateConversation("stale-parent");
|
|
173
|
+
} catch {
|
|
174
|
+
// Isolated test has no provider wiring; rebuild intent is abort + dispose.
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
expect(parent.disposed).toBe(true);
|
|
178
|
+
expect(abortedParents).toEqual(["stale-parent"]);
|
|
179
|
+
});
|
|
140
180
|
});
|
|
@@ -10,7 +10,8 @@ import { tmpdir } from "node:os";
|
|
|
10
10
|
import { join } from "node:path";
|
|
11
11
|
import { afterEach, describe, expect, test } from "bun:test";
|
|
12
12
|
|
|
13
|
-
import {
|
|
13
|
+
import { FILE_READ_TOOL_NAMES } from "../context/post-turn-tool-result-truncation.js";
|
|
14
|
+
import { RESULT_TIME_SPOOL_EXEMPT_TOOLS } from "../context/tool-result-spool.js";
|
|
14
15
|
import {
|
|
15
16
|
FileSystemOps,
|
|
16
17
|
type PathPolicy,
|
|
@@ -330,8 +331,12 @@ describe("FileSystemOps.readFileSafe", () => {
|
|
|
330
331
|
expect(body).toBe("abcd");
|
|
331
332
|
});
|
|
332
333
|
|
|
333
|
-
test("
|
|
334
|
-
|
|
334
|
+
test("file reads are result-time spool-exempt so a full window survives its turn", () => {
|
|
335
|
+
// The budget exceeds the spool threshold, so without the exemption a
|
|
336
|
+
// default read of a large file would be stubbed before the model saw it.
|
|
337
|
+
for (const name of FILE_READ_TOOL_NAMES) {
|
|
338
|
+
expect(RESULT_TIME_SPOOL_EXEMPT_TOOLS.has(name)).toBe(true);
|
|
339
|
+
}
|
|
335
340
|
});
|
|
336
341
|
});
|
|
337
342
|
|
|
@@ -403,6 +403,70 @@ describe("GeminiProvider", () => {
|
|
|
403
403
|
});
|
|
404
404
|
});
|
|
405
405
|
|
|
406
|
+
test("3.7 Flash: disabled maps to the LOW floor, not MINIMAL", async () => {
|
|
407
|
+
fakeChunks = [textChunk("OK"), finishChunk("STOP", 10, 2)];
|
|
408
|
+
|
|
409
|
+
const flash37Provider = new GeminiProvider(
|
|
410
|
+
"test-api-key",
|
|
411
|
+
"gemini-3.7-flash",
|
|
412
|
+
);
|
|
413
|
+
await flash37Provider.sendMessage(
|
|
414
|
+
[{ role: "user", content: [{ type: "text", text: "Hi" }] }],
|
|
415
|
+
{ config: { thinking: { type: "disabled" } } },
|
|
416
|
+
);
|
|
417
|
+
|
|
418
|
+
const config = lastStreamParams!.config as Record<string, unknown>;
|
|
419
|
+
expect(config.thinkingConfig).toEqual({
|
|
420
|
+
thinkingLevel: "LOW",
|
|
421
|
+
includeThoughts: false,
|
|
422
|
+
});
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
test("3.7 Flash: an explicit minimal level is clamped up to LOW", async () => {
|
|
426
|
+
fakeChunks = [textChunk("OK"), finishChunk("STOP", 10, 2)];
|
|
427
|
+
|
|
428
|
+
const flash37Provider = new GeminiProvider(
|
|
429
|
+
"test-api-key",
|
|
430
|
+
"models/gemini-3.7-flash",
|
|
431
|
+
);
|
|
432
|
+
await flash37Provider.sendMessage(
|
|
433
|
+
[{ role: "user", content: [{ type: "text", text: "Hi" }] }],
|
|
434
|
+
{
|
|
435
|
+
config: {
|
|
436
|
+
thinking: {
|
|
437
|
+
type: "adaptive",
|
|
438
|
+
level: "minimal",
|
|
439
|
+
streamThinking: false,
|
|
440
|
+
},
|
|
441
|
+
},
|
|
442
|
+
},
|
|
443
|
+
);
|
|
444
|
+
|
|
445
|
+
const config = lastStreamParams!.config as Record<string, unknown>;
|
|
446
|
+
expect(config.thinkingConfig).toEqual({
|
|
447
|
+
thinkingLevel: "LOW",
|
|
448
|
+
includeThoughts: false,
|
|
449
|
+
});
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
test("3.7 Flash: adaptive with no level keeps Google's default", async () => {
|
|
453
|
+
fakeChunks = [textChunk("OK"), finishChunk("STOP", 10, 2)];
|
|
454
|
+
|
|
455
|
+
const flash37Provider = new GeminiProvider(
|
|
456
|
+
"test-api-key",
|
|
457
|
+
"gemini-3.7-flash",
|
|
458
|
+
);
|
|
459
|
+
await flash37Provider.sendMessage(
|
|
460
|
+
[{ role: "user", content: [{ type: "text", text: "Hi" }] }],
|
|
461
|
+
{ config: { thinking: { type: "adaptive", streamThinking: true } } },
|
|
462
|
+
);
|
|
463
|
+
|
|
464
|
+
const config = lastStreamParams!.config as Record<string, unknown>;
|
|
465
|
+
expect(config.thinkingConfig).toEqual({
|
|
466
|
+
includeThoughts: true,
|
|
467
|
+
});
|
|
468
|
+
});
|
|
469
|
+
|
|
406
470
|
test("Pro: a supported explicit level passes through unchanged", async () => {
|
|
407
471
|
fakeChunks = [textChunk("OK"), finishChunk("STOP", 10, 2)];
|
|
408
472
|
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conversation scoping for in-app guardian decisions
|
|
3
|
+
* (`processGuardianDecision` step 2).
|
|
4
|
+
*
|
|
5
|
+
* A guardian request projects an actionable card into every conversation a
|
|
6
|
+
* delivery was paired with: the vellum card's pinned conversation AND each
|
|
7
|
+
* channel card's paired conversation (the decision engine injects the same
|
|
8
|
+
* seed blocks into every channel's copy). Taps from any of those
|
|
9
|
+
* conversations are legitimate; taps from unrelated conversations are not.
|
|
10
|
+
* The scope check must therefore accept any recorded delivery destination
|
|
11
|
+
* conversation regardless of the delivery's channel: narrowing to vellum
|
|
12
|
+
* rows silently no-opped every tap on a channel card's in-app projection
|
|
13
|
+
* (LUM-3489's "one request, two independent tasks" confusion).
|
|
14
|
+
*/
|
|
15
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
bridgeState,
|
|
19
|
+
gatewayGuardianRequestsStoreBridge,
|
|
20
|
+
} from "./helpers/gateway-guardian-requests-store-bridge.js";
|
|
21
|
+
|
|
22
|
+
mock.module(
|
|
23
|
+
"../channels/gateway-guardian-requests.js",
|
|
24
|
+
() => gatewayGuardianRequestsStoreBridge,
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const applyGuardianDecision = mock(async () => ({
|
|
28
|
+
applied: true as const,
|
|
29
|
+
requestId: "req",
|
|
30
|
+
grantMinted: false,
|
|
31
|
+
}));
|
|
32
|
+
const actualPrimitive =
|
|
33
|
+
await import("../approvals/guardian-decision-primitive.js");
|
|
34
|
+
mock.module("../approvals/guardian-decision-primitive.js", () => ({
|
|
35
|
+
...actualPrimitive,
|
|
36
|
+
applyGuardianDecision,
|
|
37
|
+
}));
|
|
38
|
+
|
|
39
|
+
import { processGuardianDecision } from "../runtime/guardian-action-service.js";
|
|
40
|
+
|
|
41
|
+
const PRINCIPAL_ID = "scope-test-principal";
|
|
42
|
+
|
|
43
|
+
function seedRequestWithProjections() {
|
|
44
|
+
const req = bridgeState.seedRequest({
|
|
45
|
+
kind: "tool_approval",
|
|
46
|
+
sourceType: "channel",
|
|
47
|
+
sourceChannel: "slack",
|
|
48
|
+
sourceConversationId: "conv-thread",
|
|
49
|
+
toolName: "shell",
|
|
50
|
+
guardianPrincipalId: PRINCIPAL_ID,
|
|
51
|
+
});
|
|
52
|
+
bridgeState.seedDelivery({
|
|
53
|
+
requestId: req.id,
|
|
54
|
+
destinationChannel: "vellum",
|
|
55
|
+
destinationConversationId: "conv-thread",
|
|
56
|
+
});
|
|
57
|
+
bridgeState.seedDelivery({
|
|
58
|
+
requestId: req.id,
|
|
59
|
+
destinationChannel: "slack",
|
|
60
|
+
destinationChatId: "C1",
|
|
61
|
+
destinationMessageId: "1.0",
|
|
62
|
+
destinationConversationId: "conv-dm",
|
|
63
|
+
});
|
|
64
|
+
return req;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function decideFrom(requestId: string, conversationId: string) {
|
|
68
|
+
return processGuardianDecision({
|
|
69
|
+
requestId,
|
|
70
|
+
action: "approve_once",
|
|
71
|
+
conversationId,
|
|
72
|
+
channel: "vellum",
|
|
73
|
+
actorContext: {
|
|
74
|
+
actorPrincipalId: PRINCIPAL_ID,
|
|
75
|
+
guardianPrincipalId: PRINCIPAL_ID,
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
describe("processGuardianDecision conversation scoping", () => {
|
|
81
|
+
beforeEach(() => {
|
|
82
|
+
bridgeState.reset();
|
|
83
|
+
applyGuardianDecision.mockClear();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("accepts a tap from the request's source conversation", async () => {
|
|
87
|
+
const req = seedRequestWithProjections();
|
|
88
|
+
const result = await decideFrom(req.id, "conv-thread");
|
|
89
|
+
expect(result).toMatchObject({ ok: true, applied: true });
|
|
90
|
+
expect(applyGuardianDecision).toHaveBeenCalledTimes(1);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("accepts a tap from a channel card's paired conversation", async () => {
|
|
94
|
+
const req = seedRequestWithProjections();
|
|
95
|
+
const result = await decideFrom(req.id, "conv-dm");
|
|
96
|
+
expect(result).toMatchObject({ ok: true, applied: true });
|
|
97
|
+
expect(applyGuardianDecision).toHaveBeenCalledTimes(1);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("rejects a tap from a conversation holding no projection", async () => {
|
|
101
|
+
const req = seedRequestWithProjections();
|
|
102
|
+
const result = await decideFrom(req.id, "conv-unrelated");
|
|
103
|
+
expect(result).toMatchObject({
|
|
104
|
+
ok: true,
|
|
105
|
+
applied: false,
|
|
106
|
+
reason: "not_found",
|
|
107
|
+
});
|
|
108
|
+
expect(applyGuardianDecision).not.toHaveBeenCalled();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("threads the acting conversation to the primitive for broadcast suppression", async () => {
|
|
112
|
+
const req = seedRequestWithProjections();
|
|
113
|
+
await decideFrom(req.id, "conv-dm");
|
|
114
|
+
expect(applyGuardianDecision).toHaveBeenCalledWith(
|
|
115
|
+
expect.objectContaining({ originConversationId: "conv-dm" }),
|
|
116
|
+
);
|
|
117
|
+
});
|
|
118
|
+
});
|