@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
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { getDocumentById } from "../documents/document-store.js";
|
|
4
|
+
import { getSqlite, resetDb } from "../memory/db-connection.js";
|
|
5
|
+
import {
|
|
6
|
+
executeDocumentDelete,
|
|
7
|
+
executeDocumentList,
|
|
8
|
+
executeDocumentRead,
|
|
9
|
+
executeDocumentUpdate,
|
|
10
|
+
} from "../tools/document/document-tool.js";
|
|
11
|
+
import type { ToolContext, ToolExecutionResult } from "../tools/types.js";
|
|
12
|
+
|
|
13
|
+
function makeContext(overrides: Partial<ToolContext> = {}): ToolContext {
|
|
14
|
+
return {
|
|
15
|
+
workingDir: "/tmp/project",
|
|
16
|
+
conversationId: "conv-current",
|
|
17
|
+
trustClass: "trusted_contact",
|
|
18
|
+
executionChannel: "slack",
|
|
19
|
+
...overrides,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function parseResult<T>(result: ToolExecutionResult): T {
|
|
24
|
+
return JSON.parse(result.content) as T;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function bootstrapDocumentTables(): void {
|
|
28
|
+
resetDb();
|
|
29
|
+
const raw = getSqlite();
|
|
30
|
+
raw.exec(/*sql*/ `
|
|
31
|
+
DROP TABLE IF EXISTS document_conversations;
|
|
32
|
+
DROP TABLE IF EXISTS documents;
|
|
33
|
+
DROP TABLE IF EXISTS conversations;
|
|
34
|
+
|
|
35
|
+
CREATE TABLE conversations (
|
|
36
|
+
id TEXT PRIMARY KEY,
|
|
37
|
+
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
CREATE TABLE documents (
|
|
41
|
+
surface_id TEXT PRIMARY KEY,
|
|
42
|
+
conversation_id TEXT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
|
|
43
|
+
title TEXT NOT NULL,
|
|
44
|
+
content TEXT NOT NULL,
|
|
45
|
+
word_count INTEGER NOT NULL DEFAULT 0,
|
|
46
|
+
created_at INTEGER NOT NULL,
|
|
47
|
+
updated_at INTEGER NOT NULL
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
CREATE TABLE document_conversations (
|
|
51
|
+
surface_id TEXT NOT NULL,
|
|
52
|
+
conversation_id TEXT NOT NULL,
|
|
53
|
+
created_at INTEGER NOT NULL,
|
|
54
|
+
PRIMARY KEY (surface_id, conversation_id),
|
|
55
|
+
FOREIGN KEY (surface_id) REFERENCES documents(surface_id) ON DELETE CASCADE
|
|
56
|
+
);
|
|
57
|
+
`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function seedDocument(params: {
|
|
61
|
+
surfaceId: string;
|
|
62
|
+
conversationId: string;
|
|
63
|
+
title: string;
|
|
64
|
+
content: string;
|
|
65
|
+
updatedAt: number;
|
|
66
|
+
}): void {
|
|
67
|
+
const raw = getSqlite();
|
|
68
|
+
raw
|
|
69
|
+
.query(`INSERT OR IGNORE INTO conversations (id, created_at) VALUES (?, ?)`)
|
|
70
|
+
.run(params.conversationId, params.updatedAt);
|
|
71
|
+
raw
|
|
72
|
+
.query(
|
|
73
|
+
`INSERT INTO documents (surface_id, conversation_id, title, content, word_count, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
74
|
+
)
|
|
75
|
+
.run(
|
|
76
|
+
params.surfaceId,
|
|
77
|
+
params.conversationId,
|
|
78
|
+
params.title,
|
|
79
|
+
params.content,
|
|
80
|
+
params.content.split(/\s+/).filter(Boolean).length,
|
|
81
|
+
params.updatedAt,
|
|
82
|
+
params.updatedAt,
|
|
83
|
+
);
|
|
84
|
+
raw
|
|
85
|
+
.query(
|
|
86
|
+
`INSERT OR IGNORE INTO document_conversations (surface_id, conversation_id, created_at) VALUES (?, ?, ?)`,
|
|
87
|
+
)
|
|
88
|
+
.run(params.surfaceId, params.conversationId, params.updatedAt);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function seedFixtureDocuments(): void {
|
|
92
|
+
seedDocument({
|
|
93
|
+
surfaceId: "doc-current",
|
|
94
|
+
conversationId: "conv-current",
|
|
95
|
+
title: "Current Business Plan",
|
|
96
|
+
content: "current plan",
|
|
97
|
+
updatedAt: 1000,
|
|
98
|
+
});
|
|
99
|
+
seedDocument({
|
|
100
|
+
surfaceId: "doc-other",
|
|
101
|
+
conversationId: "conv-other",
|
|
102
|
+
title: "Other Business Plan",
|
|
103
|
+
content: "other plan",
|
|
104
|
+
updatedAt: 2000,
|
|
105
|
+
});
|
|
106
|
+
seedDocument({
|
|
107
|
+
surfaceId: "doc-percent",
|
|
108
|
+
conversationId: "conv-other",
|
|
109
|
+
title: "100% Plan",
|
|
110
|
+
content: "literal percent",
|
|
111
|
+
updatedAt: 3000,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
describe("document tool security", () => {
|
|
116
|
+
beforeEach(() => {
|
|
117
|
+
bootstrapDocumentTables();
|
|
118
|
+
seedFixtureDocuments();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("scopes title search to the current conversation for non-guardian remote actors", () => {
|
|
122
|
+
const result = executeDocumentList(
|
|
123
|
+
{ query: "Business Plan" },
|
|
124
|
+
makeContext({ trustClass: "trusted_contact", executionChannel: "slack" }),
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
const body = parseResult<{ documents: Array<{ surface_id: string }> }>(
|
|
128
|
+
result,
|
|
129
|
+
);
|
|
130
|
+
expect(body.documents.map((doc) => doc.surface_id)).toEqual([
|
|
131
|
+
"doc-current",
|
|
132
|
+
]);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("does not treat SQL LIKE wildcards as title-search wildcards", () => {
|
|
136
|
+
const result = executeDocumentList(
|
|
137
|
+
{ query: "%" },
|
|
138
|
+
makeContext({ trustClass: "guardian", executionChannel: "telegram" }),
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
const body = parseResult<{ documents: Array<{ surface_id: string }> }>(
|
|
142
|
+
result,
|
|
143
|
+
);
|
|
144
|
+
expect(body.documents.map((doc) => doc.surface_id)).toEqual([
|
|
145
|
+
"doc-percent",
|
|
146
|
+
]);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("allows guardian and local actors to use documents from previous conversations", () => {
|
|
150
|
+
const guardianContext = makeContext({
|
|
151
|
+
trustClass: "guardian",
|
|
152
|
+
executionChannel: "telegram",
|
|
153
|
+
sendToClient: () => {},
|
|
154
|
+
});
|
|
155
|
+
const guardianList = executeDocumentList(
|
|
156
|
+
{ query: "Other Business" },
|
|
157
|
+
guardianContext,
|
|
158
|
+
);
|
|
159
|
+
const guardianBody = parseResult<{
|
|
160
|
+
documents: Array<{ surface_id: string }>;
|
|
161
|
+
}>(guardianList);
|
|
162
|
+
expect(guardianBody.documents.map((doc) => doc.surface_id)).toEqual([
|
|
163
|
+
"doc-other",
|
|
164
|
+
]);
|
|
165
|
+
|
|
166
|
+
const localRead = executeDocumentRead(
|
|
167
|
+
{ surface_id: "doc-other" },
|
|
168
|
+
makeContext({ trustClass: "unknown", executionChannel: "vellum" }),
|
|
169
|
+
);
|
|
170
|
+
const localBody = parseResult<{
|
|
171
|
+
success: boolean;
|
|
172
|
+
surface_id: string;
|
|
173
|
+
content: string;
|
|
174
|
+
}>(localRead);
|
|
175
|
+
expect(localBody).toMatchObject({
|
|
176
|
+
success: true,
|
|
177
|
+
surface_id: "doc-other",
|
|
178
|
+
content: "other plan",
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
const guardianUpdate = executeDocumentUpdate(
|
|
182
|
+
{ surface_id: "doc-other", content: "guardian edit", mode: "replace" },
|
|
183
|
+
guardianContext,
|
|
184
|
+
);
|
|
185
|
+
expect(guardianUpdate.isError).toBe(false);
|
|
186
|
+
expect(getDocumentById("doc-other")?.content).toBe("guardian edit");
|
|
187
|
+
|
|
188
|
+
const guardianDelete = executeDocumentDelete(
|
|
189
|
+
{ surface_id: "doc-other" },
|
|
190
|
+
guardianContext,
|
|
191
|
+
);
|
|
192
|
+
expect(guardianDelete.isError).toBe(false);
|
|
193
|
+
expect(getDocumentById("doc-other")).toBeNull();
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test("blocks cross-conversation read, update, and delete for non-guardian remote actors", () => {
|
|
197
|
+
const remoteContext = makeContext({
|
|
198
|
+
trustClass: "trusted_contact",
|
|
199
|
+
executionChannel: "slack",
|
|
200
|
+
sendToClient: () => {},
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const read = executeDocumentRead(
|
|
204
|
+
{ surface_id: "doc-other" },
|
|
205
|
+
remoteContext,
|
|
206
|
+
);
|
|
207
|
+
expect(read.isError).toBe(true);
|
|
208
|
+
expect(parseResult<{ error: string }>(read).error).toBe(
|
|
209
|
+
"Document not found",
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
const update = executeDocumentUpdate(
|
|
213
|
+
{
|
|
214
|
+
surface_id: "doc-other",
|
|
215
|
+
content: "updated by another conversation",
|
|
216
|
+
mode: "replace",
|
|
217
|
+
},
|
|
218
|
+
remoteContext,
|
|
219
|
+
);
|
|
220
|
+
expect(update.isError).toBe(true);
|
|
221
|
+
expect(getDocumentById("doc-other")?.content).toBe("other plan");
|
|
222
|
+
|
|
223
|
+
const deleted = executeDocumentDelete(
|
|
224
|
+
{ surface_id: "doc-other" },
|
|
225
|
+
remoteContext,
|
|
226
|
+
);
|
|
227
|
+
expect(deleted.isError).toBe(true);
|
|
228
|
+
expect(getDocumentById("doc-other")).not.toBeNull();
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test("keeps current-conversation documents editable and deletable", () => {
|
|
232
|
+
const remoteContext = makeContext({
|
|
233
|
+
trustClass: "trusted_contact",
|
|
234
|
+
executionChannel: "slack",
|
|
235
|
+
sendToClient: () => {},
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
const read = executeDocumentRead(
|
|
239
|
+
{ surface_id: "doc-current" },
|
|
240
|
+
remoteContext,
|
|
241
|
+
);
|
|
242
|
+
expect(read.isError).toBe(false);
|
|
243
|
+
|
|
244
|
+
const update = executeDocumentUpdate(
|
|
245
|
+
{ surface_id: "doc-current", content: "revised plan", mode: "replace" },
|
|
246
|
+
remoteContext,
|
|
247
|
+
);
|
|
248
|
+
expect(update.isError).toBe(false);
|
|
249
|
+
expect(getDocumentById("doc-current")?.content).toBe("revised plan");
|
|
250
|
+
|
|
251
|
+
const deleted = executeDocumentDelete(
|
|
252
|
+
{ surface_id: "doc-current" },
|
|
253
|
+
remoteContext,
|
|
254
|
+
);
|
|
255
|
+
expect(deleted.isError).toBe(false);
|
|
256
|
+
expect(getDocumentById("doc-current")).toBeNull();
|
|
257
|
+
});
|
|
258
|
+
});
|
|
@@ -74,7 +74,6 @@ describe("Dynamic Skill Authoring Workflow moved to tool descriptions", () => {
|
|
|
74
74
|
join(skillsDir, "test-skill", "SKILL.md"),
|
|
75
75
|
'---\nname: "Test Skill"\ndescription: "For testing."\n---\n\nDo testing.\n',
|
|
76
76
|
);
|
|
77
|
-
writeFileSync(join(skillsDir, "SKILLS.md"), "- test-skill\n");
|
|
78
77
|
writeFileSync(join(TEST_DIR, "IDENTITY.md"), "I am Vellum.");
|
|
79
78
|
|
|
80
79
|
const result = buildSystemPrompt();
|
|
@@ -19,6 +19,7 @@ mock.module("../util/logger.js", () => ({
|
|
|
19
19
|
}));
|
|
20
20
|
|
|
21
21
|
import { addMessage } from "../memory/conversation-crud.js";
|
|
22
|
+
import { getConversationByKey } from "../memory/conversation-key-store.js";
|
|
22
23
|
import { getDb } from "../memory/db-connection.js";
|
|
23
24
|
import { initializeDb } from "../memory/db-init.js";
|
|
24
25
|
import { linkMessage, recordInbound } from "../memory/delivery-crud.js";
|
|
@@ -160,6 +161,38 @@ describe("Slack edit propagation", () => {
|
|
|
160
161
|
expect(slackMeta!.editedAt!).toBeGreaterThanOrEqual(t0);
|
|
161
162
|
});
|
|
162
163
|
|
|
164
|
+
test("threaded Slack edits use the threaded conversation key and preserve thread metadata", async () => {
|
|
165
|
+
const conversationExternalId = "C0123CHANNEL";
|
|
166
|
+
const threadTs = "1234.0000";
|
|
167
|
+
const seeded = await seedSlackMessage({
|
|
168
|
+
conversationExternalId,
|
|
169
|
+
channelTs: "1234.5678",
|
|
170
|
+
initialContent: "original text",
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
const resp = await handleEditIntercept({
|
|
174
|
+
sourceChannel: "slack",
|
|
175
|
+
conversationExternalId,
|
|
176
|
+
externalMessageId: nextEditEventId(),
|
|
177
|
+
sourceMessageId: seeded.channelTs,
|
|
178
|
+
sourceThreadId: threadTs,
|
|
179
|
+
canonicalAssistantId: "self",
|
|
180
|
+
assistantId: "self",
|
|
181
|
+
content: "new text",
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
expect((resp as Record<string, unknown>).accepted).toBe(true);
|
|
185
|
+
const threadedKey = `asst:self:slack:${conversationExternalId}:thread:${threadTs}`;
|
|
186
|
+
const editConversation = getConversationByKey(threadedKey);
|
|
187
|
+
expect(editConversation).not.toBeNull();
|
|
188
|
+
expect(editConversation!.conversationId).not.toBe(seeded.conversationId);
|
|
189
|
+
|
|
190
|
+
const after = readMessageRow(seeded.messageId);
|
|
191
|
+
const outer = JSON.parse(after.metadata!) as Record<string, unknown>;
|
|
192
|
+
const slackMeta = readSlackMetadata(outer.slackMeta as string);
|
|
193
|
+
expect(slackMeta?.threadTs).toBe(threadTs);
|
|
194
|
+
});
|
|
195
|
+
|
|
163
196
|
test("is idempotent across successive edits", async () => {
|
|
164
197
|
const seeded = await seedSlackMessage({
|
|
165
198
|
conversationExternalId: "C0123CHANNEL",
|
|
@@ -192,7 +192,6 @@ describe("emptyResponse pipeline — custom middleware overrides", () => {
|
|
|
192
192
|
manifest: {
|
|
193
193
|
name: "force-accept",
|
|
194
194
|
version: "1.0.0",
|
|
195
|
-
requires: { pluginRuntime: "v1" },
|
|
196
195
|
},
|
|
197
196
|
middleware: {
|
|
198
197
|
emptyResponse: async () => ({ action: "accept" }),
|
|
@@ -219,7 +218,6 @@ describe("emptyResponse pipeline — custom middleware overrides", () => {
|
|
|
219
218
|
manifest: {
|
|
220
219
|
name: "force-error",
|
|
221
220
|
version: "1.0.0",
|
|
222
|
-
requires: { pluginRuntime: "v1" },
|
|
223
221
|
},
|
|
224
222
|
middleware: {
|
|
225
223
|
emptyResponse: async () => ({ action: "error" }),
|
|
@@ -239,7 +237,6 @@ describe("emptyResponse pipeline — custom middleware overrides", () => {
|
|
|
239
237
|
manifest: {
|
|
240
238
|
name: "rewrite-nudge",
|
|
241
239
|
version: "1.0.0",
|
|
242
|
-
requires: { pluginRuntime: "v1" },
|
|
243
240
|
},
|
|
244
241
|
middleware: {
|
|
245
242
|
emptyResponse: async (args, next, ctx) => {
|
|
@@ -287,7 +284,6 @@ describe("emptyResponse pipeline — custom middleware overrides", () => {
|
|
|
287
284
|
manifest: {
|
|
288
285
|
name: "late-user-empty-response",
|
|
289
286
|
version: "0.0.1",
|
|
290
|
-
requires: { pluginRuntime: "v1", emptyResponseApi: "v1" },
|
|
291
287
|
},
|
|
292
288
|
middleware: { emptyResponse: userMiddleware },
|
|
293
289
|
});
|
|
@@ -70,8 +70,6 @@ describe("loadExternalPlugin — manifest", () => {
|
|
|
70
70
|
);
|
|
71
71
|
expect(registered).toBeDefined();
|
|
72
72
|
expect(registered?.manifest.version).toBe("1.2.3");
|
|
73
|
-
// Defaults to pluginRuntime v1 when no `vellum.requires` is set.
|
|
74
|
-
expect(registered?.manifest.requires).toEqual({ pluginRuntime: "v1" });
|
|
75
73
|
});
|
|
76
74
|
|
|
77
75
|
test("strips npm scope from name", async () => {
|
|
@@ -97,56 +95,84 @@ describe("loadExternalPlugin — manifest", () => {
|
|
|
97
95
|
);
|
|
98
96
|
expect(registered?.manifest.version).toBe("0.0.0");
|
|
99
97
|
});
|
|
98
|
+
});
|
|
100
99
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const dir = freshPluginDir("custom-requires");
|
|
100
|
+
describe("loadExternalPlugin — plugin-api peerDependency", () => {
|
|
101
|
+
// Tests anchor against assistantPkg.version (read from the assistant's
|
|
102
|
+
// own package.json) so the matrix below stays correct across version
|
|
103
|
+
// bumps. Constructing a range from the live version + nudging up/down
|
|
104
|
+
// by one keeps the satisfy/un-satisfy cases honest.
|
|
105
|
+
test("loads when peerDependency range satisfies assistant version", async () => {
|
|
106
|
+
const dir = freshPluginDir("compat-ok");
|
|
109
107
|
writePackageJson(dir, {
|
|
110
|
-
name: "
|
|
108
|
+
name: "compat-ok",
|
|
111
109
|
version: "0.1.0",
|
|
112
|
-
|
|
110
|
+
peerDependencies: { "@vellumai/plugin-api": "*" },
|
|
113
111
|
});
|
|
114
112
|
|
|
115
113
|
await loadExternalPlugin(dir);
|
|
116
114
|
|
|
117
|
-
|
|
118
|
-
(p) => p.manifest.name === "custom-requires",
|
|
119
|
-
);
|
|
120
|
-
expect(registered?.manifest.requires).toEqual({ pluginRuntime: "v1" });
|
|
115
|
+
expect(registeredNames()).toContain("compat-ok");
|
|
121
116
|
});
|
|
122
117
|
|
|
123
|
-
test("
|
|
124
|
-
//
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
const dir = freshPluginDir("
|
|
118
|
+
test("loads plugin whose peerDependency range excludes assistant version (logs error)", async () => {
|
|
119
|
+
// The host-compat gate is soft while the installation flow is in
|
|
120
|
+
// flux — an unsatisfied range produces a `log.error` but the
|
|
121
|
+
// plugin still loads. Once installation settles, this case should
|
|
122
|
+
// harden back into a hard reject.
|
|
123
|
+
const dir = freshPluginDir("compat-bad");
|
|
129
124
|
writePackageJson(dir, {
|
|
130
|
-
name: "
|
|
125
|
+
name: "compat-bad",
|
|
131
126
|
version: "0.1.0",
|
|
132
|
-
|
|
127
|
+
// A range that no real assistant version will satisfy.
|
|
128
|
+
peerDependencies: { "@vellumai/plugin-api": ">=999.0.0" },
|
|
133
129
|
});
|
|
134
130
|
|
|
135
131
|
await loadExternalPlugin(dir);
|
|
136
132
|
|
|
137
|
-
expect(registeredNames()).
|
|
133
|
+
expect(registeredNames()).toContain("compat-bad");
|
|
138
134
|
});
|
|
139
135
|
|
|
140
|
-
test("
|
|
141
|
-
|
|
142
|
-
|
|
136
|
+
test("loads plugin whose peerDependency range is unparseable (logs error)", async () => {
|
|
137
|
+
// Same soft-gate rationale as the excluded-range case above.
|
|
138
|
+
const dir = freshPluginDir("compat-bogus");
|
|
139
|
+
writePackageJson(dir, {
|
|
140
|
+
name: "compat-bogus",
|
|
141
|
+
version: "0.1.0",
|
|
142
|
+
peerDependencies: { "@vellumai/plugin-api": "not-a-real-range" },
|
|
143
|
+
});
|
|
143
144
|
|
|
144
145
|
await loadExternalPlugin(dir);
|
|
145
146
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
147
|
+
expect(registeredNames()).toContain("compat-bogus");
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("loads with warning when no peerDependency on plugin-api is declared", async () => {
|
|
151
|
+
// Absent peerDep is non-fatal — the loader logs a warn and proceeds
|
|
152
|
+
// with no host-compat claim. The convention is opt-in while the
|
|
153
|
+
// plugin-api framework is experimental.
|
|
154
|
+
const dir = freshPluginDir("compat-absent");
|
|
155
|
+
writePackageJson(dir, {
|
|
156
|
+
name: "compat-absent",
|
|
157
|
+
version: "0.1.0",
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
await loadExternalPlugin(dir);
|
|
161
|
+
|
|
162
|
+
expect(registeredNames()).toContain("compat-absent");
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("loads with warning when peerDependencies is present but lacks plugin-api key", async () => {
|
|
166
|
+
const dir = freshPluginDir("compat-other-peer");
|
|
167
|
+
writePackageJson(dir, {
|
|
168
|
+
name: "compat-other-peer",
|
|
169
|
+
version: "0.1.0",
|
|
170
|
+
peerDependencies: { react: "^18.0.0" },
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
await loadExternalPlugin(dir);
|
|
174
|
+
|
|
175
|
+
expect(registeredNames()).toContain("compat-other-peer");
|
|
150
176
|
});
|
|
151
177
|
|
|
152
178
|
test("malformed package.json is logged and skipped (registry untouched)", async () => {
|
|
@@ -321,9 +347,8 @@ describe("loadExternalPlugin — tools", () => {
|
|
|
321
347
|
`export default {
|
|
322
348
|
name: "two_tools_alpha",
|
|
323
349
|
description: "alpha",
|
|
324
|
-
category: "plugin",
|
|
325
350
|
defaultRiskLevel: "low" as const,
|
|
326
|
-
|
|
351
|
+
input_schema: { type: "object", properties: {}, required: [] },
|
|
327
352
|
async execute() { return { content: "a", isError: false }; },
|
|
328
353
|
};
|
|
329
354
|
`,
|
|
@@ -334,9 +359,8 @@ describe("loadExternalPlugin — tools", () => {
|
|
|
334
359
|
`export default {
|
|
335
360
|
name: "two_tools_beta",
|
|
336
361
|
description: "beta",
|
|
337
|
-
category: "plugin",
|
|
338
362
|
defaultRiskLevel: "low" as const,
|
|
339
|
-
|
|
363
|
+
input_schema: { type: "object", properties: {}, required: [] },
|
|
340
364
|
async execute() { return { content: "b", isError: false }; },
|
|
341
365
|
};
|
|
342
366
|
`,
|
|
@@ -303,6 +303,146 @@ describe("FilingService", () => {
|
|
|
303
303
|
expect(processMessageCalls).toHaveLength(1);
|
|
304
304
|
});
|
|
305
305
|
|
|
306
|
+
// Helpers for the compaction-retry tests: hold the filing run open by
|
|
307
|
+
// making processMessage return a manually-resolved promise, so `activeRun`
|
|
308
|
+
// stays set and runCompactionOnce() sees the contention path.
|
|
309
|
+
function holdFilingRun(): {
|
|
310
|
+
release: () => void;
|
|
311
|
+
filingCalls: () => number;
|
|
312
|
+
compactionCalls: () => number;
|
|
313
|
+
waitForFilingStarted: () => Promise<void>;
|
|
314
|
+
} {
|
|
315
|
+
let release: (() => void) | undefined;
|
|
316
|
+
let started = false;
|
|
317
|
+
let filingCalls = 0;
|
|
318
|
+
let compactionCalls = 0;
|
|
319
|
+
|
|
320
|
+
setTestProcessMessage((...args: unknown[]) => {
|
|
321
|
+
const callSite = (args[3] as { callSite?: string } | undefined)
|
|
322
|
+
?.callSite;
|
|
323
|
+
if (callSite === "filingAgent") {
|
|
324
|
+
filingCalls += 1;
|
|
325
|
+
started = true;
|
|
326
|
+
return new Promise((resolve) => {
|
|
327
|
+
release = () => resolve({ messageId: "filing-done" });
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
if (callSite === "compactionAgent") {
|
|
331
|
+
compactionCalls += 1;
|
|
332
|
+
}
|
|
333
|
+
return Promise.resolve({ messageId: "mock" });
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
return {
|
|
337
|
+
release: () => release?.(),
|
|
338
|
+
filingCalls: () => filingCalls,
|
|
339
|
+
compactionCalls: () => compactionCalls,
|
|
340
|
+
waitForFilingStarted: async () => {
|
|
341
|
+
while (!started) await Promise.resolve();
|
|
342
|
+
},
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
test("schedules a near-term retry when filing run is in-flight", async () => {
|
|
347
|
+
const hold = holdFilingRun();
|
|
348
|
+
// 5s retry override paired with a production-realistic 24h compaction
|
|
349
|
+
// interval — the assertion proves retry << interval.
|
|
350
|
+
const retryMs = 5_000;
|
|
351
|
+
mockConfig.filing.compactionIntervalMs = 24 * 60 * 60 * 1000;
|
|
352
|
+
const service = new FilingService({
|
|
353
|
+
compactionContendedRetryMs: retryMs,
|
|
354
|
+
});
|
|
355
|
+
const filingPromise = service.runOnce();
|
|
356
|
+
await hold.waitForFilingStarted();
|
|
357
|
+
|
|
358
|
+
const beforeRetry = Date.now();
|
|
359
|
+
const ran = await service.runCompactionOnce();
|
|
360
|
+
|
|
361
|
+
expect(ran).toBe(false);
|
|
362
|
+
expect(service.nextCompactionAt).not.toBeNull();
|
|
363
|
+
const nextAt = service.nextCompactionAt!;
|
|
364
|
+
expect(nextAt - beforeRetry).toBeLessThan(
|
|
365
|
+
mockConfig.filing.compactionIntervalMs,
|
|
366
|
+
);
|
|
367
|
+
expect(nextAt - beforeRetry).toBeLessThanOrEqual(retryMs + 100);
|
|
368
|
+
|
|
369
|
+
hold.release();
|
|
370
|
+
await filingPromise;
|
|
371
|
+
await service.stop();
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
test("retry fires after filing run completes", async () => {
|
|
375
|
+
const hold = holdFilingRun();
|
|
376
|
+
const service = new FilingService({ compactionContendedRetryMs: 1 });
|
|
377
|
+
const filingPromise = service.runOnce();
|
|
378
|
+
await hold.waitForFilingStarted();
|
|
379
|
+
|
|
380
|
+
const skipped = await service.runCompactionOnce();
|
|
381
|
+
expect(skipped).toBe(false);
|
|
382
|
+
expect(hold.compactionCalls()).toBe(0);
|
|
383
|
+
|
|
384
|
+
hold.release();
|
|
385
|
+
await filingPromise;
|
|
386
|
+
|
|
387
|
+
const start = Date.now();
|
|
388
|
+
while (hold.compactionCalls() === 0 && Date.now() - start < 1000) {
|
|
389
|
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
expect(hold.filingCalls()).toBe(1);
|
|
393
|
+
expect(hold.compactionCalls()).toBe(1);
|
|
394
|
+
|
|
395
|
+
await service.stop();
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
test("stop() clears a scheduled compaction retry", async () => {
|
|
399
|
+
const hold = holdFilingRun();
|
|
400
|
+
const service = new FilingService({ compactionContendedRetryMs: 50 });
|
|
401
|
+
const filingPromise = service.runOnce();
|
|
402
|
+
await hold.waitForFilingStarted();
|
|
403
|
+
await service.runCompactionOnce();
|
|
404
|
+
expect(service.nextCompactionAt).not.toBeNull();
|
|
405
|
+
|
|
406
|
+
hold.release();
|
|
407
|
+
await filingPromise;
|
|
408
|
+
await service.stop();
|
|
409
|
+
|
|
410
|
+
// After stop, the retry timer must be cleared and never fire.
|
|
411
|
+
expect(service.nextCompactionAt).toBeNull();
|
|
412
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
413
|
+
expect(hold.compactionCalls()).toBe(0);
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
test("stop() prevents retry callback from re-arming a fresh timer", async () => {
|
|
417
|
+
// Race: the retry callback fires while filing is still in-flight and
|
|
418
|
+
// stop() has begun. The callback already cleared compactionRetryTimer,
|
|
419
|
+
// so clearCompactionRetry is a no-op. Without a stopped flag, the
|
|
420
|
+
// callback's runCompactionOnce() hits the activeRun branch and schedules
|
|
421
|
+
// a fresh retry, leaving a live timer after stop() resolves.
|
|
422
|
+
const hold = holdFilingRun();
|
|
423
|
+
const service = new FilingService({ compactionContendedRetryMs: 5 });
|
|
424
|
+
const filingPromise = service.runOnce();
|
|
425
|
+
await hold.waitForFilingStarted();
|
|
426
|
+
await service.runCompactionOnce();
|
|
427
|
+
expect(service.nextCompactionAt).not.toBeNull();
|
|
428
|
+
|
|
429
|
+
// Begin stop without awaiting — it would block on the held filing run.
|
|
430
|
+
// stop() flips `stopped` synchronously before the retry timer fires.
|
|
431
|
+
const stopPromise = service.stop();
|
|
432
|
+
|
|
433
|
+
// Wait past the retry delay. Without the guard, the callback would call
|
|
434
|
+
// runCompactionOnce(), observe activeRun, and re-arm a new retry.
|
|
435
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
436
|
+
expect(service.nextCompactionAt).toBeNull();
|
|
437
|
+
|
|
438
|
+
hold.release();
|
|
439
|
+
await filingPromise;
|
|
440
|
+
await stopPromise;
|
|
441
|
+
|
|
442
|
+
expect(service.nextCompactionAt).toBeNull();
|
|
443
|
+
expect(hold.compactionCalls()).toBe(0);
|
|
444
|
+
});
|
|
445
|
+
|
|
306
446
|
test("respects active hours", async () => {
|
|
307
447
|
mockConfig.filing.activeHoursStart = 9;
|
|
308
448
|
mockConfig.filing.activeHoursEnd = 17;
|
|
@@ -150,13 +150,11 @@ mock.module("../skills/clawhub-files.js", () => ({
|
|
|
150
150
|
|
|
151
151
|
mock.module("../skills/catalog-install.js", () => ({
|
|
152
152
|
installSkillLocally: async () => {},
|
|
153
|
-
upsertSkillsIndex: () => {},
|
|
154
153
|
}));
|
|
155
154
|
|
|
156
155
|
mock.module("../skills/managed-store.js", () => ({
|
|
157
156
|
createManagedSkill: () => ({ created: true }),
|
|
158
157
|
deleteManagedSkill: () => ({ deleted: true }),
|
|
159
|
-
removeSkillsIndexEntry: () => {},
|
|
160
158
|
validateManagedSkillId: () => null,
|
|
161
159
|
}));
|
|
162
160
|
|
|
@@ -188,8 +186,6 @@ import { getSkill } from "../daemon/handlers/skills.js";
|
|
|
188
186
|
// Helpers
|
|
189
187
|
// ---------------------------------------------------------------------------
|
|
190
188
|
|
|
191
|
-
|
|
192
|
-
|
|
193
189
|
// ---------------------------------------------------------------------------
|
|
194
190
|
// Tests
|
|
195
191
|
// ---------------------------------------------------------------------------
|