@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,309 @@
|
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
mkdirSync,
|
|
4
|
+
mkdtempSync,
|
|
5
|
+
readFileSync,
|
|
6
|
+
rmSync,
|
|
7
|
+
writeFileSync,
|
|
8
|
+
} from "node:fs";
|
|
9
|
+
import * as fs from "node:fs";
|
|
10
|
+
import { tmpdir } from "node:os";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { afterEach, beforeEach, describe, expect, spyOn, test } from "bun:test";
|
|
13
|
+
|
|
14
|
+
import { removeLegacySkillsIndexMigration } from "../workspace/migrations/084-remove-legacy-skills-index.js";
|
|
15
|
+
|
|
16
|
+
let workspaceDir: string;
|
|
17
|
+
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
workspaceDir = mkdtempSync(join(tmpdir(), "vellum-migration-084-test-"));
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
if (existsSync(workspaceDir)) {
|
|
24
|
+
rmSync(workspaceDir, { recursive: true, force: true });
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
function writeSkill(skillId: string, body = "Body."): string {
|
|
29
|
+
const skillDir = join(workspaceDir, "skills", skillId);
|
|
30
|
+
mkdirSync(skillDir, { recursive: true });
|
|
31
|
+
const skillFilePath = join(skillDir, "SKILL.md");
|
|
32
|
+
writeFileSync(
|
|
33
|
+
skillFilePath,
|
|
34
|
+
`---\nname: "${skillId}"\ndescription: "Test skill."\n---\n\n${body}\n`,
|
|
35
|
+
"utf-8",
|
|
36
|
+
);
|
|
37
|
+
return skillFilePath;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function writeLegacyIndex(contents = "- alpha\n"): string {
|
|
41
|
+
const skillsDir = join(workspaceDir, "skills");
|
|
42
|
+
mkdirSync(skillsDir, { recursive: true });
|
|
43
|
+
const legacyIndexPath = join(skillsDir, "SKILLS.md");
|
|
44
|
+
writeFileSync(legacyIndexPath, contents, "utf-8");
|
|
45
|
+
return legacyIndexPath;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function expectNestedSkillPreserved(
|
|
49
|
+
legacyIndexPath: string,
|
|
50
|
+
nestedSkillPath: string,
|
|
51
|
+
): void {
|
|
52
|
+
const topLevelSkillPath = join(
|
|
53
|
+
workspaceDir,
|
|
54
|
+
"skills",
|
|
55
|
+
"my-skill",
|
|
56
|
+
"SKILL.md",
|
|
57
|
+
);
|
|
58
|
+
expect(existsSync(legacyIndexPath)).toBe(false);
|
|
59
|
+
expect(existsSync(nestedSkillPath)).toBe(true);
|
|
60
|
+
expect(readFileSync(topLevelSkillPath, "utf-8")).toContain("org/my-skill");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
describe("084-remove-legacy-skills-index migration", () => {
|
|
64
|
+
test("has correct id and description", () => {
|
|
65
|
+
expect(removeLegacySkillsIndexMigration.id).toBe(
|
|
66
|
+
"084-remove-legacy-skills-index",
|
|
67
|
+
);
|
|
68
|
+
expect(removeLegacySkillsIndexMigration.description).toContain("SKILLS.md");
|
|
69
|
+
expect(removeLegacySkillsIndexMigration.retryFailedCheckpoint).toBe(true);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("removes only skills/SKILLS.md when present", () => {
|
|
73
|
+
const skillsDir = join(workspaceDir, "skills");
|
|
74
|
+
const legacyIndexPath = writeLegacyIndex();
|
|
75
|
+
|
|
76
|
+
const alphaSkillPath = writeSkill("alpha");
|
|
77
|
+
const betaSkillPath = writeSkill("beta");
|
|
78
|
+
const topLevelSkillPath = join(skillsDir, "SKILL.md");
|
|
79
|
+
writeFileSync(
|
|
80
|
+
topLevelSkillPath,
|
|
81
|
+
"---\nname: Root\ndescription: Root file.\n---\n\nRoot.\n",
|
|
82
|
+
"utf-8",
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
removeLegacySkillsIndexMigration.run(workspaceDir);
|
|
86
|
+
|
|
87
|
+
expect(existsSync(legacyIndexPath)).toBe(false);
|
|
88
|
+
expect(readFileSync(alphaSkillPath, "utf-8")).toContain("alpha");
|
|
89
|
+
expect(readFileSync(betaSkillPath, "utf-8")).toContain("beta");
|
|
90
|
+
expect(readFileSync(topLevelSkillPath, "utf-8")).toContain("Root");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("is a no-op when skills/SKILLS.md is absent", () => {
|
|
94
|
+
writeSkill("alpha");
|
|
95
|
+
|
|
96
|
+
expect(() =>
|
|
97
|
+
removeLegacySkillsIndexMigration.run(workspaceDir),
|
|
98
|
+
).not.toThrow();
|
|
99
|
+
expect(existsSync(join(workspaceDir, "skills", "alpha", "SKILL.md"))).toBe(
|
|
100
|
+
true,
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("removing a stale SKILLS.md index preserves omitted valid skill directories", () => {
|
|
105
|
+
const legacyIndexPath = writeLegacyIndex("- indexed-skill\n");
|
|
106
|
+
|
|
107
|
+
writeSkill("indexed-skill");
|
|
108
|
+
writeSkill("omitted-skill");
|
|
109
|
+
|
|
110
|
+
removeLegacySkillsIndexMigration.run(workspaceDir);
|
|
111
|
+
|
|
112
|
+
expect(existsSync(legacyIndexPath)).toBe(false);
|
|
113
|
+
expect(
|
|
114
|
+
readFileSync(
|
|
115
|
+
join(workspaceDir, "skills", "omitted-skill", "SKILL.md"),
|
|
116
|
+
"utf-8",
|
|
117
|
+
),
|
|
118
|
+
).toContain("Body.");
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("copies nested indexed skills to top-level discovery location", () => {
|
|
122
|
+
const legacyIndexPath = writeLegacyIndex("- org/my-skill\n");
|
|
123
|
+
const nestedSkillPath = writeSkill("org/my-skill");
|
|
124
|
+
|
|
125
|
+
removeLegacySkillsIndexMigration.run(workspaceDir);
|
|
126
|
+
|
|
127
|
+
const topLevelSkillPath = join(
|
|
128
|
+
workspaceDir,
|
|
129
|
+
"skills",
|
|
130
|
+
"my-skill",
|
|
131
|
+
"SKILL.md",
|
|
132
|
+
);
|
|
133
|
+
expect(existsSync(legacyIndexPath)).toBe(false);
|
|
134
|
+
expect(existsSync(nestedSkillPath)).toBe(true);
|
|
135
|
+
expect(readFileSync(topLevelSkillPath, "utf-8")).toContain("org/my-skill");
|
|
136
|
+
|
|
137
|
+
expect(readFileSync(topLevelSkillPath, "utf-8")).toContain("Body.");
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test("copies nested indexed skills from plain entries that point to SKILL.md", () => {
|
|
141
|
+
const legacyIndexPath = writeLegacyIndex("- org/my-skill/SKILL.md\n");
|
|
142
|
+
const nestedSkillPath = writeSkill("org/my-skill");
|
|
143
|
+
|
|
144
|
+
removeLegacySkillsIndexMigration.run(workspaceDir);
|
|
145
|
+
|
|
146
|
+
expectNestedSkillPreserved(legacyIndexPath, nestedSkillPath);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("copies nested indexed skills from markdown links that point to SKILL.md", () => {
|
|
150
|
+
const legacyIndexPath = writeLegacyIndex(
|
|
151
|
+
"- [My Skill](org/my-skill/skill.md)\n",
|
|
152
|
+
);
|
|
153
|
+
const nestedSkillPath = writeSkill("org/my-skill");
|
|
154
|
+
|
|
155
|
+
removeLegacySkillsIndexMigration.run(workspaceDir);
|
|
156
|
+
|
|
157
|
+
expectNestedSkillPreserved(legacyIndexPath, nestedSkillPath);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("preserves nested indexed skill with alternate id when top-level basename exists", () => {
|
|
161
|
+
const legacyIndexPath = writeLegacyIndex("- org/my-skill\n");
|
|
162
|
+
const nestedSkillPath = writeSkill("org/my-skill", "Nested body.");
|
|
163
|
+
const topLevelSkillPath = writeSkill("my-skill", "Top-level body.");
|
|
164
|
+
|
|
165
|
+
removeLegacySkillsIndexMigration.run(workspaceDir);
|
|
166
|
+
|
|
167
|
+
const preservedSkillPath = join(
|
|
168
|
+
workspaceDir,
|
|
169
|
+
"skills",
|
|
170
|
+
"org__my-skill",
|
|
171
|
+
"SKILL.md",
|
|
172
|
+
);
|
|
173
|
+
expect(existsSync(legacyIndexPath)).toBe(false);
|
|
174
|
+
expect(readFileSync(nestedSkillPath, "utf-8")).toContain("Nested body.");
|
|
175
|
+
expect(readFileSync(topLevelSkillPath, "utf-8")).toContain(
|
|
176
|
+
"Top-level body.",
|
|
177
|
+
);
|
|
178
|
+
expect(readFileSync(preservedSkillPath, "utf-8")).toContain("Nested body.");
|
|
179
|
+
|
|
180
|
+
expect(readFileSync(topLevelSkillPath, "utf-8")).toContain(
|
|
181
|
+
"Top-level body.",
|
|
182
|
+
);
|
|
183
|
+
expect(readFileSync(preservedSkillPath, "utf-8")).toContain("Nested body.");
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("preserves same-basename nested indexed skills with unique top-level ids", () => {
|
|
187
|
+
const legacyIndexPath = writeLegacyIndex(
|
|
188
|
+
"- team-a/deploy\n- team-b/deploy\n",
|
|
189
|
+
);
|
|
190
|
+
const teamASkillPath = writeSkill("team-a/deploy", "Team A body.");
|
|
191
|
+
const teamBSkillPath = writeSkill("team-b/deploy", "Team B body.");
|
|
192
|
+
|
|
193
|
+
removeLegacySkillsIndexMigration.run(workspaceDir);
|
|
194
|
+
|
|
195
|
+
const primaryPreservedSkillPath = join(
|
|
196
|
+
workspaceDir,
|
|
197
|
+
"skills",
|
|
198
|
+
"deploy",
|
|
199
|
+
"SKILL.md",
|
|
200
|
+
);
|
|
201
|
+
const alternatePreservedSkillPath = join(
|
|
202
|
+
workspaceDir,
|
|
203
|
+
"skills",
|
|
204
|
+
"team-b__deploy",
|
|
205
|
+
"SKILL.md",
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
expect(existsSync(legacyIndexPath)).toBe(false);
|
|
209
|
+
expect(existsSync(teamASkillPath)).toBe(true);
|
|
210
|
+
expect(existsSync(teamBSkillPath)).toBe(true);
|
|
211
|
+
expect(readFileSync(primaryPreservedSkillPath, "utf-8")).toContain(
|
|
212
|
+
"Team A body.",
|
|
213
|
+
);
|
|
214
|
+
expect(readFileSync(alternatePreservedSkillPath, "utf-8")).toContain(
|
|
215
|
+
"Team B body.",
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
expect(readFileSync(primaryPreservedSkillPath, "utf-8")).toContain(
|
|
219
|
+
"Team A body.",
|
|
220
|
+
);
|
|
221
|
+
expect(readFileSync(alternatePreservedSkillPath, "utf-8")).toContain(
|
|
222
|
+
"Team B body.",
|
|
223
|
+
);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test("does not follow legacy index entries outside the skills root", () => {
|
|
227
|
+
const legacyIndexPath = writeLegacyIndex("- ../outside/my-skill\n");
|
|
228
|
+
const outsideSkillDir = join(workspaceDir, "outside", "my-skill");
|
|
229
|
+
mkdirSync(outsideSkillDir, { recursive: true });
|
|
230
|
+
writeFileSync(
|
|
231
|
+
join(outsideSkillDir, "SKILL.md"),
|
|
232
|
+
"---\nname: Outside\ndescription: Outside skill.\n---\n\nOutside.\n",
|
|
233
|
+
"utf-8",
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
removeLegacySkillsIndexMigration.run(workspaceDir);
|
|
237
|
+
|
|
238
|
+
expect(existsSync(legacyIndexPath)).toBe(false);
|
|
239
|
+
expect(existsSync(join(workspaceDir, "skills", "my-skill"))).toBe(false);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test("is safe to re-run", () => {
|
|
243
|
+
const legacyIndexPath = writeLegacyIndex();
|
|
244
|
+
|
|
245
|
+
removeLegacySkillsIndexMigration.run(workspaceDir);
|
|
246
|
+
expect(() =>
|
|
247
|
+
removeLegacySkillsIndexMigration.run(workspaceDir),
|
|
248
|
+
).not.toThrow();
|
|
249
|
+
expect(existsSync(legacyIndexPath)).toBe(false);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
test("does not recursively delete a directory named SKILLS.md", () => {
|
|
253
|
+
const legacyIndexDir = join(workspaceDir, "skills", "SKILLS.md");
|
|
254
|
+
mkdirSync(legacyIndexDir, { recursive: true });
|
|
255
|
+
writeFileSync(join(legacyIndexDir, "nested.txt"), "keep\n", "utf-8");
|
|
256
|
+
|
|
257
|
+
removeLegacySkillsIndexMigration.run(workspaceDir);
|
|
258
|
+
|
|
259
|
+
expect(readFileSync(join(legacyIndexDir, "nested.txt"), "utf-8")).toBe(
|
|
260
|
+
"keep\n",
|
|
261
|
+
);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
test("rethrows unexpected lstat failures", () => {
|
|
265
|
+
const legacyIndexPath = writeLegacyIndex();
|
|
266
|
+
|
|
267
|
+
const lstatError = Object.assign(new Error("simulated lstat failure"), {
|
|
268
|
+
code: "EACCES",
|
|
269
|
+
});
|
|
270
|
+
const lstatSpy = spyOn(fs, "lstatSync").mockImplementation(() => {
|
|
271
|
+
throw lstatError;
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
try {
|
|
275
|
+
expect(() => removeLegacySkillsIndexMigration.run(workspaceDir)).toThrow(
|
|
276
|
+
lstatError,
|
|
277
|
+
);
|
|
278
|
+
expect(existsSync(legacyIndexPath)).toBe(true);
|
|
279
|
+
} finally {
|
|
280
|
+
lstatSpy.mockRestore();
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
test("rethrows unexpected unlink failures and leaves SKILLS.md for retry", () => {
|
|
285
|
+
const legacyIndexPath = writeLegacyIndex();
|
|
286
|
+
|
|
287
|
+
const unlinkError = Object.assign(new Error("simulated unlink failure"), {
|
|
288
|
+
code: "EACCES",
|
|
289
|
+
});
|
|
290
|
+
const unlinkSpy = spyOn(fs, "unlinkSync").mockImplementation(() => {
|
|
291
|
+
throw unlinkError;
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
try {
|
|
295
|
+
expect(() => removeLegacySkillsIndexMigration.run(workspaceDir)).toThrow(
|
|
296
|
+
unlinkError,
|
|
297
|
+
);
|
|
298
|
+
expect(existsSync(legacyIndexPath)).toBe(true);
|
|
299
|
+
} finally {
|
|
300
|
+
unlinkSpy.mockRestore();
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
test("down() is a no-op", () => {
|
|
305
|
+
expect(() =>
|
|
306
|
+
removeLegacySkillsIndexMigration.down(workspaceDir),
|
|
307
|
+
).not.toThrow();
|
|
308
|
+
});
|
|
309
|
+
});
|
|
@@ -137,9 +137,10 @@ describe("runWorkspaceMigrations", () => {
|
|
|
137
137
|
expect(m1.run).toHaveBeenCalledTimes(1);
|
|
138
138
|
expect(m2.run).toHaveBeenCalledTimes(1);
|
|
139
139
|
|
|
140
|
-
// Checkpoints saved: started m1, completed m1, started m2, failed m2
|
|
141
|
-
|
|
142
|
-
expect(
|
|
140
|
+
// Checkpoints saved: started m1, completed m1, started m2, failed m2,
|
|
141
|
+
// then the post-loop flip clearing isNewWorkspace = 5 writes.
|
|
142
|
+
expect(writeFileSyncFn).toHaveBeenCalledTimes(5);
|
|
143
|
+
expect(renameSyncFn).toHaveBeenCalledTimes(5);
|
|
143
144
|
|
|
144
145
|
// Verify the completed checkpoint contains m1
|
|
145
146
|
// The second write is the "completed" marker for m1
|
|
@@ -246,6 +247,33 @@ describe("runWorkspaceMigrations", () => {
|
|
|
246
247
|
expect(m1.run).not.toHaveBeenCalled();
|
|
247
248
|
});
|
|
248
249
|
|
|
250
|
+
test("skips failed migrations by default", async () => {
|
|
251
|
+
mockCheckpointContents = JSON.stringify({
|
|
252
|
+
applied: {
|
|
253
|
+
"001": { appliedAt: "2025-01-01T00:00:00.000Z", status: "failed" },
|
|
254
|
+
},
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
const m1 = makeMigration("001");
|
|
258
|
+
await runWorkspaceMigrations(WORKSPACE_DIR, [m1]);
|
|
259
|
+
|
|
260
|
+
expect(m1.run).not.toHaveBeenCalled();
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
test("retries failed migrations that opt in", async () => {
|
|
264
|
+
mockCheckpointContents = JSON.stringify({
|
|
265
|
+
applied: {
|
|
266
|
+
"001": { appliedAt: "2025-01-01T00:00:00.000Z", status: "failed" },
|
|
267
|
+
},
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
const m1 = makeMigration("001");
|
|
271
|
+
m1.retryFailedCheckpoint = true;
|
|
272
|
+
await runWorkspaceMigrations(WORKSPACE_DIR, [m1]);
|
|
273
|
+
|
|
274
|
+
expect(m1.run).toHaveBeenCalledTimes(1);
|
|
275
|
+
});
|
|
276
|
+
|
|
249
277
|
test("supports async migrations", async () => {
|
|
250
278
|
const asyncMigration: WorkspaceMigration = {
|
|
251
279
|
id: "001",
|
|
@@ -278,6 +306,86 @@ describe("runWorkspaceMigrations", () => {
|
|
|
278
306
|
expect(m1.run).not.toHaveBeenCalled();
|
|
279
307
|
});
|
|
280
308
|
|
|
309
|
+
test("persists isNewWorkspace=true on first creation, then flips to false after sweep", async () => {
|
|
310
|
+
// No checkpoint file → fresh workspace.
|
|
311
|
+
const m1 = makeMigration("001");
|
|
312
|
+
let observed: boolean | undefined;
|
|
313
|
+
(m1.run as ReturnType<typeof mock>).mockImplementation(
|
|
314
|
+
(_dir: string, ctx?: { isNewWorkspace: boolean }) => {
|
|
315
|
+
observed = ctx?.isNewWorkspace;
|
|
316
|
+
},
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
await runWorkspaceMigrations(WORKSPACE_DIR, [m1]);
|
|
320
|
+
|
|
321
|
+
// The migration saw the new-workspace flag.
|
|
322
|
+
expect(observed).toBe(true);
|
|
323
|
+
|
|
324
|
+
// The first persisted checkpoint (m1's "started" save) carries the flag.
|
|
325
|
+
const firstSave = JSON.parse(
|
|
326
|
+
(writeFileSyncFn.mock.calls[0] as unknown[])[1] as string,
|
|
327
|
+
);
|
|
328
|
+
expect(firstSave.isNewWorkspace).toBe(true);
|
|
329
|
+
|
|
330
|
+
// The final persisted checkpoint clears the flag so subsequent boots
|
|
331
|
+
// treat this workspace as an upgrade.
|
|
332
|
+
const finalSave = JSON.parse(
|
|
333
|
+
(writeFileSyncFn.mock.calls.at(-1) as unknown[])[1] as string,
|
|
334
|
+
);
|
|
335
|
+
expect(finalSave.isNewWorkspace).toBe(false);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
test("preserves isNewWorkspace=true across a crash before seeding migrations run", async () => {
|
|
339
|
+
// Simulate a crash mid-first-boot: an earlier migration recorded its
|
|
340
|
+
// "started" marker (writing the checkpoint file) and the daemon then
|
|
341
|
+
// died before reaching the seeding migration. The persisted flag must
|
|
342
|
+
// survive the reboot so the seeding migration still observes the
|
|
343
|
+
// brand-new workspace.
|
|
344
|
+
mockCheckpointContents = JSON.stringify({
|
|
345
|
+
isNewWorkspace: true,
|
|
346
|
+
applied: {
|
|
347
|
+
"001": { appliedAt: "2025-01-01T00:00:00.000Z", status: "started" },
|
|
348
|
+
},
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
const seedingMigration = makeMigration("seed");
|
|
352
|
+
let observed: boolean | undefined;
|
|
353
|
+
(seedingMigration.run as ReturnType<typeof mock>).mockImplementation(
|
|
354
|
+
(_dir: string, ctx?: { isNewWorkspace: boolean }) => {
|
|
355
|
+
observed = ctx?.isNewWorkspace;
|
|
356
|
+
},
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
const m1 = makeMigration("001");
|
|
360
|
+
await runWorkspaceMigrations(WORKSPACE_DIR, [m1, seedingMigration]);
|
|
361
|
+
|
|
362
|
+
expect(m1.run).toHaveBeenCalledTimes(1);
|
|
363
|
+
expect(seedingMigration.run).toHaveBeenCalledTimes(1);
|
|
364
|
+
expect(observed).toBe(true);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
test("treats pre-existing checkpoint without isNewWorkspace field as upgrade", async () => {
|
|
368
|
+
// Workspaces created before this field was introduced have a checkpoint
|
|
369
|
+
// file with only `applied`. They must not be re-seeded.
|
|
370
|
+
mockCheckpointContents = JSON.stringify({
|
|
371
|
+
applied: {
|
|
372
|
+
"001": { appliedAt: "2025-01-01T00:00:00.000Z", status: "completed" },
|
|
373
|
+
},
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
const seedingMigration = makeMigration("seed");
|
|
377
|
+
let observed: boolean | undefined;
|
|
378
|
+
(seedingMigration.run as ReturnType<typeof mock>).mockImplementation(
|
|
379
|
+
(_dir: string, ctx?: { isNewWorkspace: boolean }) => {
|
|
380
|
+
observed = ctx?.isNewWorkspace;
|
|
381
|
+
},
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
await runWorkspaceMigrations(WORKSPACE_DIR, [seedingMigration]);
|
|
385
|
+
|
|
386
|
+
expect(observed).toBe(false);
|
|
387
|
+
});
|
|
388
|
+
|
|
281
389
|
test("warns on malformed checkpoint file", async () => {
|
|
282
390
|
mockCheckpointContents = "not valid json";
|
|
283
391
|
|
package/src/acp/resolve-agent.ts
CHANGED
|
@@ -73,7 +73,7 @@ function installHintFor(command: string): string {
|
|
|
73
73
|
*/
|
|
74
74
|
function findAgentBinary(agent: AcpAgentConfig): string | null {
|
|
75
75
|
const PATH = agent.env?.PATH ?? process.env.PATH;
|
|
76
|
-
return Bun.which(agent.command, PATH ? { PATH } : undefined);
|
|
76
|
+
return Bun.which(agent.command, PATH != null ? { PATH } : undefined);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
/**
|
|
@@ -21,6 +21,11 @@ const MAX_DIMENSION = 1568;
|
|
|
21
21
|
// Threshold below which we skip optimization — small images don't need it.
|
|
22
22
|
const OPTIMIZE_THRESHOLD_BYTES = 300 * 1024; // 300 KB
|
|
23
23
|
|
|
24
|
+
// Anthropic rejects any single image whose source payload exceeds 5 MB,
|
|
25
|
+
// regardless of pixel dimensions. Cap at ~3.5 MB raw so the base64-encoded
|
|
26
|
+
// form (raw * 4/3) stays comfortably under 5 MB even after re-encoding.
|
|
27
|
+
const MAX_TRANSPORT_BYTES = Math.floor(3.5 * 1024 * 1024); // ~3.5 MB raw
|
|
28
|
+
|
|
24
29
|
const JPEG_QUALITY = 80;
|
|
25
30
|
|
|
26
31
|
// Content-addressed disk cache to avoid re-running sips on the same image.
|
|
@@ -130,10 +135,13 @@ function runSips(inputBytes: Buffer): Buffer | null {
|
|
|
130
135
|
/**
|
|
131
136
|
* Decide whether an image needs to be rescaled before sending.
|
|
132
137
|
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
138
|
+
* Two independent gates apply:
|
|
139
|
+
* 1. Pixel dimensions — Anthropic rejects many-image requests when any
|
|
140
|
+
* image exceeds 2000 px on a side. A sparse screenshot can be under
|
|
141
|
+
* 300 KB while still being 3000+ px wide.
|
|
142
|
+
* 2. Byte size — Anthropic rejects any image whose source payload
|
|
143
|
+
* exceeds 5 MB. A 1500×1500 high-color screenshot can produce a >5 MB
|
|
144
|
+
* payload while staying well under the dimension cap.
|
|
137
145
|
*
|
|
138
146
|
* Exported for unit testing.
|
|
139
147
|
*/
|
|
@@ -141,8 +149,8 @@ export function shouldRescaleImage(
|
|
|
141
149
|
dims: { width: number; height: number } | null,
|
|
142
150
|
byteLength: number,
|
|
143
151
|
): boolean {
|
|
152
|
+
if (byteLength > MAX_TRANSPORT_BYTES) return true;
|
|
144
153
|
if (dims) {
|
|
145
|
-
// Dimensions known — they are the authoritative check.
|
|
146
154
|
return dims.width > MAX_DIMENSION || dims.height > MAX_DIMENSION;
|
|
147
155
|
}
|
|
148
156
|
// Dimensions unparseable — fall back to file size as a rough proxy.
|
|
@@ -318,8 +318,9 @@ export async function startVoiceTurn(
|
|
|
318
318
|
|
|
319
319
|
// Phone voice has no interactive permission/secret UI, so apply explicit
|
|
320
320
|
// per-role policies by default. Local live voice opts into the normal
|
|
321
|
-
// client approval path instead. Side-effect double-defense
|
|
322
|
-
//
|
|
321
|
+
// client approval path instead. Side-effect double-defense
|
|
322
|
+
// (forcePromptSideEffects) is wired inside the agent-loop IIFE so it
|
|
323
|
+
// is always paired with cleanup() in the IIFE's finally.
|
|
323
324
|
const trustClass = opts.trustContext?.trustClass;
|
|
324
325
|
const isGuardian = trustClass === "guardian";
|
|
325
326
|
const approvalMode = opts.approvalMode ?? "phone-call";
|
|
@@ -391,34 +392,57 @@ export async function startVoiceTurn(
|
|
|
391
392
|
}
|
|
392
393
|
}
|
|
393
394
|
|
|
394
|
-
//
|
|
395
|
-
//
|
|
396
|
-
//
|
|
397
|
-
//
|
|
398
|
-
//
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
395
|
+
// Hoisted so the catch below can clear partially-applied turn state
|
|
396
|
+
// when a setter or `persistUserMessage` throws — otherwise `trustContext`,
|
|
397
|
+
// `callSessionId`, etc. leak into subsequent non-voice turns on the same
|
|
398
|
+
// conversation. The client callback is only reset when this turn actually
|
|
399
|
+
// installed it (tracked via `clientCallbackInstalled`); otherwise cleanup
|
|
400
|
+
// would detach an active sender installed by a prior turn.
|
|
401
|
+
let clientCallbackInstalled = false;
|
|
402
|
+
const cleanup = () => {
|
|
403
|
+
conversation.setChannelCapabilities(null);
|
|
404
|
+
conversation.setTrustContext(null);
|
|
405
|
+
conversation.setCommandIntent(null);
|
|
406
|
+
conversation.setAssistantId("self");
|
|
407
|
+
conversation.setVoiceCallControlPrompt(null);
|
|
408
|
+
conversation.callSessionId = undefined;
|
|
409
|
+
conversation.forcePromptSideEffects = false;
|
|
410
|
+
if (clientCallbackInstalled) {
|
|
411
|
+
// Reset the client callback to a no-op so the stale closure doesn't
|
|
412
|
+
// intercept events from future turns on the same conversation.
|
|
413
|
+
conversation.updateClient(() => {}, true);
|
|
414
|
+
}
|
|
415
|
+
};
|
|
414
416
|
|
|
415
417
|
const requestId = crypto.randomUUID();
|
|
416
418
|
const turnId = crypto.randomUUID();
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
419
|
+
let messageId: string;
|
|
420
|
+
try {
|
|
421
|
+
conversation.setAssistantId(
|
|
422
|
+
opts.assistantId ?? DAEMON_INTERNAL_ASSISTANT_ID,
|
|
423
|
+
);
|
|
424
|
+
conversation.callSessionId = voiceSessionId;
|
|
425
|
+
conversation.setTrustContext(opts.trustContext ?? null);
|
|
426
|
+
conversation.setCommandIntent(null);
|
|
427
|
+
conversation.setTurnChannelContext(turnChannelContext);
|
|
428
|
+
conversation.setTurnInterfaceContext?.(turnInterfaceContext);
|
|
429
|
+
conversation.setChannelCapabilities(
|
|
430
|
+
resolveChannelCapabilities(
|
|
431
|
+
turnChannelContext.userMessageChannel,
|
|
432
|
+
turnInterfaceContext.userMessageInterface,
|
|
433
|
+
),
|
|
434
|
+
);
|
|
435
|
+
conversation.setVoiceCallControlPrompt(voiceCallControlPrompt);
|
|
436
|
+
|
|
437
|
+
messageId = await conversation.persistUserMessage(
|
|
438
|
+
persistedContent,
|
|
439
|
+
[],
|
|
440
|
+
requestId,
|
|
441
|
+
);
|
|
442
|
+
} catch (err) {
|
|
443
|
+
cleanup();
|
|
444
|
+
throw err;
|
|
445
|
+
}
|
|
422
446
|
try {
|
|
423
447
|
opts.callbacks?.persisted_user_message_id?.(messageId);
|
|
424
448
|
} catch (err) {
|
|
@@ -556,25 +580,20 @@ export async function startVoiceTurn(
|
|
|
556
580
|
}
|
|
557
581
|
broadcastMessage(msg);
|
|
558
582
|
});
|
|
583
|
+
clientCallbackInstalled = true;
|
|
559
584
|
|
|
560
585
|
// Fire-and-forget the agent loop
|
|
561
|
-
const cleanup = () => {
|
|
562
|
-
// Reset channel capabilities so a subsequent desktop session on the
|
|
563
|
-
// same conversation is not incorrectly treated as a voice client.
|
|
564
|
-
conversation.setChannelCapabilities(null);
|
|
565
|
-
conversation.setTrustContext(null);
|
|
566
|
-
conversation.setCommandIntent(null);
|
|
567
|
-
conversation.setAssistantId("self");
|
|
568
|
-
conversation.setVoiceCallControlPrompt(null);
|
|
569
|
-
conversation.callSessionId = undefined;
|
|
570
|
-
conversation.forcePromptSideEffects = false;
|
|
571
|
-
// Reset the conversation's client callback to a no-op so the stale
|
|
572
|
-
// closure doesn't intercept events from future turns on the same conversation.
|
|
573
|
-
conversation.updateClient(() => {}, true);
|
|
574
|
-
};
|
|
575
|
-
|
|
576
586
|
void (async () => {
|
|
577
587
|
try {
|
|
588
|
+
// Non-guardian phone voice forces side-effect tools to prompt so the
|
|
589
|
+
// auto-deny handler above reliably sees a confirmation_request. Without
|
|
590
|
+
// this, a broad allow trust rule (e.g. wildcard bash) would let
|
|
591
|
+
// side-effect tools execute without ever emitting an event for the
|
|
592
|
+
// auto-deny / scoped-grant handler to intercept. Set inside the
|
|
593
|
+
// try/finally so a failed setup before this point cannot leak the
|
|
594
|
+
// flag into subsequent non-voice turns on the same conversation.
|
|
595
|
+
conversation.forcePromptSideEffects =
|
|
596
|
+
!isGuardian && !usesLocalInteractiveApprovals;
|
|
578
597
|
await conversation.runAgentLoop(
|
|
579
598
|
persistedContent,
|
|
580
599
|
messageId,
|