@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
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
* verification, guardian action answers, approval interception, and
|
|
5
5
|
* invite token redemption.
|
|
6
6
|
*/
|
|
7
|
+
import {
|
|
8
|
+
attachmentsToContentBlocks,
|
|
9
|
+
type MessageAttachmentInput,
|
|
10
|
+
} from "../../agent/attachments.js";
|
|
7
11
|
import { getChannelPermissionProfile } from "../../channels/permission-profiles.js";
|
|
8
12
|
import {
|
|
9
13
|
CHANNEL_IDS,
|
|
@@ -20,7 +24,12 @@ import { classifyDiskPressureTurnPolicy } from "../../daemon/disk-pressure-polic
|
|
|
20
24
|
import { processMessage } from "../../daemon/process-message.js";
|
|
21
25
|
import type { TrustContext } from "../../daemon/trust-context.js";
|
|
22
26
|
import { HeartbeatService } from "../../heartbeat/heartbeat-service.js";
|
|
23
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
attachInlineAttachmentToMessage,
|
|
29
|
+
AttachmentUploadError,
|
|
30
|
+
getAttachmentsByIds,
|
|
31
|
+
validateAttachmentUpload,
|
|
32
|
+
} from "../../memory/attachments-store.js";
|
|
24
33
|
import {
|
|
25
34
|
recordConversationSeenSignal,
|
|
26
35
|
type SignalType,
|
|
@@ -30,6 +39,7 @@ import {
|
|
|
30
39
|
getMessageById,
|
|
31
40
|
getMessages,
|
|
32
41
|
selectSlackMetaCandidateMetadata,
|
|
42
|
+
updateMessageContent,
|
|
33
43
|
updateMessageMetadata,
|
|
34
44
|
} from "../../memory/conversation-crud.js";
|
|
35
45
|
import {
|
|
@@ -45,18 +55,21 @@ import {
|
|
|
45
55
|
import { markProcessed } from "../../memory/delivery-status.js";
|
|
46
56
|
import { upsertBinding } from "../../memory/external-conversation-store.js";
|
|
47
57
|
import type { Message as ProviderMessage } from "../../messaging/provider-types.js";
|
|
58
|
+
import { withSlackBotToken } from "../../messaging/providers/slack/adapter.js";
|
|
48
59
|
import {
|
|
49
60
|
backfillDm,
|
|
50
61
|
backfillThreadWindowPage,
|
|
51
62
|
type SlackBackfillWindowPage,
|
|
52
63
|
} from "../../messaging/providers/slack/backfill.js";
|
|
64
|
+
import { downloadSlackFile } from "../../messaging/providers/slack/download.js";
|
|
53
65
|
import {
|
|
54
66
|
mergeSlackMetadata,
|
|
55
|
-
|
|
67
|
+
readSlackMetadataFromMessageMetadata,
|
|
56
68
|
type SlackFileMetadata,
|
|
57
69
|
type SlackMessageMetadata,
|
|
58
70
|
writeSlackMetadata,
|
|
59
71
|
} from "../../messaging/providers/slack/message-metadata.js";
|
|
72
|
+
import type { ContentBlock } from "../../providers/types.js";
|
|
60
73
|
import { wrapUntrustedContent } from "../../security/untrusted-content.js";
|
|
61
74
|
import { canonicalizeInboundIdentity } from "../../util/canonicalize-identity.js";
|
|
62
75
|
import { getLogger } from "../../util/logger.js";
|
|
@@ -454,6 +467,12 @@ export async function handleChannelInbound({
|
|
|
454
467
|
typeof sourceMetadata?.messageId === "string"
|
|
455
468
|
? sourceMetadata.messageId
|
|
456
469
|
: undefined;
|
|
470
|
+
const slackThreadTs =
|
|
471
|
+
sourceChannel === "slack" &&
|
|
472
|
+
typeof sourceMetadata?.threadId === "string" &&
|
|
473
|
+
sourceMetadata.threadId.trim().length > 0
|
|
474
|
+
? sourceMetadata.threadId.trim()
|
|
475
|
+
: undefined;
|
|
457
476
|
|
|
458
477
|
if (isEdit && !sourceMessageId) {
|
|
459
478
|
throw new BadRequestError("sourceMetadata.messageId is required for edits");
|
|
@@ -466,6 +485,7 @@ export async function handleChannelInbound({
|
|
|
466
485
|
conversationExternalId,
|
|
467
486
|
externalMessageId,
|
|
468
487
|
sourceMessageId,
|
|
488
|
+
sourceThreadId: slackThreadTs,
|
|
469
489
|
canonicalAssistantId,
|
|
470
490
|
assistantId,
|
|
471
491
|
content,
|
|
@@ -478,7 +498,11 @@ export async function handleChannelInbound({
|
|
|
478
498
|
sourceChannel,
|
|
479
499
|
conversationExternalId,
|
|
480
500
|
externalMessageId,
|
|
481
|
-
{
|
|
501
|
+
{
|
|
502
|
+
sourceMessageId,
|
|
503
|
+
assistantId: canonicalAssistantId,
|
|
504
|
+
sourceThreadId: slackThreadTs,
|
|
505
|
+
},
|
|
482
506
|
);
|
|
483
507
|
|
|
484
508
|
const replyCallbackUrl = body.replyCallbackUrl;
|
|
@@ -523,6 +547,7 @@ export async function handleChannelInbound({
|
|
|
523
547
|
conversationId: result.conversationId,
|
|
524
548
|
sourceChannel,
|
|
525
549
|
externalChatId: conversationExternalId,
|
|
550
|
+
externalThreadId: slackThreadTs ?? null,
|
|
526
551
|
externalUserId: canonicalSenderId ?? rawSenderId ?? null,
|
|
527
552
|
displayName: body.actorDisplayName ?? null,
|
|
528
553
|
username: body.actorUsername ?? null,
|
|
@@ -1018,11 +1043,6 @@ export async function handleChannelInbound({
|
|
|
1018
1043
|
// this into a `slackMeta` sub-object in the row's metadata column so
|
|
1019
1044
|
// the chronological renderer can reconstruct thread structure without
|
|
1020
1045
|
// re-fetching from Slack.
|
|
1021
|
-
const slackThreadTs =
|
|
1022
|
-
sourceChannel === "slack" &&
|
|
1023
|
-
typeof sourceMetadata?.threadId === "string"
|
|
1024
|
-
? sourceMetadata.threadId
|
|
1025
|
-
: undefined;
|
|
1026
1046
|
const slackInbound =
|
|
1027
1047
|
sourceChannel === "slack"
|
|
1028
1048
|
? {
|
|
@@ -1034,6 +1054,9 @@ export async function handleChannelInbound({
|
|
|
1034
1054
|
displayName: body.actorDisplayName ?? body.actorUsername!,
|
|
1035
1055
|
}
|
|
1036
1056
|
: {}),
|
|
1057
|
+
...(trustCtx.requesterExternalUserId
|
|
1058
|
+
? { actorExternalUserId: trustCtx.requesterExternalUserId }
|
|
1059
|
+
: {}),
|
|
1037
1060
|
}
|
|
1038
1061
|
: undefined;
|
|
1039
1062
|
|
|
@@ -1048,6 +1071,8 @@ export async function handleChannelInbound({
|
|
|
1048
1071
|
sourceMetadata.account.length > 0
|
|
1049
1072
|
? sourceMetadata.account
|
|
1050
1073
|
: undefined;
|
|
1074
|
+
const slackBotMentioned =
|
|
1075
|
+
sourceChannel === "slack" && sourceMetadata?.slackBotMentioned === true;
|
|
1051
1076
|
|
|
1052
1077
|
// ── DM cold-start backfill ──
|
|
1053
1078
|
// First time a Slack DM lands in a conversation that has fewer than
|
|
@@ -1071,6 +1096,7 @@ export async function handleChannelInbound({
|
|
|
1071
1096
|
conversationId: result.conversationId,
|
|
1072
1097
|
channelId: conversationExternalId,
|
|
1073
1098
|
account: slackAccount,
|
|
1099
|
+
guardianExternalUserId: trustCtx.guardianExternalUserId,
|
|
1074
1100
|
latestTs: boundingTs,
|
|
1075
1101
|
});
|
|
1076
1102
|
}
|
|
@@ -1093,6 +1119,7 @@ export async function handleChannelInbound({
|
|
|
1093
1119
|
threadTs: slackThreadTs,
|
|
1094
1120
|
excludeChannelTs: slackInbound?.channelTs,
|
|
1095
1121
|
account: slackAccount,
|
|
1122
|
+
guardianExternalUserId: trustCtx.guardianExternalUserId,
|
|
1096
1123
|
});
|
|
1097
1124
|
const lateJoinNotice = buildSlackLateJoinNotice(backfillResult);
|
|
1098
1125
|
if (lateJoinNotice) slackRuntimeContextNotice = lateJoinNotice;
|
|
@@ -1103,10 +1130,14 @@ export async function handleChannelInbound({
|
|
|
1103
1130
|
const contentForProcessing =
|
|
1104
1131
|
trustCtx.trustClass !== "guardian"
|
|
1105
1132
|
? wrapUntrustedContent(trimmedContent, {
|
|
1106
|
-
source: "webhook",
|
|
1133
|
+
source: sourceChannel === "slack" ? "slack" : "webhook",
|
|
1107
1134
|
sourceDetail: trustCtx.requesterIdentifier,
|
|
1108
1135
|
})
|
|
1109
1136
|
: trimmedContent;
|
|
1137
|
+
const displayContentForProcessing =
|
|
1138
|
+
sourceChannel === "slack" && trustCtx.trustClass !== "guardian"
|
|
1139
|
+
? trimmedContent
|
|
1140
|
+
: undefined;
|
|
1110
1141
|
|
|
1111
1142
|
// Fire-and-forget: process the message and deliver the reply in the background.
|
|
1112
1143
|
// The HTTP response returns immediately so the gateway webhook is not blocked.
|
|
@@ -1117,6 +1148,7 @@ export async function handleChannelInbound({
|
|
|
1117
1148
|
conversationId: result.conversationId,
|
|
1118
1149
|
eventId: result.eventId,
|
|
1119
1150
|
content: contentForProcessing,
|
|
1151
|
+
displayContent: displayContentForProcessing,
|
|
1120
1152
|
attachmentIds: hasAttachments ? attachmentIds : undefined,
|
|
1121
1153
|
sourceChannel,
|
|
1122
1154
|
sourceInterface,
|
|
@@ -1131,6 +1163,7 @@ export async function handleChannelInbound({
|
|
|
1131
1163
|
assistantId: canonicalAssistantId,
|
|
1132
1164
|
approvalCopyGenerator,
|
|
1133
1165
|
chatType: sourceChatType,
|
|
1166
|
+
slackBotMentioned,
|
|
1134
1167
|
slackInbound,
|
|
1135
1168
|
});
|
|
1136
1169
|
}
|
|
@@ -1330,25 +1363,6 @@ function countSlackMetaMessages(conversationId: string): number {
|
|
|
1330
1363
|
return count;
|
|
1331
1364
|
}
|
|
1332
1365
|
|
|
1333
|
-
function readSlackMetadataFromMessageMetadata(
|
|
1334
|
-
metadata: string | null | undefined,
|
|
1335
|
-
): SlackMessageMetadata | null {
|
|
1336
|
-
if (!metadata) return null;
|
|
1337
|
-
let parent: Record<string, unknown> | null = null;
|
|
1338
|
-
try {
|
|
1339
|
-
const parsed = JSON.parse(metadata) as unknown;
|
|
1340
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
1341
|
-
parent = parsed as Record<string, unknown>;
|
|
1342
|
-
}
|
|
1343
|
-
} catch {
|
|
1344
|
-
return null;
|
|
1345
|
-
}
|
|
1346
|
-
if (!parent) return null;
|
|
1347
|
-
const raw = parent.slackMeta;
|
|
1348
|
-
if (typeof raw !== "string") return null;
|
|
1349
|
-
return readSlackMetadata(raw);
|
|
1350
|
-
}
|
|
1351
|
-
|
|
1352
1366
|
/**
|
|
1353
1367
|
* Build the set of `slackMeta.channelTs` values already stored on a
|
|
1354
1368
|
* conversation. Used by both DM cold-start backfill and thread gap/delta
|
|
@@ -1433,11 +1447,10 @@ function readStoredSlackThreadState(
|
|
|
1433
1447
|
* `slackMeta` envelope.
|
|
1434
1448
|
*
|
|
1435
1449
|
* Shared insertion point for any path that hydrates Slack history lazily
|
|
1436
|
-
* (DM cold-start backfill, thread gap/delta backfill, etc.).
|
|
1437
|
-
*
|
|
1438
|
-
*
|
|
1439
|
-
*
|
|
1440
|
-
* the assistant treat its own outputs as new user input on later turns.
|
|
1450
|
+
* (DM cold-start backfill, thread gap/delta backfill, etc.). Backfilled Slack
|
|
1451
|
+
* rows are always persisted as `user` history: `assistant` rows are reserved
|
|
1452
|
+
* for messages produced by the local assistant loop, not third-party channel
|
|
1453
|
+
* replay.
|
|
1441
1454
|
* Caller is responsible for dedup checks before invoking; this helper
|
|
1442
1455
|
* performs no idempotency check itself.
|
|
1443
1456
|
*/
|
|
@@ -1445,9 +1458,21 @@ async function persistBackfilledSlackMessage(params: {
|
|
|
1445
1458
|
conversationId: string;
|
|
1446
1459
|
channelId: string;
|
|
1447
1460
|
message: ProviderMessage;
|
|
1461
|
+
account?: string;
|
|
1462
|
+
guardianExternalUserId?: string;
|
|
1448
1463
|
}): Promise<void> {
|
|
1449
1464
|
const { message } = params;
|
|
1450
|
-
const
|
|
1465
|
+
const slackFilesWithUrls = readSlackFilesWithUrlsFromProviderMetadata(
|
|
1466
|
+
message.metadata,
|
|
1467
|
+
);
|
|
1468
|
+
// Persisted shape strips the transient URL fields; only `{ id, name,
|
|
1469
|
+
// mimetype }` is allowed by `slackFileMetadataSchema`.
|
|
1470
|
+
const slackFiles: SlackFileMetadata[] = slackFilesWithUrls.map((f) => ({
|
|
1471
|
+
...(f.id ? { id: f.id } : {}),
|
|
1472
|
+
name: f.name,
|
|
1473
|
+
...(f.mimetype ? { mimetype: f.mimetype } : {}),
|
|
1474
|
+
}));
|
|
1475
|
+
const actorExternalUserId = message.sender?.id?.trim();
|
|
1451
1476
|
const slackMeta: SlackMessageMetadata = {
|
|
1452
1477
|
source: "slack",
|
|
1453
1478
|
channelId: params.channelId,
|
|
@@ -1455,20 +1480,163 @@ async function persistBackfilledSlackMessage(params: {
|
|
|
1455
1480
|
eventKind: "message",
|
|
1456
1481
|
...(message.threadId ? { threadTs: message.threadId } : {}),
|
|
1457
1482
|
...(message.sender?.name ? { displayName: message.sender.name } : {}),
|
|
1483
|
+
...(actorExternalUserId ? { actorExternalUserId } : {}),
|
|
1458
1484
|
...(slackFiles.length > 0 ? { slackFiles } : {}),
|
|
1459
1485
|
};
|
|
1460
|
-
|
|
1461
|
-
|
|
1486
|
+
|
|
1487
|
+
const isGuardian = isBackfilledSlackGuardianMessage(
|
|
1488
|
+
message,
|
|
1489
|
+
params.guardianExternalUserId,
|
|
1490
|
+
);
|
|
1491
|
+
const role = "user";
|
|
1492
|
+
|
|
1493
|
+
const rawText = message.text ?? "";
|
|
1494
|
+
|
|
1495
|
+
const persisted = await addMessage(params.conversationId, role, rawText, {
|
|
1462
1496
|
slackMeta: writeSlackMetadata(slackMeta),
|
|
1497
|
+
provenanceTrustClass: isGuardian ? "guardian" : "unknown",
|
|
1498
|
+
provenanceSourceChannel: "slack",
|
|
1499
|
+
...(params.guardianExternalUserId
|
|
1500
|
+
? { provenanceGuardianExternalUserId: params.guardianExternalUserId }
|
|
1501
|
+
: {}),
|
|
1502
|
+
...(actorExternalUserId
|
|
1503
|
+
? { provenanceRequesterIdentifier: actorExternalUserId }
|
|
1504
|
+
: {}),
|
|
1463
1505
|
});
|
|
1506
|
+
|
|
1507
|
+
// Hydrate image attachments inline, then rewrite the saved row to include
|
|
1508
|
+
// `type: "image"` content blocks. Slack context assembly reloads from
|
|
1509
|
+
// `messages.content`; the attachment link alone is not part of the model
|
|
1510
|
+
// transcript. Non-image files keep the marker produced by the Slack renderer.
|
|
1511
|
+
const imageFiles = slackFilesWithUrls.filter(
|
|
1512
|
+
(f) =>
|
|
1513
|
+
(f.urlPrivateDownload || f.urlPrivate) &&
|
|
1514
|
+
typeof f.mimetype === "string" &&
|
|
1515
|
+
f.mimetype.startsWith("image/"),
|
|
1516
|
+
);
|
|
1517
|
+
if (imageFiles.length === 0) return;
|
|
1518
|
+
|
|
1519
|
+
const hydratedAttachments = await withSlackBotToken(
|
|
1520
|
+
params.account,
|
|
1521
|
+
async (token) => {
|
|
1522
|
+
const attachments: MessageAttachmentInput[] = [];
|
|
1523
|
+
|
|
1524
|
+
for (let i = 0; i < imageFiles.length; i++) {
|
|
1525
|
+
const file = imageFiles[i];
|
|
1526
|
+
try {
|
|
1527
|
+
const downloaded = await downloadSlackFile(file, token);
|
|
1528
|
+
if (!downloaded) continue;
|
|
1529
|
+
const validation = validateAttachmentUpload(
|
|
1530
|
+
downloaded.filename,
|
|
1531
|
+
downloaded.mimeType,
|
|
1532
|
+
);
|
|
1533
|
+
if (!validation.ok) {
|
|
1534
|
+
log.warn(
|
|
1535
|
+
{
|
|
1536
|
+
filename: downloaded.filename,
|
|
1537
|
+
mimeType: downloaded.mimeType,
|
|
1538
|
+
error: validation.error,
|
|
1539
|
+
channelTs: message.id,
|
|
1540
|
+
},
|
|
1541
|
+
"Skipping backfilled Slack image: validation failed",
|
|
1542
|
+
);
|
|
1543
|
+
continue;
|
|
1544
|
+
}
|
|
1545
|
+
attachInlineAttachmentToMessage(
|
|
1546
|
+
persisted.id,
|
|
1547
|
+
i,
|
|
1548
|
+
downloaded.filename,
|
|
1549
|
+
downloaded.mimeType,
|
|
1550
|
+
downloaded.data,
|
|
1551
|
+
);
|
|
1552
|
+
attachments.push({
|
|
1553
|
+
filename: downloaded.filename,
|
|
1554
|
+
mimeType: downloaded.mimeType,
|
|
1555
|
+
data: downloaded.data,
|
|
1556
|
+
});
|
|
1557
|
+
} catch (err) {
|
|
1558
|
+
if (err instanceof AttachmentUploadError) {
|
|
1559
|
+
log.warn(
|
|
1560
|
+
{
|
|
1561
|
+
filename: file.name,
|
|
1562
|
+
error: err.message,
|
|
1563
|
+
channelTs: message.id,
|
|
1564
|
+
},
|
|
1565
|
+
"Skipping backfilled Slack image: upload error",
|
|
1566
|
+
);
|
|
1567
|
+
continue;
|
|
1568
|
+
}
|
|
1569
|
+
log.warn(
|
|
1570
|
+
{ err, fileId: file.id, name: file.name, channelTs: message.id },
|
|
1571
|
+
"Failed to hydrate backfilled Slack image; proceeding without it",
|
|
1572
|
+
);
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
return attachments;
|
|
1577
|
+
},
|
|
1578
|
+
);
|
|
1579
|
+
if (hydratedAttachments === null) {
|
|
1580
|
+
log.debug(
|
|
1581
|
+
{ conversationId: params.conversationId, channelTs: message.id },
|
|
1582
|
+
"No Slack token available for backfill image hydration; skipping",
|
|
1583
|
+
);
|
|
1584
|
+
return;
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
if (hydratedAttachments.length > 0) {
|
|
1588
|
+
updateMessageContent(
|
|
1589
|
+
persisted.id,
|
|
1590
|
+
JSON.stringify(
|
|
1591
|
+
buildBackfilledSlackContentBlocks(rawText, hydratedAttachments),
|
|
1592
|
+
),
|
|
1593
|
+
);
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
function buildBackfilledSlackContentBlocks(
|
|
1598
|
+
text: string,
|
|
1599
|
+
attachments: MessageAttachmentInput[],
|
|
1600
|
+
): ContentBlock[] {
|
|
1601
|
+
const blocks: ContentBlock[] = [];
|
|
1602
|
+
if (text.trim().length > 0) {
|
|
1603
|
+
blocks.push({ type: "text", text });
|
|
1604
|
+
}
|
|
1605
|
+
blocks.push(...attachmentsToContentBlocks(attachments));
|
|
1606
|
+
return blocks;
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
function isBackfilledSlackGuardianMessage(
|
|
1610
|
+
message: ProviderMessage,
|
|
1611
|
+
guardianExternalUserId: string | undefined,
|
|
1612
|
+
): boolean {
|
|
1613
|
+
const rawSenderId = message.sender?.id?.trim();
|
|
1614
|
+
if (!rawSenderId || !guardianExternalUserId) return false;
|
|
1615
|
+
const canonicalSender =
|
|
1616
|
+
canonicalizeInboundIdentity("slack", rawSenderId) ?? rawSenderId;
|
|
1617
|
+
const canonicalGuardian =
|
|
1618
|
+
canonicalizeInboundIdentity("slack", guardianExternalUserId) ??
|
|
1619
|
+
guardianExternalUserId.trim();
|
|
1620
|
+
return canonicalSender === canonicalGuardian;
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
/**
|
|
1624
|
+
* Transient view of `slackFiles` that preserves the download URLs added by
|
|
1625
|
+
* `mapSlackFiles` on the in-flight `ProviderMessage`. These URLs never reach
|
|
1626
|
+
* persisted storage — see `slackFileMetadataSchema`. The backfill image
|
|
1627
|
+
* hydration path is the only consumer; URLs are absent from persisted rows.
|
|
1628
|
+
*/
|
|
1629
|
+
interface SlackFileWithUrls extends SlackFileMetadata {
|
|
1630
|
+
urlPrivateDownload?: string;
|
|
1631
|
+
urlPrivate?: string;
|
|
1464
1632
|
}
|
|
1465
1633
|
|
|
1466
|
-
function
|
|
1634
|
+
function readSlackFilesWithUrlsFromProviderMetadata(
|
|
1467
1635
|
metadata: Record<string, unknown> | undefined,
|
|
1468
|
-
):
|
|
1636
|
+
): SlackFileWithUrls[] {
|
|
1469
1637
|
const raw = metadata?.slackFiles;
|
|
1470
1638
|
if (!Array.isArray(raw)) return [];
|
|
1471
|
-
const files:
|
|
1639
|
+
const files: SlackFileWithUrls[] = [];
|
|
1472
1640
|
for (const item of raw) {
|
|
1473
1641
|
if (item === null || typeof item !== "object" || Array.isArray(item)) {
|
|
1474
1642
|
continue;
|
|
@@ -1484,6 +1652,13 @@ function readSlackFilesFromProviderMetadata(
|
|
|
1484
1652
|
...(typeof record.mimetype === "string" && record.mimetype.length > 0
|
|
1485
1653
|
? { mimetype: record.mimetype }
|
|
1486
1654
|
: {}),
|
|
1655
|
+
...(typeof record.urlPrivateDownload === "string" &&
|
|
1656
|
+
record.urlPrivateDownload.length > 0
|
|
1657
|
+
? { urlPrivateDownload: record.urlPrivateDownload }
|
|
1658
|
+
: {}),
|
|
1659
|
+
...(typeof record.urlPrivate === "string" && record.urlPrivate.length > 0
|
|
1660
|
+
? { urlPrivate: record.urlPrivate }
|
|
1661
|
+
: {}),
|
|
1487
1662
|
});
|
|
1488
1663
|
}
|
|
1489
1664
|
return files;
|
|
@@ -1515,6 +1690,7 @@ async function tryBackfillSlackDmIfCold(params: {
|
|
|
1515
1690
|
conversationId: string;
|
|
1516
1691
|
channelId: string;
|
|
1517
1692
|
account?: string;
|
|
1693
|
+
guardianExternalUserId?: string;
|
|
1518
1694
|
latestTs?: string;
|
|
1519
1695
|
}): Promise<void> {
|
|
1520
1696
|
const existing = _dmBackfillInFlight.get(params.conversationId);
|
|
@@ -1533,6 +1709,7 @@ async function runBackfillSlackDmIfCold(params: {
|
|
|
1533
1709
|
conversationId: string;
|
|
1534
1710
|
channelId: string;
|
|
1535
1711
|
account?: string;
|
|
1712
|
+
guardianExternalUserId?: string;
|
|
1536
1713
|
latestTs?: string;
|
|
1537
1714
|
}): Promise<void> {
|
|
1538
1715
|
try {
|
|
@@ -1573,6 +1750,10 @@ async function runBackfillSlackDmIfCold(params: {
|
|
|
1573
1750
|
conversationId: params.conversationId,
|
|
1574
1751
|
channelId: params.channelId,
|
|
1575
1752
|
message,
|
|
1753
|
+
...(params.account ? { account: params.account } : {}),
|
|
1754
|
+
...(params.guardianExternalUserId
|
|
1755
|
+
? { guardianExternalUserId: params.guardianExternalUserId }
|
|
1756
|
+
: {}),
|
|
1576
1757
|
});
|
|
1577
1758
|
seen.add(message.id);
|
|
1578
1759
|
written++;
|
|
@@ -2045,9 +2226,21 @@ export async function triggerSlackThreadBackfillIfNeeded(params: {
|
|
|
2045
2226
|
* connection.
|
|
2046
2227
|
*/
|
|
2047
2228
|
account?: string;
|
|
2229
|
+
/**
|
|
2230
|
+
* Canonical Slack user ID for the guardian, when a verified Slack guardian
|
|
2231
|
+
* binding exists. Backfilled messages from this sender are trusted history
|
|
2232
|
+
* and should not be wrapped as external content.
|
|
2233
|
+
*/
|
|
2234
|
+
guardianExternalUserId?: string;
|
|
2048
2235
|
}): Promise<SlackThreadBackfillResult> {
|
|
2049
|
-
const {
|
|
2050
|
-
|
|
2236
|
+
const {
|
|
2237
|
+
conversationId,
|
|
2238
|
+
channelId,
|
|
2239
|
+
threadTs,
|
|
2240
|
+
excludeChannelTs,
|
|
2241
|
+
account,
|
|
2242
|
+
guardianExternalUserId,
|
|
2243
|
+
} = params;
|
|
2051
2244
|
|
|
2052
2245
|
try {
|
|
2053
2246
|
const upperBoundTs = parseSlackTimestamp(excludeChannelTs)
|
|
@@ -2132,6 +2325,8 @@ export async function triggerSlackThreadBackfillIfNeeded(params: {
|
|
|
2132
2325
|
conversationId,
|
|
2133
2326
|
channelId,
|
|
2134
2327
|
message,
|
|
2328
|
+
...(account ? { account } : {}),
|
|
2329
|
+
...(guardianExternalUserId ? { guardianExternalUserId } : {}),
|
|
2135
2330
|
});
|
|
2136
2331
|
threadState.storedChannelTs.add(message.id);
|
|
2137
2332
|
persisted++;
|
|
@@ -50,6 +50,7 @@ import {
|
|
|
50
50
|
isBoundGuardianActor,
|
|
51
51
|
processChannelMessageInBackground,
|
|
52
52
|
shouldStartSlackThinkingStatusForText,
|
|
53
|
+
shouldStartSlackThinkingStatusImmediately,
|
|
53
54
|
} from "./background-dispatch.js";
|
|
54
55
|
|
|
55
56
|
describe("isBoundGuardianActor", () => {
|
|
@@ -201,6 +202,116 @@ describe("Slack thinking status timing", () => {
|
|
|
201
202
|
).toBe(true);
|
|
202
203
|
});
|
|
203
204
|
|
|
205
|
+
test("starts Slack thinking status immediately for DMs and direct mentions", () => {
|
|
206
|
+
expect(
|
|
207
|
+
shouldStartSlackThinkingStatusImmediately({
|
|
208
|
+
sourceChannel: "slack",
|
|
209
|
+
chatType: "im",
|
|
210
|
+
}),
|
|
211
|
+
).toBe(true);
|
|
212
|
+
expect(
|
|
213
|
+
shouldStartSlackThinkingStatusImmediately({
|
|
214
|
+
sourceChannel: "slack",
|
|
215
|
+
slackBotMentioned: true,
|
|
216
|
+
}),
|
|
217
|
+
).toBe(true);
|
|
218
|
+
expect(
|
|
219
|
+
shouldStartSlackThinkingStatusImmediately({
|
|
220
|
+
sourceChannel: "slack",
|
|
221
|
+
chatType: "channel",
|
|
222
|
+
}),
|
|
223
|
+
).toBe(false);
|
|
224
|
+
expect(
|
|
225
|
+
shouldStartSlackThinkingStatusImmediately({
|
|
226
|
+
sourceChannel: "telegram",
|
|
227
|
+
chatType: "im",
|
|
228
|
+
slackBotMentioned: true,
|
|
229
|
+
}),
|
|
230
|
+
).toBe(false);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
test("sets Slack thinking indicator immediately for a DM", async () => {
|
|
234
|
+
const conversationId = "conv-dm-immediate-status";
|
|
235
|
+
const channelId = "D-DM-IMMEDIATE";
|
|
236
|
+
const messageTs = "1700000000.000010";
|
|
237
|
+
|
|
238
|
+
const processMessage: MessageProcessor = async () => {
|
|
239
|
+
expect(deliveredChannelReplies).toHaveLength(1);
|
|
240
|
+
expect(deliveredChannelReplies[0]!.payload.reaction).toEqual({
|
|
241
|
+
action: "add",
|
|
242
|
+
name: "eyes",
|
|
243
|
+
messageTs,
|
|
244
|
+
});
|
|
245
|
+
return { messageId: "user-msg-dm-immediate" };
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
processChannelMessageInBackground({
|
|
249
|
+
processMessage,
|
|
250
|
+
conversationId,
|
|
251
|
+
eventId: "evt-dm-immediate-status",
|
|
252
|
+
content: "dm message",
|
|
253
|
+
sourceChannel: "slack",
|
|
254
|
+
sourceInterface: "slack",
|
|
255
|
+
externalChatId: channelId,
|
|
256
|
+
trustCtx,
|
|
257
|
+
metadataHints: [],
|
|
258
|
+
chatType: "im",
|
|
259
|
+
replyCallbackUrl: `https://example.test/deliver/slack?channel=${channelId}&messageTs=${messageTs}`,
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
await flush();
|
|
263
|
+
|
|
264
|
+
const reactions = deliveredChannelReplies.map(
|
|
265
|
+
(entry) => entry.payload.reaction,
|
|
266
|
+
);
|
|
267
|
+
expect(reactions).toEqual([
|
|
268
|
+
{ action: "add", name: "eyes", messageTs },
|
|
269
|
+
{ action: "remove", name: "eyes", messageTs },
|
|
270
|
+
]);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
test("sets Slack thinking status immediately for an app mention", async () => {
|
|
274
|
+
const conversationId = "conv-mention-immediate-status";
|
|
275
|
+
const channelId = "C-MENTION-IMMEDIATE";
|
|
276
|
+
const threadTs = "1700000000.000011";
|
|
277
|
+
|
|
278
|
+
const processMessage: MessageProcessor = async () => {
|
|
279
|
+
expect(deliveredChannelReplies).toHaveLength(1);
|
|
280
|
+
expect(deliveredChannelReplies[0]!.payload.assistantThreadStatus).toEqual(
|
|
281
|
+
{
|
|
282
|
+
channel: channelId,
|
|
283
|
+
threadTs,
|
|
284
|
+
status: "is thinking...",
|
|
285
|
+
},
|
|
286
|
+
);
|
|
287
|
+
return { messageId: "user-msg-mention-immediate" };
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
processChannelMessageInBackground({
|
|
291
|
+
processMessage,
|
|
292
|
+
conversationId,
|
|
293
|
+
eventId: "evt-mention-immediate-status",
|
|
294
|
+
content: "@assistant please respond",
|
|
295
|
+
sourceChannel: "slack",
|
|
296
|
+
sourceInterface: "slack",
|
|
297
|
+
externalChatId: channelId,
|
|
298
|
+
trustCtx,
|
|
299
|
+
metadataHints: [],
|
|
300
|
+
slackBotMentioned: true,
|
|
301
|
+
replyCallbackUrl: `https://example.test/deliver/slack?channel=${channelId}&threadTs=${threadTs}`,
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
await flush();
|
|
305
|
+
|
|
306
|
+
const statuses = deliveredChannelReplies.map((entry) => {
|
|
307
|
+
const status = entry.payload.assistantThreadStatus as
|
|
308
|
+
| { status?: string }
|
|
309
|
+
| undefined;
|
|
310
|
+
return status?.status;
|
|
311
|
+
});
|
|
312
|
+
expect(statuses).toEqual(["is thinking...", ""]);
|
|
313
|
+
});
|
|
314
|
+
|
|
204
315
|
test("does not set Slack thinking status for no_response text deltas", async () => {
|
|
205
316
|
const conversationId = "conv-no-response-status";
|
|
206
317
|
const channelId = "C-NO-RESPONSE";
|
|
@@ -69,6 +69,7 @@ export interface BackgroundProcessingParams {
|
|
|
69
69
|
conversationId: string;
|
|
70
70
|
eventId: string;
|
|
71
71
|
content: string;
|
|
72
|
+
displayContent?: string;
|
|
72
73
|
attachmentIds?: string[];
|
|
73
74
|
sourceChannel: ChannelId;
|
|
74
75
|
sourceInterface: InterfaceId;
|
|
@@ -84,6 +85,8 @@ export interface BackgroundProcessingParams {
|
|
|
84
85
|
sourceLanguageCode?: string;
|
|
85
86
|
/** Chat type from the gateway (e.g. "private", "group", "supergroup"). */
|
|
86
87
|
chatType?: string;
|
|
88
|
+
/** Slack app_mention/direct bot mention signal from the gateway. */
|
|
89
|
+
slackBotMentioned?: boolean;
|
|
87
90
|
/**
|
|
88
91
|
* Slack-specific inbound metadata extracted at the HTTP boundary. Threaded
|
|
89
92
|
* through to `persistUserMessage` so the row can be tagged with a
|
|
@@ -104,6 +107,7 @@ export function processChannelMessageInBackground(
|
|
|
104
107
|
conversationId,
|
|
105
108
|
eventId,
|
|
106
109
|
content,
|
|
110
|
+
displayContent,
|
|
107
111
|
attachmentIds,
|
|
108
112
|
sourceChannel,
|
|
109
113
|
sourceInterface,
|
|
@@ -118,6 +122,7 @@ export function processChannelMessageInBackground(
|
|
|
118
122
|
commandIntent,
|
|
119
123
|
sourceLanguageCode,
|
|
120
124
|
chatType,
|
|
125
|
+
slackBotMentioned,
|
|
121
126
|
slackInbound,
|
|
122
127
|
} = params;
|
|
123
128
|
|
|
@@ -141,6 +146,11 @@ export function processChannelMessageInBackground(
|
|
|
141
146
|
replyCallbackUrl,
|
|
142
147
|
chatId: externalChatId,
|
|
143
148
|
assistantId,
|
|
149
|
+
startImmediately: shouldStartSlackThinkingStatusImmediately({
|
|
150
|
+
sourceChannel,
|
|
151
|
+
chatType,
|
|
152
|
+
slackBotMentioned,
|
|
153
|
+
}),
|
|
144
154
|
});
|
|
145
155
|
const stopApprovalWatcher = replyCallbackUrl
|
|
146
156
|
? startPendingApprovalPromptWatcher({
|
|
@@ -223,6 +233,7 @@ export function processChannelMessageInBackground(
|
|
|
223
233
|
assistantId,
|
|
224
234
|
trustContext: trustCtx,
|
|
225
235
|
isInteractive: resolveRoutingState(trustCtx).promptWaitingAllowed,
|
|
236
|
+
...(displayContent !== undefined ? { displayContent } : {}),
|
|
226
237
|
...(cmdIntent ? { commandIntent: cmdIntent } : {}),
|
|
227
238
|
...(slackRuntimeContextNotice ? { slackRuntimeContextNotice } : {}),
|
|
228
239
|
...(slackInbound ? { slackInbound } : {}),
|
|
@@ -388,13 +399,29 @@ function shouldEmitSlackThinkingStatus(
|
|
|
388
399
|
}
|
|
389
400
|
}
|
|
390
401
|
|
|
402
|
+
export function shouldStartSlackThinkingStatusImmediately(params: {
|
|
403
|
+
sourceChannel: ChannelId;
|
|
404
|
+
chatType?: string;
|
|
405
|
+
slackBotMentioned?: boolean;
|
|
406
|
+
}): boolean {
|
|
407
|
+
if (params.sourceChannel !== "slack") return false;
|
|
408
|
+
return params.chatType === "im" || params.slackBotMentioned === true;
|
|
409
|
+
}
|
|
410
|
+
|
|
391
411
|
function createSlackThinkingStatusController(params: {
|
|
392
412
|
sourceChannel: ChannelId;
|
|
393
413
|
replyCallbackUrl?: string;
|
|
394
414
|
chatId: string;
|
|
395
415
|
assistantId?: string;
|
|
416
|
+
startImmediately?: boolean;
|
|
396
417
|
}): SlackThinkingStatusController | undefined {
|
|
397
|
-
const {
|
|
418
|
+
const {
|
|
419
|
+
sourceChannel,
|
|
420
|
+
replyCallbackUrl,
|
|
421
|
+
chatId,
|
|
422
|
+
assistantId,
|
|
423
|
+
startImmediately,
|
|
424
|
+
} = params;
|
|
398
425
|
if (
|
|
399
426
|
!replyCallbackUrl ||
|
|
400
427
|
!shouldEmitSlackThinkingStatus(sourceChannel, replyCallbackUrl)
|
|
@@ -416,6 +443,10 @@ function createSlackThinkingStatusController(params: {
|
|
|
416
443
|
);
|
|
417
444
|
};
|
|
418
445
|
|
|
446
|
+
if (startImmediately) {
|
|
447
|
+
start();
|
|
448
|
+
}
|
|
449
|
+
|
|
419
450
|
return {
|
|
420
451
|
observeEvent(msg) {
|
|
421
452
|
if (stopped || clearSlackThinkingStatus) return;
|