@vellumai/assistant 0.9.0 → 0.10.0-dev.202606192226.8ed8a6a
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 +19 -35
- 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 +1128 -119
- 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__/agent-loop-pushes-post-hook-prompt.test.ts +306 -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__/approval-cascade.test.ts +6 -0
- package/src/__tests__/approval-routes-http.test.ts +128 -0
- 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__/compaction-events.test.ts +6 -0
- 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-abort-tool-results.test.ts +6 -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-confirmation-signals.test.ts +6 -0
- package/src/__tests__/conversation-lifecycle.test.ts +7 -9
- package/src/__tests__/conversation-load-history-repair.test.ts +101 -0
- package/src/__tests__/conversation-process-callsite.test.ts +6 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +6 -0
- package/src/__tests__/conversation-queue.test.ts +6 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +15 -12
- package/src/__tests__/conversation-slash-queue.test.ts +6 -0
- package/src/__tests__/conversation-slash-unknown.test.ts +6 -0
- package/src/__tests__/conversation-speed-override.test.ts +6 -0
- package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -3
- package/src/__tests__/conversation-title-service.test.ts +62 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +6 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +6 -0
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +6 -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-pressure-tools.test.ts +41 -1
- package/src/__tests__/disk-usage.test.ts +65 -0
- package/src/__tests__/document-tool-security.test.ts +9 -6
- package/src/__tests__/document-update-default-surface.test.ts +202 -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__/gemini-count-tokens.test.ts +70 -0
- 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-disk-pressure.test.ts +10 -5
- 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__/secret-response-routing.test.ts +35 -0
- package/src/__tests__/secret-routes-platform-proxy.test.ts +16 -2
- package/src/__tests__/skill-execute-input.test.ts +177 -1
- package/src/__tests__/skill-load-tool.test.ts +72 -0
- 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__/system-storage-cleanup-skill.test.ts +56 -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 +75 -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/db/status.ts +37 -3
- 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 +400 -14
- 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__/install-from-github.test.ts +31 -0
- package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +443 -0
- package/src/cli/lib/__tests__/plugin-pin-history.test.ts +162 -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 +128 -19
- package/src/cli/lib/merge-plugin-tree.ts +328 -0
- package/src/cli/lib/plugin-fingerprint.ts +14 -0
- package/src/cli/lib/plugin-marketplace.ts +11 -0
- package/src/cli/lib/plugin-pin-history.ts +257 -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/document-editor/SKILL.md +3 -3
- package/src/config/bundled-skills/document-editor/TOOLS.json +2 -2
- 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/system-storage-cleanup/SKILL.md +74 -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/__tests__/conversation-tool-setup.test.ts +44 -0
- 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 +53 -19
- package/src/daemon/conversation.ts +131 -20
- 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/memory-v2-startup.test.ts +131 -0
- package/src/daemon/memory-v2-startup.ts +100 -1
- 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/daemon/wake-conversation-ops.ts +1 -0
- 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__/db-logs-attach.test.ts +122 -0
- 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-async-query.ts +20 -2
- package/src/memory/db-connection.ts +49 -5
- 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/permissions/secret-prompter.ts +15 -1
- 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/compaction/window-manager.ts +18 -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/injectors.ts +4 -4
- 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__/count-tokens-forwarding.test.ts +98 -0
- 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/anthropic/client.ts +230 -175
- package/src/providers/atlascloud/client.ts +85 -0
- package/src/providers/call-site-routing.ts +10 -0
- package/src/providers/fetch-provider-catalog.ts +85 -0
- package/src/providers/gemini/client.ts +43 -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/ratelimit.ts +9 -0
- package/src/providers/retry.ts +62 -12
- package/src/providers/search-provider-catalog.ts +10 -0
- package/src/providers/types.ts +16 -0
- package/src/providers/usage-tracking.ts +7 -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 +227 -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 +71 -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 +126 -0
- package/src/runtime/migrations/vbundle-builder.ts +49 -20
- package/src/runtime/pending-interactions.ts +35 -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 +383 -1
- package/src/runtime/routes/app-routes.ts +1 -1
- package/src/runtime/routes/approval-routes.ts +40 -9
- 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 +366 -11
- package/src/runtime/routes/schedule-routes.ts +0 -22
- package/src/runtime/routes/secret-routes.ts +15 -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 +30 -6
- 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 +99 -2
- package/src/tools/skills/load.ts +43 -2
- package/src/tools/subagent/spawn.ts +64 -15
- 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 +10 -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/logs-db-path.ts +22 -0
- 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 () => {
|
|
@@ -106,6 +106,41 @@ describe("secret response routing", () => {
|
|
|
106
106
|
await promise;
|
|
107
107
|
});
|
|
108
108
|
|
|
109
|
+
test("prompt registers public secretDetails without the value", async () => {
|
|
110
|
+
const promise = prompter.prompt(
|
|
111
|
+
"github",
|
|
112
|
+
"token",
|
|
113
|
+
"GitHub Token",
|
|
114
|
+
"desc",
|
|
115
|
+
"placeholder",
|
|
116
|
+
"session-1",
|
|
117
|
+
"Push commits",
|
|
118
|
+
["git_push"],
|
|
119
|
+
["github.com"],
|
|
120
|
+
);
|
|
121
|
+
const msg = broadcastedMessages[0] as SecretRequestEvent;
|
|
122
|
+
const entry = _piStore.get(msg.requestId) as {
|
|
123
|
+
kind: string;
|
|
124
|
+
secretDetails?: Record<string, unknown>;
|
|
125
|
+
};
|
|
126
|
+
expect(entry.kind).toBe("secret");
|
|
127
|
+
expect(entry.secretDetails).toMatchObject({
|
|
128
|
+
service: "github",
|
|
129
|
+
field: "token",
|
|
130
|
+
label: "GitHub Token",
|
|
131
|
+
description: "desc",
|
|
132
|
+
placeholder: "placeholder",
|
|
133
|
+
purpose: "Push commits",
|
|
134
|
+
allowedTools: ["git_push"],
|
|
135
|
+
allowedDomains: ["github.com"],
|
|
136
|
+
});
|
|
137
|
+
// SECURITY: the secret value is never part of the registered metadata.
|
|
138
|
+
expect(JSON.stringify(entry.secretDetails)).not.toContain("test-value");
|
|
139
|
+
// Clean up
|
|
140
|
+
prompter.resolveSecret(msg.requestId, undefined);
|
|
141
|
+
await promise;
|
|
142
|
+
});
|
|
143
|
+
|
|
109
144
|
test("resolveSecret for unknown requestId is a no-op", () => {
|
|
110
145
|
// Should not throw
|
|
111
146
|
prompter.resolveSecret("unknown-id", "value", "store");
|
|
@@ -12,7 +12,12 @@ let providerRefreshCalls = 0;
|
|
|
12
12
|
const PLATFORM_BASE_URL = "https://platform.example.com";
|
|
13
13
|
const ASSISTANT_API_KEY_PATH = credentialKey("vellum", "assistant_api_key");
|
|
14
14
|
const PLATFORM_BASE_URL_PATH = credentialKey("vellum", "platform_base_url");
|
|
15
|
-
const MANAGED_PROVIDERS = [
|
|
15
|
+
const MANAGED_PROVIDERS = [
|
|
16
|
+
"anthropic",
|
|
17
|
+
"openai",
|
|
18
|
+
"gemini",
|
|
19
|
+
"fireworks",
|
|
20
|
+
] as const;
|
|
16
21
|
|
|
17
22
|
let platformBaseUrlOverride: string | undefined;
|
|
18
23
|
|
|
@@ -116,6 +121,13 @@ mock.module("../util/logger.js", () => ({
|
|
|
116
121
|
}),
|
|
117
122
|
}));
|
|
118
123
|
|
|
124
|
+
// `handleAddSecret` fires this detached when a managed-proxy credential lands —
|
|
125
|
+
// a v2-memory side effect outside this suite's provider-registry scope. Stub it
|
|
126
|
+
// to a no-op; its behavior is covered by memory-v2-startup.test.ts.
|
|
127
|
+
mock.module("../daemon/memory-v2-startup.js", () => ({
|
|
128
|
+
maybeReseedCapabilitiesAfterManagedCredential: async () => {},
|
|
129
|
+
}));
|
|
130
|
+
|
|
119
131
|
import {
|
|
120
132
|
getProviderRoutingSource,
|
|
121
133
|
initializeProviders,
|
|
@@ -199,7 +211,9 @@ describe("secret routes managed proxy registry sync", () => {
|
|
|
199
211
|
test("provider API key writes notify live-conversation refresh listeners", async () => {
|
|
200
212
|
await addApiKey("fireworks", "fw-key");
|
|
201
213
|
|
|
202
|
-
expect(secureKeyStore[credentialKey("fireworks", "api_key")]).toBe(
|
|
214
|
+
expect(secureKeyStore[credentialKey("fireworks", "api_key")]).toBe(
|
|
215
|
+
"fw-key",
|
|
216
|
+
);
|
|
203
217
|
expect(providerRefreshCalls).toBe(1);
|
|
204
218
|
|
|
205
219
|
await deleteApiKey("fireworks");
|
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
augmentSkillExecuteError,
|
|
5
|
+
recoverSkillExecuteEnvelope,
|
|
6
|
+
resolveSkillExecuteInput,
|
|
7
|
+
} from "../tools/skills/execute.js";
|
|
8
|
+
|
|
9
|
+
/** Schema with exactly one required string field (e.g. document_update). */
|
|
10
|
+
const SINGLE_REQUIRED_STRING_SCHEMA = {
|
|
11
|
+
type: "object",
|
|
12
|
+
properties: {
|
|
13
|
+
content: { type: "string" },
|
|
14
|
+
mode: { type: "string", enum: ["replace", "append"] },
|
|
15
|
+
},
|
|
16
|
+
required: ["content"],
|
|
17
|
+
};
|
|
4
18
|
|
|
5
19
|
describe("resolveSkillExecuteInput", () => {
|
|
6
20
|
test("returns a correctly nested object unchanged", () => {
|
|
@@ -82,4 +96,166 @@ describe("resolveSkillExecuteInput", () => {
|
|
|
82
96
|
});
|
|
83
97
|
expect(result).toEqual({ foo: "bar" });
|
|
84
98
|
});
|
|
99
|
+
|
|
100
|
+
test("maps a bare (non-JSON) input string to the sole required string field", () => {
|
|
101
|
+
// The exact shape from the doc-writer incident: the full Markdown body
|
|
102
|
+
// passed as `input` instead of `{ "content": "..." }`.
|
|
103
|
+
const body = "# AI in 2026\n\nWe're halfway through the year.";
|
|
104
|
+
const result = resolveSkillExecuteInput(
|
|
105
|
+
{ tool: "document_update", input: body, activity: "Streaming article" },
|
|
106
|
+
SINGLE_REQUIRED_STRING_SCHEMA,
|
|
107
|
+
);
|
|
108
|
+
expect(result).toEqual({ content: body });
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("does not map a bare string without the inner schema", () => {
|
|
112
|
+
const result = resolveSkillExecuteInput({
|
|
113
|
+
tool: "document_update",
|
|
114
|
+
input: "# AI in 2026",
|
|
115
|
+
activity: "Streaming article",
|
|
116
|
+
});
|
|
117
|
+
expect(result).toEqual({});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("does not map a bare string when the schema has multiple required fields", () => {
|
|
121
|
+
const schema = {
|
|
122
|
+
type: "object",
|
|
123
|
+
properties: { a: { type: "string" }, b: { type: "string" } },
|
|
124
|
+
required: ["a", "b"],
|
|
125
|
+
};
|
|
126
|
+
const result = resolveSkillExecuteInput(
|
|
127
|
+
{ tool: "t", input: "some text", activity: "x" },
|
|
128
|
+
schema,
|
|
129
|
+
);
|
|
130
|
+
expect(result).toEqual({});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("does not map a bare string when the sole required field is not a string", () => {
|
|
134
|
+
const schema = {
|
|
135
|
+
type: "object",
|
|
136
|
+
properties: { count: { type: "number" } },
|
|
137
|
+
required: ["count"],
|
|
138
|
+
};
|
|
139
|
+
const result = resolveSkillExecuteInput(
|
|
140
|
+
{ tool: "t", input: "42", activity: "x" },
|
|
141
|
+
schema,
|
|
142
|
+
);
|
|
143
|
+
// "42" parses as JSON but isn't an object, and the lone required field is
|
|
144
|
+
// not a string — no rescue applies.
|
|
145
|
+
expect(result).toEqual({});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("a valid JSON-object string still wins over the bare-string rescue", () => {
|
|
149
|
+
const result = resolveSkillExecuteInput(
|
|
150
|
+
{
|
|
151
|
+
tool: "document_update",
|
|
152
|
+
input: '{"content":"hello","mode":"append"}',
|
|
153
|
+
activity: "x",
|
|
154
|
+
},
|
|
155
|
+
SINGLE_REQUIRED_STRING_SCHEMA,
|
|
156
|
+
);
|
|
157
|
+
expect(result).toEqual({ content: "hello", mode: "append" });
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("an empty input string is not rescued (nothing to map)", () => {
|
|
161
|
+
const result = resolveSkillExecuteInput(
|
|
162
|
+
{ tool: "document_update", input: "", activity: "x" },
|
|
163
|
+
SINGLE_REQUIRED_STRING_SCHEMA,
|
|
164
|
+
);
|
|
165
|
+
expect(result).toEqual({});
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe("recoverSkillExecuteEnvelope", () => {
|
|
170
|
+
test("recovers a valid envelope wrapped under the _raw marker", () => {
|
|
171
|
+
// MiniMax coercion marks a bare-string `input` call unparseable even though
|
|
172
|
+
// the outer arguments are valid JSON.
|
|
173
|
+
const raw = JSON.stringify({
|
|
174
|
+
tool: "document_update",
|
|
175
|
+
input: "# AI in 2026\n\nbody",
|
|
176
|
+
activity: "Streaming",
|
|
177
|
+
});
|
|
178
|
+
const recovered = recoverSkillExecuteEnvelope({ _raw: raw });
|
|
179
|
+
expect(recovered).toEqual({
|
|
180
|
+
tool: "document_update",
|
|
181
|
+
input: "# AI in 2026\n\nbody",
|
|
182
|
+
activity: "Streaming",
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("leaves a genuinely unparseable (truncated) call wrapped", () => {
|
|
187
|
+
const wrapped = { _raw: '{"tool":"document_update","input":"# AI' };
|
|
188
|
+
expect(recoverSkillExecuteEnvelope(wrapped)).toBe(wrapped);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test("passes a normal envelope through untouched", () => {
|
|
192
|
+
const envelope = {
|
|
193
|
+
tool: "document_update",
|
|
194
|
+
input: { content: "hi" },
|
|
195
|
+
activity: "x",
|
|
196
|
+
};
|
|
197
|
+
expect(recoverSkillExecuteEnvelope(envelope)).toBe(envelope);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test("end-to-end: recovered bare-string envelope resolves to content", () => {
|
|
201
|
+
const body = "# Title\n\nThe full article body.";
|
|
202
|
+
const raw = JSON.stringify({
|
|
203
|
+
tool: "document_update",
|
|
204
|
+
input: body,
|
|
205
|
+
activity: "Streaming",
|
|
206
|
+
});
|
|
207
|
+
const envelope = recoverSkillExecuteEnvelope({ _raw: raw });
|
|
208
|
+
const resolved = resolveSkillExecuteInput(
|
|
209
|
+
envelope,
|
|
210
|
+
SINGLE_REQUIRED_STRING_SCHEMA,
|
|
211
|
+
);
|
|
212
|
+
expect(resolved).toEqual({ content: body });
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
describe("augmentSkillExecuteError", () => {
|
|
217
|
+
test("appends envelope guidance when an empty-input call errors", () => {
|
|
218
|
+
// The subagent_spawn failure: empty input, tool rejects with a field-level
|
|
219
|
+
// message that says nothing about the skill_execute envelope.
|
|
220
|
+
const result = augmentSkillExecuteError(
|
|
221
|
+
"subagent_spawn",
|
|
222
|
+
{},
|
|
223
|
+
{ content: 'Both "label" and "objective" are required.', isError: true },
|
|
224
|
+
);
|
|
225
|
+
expect(result.isError).toBe(true);
|
|
226
|
+
expect(result.content).toContain(
|
|
227
|
+
'Both "label" and "objective" are required.',
|
|
228
|
+
);
|
|
229
|
+
expect(result.content).toContain("carried no parameters");
|
|
230
|
+
expect(result.content).toContain('"tool": "subagent_spawn"');
|
|
231
|
+
expect(result.content).toContain("inside `input`");
|
|
232
|
+
// The guidance must not condemn the JSON-encoded-string form: the resolver
|
|
233
|
+
// accepts it (resolveSkillExecuteInput parses string input), and it is a
|
|
234
|
+
// shape weak models successfully use. Telling them it is wrong steers them
|
|
235
|
+
// toward dropping the payload entirely.
|
|
236
|
+
expect(result.content).not.toContain("JSON-encoded string");
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
test("leaves errors untouched when parameters were resolved", () => {
|
|
240
|
+
// A non-empty resolved input means the model structured the call; any error
|
|
241
|
+
// is a real tool-level failure, not an envelope-shape mistake.
|
|
242
|
+
const original = {
|
|
243
|
+
content: "Subagent quota exceeded.",
|
|
244
|
+
isError: true,
|
|
245
|
+
};
|
|
246
|
+
const result = augmentSkillExecuteError(
|
|
247
|
+
"subagent_spawn",
|
|
248
|
+
{ label: "x", objective: "y" },
|
|
249
|
+
original,
|
|
250
|
+
);
|
|
251
|
+
expect(result).toBe(original);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test("leaves successful empty-input calls untouched", () => {
|
|
255
|
+
// Tools that legitimately accept no parameters (e.g. subagent_status) must
|
|
256
|
+
// not have guidance appended to their successful results.
|
|
257
|
+
const original = { content: "No subagents running.", isError: false };
|
|
258
|
+
const result = augmentSkillExecuteError("subagent_status", {}, original);
|
|
259
|
+
expect(result).toBe(original);
|
|
260
|
+
});
|
|
85
261
|
});
|
|
@@ -100,6 +100,21 @@ async function executeSkillLoad(
|
|
|
100
100
|
return { content: result.content, isError: result.isError };
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
async function executeSkillLoadCleanup(
|
|
104
|
+
input: Record<string, unknown>,
|
|
105
|
+
): Promise<{ content: string; isError: boolean }> {
|
|
106
|
+
const tool = getTool("skill_load");
|
|
107
|
+
if (!tool) throw new Error("skill_load tool was not registered");
|
|
108
|
+
|
|
109
|
+
const result = await tool.execute(input, {
|
|
110
|
+
workingDir: "/tmp",
|
|
111
|
+
conversationId: "conversation-1",
|
|
112
|
+
trustClass: "guardian",
|
|
113
|
+
diskPressureCleanupModeActive: true,
|
|
114
|
+
});
|
|
115
|
+
return { content: result.content, isError: result.isError };
|
|
116
|
+
}
|
|
117
|
+
|
|
103
118
|
describe("skill_load tool", () => {
|
|
104
119
|
beforeEach(() => {
|
|
105
120
|
mkdirSync(join(TEST_DIR, "skills"), { recursive: true });
|
|
@@ -911,4 +926,61 @@ describe("skill_load tool", () => {
|
|
|
911
926
|
expect(result.content).toContain("Suggested Included Skills (not loaded):");
|
|
912
927
|
expect(installCount).toBeLessThanOrEqual(5);
|
|
913
928
|
});
|
|
929
|
+
|
|
930
|
+
// During disk-pressure cleanup mode, skill_load is allowed but must not
|
|
931
|
+
// produce side effects: no catalog auto-install (workspace writes / bun
|
|
932
|
+
// install) and no inline command execution. Instructions are still returned.
|
|
933
|
+
test("does not auto-install a missing catalog skill during cleanup mode", async () => {
|
|
934
|
+
mockAutoInstall.mockImplementation((skillId: string) => {
|
|
935
|
+
writeSkill(skillId, "Should Not Install", "nope", "body");
|
|
936
|
+
return Promise.resolve(true);
|
|
937
|
+
});
|
|
938
|
+
|
|
939
|
+
const result = await executeSkillLoadCleanup({ skill: "absent-skill" });
|
|
940
|
+
|
|
941
|
+
expect(mockAutoInstall).not.toHaveBeenCalled();
|
|
942
|
+
expect(result.isError).toBe(true);
|
|
943
|
+
});
|
|
944
|
+
|
|
945
|
+
test("does not auto-install missing includes during cleanup mode", async () => {
|
|
946
|
+
writeSkillWithIncludes(
|
|
947
|
+
"cleanup-parent",
|
|
948
|
+
"Cleanup Parent",
|
|
949
|
+
"Has a missing include",
|
|
950
|
+
"Parent body",
|
|
951
|
+
["cleanup-dep"],
|
|
952
|
+
);
|
|
953
|
+
mockAutoInstall.mockImplementation((skillId: string) => {
|
|
954
|
+
writeSkill(skillId, "Cleanup Dep", "should not install", "Dep body");
|
|
955
|
+
return Promise.resolve(true);
|
|
956
|
+
});
|
|
957
|
+
|
|
958
|
+
const result = await executeSkillLoadCleanup({ skill: "cleanup-parent" });
|
|
959
|
+
|
|
960
|
+
expect(mockAutoInstall).not.toHaveBeenCalled();
|
|
961
|
+
expect(result.isError).toBe(false);
|
|
962
|
+
expect(result.content).toContain("Skill: Cleanup Parent");
|
|
963
|
+
expect(result.content).toContain("Suggested Included Skills (not loaded):");
|
|
964
|
+
expect(result.content).toContain("cleanup-dep");
|
|
965
|
+
});
|
|
966
|
+
|
|
967
|
+
test("strips inline command tokens instead of executing them during cleanup mode", async () => {
|
|
968
|
+
writeSkill(
|
|
969
|
+
"cleanup-inline",
|
|
970
|
+
"Cleanup Inline",
|
|
971
|
+
"Has an inline command",
|
|
972
|
+
"Before !`echo SENTINEL_OUTPUT` after",
|
|
973
|
+
);
|
|
974
|
+
|
|
975
|
+
const result = await executeSkillLoadCleanup({ skill: "cleanup-inline" });
|
|
976
|
+
|
|
977
|
+
expect(result.isError).toBe(false);
|
|
978
|
+
expect(result.content).toContain(
|
|
979
|
+
"[inline command skipped: storage cleanup mode]",
|
|
980
|
+
);
|
|
981
|
+
expect(result.content).not.toContain("SENTINEL_OUTPUT");
|
|
982
|
+
expect(result.content).not.toContain("!`echo SENTINEL_OUTPUT`");
|
|
983
|
+
expect(result.content).toContain("Before");
|
|
984
|
+
expect(result.content).toContain("after");
|
|
985
|
+
});
|
|
914
986
|
});
|
|
@@ -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
|
+
});
|