@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
|
@@ -105,6 +105,16 @@ export const SEARCH_PROVIDER_CATALOG: readonly SearchProviderCatalogEntry[] = [
|
|
|
105
105
|
fallbackOrder: 3,
|
|
106
106
|
privacyPolicyUrl: "https://tavily.com/privacy",
|
|
107
107
|
},
|
|
108
|
+
{
|
|
109
|
+
id: "firecrawl",
|
|
110
|
+
displayName: "Firecrawl",
|
|
111
|
+
kind: "byok",
|
|
112
|
+
apiKeyPrefix: "fc-...",
|
|
113
|
+
envVar: "FIRECRAWL_API_KEY",
|
|
114
|
+
secretKey: "firecrawl",
|
|
115
|
+
fallbackOrder: 4,
|
|
116
|
+
privacyPolicyUrl: "https://www.firecrawl.dev/privacy-policy",
|
|
117
|
+
},
|
|
108
118
|
];
|
|
109
119
|
|
|
110
120
|
/** Provider ids accepted by the web-search config schema. */
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Family-level classification of "weak open models" — open-weight models
|
|
3
|
+
* (Kimi, DeepSeek, MiniMax, GLM) that disregard static instructions and have
|
|
4
|
+
* capability gaps that capable models (Claude, GPT) do not. Used by harness
|
|
5
|
+
* levers that coach or redirect these models without touching capable-model
|
|
6
|
+
* behavior: the task-progress-nudge plugin and the empty-dynamic_page surface
|
|
7
|
+
* redirect.
|
|
8
|
+
*
|
|
9
|
+
* Family-level matching spans provider naming conventions: OpenRouter
|
|
10
|
+
* `moonshotai/kimi-k2.6`, `deepseek/deepseek-chat`, `minimax/minimax-m3`;
|
|
11
|
+
* Fireworks `accounts/fireworks/models/minimax-m3`, `kimi-k2p6`. Extend the
|
|
12
|
+
* pattern as other open models show the same gaps.
|
|
13
|
+
*
|
|
14
|
+
* Distinct from exploration-drift's narrower `LOOP_PRONE_MODEL_PATTERN`, which
|
|
15
|
+
* targets specific loop-prone versions rather than the whole capability family.
|
|
16
|
+
*/
|
|
17
|
+
export const WEAK_OPEN_MODEL_PATTERN = /kimi|deepseek|minimax|glm/i;
|
|
18
|
+
|
|
19
|
+
/** True when `model` is a weak open model (see {@link WEAK_OPEN_MODEL_PATTERN}). */
|
|
20
|
+
export function isWeakOpenModel(model: string | null | undefined): boolean {
|
|
21
|
+
return typeof model === "string" && WEAK_OPEN_MODEL_PATTERN.test(model);
|
|
22
|
+
}
|
|
@@ -244,6 +244,55 @@ const recordRequestLogCalls: Array<{
|
|
|
244
244
|
provider?: string;
|
|
245
245
|
callSite?: string;
|
|
246
246
|
}> = [];
|
|
247
|
+
const recordUsageCalls: Array<{
|
|
248
|
+
conversationId: string;
|
|
249
|
+
inputTokens: number;
|
|
250
|
+
outputTokens: number;
|
|
251
|
+
model: string;
|
|
252
|
+
actor: string;
|
|
253
|
+
cacheCreationInputTokens: number;
|
|
254
|
+
cacheReadInputTokens: number;
|
|
255
|
+
callSite: unknown;
|
|
256
|
+
overrideProfile: unknown;
|
|
257
|
+
forceOverrideProfile: unknown;
|
|
258
|
+
selectionSeed: unknown;
|
|
259
|
+
}> = [];
|
|
260
|
+
mock.module("../../daemon/conversation-usage.js", () => ({
|
|
261
|
+
recordUsage: (
|
|
262
|
+
ctx: { conversationId: string },
|
|
263
|
+
inputTokens: number,
|
|
264
|
+
outputTokens: number,
|
|
265
|
+
model: string,
|
|
266
|
+
_onEvent: unknown,
|
|
267
|
+
actor: string,
|
|
268
|
+
_requestId: unknown,
|
|
269
|
+
cacheCreationInputTokens = 0,
|
|
270
|
+
cacheReadInputTokens = 0,
|
|
271
|
+
_rawResponse?: unknown,
|
|
272
|
+
_llmCallCount?: number,
|
|
273
|
+
_contextWindow?: unknown,
|
|
274
|
+
attribution?: {
|
|
275
|
+
callSite?: unknown;
|
|
276
|
+
overrideProfile?: unknown;
|
|
277
|
+
forceOverrideProfile?: unknown;
|
|
278
|
+
selectionSeed?: unknown;
|
|
279
|
+
},
|
|
280
|
+
) => {
|
|
281
|
+
recordUsageCalls.push({
|
|
282
|
+
conversationId: ctx.conversationId,
|
|
283
|
+
inputTokens,
|
|
284
|
+
outputTokens,
|
|
285
|
+
model,
|
|
286
|
+
actor,
|
|
287
|
+
cacheCreationInputTokens,
|
|
288
|
+
cacheReadInputTokens,
|
|
289
|
+
callSite: attribution?.callSite ?? null,
|
|
290
|
+
overrideProfile: attribution?.overrideProfile ?? null,
|
|
291
|
+
forceOverrideProfile: attribution?.forceOverrideProfile,
|
|
292
|
+
selectionSeed: attribution?.selectionSeed,
|
|
293
|
+
});
|
|
294
|
+
},
|
|
295
|
+
}));
|
|
247
296
|
mock.module("../../memory/llm-request-log-store.js", () => ({
|
|
248
297
|
recordRequestLog: (
|
|
249
298
|
conversationId: string,
|
|
@@ -464,6 +513,7 @@ beforeEach(() => {
|
|
|
464
513
|
__resetWakeChainForTests();
|
|
465
514
|
wakeConvRegistry.clear();
|
|
466
515
|
recordRequestLogCalls.length = 0;
|
|
516
|
+
recordUsageCalls.length = 0;
|
|
467
517
|
mockGetOrCreateConversationCalls.length = 0;
|
|
468
518
|
mockResolverTarget = null;
|
|
469
519
|
mockGetConversationOverrideProfile = () => undefined;
|
|
@@ -1896,6 +1946,137 @@ describe("wakeAgentForOpportunity", () => {
|
|
|
1896
1946
|
expect(recordRequestLogCalls[0]?.callSite).toBe("memoryRetrospective");
|
|
1897
1947
|
});
|
|
1898
1948
|
|
|
1949
|
+
test("wake records LLM usage to the cost ledger, attributed to its call site", async () => {
|
|
1950
|
+
const usageEvent: AgentEvent = {
|
|
1951
|
+
type: "usage",
|
|
1952
|
+
inputTokens: 100,
|
|
1953
|
+
outputTokens: 5,
|
|
1954
|
+
model: "test-model",
|
|
1955
|
+
actualProvider: "test-provider",
|
|
1956
|
+
providerDurationMs: 10,
|
|
1957
|
+
cacheCreationInputTokens: 7,
|
|
1958
|
+
cacheReadInputTokens: 11,
|
|
1959
|
+
rawRequest: { request: "retrospective wake" },
|
|
1960
|
+
rawResponse: { response: "real reply" },
|
|
1961
|
+
};
|
|
1962
|
+
const conversation = makeWakeConversation({
|
|
1963
|
+
baseline: [{ role: "user", content: [{ type: "text", text: "hi" }] }],
|
|
1964
|
+
scriptedEvents: [usageEvent],
|
|
1965
|
+
scriptedAssistant: {
|
|
1966
|
+
role: "assistant",
|
|
1967
|
+
content: [{ type: "text", text: "real reply" }],
|
|
1968
|
+
},
|
|
1969
|
+
});
|
|
1970
|
+
|
|
1971
|
+
await wakeAgentForOpportunity(
|
|
1972
|
+
{
|
|
1973
|
+
conversationId: conversation.conversationId,
|
|
1974
|
+
hint: "do reply",
|
|
1975
|
+
source: "unit-test",
|
|
1976
|
+
callSite: "memoryRetrospective",
|
|
1977
|
+
},
|
|
1978
|
+
{ resolveTarget: async () => conversation },
|
|
1979
|
+
);
|
|
1980
|
+
|
|
1981
|
+
// A wake-driven LLM call records a usage row attributed to its
|
|
1982
|
+
// conversation, so its cost reaches the ledger.
|
|
1983
|
+
expect(recordUsageCalls).toHaveLength(1);
|
|
1984
|
+
expect(recordUsageCalls[0]).toMatchObject({
|
|
1985
|
+
conversationId: conversation.conversationId,
|
|
1986
|
+
inputTokens: 100,
|
|
1987
|
+
outputTokens: 5,
|
|
1988
|
+
model: "test-model",
|
|
1989
|
+
actor: "main_agent",
|
|
1990
|
+
cacheCreationInputTokens: 7,
|
|
1991
|
+
cacheReadInputTokens: 11,
|
|
1992
|
+
callSite: "memoryRetrospective",
|
|
1993
|
+
// Seed the dispatch path used for mix-arm resolution.
|
|
1994
|
+
selectionSeed: conversation.conversationId,
|
|
1995
|
+
});
|
|
1996
|
+
});
|
|
1997
|
+
|
|
1998
|
+
test("forced-profile wake records usage under the forced profile, not the call site", async () => {
|
|
1999
|
+
const usageEvent: AgentEvent = {
|
|
2000
|
+
type: "usage",
|
|
2001
|
+
inputTokens: 100,
|
|
2002
|
+
outputTokens: 5,
|
|
2003
|
+
model: "test-model",
|
|
2004
|
+
actualProvider: "test-provider",
|
|
2005
|
+
providerDurationMs: 10,
|
|
2006
|
+
rawRequest: { request: "forced wake" },
|
|
2007
|
+
rawResponse: { response: "real reply" },
|
|
2008
|
+
};
|
|
2009
|
+
const conversation = makeWakeConversation({
|
|
2010
|
+
baseline: [{ role: "user", content: [{ type: "text", text: "hi" }] }],
|
|
2011
|
+
scriptedEvents: [usageEvent],
|
|
2012
|
+
scriptedAssistant: {
|
|
2013
|
+
role: "assistant",
|
|
2014
|
+
content: [{ type: "text", text: "real reply" }],
|
|
2015
|
+
},
|
|
2016
|
+
});
|
|
2017
|
+
|
|
2018
|
+
await wakeAgentForOpportunity(
|
|
2019
|
+
{
|
|
2020
|
+
conversationId: conversation.conversationId,
|
|
2021
|
+
hint: "do reply",
|
|
2022
|
+
source: "unit-test",
|
|
2023
|
+
callSite: "memoryRetrospective",
|
|
2024
|
+
// Stand-in for the source conversation's profile, floated above the
|
|
2025
|
+
// call-site profile (fork retrospectives). `recordUsage` is mocked, so
|
|
2026
|
+
// this value is only threaded through and asserted — not resolved.
|
|
2027
|
+
forceOverrideProfile: "source-profile",
|
|
2028
|
+
},
|
|
2029
|
+
{ resolveTarget: async () => conversation },
|
|
2030
|
+
);
|
|
2031
|
+
|
|
2032
|
+
expect(recordUsageCalls).toHaveLength(1);
|
|
2033
|
+
expect(recordUsageCalls[0]).toMatchObject({
|
|
2034
|
+
overrideProfile: "source-profile",
|
|
2035
|
+
forceOverrideProfile: true,
|
|
2036
|
+
selectionSeed: conversation.conversationId,
|
|
2037
|
+
});
|
|
2038
|
+
});
|
|
2039
|
+
|
|
2040
|
+
test("silent no-op wake still records usage even though its request log is dropped", async () => {
|
|
2041
|
+
const usageEvent: AgentEvent = {
|
|
2042
|
+
type: "usage",
|
|
2043
|
+
inputTokens: 100,
|
|
2044
|
+
outputTokens: 5,
|
|
2045
|
+
model: "test-model",
|
|
2046
|
+
actualProvider: "test-provider",
|
|
2047
|
+
providerDurationMs: 10,
|
|
2048
|
+
rawRequest: { request: "no-op wake" },
|
|
2049
|
+
rawResponse: { response: "no output" },
|
|
2050
|
+
};
|
|
2051
|
+
const conversation = makeWakeConversation({
|
|
2052
|
+
baseline: [{ role: "user", content: [{ type: "text", text: "hi" }] }],
|
|
2053
|
+
scriptedEvents: [usageEvent],
|
|
2054
|
+
// Empty assistant text → silent no-op, so the request log is dropped.
|
|
2055
|
+
scriptedAssistant: {
|
|
2056
|
+
role: "assistant",
|
|
2057
|
+
content: [{ type: "text", text: "" }],
|
|
2058
|
+
},
|
|
2059
|
+
});
|
|
2060
|
+
|
|
2061
|
+
await wakeAgentForOpportunity(
|
|
2062
|
+
{
|
|
2063
|
+
conversationId: conversation.conversationId,
|
|
2064
|
+
hint: "consider doing nothing",
|
|
2065
|
+
source: "unit-test",
|
|
2066
|
+
},
|
|
2067
|
+
{ resolveTarget: async () => conversation },
|
|
2068
|
+
);
|
|
2069
|
+
|
|
2070
|
+
// Silent wake drops its request log, but the call still cost money.
|
|
2071
|
+
expect(recordRequestLogCalls).toHaveLength(0);
|
|
2072
|
+
expect(recordUsageCalls).toHaveLength(1);
|
|
2073
|
+
expect(recordUsageCalls[0]).toMatchObject({
|
|
2074
|
+
conversationId: conversation.conversationId,
|
|
2075
|
+
inputTokens: 100,
|
|
2076
|
+
outputTokens: 5,
|
|
2077
|
+
});
|
|
2078
|
+
});
|
|
2079
|
+
|
|
1899
2080
|
test("non-serializable usage payload does not abort the wake", async () => {
|
|
1900
2081
|
// Circular reference in rawRequest — JSON.stringify throws on this.
|
|
1901
2082
|
// Serialization must happen inside persistLog's try/catch so the
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { isClientDegraded } from "../client-health.js";
|
|
4
|
+
|
|
5
|
+
const HEARTBEAT_MS = 7_000;
|
|
6
|
+
|
|
7
|
+
describe("isClientDegraded", () => {
|
|
8
|
+
test("fresh connection is not degraded", () => {
|
|
9
|
+
const now = new Date(1_000_000);
|
|
10
|
+
const lastActiveAt = now; // just connected
|
|
11
|
+
expect(isClientDegraded(lastActiveAt, now, HEARTBEAT_MS)).toBe(false);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("actively heartbeating connection is not degraded", () => {
|
|
15
|
+
const now = new Date(1_000_000);
|
|
16
|
+
const lastActiveAt = new Date(now.getTime() - 2_000); // heartbeat 2s ago
|
|
17
|
+
expect(isClientDegraded(lastActiveAt, now, HEARTBEAT_MS)).toBe(false);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("never-heartbeating connection that has gone stale is degraded", () => {
|
|
21
|
+
const now = new Date(1_000_000);
|
|
22
|
+
const lastActiveAt = new Date(now.getTime() - 30_000); // 30s since any activity
|
|
23
|
+
expect(isClientDegraded(lastActiveAt, now, HEARTBEAT_MS)).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("heartbeated then froze is degraded (stale relative to now, not connectedAt)", () => {
|
|
27
|
+
const now = new Date(1_000_000);
|
|
28
|
+
// Connected long ago, heartbeated for a while, then stopped an hour ago.
|
|
29
|
+
const lastActiveAt = new Date(now.getTime() - 3_600_000);
|
|
30
|
+
expect(isClientDegraded(lastActiveAt, now, HEARTBEAT_MS)).toBe(true);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("boundary: just under 2 heartbeat intervals stale is not degraded", () => {
|
|
34
|
+
const now = new Date(1_000_000);
|
|
35
|
+
const lastActiveAt = new Date(now.getTime() - (2 * HEARTBEAT_MS - 1));
|
|
36
|
+
expect(isClientDegraded(lastActiveAt, now, HEARTBEAT_MS)).toBe(false);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("boundary: just over 2 heartbeat intervals stale is degraded", () => {
|
|
40
|
+
const now = new Date(1_000_000);
|
|
41
|
+
const lastActiveAt = new Date(now.getTime() - (2 * HEARTBEAT_MS + 1));
|
|
42
|
+
expect(isClientDegraded(lastActiveAt, now, HEARTBEAT_MS)).toBe(true);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -15,33 +15,20 @@ import type { ChannelId } from "../channels/types.js";
|
|
|
15
15
|
import { findGuardianForChannel } from "../contacts/contact-store.js";
|
|
16
16
|
import type { ChannelStatus } from "../contacts/types.js";
|
|
17
17
|
import {
|
|
18
|
-
createCanonicalGuardianDelivery,
|
|
19
18
|
createCanonicalGuardianRequest,
|
|
20
19
|
listCanonicalGuardianRequests,
|
|
21
|
-
updateCanonicalGuardianDelivery,
|
|
22
20
|
} from "../memory/canonical-guardian-store.js";
|
|
21
|
+
import {
|
|
22
|
+
recordApprovalCardDelivery,
|
|
23
|
+
recordGuardianRequestDeliveries,
|
|
24
|
+
} from "../notifications/canonical-delivery-recorder.js";
|
|
23
25
|
import { emitNotificationSignal } from "../notifications/emit-signal.js";
|
|
24
|
-
import type {
|
|
25
|
-
GuardianResolutionSource,
|
|
26
|
-
NotificationSourceChannel,
|
|
27
|
-
} from "../notifications/signal.js";
|
|
28
|
-
import type { NotificationDeliveryResult } from "../notifications/types.js";
|
|
26
|
+
import type { GuardianResolutionSource } from "../notifications/signal.js";
|
|
29
27
|
import { getLogger } from "../util/logger.js";
|
|
30
28
|
import { GUARDIAN_APPROVAL_TTL_MS } from "./routes/channel-route-shared.js";
|
|
31
29
|
|
|
32
30
|
const log = getLogger("access-request-helper");
|
|
33
31
|
|
|
34
|
-
function applyDeliveryStatus(
|
|
35
|
-
deliveryId: string,
|
|
36
|
-
result: NotificationDeliveryResult,
|
|
37
|
-
): void {
|
|
38
|
-
if (result.status === "sent") {
|
|
39
|
-
updateCanonicalGuardianDelivery(deliveryId, { status: "sent" });
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
updateCanonicalGuardianDelivery(deliveryId, { status: "failed" });
|
|
43
|
-
}
|
|
44
|
-
|
|
45
32
|
// ---------------------------------------------------------------------------
|
|
46
33
|
// Types
|
|
47
34
|
// ---------------------------------------------------------------------------
|
|
@@ -127,7 +114,7 @@ export function notifyGuardianOfAccessRequest(
|
|
|
127
114
|
sourceGuardian &&
|
|
128
115
|
sourceGuardian.contact.principalId === assistantGuardianPrincipalId
|
|
129
116
|
) {
|
|
130
|
-
guardianExternalUserId = sourceGuardian.channel.
|
|
117
|
+
guardianExternalUserId = sourceGuardian.channel.address;
|
|
131
118
|
guardianPrincipalId = sourceGuardian.contact.principalId;
|
|
132
119
|
guardianBindingChannel = sourceGuardian.channel.type;
|
|
133
120
|
guardianResolutionSource = "source-channel-contact";
|
|
@@ -136,8 +123,7 @@ export function notifyGuardianOfAccessRequest(
|
|
|
136
123
|
// Access requests always require a principal. If source-channel resolution
|
|
137
124
|
// did not match the assistant anchor, use the anchored vellum identity.
|
|
138
125
|
if (!guardianPrincipalId && vellumGuardian) {
|
|
139
|
-
guardianExternalUserId =
|
|
140
|
-
vellumGuardian.channel.externalUserId ?? guardianExternalUserId;
|
|
126
|
+
guardianExternalUserId = vellumGuardian.channel.address;
|
|
141
127
|
guardianPrincipalId = assistantGuardianPrincipalId ?? null;
|
|
142
128
|
guardianBindingChannel = guardianBindingChannel ?? "vellum";
|
|
143
129
|
guardianResolutionSource = "vellum-anchor";
|
|
@@ -200,7 +186,7 @@ export function notifyGuardianOfAccessRequest(
|
|
|
200
186
|
expiresAt: Date.now() + GUARDIAN_APPROVAL_TTL_MS,
|
|
201
187
|
});
|
|
202
188
|
|
|
203
|
-
let vellumDeliveryId: string |
|
|
189
|
+
let vellumDeliveryId: string | undefined;
|
|
204
190
|
// When the access request originates from a text channel with
|
|
205
191
|
// notification delivery support (Slack, Telegram) and the guardian was
|
|
206
192
|
// resolved via a verified same-channel contact, route the notification
|
|
@@ -219,7 +205,7 @@ export function notifyGuardianOfAccessRequest(
|
|
|
219
205
|
|
|
220
206
|
void emitNotificationSignal({
|
|
221
207
|
sourceEventName: "ingress.access_request",
|
|
222
|
-
sourceChannel
|
|
208
|
+
sourceChannel,
|
|
223
209
|
sourceContextId: `access-req-${sourceChannel}-${actorExternalId}`,
|
|
224
210
|
requiresConversation: true,
|
|
225
211
|
...(sameChannelOnly ? { routingIntent: "single_channel" as const } : {}),
|
|
@@ -250,44 +236,26 @@ export function notifyGuardianOfAccessRequest(
|
|
|
250
236
|
onConversationCreated: (info) => {
|
|
251
237
|
if (info.sourceEventName !== "ingress.access_request" || vellumDeliveryId)
|
|
252
238
|
return;
|
|
253
|
-
|
|
239
|
+
vellumDeliveryId = recordApprovalCardDelivery({
|
|
254
240
|
requestId: canonicalRequest.id,
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
});
|
|
258
|
-
vellumDeliveryId = delivery.id;
|
|
241
|
+
channel: "vellum",
|
|
242
|
+
conversationId: info.conversationId,
|
|
243
|
+
})?.id;
|
|
259
244
|
},
|
|
260
245
|
})
|
|
261
246
|
.then((signalResult) => {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
destinationChannel: "vellum",
|
|
268
|
-
destinationConversationId: result.conversationId,
|
|
269
|
-
});
|
|
270
|
-
vellumDeliveryId = delivery.id;
|
|
271
|
-
}
|
|
272
|
-
applyDeliveryStatus(vellumDeliveryId, result);
|
|
273
|
-
continue;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
const delivery = createCanonicalGuardianDelivery({
|
|
277
|
-
requestId: canonicalRequest.id,
|
|
278
|
-
destinationChannel: result.channel,
|
|
279
|
-
destinationChatId:
|
|
280
|
-
result.destination.length > 0 ? result.destination : undefined,
|
|
281
|
-
});
|
|
282
|
-
applyDeliveryStatus(delivery.id, result);
|
|
283
|
-
}
|
|
247
|
+
vellumDeliveryId = recordGuardianRequestDeliveries({
|
|
248
|
+
requestId: canonicalRequest.id,
|
|
249
|
+
deliveryResults: signalResult.deliveryResults,
|
|
250
|
+
vellumDeliveryId,
|
|
251
|
+
});
|
|
284
252
|
|
|
285
253
|
if (!vellumDeliveryId && !sameChannelOnly) {
|
|
286
|
-
|
|
254
|
+
recordApprovalCardDelivery({
|
|
287
255
|
requestId: canonicalRequest.id,
|
|
288
|
-
|
|
256
|
+
channel: "vellum",
|
|
257
|
+
status: "failed",
|
|
289
258
|
});
|
|
290
|
-
updateCanonicalGuardianDelivery(fallback.id, { status: "failed" });
|
|
291
259
|
log.warn(
|
|
292
260
|
{ requestId: canonicalRequest.id, reason: signalResult.reason },
|
|
293
261
|
"Notification pipeline did not produce a vellum delivery result for access request",
|
|
@@ -8,7 +8,12 @@
|
|
|
8
8
|
* Trust classifications:
|
|
9
9
|
* - `guardian`: sender matches the guardian contact's channel for this channel type.
|
|
10
10
|
* - `trusted_contact`: sender is an active contact channel (not the guardian).
|
|
11
|
-
* - `
|
|
11
|
+
* - `unverified_contact`: sender matches a contact channel that is pending or
|
|
12
|
+
* unverified — known to the guardian but not yet through verification.
|
|
13
|
+
* Treated identically to `trusted_contact` downstream; the distinction only
|
|
14
|
+
* matters at the admission floor (see channel admission policy).
|
|
15
|
+
* - `unknown`: sender has no matching contact, no identity could be
|
|
16
|
+
* established, or the contact's channel is blocked/revoked.
|
|
12
17
|
*/
|
|
13
18
|
|
|
14
19
|
import type { ChannelId } from "../channels/types.js";
|
|
@@ -38,22 +43,35 @@ export type { TrustContext } from "../daemon/trust-context.js";
|
|
|
38
43
|
* - `'trusted_contact'`: The sender is an active contact with a channel
|
|
39
44
|
* (not the guardian). Trusted contacts can invoke tools but require
|
|
40
45
|
* guardian approval for sensitive operations.
|
|
46
|
+
* - `'unverified_contact'`: The sender matches a contact channel whose
|
|
47
|
+
* status is `pending` or `unverified` — known to the guardian but not yet
|
|
48
|
+
* verified. Treated identically to `trusted_contact` for every downstream
|
|
49
|
+
* capability/tool/approval decision; the distinction is admission-only.
|
|
41
50
|
* - `'unknown'`: The sender has no contact record, no identity could be
|
|
42
|
-
* established, or the sender is
|
|
51
|
+
* established, or the sender is a blocked/revoked contact. Unknown
|
|
43
52
|
* actors are fail-closed with no escalation path.
|
|
44
53
|
*/
|
|
45
|
-
export type TrustClass =
|
|
54
|
+
export type TrustClass =
|
|
55
|
+
| "guardian"
|
|
56
|
+
| "trusted_contact"
|
|
57
|
+
| "unverified_contact"
|
|
58
|
+
| "unknown";
|
|
46
59
|
|
|
47
|
-
/**
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
60
|
+
/**
|
|
61
|
+
* Trust-class ordinal used by the per-channel admission policy floor check.
|
|
62
|
+
* Higher rank = more trusted. Blocked/revoked never reach classification —
|
|
63
|
+
* their effective rank is 0 and is enforced by the inbound ACL stage's
|
|
64
|
+
* member-status short-circuit, not via this table.
|
|
65
|
+
*
|
|
66
|
+
* See `wave-b-plan.md` §2.4. Paired with `ADMISSION_FLOOR` from
|
|
67
|
+
* `@vellumai/gateway-client` — both tables move together.
|
|
68
|
+
*/
|
|
69
|
+
export const TRUST_CLASS_RANK: Record<TrustClass, number> = {
|
|
70
|
+
guardian: 4,
|
|
71
|
+
trusted_contact: 3,
|
|
72
|
+
unverified_contact: 2,
|
|
73
|
+
unknown: 1,
|
|
74
|
+
};
|
|
57
75
|
|
|
58
76
|
/**
|
|
59
77
|
* Fully resolved trust context from the actor trust resolver.
|
|
@@ -181,8 +199,7 @@ export function resolveActorTrust(
|
|
|
181
199
|
const { contact: guardianContact, channel: guardianChannel } =
|
|
182
200
|
guardianResult;
|
|
183
201
|
guardianBindingMatch = {
|
|
184
|
-
guardianExternalUserId:
|
|
185
|
-
guardianChannel.externalUserId ?? guardianChannel.address ?? "",
|
|
202
|
+
guardianExternalUserId: guardianChannel.address,
|
|
186
203
|
guardianDeliveryChatId: guardianChannel.externalChatId,
|
|
187
204
|
};
|
|
188
205
|
guardianPrincipalId = guardianContact.principalId ?? undefined;
|
|
@@ -249,12 +266,23 @@ export function resolveActorTrust(
|
|
|
249
266
|
let trustClass: TrustClass;
|
|
250
267
|
if (isGuardian) {
|
|
251
268
|
trustClass = "guardian";
|
|
252
|
-
} else if (
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
269
|
+
} else if (memberMatchesSender && memberRecord) {
|
|
270
|
+
const status = memberRecord.channel.status;
|
|
271
|
+
if (status === "active") {
|
|
272
|
+
trustClass = "trusted_contact";
|
|
273
|
+
} else if (status === "unverified" || status === "pending") {
|
|
274
|
+
// Pre-verification / awaiting-verification contacts get their own
|
|
275
|
+
// admission tier. Treated identically to trusted_contact for ALL
|
|
276
|
+
// downstream capability/tool/approval decisions; the distinction
|
|
277
|
+
// only matters at the channel admission floor.
|
|
278
|
+
trustClass = "unverified_contact";
|
|
279
|
+
} else {
|
|
280
|
+
// status === "blocked" or "revoked" → unknown. acl-enforcement
|
|
281
|
+
// re-checks resolvedMember.channel.status and emits the appropriate
|
|
282
|
+
// member_blocked / member_revoked reasons, so hard-deny semantics
|
|
283
|
+
// for these statuses are preserved end-to-end.
|
|
284
|
+
trustClass = "unknown";
|
|
285
|
+
}
|
|
258
286
|
} else {
|
|
259
287
|
trustClass = "unknown";
|
|
260
288
|
}
|
|
@@ -58,6 +58,7 @@ import { resolveEffectiveContextWindow } from "../config/llm-context-resolution.
|
|
|
58
58
|
import { getConfig } from "../config/loader.js";
|
|
59
59
|
import type { LLMCallSite } from "../config/schemas/llm.js";
|
|
60
60
|
import type { Conversation } from "../daemon/conversation.js";
|
|
61
|
+
import { recordUsage } from "../daemon/conversation-usage.js";
|
|
61
62
|
import { getDiskPressureStatus } from "../daemon/disk-pressure-guard.js";
|
|
62
63
|
import {
|
|
63
64
|
classifyDiskPressureTurnPolicy,
|
|
@@ -195,6 +196,14 @@ export interface WakeOptions {
|
|
|
195
196
|
* retrospectives whose conversation title already says "(Retrospective)").
|
|
196
197
|
*/
|
|
197
198
|
suppressWakeSurface?: boolean;
|
|
199
|
+
/**
|
|
200
|
+
* Run the wake's turn as non-interactive (no client present). Threads to the
|
|
201
|
+
* agent loop's `isNonInteractive`, which gates the `<non_interactive_context>`
|
|
202
|
+
* and `<background_turn>` injectors. Fork-based memory retrospectives set this
|
|
203
|
+
* to the source conversation's background-turn state so the fork reproduces
|
|
204
|
+
* the source's injected turn block (prompt-cache prefix parity). Default false.
|
|
205
|
+
*/
|
|
206
|
+
isNonInteractive?: boolean;
|
|
198
207
|
/**
|
|
199
208
|
* Optional exact tool allowlist for this wake. Used by internal maintenance
|
|
200
209
|
* jobs that need the assistant's judgment but must not execute arbitrary
|
|
@@ -587,6 +596,7 @@ export async function wakeAgentForOpportunity(
|
|
|
587
596
|
opts.forceOverrideProfile ??
|
|
588
597
|
getConversationOverrideProfile(conversationId);
|
|
589
598
|
const callSite = opts.callSite ?? "mainAgent";
|
|
599
|
+
const isNonInteractive = opts.isNonInteractive ?? false;
|
|
590
600
|
const config = getConfig();
|
|
591
601
|
const effectiveContextWindow = resolveEffectiveContextWindow({
|
|
592
602
|
llm: config.llm,
|
|
@@ -784,6 +794,47 @@ export async function wakeAgentForOpportunity(
|
|
|
784
794
|
if (event.type === "compaction_completed") {
|
|
785
795
|
recordCompactionEndBestEffort(conversationId, event);
|
|
786
796
|
}
|
|
797
|
+
// Normal user turns record usage via the `dispatchAgentEvent` event handler)
|
|
798
|
+
// Wakes run their own onEvent and bypass it, so record here.
|
|
799
|
+
if (event.type === "usage") {
|
|
800
|
+
try {
|
|
801
|
+
recordUsage(
|
|
802
|
+
{
|
|
803
|
+
conversationId,
|
|
804
|
+
providerName: event.actualProvider ?? conversation.provider.name,
|
|
805
|
+
usageStats: conversation.usageStats,
|
|
806
|
+
},
|
|
807
|
+
event.inputTokens,
|
|
808
|
+
event.outputTokens,
|
|
809
|
+
event.model,
|
|
810
|
+
() => {},
|
|
811
|
+
"main_agent",
|
|
812
|
+
`wake:${source}`,
|
|
813
|
+
event.cacheCreationInputTokens ?? 0,
|
|
814
|
+
event.cacheReadInputTokens ?? 0,
|
|
815
|
+
event.rawResponse,
|
|
816
|
+
1,
|
|
817
|
+
undefined,
|
|
818
|
+
// Mirror the profile state the request actually ran under:
|
|
819
|
+
// `forceOverrideProfile` floats the override above the call-site
|
|
820
|
+
// profile (fork retrospectives with matchConversationProfile), and
|
|
821
|
+
// the conversation-id seed resolves the same mix arm the dispatch
|
|
822
|
+
// path chose. Without these, attribution credits the call-site
|
|
823
|
+
// profile/arm instead of the one that ran.
|
|
824
|
+
{
|
|
825
|
+
callSite,
|
|
826
|
+
overrideProfile: overrideProfile ?? null,
|
|
827
|
+
forceOverrideProfile,
|
|
828
|
+
selectionSeed: conversationId,
|
|
829
|
+
},
|
|
830
|
+
);
|
|
831
|
+
} catch (err) {
|
|
832
|
+
log.warn(
|
|
833
|
+
{ conversationId, source, err },
|
|
834
|
+
"agent-wake: usage recording failed (non-fatal)",
|
|
835
|
+
);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
787
838
|
// Replicates the recordRequestLog side-effect in `handleUsage` because
|
|
788
839
|
// wakes own their own onEvent and never reach `dispatchAgentEvent`.
|
|
789
840
|
// Defer persistence while buffering — see `pendingLogs` above.
|
|
@@ -1080,6 +1131,7 @@ export async function wakeAgentForOpportunity(
|
|
|
1080
1131
|
trust: wakeTrust,
|
|
1081
1132
|
overrideProfile,
|
|
1082
1133
|
forceOverrideProfile,
|
|
1134
|
+
isNonInteractive,
|
|
1083
1135
|
// The wake's compaction lives in the pre-run gate above
|
|
1084
1136
|
// (`conversation.maybeCompact()`), never in the loop: the in-loop
|
|
1085
1137
|
// budget gate and overflow-recovery ladder stay disabled because
|
|
@@ -40,6 +40,7 @@ export function capabilityForMessageType(
|
|
|
40
40
|
return HOST_PREFIX_TO_CAPABILITY[stem];
|
|
41
41
|
}
|
|
42
42
|
import { appendEventToStream } from "../signals/event-stream.js";
|
|
43
|
+
import { IntegrityError } from "../util/errors.js";
|
|
43
44
|
import { getLogger } from "../util/logger.js";
|
|
44
45
|
import type { AssistantEvent } from "./assistant-event.js";
|
|
45
46
|
import { buildAssistantEvent } from "./assistant-event.js";
|
|
@@ -757,9 +758,22 @@ async function createCanonicalRequestForConfirmation(
|
|
|
757
758
|
});
|
|
758
759
|
}
|
|
759
760
|
} catch (err) {
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
761
|
+
if (err instanceof IntegrityError) {
|
|
762
|
+
// The confirmation could not be promoted to a canonical guardian request
|
|
763
|
+
// (e.g. its trust context resolved no guardianPrincipalId). Channel
|
|
764
|
+
// guardian decisions — reactions, buttons, and text — all route through
|
|
765
|
+
// the canonical pipeline, so without this record none of them can resolve
|
|
766
|
+
// the confirmation. Surface it rather than swallowing: for a guardian's
|
|
767
|
+
// own confirmation a bound principal should always be present.
|
|
768
|
+
log.warn(
|
|
769
|
+
{ err, conversationId, requestId: msg.requestId },
|
|
770
|
+
"Could not create canonical guardian request for confirmation; channel guardian decisions will not work for it",
|
|
771
|
+
);
|
|
772
|
+
} else {
|
|
773
|
+
log.debug(
|
|
774
|
+
{ err, conversationId },
|
|
775
|
+
"Failed to create canonical request from broadcast",
|
|
776
|
+
);
|
|
777
|
+
}
|
|
764
778
|
}
|
|
765
779
|
}
|
|
@@ -210,6 +210,18 @@ describe("ROUTES policy declarations", () => {
|
|
|
210
210
|
expect(route!.policy!.requiredScopes).toContain("chat.write");
|
|
211
211
|
});
|
|
212
212
|
|
|
213
|
+
test("platform/status is readable by browser actors", async () => {
|
|
214
|
+
const { ROUTES } = await import("../../routes/index.js");
|
|
215
|
+
const route = ROUTES.find(
|
|
216
|
+
(r) => r.endpoint === "platform/status" && r.method === "GET",
|
|
217
|
+
);
|
|
218
|
+
expect(route).toBeDefined();
|
|
219
|
+
expect(route!.policy).not.toBeNull();
|
|
220
|
+
expect(route!.policy!.allowedPrincipalTypes).toContain("actor");
|
|
221
|
+
expect(route!.policy!.allowedPrincipalTypes).toContain("local");
|
|
222
|
+
expect(route!.policy!.requiredScopes).toContain("settings.read");
|
|
223
|
+
});
|
|
224
|
+
|
|
213
225
|
test("confirm declares an approval-write policy", async () => {
|
|
214
226
|
const { ROUTES } = await import("../../routes/index.js");
|
|
215
227
|
const route = ROUTES.find((r) => r.endpoint === "confirm");
|
|
@@ -27,10 +27,7 @@ export function requireBoundGuardian(
|
|
|
27
27
|
// No guardian yet — in pre-bootstrap state, allow through
|
|
28
28
|
return null;
|
|
29
29
|
}
|
|
30
|
-
if (
|
|
31
|
-
(guardianResult.channel.externalUserId ??
|
|
32
|
-
guardianResult.contact.principalId) !== authContext.actorPrincipalId
|
|
33
|
-
) {
|
|
30
|
+
if (guardianResult.channel.address !== authContext.actorPrincipalId) {
|
|
34
31
|
return httpError(
|
|
35
32
|
"FORBIDDEN",
|
|
36
33
|
"Actor is not the bound guardian for this channel",
|