@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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { PLATFORM_PROVIDER_META } from "../providers/platform-proxy/constants.js";
|
|
4
4
|
import { credentialKey } from "../security/credential-key.js";
|
|
5
5
|
|
|
6
6
|
// ---------------------------------------------------------------------------
|
|
@@ -436,7 +436,7 @@ describe("managed proxy integration — ollama exclusion", () => {
|
|
|
436
436
|
});
|
|
437
437
|
|
|
438
438
|
test("ollama metadata is marked as non-managed", () => {
|
|
439
|
-
const meta =
|
|
439
|
+
const meta = PLATFORM_PROVIDER_META.ollama;
|
|
440
440
|
expect(meta).toBeDefined();
|
|
441
441
|
expect(meta.managed).toBe(false);
|
|
442
442
|
expect(meta.proxyPath).toBeUndefined();
|
|
@@ -478,7 +478,7 @@ describe("config mode flip → provider reinit", () => {
|
|
|
478
478
|
describe("managed proxy integration — constants integrity", () => {
|
|
479
479
|
test("anthropic, openai, and gemini have metadata with managed=true and a proxyPath", () => {
|
|
480
480
|
for (const provider of ["anthropic", "openai", "gemini"]) {
|
|
481
|
-
const meta =
|
|
481
|
+
const meta = PLATFORM_PROVIDER_META[provider];
|
|
482
482
|
expect(meta).toBeDefined();
|
|
483
483
|
expect(meta.managed).toBe(true);
|
|
484
484
|
expect(meta.proxyPath).toBeTruthy();
|
|
@@ -487,27 +487,27 @@ describe("managed proxy integration — constants integrity", () => {
|
|
|
487
487
|
});
|
|
488
488
|
|
|
489
489
|
test("anthropic routes through anthropic proxy path", () => {
|
|
490
|
-
expect(
|
|
490
|
+
expect(PLATFORM_PROVIDER_META.anthropic.proxyPath).toBe(
|
|
491
491
|
"/v1/runtime-proxy/anthropic",
|
|
492
492
|
);
|
|
493
493
|
});
|
|
494
494
|
|
|
495
495
|
test("gemini routes through gemini proxy path", () => {
|
|
496
|
-
expect(
|
|
496
|
+
expect(PLATFORM_PROVIDER_META.gemini.proxyPath).toBe(
|
|
497
497
|
"/v1/runtime-proxy/gemini",
|
|
498
498
|
);
|
|
499
499
|
});
|
|
500
500
|
|
|
501
501
|
test("openai routes through openai proxy path", () => {
|
|
502
|
-
expect(
|
|
502
|
+
expect(PLATFORM_PROVIDER_META.openai.proxyPath).toBe(
|
|
503
503
|
"/v1/runtime-proxy/openai",
|
|
504
504
|
);
|
|
505
505
|
});
|
|
506
506
|
|
|
507
507
|
test("fireworks and openrouter are not managed proxy capable", () => {
|
|
508
508
|
for (const provider of ["fireworks", "openrouter"]) {
|
|
509
|
-
expect(
|
|
510
|
-
expect(
|
|
509
|
+
expect(PLATFORM_PROVIDER_META[provider].managed).toBe(false);
|
|
510
|
+
expect(PLATFORM_PROVIDER_META[provider].proxyPath).toBeUndefined();
|
|
511
511
|
}
|
|
512
512
|
});
|
|
513
513
|
});
|
|
@@ -3,6 +3,7 @@ import { join } from "node:path";
|
|
|
3
3
|
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
4
4
|
|
|
5
5
|
const TEST_DIR = process.env.VELLUM_WORKSPACE_DIR!;
|
|
6
|
+
const mockRefreshSkillCapabilityMemories = mock(() => {});
|
|
6
7
|
|
|
7
8
|
mock.module("../util/logger.js", () => ({
|
|
8
9
|
getLogger: () =>
|
|
@@ -11,6 +12,11 @@ mock.module("../util/logger.js", () => ({
|
|
|
11
12
|
}),
|
|
12
13
|
}));
|
|
13
14
|
|
|
15
|
+
mock.module("../daemon/skill-memory-refresh.js", () => ({
|
|
16
|
+
refreshSkillCapabilityMemories: mockRefreshSkillCapabilityMemories,
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
import { loadSkillCatalog } from "../config/skills.js";
|
|
14
20
|
import { executeScaffoldManagedSkill } from "../tools/skills/scaffold-managed.js";
|
|
15
21
|
import type { ToolContext } from "../tools/types.js";
|
|
16
22
|
|
|
@@ -24,6 +30,7 @@ function makeContext(): ToolContext {
|
|
|
24
30
|
|
|
25
31
|
beforeEach(() => {
|
|
26
32
|
mkdirSync(join(TEST_DIR, "skills"), { recursive: true });
|
|
33
|
+
mockRefreshSkillCapabilityMemories.mockClear();
|
|
27
34
|
});
|
|
28
35
|
|
|
29
36
|
afterEach(() => {
|
|
@@ -31,7 +38,29 @@ afterEach(() => {
|
|
|
31
38
|
});
|
|
32
39
|
|
|
33
40
|
describe("scaffold_managed_skill tool", () => {
|
|
34
|
-
test("
|
|
41
|
+
test("keeps legacy index control as a deprecated no-op schema field", () => {
|
|
42
|
+
const tools = JSON.parse(
|
|
43
|
+
readFileSync(
|
|
44
|
+
join(
|
|
45
|
+
import.meta.dirname,
|
|
46
|
+
"../config/bundled-skills/skill-management/TOOLS.json",
|
|
47
|
+
),
|
|
48
|
+
"utf-8",
|
|
49
|
+
),
|
|
50
|
+
);
|
|
51
|
+
const scaffoldTool = tools.tools.find(
|
|
52
|
+
(tool: { name: string }) => tool.name === "scaffold_managed_skill",
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
expect(scaffoldTool).toBeDefined();
|
|
56
|
+
expect(scaffoldTool.input_schema.properties.add_to_index).toEqual({
|
|
57
|
+
type: "boolean",
|
|
58
|
+
description:
|
|
59
|
+
"Deprecated no-op compatibility field. Skills are discovered from top-level SKILL.md files.",
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("creates a valid skill discovered from its SKILL.md directory", async () => {
|
|
35
64
|
const result = await executeScaffoldManagedSkill(
|
|
36
65
|
{
|
|
37
66
|
skill_id: "test-skill",
|
|
@@ -46,18 +75,40 @@ describe("scaffold_managed_skill tool", () => {
|
|
|
46
75
|
const parsed = JSON.parse(result.content);
|
|
47
76
|
expect(parsed.created).toBe(true);
|
|
48
77
|
expect(parsed.skill_id).toBe("test-skill");
|
|
49
|
-
expect(parsed
|
|
78
|
+
expect(parsed).not.toHaveProperty("index_updated");
|
|
50
79
|
|
|
51
80
|
const skillFile = join(TEST_DIR, "skills", "test-skill", "SKILL.md");
|
|
52
81
|
expect(existsSync(skillFile)).toBe(true);
|
|
53
82
|
const content = readFileSync(skillFile, "utf-8");
|
|
54
83
|
expect(content).toContain('name: "Test Skill"');
|
|
55
84
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
85
|
+
expect(existsSync(join(TEST_DIR, "skills", "SKILLS.md"))).toBe(false);
|
|
86
|
+
|
|
87
|
+
const catalog = loadSkillCatalog();
|
|
88
|
+
const skill = catalog.find((s) => s.id === "test-skill");
|
|
89
|
+
expect(skill).toBeDefined();
|
|
90
|
+
expect(skill!.name).toBe("Test Skill");
|
|
91
|
+
expect(mockRefreshSkillCapabilityMemories).toHaveBeenCalledTimes(1);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("accepts legacy add_to_index input without returning index metadata", async () => {
|
|
95
|
+
const result = await executeScaffoldManagedSkill(
|
|
96
|
+
{
|
|
97
|
+
skill_id: "legacy-input",
|
|
98
|
+
name: "Legacy Input",
|
|
99
|
+
description: "A test skill",
|
|
100
|
+
body_markdown: "Do the thing.",
|
|
101
|
+
add_to_index: true,
|
|
102
|
+
},
|
|
103
|
+
makeContext(),
|
|
59
104
|
);
|
|
60
|
-
|
|
105
|
+
|
|
106
|
+
expect(result.isError).toBe(false);
|
|
107
|
+
const parsed = JSON.parse(result.content);
|
|
108
|
+
expect(parsed.created).toBe(true);
|
|
109
|
+
expect(parsed).not.toHaveProperty("index_updated");
|
|
110
|
+
expect(existsSync(join(TEST_DIR, "skills", "SKILLS.md"))).toBe(false);
|
|
111
|
+
expect(mockRefreshSkillCapabilityMemories).toHaveBeenCalledTimes(1);
|
|
61
112
|
});
|
|
62
113
|
|
|
63
114
|
test("rejects duplicate unless overwrite=true", async () => {
|
|
@@ -258,7 +309,7 @@ describe("scaffold_managed_skill tool", () => {
|
|
|
258
309
|
expect(result.content).toContain("traversal");
|
|
259
310
|
});
|
|
260
311
|
|
|
261
|
-
test("e2e: scaffold child then parent with includes, verify
|
|
312
|
+
test("e2e: scaffold child then parent with includes, verify file discovery", async () => {
|
|
262
313
|
const childResult = await executeScaffoldManagedSkill(
|
|
263
314
|
{
|
|
264
315
|
skill_id: "e2e-child",
|
|
@@ -288,11 +339,12 @@ describe("scaffold_managed_skill tool", () => {
|
|
|
288
339
|
expect(parentContent).toContain(" includes:");
|
|
289
340
|
expect(parentContent).toContain(" - e2e-child");
|
|
290
341
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
);
|
|
295
|
-
|
|
296
|
-
expect(
|
|
342
|
+
expect(existsSync(join(TEST_DIR, "skills", "SKILLS.md"))).toBe(false);
|
|
343
|
+
|
|
344
|
+
const catalog = loadSkillCatalog();
|
|
345
|
+
expect(catalog.find((s) => s.id === "e2e-child")).toBeDefined();
|
|
346
|
+
const parent = catalog.find((s) => s.id === "e2e-parent");
|
|
347
|
+
expect(parent).toBeDefined();
|
|
348
|
+
expect(parent!.includes).toEqual(["e2e-child"]);
|
|
297
349
|
});
|
|
298
350
|
});
|
|
@@ -39,8 +39,11 @@ mock.module("../daemon/conversation-store.js", () => ({
|
|
|
39
39
|
},
|
|
40
40
|
}));
|
|
41
41
|
|
|
42
|
+
import { SYNC_TAGS } from "../daemon/message-types/sync.js";
|
|
42
43
|
import { getDb } from "../memory/db-connection.js";
|
|
43
44
|
import { initializeDb } from "../memory/db-init.js";
|
|
45
|
+
import type { AssistantEvent } from "../runtime/assistant-event.js";
|
|
46
|
+
import { assistantEventHub } from "../runtime/assistant-event-hub.js";
|
|
44
47
|
import { ROUTES } from "../runtime/routes/schedule-routes.js";
|
|
45
48
|
import type { RouteDefinition } from "../runtime/routes/types.js";
|
|
46
49
|
import {
|
|
@@ -71,6 +74,15 @@ function findRoute(endpoint: string, method: string): RouteDefinition {
|
|
|
71
74
|
return route;
|
|
72
75
|
}
|
|
73
76
|
|
|
77
|
+
async function waitFor(predicate: () => boolean): Promise<void> {
|
|
78
|
+
const deadline = Date.now() + 500;
|
|
79
|
+
while (Date.now() < deadline) {
|
|
80
|
+
if (predicate()) return;
|
|
81
|
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
82
|
+
}
|
|
83
|
+
throw new Error("Timed out waiting for schedule route event");
|
|
84
|
+
}
|
|
85
|
+
|
|
74
86
|
describe("schedule run-now trust propagation", () => {
|
|
75
87
|
beforeEach(() => {
|
|
76
88
|
clearTables();
|
|
@@ -218,6 +230,41 @@ describe("GET /schedules — default defer exclusion", () => {
|
|
|
218
230
|
expect(result.schedules).toHaveLength(1);
|
|
219
231
|
expect(result.schedules[0].id).toBe(agent.id);
|
|
220
232
|
});
|
|
233
|
+
|
|
234
|
+
test("mutation routes emit schedule sync invalidation", async () => {
|
|
235
|
+
const received: AssistantEvent[] = [];
|
|
236
|
+
const subscription = assistantEventHub.subscribe({
|
|
237
|
+
type: "process",
|
|
238
|
+
callback: (event) => {
|
|
239
|
+
received.push(event);
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
try {
|
|
244
|
+
const agent = createSchedule({
|
|
245
|
+
name: "Agent schedule",
|
|
246
|
+
cronExpression: "* * * * *",
|
|
247
|
+
message: "hello",
|
|
248
|
+
syntax: "cron",
|
|
249
|
+
});
|
|
250
|
+
await waitFor(() => received.length >= 1);
|
|
251
|
+
received.length = 0;
|
|
252
|
+
|
|
253
|
+
const route = findRoute("schedules/:id/toggle", "POST");
|
|
254
|
+
route.handler({
|
|
255
|
+
pathParams: { id: agent.id },
|
|
256
|
+
body: { enabled: false },
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
await waitFor(() => received.length >= 1);
|
|
260
|
+
expect(received[0].message).toEqual({
|
|
261
|
+
type: "sync_changed",
|
|
262
|
+
tags: [SYNC_TAGS.assistantSchedules],
|
|
263
|
+
});
|
|
264
|
+
} finally {
|
|
265
|
+
subscription.dispose();
|
|
266
|
+
}
|
|
267
|
+
});
|
|
221
268
|
});
|
|
222
269
|
|
|
223
270
|
// ── schedules/:id/runs limit handling ─────────────────────────────────────
|
|
@@ -388,9 +435,9 @@ describe("POST /schedules — create", () => {
|
|
|
388
435
|
});
|
|
389
436
|
|
|
390
437
|
test("rejects missing required fields", () => {
|
|
391
|
-
expect(() =>
|
|
392
|
-
"
|
|
393
|
-
);
|
|
438
|
+
expect(() =>
|
|
439
|
+
postCreate({ expression: "* * * * *", message: "hi" }),
|
|
440
|
+
).toThrow("name is required");
|
|
394
441
|
expect(() => postCreate({ name: "x", message: "hi" })).toThrow(
|
|
395
442
|
"expression is required",
|
|
396
443
|
);
|
|
@@ -9,13 +9,19 @@ mock.module("../util/logger.js", () => ({
|
|
|
9
9
|
truncateForLog: (value: string) => value,
|
|
10
10
|
}));
|
|
11
11
|
|
|
12
|
+
import { SYNC_TAGS } from "../daemon/message-types/sync.js";
|
|
12
13
|
import { getDb } from "../memory/db-connection.js";
|
|
13
14
|
import { initializeDb } from "../memory/db-init.js";
|
|
15
|
+
import type { AssistantEvent } from "../runtime/assistant-event.js";
|
|
16
|
+
import { assistantEventHub } from "../runtime/assistant-event-hub.js";
|
|
14
17
|
import {
|
|
15
18
|
cancelSchedule,
|
|
16
19
|
claimDueSchedules,
|
|
17
20
|
completeOneShot,
|
|
21
|
+
completeScheduleRun,
|
|
18
22
|
createSchedule,
|
|
23
|
+
createScheduleRun,
|
|
24
|
+
deleteSchedule,
|
|
19
25
|
describeCronExpression,
|
|
20
26
|
failOneShot,
|
|
21
27
|
getSchedule,
|
|
@@ -31,6 +37,94 @@ function getRawDb(): import("bun:sqlite").Database {
|
|
|
31
37
|
.$client;
|
|
32
38
|
}
|
|
33
39
|
|
|
40
|
+
async function waitFor(predicate: () => boolean): Promise<void> {
|
|
41
|
+
const deadline = Date.now() + 500;
|
|
42
|
+
while (Date.now() < deadline) {
|
|
43
|
+
if (predicate()) return;
|
|
44
|
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
45
|
+
}
|
|
46
|
+
throw new Error("Timed out waiting for schedule-store event");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function expectScheduleSyncEvent(
|
|
50
|
+
received: AssistantEvent[],
|
|
51
|
+
): Promise<void> {
|
|
52
|
+
await waitFor(() =>
|
|
53
|
+
received.some(
|
|
54
|
+
(event) =>
|
|
55
|
+
event.message.type === "sync_changed" &&
|
|
56
|
+
event.message.tags.includes(SYNC_TAGS.assistantSchedules),
|
|
57
|
+
),
|
|
58
|
+
);
|
|
59
|
+
const syncEvent = received.find(
|
|
60
|
+
(event) =>
|
|
61
|
+
event.message.type === "sync_changed" &&
|
|
62
|
+
event.message.tags.includes(SYNC_TAGS.assistantSchedules),
|
|
63
|
+
);
|
|
64
|
+
expect(syncEvent?.message).toEqual({
|
|
65
|
+
type: "sync_changed",
|
|
66
|
+
tags: [SYNC_TAGS.assistantSchedules],
|
|
67
|
+
});
|
|
68
|
+
received.length = 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
describe("schedule sync invalidation", () => {
|
|
72
|
+
beforeEach(() => {
|
|
73
|
+
const db = getDb();
|
|
74
|
+
db.run("DELETE FROM cron_runs");
|
|
75
|
+
db.run("DELETE FROM cron_jobs");
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("store-level schedule mutations emit schedule sync invalidation", async () => {
|
|
79
|
+
const received: AssistantEvent[] = [];
|
|
80
|
+
const subscription = assistantEventHub.subscribe({
|
|
81
|
+
type: "process",
|
|
82
|
+
callback: (event) => {
|
|
83
|
+
received.push(event);
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
const job = createSchedule({
|
|
89
|
+
name: "Sync test",
|
|
90
|
+
cronExpression: "* * * * *",
|
|
91
|
+
message: "sync me",
|
|
92
|
+
syntax: "cron",
|
|
93
|
+
});
|
|
94
|
+
await expectScheduleSyncEvent(received);
|
|
95
|
+
|
|
96
|
+
updateSchedule(job.id, { name: "Updated sync test" });
|
|
97
|
+
await expectScheduleSyncEvent(received);
|
|
98
|
+
|
|
99
|
+
getRawDb().run("UPDATE cron_jobs SET next_run_at = ? WHERE id = ?", [
|
|
100
|
+
Date.now() - 1000,
|
|
101
|
+
job.id,
|
|
102
|
+
]);
|
|
103
|
+
expect(claimDueSchedules(Date.now())).toHaveLength(1);
|
|
104
|
+
await expectScheduleSyncEvent(received);
|
|
105
|
+
|
|
106
|
+
const runId = createScheduleRun(job.id, "conv-123");
|
|
107
|
+
completeScheduleRun(runId, { status: "ok" });
|
|
108
|
+
await expectScheduleSyncEvent(received);
|
|
109
|
+
|
|
110
|
+
const oneShot = createSchedule({
|
|
111
|
+
name: "One-shot sync test",
|
|
112
|
+
message: "cancel me",
|
|
113
|
+
nextRunAt: Date.now() + 60_000,
|
|
114
|
+
});
|
|
115
|
+
await expectScheduleSyncEvent(received);
|
|
116
|
+
|
|
117
|
+
expect(cancelSchedule(oneShot.id)).toBe(true);
|
|
118
|
+
await expectScheduleSyncEvent(received);
|
|
119
|
+
|
|
120
|
+
expect(deleteSchedule(job.id)).toBe(true);
|
|
121
|
+
await expectScheduleSyncEvent(received);
|
|
122
|
+
} finally {
|
|
123
|
+
subscription.dispose();
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
34
128
|
// ── Cron schedules ──────────────────────────────────────────────────
|
|
35
129
|
|
|
36
130
|
describe("createSchedule (cron)", () => {
|
|
@@ -31,6 +31,7 @@ const runBackgroundJobOptions: Array<{
|
|
|
31
31
|
onConversationCreated?: (id: string) => void;
|
|
32
32
|
}> = [];
|
|
33
33
|
let runBackgroundJobShouldFail = false;
|
|
34
|
+
let runBackgroundJobBootstrapFails = false;
|
|
34
35
|
mock.module("../runtime/background-job-runner.js", () => ({
|
|
35
36
|
runBackgroundJob: async (opts: {
|
|
36
37
|
prompt: string;
|
|
@@ -40,6 +41,25 @@ mock.module("../runtime/background-job-runner.js", () => ({
|
|
|
40
41
|
suppressFailureNotifications?: boolean;
|
|
41
42
|
onConversationCreated?: (id: string) => void;
|
|
42
43
|
}) => {
|
|
44
|
+
runBackgroundJobOptions.push({
|
|
45
|
+
conversationType: opts.conversationType,
|
|
46
|
+
scheduleJobId: opts.scheduleJobId,
|
|
47
|
+
groupId: opts.groupId,
|
|
48
|
+
suppressFailureNotifications: opts.suppressFailureNotifications,
|
|
49
|
+
onConversationCreated: opts.onConversationCreated,
|
|
50
|
+
});
|
|
51
|
+
// Bootstrap-failure path: the real runner returns conversationId: ""
|
|
52
|
+
// when `bootstrapConversation` throws before assignment. Skip the
|
|
53
|
+
// callback (it was never reached) and surface the empty id to the
|
|
54
|
+
// scheduler so it can exercise the sentinel guard.
|
|
55
|
+
if (runBackgroundJobBootstrapFails) {
|
|
56
|
+
return {
|
|
57
|
+
conversationId: "",
|
|
58
|
+
ok: false,
|
|
59
|
+
error: new Error("Bootstrap failure"),
|
|
60
|
+
errorKind: "exception" as const,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
43
63
|
const { createConversation } =
|
|
44
64
|
await import("../memory/conversation-crud.js");
|
|
45
65
|
const conv = createConversation({
|
|
@@ -49,13 +69,6 @@ mock.module("../runtime/background-job-runner.js", () => ({
|
|
|
49
69
|
...(opts.groupId ? { groupId: opts.groupId } : {}),
|
|
50
70
|
...(opts.scheduleJobId ? { scheduleJobId: opts.scheduleJobId } : {}),
|
|
51
71
|
});
|
|
52
|
-
runBackgroundJobOptions.push({
|
|
53
|
-
conversationType: opts.conversationType,
|
|
54
|
-
scheduleJobId: opts.scheduleJobId,
|
|
55
|
-
groupId: opts.groupId,
|
|
56
|
-
suppressFailureNotifications: opts.suppressFailureNotifications,
|
|
57
|
-
onConversationCreated: opts.onConversationCreated,
|
|
58
|
-
});
|
|
59
72
|
// Mirror the real runner's contract: fire the SSE callback synchronously
|
|
60
73
|
// BEFORE the job's processMessage finishes, with the bootstrap-returned
|
|
61
74
|
// conversation id.
|
|
@@ -137,6 +150,7 @@ describe("scheduler conversation reuse", () => {
|
|
|
137
150
|
processedMessages.length = 0;
|
|
138
151
|
runBackgroundJobOptions.length = 0;
|
|
139
152
|
runBackgroundJobShouldFail = false;
|
|
153
|
+
runBackgroundJobBootstrapFails = false;
|
|
140
154
|
});
|
|
141
155
|
|
|
142
156
|
test("recurring schedule with reuseConversation=true reuses conversation across runs", async () => {
|
|
@@ -393,6 +407,7 @@ describe("scheduler talk-mode runner option propagation", () => {
|
|
|
393
407
|
processedMessages.length = 0;
|
|
394
408
|
runBackgroundJobOptions.length = 0;
|
|
395
409
|
runBackgroundJobShouldFail = false;
|
|
410
|
+
runBackgroundJobBootstrapFails = false;
|
|
396
411
|
});
|
|
397
412
|
|
|
398
413
|
test("talk-mode propagates conversationType=scheduled, scheduleJobId, and quiet=>suppressFailureNotifications", async () => {
|
|
@@ -443,6 +458,38 @@ describe("scheduler talk-mode runner option propagation", () => {
|
|
|
443
458
|
);
|
|
444
459
|
});
|
|
445
460
|
|
|
461
|
+
test("talk-mode bootstrap failure writes sentinel conversationId, not empty string", async () => {
|
|
462
|
+
/**
|
|
463
|
+
* Regression: `runBackgroundJob` returns `{ conversationId: "", ok: false }`
|
|
464
|
+
* when bootstrap throws before the conversation row is assigned. The
|
|
465
|
+
* scheduler previously stored that empty string in the cron_runs DB row.
|
|
466
|
+
* Guard ensures we substitute a recognizable sentinel.
|
|
467
|
+
*/
|
|
468
|
+
const rruleExpr = buildEveryMinuteRrule();
|
|
469
|
+
const schedule = createSchedule({
|
|
470
|
+
name: "Bootstrap Failure",
|
|
471
|
+
cronExpression: rruleExpr,
|
|
472
|
+
message: "x",
|
|
473
|
+
syntax: "rrule",
|
|
474
|
+
expression: rruleExpr,
|
|
475
|
+
});
|
|
476
|
+
forceScheduleDue(schedule.id);
|
|
477
|
+
runBackgroundJobBootstrapFails = true;
|
|
478
|
+
|
|
479
|
+
const processMessage = async () => {};
|
|
480
|
+
const scheduler = startScheduler(processMessage, () => {});
|
|
481
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
482
|
+
scheduler.stop();
|
|
483
|
+
|
|
484
|
+
const runs = getScheduleRuns(schedule.id);
|
|
485
|
+
expect(runs.length).toBe(1);
|
|
486
|
+
expect(runs[0].status).toBe("error");
|
|
487
|
+
// Critical: must NOT be empty — the DB column should carry a marker that
|
|
488
|
+
// identifies this run as a bootstrap-failure case.
|
|
489
|
+
expect(runs[0].conversationId).not.toBe("");
|
|
490
|
+
expect(runs[0].conversationId).toBe(`bootstrap-error:${schedule.id}`);
|
|
491
|
+
});
|
|
492
|
+
|
|
446
493
|
test("talk-mode fires onScheduleConversationCreated synchronously via runner callback (BEFORE the runner returns)", async () => {
|
|
447
494
|
const rruleExpr = buildEveryMinuteRrule();
|
|
448
495
|
const schedule = createSchedule({
|
|
@@ -157,6 +157,26 @@ describe("injectActivityField", () => {
|
|
|
157
157
|
expect(Object.is(result[0], defs[0])).toBe(true);
|
|
158
158
|
});
|
|
159
159
|
|
|
160
|
+
test("passes through undefined/null input_schema unchanged (no crash)", () => {
|
|
161
|
+
// Repro for the crash where an MCP server returned a tool with missing
|
|
162
|
+
// inputSchema and `injectActivityField` threw on `schema.type` access.
|
|
163
|
+
const defs: ToolDefinition[] = [
|
|
164
|
+
{
|
|
165
|
+
name: "missing",
|
|
166
|
+
description: "x",
|
|
167
|
+
input_schema: undefined as unknown as object,
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
name: "nullish",
|
|
171
|
+
description: "x",
|
|
172
|
+
input_schema: null as unknown as object,
|
|
173
|
+
},
|
|
174
|
+
];
|
|
175
|
+
const result = injectActivityField(defs);
|
|
176
|
+
expect(Object.is(result[0], defs[0])).toBe(true);
|
|
177
|
+
expect(Object.is(result[1], defs[1])).toBe(true);
|
|
178
|
+
});
|
|
179
|
+
|
|
160
180
|
test("does NOT add activity to top-level required when only in oneOf branch", () => {
|
|
161
181
|
const defs = [
|
|
162
182
|
makeDef("my_tool", {
|
|
@@ -124,9 +124,6 @@ mock.module("../providers/provider-send-message.js", () => ({
|
|
|
124
124
|
getConfiguredProvider: async () => null,
|
|
125
125
|
userMessage: () => ({}),
|
|
126
126
|
}));
|
|
127
|
-
mock.module("../runtime/routes/workspace-utils.js", () => ({
|
|
128
|
-
isTextMimeType: () => true,
|
|
129
|
-
}));
|
|
130
127
|
mock.module("../skills/catalog-cache.js", () => ({
|
|
131
128
|
getCatalog: async () => [],
|
|
132
129
|
}));
|
|
@@ -136,7 +133,6 @@ mock.module("../skills/catalog-install.js", () => ({
|
|
|
136
133
|
mock.module("../skills/managed-store.js", () => ({
|
|
137
134
|
createManagedSkill: () => ({ created: true }),
|
|
138
135
|
deleteManagedSkill: () => ({ deleted: true }),
|
|
139
|
-
removeSkillsIndexEntry: () => {},
|
|
140
136
|
validateManagedSkillId: () => null,
|
|
141
137
|
}));
|
|
142
138
|
mock.module("../memory/graph/capability-seed.js", () => ({
|
|
@@ -165,7 +161,6 @@ import { searchSkills } from "../daemon/handlers/skills.js";
|
|
|
165
161
|
// Helpers
|
|
166
162
|
// ---------------------------------------------------------------------------
|
|
167
163
|
|
|
168
|
-
|
|
169
164
|
// ---------------------------------------------------------------------------
|
|
170
165
|
// Tests
|
|
171
166
|
// ---------------------------------------------------------------------------
|
|
@@ -91,6 +91,49 @@ describe("renderHistoryContent", () => {
|
|
|
91
91
|
expect(renderHistoryContent(42).text).toBe("42");
|
|
92
92
|
});
|
|
93
93
|
|
|
94
|
+
test("unwraps complete legacy external_content envelopes for plain string content", () => {
|
|
95
|
+
const output = renderHistoryContent(
|
|
96
|
+
'<external_content source="slack">\nVisible Slack text\n</external_content>',
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
expect(output.text).toBe("Visible Slack text");
|
|
100
|
+
expect(output.textSegments).toEqual(["Visible Slack text"]);
|
|
101
|
+
expect(output.contentOrder).toEqual(["text:0"]);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("unwraps complete legacy external_content envelopes in text blocks", () => {
|
|
105
|
+
const output = renderHistoryContent([
|
|
106
|
+
{
|
|
107
|
+
type: "text",
|
|
108
|
+
text: '<external_content source="slack">\nVisible block text\n</external_content>',
|
|
109
|
+
},
|
|
110
|
+
{ type: "text", text: "Plain follow-up." },
|
|
111
|
+
]);
|
|
112
|
+
|
|
113
|
+
expect(output.text).toBe("Visible block text Plain follow-up.");
|
|
114
|
+
expect(output.textSegments).toEqual([
|
|
115
|
+
"Visible block text Plain follow-up.",
|
|
116
|
+
]);
|
|
117
|
+
expect(output.contentOrder).toEqual(["text:0"]);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("leaves malformed or mixed external_content text unchanged", () => {
|
|
121
|
+
const malformed =
|
|
122
|
+
'<external_content source="slack">Visible text</external_content>';
|
|
123
|
+
const mixed =
|
|
124
|
+
'prefix <external_content source="slack">\nVisible text\n</external_content>';
|
|
125
|
+
|
|
126
|
+
const malformedOutput = renderHistoryContent([
|
|
127
|
+
{ type: "text", text: malformed },
|
|
128
|
+
]);
|
|
129
|
+
expect(malformedOutput.text).toBe(malformed);
|
|
130
|
+
expect(malformedOutput.textSegments).toEqual([malformed]);
|
|
131
|
+
|
|
132
|
+
const mixedOutput = renderHistoryContent(mixed);
|
|
133
|
+
expect(mixedOutput.text).toBe(mixed);
|
|
134
|
+
expect(mixedOutput.textSegments).toEqual([mixed]);
|
|
135
|
+
});
|
|
136
|
+
|
|
94
137
|
test("preserves JSON object content as JSON string", () => {
|
|
95
138
|
expect(renderHistoryContent({ foo: "bar" }).text).toBe('{"foo":"bar"}');
|
|
96
139
|
});
|
|
@@ -103,10 +103,6 @@ describe("skill_load feature flag enforcement", () => {
|
|
|
103
103
|
"Toggle email channel behavior",
|
|
104
104
|
"Use the feature.",
|
|
105
105
|
);
|
|
106
|
-
writeFileSync(
|
|
107
|
-
join(TEST_DIR, "skills", "SKILLS.md"),
|
|
108
|
-
`- ${DECLARED_SKILL_ID}\n`,
|
|
109
|
-
);
|
|
110
106
|
|
|
111
107
|
_setOverridesForTesting({ [DECLARED_FLAG_KEY]: false });
|
|
112
108
|
|
|
@@ -124,10 +120,6 @@ describe("skill_load feature flag enforcement", () => {
|
|
|
124
120
|
"Toggle email channel behavior",
|
|
125
121
|
"Use the feature.",
|
|
126
122
|
);
|
|
127
|
-
writeFileSync(
|
|
128
|
-
join(TEST_DIR, "skills", "SKILLS.md"),
|
|
129
|
-
`- ${DECLARED_SKILL_ID}\n`,
|
|
130
|
-
);
|
|
131
123
|
|
|
132
124
|
_setOverridesForTesting({ [DECLARED_FLAG_KEY]: true });
|
|
133
125
|
|
|
@@ -144,10 +136,6 @@ describe("skill_load feature flag enforcement", () => {
|
|
|
144
136
|
"Toggle email channel behavior",
|
|
145
137
|
"Use the feature.",
|
|
146
138
|
);
|
|
147
|
-
writeFileSync(
|
|
148
|
-
join(TEST_DIR, "skills", "SKILLS.md"),
|
|
149
|
-
`- ${DECLARED_SKILL_ID}\n`,
|
|
150
|
-
);
|
|
151
139
|
|
|
152
140
|
// No overrides — uses registry defaults
|
|
153
141
|
|