@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,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for guardian approval-by-reaction on the canonical pipeline.
|
|
3
|
+
*
|
|
4
|
+
* A Slack emoji reaction (✅ / ❌) on a delivered approval card is routed
|
|
5
|
+
* through `routeGuardianReply` exactly like a button press or text reply. The
|
|
6
|
+
* emoji maps to an action and the reacted card's message id (`reactedMessageTs`)
|
|
7
|
+
* resolves the target request via its canonical delivery record — so the
|
|
8
|
+
* decision is applied precisely even when several cards are pending in the same
|
|
9
|
+
* chat, with no clarification prompt.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
13
|
+
|
|
14
|
+
mock.module("../util/logger.js", () => ({
|
|
15
|
+
getLogger: () =>
|
|
16
|
+
new Proxy({} as Record<string, unknown>, {
|
|
17
|
+
get: () => () => {},
|
|
18
|
+
}),
|
|
19
|
+
truncateForLog: (value: string) => value,
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
const _conversationMocks = new Map<string, unknown>();
|
|
23
|
+
mock.module("../daemon/conversation-registry.js", () => ({
|
|
24
|
+
findConversation: (id: string) => _conversationMocks.get(id),
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
import type { Conversation } from "../daemon/conversation.js";
|
|
28
|
+
import {
|
|
29
|
+
createCanonicalGuardianDelivery,
|
|
30
|
+
createCanonicalGuardianRequest,
|
|
31
|
+
getCanonicalGuardianRequest,
|
|
32
|
+
getPendingCanonicalRequestByDestinationMessage,
|
|
33
|
+
resolveCanonicalGuardianRequest,
|
|
34
|
+
} from "../memory/canonical-guardian-store.js";
|
|
35
|
+
import { getDb } from "../memory/db-connection.js";
|
|
36
|
+
import { initializeDb } from "../memory/db-init.js";
|
|
37
|
+
import { routeGuardianReply } from "../runtime/guardian-reply-router.js";
|
|
38
|
+
import * as pendingInteractions from "../runtime/pending-interactions.js";
|
|
39
|
+
|
|
40
|
+
initializeDb();
|
|
41
|
+
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
// Constants & helpers
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
const GUARDIAN_USER = "U_GUARDIAN";
|
|
47
|
+
const PRINCIPAL = "principal-1";
|
|
48
|
+
const CHAT_ID = "D_GUARDIAN_DM";
|
|
49
|
+
const TOOL_NAME = "execute_shell";
|
|
50
|
+
|
|
51
|
+
function resetTables(): void {
|
|
52
|
+
const db = getDb();
|
|
53
|
+
db.run("DELETE FROM canonical_guardian_deliveries");
|
|
54
|
+
db.run("DELETE FROM canonical_guardian_requests");
|
|
55
|
+
db.run("DELETE FROM scoped_approval_grants");
|
|
56
|
+
pendingInteractions.clear();
|
|
57
|
+
_conversationMocks.clear();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Seed a pending tool-approval (canonical request + delivery + pending
|
|
62
|
+
* interaction with a mock conversation) and return the mocked
|
|
63
|
+
* `handleConfirmationResponse` so callers can assert it was driven.
|
|
64
|
+
*/
|
|
65
|
+
function seedApproval(opts: {
|
|
66
|
+
requestId: string;
|
|
67
|
+
ts: string;
|
|
68
|
+
chatId?: string;
|
|
69
|
+
principal?: string;
|
|
70
|
+
}): ReturnType<typeof mock> {
|
|
71
|
+
const chatId = opts.chatId ?? CHAT_ID;
|
|
72
|
+
const principal = opts.principal ?? PRINCIPAL;
|
|
73
|
+
const conversationId = `conv-${opts.requestId}`;
|
|
74
|
+
|
|
75
|
+
createCanonicalGuardianRequest({
|
|
76
|
+
id: opts.requestId,
|
|
77
|
+
kind: "tool_approval",
|
|
78
|
+
sourceType: "channel",
|
|
79
|
+
sourceChannel: "slack",
|
|
80
|
+
conversationId,
|
|
81
|
+
requesterExternalUserId: "requester-1",
|
|
82
|
+
requesterChatId: chatId,
|
|
83
|
+
guardianExternalUserId: GUARDIAN_USER,
|
|
84
|
+
guardianPrincipalId: principal,
|
|
85
|
+
toolName: TOOL_NAME,
|
|
86
|
+
status: "pending",
|
|
87
|
+
expiresAt: Date.now() + 300_000,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
createCanonicalGuardianDelivery({
|
|
91
|
+
requestId: opts.requestId,
|
|
92
|
+
destinationChannel: "slack",
|
|
93
|
+
destinationChatId: chatId,
|
|
94
|
+
destinationMessageId: opts.ts,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const handleConfirmationResponse = mock(() => {});
|
|
98
|
+
_conversationMocks.set(conversationId, {
|
|
99
|
+
handleConfirmationResponse,
|
|
100
|
+
ensureActorScopedHistory: async () => {},
|
|
101
|
+
} as unknown as Conversation);
|
|
102
|
+
pendingInteractions.register(opts.requestId, {
|
|
103
|
+
conversationId,
|
|
104
|
+
kind: "confirmation",
|
|
105
|
+
confirmationDetails: {
|
|
106
|
+
toolName: TOOL_NAME,
|
|
107
|
+
input: { command: "echo hi" },
|
|
108
|
+
riskLevel: "high",
|
|
109
|
+
allowlistOptions: [{ label: "t", description: "t", pattern: "t" }],
|
|
110
|
+
scopeOptions: [{ label: "everywhere", scope: "everywhere" }],
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
return handleConfirmationResponse;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function reaction(opts: {
|
|
117
|
+
emoji: string;
|
|
118
|
+
reactedMessageTs?: string;
|
|
119
|
+
chatId?: string;
|
|
120
|
+
principal?: string;
|
|
121
|
+
}): Parameters<typeof routeGuardianReply>[0] {
|
|
122
|
+
return {
|
|
123
|
+
messageText: "",
|
|
124
|
+
channel: "slack",
|
|
125
|
+
actor: {
|
|
126
|
+
actorPrincipalId: opts.principal ?? PRINCIPAL,
|
|
127
|
+
actorExternalUserId: GUARDIAN_USER,
|
|
128
|
+
channel: "slack",
|
|
129
|
+
guardianPrincipalId: opts.principal ?? PRINCIPAL,
|
|
130
|
+
},
|
|
131
|
+
conversationId: "guardian-conv",
|
|
132
|
+
callbackData: `reaction:${opts.emoji}`,
|
|
133
|
+
reactedMessageTs: opts.reactedMessageTs,
|
|
134
|
+
channelDeliveryContext: {
|
|
135
|
+
replyCallbackUrl: "https://gateway.test/deliver",
|
|
136
|
+
guardianChatId: opts.chatId ?? CHAT_ID,
|
|
137
|
+
assistantId: "self",
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
// Store lookup
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
|
|
146
|
+
describe("getPendingCanonicalRequestByDestinationMessage", () => {
|
|
147
|
+
beforeEach(resetTables);
|
|
148
|
+
|
|
149
|
+
test("resolves the request whose delivery matches channel + chat + message id", () => {
|
|
150
|
+
seedApproval({ requestId: "req-1", ts: "1700000000.000001" });
|
|
151
|
+
const found = getPendingCanonicalRequestByDestinationMessage(
|
|
152
|
+
"slack",
|
|
153
|
+
CHAT_ID,
|
|
154
|
+
"1700000000.000001",
|
|
155
|
+
);
|
|
156
|
+
expect(found?.id).toBe("req-1");
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("returns null when no delivery matches the message id", () => {
|
|
160
|
+
seedApproval({ requestId: "req-1", ts: "1700000000.000001" });
|
|
161
|
+
expect(
|
|
162
|
+
getPendingCanonicalRequestByDestinationMessage(
|
|
163
|
+
"slack",
|
|
164
|
+
CHAT_ID,
|
|
165
|
+
"9999.0",
|
|
166
|
+
),
|
|
167
|
+
).toBeNull();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test("returns null once the matched request is no longer pending", () => {
|
|
171
|
+
seedApproval({ requestId: "req-1", ts: "1700000000.000001" });
|
|
172
|
+
resolveCanonicalGuardianRequest("req-1", "pending", { status: "approved" });
|
|
173
|
+
expect(
|
|
174
|
+
getPendingCanonicalRequestByDestinationMessage(
|
|
175
|
+
"slack",
|
|
176
|
+
CHAT_ID,
|
|
177
|
+
"1700000000.000001",
|
|
178
|
+
),
|
|
179
|
+
).toBeNull();
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// ---------------------------------------------------------------------------
|
|
184
|
+
// Reaction routing
|
|
185
|
+
// ---------------------------------------------------------------------------
|
|
186
|
+
|
|
187
|
+
describe("routeGuardianReply / reactions", () => {
|
|
188
|
+
beforeEach(resetTables);
|
|
189
|
+
|
|
190
|
+
test("✅ on a delivered card approves the matching request", async () => {
|
|
191
|
+
const hcr = seedApproval({ requestId: "req-1", ts: "111.1" });
|
|
192
|
+
|
|
193
|
+
const result = await routeGuardianReply(
|
|
194
|
+
reaction({ emoji: "white_check_mark", reactedMessageTs: "111.1" }),
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
expect(result.consumed).toBe(true);
|
|
198
|
+
expect(result.decisionApplied).toBe(true);
|
|
199
|
+
expect(result.type).toBe("canonical_decision_applied");
|
|
200
|
+
expect(getCanonicalGuardianRequest("req-1")?.status).toBe("approved");
|
|
201
|
+
expect(hcr).toHaveBeenCalledTimes(1);
|
|
202
|
+
expect(hcr.mock.calls[0]?.[0]).toBe("req-1");
|
|
203
|
+
expect(hcr.mock.calls[0]?.[1]).toBe("allow");
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test("❌ on a delivered card rejects the matching request", async () => {
|
|
207
|
+
const hcr = seedApproval({ requestId: "req-1", ts: "111.1" });
|
|
208
|
+
|
|
209
|
+
const result = await routeGuardianReply(
|
|
210
|
+
reaction({ emoji: "-1", reactedMessageTs: "111.1" }),
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
expect(result.decisionApplied).toBe(true);
|
|
214
|
+
expect(getCanonicalGuardianRequest("req-1")?.status).toBe("denied");
|
|
215
|
+
expect(hcr.mock.calls[0]?.[1]).toBe("deny");
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test("reacting on a specific card resolves only that card (disambiguates N>1)", async () => {
|
|
219
|
+
const hcrA = seedApproval({ requestId: "req-A", ts: "100.1" });
|
|
220
|
+
const hcrB = seedApproval({ requestId: "req-B", ts: "200.2" });
|
|
221
|
+
|
|
222
|
+
// React on card B's message — only B should resolve.
|
|
223
|
+
const result = await routeGuardianReply(
|
|
224
|
+
reaction({ emoji: "white_check_mark", reactedMessageTs: "200.2" }),
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
expect(result.requestId).toBe("req-B");
|
|
228
|
+
expect(getCanonicalGuardianRequest("req-B")?.status).toBe("approved");
|
|
229
|
+
expect(getCanonicalGuardianRequest("req-A")?.status).toBe("pending");
|
|
230
|
+
expect(hcrB).toHaveBeenCalledTimes(1);
|
|
231
|
+
expect(hcrA).not.toHaveBeenCalled();
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test("a reaction on an unknown message is not consumed (left for transcript)", async () => {
|
|
235
|
+
seedApproval({ requestId: "req-1", ts: "111.1" });
|
|
236
|
+
|
|
237
|
+
const result = await routeGuardianReply(
|
|
238
|
+
reaction({ emoji: "white_check_mark", reactedMessageTs: "no-such-ts" }),
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
expect(result.consumed).toBe(false);
|
|
242
|
+
expect(result.type).toBe("not_consumed");
|
|
243
|
+
expect(getCanonicalGuardianRequest("req-1")?.status).toBe("pending");
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test("an unknown emoji is not consumed", async () => {
|
|
247
|
+
seedApproval({ requestId: "req-1", ts: "111.1" });
|
|
248
|
+
|
|
249
|
+
const result = await routeGuardianReply(
|
|
250
|
+
reaction({ emoji: "tada", reactedMessageTs: "111.1" }),
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
expect(result.consumed).toBe(false);
|
|
254
|
+
expect(getCanonicalGuardianRequest("req-1")?.status).toBe("pending");
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
test("a reaction without a reacted message id is not consumed", async () => {
|
|
258
|
+
seedApproval({ requestId: "req-1", ts: "111.1" });
|
|
259
|
+
|
|
260
|
+
const result = await routeGuardianReply(
|
|
261
|
+
reaction({ emoji: "white_check_mark" }),
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
expect(result.consumed).toBe(false);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
test("a reaction from a different principal is surfaced, not silently dropped", async () => {
|
|
268
|
+
const hcr = seedApproval({ requestId: "req-1", ts: "111.1" });
|
|
269
|
+
|
|
270
|
+
const result = await routeGuardianReply(
|
|
271
|
+
reaction({
|
|
272
|
+
emoji: "white_check_mark",
|
|
273
|
+
reactedMessageTs: "111.1",
|
|
274
|
+
principal: "someone-else",
|
|
275
|
+
}),
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
// Consumed with a user-facing reply, request untouched, side effect not run.
|
|
279
|
+
expect(result.consumed).toBe(true);
|
|
280
|
+
expect(result.decisionApplied).toBe(false);
|
|
281
|
+
expect(result.replyText).toMatch(/permission/i);
|
|
282
|
+
expect(getCanonicalGuardianRequest("req-1")?.status).toBe("pending");
|
|
283
|
+
expect(hcr).not.toHaveBeenCalled();
|
|
284
|
+
});
|
|
285
|
+
});
|
|
@@ -8,6 +8,7 @@ let mockGetMessages: (
|
|
|
8
8
|
) => Array<{ role: string; content: string }> | null = () => null;
|
|
9
9
|
const mockProfiles = {
|
|
10
10
|
balanced: {},
|
|
11
|
+
"cost-optimized": {},
|
|
11
12
|
disabled: { status: "disabled" },
|
|
12
13
|
"quality-optimized": {},
|
|
13
14
|
};
|
|
@@ -476,6 +477,155 @@ describe("Subagent spawn success and failure", () => {
|
|
|
476
477
|
}
|
|
477
478
|
});
|
|
478
479
|
|
|
480
|
+
test("spawn inherits the invoking call site's default profile when no override is present", async () => {
|
|
481
|
+
const manager = getSubagentManager();
|
|
482
|
+
const originalSpawn = manager.spawn.bind(manager);
|
|
483
|
+
let capturedConfig: Record<string, unknown> | undefined;
|
|
484
|
+
|
|
485
|
+
manager.spawn = async (config: Record<string, unknown>) => {
|
|
486
|
+
capturedConfig = config;
|
|
487
|
+
return "inherit-default-id";
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
try {
|
|
491
|
+
const result = await executeSubagentSpawn(
|
|
492
|
+
{ label: "Inherit default", objective: "Do it" },
|
|
493
|
+
makeContext("sess-inherit-default", {
|
|
494
|
+
sendToClient: () => {},
|
|
495
|
+
invokingCallSite: "mainAgent",
|
|
496
|
+
}),
|
|
497
|
+
);
|
|
498
|
+
|
|
499
|
+
expect(result.isError).toBe(false);
|
|
500
|
+
// No explicit profile and no per-turn override → the child matches the
|
|
501
|
+
// invoking call site's resolved default profile (balanced for mainAgent
|
|
502
|
+
// in the test config).
|
|
503
|
+
expect(capturedConfig!.overrideProfile).toBe("balanced");
|
|
504
|
+
expect(capturedConfig!.forceOverrideProfile).toBeUndefined();
|
|
505
|
+
} finally {
|
|
506
|
+
manager.spawn = originalSpawn;
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
test("spawn inherits a non-main invoker's call-site default profile", async () => {
|
|
511
|
+
const manager = getSubagentManager();
|
|
512
|
+
const originalSpawn = manager.spawn.bind(manager);
|
|
513
|
+
let capturedConfig: Record<string, unknown> | undefined;
|
|
514
|
+
|
|
515
|
+
manager.spawn = async (config: Record<string, unknown>) => {
|
|
516
|
+
capturedConfig = config;
|
|
517
|
+
return "inherit-heartbeat-id";
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
try {
|
|
521
|
+
const result = await executeSubagentSpawn(
|
|
522
|
+
{ label: "Heartbeat child", objective: "Do it" },
|
|
523
|
+
makeContext("sess-inherit-heartbeat", {
|
|
524
|
+
sendToClient: () => {},
|
|
525
|
+
invokingCallSite: "heartbeatAgent",
|
|
526
|
+
}),
|
|
527
|
+
);
|
|
528
|
+
|
|
529
|
+
expect(result.isError).toBe(false);
|
|
530
|
+
// A subagent spawned from a heartbeat turn matches heartbeatAgent's own
|
|
531
|
+
// cost-optimized default, not the mainAgent default.
|
|
532
|
+
expect(capturedConfig!.overrideProfile).toBe("cost-optimized");
|
|
533
|
+
} finally {
|
|
534
|
+
manager.spawn = originalSpawn;
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
test("spawn prefers a per-turn override profile over the invoker default", async () => {
|
|
539
|
+
const manager = getSubagentManager();
|
|
540
|
+
const originalSpawn = manager.spawn.bind(manager);
|
|
541
|
+
let capturedConfig: Record<string, unknown> | undefined;
|
|
542
|
+
|
|
543
|
+
manager.spawn = async (config: Record<string, unknown>) => {
|
|
544
|
+
capturedConfig = config;
|
|
545
|
+
return "inherit-override-id";
|
|
546
|
+
};
|
|
547
|
+
|
|
548
|
+
try {
|
|
549
|
+
const result = await executeSubagentSpawn(
|
|
550
|
+
{ label: "Override child", objective: "Do it" },
|
|
551
|
+
makeContext("sess-inherit-override", {
|
|
552
|
+
sendToClient: () => {},
|
|
553
|
+
invokingCallSite: "mainAgent",
|
|
554
|
+
overrideProfile: "quality-optimized",
|
|
555
|
+
}),
|
|
556
|
+
);
|
|
557
|
+
|
|
558
|
+
expect(result.isError).toBe(false);
|
|
559
|
+
// The live per-turn override (per-conversation or tool-routed) wins over
|
|
560
|
+
// the call-site default, and is forwarded non-forced.
|
|
561
|
+
expect(capturedConfig!.overrideProfile).toBe("quality-optimized");
|
|
562
|
+
expect(capturedConfig!.forceOverrideProfile).toBeUndefined();
|
|
563
|
+
} finally {
|
|
564
|
+
manager.spawn = originalSpawn;
|
|
565
|
+
}
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
test("spawn skips the auto profile so the child keeps its own default", async () => {
|
|
569
|
+
const manager = getSubagentManager();
|
|
570
|
+
const originalSpawn = manager.spawn.bind(manager);
|
|
571
|
+
let capturedConfig: Record<string, unknown> | undefined;
|
|
572
|
+
|
|
573
|
+
manager.spawn = async (config: Record<string, unknown>) => {
|
|
574
|
+
capturedConfig = config;
|
|
575
|
+
return "inherit-auto-id";
|
|
576
|
+
};
|
|
577
|
+
|
|
578
|
+
try {
|
|
579
|
+
const result = await executeSubagentSpawn(
|
|
580
|
+
{ label: "Auto child", objective: "Do it" },
|
|
581
|
+
makeContext("sess-inherit-auto", {
|
|
582
|
+
sendToClient: () => {},
|
|
583
|
+
invokingCallSite: "mainAgent",
|
|
584
|
+
// "auto" is metadata-only; forwarding it would collapse the child to
|
|
585
|
+
// llm.default, so the inherited path drops it and the child keeps its
|
|
586
|
+
// own subagentSpawn default.
|
|
587
|
+
overrideProfile: "auto",
|
|
588
|
+
}),
|
|
589
|
+
);
|
|
590
|
+
|
|
591
|
+
expect(result.isError).toBe(false);
|
|
592
|
+
expect(capturedConfig!.overrideProfile).toBeUndefined();
|
|
593
|
+
} finally {
|
|
594
|
+
manager.spawn = originalSpawn;
|
|
595
|
+
}
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
test("spawn still forces an explicit inference_profile over the invoker default", async () => {
|
|
599
|
+
const manager = getSubagentManager();
|
|
600
|
+
const originalSpawn = manager.spawn.bind(manager);
|
|
601
|
+
let capturedConfig: Record<string, unknown> | undefined;
|
|
602
|
+
|
|
603
|
+
manager.spawn = async (config: Record<string, unknown>) => {
|
|
604
|
+
capturedConfig = config;
|
|
605
|
+
return "inherit-explicit-id";
|
|
606
|
+
};
|
|
607
|
+
|
|
608
|
+
try {
|
|
609
|
+
const result = await executeSubagentSpawn(
|
|
610
|
+
{
|
|
611
|
+
label: "Explicit child",
|
|
612
|
+
objective: "Do it",
|
|
613
|
+
inference_profile: "cost-optimized",
|
|
614
|
+
},
|
|
615
|
+
makeContext("sess-inherit-explicit", {
|
|
616
|
+
sendToClient: () => {},
|
|
617
|
+
invokingCallSite: "mainAgent",
|
|
618
|
+
}),
|
|
619
|
+
);
|
|
620
|
+
|
|
621
|
+
expect(result.isError).toBe(false);
|
|
622
|
+
expect(capturedConfig!.overrideProfile).toBe("cost-optimized");
|
|
623
|
+
expect(capturedConfig!.forceOverrideProfile).toBe(true);
|
|
624
|
+
} finally {
|
|
625
|
+
manager.spawn = originalSpawn;
|
|
626
|
+
}
|
|
627
|
+
});
|
|
628
|
+
|
|
479
629
|
test("spawn returns error for unknown inference_profile", async () => {
|
|
480
630
|
const manager = getSubagentManager();
|
|
481
631
|
const originalSpawn = manager.spawn.bind(manager);
|
|
@@ -104,7 +104,7 @@ function currentResponse(toolUseId: string): ToolResultContent {
|
|
|
104
104
|
};
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
/** A weak-model id that matches
|
|
107
|
+
/** A weak-model id that matches WEAK_OPEN_MODEL_PATTERN (the gated population). */
|
|
108
108
|
const WEAK_MODEL = "minimax/minimax-m3";
|
|
109
109
|
|
|
110
110
|
function makeCtx(
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* - `user-prompt-submit` — first-pass generation from the submitted prompt,
|
|
8
8
|
* scheduled on a later macrotask so the main agent-loop LLM request is
|
|
9
9
|
* issued first.
|
|
10
|
-
* - `stop` —
|
|
11
|
-
*
|
|
10
|
+
* - `stop` — fallback retry for replaceable titles and second-pass
|
|
11
|
+
* regeneration once the conversation reaches its third user turn.
|
|
12
12
|
*
|
|
13
13
|
* Both let the title service resolve the provider, persist the title, and
|
|
14
14
|
* broadcast the resulting `conversation_title_updated` / `sync_changed`
|
|
@@ -31,13 +31,42 @@ const queueGenerateConversationTitleMock = mock(
|
|
|
31
31
|
}): void => undefined,
|
|
32
32
|
);
|
|
33
33
|
const queueRegenerateConversationTitleMock = mock(
|
|
34
|
-
(_params: {
|
|
34
|
+
(_params: {
|
|
35
|
+
conversationId: string;
|
|
36
|
+
provider?: unknown;
|
|
37
|
+
onlyIfReplaceable?: boolean;
|
|
38
|
+
}): void => undefined,
|
|
35
39
|
);
|
|
36
40
|
mock.module("../memory/conversation-title-service.js", () => ({
|
|
41
|
+
AUTO_TITLE_DETERMINISTIC: 2,
|
|
42
|
+
isReplaceableTitle: (title: string | null) =>
|
|
43
|
+
title == null ||
|
|
44
|
+
title === "" ||
|
|
45
|
+
title === "Generating title..." ||
|
|
46
|
+
title === "New Conversation" ||
|
|
47
|
+
title === "Untitled" ||
|
|
48
|
+
title === "Untitled Conversation" ||
|
|
49
|
+
title.startsWith("Runtime: "),
|
|
37
50
|
queueGenerateConversationTitle: queueGenerateConversationTitleMock,
|
|
38
51
|
queueRegenerateConversationTitle: queueRegenerateConversationTitleMock,
|
|
39
52
|
}));
|
|
40
53
|
|
|
54
|
+
const mockGetConversation = mock(
|
|
55
|
+
(_conversationId: string) =>
|
|
56
|
+
({
|
|
57
|
+
title: "Existing Title",
|
|
58
|
+
isAutoTitle: 1,
|
|
59
|
+
conversationType: "standard",
|
|
60
|
+
}) as {
|
|
61
|
+
title: string;
|
|
62
|
+
isAutoTitle: number;
|
|
63
|
+
conversationType: string;
|
|
64
|
+
},
|
|
65
|
+
);
|
|
66
|
+
mock.module("../memory/conversation-crud.js", () => ({
|
|
67
|
+
getConversation: mockGetConversation,
|
|
68
|
+
}));
|
|
69
|
+
|
|
41
70
|
// The `stop` hook reads `conversations.skipAutoRetitling`; stub the loader so
|
|
42
71
|
// the opt-out is controllable per test.
|
|
43
72
|
let skipAutoRetitling = false;
|
|
@@ -192,6 +221,19 @@ describe("title-generate stop hook", () => {
|
|
|
192
221
|
resetPluginRegistryForTests();
|
|
193
222
|
queueRegenerateConversationTitleMock.mockReset();
|
|
194
223
|
queueRegenerateConversationTitleMock.mockImplementation(() => undefined);
|
|
224
|
+
mockGetConversation.mockReset();
|
|
225
|
+
mockGetConversation.mockImplementation(
|
|
226
|
+
(_conversationId: string) =>
|
|
227
|
+
({
|
|
228
|
+
title: "Existing Title",
|
|
229
|
+
isAutoTitle: 1,
|
|
230
|
+
conversationType: "standard",
|
|
231
|
+
}) as {
|
|
232
|
+
title: string;
|
|
233
|
+
isAutoTitle: number;
|
|
234
|
+
conversationType: string;
|
|
235
|
+
},
|
|
236
|
+
);
|
|
195
237
|
skipAutoRetitling = false;
|
|
196
238
|
});
|
|
197
239
|
|
|
@@ -210,6 +252,61 @@ describe("title-generate stop hook", () => {
|
|
|
210
252
|
expect(call?.conversationId).toBe("conv-1");
|
|
211
253
|
expect(call).not.toHaveProperty("provider");
|
|
212
254
|
expect(call).not.toHaveProperty("signal");
|
|
255
|
+
expect(call).not.toHaveProperty("onlyIfReplaceable");
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
test("retries a replaceable fallback title after a successful turn", async () => {
|
|
259
|
+
mockGetConversation.mockReturnValueOnce({
|
|
260
|
+
title: "Untitled Conversation",
|
|
261
|
+
isAutoTitle: 2,
|
|
262
|
+
conversationType: "standard",
|
|
263
|
+
});
|
|
264
|
+
const ctx = makeStopCtx({ messages: historyWithUserTurns(1) });
|
|
265
|
+
|
|
266
|
+
await stop(ctx);
|
|
267
|
+
await flushMacrotasks();
|
|
268
|
+
|
|
269
|
+
expect(queueRegenerateConversationTitleMock).toHaveBeenCalledTimes(1);
|
|
270
|
+
expect(queueRegenerateConversationTitleMock).toHaveBeenCalledWith({
|
|
271
|
+
conversationId: "conv-1",
|
|
272
|
+
onlyIfReplaceable: true,
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
test("preserves the third-turn retitle when the title is still replaceable", async () => {
|
|
277
|
+
mockGetConversation.mockReturnValueOnce({
|
|
278
|
+
title: "Untitled Conversation",
|
|
279
|
+
isAutoTitle: 2,
|
|
280
|
+
conversationType: "standard",
|
|
281
|
+
});
|
|
282
|
+
const ctx = makeStopCtx({ messages: historyWithUserTurns(3) });
|
|
283
|
+
|
|
284
|
+
await stop(ctx);
|
|
285
|
+
await flushMacrotasks();
|
|
286
|
+
|
|
287
|
+
expect(queueRegenerateConversationTitleMock).toHaveBeenCalledTimes(1);
|
|
288
|
+
const call = queueRegenerateConversationTitleMock.mock.calls[0]?.[0];
|
|
289
|
+
expect(call?.conversationId).toBe("conv-1");
|
|
290
|
+
expect(call).not.toHaveProperty("onlyIfReplaceable");
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
test("fallback title retry is not blocked by the retitling opt-out", async () => {
|
|
294
|
+
skipAutoRetitling = true;
|
|
295
|
+
mockGetConversation.mockReturnValueOnce({
|
|
296
|
+
title: "Generating title...",
|
|
297
|
+
isAutoTitle: 1,
|
|
298
|
+
conversationType: "standard",
|
|
299
|
+
});
|
|
300
|
+
const ctx = makeStopCtx({ messages: historyWithUserTurns(1) });
|
|
301
|
+
|
|
302
|
+
await stop(ctx);
|
|
303
|
+
await flushMacrotasks();
|
|
304
|
+
|
|
305
|
+
expect(queueRegenerateConversationTitleMock).toHaveBeenCalledTimes(1);
|
|
306
|
+
expect(queueRegenerateConversationTitleMock).toHaveBeenCalledWith({
|
|
307
|
+
conversationId: "conv-1",
|
|
308
|
+
onlyIfReplaceable: true,
|
|
309
|
+
});
|
|
213
310
|
});
|
|
214
311
|
|
|
215
312
|
test("defers the regeneration so the completed turn is persisted first", async () => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
|
|
3
|
-
import { buildToolApprovalSeedContentBlocks } from "../notifications/
|
|
3
|
+
import { buildToolApprovalSeedContentBlocks } from "../notifications/approval-card-data.js";
|
|
4
4
|
|
|
5
5
|
describe("buildToolApprovalSeedContentBlocks", () => {
|
|
6
6
|
const toolApprovalPayload: Record<string, unknown> = {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
2
|
|
|
3
|
-
// Toggle for the
|
|
3
|
+
// Toggle for the share_analytics opt-out gate the listener consults when
|
|
4
4
|
// populating the telemetry columns.
|
|
5
|
-
let
|
|
5
|
+
let shareAnalytics = true;
|
|
6
6
|
|
|
7
|
-
mock.module("../
|
|
8
|
-
|
|
7
|
+
mock.module("../platform/consent-cache.js", () => ({
|
|
8
|
+
getCachedShareAnalytics: () => shareAnalytics,
|
|
9
9
|
}));
|
|
10
10
|
|
|
11
11
|
import { createToolAuditListener } from "../events/tool-audit-listener.js";
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
|
|
18
18
|
describe("tool audit listener", () => {
|
|
19
19
|
beforeEach(() => {
|
|
20
|
-
|
|
20
|
+
shareAnalytics = true;
|
|
21
21
|
});
|
|
22
22
|
|
|
23
23
|
test("records executed events with truncated output", () => {
|
|
@@ -361,8 +361,8 @@ describe("tool audit listener", () => {
|
|
|
361
361
|
);
|
|
362
362
|
});
|
|
363
363
|
|
|
364
|
-
test("persists NULL telemetry columns when
|
|
365
|
-
|
|
364
|
+
test("persists NULL telemetry columns when share_analytics is opted out", () => {
|
|
365
|
+
shareAnalytics = false;
|
|
366
366
|
const records: ToolInvocationRecord[] = [];
|
|
367
367
|
const listener = createToolAuditListener((record) => records.push(record));
|
|
368
368
|
|
|
@@ -34,9 +34,6 @@ const mockConfig = {
|
|
|
34
34
|
permissions: {
|
|
35
35
|
mode: "workspace" as const,
|
|
36
36
|
},
|
|
37
|
-
// The audit listener nulls the telemetry columns when this is false; the
|
|
38
|
-
// end-to-end listener tests below assert the opted-in sizing behavior.
|
|
39
|
-
collectUsageData: true,
|
|
40
37
|
};
|
|
41
38
|
|
|
42
39
|
let checkerDecision: "allow" | "prompt" | "deny" = "allow";
|
|
@@ -56,6 +53,12 @@ mock.module("../config/loader.js", () => ({
|
|
|
56
53
|
setNestedValue: () => {},
|
|
57
54
|
}));
|
|
58
55
|
|
|
56
|
+
// Analytics consent is granted so the audit listener populates the telemetry
|
|
57
|
+
// columns; the end-to-end listener tests below assert the opted-in sizing.
|
|
58
|
+
mock.module("../platform/consent-cache.js", () => ({
|
|
59
|
+
getCachedShareAnalytics: () => true,
|
|
60
|
+
}));
|
|
61
|
+
|
|
59
62
|
mock.module("../util/logger.js", () => ({
|
|
60
63
|
getLogger: () =>
|
|
61
64
|
new Proxy({} as Record<string, unknown>, {
|
|
@@ -169,8 +169,10 @@ async function simulateNotifierPoll(params: {
|
|
|
169
169
|
notifiedRequestIds,
|
|
170
170
|
} = params;
|
|
171
171
|
|
|
172
|
-
// Gate check: only
|
|
173
|
-
|
|
172
|
+
// Gate check: only identity-known non-guardian contacts with guardian route
|
|
173
|
+
const isIdentityKnownNonGuardian =
|
|
174
|
+
trustClass === "trusted_contact" || trustClass === "unverified_contact";
|
|
175
|
+
if (!isIdentityKnownNonGuardian || !guardianExternalUserId) {
|
|
174
176
|
return false;
|
|
175
177
|
}
|
|
176
178
|
|