@vellumai/assistant 0.9.1-staging.1 → 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/docs/activation-funnel-telemetry.md +24 -18
- 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 +15 -0
- package/openapi.yaml +852 -15
- package/package.json +1 -1
- 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 +59 -7
- package/src/__tests__/agent-loop-compaction-strip.test.ts +17 -16
- package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +16 -13
- package/src/__tests__/app-compiler.test.ts +15 -1
- package/src/__tests__/auth-fallback-events-store.test.ts +6 -14
- 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__/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 +174 -30
- package/src/__tests__/config-schema.test.ts +35 -0
- 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-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-execution-shell-lockdown.test.ts +18 -11
- package/src/__tests__/credential-prompt-route.test.ts +1 -0
- package/src/__tests__/credential-security-invariants.test.ts +2 -0
- 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 +51 -0
- package/src/__tests__/gateway-flag-listener.test.ts +110 -1
- 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__/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 +0 -3
- package/src/__tests__/llm-catalog-parity.test.ts +30 -1
- package/src/__tests__/llm-resolver.test.ts +21 -0
- 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__/path-policy.test.ts +34 -0
- package/src/__tests__/persona-resolver.test.ts +38 -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__/relay-server.test.ts +285 -0
- package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
- package/src/__tests__/scheduler-reuse-conversation.test.ts +8 -5
- package/src/__tests__/skill-execute-input.test.ts +5 -0
- 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 +150 -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__/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__/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-verification.test.ts +2 -4
- package/src/__tests__/twilio-routes.test.ts +81 -1
- package/src/__tests__/voice-invite-redemption.test.ts +0 -1
- package/src/__tests__/weak-open-model.test.ts +30 -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 +33 -33
- 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 +26 -0
- package/src/api/responses/home.ts +26 -0
- 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 +181 -78
- 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-server.ts +66 -0
- package/src/calls/relay-setup-router.ts +82 -1
- package/src/calls/twilio-routes.ts +17 -8
- 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 +57 -5
- package/src/cli/lib/__tests__/inspect-plugin.test.ts +54 -0
- package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +134 -4
- package/src/cli/lib/__tests__/plugin-surfaces.test.ts +111 -0
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +53 -11
- package/src/cli/lib/inspect-plugin.ts +12 -1
- package/src/cli/lib/merge-plugin-tree.ts +149 -49
- package/src/cli/lib/plugin-surfaces.ts +104 -0
- package/src/cli/lib/upgrade-plugin.ts +64 -36
- package/src/cli/program.ts +2 -4
- 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/workflows/SKILL.md +14 -7
- package/src/config/call-site-defaults.ts +3 -0
- package/src/config/feature-flag-registry.json +49 -18
- package/src/config/llm-resolver.ts +3 -0
- package/src/config/memory-v3-gate.ts +11 -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/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/services.ts +18 -0
- package/src/config/seed-inference-profiles.ts +94 -34
- package/src/config/skills.ts +21 -0
- package/src/config/sync-gated-profiles.ts +220 -0
- package/src/contacts/contact-store.ts +2 -10
- package/src/contacts/contacts-write.ts +1 -2
- package/src/contacts/types.ts +0 -1
- package/src/context/compactor.ts +86 -52
- package/src/context/strip-injections.ts +58 -10
- package/src/context/token-estimator.ts +1 -1
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -2
- package/src/daemon/conversation-agent-loop-handlers.ts +2 -0
- package/src/daemon/conversation-agent-loop.ts +100 -19
- 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-surfaces.ts +26 -0
- package/src/daemon/conversation-tool-setup.ts +16 -11
- package/src/daemon/conversation.ts +64 -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 +5 -10
- package/src/daemon/handlers/config-slack-channel.ts +20 -0
- package/src/daemon/handlers/conversations.ts +107 -0
- package/src/daemon/host-browser-proxy.ts +41 -0
- package/src/daemon/lifecycle.ts +55 -20
- package/src/daemon/message-provenance.ts +2 -0
- package/src/daemon/message-types/contacts.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/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/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 +34 -0
- 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/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 +10 -0
- package/src/memory/embedding-backend.ts +15 -1
- package/src/memory/jobs-worker.ts +2 -1
- package/src/memory/lifecycle-events-store.ts +2 -2
- package/src/memory/memory-retrospective-enqueue.ts +31 -6
- package/src/memory/memory-retrospective-job.ts +9 -0
- 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 +10 -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 +30 -0
- package/src/memory/migrations/index.ts +5 -0
- package/src/memory/onboarding-events-store.ts +3 -3
- package/src/memory/schema/contacts.ts +0 -1
- 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/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/slack.ts +55 -73
- package/src/notifications/approval-card-data.ts +333 -0
- package/src/notifications/broadcaster.ts +6 -2
- 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 +3 -13
- package/src/notifications/notification-utils.ts +2 -1
- package/src/notifications/signal.ts +79 -43
- package/src/notifications/types.ts +98 -128
- 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/index.ts +27 -0
- 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 +35 -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 +75 -7
- 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 +37 -4
- 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/task-progress-nudge/hooks/post-tool-use.ts +2 -12
- package/src/plugins/defaults/title-generate/hooks/stop.ts +56 -21
- package/src/prompts/persona-resolver.ts +12 -2
- 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/__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 +49 -21
- 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/capabilities.test.ts +120 -0
- package/src/runtime/capabilities.ts +197 -0
- package/src/runtime/channel-approval-types.ts +5 -1
- package/src/runtime/channel-retry-sweep.ts +1 -0
- package/src/runtime/channel-verification-service.ts +1 -2
- 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 +6 -22
- 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 +35 -13
- package/src/runtime/routes/browser-tabs-routes.ts +9 -0
- 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-management-routes.ts +80 -1
- package/src/runtime/routes/conversation-query-routes.ts +68 -22
- package/src/runtime/routes/conversation-routes.ts +37 -12
- 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/inbound-message-handler.ts +214 -228
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +88 -6
- 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 +40 -7
- 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 +225 -1
- package/src/runtime/routes/workflow-routes.ts +131 -1
- package/src/runtime/tool-grant-request-helper.ts +18 -16
- package/src/runtime/trust-context-resolver.ts +8 -5
- 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 +11 -4
- 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/browser/__tests__/browser-execution-acquire.test.ts +31 -0
- package/src/tools/browser/browser-execution.ts +29 -18
- 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.ts +213 -10
- package/src/tools/permission-checker.ts +3 -2
- package/src/tools/registry.ts +20 -0
- package/src/tools/schedule/create.ts +4 -3
- package/src/tools/schedule/update.ts +2 -1
- package/src/tools/shared/filesystem/path-policy.ts +39 -13
- package/src/tools/skills/execute.ts +1 -2
- package/src/tools/subagent/spawn.ts +37 -13
- package/src/tools/terminal/shell.ts +10 -4
- package/src/tools/tool-approval-handler.ts +17 -10
- package/src/tools/types.ts +9 -0
- package/src/tools/ui-surface/definitions.ts +25 -2
- package/src/tools/verification-control-plane-policy.ts +3 -1
- package/src/tools/workflows/run-workflow.ts +1 -0
- package/src/util/disk-usage.ts +78 -23
- package/src/util/platform.ts +8 -1
- package/src/watcher/telemetry.ts +2 -2
- 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 -3
- package/src/workflows/run-manager.ts +64 -0
- 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/notifications/tool-approval-copy.ts +0 -142
- package/src/runtime/routes/approval-prompt-ts-tracker.ts +0 -78
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Effective capabilities — trust-class capabilities COMPOSED with runtime context.
|
|
3
|
+
*
|
|
4
|
+
* `resolveCapabilities` (`capabilities.ts`) answers "what may this trust class
|
|
5
|
+
* do" from the actor's class alone, and stays deliberately pure (no context
|
|
6
|
+
* dependencies) so it remains the single fail-closed trust boundary. Some real
|
|
7
|
+
* decisions additionally depend on runtime context — the channel a request
|
|
8
|
+
* arrived on, surface actions, task authorization, a feature flag. Those
|
|
9
|
+
* compositions belong here, in named/testable helpers, rather than re-derived
|
|
10
|
+
* inline at each call site (which scatters a single policy across the codebase).
|
|
11
|
+
*
|
|
12
|
+
* Scope: this module composes capabilities with *actor/request* context. It does
|
|
13
|
+
* not read global config or feature flags itself — callers resolve those and
|
|
14
|
+
* pass the result in (e.g. `lockdownEnabled`), keeping this layer focused on the
|
|
15
|
+
* capability composition. `resolveRoutingState` in `trust-context-resolver.ts`
|
|
16
|
+
* is the same shape (capability + guardian-route context → `promptWaitingAllowed`)
|
|
17
|
+
* and predates this module; it stays where it is.
|
|
18
|
+
*/
|
|
19
|
+
import type { TrustClass } from "./actor-trust-resolver.js";
|
|
20
|
+
import { resolveCapabilities } from "./capabilities.js";
|
|
21
|
+
|
|
22
|
+
type RawTrustClass = TrustClass | (string & {}) | undefined;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Channels that are themselves privileged document surfaces. Actors on these get
|
|
26
|
+
* privileged document access regardless of trust class — the `vellum` first-party
|
|
27
|
+
* console is the operator's own surface, not an external contact channel.
|
|
28
|
+
*/
|
|
29
|
+
const PRIVILEGED_DOCUMENT_CHANNELS = new Set<string>(["vellum"]);
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Whether an actor may perform privileged (non-conversation-scoped) document
|
|
33
|
+
* operations. True when the trust class grants it OR the request arrived on a
|
|
34
|
+
* privileged channel.
|
|
35
|
+
*/
|
|
36
|
+
export function canActOnPrivilegedDocuments(actor: {
|
|
37
|
+
trustClass: RawTrustClass;
|
|
38
|
+
executionChannel?: string;
|
|
39
|
+
}): boolean {
|
|
40
|
+
return (
|
|
41
|
+
resolveCapabilities(actor.trustClass).canAccessPrivilegedDocuments ||
|
|
42
|
+
(actor.executionChannel != null &&
|
|
43
|
+
PRIVILEGED_DOCUMENT_CHANNELS.has(actor.executionChannel))
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Whether the CES shell lockdown applies to this actor. Active when the lockdown
|
|
49
|
+
* flag is enabled AND the actor's trust class cannot run an unsandboxed shell.
|
|
50
|
+
* Callers resolve the flag (`isCesShellLockdownEnabled(config)`) and pass it in.
|
|
51
|
+
*/
|
|
52
|
+
export function isUntrustedShellLockdownActive(actor: {
|
|
53
|
+
trustClass: RawTrustClass;
|
|
54
|
+
lockdownEnabled: boolean;
|
|
55
|
+
}): boolean {
|
|
56
|
+
return (
|
|
57
|
+
actor.lockdownEnabled &&
|
|
58
|
+
!resolveCapabilities(actor.trustClass).canRunUnsandboxedShell
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Whether an archive-by-sender invocation is authorized. Any one of a surface
|
|
64
|
+
* action, a task-batch authorization, or an explicit prompt approval suffices.
|
|
65
|
+
* Absent those, the actor's own `user_approved` flag only counts when its trust
|
|
66
|
+
* class may self-authorize archive-by-sender.
|
|
67
|
+
*/
|
|
68
|
+
export function isArchiveBySenderAuthorized(args: {
|
|
69
|
+
trustClass: RawTrustClass;
|
|
70
|
+
triggeredBySurfaceAction?: boolean;
|
|
71
|
+
batchAuthorizedByTask?: boolean;
|
|
72
|
+
approvedViaPrompt?: boolean;
|
|
73
|
+
userApproved?: boolean;
|
|
74
|
+
}): boolean {
|
|
75
|
+
const selfAuthorized =
|
|
76
|
+
args.userApproved === true &&
|
|
77
|
+
resolveCapabilities(args.trustClass).canSelfAuthorizeArchiveBySender;
|
|
78
|
+
return (
|
|
79
|
+
args.triggeredBySurfaceAction === true ||
|
|
80
|
+
args.batchAuthorizedByTask === true ||
|
|
81
|
+
args.approvedViaPrompt === true ||
|
|
82
|
+
selfAuthorized
|
|
83
|
+
);
|
|
84
|
+
}
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
type CanonicalGuardianRequest,
|
|
31
31
|
getCanonicalGuardianRequest,
|
|
32
32
|
getCanonicalGuardianRequestByCode,
|
|
33
|
+
getPendingCanonicalRequestByDestinationMessage,
|
|
33
34
|
isRequestExpired,
|
|
34
35
|
listCanonicalGuardianRequests,
|
|
35
36
|
} from "../memory/canonical-guardian-store.js";
|
|
@@ -47,6 +48,7 @@ import type {
|
|
|
47
48
|
ApprovalConversationContext,
|
|
48
49
|
ApprovalConversationGenerator,
|
|
49
50
|
} from "./http-types.js";
|
|
51
|
+
import { parseReactionCallbackData } from "./routes/channel-route-shared.js";
|
|
50
52
|
|
|
51
53
|
const log = getLogger("guardian-reply-router");
|
|
52
54
|
|
|
@@ -54,6 +56,26 @@ const log = getLogger("guardian-reply-router");
|
|
|
54
56
|
// Types
|
|
55
57
|
// ---------------------------------------------------------------------------
|
|
56
58
|
|
|
59
|
+
/**
|
|
60
|
+
* How to scope a guardian's pending requests when resolving an inbound reply.
|
|
61
|
+
* The three states are mutually exclusive and named so the security-critical
|
|
62
|
+
* `blocked` cannot be confused with the absence of a hint:
|
|
63
|
+
*
|
|
64
|
+
* - `scoped`: resolve only these request ids, and constrain request-code
|
|
65
|
+
* routing to this set (delivery-/conversation-scoped hints).
|
|
66
|
+
* - `blocked`: fail closed — no pending requests and no identity fallback.
|
|
67
|
+
* The Slack cross-chat guard: a guardian's unrelated message in a chat
|
|
68
|
+
* where no card was delivered must not resolve a request delivered
|
|
69
|
+
* elsewhere. Explicit callbacks and request codes still work (they carry
|
|
70
|
+
* their own request id).
|
|
71
|
+
* - `identity-fallback`: discover pending requests by guardian identity,
|
|
72
|
+
* conversation, or principal. The default when no scope is supplied.
|
|
73
|
+
*/
|
|
74
|
+
export type GuardianPendingScope =
|
|
75
|
+
| { mode: "scoped"; requestIds: string[] }
|
|
76
|
+
| { mode: "blocked" }
|
|
77
|
+
| { mode: "identity-fallback" };
|
|
78
|
+
|
|
57
79
|
/** Context for an inbound message that may be a guardian reply. */
|
|
58
80
|
export interface GuardianReplyContext {
|
|
59
81
|
/** The raw message text (trimmed). */
|
|
@@ -66,8 +88,18 @@ export interface GuardianReplyContext {
|
|
|
66
88
|
conversationId: string;
|
|
67
89
|
/** Callback data from button presses (e.g. `apr:<requestId>:<action>`). */
|
|
68
90
|
callbackData?: string;
|
|
69
|
-
/**
|
|
70
|
-
|
|
91
|
+
/**
|
|
92
|
+
* For emoji-reaction decisions (`callbackData` of `reaction:<emoji>`): the
|
|
93
|
+
* channel-native id (e.g. Slack `ts`) of the message the reaction was
|
|
94
|
+
* attached to. Used to recover the target request from its delivery record.
|
|
95
|
+
*/
|
|
96
|
+
reactedMessageTs?: string;
|
|
97
|
+
/**
|
|
98
|
+
* How to scope this guardian's pending requests (see {@link
|
|
99
|
+
* GuardianPendingScope}). Omitted is equivalent to
|
|
100
|
+
* `{ mode: "identity-fallback" }`.
|
|
101
|
+
*/
|
|
102
|
+
pendingScope?: GuardianPendingScope;
|
|
71
103
|
/** Conversation generator for NL classification (injected by daemon). */
|
|
72
104
|
approvalConversationGenerator?: ApprovalConversationGenerator;
|
|
73
105
|
/** Optional channel delivery context for resolver-driven side effects. */
|
|
@@ -204,30 +236,33 @@ function parseRequestCode(
|
|
|
204
236
|
/** Find all pending canonical requests for a guardian actor. */
|
|
205
237
|
function findPendingCanonicalRequests(
|
|
206
238
|
actor: ActorContext,
|
|
207
|
-
|
|
239
|
+
scope: GuardianPendingScope,
|
|
208
240
|
conversationId?: string,
|
|
209
241
|
): CanonicalGuardianRequest[] {
|
|
242
|
+
// `blocked` fails closed: no pending requests and no identity fallback — the
|
|
243
|
+
// Slack cross-chat hijack guard.
|
|
244
|
+
if (scope.mode === "blocked") {
|
|
245
|
+
return [];
|
|
246
|
+
}
|
|
247
|
+
|
|
210
248
|
let results: CanonicalGuardianRequest[];
|
|
211
249
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
return [];
|
|
216
|
-
}
|
|
217
|
-
results = pendingRequestIds
|
|
250
|
+
if (scope.mode === "scoped") {
|
|
251
|
+
// Resolve exactly the supplied ids.
|
|
252
|
+
results = scope.requestIds
|
|
218
253
|
.map(getCanonicalGuardianRequest)
|
|
219
254
|
.filter((r): r is CanonicalGuardianRequest => r?.status === "pending");
|
|
220
255
|
} else if (actor.actorExternalUserId) {
|
|
221
|
-
//
|
|
256
|
+
// identity-fallback: query by guardian identity when available
|
|
222
257
|
results = listCanonicalGuardianRequests({
|
|
223
258
|
status: "pending",
|
|
224
259
|
guardianExternalUserId: actor.actorExternalUserId,
|
|
225
260
|
});
|
|
226
261
|
} else if (conversationId) {
|
|
227
|
-
//
|
|
228
|
-
// path can discover pending requests bound to this conversation.
|
|
229
|
-
// Include guardianPrincipalId
|
|
230
|
-
//
|
|
262
|
+
// identity-fallback without an actorExternalUserId: scope by conversationId
|
|
263
|
+
// so the NL path can discover pending requests bound to this conversation.
|
|
264
|
+
// Include guardianPrincipalId when available so the guardian only sees
|
|
265
|
+
// requests they are authorized to act on.
|
|
231
266
|
results = listCanonicalGuardianRequests({
|
|
232
267
|
status: "pending",
|
|
233
268
|
conversationId,
|
|
@@ -236,9 +271,8 @@ function findPendingCanonicalRequests(
|
|
|
236
271
|
: {}),
|
|
237
272
|
});
|
|
238
273
|
} else if (actor.guardianPrincipalId) {
|
|
239
|
-
//
|
|
240
|
-
//
|
|
241
|
-
// discover pending guardian work via their bound principal.
|
|
274
|
+
// identity-fallback by principal: desktop sessions discover pending
|
|
275
|
+
// guardian work via their bound principal.
|
|
242
276
|
results = listCanonicalGuardianRequests({
|
|
243
277
|
status: "pending",
|
|
244
278
|
guardianPrincipalId: actor.guardianPrincipalId,
|
|
@@ -284,22 +318,68 @@ export async function routeGuardianReply(
|
|
|
284
318
|
): Promise<GuardianReplyResult> {
|
|
285
319
|
const {
|
|
286
320
|
messageText,
|
|
321
|
+
channel,
|
|
287
322
|
actor,
|
|
288
323
|
conversationId,
|
|
289
324
|
callbackData,
|
|
325
|
+
reactedMessageTs,
|
|
290
326
|
approvalConversationGenerator,
|
|
291
327
|
channelDeliveryContext,
|
|
292
328
|
emissionContext,
|
|
293
329
|
} = ctx;
|
|
330
|
+
|
|
331
|
+
// ── 0. Reaction decisions (emoji on a delivered approval card) ──
|
|
332
|
+
// A reaction carries an emoji plus the message it is attached to. Map the
|
|
333
|
+
// emoji to an action and recover the target request from that card's
|
|
334
|
+
// delivery record. Addressing by the reacted message disambiguates precisely
|
|
335
|
+
// even when several cards are pending in the same chat, so — unlike the
|
|
336
|
+
// text/NL paths — no clarification prompt is ever needed. `reaction_removed`
|
|
337
|
+
// never expresses intent and is filtered out before reaching the router.
|
|
338
|
+
if (
|
|
339
|
+
callbackData?.startsWith("reaction:") &&
|
|
340
|
+
!callbackData.startsWith("reaction_removed:")
|
|
341
|
+
) {
|
|
342
|
+
const reaction = parseReactionCallbackData(callbackData);
|
|
343
|
+
const guardianChatId = channelDeliveryContext?.guardianChatId;
|
|
344
|
+
if (!reaction || !reactedMessageTs || !guardianChatId) {
|
|
345
|
+
// Unknown emoji, or missing addressing context — not an actionable
|
|
346
|
+
// approval reaction. Leave it for the caller to persist as a transcript
|
|
347
|
+
// signal (it must not trigger an agent turn).
|
|
348
|
+
return notConsumed();
|
|
349
|
+
}
|
|
350
|
+
const request = getPendingCanonicalRequestByDestinationMessage(
|
|
351
|
+
channel,
|
|
352
|
+
guardianChatId,
|
|
353
|
+
reactedMessageTs,
|
|
354
|
+
);
|
|
355
|
+
if (!request) {
|
|
356
|
+
// The reacted message is not a known pending approval card (a stray
|
|
357
|
+
// reaction, or one whose request was already resolved). Never approve
|
|
358
|
+
// off an unrecognized message.
|
|
359
|
+
return notConsumed();
|
|
360
|
+
}
|
|
361
|
+
return applyDecision(
|
|
362
|
+
request.id,
|
|
363
|
+
reaction.action,
|
|
364
|
+
actor,
|
|
365
|
+
undefined,
|
|
366
|
+
channelDeliveryContext,
|
|
367
|
+
emissionContext,
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const pendingScope: GuardianPendingScope = ctx.pendingScope ?? {
|
|
372
|
+
mode: "identity-fallback",
|
|
373
|
+
};
|
|
294
374
|
const pendingRequests = findPendingCanonicalRequests(
|
|
295
375
|
actor,
|
|
296
|
-
|
|
376
|
+
pendingScope,
|
|
297
377
|
conversationId,
|
|
298
378
|
);
|
|
379
|
+
// Request codes carry their own id, so constrain them only under an explicit
|
|
380
|
+
// scope; under blocked/identity-fallback they still resolve cross-chat.
|
|
299
381
|
const scopedPendingRequestIds =
|
|
300
|
-
|
|
301
|
-
? new Set(ctx.pendingRequestIds)
|
|
302
|
-
: null;
|
|
382
|
+
pendingScope.mode === "scoped" ? new Set(pendingScope.requestIds) : null;
|
|
303
383
|
|
|
304
384
|
// ── 1. Deterministic callback parsing (button presses) ──
|
|
305
385
|
// No conversationId scoping here — the guardian's reply comes from a
|
|
@@ -800,6 +880,7 @@ function resolveRequestInstructionMode(
|
|
|
800
880
|
type CanonicalFailureReason =
|
|
801
881
|
| "already_resolved"
|
|
802
882
|
| "identity_mismatch"
|
|
883
|
+
| "request_misconfigured"
|
|
803
884
|
| "invalid_action"
|
|
804
885
|
| "expired";
|
|
805
886
|
|
|
@@ -819,6 +900,10 @@ function failureReplyText(
|
|
|
819
900
|
return "This request has expired.";
|
|
820
901
|
case "identity_mismatch":
|
|
821
902
|
return "You don't have permission to decide on this request.";
|
|
903
|
+
case "request_misconfigured":
|
|
904
|
+
// The actor is authorized; the request record itself is incomplete
|
|
905
|
+
// (e.g. missing its bound principal). Do not imply a permission problem.
|
|
906
|
+
return "Something went wrong with this request on our end, so I couldn't apply your decision.";
|
|
822
907
|
case "invalid_action":
|
|
823
908
|
return buildGuardianInvalidActionReply(
|
|
824
909
|
resolveRequestInstructionMode(request),
|
|
@@ -164,20 +164,12 @@ export function redeemInvite(params: {
|
|
|
164
164
|
// Sentinel error used to trigger a transaction rollback when the invite
|
|
165
165
|
// was concurrently revoked/expired between pre-validation and write time.
|
|
166
166
|
const STALE_INVITE = Symbol("stale_invite");
|
|
167
|
-
const canonicalMemberId = existingChannel.
|
|
168
|
-
? canonicalizeInboundIdentity(
|
|
169
|
-
sourceChannel as ChannelId,
|
|
170
|
-
existingChannel.externalUserId,
|
|
171
|
-
)
|
|
172
|
-
: null;
|
|
167
|
+
const canonicalMemberId = existingChannel.address;
|
|
173
168
|
const canonicalCallerId = externalUserId
|
|
174
169
|
? canonicalizeInboundIdentity(sourceChannel as ChannelId, externalUserId)
|
|
175
170
|
: null;
|
|
176
|
-
const memberMatchesSender =
|
|
177
|
-
canonicalMemberId
|
|
178
|
-
canonicalCallerId &&
|
|
179
|
-
canonicalMemberId === canonicalCallerId
|
|
180
|
-
);
|
|
171
|
+
const memberMatchesSender =
|
|
172
|
+
!!canonicalCallerId && canonicalMemberId === canonicalCallerId;
|
|
181
173
|
const preservedDisplayName =
|
|
182
174
|
memberMatchesSender && existingContact?.displayName?.trim().length
|
|
183
175
|
? existingContact.displayName
|
|
@@ -542,20 +534,12 @@ export function redeemInviteByCode(params: {
|
|
|
542
534
|
// an invite use atomically.
|
|
543
535
|
if (existingChannel && !targetMismatch) {
|
|
544
536
|
const STALE_INVITE_REACTIVATE = Symbol("stale_invite_reactivate");
|
|
545
|
-
const canonicalMemberId = existingChannel.
|
|
546
|
-
? canonicalizeInboundIdentity(
|
|
547
|
-
sourceChannel as ChannelId,
|
|
548
|
-
existingChannel.externalUserId,
|
|
549
|
-
)
|
|
550
|
-
: null;
|
|
537
|
+
const canonicalMemberId = existingChannel.address;
|
|
551
538
|
const canonicalCallerId = externalUserId
|
|
552
539
|
? canonicalizeInboundIdentity(sourceChannel as ChannelId, externalUserId)
|
|
553
540
|
: null;
|
|
554
|
-
const memberMatchesSender =
|
|
555
|
-
canonicalMemberId
|
|
556
|
-
canonicalCallerId &&
|
|
557
|
-
canonicalMemberId === canonicalCallerId
|
|
558
|
-
);
|
|
541
|
+
const memberMatchesSender =
|
|
542
|
+
!!canonicalCallerId && canonicalMemberId === canonicalCallerId;
|
|
559
543
|
const preservedDisplayName =
|
|
560
544
|
memberMatchesSender && existingContact?.displayName?.trim().length
|
|
561
545
|
? existingContact.displayName
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression test for descriptor hygiene in the vbundle export path.
|
|
3
|
+
*
|
|
4
|
+
* `streamExportVBundle` opens every file in the workspace twice — once to hash
|
|
5
|
+
* it (Pass 1) and once to stream it into the tar (Pass 2). This test pins the
|
|
6
|
+
* invariant that those reads release their descriptors: the number of open
|
|
7
|
+
* descriptors held by the process must not grow proportionally to the number
|
|
8
|
+
* of files exported. Without that guarantee a workspace-sized export exhausts
|
|
9
|
+
* the daemon's descriptor limit (EMFILE) and every subsequent subprocess spawn
|
|
10
|
+
* fails.
|
|
11
|
+
*
|
|
12
|
+
* Descriptor counting uses `/proc/self/fd`, which only exists on Linux. The
|
|
13
|
+
* assertion is skipped on platforms without it (e.g. macOS dev machines); CI
|
|
14
|
+
* runs on Linux, so the regression stays covered there.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
existsSync,
|
|
19
|
+
mkdirSync,
|
|
20
|
+
readdirSync,
|
|
21
|
+
rmSync,
|
|
22
|
+
writeFileSync,
|
|
23
|
+
} from "node:fs";
|
|
24
|
+
import { tmpdir } from "node:os";
|
|
25
|
+
import { join } from "node:path";
|
|
26
|
+
import { describe, expect, test } from "bun:test";
|
|
27
|
+
|
|
28
|
+
import { assertNotLiveDb } from "../../../__tests__/assert-not-live-db.js";
|
|
29
|
+
import { streamExportVBundle } from "../vbundle-builder.js";
|
|
30
|
+
import { defaultV1Options } from "./v1-test-helpers.js";
|
|
31
|
+
|
|
32
|
+
const PROC_SELF_FD = "/proc/self/fd";
|
|
33
|
+
const hasProcFd = existsSync(PROC_SELF_FD);
|
|
34
|
+
|
|
35
|
+
/** Count the descriptors currently open by this process. */
|
|
36
|
+
function openFdCount(): number {
|
|
37
|
+
return readdirSync(PROC_SELF_FD).length;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Build a workspace with enough distinct files (across nested directories) that
|
|
42
|
+
* a per-file descriptor leak would be obvious against the assertion bound.
|
|
43
|
+
*/
|
|
44
|
+
function createPopulatedWorkspace(fileCount: number): {
|
|
45
|
+
dir: string;
|
|
46
|
+
cleanup: () => void;
|
|
47
|
+
} {
|
|
48
|
+
const dir = join(
|
|
49
|
+
tmpdir(),
|
|
50
|
+
`vbundle-fd-leak-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
51
|
+
);
|
|
52
|
+
mkdirSync(dir, { recursive: true });
|
|
53
|
+
writeFileSync(join(dir, "config.json"), JSON.stringify({ test: true }));
|
|
54
|
+
const dbDir = join(dir, "data", "db");
|
|
55
|
+
mkdirSync(dbDir, { recursive: true });
|
|
56
|
+
writeFileSync(join(dbDir, "assistant.db"), "fake-db-content");
|
|
57
|
+
|
|
58
|
+
// A spread of files under a few nested directories, each with non-trivial
|
|
59
|
+
// (multi-chunk) content so the read loop iterates more than once.
|
|
60
|
+
const filler = "x".repeat(200 * 1024);
|
|
61
|
+
for (let i = 0; i < fileCount; i++) {
|
|
62
|
+
const sub = join(dir, "data", "files", `dir-${i % 5}`);
|
|
63
|
+
mkdirSync(sub, { recursive: true });
|
|
64
|
+
writeFileSync(join(sub, `file-${i}.txt`), `${filler}\n${i}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
dir,
|
|
69
|
+
cleanup: () => {
|
|
70
|
+
assertNotLiveDb(dir);
|
|
71
|
+
rmSync(dir, { recursive: true, force: true });
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
describe("streamExportVBundle — descriptor lifecycle", () => {
|
|
77
|
+
test.skipIf(!hasProcFd)(
|
|
78
|
+
"does not leak a file descriptor per exported file",
|
|
79
|
+
async () => {
|
|
80
|
+
const fileCount = 60;
|
|
81
|
+
const workspace = createPopulatedWorkspace(fileCount);
|
|
82
|
+
|
|
83
|
+
// Warm-up export: first run can open and cache descriptors that legitimately
|
|
84
|
+
// persist (loggers, the daemon DB, etc.). Measuring the delta around a
|
|
85
|
+
// second export isolates per-file leakage from one-time setup.
|
|
86
|
+
let warmup: Awaited<ReturnType<typeof streamExportVBundle>> | undefined;
|
|
87
|
+
try {
|
|
88
|
+
warmup = await streamExportVBundle({
|
|
89
|
+
workspaceDir: workspace.dir,
|
|
90
|
+
...defaultV1Options(),
|
|
91
|
+
});
|
|
92
|
+
} finally {
|
|
93
|
+
await warmup?.cleanup();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const before = openFdCount();
|
|
97
|
+
|
|
98
|
+
let result: Awaited<ReturnType<typeof streamExportVBundle>> | undefined;
|
|
99
|
+
try {
|
|
100
|
+
result = await streamExportVBundle({
|
|
101
|
+
workspaceDir: workspace.dir,
|
|
102
|
+
...defaultV1Options(),
|
|
103
|
+
});
|
|
104
|
+
// Manifest covers every file we wrote — proves the walk actually read
|
|
105
|
+
// them (otherwise a no-op export would trivially leak nothing).
|
|
106
|
+
expect(result.manifest.contents.length).toBeGreaterThanOrEqual(
|
|
107
|
+
fileCount,
|
|
108
|
+
);
|
|
109
|
+
} finally {
|
|
110
|
+
await result?.cleanup();
|
|
111
|
+
workspace.cleanup();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const after = openFdCount();
|
|
115
|
+
const delta = after - before;
|
|
116
|
+
|
|
117
|
+
// Each export reads 2×fileCount files (hash pass + tar pass). A per-file
|
|
118
|
+
// leak would push the delta past `fileCount`. Allow a small constant for
|
|
119
|
+
// incidental descriptors (temp output file already cleaned up, jitter).
|
|
120
|
+
expect(delta).toBeLessThan(8);
|
|
121
|
+
},
|
|
122
|
+
);
|
|
123
|
+
});
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
import { createHash, randomUUID } from "node:crypto";
|
|
12
12
|
import {
|
|
13
13
|
closeSync,
|
|
14
|
-
createReadStream,
|
|
15
14
|
createWriteStream,
|
|
16
15
|
existsSync,
|
|
17
16
|
lstatSync,
|
|
@@ -21,7 +20,7 @@ import {
|
|
|
21
20
|
readSync,
|
|
22
21
|
realpathSync,
|
|
23
22
|
} from "node:fs";
|
|
24
|
-
import { stat, unlink } from "node:fs/promises";
|
|
23
|
+
import { type FileHandle, open, stat, unlink } from "node:fs/promises";
|
|
25
24
|
import { tmpdir } from "node:os";
|
|
26
25
|
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
27
26
|
import { Readable } from "node:stream";
|
|
@@ -886,9 +885,10 @@ export function walkDirectoryForMetadata(
|
|
|
886
885
|
}
|
|
887
886
|
|
|
888
887
|
/**
|
|
889
|
-
* Compute SHA-256 hex digest of a file by
|
|
890
|
-
* entire file in memory. When `size` is provided, only
|
|
891
|
-
* `size` bytes to match what will be archived in the tar
|
|
888
|
+
* Compute SHA-256 hex digest of a file by reading it in bounded chunks —
|
|
889
|
+
* never buffers the entire file in memory. When `size` is provided, only
|
|
890
|
+
* hashes the first `size` bytes to match what will be archived in the tar
|
|
891
|
+
* entry.
|
|
892
892
|
*/
|
|
893
893
|
async function computeFileSha256(
|
|
894
894
|
filePath: string,
|
|
@@ -896,11 +896,28 @@ async function computeFileSha256(
|
|
|
896
896
|
): Promise<string> {
|
|
897
897
|
const hash = createHash("sha256");
|
|
898
898
|
if (size === 0) return hash.digest("hex");
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
899
|
+
|
|
900
|
+
// Read through an explicitly-managed FileHandle so the descriptor is
|
|
901
|
+
// always released in `finally`. This pass opens every file in the
|
|
902
|
+
// workspace, so any per-file descriptor leak here would exhaust the
|
|
903
|
+
// daemon's file-descriptor limit (EMFILE). A bounded read loop keeps peak
|
|
904
|
+
// memory flat regardless of file size.
|
|
905
|
+
const readChunkBytes = 64 * 1024;
|
|
906
|
+
const handle = await open(filePath, "r");
|
|
907
|
+
try {
|
|
908
|
+
const chunk = Buffer.allocUnsafe(readChunkBytes);
|
|
909
|
+
let position = 0;
|
|
910
|
+
for (;;) {
|
|
911
|
+
const remaining = size !== undefined ? size - position : Infinity;
|
|
912
|
+
if (remaining <= 0) break;
|
|
913
|
+
const toRead = Math.min(chunk.length, remaining);
|
|
914
|
+
const { bytesRead } = await handle.read(chunk, 0, toRead, position);
|
|
915
|
+
if (bytesRead === 0) break;
|
|
916
|
+
hash.update(chunk.subarray(0, bytesRead));
|
|
917
|
+
position += bytesRead;
|
|
918
|
+
}
|
|
919
|
+
} finally {
|
|
920
|
+
await handle.close();
|
|
904
921
|
}
|
|
905
922
|
return hash.digest("hex");
|
|
906
923
|
}
|
|
@@ -1056,21 +1073,33 @@ async function* generateTarStream(
|
|
|
1056
1073
|
// alignment. The WAL checkpoint before export is the primary
|
|
1057
1074
|
// consistency mechanism for the database.
|
|
1058
1075
|
let bytesWritten = 0;
|
|
1059
|
-
if (
|
|
1076
|
+
if (entrySize > 0) {
|
|
1077
|
+
// Read through an explicitly-managed FileHandle so the descriptor is
|
|
1078
|
+
// released in `finally` even when the consumer abandons the generator
|
|
1079
|
+
// mid-file. Each read uses a fresh buffer so a yielded chunk is never
|
|
1080
|
+
// overwritten by the next read before the consumer drains it.
|
|
1081
|
+
let handle: FileHandle | undefined;
|
|
1060
1082
|
try {
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
const
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1083
|
+
handle = await open(file.diskPath, "r");
|
|
1084
|
+
const readChunkBytes = 64 * 1024;
|
|
1085
|
+
while (bytesWritten < entrySize) {
|
|
1086
|
+
const toRead = Math.min(readChunkBytes, entrySize - bytesWritten);
|
|
1087
|
+
const buf = Buffer.allocUnsafe(toRead);
|
|
1088
|
+
const { bytesRead } = await handle.read(
|
|
1089
|
+
buf,
|
|
1090
|
+
0,
|
|
1091
|
+
toRead,
|
|
1092
|
+
bytesWritten,
|
|
1093
|
+
);
|
|
1094
|
+
if (bytesRead === 0) break;
|
|
1095
|
+
bytesWritten += bytesRead;
|
|
1096
|
+
yield bytesRead === buf.length ? buf : buf.subarray(0, bytesRead);
|
|
1070
1097
|
}
|
|
1071
1098
|
} catch {
|
|
1072
1099
|
// File was deleted or rotated between passes — emit zeros for
|
|
1073
1100
|
// the full declared size so the tar structure stays valid
|
|
1101
|
+
} finally {
|
|
1102
|
+
if (handle) await handle.close();
|
|
1074
1103
|
}
|
|
1075
1104
|
}
|
|
1076
1105
|
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
import type { InteractionResolutionState } from "../api/events/interaction-resolved.js";
|
|
22
|
+
import type { QuestionEntry } from "../api/events/question-request.js";
|
|
22
23
|
import type { UserDecision } from "../permissions/types.js";
|
|
23
24
|
import { getLogger } from "../util/logger.js";
|
|
24
25
|
import { broadcastMessage } from "./assistant-event-hub.js";
|
|
@@ -50,6 +51,18 @@ export interface ConfirmationDetails {
|
|
|
50
51
|
}>;
|
|
51
52
|
}
|
|
52
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Full batched question payload carried on a pending `question` interaction, so
|
|
56
|
+
* a history-load render can stamp the outstanding prompt back onto its tool
|
|
57
|
+
* call and rehydrate the question card on a cold reconnect. Mirrors the
|
|
58
|
+
* `question_request` event's `questions[]` — `metadata` only retains the
|
|
59
|
+
* `orderedIds`/`optionsById` the response route needs to validate submissions,
|
|
60
|
+
* which is insufficient to reconstruct the card.
|
|
61
|
+
*/
|
|
62
|
+
export interface QuestionDetails {
|
|
63
|
+
entries: QuestionEntry[];
|
|
64
|
+
}
|
|
65
|
+
|
|
53
66
|
export interface PendingInteraction {
|
|
54
67
|
/**
|
|
55
68
|
* Owning conversation, when the interaction was raised inside one. Absent
|
|
@@ -69,6 +82,8 @@ export interface PendingInteraction {
|
|
|
69
82
|
| "host_transfer"
|
|
70
83
|
| "acp_confirmation";
|
|
71
84
|
confirmationDetails?: ConfirmationDetails;
|
|
85
|
+
/** For a pending `question`: the full batched entries, so a history-load render can rehydrate the question card. */
|
|
86
|
+
questionDetails?: QuestionDetails;
|
|
72
87
|
/** For ACP permissions: resolves directly without a Conversation object. */
|
|
73
88
|
directResolve?: (decision: UserDecision) => void;
|
|
74
89
|
/** When set, the host_bash request should be routed to this specific client. */
|
|
@@ -51,6 +51,7 @@ type ListClientsResponse = {
|
|
|
51
51
|
machineName?: string;
|
|
52
52
|
connectedAt: string;
|
|
53
53
|
lastActiveAt: string;
|
|
54
|
+
degraded?: boolean;
|
|
54
55
|
}>;
|
|
55
56
|
};
|
|
56
57
|
|
|
@@ -152,4 +153,16 @@ describe("list_clients route — same-user filter", () => {
|
|
|
152
153
|
const ids = result.clients.map((c) => c.clientId).sort();
|
|
153
154
|
expect(ids).toEqual(["client-A1", "client-B1", "client-noprincipal"]);
|
|
154
155
|
});
|
|
156
|
+
|
|
157
|
+
test("includes a degraded flag (false for a freshly connected client)", () => {
|
|
158
|
+
registerClient({ clientId: "client-A1", actorPrincipalId: "user-A" });
|
|
159
|
+
|
|
160
|
+
const handler = findHandler("list_clients");
|
|
161
|
+
const result = handler({
|
|
162
|
+
headers: { "x-vellum-actor-principal-id": "user-A" },
|
|
163
|
+
}) as ListClientsResponse;
|
|
164
|
+
|
|
165
|
+
expect(result.clients).toHaveLength(1);
|
|
166
|
+
expect(result.clients[0].degraded).toBe(false);
|
|
167
|
+
});
|
|
155
168
|
});
|