@vellumai/assistant 0.10.1 → 0.10.2-dev.202606241651.2d2b40d
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/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/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/openapi.yaml +74 -1
- package/package.json +1 -1
- package/scripts/test.sh +36 -15
- package/src/__tests__/actor-token-service.test.ts +36 -14
- 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__/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-guardian.test.ts +94 -58
- package/src/__tests__/channel-reply-delivery.test.ts +2 -0
- package/src/__tests__/compaction-events.test.ts +2 -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__/computer-use-tools.test.ts +13 -0
- package/src/__tests__/config-loader-backfill.test.ts +5 -1
- package/src/__tests__/config-schema.test.ts +1 -0
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +31 -29
- package/src/__tests__/contacts-relay-reads.test.ts +13 -15
- 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 +7 -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-confirmation-signals.test.ts +2 -0
- 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-action-delivery.test.ts +65 -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 +0 -1
- package/src/__tests__/db-migration-rollback.test.ts +205 -171
- package/src/__tests__/db-test-helpers.ts +5 -4
- 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-persistence.test.ts +2 -0
- package/src/__tests__/emit-signal-routing-intent.test.ts +10 -5
- package/src/__tests__/events-dev-bypass-actor.test.ts +7 -1
- package/src/__tests__/filing-service.test.ts +2 -0
- package/src/__tests__/guardian-binding-drift-heal.test.ts +75 -10
- package/src/__tests__/guardian-dispatch.test.ts +95 -1
- package/src/__tests__/guardian-outbound-http.test.ts +13 -0
- 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 +1 -7
- 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-proxy.test.ts +299 -0
- 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 +167 -8
- package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
- package/src/__tests__/invite-redemption-service.test.ts +43 -0
- package/src/__tests__/llm-context-normalization.test.ts +105 -0
- package/src/__tests__/llm-usage-store.test.ts +25 -0
- 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__/native-web-search.test.ts +2 -0
- package/src/__tests__/non-member-access-request.test.ts +189 -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__/outbound-slack-persistence.test.ts +2 -0
- package/src/__tests__/pending-interactions-resolved-event.test.ts +7 -4
- package/src/__tests__/persistence-secret-redaction.test.ts +2 -0
- package/src/__tests__/plugin-bootstrap.test.ts +3 -73
- package/src/__tests__/plugin-route-contribution.test.ts +4 -17
- package/src/__tests__/plugin-tool-contribution.test.ts +3 -18
- package/src/__tests__/plugin-types.test.ts +0 -2
- 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-usage-tracking.test.ts +39 -0
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +2 -0
- package/src/__tests__/registry.test.ts +3 -0
- package/src/__tests__/relay-server.test.ts +694 -25
- package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
- package/src/__tests__/secret-ingress-http.test.ts +14 -0
- package/src/__tests__/send-endpoint-busy.test.ts +30 -8
- 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 +102 -0
- package/src/__tests__/steer-on-enqueue-question.test.ts +181 -0
- package/src/__tests__/stt-hints.test.ts +44 -13
- package/src/__tests__/subagent-detail.test.ts +27 -0
- package/src/__tests__/subagent-disposal.test.ts +65 -0
- package/src/__tests__/subagent-notify-parent.test.ts +2 -0
- package/src/__tests__/subagent-spawn-tool-fork.test.ts +2 -0
- package/src/__tests__/subagent-tools.test.ts +2 -0
- package/src/__tests__/suggestion-routes.test.ts +2 -0
- 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-inline-approval-integration.test.ts +10 -10
- package/src/__tests__/twilio-routes.test.ts +96 -0
- package/src/__tests__/verification-control-plane-policy.test.ts +2 -0
- package/src/__tests__/web-search-backend-failure.test.ts +2 -0
- package/src/__tests__/workspace-tool-loader.test.ts +195 -2
- package/src/agent/loop-exclusive-tool.test.ts +150 -0
- package/src/agent/loop.ts +56 -0
- package/src/api/constants/sse-replay.ts +41 -0
- package/src/api/index.ts +6 -0
- package/src/api/responses/llm-request-log-entry.ts +25 -0
- package/src/api/responses/subagent-detail.ts +17 -0
- package/src/calls/__tests__/relay-setup-router.test.ts +262 -4
- package/src/calls/call-domain.ts +3 -3
- package/src/calls/guardian-dispatch.ts +10 -8
- package/src/calls/inbound-trust-reader.ts +17 -1
- package/src/calls/media-stream-server.ts +21 -0
- package/src/calls/relay-server.ts +167 -50
- package/src/calls/relay-setup-router.ts +37 -7
- 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/cli/commands/__tests__/cache.test.ts +8 -1
- package/src/cli/commands/cache.ts +194 -181
- 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 +302 -0
- package/src/cli/commands/memory/index.ts +2 -0
- package/src/cli/commands/memory/worker.ts +175 -0
- package/src/cli/commands/plugins.ts +75 -3
- 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/list-installed-plugins.ts +179 -1
- package/src/config/__tests__/loader-callsite-strip-fallback.test.ts +143 -0
- package/src/config/bundled-skills/computer-use/TOOLS.json +6 -1
- 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/feature-flag-registry.json +0 -8
- package/src/config/loader.ts +36 -5
- package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
- 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 +14 -5
- package/src/config/skills.ts +27 -5
- package/src/contacts/__tests__/guardian-delivery-reader.test.ts +312 -0
- package/src/contacts/contacts-write.ts +3 -0
- package/src/contacts/guardian-delivery-reader.ts +223 -0
- package/src/daemon/conversation-agent-loop.ts +9 -0
- package/src/daemon/conversation-process.ts +39 -17
- package/src/daemon/conversation-surfaces.ts +8 -0
- package/src/daemon/conversation-tool-setup.ts +49 -16
- package/src/daemon/conversation.ts +21 -2
- 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 +4 -34
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +25 -0
- package/src/daemon/handlers/__tests__/config-channels.test.ts +225 -0
- package/src/daemon/handlers/config-a2a.ts +6 -14
- package/src/daemon/handlers/config-channels.ts +78 -22
- package/src/daemon/handlers/conversations.ts +77 -0
- package/src/daemon/host-cu-proxy.ts +102 -11
- package/src/daemon/lifecycle.ts +4 -0
- package/src/daemon/memory-v2-startup.test.ts +72 -0
- package/src/daemon/memory-v2-startup.ts +87 -19
- 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/ipc/__tests__/clients-list-ipc.test.ts +1 -1
- package/src/ipc/assistant-server.ts +2 -2
- 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/conversation-crud.ts +38 -0
- package/src/memory/db-connection.ts +22 -3
- package/src/memory/db-init.ts +36 -502
- package/src/memory/db-singleton.ts +6 -4
- package/src/memory/jobs-worker.ts +58 -0
- package/src/memory/llm-usage-store.ts +48 -20
- package/src/memory/memory-retrospective-job.ts +9 -8
- package/src/memory/migrations/014-backfill-inbox-thread-state.ts +13 -3
- package/src/memory/migrations/136-drop-assistant-id-columns.ts +52 -27
- package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +130 -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/__tests__/014-backfill-inbox-thread-state.test.ts +108 -0
- package/src/memory/migrations/__tests__/136-drop-assistant-id-columns.test.ts +82 -0
- package/src/memory/migrations/__tests__/209-strip-thinking-from-consolidated.test.ts +224 -0
- package/src/memory/migrations/__tests__/run-migrations.test.ts +2 -2
- package/src/memory/migrations/run-migrations.ts +90 -6
- package/src/memory/migrations/schema-introspection.ts +14 -0
- package/src/memory/migrations/validate-migration-state.ts +101 -66
- package/src/memory/prompt-override.ts +129 -0
- package/src/memory/schema/conversations.ts +9 -0
- package/src/memory/schema/infrastructure.ts +20 -0
- package/src/memory/steps.ts +573 -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 +118 -0
- package/src/memory/worker-process.ts +72 -0
- package/src/notifications/__tests__/broadcaster.test.ts +16 -8
- package/src/notifications/__tests__/connected-channels.test.ts +114 -0
- package/src/notifications/__tests__/decision-engine.test.ts +78 -9
- package/src/notifications/__tests__/destination-resolver.test.ts +256 -0
- package/src/notifications/broadcaster.ts +8 -1
- package/src/notifications/decision-engine.ts +15 -7
- package/src/notifications/destination-resolver.ts +68 -24
- package/src/notifications/emit-signal.ts +39 -14
- package/src/onboarding/checkin-event.test.ts +220 -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 +6 -6
- package/src/plugin-api/types.ts +3 -5
- package/src/plugin-api/vision-support.test.ts +28 -4
- package/src/plugin-api/vision-support.ts +66 -31
- package/src/plugins/defaults/advisor/__tests__/consult.test.ts +161 -0
- package/src/plugins/defaults/advisor/__tests__/context-pack-gating.test.ts +106 -0
- package/src/plugins/defaults/advisor/__tests__/context-pack.test.ts +60 -0
- package/src/plugins/defaults/advisor/consult.ts +110 -6
- package/src/plugins/defaults/advisor/context-pack.ts +288 -0
- package/src/plugins/defaults/advisor/steering.ts +14 -2
- package/src/plugins/defaults/advisor/tools/advisor.ts +32 -5
- package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +47 -7
- package/src/plugins/defaults/image-fallback/hooks/post-tool-use.ts +10 -11
- package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +12 -20
- package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +42 -11
- 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 +29 -1
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +8 -1
- package/src/plugins/mtime-cache.ts +7 -2
- package/src/plugins/types.ts +0 -2
- package/src/providers/anthropic/client.ts +5 -0
- package/src/providers/call-site-routing.ts +4 -0
- package/src/providers/model-catalog.ts +16 -0
- package/src/providers/openai/responses-provider.ts +5 -0
- package/src/providers/openrouter/client.ts +5 -0
- package/src/providers/provider-send-message.ts +4 -0
- package/src/providers/ratelimit.ts +4 -0
- package/src/providers/retry.ts +4 -0
- package/src/providers/types.ts +9 -0
- package/src/providers/usage-tracking.ts +4 -0
- package/src/runtime/__tests__/channel-verification-service.test.ts +133 -0
- package/src/runtime/__tests__/guardian-vellum-migration.test.ts +181 -0
- package/src/runtime/__tests__/is-guardian-bound-for-channel.test.ts +66 -0
- package/src/runtime/__tests__/local-principal-trust.test.ts +164 -0
- package/src/runtime/__tests__/trust-verdict-consumer.test.ts +335 -3
- package/src/runtime/access-request-helper.ts +19 -39
- package/src/runtime/actor-trust-resolver.ts +2 -2
- package/src/runtime/anchored-guardian.test.ts +156 -0
- package/src/runtime/anchored-guardian.ts +135 -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 +66 -7
- package/src/runtime/invite-redemption-service.ts +50 -18
- package/src/runtime/local-actor-identity.ts +76 -11
- package/src/runtime/local-principal-trust.ts +52 -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 +212 -0
- package/src/runtime/routes/__tests__/global-search-routes.test.ts +93 -0
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +215 -1
- package/src/runtime/routes/browser-routes.ts +1 -1
- package/src/runtime/routes/channel-verification-routes.ts +3 -3
- package/src/runtime/routes/contact-routes.ts +8 -32
- 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-routes.ts +74 -81
- 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 +1 -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 -49
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +16 -4
- 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/index.ts +2 -0
- package/src/runtime/routes/llm-context-normalization.ts +71 -0
- package/src/runtime/routes/mcp-auth-routes.ts +38 -15
- 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/subagents-routes.ts +5 -0
- package/src/runtime/routes/surface-action-routes.ts +51 -55
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +1 -0
- package/src/runtime/services/conversation-serializer.ts +7 -9
- package/src/runtime/tool-grant-request-helper.ts +3 -3
- package/src/runtime/trust-verdict-consumer.ts +85 -9
- package/src/runtime/verification-outbound-actions.ts +18 -18
- package/src/signals/user-message.ts +16 -0
- package/src/subagent/manager.ts +9 -0
- 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/computer-use/definitions.ts +8 -2
- package/src/tools/executor.ts +4 -4
- package/src/tools/registry.ts +18 -0
- package/src/tools/tool-approval-handler.ts +1 -1
- package/src/tools/tool-defaults.ts +9 -2
- package/src/tools/types.ts +17 -2
- package/src/tools/workspace-tools/loader.ts +348 -244
- package/src/util/platform.ts +5 -0
- package/src/util/telemetry-db-path.ts +24 -0
- 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/__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
|
@@ -30,6 +30,9 @@ import type {
|
|
|
30
30
|
* Returns true when a guardian channel was found and revoked, false otherwise.
|
|
31
31
|
*/
|
|
32
32
|
export function revokeGuardianBinding(channel: string): boolean {
|
|
33
|
+
// Local-store read, not the gateway: this read selects the row that the
|
|
34
|
+
// updateChannelStatus write below mutates, so it must stay transactionally
|
|
35
|
+
// consistent with that write. Leave for Combo 11 / gateway-bootstrap-binding.
|
|
33
36
|
const guardian = findGuardianForChannel(channel);
|
|
34
37
|
if (!guardian) return false;
|
|
35
38
|
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gateway-backed guardian binding + delivery reader.
|
|
3
|
+
*
|
|
4
|
+
* Resolves the active guardian binding(s) and their per-channel delivery
|
|
5
|
+
* endpoints from the gateway via the `resolve_guardian_delivery` IPC route,
|
|
6
|
+
* validating the response against {@link ResolveGuardianDeliveryResponseSchema}.
|
|
7
|
+
*
|
|
8
|
+
* Guardian binding is near-static — it only changes on guardian onboarding /
|
|
9
|
+
* verification or revocation — yet this reader sits on many hot paths. To keep
|
|
10
|
+
* those paths off the IPC, results are cached behind a minutes-scale TTL and
|
|
11
|
+
* coalesced single-flight so a cold cache storms the gateway at most once.
|
|
12
|
+
*
|
|
13
|
+
* Freshness comes from two sources: the {@link invalidateGuardianDeliveryCache}
|
|
14
|
+
* subscription to `onContactChange` (contact mutations clear the cache), and
|
|
15
|
+
* {@link getGuardianDeliveryFresh} reads on existence guards (gateway-side
|
|
16
|
+
* binding writes don't invalidate the daemon cache, so those paths read fresh).
|
|
17
|
+
*
|
|
18
|
+
* Returns `null` on ANY failure (transport failure, malformed shape, timeout,
|
|
19
|
+
* or thrown error); failures are NOT cached, so a recovered gateway is retried
|
|
20
|
+
* on the next call.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import {
|
|
24
|
+
type GuardianDelivery,
|
|
25
|
+
ResolveGuardianDeliveryResponseSchema,
|
|
26
|
+
} from "@vellumai/gateway-client";
|
|
27
|
+
|
|
28
|
+
import { ipcCall } from "../ipc/gateway-client.js";
|
|
29
|
+
import { onContactChange } from "./contact-events.js";
|
|
30
|
+
|
|
31
|
+
// Short IPC timeout so the read resolves promptly rather than stalling a hot
|
|
32
|
+
// path on a gateway that accepts the socket but hangs.
|
|
33
|
+
const GUARDIAN_DELIVERY_IPC_TIMEOUT_MS = 2_000;
|
|
34
|
+
|
|
35
|
+
// Guardian binding is near-static, so a minutes-scale TTL is safe; freshness is
|
|
36
|
+
// driven primarily by event-based invalidation, not by this backstop expiry.
|
|
37
|
+
const GUARDIAN_DELIVERY_CACHE_TTL_MS = 300_000;
|
|
38
|
+
|
|
39
|
+
interface CacheEntry {
|
|
40
|
+
guardians: GuardianDelivery[];
|
|
41
|
+
fetchedAt: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const cache = new Map<string, CacheEntry>();
|
|
45
|
+
// Tracks whether the in-flight fetch was a force-refresh, so a fresh read never
|
|
46
|
+
// coalesces with an older non-force fetch that may predate a gateway-side write.
|
|
47
|
+
const inFlight = new Map<
|
|
48
|
+
string,
|
|
49
|
+
{ promise: Promise<GuardianDelivery[] | null>; fresh: boolean }
|
|
50
|
+
>();
|
|
51
|
+
|
|
52
|
+
// Bumped on every invalidation. A fetch captures the generation when it starts
|
|
53
|
+
// and only writes its result to the cache if the generation is unchanged on
|
|
54
|
+
// resolve, so an invalidation mid-flight can't repopulate a stale pre-change
|
|
55
|
+
// result and mask a guardian-binding change.
|
|
56
|
+
let cacheGeneration = 0;
|
|
57
|
+
|
|
58
|
+
function cacheKey(channelTypes?: string[]): string {
|
|
59
|
+
if (!channelTypes || channelTypes.length === 0) return "ALL";
|
|
60
|
+
return [...channelTypes].sort().join(",");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function fetchGuardianDelivery(
|
|
64
|
+
input: { channelTypes?: string[] },
|
|
65
|
+
): Promise<GuardianDelivery[] | null> {
|
|
66
|
+
try {
|
|
67
|
+
const result = await ipcCall(
|
|
68
|
+
"resolve_guardian_delivery",
|
|
69
|
+
input,
|
|
70
|
+
GUARDIAN_DELIVERY_IPC_TIMEOUT_MS,
|
|
71
|
+
);
|
|
72
|
+
if (!result) return null;
|
|
73
|
+
|
|
74
|
+
const parsed = ResolveGuardianDeliveryResponseSchema.safeParse(result);
|
|
75
|
+
return parsed.success ? parsed.data.guardians : null;
|
|
76
|
+
} catch {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Shared fetch path for both the cached and fresh public surfaces. When
|
|
82
|
+
// `forceRefresh` is set the cached entry is bypassed; the read is still
|
|
83
|
+
// single-flight and still populates the cache with the fresh result.
|
|
84
|
+
async function readGuardianDelivery(
|
|
85
|
+
input: { channelTypes?: string[]; forceRefresh?: boolean },
|
|
86
|
+
): Promise<GuardianDelivery[] | null> {
|
|
87
|
+
const key = cacheKey(input.channelTypes);
|
|
88
|
+
|
|
89
|
+
if (!input.forceRefresh) {
|
|
90
|
+
const cached = cache.get(key);
|
|
91
|
+
if (
|
|
92
|
+
cached &&
|
|
93
|
+
Date.now() - cached.fetchedAt < GUARDIAN_DELIVERY_CACHE_TTL_MS
|
|
94
|
+
) {
|
|
95
|
+
return cached.guardians;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// A non-force read may coalesce with any in-flight fetch. A force read may
|
|
100
|
+
// only coalesce with another force fetch — never with a non-force fetch that
|
|
101
|
+
// could have started before a gateway-side binding write and resolve stale.
|
|
102
|
+
const pending = inFlight.get(key);
|
|
103
|
+
if (pending && (!input.forceRefresh || pending.fresh)) return pending.promise;
|
|
104
|
+
|
|
105
|
+
const startGen = cacheGeneration;
|
|
106
|
+
const promise = fetchGuardianDelivery({ channelTypes: input.channelTypes })
|
|
107
|
+
.then((guardians) => {
|
|
108
|
+
// Skip the write if an invalidation fired during the fetch: the result
|
|
109
|
+
// may predate the change. Return it to this caller (freshest it has) but
|
|
110
|
+
// leave the cache empty so the next call re-fetches.
|
|
111
|
+
if (guardians && cacheGeneration === startGen) {
|
|
112
|
+
cache.set(key, { guardians, fetchedAt: Date.now() });
|
|
113
|
+
}
|
|
114
|
+
return guardians;
|
|
115
|
+
})
|
|
116
|
+
.finally(() => {
|
|
117
|
+
// Only clear the slot if it still holds this fetch — a concurrent force
|
|
118
|
+
// read may have replaced a non-force entry (or vice versa).
|
|
119
|
+
if (inFlight.get(key)?.promise === promise) inFlight.delete(key);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
inFlight.set(key, { promise, fresh: !!input.forceRefresh });
|
|
123
|
+
return promise;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Resolve active guardian deliveries, optionally filtered by channel type.
|
|
128
|
+
* Returns the cached list when fresh, otherwise fetches (single-flight) and
|
|
129
|
+
* caches on success. Returns `null` on failure without caching.
|
|
130
|
+
*
|
|
131
|
+
* To force an uncached read, call {@link getGuardianDeliveryFresh} — the only
|
|
132
|
+
* public fresh-read entry point.
|
|
133
|
+
*/
|
|
134
|
+
export async function getGuardianDelivery(
|
|
135
|
+
input?: { channelTypes?: string[] },
|
|
136
|
+
): Promise<GuardianDelivery[] | null> {
|
|
137
|
+
return readGuardianDelivery({ channelTypes: input?.channelTypes });
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Synchronous read of the already-cached guardian deliveries, without any IO.
|
|
142
|
+
*
|
|
143
|
+
* Returns the fresh cached list for the given channel filter, or `undefined`
|
|
144
|
+
* when the cache is cold or expired. Used by sync hot paths (SSE subscribe)
|
|
145
|
+
* that cannot await {@link getGuardianDelivery} but must resolve the SAME
|
|
146
|
+
* gateway-owned principal the async paths land on. A cold/expired return lets
|
|
147
|
+
* the caller fall back to the local store as before.
|
|
148
|
+
*/
|
|
149
|
+
export function peekCachedGuardianDelivery(
|
|
150
|
+
input?: { channelTypes?: string[] },
|
|
151
|
+
): GuardianDelivery[] | undefined {
|
|
152
|
+
const cached = cache.get(cacheKey(input?.channelTypes));
|
|
153
|
+
if (!cached) return undefined;
|
|
154
|
+
if (Date.now() - cached.fetchedAt >= GUARDIAN_DELIVERY_CACHE_TTL_MS) {
|
|
155
|
+
return undefined;
|
|
156
|
+
}
|
|
157
|
+
return cached.guardians;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Fresh (uncached) variant of {@link getGuardianDelivery}. Existence guards read
|
|
162
|
+
* fresh because gateway-side binding writes don't invalidate the daemon cache.
|
|
163
|
+
* Still single-flight, and still populates the cache with the fresh result.
|
|
164
|
+
*/
|
|
165
|
+
export async function getGuardianDeliveryFresh(
|
|
166
|
+
input?: { channelTypes?: string[] },
|
|
167
|
+
): Promise<GuardianDelivery[] | null> {
|
|
168
|
+
return readGuardianDelivery({
|
|
169
|
+
channelTypes: input?.channelTypes,
|
|
170
|
+
forceRefresh: true,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Clear ALL cached guardian deliveries. Subscribed to `onContactChange` so
|
|
176
|
+
* contact mutations refetch on the next read; also exported for any caller that
|
|
177
|
+
* wants to invalidate explicitly.
|
|
178
|
+
*/
|
|
179
|
+
export function invalidateGuardianDeliveryCache(): void {
|
|
180
|
+
cacheGeneration += 1;
|
|
181
|
+
cache.clear();
|
|
182
|
+
inFlight.clear();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
onContactChange(invalidateGuardianDeliveryCache);
|
|
186
|
+
|
|
187
|
+
/** First active guardian delivery for the given channel type, if any. */
|
|
188
|
+
export function guardianForChannel(
|
|
189
|
+
list: GuardianDelivery[],
|
|
190
|
+
channelType: string,
|
|
191
|
+
): GuardianDelivery | undefined {
|
|
192
|
+
return list.find(
|
|
193
|
+
(g) => g.channelType === channelType && g.status === "active",
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** First guardian delivery overall — the `listGuardianChannels` fallback. */
|
|
198
|
+
export function anyGuardian(
|
|
199
|
+
list: GuardianDelivery[],
|
|
200
|
+
): GuardianDelivery | undefined {
|
|
201
|
+
return list[0];
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Resolve a guardian displayName for voice surfaces: prefer the phone-channel
|
|
206
|
+
* guardian, falling back to any guardian. Returns `undefined` when the list is
|
|
207
|
+
* absent or no guardian carries a displayName.
|
|
208
|
+
*/
|
|
209
|
+
export function voiceGuardianDisplayName(
|
|
210
|
+
list: GuardianDelivery[] | null,
|
|
211
|
+
): string | undefined {
|
|
212
|
+
const guardian = list
|
|
213
|
+
? (guardianForChannel(list, "phone") ?? anyGuardian(list))
|
|
214
|
+
: undefined;
|
|
215
|
+
return guardian?.displayName ?? undefined;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** Test-only: reset cache + in-flight state for deterministic test runs. */
|
|
219
|
+
export function __resetGuardianDeliveryCacheForTest(): void {
|
|
220
|
+
cache.clear();
|
|
221
|
+
inFlight.clear();
|
|
222
|
+
cacheGeneration = 0;
|
|
223
|
+
}
|
|
@@ -480,6 +480,11 @@ export async function runAgentLoopImpl(
|
|
|
480
480
|
// resolved once here and threaded into every re-injection — including the
|
|
481
481
|
// post-compaction hook — rather than re-read per assembly call.
|
|
482
482
|
const isNonInteractive = !isInteractiveResolved;
|
|
483
|
+
// Expose the resolved turn-level interactivity to tool execution so tools
|
|
484
|
+
// (e.g. ask_question) see whether a human is present to answer, rather than
|
|
485
|
+
// re-deriving it from live client state that misclassifies a scheduled turn
|
|
486
|
+
// running on a client-attached conversation.
|
|
487
|
+
ctx.currentTurnIsNonInteractive = isNonInteractive;
|
|
483
488
|
const diskPressureDecision = classifyDiskPressureTurnPolicy(
|
|
484
489
|
getDiskPressureStatus(),
|
|
485
490
|
{
|
|
@@ -1515,6 +1520,10 @@ export async function runAgentLoopImpl(
|
|
|
1515
1520
|
ctx.diskPressureCleanupModeActive = false;
|
|
1516
1521
|
ctx.preactivatedSkillIds = undefined;
|
|
1517
1522
|
ctx.currentTurnOverrideProfile = undefined;
|
|
1523
|
+
// Turn-scoped interactivity. Clear it so paths that bypass this loop (e.g.
|
|
1524
|
+
// opportunity wakes calling `agentLoop.run` directly) don't inherit a stale
|
|
1525
|
+
// value and instead fall back to live client state in the tool context.
|
|
1526
|
+
ctx.currentTurnIsNonInteractive = undefined;
|
|
1518
1527
|
// Channel command intents (e.g. Telegram /start) are single-turn metadata.
|
|
1519
1528
|
// Clear at turn end so they never leak into subsequent unrelated messages.
|
|
1520
1529
|
ctx.commandIntent = undefined;
|
|
@@ -60,6 +60,21 @@ import { resolveVerificationSessionIntent } from "./verification-session-intent.
|
|
|
60
60
|
|
|
61
61
|
const log = getLogger("conversation-process");
|
|
62
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Daemon-injected subagent lifecycle notifications carry `subagentNotification`
|
|
65
|
+
* metadata. They are persisted into the parent conversation so the orchestrator
|
|
66
|
+
* wakes and reads the subagent's result, but they are internal scaffolding — the
|
|
67
|
+
* user sees subagent activity through the inline progress card, not a chat turn.
|
|
68
|
+
* Skip the `user_message_echo` broadcast for these so they never render as a live
|
|
69
|
+
* user bubble; the persisted row is filtered from the rendered transcript on the
|
|
70
|
+
* client.
|
|
71
|
+
*/
|
|
72
|
+
function isSubagentNotificationMessage(
|
|
73
|
+
metadata: Record<string, unknown> | undefined,
|
|
74
|
+
): boolean {
|
|
75
|
+
return metadata?.subagentNotification != null;
|
|
76
|
+
}
|
|
77
|
+
|
|
63
78
|
/** Format the result of a forced compaction into a user-facing message. */
|
|
64
79
|
export function formatCompactResult(result: ContextWindowResult): string {
|
|
65
80
|
const fmt = (n: number | undefined) => (n ?? 0).toLocaleString("en-US");
|
|
@@ -853,14 +868,16 @@ async function drainSingleMessage(
|
|
|
853
868
|
|
|
854
869
|
// Broadcast the user message to all hub subscribers so passive devices
|
|
855
870
|
// see the user turn before the assistant reply starts streaming.
|
|
856
|
-
next.
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
871
|
+
if (!isSubagentNotificationMessage(next.metadata)) {
|
|
872
|
+
next.onEvent({
|
|
873
|
+
type: "user_message_echo",
|
|
874
|
+
text: resolvedContent,
|
|
875
|
+
conversationId: conversation.conversationId,
|
|
876
|
+
messageId: userMessageId,
|
|
877
|
+
requestId: next.requestId,
|
|
878
|
+
clientMessageId: next.clientMessageId,
|
|
879
|
+
});
|
|
880
|
+
}
|
|
864
881
|
publishConversationMessagesChanged(conversation.conversationId);
|
|
865
882
|
|
|
866
883
|
// Set the active surface for the dequeued message so runAgentLoop can inject context
|
|
@@ -1204,14 +1221,16 @@ async function drainBatch(
|
|
|
1204
1221
|
|
|
1205
1222
|
// Broadcast the user message to all hub subscribers so passive devices
|
|
1206
1223
|
// see each batched user turn before the assistant reply starts streaming.
|
|
1207
|
-
qm.
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1224
|
+
if (!isSubagentNotificationMessage(qm.metadata)) {
|
|
1225
|
+
qm.onEvent({
|
|
1226
|
+
type: "user_message_echo",
|
|
1227
|
+
text: qmContent,
|
|
1228
|
+
conversationId: conversation.conversationId,
|
|
1229
|
+
messageId: lastUserMessageId,
|
|
1230
|
+
requestId: qm.requestId,
|
|
1231
|
+
clientMessageId: qm.clientMessageId,
|
|
1232
|
+
});
|
|
1233
|
+
}
|
|
1215
1234
|
publishConversationMessagesChanged(conversation.conversationId);
|
|
1216
1235
|
|
|
1217
1236
|
// Persist succeeded. Update last-successful markers so a later tail
|
|
@@ -1367,6 +1386,8 @@ export interface ProcessMessageOptions {
|
|
|
1367
1386
|
*/
|
|
1368
1387
|
overrideProfile?: string;
|
|
1369
1388
|
displayContent?: string;
|
|
1389
|
+
/** JWT-verified committer principal for turn-scoped host-proxy authorization. */
|
|
1390
|
+
sourceActorPrincipalId?: string;
|
|
1370
1391
|
}
|
|
1371
1392
|
|
|
1372
1393
|
// ── processMessage ───────────────────────────────────────────────────
|
|
@@ -1390,6 +1411,7 @@ export async function processMessage(
|
|
|
1390
1411
|
callSite,
|
|
1391
1412
|
overrideProfile,
|
|
1392
1413
|
displayContent,
|
|
1414
|
+
sourceActorPrincipalId,
|
|
1393
1415
|
} = options;
|
|
1394
1416
|
await conversation.ensureActorScopedHistory();
|
|
1395
1417
|
// Snapshot persona context at turn start so later tool turns can't pick up
|
|
@@ -1397,7 +1419,7 @@ export async function processMessage(
|
|
|
1397
1419
|
conversation.currentTurnTrustContext = conversation.trustContext;
|
|
1398
1420
|
conversation.currentTurnAuthContext = conversation.authContext;
|
|
1399
1421
|
conversation.currentTurnSourceActorPrincipalId =
|
|
1400
|
-
conversation.authContext?.actorPrincipalId;
|
|
1422
|
+
sourceActorPrincipalId ?? conversation.authContext?.actorPrincipalId;
|
|
1401
1423
|
conversation.currentTurnChannelCapabilities =
|
|
1402
1424
|
conversation.channelCapabilities;
|
|
1403
1425
|
conversation.currentActiveSurfaceId = activeSurfaceId;
|
|
@@ -1695,6 +1695,10 @@ export async function handleSurfaceAction(
|
|
|
1695
1695
|
surfaceId: string,
|
|
1696
1696
|
actionId: string,
|
|
1697
1697
|
data?: Record<string, unknown>,
|
|
1698
|
+
// JWT-verified committer principal; threaded so enqueued turns can
|
|
1699
|
+
// reconstruct the same-user binding for host proxies (CU / app-control),
|
|
1700
|
+
// mirroring the normal message path.
|
|
1701
|
+
sourceActorPrincipalId?: string,
|
|
1698
1702
|
): Promise<SurfaceActionResult> {
|
|
1699
1703
|
// ── Standalone surface interception ──────────────────────────────
|
|
1700
1704
|
// Daemon-driven surfaces (from `requestInteractiveUi`) register a
|
|
@@ -1987,6 +1991,7 @@ export async function handleSurfaceAction(
|
|
|
1987
1991
|
requestId,
|
|
1988
1992
|
activeSurfaceId: surfaceId,
|
|
1989
1993
|
displayContent,
|
|
1994
|
+
sourceActorPrincipalId,
|
|
1990
1995
|
});
|
|
1991
1996
|
|
|
1992
1997
|
if (result.rejected) {
|
|
@@ -2043,6 +2048,7 @@ export async function handleSurfaceAction(
|
|
|
2043
2048
|
requestId,
|
|
2044
2049
|
activeSurfaceId: surfaceId,
|
|
2045
2050
|
displayContent,
|
|
2051
|
+
sourceActorPrincipalId,
|
|
2046
2052
|
})
|
|
2047
2053
|
.catch((err) => {
|
|
2048
2054
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -2233,6 +2239,7 @@ export async function handleSurfaceAction(
|
|
|
2233
2239
|
requestId,
|
|
2234
2240
|
activeSurfaceId: surfaceId,
|
|
2235
2241
|
displayContent,
|
|
2242
|
+
sourceActorPrincipalId,
|
|
2236
2243
|
});
|
|
2237
2244
|
if (result.rejected) {
|
|
2238
2245
|
ctx.surfaceActionRequestIds.delete(requestId);
|
|
@@ -2336,6 +2343,7 @@ export async function handleSurfaceAction(
|
|
|
2336
2343
|
requestId,
|
|
2337
2344
|
activeSurfaceId: surfaceId,
|
|
2338
2345
|
displayContent,
|
|
2346
|
+
sourceActorPrincipalId,
|
|
2339
2347
|
})
|
|
2340
2348
|
.catch((err) => {
|
|
2341
2349
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -22,7 +22,12 @@ import type { Message, ToolDefinition } from "../providers/types.js";
|
|
|
22
22
|
import { assistantEventHub } from "../runtime/assistant-event-hub.js";
|
|
23
23
|
import { registerConversationSender } from "../tools/browser/browser-screencast.js";
|
|
24
24
|
import type { ToolExecutor } from "../tools/executor.js";
|
|
25
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
getMcpToolDefinitions,
|
|
27
|
+
getTool,
|
|
28
|
+
getWorkspaceToolDefinitions,
|
|
29
|
+
getWorkspaceToolNames,
|
|
30
|
+
} from "../tools/registry.js";
|
|
26
31
|
import {
|
|
27
32
|
ACTIVITY_SKIP_SET,
|
|
28
33
|
injectActivityField,
|
|
@@ -41,6 +46,7 @@ import {
|
|
|
41
46
|
type ToolExecutionResult,
|
|
42
47
|
type ToolLifecycleEventHandler,
|
|
43
48
|
} from "../tools/types.js";
|
|
49
|
+
import { loadWorkspaceTools } from "../tools/workspace-tools/loader.js";
|
|
44
50
|
import {
|
|
45
51
|
resolveUsageAttribution,
|
|
46
52
|
type UsageAttributionSnapshot,
|
|
@@ -238,7 +244,10 @@ export function createToolExecutor(
|
|
|
238
244
|
});
|
|
239
245
|
}
|
|
240
246
|
},
|
|
241
|
-
isInteractive:
|
|
247
|
+
isInteractive:
|
|
248
|
+
ctx.currentTurnIsNonInteractive !== undefined
|
|
249
|
+
? !ctx.currentTurnIsNonInteractive
|
|
250
|
+
: !ctx.hasNoClient && !ctx.headlessLock,
|
|
242
251
|
proxyToolResolver: (
|
|
243
252
|
toolName: string,
|
|
244
253
|
proxyInput: Record<string, unknown>,
|
|
@@ -650,11 +659,13 @@ export function isToolActiveForContext(
|
|
|
650
659
|
* allowedToolNames so newly-activated skill tools aren't blocked by
|
|
651
660
|
* the executor's stale gate.
|
|
652
661
|
*
|
|
653
|
-
* Core (non-MCP) tool definitions are captured at conversation
|
|
654
|
-
* reused on each turn. MCP tool definitions are
|
|
655
|
-
* registry on each turn so that tools registered
|
|
656
|
-
*
|
|
657
|
-
* requiring conversation disposal or app restart
|
|
662
|
+
* Core (non-MCP, non-workspace) tool definitions are captured at conversation
|
|
663
|
+
* creation and reused on each turn. MCP and workspace tool definitions are
|
|
664
|
+
* re-read from the global registry on each turn so that tools registered or
|
|
665
|
+
* changed after conversation creation are automatically picked up without
|
|
666
|
+
* requiring conversation disposal or app restart — MCP via `vellum mcp
|
|
667
|
+
* reload`, workspace tools via edits under `<workspaceDir>/tools/` that the
|
|
668
|
+
* per-turn reconcile (kicked below) folds into the registry.
|
|
658
669
|
*/
|
|
659
670
|
export function createResolveToolsCallback(
|
|
660
671
|
toolDefs: ToolDefinition[],
|
|
@@ -662,16 +673,21 @@ export function createResolveToolsCallback(
|
|
|
662
673
|
): ((history: Message[]) => ToolDefinition[]) | undefined {
|
|
663
674
|
if (toolDefs.length === 0) return undefined;
|
|
664
675
|
|
|
665
|
-
// Separate the initial tool defs into core (stable) and
|
|
666
|
-
// We keep core tools from the snapshot and
|
|
676
|
+
// Separate the initial tool defs into core (stable) and the two dynamic
|
|
677
|
+
// categories (MCP, workspace). We keep core tools from the snapshot and
|
|
678
|
+
// re-read MCP + workspace tools from the registry each turn.
|
|
667
679
|
const initialMcpDefs = getMcpToolDefinitions();
|
|
668
680
|
const initialMcpNames = new Set(initialMcpDefs.map((d) => d.name));
|
|
669
|
-
const
|
|
681
|
+
const initialWorkspaceNames = new Set(getWorkspaceToolNames());
|
|
682
|
+
const coreToolDefs = toolDefs.filter(
|
|
683
|
+
(d) => !initialMcpNames.has(d.name) && !initialWorkspaceNames.has(d.name),
|
|
684
|
+
);
|
|
670
685
|
log.debug(
|
|
671
686
|
{
|
|
672
687
|
coreCount: coreToolDefs.length,
|
|
673
688
|
mcpCount: initialMcpDefs.length,
|
|
674
689
|
mcpTools: initialMcpDefs.map((d) => d.name),
|
|
690
|
+
workspaceCount: initialWorkspaceNames.size,
|
|
675
691
|
},
|
|
676
692
|
"Conversation tool resolver initialized",
|
|
677
693
|
);
|
|
@@ -685,6 +701,13 @@ export function createResolveToolsCallback(
|
|
|
685
701
|
return [];
|
|
686
702
|
}
|
|
687
703
|
|
|
704
|
+
// Reconcile workspace tool overrides under `<workspaceDir>/tools/` into
|
|
705
|
+
// the registry, then re-read them below — the on-read replacement for a
|
|
706
|
+
// filesystem watcher. Fire-and-forget: the reconcile is idempotent,
|
|
707
|
+
// mtime-cached (a no-op costs one readdir + a stat per file) and
|
|
708
|
+
// serialized, so the registry settles for a subsequent turn to read.
|
|
709
|
+
void loadWorkspaceTools();
|
|
710
|
+
|
|
688
711
|
// Filter core tools based on current conversation context so that tools
|
|
689
712
|
// irrelevant to this turn (e.g. UI tools when no client is connected)
|
|
690
713
|
// are omitted from the definitions sent to the provider.
|
|
@@ -705,24 +728,34 @@ export function createResolveToolsCallback(
|
|
|
705
728
|
? filteredCoreDefs.filter((d) => wireAllowlist.has(d.name))
|
|
706
729
|
: filteredCoreDefs;
|
|
707
730
|
|
|
708
|
-
// Re-read MCP tool definitions from the registry each turn
|
|
709
|
-
// automatically pick up tools added/removed by `vellum
|
|
731
|
+
// Re-read MCP and workspace tool definitions from the registry each turn
|
|
732
|
+
// so conversations automatically pick up tools added/removed by `vellum
|
|
733
|
+
// mcp reload` and workspace-tool edits reconciled from disk, without
|
|
734
|
+
// recreating the conversation.
|
|
710
735
|
const currentMcpDefs = getMcpToolDefinitions();
|
|
736
|
+
const currentWorkspaceDefs = getWorkspaceToolDefinitions();
|
|
711
737
|
log.debug(
|
|
712
738
|
{
|
|
713
739
|
coreCount: scopedCoreDefs.length,
|
|
714
740
|
mcpCount: currentMcpDefs.length,
|
|
715
741
|
mcpTools: currentMcpDefs.map((d) => d.name),
|
|
742
|
+
workspaceCount: currentWorkspaceDefs.length,
|
|
743
|
+
workspaceTools: currentWorkspaceDefs.map((d) => d.name),
|
|
716
744
|
},
|
|
717
|
-
"MCP tools resolved for turn",
|
|
745
|
+
"MCP and workspace tools resolved for turn",
|
|
718
746
|
);
|
|
719
747
|
const scopedMcpDefs = wireAllowlist
|
|
720
748
|
? currentMcpDefs.filter((d) => wireAllowlist.has(d.name))
|
|
721
749
|
: currentMcpDefs;
|
|
750
|
+
const scopedWorkspaceDefs = wireAllowlist
|
|
751
|
+
? currentWorkspaceDefs.filter((d) => wireAllowlist.has(d.name))
|
|
752
|
+
: currentWorkspaceDefs;
|
|
722
753
|
const excluded = new Set(getConfig().tools.exclude);
|
|
723
|
-
const allBaseDefs = [
|
|
724
|
-
|
|
725
|
-
|
|
754
|
+
const allBaseDefs = [
|
|
755
|
+
...scopedCoreDefs,
|
|
756
|
+
...scopedWorkspaceDefs,
|
|
757
|
+
...scopedMcpDefs,
|
|
758
|
+
].filter((d) => !excluded.has(d.name));
|
|
726
759
|
|
|
727
760
|
const effectivePreactivated = [
|
|
728
761
|
...DEFAULT_PREACTIVATED_SKILL_IDS,
|
|
@@ -50,6 +50,7 @@ import {
|
|
|
50
50
|
getMessages,
|
|
51
51
|
resolveOverrideProfile,
|
|
52
52
|
setConversationHistoryStrippedAt,
|
|
53
|
+
setConversationProcessingStartedAt,
|
|
53
54
|
} from "../memory/conversation-crud.js";
|
|
54
55
|
import { getResolvedConversationDirPath } from "../memory/conversation-directories.js";
|
|
55
56
|
import { ConversationGraphMemory } from "../memory/graph/conversation-graph-memory.js";
|
|
@@ -91,7 +92,7 @@ import {
|
|
|
91
92
|
isActivationMomentParam,
|
|
92
93
|
} from "../telemetry/activation-funnel.js";
|
|
93
94
|
import { ToolExecutor } from "../tools/executor.js";
|
|
94
|
-
import { getAllToolDefinitions } from "../tools/registry.js";
|
|
95
|
+
import { getAllToolDefinitions, getTool } from "../tools/registry.js";
|
|
95
96
|
import type { ToolLifecycleEvent } from "../tools/types.js";
|
|
96
97
|
import type { OnboardingContext } from "../types/onboarding-context.js";
|
|
97
98
|
import type { AbortReason } from "../util/abort-reasons.js";
|
|
@@ -376,6 +377,7 @@ export class Conversation {
|
|
|
376
377
|
*/
|
|
377
378
|
wakePersonaOverride?: SystemPromptPersonaOverride;
|
|
378
379
|
/** @internal */ currentTurnOverrideProfile?: string;
|
|
380
|
+
/** @internal */ currentTurnIsNonInteractive?: boolean;
|
|
379
381
|
/** @internal */ authContext?: AuthContext;
|
|
380
382
|
/** @internal */ currentTurnAuthContext?: AuthContext;
|
|
381
383
|
/** @internal */ currentTurnSourceActorPrincipalId?: string;
|
|
@@ -701,6 +703,9 @@ export class Conversation {
|
|
|
701
703
|
tools: toolDefs.length > 0 ? toolDefs : undefined,
|
|
702
704
|
toolExecutor: toolDefs.length > 0 ? toolExecutor : undefined,
|
|
703
705
|
resolveTools,
|
|
706
|
+
// A tool the registry marks exclusive (e.g. `advisor`) runs alone in its
|
|
707
|
+
// turn; the loop defers any sibling calls until the next turn.
|
|
708
|
+
isExclusiveTool: (name) => getTool(name)?.exclusive === true,
|
|
704
709
|
resolveConversationDir: () => {
|
|
705
710
|
const conv = getConversation(this.conversationId);
|
|
706
711
|
if (!conv) return null;
|
|
@@ -1335,6 +1340,13 @@ export class Conversation {
|
|
|
1335
1340
|
setProcessing(value: boolean): void {
|
|
1336
1341
|
const wasProcessing = this._processing;
|
|
1337
1342
|
this._processing = value;
|
|
1343
|
+
// Persist the cross-process source of truth so out-of-process callers
|
|
1344
|
+
// (retrospective CLI, future detached workers) can detect mid-turn state
|
|
1345
|
+
// by reading the conversations row directly.
|
|
1346
|
+
setConversationProcessingStartedAt(
|
|
1347
|
+
this.conversationId,
|
|
1348
|
+
value ? Date.now() : null,
|
|
1349
|
+
);
|
|
1338
1350
|
if (wasProcessing && !value) {
|
|
1339
1351
|
void publishSyncInvalidation([
|
|
1340
1352
|
conversationMetadataSyncTag(this.conversationId),
|
|
@@ -2048,8 +2060,15 @@ export class Conversation {
|
|
|
2048
2060
|
surfaceId: string,
|
|
2049
2061
|
actionId: string,
|
|
2050
2062
|
data?: Record<string, unknown>,
|
|
2063
|
+
sourceActorPrincipalId?: string,
|
|
2051
2064
|
): Promise<SurfaceActionResult> {
|
|
2052
|
-
return handleSurfaceActionImpl(
|
|
2065
|
+
return handleSurfaceActionImpl(
|
|
2066
|
+
this,
|
|
2067
|
+
surfaceId,
|
|
2068
|
+
actionId,
|
|
2069
|
+
data,
|
|
2070
|
+
sourceActorPrincipalId,
|
|
2071
|
+
);
|
|
2053
2072
|
}
|
|
2054
2073
|
|
|
2055
2074
|
handleSurfaceUndo(surfaceId: string): void {
|
|
@@ -24,6 +24,12 @@ export const DISK_PRESSURE_CLEAR_THRESHOLD_PERCENT = 90;
|
|
|
24
24
|
// clears the warning state, which discards the banner's (state-scoped) dismissal
|
|
25
25
|
// so it re-appears the moment usage ticks back up.
|
|
26
26
|
export const DISK_PRESSURE_WARNING_CLEAR_THRESHOLD_PERCENT = 77;
|
|
27
|
+
// Absolute free-space floor (MiB). Regardless of usage percentage, never enter
|
|
28
|
+
// the warning or critical state while at least this much space remains free. A
|
|
29
|
+
// high usage percentage on a large disk can still leave many gigabytes
|
|
30
|
+
// available, where locking is pointless. Small volumes (where a high percentage
|
|
31
|
+
// genuinely means near-full) drop below the floor and remain protected.
|
|
32
|
+
export const DISK_PRESSURE_MIN_FREE_FLOOR_MB = 2048;
|
|
27
33
|
export const DISK_PRESSURE_CHECK_INTERVAL_MS = 60_000;
|
|
28
34
|
export const DISK_PRESSURE_OVERRIDE_CONFIRMATION = "I understand the risks";
|
|
29
35
|
export const DISK_PRESSURE_BLOCKED_CAPABILITIES = [
|
|
@@ -219,7 +225,10 @@ export function evaluateDiskPressureNow(): DiskPressureStatus {
|
|
|
219
225
|
const criticalThreshold = state.status.locked
|
|
220
226
|
? DISK_PRESSURE_CLEAR_THRESHOLD_PERCENT
|
|
221
227
|
: DISK_PRESSURE_THRESHOLD_PERCENT;
|
|
222
|
-
|
|
228
|
+
// Absolute free-space floor overrides the percentage thresholds: while ample
|
|
229
|
+
// space remains free, report "ok" no matter how full the volume is by percent.
|
|
230
|
+
const hasAmpleFreeSpace = usageInfo.freeMb >= DISK_PRESSURE_MIN_FREE_FLOOR_MB;
|
|
231
|
+
const isCritical = !hasAmpleFreeSpace && usagePercent >= criticalThreshold;
|
|
223
232
|
// Mirror the critical deadband for the warning band: once in an active
|
|
224
233
|
// pressure state (warning or critical), hold warning until usage clears the
|
|
225
234
|
// lower warning-clear threshold. Treating "critical" as active here matters
|
|
@@ -235,7 +244,8 @@ export function evaluateDiskPressureNow(): DiskPressureStatus {
|
|
|
235
244
|
const warningThreshold = inActivePressureState
|
|
236
245
|
? DISK_PRESSURE_WARNING_CLEAR_THRESHOLD_PERCENT
|
|
237
246
|
: DISK_PRESSURE_WARNING_THRESHOLD_PERCENT;
|
|
238
|
-
const isWarning =
|
|
247
|
+
const isWarning =
|
|
248
|
+
!hasAmpleFreeSpace && !isCritical && usagePercent >= warningThreshold;
|
|
239
249
|
const lastCheckedAt = new Date().toISOString();
|
|
240
250
|
|
|
241
251
|
if (!isCritical && !isWarning) {
|