@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
|
@@ -15,6 +15,8 @@ type ConvRow = {
|
|
|
15
15
|
id: string;
|
|
16
16
|
source: string | null;
|
|
17
17
|
last_message_at: number | null;
|
|
18
|
+
fork_parent_conversation_id: string | null;
|
|
19
|
+
created_at: number;
|
|
18
20
|
};
|
|
19
21
|
type JobRow = {
|
|
20
22
|
type: string;
|
|
@@ -58,11 +60,26 @@ mock.module("../db-connection.js", () => ({
|
|
|
58
60
|
return { conversationId: convId };
|
|
59
61
|
});
|
|
60
62
|
}
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
// The "all retros" query (used to compute most-recent-per-source
|
|
64
|
+
// preservation) requests id + forkParentConversationId + createdAt
|
|
65
|
+
// with only the source + isNotNull(forkParent) predicate.
|
|
66
|
+
if (
|
|
67
|
+
colKeys.includes("forkParentConversationId") &&
|
|
68
|
+
colKeys.includes("createdAt")
|
|
69
|
+
) {
|
|
70
|
+
return mockConversations
|
|
71
|
+
.filter((c) => c.source === "memory-retrospective")
|
|
72
|
+
.filter((c) => c.fork_parent_conversation_id !== null)
|
|
73
|
+
.map((c) => ({
|
|
74
|
+
id: c.id,
|
|
75
|
+
forkParentConversationId: c.fork_parent_conversation_id,
|
|
76
|
+
createdAt: c.created_at,
|
|
77
|
+
}));
|
|
78
|
+
}
|
|
79
|
+
// Otherwise, this is the orphan-candidate query. The production
|
|
80
|
+
// predicate compares `forkParentConversationId` (the source ID
|
|
81
|
+
// encoded on the background conversation row) against the set
|
|
82
|
+
// of source IDs extracted from active jobs.
|
|
66
83
|
return mockConversations
|
|
67
84
|
.filter((c) => c.source === "memory-retrospective")
|
|
68
85
|
.filter(
|
|
@@ -70,7 +87,11 @@ mock.module("../db-connection.js", () => ({
|
|
|
70
87
|
c.last_message_at !== null &&
|
|
71
88
|
c.last_message_at < injectedNowMinusOrphanAgeMs,
|
|
72
89
|
)
|
|
73
|
-
.filter(
|
|
90
|
+
.filter(
|
|
91
|
+
(c) =>
|
|
92
|
+
c.fork_parent_conversation_id === null ||
|
|
93
|
+
!activeJobSourceConvIds.has(c.fork_parent_conversation_id),
|
|
94
|
+
)
|
|
74
95
|
.map((c) => ({ id: c.id }));
|
|
75
96
|
},
|
|
76
97
|
}),
|
|
@@ -79,7 +100,7 @@ mock.module("../db-connection.js", () => ({
|
|
|
79
100
|
}),
|
|
80
101
|
}));
|
|
81
102
|
|
|
82
|
-
let
|
|
103
|
+
let activeJobSourceConvIds = new Set<string>();
|
|
83
104
|
let injectedNowMinusOrphanAgeMs = 0;
|
|
84
105
|
|
|
85
106
|
mock.module("../conversation-crud.js", () => ({
|
|
@@ -94,7 +115,7 @@ import { sweepOrphanMemoryRetrospectiveConversations } from "../memory-retrospec
|
|
|
94
115
|
const ORPHAN_AGE_MS = 60 * 60 * 1000;
|
|
95
116
|
|
|
96
117
|
function rebuildActiveJobSet(): void {
|
|
97
|
-
|
|
118
|
+
activeJobSourceConvIds = new Set();
|
|
98
119
|
for (const j of mockJobs) {
|
|
99
120
|
if (
|
|
100
121
|
j.type !== "memory_retrospective" ||
|
|
@@ -105,7 +126,7 @@ function rebuildActiveJobSet(): void {
|
|
|
105
126
|
try {
|
|
106
127
|
const parsed = JSON.parse(j.payload) as { conversationId?: unknown };
|
|
107
128
|
if (typeof parsed.conversationId === "string") {
|
|
108
|
-
|
|
129
|
+
activeJobSourceConvIds.add(parsed.conversationId);
|
|
109
130
|
}
|
|
110
131
|
} catch {
|
|
111
132
|
// ignore
|
|
@@ -118,7 +139,7 @@ describe("sweepOrphanMemoryRetrospectiveConversations", () => {
|
|
|
118
139
|
mockConversations = [];
|
|
119
140
|
mockJobs = [];
|
|
120
141
|
deletedIds = [];
|
|
121
|
-
|
|
142
|
+
activeJobSourceConvIds = new Set();
|
|
122
143
|
injectedNowMinusOrphanAgeMs = 0;
|
|
123
144
|
});
|
|
124
145
|
|
|
@@ -127,7 +148,7 @@ describe("sweepOrphanMemoryRetrospectiveConversations", () => {
|
|
|
127
148
|
mockJobs = [];
|
|
128
149
|
});
|
|
129
150
|
|
|
130
|
-
test("sweeps an old memory-retrospective
|
|
151
|
+
test("sweeps an old memory-retrospective orphan that has been superseded by a newer retro for the same source", () => {
|
|
131
152
|
const now = Date.now();
|
|
132
153
|
injectedNowMinusOrphanAgeMs = now - ORPHAN_AGE_MS;
|
|
133
154
|
mockConversations = [
|
|
@@ -135,6 +156,16 @@ describe("sweepOrphanMemoryRetrospectiveConversations", () => {
|
|
|
135
156
|
id: "old-orphan",
|
|
136
157
|
source: "memory-retrospective",
|
|
137
158
|
last_message_at: now - 2 * ORPHAN_AGE_MS,
|
|
159
|
+
fork_parent_conversation_id: "source-A",
|
|
160
|
+
created_at: now - 3 * ORPHAN_AGE_MS,
|
|
161
|
+
},
|
|
162
|
+
// Newer successful retro for the same source — this one is preserved.
|
|
163
|
+
{
|
|
164
|
+
id: "newer-retro",
|
|
165
|
+
source: "memory-retrospective",
|
|
166
|
+
last_message_at: now - 90 * 60 * 1000,
|
|
167
|
+
fork_parent_conversation_id: "source-A",
|
|
168
|
+
created_at: now - 2 * ORPHAN_AGE_MS,
|
|
138
169
|
},
|
|
139
170
|
];
|
|
140
171
|
rebuildActiveJobSet();
|
|
@@ -153,6 +184,8 @@ describe("sweepOrphanMemoryRetrospectiveConversations", () => {
|
|
|
153
184
|
id: "fresh-bg",
|
|
154
185
|
source: "memory-retrospective",
|
|
155
186
|
last_message_at: now - 60_000,
|
|
187
|
+
fork_parent_conversation_id: "source-A",
|
|
188
|
+
created_at: now - 60_000,
|
|
156
189
|
},
|
|
157
190
|
];
|
|
158
191
|
rebuildActiveJobSet();
|
|
@@ -171,6 +204,8 @@ describe("sweepOrphanMemoryRetrospectiveConversations", () => {
|
|
|
171
204
|
id: "auto-analysis-old",
|
|
172
205
|
source: "auto-analysis",
|
|
173
206
|
last_message_at: now - 2 * ORPHAN_AGE_MS,
|
|
207
|
+
fork_parent_conversation_id: "source-A",
|
|
208
|
+
created_at: now - 2 * ORPHAN_AGE_MS,
|
|
174
209
|
},
|
|
175
210
|
];
|
|
176
211
|
rebuildActiveJobSet();
|
|
@@ -180,21 +215,91 @@ describe("sweepOrphanMemoryRetrospectiveConversations", () => {
|
|
|
180
215
|
expect(result.swept).toBe(0);
|
|
181
216
|
});
|
|
182
217
|
|
|
183
|
-
|
|
218
|
+
// Regression test for the previously-broken active-job guard. Before the
|
|
219
|
+
// fix, the predicate compared `conversations.id` (the BACKGROUND-conv id)
|
|
220
|
+
// to source-conv ids extracted from job payloads — two different identifier
|
|
221
|
+
// spaces — so the guard never matched and in-flight retros were swept.
|
|
222
|
+
test("does NOT sweep a background conversation whose SOURCE has an active job (different identifier spaces)", () => {
|
|
184
223
|
const now = Date.now();
|
|
185
224
|
injectedNowMinusOrphanAgeMs = now - ORPHAN_AGE_MS;
|
|
225
|
+
// The background conv has its own id, distinct from the source it forks
|
|
226
|
+
// from. The active job's payload references the SOURCE, not the
|
|
227
|
+
// background.
|
|
186
228
|
mockConversations = [
|
|
187
229
|
{
|
|
188
|
-
id: "
|
|
230
|
+
id: "background-conv-id",
|
|
189
231
|
source: "memory-retrospective",
|
|
190
232
|
last_message_at: now - 2 * ORPHAN_AGE_MS,
|
|
233
|
+
fork_parent_conversation_id: "source-conv-id",
|
|
234
|
+
created_at: now - 2 * ORPHAN_AGE_MS,
|
|
191
235
|
},
|
|
192
236
|
];
|
|
193
237
|
mockJobs = [
|
|
194
238
|
{
|
|
195
239
|
type: "memory_retrospective",
|
|
196
240
|
status: "pending",
|
|
197
|
-
payload: JSON.stringify({ conversationId: "
|
|
241
|
+
payload: JSON.stringify({ conversationId: "source-conv-id" }),
|
|
242
|
+
},
|
|
243
|
+
];
|
|
244
|
+
rebuildActiveJobSet();
|
|
245
|
+
|
|
246
|
+
const result = sweepOrphanMemoryRetrospectiveConversations(now);
|
|
247
|
+
|
|
248
|
+
expect(result.swept).toBe(0);
|
|
249
|
+
expect(deletedIds).toEqual([]);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
test("sweeps a superseded background conversation whose source has NO active job, even when another unrelated job is pending", () => {
|
|
253
|
+
const now = Date.now();
|
|
254
|
+
injectedNowMinusOrphanAgeMs = now - ORPHAN_AGE_MS;
|
|
255
|
+
mockConversations = [
|
|
256
|
+
{
|
|
257
|
+
id: "background-A",
|
|
258
|
+
source: "memory-retrospective",
|
|
259
|
+
last_message_at: now - 2 * ORPHAN_AGE_MS,
|
|
260
|
+
fork_parent_conversation_id: "source-A",
|
|
261
|
+
created_at: now - 3 * ORPHAN_AGE_MS,
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
id: "newer-A",
|
|
265
|
+
source: "memory-retrospective",
|
|
266
|
+
last_message_at: now - 90 * 60 * 1000,
|
|
267
|
+
fork_parent_conversation_id: "source-A",
|
|
268
|
+
created_at: now - 2 * ORPHAN_AGE_MS,
|
|
269
|
+
},
|
|
270
|
+
];
|
|
271
|
+
// Active job references a DIFFERENT source — neither retro above is
|
|
272
|
+
// protected by the active-job guard.
|
|
273
|
+
mockJobs = [
|
|
274
|
+
{
|
|
275
|
+
type: "memory_retrospective",
|
|
276
|
+
status: "pending",
|
|
277
|
+
payload: JSON.stringify({ conversationId: "source-B" }),
|
|
278
|
+
},
|
|
279
|
+
];
|
|
280
|
+
rebuildActiveJobSet();
|
|
281
|
+
|
|
282
|
+
const result = sweepOrphanMemoryRetrospectiveConversations(now);
|
|
283
|
+
|
|
284
|
+
expect(result.swept).toBe(1);
|
|
285
|
+
expect(deletedIds).toEqual(["background-A"]);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// Regression test for Devin's concern on PR #30331: the sweep used to
|
|
289
|
+
// delete every memory-retrospective conversation older than 1h, including
|
|
290
|
+
// the most-recent successful one per source. That broke
|
|
291
|
+
// `findMostRecentRetrospectiveFor` on the next run — the next retro had
|
|
292
|
+
// no dedup context and could re-save facts the prior pass already captured.
|
|
293
|
+
test("PRESERVES the most-recent retro per source even when older than the orphan cutoff", () => {
|
|
294
|
+
const now = Date.now();
|
|
295
|
+
injectedNowMinusOrphanAgeMs = now - ORPHAN_AGE_MS;
|
|
296
|
+
mockConversations = [
|
|
297
|
+
{
|
|
298
|
+
id: "successful-retro",
|
|
299
|
+
source: "memory-retrospective",
|
|
300
|
+
last_message_at: now - 2 * ORPHAN_AGE_MS,
|
|
301
|
+
fork_parent_conversation_id: "source-A",
|
|
302
|
+
created_at: now - 2 * ORPHAN_AGE_MS,
|
|
198
303
|
},
|
|
199
304
|
];
|
|
200
305
|
rebuildActiveJobSet();
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { stringifyMessageContent } from "../message-content.js";
|
|
4
|
+
|
|
5
|
+
describe("stringifyMessageContent", () => {
|
|
6
|
+
test("returns trimmed raw text for legacy plain-string rows", () => {
|
|
7
|
+
expect(stringifyMessageContent(" hello world ")).toBe("hello world");
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test("returns trimmed inner string when JSON parses to a string", () => {
|
|
11
|
+
expect(stringifyMessageContent(JSON.stringify(" inner "))).toBe("inner");
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("concatenates text blocks from a ContentBlock[] payload", () => {
|
|
15
|
+
const raw = JSON.stringify([
|
|
16
|
+
{ type: "text", text: "alpha" },
|
|
17
|
+
{ type: "tool_use", id: "x", name: "noop", input: {} },
|
|
18
|
+
{ type: "text", text: "beta" },
|
|
19
|
+
]);
|
|
20
|
+
expect(stringifyMessageContent(raw)).toBe("alpha\nbeta");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("falls back to raw trimmed text when JSON parses to a non-array object", () => {
|
|
24
|
+
const raw = ' {"type":"text","text":"hi"} ';
|
|
25
|
+
expect(stringifyMessageContent(raw)).toBe('{"type":"text","text":"hi"}');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("falls back to raw trimmed text when JSON parses to a number", () => {
|
|
29
|
+
expect(stringifyMessageContent(" 42 ")).toBe("42");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("returns trimmed raw text when JSON parsing fails", () => {
|
|
33
|
+
expect(stringifyMessageContent(" not json ")).toBe("not json");
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -106,22 +106,51 @@ export function listBookmarks(db: DrizzleDb): BookmarkSummary[] {
|
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
/**
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
109
|
+
* Discriminated result returned by {@link createBookmark}. `inserted`
|
|
110
|
+
* distinguishes a brand-new row from an idempotent return of an existing
|
|
111
|
+
* one, so callers can suppress side effects (e.g. `bookmark.created` SSE
|
|
112
|
+
* publishes) on duplicate POSTs.
|
|
113
|
+
*/
|
|
114
|
+
export type CreateBookmarkResult =
|
|
115
|
+
| { inserted: true; bookmark: BookmarkSummary }
|
|
116
|
+
| { inserted: false; bookmark: BookmarkSummary };
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Create a bookmark for the given message and return a discriminated
|
|
120
|
+
* result indicating whether a new row was actually inserted. Idempotent
|
|
121
|
+
* on the unique `message_id` index — if a bookmark already exists for
|
|
122
|
+
* `messageId`, the existing summary is returned with `inserted: false`.
|
|
123
|
+
*
|
|
124
|
+
* `conversationId` is derived from the message row rather than trusted from
|
|
125
|
+
* the caller, so a buggy or malicious caller cannot persist a bookmark
|
|
126
|
+
* whose `conversationId` disagrees with the message's actual conversation.
|
|
113
127
|
*/
|
|
114
128
|
export function createBookmark(
|
|
115
129
|
db: DrizzleDb,
|
|
116
|
-
params: { messageId: string
|
|
117
|
-
):
|
|
118
|
-
const { messageId
|
|
130
|
+
params: { messageId: string },
|
|
131
|
+
): CreateBookmarkResult {
|
|
132
|
+
const { messageId } = params;
|
|
133
|
+
const message = db
|
|
134
|
+
.select({ conversationId: messages.conversationId })
|
|
135
|
+
.from(messages)
|
|
136
|
+
.where(eq(messages.id, messageId))
|
|
137
|
+
.get();
|
|
138
|
+
if (!message) {
|
|
139
|
+
throw new Error(`Message ${messageId} not found`);
|
|
140
|
+
}
|
|
141
|
+
const conversationId = message.conversationId;
|
|
142
|
+
|
|
119
143
|
const existing = db
|
|
120
144
|
.select({ id: messageBookmarks.id })
|
|
121
145
|
.from(messageBookmarks)
|
|
122
146
|
.where(eq(messageBookmarks.messageId, messageId))
|
|
123
147
|
.get();
|
|
124
|
-
if (existing)
|
|
148
|
+
if (existing) {
|
|
149
|
+
return {
|
|
150
|
+
inserted: false,
|
|
151
|
+
bookmark: readBookmarkSummaryOrThrow(db, existing.id),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
125
154
|
|
|
126
155
|
const id = uuid();
|
|
127
156
|
try {
|
|
@@ -137,9 +166,12 @@ export function createBookmark(
|
|
|
137
166
|
.where(eq(messageBookmarks.messageId, messageId))
|
|
138
167
|
.get();
|
|
139
168
|
if (!winner) throw err;
|
|
140
|
-
return
|
|
169
|
+
return {
|
|
170
|
+
inserted: false,
|
|
171
|
+
bookmark: readBookmarkSummaryOrThrow(db, winner.id),
|
|
172
|
+
};
|
|
141
173
|
}
|
|
142
|
-
return readBookmarkSummaryOrThrow(db, id);
|
|
174
|
+
return { inserted: true, bookmark: readBookmarkSummaryOrThrow(db, id) };
|
|
143
175
|
}
|
|
144
176
|
|
|
145
177
|
function readBookmarkSummaryOrThrow(
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
import { readSlackMetadata } from "../../../messaging/providers/slack/message-metadata.js";
|
|
2
|
+
import {
|
|
3
|
+
parseExternalContentEnvelope,
|
|
4
|
+
wrapUntrustedContent,
|
|
5
|
+
} from "../../../security/untrusted-content.js";
|
|
1
6
|
import { AUTO_ANALYSIS_SOURCE } from "../../auto-analysis-guard.js";
|
|
2
7
|
import {
|
|
3
|
-
buildExcerpt,
|
|
4
8
|
buildFtsMatchQuery,
|
|
9
|
+
buildRecallEvidenceExcerpt,
|
|
5
10
|
} from "../../conversation-queries.js";
|
|
6
11
|
import { rawAll } from "../../raw-query.js";
|
|
7
12
|
import type { RecallSearchContext, RecallSearchResult } from "../types.js";
|
|
@@ -15,6 +20,7 @@ interface ConversationEvidenceRow {
|
|
|
15
20
|
role: string;
|
|
16
21
|
content: string;
|
|
17
22
|
created_at: number;
|
|
23
|
+
metadata: string | null;
|
|
18
24
|
title: string | null;
|
|
19
25
|
}
|
|
20
26
|
|
|
@@ -118,7 +124,7 @@ export async function searchConversationSource(
|
|
|
118
124
|
source: "conversations",
|
|
119
125
|
title: row.title?.trim() || "Untitled conversation",
|
|
120
126
|
locator: `${row.conversation_id}#${row.message_id}`,
|
|
121
|
-
excerpt:
|
|
127
|
+
excerpt: buildRecallExcerpt(row, trimmedQuery),
|
|
122
128
|
timestampMs: row.created_at,
|
|
123
129
|
score,
|
|
124
130
|
metadata: {
|
|
@@ -142,6 +148,7 @@ function searchWithFts(
|
|
|
142
148
|
m.role,
|
|
143
149
|
m.content,
|
|
144
150
|
m.created_at,
|
|
151
|
+
m.metadata,
|
|
145
152
|
c.title
|
|
146
153
|
FROM messages_fts
|
|
147
154
|
JOIN messages m ON m.id = messages_fts.message_id
|
|
@@ -175,6 +182,7 @@ function searchWithLike(
|
|
|
175
182
|
m.role,
|
|
176
183
|
m.content,
|
|
177
184
|
m.created_at,
|
|
185
|
+
m.metadata,
|
|
178
186
|
c.title
|
|
179
187
|
FROM messages m
|
|
180
188
|
JOIN conversations c ON c.id = m.conversation_id
|
|
@@ -194,6 +202,58 @@ function searchWithLike(
|
|
|
194
202
|
);
|
|
195
203
|
}
|
|
196
204
|
|
|
205
|
+
function buildRecallExcerpt(
|
|
206
|
+
row: ConversationEvidenceRow,
|
|
207
|
+
query: string,
|
|
208
|
+
): string {
|
|
209
|
+
const excerpt = buildRecallEvidenceExcerpt(row.content, query);
|
|
210
|
+
const slackMeta = parseSlackRecallMetadata(row.metadata);
|
|
211
|
+
if (
|
|
212
|
+
row.role !== "user" ||
|
|
213
|
+
!slackMeta ||
|
|
214
|
+
slackMeta.provenanceTrustClass === "guardian" ||
|
|
215
|
+
excerpt.length === 0 ||
|
|
216
|
+
parseExternalContentEnvelope(excerpt)
|
|
217
|
+
) {
|
|
218
|
+
return excerpt;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return wrapUntrustedContent(excerpt, {
|
|
222
|
+
source: "slack",
|
|
223
|
+
...(slackMeta.displayName ? { sourceDetail: slackMeta.displayName } : {}),
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function parseSlackRecallMetadata(rawMetadata: string | null): {
|
|
228
|
+
displayName?: string;
|
|
229
|
+
provenanceTrustClass?: string;
|
|
230
|
+
} | null {
|
|
231
|
+
if (!rawMetadata) return null;
|
|
232
|
+
|
|
233
|
+
let parsed: unknown;
|
|
234
|
+
try {
|
|
235
|
+
parsed = JSON.parse(rawMetadata);
|
|
236
|
+
} catch {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const metadata = parsed as Record<string, unknown>;
|
|
245
|
+
if (typeof metadata.slackMeta !== "string") return null;
|
|
246
|
+
const slackMeta = readSlackMetadata(metadata.slackMeta);
|
|
247
|
+
if (!slackMeta) return null;
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
...(slackMeta.displayName ? { displayName: slackMeta.displayName } : {}),
|
|
251
|
+
...(typeof metadata.provenanceTrustClass === "string"
|
|
252
|
+
? { provenanceTrustClass: metadata.provenanceTrustClass }
|
|
253
|
+
: {}),
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
197
257
|
function buildRecallFtsMatchQueries(query: string): string[] {
|
|
198
258
|
const queries: string[] = [];
|
|
199
259
|
const exact = buildFtsMatchQuery(query);
|
|
@@ -68,6 +68,9 @@ const SECRET_SEGMENT_NAMES = new Set([
|
|
|
68
68
|
const SECRET_TOKEN_PATTERN =
|
|
69
69
|
/(?:^|[-_.])(?:keys?|secrets?|tokens?)(?:[-_.]|$)/i;
|
|
70
70
|
|
|
71
|
+
const SECRET_TOKEN_CAMEL_CASE_PATTERN =
|
|
72
|
+
/(?<=[a-z0-9])(?:Keys?|Secrets?|Tokens?)(?=[A-Z]|[-_.]|$)/;
|
|
73
|
+
|
|
71
74
|
const QUERY_STOP_WORDS = new Set([
|
|
72
75
|
"a",
|
|
73
76
|
"about",
|
|
@@ -1185,6 +1188,7 @@ function shouldSkipSegmentName(name: string): boolean {
|
|
|
1185
1188
|
GENERATED_OR_DEPENDENCY_DIR_NAMES.has(lowerName) ||
|
|
1186
1189
|
lowerName.startsWith(".env") ||
|
|
1187
1190
|
SECRET_TOKEN_PATTERN.test(lowerName) ||
|
|
1191
|
+
SECRET_TOKEN_CAMEL_CASE_PATTERN.test(name) ||
|
|
1188
1192
|
lowerName.startsWith("credentials") ||
|
|
1189
1193
|
SECRET_SEGMENT_NAMES.has(lowerName)
|
|
1190
1194
|
);
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
like,
|
|
15
15
|
lt,
|
|
16
16
|
lte,
|
|
17
|
+
or,
|
|
17
18
|
sql,
|
|
18
19
|
} from "drizzle-orm";
|
|
19
20
|
import { v4 as uuid } from "uuid";
|
|
@@ -86,6 +87,7 @@ const subagentNotificationSchema = z.object({
|
|
|
86
87
|
status: z.enum(["running", "completed", "failed", "aborted"]),
|
|
87
88
|
error: z.string().optional(),
|
|
88
89
|
conversationId: z.string().optional(),
|
|
90
|
+
objective: z.string().optional(),
|
|
89
91
|
});
|
|
90
92
|
|
|
91
93
|
export const messageMetadataSchema = z
|
|
@@ -94,6 +96,16 @@ export const messageMetadataSchema = z
|
|
|
94
96
|
assistantMessageChannel: channelIdSchema.optional(),
|
|
95
97
|
userMessageInterface: interfaceIdSchema.optional(),
|
|
96
98
|
assistantMessageInterface: interfaceIdSchema.optional(),
|
|
99
|
+
/**
|
|
100
|
+
* Optional client-side metadata bag attached to user messages by HTTP
|
|
101
|
+
* header middleware (reads `x-vellum-browser-family`,
|
|
102
|
+
* `x-vellum-browser-version`, `x-vellum-client-os`,
|
|
103
|
+
* `x-vellum-interface-version`). Forwarded verbatim onto
|
|
104
|
+
* `TurnTelemetryEvent.client` for downstream analytics. Kept as a
|
|
105
|
+
* permissive `record` so adding a new client field doesn't require a
|
|
106
|
+
* migration -- dbt can unpack later via JSON_VALUE.
|
|
107
|
+
*/
|
|
108
|
+
client: z.record(z.string(), z.unknown()).optional(),
|
|
97
109
|
subagentNotification: subagentNotificationSchema.optional(),
|
|
98
110
|
/**
|
|
99
111
|
* Trust class of the actor at the time this message was persisted.
|
|
@@ -727,9 +739,14 @@ export function forkConversation(params: {
|
|
|
727
739
|
|
|
728
740
|
// Carry the parent's per-conversation memory state into the child so the
|
|
729
741
|
// forked thread resumes with the same activation/injection log and
|
|
730
|
-
// in-context tracker the parent had at fork time.
|
|
731
|
-
|
|
732
|
-
|
|
742
|
+
// in-context tracker the parent had at fork time. Only valid for
|
|
743
|
+
// full-history forks: a truncated fork would inherit activation/tracker
|
|
744
|
+
// entries for turns the child does not actually contain.
|
|
745
|
+
const isFullHistoryFork = copyBoundaryIndex === sourceMessages.length - 1;
|
|
746
|
+
if (isFullHistoryFork) {
|
|
747
|
+
forkActivationState(db, sourceConversation.id, fc.id);
|
|
748
|
+
forkGraphMemoryState(sourceConversation.id, fc.id);
|
|
749
|
+
}
|
|
733
750
|
forkRetrospectiveState({
|
|
734
751
|
database: db,
|
|
735
752
|
sourceConversationId: sourceConversation.id,
|
|
@@ -1040,22 +1057,23 @@ export function getMessages(conversationId: string): MessageRow[] {
|
|
|
1040
1057
|
}
|
|
1041
1058
|
|
|
1042
1059
|
/**
|
|
1043
|
-
* Return raw `metadata` strings for messages whose metadata
|
|
1044
|
-
*
|
|
1045
|
-
*
|
|
1046
|
-
*
|
|
1047
|
-
*
|
|
1048
|
-
*
|
|
1049
|
-
*
|
|
1050
|
-
*
|
|
1051
|
-
*
|
|
1052
|
-
*
|
|
1053
|
-
*
|
|
1060
|
+
* Return raw `metadata` strings for messages whose metadata looks like it may
|
|
1061
|
+
* contain Slack metadata, capped at `limit` and skipping the first `offset`
|
|
1062
|
+
* matches. Pushes `LIKE` + `LIMIT`/`OFFSET` into SQL so warm Slack DM
|
|
1063
|
+
* conversations don't require a full-table scan on the webhook critical path.
|
|
1064
|
+
* The substring match is an indexable prefilter only — callers must parse and
|
|
1065
|
+
* validate each returned string against the Slack metadata schema, because a
|
|
1066
|
+
* malformed row (partial write, legacy format, unrelated key accidentally
|
|
1067
|
+
* containing the literal) can still slip through the substring match. Callers
|
|
1068
|
+
* that need a fixed number of *valid* rows should iterate with increasing
|
|
1069
|
+
* offsets until the target is reached (capped at a reasonable maximum to bound
|
|
1070
|
+
* scan cost).
|
|
1054
1071
|
*/
|
|
1055
1072
|
export function selectSlackMetaCandidateMetadata(
|
|
1056
1073
|
conversationId: string,
|
|
1057
1074
|
limit: number,
|
|
1058
1075
|
offset = 0,
|
|
1076
|
+
opts?: { includeFlatLegacy?: boolean },
|
|
1059
1077
|
): string[] {
|
|
1060
1078
|
const db = getDb();
|
|
1061
1079
|
const rows = db
|
|
@@ -1064,7 +1082,12 @@ export function selectSlackMetaCandidateMetadata(
|
|
|
1064
1082
|
.where(
|
|
1065
1083
|
and(
|
|
1066
1084
|
eq(messages.conversationId, conversationId),
|
|
1067
|
-
|
|
1085
|
+
opts?.includeFlatLegacy
|
|
1086
|
+
? or(
|
|
1087
|
+
like(messages.metadata, '%"slackMeta"%'),
|
|
1088
|
+
like(messages.metadata, '%"source":"slack"%'),
|
|
1089
|
+
)
|
|
1090
|
+
: like(messages.metadata, '%"slackMeta"%'),
|
|
1068
1091
|
),
|
|
1069
1092
|
)
|
|
1070
1093
|
.orderBy(asc(messages.createdAt))
|
|
@@ -1110,13 +1133,23 @@ export function countMessagesAfter(
|
|
|
1110
1133
|
.where(eq(messages.id, afterMessageId))
|
|
1111
1134
|
.get();
|
|
1112
1135
|
if (!ref) return 0;
|
|
1136
|
+
// Tie-breaker on `messages.id` so rows that share a millisecond timestamp
|
|
1137
|
+
// with the reference are not permanently skipped. Mirrors the
|
|
1138
|
+
// `(createdAt, id)` cursor pattern used by the backfill job-handler and
|
|
1139
|
+
// turn-events-store.
|
|
1113
1140
|
const row = db
|
|
1114
1141
|
.select({ c: count() })
|
|
1115
1142
|
.from(messages)
|
|
1116
1143
|
.where(
|
|
1117
1144
|
and(
|
|
1118
1145
|
eq(messages.conversationId, conversationId),
|
|
1119
|
-
|
|
1146
|
+
or(
|
|
1147
|
+
gt(messages.createdAt, ref.createdAt),
|
|
1148
|
+
and(
|
|
1149
|
+
eq(messages.createdAt, ref.createdAt),
|
|
1150
|
+
gt(messages.id, afterMessageId),
|
|
1151
|
+
),
|
|
1152
|
+
),
|
|
1120
1153
|
),
|
|
1121
1154
|
)
|
|
1122
1155
|
.get();
|
|
@@ -1136,11 +1169,14 @@ export function getMessagesAfter(
|
|
|
1136
1169
|
): MessageRow[] {
|
|
1137
1170
|
const db = getDb();
|
|
1138
1171
|
if (afterMessageId === null || afterMessageId === "") {
|
|
1172
|
+
// Secondary `asc(messages.id)` matches the non-null path's cursor
|
|
1173
|
+
// ordering, so callers tracking `cutoffMessageId` across runs see a
|
|
1174
|
+
// consistent ordering when multiple rows share a millisecond timestamp.
|
|
1139
1175
|
return db
|
|
1140
1176
|
.select()
|
|
1141
1177
|
.from(messages)
|
|
1142
1178
|
.where(eq(messages.conversationId, conversationId))
|
|
1143
|
-
.orderBy(asc(messages.createdAt))
|
|
1179
|
+
.orderBy(asc(messages.createdAt), asc(messages.id))
|
|
1144
1180
|
.all()
|
|
1145
1181
|
.map(parseMessage);
|
|
1146
1182
|
}
|
|
@@ -1150,16 +1186,24 @@ export function getMessagesAfter(
|
|
|
1150
1186
|
.where(eq(messages.id, afterMessageId))
|
|
1151
1187
|
.get();
|
|
1152
1188
|
if (!ref) return [];
|
|
1189
|
+
// Same `(createdAt, id)` cursor as `countMessagesAfter` — rows sharing
|
|
1190
|
+
// the reference's millisecond timestamp would otherwise be skipped.
|
|
1153
1191
|
return db
|
|
1154
1192
|
.select()
|
|
1155
1193
|
.from(messages)
|
|
1156
1194
|
.where(
|
|
1157
1195
|
and(
|
|
1158
1196
|
eq(messages.conversationId, conversationId),
|
|
1159
|
-
|
|
1197
|
+
or(
|
|
1198
|
+
gt(messages.createdAt, ref.createdAt),
|
|
1199
|
+
and(
|
|
1200
|
+
eq(messages.createdAt, ref.createdAt),
|
|
1201
|
+
gt(messages.id, afterMessageId),
|
|
1202
|
+
),
|
|
1203
|
+
),
|
|
1160
1204
|
),
|
|
1161
1205
|
)
|
|
1162
|
-
.orderBy(asc(messages.createdAt))
|
|
1206
|
+
.orderBy(asc(messages.createdAt), asc(messages.id))
|
|
1163
1207
|
.all()
|
|
1164
1208
|
.map(parseMessage);
|
|
1165
1209
|
}
|