@vellumai/assistant 0.9.0 → 0.10.0-staging.2
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 +18 -34
- package/bun.lock +7 -8
- package/docs/activation-funnel-telemetry.md +28 -22
- package/docs/architecture/security.md +29 -28
- package/docs/stt-provider-onboarding.md +3 -5
- package/docs/workflows-testing.md +13 -44
- package/docs/workflows.md +3 -5
- package/node_modules/@vellumai/ces-client/src/__tests__/ces-client.test.ts +47 -0
- package/node_modules/@vellumai/ces-client/src/rpc-client.ts +28 -5
- package/node_modules/@vellumai/environments/src/seeds.ts +2 -5
- package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +97 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +10 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +32 -6
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +119 -0
- package/node_modules/@vellumai/gateway-client/src/types.ts +15 -84
- package/openapi.yaml +976 -63
- package/package.json +2 -1
- package/scripts/sync-llm-catalog.ts +6 -15
- package/scripts/sync-web-search-catalog.ts +3 -11
- package/src/__tests__/access-request-card-view.test.ts +98 -0
- package/src/__tests__/access-request-seed-content-blocks.test.ts +2 -4
- package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +72 -32
- package/src/__tests__/agent-loop-compaction-strip.test.ts +241 -0
- package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +16 -13
- package/src/__tests__/agent-loop-output-hooks.test.ts +69 -0
- package/src/__tests__/agent-loop-override-profile.test.ts +25 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +2 -3
- package/src/__tests__/app-compiler.test.ts +15 -1
- package/src/__tests__/app-dir-path-guard.test.ts +0 -1
- package/src/__tests__/assistant-feature-flag-guard.test.ts +1 -4
- package/src/__tests__/assistant-feature-flag-guardrails.test.ts +0 -2
- package/src/__tests__/auth-fallback-events-store.test.ts +6 -14
- package/src/__tests__/avatar-identity-sync.test.ts +2 -27
- package/src/__tests__/btw-routes.test.ts +6 -8
- package/src/__tests__/call-pointer-messages.test.ts +28 -0
- package/src/__tests__/cancel-clears-processing.test.ts +89 -0
- package/src/__tests__/channel-approval-routes.test.ts +0 -4
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -15
- package/src/__tests__/checker.test.ts +0 -3
- package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +3 -4
- package/src/__tests__/compactor-image-manifest-trust.test.ts +21 -1
- package/src/__tests__/compactor-summary-call-truncation.test.ts +223 -0
- package/src/__tests__/config-loader-backfill.test.ts +268 -27
- package/src/__tests__/config-schema.test.ts +35 -0
- package/src/__tests__/config-watcher.test.ts +0 -18
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -2
- package/src/__tests__/contact-store-user-file.test.ts +0 -6
- package/src/__tests__/contacts-tools.test.ts +29 -0
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +22 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop.test.ts +58 -0
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
- package/src/__tests__/conversation-lifecycle.test.ts +7 -9
- package/src/__tests__/conversation-load-history-repair.test.ts +101 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +15 -12
- package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -3
- package/src/__tests__/conversation-title-service.test.ts +62 -0
- package/src/__tests__/credential-broker.test.ts +449 -1
- package/src/__tests__/credential-execution-shell-lockdown.test.ts +18 -11
- package/src/__tests__/credential-execution-tools.test.ts +0 -1
- package/src/__tests__/credential-prompt-route.test.ts +4 -4
- package/src/__tests__/credential-routes.test.ts +360 -0
- package/src/__tests__/credential-security-invariants.test.ts +4 -13
- package/src/__tests__/disk-pressure-policy.test.ts +12 -0
- package/src/__tests__/disk-usage.test.ts +65 -0
- package/src/__tests__/dynamic-page-surface.test.ts +152 -1
- package/src/__tests__/fixtures/credential-security-fixtures.ts +2 -33
- package/src/__tests__/gateway-flag-listener.test.ts +110 -1
- package/src/__tests__/gateway-only-guard.test.ts +3 -7
- package/src/__tests__/guardian-binding-drift-heal.test.ts +1 -1
- package/src/__tests__/guardian-card-withdrawal.test.ts +403 -0
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +5 -3
- package/src/__tests__/guardian-grant-minting.test.ts +3 -35
- package/src/__tests__/guardian-routing-invariants.test.ts +64 -26
- package/src/__tests__/guardian-routing-state.test.ts +0 -1
- package/src/__tests__/headless-browser-mode.test.ts +10 -0
- package/src/__tests__/headless-browser-navigate.test.ts +8 -3
- package/src/__tests__/helpers/create-guardian-binding.ts +0 -1
- package/src/__tests__/host-browser-proxy.test.ts +87 -0
- package/src/__tests__/identity-routes.test.ts +0 -189
- package/src/__tests__/inbound-invite-redemption.test.ts +4 -4
- package/src/__tests__/injector-v3-suppression.test.ts +27 -20
- package/src/__tests__/internal-telemetry-routes.test.ts +6 -14
- package/src/__tests__/invite-redemption-service.test.ts +4 -7
- package/src/__tests__/llm-callsite-catalog.test.ts +5 -6
- package/src/__tests__/llm-catalog-parity.test.ts +30 -23
- package/src/__tests__/llm-resolver.test.ts +70 -24
- package/src/__tests__/llm-schema.test.ts +1 -0
- package/src/__tests__/managed-profile-guard.test.ts +163 -4
- package/src/__tests__/mcp-health-check.test.ts +6 -7
- package/src/__tests__/media-stream-server-integration.test.ts +317 -13
- package/src/__tests__/oauth-provider-seed-logos.test.ts +4 -6
- package/src/__tests__/onboarding-persona-write.test.ts +1 -1
- package/src/__tests__/path-policy.test.ts +34 -0
- package/src/__tests__/persona-resolver.test.ts +49 -14
- package/src/__tests__/plugin-api-model-profiles.test.ts +178 -0
- package/src/__tests__/plugin-api-provider.test.ts +24 -0
- package/src/__tests__/plugin-tool-contribution.test.ts +6 -3
- package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +214 -0
- package/src/__tests__/provider-send-message-override-profile.test.ts +76 -0
- package/src/__tests__/reaction-persistence.test.ts +150 -29
- package/src/__tests__/registry.test.ts +2 -7
- package/src/__tests__/relay-server.test.ts +285 -0
- package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
- package/src/__tests__/schedule-routes-workflow-validation.test.ts +1 -10
- package/src/__tests__/schedule-routes.test.ts +0 -30
- package/src/__tests__/schedule-tools.test.ts +2 -18
- package/src/__tests__/scheduler-reuse-conversation.test.ts +8 -5
- package/src/__tests__/skill-execute-input.test.ts +51 -1
- package/src/__tests__/skill-runtime-path.test.ts +2 -3
- package/src/__tests__/skills.test.ts +51 -0
- package/src/__tests__/slack-notification-approval-card.test.ts +176 -0
- package/src/__tests__/slack-reaction-canonical-approval.test.ts +285 -0
- package/src/__tests__/subagent-tools.test.ts +266 -0
- package/src/__tests__/surface-completion-nudge-hook.test.ts +367 -0
- package/src/__tests__/task-progress-nudge-hook.test.ts +1 -1
- package/src/__tests__/title-generate-hook.test.ts +100 -3
- package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1 -29
- package/src/__tests__/token-manager.test.ts +519 -0
- package/src/__tests__/tool-approval-seed-content-blocks.test.ts +1 -1
- package/src/__tests__/tool-audit-listener.test.ts +7 -7
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +6 -3
- package/src/__tests__/tool-executor.test.ts +0 -79
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +4 -2
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +220 -3
- package/src/__tests__/trusted-contact-multichannel.test.ts +3 -3
- package/src/__tests__/trusted-contact-verification.test.ts +8 -10
- package/src/__tests__/twilio-routes.test.ts +81 -1
- package/src/__tests__/voice-invite-redemption.test.ts +2 -3
- package/src/__tests__/weak-open-model.test.ts +30 -0
- package/src/__tests__/web-search-catalog-parity.test.ts +6 -25
- package/src/__tests__/workspace-greetings.test.ts +152 -0
- package/src/__tests__/workspace-migration-105-enable-memory-v3-live-for-new-workspaces.test.ts +149 -0
- package/src/__tests__/workspace-migration-108-drop-balanced-economy-profile.test.ts +285 -0
- package/src/__tests__/workspace-migration-add-send-diagnostics.test.ts +1 -1
- package/src/__tests__/workspace-migration-drop-collect-usage-data.test.ts +118 -0
- package/src/__tests__/workspace-migration-drop-send-diagnostics.test.ts +118 -0
- package/src/a2a/__tests__/e2e-a2a-channel.test.ts +0 -4
- package/src/agent/loop.ts +49 -29
- package/src/api/README.md +6 -6
- package/src/api/events/tool-result.ts +6 -0
- package/src/api/events/workflow-completed.ts +53 -0
- package/src/api/events/workflow-leaf-finished.ts +38 -0
- package/src/api/events/workflow-leaf-started.ts +35 -0
- package/src/api/events/workflow-progress.ts +32 -0
- package/src/api/events/workflow-started.ts +31 -0
- package/src/api/index.ts +40 -0
- package/src/api/responses/conversation-message.ts +28 -4
- package/src/api/responses/home.ts +26 -4
- package/src/api/responses/workflow-journal.ts +53 -0
- package/src/approvals/guardian-card-withdrawal.ts +145 -0
- package/src/approvals/guardian-decision-primitive.ts +26 -3
- package/src/approvals/guardian-request-resolvers.ts +183 -80
- package/src/calls/__tests__/channel-admission-reader.test.ts +132 -0
- package/src/calls/__tests__/relay-setup-router.test.ts +350 -0
- package/src/calls/call-pointer-messages.ts +10 -4
- package/src/calls/channel-admission-reader.ts +104 -0
- package/src/calls/guardian-dispatch.ts +17 -45
- package/src/calls/media-stream-server.ts +84 -2
- package/src/calls/relay-access-wait.ts +1 -1
- package/src/calls/relay-server.ts +66 -0
- package/src/calls/relay-setup-router.ts +82 -1
- package/src/calls/twilio-routes.ts +17 -8
- package/src/calls/voice-session-bridge.ts +2 -2
- package/src/cli/commands/clients.ts +3 -0
- package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2-compare-render.test.ts +1 -1
- package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2.test.ts +8 -7
- package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v3.test.ts +5 -4
- package/src/cli/commands/memory/index.ts +30 -0
- package/src/cli/commands/{memory-v2-compare-render.ts → memory/memory-v2-compare-render.ts} +1 -1
- package/src/cli/commands/{memory-v2.ts → memory/memory-v2.ts} +6 -15
- package/src/cli/commands/{memory-v3.ts → memory/memory-v3.ts} +97 -11
- package/src/cli/commands/oauth/status.test.ts +36 -0
- package/src/cli/commands/oauth/status.ts +23 -3
- package/src/cli/commands/plugins.ts +197 -4
- package/src/cli/lib/__tests__/diff-plugin.test.ts +443 -0
- package/src/cli/lib/__tests__/inspect-plugin.test.ts +54 -0
- package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +443 -0
- package/src/cli/lib/__tests__/plugin-surfaces.test.ts +111 -0
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +295 -2
- package/src/cli/lib/diff-plugin.ts +346 -0
- package/src/cli/lib/inspect-plugin.ts +12 -1
- package/src/cli/lib/install-from-github.ts +105 -17
- package/src/cli/lib/merge-plugin-tree.ts +328 -0
- package/src/cli/lib/plugin-fingerprint.ts +14 -0
- package/src/cli/lib/plugin-surfaces.ts +104 -0
- package/src/cli/lib/upgrade-plugin.ts +298 -10
- package/src/cli/program.ts +2 -6
- package/src/config/__tests__/sync-gated-profiles.test.ts +368 -0
- package/src/config/assistant-feature-flags.ts +22 -7
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +0 -1
- package/src/config/bundled-skills/messaging/SKILL.md +6 -4
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +9 -8
- package/src/config/bundled-skills/subagent/SKILL.md +4 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +4 -0
- package/src/config/bundled-skills/workflows/SKILL.md +14 -8
- package/src/config/bundled-tool-registry.ts +2 -7
- package/src/config/call-site-defaults.ts +15 -2
- package/src/config/feature-flag-registry.json +46 -31
- package/src/config/inference-profile-validation.ts +26 -0
- package/src/config/llm-resolver.ts +3 -0
- package/src/config/loader.ts +4 -0
- package/src/config/memory-v3-gate.ts +11 -0
- package/src/config/profile-order.ts +28 -0
- package/src/config/schema.ts +8 -6
- package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
- package/src/config/schemas/call-site-catalog.ts +7 -0
- package/src/config/schemas/channels.ts +11 -0
- package/src/config/schemas/elevenlabs.ts +0 -1
- package/src/config/schemas/llm.ts +31 -0
- package/src/config/schemas/memory-lifecycle.ts +3 -7
- package/src/config/schemas/memory-v3.ts +6 -0
- package/src/config/schemas/platform.ts +0 -8
- package/src/config/schemas/services.ts +18 -0
- package/src/config/seed-inference-profiles.ts +109 -44
- package/src/config/skills.ts +21 -0
- package/src/config/sync-gated-profiles.ts +220 -0
- package/src/contacts/contact-store.ts +89 -106
- package/src/contacts/contacts-write.ts +5 -22
- package/src/contacts/types.ts +0 -1
- package/src/context/compactor.ts +88 -54
- package/src/context/strip-injections.ts +58 -10
- package/src/context/token-estimator.ts +1 -1
- package/src/credential-execution/process-manager.ts +55 -14
- package/src/credential-execution/prompted-credential.ts +2 -3
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -2
- package/src/daemon/config-watcher.ts +0 -4
- package/src/daemon/conversation-agent-loop-handlers.ts +2 -0
- package/src/daemon/conversation-agent-loop.ts +114 -22
- package/src/daemon/conversation-history.ts +1 -1
- package/src/daemon/conversation-lifecycle.ts +3 -5
- package/src/daemon/conversation-process.ts +13 -5
- package/src/daemon/conversation-runtime-assembly.ts +13 -15
- package/src/daemon/conversation-slash.ts +2 -23
- package/src/daemon/conversation-surfaces.ts +26 -0
- package/src/daemon/conversation-tool-setup.ts +27 -14
- package/src/daemon/conversation.ts +66 -14
- package/src/daemon/disk-pressure-policy.ts +5 -3
- package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -1
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -1
- package/src/daemon/handlers/config-a2a.ts +0 -2
- package/src/daemon/handlers/config-channels.ts +15 -16
- package/src/daemon/handlers/config-slack-channel.ts +22 -3
- package/src/daemon/handlers/conversations.ts +107 -0
- package/src/daemon/host-browser-proxy.ts +41 -0
- package/src/daemon/lifecycle.ts +55 -27
- package/src/daemon/message-provenance.ts +2 -0
- package/src/daemon/message-types/contacts.ts +0 -1
- package/src/daemon/message-types/conversations.ts +3 -3
- package/src/daemon/message-types/sync.ts +0 -1
- package/src/daemon/message-types/web-activity.ts +7 -1
- package/src/daemon/message-types/workflows.ts +83 -1
- package/src/daemon/orphan-reaper.test.ts +0 -19
- package/src/daemon/orphan-reaper.ts +2 -24
- package/src/daemon/server.ts +0 -10
- package/src/daemon/tool-setup-types.ts +4 -0
- package/src/daemon/trust-context.ts +1 -1
- package/src/events/tool-audit-listener.ts +2 -2
- package/src/home/feed-source-enrichment.test.ts +151 -0
- package/src/home/feed-source-enrichment.ts +176 -0
- package/src/home/relationship-state.ts +2 -4
- package/src/instrument.ts +18 -6
- package/src/ipc/__tests__/binary-result-ipc.test.ts +81 -0
- package/src/ipc/__tests__/clients-list-ipc.test.ts +20 -0
- package/src/ipc/assistant-server.ts +37 -4
- package/src/ipc/gateway-flag-listener.ts +18 -2
- package/src/memory/__tests__/auto-analysis-enqueue.test.ts +5 -16
- package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +7 -11
- package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +37 -7
- package/src/memory/__tests__/memory-retrospective-job.test.ts +229 -401
- package/src/memory/__tests__/onboarding-events-store.test.ts +7 -7
- package/src/memory/auth-fallback-events-store.ts +2 -2
- package/src/memory/auto-analysis-enqueue.ts +3 -5
- package/src/memory/bookmark-crud.ts +1 -2
- package/src/memory/canonical-guardian-store.ts +39 -1
- package/src/memory/conversation-crud.ts +9 -4
- package/src/memory/conversation-key-store.ts +17 -2
- package/src/memory/conversation-title-service.ts +64 -7
- package/src/memory/db-init.ts +17 -17
- package/src/memory/embedding-backend.ts +38 -1
- package/src/memory/embedding-billing-breaker.ts +96 -0
- package/src/memory/jobs-store.ts +25 -13
- package/src/memory/jobs-worker.ts +54 -1
- package/src/memory/lifecycle-events-store.ts +2 -2
- package/src/memory/memory-retrospective-constants.ts +4 -4
- package/src/memory/memory-retrospective-enqueue.ts +31 -6
- package/src/memory/memory-retrospective-job.ts +28 -227
- package/src/memory/migrations/129-contact-channels-access-fields.ts +18 -9
- package/src/memory/migrations/131-drop-legacy-member-guardian-tables.ts +14 -2
- package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +10 -0
- package/src/memory/migrations/291-contact-channels-renormalize-addresses.ts +72 -0
- package/src/memory/migrations/292-schedule-default-no-reuse-conversation.test.ts +67 -0
- package/src/memory/migrations/292-schedule-default-no-reuse-conversation.ts +25 -0
- package/src/memory/migrations/293-workflow-journal-leaf-tokens.ts +32 -0
- package/src/memory/migrations/294-drop-external-user-id.ts +31 -0
- package/src/memory/migrations/295-drop-approval-prompt-ts-tracker.ts +20 -0
- package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.test.ts +110 -0
- package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.ts +68 -0
- package/src/memory/migrations/__tests__/131-drop-legacy-member-guardian-tables.test.ts +154 -0
- package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +31 -0
- package/src/memory/migrations/__tests__/291-contact-channels-renormalize-addresses.test.ts +341 -0
- package/src/memory/migrations/__tests__/run-migrations.test.ts +52 -0
- package/src/memory/migrations/index.ts +6 -0
- package/src/memory/migrations/run-migrations.ts +41 -0
- package/src/memory/migrations/validate-migration-state.ts +1 -1
- package/src/memory/onboarding-events-store.ts +3 -3
- package/src/memory/schema/contacts.ts +0 -5
- package/src/memory/skill-loaded-events-store.test.ts +7 -15
- package/src/memory/skill-loaded-events-store.ts +2 -2
- package/src/memory/tool-executed-events-store.test.ts +7 -7
- package/src/memory/turn-trace-store.test.ts +736 -0
- package/src/memory/turn-trace-store.ts +364 -0
- package/src/memory/v2/__tests__/consolidation-job.test.ts +8 -0
- package/src/memory/v2/__tests__/skill-content.test.ts +30 -0
- package/src/memory/v2/consolidation-job.ts +2 -2
- package/src/memory/v2/skill-content.ts +25 -7
- package/src/memory/v2/skill-store.ts +7 -1
- package/src/memory/v3-eval/__tests__/eval-packets.test.ts +248 -0
- package/src/memory/v3-eval/eval-packets.ts +546 -0
- package/src/messaging/providers/slack/adapter.ts +1 -1
- package/src/messaging/providers/slack/api.ts +31 -0
- package/src/messaging/providers/slack/send.test.ts +114 -2
- package/src/messaging/providers/slack/send.ts +30 -7
- package/src/messaging/providers/slack/withdraw.test.ts +200 -0
- package/src/messaging/providers/slack/withdraw.ts +161 -0
- package/src/notifications/AGENTS.md +2 -0
- package/src/notifications/access-request-copy.ts +72 -59
- package/src/notifications/adapters/shared.ts +29 -0
- package/src/notifications/adapters/slack.ts +58 -103
- package/src/notifications/adapters/telegram.ts +2 -20
- package/src/notifications/approval-card-data.ts +333 -0
- package/src/notifications/broadcaster.ts +16 -3
- package/src/notifications/canonical-delivery-recorder.ts +139 -0
- package/src/notifications/copy-composer.ts +3 -3
- package/src/notifications/decision-engine.ts +4 -2
- package/src/notifications/destination-resolver.ts +4 -6
- package/src/notifications/guardian-question-mode.ts +10 -0
- package/src/notifications/home-feed-side-effect.ts +7 -16
- package/src/notifications/notification-utils.ts +19 -20
- package/src/notifications/signal.ts +79 -43
- package/src/notifications/types.ts +98 -121
- package/src/oauth/AGENTS.md +5 -24
- package/src/permissions/checker.test.ts +51 -0
- package/src/permissions/checker.ts +185 -26
- package/src/permissions/ipc-risk-types.ts +24 -0
- package/src/permissions/question-prompter.test.ts +27 -0
- package/src/permissions/question-prompter.ts +4 -0
- package/src/platform/client.test.ts +119 -0
- package/src/platform/client.ts +66 -0
- package/src/platform/consent-cache.test.ts +267 -0
- package/src/platform/consent-cache.ts +174 -0
- package/src/plugin-api/constants.ts +1 -1
- package/src/plugin-api/index.ts +33 -1
- package/src/plugin-api/model-profiles.ts +33 -0
- package/src/plugin-api/types.ts +50 -2
- package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +56 -0
- package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +43 -0
- package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +137 -0
- package/src/plugins/defaults/advisor/__tests__/consult.test.ts +153 -0
- package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +138 -0
- package/src/plugins/defaults/advisor/__tests__/transcript.test.ts +147 -0
- package/src/plugins/defaults/advisor/advisor-gate.ts +29 -0
- package/src/plugins/defaults/advisor/advisor-state-store.ts +94 -0
- package/src/plugins/defaults/advisor/config.ts +21 -0
- package/src/plugins/defaults/advisor/consult.ts +93 -0
- package/src/plugins/defaults/advisor/hooks/post-model-call.ts +34 -0
- package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +30 -0
- package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +19 -0
- package/src/plugins/defaults/advisor/package.json +14 -0
- package/src/plugins/defaults/advisor/steering.ts +67 -0
- package/src/plugins/defaults/advisor/tools/advisor.ts +65 -0
- package/src/plugins/defaults/advisor/transcript.ts +76 -0
- package/src/plugins/defaults/index.ts +60 -0
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +22 -9
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
- package/src/plugins/defaults/memory-retrieval/tail-reinjection-strip.ts +64 -0
- package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +29 -21
- package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +1 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +129 -9
- package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +31 -4
- package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +77 -2
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +1 -0
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +7 -10
- package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +144 -11
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +32 -20
- package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +56 -3
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +23 -2
- package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +276 -0
- package/src/plugins/defaults/surface-completion-nudge/hooks/stop.ts +22 -0
- package/src/plugins/defaults/surface-completion-nudge/nudge-state-store.ts +46 -0
- package/src/plugins/defaults/surface-completion-nudge/package.json +14 -0
- package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +3 -13
- package/src/plugins/defaults/title-generate/hooks/stop.ts +56 -21
- package/src/prompts/persona-resolver.ts +14 -4
- package/src/prompts/templates/system-sections.ts +7 -2
- package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
- package/src/providers/__tests__/provider-secret-catalog.test.ts +1 -0
- package/src/providers/__tests__/retry-callsite.test.ts +176 -0
- package/src/providers/atlascloud/client.ts +85 -0
- package/src/providers/fetch-provider-catalog.ts +85 -0
- package/src/providers/inference/adapter-factory.ts +3 -0
- package/src/providers/model-catalog.ts +58 -0
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +33 -0
- package/src/providers/openai/chat-completions-provider.ts +7 -0
- package/src/providers/openai/responses-provider.ts +10 -0
- package/src/providers/provider-send-message.ts +11 -3
- package/src/providers/retry.ts +53 -12
- package/src/providers/search-provider-catalog.ts +10 -0
- package/src/providers/weak-open-model.ts +22 -0
- package/src/runtime/AGENTS.md +0 -1
- package/src/runtime/__tests__/agent-wake.test.ts +181 -0
- package/src/runtime/__tests__/client-health.test.ts +44 -0
- package/src/runtime/access-request-helper.ts +21 -53
- package/src/runtime/actor-trust-resolver.ts +59 -63
- package/src/runtime/agent-wake.ts +52 -0
- package/src/runtime/assistant-event-hub.ts +18 -4
- package/src/runtime/auth/__tests__/route-policy.test.ts +12 -0
- package/src/runtime/auth/require-bound-guardian.ts +1 -4
- package/src/runtime/btw-sidechain.ts +3 -6
- package/src/runtime/capabilities.test.ts +120 -0
- package/src/runtime/capabilities.ts +197 -0
- package/src/runtime/channel-approval-types.ts +22 -45
- package/src/runtime/channel-invite-transports/telegram.ts +4 -4
- package/src/runtime/channel-retry-sweep.ts +1 -0
- package/src/runtime/channel-verification-service.ts +3 -3
- package/src/runtime/client-health.ts +26 -0
- package/src/runtime/confirmation-request-guardian-bridge.ts +38 -29
- package/src/runtime/effective-capabilities.test.ts +128 -0
- package/src/runtime/effective-capabilities.ts +84 -0
- package/src/runtime/guardian-reply-router.ts +106 -21
- package/src/runtime/invite-redemption-service.ts +9 -25
- package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +123 -0
- package/src/runtime/migrations/vbundle-builder.ts +49 -20
- package/src/runtime/pending-interactions.ts +15 -0
- package/src/runtime/routes/__tests__/client-routes.test.ts +13 -0
- package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +67 -0
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +240 -1
- package/src/runtime/routes/app-routes.ts +1 -1
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -2
- package/src/runtime/routes/assets/vellum-design-system.css +1959 -0
- package/src/runtime/routes/browser-tabs-routes.ts +9 -0
- package/src/runtime/routes/btw-routes.ts +1 -27
- package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +17 -8
- package/src/runtime/routes/client-routes.ts +10 -0
- package/src/runtime/routes/contact-routes.ts +31 -8
- package/src/runtime/routes/conversation-compaction-routes.ts +1 -1
- package/src/runtime/routes/conversation-management-routes.ts +80 -1
- package/src/runtime/routes/conversation-query-routes.ts +68 -22
- package/src/runtime/routes/conversation-routes.ts +39 -14
- package/src/runtime/routes/credential-routes.ts +40 -16
- package/src/runtime/routes/empty-state-greeting-cache.ts +1 -2
- package/src/runtime/routes/events-routes.ts +1 -3
- package/src/runtime/routes/guardian-approval-interception.ts +14 -73
- package/src/runtime/routes/guardian-approval-prompt.ts +22 -4
- package/src/runtime/routes/home-feed-routes.ts +8 -3
- package/src/runtime/routes/identity-routes.ts +1 -296
- package/src/runtime/routes/inbound-message-handler.ts +214 -228
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +89 -7
- package/src/runtime/routes/inbound-stages/admission-policy.test.ts +154 -0
- package/src/runtime/routes/inbound-stages/admission-policy.ts +140 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +3 -3
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +11 -6
- package/src/runtime/routes/inbound-stages/escalation-intercept.ts +1 -2
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +1 -2
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +7 -7
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +47 -28
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +358 -0
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +8 -0
- package/src/runtime/routes/integrations/slack/channel.ts +36 -0
- package/src/runtime/routes/internal-telemetry-routes.ts +1 -1
- package/src/runtime/routes/mcp-auth-routes.ts +233 -41
- package/src/runtime/routes/memory-eval-routes.ts +87 -0
- package/src/runtime/routes/notification-routes.ts +122 -133
- package/src/runtime/routes/platform-routes.ts +2 -2
- package/src/runtime/routes/plugins-routes.ts +202 -3
- package/src/runtime/routes/schedule-routes.ts +0 -22
- package/src/runtime/routes/secret-routes.ts +10 -0
- package/src/runtime/routes/surface-action-routes.ts +2 -1
- package/src/runtime/routes/tool-call-question-enrichment.test.ts +146 -0
- package/src/runtime/routes/tool-call-question-enrichment.ts +66 -0
- package/src/runtime/routes/workflow-routes.test.ts +229 -44
- package/src/runtime/routes/workflow-routes.ts +131 -29
- package/src/runtime/routes/workspace-greetings.ts +55 -0
- package/src/runtime/sync/resource-sync-events.ts +1 -11
- package/src/runtime/tool-grant-request-helper.ts +18 -16
- package/src/runtime/trust-context-resolver.ts +8 -5
- package/src/schedule/inference-profile.ts +2 -14
- package/src/schedule/schedule-store.ts +1 -1
- package/src/schedule/scheduler-types.ts +5 -1
- package/src/security/__tests__/provider-key-env-fallback.test.ts +6 -0
- package/src/security/secret-patterns.ts +3 -0
- package/src/subagent/manager.ts +17 -4
- package/src/subagent/types.ts +6 -0
- package/src/telemetry/trace-collection-policy.test.ts +28 -0
- package/src/telemetry/trace-collection-policy.ts +30 -0
- package/src/telemetry/types.ts +89 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +586 -36
- package/src/telemetry/usage-telemetry-reporter.ts +148 -41
- package/src/tools/AGENTS.md +3 -3
- package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +31 -0
- package/src/tools/browser/browser-execution.ts +30 -19
- package/src/tools/document/document-tool.ts +2 -3
- package/src/tools/executor.ts +5 -3
- package/src/tools/host-terminal/host-shell.ts +5 -4
- package/src/tools/memory/register.ts +2 -2
- package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +360 -0
- package/src/tools/network/__tests__/web-search.test.ts +143 -0
- package/src/tools/network/web-fetch.ts +372 -1
- package/src/tools/network/web-search-error.ts +1 -1
- package/src/tools/network/web-search.ts +213 -10
- package/src/tools/permission-checker.ts +4 -3
- package/src/tools/registry.ts +20 -0
- package/src/tools/schedule/create.ts +7 -12
- package/src/tools/schedule/update.ts +4 -11
- package/src/tools/shared/filesystem/path-policy.ts +39 -13
- package/src/tools/side-effects.ts +2 -17
- package/src/tools/skills/execute.ts +33 -0
- package/src/tools/subagent/spawn.ts +61 -12
- package/src/tools/terminal/shell.ts +10 -4
- package/src/tools/tool-approval-handler.ts +18 -13
- package/src/tools/tool-manifest.ts +0 -2
- package/src/tools/types.ts +9 -0
- package/src/tools/ui-surface/definitions.ts +64 -3
- package/src/tools/verification-control-plane-policy.ts +3 -1
- package/src/tools/workflows/run-workflow.test.ts +8 -18
- package/src/tools/workflows/run-workflow.ts +1 -0
- package/src/util/disk-usage.ts +78 -23
- package/src/util/platform.ts +10 -3
- package/src/watcher/telemetry.ts +2 -2
- package/src/workflows/capabilities.ts +2 -3
- package/src/workflows/engine.test.ts +175 -1
- package/src/workflows/engine.ts +82 -0
- package/src/workflows/journal-store.test.ts +70 -0
- package/src/workflows/journal-store.ts +18 -3
- package/src/workflows/run-manager.test.ts +171 -28
- package/src/workflows/run-manager.ts +66 -24
- package/src/workspace/migrations/105-enable-memory-v3-live-for-new-workspaces.ts +63 -0
- package/src/workspace/migrations/106-drop-collect-usage-data.ts +47 -0
- package/src/workspace/migrations/107-drop-send-diagnostics.ts +47 -0
- package/src/workspace/migrations/108-drop-balanced-economy-profile.ts +129 -0
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/__tests__/app-control-no-global-cgevent.test.ts +0 -98
- package/src/__tests__/credential-security-e2e.test.ts +0 -362
- package/src/__tests__/credential-vault-unit.test.ts +0 -1528
- package/src/__tests__/credential-vault.test.ts +0 -1706
- package/src/__tests__/identity-intro-cache.test.ts +0 -315
- package/src/__tests__/secret-onetime-send.test.ts +0 -182
- package/src/cli/commands/__tests__/task.test.ts +0 -914
- package/src/cli/commands/task.ts +0 -771
- package/src/config/bundled-skills/personal-page/SKILL.md +0 -57
- package/src/config/bundled-skills/personal-page/TOOLS.json +0 -27
- package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +0 -17
- package/src/config/preloaded-apps/personal-page/src/components/About.tsx +0 -22
- package/src/config/preloaded-apps/personal-page/src/components/App.tsx +0 -16
- package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +0 -77
- package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +0 -57
- package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +0 -28
- package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +0 -234
- package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +0 -48
- package/src/config/preloaded-apps/personal-page/src/components/media.ts +0 -16
- package/src/config/preloaded-apps/personal-page/src/index.html +0 -20
- package/src/config/preloaded-apps/personal-page/src/main.tsx +0 -7
- package/src/config/preloaded-apps/personal-page/src/profile-data.ts +0 -82
- package/src/config/preloaded-apps/personal-page/src/styles.css +0 -759
- package/src/memory/__tests__/preloaded-apps.test.ts +0 -85
- package/src/memory/preloaded-apps.ts +0 -116
- package/src/notifications/tool-approval-copy.ts +0 -142
- package/src/runtime/routes/approval-prompt-ts-tracker.ts +0 -78
- package/src/runtime/routes/identity-intro-cache.ts +0 -172
- package/src/tools/credentials/vault.ts +0 -712
|
@@ -421,7 +421,7 @@ describe("schedule tools — workflow mode", () => {
|
|
|
421
421
|
beforeEach(() => {
|
|
422
422
|
getRawDb().run("DELETE FROM cron_runs");
|
|
423
423
|
getRawDb().run("DELETE FROM cron_jobs");
|
|
424
|
-
setOverridesForTesting({
|
|
424
|
+
setOverridesForTesting({});
|
|
425
425
|
});
|
|
426
426
|
afterAll(() => {
|
|
427
427
|
setOverridesForTesting({});
|
|
@@ -466,22 +466,6 @@ describe("schedule tools — workflow mode", () => {
|
|
|
466
466
|
expect(result.content).toContain("workflow_name is required");
|
|
467
467
|
});
|
|
468
468
|
|
|
469
|
-
test("rejects workflow mode when the workflows flag is off", async () => {
|
|
470
|
-
setOverridesForTesting({}); // flag off
|
|
471
|
-
const result = await executeScheduleCreate(
|
|
472
|
-
{
|
|
473
|
-
name: "Flag off",
|
|
474
|
-
expression: "0 8 * * *",
|
|
475
|
-
mode: "workflow",
|
|
476
|
-
workflow_name: "inbox-triage",
|
|
477
|
-
},
|
|
478
|
-
ctx,
|
|
479
|
-
);
|
|
480
|
-
|
|
481
|
-
expect(result.isError).toBe(true);
|
|
482
|
-
expect(result.content).toContain("workflows are not enabled");
|
|
483
|
-
});
|
|
484
|
-
|
|
485
469
|
test("update to workflow mode requires a workflow_name", async () => {
|
|
486
470
|
await executeScheduleCreate(
|
|
487
471
|
{ name: "To workflow", expression: "0 9 * * *", message: "test" },
|
|
@@ -544,7 +528,7 @@ describe("schedule_create — workflow capability manifest", () => {
|
|
|
544
528
|
beforeEach(() => {
|
|
545
529
|
getRawDb().run("DELETE FROM cron_runs");
|
|
546
530
|
getRawDb().run("DELETE FROM cron_jobs");
|
|
547
|
-
setOverridesForTesting({
|
|
531
|
+
setOverridesForTesting({});
|
|
548
532
|
// Deterministic registry so a declared side-effecting tool resolves.
|
|
549
533
|
__clearRegistryForTesting();
|
|
550
534
|
registerTool(makeFakeTool("file_write"));
|
|
@@ -210,10 +210,13 @@ describe("scheduler conversation reuse", () => {
|
|
|
210
210
|
expect(runs2[0].conversationId).toBe(firstConversationId);
|
|
211
211
|
});
|
|
212
212
|
|
|
213
|
-
test("recurring schedule defaults to reuseConversation=
|
|
213
|
+
test("recurring schedule defaults to reuseConversation=false", async () => {
|
|
214
214
|
/**
|
|
215
215
|
* When no explicit reuseConversation is provided, recurring schedules
|
|
216
|
-
* default to
|
|
216
|
+
* default to false — each run gets a fresh conversation. This keeps a
|
|
217
|
+
* weak model's per-run context bounded instead of accumulating a long,
|
|
218
|
+
* self-similar transcript that primes it to repeat or drift; durable
|
|
219
|
+
* cross-run state belongs in workspace files and memory, not history.
|
|
217
220
|
*/
|
|
218
221
|
|
|
219
222
|
// GIVEN a recurring schedule with no explicit reuseConversation
|
|
@@ -224,7 +227,7 @@ describe("scheduler conversation reuse", () => {
|
|
|
224
227
|
message: "Default reuse message",
|
|
225
228
|
syntax: "rrule",
|
|
226
229
|
expression: rruleExpr,
|
|
227
|
-
// no explicit reuseConversation — should default to
|
|
230
|
+
// no explicit reuseConversation — should default to false
|
|
228
231
|
});
|
|
229
232
|
|
|
230
233
|
// WHEN the schedule fires for the first time
|
|
@@ -250,9 +253,9 @@ describe("scheduler conversation reuse", () => {
|
|
|
250
253
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
251
254
|
scheduler2.stop();
|
|
252
255
|
|
|
253
|
-
// THEN
|
|
256
|
+
// THEN a fresh conversation is created instead of reusing the first
|
|
254
257
|
expect(processedMessages).toHaveLength(1);
|
|
255
|
-
expect(processedMessages[0].conversationId).toBe(firstConversationId);
|
|
258
|
+
expect(processedMessages[0].conversationId).not.toBe(firstConversationId);
|
|
256
259
|
});
|
|
257
260
|
|
|
258
261
|
test("recurring schedule with reuseConversation=false creates new conversation each run", async () => {
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
augmentSkillExecuteError,
|
|
5
|
+
resolveSkillExecuteInput,
|
|
6
|
+
} from "../tools/skills/execute.js";
|
|
4
7
|
|
|
5
8
|
describe("resolveSkillExecuteInput", () => {
|
|
6
9
|
test("returns a correctly nested object unchanged", () => {
|
|
@@ -83,3 +86,50 @@ describe("resolveSkillExecuteInput", () => {
|
|
|
83
86
|
expect(result).toEqual({ foo: "bar" });
|
|
84
87
|
});
|
|
85
88
|
});
|
|
89
|
+
|
|
90
|
+
describe("augmentSkillExecuteError", () => {
|
|
91
|
+
test("appends envelope guidance when an empty-input call errors", () => {
|
|
92
|
+
// The subagent_spawn failure: empty input, tool rejects with a field-level
|
|
93
|
+
// message that says nothing about the skill_execute envelope.
|
|
94
|
+
const result = augmentSkillExecuteError(
|
|
95
|
+
"subagent_spawn",
|
|
96
|
+
{},
|
|
97
|
+
{ content: 'Both "label" and "objective" are required.', isError: true },
|
|
98
|
+
);
|
|
99
|
+
expect(result.isError).toBe(true);
|
|
100
|
+
expect(result.content).toContain(
|
|
101
|
+
'Both "label" and "objective" are required.',
|
|
102
|
+
);
|
|
103
|
+
expect(result.content).toContain("carried no parameters");
|
|
104
|
+
expect(result.content).toContain('"tool": "subagent_spawn"');
|
|
105
|
+
expect(result.content).toContain("inside `input`");
|
|
106
|
+
// The guidance must not condemn the JSON-encoded-string form: the resolver
|
|
107
|
+
// accepts it (resolveSkillExecuteInput parses string input), and it is a
|
|
108
|
+
// shape weak models successfully use. Telling them it is wrong steers them
|
|
109
|
+
// toward dropping the payload entirely.
|
|
110
|
+
expect(result.content).not.toContain("JSON-encoded string");
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("leaves errors untouched when parameters were resolved", () => {
|
|
114
|
+
// A non-empty resolved input means the model structured the call; any error
|
|
115
|
+
// is a real tool-level failure, not an envelope-shape mistake.
|
|
116
|
+
const original = {
|
|
117
|
+
content: "Subagent quota exceeded.",
|
|
118
|
+
isError: true,
|
|
119
|
+
};
|
|
120
|
+
const result = augmentSkillExecuteError(
|
|
121
|
+
"subagent_spawn",
|
|
122
|
+
{ label: "x", objective: "y" },
|
|
123
|
+
original,
|
|
124
|
+
);
|
|
125
|
+
expect(result).toBe(original);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("leaves successful empty-input calls untouched", () => {
|
|
129
|
+
// Tools that legitimately accept no parameters (e.g. subagent_status) must
|
|
130
|
+
// not have guidance appended to their successful results.
|
|
131
|
+
const original = { content: "No subagents running.", isError: false };
|
|
132
|
+
const result = augmentSkillExecuteError("subagent_status", {}, original);
|
|
133
|
+
expect(result).toBe(original);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
@@ -9,9 +9,8 @@
|
|
|
9
9
|
* branches (macOS `.app` Resources, next-to-binary) key off
|
|
10
10
|
* `import.meta.dir.startsWith("/$bunfs/")`, so at test time only the
|
|
11
11
|
* source-mode early-return is exercised here; the compiled branch is
|
|
12
|
-
* covered structurally via the signing + packaging step in
|
|
13
|
-
* `clients/macos/
|
|
14
|
-
* supervisor integration test added in PR 27.
|
|
12
|
+
* covered structurally via the Electron signing + packaging step in
|
|
13
|
+
* `clients/macos/scripts/pack.sh`.
|
|
15
14
|
*/
|
|
16
15
|
|
|
17
16
|
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
@@ -691,6 +691,57 @@ describe("category frontmatter parsing", () => {
|
|
|
691
691
|
});
|
|
692
692
|
});
|
|
693
693
|
|
|
694
|
+
describe("always-candidate frontmatter parsing", () => {
|
|
695
|
+
beforeEach(() => {
|
|
696
|
+
mkdirSync(join(TEST_DIR, "skills"), { recursive: true });
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
afterEach(() => {
|
|
700
|
+
const skillsDir = join(TEST_DIR, "skills");
|
|
701
|
+
if (existsSync(skillsDir))
|
|
702
|
+
rmSync(skillsDir, { recursive: true, force: true });
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
function writeSkillWithAlwaysCandidate(skillId: string, value: string): void {
|
|
706
|
+
const skillDir = join(TEST_DIR, "skills", skillId);
|
|
707
|
+
mkdirSync(skillDir, { recursive: true });
|
|
708
|
+
const metadata = `{"vellum":{"always-candidate":${value}}}`;
|
|
709
|
+
writeFileSync(
|
|
710
|
+
join(skillDir, "SKILL.md"),
|
|
711
|
+
`---\nname: "${skillId}"\ndescription: "test"\nmetadata: ${metadata}\n---\n\nBody.\n`,
|
|
712
|
+
);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
test("parses always-candidate: true from metadata.vellum", () => {
|
|
716
|
+
writeSkillWithAlwaysCandidate("pinned", "true");
|
|
717
|
+
const skill = loadUserSkillCatalog().find((s) => s.id === "pinned");
|
|
718
|
+
expect(skill!.alwaysCandidate).toBe(true);
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
test("parses always-candidate: false", () => {
|
|
722
|
+
writeSkillWithAlwaysCandidate("unpinned", "false");
|
|
723
|
+
const skill = loadUserSkillCatalog().find((s) => s.id === "unpinned");
|
|
724
|
+
expect(skill!.alwaysCandidate).toBe(false);
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
test("returns undefined for a non-boolean always-candidate", () => {
|
|
728
|
+
writeSkillWithAlwaysCandidate("bad", '"yes"');
|
|
729
|
+
const skill = loadUserSkillCatalog().find((s) => s.id === "bad");
|
|
730
|
+
expect(skill!.alwaysCandidate).toBeUndefined();
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
test("skill without always-candidate has undefined", () => {
|
|
734
|
+
writeSkill("plain", "Plain", "Test");
|
|
735
|
+
const skill = loadUserSkillCatalog().find((s) => s.id === "plain");
|
|
736
|
+
expect(skill!.alwaysCandidate).toBeUndefined();
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
test("the bundled workflows skill is flagged always-candidate", () => {
|
|
740
|
+
const wf = loadSkillCatalog().find((s) => s.id === "workflows");
|
|
741
|
+
expect(wf?.alwaysCandidate).toBe(true);
|
|
742
|
+
});
|
|
743
|
+
});
|
|
744
|
+
|
|
694
745
|
describe("bundled skill categories", () => {
|
|
695
746
|
test("every bundled skill declares a valid category slug", () => {
|
|
696
747
|
const yamlPath = join(
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { parseAccessRequestPayload } from "../notifications/access-request-copy.js";
|
|
4
|
+
import { buildApprovalNotificationBlocks } from "../notifications/adapters/slack.js";
|
|
5
|
+
import type { ChannelDeliveryPayload } from "../notifications/types.js";
|
|
6
|
+
import type { ApprovalUIMetadata } from "../runtime/channel-approval-types.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Pins the Slack approval-card block contract: the title, identity subtitle,
|
|
10
|
+
* quoted-preview body, action callback ids, source/permalink and requester-id
|
|
11
|
+
* context blocks, the security-warning context block, and guardian
|
|
12
|
+
* verification note that `buildApprovalNotificationBlocks` emits for an
|
|
13
|
+
* access request.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const APPROVAL: ApprovalUIMetadata = {
|
|
17
|
+
requestId: "req-123",
|
|
18
|
+
actions: [
|
|
19
|
+
{ id: "approve_once", label: "Approve once" },
|
|
20
|
+
{ id: "reject", label: "Reject" },
|
|
21
|
+
],
|
|
22
|
+
plainTextFallback: 'Reply "ABC123 approve" or "ABC123 reject"',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function buildPayload(
|
|
26
|
+
raw: Record<string, unknown>,
|
|
27
|
+
approval: ApprovalUIMetadata = APPROVAL,
|
|
28
|
+
): ChannelDeliveryPayload {
|
|
29
|
+
return {
|
|
30
|
+
sourceEventName: "ingress.access_request",
|
|
31
|
+
copy: { title: "Access Request", body: "Someone is requesting access" },
|
|
32
|
+
urgency: "high",
|
|
33
|
+
approvalContext: approval,
|
|
34
|
+
accessRequestContext: parseAccessRequestPayload(raw),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
type Block = Record<string, unknown>;
|
|
39
|
+
|
|
40
|
+
function card(blocks: unknown[]): Block {
|
|
41
|
+
const c = (blocks as Block[]).find((b) => b.type === "card");
|
|
42
|
+
if (!c) throw new Error("no card block");
|
|
43
|
+
return c;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function contextTexts(blocks: unknown[]): string[] {
|
|
47
|
+
return (blocks as Block[])
|
|
48
|
+
.filter((b) => b.type === "context")
|
|
49
|
+
.map((b) => {
|
|
50
|
+
const elements = b.elements as Array<{ text: string }>;
|
|
51
|
+
return elements.map((e) => e.text).join("");
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function text(node: unknown): string {
|
|
56
|
+
return (node as { text: string }).text;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const BASE: Record<string, unknown> = {
|
|
60
|
+
requestId: "req-123",
|
|
61
|
+
requestCode: "ABC123",
|
|
62
|
+
sourceChannel: "slack",
|
|
63
|
+
conversationExternalId: "C01ABC",
|
|
64
|
+
actorExternalId: "U999",
|
|
65
|
+
actorDisplayName: "Alice",
|
|
66
|
+
actorUsername: "alice",
|
|
67
|
+
senderIdentifier: "U999",
|
|
68
|
+
messagePreview: "Hello, I need help with something",
|
|
69
|
+
messageTs: "1700000000.000100",
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
describe("Slack access-request card blocks", () => {
|
|
73
|
+
test("card carries title, identity subtitle, and quoted preview body", () => {
|
|
74
|
+
const c = card(buildApprovalNotificationBlocks(buildPayload(BASE), "msg"));
|
|
75
|
+
expect(text(c.title)).toBe("Access Request");
|
|
76
|
+
expect(text(c.subtitle)).toBe("Alice (@alice) via slack");
|
|
77
|
+
expect(text(c.body)).toBe('> _"Hello, I need help with something"_');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("card actions encode the apr:<requestId>:<action> callback ids", () => {
|
|
81
|
+
const c = card(buildApprovalNotificationBlocks(buildPayload(BASE), "msg"));
|
|
82
|
+
const actions = c.actions as Array<Record<string, unknown>>;
|
|
83
|
+
expect(actions).toHaveLength(2);
|
|
84
|
+
expect(actions[0].action_id).toBe("apr:req-123:approve_once");
|
|
85
|
+
expect(actions[0].style).toBe("primary");
|
|
86
|
+
expect(actions[1].action_id).toBe("apr:req-123:reject");
|
|
87
|
+
expect(actions[1].style).toBe("danger");
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("source context renders a channel mention with permalink", () => {
|
|
91
|
+
const texts = contextTexts(
|
|
92
|
+
buildApprovalNotificationBlocks(buildPayload(BASE), "msg"),
|
|
93
|
+
);
|
|
94
|
+
expect(texts).toContain(
|
|
95
|
+
"Source: Slack — <#C01ABC> · <https://slack.com/archives/C01ABC/p1700000000000100|View message>",
|
|
96
|
+
);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("DM source renders as Direct message", () => {
|
|
100
|
+
const blocks = buildApprovalNotificationBlocks(
|
|
101
|
+
buildPayload({ ...BASE, conversationExternalId: "D01XYZ" }),
|
|
102
|
+
"msg",
|
|
103
|
+
);
|
|
104
|
+
expect(contextTexts(blocks)).toContain(
|
|
105
|
+
"Source: Slack — Direct message · <https://slack.com/archives/D01XYZ/p1700000000000100|View message>",
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("requester id block appears when external id adds info", () => {
|
|
110
|
+
const texts = contextTexts(
|
|
111
|
+
buildApprovalNotificationBlocks(buildPayload(BASE), "msg"),
|
|
112
|
+
);
|
|
113
|
+
expect(texts).toContain("ID: U999");
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("invite directive context block is always present", () => {
|
|
117
|
+
const texts = contextTexts(
|
|
118
|
+
buildApprovalNotificationBlocks(buildPayload(BASE), "msg"),
|
|
119
|
+
);
|
|
120
|
+
expect(texts).toContain(
|
|
121
|
+
'Reply "open invite flow" to start Trusted Contacts invite flow.',
|
|
122
|
+
);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("warnings render in a context block under the card", () => {
|
|
126
|
+
const blocks = buildApprovalNotificationBlocks(
|
|
127
|
+
buildPayload({
|
|
128
|
+
...BASE,
|
|
129
|
+
isStranger: true,
|
|
130
|
+
isRestricted: true,
|
|
131
|
+
previousMemberStatus: "revoked",
|
|
132
|
+
}),
|
|
133
|
+
"msg",
|
|
134
|
+
);
|
|
135
|
+
// `subtext` is not a Slack card field; warnings must live in a real block
|
|
136
|
+
// or Slack drops them and the guardian never sees them.
|
|
137
|
+
expect(card(blocks).subtext).toBeUndefined();
|
|
138
|
+
const warning = contextTexts(blocks).find((t) => t.includes(":warning:"));
|
|
139
|
+
expect(warning).toBeDefined();
|
|
140
|
+
expect(warning).toContain(":warning: This user was previously revoked.");
|
|
141
|
+
expect(warning).toContain(
|
|
142
|
+
":warning: External Slack user (not in this workspace).",
|
|
143
|
+
);
|
|
144
|
+
expect(warning).toContain(":warning: Guest / restricted account.");
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("no warning context block when there are no warnings", () => {
|
|
148
|
+
const blocks = buildApprovalNotificationBlocks(buildPayload(BASE), "msg");
|
|
149
|
+
expect(card(blocks).subtext).toBeUndefined();
|
|
150
|
+
expect(contextTexts(blocks).some((t) => t.includes(":warning:"))).toBe(
|
|
151
|
+
false,
|
|
152
|
+
);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("body falls back to a default label when no preview", () => {
|
|
156
|
+
const c = card(
|
|
157
|
+
buildApprovalNotificationBlocks(
|
|
158
|
+
buildPayload({ ...BASE, messagePreview: undefined }),
|
|
159
|
+
"msg",
|
|
160
|
+
),
|
|
161
|
+
);
|
|
162
|
+
expect(text(c.body)).toBe("Requesting access to the assistant");
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("guardian verification note appears for fallback guardian resolution", () => {
|
|
166
|
+
const texts = contextTexts(
|
|
167
|
+
buildApprovalNotificationBlocks(
|
|
168
|
+
buildPayload({ ...BASE, guardianResolutionSource: "vellum-anchor" }),
|
|
169
|
+
"msg",
|
|
170
|
+
),
|
|
171
|
+
);
|
|
172
|
+
expect(
|
|
173
|
+
texts.some((t) => t.includes("haven't verified your identity on slack")),
|
|
174
|
+
).toBe(true);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for guardian approval-by-reaction on the canonical pipeline.
|
|
3
|
+
*
|
|
4
|
+
* A Slack emoji reaction (✅ / ❌) on a delivered approval card is routed
|
|
5
|
+
* through `routeGuardianReply` exactly like a button press or text reply. The
|
|
6
|
+
* emoji maps to an action and the reacted card's message id (`reactedMessageTs`)
|
|
7
|
+
* resolves the target request via its canonical delivery record — so the
|
|
8
|
+
* decision is applied precisely even when several cards are pending in the same
|
|
9
|
+
* chat, with no clarification prompt.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
13
|
+
|
|
14
|
+
mock.module("../util/logger.js", () => ({
|
|
15
|
+
getLogger: () =>
|
|
16
|
+
new Proxy({} as Record<string, unknown>, {
|
|
17
|
+
get: () => () => {},
|
|
18
|
+
}),
|
|
19
|
+
truncateForLog: (value: string) => value,
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
const _conversationMocks = new Map<string, unknown>();
|
|
23
|
+
mock.module("../daemon/conversation-registry.js", () => ({
|
|
24
|
+
findConversation: (id: string) => _conversationMocks.get(id),
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
import type { Conversation } from "../daemon/conversation.js";
|
|
28
|
+
import {
|
|
29
|
+
createCanonicalGuardianDelivery,
|
|
30
|
+
createCanonicalGuardianRequest,
|
|
31
|
+
getCanonicalGuardianRequest,
|
|
32
|
+
getPendingCanonicalRequestByDestinationMessage,
|
|
33
|
+
resolveCanonicalGuardianRequest,
|
|
34
|
+
} from "../memory/canonical-guardian-store.js";
|
|
35
|
+
import { getDb } from "../memory/db-connection.js";
|
|
36
|
+
import { initializeDb } from "../memory/db-init.js";
|
|
37
|
+
import { routeGuardianReply } from "../runtime/guardian-reply-router.js";
|
|
38
|
+
import * as pendingInteractions from "../runtime/pending-interactions.js";
|
|
39
|
+
|
|
40
|
+
initializeDb();
|
|
41
|
+
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
// Constants & helpers
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
const GUARDIAN_USER = "U_GUARDIAN";
|
|
47
|
+
const PRINCIPAL = "principal-1";
|
|
48
|
+
const CHAT_ID = "D_GUARDIAN_DM";
|
|
49
|
+
const TOOL_NAME = "execute_shell";
|
|
50
|
+
|
|
51
|
+
function resetTables(): void {
|
|
52
|
+
const db = getDb();
|
|
53
|
+
db.run("DELETE FROM canonical_guardian_deliveries");
|
|
54
|
+
db.run("DELETE FROM canonical_guardian_requests");
|
|
55
|
+
db.run("DELETE FROM scoped_approval_grants");
|
|
56
|
+
pendingInteractions.clear();
|
|
57
|
+
_conversationMocks.clear();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Seed a pending tool-approval (canonical request + delivery + pending
|
|
62
|
+
* interaction with a mock conversation) and return the mocked
|
|
63
|
+
* `handleConfirmationResponse` so callers can assert it was driven.
|
|
64
|
+
*/
|
|
65
|
+
function seedApproval(opts: {
|
|
66
|
+
requestId: string;
|
|
67
|
+
ts: string;
|
|
68
|
+
chatId?: string;
|
|
69
|
+
principal?: string;
|
|
70
|
+
}): ReturnType<typeof mock> {
|
|
71
|
+
const chatId = opts.chatId ?? CHAT_ID;
|
|
72
|
+
const principal = opts.principal ?? PRINCIPAL;
|
|
73
|
+
const conversationId = `conv-${opts.requestId}`;
|
|
74
|
+
|
|
75
|
+
createCanonicalGuardianRequest({
|
|
76
|
+
id: opts.requestId,
|
|
77
|
+
kind: "tool_approval",
|
|
78
|
+
sourceType: "channel",
|
|
79
|
+
sourceChannel: "slack",
|
|
80
|
+
conversationId,
|
|
81
|
+
requesterExternalUserId: "requester-1",
|
|
82
|
+
requesterChatId: chatId,
|
|
83
|
+
guardianExternalUserId: GUARDIAN_USER,
|
|
84
|
+
guardianPrincipalId: principal,
|
|
85
|
+
toolName: TOOL_NAME,
|
|
86
|
+
status: "pending",
|
|
87
|
+
expiresAt: Date.now() + 300_000,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
createCanonicalGuardianDelivery({
|
|
91
|
+
requestId: opts.requestId,
|
|
92
|
+
destinationChannel: "slack",
|
|
93
|
+
destinationChatId: chatId,
|
|
94
|
+
destinationMessageId: opts.ts,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const handleConfirmationResponse = mock(() => {});
|
|
98
|
+
_conversationMocks.set(conversationId, {
|
|
99
|
+
handleConfirmationResponse,
|
|
100
|
+
ensureActorScopedHistory: async () => {},
|
|
101
|
+
} as unknown as Conversation);
|
|
102
|
+
pendingInteractions.register(opts.requestId, {
|
|
103
|
+
conversationId,
|
|
104
|
+
kind: "confirmation",
|
|
105
|
+
confirmationDetails: {
|
|
106
|
+
toolName: TOOL_NAME,
|
|
107
|
+
input: { command: "echo hi" },
|
|
108
|
+
riskLevel: "high",
|
|
109
|
+
allowlistOptions: [{ label: "t", description: "t", pattern: "t" }],
|
|
110
|
+
scopeOptions: [{ label: "everywhere", scope: "everywhere" }],
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
return handleConfirmationResponse;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function reaction(opts: {
|
|
117
|
+
emoji: string;
|
|
118
|
+
reactedMessageTs?: string;
|
|
119
|
+
chatId?: string;
|
|
120
|
+
principal?: string;
|
|
121
|
+
}): Parameters<typeof routeGuardianReply>[0] {
|
|
122
|
+
return {
|
|
123
|
+
messageText: "",
|
|
124
|
+
channel: "slack",
|
|
125
|
+
actor: {
|
|
126
|
+
actorPrincipalId: opts.principal ?? PRINCIPAL,
|
|
127
|
+
actorExternalUserId: GUARDIAN_USER,
|
|
128
|
+
channel: "slack",
|
|
129
|
+
guardianPrincipalId: opts.principal ?? PRINCIPAL,
|
|
130
|
+
},
|
|
131
|
+
conversationId: "guardian-conv",
|
|
132
|
+
callbackData: `reaction:${opts.emoji}`,
|
|
133
|
+
reactedMessageTs: opts.reactedMessageTs,
|
|
134
|
+
channelDeliveryContext: {
|
|
135
|
+
replyCallbackUrl: "https://gateway.test/deliver",
|
|
136
|
+
guardianChatId: opts.chatId ?? CHAT_ID,
|
|
137
|
+
assistantId: "self",
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
// Store lookup
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
|
|
146
|
+
describe("getPendingCanonicalRequestByDestinationMessage", () => {
|
|
147
|
+
beforeEach(resetTables);
|
|
148
|
+
|
|
149
|
+
test("resolves the request whose delivery matches channel + chat + message id", () => {
|
|
150
|
+
seedApproval({ requestId: "req-1", ts: "1700000000.000001" });
|
|
151
|
+
const found = getPendingCanonicalRequestByDestinationMessage(
|
|
152
|
+
"slack",
|
|
153
|
+
CHAT_ID,
|
|
154
|
+
"1700000000.000001",
|
|
155
|
+
);
|
|
156
|
+
expect(found?.id).toBe("req-1");
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("returns null when no delivery matches the message id", () => {
|
|
160
|
+
seedApproval({ requestId: "req-1", ts: "1700000000.000001" });
|
|
161
|
+
expect(
|
|
162
|
+
getPendingCanonicalRequestByDestinationMessage(
|
|
163
|
+
"slack",
|
|
164
|
+
CHAT_ID,
|
|
165
|
+
"9999.0",
|
|
166
|
+
),
|
|
167
|
+
).toBeNull();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test("returns null once the matched request is no longer pending", () => {
|
|
171
|
+
seedApproval({ requestId: "req-1", ts: "1700000000.000001" });
|
|
172
|
+
resolveCanonicalGuardianRequest("req-1", "pending", { status: "approved" });
|
|
173
|
+
expect(
|
|
174
|
+
getPendingCanonicalRequestByDestinationMessage(
|
|
175
|
+
"slack",
|
|
176
|
+
CHAT_ID,
|
|
177
|
+
"1700000000.000001",
|
|
178
|
+
),
|
|
179
|
+
).toBeNull();
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// ---------------------------------------------------------------------------
|
|
184
|
+
// Reaction routing
|
|
185
|
+
// ---------------------------------------------------------------------------
|
|
186
|
+
|
|
187
|
+
describe("routeGuardianReply / reactions", () => {
|
|
188
|
+
beforeEach(resetTables);
|
|
189
|
+
|
|
190
|
+
test("✅ on a delivered card approves the matching request", async () => {
|
|
191
|
+
const hcr = seedApproval({ requestId: "req-1", ts: "111.1" });
|
|
192
|
+
|
|
193
|
+
const result = await routeGuardianReply(
|
|
194
|
+
reaction({ emoji: "white_check_mark", reactedMessageTs: "111.1" }),
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
expect(result.consumed).toBe(true);
|
|
198
|
+
expect(result.decisionApplied).toBe(true);
|
|
199
|
+
expect(result.type).toBe("canonical_decision_applied");
|
|
200
|
+
expect(getCanonicalGuardianRequest("req-1")?.status).toBe("approved");
|
|
201
|
+
expect(hcr).toHaveBeenCalledTimes(1);
|
|
202
|
+
expect(hcr.mock.calls[0]?.[0]).toBe("req-1");
|
|
203
|
+
expect(hcr.mock.calls[0]?.[1]).toBe("allow");
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test("❌ on a delivered card rejects the matching request", async () => {
|
|
207
|
+
const hcr = seedApproval({ requestId: "req-1", ts: "111.1" });
|
|
208
|
+
|
|
209
|
+
const result = await routeGuardianReply(
|
|
210
|
+
reaction({ emoji: "-1", reactedMessageTs: "111.1" }),
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
expect(result.decisionApplied).toBe(true);
|
|
214
|
+
expect(getCanonicalGuardianRequest("req-1")?.status).toBe("denied");
|
|
215
|
+
expect(hcr.mock.calls[0]?.[1]).toBe("deny");
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test("reacting on a specific card resolves only that card (disambiguates N>1)", async () => {
|
|
219
|
+
const hcrA = seedApproval({ requestId: "req-A", ts: "100.1" });
|
|
220
|
+
const hcrB = seedApproval({ requestId: "req-B", ts: "200.2" });
|
|
221
|
+
|
|
222
|
+
// React on card B's message — only B should resolve.
|
|
223
|
+
const result = await routeGuardianReply(
|
|
224
|
+
reaction({ emoji: "white_check_mark", reactedMessageTs: "200.2" }),
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
expect(result.requestId).toBe("req-B");
|
|
228
|
+
expect(getCanonicalGuardianRequest("req-B")?.status).toBe("approved");
|
|
229
|
+
expect(getCanonicalGuardianRequest("req-A")?.status).toBe("pending");
|
|
230
|
+
expect(hcrB).toHaveBeenCalledTimes(1);
|
|
231
|
+
expect(hcrA).not.toHaveBeenCalled();
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test("a reaction on an unknown message is not consumed (left for transcript)", async () => {
|
|
235
|
+
seedApproval({ requestId: "req-1", ts: "111.1" });
|
|
236
|
+
|
|
237
|
+
const result = await routeGuardianReply(
|
|
238
|
+
reaction({ emoji: "white_check_mark", reactedMessageTs: "no-such-ts" }),
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
expect(result.consumed).toBe(false);
|
|
242
|
+
expect(result.type).toBe("not_consumed");
|
|
243
|
+
expect(getCanonicalGuardianRequest("req-1")?.status).toBe("pending");
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test("an unknown emoji is not consumed", async () => {
|
|
247
|
+
seedApproval({ requestId: "req-1", ts: "111.1" });
|
|
248
|
+
|
|
249
|
+
const result = await routeGuardianReply(
|
|
250
|
+
reaction({ emoji: "tada", reactedMessageTs: "111.1" }),
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
expect(result.consumed).toBe(false);
|
|
254
|
+
expect(getCanonicalGuardianRequest("req-1")?.status).toBe("pending");
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
test("a reaction without a reacted message id is not consumed", async () => {
|
|
258
|
+
seedApproval({ requestId: "req-1", ts: "111.1" });
|
|
259
|
+
|
|
260
|
+
const result = await routeGuardianReply(
|
|
261
|
+
reaction({ emoji: "white_check_mark" }),
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
expect(result.consumed).toBe(false);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
test("a reaction from a different principal is surfaced, not silently dropped", async () => {
|
|
268
|
+
const hcr = seedApproval({ requestId: "req-1", ts: "111.1" });
|
|
269
|
+
|
|
270
|
+
const result = await routeGuardianReply(
|
|
271
|
+
reaction({
|
|
272
|
+
emoji: "white_check_mark",
|
|
273
|
+
reactedMessageTs: "111.1",
|
|
274
|
+
principal: "someone-else",
|
|
275
|
+
}),
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
// Consumed with a user-facing reply, request untouched, side effect not run.
|
|
279
|
+
expect(result.consumed).toBe(true);
|
|
280
|
+
expect(result.decisionApplied).toBe(false);
|
|
281
|
+
expect(result.replyText).toMatch(/permission/i);
|
|
282
|
+
expect(getCanonicalGuardianRequest("req-1")?.status).toBe("pending");
|
|
283
|
+
expect(hcr).not.toHaveBeenCalled();
|
|
284
|
+
});
|
|
285
|
+
});
|