@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
package/src/util/platform.ts
CHANGED
|
@@ -242,6 +242,11 @@ export function getEmbedWorkerPidPath(): string {
|
|
|
242
242
|
return join(getWorkspaceDir(), "embed-worker.pid");
|
|
243
243
|
}
|
|
244
244
|
|
|
245
|
+
/** Returns the path to the memory-worker PID file ($VELLUM_WORKSPACE_DIR/memory-worker.pid). */
|
|
246
|
+
export function getMemoryWorkerPidPath(): string {
|
|
247
|
+
return join(getWorkspaceDir(), "memory-worker.pid");
|
|
248
|
+
}
|
|
249
|
+
|
|
245
250
|
/**
|
|
246
251
|
* Returns the workspace root for user-facing state.
|
|
247
252
|
*
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
|
|
3
|
+
import { getDataDir } from "./platform.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Path to the dedicated SQLite file that houses telemetry event tables
|
|
7
|
+
* (starting with `watchdog_events`). It lives in the same `data/db` directory
|
|
8
|
+
* as the main DB and is opened on its own connection (see `getTelemetryDb()`
|
|
9
|
+
* in `memory/db-connection.ts`). Splitting telemetry tables into their own
|
|
10
|
+
* file keeps the main DB — and its WAL — small and lets the two files
|
|
11
|
+
* VACUUM/checkpoint independently, so a burst of watchdog events can no longer
|
|
12
|
+
* bloat the main database.
|
|
13
|
+
*
|
|
14
|
+
* Kept in its own leaf module rather than alongside `getDbPath()` in
|
|
15
|
+
* `platform.ts`: `platform.ts` is imported very early and widely, and adding an
|
|
16
|
+
* export to it that low-level consumers (e.g. `db-connection.ts`) pull in
|
|
17
|
+
* across the daemon's large, cyclic import graph trips a Bun link-order bug
|
|
18
|
+
* ("Export named 'getTelemetryDbPath' not found"). Isolating it here keeps
|
|
19
|
+
* `platform.ts`'s module shape stable — same reasoning as `logs-db-path.ts`
|
|
20
|
+
* and `memory-db-path.ts`.
|
|
21
|
+
*/
|
|
22
|
+
export function getTelemetryDbPath(): string {
|
|
23
|
+
return join(getDataDir(), "db", "assistant-telemetry.db");
|
|
24
|
+
}
|
|
@@ -8,11 +8,6 @@ import {
|
|
|
8
8
|
} from "node:fs";
|
|
9
9
|
import { join } from "node:path";
|
|
10
10
|
|
|
11
|
-
import { desc, eq } from "drizzle-orm";
|
|
12
|
-
|
|
13
|
-
import { generateUserFileSlug } from "../../contacts/contact-store.js";
|
|
14
|
-
import { getDb } from "../../memory/db-connection.js";
|
|
15
|
-
import { contacts } from "../../memory/schema/contacts.js";
|
|
16
11
|
import type { WorkspaceMigration } from "./types.js";
|
|
17
12
|
|
|
18
13
|
// ── Inlined helpers ───────────────────────────────────────────────
|
|
@@ -122,35 +117,9 @@ export const seedPersonaDirsMigration: WorkspaceMigration = {
|
|
|
122
117
|
// template from disk, since migration 031 deletes that template file.
|
|
123
118
|
if (content === LEGACY_USER_MD_TEMPLATE_STRIPPED) return;
|
|
124
119
|
|
|
125
|
-
//
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const db = getDb();
|
|
129
|
-
const guardian = db
|
|
130
|
-
.select()
|
|
131
|
-
.from(contacts)
|
|
132
|
-
.where(eq(contacts.role, "guardian"))
|
|
133
|
-
.orderBy(desc(contacts.createdAt))
|
|
134
|
-
.limit(1)
|
|
135
|
-
.get();
|
|
136
|
-
|
|
137
|
-
if (guardian) {
|
|
138
|
-
if (guardian.userFile) {
|
|
139
|
-
destFilename = guardian.userFile;
|
|
140
|
-
} else {
|
|
141
|
-
const slug = generateUserFileSlug(guardian.displayName);
|
|
142
|
-
db.update(contacts)
|
|
143
|
-
.set({ userFile: slug })
|
|
144
|
-
.where(eq(contacts.id, guardian.id))
|
|
145
|
-
.run();
|
|
146
|
-
destFilename = slug;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
} catch {
|
|
150
|
-
// DB might not be initialized yet — fall back to guardian.md
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const destPath = join(workspaceDir, "users", destFilename);
|
|
120
|
+
// Seed the canonical `users/guardian.md` — the runtime persona resolver
|
|
121
|
+
// falls back to this name, so no assistant-DB lookup is needed.
|
|
122
|
+
const destPath = join(workspaceDir, "users", "guardian.md");
|
|
154
123
|
if (!existsSync(destPath)) {
|
|
155
124
|
copyFileSync(userMdPath, destPath);
|
|
156
125
|
}
|
|
@@ -8,10 +8,6 @@ import {
|
|
|
8
8
|
} from "node:fs";
|
|
9
9
|
import { join } from "node:path";
|
|
10
10
|
|
|
11
|
-
import { desc, eq } from "drizzle-orm";
|
|
12
|
-
|
|
13
|
-
import { getDb } from "../../memory/db-connection.js";
|
|
14
|
-
import { contacts } from "../../memory/schema/contacts.js";
|
|
15
11
|
import type { WorkspaceMigration } from "./types.js";
|
|
16
12
|
|
|
17
13
|
export const scopeJournalToGuardianMigration: WorkspaceMigration = {
|
|
@@ -40,26 +36,9 @@ export const scopeJournalToGuardianMigration: WorkspaceMigration = {
|
|
|
40
36
|
});
|
|
41
37
|
if (mdFiles.length === 0) return;
|
|
42
38
|
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const db = getDb();
|
|
47
|
-
const guardian = db
|
|
48
|
-
.select()
|
|
49
|
-
.from(contacts)
|
|
50
|
-
.where(eq(contacts.role, "guardian"))
|
|
51
|
-
.orderBy(desc(contacts.createdAt))
|
|
52
|
-
.limit(1)
|
|
53
|
-
.get();
|
|
54
|
-
if (guardian?.userFile) {
|
|
55
|
-
slug = guardian.userFile.replace(/\.md$/, "");
|
|
56
|
-
}
|
|
57
|
-
} catch {
|
|
58
|
-
// DB not ready — use fallback "guardian"
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Create per-user directory and move files (renameSync preserves birthtimes)
|
|
62
|
-
const destDir = join(journalDir, slug);
|
|
39
|
+
// Scope under the canonical `guardian` slug — the runtime resolver falls
|
|
40
|
+
// back to it, so no assistant-DB lookup is needed.
|
|
41
|
+
const destDir = join(journalDir, "guardian");
|
|
63
42
|
mkdirSync(destDir, { recursive: true });
|
|
64
43
|
for (const f of mdFiles) {
|
|
65
44
|
const src = join(journalDir, f);
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests that the `workspace-tools-watcher` feature flag gates the dynamic
|
|
3
|
-
* hot-reload watcher in `WorkspaceToolsWatcher.start()`.
|
|
4
|
-
*
|
|
5
|
-
* The initial disk scan (`loadWorkspaceTools()`) is unconditional and lives
|
|
6
|
-
* elsewhere; this suite only covers whether the live `fs.watch` loop mounts.
|
|
7
|
-
*/
|
|
8
|
-
import { mkdirSync, rmSync } from "node:fs";
|
|
9
|
-
import { tmpdir } from "node:os";
|
|
10
|
-
import { join } from "node:path";
|
|
11
|
-
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
12
|
-
|
|
13
|
-
import { clearFeatureFlagOverridesCache } from "../config/assistant-feature-flags.js";
|
|
14
|
-
import { WorkspaceToolsWatcher } from "../daemon/workspace-tools-watcher.js";
|
|
15
|
-
import { setOverridesForTesting } from "./feature-flag-test-helpers.js";
|
|
16
|
-
|
|
17
|
-
const FLAG = "workspace-tools-watcher";
|
|
18
|
-
const TEST_BASE_DIR = join(
|
|
19
|
-
tmpdir(),
|
|
20
|
-
`vellum-workspace-tools-watcher-flag-test-${process.pid}-${Date.now()}`,
|
|
21
|
-
);
|
|
22
|
-
let caseCounter = 0;
|
|
23
|
-
|
|
24
|
-
function freshWorkspaceWithToolsDir(): void {
|
|
25
|
-
caseCounter += 1;
|
|
26
|
-
const dir = join(TEST_BASE_DIR, `case-${caseCounter}`);
|
|
27
|
-
mkdirSync(join(dir, "tools"), { recursive: true });
|
|
28
|
-
process.env.VELLUM_WORKSPACE_DIR = dir;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
beforeEach(() => {
|
|
32
|
-
WorkspaceToolsWatcher.resetForTests();
|
|
33
|
-
clearFeatureFlagOverridesCache();
|
|
34
|
-
freshWorkspaceWithToolsDir();
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
afterEach(() => {
|
|
38
|
-
WorkspaceToolsWatcher.resetForTests();
|
|
39
|
-
clearFeatureFlagOverridesCache();
|
|
40
|
-
delete process.env.VELLUM_WORKSPACE_DIR;
|
|
41
|
-
rmSync(TEST_BASE_DIR, { recursive: true, force: true });
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
describe("WorkspaceToolsWatcher feature-flag gate", () => {
|
|
45
|
-
test("does not mount the watch loop when the flag is off", () => {
|
|
46
|
-
// GIVEN the workspace tools directory exists
|
|
47
|
-
// AND the watcher flag is disabled
|
|
48
|
-
setOverridesForTesting({ [FLAG]: false });
|
|
49
|
-
|
|
50
|
-
// WHEN the watcher starts
|
|
51
|
-
const watcher = WorkspaceToolsWatcher.getInstance();
|
|
52
|
-
watcher.start();
|
|
53
|
-
|
|
54
|
-
// THEN no fs.watch loop is mounted
|
|
55
|
-
expect(watcher.isWatchingForTests()).toBe(false);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test("mounts the watch loop when the flag is on", () => {
|
|
59
|
-
// GIVEN the workspace tools directory exists
|
|
60
|
-
// AND the watcher flag is enabled
|
|
61
|
-
setOverridesForTesting({ [FLAG]: true });
|
|
62
|
-
|
|
63
|
-
// WHEN the watcher starts
|
|
64
|
-
const watcher = WorkspaceToolsWatcher.getInstance();
|
|
65
|
-
watcher.start();
|
|
66
|
-
|
|
67
|
-
// THEN a live fs.watch loop is mounted
|
|
68
|
-
expect(watcher.isWatchingForTests()).toBe(true);
|
|
69
|
-
});
|
|
70
|
-
});
|
|
@@ -1,328 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Filesystem watcher for `<workspaceDir>/tools/`.
|
|
3
|
-
*
|
|
4
|
-
* Watches the workspace-tools directory non-recursively using fs.watch.
|
|
5
|
-
* On any add/change/delete event, debounces per filename stem (= tool
|
|
6
|
-
* name) and reconciles registry state against on-disk state: registers
|
|
7
|
-
* newly added tools, re-imports changed tools (cache-busting via the
|
|
8
|
-
* loader's per-import URL query string), unregisters deleted tools,
|
|
9
|
-
* strips core tools when a `.removed` sentinel appears, restores them
|
|
10
|
-
* when the sentinel disappears.
|
|
11
|
-
*
|
|
12
|
-
* No assistant restart is required — the file watcher closes the
|
|
13
|
-
* "edit a file, see the change" loop the same way the apps watcher and
|
|
14
|
-
* plugin source watcher do for their respective directories.
|
|
15
|
-
*
|
|
16
|
-
* ## Why per-stem reconciliation
|
|
17
|
-
*
|
|
18
|
-
* The watcher receives `(eventType, filename)` from fs.watch but the
|
|
19
|
-
* eventType ("rename" vs "change") is unreliable across editors and
|
|
20
|
-
* platforms — vim atomic-save shows as a rename of the original file
|
|
21
|
-
* plus an add of a new file, VS Code shows as a change in place, etc.
|
|
22
|
-
* Rather than route on eventType, we debounce per stem and re-derive
|
|
23
|
-
* the world: "given what's on disk right now for `<stem>.*`, what
|
|
24
|
-
* registry state should the assistant be in?"
|
|
25
|
-
*
|
|
26
|
-
* This is the same eventual-consistency pattern the plugin mtime cache
|
|
27
|
-
* uses — the watcher exists to KICK the reconciler, not to be the
|
|
28
|
-
* source of truth about what changed.
|
|
29
|
-
*
|
|
30
|
-
* ## Lifecycle position
|
|
31
|
-
*
|
|
32
|
-
* Started after the initial `loadWorkspaceTools()` scan completes
|
|
33
|
-
* during daemon startup, gated on the `workspace-tools-watcher` feature
|
|
34
|
-
* flag — when the flag is off the initial scan still runs but no watch
|
|
35
|
-
* loop is mounted, so workspace tools load from disk once and live edits
|
|
36
|
-
* need a restart. Stopped on assistant shutdown alongside the
|
|
37
|
-
* other long-lived watchers. Stoppage during shutdown does not
|
|
38
|
-
* unregister tools — those go away with the process; the watcher's
|
|
39
|
-
* only job is to keep the registry fresh while the assistant is up.
|
|
40
|
-
*/
|
|
41
|
-
|
|
42
|
-
import { existsSync, type FSWatcher, watch } from "node:fs";
|
|
43
|
-
|
|
44
|
-
import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
|
|
45
|
-
import { getConfig } from "../config/loader.js";
|
|
46
|
-
import {
|
|
47
|
-
getCoreToolOverride,
|
|
48
|
-
getTool,
|
|
49
|
-
getToolOwner,
|
|
50
|
-
removeCoreToolViaWorkspace,
|
|
51
|
-
restoreStrippedCoreTool,
|
|
52
|
-
unregisterWorkspaceTool,
|
|
53
|
-
} from "../tools/registry.js";
|
|
54
|
-
import {
|
|
55
|
-
classifyWorkspaceToolEntry,
|
|
56
|
-
findWinningWorkspaceToolPath,
|
|
57
|
-
loadSingleWorkspaceTool,
|
|
58
|
-
} from "../tools/workspace-tools/loader.js";
|
|
59
|
-
import { DebouncerMap } from "../util/debounce.js";
|
|
60
|
-
import { attachFsWatcherErrorHandler } from "../util/fs-watcher-error.js";
|
|
61
|
-
import { getLogger } from "../util/logger.js";
|
|
62
|
-
import { getWorkspaceToolsDir } from "../util/platform.js";
|
|
63
|
-
|
|
64
|
-
const log = getLogger("workspace-tools-watcher");
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Gates the dynamic hot-reload path. When disabled, workspace tools still
|
|
68
|
-
* load from disk once at daemon startup via {@link loadWorkspaceTools}; only
|
|
69
|
-
* the live watch → re-registration loop is suppressed. Read at its point of
|
|
70
|
-
* use in {@link WorkspaceToolsWatcher.start} so a daemon restart picks up a
|
|
71
|
-
* changed value.
|
|
72
|
-
*/
|
|
73
|
-
const WORKSPACE_TOOLS_WATCHER_FLAG = "workspace-tools-watcher" as const;
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Wait this long after the last fs event for a given stem before
|
|
77
|
-
* reconciling. Editor saves often emit a burst of 2–4 events; the
|
|
78
|
-
* debounce collapses them into a single load.
|
|
79
|
-
*/
|
|
80
|
-
const RECONCILE_DEBOUNCE_MS = 250;
|
|
81
|
-
|
|
82
|
-
export class WorkspaceToolsWatcher {
|
|
83
|
-
/**
|
|
84
|
-
* Process-wide singleton. Callers reach the watcher via
|
|
85
|
-
* {@link WorkspaceToolsWatcher.getInstance} rather than instantiating
|
|
86
|
-
* directly so the daemon `start()`/`stop()` lifecycle and any future
|
|
87
|
-
* "trigger a manual reconcile" code paths share one watcher across the
|
|
88
|
-
* assistant process lifetime.
|
|
89
|
-
*/
|
|
90
|
-
private static singleton: WorkspaceToolsWatcher | null = null;
|
|
91
|
-
|
|
92
|
-
static getInstance(): WorkspaceToolsWatcher {
|
|
93
|
-
WorkspaceToolsWatcher.singleton ??= new WorkspaceToolsWatcher();
|
|
94
|
-
return WorkspaceToolsWatcher.singleton;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/** Test-only — drops the singleton so the next `getInstance()` rebuilds. */
|
|
98
|
-
static resetForTests(): void {
|
|
99
|
-
WorkspaceToolsWatcher.singleton?.stop();
|
|
100
|
-
WorkspaceToolsWatcher.singleton = null;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/** Test-only — whether a live `fs.watch` loop is currently mounted. */
|
|
104
|
-
isWatchingForTests(): boolean {
|
|
105
|
-
return this.watcher !== null;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
private watcher: FSWatcher | null = null;
|
|
109
|
-
private debouncer = new DebouncerMap({
|
|
110
|
-
defaultDelayMs: RECONCILE_DEBOUNCE_MS,
|
|
111
|
-
maxEntries: 100,
|
|
112
|
-
});
|
|
113
|
-
/**
|
|
114
|
-
* Promise queue per stem — guarantees that two events for the same
|
|
115
|
-
* stem can't run concurrently and corrupt the unregister/load
|
|
116
|
-
* sequence. The queue is single-deep (the chained promise simply
|
|
117
|
-
* awaits the in-flight one before running its own work), so the
|
|
118
|
-
* debouncer's collapsing already does most of the deduplication; this
|
|
119
|
-
* queue exists for the case where a second event lands during the
|
|
120
|
-
* in-flight reconcile's `await loadSingleWorkspaceTool`.
|
|
121
|
-
*/
|
|
122
|
-
private inflight = new Map<string, Promise<void>>();
|
|
123
|
-
|
|
124
|
-
start(): void {
|
|
125
|
-
if (this.watcher) return;
|
|
126
|
-
if (
|
|
127
|
-
!isAssistantFeatureFlagEnabled(WORKSPACE_TOOLS_WATCHER_FLAG, getConfig())
|
|
128
|
-
) {
|
|
129
|
-
log.debug(
|
|
130
|
-
"Workspace tools watcher disabled by feature flag; workspace tools load from disk at startup only (restart required to pick up edits)",
|
|
131
|
-
);
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
const toolsDir = getWorkspaceToolsDir();
|
|
135
|
-
if (!existsSync(toolsDir)) {
|
|
136
|
-
log.debug(
|
|
137
|
-
{ toolsDir },
|
|
138
|
-
"Workspace tools directory does not exist; watcher not started (will not auto-start on directory creation — restart required)",
|
|
139
|
-
);
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
try {
|
|
143
|
-
this.watcher = watch(
|
|
144
|
-
toolsDir,
|
|
145
|
-
{ recursive: false },
|
|
146
|
-
(_eventType, filename) => {
|
|
147
|
-
if (!filename) return;
|
|
148
|
-
const classified = classifyWorkspaceToolEntry(filename);
|
|
149
|
-
if (!classified) {
|
|
150
|
-
// Not a workspace-tool file — ignore (README.md, .DS_Store, etc.)
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
this.debouncer.schedule(`stem:${classified.stem}`, () => {
|
|
154
|
-
this.scheduleReconcile(classified.stem);
|
|
155
|
-
});
|
|
156
|
-
},
|
|
157
|
-
);
|
|
158
|
-
// Async FSWatcher errors (e.g. ENOSPC, ENXIO) arrive as an 'error' event;
|
|
159
|
-
// without a listener they crash the daemon. Degrade to a dead watcher.
|
|
160
|
-
attachFsWatcherErrorHandler(this.watcher, log, toolsDir);
|
|
161
|
-
log.info({ toolsDir }, "Workspace tools watcher started");
|
|
162
|
-
} catch (err) {
|
|
163
|
-
log.warn(
|
|
164
|
-
{ err, toolsDir },
|
|
165
|
-
"Failed to start workspace tools watcher — workspace tools will only register at startup",
|
|
166
|
-
);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
stop(): void {
|
|
171
|
-
this.debouncer.cancelAll();
|
|
172
|
-
if (this.watcher) {
|
|
173
|
-
this.watcher.close();
|
|
174
|
-
this.watcher = null;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Chain `reconcileStem(stem)` after any in-flight reconcile for the
|
|
180
|
-
* same stem so we never run two `loadSingleWorkspaceTool` calls
|
|
181
|
-
* concurrently for the same name.
|
|
182
|
-
*/
|
|
183
|
-
private scheduleReconcile(stem: string): void {
|
|
184
|
-
const prev = this.inflight.get(stem) ?? Promise.resolve();
|
|
185
|
-
const next = prev
|
|
186
|
-
.catch(() => {
|
|
187
|
-
/* swallow — error already logged in the prior tick */
|
|
188
|
-
})
|
|
189
|
-
.then(() => this.reconcileStem(stem))
|
|
190
|
-
.catch((err) => {
|
|
191
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
192
|
-
log.error(
|
|
193
|
-
{ err, stem },
|
|
194
|
-
`workspace-tools-watcher: reconcile for "${stem}" threw: ${message}`,
|
|
195
|
-
);
|
|
196
|
-
})
|
|
197
|
-
.finally(() => {
|
|
198
|
-
// Only clear if we're still the current in-flight promise.
|
|
199
|
-
if (this.inflight.get(stem) === next) {
|
|
200
|
-
this.inflight.delete(stem);
|
|
201
|
-
}
|
|
202
|
-
});
|
|
203
|
-
this.inflight.set(stem, next);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* Reconcile a single stem (= tool name) with on-disk state.
|
|
208
|
-
*
|
|
209
|
-
* Possible (live, removed) tuples and their resolution:
|
|
210
|
-
*
|
|
211
|
-
* - `(present, absent)` → ensure workspace tool is registered using
|
|
212
|
-
* the winning live file; re-import if a previous registration
|
|
213
|
-
* pointed at a different path
|
|
214
|
-
* - `(absent, present)` → ensure core tool is stripped (and any
|
|
215
|
-
* previous workspace registration torn down first)
|
|
216
|
-
* - `(absent, absent)` → ensure neither stripped state nor live
|
|
217
|
-
* registration remains
|
|
218
|
-
* - `(present, present)` → ambiguous; tear down both so neither
|
|
219
|
-
* state survives (matches the initial-scan contract)
|
|
220
|
-
*/
|
|
221
|
-
private async reconcileStem(stem: string): Promise<void> {
|
|
222
|
-
const toolsDir = getWorkspaceToolsDir();
|
|
223
|
-
if (!existsSync(toolsDir)) {
|
|
224
|
-
this.teardownStem(stem);
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
const { livePath, hasRemovedSentinel } = findWinningWorkspaceToolPath(
|
|
229
|
-
toolsDir,
|
|
230
|
-
stem,
|
|
231
|
-
);
|
|
232
|
-
|
|
233
|
-
// Ambiguous: tear down everything for this stem.
|
|
234
|
-
if (hasRemovedSentinel && livePath !== null) {
|
|
235
|
-
log.error(
|
|
236
|
-
{ stem, toolsDir, livePath },
|
|
237
|
-
`workspace-tools-watcher: "${stem}" has both a live file and a .removed sentinel — tearing down both`,
|
|
238
|
-
);
|
|
239
|
-
this.teardownStem(stem);
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// Neither: tear down anything we own.
|
|
244
|
-
if (!hasRemovedSentinel && livePath === null) {
|
|
245
|
-
this.teardownStem(stem);
|
|
246
|
-
return;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
if (hasRemovedSentinel) {
|
|
250
|
-
// Strip the core tool. Unregister any prior workspace registration
|
|
251
|
-
// first so removeCoreToolViaWorkspace doesn't throw on the
|
|
252
|
-
// workspace-owned-name check.
|
|
253
|
-
if (getToolOwner(stem)?.kind === "workspace") {
|
|
254
|
-
unregisterWorkspaceTool(stem);
|
|
255
|
-
}
|
|
256
|
-
try {
|
|
257
|
-
removeCoreToolViaWorkspace(stem);
|
|
258
|
-
} catch (err) {
|
|
259
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
260
|
-
log.error(
|
|
261
|
-
{ err, stem },
|
|
262
|
-
`workspace-tools-watcher: failed to strip "${stem}": ${message}`,
|
|
263
|
-
);
|
|
264
|
-
}
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
if (livePath !== null) {
|
|
269
|
-
// If we previously stripped this core tool, restore it before the
|
|
270
|
-
// workspace registration runs so the override path sees the
|
|
271
|
-
// expected baseline (core tool present → stash + replace).
|
|
272
|
-
if (getCoreToolOverride(stem) && !getTool(stem)) {
|
|
273
|
-
restoreStrippedCoreTool(stem);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// If a workspace tool is already registered under this name,
|
|
277
|
-
// unregister it so the loader can re-import cleanly. This covers
|
|
278
|
-
// file-change events (same path, new contents) as well as
|
|
279
|
-
// extension-precedence flips (e.g. user added foo.js next to
|
|
280
|
-
// foo.ts — now .js wins and the registration must update).
|
|
281
|
-
if (getToolOwner(stem)?.kind === "workspace") {
|
|
282
|
-
unregisterWorkspaceTool(stem);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
const registered = await loadSingleWorkspaceTool(livePath);
|
|
286
|
-
if (registered) {
|
|
287
|
-
log.info(
|
|
288
|
-
{ stem, livePath },
|
|
289
|
-
`Workspace tool "${stem}" registered via watcher`,
|
|
290
|
-
);
|
|
291
|
-
}
|
|
292
|
-
// loadSingleWorkspaceTool already logs failures with attribution.
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* Tear down any workspace-tool state we own for `stem`: unregister a
|
|
298
|
-
* live workspace tool, restore a core tool we stripped. Both no-ops
|
|
299
|
-
* when there's nothing to undo.
|
|
300
|
-
*/
|
|
301
|
-
private teardownStem(stem: string): void {
|
|
302
|
-
if (getToolOwner(stem)?.kind === "workspace") {
|
|
303
|
-
unregisterWorkspaceTool(stem);
|
|
304
|
-
log.info(
|
|
305
|
-
{ stem },
|
|
306
|
-
`Workspace tool "${stem}" unregistered via watcher (file removed)`,
|
|
307
|
-
);
|
|
308
|
-
}
|
|
309
|
-
if (getCoreToolOverride(stem) && !getTool(stem)) {
|
|
310
|
-
restoreStrippedCoreTool(stem);
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// ── Test affordances ────────────────────────────────────────────────
|
|
315
|
-
//
|
|
316
|
-
// These are intentionally narrow — exposed for the watcher tests so
|
|
317
|
-
// they can drive reconcile() deterministically without waiting on the
|
|
318
|
-
// debouncer, and inspect whether an in-flight promise is settled.
|
|
319
|
-
// Not part of the public lifecycle API.
|
|
320
|
-
|
|
321
|
-
/**
|
|
322
|
-
* Run a reconcile for `stem` synchronously (well, asynchronously, but
|
|
323
|
-
* without going through the debouncer). For tests.
|
|
324
|
-
*/
|
|
325
|
-
async _testReconcile(stem: string): Promise<void> {
|
|
326
|
-
await this.reconcileStem(stem);
|
|
327
|
-
}
|
|
328
|
-
}
|