@vellumai/assistant 0.10.2 → 0.10.3-dev.202606252046.9075fd5
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/bun.lock +20 -0
- package/docs/workspace-tools.md +42 -33
- package/eslint-rules/cli-no-daemon-internals.js +6 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-delivery-contract.test.ts +91 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +31 -0
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +44 -0
- package/node_modules/@vellumai/gateway-client/src/guardian-delivery-contract.ts +48 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +14 -0
- package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +17 -0
- package/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/channels.test.ts +28 -0
- package/node_modules/@vellumai/service-contracts/src/channels.ts +41 -0
- package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/openapi.yaml +196 -56
- package/package.json +4 -1
- package/scripts/test.sh +36 -15
- package/src/__tests__/actor-token-service.test.ts +36 -14
- package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +83 -31
- package/src/__tests__/agent-loop-override-profile.test.ts +1 -0
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
- package/src/__tests__/agent-wake-override-profile.test.ts +2 -0
- package/src/__tests__/annotate-activity-metadata.test.ts +2 -0
- package/src/__tests__/annotate-risk-options.test.ts +2 -0
- package/src/__tests__/approval-cascade.test.ts +2 -0
- package/src/__tests__/assistant-attachments.test.ts +42 -0
- package/src/__tests__/background-workers-disk-pressure.test.ts +2 -0
- package/src/__tests__/btw-routes.test.ts +2 -0
- package/src/__tests__/build-persisted-content.test.ts +2 -0
- package/src/__tests__/call-controller.test.ts +19 -0
- package/src/__tests__/channel-approval-routes.test.ts +21 -26
- package/src/__tests__/channel-delivery-store.test.ts +28 -0
- package/src/__tests__/channel-guardian.test.ts +164 -78
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +11 -19
- package/src/__tests__/channel-reply-delivery.test.ts +2 -0
- package/src/__tests__/code-search-tool.test.ts +585 -0
- package/src/__tests__/compaction-events.test.ts +2 -0
- package/src/__tests__/compaction-ledger-store.test.ts +128 -0
- package/src/__tests__/compaction.benchmark.test.ts +2 -0
- package/src/__tests__/compactor-call-site-logging.test.ts +2 -0
- package/src/__tests__/compactor-low-watermark-cut.test.ts +2 -0
- package/src/__tests__/compactor-preserved-tail-count.test.ts +2 -0
- package/src/__tests__/compactor-summary-call-truncation.test.ts +2 -0
- package/src/__tests__/compactor-web-search-strip.test.ts +2 -0
- package/src/__tests__/config-loader-backfill.test.ts +123 -10
- package/src/__tests__/config-schema.test.ts +1 -0
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +31 -29
- package/src/__tests__/consult-deadline.test.ts +60 -0
- package/src/__tests__/contact-store-interaction-info.test.ts +156 -0
- package/src/__tests__/contact-store-user-file.test.ts +7 -10
- package/src/__tests__/contacts-relay-reads.test.ts +19 -24
- package/src/__tests__/contacts-write.test.ts +0 -2
- package/src/__tests__/conversation-abort-tool-results.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop.test.ts +134 -0
- package/src/__tests__/conversation-analysis-routes.test.ts +2 -0
- package/src/__tests__/conversation-app-control-lifecycle.test.ts +2 -0
- package/src/__tests__/conversation-attention-telegram.test.ts +9 -11
- package/src/__tests__/conversation-confirmation-signals.test.ts +2 -0
- package/src/__tests__/conversation-fork-crud.test.ts +354 -24
- package/src/__tests__/conversation-history-web-search.test.ts +2 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +2 -0
- package/src/__tests__/conversation-load-history-stripped.test.ts +2 -0
- package/src/__tests__/conversation-pairing.test.ts +2 -0
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +2 -0
- package/src/__tests__/conversation-process-callsite.test.ts +2 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -0
- package/src/__tests__/conversation-queue.test.ts +91 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +14 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +14 -0
- package/src/__tests__/conversation-slash-queue.test.ts +2 -0
- package/src/__tests__/conversation-slash-unknown.test.ts +2 -0
- package/src/__tests__/conversation-speed-override.test.ts +2 -0
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +29 -0
- package/src/__tests__/conversation-title-service.test.ts +2 -0
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +47 -0
- package/src/__tests__/conversation-usage.test.ts +2 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +2 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +2 -0
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +2 -0
- package/src/__tests__/credential-security-invariants.test.ts +1 -1
- package/src/__tests__/db-compaction-events-migration.test.ts +129 -0
- package/src/__tests__/db-migration-rollback.test.ts +205 -171
- package/src/__tests__/db-test-helpers.ts +5 -4
- package/src/__tests__/delete-propagation.test.ts +5 -3
- package/src/__tests__/deterministic-verification-control-plane.test.ts +4 -2
- package/src/__tests__/disk-pressure-guard.test.ts +41 -0
- package/src/__tests__/dm-backfill.test.ts +6 -4
- package/src/__tests__/dm-persistence.test.ts +2 -0
- package/src/__tests__/emit-signal-routing-intent.test.ts +7 -6
- package/src/__tests__/events-dev-bypass-actor.test.ts +7 -1
- package/src/__tests__/exploration-drift-hook.test.ts +3 -2
- package/src/__tests__/filing-service.test.ts +2 -0
- package/src/__tests__/guardian-binding-drift-heal.test.ts +104 -19
- package/src/__tests__/guardian-dispatch.test.ts +140 -1
- package/src/__tests__/guardian-expiry-notifier.test.ts +282 -0
- package/src/__tests__/guardian-outbound-http.test.ts +13 -0
- package/src/__tests__/guardian-routing-state.test.ts +6 -10
- package/src/__tests__/heartbeat-disk-pressure.test.ts +2 -0
- package/src/__tests__/heartbeat-service.test.ts +2 -0
- package/src/__tests__/helpers/channel-test-adapter.ts +46 -19
- package/src/__tests__/helpers/create-guardian-binding.ts +15 -23
- package/src/__tests__/helpers/mock-logger.ts +1 -0
- package/src/__tests__/helpers/seed-contact-channel.ts +96 -0
- package/src/__tests__/host-app-control-routes.test.ts +24 -30
- package/src/__tests__/host-bash-routes.test.ts +31 -41
- package/src/__tests__/host-browser-routes.test.ts +26 -32
- package/src/__tests__/host-cu-routes-targeted.test.ts +25 -33
- package/src/__tests__/host-file-routes-targeted.test.ts +40 -52
- package/src/__tests__/host-transfer-routes-targeted.test.ts +31 -43
- package/src/__tests__/http-user-message-parity.test.ts +290 -8
- package/src/__tests__/inbound-invite-redemption.test.ts +115 -10
- package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
- package/src/__tests__/invite-redemption-service.test.ts +471 -53
- package/src/__tests__/invite-routes-http.test.ts +34 -0
- package/src/__tests__/invite-service-ipc.test.ts +65 -2
- package/src/__tests__/llm-context-normalization.test.ts +105 -0
- package/src/__tests__/llm-request-log-error-payload.test.ts +71 -9
- package/src/__tests__/llm-usage-store.test.ts +25 -0
- package/src/__tests__/mcp-config-secret-boundary.test.ts +392 -0
- package/src/__tests__/mcp-health-check.test.ts +2 -1
- package/src/__tests__/media-stream-server-integration.test.ts +127 -0
- package/src/__tests__/memory-retrieval-hook.test.ts +2 -0
- package/src/__tests__/messaging-send-tool.test.ts +2 -0
- package/src/__tests__/migration-import-from-url.test.ts +2 -2
- package/src/__tests__/mtime-cache.test.ts +275 -5
- package/src/__tests__/native-web-search.test.ts +2 -0
- package/src/__tests__/non-member-access-request.test.ts +191 -17
- package/src/__tests__/notification-broadcaster.test.ts +4 -0
- package/src/__tests__/notification-decision-recipient-context.test.ts +33 -32
- package/src/__tests__/notification-deep-link.test.ts +6 -0
- package/src/__tests__/notification-guardian-path.test.ts +19 -0
- package/src/__tests__/onboarding-persona-write.test.ts +52 -22
- package/src/__tests__/openai-provider.test.ts +22 -12
- package/src/__tests__/openai-responses-provider.test.ts +12 -2
- package/src/__tests__/outbound-slack-persistence.test.ts +2 -0
- package/src/__tests__/pending-interactions-resolved-event.test.ts +7 -4
- package/src/__tests__/persist-onboarding-artifacts.test.ts +1 -0
- package/src/__tests__/persistence-secret-redaction.test.ts +2 -0
- package/src/__tests__/persona-resolver.test.ts +75 -45
- package/src/__tests__/plugin-bootstrap.test.ts +21 -83
- package/src/__tests__/plugin-disabled-state.test.ts +190 -0
- package/src/__tests__/plugin-pipeline.test.ts +96 -0
- package/src/__tests__/plugin-route-contribution.test.ts +6 -19
- package/src/__tests__/plugin-tool-contribution.test.ts +5 -20
- package/src/__tests__/plugin-types.test.ts +2 -4
- package/src/__tests__/process-message-background-slack.test.ts +2 -0
- package/src/__tests__/process-message-display-content.test.ts +2 -0
- package/src/__tests__/provider-error-scenarios.test.ts +5 -4
- package/src/__tests__/provider-usage-tracking.test.ts +39 -0
- package/src/__tests__/reaction-intercept-cold-cache-warm.test.ts +135 -0
- package/src/__tests__/reaction-intercept-member-verdict-warm.test.ts +158 -0
- package/src/__tests__/reaction-persistence.test.ts +51 -4
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +2 -0
- package/src/__tests__/registry.test.ts +4 -1
- package/src/__tests__/relay-server.test.ts +758 -32
- package/src/__tests__/runtime-attachment-metadata.test.ts +9 -12
- package/src/__tests__/secret-ingress-http.test.ts +14 -0
- package/src/__tests__/send-endpoint-busy.test.ts +30 -8
- package/src/__tests__/settings-routes.test.ts +32 -0
- package/src/__tests__/skills.test.ts +44 -0
- package/src/__tests__/slack-inbound-verification.test.ts +47 -2
- package/src/__tests__/sse-actor-principal-guardian-source.test.ts +79 -0
- package/src/__tests__/steer-on-enqueue-question.test.ts +181 -0
- package/src/__tests__/stt-hints.test.ts +49 -15
- package/src/__tests__/subagent-detail.test.ts +27 -0
- package/src/__tests__/subagent-disposal.test.ts +65 -0
- package/src/__tests__/subagent-fork-prompt-role.test.ts +195 -0
- package/src/__tests__/subagent-fork-spawn.test.ts +6 -7
- package/src/__tests__/subagent-notify-parent.test.ts +2 -0
- package/src/__tests__/subagent-role-registry.test.ts +24 -6
- package/src/__tests__/subagent-spawn-and-await.test.ts +546 -0
- package/src/__tests__/subagent-spawn-tool-fork.test.ts +2 -0
- package/src/__tests__/subagent-tools.test.ts +398 -1
- package/src/__tests__/suggestion-routes.test.ts +2 -0
- package/src/__tests__/thread-backfill.test.ts +3 -3
- package/src/__tests__/title-generate-hook.test.ts +2 -0
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +2 -0
- package/src/__tests__/tool-executor.test.ts +16 -11
- package/src/__tests__/tool-preview-lifecycle.test.ts +2 -0
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +2 -0
- package/src/__tests__/tool-start-timestamp.test.ts +2 -0
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +37 -51
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +12 -12
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +9 -7
- package/src/__tests__/trusted-contact-multichannel.test.ts +16 -7
- package/src/__tests__/trusted-contact-verification.test.ts +79 -54
- package/src/__tests__/twilio-routes.test.ts +96 -0
- package/src/__tests__/ui-file-upload-surface.test.ts +86 -0
- package/src/__tests__/verification-control-plane-policy.test.ts +2 -0
- package/src/__tests__/voice-guardian-cold-cache-warm.test.ts +137 -0
- package/src/__tests__/voice-invite-redemption.test.ts +216 -20
- package/src/__tests__/web-search-backend-failure.test.ts +2 -0
- package/src/__tests__/workspace-migration-102-preserve-heartbeat-enabled-for-existing-workspaces.test.ts +3 -3
- package/src/__tests__/workspace-migration-111-prune-seeded-callsite-defaults.test.ts +208 -0
- package/src/__tests__/workspace-migration-112-remove-advisor-callsite-override.test.ts +170 -0
- package/src/__tests__/workspace-migration-drop-user-md.test.ts +196 -238
- package/src/__tests__/workspace-migration-remove-hooks.test.ts +14 -35
- package/src/__tests__/workspace-tool-loader.test.ts +195 -2
- package/src/a2a/__tests__/e2e-a2a-channel.test.ts +35 -47
- package/src/agent/loop-exclusive-tool.test.ts +154 -0
- package/src/agent/loop-native-web-search.test.ts +200 -0
- package/src/agent/loop.ts +164 -1
- package/src/api/constants/sse-replay.ts +41 -0
- package/src/api/events/conversation-notice.ts +26 -0
- package/src/api/index.ts +19 -1
- package/src/api/responses/llm-request-log-entry.ts +29 -0
- package/src/api/responses/subagent-detail.ts +17 -0
- package/src/api/surfaces.ts +39 -3
- package/src/approvals/guardian-channel-delivery.ts +30 -0
- package/src/approvals/guardian-expiry-notifier.ts +148 -0
- package/src/approvals/guardian-request-resolvers.ts +17 -15
- package/src/calls/__tests__/relay-setup-router.test.ts +267 -17
- package/src/calls/call-domain.ts +3 -3
- package/src/calls/guardian-dispatch.ts +14 -9
- package/src/calls/inbound-trust-reader.ts +24 -2
- package/src/calls/media-stream-server.ts +21 -0
- package/src/calls/relay-access-wait.ts +6 -6
- package/src/calls/relay-server.ts +188 -51
- package/src/calls/relay-setup-router.ts +47 -17
- package/src/calls/relay-verification.ts +4 -4
- package/src/calls/stt-hints.ts +9 -12
- package/src/calls/twilio-routes.ts +14 -4
- package/src/channels/types.ts +10 -20
- package/src/cli/commands/__tests__/cache.test.ts +8 -1
- package/src/cli/commands/__tests__/conversations-slack.test.ts +1 -0
- package/src/cli/commands/cache.ts +194 -181
- package/src/cli/commands/contacts.ts +10 -7
- package/src/cli/commands/db/__tests__/repair.test.ts +6 -5
- package/src/cli/commands/db/status.ts +37 -1
- package/src/cli/commands/mcp.ts +252 -218
- package/src/cli/commands/memory/__tests__/worker.test.ts +432 -0
- package/src/cli/commands/memory/index.ts +2 -0
- package/src/cli/commands/memory/worker.ts +242 -0
- package/src/cli/commands/plugins.ts +125 -13
- package/src/cli/lib/__tests__/install-from-github.test.ts +102 -0
- package/src/cli/lib/__tests__/list-installed-plugins.test.ts +160 -1
- package/src/cli/lib/__tests__/publish-plugin.test.ts +404 -0
- package/src/cli/lib/list-installed-plugins.ts +179 -1
- package/src/cli/lib/publish-plugin.ts +633 -0
- package/src/config/__tests__/loader-callsite-strip-fallback.test.ts +143 -0
- package/src/config/__tests__/sync-gated-profiles.test.ts +16 -10
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +27 -17
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +13 -3
- package/src/config/bundled-skills/subagent/SKILL.md +17 -2
- package/src/config/bundled-skills/subagent/TOOLS.json +5 -4
- package/src/config/call-site-defaults.ts +0 -6
- package/src/config/feature-flag-registry.json +5 -13
- package/src/config/llm-resolver.ts +0 -3
- package/src/config/loader.ts +38 -5
- package/src/config/prune-seeded-callsite-defaults.ts +110 -0
- package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
- package/src/config/schemas/call-site-catalog.ts +0 -7
- package/src/config/schemas/heartbeat.ts +2 -5
- package/src/config/schemas/llm.ts +3 -12
- package/src/config/schemas/memory-lifecycle.ts +12 -0
- package/src/config/schemas/memory-v3.ts +7 -0
- package/src/config/schemas/memory.ts +4 -0
- package/src/config/schemas/timeouts.ts +8 -0
- package/src/config/seed-inference-profiles.ts +30 -34
- package/src/config/skills.ts +27 -5
- package/src/config/sync-gated-profiles.ts +12 -16
- package/src/contacts/__tests__/contacts-write-revoke-relay.test.ts +128 -0
- package/src/contacts/__tests__/guardian-delivery-reader.test.ts +312 -0
- package/src/contacts/__tests__/member-write-relay.test.ts +226 -0
- package/src/contacts/contact-store.ts +27 -237
- package/src/contacts/contacts-write.ts +18 -55
- package/src/contacts/gateway-channel-read.ts +51 -0
- package/src/contacts/guardian-delivery-reader.ts +223 -0
- package/src/contacts/member-write-relay.ts +183 -0
- package/src/contacts/types.ts +3 -15
- package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +35 -0
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +0 -44
- package/src/daemon/assistant-attachments.ts +27 -4
- package/src/daemon/conversation-agent-loop.ts +28 -0
- package/src/daemon/conversation-notices.ts +60 -0
- package/src/daemon/conversation-process.ts +35 -16
- package/src/daemon/conversation-surfaces.ts +111 -38
- package/src/daemon/conversation-tool-setup.ts +71 -30
- package/src/daemon/conversation.ts +23 -1
- package/src/daemon/disk-pressure-guard.ts +12 -2
- package/src/daemon/event-loop-watchdog.ts +28 -1
- package/src/daemon/external-plugins-bootstrap.ts +17 -41
- package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +0 -1
- package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -2
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +25 -2
- package/src/daemon/handlers/__tests__/config-channels.test.ts +220 -0
- package/src/daemon/handlers/config-a2a.ts +6 -14
- package/src/daemon/handlers/config-channels.ts +67 -26
- package/src/daemon/handlers/conversations.ts +77 -0
- package/src/daemon/lifecycle.ts +16 -0
- package/src/daemon/mcp-reload-service.ts +10 -0
- package/src/daemon/memory-v2-startup.test.ts +72 -0
- package/src/daemon/memory-v2-startup.ts +87 -19
- package/src/daemon/message-types/conversations.ts +2 -0
- package/src/daemon/message-types/surfaces.ts +12 -12
- package/src/daemon/server.ts +0 -4
- package/src/daemon/shutdown-handlers.ts +20 -0
- package/src/daemon/tool-setup-types.ts +9 -0
- package/src/heartbeat/heartbeat-service.ts +5 -0
- package/src/home/relationship-state-writer.ts +5 -0
- package/src/hooks/hook-loader.ts +341 -0
- package/src/ipc/__tests__/clients-list-ipc.test.ts +1 -1
- package/src/ipc/assistant-server.ts +2 -2
- package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +1 -0
- package/src/mcp/client.ts +15 -1
- package/src/mcp/mcp-auth-orchestrator.ts +6 -1
- package/src/mcp/mcp-header-store.ts +134 -0
- package/src/mcp/mcp-oauth-provider.ts +19 -8
- package/src/memory/__tests__/301-create-watchdog-events.test.ts +110 -0
- package/src/memory/__tests__/memory-retrospective-job.test.ts +8 -0
- package/src/memory/__tests__/prompt-override.test.ts +192 -0
- package/src/memory/__tests__/watchdog-events-store.test.ts +161 -0
- package/src/memory/compaction-ledger-store.ts +107 -0
- package/src/memory/conversation-crud.ts +129 -61
- package/src/memory/db-connection.ts +22 -3
- package/src/memory/db-init.ts +37 -503
- package/src/memory/db-singleton.ts +6 -4
- package/src/memory/jobs-worker.ts +104 -0
- package/src/memory/llm-request-log-store.ts +26 -1
- package/src/memory/llm-usage-store.ts +48 -20
- package/src/memory/memory-retrospective-job.ts +14 -8
- package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +152 -56
- package/src/memory/migrations/300-add-processing-started-at.ts +30 -0
- package/src/memory/migrations/301-create-watchdog-events.ts +45 -0
- package/src/memory/migrations/302-create-compaction-events.ts +107 -0
- package/src/memory/migrations/__tests__/209-strip-thinking-from-consolidated.test.ts +297 -0
- package/src/memory/migrations/__tests__/run-migrations.test.ts +2 -2
- package/src/memory/migrations/run-migrations.ts +106 -10
- package/src/memory/migrations/validate-migration-state.ts +105 -67
- package/src/memory/prompt-override.ts +129 -0
- package/src/memory/schema/contacts.ts +6 -2
- package/src/memory/schema/conversations.ts +37 -0
- package/src/memory/schema/infrastructure.ts +20 -0
- package/src/memory/steps.ts +1294 -0
- package/src/memory/v2/__tests__/cli-command-store.test.ts +25 -0
- package/src/memory/v2/__tests__/skill-store.test.ts +80 -0
- package/src/memory/v2/cli-command-store.ts +75 -38
- package/src/memory/v2/prompts/consolidation.ts +13 -82
- package/src/memory/v2/prompts/router.ts +21 -93
- package/src/memory/v2/skill-store.ts +68 -31
- package/src/memory/watchdog-events-store.ts +87 -0
- package/src/memory/worker-control.ts +204 -0
- package/src/memory/worker-process.ts +89 -0
- package/src/messaging/channel-binding-metadata.ts +31 -0
- package/src/messaging/provider-types.ts +8 -0
- package/src/messaging/providers/slack/binding-metadata.ts +60 -0
- package/src/notifications/__tests__/broadcaster.test.ts +8 -8
- package/src/notifications/__tests__/connected-channels.test.ts +86 -0
- package/src/notifications/__tests__/decision-engine.test.ts +78 -9
- package/src/notifications/__tests__/destination-resolver.test.ts +151 -0
- package/src/notifications/broadcaster.ts +8 -1
- package/src/notifications/decision-engine.ts +15 -7
- package/src/notifications/destination-resolver.ts +53 -25
- package/src/notifications/emit-signal.ts +34 -15
- package/src/onboarding/checkin-event.test.ts +222 -0
- package/src/onboarding/checkin-event.ts +321 -0
- package/src/onboarding/schedule-checkin.ts +190 -0
- package/src/permissions/question-prompter.test.ts +1 -1
- package/src/permissions/question-prompter.ts +7 -4
- package/src/plugin-api/index.ts +12 -12
- package/src/plugin-api/types.ts +12 -14
- package/src/plugin-api/vision-support.test.ts +28 -4
- package/src/plugin-api/vision-support.ts +66 -31
- package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +2 -2
- package/src/plugins/defaults/empty-response/hooks/stop.ts +2 -2
- package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +4 -3
- package/src/plugins/defaults/history-repair/hooks/post-model-call.ts +2 -2
- package/src/plugins/defaults/history-repair/hooks/stop.ts +2 -2
- package/src/plugins/defaults/history-repair/hooks/user-prompt-submit.ts +2 -2
- package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +47 -7
- package/src/plugins/defaults/image-fallback/hooks/post-tool-use.ts +12 -13
- package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +14 -22
- package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +42 -11
- package/src/plugins/defaults/image-recovery/hooks/post-model-call.ts +2 -2
- package/src/plugins/defaults/image-recovery/hooks/stop.ts +2 -2
- package/src/plugins/defaults/index.ts +0 -35
- package/src/plugins/defaults/max-tokens-continue/hooks/post-model-call.ts +2 -2
- package/src/plugins/defaults/max-tokens-continue/hooks/stop.ts +2 -2
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +2 -2
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
- package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +33 -3
- package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +48 -4
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +4 -8
- package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +2 -2
- package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +2 -2
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +43 -15
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +11 -2
- package/src/plugins/defaults/memory-v3-shadow/pool-select.test.ts +146 -0
- package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +77 -13
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +12 -11
- package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +2 -2
- package/src/plugins/defaults/surface-completion-nudge/hooks/stop.ts +2 -2
- package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +2 -2
- package/src/plugins/defaults/title-generate/hooks/stop.ts +2 -2
- package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +2 -2
- package/src/plugins/defaults/tool-error/hooks/post-tool-use.ts +2 -2
- package/src/plugins/defaults/tool-result-truncate/hooks/post-tool-use.ts +2 -2
- package/src/plugins/disabled-state.ts +31 -0
- package/src/plugins/external-plugin-loader.ts +2 -2
- package/src/plugins/mtime-cache.ts +186 -330
- package/src/plugins/pipeline.ts +111 -13
- package/src/plugins/registry.ts +59 -16
- package/src/plugins/surface-import.ts +121 -0
- package/src/plugins/types.ts +7 -9
- package/src/prompts/persona-resolver.ts +43 -11
- package/src/providers/anthropic/client.ts +5 -0
- package/src/providers/call-site-routing.ts +45 -0
- package/src/providers/model-catalog.ts +16 -0
- package/src/providers/openai/__tests__/api-error-normalization.test.ts +321 -0
- package/src/providers/openai/api-error-normalization.ts +270 -0
- package/src/providers/openai/chat-completions-provider.ts +37 -83
- package/src/providers/openai/responses-provider.ts +50 -46
- package/src/providers/openrouter/client.ts +5 -0
- package/src/providers/provider-send-message.ts +10 -0
- package/src/providers/ratelimit.ts +10 -0
- package/src/providers/registry.ts +1 -1
- package/src/providers/retry.ts +10 -0
- package/src/providers/types.ts +22 -0
- package/src/providers/usage-tracking.ts +10 -0
- package/src/runtime/__tests__/channel-verification-service.test.ts +133 -0
- package/src/runtime/__tests__/guardian-vellum-migration.test.ts +184 -0
- package/src/runtime/__tests__/is-guardian-bound-for-channel.test.ts +66 -0
- package/src/runtime/__tests__/local-principal-trust.test.ts +162 -0
- package/src/runtime/__tests__/member-verdict-cache.test.ts +119 -0
- package/src/runtime/__tests__/trust-verdict-consumer.test.ts +402 -123
- package/src/runtime/access-request-helper.ts +18 -39
- package/src/runtime/actor-trust-resolver.ts +46 -19
- package/src/runtime/anchored-guardian.test.ts +109 -0
- package/src/runtime/anchored-guardian.ts +86 -0
- package/src/runtime/assistant-event-hub.ts +1 -1
- package/src/runtime/assistant-stream-state.ts +9 -2
- package/src/runtime/auth/__tests__/require-bound-guardian.test.ts +99 -0
- package/src/runtime/auth/require-bound-guardian.ts +21 -11
- package/src/runtime/channel-verification-service.ts +56 -31
- package/src/runtime/confirmation-request-guardian-bridge.ts +3 -3
- package/src/runtime/guardian-vellum-migration.ts +69 -8
- package/src/runtime/invite-redemption-service.ts +213 -187
- package/src/runtime/local-actor-identity.test.ts +108 -0
- package/src/runtime/local-actor-identity.ts +85 -13
- package/src/runtime/local-principal-trust.ts +52 -0
- package/src/runtime/member-verdict-cache.ts +0 -0
- package/src/runtime/pending-interactions.ts +11 -1
- package/src/runtime/routes/__tests__/channel-verification-revoke.test.ts +56 -5
- package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/contact-routes.test.ts +305 -0
- package/src/runtime/routes/__tests__/global-search-routes.test.ts +92 -0
- package/src/runtime/routes/__tests__/http-adapter-actor-header.test.ts +129 -0
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +188 -0
- package/src/runtime/routes/browser-routes.ts +1 -1
- package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +13 -5
- package/src/runtime/routes/channel-verification-routes.ts +3 -3
- package/src/runtime/routes/contact-routes.ts +48 -57
- package/src/runtime/routes/conversation-cli-routes.ts +4 -5
- package/src/runtime/routes/conversation-list-routes.ts +4 -7
- package/src/runtime/routes/conversation-query-routes.ts +72 -0
- package/src/runtime/routes/conversation-routes.ts +84 -85
- package/src/runtime/routes/events-routes.ts +2 -2
- package/src/runtime/routes/global-search-routes.ts +3 -1
- package/src/runtime/routes/guardian-action-routes.ts +4 -5
- package/src/runtime/routes/host-app-control-routes.ts +5 -4
- package/src/runtime/routes/host-bash-routes.ts +5 -4
- package/src/runtime/routes/host-browser-routes.ts +9 -11
- package/src/runtime/routes/host-cu-routes.ts +5 -4
- package/src/runtime/routes/host-file-routes.ts +5 -4
- package/src/runtime/routes/host-transfer-routes.ts +6 -6
- package/src/runtime/routes/http-adapter.ts +16 -1
- package/src/runtime/routes/identity-routes.ts +3 -2
- package/src/runtime/routes/inbound-message-handler.ts +5 -5
- package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +97 -5
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +61 -59
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +13 -5
- package/src/runtime/routes/inbound-stages/escalation-intercept.ts +7 -7
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +21 -8
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +14 -3
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +19 -0
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/llm-context-normalization.ts +83 -0
- package/src/runtime/routes/mcp-auth-routes.ts +171 -19
- package/src/runtime/routes/migration-rollback-routes.ts +4 -3
- package/src/runtime/routes/migration-routes.ts +4 -1
- package/src/runtime/routes/onboarding-checkin-routes.ts +86 -0
- package/src/runtime/routes/settings-routes.ts +8 -3
- package/src/runtime/routes/subagents-routes.ts +5 -0
- package/src/runtime/routes/surface-action-routes.ts +42 -56
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +1 -0
- package/src/runtime/services/conversation-serializer.ts +13 -58
- package/src/runtime/tool-grant-request-helper.ts +3 -3
- package/src/runtime/trust-verdict-consumer.ts +111 -40
- package/src/runtime/verification-outbound-actions.ts +18 -18
- package/src/signals/user-message.ts +16 -0
- package/src/subagent/__tests__/consult-prompt.test.ts +35 -0
- package/src/{plugins/defaults/advisor/__tests__/transcript.test.ts → subagent/__tests__/consult-transcript.test.ts} +47 -10
- package/src/{plugins/defaults/advisor/steering.ts → subagent/consult-prompt.ts} +22 -32
- package/src/{plugins/defaults/advisor/transcript.ts → subagent/consult-transcript.ts} +18 -8
- package/src/subagent/index.ts +1 -1
- package/src/subagent/manager.ts +254 -33
- package/src/subagent/types.ts +11 -4
- package/src/telemetry/types.ts +34 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +3 -2
- package/src/telemetry/usage-telemetry-reporter.ts +87 -3
- package/src/tools/ask-question/ask-question-tool.test.ts +29 -0
- package/src/tools/ask-question/ask-question-tool.ts +13 -0
- package/src/tools/executor.ts +4 -4
- package/src/tools/filesystem/search.ts +543 -0
- package/src/tools/registry.ts +38 -0
- package/src/tools/shared/filesystem/path-policy.ts +12 -5
- package/src/tools/subagent/consult-deadline.ts +49 -0
- package/src/tools/subagent/spawn.ts +234 -5
- package/src/tools/tool-approval-handler.ts +1 -1
- package/src/tools/tool-defaults.ts +9 -2
- package/src/tools/tool-manifest.ts +3 -0
- package/src/tools/types.ts +131 -25
- package/src/tools/workspace-tools/loader.ts +348 -244
- package/src/util/errors.ts +26 -1
- package/src/util/logger.ts +9 -0
- package/src/util/platform.ts +19 -0
- package/src/util/telemetry-db-path.ts +24 -0
- package/src/workflows/library.test.ts +140 -0
- package/src/workflows/library.ts +82 -28
- package/src/workspace/migrations/017-seed-persona-dirs.ts +3 -34
- package/src/workspace/migrations/019-scope-journal-to-guardian.ts +3 -24
- package/src/workspace/migrations/031-drop-user-md.ts +232 -148
- package/src/workspace/migrations/048-remove-workspace-hooks.ts +14 -66
- package/src/workspace/migrations/111-prune-seeded-callsite-defaults.ts +134 -0
- package/src/workspace/migrations/112-remove-advisor-callsite-override.ts +64 -0
- package/src/workspace/migrations/registry.ts +4 -0
- package/src/__tests__/workspace-tools-watcher-flag.test.ts +0 -70
- package/src/daemon/workspace-tools-watcher.ts +0 -328
- package/src/memory/migrations/registry.ts +0 -573
- package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +0 -56
- package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +0 -43
- package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +0 -137
- package/src/plugins/defaults/advisor/__tests__/consult.test.ts +0 -153
- package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +0 -138
- package/src/plugins/defaults/advisor/advisor-gate.ts +0 -29
- package/src/plugins/defaults/advisor/advisor-state-store.ts +0 -94
- package/src/plugins/defaults/advisor/config.ts +0 -21
- package/src/plugins/defaults/advisor/consult.ts +0 -93
- package/src/plugins/defaults/advisor/hooks/post-model-call.ts +0 -34
- package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +0 -30
- package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +0 -19
- package/src/plugins/defaults/advisor/package.json +0 -14
- package/src/plugins/defaults/advisor/tools/advisor.ts +0 -65
- package/src/providers/openai/__tests__/api-error-detail.test.ts +0 -120
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* and skipped (no silent provider-safe rewrite — operator must rename).
|
|
27
27
|
* - Multiple workspace tools register in a single batch.
|
|
28
28
|
*/
|
|
29
|
-
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
29
|
+
import { mkdirSync, rmSync, utimesSync, writeFileSync } from "node:fs";
|
|
30
30
|
import { tmpdir } from "node:os";
|
|
31
31
|
import { join } from "node:path";
|
|
32
32
|
import { afterAll, beforeEach, describe, expect, test } from "bun:test";
|
|
@@ -42,7 +42,10 @@ import {
|
|
|
42
42
|
registerTool,
|
|
43
43
|
} from "../tools/registry.js";
|
|
44
44
|
import type { Tool, ToolContext, ToolExecutionResult } from "../tools/types.js";
|
|
45
|
-
import {
|
|
45
|
+
import {
|
|
46
|
+
__resetWorkspaceToolCacheForTesting,
|
|
47
|
+
loadWorkspaceTools,
|
|
48
|
+
} from "../tools/workspace-tools/loader.js";
|
|
46
49
|
|
|
47
50
|
// Per-test counter so each writeTool() call lands in a unique tempdir,
|
|
48
51
|
// defeating bun's per-URL ESM cache between tests. Without this, a
|
|
@@ -87,6 +90,23 @@ function writeRemovedSentinel(name: string): void {
|
|
|
87
90
|
writeFileSync(join(toolsDir, `${name}.removed`), "");
|
|
88
91
|
}
|
|
89
92
|
|
|
93
|
+
/** Delete `<workspaceDir>/tools/<name><ext>` (defaults to `.ts`). */
|
|
94
|
+
function removeToolFile(name: string, ext = ".ts"): void {
|
|
95
|
+
rmSync(join(currentWorkspaceDir, "tools", `${name}${ext}`), { force: true });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Overwrite an existing tool file and bump its mtime into the future so the
|
|
100
|
+
* reconcile's mtime gate re-imports it even when the rewrite lands within
|
|
101
|
+
* the same millisecond as the original write.
|
|
102
|
+
*/
|
|
103
|
+
function rewriteTool(name: string, body: string, ext = ".ts"): void {
|
|
104
|
+
const path = join(currentWorkspaceDir, "tools", `${name}${ext}`);
|
|
105
|
+
writeFileSync(path, body);
|
|
106
|
+
const future = new Date(Date.now() + 5000);
|
|
107
|
+
utimesSync(path, future, future);
|
|
108
|
+
}
|
|
109
|
+
|
|
90
110
|
function makeFakeCoreTool(name: string): Tool {
|
|
91
111
|
return {
|
|
92
112
|
name,
|
|
@@ -94,6 +114,9 @@ function makeFakeCoreTool(name: string): Tool {
|
|
|
94
114
|
category: "test",
|
|
95
115
|
defaultRiskLevel: RiskLevel.Low,
|
|
96
116
|
executionTarget: "sandbox",
|
|
117
|
+
// Match the finalized shape the registry stores (defaults filled), so
|
|
118
|
+
// `getCoreToolOverride(name)` toEqual comparisons hold after registration.
|
|
119
|
+
exclusive: false,
|
|
97
120
|
input_schema: { type: "object", properties: {}, required: [] },
|
|
98
121
|
async execute(
|
|
99
122
|
_input: Record<string, unknown>,
|
|
@@ -133,6 +156,7 @@ export default {
|
|
|
133
156
|
describe("workspace tool loader", () => {
|
|
134
157
|
beforeEach(() => {
|
|
135
158
|
__clearRegistryForTesting();
|
|
159
|
+
__resetWorkspaceToolCacheForTesting();
|
|
136
160
|
freshWorkspace();
|
|
137
161
|
});
|
|
138
162
|
|
|
@@ -316,4 +340,173 @@ export default 42;
|
|
|
316
340
|
const names = getWorkspaceToolNames().sort();
|
|
317
341
|
expect(names).toEqual(["alpha", "beta", "gamma"]);
|
|
318
342
|
});
|
|
343
|
+
|
|
344
|
+
// ── Reconcile-on-read behavior ─────────────────────────────────────────
|
|
345
|
+
//
|
|
346
|
+
// loadWorkspaceTools() is idempotent and re-derives registry state from
|
|
347
|
+
// disk on every call. These cases cover the deltas a repeat call applies,
|
|
348
|
+
// which is what replaces the old filesystem watcher.
|
|
349
|
+
|
|
350
|
+
test("repeat call with no disk changes is a no-op (does not throw or duplicate)", async () => {
|
|
351
|
+
writeTool("stable_tool", WELL_FORMED_BODY);
|
|
352
|
+
|
|
353
|
+
await loadWorkspaceTools();
|
|
354
|
+
// A second reconcile must not throw on the already-registered name —
|
|
355
|
+
// the mtime cache recognizes the unchanged file and skips re-import.
|
|
356
|
+
await loadWorkspaceTools();
|
|
357
|
+
|
|
358
|
+
expect(getTool("stable_tool")).toBeDefined();
|
|
359
|
+
expect(getWorkspaceToolNames()).toEqual(["stable_tool"]);
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
test("a file added after the first reconcile registers on the next", async () => {
|
|
363
|
+
writeTool("first", WELL_FORMED_BODY);
|
|
364
|
+
await loadWorkspaceTools();
|
|
365
|
+
expect(getWorkspaceToolNames()).toEqual(["first"]);
|
|
366
|
+
|
|
367
|
+
writeTool("second", WELL_FORMED_BODY);
|
|
368
|
+
await loadWorkspaceTools();
|
|
369
|
+
|
|
370
|
+
expect(getWorkspaceToolNames().sort()).toEqual(["first", "second"]);
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
test("a changed file is re-imported on the next reconcile", async () => {
|
|
374
|
+
writeTool("mutable", WELL_FORMED_BODY);
|
|
375
|
+
await loadWorkspaceTools();
|
|
376
|
+
expect(getTool("mutable")?.description).toBe("from workspace");
|
|
377
|
+
|
|
378
|
+
rewriteTool(
|
|
379
|
+
"mutable",
|
|
380
|
+
`
|
|
381
|
+
export default {
|
|
382
|
+
description: "edited in place",
|
|
383
|
+
defaultRiskLevel: "low",
|
|
384
|
+
input_schema: { type: "object", properties: {}, required: [] },
|
|
385
|
+
async execute() {
|
|
386
|
+
return { content: "edited", isError: false };
|
|
387
|
+
},
|
|
388
|
+
};
|
|
389
|
+
`,
|
|
390
|
+
);
|
|
391
|
+
await loadWorkspaceTools();
|
|
392
|
+
|
|
393
|
+
expect(getTool("mutable")?.description).toBe("edited in place");
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
test("a deleted net-new tool file is unregistered on the next reconcile", async () => {
|
|
397
|
+
writeTool("ephemeral", WELL_FORMED_BODY);
|
|
398
|
+
await loadWorkspaceTools();
|
|
399
|
+
expect(getTool("ephemeral")).toBeDefined();
|
|
400
|
+
|
|
401
|
+
removeToolFile("ephemeral");
|
|
402
|
+
await loadWorkspaceTools();
|
|
403
|
+
|
|
404
|
+
expect(getTool("ephemeral")).toBeUndefined();
|
|
405
|
+
expect(getWorkspaceToolNames()).toEqual([]);
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
test("deleting an override file restores the stashed core tool", async () => {
|
|
409
|
+
const core = makeFakeCoreTool("restore_me");
|
|
410
|
+
registerTool(core);
|
|
411
|
+
writeTool("restore_me", WELL_FORMED_BODY);
|
|
412
|
+
|
|
413
|
+
await loadWorkspaceTools();
|
|
414
|
+
expect(getToolOwner("restore_me")?.kind).toBe("workspace");
|
|
415
|
+
|
|
416
|
+
removeToolFile("restore_me");
|
|
417
|
+
await loadWorkspaceTools();
|
|
418
|
+
|
|
419
|
+
expect(getToolOwner("restore_me")).toBeUndefined();
|
|
420
|
+
expect(getTool("restore_me")).toEqual(core);
|
|
421
|
+
expect(getCoreToolOverride("restore_me")).toBeUndefined();
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
test("deleting a .removed sentinel restores the stripped core tool", async () => {
|
|
425
|
+
const core = makeFakeCoreTool("strip_then_restore");
|
|
426
|
+
registerTool(core);
|
|
427
|
+
writeRemovedSentinel("strip_then_restore");
|
|
428
|
+
|
|
429
|
+
await loadWorkspaceTools();
|
|
430
|
+
expect(getTool("strip_then_restore")).toBeUndefined();
|
|
431
|
+
expect(getStrippedCoreToolNames()).toContain("strip_then_restore");
|
|
432
|
+
|
|
433
|
+
removeToolFile("strip_then_restore", ".removed");
|
|
434
|
+
await loadWorkspaceTools();
|
|
435
|
+
|
|
436
|
+
expect(getTool("strip_then_restore")).toEqual(core);
|
|
437
|
+
expect(getStrippedCoreToolNames()).not.toContain("strip_then_restore");
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
test("the registered name is the filename stem, ignoring the file's own name field", async () => {
|
|
441
|
+
// The default export sets a different `name` — the loader must pin the
|
|
442
|
+
// registered name to the stem ("stem_wins") so the mtime cache and the
|
|
443
|
+
// unregister-on-delete path stay keyed by the same name.
|
|
444
|
+
writeTool(
|
|
445
|
+
"stem_wins",
|
|
446
|
+
`
|
|
447
|
+
export default {
|
|
448
|
+
name: "different_name",
|
|
449
|
+
description: "name field should be ignored",
|
|
450
|
+
defaultRiskLevel: "low",
|
|
451
|
+
input_schema: { type: "object", properties: {}, required: [] },
|
|
452
|
+
async execute() {
|
|
453
|
+
return { content: "ok", isError: false };
|
|
454
|
+
},
|
|
455
|
+
};
|
|
456
|
+
`,
|
|
457
|
+
);
|
|
458
|
+
|
|
459
|
+
await loadWorkspaceTools();
|
|
460
|
+
|
|
461
|
+
expect(getTool("stem_wins")).toBeDefined();
|
|
462
|
+
expect(getTool("different_name")).toBeUndefined();
|
|
463
|
+
expect(getWorkspaceToolNames()).toEqual(["stem_wins"]);
|
|
464
|
+
|
|
465
|
+
// Deleting the file unregisters by stem — no leaked "different_name".
|
|
466
|
+
removeToolFile("stem_wins");
|
|
467
|
+
await loadWorkspaceTools();
|
|
468
|
+
expect(getTool("stem_wins")).toBeUndefined();
|
|
469
|
+
expect(getTool("different_name")).toBeUndefined();
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
test("per-tool isolation on reconcile: a bad file does not drop a valid edited tool", async () => {
|
|
473
|
+
writeTool("good_edit", WELL_FORMED_BODY);
|
|
474
|
+
await loadWorkspaceTools();
|
|
475
|
+
expect(getTool("good_edit")?.description).toBe("from workspace");
|
|
476
|
+
|
|
477
|
+
// Add a file that throws at import, and edit the good tool, in the same
|
|
478
|
+
// reconcile. The broken file must not prevent the edited tool from
|
|
479
|
+
// re-registering.
|
|
480
|
+
writeTool("broken_now", `throw new Error("boom at import");`);
|
|
481
|
+
rewriteTool(
|
|
482
|
+
"good_edit",
|
|
483
|
+
`
|
|
484
|
+
export default {
|
|
485
|
+
description: "edited and still here",
|
|
486
|
+
defaultRiskLevel: "low",
|
|
487
|
+
input_schema: { type: "object", properties: {}, required: [] },
|
|
488
|
+
async execute() {
|
|
489
|
+
return { content: "ok", isError: false };
|
|
490
|
+
},
|
|
491
|
+
};
|
|
492
|
+
`,
|
|
493
|
+
);
|
|
494
|
+
await loadWorkspaceTools();
|
|
495
|
+
|
|
496
|
+
expect(getTool("broken_now")).toBeUndefined();
|
|
497
|
+
expect(getTool("good_edit")?.description).toBe("edited and still here");
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
test("an edit that breaks an existing tool keeps the prior registration", async () => {
|
|
501
|
+
writeTool("was_good", WELL_FORMED_BODY);
|
|
502
|
+
await loadWorkspaceTools();
|
|
503
|
+
expect(getTool("was_good")?.description).toBe("from workspace");
|
|
504
|
+
|
|
505
|
+
// Rewrite the file into something that throws at import. The prior,
|
|
506
|
+
// working registration must stay in place rather than being torn down.
|
|
507
|
+
rewriteTool("was_good", `throw new Error("now broken");`);
|
|
508
|
+
await loadWorkspaceTools();
|
|
509
|
+
|
|
510
|
+
expect(getTool("was_good")?.description).toBe("from workspace");
|
|
511
|
+
});
|
|
319
512
|
});
|
|
@@ -42,6 +42,7 @@ mock.module("../../util/logger.js", () => ({
|
|
|
42
42
|
// the a2a.enabled flag. We use the real config system backed by initializeDb's
|
|
43
43
|
// workspace directory.
|
|
44
44
|
|
|
45
|
+
import { seedContactChannel } from "../../__tests__/helpers/seed-contact-channel.js";
|
|
45
46
|
import {
|
|
46
47
|
invalidateConfigCache,
|
|
47
48
|
loadRawConfig,
|
|
@@ -80,6 +81,15 @@ const originalFetch = globalThis.fetch;
|
|
|
80
81
|
// Helpers
|
|
81
82
|
// ---------------------------------------------------------------------------
|
|
82
83
|
|
|
84
|
+
/** Read a channel's local ACL columns directly to assert the gateway dual-write. */
|
|
85
|
+
function aclColumns(
|
|
86
|
+
channelId: string,
|
|
87
|
+
): { status: string; policy: string } | null {
|
|
88
|
+
return getSqlite()
|
|
89
|
+
.query("SELECT status, policy FROM contact_channels WHERE id = ?")
|
|
90
|
+
.get(channelId) as { status: string; policy: string } | null;
|
|
91
|
+
}
|
|
92
|
+
|
|
83
93
|
function resetTables(): void {
|
|
84
94
|
const sqlite = getSqlite();
|
|
85
95
|
sqlite.run("DELETE FROM a2a_tasks");
|
|
@@ -164,17 +174,16 @@ describe("e2e: trusted contact setup", () => {
|
|
|
164
174
|
{
|
|
165
175
|
type: "a2a",
|
|
166
176
|
address: "assistant-b",
|
|
167
|
-
status: "active",
|
|
168
|
-
policy: "allow",
|
|
169
177
|
},
|
|
170
178
|
],
|
|
171
179
|
});
|
|
172
180
|
|
|
181
|
+
// upsertContact persists the a2a channel identity; the gateway owns the ACL
|
|
182
|
+
// status verdict.
|
|
173
183
|
const contact = findContactByAddress("a2a", "assistant-b");
|
|
174
184
|
expect(contact).not.toBeNull();
|
|
175
185
|
expect(contact!.channels.some((ch) => ch.type === "a2a")).toBe(true);
|
|
176
186
|
const a2aChannel = contact!.channels.find((ch) => ch.type === "a2a");
|
|
177
|
-
expect(a2aChannel!.status).toBe("active");
|
|
178
187
|
expect(a2aChannel!.address).toBe("assistant-b");
|
|
179
188
|
});
|
|
180
189
|
});
|
|
@@ -355,21 +364,13 @@ describe("e2e: unknown sender blocked (ACL enforcement)", () => {
|
|
|
355
364
|
});
|
|
356
365
|
|
|
357
366
|
test("trusted contact exists with active a2a channel — ACL passes", async () => {
|
|
358
|
-
const { upsertContact } = await import("../../contacts/contact-store.js");
|
|
359
|
-
|
|
360
367
|
// Pre-create a trusted contact for the sender
|
|
361
|
-
|
|
368
|
+
seedContactChannel({
|
|
369
|
+
sourceChannel: "a2a",
|
|
370
|
+
externalUserId: "trusted-assistant",
|
|
362
371
|
displayName: "Trusted Bot",
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
channels: [
|
|
366
|
-
{
|
|
367
|
-
type: "a2a",
|
|
368
|
-
address: "trusted-assistant",
|
|
369
|
-
status: "active",
|
|
370
|
-
policy: "allow",
|
|
371
|
-
},
|
|
372
|
-
],
|
|
372
|
+
status: "active",
|
|
373
|
+
policy: "allow",
|
|
373
374
|
});
|
|
374
375
|
|
|
375
376
|
// Verify the contact exists (the ACL check the runtime performs)
|
|
@@ -378,8 +379,9 @@ describe("e2e: unknown sender blocked (ACL enforcement)", () => {
|
|
|
378
379
|
|
|
379
380
|
const a2aChannel = contact!.channels.find((ch) => ch.type === "a2a");
|
|
380
381
|
expect(a2aChannel).toBeTruthy();
|
|
381
|
-
|
|
382
|
-
expect(
|
|
382
|
+
const acl = aclColumns(a2aChannel!.id);
|
|
383
|
+
expect(acl!.status).toBe("active");
|
|
384
|
+
expect(acl!.policy).toBe("allow");
|
|
383
385
|
|
|
384
386
|
// A task from this sender would pass the ACL check
|
|
385
387
|
const msg = makeRequestMessage();
|
|
@@ -391,28 +393,21 @@ describe("e2e: unknown sender blocked (ACL enforcement)", () => {
|
|
|
391
393
|
});
|
|
392
394
|
|
|
393
395
|
test("contact exists but channel is blocked — ACL would reject", async () => {
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
396
|
+
seedContactChannel({
|
|
397
|
+
sourceChannel: "a2a",
|
|
398
|
+
externalUserId: "blocked-assistant",
|
|
397
399
|
displayName: "Blocked Bot",
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
channels: [
|
|
401
|
-
{
|
|
402
|
-
type: "a2a",
|
|
403
|
-
address: "blocked-assistant",
|
|
404
|
-
status: "blocked",
|
|
405
|
-
policy: "deny",
|
|
406
|
-
},
|
|
407
|
-
],
|
|
400
|
+
status: "blocked",
|
|
401
|
+
policy: "deny",
|
|
408
402
|
});
|
|
409
403
|
|
|
410
404
|
const contact = findContactByAddress("a2a", "blocked-assistant");
|
|
411
405
|
expect(contact).not.toBeNull();
|
|
412
406
|
|
|
413
407
|
const a2aChannel = contact!.channels.find((ch) => ch.type === "a2a");
|
|
414
|
-
|
|
415
|
-
expect(
|
|
408
|
+
const acl = aclColumns(a2aChannel!.id);
|
|
409
|
+
expect(acl!.status).toBe("blocked");
|
|
410
|
+
expect(acl!.policy).toBe("deny");
|
|
416
411
|
});
|
|
417
412
|
});
|
|
418
413
|
|
|
@@ -507,26 +502,19 @@ describe("e2e: full A2A round-trip", () => {
|
|
|
507
502
|
setConfigEnabled(true);
|
|
508
503
|
|
|
509
504
|
// Step 1: Create trusted contact for Assistant B (platform-mediated)
|
|
510
|
-
|
|
505
|
+
seedContactChannel({
|
|
506
|
+
sourceChannel: "a2a",
|
|
507
|
+
externalUserId: "assistant-b",
|
|
511
508
|
displayName: "Assistant B",
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
channels: [
|
|
515
|
-
{
|
|
516
|
-
type: "a2a",
|
|
517
|
-
address: "assistant-b",
|
|
518
|
-
status: "active",
|
|
519
|
-
policy: "allow",
|
|
520
|
-
},
|
|
521
|
-
],
|
|
509
|
+
status: "active",
|
|
510
|
+
policy: "allow",
|
|
522
511
|
});
|
|
523
512
|
|
|
524
513
|
// Step 2: Verify trusted contact was created
|
|
525
514
|
const contact = findContactByAddress("a2a", "assistant-b");
|
|
526
515
|
expect(contact).not.toBeNull();
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
);
|
|
516
|
+
const a2aChannel = contact!.channels.find((ch) => ch.type === "a2a")!;
|
|
517
|
+
expect(aclColumns(a2aChannel.id)!.status).toBe("active");
|
|
530
518
|
|
|
531
519
|
// Step 3: Simulate inbound A2A message from B (as if B sent us a request)
|
|
532
520
|
const inboundMsg = makeRequestMessage({
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verifies the agent loop's exclusive-tool dispatch: when a tool the loop is
|
|
3
|
+
* told is exclusive appears in a multi-call turn, only that tool runs and the
|
|
4
|
+
* siblings are deferred un-run with a benign result — so the model incorporates
|
|
5
|
+
* the exclusive tool's output before acting on anything else. Drives the REAL
|
|
6
|
+
* loop, mocking only the provider boundary.
|
|
7
|
+
*/
|
|
8
|
+
import { describe, expect, test } from "bun:test";
|
|
9
|
+
|
|
10
|
+
import { createMockProvider } from "../__tests__/helpers/mock-provider.js";
|
|
11
|
+
import type { ContentBlock, ProviderResponse } from "../providers/types.js";
|
|
12
|
+
import { AgentLoop } from "./loop.js";
|
|
13
|
+
|
|
14
|
+
const endTurn = (text: string): ProviderResponse => ({
|
|
15
|
+
content: [{ type: "text", text }],
|
|
16
|
+
model: "mock-model",
|
|
17
|
+
usage: { inputTokens: 1, outputTokens: 1 },
|
|
18
|
+
stopReason: "end_turn",
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const toolUseTurn = (
|
|
22
|
+
blocks: Array<{ id: string; name: string }>,
|
|
23
|
+
): ProviderResponse => ({
|
|
24
|
+
content: [
|
|
25
|
+
{ type: "text", text: "working" },
|
|
26
|
+
...blocks.map((b) => ({
|
|
27
|
+
type: "tool_use" as const,
|
|
28
|
+
id: b.id,
|
|
29
|
+
name: b.name,
|
|
30
|
+
input: {},
|
|
31
|
+
})),
|
|
32
|
+
],
|
|
33
|
+
model: "mock-model",
|
|
34
|
+
usage: { inputTokens: 1, outputTokens: 1 },
|
|
35
|
+
stopReason: "tool_use",
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
function toolResults(history: { content: ContentBlock[] }[]) {
|
|
39
|
+
return history
|
|
40
|
+
.flatMap((m) => m.content)
|
|
41
|
+
.filter(
|
|
42
|
+
(b): b is Extract<ContentBlock, { type: "tool_result" }> =>
|
|
43
|
+
b.type === "tool_result",
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const baseRun = {
|
|
48
|
+
requestId: "req-excl",
|
|
49
|
+
onEvent: () => {},
|
|
50
|
+
callSite: "mainAgent" as const,
|
|
51
|
+
trust: { sourceChannel: "vellum" as const, trustClass: "unknown" as const },
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
describe("AgentLoop — exclusive tool deferral", () => {
|
|
55
|
+
test("runs the exclusive tool alone and defers sibling calls un-run", async () => {
|
|
56
|
+
const { provider } = createMockProvider([
|
|
57
|
+
toolUseTurn([
|
|
58
|
+
{ id: "call-exclusive", name: "exclusive_tool" },
|
|
59
|
+
{ id: "call-edit", name: "write_file" },
|
|
60
|
+
]),
|
|
61
|
+
endTurn("done"),
|
|
62
|
+
]);
|
|
63
|
+
|
|
64
|
+
const executed: string[] = [];
|
|
65
|
+
const loop = new AgentLoop({
|
|
66
|
+
provider,
|
|
67
|
+
systemPrompt: "sys",
|
|
68
|
+
conversationId: "excl-1",
|
|
69
|
+
tools: [
|
|
70
|
+
{
|
|
71
|
+
name: "exclusive_tool",
|
|
72
|
+
description: "",
|
|
73
|
+
input_schema: { type: "object" },
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: "write_file",
|
|
77
|
+
description: "",
|
|
78
|
+
input_schema: { type: "object" },
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
toolExecutor: async (name) => {
|
|
82
|
+
executed.push(name);
|
|
83
|
+
return { content: `ran ${name}`, isError: false };
|
|
84
|
+
},
|
|
85
|
+
isExclusiveTool: (name) => name === "exclusive_tool",
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const { history } = await loop.run({
|
|
89
|
+
...baseRun,
|
|
90
|
+
messages: [{ role: "user", content: [{ type: "text", text: "do it" }] }],
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Only the exclusive tool actually executed.
|
|
94
|
+
expect(executed).toEqual(["exclusive_tool"]);
|
|
95
|
+
|
|
96
|
+
const results = toolResults(history);
|
|
97
|
+
const exclusiveResult = results.find(
|
|
98
|
+
(b) => b.tool_use_id === "call-exclusive",
|
|
99
|
+
)!;
|
|
100
|
+
const editResult = results.find((b) => b.tool_use_id === "call-edit")!;
|
|
101
|
+
|
|
102
|
+
// The exclusive tool ran; the sibling came back un-run (not an error) so the
|
|
103
|
+
// model can re-issue it after reading the guidance.
|
|
104
|
+
expect(exclusiveResult.content).toBe("ran exclusive_tool");
|
|
105
|
+
expect(editResult.content).toContain("not run");
|
|
106
|
+
expect(editResult.content).toContain("exclusive_tool");
|
|
107
|
+
expect(editResult.is_error).toBe(false);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("runs sibling tools normally when no exclusive tool is present", async () => {
|
|
111
|
+
const { provider } = createMockProvider([
|
|
112
|
+
toolUseTurn([
|
|
113
|
+
{ id: "call-read", name: "read_file" },
|
|
114
|
+
{ id: "call-write", name: "write_file" },
|
|
115
|
+
]),
|
|
116
|
+
endTurn("done"),
|
|
117
|
+
]);
|
|
118
|
+
|
|
119
|
+
const executed: string[] = [];
|
|
120
|
+
const loop = new AgentLoop({
|
|
121
|
+
provider,
|
|
122
|
+
systemPrompt: "sys",
|
|
123
|
+
conversationId: "excl-2",
|
|
124
|
+
tools: [
|
|
125
|
+
{
|
|
126
|
+
name: "read_file",
|
|
127
|
+
description: "",
|
|
128
|
+
input_schema: { type: "object" },
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
name: "write_file",
|
|
132
|
+
description: "",
|
|
133
|
+
input_schema: { type: "object" },
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
toolExecutor: async (name) => {
|
|
137
|
+
executed.push(name);
|
|
138
|
+
return { content: `ran ${name}`, isError: false };
|
|
139
|
+
},
|
|
140
|
+
isExclusiveTool: (name) => name === "exclusive_tool",
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const { history } = await loop.run({
|
|
144
|
+
...baseRun,
|
|
145
|
+
messages: [{ role: "user", content: [{ type: "text", text: "do it" }] }],
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Both non-exclusive tools ran; nothing was deferred.
|
|
149
|
+
expect(executed.sort()).toEqual(["read_file", "write_file"]);
|
|
150
|
+
for (const result of toolResults(history)) {
|
|
151
|
+
expect(result.content).not.toContain("not run");
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
});
|