@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
|
@@ -3,18 +3,13 @@
|
|
|
3
3
|
* `assistant/src/daemon/handlers/skills.ts`.
|
|
4
4
|
*
|
|
5
5
|
* One representative call site (the `installSkill` bundled branch) is
|
|
6
|
-
* exercised
|
|
7
|
-
* `
|
|
8
|
-
* -
|
|
9
|
-
* observed (callOrder picks up "v2")
|
|
10
|
-
* - config off → helper still invoked, but the seed short-circuits
|
|
6
|
+
* exercised; all handler seed sites share the same delegation to
|
|
7
|
+
* `refreshSkillCapabilityMemories`, so a single suite covers behavior. Validates:
|
|
8
|
+
* - handler invokes the centralized refresh helper with the live config.
|
|
11
9
|
*
|
|
12
|
-
* The
|
|
13
|
-
* `
|
|
14
|
-
*
|
|
15
|
-
* gate semantics are covered by `lifecycle-memory-v2-seed.test.ts`; here
|
|
16
|
-
* we only verify that the handler invokes the helper synchronously with
|
|
17
|
-
* the live config.
|
|
10
|
+
* The helper's gate semantics (flag + config + rejection swallowing) are
|
|
11
|
+
* covered by `lifecycle-memory-v2-seed.test.ts`; here we only verify that the
|
|
12
|
+
* handler delegates to the centralized refresh path synchronously.
|
|
18
13
|
*/
|
|
19
14
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
20
15
|
|
|
@@ -22,16 +17,9 @@ import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
|
22
17
|
// Programmable test state
|
|
23
18
|
// ---------------------------------------------------------------------------
|
|
24
19
|
|
|
25
|
-
const
|
|
20
|
+
const configState = { v2Enabled: true };
|
|
26
21
|
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
const mockSeedSkillGraphNodes = mock(() => {
|
|
30
|
-
callOrder.push("v1");
|
|
31
|
-
});
|
|
32
|
-
// Body installed in `beforeEach` so each test sees a fresh implementation
|
|
33
|
-
// that closes over the up-to-date `flagsState`.
|
|
34
|
-
const mockMaybeSeedMemoryV2Skills = mock(
|
|
22
|
+
const mockRefreshSkillCapabilityMemories = mock(
|
|
35
23
|
(_config: { memory: { v2: { enabled: boolean } } }) => {},
|
|
36
24
|
);
|
|
37
25
|
|
|
@@ -53,10 +41,6 @@ mock.module("../config/skills.js", () => ({
|
|
|
53
41
|
],
|
|
54
42
|
}));
|
|
55
43
|
|
|
56
|
-
mock.module("../config/assistant-feature-flags.js", () => ({
|
|
57
|
-
isAssistantFeatureFlagEnabled: () => true,
|
|
58
|
-
}));
|
|
59
|
-
|
|
60
44
|
// Stub both `getConfig` and `loadConfig`. `loadConfig` is reached by code
|
|
61
45
|
// paths transitively imported during teardown (e.g. dynamic imports inside
|
|
62
46
|
// `oauth2.ts`); leaving it undefined here would break sibling test files
|
|
@@ -68,13 +52,13 @@ mock.module("../config/loader.js", () => ({
|
|
|
68
52
|
deepMergeOverwrite: (a: unknown) => a,
|
|
69
53
|
mergeDefaultWorkspaceConfig: () => {},
|
|
70
54
|
getConfig: () => ({
|
|
71
|
-
memory: { v2: { enabled:
|
|
55
|
+
memory: { v2: { enabled: configState.v2Enabled } },
|
|
72
56
|
}),
|
|
73
57
|
getConfigReadOnly: () => ({
|
|
74
|
-
memory: { v2: { enabled:
|
|
58
|
+
memory: { v2: { enabled: configState.v2Enabled } },
|
|
75
59
|
}),
|
|
76
60
|
loadConfig: () => ({
|
|
77
|
-
memory: { v2: { enabled:
|
|
61
|
+
memory: { v2: { enabled: configState.v2Enabled } },
|
|
78
62
|
}),
|
|
79
63
|
invalidateConfigCache: () => {},
|
|
80
64
|
loadRawConfig: () => ({}),
|
|
@@ -147,9 +131,11 @@ mock.module("../skills/catalog-cache.js", () => ({
|
|
|
147
131
|
}));
|
|
148
132
|
|
|
149
133
|
mock.module("../skills/catalog-install.js", () => ({
|
|
150
|
-
|
|
151
|
-
|
|
134
|
+
commitStagedSkillInstall: () => {},
|
|
135
|
+
createSkillInstallStagingDir: () => "/tmp/test-skills/.install-staging/test",
|
|
152
136
|
getRepoSkillsDir: () => undefined,
|
|
137
|
+
installSkillDependenciesIfPresent: () => {},
|
|
138
|
+
installSkillLocally: async () => {},
|
|
153
139
|
}));
|
|
154
140
|
|
|
155
141
|
mock.module("../skills/catalog-search.js", () => ({
|
|
@@ -159,23 +145,15 @@ mock.module("../skills/catalog-search.js", () => ({
|
|
|
159
145
|
mock.module("../skills/managed-store.js", () => ({
|
|
160
146
|
createManagedSkill: () => ({ created: true }),
|
|
161
147
|
deleteManagedSkill: () => ({ deleted: true }),
|
|
162
|
-
removeSkillsIndexEntry: () => {},
|
|
163
148
|
validateManagedSkillId: () => null,
|
|
164
149
|
}));
|
|
165
150
|
|
|
166
151
|
mock.module("../memory/graph/capability-seed.js", () => ({
|
|
167
152
|
deleteSkillCapabilityNode: () => {},
|
|
168
|
-
seedSkillGraphNodes: mockSeedSkillGraphNodes,
|
|
169
|
-
seedUninstalledCatalogSkillMemories: async () => {},
|
|
170
|
-
}));
|
|
171
|
-
|
|
172
|
-
mock.module("../memory/v2/skill-store.js", () => ({
|
|
173
|
-
seedV2SkillEntries: mock(async () => {}),
|
|
174
|
-
getSkillCapability: () => null,
|
|
175
153
|
}));
|
|
176
154
|
|
|
177
|
-
mock.module("../daemon/memory-
|
|
178
|
-
|
|
155
|
+
mock.module("../daemon/skill-memory-refresh.js", () => ({
|
|
156
|
+
refreshSkillCapabilityMemories: mockRefreshSkillCapabilityMemories,
|
|
179
157
|
}));
|
|
180
158
|
|
|
181
159
|
mock.module("../util/platform.js", () => ({
|
|
@@ -204,45 +182,48 @@ mock.module("../daemon/config-watcher.js", () => ({
|
|
|
204
182
|
}));
|
|
205
183
|
|
|
206
184
|
// Import after mocking
|
|
207
|
-
const { installSkill } =
|
|
208
|
-
|
|
209
|
-
// ---------------------------------------------------------------------------
|
|
210
|
-
// Helpers
|
|
211
|
-
// ---------------------------------------------------------------------------
|
|
185
|
+
const { installSkill, uninstallSkill } =
|
|
186
|
+
await import("../daemon/handlers/skills.js");
|
|
212
187
|
|
|
213
188
|
// ---------------------------------------------------------------------------
|
|
214
189
|
// Tests
|
|
215
190
|
// ---------------------------------------------------------------------------
|
|
216
191
|
|
|
217
|
-
describe("v2 skill
|
|
192
|
+
describe("v2 skill refresh delegation in skill handlers", () => {
|
|
218
193
|
beforeEach(() => {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
mockSeedSkillGraphNodes.mockClear();
|
|
222
|
-
mockMaybeSeedMemoryV2Skills.mockClear();
|
|
223
|
-
mockMaybeSeedMemoryV2Skills.mockImplementation((config) => {
|
|
224
|
-
if (!config.memory.v2.enabled) return;
|
|
225
|
-
callOrder.push("v2");
|
|
226
|
-
});
|
|
194
|
+
configState.v2Enabled = true;
|
|
195
|
+
mockRefreshSkillCapabilityMemories.mockClear();
|
|
227
196
|
});
|
|
228
197
|
|
|
229
|
-
test("config
|
|
198
|
+
test("enabled config → refresh helper invoked with live config", async () => {
|
|
230
199
|
const result = await installSkill({ slug: "bundled-skill" });
|
|
231
200
|
|
|
232
201
|
expect(result.success).toBe(true);
|
|
233
|
-
expect(
|
|
234
|
-
expect(
|
|
235
|
-
|
|
202
|
+
expect(mockRefreshSkillCapabilityMemories).toHaveBeenCalledTimes(1);
|
|
203
|
+
expect(mockRefreshSkillCapabilityMemories.mock.calls[0]?.[0]).toEqual({
|
|
204
|
+
memory: { v2: { enabled: true } },
|
|
205
|
+
});
|
|
236
206
|
});
|
|
237
207
|
|
|
238
|
-
test("config.memory.v2.enabled off →
|
|
239
|
-
|
|
208
|
+
test("config.memory.v2.enabled off → helper receives disabled config", async () => {
|
|
209
|
+
configState.v2Enabled = false;
|
|
240
210
|
|
|
241
211
|
const result = await installSkill({ slug: "bundled-skill" });
|
|
242
212
|
|
|
243
213
|
expect(result.success).toBe(true);
|
|
244
|
-
expect(
|
|
245
|
-
expect(
|
|
246
|
-
|
|
214
|
+
expect(mockRefreshSkillCapabilityMemories).toHaveBeenCalledTimes(1);
|
|
215
|
+
expect(mockRefreshSkillCapabilityMemories.mock.calls[0]?.[0]).toEqual({
|
|
216
|
+
memory: { v2: { enabled: false } },
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test("uninstall delegates to refresh helper", async () => {
|
|
221
|
+
const result = await uninstallSkill("managed-skill");
|
|
222
|
+
|
|
223
|
+
expect(result.success).toBe(true);
|
|
224
|
+
expect(mockRefreshSkillCapabilityMemories).toHaveBeenCalledTimes(1);
|
|
225
|
+
expect(mockRefreshSkillCapabilityMemories.mock.calls[0]?.[0]).toEqual({
|
|
226
|
+
memory: { v2: { enabled: true } },
|
|
227
|
+
});
|
|
247
228
|
});
|
|
248
229
|
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export function makeTarEntry(name: string, content: string): Buffer {
|
|
2
|
+
const header = Buffer.alloc(512, 0);
|
|
3
|
+
const nameBuffer = Buffer.from(name, "utf-8");
|
|
4
|
+
nameBuffer.copy(header, 0, 0, Math.min(nameBuffer.length, 100));
|
|
5
|
+
|
|
6
|
+
Buffer.from("0000644\0", "ascii").copy(header, 100);
|
|
7
|
+
Buffer.from("0000000\0", "ascii").copy(header, 108);
|
|
8
|
+
Buffer.from("0000000\0", "ascii").copy(header, 116);
|
|
9
|
+
Buffer.from(
|
|
10
|
+
`${content.length.toString(8).padStart(11, "0")}\0`,
|
|
11
|
+
"ascii",
|
|
12
|
+
).copy(header, 124);
|
|
13
|
+
Buffer.from("00000000000\0", "ascii").copy(header, 136);
|
|
14
|
+
Buffer.from(" ", "ascii").copy(header, 148);
|
|
15
|
+
header[156] = "0".charCodeAt(0);
|
|
16
|
+
Buffer.from("ustar\0", "ascii").copy(header, 257);
|
|
17
|
+
Buffer.from("00", "ascii").copy(header, 263);
|
|
18
|
+
|
|
19
|
+
let sum = 0;
|
|
20
|
+
for (let i = 0; i < 512; i += 1) sum += header[i] ?? 0;
|
|
21
|
+
Buffer.from(`${sum.toString(8).padStart(6, "0")}\0 `, "ascii").copy(
|
|
22
|
+
header,
|
|
23
|
+
148,
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
const data = Buffer.from(content, "utf-8");
|
|
27
|
+
const padded = Buffer.alloc(Math.ceil(data.length / 512) * 512, 0);
|
|
28
|
+
data.copy(padded);
|
|
29
|
+
return Buffer.concat([header, padded]);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function makeTar(
|
|
33
|
+
entries: Array<{ name: string; content: string }>,
|
|
34
|
+
): Buffer {
|
|
35
|
+
return Buffer.concat([
|
|
36
|
+
...entries.map((entry) => makeTarEntry(entry.name, entry.content)),
|
|
37
|
+
Buffer.alloc(1024, 0),
|
|
38
|
+
]);
|
|
39
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export async function waitFor(
|
|
2
|
+
predicate: () => boolean | Promise<boolean>,
|
|
3
|
+
options: {
|
|
4
|
+
timeoutMs?: number;
|
|
5
|
+
intervalMs?: number;
|
|
6
|
+
message?: string;
|
|
7
|
+
} = {},
|
|
8
|
+
): Promise<void> {
|
|
9
|
+
const timeoutMs = options.timeoutMs ?? 500;
|
|
10
|
+
const intervalMs = options.intervalMs ?? 5;
|
|
11
|
+
const deadline = Date.now() + timeoutMs;
|
|
12
|
+
|
|
13
|
+
while (Date.now() < deadline) {
|
|
14
|
+
if (await predicate()) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
throw new Error(options.message ?? "Timed out waiting for test condition");
|
|
21
|
+
}
|
|
@@ -248,7 +248,6 @@ describe("historyRepair pipeline — end-to-end via runPipeline", () => {
|
|
|
248
248
|
manifest: {
|
|
249
249
|
name: "observer-plugin",
|
|
250
250
|
version: "0.0.1",
|
|
251
|
-
requires: { pluginRuntime: "v1", historyRepairApi: "v1" },
|
|
252
251
|
},
|
|
253
252
|
middleware: { historyRepair: observer },
|
|
254
253
|
});
|
|
@@ -297,7 +296,6 @@ describe("historyRepair pipeline — end-to-end via runPipeline", () => {
|
|
|
297
296
|
manifest: {
|
|
298
297
|
name: "override-plugin",
|
|
299
298
|
version: "0.0.1",
|
|
300
|
-
requires: { pluginRuntime: "v1", historyRepairApi: "v1" },
|
|
301
299
|
},
|
|
302
300
|
middleware: { historyRepair: shortCircuit },
|
|
303
301
|
});
|
|
@@ -342,7 +340,6 @@ describe("historyRepair pipeline — end-to-end via runPipeline", () => {
|
|
|
342
340
|
manifest: {
|
|
343
341
|
name: "late-user-plugin",
|
|
344
342
|
version: "0.0.1",
|
|
345
|
-
requires: { pluginRuntime: "v1", historyRepairApi: "v1" },
|
|
346
343
|
},
|
|
347
344
|
middleware: { historyRepair: userMiddleware },
|
|
348
345
|
});
|
|
@@ -766,6 +766,79 @@ describe("repairHistory", () => {
|
|
|
766
766
|
(b as { text: string }).text.includes("srvtoolu_orphan"),
|
|
767
767
|
);
|
|
768
768
|
expect(downgraded).toBeDefined();
|
|
769
|
+
// Titles/URLs from the original results must survive the downgrade so
|
|
770
|
+
// the model can still reason about what was searched.
|
|
771
|
+
const text = (downgraded as { text: string }).text;
|
|
772
|
+
expect(text).toContain("Example");
|
|
773
|
+
expect(text).toContain("https://example.com");
|
|
774
|
+
});
|
|
775
|
+
|
|
776
|
+
test("preserves all titles/URLs when downgrading multi-result orphan", () => {
|
|
777
|
+
const messages: Message[] = [
|
|
778
|
+
{ role: "user", content: [{ type: "text", text: "search" }] },
|
|
779
|
+
{
|
|
780
|
+
role: "assistant",
|
|
781
|
+
content: [
|
|
782
|
+
{
|
|
783
|
+
type: "web_search_tool_result",
|
|
784
|
+
tool_use_id: "srvtoolu_multi",
|
|
785
|
+
content: [
|
|
786
|
+
{
|
|
787
|
+
type: "web_search_result",
|
|
788
|
+
url: "https://alpha.test",
|
|
789
|
+
title: "Alpha",
|
|
790
|
+
encrypted_content: "enc_a",
|
|
791
|
+
},
|
|
792
|
+
{
|
|
793
|
+
type: "web_search_result",
|
|
794
|
+
url: "https://beta.test",
|
|
795
|
+
title: "Beta",
|
|
796
|
+
encrypted_content: "enc_b",
|
|
797
|
+
},
|
|
798
|
+
],
|
|
799
|
+
},
|
|
800
|
+
],
|
|
801
|
+
},
|
|
802
|
+
];
|
|
803
|
+
|
|
804
|
+
const { messages: repaired } = repairHistory(messages);
|
|
805
|
+
const downgraded = repaired[1].content.find((b) => b.type === "text") as
|
|
806
|
+
| { text: string }
|
|
807
|
+
| undefined;
|
|
808
|
+
expect(downgraded).toBeDefined();
|
|
809
|
+
expect(downgraded!.text).toContain("Alpha");
|
|
810
|
+
expect(downgraded!.text).toContain("https://alpha.test");
|
|
811
|
+
expect(downgraded!.text).toContain("Beta");
|
|
812
|
+
expect(downgraded!.text).toContain("https://beta.test");
|
|
813
|
+
// Must NOT emit the legacy fixed placeholder.
|
|
814
|
+
expect(downgraded!.text).not.toContain("[web search result]");
|
|
815
|
+
});
|
|
816
|
+
|
|
817
|
+
test("downgrades error-envelope web_search orphan to a stable marker", () => {
|
|
818
|
+
const messages: Message[] = [
|
|
819
|
+
{ role: "user", content: [{ type: "text", text: "search" }] },
|
|
820
|
+
{
|
|
821
|
+
role: "assistant",
|
|
822
|
+
content: [
|
|
823
|
+
{
|
|
824
|
+
type: "web_search_tool_result",
|
|
825
|
+
tool_use_id: "srvtoolu_err",
|
|
826
|
+
content: {
|
|
827
|
+
type: "web_search_tool_result_error",
|
|
828
|
+
error_code: "unavailable",
|
|
829
|
+
},
|
|
830
|
+
},
|
|
831
|
+
],
|
|
832
|
+
},
|
|
833
|
+
];
|
|
834
|
+
|
|
835
|
+
const { messages: repaired } = repairHistory(messages);
|
|
836
|
+
const downgraded = repaired[1].content.find((b) => b.type === "text") as
|
|
837
|
+
| { text: string }
|
|
838
|
+
| undefined;
|
|
839
|
+
expect(downgraded).toBeDefined();
|
|
840
|
+
expect(downgraded!.text).toContain("srvtoolu_err");
|
|
841
|
+
expect(downgraded!.text).toContain("results unavailable");
|
|
769
842
|
});
|
|
770
843
|
|
|
771
844
|
test("repairs both orphan directions within the same assistant message", () => {
|
|
@@ -27,10 +27,8 @@ interface RegisteredInteraction {
|
|
|
27
27
|
const registeredInteractions: RegisteredInteraction[] = [];
|
|
28
28
|
|
|
29
29
|
mock.module("../runtime/pending-interactions.js", () => ({
|
|
30
|
-
register: (
|
|
31
|
-
|
|
32
|
-
entry: RegisteredInteraction,
|
|
33
|
-
) => registeredInteractions.push(entry),
|
|
30
|
+
register: (_requestId: string, entry: RegisteredInteraction) =>
|
|
31
|
+
registeredInteractions.push(entry),
|
|
34
32
|
resolve: (requestId: string) => {
|
|
35
33
|
resolvedInteractionIds.push(requestId);
|
|
36
34
|
return undefined;
|
|
@@ -594,6 +592,264 @@ describe("HostAppControlProxy", () => {
|
|
|
594
592
|
});
|
|
595
593
|
});
|
|
596
594
|
|
|
595
|
+
// -------------------------------------------------------------------------
|
|
596
|
+
// (c.1) Failed re-start restores the prior session
|
|
597
|
+
// -------------------------------------------------------------------------
|
|
598
|
+
|
|
599
|
+
describe("failed re-start restores prior session", () => {
|
|
600
|
+
test("non-running re-start in the same conversation restores the prior session", async () => {
|
|
601
|
+
const proxy = new HostAppControlProxy("conv-1");
|
|
602
|
+
const ctrl = new AbortController();
|
|
603
|
+
|
|
604
|
+
// Establish an active session targeting the editor.
|
|
605
|
+
const p1 = proxy.request(
|
|
606
|
+
"app_control_start",
|
|
607
|
+
{ tool: "start", app: "com.example.editor" },
|
|
608
|
+
"conv-1",
|
|
609
|
+
ctrl.signal,
|
|
610
|
+
);
|
|
611
|
+
proxy.resolve(
|
|
612
|
+
(sentMessages[0] as Record<string, unknown>).requestId as string,
|
|
613
|
+
payload({ pngBase64: PNG_A }),
|
|
614
|
+
);
|
|
615
|
+
await p1;
|
|
616
|
+
expect(_getActiveAppControlSession()?.app).toBe("com.example.editor");
|
|
617
|
+
|
|
618
|
+
// Re-start against a different app — host returns "missing".
|
|
619
|
+
sentMessages.length = 0;
|
|
620
|
+
const p2 = proxy.request(
|
|
621
|
+
"app_control_start",
|
|
622
|
+
{ tool: "start", app: "com.example.other" },
|
|
623
|
+
"conv-1",
|
|
624
|
+
ctrl.signal,
|
|
625
|
+
);
|
|
626
|
+
proxy.resolve(
|
|
627
|
+
(sentMessages[0] as Record<string, unknown>).requestId as string,
|
|
628
|
+
payload({ state: "missing" }),
|
|
629
|
+
);
|
|
630
|
+
await p2;
|
|
631
|
+
|
|
632
|
+
// Prior session restored (editor) — not stranded as undefined and not
|
|
633
|
+
// overwritten with the failed re-start target.
|
|
634
|
+
const session = _getActiveAppControlSession();
|
|
635
|
+
expect(session?.conversationId).toBe("conv-1");
|
|
636
|
+
expect(session?.app).toBe("com.example.editor");
|
|
637
|
+
|
|
638
|
+
proxy.dispose();
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
test("dispatch failure on re-start in the same conversation restores the prior session", async () => {
|
|
642
|
+
const proxy = new HostAppControlProxy("conv-1");
|
|
643
|
+
const ctrl = new AbortController();
|
|
644
|
+
|
|
645
|
+
const p1 = proxy.request(
|
|
646
|
+
"app_control_start",
|
|
647
|
+
{ tool: "start", app: "com.example.editor" },
|
|
648
|
+
"conv-1",
|
|
649
|
+
ctrl.signal,
|
|
650
|
+
);
|
|
651
|
+
proxy.resolve(
|
|
652
|
+
(sentMessages[0] as Record<string, unknown>).requestId as string,
|
|
653
|
+
payload({ pngBase64: PNG_A }),
|
|
654
|
+
);
|
|
655
|
+
await p1;
|
|
656
|
+
expect(_getActiveAppControlSession()?.app).toBe("com.example.editor");
|
|
657
|
+
|
|
658
|
+
// Re-start against a different app, then abort before the host
|
|
659
|
+
// responds. The catch path in `request()` should restore the prior
|
|
660
|
+
// session rather than stranding the lock.
|
|
661
|
+
sentMessages.length = 0;
|
|
662
|
+
const reCtrl = new AbortController();
|
|
663
|
+
const p2 = proxy.request(
|
|
664
|
+
"app_control_start",
|
|
665
|
+
{ tool: "start", app: "com.example.other" },
|
|
666
|
+
"conv-1",
|
|
667
|
+
reCtrl.signal,
|
|
668
|
+
);
|
|
669
|
+
reCtrl.abort();
|
|
670
|
+
const r = await p2;
|
|
671
|
+
expect(r.isError).toBe(true);
|
|
672
|
+
expect(r.content).toContain("Aborted");
|
|
673
|
+
|
|
674
|
+
const session = _getActiveAppControlSession();
|
|
675
|
+
expect(session?.conversationId).toBe("conv-1");
|
|
676
|
+
expect(session?.app).toBe("com.example.editor");
|
|
677
|
+
|
|
678
|
+
proxy.dispose();
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
test("late-failing start does not clobber a newer successful start (out-of-order rollback)", async () => {
|
|
682
|
+
// Overlapping starts from the same conversation where the older one
|
|
683
|
+
// fails AFTER the newer succeeds. Identity-keyed rollback must make
|
|
684
|
+
// the stale failure a no-op rather than restoring the pre-A session.
|
|
685
|
+
const proxy = new HostAppControlProxy("conv-1");
|
|
686
|
+
const ctrl = new AbortController();
|
|
687
|
+
|
|
688
|
+
// Establish prior session A.
|
|
689
|
+
const pA = proxy.request(
|
|
690
|
+
"app_control_start",
|
|
691
|
+
{ tool: "start", app: "com.example.a" },
|
|
692
|
+
"conv-1",
|
|
693
|
+
ctrl.signal,
|
|
694
|
+
);
|
|
695
|
+
const reqIdA = (sentMessages[0] as Record<string, unknown>)
|
|
696
|
+
.requestId as string;
|
|
697
|
+
proxy.resolve(reqIdA, payload({ pngBase64: PNG_A }));
|
|
698
|
+
await pA;
|
|
699
|
+
expect(_getActiveAppControlSession()?.app).toBe("com.example.a");
|
|
700
|
+
|
|
701
|
+
// Start B is dispatched but its host response is delayed.
|
|
702
|
+
sentMessages.length = 0;
|
|
703
|
+
const pB = proxy.request(
|
|
704
|
+
"app_control_start",
|
|
705
|
+
{ tool: "start", app: "com.example.b" },
|
|
706
|
+
"conv-1",
|
|
707
|
+
ctrl.signal,
|
|
708
|
+
);
|
|
709
|
+
const reqIdB = (sentMessages[0] as Record<string, unknown>)
|
|
710
|
+
.requestId as string;
|
|
711
|
+
|
|
712
|
+
// Start C overtakes B and succeeds first.
|
|
713
|
+
sentMessages.length = 0;
|
|
714
|
+
const pC = proxy.request(
|
|
715
|
+
"app_control_start",
|
|
716
|
+
{ tool: "start", app: "com.example.c" },
|
|
717
|
+
"conv-1",
|
|
718
|
+
ctrl.signal,
|
|
719
|
+
);
|
|
720
|
+
const reqIdC = (sentMessages[0] as Record<string, unknown>)
|
|
721
|
+
.requestId as string;
|
|
722
|
+
proxy.resolve(reqIdC, payload({ pngBase64: PNG_A }));
|
|
723
|
+
await pC;
|
|
724
|
+
expect(_getActiveAppControlSession()?.app).toBe("com.example.c");
|
|
725
|
+
|
|
726
|
+
// Now B finally fails — rollback must NOT restore A or clobber C.
|
|
727
|
+
proxy.resolve(reqIdB, payload({ state: "missing" }));
|
|
728
|
+
await pB;
|
|
729
|
+
|
|
730
|
+
const session = _getActiveAppControlSession();
|
|
731
|
+
expect(session?.conversationId).toBe("conv-1");
|
|
732
|
+
expect(session?.app).toBe("com.example.c");
|
|
733
|
+
|
|
734
|
+
proxy.dispose();
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
test("both-fail overlapping starts release the lock (no phantom session)", async () => {
|
|
738
|
+
// Two same-conversation starts overlap, both fail. The later
|
|
739
|
+
// rollback must release the lock instead of resurrecting the
|
|
740
|
+
// earlier (never-confirmed) optimistic write.
|
|
741
|
+
const proxy = new HostAppControlProxy("conv-1");
|
|
742
|
+
const ctrl = new AbortController();
|
|
743
|
+
|
|
744
|
+
const pA = proxy.request(
|
|
745
|
+
"app_control_start",
|
|
746
|
+
{ tool: "start", app: "com.example.a" },
|
|
747
|
+
"conv-1",
|
|
748
|
+
ctrl.signal,
|
|
749
|
+
);
|
|
750
|
+
const reqIdA = (sentMessages[0] as Record<string, unknown>)
|
|
751
|
+
.requestId as string;
|
|
752
|
+
|
|
753
|
+
sentMessages.length = 0;
|
|
754
|
+
const pC = proxy.request(
|
|
755
|
+
"app_control_start",
|
|
756
|
+
{ tool: "start", app: "com.example.c" },
|
|
757
|
+
"conv-1",
|
|
758
|
+
ctrl.signal,
|
|
759
|
+
);
|
|
760
|
+
const reqIdC = (sentMessages[0] as Record<string, unknown>)
|
|
761
|
+
.requestId as string;
|
|
762
|
+
expect(_getActiveAppControlSession()?.app).toBe("com.example.c");
|
|
763
|
+
|
|
764
|
+
// A fails first — identity check makes it a no-op (C is live).
|
|
765
|
+
proxy.resolve(reqIdA, payload({ state: "missing" }));
|
|
766
|
+
await pA;
|
|
767
|
+
expect(_getActiveAppControlSession()?.app).toBe("com.example.c");
|
|
768
|
+
|
|
769
|
+
// C fails second — must roll back to confirmed (undefined), not
|
|
770
|
+
// to A's never-confirmed optimistic write.
|
|
771
|
+
proxy.resolve(reqIdC, payload({ state: "missing" }));
|
|
772
|
+
await pC;
|
|
773
|
+
|
|
774
|
+
expect(_getActiveAppControlSession()).toBeUndefined();
|
|
775
|
+
|
|
776
|
+
proxy.dispose();
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
test("late running confirmation from older overlapping start is preserved", async () => {
|
|
780
|
+
// Same-conversation overlapping starts where the older one's `running`
|
|
781
|
+
// response arrives AFTER a newer optimistic start has superseded it,
|
|
782
|
+
// and the newer one then fails. The host has actually confirmed A as
|
|
783
|
+
// running, so the lock must remain held for A rather than going
|
|
784
|
+
// undefined and desyncing from the host.
|
|
785
|
+
const proxy = new HostAppControlProxy("conv-1");
|
|
786
|
+
const ctrl = new AbortController();
|
|
787
|
+
|
|
788
|
+
// A is dispatched; do not resolve yet.
|
|
789
|
+
const pA = proxy.request(
|
|
790
|
+
"app_control_start",
|
|
791
|
+
{ tool: "start", app: "com.example.a" },
|
|
792
|
+
"conv-1",
|
|
793
|
+
ctrl.signal,
|
|
794
|
+
);
|
|
795
|
+
const reqIdA = (sentMessages[0] as Record<string, unknown>)
|
|
796
|
+
.requestId as string;
|
|
797
|
+
|
|
798
|
+
// C is dispatched, overwriting the optimistic active pointer to C.
|
|
799
|
+
sentMessages.length = 0;
|
|
800
|
+
const pC = proxy.request(
|
|
801
|
+
"app_control_start",
|
|
802
|
+
{ tool: "start", app: "com.example.c" },
|
|
803
|
+
"conv-1",
|
|
804
|
+
ctrl.signal,
|
|
805
|
+
);
|
|
806
|
+
const reqIdC = (sentMessages[0] as Record<string, unknown>)
|
|
807
|
+
.requestId as string;
|
|
808
|
+
expect(_getActiveAppControlSession()?.app).toBe("com.example.c");
|
|
809
|
+
|
|
810
|
+
// A finally returns running — by object identity active is no longer A,
|
|
811
|
+
// but the host has confirmed A so we must still record it as confirmed.
|
|
812
|
+
proxy.resolve(reqIdA, payload({ pngBase64: PNG_A }));
|
|
813
|
+
await pA;
|
|
814
|
+
// Active is still the newer optimistic C; nothing has rolled it back.
|
|
815
|
+
expect(_getActiveAppControlSession()?.app).toBe("com.example.c");
|
|
816
|
+
|
|
817
|
+
// C fails — rollback must restore active to A (the current confirmed),
|
|
818
|
+
// not to undefined (the snapshot prior at C's dispatch time).
|
|
819
|
+
proxy.resolve(reqIdC, payload({ state: "missing" }));
|
|
820
|
+
await pC;
|
|
821
|
+
|
|
822
|
+
const session = _getActiveAppControlSession();
|
|
823
|
+
expect(session?.conversationId).toBe("conv-1");
|
|
824
|
+
expect(session?.app).toBe("com.example.a");
|
|
825
|
+
|
|
826
|
+
proxy.dispose();
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
test("first-start failure releases the lock (no prior session to restore)", async () => {
|
|
830
|
+
const proxy = new HostAppControlProxy("conv-1");
|
|
831
|
+
const ctrl = new AbortController();
|
|
832
|
+
|
|
833
|
+
// No prior session; re-start the first time and get a non-running.
|
|
834
|
+
const p1 = proxy.request(
|
|
835
|
+
"app_control_start",
|
|
836
|
+
{ tool: "start", app: "com.example.editor" },
|
|
837
|
+
"conv-1",
|
|
838
|
+
ctrl.signal,
|
|
839
|
+
);
|
|
840
|
+
proxy.resolve(
|
|
841
|
+
(sentMessages[0] as Record<string, unknown>).requestId as string,
|
|
842
|
+
payload({ state: "missing" }),
|
|
843
|
+
);
|
|
844
|
+
await p1;
|
|
845
|
+
|
|
846
|
+
// Lock released so another conversation can acquire.
|
|
847
|
+
expect(_getActiveAppControlSession()).toBeUndefined();
|
|
848
|
+
|
|
849
|
+
proxy.dispose();
|
|
850
|
+
});
|
|
851
|
+
});
|
|
852
|
+
|
|
597
853
|
// -------------------------------------------------------------------------
|
|
598
854
|
// (d) dispose releases the lock
|
|
599
855
|
// -------------------------------------------------------------------------
|
|
@@ -805,8 +1061,8 @@ describe("HostAppControlProxy", () => {
|
|
|
805
1061
|
{ tool: "observe", app: "com.example.app" },
|
|
806
1062
|
"conv-1",
|
|
807
1063
|
ctrl.signal,
|
|
808
|
-
"actor-principal-1",
|
|
809
|
-
"client-A",
|
|
1064
|
+
"actor-principal-1", // sourceActorPrincipalId
|
|
1065
|
+
"client-A", // targetClientId
|
|
810
1066
|
);
|
|
811
1067
|
|
|
812
1068
|
expect(sentMessages).toHaveLength(1);
|
|
@@ -840,8 +1096,8 @@ describe("HostAppControlProxy", () => {
|
|
|
840
1096
|
{ tool: "observe", app: "com.example.app" },
|
|
841
1097
|
"conv-1",
|
|
842
1098
|
ctrl.signal,
|
|
843
|
-
"user-1",
|
|
844
|
-
"client-A",
|
|
1099
|
+
"user-1", // sourceActorPrincipalId
|
|
1100
|
+
"client-A", // targetClientId → hub resolves actorPrincipalId = "user-1"
|
|
845
1101
|
);
|
|
846
1102
|
|
|
847
1103
|
const sent = sentMessages[0] as Record<string, unknown>;
|
|
@@ -863,8 +1119,8 @@ describe("HostAppControlProxy", () => {
|
|
|
863
1119
|
{ tool: "start", app: "com.example.app" },
|
|
864
1120
|
"conv-1",
|
|
865
1121
|
ctrl.signal,
|
|
866
|
-
"user-1",
|
|
867
|
-
undefined,
|
|
1122
|
+
"user-1", // sourceActorPrincipalId
|
|
1123
|
+
undefined, // no targetClientId
|
|
868
1124
|
);
|
|
869
1125
|
|
|
870
1126
|
const sent = sentMessages[0] as Record<string, unknown>;
|
|
@@ -12,7 +12,7 @@ mock.module("../security/secure-keys.js", () => ({
|
|
|
12
12
|
getProviderKeyAsync: async (_provider: string) => mockProviderKey,
|
|
13
13
|
}));
|
|
14
14
|
|
|
15
|
-
mock.module("../providers/
|
|
15
|
+
mock.module("../providers/platform-proxy/context.js", () => ({
|
|
16
16
|
resolveManagedProxyContext: async () => ({
|
|
17
17
|
enabled: !!mockPlatformBaseUrl && !!mockAssistantApiKey,
|
|
18
18
|
platformBaseUrl: mockPlatformBaseUrl,
|