@vellumai/assistant 0.10.2-dev.202606250318.5e7cfb0 → 0.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bun.lock +0 -20
- package/docs/workspace-tools.md +33 -42
- package/eslint-rules/cli-no-daemon-internals.js +0 -6
- package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +0 -31
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +0 -44
- package/node_modules/@vellumai/gateway-client/src/index.ts +0 -14
- package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +0 -17
- package/node_modules/@vellumai/service-contracts/package.json +0 -1
- package/node_modules/@vellumai/service-contracts/src/index.ts +0 -1
- package/openapi.yaml +0 -155
- package/package.json +1 -4
- package/scripts/test.sh +15 -36
- package/src/__tests__/actor-token-service.test.ts +14 -36
- package/src/__tests__/agent-loop-override-profile.test.ts +0 -1
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +0 -2
- package/src/__tests__/agent-wake-override-profile.test.ts +0 -2
- package/src/__tests__/annotate-activity-metadata.test.ts +0 -2
- package/src/__tests__/annotate-risk-options.test.ts +0 -2
- package/src/__tests__/approval-cascade.test.ts +0 -2
- package/src/__tests__/assistant-attachments.test.ts +0 -42
- package/src/__tests__/background-workers-disk-pressure.test.ts +0 -2
- package/src/__tests__/btw-routes.test.ts +0 -2
- package/src/__tests__/build-persisted-content.test.ts +0 -2
- package/src/__tests__/call-controller.test.ts +0 -19
- package/src/__tests__/channel-guardian.test.ts +58 -94
- package/src/__tests__/channel-reply-delivery.test.ts +0 -2
- package/src/__tests__/compaction-events.test.ts +0 -2
- package/src/__tests__/compaction.benchmark.test.ts +0 -2
- package/src/__tests__/compactor-call-site-logging.test.ts +0 -2
- package/src/__tests__/compactor-low-watermark-cut.test.ts +0 -2
- package/src/__tests__/compactor-preserved-tail-count.test.ts +0 -2
- package/src/__tests__/compactor-summary-call-truncation.test.ts +0 -2
- package/src/__tests__/compactor-web-search-strip.test.ts +0 -2
- package/src/__tests__/config-loader-backfill.test.ts +10 -123
- package/src/__tests__/config-schema.test.ts +0 -1
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +29 -31
- package/src/__tests__/contacts-relay-reads.test.ts +15 -13
- package/src/__tests__/conversation-abort-tool-results.test.ts +0 -2
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +0 -2
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +0 -2
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +0 -2
- package/src/__tests__/conversation-agent-loop.test.ts +0 -134
- package/src/__tests__/conversation-analysis-routes.test.ts +0 -2
- package/src/__tests__/conversation-app-control-lifecycle.test.ts +0 -2
- package/src/__tests__/conversation-confirmation-signals.test.ts +0 -2
- package/src/__tests__/conversation-history-web-search.test.ts +0 -2
- package/src/__tests__/conversation-load-history-repair.test.ts +0 -2
- package/src/__tests__/conversation-load-history-stripped.test.ts +0 -2
- package/src/__tests__/conversation-pairing.test.ts +0 -2
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +0 -2
- package/src/__tests__/conversation-process-callsite.test.ts +0 -2
- package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -2
- package/src/__tests__/conversation-queue.test.ts +0 -91
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +0 -14
- package/src/__tests__/conversation-routes-slash-commands.test.ts +0 -14
- package/src/__tests__/conversation-slash-queue.test.ts +0 -2
- package/src/__tests__/conversation-slash-unknown.test.ts +0 -2
- package/src/__tests__/conversation-speed-override.test.ts +0 -2
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +0 -29
- package/src/__tests__/conversation-title-service.test.ts +0 -2
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +0 -47
- package/src/__tests__/conversation-usage.test.ts +0 -2
- package/src/__tests__/conversation-workspace-cache-state.test.ts +0 -2
- package/src/__tests__/conversation-workspace-injection.test.ts +0 -2
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -2
- package/src/__tests__/credential-security-invariants.test.ts +1 -1
- package/src/__tests__/db-migration-rollback.test.ts +171 -205
- package/src/__tests__/db-test-helpers.ts +4 -5
- package/src/__tests__/deterministic-verification-control-plane.test.ts +2 -4
- package/src/__tests__/disk-pressure-guard.test.ts +0 -41
- package/src/__tests__/dm-persistence.test.ts +0 -2
- package/src/__tests__/emit-signal-routing-intent.test.ts +5 -10
- package/src/__tests__/events-dev-bypass-actor.test.ts +1 -7
- package/src/__tests__/exploration-drift-hook.test.ts +2 -3
- package/src/__tests__/filing-service.test.ts +0 -2
- package/src/__tests__/guardian-binding-drift-heal.test.ts +10 -75
- package/src/__tests__/guardian-dispatch.test.ts +1 -95
- package/src/__tests__/guardian-outbound-http.test.ts +0 -13
- package/src/__tests__/heartbeat-disk-pressure.test.ts +0 -2
- package/src/__tests__/heartbeat-service.test.ts +0 -2
- package/src/__tests__/helpers/channel-test-adapter.ts +7 -1
- package/src/__tests__/host-app-control-routes.test.ts +30 -24
- package/src/__tests__/host-bash-routes.test.ts +41 -31
- package/src/__tests__/host-browser-routes.test.ts +32 -26
- package/src/__tests__/host-cu-routes-targeted.test.ts +33 -25
- package/src/__tests__/host-file-routes-targeted.test.ts +52 -40
- package/src/__tests__/host-transfer-routes-targeted.test.ts +43 -31
- package/src/__tests__/http-user-message-parity.test.ts +8 -290
- package/src/__tests__/inbound-invite-redemption.test.ts +0 -28
- package/src/__tests__/inbound-slack-persistence.test.ts +0 -2
- package/src/__tests__/invite-redemption-service.test.ts +0 -198
- package/src/__tests__/llm-context-normalization.test.ts +0 -105
- package/src/__tests__/llm-request-log-error-payload.test.ts +9 -71
- package/src/__tests__/llm-usage-store.test.ts +0 -25
- package/src/__tests__/mcp-health-check.test.ts +1 -2
- package/src/__tests__/media-stream-server-integration.test.ts +0 -127
- package/src/__tests__/memory-retrieval-hook.test.ts +0 -2
- package/src/__tests__/messaging-send-tool.test.ts +0 -2
- package/src/__tests__/migration-import-from-url.test.ts +2 -2
- package/src/__tests__/mtime-cache.test.ts +5 -146
- package/src/__tests__/native-web-search.test.ts +0 -2
- package/src/__tests__/non-member-access-request.test.ts +17 -189
- package/src/__tests__/notification-broadcaster.test.ts +0 -4
- package/src/__tests__/notification-decision-recipient-context.test.ts +32 -33
- package/src/__tests__/notification-deep-link.test.ts +0 -6
- package/src/__tests__/notification-guardian-path.test.ts +0 -19
- package/src/__tests__/openai-provider.test.ts +12 -22
- package/src/__tests__/openai-responses-provider.test.ts +2 -12
- package/src/__tests__/outbound-slack-persistence.test.ts +0 -2
- package/src/__tests__/pending-interactions-resolved-event.test.ts +4 -7
- package/src/__tests__/persistence-secret-redaction.test.ts +0 -2
- package/src/__tests__/plugin-bootstrap.test.ts +73 -3
- package/src/__tests__/plugin-route-contribution.test.ts +17 -4
- package/src/__tests__/plugin-tool-contribution.test.ts +18 -3
- package/src/__tests__/plugin-types.test.ts +2 -0
- package/src/__tests__/process-message-background-slack.test.ts +0 -2
- package/src/__tests__/process-message-display-content.test.ts +0 -2
- package/src/__tests__/provider-error-scenarios.test.ts +4 -5
- package/src/__tests__/provider-usage-tracking.test.ts +0 -39
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +0 -2
- package/src/__tests__/registry.test.ts +1 -4
- package/src/__tests__/relay-server.test.ts +25 -694
- package/src/__tests__/runtime-attachment-metadata.test.ts +1 -0
- package/src/__tests__/secret-ingress-http.test.ts +0 -14
- package/src/__tests__/send-endpoint-busy.test.ts +8 -30
- package/src/__tests__/skills.test.ts +0 -44
- package/src/__tests__/slack-inbound-verification.test.ts +2 -47
- package/src/__tests__/stt-hints.test.ts +13 -44
- package/src/__tests__/subagent-detail.test.ts +0 -27
- package/src/__tests__/subagent-disposal.test.ts +0 -65
- package/src/__tests__/subagent-notify-parent.test.ts +0 -2
- package/src/__tests__/subagent-role-registry.test.ts +2 -7
- package/src/__tests__/subagent-spawn-tool-fork.test.ts +0 -2
- package/src/__tests__/subagent-tools.test.ts +0 -2
- package/src/__tests__/suggestion-routes.test.ts +0 -2
- package/src/__tests__/title-generate-hook.test.ts +0 -2
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -2
- package/src/__tests__/tool-executor.test.ts +11 -16
- package/src/__tests__/tool-preview-lifecycle.test.ts +0 -2
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +0 -2
- package/src/__tests__/tool-start-timestamp.test.ts +0 -2
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +10 -10
- package/src/__tests__/twilio-routes.test.ts +0 -96
- package/src/__tests__/ui-file-upload-surface.test.ts +0 -86
- package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
- package/src/__tests__/voice-invite-redemption.test.ts +0 -33
- package/src/__tests__/web-search-backend-failure.test.ts +0 -2
- package/src/__tests__/workspace-migration-remove-hooks.test.ts +35 -14
- package/src/__tests__/workspace-tool-loader.test.ts +2 -195
- package/src/__tests__/workspace-tools-watcher-flag.test.ts +70 -0
- package/src/agent/loop.ts +0 -56
- package/src/api/index.ts +1 -19
- package/src/api/responses/llm-request-log-entry.ts +0 -29
- package/src/api/responses/subagent-detail.ts +0 -17
- package/src/api/surfaces.ts +3 -39
- package/src/approvals/guardian-request-resolvers.ts +11 -1
- package/src/calls/__tests__/relay-setup-router.test.ts +4 -262
- package/src/calls/call-domain.ts +3 -3
- package/src/calls/guardian-dispatch.ts +8 -10
- package/src/calls/inbound-trust-reader.ts +1 -17
- package/src/calls/media-stream-server.ts +0 -21
- package/src/calls/relay-server.ts +50 -167
- package/src/calls/relay-setup-router.ts +7 -37
- package/src/calls/relay-verification.ts +4 -4
- package/src/calls/stt-hints.ts +12 -9
- package/src/calls/twilio-routes.ts +4 -14
- package/src/channels/types.ts +20 -10
- package/src/cli/commands/__tests__/cache.test.ts +1 -8
- package/src/cli/commands/cache.ts +181 -194
- package/src/cli/commands/db/__tests__/repair.test.ts +5 -6
- package/src/cli/commands/db/status.ts +1 -37
- package/src/cli/commands/mcp.ts +218 -252
- package/src/cli/commands/memory/index.ts +0 -2
- package/src/cli/commands/plugins.ts +3 -75
- package/src/cli/lib/__tests__/install-from-github.test.ts +0 -102
- package/src/cli/lib/__tests__/list-installed-plugins.test.ts +1 -160
- package/src/cli/lib/list-installed-plugins.ts +1 -179
- package/src/config/__tests__/sync-gated-profiles.test.ts +3 -11
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +17 -27
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +3 -13
- package/src/config/bundled-skills/subagent/SKILL.md +1 -1
- package/src/config/bundled-skills/subagent/TOOLS.json +1 -1
- package/src/config/feature-flag-registry.json +13 -5
- package/src/config/loader.ts +5 -38
- package/src/config/schemas/__tests__/memory-v3.test.ts +0 -1
- package/src/config/schemas/memory-lifecycle.ts +0 -12
- package/src/config/schemas/memory-v3.ts +0 -7
- package/src/config/schemas/memory.ts +0 -4
- package/src/config/schemas/timeouts.ts +0 -8
- package/src/config/seed-inference-profiles.ts +11 -21
- package/src/config/skills.ts +5 -27
- package/src/config/sync-gated-profiles.ts +13 -12
- package/src/contacts/contacts-write.ts +0 -3
- package/src/daemon/assistant-attachments.ts +4 -27
- package/src/daemon/conversation-agent-loop.ts +0 -28
- package/src/daemon/conversation-process.ts +16 -35
- package/src/daemon/conversation-surfaces.ts +38 -111
- package/src/daemon/conversation-tool-setup.ts +16 -50
- package/src/daemon/conversation.ts +1 -13
- package/src/daemon/disk-pressure-guard.ts +2 -12
- package/src/daemon/event-loop-watchdog.ts +1 -28
- package/src/daemon/external-plugins-bootstrap.ts +34 -4
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -25
- package/src/daemon/handlers/config-a2a.ts +14 -6
- package/src/daemon/handlers/config-channels.ts +22 -78
- package/src/daemon/handlers/conversations.ts +0 -77
- package/src/daemon/lifecycle.ts +0 -4
- package/src/daemon/mcp-reload-service.ts +0 -10
- package/src/daemon/memory-v2-startup.test.ts +0 -72
- package/src/daemon/memory-v2-startup.ts +19 -87
- package/src/daemon/message-types/conversations.ts +0 -2
- package/src/daemon/message-types/surfaces.ts +12 -12
- package/src/daemon/server.ts +4 -0
- package/src/daemon/shutdown-handlers.ts +0 -20
- package/src/daemon/tool-setup-types.ts +0 -9
- package/src/daemon/workspace-tools-watcher.ts +328 -0
- package/src/ipc/__tests__/clients-list-ipc.test.ts +1 -1
- package/src/ipc/assistant-server.ts +2 -2
- package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +0 -1
- package/src/mcp/client.ts +1 -15
- package/src/mcp/mcp-auth-orchestrator.ts +1 -6
- package/src/mcp/mcp-oauth-provider.ts +8 -19
- package/src/memory/__tests__/memory-retrospective-job.test.ts +0 -8
- package/src/memory/conversation-crud.ts +0 -38
- package/src/memory/db-connection.ts +3 -22
- package/src/memory/db-init.ts +502 -36
- package/src/memory/db-singleton.ts +4 -6
- package/src/memory/jobs-worker.ts +0 -58
- package/src/memory/llm-request-log-store.ts +1 -26
- package/src/memory/llm-usage-store.ts +20 -48
- package/src/memory/memory-retrospective-job.ts +8 -9
- package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +56 -130
- package/src/memory/migrations/__tests__/run-migrations.test.ts +2 -2
- package/src/memory/migrations/registry.ts +573 -0
- package/src/memory/migrations/run-migrations.ts +6 -90
- package/src/memory/migrations/validate-migration-state.ts +66 -101
- package/src/memory/schema/conversations.ts +0 -9
- package/src/memory/schema/infrastructure.ts +0 -20
- package/src/memory/v2/__tests__/cli-command-store.test.ts +0 -25
- package/src/memory/v2/__tests__/skill-store.test.ts +0 -80
- package/src/memory/v2/cli-command-store.ts +38 -75
- package/src/memory/v2/prompts/consolidation.ts +82 -13
- package/src/memory/v2/prompts/router.ts +93 -21
- package/src/memory/v2/skill-store.ts +31 -68
- package/src/notifications/__tests__/broadcaster.test.ts +8 -16
- package/src/notifications/__tests__/decision-engine.test.ts +9 -78
- package/src/notifications/broadcaster.ts +1 -8
- package/src/notifications/decision-engine.ts +7 -15
- package/src/notifications/destination-resolver.ts +24 -68
- package/src/notifications/emit-signal.ts +14 -39
- package/src/permissions/question-prompter.test.ts +1 -1
- package/src/permissions/question-prompter.ts +4 -7
- package/src/plugin-api/index.ts +6 -6
- package/src/plugin-api/types.ts +5 -3
- package/src/plugin-api/vision-support.test.ts +4 -28
- package/src/plugin-api/vision-support.ts +31 -66
- package/src/plugins/defaults/advisor/__tests__/consult.test.ts +0 -161
- package/src/plugins/defaults/advisor/consult.ts +6 -110
- package/src/plugins/defaults/advisor/steering.ts +2 -14
- package/src/plugins/defaults/advisor/tools/advisor.ts +5 -32
- package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +1 -2
- package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +7 -47
- package/src/plugins/defaults/image-fallback/hooks/post-tool-use.ts +11 -10
- package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +20 -12
- package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +11 -42
- package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +3 -33
- package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +4 -48
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +8 -4
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +15 -43
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +2 -11
- package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +13 -77
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +11 -12
- package/src/plugins/mtime-cache.ts +291 -76
- package/src/plugins/pipeline.ts +13 -111
- package/src/plugins/types.ts +2 -0
- package/src/providers/anthropic/client.ts +0 -5
- package/src/providers/call-site-routing.ts +0 -4
- package/src/providers/model-catalog.ts +0 -16
- package/src/providers/openai/__tests__/api-error-detail.test.ts +120 -0
- package/src/providers/openai/chat-completions-provider.ts +83 -37
- package/src/providers/openai/responses-provider.ts +46 -50
- package/src/providers/openrouter/client.ts +0 -5
- package/src/providers/provider-send-message.ts +0 -4
- package/src/providers/ratelimit.ts +0 -4
- package/src/providers/retry.ts +0 -4
- package/src/providers/types.ts +0 -9
- package/src/providers/usage-tracking.ts +0 -4
- package/src/runtime/__tests__/trust-verdict-consumer.test.ts +3 -335
- package/src/runtime/access-request-helper.ts +39 -19
- package/src/runtime/actor-trust-resolver.ts +2 -2
- package/src/runtime/assistant-event-hub.ts +1 -1
- package/src/runtime/assistant-stream-state.ts +2 -9
- package/src/runtime/auth/require-bound-guardian.ts +11 -21
- package/src/runtime/channel-verification-service.ts +31 -56
- package/src/runtime/confirmation-request-guardian-bridge.ts +3 -3
- package/src/runtime/guardian-vellum-migration.ts +7 -66
- package/src/runtime/invite-redemption-service.ts +187 -198
- package/src/runtime/local-actor-identity.ts +11 -76
- package/src/runtime/pending-interactions.ts +1 -11
- package/src/runtime/routes/__tests__/channel-verification-revoke.test.ts +5 -56
- package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +0 -187
- package/src/runtime/routes/browser-routes.ts +1 -1
- package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +5 -13
- package/src/runtime/routes/channel-verification-routes.ts +3 -3
- package/src/runtime/routes/contact-routes.ts +32 -8
- package/src/runtime/routes/conversation-cli-routes.ts +5 -4
- package/src/runtime/routes/conversation-list-routes.ts +7 -4
- package/src/runtime/routes/conversation-query-routes.ts +0 -72
- package/src/runtime/routes/conversation-routes.ts +85 -84
- package/src/runtime/routes/events-routes.ts +2 -2
- package/src/runtime/routes/global-search-routes.ts +1 -3
- package/src/runtime/routes/guardian-action-routes.ts +5 -4
- package/src/runtime/routes/host-app-control-routes.ts +4 -5
- package/src/runtime/routes/host-bash-routes.ts +4 -5
- package/src/runtime/routes/host-browser-routes.ts +11 -9
- package/src/runtime/routes/host-cu-routes.ts +4 -5
- package/src/runtime/routes/host-file-routes.ts +4 -5
- 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 +2 -3
- package/src/runtime/routes/inbound-message-handler.ts +5 -5
- package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +5 -97
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +49 -61
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +4 -16
- package/src/runtime/routes/inbound-stages/escalation-intercept.ts +7 -7
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +8 -21
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +3 -14
- package/src/runtime/routes/index.ts +0 -2
- package/src/runtime/routes/llm-context-normalization.ts +0 -83
- package/src/runtime/routes/mcp-auth-routes.ts +19 -171
- package/src/runtime/routes/migration-rollback-routes.ts +3 -4
- package/src/runtime/routes/migration-routes.ts +1 -4
- package/src/runtime/routes/subagents-routes.ts +0 -5
- package/src/runtime/routes/surface-action-routes.ts +56 -42
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +0 -1
- package/src/runtime/services/conversation-serializer.ts +9 -7
- package/src/runtime/tool-grant-request-helper.ts +3 -3
- package/src/runtime/trust-verdict-consumer.ts +9 -85
- package/src/runtime/verification-outbound-actions.ts +18 -18
- package/src/signals/user-message.ts +0 -16
- package/src/subagent/manager.ts +0 -9
- package/src/subagent/types.ts +3 -3
- package/src/telemetry/types.ts +1 -34
- package/src/telemetry/usage-telemetry-reporter.test.ts +2 -3
- package/src/telemetry/usage-telemetry-reporter.ts +3 -87
- package/src/tools/ask-question/ask-question-tool.test.ts +0 -29
- package/src/tools/ask-question/ask-question-tool.ts +0 -13
- package/src/tools/executor.ts +4 -4
- package/src/tools/registry.ts +0 -18
- package/src/tools/shared/filesystem/path-policy.ts +5 -12
- package/src/tools/tool-approval-handler.ts +1 -1
- package/src/tools/tool-defaults.ts +2 -9
- package/src/tools/tool-manifest.ts +0 -3
- package/src/tools/types.ts +2 -17
- package/src/tools/workspace-tools/loader.ts +244 -348
- package/src/util/errors.ts +1 -26
- package/src/util/platform.ts +0 -5
- package/src/workflows/library.test.ts +0 -140
- package/src/workflows/library.ts +28 -82
- package/src/workspace/migrations/017-seed-persona-dirs.ts +34 -3
- package/src/workspace/migrations/019-scope-journal-to-guardian.ts +24 -3
- package/src/workspace/migrations/048-remove-workspace-hooks.ts +66 -14
- package/src/workspace/migrations/registry.ts +0 -2
- package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-delivery-contract.test.ts +0 -91
- package/node_modules/@vellumai/gateway-client/src/guardian-delivery-contract.ts +0 -48
- package/node_modules/@vellumai/service-contracts/src/__tests__/channels.test.ts +0 -28
- package/node_modules/@vellumai/service-contracts/src/channels.ts +0 -41
- package/src/__tests__/code-search-tool.test.ts +0 -585
- package/src/__tests__/guardian-expiry-notifier.test.ts +0 -282
- package/src/__tests__/mcp-config-secret-boundary.test.ts +0 -390
- package/src/__tests__/plugin-pipeline.test.ts +0 -96
- package/src/__tests__/sse-actor-principal-guardian-source.test.ts +0 -102
- package/src/__tests__/steer-on-enqueue-question.test.ts +0 -181
- package/src/__tests__/workspace-migration-111-prune-seeded-callsite-defaults.test.ts +0 -208
- package/src/agent/loop-exclusive-tool.test.ts +0 -150
- package/src/api/constants/sse-replay.ts +0 -41
- package/src/api/events/conversation-notice.ts +0 -26
- package/src/approvals/guardian-channel-delivery.ts +0 -30
- package/src/approvals/guardian-expiry-notifier.ts +0 -148
- package/src/cli/commands/memory/__tests__/worker.test.ts +0 -302
- package/src/cli/commands/memory/worker.ts +0 -175
- package/src/config/__tests__/loader-callsite-strip-fallback.test.ts +0 -143
- package/src/config/prune-seeded-callsite-defaults.ts +0 -110
- package/src/contacts/__tests__/contacts-write-revoke-relay.test.ts +0 -129
- package/src/contacts/__tests__/guardian-delivery-reader.test.ts +0 -312
- package/src/contacts/__tests__/member-write-relay.test.ts +0 -202
- package/src/contacts/guardian-delivery-reader.ts +0 -223
- package/src/contacts/member-write-relay.ts +0 -189
- package/src/daemon/conversation-notices.ts +0 -60
- package/src/daemon/handlers/__tests__/config-channels.test.ts +0 -225
- package/src/hooks/hook-loader.ts +0 -341
- package/src/mcp/mcp-header-store.ts +0 -134
- package/src/memory/__tests__/301-create-watchdog-events.test.ts +0 -110
- package/src/memory/__tests__/prompt-override.test.ts +0 -192
- package/src/memory/__tests__/watchdog-events-store.test.ts +0 -161
- package/src/memory/migrations/300-add-processing-started-at.ts +0 -30
- package/src/memory/migrations/301-create-watchdog-events.ts +0 -45
- package/src/memory/migrations/__tests__/209-strip-thinking-from-consolidated.test.ts +0 -224
- package/src/memory/prompt-override.ts +0 -129
- package/src/memory/steps.ts +0 -573
- package/src/memory/watchdog-events-store.ts +0 -87
- package/src/memory/worker-control.ts +0 -118
- package/src/memory/worker-process.ts +0 -72
- package/src/notifications/__tests__/connected-channels.test.ts +0 -114
- package/src/notifications/__tests__/destination-resolver.test.ts +0 -256
- package/src/onboarding/checkin-event.test.ts +0 -222
- package/src/onboarding/checkin-event.ts +0 -321
- package/src/onboarding/schedule-checkin.ts +0 -190
- package/src/plugins/defaults/advisor/__tests__/context-pack-gating.test.ts +0 -106
- package/src/plugins/defaults/advisor/__tests__/context-pack.test.ts +0 -60
- package/src/plugins/defaults/advisor/context-pack.ts +0 -288
- package/src/plugins/defaults/memory-v3-shadow/pool-select.test.ts +0 -146
- package/src/plugins/surface-import.ts +0 -121
- package/src/providers/openai/__tests__/api-error-normalization.test.ts +0 -321
- package/src/providers/openai/api-error-normalization.ts +0 -270
- package/src/runtime/__tests__/channel-verification-service.test.ts +0 -133
- package/src/runtime/__tests__/guardian-vellum-migration.test.ts +0 -181
- package/src/runtime/__tests__/is-guardian-bound-for-channel.test.ts +0 -66
- package/src/runtime/__tests__/local-principal-trust.test.ts +0 -164
- package/src/runtime/anchored-guardian.test.ts +0 -156
- package/src/runtime/anchored-guardian.ts +0 -135
- package/src/runtime/auth/__tests__/require-bound-guardian.test.ts +0 -99
- package/src/runtime/local-principal-trust.ts +0 -52
- package/src/runtime/routes/__tests__/contact-routes.test.ts +0 -212
- package/src/runtime/routes/__tests__/global-search-routes.test.ts +0 -93
- package/src/runtime/routes/onboarding-checkin-routes.ts +0 -86
- package/src/tools/filesystem/search.ts +0 -543
- package/src/util/telemetry-db-path.ts +0 -24
- package/src/workspace/migrations/111-prune-seeded-callsite-defaults.ts +0 -134
|
@@ -1,42 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Per-surface mtime cache for user plugins
|
|
2
|
+
* Per-surface mtime cache for user plugins.
|
|
3
3
|
*
|
|
4
|
-
* Instead of caching whole `Plugin` objects,
|
|
5
|
-
*
|
|
6
|
-
* plugin **discovery** (which plugin directories exist, in what order) and the
|
|
7
|
-
* **tool** cache; the **hook** cache and every hook operation live in
|
|
8
|
-
* `../hooks/hook-loader.ts`. This module is the boot orchestrator: it scans the
|
|
9
|
-
* plugins directory, registers tools, and drives hook pre-import / init /
|
|
10
|
-
* shutdown by handing the discovered directories to the hook loader.
|
|
4
|
+
* Instead of caching whole `Plugin` objects, this module caches individual
|
|
5
|
+
* hooks and tools keyed by their source file's mtime. This means:
|
|
11
6
|
*
|
|
12
|
-
* - A changed
|
|
13
|
-
* plugin rebuild.
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
7
|
+
* - A changed hook file triggers a re-import of just that hook, not a full
|
|
8
|
+
* plugin rebuild.
|
|
9
|
+
* - The same machinery extends to workspace-driven hooks and tools in the
|
|
10
|
+
* future (PR B), since each surface is cached independently.
|
|
11
|
+
* - Plugins are never "registered" as a unit — we just register their tools
|
|
12
|
+
* and hooks into the global registries, then cache-bust them using mtime
|
|
13
|
+
* on reads.
|
|
17
14
|
*
|
|
18
15
|
* The cache is populated at boot by `loadUserPlugins()` and read on every
|
|
19
|
-
* `getHooksFor` / `getAllTools` call.
|
|
16
|
+
* `getHooksFor` / `getAllTools` call. When a surface file's mtime changes,
|
|
17
|
+
* the next read detects the mismatch, re-imports the file, and swaps the
|
|
18
|
+
* cached entry.
|
|
20
19
|
*/
|
|
21
20
|
|
|
22
|
-
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
21
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
23
22
|
import { join } from "node:path";
|
|
24
23
|
|
|
24
|
+
import { getConfig } from "../config/loader.js";
|
|
25
25
|
import { registerShutdownHook } from "../daemon/shutdown-registry.js";
|
|
26
|
-
import {
|
|
27
|
-
clearPluginHooks,
|
|
28
|
-
collectUserHooks,
|
|
29
|
-
evictHooksForOwner,
|
|
30
|
-
hasWorkspaceHooks,
|
|
31
|
-
preImportHooksDir,
|
|
32
|
-
preImportWorkspaceHooks,
|
|
33
|
-
resetHookCacheForTests,
|
|
34
|
-
runInitHook,
|
|
35
|
-
runShutdownHook,
|
|
36
|
-
WORKSPACE_HOOKS_OWNER,
|
|
37
|
-
} from "../hooks/hook-loader.js";
|
|
26
|
+
import { HOOKS } from "../plugin-api/constants.js";
|
|
38
27
|
import type {
|
|
39
28
|
PluginHookFn,
|
|
29
|
+
PluginInitContext,
|
|
40
30
|
PluginShutdownContext,
|
|
41
31
|
} from "../plugin-api/types.js";
|
|
42
32
|
import {
|
|
@@ -46,19 +36,14 @@ import {
|
|
|
46
36
|
import { finalizeTool } from "../tools/tool-defaults.js";
|
|
47
37
|
import type { Tool, ToolDefinition } from "../tools/types.js";
|
|
48
38
|
import { getLogger } from "../util/logger.js";
|
|
49
|
-
import { getWorkspacePluginsDir } from "../util/platform.js";
|
|
39
|
+
import { getWorkspaceDir, getWorkspacePluginsDir } from "../util/platform.js";
|
|
50
40
|
import { APP_VERSION } from "../version.js";
|
|
51
41
|
import {
|
|
52
42
|
deriveToolName,
|
|
43
|
+
importDefault,
|
|
53
44
|
listSurfaceDir,
|
|
54
45
|
parsePluginManifest,
|
|
55
46
|
} from "./external-plugin-loader.js";
|
|
56
|
-
import {
|
|
57
|
-
clearSurfaceImportInflight,
|
|
58
|
-
getMtime,
|
|
59
|
-
importWithTimeout,
|
|
60
|
-
setSurfaceImportTimeout,
|
|
61
|
-
} from "./surface-import.js";
|
|
62
47
|
|
|
63
48
|
// Re-export for type compat — consumers that import PluginHookFn from
|
|
64
49
|
// the mtime cache module still resolve.
|
|
@@ -66,6 +51,13 @@ export type { PluginHookFn } from "./types.js";
|
|
|
66
51
|
|
|
67
52
|
const log = getLogger("plugin-mtime-cache");
|
|
68
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Import timeout for surface file imports. Set by `populateCacheAtBoot` from
|
|
56
|
+
* the value passed by `loadUserPlugins`, and used by `getUserHooksFor` and
|
|
57
|
+
* `reconcilePluginTools` for runtime re-imports. Defaults to 10s.
|
|
58
|
+
*/
|
|
59
|
+
let importTimeoutMs = 10_000;
|
|
60
|
+
|
|
69
61
|
/**
|
|
70
62
|
* Cached install-date timestamps per plugin directory, so `scanPlugins`
|
|
71
63
|
* doesn't re-read `install-meta.json` on every turn. Populated on first
|
|
@@ -138,6 +130,16 @@ function getInstallDate(pluginDir: string): number {
|
|
|
138
130
|
|
|
139
131
|
// ─── Cache entries ───────────────────────────────────────────────────────────
|
|
140
132
|
|
|
133
|
+
/**
|
|
134
|
+
* A cached hook function plus the mtime of its source file. When the on-disk
|
|
135
|
+
* mtime changes, the hook is re-imported and the entry is replaced.
|
|
136
|
+
*/
|
|
137
|
+
interface CachedHook {
|
|
138
|
+
readonly hook: PluginHookFn;
|
|
139
|
+
/** mtimeMs of the source file this hook was imported from. */
|
|
140
|
+
readonly sourceMtime: number;
|
|
141
|
+
}
|
|
142
|
+
|
|
141
143
|
/**
|
|
142
144
|
* A cached tool plus the mtime of its source file. When the on-disk mtime
|
|
143
145
|
* changes, the tool is re-imported, the old tool is unregistered from the
|
|
@@ -153,12 +155,25 @@ interface CachedTool {
|
|
|
153
155
|
|
|
154
156
|
// ─── Internal state ──────────────────────────────────────────────────────────
|
|
155
157
|
|
|
158
|
+
/**
|
|
159
|
+
* Cached hooks keyed by `${pluginName}/${hookName}`. The key includes the
|
|
160
|
+
* plugin name so hooks from different plugins don't collide.
|
|
161
|
+
*/
|
|
162
|
+
const hookCache = new Map<string, CachedHook>();
|
|
163
|
+
|
|
156
164
|
/**
|
|
157
165
|
* Cached tools keyed by `${pluginName}/${toolName}`. The key includes the
|
|
158
166
|
* plugin name so tools from different plugins don't collide.
|
|
159
167
|
*/
|
|
160
168
|
const toolCache = new Map<string, CachedTool>();
|
|
161
169
|
|
|
170
|
+
/**
|
|
171
|
+
* In-flight import promises, keyed by file path. Prevents duplicate
|
|
172
|
+
* `import()` calls when multiple readers request the same surface
|
|
173
|
+
* concurrently.
|
|
174
|
+
*/
|
|
175
|
+
const inflight = new Map<string, Promise<unknown>>();
|
|
176
|
+
|
|
162
177
|
/**
|
|
163
178
|
* Plugin directories discovered at boot, in discovery order. Maps directory
|
|
164
179
|
* path to the plugin's scope-stripped manifest name so eviction can find
|
|
@@ -174,19 +189,94 @@ const discoveredPluginDirs = new Map<string, string>();
|
|
|
174
189
|
*/
|
|
175
190
|
const disabledPluginDirs = new Set<string>();
|
|
176
191
|
|
|
177
|
-
// ───
|
|
192
|
+
// ─── Mtime helpers ───────────────────────────────────────────────────────────
|
|
178
193
|
|
|
179
194
|
/**
|
|
180
|
-
* Get
|
|
181
|
-
*
|
|
182
|
-
|
|
183
|
-
|
|
195
|
+
* Get the mtimeMs of a file, or 0 if the file doesn't exist or can't be
|
|
196
|
+
* stat'd.
|
|
197
|
+
*/
|
|
198
|
+
function getMtime(filePath: string): number {
|
|
199
|
+
try {
|
|
200
|
+
return statSync(filePath).mtimeMs;
|
|
201
|
+
} catch {
|
|
202
|
+
return 0;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ─── Hook cache ──────────────────────────────────────────────────────────────
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Cache key for a hook: `${pluginName}/${hookName}`.
|
|
210
|
+
*/
|
|
211
|
+
function hookKey(pluginName: string, hookName: string): string {
|
|
212
|
+
return `${pluginName}/${hookName}`;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Get all hooks for a given event name from user plugins, re-importing
|
|
217
|
+
* any whose source files have changed since the cache was populated.
|
|
218
|
+
*
|
|
219
|
+
* Also scans for newly added plugins and hooks (via directory listing).
|
|
220
|
+
* Deleted plugins/hooks are skipped naturally (their directories/files
|
|
221
|
+
* no longer appear in the listing).
|
|
184
222
|
*/
|
|
185
223
|
export async function getUserHooksFor<TCtx = unknown>(
|
|
186
224
|
hookName: string,
|
|
187
225
|
): Promise<PluginHookFn<TCtx>[]> {
|
|
188
226
|
await scanPlugins();
|
|
189
|
-
|
|
227
|
+
|
|
228
|
+
const out: PluginHookFn<TCtx>[] = [];
|
|
229
|
+
|
|
230
|
+
for (const [pluginDir, pluginName] of discoveredPluginDirs) {
|
|
231
|
+
const hooksDir = join(pluginDir, "hooks");
|
|
232
|
+
const surfaceFiles = listSurfaceDir(hooksDir);
|
|
233
|
+
const hookFile = surfaceFiles.find((f) => f.name === hookName);
|
|
234
|
+
if (hookFile === undefined) continue;
|
|
235
|
+
|
|
236
|
+
const key = hookKey(pluginName, hookName);
|
|
237
|
+
const currentMtime = getMtime(hookFile.path);
|
|
238
|
+
|
|
239
|
+
// Cache hit — same mtime.
|
|
240
|
+
const cached = hookCache.get(key);
|
|
241
|
+
if (
|
|
242
|
+
cached !== undefined &&
|
|
243
|
+
cached.sourceMtime === currentMtime &&
|
|
244
|
+
currentMtime > 0
|
|
245
|
+
) {
|
|
246
|
+
out.push(cached.hook as PluginHookFn<TCtx>);
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Cache miss — re-import.
|
|
251
|
+
if (currentMtime === 0) {
|
|
252
|
+
// File was deleted — evict cache entry.
|
|
253
|
+
hookCache.delete(key);
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
try {
|
|
258
|
+
const hook = await importWithTimeout<PluginHookFn>(
|
|
259
|
+
hookFile.path,
|
|
260
|
+
importTimeoutMs,
|
|
261
|
+
);
|
|
262
|
+
if (hook === undefined || typeof hook !== "function") {
|
|
263
|
+
log.error(
|
|
264
|
+
{ plugin: pluginName, hook: hookName, path: hookFile.path },
|
|
265
|
+
`hook ${hookName} default export must be a function (got ${typeof hook}) — skipping`,
|
|
266
|
+
);
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
hookCache.set(key, { hook, sourceMtime: currentMtime });
|
|
270
|
+
out.push(hook as PluginHookFn<TCtx>);
|
|
271
|
+
} catch (err) {
|
|
272
|
+
log.error(
|
|
273
|
+
{ err, plugin: pluginName, hook: hookName, path: hookFile.path },
|
|
274
|
+
`Failed to import hook ${hookName} from ${hookFile.path}`,
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return out;
|
|
190
280
|
}
|
|
191
281
|
|
|
192
282
|
// ─── Tool cache ──────────────────────────────────────────────────────────────
|
|
@@ -236,7 +326,10 @@ async function reconcilePluginTools(
|
|
|
236
326
|
}
|
|
237
327
|
|
|
238
328
|
try {
|
|
239
|
-
const toolSpec = await importWithTimeout<ToolDefinition>(
|
|
329
|
+
const toolSpec = await importWithTimeout<ToolDefinition>(
|
|
330
|
+
file.path,
|
|
331
|
+
importTimeoutMs,
|
|
332
|
+
);
|
|
240
333
|
if (
|
|
241
334
|
toolSpec === undefined ||
|
|
242
335
|
toolSpec === null ||
|
|
@@ -377,13 +470,17 @@ async function evictPlugin(
|
|
|
377
470
|
pluginDir: string,
|
|
378
471
|
pluginName: string,
|
|
379
472
|
): Promise<void> {
|
|
380
|
-
// Evict hooks
|
|
381
|
-
|
|
473
|
+
// Evict hooks.
|
|
474
|
+
const hookPrefix = `${pluginName}/`;
|
|
475
|
+
for (const key of hookCache.keys()) {
|
|
476
|
+
if (key.startsWith(hookPrefix)) {
|
|
477
|
+
hookCache.delete(key);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
382
480
|
|
|
383
481
|
// Evict tools.
|
|
384
|
-
const toolPrefix = `${pluginName}/`;
|
|
385
482
|
for (const key of toolCache.keys()) {
|
|
386
|
-
if (key.startsWith(
|
|
483
|
+
if (key.startsWith(hookPrefix)) {
|
|
387
484
|
toolCache.delete(key);
|
|
388
485
|
}
|
|
389
486
|
}
|
|
@@ -397,30 +494,83 @@ async function evictPlugin(
|
|
|
397
494
|
}
|
|
398
495
|
|
|
399
496
|
/**
|
|
400
|
-
* Evict all
|
|
401
|
-
* entirely). Standalone workspace hooks are preserved by the hook loader:
|
|
402
|
-
* they live outside the plugins directory, so the absence of any plugin must
|
|
403
|
-
* not evict them.
|
|
497
|
+
* Evict all cache entries (when the plugins directory is gone entirely).
|
|
404
498
|
*/
|
|
405
499
|
async function evictAll(): Promise<void> {
|
|
406
|
-
|
|
500
|
+
hookCache.clear();
|
|
407
501
|
toolCache.clear();
|
|
408
502
|
discoveredPluginDirs.clear();
|
|
409
503
|
installDateCache.clear();
|
|
410
504
|
disabledPluginDirs.clear();
|
|
411
505
|
}
|
|
412
506
|
|
|
507
|
+
// ─── Import dedup ────────────────────────────────────────────────────────────
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Import a module's default export with a timeout. If the import doesn't
|
|
511
|
+
* resolve within `timeoutMs`, logs a warning and returns `undefined` so
|
|
512
|
+
* a hanging plugin module doesn't block daemon startup indefinitely.
|
|
513
|
+
*/
|
|
514
|
+
async function importWithTimeout<T>(
|
|
515
|
+
filePath: string,
|
|
516
|
+
timeoutMs: number,
|
|
517
|
+
): Promise<T | undefined> {
|
|
518
|
+
let timeoutHandle: ReturnType<typeof setTimeout> | undefined;
|
|
519
|
+
try {
|
|
520
|
+
const timeoutSentinel = Symbol("import-timeout");
|
|
521
|
+
const importPromise = importWithDedup<T>(filePath);
|
|
522
|
+
const timeoutPromise = new Promise<typeof timeoutSentinel>((resolve) => {
|
|
523
|
+
timeoutHandle = setTimeout(() => resolve(timeoutSentinel), timeoutMs);
|
|
524
|
+
});
|
|
525
|
+
const result = await Promise.race([importPromise, timeoutPromise]);
|
|
526
|
+
if (result === timeoutSentinel) {
|
|
527
|
+
importPromise.catch(() => {
|
|
528
|
+
/* swallow — late rejection from abandoned import */
|
|
529
|
+
});
|
|
530
|
+
log.warn(
|
|
531
|
+
{ filePath, timeoutMs },
|
|
532
|
+
`Import timed out after ${timeoutMs}ms — skipping surface`,
|
|
533
|
+
);
|
|
534
|
+
return undefined;
|
|
535
|
+
}
|
|
536
|
+
return result as T;
|
|
537
|
+
} finally {
|
|
538
|
+
if (timeoutHandle !== undefined) clearTimeout(timeoutHandle);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Import a module's default export, deduplicating concurrent imports for
|
|
544
|
+
* the same file path. This prevents two readers from triggering duplicate
|
|
545
|
+
* `import()` calls when they request the same surface simultaneously.
|
|
546
|
+
*
|
|
547
|
+
* Note: Bun caches `import()` by URL within a process, so the dedup is
|
|
548
|
+
* primarily about avoiding redundant async work, not about cache-busting.
|
|
549
|
+
*/
|
|
550
|
+
async function importWithDedup<T>(filePath: string): Promise<T> {
|
|
551
|
+
let promise = inflight.get(filePath);
|
|
552
|
+
if (promise === undefined) {
|
|
553
|
+
promise = importDefault<T>(filePath);
|
|
554
|
+
inflight.set(filePath, promise);
|
|
555
|
+
}
|
|
556
|
+
try {
|
|
557
|
+
return (await promise) as T;
|
|
558
|
+
} finally {
|
|
559
|
+
inflight.delete(filePath);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
413
563
|
// ─── Boot population ─────────────────────────────────────────────────────────
|
|
414
564
|
|
|
415
565
|
/**
|
|
416
|
-
* Plugins
|
|
417
|
-
*
|
|
418
|
-
*
|
|
566
|
+
* Plugins that were fully activated at boot (tools registered + init hook
|
|
567
|
+
* run). Used by the shutdown hook to tear down only what was actually
|
|
568
|
+
* brought up.
|
|
419
569
|
*/
|
|
420
570
|
const activatedPlugins: Array<{ name: string }> = [];
|
|
421
571
|
|
|
422
572
|
/**
|
|
423
|
-
* Populate the
|
|
573
|
+
* Populate the cache at boot by scanning the plugins directory once,
|
|
424
574
|
* importing all surfaces, registering tools into the tool registry,
|
|
425
575
|
* running `init` hooks, and installing a shutdown hook.
|
|
426
576
|
*
|
|
@@ -437,7 +587,7 @@ export async function populateCacheAtBoot(
|
|
|
437
587
|
opts: { importTimeoutMs?: number } = {},
|
|
438
588
|
): Promise<void> {
|
|
439
589
|
if (opts.importTimeoutMs !== undefined) {
|
|
440
|
-
|
|
590
|
+
importTimeoutMs = opts.importTimeoutMs;
|
|
441
591
|
}
|
|
442
592
|
|
|
443
593
|
await scanPlugins();
|
|
@@ -448,7 +598,28 @@ export async function populateCacheAtBoot(
|
|
|
448
598
|
|
|
449
599
|
for (const [pluginDir, pluginName] of discoveredPluginDirs) {
|
|
450
600
|
// Pre-import all hooks so the first turn doesn't pay the import cost.
|
|
451
|
-
|
|
601
|
+
const hooksDir = join(pluginDir, "hooks");
|
|
602
|
+
const hookFiles = listSurfaceDir(hooksDir);
|
|
603
|
+
for (const file of hookFiles) {
|
|
604
|
+
const key = hookKey(pluginName, file.name);
|
|
605
|
+
const currentMtime = getMtime(file.path);
|
|
606
|
+
if (currentMtime === 0) continue;
|
|
607
|
+
|
|
608
|
+
try {
|
|
609
|
+
const hook = await importWithTimeout<PluginHookFn>(
|
|
610
|
+
file.path,
|
|
611
|
+
importTimeoutMs,
|
|
612
|
+
);
|
|
613
|
+
if (hook !== undefined && typeof hook === "function") {
|
|
614
|
+
hookCache.set(key, { hook, sourceMtime: currentMtime });
|
|
615
|
+
}
|
|
616
|
+
} catch (err) {
|
|
617
|
+
log.error(
|
|
618
|
+
{ err, plugin: pluginName, hook: file.name, path: file.path },
|
|
619
|
+
`Failed to pre-import hook ${file.name}`,
|
|
620
|
+
);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
452
623
|
|
|
453
624
|
// Register user plugin tools into the global tool registry so
|
|
454
625
|
// `getAllTools()` and `getTool()` can find them. Tools were already
|
|
@@ -472,33 +643,38 @@ export async function populateCacheAtBoot(
|
|
|
472
643
|
}
|
|
473
644
|
|
|
474
645
|
// Run the `init` hook if present.
|
|
475
|
-
|
|
646
|
+
const initHookEntry = hookCache.get(hookKey(pluginName, HOOKS.INIT));
|
|
647
|
+
if (initHookEntry !== undefined) {
|
|
648
|
+
try {
|
|
649
|
+
const initContext: PluginInitContext = {
|
|
650
|
+
config: getConfig().plugins?.[pluginName],
|
|
651
|
+
credentials: {},
|
|
652
|
+
logger: log.child({ plugin: pluginName }),
|
|
653
|
+
pluginStorageDir: ensurePluginStorageDir(pluginName),
|
|
654
|
+
assistantVersion: APP_VERSION,
|
|
655
|
+
};
|
|
656
|
+
await initHookEntry.hook(initContext);
|
|
657
|
+
log.info({ plugin: pluginName }, "user plugin initialized");
|
|
658
|
+
} catch (err) {
|
|
659
|
+
log.error(
|
|
660
|
+
{ err, plugin: pluginName },
|
|
661
|
+
`User plugin ${pluginName} init() failed — continuing`,
|
|
662
|
+
);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
476
665
|
|
|
477
666
|
activatedPlugins.push({ name: pluginName });
|
|
478
667
|
}
|
|
479
668
|
|
|
480
|
-
//
|
|
481
|
-
//
|
|
482
|
-
// files. Pre-import them and run their `init` hook so a workspace-wide
|
|
483
|
-
// `init`/`shutdown` lifecycle works the same way a plugin's does. Only
|
|
484
|
-
// register for teardown when at least one hook file is actually present, so
|
|
485
|
-
// an empty/absent directory adds no shutdown work.
|
|
486
|
-
if (hasWorkspaceHooks()) {
|
|
487
|
-
await preImportWorkspaceHooks();
|
|
488
|
-
await runInitHook(WORKSPACE_HOOKS_OWNER);
|
|
489
|
-
activatedPlugins.push({ name: WORKSPACE_HOOKS_OWNER });
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
// Register a single shutdown hook that walks all activated owners in reverse
|
|
493
|
-
// order, unregistering tools and running shutdown hooks.
|
|
669
|
+
// Register a single shutdown hook that walks all activated user plugins
|
|
670
|
+
// in reverse order, unregistering tools and running shutdown hooks.
|
|
494
671
|
const shutdownSnapshot = [...activatedPlugins];
|
|
495
672
|
registerShutdownHook("user-plugins", async (reason) => {
|
|
496
673
|
for (let i = shutdownSnapshot.length - 1; i >= 0; i--) {
|
|
497
674
|
const { name } = shutdownSnapshot[i]!;
|
|
498
675
|
|
|
499
676
|
// Unregister tools before running shutdown so onShutdown sees a
|
|
500
|
-
// clean model-visible surface.
|
|
501
|
-
// which registers no tools.)
|
|
677
|
+
// clean model-visible surface.
|
|
502
678
|
try {
|
|
503
679
|
unregisterPluginTools(name);
|
|
504
680
|
} catch (err) {
|
|
@@ -509,11 +685,30 @@ export async function populateCacheAtBoot(
|
|
|
509
685
|
}
|
|
510
686
|
|
|
511
687
|
// Run the `shutdown` hook if present.
|
|
512
|
-
|
|
688
|
+
const shutdownHookEntry = hookCache.get(hookKey(name, HOOKS.SHUTDOWN));
|
|
689
|
+
if (shutdownHookEntry !== undefined) {
|
|
690
|
+
try {
|
|
691
|
+
await shutdownHookEntry.hook(shutdownContext);
|
|
692
|
+
} catch (err) {
|
|
693
|
+
log.warn(
|
|
694
|
+
{ err, plugin: name, reason },
|
|
695
|
+
"user plugin shutdown hook failed (continuing)",
|
|
696
|
+
);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
513
699
|
}
|
|
514
700
|
});
|
|
515
701
|
}
|
|
516
702
|
|
|
703
|
+
/**
|
|
704
|
+
* Ensure `<workspaceDir>/plugins-data/<name>/` exists and return its path.
|
|
705
|
+
*/
|
|
706
|
+
function ensurePluginStorageDir(pluginName: string): string {
|
|
707
|
+
const dir = join(getWorkspaceDir(), "plugins-data", pluginName);
|
|
708
|
+
mkdirSync(dir, { recursive: true });
|
|
709
|
+
return dir;
|
|
710
|
+
}
|
|
711
|
+
|
|
517
712
|
// ─── Test hooks ──────────────────────────────────────────────────────────────
|
|
518
713
|
|
|
519
714
|
/**
|
|
@@ -527,15 +722,35 @@ export function resetPluginCacheForTests(): void {
|
|
|
527
722
|
"resetPluginCacheForTests may only be called in test environments",
|
|
528
723
|
);
|
|
529
724
|
}
|
|
530
|
-
|
|
531
|
-
clearSurfaceImportInflight();
|
|
725
|
+
hookCache.clear();
|
|
532
726
|
toolCache.clear();
|
|
727
|
+
inflight.clear();
|
|
533
728
|
discoveredPluginDirs.clear();
|
|
534
729
|
installDateCache.clear();
|
|
535
730
|
activatedPlugins.length = 0;
|
|
536
731
|
disabledPluginDirs.clear();
|
|
537
732
|
}
|
|
538
733
|
|
|
734
|
+
/**
|
|
735
|
+
* Test-only: inspect the hook cache.
|
|
736
|
+
*/
|
|
737
|
+
export function _inspectHookCacheForTests(): Array<{
|
|
738
|
+
key: string;
|
|
739
|
+
sourceMtime: number;
|
|
740
|
+
}> {
|
|
741
|
+
const isTest =
|
|
742
|
+
process.env.BUN_TEST === "1" || process.env.NODE_ENV === "test";
|
|
743
|
+
if (!isTest) {
|
|
744
|
+
throw new Error(
|
|
745
|
+
"_inspectHookCacheForTests may only be called in test environments",
|
|
746
|
+
);
|
|
747
|
+
}
|
|
748
|
+
return Array.from(hookCache.entries()).map(([key, c]) => ({
|
|
749
|
+
key,
|
|
750
|
+
sourceMtime: c.sourceMtime,
|
|
751
|
+
}));
|
|
752
|
+
}
|
|
753
|
+
|
|
539
754
|
/**
|
|
540
755
|
* Test-only: inspect the tool cache.
|
|
541
756
|
*/
|
package/src/plugins/pipeline.ts
CHANGED
|
@@ -4,10 +4,9 @@
|
|
|
4
4
|
* A "hook" is a named lifecycle event (`user-prompt-submit`, `post-tool-use`,
|
|
5
5
|
* ...) that every registered plugin may handle. The runner walks each plugin's
|
|
6
6
|
* hook for a given event in registration order, threading a context value
|
|
7
|
-
* through the chain so hooks can observe and transform it.
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* onto the draft. Failed hook drafts are discarded.
|
|
7
|
+
* through the chain so hooks can observe and transform it. A hook either
|
|
8
|
+
* mutates the context in place (returning `void`) or returns a partial
|
|
9
|
+
* context whose fields are merged onto the threaded value.
|
|
11
10
|
*
|
|
12
11
|
* `getHooksFor` is now async — it pulls user-land hooks from the mtime
|
|
13
12
|
* cache (filesystem-as-truth) and default plugin hooks from the registry
|
|
@@ -17,131 +16,34 @@
|
|
|
17
16
|
*/
|
|
18
17
|
|
|
19
18
|
import type { HookName } from "../plugin-api/constants.js";
|
|
20
|
-
import { getLogger } from "../util/logger.js";
|
|
21
19
|
import { getHooksFor } from "./registry.js";
|
|
22
|
-
import type { PluginHookFn } from "./types.js";
|
|
23
20
|
|
|
24
21
|
// ─── Hook runner ────────────────────────────────────────────────────────────
|
|
25
22
|
|
|
26
|
-
const log = getLogger("plugin-pipeline");
|
|
27
|
-
|
|
28
|
-
function isPluginLogger(value: unknown): value is {
|
|
29
|
-
info: unknown;
|
|
30
|
-
warn: unknown;
|
|
31
|
-
error: unknown;
|
|
32
|
-
debug: unknown;
|
|
33
|
-
} {
|
|
34
|
-
return (
|
|
35
|
-
value !== null &&
|
|
36
|
-
typeof value === "object" &&
|
|
37
|
-
typeof (value as { info?: unknown }).info === "function" &&
|
|
38
|
-
typeof (value as { warn?: unknown }).warn === "function" &&
|
|
39
|
-
typeof (value as { error?: unknown }).error === "function" &&
|
|
40
|
-
typeof (value as { debug?: unknown }).debug === "function"
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function isPlainObject(value: object): boolean {
|
|
45
|
-
const prototype = Object.getPrototypeOf(value);
|
|
46
|
-
return prototype === Object.prototype || prototype === null;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function cloneHookValue<T>(value: T, seen = new WeakMap<object, unknown>()): T {
|
|
50
|
-
if (value === null || typeof value !== "object") return value;
|
|
51
|
-
if (value instanceof Error || isPluginLogger(value)) return value;
|
|
52
|
-
|
|
53
|
-
const existing = seen.get(value);
|
|
54
|
-
if (existing !== undefined) return existing as T;
|
|
55
|
-
|
|
56
|
-
if (Array.isArray(value)) {
|
|
57
|
-
const copy: unknown[] = [];
|
|
58
|
-
seen.set(value, copy);
|
|
59
|
-
for (const item of value) {
|
|
60
|
-
copy.push(cloneHookValue(item, seen));
|
|
61
|
-
}
|
|
62
|
-
return copy as T;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (value instanceof Date) {
|
|
66
|
-
return new Date(value.getTime()) as T;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (value instanceof Map) {
|
|
70
|
-
const copy = new Map();
|
|
71
|
-
seen.set(value, copy);
|
|
72
|
-
for (const [key, mapValue] of value) {
|
|
73
|
-
copy.set(cloneHookValue(key, seen), cloneHookValue(mapValue, seen));
|
|
74
|
-
}
|
|
75
|
-
return copy as T;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
if (value instanceof Set) {
|
|
79
|
-
const copy = new Set();
|
|
80
|
-
seen.set(value, copy);
|
|
81
|
-
for (const item of value) {
|
|
82
|
-
copy.add(cloneHookValue(item, seen));
|
|
83
|
-
}
|
|
84
|
-
return copy as T;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (!isPlainObject(value)) return value;
|
|
88
|
-
|
|
89
|
-
const copy: Record<PropertyKey, unknown> = {};
|
|
90
|
-
seen.set(value, copy);
|
|
91
|
-
for (const key of Reflect.ownKeys(value)) {
|
|
92
|
-
copy[key] = cloneHookValue(
|
|
93
|
-
(value as Record<PropertyKey, unknown>)[key],
|
|
94
|
-
seen,
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
return copy as T;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
23
|
/**
|
|
101
24
|
* Execute a hook chain: walk every registered plugin's hook for `name` in
|
|
102
25
|
* registration order, threading `initialCtx` through each. Hooks may either
|
|
103
|
-
* mutate
|
|
104
|
-
*
|
|
105
|
-
* overwrite the running context, every other field is preserved.
|
|
106
|
-
*
|
|
107
|
-
* successfully committed context. The final context after the chain settles is
|
|
108
|
-
* returned.
|
|
26
|
+
* mutate the context in place (returning `void`) or return a partial context
|
|
27
|
+
* whose fields are merged onto the threaded value — keys the hook returns
|
|
28
|
+
* overwrite the running context, every other field is preserved. The final
|
|
29
|
+
* context after the chain settles is returned.
|
|
109
30
|
*
|
|
110
31
|
* @param name The hook identifier — pick one from {@link HOOKS}.
|
|
111
32
|
* @param initialCtx Context the first hook receives.
|
|
112
33
|
* @returns The final context after the chain settles. Same reference as
|
|
113
|
-
* `initialCtx` when no plugin registers `name
|
|
34
|
+
* `initialCtx` when no plugin registers `name`, and when every
|
|
35
|
+
* chained hook returns `void` (mutation-in-place style).
|
|
114
36
|
*/
|
|
115
37
|
export async function runHook<TCtx>(
|
|
116
38
|
name: HookName,
|
|
117
39
|
initialCtx: TCtx,
|
|
118
40
|
): Promise<TCtx> {
|
|
119
|
-
|
|
120
|
-
try {
|
|
121
|
-
hooks = await getHooksFor<TCtx>(name);
|
|
122
|
-
} catch (err) {
|
|
123
|
-
log.error(
|
|
124
|
-
{ err, hookName: name },
|
|
125
|
-
"plugin hook discovery failed — proceeding without hooks",
|
|
126
|
-
);
|
|
127
|
-
return initialCtx;
|
|
128
|
-
}
|
|
129
|
-
|
|
41
|
+
const hooks = await getHooksFor<TCtx>(name);
|
|
130
42
|
let active = initialCtx;
|
|
131
43
|
for (const hook of hooks) {
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if (result !== undefined) {
|
|
136
|
-
active = { ...draft, ...result };
|
|
137
|
-
} else {
|
|
138
|
-
active = draft;
|
|
139
|
-
}
|
|
140
|
-
} catch (err) {
|
|
141
|
-
log.error(
|
|
142
|
-
{ err, hookName: name },
|
|
143
|
-
"plugin hook failed — proceeding with current context",
|
|
144
|
-
);
|
|
44
|
+
const result = await hook(active);
|
|
45
|
+
if (result !== undefined) {
|
|
46
|
+
active = { ...active, ...result };
|
|
145
47
|
}
|
|
146
48
|
}
|
|
147
49
|
return active;
|
package/src/plugins/types.ts
CHANGED
|
@@ -47,6 +47,8 @@ export interface PluginManifest {
|
|
|
47
47
|
* own version at load time.
|
|
48
48
|
*/
|
|
49
49
|
version: string;
|
|
50
|
+
/** Credential keys the plugin needs resolved before `init()` runs. */
|
|
51
|
+
requiresCredential?: string[];
|
|
50
52
|
/**
|
|
51
53
|
* Assistant feature-flag keys that must all be enabled for this plugin to
|
|
52
54
|
* activate. Checked by `bootstrapPlugins` via `isAssistantFeatureFlagEnabled`
|