@vellumai/assistant 0.8.1 → 0.8.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/ARCHITECTURE.md +2 -7
- package/Dockerfile +75 -1
- package/bun.lock +11 -1
- package/docker-entrypoint.sh +5 -0
- package/docker-init-apt-root.sh +94 -0
- package/docker-kata-apt-env.sh +39 -0
- package/docs/plugins.md +88 -47
- package/docs/skills.md +9 -7
- package/examples/plugins/echo/README.md +27 -27
- package/examples/plugins/echo/package.json +3 -0
- package/examples/plugins/echo/register.ts +31 -31
- package/node_modules/@vellumai/slack-text/src/index.test.ts +114 -14
- package/node_modules/@vellumai/slack-text/src/index.ts +82 -18
- package/openapi.yaml +325 -3
- package/package.json +3 -1
- package/scripts/generate-openapi.ts +83 -10
- package/scripts/sync-llm-catalog.ts +2 -2
- package/scripts/sync-web-search-catalog.ts +47 -25
- package/src/__tests__/agent-image-optimize.test.ts +11 -3
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +131 -0
- package/src/__tests__/anthropic-provider.test.ts +45 -0
- package/src/__tests__/app-builder-tool-scripts.test.ts +9 -3
- package/src/__tests__/app-executors.test.ts +220 -4
- package/src/__tests__/auto-analysis-end-to-end.test.ts +35 -0
- package/src/__tests__/bundled-asset.test.ts +6 -6
- package/src/__tests__/channel-availability-routes.test.ts +206 -0
- package/src/__tests__/channel-delivery-store.test.ts +289 -1
- package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -1
- package/src/__tests__/clawhub.test.ts +75 -16
- package/src/__tests__/compactor-tail-resolution.test.ts +41 -0
- package/src/__tests__/config-schema.test.ts +21 -0
- package/src/__tests__/config-set-route.test.ts +80 -0
- package/src/__tests__/config-sounds-sync.test.ts +97 -0
- package/src/__tests__/config-watcher-skill-reseed.test.ts +453 -0
- package/src/__tests__/context-search-conversations-source.test.ts +117 -2
- package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
- package/src/__tests__/context-search-workspace-source.test.ts +7 -0
- package/src/__tests__/context-token-estimator.test.ts +1 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +4 -1
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +92 -92
- package/src/__tests__/conversation-agent-loop.test.ts +2 -0
- package/src/__tests__/conversation-error.test.ts +42 -3
- package/src/__tests__/conversation-fork-crud.test.ts +82 -0
- package/src/__tests__/conversation-inference-profile-route.test.ts +40 -4
- package/src/__tests__/conversation-lifecycle.test.ts +173 -0
- package/src/__tests__/conversation-message-sync-tags.test.ts +97 -0
- package/src/__tests__/conversation-pairing.test.ts +54 -0
- package/src/__tests__/conversation-process-callsite.test.ts +4 -1
- package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -1
- package/src/__tests__/conversation-queue.test.ts +4 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +76 -9
- package/src/__tests__/conversation-slash-queue.test.ts +59 -1
- package/src/__tests__/conversation-slash-unknown.test.ts +4 -1
- package/src/__tests__/conversation-surfaces-table-action.test.ts +360 -0
- package/src/__tests__/conversation-sync-tags.test.ts +235 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
- package/src/__tests__/credential-security-invariants.test.ts +3 -2
- package/src/__tests__/db-slack-external-content-normalization.test.ts +301 -0
- package/src/__tests__/delete-managed-skill-tool.test.ts +55 -13
- package/src/__tests__/disk-pressure-tools.test.ts +1 -0
- package/src/__tests__/dm-backfill.test.ts +121 -10
- package/src/__tests__/document-tool-security.test.ts +258 -0
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
- package/src/__tests__/edit-propagation.test.ts +33 -0
- package/src/__tests__/empty-response-pipeline.test.ts +0 -4
- package/src/__tests__/external-plugin-loader.test.ts +60 -36
- package/src/__tests__/filing-service.test.ts +140 -0
- package/src/__tests__/get-skill-detail-audit.test.ts +0 -4
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +43 -62
- package/src/__tests__/helpers/tar-fixtures.ts +39 -0
- package/src/__tests__/helpers/wait-for.ts +21 -0
- package/src/__tests__/history-repair-pipeline.test.ts +0 -3
- package/src/__tests__/history-repair.test.ts +73 -0
- package/src/__tests__/host-app-control-proxy.test.ts +266 -10
- package/src/__tests__/image-credentials.test.ts +1 -1
- package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
- package/src/__tests__/inference-no-mode-boot-e2e.test.ts +1 -1
- package/src/__tests__/inference-profile-reaper.test.ts +4 -2
- package/src/__tests__/inference-profile-session-handler.test.ts +18 -6
- package/src/__tests__/inference-profile-session-ipc.test.ts +17 -5
- package/src/__tests__/injector-chain.test.ts +10 -8
- package/src/__tests__/install-skill-routing.test.ts +155 -37
- package/src/__tests__/lifecycle-memory-v2-seed.test.ts +92 -3
- package/src/__tests__/list-messages-page-latest.test.ts +55 -0
- package/src/__tests__/llm-call-pipeline.test.ts +0 -3
- package/src/__tests__/llm-catalog-parity.test.ts +55 -13
- package/src/__tests__/llm-request-log-source-clickhouse.test.ts +34 -0
- package/src/__tests__/llm-request-log-source-factory.test.ts +29 -53
- package/src/__tests__/llm-usage-store.test.ts +114 -0
- package/src/__tests__/managed-profile-guard.test.ts +31 -29
- package/src/__tests__/managed-skill-lifecycle.test.ts +109 -18
- package/src/__tests__/managed-store.test.ts +84 -192
- package/src/__tests__/media-generate-image.test.ts +1 -1
- package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -2
- package/src/__tests__/messages-after-tiebreaker.test.ts +122 -0
- package/src/__tests__/oauth-commands-routes.test.ts +168 -16
- package/src/__tests__/oauth-provider-profiles.test.ts +9 -0
- package/src/__tests__/openai-provider.test.ts +24 -0
- package/src/__tests__/openai-responses-cutover-guard.test.ts +17 -9
- package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -2
- package/src/__tests__/persistence-pipeline.test.ts +0 -2
- package/src/__tests__/{managed-proxy-context.test.ts → platform-proxy-context.test.ts} +1 -1
- package/src/__tests__/platform.test.ts +2 -0
- package/src/__tests__/plugin-api-shim.test.ts +125 -0
- package/src/__tests__/plugin-bootstrap.test.ts +10 -36
- package/src/__tests__/plugin-external-api.test.ts +68 -0
- package/src/__tests__/plugin-registry.test.ts +0 -77
- package/src/__tests__/plugin-route-contribution.test.ts +0 -1
- package/src/__tests__/plugin-skill-contribution.test.ts +0 -2
- package/src/__tests__/plugin-tool-contribution.test.ts +16 -15
- package/src/__tests__/plugin-types.test.ts +3 -13
- package/src/__tests__/process-message-background-slack.test.ts +8 -1
- package/src/__tests__/process-message-display-content.test.ts +421 -0
- package/src/__tests__/provider-catalog-visibility.test.ts +142 -0
- package/src/__tests__/provider-error-scenarios.test.ts +111 -0
- package/src/__tests__/{provider-managed-proxy-integration.test.ts → provider-platform-proxy-integration.test.ts} +8 -8
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +65 -13
- package/src/__tests__/schedule-routes.test.ts +50 -3
- package/src/__tests__/schedule-store.test.ts +94 -0
- package/src/__tests__/scheduler-reuse-conversation.test.ts +54 -7
- package/src/__tests__/schema-transforms.test.ts +20 -0
- package/src/__tests__/search-skills-unified.test.ts +0 -5
- package/src/__tests__/server-history-render.test.ts +43 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +0 -12
- package/src/__tests__/skill-load-tool.test.ts +27 -89
- package/src/__tests__/skill-memory.test.ts +23 -3
- package/src/__tests__/skills-file-content-endpoint.test.ts +9 -38
- package/src/__tests__/skills-files-catalog-fallback.test.ts +0 -3
- package/src/__tests__/skills-install-extract.test.ts +49 -38
- package/src/__tests__/skills-install-staging.test.ts +159 -0
- package/src/__tests__/skills-uninstall.test.ts +9 -41
- package/src/__tests__/skills.test.ts +51 -58
- package/src/__tests__/slack-channel-config.test.ts +9 -0
- package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
- package/src/__tests__/system-prompt.test.ts +737 -63
- package/src/__tests__/terminal-tools.test.ts +28 -1
- package/src/__tests__/thread-backfill.test.ts +557 -27
- package/src/__tests__/title-generate-pipeline.test.ts +0 -13
- package/src/__tests__/token-estimate-pipeline.test.ts +0 -3
- package/src/__tests__/tool-error-pipeline.test.ts +0 -3
- package/src/__tests__/tool-execute-pipeline.test.ts +0 -5
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
- package/src/__tests__/tool-executor.test.ts +16 -4
- package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -12
- package/src/__tests__/turn-events-store.test.ts +256 -0
- package/src/__tests__/twilio-routes.test.ts +4 -0
- package/src/__tests__/user-plugin-loader.test.ts +0 -7
- package/src/__tests__/voice-session-bridge.test.ts +198 -0
- package/src/__tests__/web-search-catalog-parity.test.ts +32 -10
- package/src/__tests__/workspace-migration-057-repair-stale-gemini-model-ids.test.ts +115 -3
- package/src/__tests__/workspace-migration-072-seed-reply-suggestion-callsite.test.ts +50 -0
- package/src/__tests__/workspace-migration-073-repair-recall-callsite-empty-profile.test.ts +153 -0
- package/src/__tests__/workspace-migration-085-memory-v2-bm25-b-reembed-disabled-v2-pages.test.ts +220 -0
- package/src/__tests__/workspace-migration-086-revert-stale-gemini-mis-rewrites.test.ts +269 -0
- package/src/__tests__/workspace-migration-remove-legacy-skills-index.test.ts +309 -0
- package/src/__tests__/workspace-migrations-runner.test.ts +111 -3
- package/src/acp/resolve-agent.ts +1 -1
- package/src/agent/image-optimize.ts +13 -5
- package/src/calls/voice-session-bridge.ts +61 -42
- package/src/channels/types.ts +108 -0
- package/src/cli/__tests__/unknown-command.test.ts +24 -0
- package/src/cli/commands/__tests__/changelog.test.ts +304 -319
- package/src/cli/commands/__tests__/schedules.test.ts +491 -0
- package/src/cli/commands/changelog.ts +106 -42
- package/src/cli/commands/conversations.ts +102 -17
- package/src/cli/commands/default-action.ts +10 -53
- package/src/cli/commands/notifications.ts +329 -317
- package/src/cli/commands/plugins.ts +185 -0
- package/src/cli/commands/schedules.ts +391 -0
- package/src/cli/commands/telemetry.ts +40 -0
- package/src/cli/lib/__tests__/cli-colors.test.ts +48 -0
- package/src/cli/lib/__tests__/confirm-prompt.test.ts +159 -0
- package/src/cli/lib/__tests__/install-from-github.test.ts +355 -0
- package/src/cli/lib/__tests__/list-installed-plugins.test.ts +154 -0
- package/src/cli/lib/__tests__/uninstall-plugin.test.ts +124 -0
- package/src/cli/lib/__tests__/unknown-command.test.ts +106 -0
- package/src/cli/lib/cli-colors.ts +12 -0
- package/src/cli/lib/confirm-prompt.ts +79 -0
- package/src/cli/lib/install-from-github.ts +304 -0
- package/src/cli/lib/list-installed-plugins.ts +137 -0
- package/src/cli/lib/uninstall-plugin.ts +82 -0
- package/src/cli/lib/unknown-command.ts +111 -0
- package/src/cli/program.ts +38 -2
- package/src/config/bundled-skills/app-builder/SKILL.md +23 -21
- package/src/config/bundled-skills/app-builder/TOOLS.json +7 -0
- package/src/config/bundled-skills/computer-use/TOOLS.json +15 -52
- package/src/config/bundled-skills/document/SKILL.md +23 -3
- package/src/config/bundled-skills/document/TOOLS.json +53 -0
- package/src/config/bundled-skills/document/tools/document-delete.ts +12 -0
- package/src/config/bundled-skills/document/tools/document-list.ts +12 -0
- package/src/config/bundled-skills/document/tools/document-read.ts +12 -0
- package/src/config/bundled-skills/skill-management/SKILL.md +2 -2
- package/src/config/bundled-skills/skill-management/TOOLS.json +7 -7
- package/src/config/bundled-tool-registry.ts +6 -0
- package/src/config/feature-flag-registry.json +41 -1
- package/src/config/loader.ts +64 -38
- package/src/config/schema.ts +7 -10
- package/src/config/schemas/__tests__/llm-request-logs.test.ts +36 -0
- package/src/config/schemas/channels.ts +8 -0
- package/src/config/schemas/compaction.ts +28 -0
- package/src/config/schemas/heartbeat.ts +9 -0
- package/src/config/schemas/llm-request-logs.ts +31 -7
- package/src/config/schemas/llm.ts +3 -0
- package/src/config/schemas/memory-retrieval.ts +18 -0
- package/src/config/schemas/tools.ts +14 -0
- package/src/config/skills.ts +3 -96
- package/src/context/compactor.ts +1047 -0
- package/src/context/token-estimator.ts +2 -2
- package/src/context/window-manager.ts +197 -1520
- package/src/credential-execution/managed-catalog.ts +37 -0
- package/src/credential-health/credential-health-service.ts +280 -19
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +34 -0
- package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +138 -0
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +74 -0
- package/src/daemon/approval-generators.ts +8 -6
- package/src/daemon/config-watcher.ts +94 -31
- package/src/daemon/conversation-agent-loop.ts +169 -9
- package/src/daemon/conversation-error.ts +171 -37
- package/src/daemon/conversation-lifecycle.ts +53 -40
- package/src/daemon/conversation-messaging.ts +25 -6
- package/src/daemon/conversation-process.ts +49 -12
- package/src/daemon/conversation-runtime-assembly.ts +16 -1
- package/src/daemon/conversation-slash.ts +12 -5
- package/src/daemon/conversation-store.ts +11 -4
- package/src/daemon/conversation-tool-setup.ts +39 -7
- package/src/daemon/conversation.ts +33 -1
- package/src/daemon/external-plugins-bootstrap.ts +217 -181
- package/src/daemon/first-greeting.ts +22 -2
- package/src/daemon/handlers/config-model.ts +6 -5
- package/src/daemon/handlers/config-slack-channel.ts +15 -3
- package/src/daemon/handlers/shared.ts +14 -5
- package/src/daemon/handlers/skills.ts +111 -108
- package/src/daemon/history-repair.ts +28 -1
- package/src/daemon/host-app-control-proxy.ts +98 -23
- package/src/daemon/lifecycle.ts +45 -35
- package/src/daemon/meet-host-supervisor.ts +5 -4
- package/src/daemon/memory-v2-startup.ts +49 -0
- package/src/daemon/message-protocol.ts +1 -0
- package/src/daemon/message-types/conversations.ts +25 -0
- package/src/daemon/message-types/messages.ts +61 -0
- package/src/daemon/message-types/subagents.ts +1 -0
- package/src/daemon/message-types/sync.ts +1 -0
- package/src/daemon/pkb-reminder-builder.test.ts +1 -1
- package/src/daemon/pkb-reminder-builder.ts +1 -1
- package/src/daemon/plugin-source-watcher.ts +146 -0
- package/src/daemon/process-message.ts +21 -3
- package/src/daemon/server.ts +11 -2
- package/src/daemon/skill-memory-refresh.ts +29 -0
- package/src/documents/document-store.ts +221 -3
- package/src/embedded/plugin-api.ts +40 -0
- package/src/filing/filing-service.ts +39 -0
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +91 -6
- package/src/heartbeat/heartbeat-run-store.ts +2 -1
- package/src/heartbeat/heartbeat-service.ts +41 -0
- package/src/home/__tests__/feed-types.test.ts +40 -0
- package/src/home/feed-types.ts +22 -0
- package/src/home/post-connect-feed.ts +1 -0
- package/src/index.ts +18 -1
- package/src/live-voice/__tests__/live-voice-stt.test.ts +57 -0
- package/src/mcp/client.ts +20 -4
- package/src/media/image-credentials.ts +3 -3
- package/src/memory/__tests__/bookmark-crud.test.ts +33 -27
- package/src/memory/__tests__/conversation-queries.test.ts +263 -0
- package/src/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +113 -0
- package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +119 -14
- package/src/memory/__tests__/message-content.test.ts +35 -0
- package/src/memory/bookmark-crud.ts +42 -10
- package/src/memory/context-search/sources/conversations.ts +62 -2
- package/src/memory/context-search/sources/workspace.ts +4 -0
- package/src/memory/conversation-crud.ts +63 -19
- package/src/memory/conversation-queries.ts +110 -10
- package/src/memory/db-init.ts +6 -0
- package/src/memory/delivery-crud.ts +152 -5
- package/src/memory/embedding-backend.ts +4 -4
- package/src/memory/external-conversation-store.ts +66 -5
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +66 -9
- package/src/memory/graph/conversation-graph-memory.ts +31 -15
- package/src/memory/graph/tools.ts +3 -3
- package/src/memory/indexer.ts +34 -29
- package/src/memory/jobs/__tests__/embed-concept-page.test.ts +73 -0
- package/src/memory/jobs/embed-concept-page.ts +20 -11
- package/src/memory/jobs-worker.ts +6 -1
- package/src/memory/llm-request-log-source-clickhouse.ts +17 -10
- package/src/memory/llm-request-log-source.ts +19 -52
- package/src/memory/llm-usage-store.ts +125 -5
- package/src/memory/memory-retrospective-startup-cleanup.ts +72 -5
- package/src/memory/message-content.ts +1 -1
- package/src/memory/migrations/109-external-conversation-bindings.ts +15 -4
- package/src/memory/migrations/229-delete-private-conversations.test.ts +38 -1
- package/src/memory/migrations/229-delete-private-conversations.ts +7 -0
- package/src/memory/migrations/247-external-conversation-binding-thread-id.ts +78 -0
- package/src/memory/migrations/248-create-onboarding-events.ts +21 -0
- package/src/memory/migrations/249-normalize-slack-external-content.ts +240 -0
- package/src/memory/migrations/index.ts +6 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/onboarding-events-store.ts +106 -0
- package/src/memory/schema/bookmarks.ts +0 -2
- package/src/memory/schema/calls.ts +1 -0
- package/src/memory/schema/inference.ts +1 -3
- package/src/memory/schema/infrastructure.ts +12 -0
- package/src/memory/turn-events-store.ts +127 -2
- package/src/memory/v2/__tests__/activation.test.ts +0 -8
- package/src/memory/v2/__tests__/injection.test.ts +98 -8
- package/src/memory/v2/__tests__/migration.test.ts +87 -0
- package/src/memory/v2/__tests__/page-index.test.ts +83 -0
- package/src/memory/v2/__tests__/prompts-router.test.ts +58 -6
- package/src/memory/v2/__tests__/qdrant.test.ts +66 -3
- package/src/memory/v2/__tests__/router.test.ts +15 -0
- package/src/memory/v2/__tests__/skill-store.test.ts +387 -8
- package/src/memory/v2/injection.ts +32 -6
- package/src/memory/v2/migration.ts +49 -19
- package/src/memory/v2/page-index.ts +35 -5
- package/src/memory/v2/prompts/router.ts +11 -8
- package/src/memory/v2/prompts/sweep.ts +2 -2
- package/src/memory/v2/qdrant.ts +135 -7
- package/src/memory/v2/router.ts +9 -8
- package/src/memory/v2/skill-store.ts +120 -35
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +45 -5
- package/src/messaging/providers/slack/__tests__/download.test.ts +231 -0
- package/src/messaging/providers/slack/adapter.ts +43 -5
- package/src/messaging/providers/slack/client.ts +27 -0
- package/src/messaging/providers/slack/deep-link.ts +65 -0
- package/src/messaging/providers/slack/download.ts +104 -0
- package/src/messaging/providers/slack/message-metadata.test.ts +32 -0
- package/src/messaging/providers/slack/message-metadata.ts +27 -0
- package/src/messaging/providers/slack/render-transcript.test.ts +134 -0
- package/src/messaging/providers/slack/render-transcript.ts +69 -5
- package/src/messaging/providers/slack/types.ts +20 -1
- package/src/notifications/conversation-pairing.ts +2 -1
- package/src/notifications/decision-engine.ts +2 -1
- package/src/notifications/emit-signal.ts +20 -1
- package/src/notifications/home-feed-side-effect.ts +54 -0
- package/src/notifications/signal.ts +3 -1
- package/src/oauth/connection-resolver.ts +8 -4
- package/src/oauth/platform-connection.ts +6 -2
- package/src/oauth/seed-providers.ts +10 -1
- package/src/permissions/checker.ts +2 -0
- package/src/permissions/ipc-risk-types.ts +1 -0
- package/src/permissions/question-prompter.test.ts +416 -0
- package/src/permissions/question-prompter.ts +294 -0
- package/src/platform/client.test.ts +1 -1
- package/src/platform/client.ts +1 -1
- package/src/plugin-api/constants.ts +26 -0
- package/src/plugin-api/index.ts +34 -1
- package/src/plugin-api/types.ts +104 -22
- package/src/plugins/defaults/circuit-breaker.ts +0 -5
- package/src/plugins/defaults/compaction.ts +0 -4
- package/src/plugins/defaults/empty-response.ts +0 -2
- package/src/plugins/defaults/history-repair.ts +0 -2
- package/src/plugins/defaults/injectors.ts +36 -3
- package/src/plugins/defaults/llm-call.ts +0 -2
- package/src/plugins/defaults/memory-retrieval.ts +0 -1
- package/src/plugins/defaults/overflow-reduce.ts +0 -1
- package/src/plugins/defaults/persistence.ts +0 -2
- package/src/plugins/defaults/title-generate.ts +0 -5
- package/src/plugins/defaults/token-estimate.ts +0 -2
- package/src/plugins/defaults/tool-error.ts +0 -7
- package/src/plugins/defaults/tool-execute.ts +0 -2
- package/src/plugins/defaults/tool-result-truncate.ts +0 -4
- package/src/plugins/ensure-plugin-api-shim.ts +96 -0
- package/src/plugins/external-api.ts +104 -0
- package/src/plugins/external-plugin-loader.ts +105 -32
- package/src/plugins/feature-gate.ts +22 -0
- package/src/plugins/pipeline.ts +37 -0
- package/src/plugins/registry.ts +48 -80
- package/src/plugins/types.ts +31 -26
- package/src/plugins/user-loader.ts +21 -2
- package/src/proactive-artifact/aux-message-injector.ts +11 -0
- package/src/proactive-artifact/job.test.ts +37 -5
- package/src/prompts/__tests__/system-prompt.test.ts +12 -0
- package/src/prompts/__tests__/task-progress-hint-section.test.ts +99 -0
- package/src/prompts/normalize-onboarding.ts +27 -0
- package/src/prompts/sections.ts +302 -0
- package/src/prompts/system-prompt.ts +63 -166
- package/src/prompts/templates/BOOTSTRAP.md +17 -1
- package/src/prompts/templates/system-sections.ts +173 -0
- package/src/providers/__tests__/inference.test.ts +22 -7
- package/src/providers/anthropic/client.ts +28 -28
- package/src/providers/connection-resolution.ts +7 -0
- package/src/providers/inference/adapter-factory.ts +41 -4
- package/src/providers/inference/connections.ts +74 -29
- package/src/providers/inference/resolve-auth.ts +12 -4
- package/src/providers/model-catalog.ts +294 -12
- package/src/providers/openai/chat-completions-provider.ts +10 -2
- package/src/providers/openrouter/client.ts +7 -0
- package/src/providers/{managed-proxy → platform-proxy}/constants.ts +4 -1
- package/src/providers/{managed-proxy → platform-proxy}/context.ts +3 -3
- package/src/providers/provider-availability.ts +17 -2
- package/src/providers/provider-catalog-visibility.ts +36 -0
- package/src/providers/registry.ts +22 -14
- package/src/providers/retry.ts +47 -1
- package/src/runtime/__tests__/agent-wake.test.ts +152 -0
- package/src/runtime/agent-wake.ts +42 -14
- package/src/runtime/auth/route-policy.ts +8 -1
- package/src/runtime/btw-sidechain.ts +2 -0
- package/src/runtime/http-types.ts +19 -0
- package/src/runtime/migrations/origin-mode.ts +1 -1
- package/src/runtime/pending-interactions.ts +1 -0
- package/src/runtime/routes/__tests__/bookmark-routes.test.ts +17 -0
- package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +5 -1
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +107 -20
- package/src/runtime/routes/__tests__/question-routes.test.ts +395 -0
- package/src/runtime/routes/__tests__/tts-routes.test.ts +64 -1
- package/src/runtime/routes/acp-routes-list.test.ts +143 -0
- package/src/runtime/routes/acp-routes.ts +5 -3
- package/src/runtime/routes/auth-routes.ts +1 -1
- package/src/runtime/routes/bookmark-routes.ts +5 -3
- package/src/runtime/routes/btw-routes.ts +5 -1
- package/src/runtime/routes/channel-availability-routes.ts +121 -0
- package/src/runtime/routes/conversation-cli-routes.ts +44 -3
- package/src/runtime/routes/conversation-list-routes.ts +3 -20
- package/src/runtime/routes/conversation-management-routes.ts +17 -42
- package/src/runtime/routes/conversation-query-routes.ts +40 -35
- package/src/runtime/routes/conversation-routes.ts +90 -11
- package/src/runtime/routes/documents-routes.ts +25 -86
- package/src/runtime/routes/group-routes.ts +5 -0
- package/src/runtime/routes/inbound-conversation.ts +28 -8
- package/src/runtime/routes/inbound-message-handler.ts +236 -41
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +111 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +32 -1
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +17 -4
- package/src/runtime/routes/index.ts +6 -0
- package/src/runtime/routes/inference-profile-session-handler.ts +17 -44
- package/src/runtime/routes/inference-profile-session-reaper.ts +7 -21
- package/src/runtime/routes/inference-provider-connection-routes.ts +65 -21
- package/src/runtime/routes/integrations/slack/share.ts +4 -52
- package/src/runtime/routes/integrations/slack/token.ts +43 -0
- package/src/runtime/routes/integrations/twilio.ts +6 -13
- package/src/runtime/routes/notification-routes.ts +1 -1
- package/src/runtime/routes/oauth-commands-routes.ts +105 -15
- package/src/runtime/routes/oauth-lifecycle-routes.ts +43 -0
- package/src/runtime/routes/question-routes.ts +259 -0
- package/src/runtime/routes/rename-conversation-routes.ts +2 -33
- package/src/runtime/routes/schedule-routes.ts +4 -7
- package/src/runtime/routes/subagents-routes.ts +57 -18
- package/src/runtime/routes/telemetry-routes.ts +27 -0
- package/src/runtime/routes/tts-routes.ts +27 -2
- package/src/runtime/routes/workspace-routes.test.ts +43 -0
- package/src/runtime/routes/workspace-routes.ts +28 -0
- package/src/runtime/services/conversation-serializer.ts +39 -7
- package/src/runtime/sync/resource-sync-events.ts +93 -1
- package/src/schedule/schedule-store.ts +27 -2
- package/src/schedule/scheduler.ts +9 -1
- package/src/security/__tests__/untrusted-content.test.ts +86 -0
- package/src/security/untrusted-content.ts +93 -8
- package/src/skills/catalog-files.ts +1 -1
- package/src/skills/catalog-install.ts +233 -116
- package/src/skills/clawhub.ts +70 -13
- package/src/skills/managed-store.ts +4 -119
- package/src/skills/skillssh-registry.ts +27 -48
- package/src/subagent/manager.ts +15 -7
- package/src/telemetry/types.ts +113 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +312 -5
- package/src/telemetry/usage-telemetry-reporter.ts +113 -7
- package/src/tools/apps/executors.ts +58 -7
- package/src/tools/ask-question/ask-question-tool.test.ts +509 -0
- package/src/tools/ask-question/ask-question-tool.ts +304 -0
- package/src/tools/browser/browser-execution.ts +15 -11
- package/src/tools/computer-use/definitions.ts +3 -3
- package/src/tools/credentials/vault.ts +1 -1
- package/src/tools/document/document-tool.ts +124 -1
- package/src/tools/filesystem/edit.ts +1 -1
- package/src/tools/filesystem/list.ts +1 -1
- package/src/tools/filesystem/read.ts +1 -1
- package/src/tools/filesystem/write.ts +5 -2
- package/src/tools/host-filesystem/transfer.ts +1 -1
- package/src/tools/host-terminal/host-shell.ts +1 -1
- package/src/tools/permission-checker.ts +1 -1
- package/src/tools/registry.ts +17 -7
- package/src/tools/schedule/create.ts +2 -2
- package/src/tools/schema-transforms.ts +7 -2
- package/src/tools/side-effects.ts +1 -0
- package/src/tools/skills/delete-managed.ts +4 -4
- package/src/tools/skills/execute.ts +1 -1
- package/src/tools/skills/scaffold-managed.ts +3 -2
- package/src/tools/subagent/notify-parent.ts +1 -1
- package/src/tools/system/request-permission.ts +2 -2
- package/src/tools/terminal/safe-env.ts +60 -1
- package/src/tools/tool-manifest.ts +2 -0
- package/src/tools/types.ts +72 -21
- package/src/tools/ui-surface/definitions.ts +6 -5
- package/src/tts/__tests__/provider-adapters.test.ts +76 -2
- package/src/tts/providers/elevenlabs-provider.ts +75 -1
- package/src/types/onboarding-context.ts +2 -0
- package/src/util/errors.ts +17 -0
- package/src/util/platform.ts +10 -0
- package/src/watcher/__tests__/engine.test.ts +22 -0
- package/src/watcher/engine.ts +6 -2
- package/src/workspace/migrations/057-repair-stale-gemini-model-ids.ts +80 -15
- package/src/workspace/migrations/072-seed-reply-suggestion-callsite.ts +35 -22
- package/src/workspace/migrations/073-repair-recall-callsite-empty-profile.ts +3 -1
- package/src/workspace/migrations/083-system-prompt-prefix-to-file.ts +191 -0
- package/src/workspace/migrations/084-remove-legacy-skills-index.ts +276 -0
- package/src/workspace/migrations/085-memory-v2-bm25-b-reembed-disabled-v2-pages.ts +137 -0
- package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +198 -0
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/workspace/migrations/runner.ts +39 -9
- package/src/workspace/migrations/types.ts +4 -0
- package/examples/plugins/echo/bun.lock +0 -25
- package/src/__tests__/context-window-manager.test.ts +0 -2481
- package/src/context/__tests__/compact-prompt.test.ts +0 -63
- package/src/context/prompts/compact.md +0 -26
- package/src/prompts/__tests__/build-cli-reference-section.test.ts +0 -37
- /package/src/__tests__/{secret-routes-managed-proxy.test.ts → secret-routes-platform-proxy.test.ts} +0 -0
|
@@ -11,8 +11,11 @@
|
|
|
11
11
|
*
|
|
12
12
|
* <pluginDir>/
|
|
13
13
|
* package.json ← manifest.name comes from `name`
|
|
14
|
-
* (npm scope stripped);
|
|
15
|
-
*
|
|
14
|
+
* (npm scope stripped);
|
|
15
|
+
* peerDependencies["@vellumai/plugin-api"]
|
|
16
|
+
* semver range is checked against the
|
|
17
|
+
* running assistant version and rejects
|
|
18
|
+
* the plugin if unsatisfied
|
|
16
19
|
* hooks/
|
|
17
20
|
* <name>.ts ← default export → plugin.hooks[<name>]
|
|
18
21
|
* (today the runtime invokes "init" at
|
|
@@ -40,8 +43,10 @@ import { readFile } from "node:fs/promises";
|
|
|
40
43
|
import { join } from "node:path";
|
|
41
44
|
import { pathToFileURL } from "node:url";
|
|
42
45
|
|
|
46
|
+
import semver from "semver";
|
|
43
47
|
import { z } from "zod";
|
|
44
48
|
|
|
49
|
+
import assistantPkg from "../../package.json" with { type: "json" };
|
|
45
50
|
import { getLogger } from "../util/logger.js";
|
|
46
51
|
import { registerPlugin } from "./registry.js";
|
|
47
52
|
import type {
|
|
@@ -52,6 +57,8 @@ import type {
|
|
|
52
57
|
PluginToolRegistration,
|
|
53
58
|
} from "./types.js";
|
|
54
59
|
|
|
60
|
+
const PLUGIN_API_PEER_DEP = "@vellumai/plugin-api";
|
|
61
|
+
|
|
55
62
|
const log = getLogger("external-plugin-loader");
|
|
56
63
|
|
|
57
64
|
/** Default upper bound on how long a single plugin load may take. */
|
|
@@ -61,9 +68,11 @@ const DEFAULT_IMPORT_TIMEOUT_MS = 10_000;
|
|
|
61
68
|
* Zod schema for the subset of `package.json` the external loader reads.
|
|
62
69
|
*
|
|
63
70
|
* - `name` is the only required field; everything else is best-effort.
|
|
64
|
-
* - `
|
|
65
|
-
*
|
|
66
|
-
*
|
|
71
|
+
* - `peerDependencies["@vellumai/plugin-api"]` is the canonical host-compat
|
|
72
|
+
* declaration. If present, the loader checks `semver.satisfies(host, range)`
|
|
73
|
+
* against the running assistant version and rejects the plugin on
|
|
74
|
+
* mismatch. If absent, the plugin loads without a host-compat claim
|
|
75
|
+
* (with a warning).
|
|
67
76
|
* - Unknown fields pass through (`passthrough`) so the loader does not
|
|
68
77
|
* destructively reshape the file when the rest of the npm ecosystem
|
|
69
78
|
* writes to it.
|
|
@@ -72,12 +81,7 @@ const PluginPackageJsonSchema = z
|
|
|
72
81
|
.object({
|
|
73
82
|
name: z.string().min(1, "package.json `name` must be a non-empty string"),
|
|
74
83
|
version: z.string().optional(),
|
|
75
|
-
|
|
76
|
-
.object({
|
|
77
|
-
requires: z.record(z.string(), z.string()).optional(),
|
|
78
|
-
})
|
|
79
|
-
.passthrough()
|
|
80
|
-
.optional(),
|
|
84
|
+
peerDependencies: z.record(z.string(), z.string()).optional(),
|
|
81
85
|
})
|
|
82
86
|
.passthrough();
|
|
83
87
|
|
|
@@ -213,12 +217,49 @@ async function buildPluginFromDir(pluginDir: string): Promise<Plugin> {
|
|
|
213
217
|
const name = stripScope(pkg.name);
|
|
214
218
|
const version = pkg.version && pkg.version.length > 0 ? pkg.version : "0.0.0";
|
|
215
219
|
|
|
216
|
-
//
|
|
217
|
-
//
|
|
218
|
-
//
|
|
219
|
-
|
|
220
|
+
// Host-compat negotiation: plugins declare their plugin-api version
|
|
221
|
+
// range via standard `peerDependencies["@vellumai/plugin-api"]`. We
|
|
222
|
+
// inspect the range and report unparseable / unsatisfied cases via
|
|
223
|
+
// `log.error` but still load the plugin — the plugin-installation
|
|
224
|
+
// flow is in flux and a strict gate here would block experimentation
|
|
225
|
+
// for the customers driving the install UX. Once the install path
|
|
226
|
+
// settles, the two `log.error` branches below should harden into
|
|
227
|
+
// throws so a stale plugin can't silently run against a mismatched
|
|
228
|
+
// host.
|
|
229
|
+
//
|
|
230
|
+
// If the peerDep is absent, the plugin loads without a host-compat
|
|
231
|
+
// claim; we log a warning so the omission is visible at boot.
|
|
232
|
+
const range = pkg.peerDependencies?.[PLUGIN_API_PEER_DEP];
|
|
233
|
+
if (range !== undefined) {
|
|
234
|
+
if (!semver.validRange(range)) {
|
|
235
|
+
log.error(
|
|
236
|
+
{ pluginDir, plugin: name, peerDep: PLUGIN_API_PEER_DEP, range },
|
|
237
|
+
`external plugin ${name}: peerDependencies["${PLUGIN_API_PEER_DEP}"] is not a valid semver range — loading anyway`,
|
|
238
|
+
);
|
|
239
|
+
} else if (
|
|
240
|
+
!semver.satisfies(assistantPkg.version, range, {
|
|
241
|
+
includePrerelease: true,
|
|
242
|
+
})
|
|
243
|
+
) {
|
|
244
|
+
log.error(
|
|
245
|
+
{
|
|
246
|
+
pluginDir,
|
|
247
|
+
plugin: name,
|
|
248
|
+
peerDep: PLUGIN_API_PEER_DEP,
|
|
249
|
+
range,
|
|
250
|
+
assistantVersion: assistantPkg.version,
|
|
251
|
+
},
|
|
252
|
+
`external plugin ${name}: peerDependencies["${PLUGIN_API_PEER_DEP}"] requires "${range}" but assistant is ${assistantPkg.version} — loading anyway`,
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
} else {
|
|
256
|
+
log.warn(
|
|
257
|
+
{ pluginDir, plugin: name, peerDep: PLUGIN_API_PEER_DEP },
|
|
258
|
+
"external plugin missing plugin-api peerDependency — loading without host-compat claim",
|
|
259
|
+
);
|
|
260
|
+
}
|
|
220
261
|
|
|
221
|
-
const manifest: PluginManifest = { name, version
|
|
262
|
+
const manifest: PluginManifest = { name, version };
|
|
222
263
|
const plugin: Plugin = { manifest };
|
|
223
264
|
|
|
224
265
|
const hooks = await loadHooks(pluginDir, name);
|
|
@@ -244,16 +285,23 @@ async function buildPluginFromDir(pluginDir: string): Promise<Plugin> {
|
|
|
244
285
|
}
|
|
245
286
|
|
|
246
287
|
/**
|
|
247
|
-
*
|
|
288
|
+
* Build a {@link Plugin} from `pluginDir` with the same timeout +
|
|
289
|
+
* per-plugin isolation contract as {@link loadExternalPlugin}, but
|
|
290
|
+
* without registering it. The plugin-source watcher consumes this so it
|
|
291
|
+
* can decide between first-time registration (init once, then publish) and
|
|
292
|
+
* hot-reload (replace + skip init) based on what's already in the registry.
|
|
293
|
+
*
|
|
294
|
+
* Returns `undefined` on timeout, build failure, or abandoned surface
|
|
295
|
+
* import. Never throws — failures are logged with directory attribution.
|
|
248
296
|
*/
|
|
249
|
-
export async function
|
|
297
|
+
export async function buildExternalPlugin(
|
|
250
298
|
pluginDir: string,
|
|
251
299
|
opts: LoadExternalPluginOptions = {},
|
|
252
|
-
): Promise<
|
|
300
|
+
): Promise<Plugin | undefined> {
|
|
253
301
|
const timeoutMs = opts.importTimeoutMs ?? DEFAULT_IMPORT_TIMEOUT_MS;
|
|
254
302
|
let timeoutHandle: ReturnType<typeof setTimeout> | undefined;
|
|
255
303
|
try {
|
|
256
|
-
const timeoutSentinel = Symbol("external-plugin-
|
|
304
|
+
const timeoutSentinel = Symbol("external-plugin-build-timeout");
|
|
257
305
|
const buildPromise = buildPluginFromDir(pluginDir);
|
|
258
306
|
const timeoutPromise = new Promise<typeof timeoutSentinel>((resolve) => {
|
|
259
307
|
timeoutHandle = setTimeout(() => resolve(timeoutSentinel), timeoutMs);
|
|
@@ -262,33 +310,58 @@ export async function loadExternalPlugin(
|
|
|
262
310
|
if (result === timeoutSentinel) {
|
|
263
311
|
// Abandoned build — surface imports may still be running. Attach a
|
|
264
312
|
// terminal `.catch` so a late rejection does not surface as an
|
|
265
|
-
// unhandled-rejection crash.
|
|
266
|
-
// `
|
|
267
|
-
//
|
|
268
|
-
// moved on.
|
|
313
|
+
// unhandled-rejection crash. Callers who feed the returned plugin
|
|
314
|
+
// into `registerPlugin` rely on the closed-registration latch
|
|
315
|
+
// (registry.ts) to reject any stale late-arriving registration.
|
|
269
316
|
buildPromise.catch(() => {
|
|
270
317
|
/* swallow — see comment above */
|
|
271
318
|
});
|
|
272
319
|
log.warn(
|
|
273
320
|
{ pluginDir, timeoutMs },
|
|
274
|
-
`Timed out
|
|
321
|
+
`Timed out building external plugin ${pluginDir} after ${timeoutMs}ms — skipping`,
|
|
275
322
|
);
|
|
276
|
-
return;
|
|
323
|
+
return undefined;
|
|
277
324
|
}
|
|
278
|
-
|
|
279
|
-
log.info(
|
|
280
|
-
{ pluginDir, name: result.manifest.name },
|
|
281
|
-
"loaded external plugin",
|
|
282
|
-
);
|
|
325
|
+
return result;
|
|
283
326
|
} catch (err) {
|
|
284
327
|
// Per-plugin isolation: one bad external plugin must not crash the
|
|
285
328
|
// daemon. Surface the failure with attribution and move on.
|
|
286
329
|
const message = err instanceof Error ? err.message : String(err);
|
|
287
330
|
log.error(
|
|
288
331
|
{ err, pluginDir },
|
|
289
|
-
`Failed to
|
|
332
|
+
`Failed to build external plugin ${pluginDir}: ${message}`,
|
|
290
333
|
);
|
|
334
|
+
return undefined;
|
|
291
335
|
} finally {
|
|
292
336
|
if (timeoutHandle !== undefined) clearTimeout(timeoutHandle);
|
|
293
337
|
}
|
|
294
338
|
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Load the external plugin at `pluginDir` and register it. Thin wrapper
|
|
342
|
+
* over {@link buildExternalPlugin} that calls `registerPlugin` on the
|
|
343
|
+
* built plugin, preserving the existing `loadUserPlugins` call shape.
|
|
344
|
+
*/
|
|
345
|
+
export async function loadExternalPlugin(
|
|
346
|
+
pluginDir: string,
|
|
347
|
+
opts: LoadExternalPluginOptions = {},
|
|
348
|
+
): Promise<void> {
|
|
349
|
+
const plugin = await buildExternalPlugin(pluginDir, opts);
|
|
350
|
+
if (plugin === undefined) {
|
|
351
|
+
// buildExternalPlugin already logged the failure with attribution.
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
try {
|
|
355
|
+
registerPlugin(plugin);
|
|
356
|
+
log.info(
|
|
357
|
+
{ pluginDir, name: plugin.manifest.name },
|
|
358
|
+
"loaded external plugin",
|
|
359
|
+
);
|
|
360
|
+
} catch (err) {
|
|
361
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
362
|
+
log.error(
|
|
363
|
+
{ err, pluginDir, plugin: plugin.manifest.name },
|
|
364
|
+
`Failed to register external plugin ${pluginDir}: ${message}`,
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External-plugin feature gate.
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for whether the experimental external-plugin
|
|
5
|
+
* surface is enabled. The flag gates both the `assistant plugins` CLI
|
|
6
|
+
* command tree and (in future) the declarative external-plugin loader
|
|
7
|
+
* pathway in the daemon.
|
|
8
|
+
*
|
|
9
|
+
* The flag key uses the simple kebab-case format and is declared in
|
|
10
|
+
* `meta/feature-flags/feature-flag-registry.json`.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
|
|
14
|
+
import type { AssistantConfig } from "../config/schema.js";
|
|
15
|
+
|
|
16
|
+
/** Gate key for the external-plugin surface. */
|
|
17
|
+
const EXTERNAL_PLUGINS_FLAG_KEY = "external-plugins" as const;
|
|
18
|
+
|
|
19
|
+
/** Whether the external-plugin surface is enabled. */
|
|
20
|
+
export function isExternalPluginsEnabled(config: AssistantConfig): boolean {
|
|
21
|
+
return isAssistantFeatureFlagEnabled(EXTERNAL_PLUGINS_FLAG_KEY, config);
|
|
22
|
+
}
|
package/src/plugins/pipeline.ts
CHANGED
|
@@ -26,7 +26,9 @@
|
|
|
26
26
|
|
|
27
27
|
import type { Logger } from "pino";
|
|
28
28
|
|
|
29
|
+
import type { HookName } from "../plugin-api/constants.js";
|
|
29
30
|
import { getLogger } from "../util/logger.js";
|
|
31
|
+
import { getHooksFor } from "./registry.js";
|
|
30
32
|
import {
|
|
31
33
|
type Middleware,
|
|
32
34
|
type PipelineName,
|
|
@@ -314,3 +316,38 @@ export async function runPipeline<A, R>(
|
|
|
314
316
|
logger.info(record, "plugin.pipeline");
|
|
315
317
|
}
|
|
316
318
|
}
|
|
319
|
+
|
|
320
|
+
// ─── Hook runner ────────────────────────────────────────────────────────────
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Execute a hook chain: walk every registered plugin's hook for `name` in
|
|
324
|
+
* registration order, threading `initialCtx` through each. Hooks may either
|
|
325
|
+
* mutate the context in place (returning `void`) or return a new context
|
|
326
|
+
* that replaces the threaded value for subsequent hooks. The final context
|
|
327
|
+
* after the chain settles is returned.
|
|
328
|
+
*
|
|
329
|
+
* `runHook` is the hook-side counterpart to {@link runPipeline}:
|
|
330
|
+
* `runPipeline` composes middleware around a terminal handler for stateful
|
|
331
|
+
* request/response pipelines (memory retrieval, history repair, etc.);
|
|
332
|
+
* `runHook` walks ordered hook functions for declarative chain-style
|
|
333
|
+
* context transformations (`user-prompt-submit` today).
|
|
334
|
+
*
|
|
335
|
+
* @param name The hook identifier — pick one from {@link HOOKS}.
|
|
336
|
+
* @param initialCtx Context the first hook receives.
|
|
337
|
+
* @returns The final context after the chain settles. Same reference as
|
|
338
|
+
* `initialCtx` when no plugin registers `name`, and when every
|
|
339
|
+
* chained hook returns `void` (mutation-in-place style).
|
|
340
|
+
*/
|
|
341
|
+
export async function runHook<TCtx>(
|
|
342
|
+
name: HookName,
|
|
343
|
+
initialCtx: TCtx,
|
|
344
|
+
): Promise<TCtx> {
|
|
345
|
+
let active = initialCtx;
|
|
346
|
+
for (const hook of getHooksFor<TCtx>(name)) {
|
|
347
|
+
const result = await hook(active);
|
|
348
|
+
if (result !== undefined) {
|
|
349
|
+
active = result;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return active;
|
|
353
|
+
}
|
package/src/plugins/registry.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Plugin registry with manifest validation
|
|
2
|
+
* Plugin registry with manifest validation.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* `
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* Host-compat negotiation lives in the plugin's `package.json`
|
|
5
|
+
* `peerDependencies["@vellumai/plugin-api"]` semver range — the
|
|
6
|
+
* external-plugin loader checks it against the assistant version at
|
|
7
|
+
* load time. This module owns the rest of the manifest validation
|
|
8
|
+
* contract: name/version presence, duplicate-name detection, and the
|
|
9
|
+
* closed-registration latch that protects `bootstrapPlugins()` from
|
|
10
|
+
* late-arriving registrations.
|
|
10
11
|
*
|
|
11
12
|
* Registration is order-preserving: {@link getRegisteredPlugins},
|
|
12
13
|
* {@link getMiddlewaresFor}, and (secondarily) {@link getInjectors} all reflect
|
|
@@ -26,49 +27,9 @@ import {
|
|
|
26
27
|
type PipelineName,
|
|
27
28
|
type Plugin,
|
|
28
29
|
PluginExecutionError,
|
|
30
|
+
type PluginHookFn,
|
|
29
31
|
} from "./types.js";
|
|
30
32
|
|
|
31
|
-
/**
|
|
32
|
-
* Capability table declaring which plugin-facing API versions the assistant
|
|
33
|
-
* runtime exposes. Each capability maps to the list of supported semver-lite
|
|
34
|
-
* tags (currently a single `"v1"` per capability).
|
|
35
|
-
*
|
|
36
|
-
* New capabilities must be added here AND in their corresponding pipeline /
|
|
37
|
-
* runtime module so plugins can negotiate against them. Removing a version
|
|
38
|
-
* tag is a breaking change — all consumers in the plugin ecosystem relying on
|
|
39
|
-
* it will fail to register until they update their `requires` map.
|
|
40
|
-
*
|
|
41
|
-
* The `pluginRuntime` capability is the base runtime API every plugin must
|
|
42
|
-
* negotiate for; the remaining entries mirror {@link PipelineName} and the
|
|
43
|
-
* top-level context APIs plugins most commonly consume.
|
|
44
|
-
*/
|
|
45
|
-
export const ASSISTANT_API_VERSIONS: Record<string, string[]> = {
|
|
46
|
-
// Runtime APIs every plugin interacts with at some level. `memoryApi` is the
|
|
47
|
-
// broader memory-subsystem capability (distinct from the `memoryRetrieval`
|
|
48
|
-
// pipeline, which gets its own `memoryRetrievalApi` entry below).
|
|
49
|
-
pluginRuntime: ["v1"],
|
|
50
|
-
memoryApi: ["v1"],
|
|
51
|
-
compactionApi: ["v1"],
|
|
52
|
-
persistenceApi: ["v1"],
|
|
53
|
-
|
|
54
|
-
// Per-pipeline APIs. One entry for every slot in {@link PipelineName} that
|
|
55
|
-
// isn't already covered by the runtime-APIs block above (`compaction` and
|
|
56
|
-
// `persistence` live there because plugins commonly interact with them
|
|
57
|
-
// outside a pipeline middleware context).
|
|
58
|
-
turnApi: ["v1"],
|
|
59
|
-
llmCallApi: ["v1"],
|
|
60
|
-
toolExecuteApi: ["v1"],
|
|
61
|
-
memoryRetrievalApi: ["v1"],
|
|
62
|
-
historyRepairApi: ["v1"],
|
|
63
|
-
tokenEstimateApi: ["v1"],
|
|
64
|
-
overflowReduceApi: ["v1"],
|
|
65
|
-
titleGenerateApi: ["v1"],
|
|
66
|
-
toolResultTruncateApi: ["v1"],
|
|
67
|
-
emptyResponseApi: ["v1"],
|
|
68
|
-
toolErrorApi: ["v1"],
|
|
69
|
-
circuitBreakerApi: ["v1"],
|
|
70
|
-
};
|
|
71
|
-
|
|
72
33
|
// ─── Internal state ──────────────────────────────────────────────────────────
|
|
73
34
|
|
|
74
35
|
/**
|
|
@@ -96,11 +57,13 @@ let registrationClosed = false;
|
|
|
96
57
|
/**
|
|
97
58
|
* Validate and register a plugin. Throws {@link PluginExecutionError} if:
|
|
98
59
|
*
|
|
99
|
-
* - `manifest`, `manifest.name`, `manifest.version
|
|
100
|
-
* are missing.
|
|
60
|
+
* - `manifest`, `manifest.name`, or `manifest.version` are missing.
|
|
101
61
|
* - a plugin with the same name is already registered.
|
|
102
|
-
* -
|
|
103
|
-
*
|
|
62
|
+
* - registration has been closed by {@link closeRegistration}.
|
|
63
|
+
*
|
|
64
|
+
* Host-compat is checked upstream by the external-plugin loader against
|
|
65
|
+
* the plugin's `peerDependencies["@vellumai/plugin-api"]` semver range —
|
|
66
|
+
* the registry does not re-validate it here.
|
|
104
67
|
*
|
|
105
68
|
* On success the plugin is appended to the registry in the order this
|
|
106
69
|
* function is called. This function does NOT invoke `plugin.init()` — that
|
|
@@ -144,13 +107,6 @@ export function registerPlugin(plugin: Plugin): void {
|
|
|
144
107
|
name,
|
|
145
108
|
);
|
|
146
109
|
}
|
|
147
|
-
if (!manifest.requires || typeof manifest.requires !== "object") {
|
|
148
|
-
throw new PluginExecutionError(
|
|
149
|
-
`plugin ${name} manifest.requires is required`,
|
|
150
|
-
name,
|
|
151
|
-
);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
110
|
// Duplicate-name check — plugins must be uniquely addressable in logs,
|
|
155
111
|
// storage paths, and error messages. Runs BEFORE the closed-registration
|
|
156
112
|
// check so `registerDefaultPlugins()` (which replays every default even
|
|
@@ -174,27 +130,6 @@ export function registerPlugin(plugin: Plugin): void {
|
|
|
174
130
|
);
|
|
175
131
|
}
|
|
176
132
|
|
|
177
|
-
// Capability negotiation. Every plugin must negotiate against
|
|
178
|
-
// `pluginRuntime`; we enforce that by requiring an entry to exist rather
|
|
179
|
-
// than special-casing it here, so the per-entry mismatch error is uniform.
|
|
180
|
-
if (!("pluginRuntime" in manifest.requires)) {
|
|
181
|
-
throw new PluginExecutionError(
|
|
182
|
-
`plugin ${name} must declare requires.pluginRuntime (e.g. "v1")`,
|
|
183
|
-
name,
|
|
184
|
-
);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
for (const [api, requiredVersion] of Object.entries(manifest.requires)) {
|
|
188
|
-
const supported = ASSISTANT_API_VERSIONS[api];
|
|
189
|
-
if (!supported || !supported.includes(requiredVersion)) {
|
|
190
|
-
const exposed = supported ? supported.join(", ") : "(none)";
|
|
191
|
-
throw new PluginExecutionError(
|
|
192
|
-
`plugin ${name} requires ${api}@${requiredVersion}, assistant exposes ${exposed}`,
|
|
193
|
-
name,
|
|
194
|
-
);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
133
|
registeredPlugins.set(name, plugin);
|
|
199
134
|
}
|
|
200
135
|
|
|
@@ -230,6 +165,31 @@ export function getMiddlewaresFor<P extends PipelineName>(
|
|
|
230
165
|
return out;
|
|
231
166
|
}
|
|
232
167
|
|
|
168
|
+
/**
|
|
169
|
+
* Collect every registered plugin's hook for the given name, in
|
|
170
|
+
* registration order. Plugins that don't declare a hook for `name` are
|
|
171
|
+
* skipped. Used by the daemon to invoke chain-style hooks like
|
|
172
|
+
* `user-prompt-submit` where each plugin's hook may transform a shared
|
|
173
|
+
* context.
|
|
174
|
+
*
|
|
175
|
+
* The `TCtx` generic mirrors {@link PluginHookFn}'s — callers parameterize
|
|
176
|
+
* over the concrete context type their hook receives. Hooks that mutate
|
|
177
|
+
* the context in place return `void`; hooks that return a new context
|
|
178
|
+
* replace the threaded value for the next hook in the chain.
|
|
179
|
+
*/
|
|
180
|
+
export function getHooksFor<TCtx = unknown>(
|
|
181
|
+
name: string,
|
|
182
|
+
): PluginHookFn<TCtx>[] {
|
|
183
|
+
const out: PluginHookFn<TCtx>[] = [];
|
|
184
|
+
for (const plugin of registeredPlugins.values()) {
|
|
185
|
+
const hook = plugin.hooks?.[name];
|
|
186
|
+
if (hook) {
|
|
187
|
+
out.push(hook as PluginHookFn<TCtx>);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return out;
|
|
191
|
+
}
|
|
192
|
+
|
|
233
193
|
/**
|
|
234
194
|
* Flatten every registered plugin's `injectors` array and sort the result by
|
|
235
195
|
* `order` ascending. Two injectors with the same `order` retain their relative
|
|
@@ -275,6 +235,14 @@ export function unregisterPlugin(name: string): void {
|
|
|
275
235
|
registeredPlugins.delete(name);
|
|
276
236
|
}
|
|
277
237
|
|
|
238
|
+
export function getRegisteredPlugin(name: string): Plugin | undefined {
|
|
239
|
+
return registeredPlugins.get(name);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function setRegisteredPlugin(plugin: Plugin): void {
|
|
243
|
+
registeredPlugins.set(plugin.manifest.name, plugin);
|
|
244
|
+
}
|
|
245
|
+
|
|
278
246
|
// ─── Test hooks ──────────────────────────────────────────────────────────────
|
|
279
247
|
|
|
280
248
|
/**
|
package/src/plugins/types.ts
CHANGED
|
@@ -42,7 +42,11 @@ import type {
|
|
|
42
42
|
ToolDefinition,
|
|
43
43
|
} from "../providers/types.js";
|
|
44
44
|
import type { SkillRoute } from "../runtime/skill-route-registry.js";
|
|
45
|
-
import type {
|
|
45
|
+
import type {
|
|
46
|
+
PluginTool,
|
|
47
|
+
ToolContext,
|
|
48
|
+
ToolExecutionResult,
|
|
49
|
+
} from "../tools/types.js";
|
|
46
50
|
import { AssistantError, ErrorCode } from "../util/errors.js";
|
|
47
51
|
|
|
48
52
|
// ─── Manifest ────────────────────────────────────────────────────────────────
|
|
@@ -60,22 +64,13 @@ import { AssistantError, ErrorCode } from "../util/errors.js";
|
|
|
60
64
|
export interface PluginManifest {
|
|
61
65
|
/** Unique plugin identifier (kebab-case). Duplicate names fail registration. */
|
|
62
66
|
name: string;
|
|
63
|
-
/** Plugin version (semver). Informational — the registry compares
|
|
64
|
-
* capability versions via `requires`, not this field. */
|
|
65
|
-
version: string;
|
|
66
67
|
/**
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
* can land without a manifest version bump, but today nothing reads it and
|
|
72
|
-
* plugins must not depend on it for capability discovery. See
|
|
73
|
-
* `assistant/docs/plugins.md` (Cross-plugin communication) for the
|
|
74
|
-
* rationale.
|
|
68
|
+
* Plugin version (semver). Informational. Host-compat negotiation lives
|
|
69
|
+
* in the plugin's `package.json` `peerDependencies["@vellumai/plugin-api"]`
|
|
70
|
+
* range — checked by the external-plugin loader against the assistant's
|
|
71
|
+
* own version at load time.
|
|
75
72
|
*/
|
|
76
|
-
|
|
77
|
-
/** Capabilities this plugin needs from the assistant runtime. */
|
|
78
|
-
requires: Record<string, string>;
|
|
73
|
+
version: string;
|
|
79
74
|
/** Credential keys the plugin needs resolved before `init()` runs. */
|
|
80
75
|
requiresCredential?: string[];
|
|
81
76
|
/**
|
|
@@ -844,6 +839,17 @@ export interface TurnInjectionInputs {
|
|
|
844
839
|
* knows no human is present to answer clarification questions.
|
|
845
840
|
*/
|
|
846
841
|
readonly isNonInteractive?: boolean;
|
|
842
|
+
/**
|
|
843
|
+
* Active documents open in this conversation — surfaced by the
|
|
844
|
+
* `active-documents` injector so the assistant can target existing docs
|
|
845
|
+
* with `document_update` instead of creating duplicates.
|
|
846
|
+
*/
|
|
847
|
+
readonly activeDocuments?: ReadonlyArray<{
|
|
848
|
+
surfaceId: string;
|
|
849
|
+
title: string;
|
|
850
|
+
wordCount: number;
|
|
851
|
+
updatedAt: number;
|
|
852
|
+
}> | null;
|
|
847
853
|
}
|
|
848
854
|
|
|
849
855
|
export interface DiskPressureInjectionContext {
|
|
@@ -995,15 +1001,16 @@ export interface Injector {
|
|
|
995
1001
|
// catalog-discoverable skills today.
|
|
996
1002
|
|
|
997
1003
|
/**
|
|
998
|
-
* Tool registration contributed by a plugin. Uses the
|
|
999
|
-
*
|
|
1000
|
-
*
|
|
1001
|
-
*
|
|
1002
|
-
*
|
|
1003
|
-
*
|
|
1004
|
-
*
|
|
1004
|
+
* Tool registration contributed by a plugin. Uses the narrow
|
|
1005
|
+
* {@link PluginTool} shape — plugin authors declare functional fields
|
|
1006
|
+
* (`name`, `description`, `input_schema`, `execute`, etc.) and leave category
|
|
1007
|
+
* / ownership metadata to the bootstrap, which stamps `category: "plugin"`,
|
|
1008
|
+
* `origin: "plugin"`, and `ownerPluginId: <plugin.name>` before handing the
|
|
1009
|
+
* batch to `registerPluginTools`. The registration boundary synthesizes
|
|
1010
|
+
* `getDefinition()` from `{name, description, input_schema}` so the canonical
|
|
1011
|
+
* {@link Tool} interface used by the internal registry stays unchanged.
|
|
1005
1012
|
*/
|
|
1006
|
-
export type PluginToolRegistration =
|
|
1013
|
+
export type PluginToolRegistration = PluginTool;
|
|
1007
1014
|
/**
|
|
1008
1015
|
* HTTP route registration contributed by a plugin. Plugins express routes as
|
|
1009
1016
|
* {@link SkillRoute} values — the same shape the skill-route registry
|
|
@@ -1075,9 +1082,7 @@ export interface PluginSkillRegistration {
|
|
|
1075
1082
|
* Unknown keys are populated by the loader for forward compatibility
|
|
1076
1083
|
* but are not invoked by today's runtime.
|
|
1077
1084
|
*/
|
|
1078
|
-
export type PluginHookFn<TCtx = unknown> = (
|
|
1079
|
-
ctx: TCtx,
|
|
1080
|
-
) => Promise<TCtx | void>;
|
|
1085
|
+
export type PluginHookFn<TCtx = unknown> = (ctx: TCtx) => Promise<TCtx | void>;
|
|
1081
1086
|
|
|
1082
1087
|
/**
|
|
1083
1088
|
* Map of lifecycle hooks contributed by a plugin. Keys match file
|
|
@@ -49,7 +49,8 @@ import { join } from "node:path";
|
|
|
49
49
|
import { pathToFileURL } from "node:url";
|
|
50
50
|
|
|
51
51
|
import { getLogger } from "../util/logger.js";
|
|
52
|
-
import {
|
|
52
|
+
import { getWorkspacePluginsDir } from "../util/platform.js";
|
|
53
|
+
import { ensurePluginApiShim } from "./ensure-plugin-api-shim.js";
|
|
53
54
|
import { loadExternalPlugin } from "./external-plugin-loader.js";
|
|
54
55
|
import { closeRegistration } from "./registry.js";
|
|
55
56
|
|
|
@@ -93,7 +94,25 @@ export async function loadUserPlugins(
|
|
|
93
94
|
): Promise<void> {
|
|
94
95
|
const importTimeoutMs = options.importTimeoutMs ?? USER_PLUGIN_IMPORT_TIMEOUT_MS;
|
|
95
96
|
|
|
96
|
-
|
|
97
|
+
// Materialize the workspace-level `@vellumai/plugin-api` shim *before*
|
|
98
|
+
// we dynamic-import any user plugins. The shim file must exist on disk
|
|
99
|
+
// before the first plugin's `import "@vellumai/plugin-api"` is parsed.
|
|
100
|
+
//
|
|
101
|
+
// Wrapped in try/catch because per `AGENTS.md` the daemon must never
|
|
102
|
+
// block startup. A shim-write failure (ENOSPC, read-only workspace,
|
|
103
|
+
// perms) is logged and we continue — plugins that try to import the
|
|
104
|
+
// public specifier will fail individually inside the per-plugin import
|
|
105
|
+
// loop below, which is already isolated.
|
|
106
|
+
try {
|
|
107
|
+
await ensurePluginApiShim();
|
|
108
|
+
} catch (err) {
|
|
109
|
+
log.warn(
|
|
110
|
+
{ err },
|
|
111
|
+
"loadUserPlugins: plugin-api shim materialization failed — continuing with degraded plugin support",
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const pluginsDir = getWorkspacePluginsDir();
|
|
97
116
|
|
|
98
117
|
if (!existsSync(pluginsDir)) {
|
|
99
118
|
// The clean-install case. Closing the registration window keeps the
|
|
@@ -8,6 +8,10 @@
|
|
|
8
8
|
|
|
9
9
|
import { createAssistantMessage } from "../agent/message-types.js";
|
|
10
10
|
import { findConversation } from "../daemon/conversation-store.js";
|
|
11
|
+
import {
|
|
12
|
+
conversationMessagesSyncTag,
|
|
13
|
+
SYNC_TAGS,
|
|
14
|
+
} from "../daemon/message-types/sync.js";
|
|
11
15
|
import { addMessage } from "../memory/conversation-crud.js";
|
|
12
16
|
import type { BroadcastFn } from "../notifications/adapters/macos.js";
|
|
13
17
|
import { getLogger } from "../util/logger.js";
|
|
@@ -71,4 +75,11 @@ export async function injectAuxAssistantMessage(params: {
|
|
|
71
75
|
type: "conversation_list_invalidated",
|
|
72
76
|
reason: "reordered",
|
|
73
77
|
});
|
|
78
|
+
params.broadcastMessage({
|
|
79
|
+
type: "sync_changed",
|
|
80
|
+
tags: [
|
|
81
|
+
SYNC_TAGS.conversationsList,
|
|
82
|
+
conversationMessagesSyncTag(params.conversationId),
|
|
83
|
+
],
|
|
84
|
+
});
|
|
74
85
|
}
|