@vellumai/assistant 0.6.4 → 0.6.5
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/.prettierignore +5 -0
- package/ARCHITECTURE.md +32 -36
- package/Dockerfile +12 -0
- package/README.md +3 -4
- package/bun.lock +8 -3
- package/docs/architecture/integrations.md +1 -20
- package/docs/architecture/security.md +16 -16
- package/docs/error-handling.md +111 -0
- package/docs/skills.md +10 -10
- package/docs/stt-provider-onboarding.md +2 -1
- package/knip.json +9 -2
- package/node_modules/@vellumai/ces-contracts/package.json +2 -1
- package/node_modules/@vellumai/ces-contracts/src/__tests__/trust-rules.test.ts +471 -0
- package/node_modules/@vellumai/ces-contracts/src/trust-rules.ts +398 -4
- package/node_modules/@vellumai/credential-storage/bun.lock +2 -2
- package/node_modules/@vellumai/credential-storage/package.json +2 -2
- package/node_modules/@vellumai/credential-storage/src/oauth-runtime.ts +20 -2
- package/node_modules/@vellumai/egress-proxy/bun.lock +2 -2
- package/node_modules/@vellumai/egress-proxy/package.json +2 -2
- package/openapi.yaml +123 -11
- package/package.json +6 -3
- package/scripts/generate-openapi.ts +50 -11
- package/src/__tests__/agent-loop-callsite-precedence.test.ts +318 -0
- package/src/__tests__/agent-loop-sentry-hygiene.test.ts +137 -0
- package/src/__tests__/agent-loop.test.ts +112 -1
- package/src/__tests__/anthropic-error-formatting.test.ts +98 -0
- package/src/__tests__/anthropic-provider.test.ts +171 -2
- package/src/__tests__/approval-cascade.test.ts +31 -10
- package/src/__tests__/approval-routes-http.test.ts +134 -10
- package/src/__tests__/assistant-attachments.test.ts +44 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +29 -0
- package/src/__tests__/browser-fill-credential.test.ts +1 -1
- package/src/__tests__/browser-identifier-parity-guard.test.ts +53 -0
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +23 -33
- package/src/__tests__/browser-skill-endstate.test.ts +51 -182
- package/src/__tests__/btw-routes.test.ts +47 -1
- package/src/__tests__/call-controller.test.ts +1 -2
- package/src/__tests__/call-site-routing-provider.test.ts +214 -0
- package/src/__tests__/catalog-cache.test.ts +27 -4
- package/src/__tests__/channel-approval-routes.test.ts +4 -4
- package/src/__tests__/channel-reply-delivery.test.ts +300 -2
- package/src/__tests__/checker.test.ts +428 -501
- package/src/__tests__/cli-command-risk-guard.test.ts +30 -33
- package/src/__tests__/compaction-circuit-breaker.test.ts +336 -0
- package/src/__tests__/compaction.benchmark.test.ts +1 -1
- package/src/__tests__/config-analysis.test.ts +11 -28
- package/src/__tests__/config-loader-backfill.test.ts +174 -0
- package/src/__tests__/config-loader-corrupt.test.ts +183 -0
- package/src/__tests__/config-loader-quarantine-bulletin.test.ts +202 -0
- package/src/__tests__/config-schema-cmd.test.ts +11 -5
- package/src/__tests__/config-schema.test.ts +427 -114
- package/src/__tests__/config-watcher.test.ts +2 -2
- package/src/__tests__/contact-store-user-file.test.ts +72 -73
- package/src/__tests__/contacts-write.test.ts +4 -4
- package/src/__tests__/context-token-estimator.test.ts +191 -1
- package/src/__tests__/context-window-manager.test.ts +530 -2
- package/src/__tests__/conversation-abort-tool-results.test.ts +30 -16
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +61 -17
- package/src/__tests__/conversation-agent-loop.test.ts +412 -82
- package/src/__tests__/conversation-attachments.test.ts +1 -1
- package/src/__tests__/conversation-confirmation-signals.test.ts +30 -9
- package/src/__tests__/conversation-error.test.ts +37 -6
- package/src/__tests__/conversation-history-web-search.test.ts +6 -0
- package/src/__tests__/conversation-init.benchmark.test.ts +36 -0
- package/src/__tests__/conversation-lifecycle.test.ts +336 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +27 -10
- package/src/__tests__/conversation-pre-run-repair.test.ts +30 -16
- package/src/__tests__/conversation-process-callsite.test.ts +306 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +30 -16
- package/src/__tests__/conversation-queue.test.ts +41 -26
- package/src/__tests__/conversation-routes-disk-view.test.ts +29 -1
- package/src/__tests__/conversation-routes-slash-commands.test.ts +31 -3
- package/src/__tests__/conversation-runtime-assembly.test.ts +2735 -55
- package/src/__tests__/conversation-runtime-workspace.test.ts +12 -12
- package/src/__tests__/conversation-skill-tools.test.ts +12 -146
- package/src/__tests__/conversation-slash-queue.test.ts +34 -19
- package/src/__tests__/conversation-slash-unknown.test.ts +30 -16
- package/src/__tests__/conversation-speed-override.test.ts +30 -11
- package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +1035 -0
- package/src/__tests__/conversation-surfaces-standalone.test.ts +630 -0
- package/src/__tests__/conversation-title-service.test.ts +2 -2
- package/src/__tests__/conversation-tool-setup-batch-authorized.test.ts +1 -1
- package/src/__tests__/conversation-unread-route.test.ts +2 -2
- package/src/__tests__/conversation-usage.test.ts +3 -1
- package/src/__tests__/conversation-workspace-cache-state.test.ts +31 -10
- package/src/__tests__/conversation-workspace-injection.test.ts +43 -15
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +44 -16
- package/src/__tests__/credential-broker-browser-fill.test.ts +110 -0
- package/src/__tests__/credential-security-invariants.test.ts +3 -0
- package/src/__tests__/credential-storage-oauth-compat.test.ts +18 -0
- package/src/__tests__/credential-storage-static-compat.test.ts +28 -0
- package/src/__tests__/credential-vault-unit.test.ts +135 -19
- package/src/__tests__/credentials-cli.test.ts +1 -9
- package/src/__tests__/cross-provider-web-search.test.ts +84 -0
- package/src/__tests__/daemon-server-persist-and-process-callsite.test.ts +92 -0
- package/src/__tests__/delete-propagation.test.ts +437 -0
- package/src/__tests__/dm-backfill.test.ts +417 -0
- package/src/__tests__/dm-persistence.test.ts +227 -0
- package/src/__tests__/edit-propagation.test.ts +280 -0
- package/src/__tests__/ephemeral-permissions.test.ts +93 -3
- package/src/__tests__/estimator-calibration-integration.test.ts +208 -0
- package/src/__tests__/estimator-calibration.test.ts +213 -0
- package/src/__tests__/extension-id-sync-guard.test.ts +26 -7
- package/src/__tests__/file-write-tool.test.ts +151 -1
- package/src/__tests__/filing-service.test.ts +255 -0
- package/src/__tests__/gemini-provider.test.ts +0 -3
- package/src/__tests__/guardian-grant-minting.test.ts +8 -0
- package/src/__tests__/headless-browser-interactions.test.ts +1 -1
- package/src/__tests__/heartbeat-service.test.ts +96 -15
- package/src/__tests__/host-shell-tool.test.ts +124 -18
- package/src/__tests__/http-user-message-parity.test.ts +29 -1
- package/src/__tests__/inbound-slack-persistence.test.ts +340 -0
- package/src/__tests__/intent-routing.test.ts +1 -40
- package/src/__tests__/llm-catalog-parity.test.ts +174 -0
- package/src/__tests__/llm-context-normalization.test.ts +121 -0
- package/src/__tests__/llm-resolver.test.ts +214 -0
- package/src/__tests__/llm-schema.test.ts +223 -0
- package/src/__tests__/managed-proxy-context.test.ts +6 -2
- package/src/__tests__/messaging-skill-split.test.ts +3 -34
- package/src/__tests__/migration-import-from-url.test.ts +684 -0
- package/src/__tests__/model-intents.test.ts +9 -83
- package/src/__tests__/notification-decision-fallback.test.ts +0 -10
- package/src/__tests__/notification-decision-identity.test.ts +0 -9
- package/src/__tests__/notification-decision-recipient-context.test.ts +0 -9
- package/src/__tests__/oauth-store.test.ts +10 -7
- package/src/__tests__/oauth2-gateway-transport.test.ts +8 -3
- package/src/__tests__/oauth2-refresh-retry.test.ts +279 -0
- package/src/__tests__/openai-provider.test.ts +7 -0
- package/src/__tests__/openai-responses-provider.test.ts +396 -0
- package/src/__tests__/openrouter-provider-only.test.ts +135 -0
- package/src/__tests__/outbound-slack-persistence.test.ts +293 -0
- package/src/__tests__/permission-checker-host-gate.test.ts +1 -1
- package/src/__tests__/permission-mode.test.ts +16 -0
- package/src/__tests__/permission-types.test.ts +0 -1
- package/src/__tests__/persona-resolver.test.ts +13 -13
- package/src/__tests__/pkb-autoinject.test.ts +37 -1
- package/src/__tests__/platform-bash-auto-approve.test.ts +1 -1
- package/src/__tests__/pricing.test.ts +50 -3
- package/src/__tests__/profiler-routes.test.ts +1 -1
- package/src/__tests__/provider-commit-message-generator.test.ts +14 -84
- package/src/__tests__/provider-env-vars-scope.test.ts +52 -0
- package/src/__tests__/provider-error-scenarios.test.ts +135 -6
- package/src/__tests__/provider-managed-proxy-integration.test.ts +42 -11
- package/src/__tests__/provider-registry-ollama.test.ts +1 -2
- package/src/__tests__/proxy-approval-callback.test.ts +0 -1
- package/src/__tests__/reaction-persistence.test.ts +560 -0
- package/src/__tests__/relay-server.test.ts +1 -1
- package/src/__tests__/require-fresh-approval.test.ts +1 -1
- package/src/__tests__/retry-openrouter-only-normalization.test.ts +136 -0
- package/src/__tests__/retry-thinking-tool-choice.test.ts +226 -0
- package/src/__tests__/risk-classifier-parity.test.ts +230 -0
- package/src/__tests__/sanitize-config-for-transfer.test.ts +78 -1
- package/src/__tests__/secret-ingress-http.test.ts +28 -0
- package/src/__tests__/secret-prompter-channel-fallback.test.ts +125 -0
- package/src/__tests__/secret-routes-managed-proxy.test.ts +2 -3
- package/src/__tests__/secret-scanner-executor.test.ts +1 -1
- package/src/__tests__/send-endpoint-busy.test.ts +29 -1
- package/src/__tests__/server-history-render.test.ts +31 -0
- package/src/__tests__/shell-parser-property.test.ts +13 -13
- package/src/__tests__/skill-cache-store.test.ts +182 -0
- package/src/__tests__/skills.test.ts +19 -33
- package/src/__tests__/slack-app-setup-skill-regression.test.ts +3 -1
- package/src/__tests__/slack-skill.test.ts +3 -8
- package/src/__tests__/starter-bundle.test.ts +35 -0
- package/src/__tests__/subagent-call-site-routing.test.ts +280 -0
- package/src/__tests__/suggestion-routes.test.ts +160 -3
- package/src/__tests__/system-prompt.test.ts +22 -35
- package/src/__tests__/task-runner.test.ts +3 -1
- package/src/__tests__/tcc-sandbox-deny.test.ts +198 -0
- package/src/__tests__/terminal-tools.test.ts +8 -0
- package/src/__tests__/test-support/browser-skill-harness.ts +2 -52
- package/src/__tests__/thread-backfill.test.ts +941 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -2
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +2 -2
- package/src/__tests__/tool-executor.test.ts +60 -94
- package/src/__tests__/trust-store.test.ts +442 -109
- package/src/__tests__/update-bulletin-job.test.ts +389 -0
- package/src/__tests__/usage-cache-backfill-migration.test.ts +3 -1
- package/src/__tests__/verification-control-plane-policy.test.ts +1 -22
- package/src/__tests__/voice-session-bridge.test.ts +39 -0
- package/src/__tests__/volume-security-guard.test.ts +3 -2
- package/src/__tests__/web-search-history.test.ts +337 -0
- package/src/__tests__/workspace-migration-039-drop-legacy-llm-keys.test.ts +343 -0
- package/src/__tests__/workspace-migration-043-release-notes-latex-rendering.test.ts +202 -0
- package/src/__tests__/workspace-migration-045-release-notes-meet-avatar.test.ts +210 -0
- package/src/__tests__/workspace-migration-drop-user-md.test.ts +11 -11
- package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +841 -0
- package/src/__tests__/workspace-policy.test.ts +1 -13
- package/src/acp/client-handler.ts +1 -2
- package/src/agent/loop.ts +209 -17
- package/src/avatar/resvg-lazy.test.ts +136 -0
- package/src/avatar/resvg-lazy.ts +82 -9
- package/src/avatar/traits-png-sync.ts +21 -1
- package/src/browser/__tests__/operations.test.ts +163 -0
- package/src/browser/identifiers.ts +51 -0
- package/src/browser/operations.ts +660 -0
- package/src/browser/types.ts +81 -0
- package/src/calls/guardian-question-copy.ts +2 -2
- package/src/calls/telephony-stt-routing.ts +1 -1
- package/src/calls/voice-session-bridge.ts +1 -0
- package/src/cli/AGENTS.md +1 -1
- package/src/cli/commands/__tests__/attachment.test.ts +438 -0
- package/src/cli/commands/__tests__/browser.test.ts +554 -0
- package/src/cli/commands/__tests__/cache.test.ts +623 -0
- package/src/cli/commands/__tests__/email-list.test.ts +6 -0
- package/src/cli/commands/__tests__/email-send.test.ts +93 -1
- package/src/cli/commands/__tests__/image-generation.test.ts +666 -0
- package/src/cli/commands/__tests__/inference-send.test.ts +451 -0
- package/src/cli/commands/__tests__/stt-transcribe.test.ts +454 -0
- package/src/cli/commands/__tests__/task.test.ts +913 -0
- package/src/cli/commands/__tests__/tts-synthesize.test.ts +594 -0
- package/src/cli/commands/__tests__/ui-confirm.test.ts +650 -0
- package/src/cli/commands/__tests__/ui.test.ts +1215 -0
- package/src/cli/commands/__tests__/watchers.test.ts +716 -0
- package/src/cli/commands/attachment.ts +182 -0
- package/src/cli/commands/browser.ts +350 -0
- package/src/cli/commands/cache.ts +341 -0
- package/src/cli/commands/completions.ts +0 -3
- package/src/cli/commands/config.ts +6 -6
- package/src/cli/commands/conversations-import.ts +347 -0
- package/src/cli/commands/conversations.ts +14 -1
- package/src/cli/commands/email.ts +234 -194
- package/src/cli/commands/image-generation.ts +300 -0
- package/src/cli/commands/inference.ts +200 -0
- package/src/cli/commands/memory.ts +127 -17
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -1
- package/src/cli/commands/platform/__tests__/connect.test.ts +0 -1
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +0 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +0 -1
- package/src/cli/commands/stt.ts +339 -0
- package/src/cli/commands/task.ts +795 -0
- package/src/cli/commands/trust.ts +50 -19
- package/src/cli/commands/tts.ts +273 -0
- package/src/cli/commands/ui.ts +670 -0
- package/src/cli/commands/watchers.ts +509 -0
- package/src/cli/lib/daemon-credential-client.ts +0 -19
- package/src/cli/program.ts +23 -4
- package/src/cli.ts +0 -37
- package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +23 -1
- package/src/config/bundled-skills/media-processing/services/reduce.ts +1 -1
- package/src/config/bundled-skills/messaging/SKILL.md +2 -2
- package/src/config/bundled-skills/messaging/TOOLS.json +4 -0
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +8 -1
- package/src/config/bundled-skills/messaging/tools/messaging-read.ts +15 -1
- package/src/config/bundled-skills/messaging/tools/messaging-search.ts +21 -1
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +11 -12
- package/src/config/bundled-skills/phone-calls/references/CONFIG.md +9 -8
- package/src/config/bundled-skills/settings/TOOLS.json +3 -3
- package/src/config/bundled-tool-registry.ts +0 -175
- package/src/config/env.ts +7 -2
- package/src/config/feature-flag-registry.json +25 -9
- package/src/config/llm-resolver.ts +128 -0
- package/src/config/loader.ts +194 -10
- package/src/config/raw-config-utils.ts +30 -2
- package/src/config/sanitize-for-transfer.ts +35 -0
- package/src/config/schema.ts +30 -41
- package/src/config/schemas/analysis.ts +3 -22
- package/src/config/schemas/calls.ts +0 -4
- package/src/config/schemas/filing.ts +2 -7
- package/src/config/schemas/heartbeat.ts +0 -5
- package/src/config/schemas/inference.ts +3 -23
- package/src/config/schemas/llm.ts +318 -0
- package/src/config/schemas/memory-processing.ts +1 -9
- package/src/config/schemas/notifications.ts +4 -11
- package/src/config/schemas/platform.ts +3 -9
- package/src/config/schemas/security.ts +33 -0
- package/src/config/schemas/services.ts +9 -4
- package/src/config/schemas/stt.ts +1 -0
- package/src/config/schemas/tts.ts +53 -0
- package/src/config/schemas/updates.ts +1 -1
- package/src/config/schemas/workspace-git.ts +3 -40
- package/src/config/skills.ts +2 -2
- package/src/context/__tests__/compact-prompt.test.ts +45 -0
- package/src/context/__tests__/microcompact.test.ts +805 -0
- package/src/context/estimator-calibration.ts +136 -0
- package/src/context/microcompact.ts +443 -0
- package/src/context/prompts/compact.md +12 -0
- package/src/context/token-estimator.ts +61 -3
- package/src/context/window-manager.ts +229 -25
- package/src/credential-execution/approval-bridge.ts +0 -1
- package/src/credential-execution/executable-discovery.ts +19 -8
- package/src/credential-execution/process-manager.test.ts +109 -0
- package/src/credential-execution/process-manager.ts +65 -2
- package/src/daemon/approval-generators.ts +29 -4
- package/src/daemon/assistant-attachments.ts +24 -13
- package/src/daemon/classifier.ts +2 -2
- package/src/daemon/config-watcher.ts +0 -1
- package/src/daemon/context-overflow-reducer.ts +4 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +79 -12
- package/src/daemon/conversation-agent-loop.ts +462 -80
- package/src/daemon/conversation-attachments.ts +2 -6
- package/src/daemon/conversation-error.ts +36 -1
- package/src/daemon/conversation-lifecycle.ts +30 -6
- package/src/daemon/conversation-messaging.ts +73 -4
- package/src/daemon/conversation-process.ts +10 -4
- package/src/daemon/conversation-queue-manager.ts +3 -0
- package/src/daemon/conversation-runtime-assembly.ts +760 -29
- package/src/daemon/conversation-slash.ts +2 -2
- package/src/daemon/conversation-surfaces.ts +389 -1
- package/src/daemon/conversation-tool-setup.ts +10 -5
- package/src/daemon/conversation-usage.ts +1 -1
- package/src/daemon/conversation.ts +118 -30
- package/src/daemon/external-skills-bootstrap.ts +41 -0
- package/src/daemon/guardian-action-generators.ts +34 -14
- package/src/daemon/handlers/config-model.test.ts +86 -0
- package/src/daemon/handlers/config-model.ts +54 -12
- package/src/daemon/handlers/conversations.ts +9 -2
- package/src/daemon/handlers/shared.ts +39 -11
- package/src/daemon/handlers/skills.ts +2 -2
- package/src/daemon/handlers/slack-channel-oauth-install.ts +197 -0
- package/src/daemon/lifecycle.ts +76 -14
- package/src/daemon/message-types/conversations.ts +14 -0
- package/src/daemon/message-types/messages.ts +9 -1
- package/src/daemon/message-types/trust.ts +0 -2
- package/src/daemon/parse-actual-tokens-from-error.test.ts +57 -1
- package/src/daemon/parse-actual-tokens-from-error.ts +66 -0
- package/src/daemon/pkb-context-tracker.test.ts +169 -0
- package/src/daemon/pkb-context-tracker.ts +125 -0
- package/src/daemon/pkb-reminder-builder.test.ts +70 -0
- package/src/daemon/pkb-reminder-builder.ts +31 -0
- package/src/daemon/providers-setup.ts +6 -0
- package/src/daemon/server.ts +117 -9
- package/src/daemon/tool-side-effects.ts +0 -9
- package/src/daemon/watch-handler.ts +4 -4
- package/src/daemon/web-search-history.ts +126 -0
- package/src/events/domain-events.ts +0 -1
- package/src/filing/filing-service.ts +9 -10
- package/src/heartbeat/heartbeat-service.ts +76 -28
- package/src/home/__tests__/feed-scheduler.test.ts +39 -11
- package/src/home/__tests__/rollup-producer.test.ts +44 -0
- package/src/home/assistant-feed-authoring.ts +4 -0
- package/src/home/emit-feed-event.ts +4 -0
- package/src/home/feed-scheduler.ts +20 -4
- package/src/home/feed-types.ts +56 -2
- package/src/home/relationship-state-writer.ts +2 -2
- package/src/home/rollup-producer.ts +34 -5
- package/src/home/suggested-prompts.ts +101 -0
- package/src/ipc/__tests__/attachment-ipc.test.ts +213 -0
- package/src/ipc/__tests__/browser-ipc.test.ts +339 -0
- package/src/ipc/__tests__/cache-ipc.test.ts +266 -0
- package/src/ipc/__tests__/socket-path.test.ts +73 -0
- package/src/ipc/__tests__/task-ipc.test.ts +577 -0
- package/src/ipc/__tests__/ui-request-route.test.ts +495 -0
- package/src/ipc/__tests__/watcher-ipc.test.ts +295 -0
- package/src/ipc/cli-client.ts +2 -1
- package/src/ipc/cli-server.ts +26 -8
- package/src/ipc/gateway-client.ts +4 -4
- package/src/ipc/routes/attachment.ts +114 -0
- package/src/ipc/routes/browser-context.ts +61 -0
- package/src/ipc/routes/browser.ts +96 -0
- package/src/ipc/routes/cache.ts +96 -0
- package/src/ipc/routes/index.ts +17 -1
- package/src/ipc/routes/task-queue.ts +226 -0
- package/src/ipc/routes/task.ts +173 -0
- package/src/ipc/routes/ui-request.ts +50 -0
- package/src/ipc/routes/watcher.ts +203 -0
- package/src/ipc/socket-path.ts +100 -0
- package/src/memory/__tests__/conversation-analyze-job.test.ts +9 -8
- package/src/memory/__tests__/conversation-group-migration.test.ts +99 -0
- package/src/memory/admin.ts +18 -0
- package/src/memory/conversation-analyze-job.ts +14 -13
- package/src/memory/conversation-attention-store.ts +13 -6
- package/src/memory/conversation-crud.ts +103 -3
- package/src/memory/conversation-group-migration.ts +38 -6
- package/src/memory/conversation-title-service.ts +7 -4
- package/src/memory/db-init.ts +2 -0
- package/src/memory/embedding-backend.ts +1 -1
- package/src/memory/graph/compaction.ts +299 -0
- package/src/memory/graph/consolidation.ts +4 -4
- package/src/memory/graph/conversation-graph-memory.ts +89 -29
- package/src/memory/graph/extraction.test.ts +272 -2
- package/src/memory/graph/extraction.ts +173 -51
- package/src/memory/graph/graph-search.test.ts +92 -0
- package/src/memory/graph/graph-search.ts +4 -1
- package/src/memory/graph/narrative.ts +2 -2
- package/src/memory/graph/pattern-scan.ts +2 -2
- package/src/memory/graph/retriever.test.ts +459 -0
- package/src/memory/graph/retriever.ts +230 -48
- package/src/memory/graph/store.ts +41 -0
- package/src/memory/graph/tool-handlers.ts +27 -0
- package/src/memory/graph/tools.ts +6 -1
- package/src/memory/indexer.ts +5 -5
- package/src/memory/job-handlers/conversation-starters.ts +23 -20
- package/src/memory/job-handlers/summarization.ts +2 -2
- package/src/memory/job-utils.ts +7 -1
- package/src/memory/jobs/embed-pkb-file.test.ts +168 -0
- package/src/memory/jobs/embed-pkb-file.ts +54 -0
- package/src/memory/jobs-store.ts +44 -3
- package/src/memory/jobs-worker.ts +4 -0
- package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +1 -1
- package/src/memory/migrations/220-normalize-user-file-by-principal.ts +2 -2
- package/src/memory/migrations/222-strip-placeholder-sentinels-from-messages.ts +82 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/pkb/pkb-index.test.ts +368 -0
- package/src/memory/pkb/pkb-index.ts +255 -0
- package/src/memory/pkb/pkb-reconcile.test.ts +251 -0
- package/src/memory/pkb/pkb-reconcile.ts +148 -0
- package/src/memory/pkb/pkb-search.test.ts +438 -0
- package/src/memory/pkb/pkb-search.ts +137 -0
- package/src/memory/pkb/types.ts +53 -0
- package/src/memory/qdrant-client.ts +122 -1
- package/src/memory/slack-thread-store.ts +37 -0
- package/src/messaging/providers/gmail/adapter.ts +6 -16
- package/src/messaging/providers/gmail/client.ts +22 -0
- package/src/messaging/providers/gmail/types.ts +7 -0
- package/src/messaging/providers/slack/adapter.ts +14 -2
- package/src/messaging/providers/slack/backfill.test.ts +257 -0
- package/src/messaging/providers/slack/backfill.ts +101 -0
- package/src/messaging/providers/slack/message-metadata.test.ts +316 -0
- package/src/messaging/providers/slack/message-metadata.ts +123 -0
- package/src/messaging/providers/slack/render-transcript.test.ts +1373 -0
- package/src/messaging/providers/slack/render-transcript.ts +443 -0
- package/src/messaging/style-analyzer.ts +5 -2
- package/src/notifications/README.md +9 -5
- package/src/notifications/decision-engine.ts +3 -9
- package/src/notifications/preference-extractor.ts +2 -6
- package/src/oauth/oauth-store.ts +1 -0
- package/src/oauth/platform-connection.test.ts +47 -0
- package/src/oauth/platform-connection.ts +15 -5
- package/src/oauth/seed-providers.ts +4 -2
- package/src/permissions/approval-policy.test.ts +948 -0
- package/src/permissions/approval-policy.ts +257 -0
- package/src/permissions/bash-risk-classifier.test.ts +1208 -0
- package/src/permissions/bash-risk-classifier.ts +707 -0
- package/src/permissions/checker.ts +217 -708
- package/src/permissions/command-registry.test.ts +535 -0
- package/src/permissions/command-registry.ts +825 -0
- package/src/permissions/defaults.ts +26 -78
- package/src/permissions/file-risk-classifier.test.ts +535 -0
- package/src/permissions/file-risk-classifier.ts +274 -0
- package/src/permissions/risk-types.ts +205 -0
- package/src/permissions/secret-prompter.ts +53 -2
- package/src/permissions/skill-risk-classifier.test.ts +311 -0
- package/src/permissions/skill-risk-classifier.ts +214 -0
- package/src/permissions/trust-client.ts +52 -25
- package/src/permissions/trust-store-interface.ts +1 -6
- package/src/permissions/trust-store.ts +161 -62
- package/src/permissions/types.ts +23 -14
- package/src/permissions/web-risk-classifier.test.ts +170 -0
- package/src/permissions/web-risk-classifier.ts +89 -0
- package/src/permissions/workspace-policy.ts +1 -16
- package/src/platform/client.ts +19 -1
- package/src/prompts/persona-resolver.ts +3 -3
- package/src/prompts/system-prompt.ts +19 -20
- package/src/prompts/templates/SOUL.md +2 -2
- package/src/prompts/update-bulletin-job.ts +190 -0
- package/src/providers/__tests__/context-overflow-error.test.ts +328 -0
- package/src/providers/__tests__/provider-env-vars.test.ts +102 -0
- package/src/providers/__tests__/retry-callsite.test.ts +424 -0
- package/src/providers/anthropic/client.ts +183 -14
- package/src/providers/call-site-routing.ts +71 -0
- package/src/providers/gemini/client.ts +65 -2
- package/src/providers/managed-proxy/constants.ts +2 -1
- package/src/providers/model-catalog.ts +501 -33
- package/src/providers/model-intents.ts +4 -4
- package/src/providers/openai/chat-completions-provider.ts +57 -1
- package/src/providers/openai/responses-provider.ts +86 -9
- package/src/providers/openrouter/client.ts +76 -9
- package/src/providers/provider-env-vars.ts +56 -0
- package/src/providers/provider-send-message.ts +22 -5
- package/src/providers/ratelimit.ts +4 -0
- package/src/providers/registry.ts +19 -8
- package/src/providers/retry.ts +174 -39
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +55 -0
- package/src/providers/speech-to-text/google-gemini-live-stream.ts +4 -4
- package/src/providers/speech-to-text/provider-catalog.ts +17 -0
- package/src/providers/speech-to-text/resolve.ts +7 -0
- package/src/providers/speech-to-text/xai-realtime.test.ts +578 -0
- package/src/providers/speech-to-text/xai-realtime.ts +796 -0
- package/src/providers/speech-to-text/xai.test.ts +155 -0
- package/src/providers/speech-to-text/xai.ts +97 -0
- package/src/providers/types.ts +93 -3
- package/src/runtime/AGENTS.md +2 -2
- package/src/runtime/__tests__/agent-wake.test.ts +43 -2
- package/src/runtime/__tests__/interactive-ui.test.ts +673 -0
- package/src/runtime/agent-wake.ts +63 -22
- package/src/runtime/auth/route-policy.ts +4 -0
- package/src/runtime/btw-sidechain.ts +13 -3
- package/src/runtime/channel-reply-delivery.ts +106 -2
- package/src/runtime/decision-token.ts +116 -0
- package/src/runtime/gateway-client.ts +2 -2
- package/src/runtime/http-router.ts +32 -0
- package/src/runtime/http-server.ts +52 -1
- package/src/runtime/http-types.ts +23 -1
- package/src/runtime/interactive-ui.ts +362 -0
- package/src/runtime/invite-instruction-generator.ts +2 -2
- package/src/runtime/migrations/__tests__/gcs-signed-url.test.ts +176 -0
- package/src/runtime/migrations/__tests__/vbundle-metadata-merge-integration.test.ts +390 -0
- package/src/runtime/migrations/__tests__/vbundle-metadata-merge.test.ts +221 -0
- package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +1540 -0
- package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +453 -0
- package/src/runtime/migrations/__tests__/vbundle-tar-stream.test.ts +222 -0
- package/src/runtime/migrations/gcs-signed-url.ts +162 -0
- package/src/runtime/migrations/vbundle-importer.ts +154 -9
- package/src/runtime/migrations/vbundle-metadata-merge.ts +124 -0
- package/src/runtime/migrations/vbundle-streaming-importer.ts +2522 -0
- package/src/runtime/migrations/vbundle-streaming-validator.ts +244 -0
- package/src/runtime/migrations/vbundle-tar-stream.ts +217 -0
- package/src/runtime/migrations/vbundle-validator.ts +15 -6
- package/src/runtime/routes/__tests__/home-feed-routes.test.ts +111 -0
- package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +114 -75
- package/src/runtime/routes/__tests__/migration-vellum-metadata-reconcile.test.ts +246 -0
- package/src/runtime/routes/approval-prompt-ts-tracker.ts +58 -0
- package/src/runtime/routes/approval-routes.ts +12 -17
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +9 -0
- package/src/runtime/routes/avatar-routes.ts +20 -4
- package/src/runtime/routes/btw-routes.ts +1 -4
- package/src/runtime/routes/conversation-management-routes.ts +20 -2
- package/src/runtime/routes/conversation-routes.ts +133 -27
- package/src/runtime/routes/debug-routes.ts +1 -1
- package/src/runtime/routes/diagnostics-routes.ts +6 -4
- package/src/runtime/routes/events-routes.ts +16 -0
- package/src/runtime/routes/guardian-approval-interception.ts +33 -3
- package/src/runtime/routes/guardian-approval-prompt.ts +13 -3
- package/src/runtime/routes/home-feed-routes.ts +120 -2
- package/src/runtime/routes/inbound-message-handler.ts +912 -2
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +113 -2
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +61 -3
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +129 -6
- package/src/runtime/routes/integrations/slack/channel.ts +25 -3
- package/src/runtime/routes/llm-context-normalization.ts +23 -1
- package/src/runtime/routes/migration-routes.ts +720 -124
- package/src/runtime/routes/settings-routes.ts +4 -2
- package/src/runtime/routes/trust-rules-routes.ts +30 -14
- package/src/runtime/routes/work-items-routes.test.ts +1 -1
- package/src/runtime/routes/work-items-routes.ts +3 -2
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +25 -43
- package/src/runtime/services/analyze-conversation.ts +12 -16
- package/src/runtime/skill-route-registry.ts +28 -6
- package/src/schedule/scheduler.ts +8 -0
- package/src/security/__tests__/provider-key-env-fallback.test.ts +119 -0
- package/src/security/__tests__/untrusted-content.test.ts +109 -0
- package/src/security/oauth2.ts +98 -35
- package/src/security/secure-keys.ts +7 -8
- package/src/security/token-manager.ts +27 -13
- package/src/security/untrusted-content.ts +102 -0
- package/src/skills/catalog-cache.ts +26 -7
- package/src/skills/catalog-install.ts +31 -3
- package/src/skills/skill-cache-store.ts +97 -0
- package/src/stt/__tests__/daemon-batch-transcriber.test.ts +76 -0
- package/src/stt/daemon-batch-transcriber.ts +33 -0
- package/src/stt/stt-stream-session.ts +8 -1
- package/src/stt/types.ts +5 -1
- package/src/subagent/manager.ts +41 -13
- package/src/tasks/ephemeral-permissions.ts +9 -4
- package/src/telemetry/usage-telemetry-reporter.ts +27 -5
- package/src/tools/browser/__tests__/browser-status.test.ts +45 -2
- package/src/tools/browser/browser-execution.ts +65 -38
- package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +22 -0
- package/src/tools/credentials/tool-policy.ts +39 -5
- package/src/tools/credentials/vault.ts +9 -4
- package/src/tools/executor.ts +4 -0
- package/src/tools/filesystem/write.ts +52 -0
- package/src/tools/host-terminal/host-shell.ts +45 -5
- package/src/tools/memory/register.test.ts +185 -0
- package/src/tools/memory/register.ts +3 -1
- package/src/tools/network/web-fetch.ts +20 -10
- package/src/tools/network/web-search.ts +19 -4
- package/src/tools/permission-checker.ts +36 -15
- package/src/tools/policy-context.ts +25 -8
- package/src/tools/registry.ts +55 -3
- package/src/tools/side-effects.ts +0 -11
- package/src/tools/skills/execute.ts +2 -2
- package/src/tools/skills/sandbox-runner.ts +5 -2
- package/src/tools/terminal/backends/native.ts +51 -2
- package/src/tools/terminal/safe-env.ts +3 -2
- package/src/tools/terminal/shell.ts +1 -0
- package/src/tools/tool-manifest.ts +6 -21
- package/src/tools/types.ts +12 -3
- package/src/tools/verification-control-plane-policy.ts +1 -1
- package/src/tts/__tests__/provider-adapters.test.ts +240 -13
- package/src/tts/provider-catalog.ts +18 -0
- package/src/tts/providers/index.ts +2 -0
- package/src/tts/providers/xai-provider.ts +224 -0
- package/src/tts/types.ts +46 -0
- package/src/types/tar-stream.d.ts +66 -0
- package/src/util/json.ts +17 -0
- package/src/util/platform.ts +2 -2
- package/src/util/pricing.ts +15 -5
- package/src/watcher/engine.ts +1 -1
- package/src/watcher/providers/google-calendar.ts +134 -8
- package/src/watcher/providers/outlook-calendar.ts +42 -2
- package/src/workspace/git-service.ts +23 -4
- package/src/workspace/migrations/038-unify-llm-callsite-configs.ts +516 -0
- package/src/workspace/migrations/039-drop-legacy-llm-keys.ts +171 -0
- package/src/workspace/migrations/040-seed-latency-callsite-defaults.ts +154 -0
- package/src/workspace/migrations/041-backfill-google-gmail-settings-scope.ts +57 -0
- package/src/workspace/migrations/042-fix-backfill-google-gmail-settings-scope.ts +70 -0
- package/src/workspace/migrations/043-release-notes-latex-rendering.ts +75 -0
- package/src/workspace/migrations/044-bump-stale-provider-stream-timeout.ts +51 -0
- package/src/workspace/migrations/045-release-notes-meet-avatar.ts +130 -0
- package/src/workspace/migrations/AGENTS.md +1 -1
- package/src/workspace/migrations/registry.ts +16 -0
- package/src/workspace/provider-commit-message-generator.ts +19 -38
- package/src/__tests__/gmail-archive-fallback.test.ts +0 -193
- package/src/__tests__/gmail-archive-gate.test.ts +0 -246
- package/src/__tests__/gmail-preferences.test.ts +0 -117
- package/src/__tests__/outlook-attachments.test.ts +0 -301
- package/src/__tests__/outlook-automation-tools.test.ts +0 -425
- package/src/__tests__/outlook-categories.test.ts +0 -212
- package/src/__tests__/outlook-compose-tools.test.ts +0 -325
- package/src/__tests__/outlook-declutter-tools.test.ts +0 -585
- package/src/__tests__/outlook-follow-up.test.ts +0 -196
- package/src/__tests__/outlook-trash.test.ts +0 -77
- package/src/__tests__/outlook-unsubscribe.test.ts +0 -279
- package/src/__tests__/update-bulletin-format.test.ts +0 -181
- package/src/__tests__/update-bulletin-state.test.ts +0 -135
- package/src/__tests__/update-bulletin.test.ts +0 -478
- package/src/__tests__/update-template-contract.test.ts +0 -29
- package/src/cli/commands/doctor.ts +0 -341
- package/src/config/bundled-skills/browser/SKILL.md +0 -88
- package/src/config/bundled-skills/browser/TOOLS.json +0 -516
- package/src/config/bundled-skills/browser/tools/browser-attach.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-click.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-close.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-detach.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-extract.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-fill-credential.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-hover.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-navigate.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-press-key.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-screenshot.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-scroll.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-select-option.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-snapshot.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-status.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-type.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +0 -49
- package/src/config/bundled-skills/browser/tools/browser-wait-for.ts +0 -12
- package/src/config/bundled-skills/chatgpt-import/SKILL.md +0 -27
- package/src/config/bundled-skills/chatgpt-import/TOOLS.json +0 -27
- package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +0 -378
- package/src/config/bundled-skills/gmail/SKILL.md +0 -221
- package/src/config/bundled-skills/gmail/TOOLS.json +0 -588
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +0 -256
- package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +0 -112
- package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +0 -44
- package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +0 -81
- package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +0 -108
- package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +0 -146
- package/src/config/bundled-skills/gmail/tools/gmail-label.ts +0 -53
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +0 -347
- package/src/config/bundled-skills/gmail/tools/gmail-preferences-tool.ts +0 -59
- package/src/config/bundled-skills/gmail/tools/gmail-preferences.ts +0 -82
- package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +0 -26
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +0 -347
- package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +0 -29
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +0 -122
- package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +0 -67
- package/src/config/bundled-skills/gmail/tools/scan-result-store.ts +0 -100
- package/src/config/bundled-skills/gmail/tools/shared.ts +0 -47
- package/src/config/bundled-skills/google-calendar/SKILL.md +0 -51
- package/src/config/bundled-skills/google-calendar/TOOLS.json +0 -226
- package/src/config/bundled-skills/google-calendar/calendar-client.ts +0 -223
- package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +0 -27
- package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +0 -48
- package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +0 -19
- package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +0 -36
- package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +0 -58
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +0 -17
- package/src/config/bundled-skills/google-calendar/types.ts +0 -97
- package/src/config/bundled-skills/outlook/SKILL.md +0 -196
- package/src/config/bundled-skills/outlook/TOOLS.json +0 -530
- package/src/config/bundled-skills/outlook/tools/outlook-attachments.ts +0 -85
- package/src/config/bundled-skills/outlook/tools/outlook-categories.ts +0 -77
- package/src/config/bundled-skills/outlook/tools/outlook-draft.ts +0 -84
- package/src/config/bundled-skills/outlook/tools/outlook-follow-up.ts +0 -94
- package/src/config/bundled-skills/outlook/tools/outlook-forward.ts +0 -49
- package/src/config/bundled-skills/outlook/tools/outlook-outreach-scan.ts +0 -237
- package/src/config/bundled-skills/outlook/tools/outlook-rules.ts +0 -161
- package/src/config/bundled-skills/outlook/tools/outlook-send-draft.ts +0 -32
- package/src/config/bundled-skills/outlook/tools/outlook-sender-digest.ts +0 -272
- package/src/config/bundled-skills/outlook/tools/outlook-trash.ts +0 -29
- package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +0 -129
- package/src/config/bundled-skills/outlook/tools/outlook-vacation.ts +0 -87
- package/src/config/bundled-skills/outlook/tools/shared.ts +0 -20
- package/src/config/bundled-skills/outlook-calendar/SKILL.md +0 -51
- package/src/config/bundled-skills/outlook-calendar/TOOLS.json +0 -221
- package/src/config/bundled-skills/outlook-calendar/calendar-client.ts +0 -252
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-check-availability.ts +0 -53
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-create-event.ts +0 -74
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-get-event.ts +0 -18
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-list-events.ts +0 -46
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-rsvp.ts +0 -36
- package/src/config/bundled-skills/outlook-calendar/tools/shared.ts +0 -17
- package/src/config/bundled-skills/outlook-calendar/types.ts +0 -120
- package/src/config/bundled-skills/slack/SKILL.md +0 -108
- package/src/config/bundled-skills/tasks/SKILL.md +0 -37
- package/src/config/bundled-skills/tasks/TOOLS.json +0 -353
- package/src/config/bundled-skills/tasks/icon.svg +0 -34
- package/src/config/bundled-skills/tasks/tools/task-delete.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list-add.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list-show.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list-update.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-queue-run.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-run.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-save.ts +0 -12
- package/src/config/bundled-skills/watcher/SKILL.md +0 -31
- package/src/config/bundled-skills/watcher/TOOLS.json +0 -167
- package/src/config/bundled-skills/watcher/tools/watcher-create.ts +0 -12
- package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +0 -12
- package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +0 -12
- package/src/config/bundled-skills/watcher/tools/watcher-list.ts +0 -12
- package/src/config/bundled-skills/watcher/tools/watcher-update.ts +0 -12
- package/src/prompts/templates/UPDATES.md +0 -50
- package/src/prompts/update-bulletin-format.ts +0 -85
- package/src/prompts/update-bulletin-state.ts +0 -58
- package/src/prompts/update-bulletin-template-path.ts +0 -13
- package/src/prompts/update-bulletin.ts +0 -139
- package/src/shared/provider-env-vars.ts +0 -19
- package/src/tools/watcher/create.ts +0 -86
- package/src/tools/watcher/delete.ts +0 -36
- package/src/tools/watcher/digest.ts +0 -54
- package/src/tools/watcher/list.ts +0 -83
- package/src/tools/watcher/update.ts +0 -71
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure chronological thread-tag rendering for Slack transcripts.
|
|
3
|
+
*
|
|
4
|
+
* Given a list of stored messages (post-upgrade rows with structured metadata
|
|
5
|
+
* AND legacy pre-upgrade rows with `metadata === null`), produces a flat
|
|
6
|
+
* `{role, content}[]` chronologically ordered with compact thread tags so
|
|
7
|
+
* the model can reason across sibling threads in one channel.
|
|
8
|
+
*
|
|
9
|
+
* The function is pure: no I/O, no implicit clock reads. Time is taken from
|
|
10
|
+
* `opts.now` only when needed for relative formatting. Sort and tag rendering
|
|
11
|
+
* are deterministic.
|
|
12
|
+
*
|
|
13
|
+
* Consumers wire this into inbound history rendering and the compaction
|
|
14
|
+
* boundary.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { createHash } from "node:crypto";
|
|
18
|
+
|
|
19
|
+
import type { ContentBlock, Message } from "../../../providers/types.js";
|
|
20
|
+
import type { SlackMessageMetadata } from "./message-metadata.js";
|
|
21
|
+
|
|
22
|
+
export interface RenderableSlackMessage {
|
|
23
|
+
role: "user" | "assistant";
|
|
24
|
+
content: string;
|
|
25
|
+
/** `null` indicates a legacy pre-upgrade row stored without Slack metadata. */
|
|
26
|
+
metadata: SlackMessageMetadata | null;
|
|
27
|
+
/**
|
|
28
|
+
* Sender display name to prepend to the tag line (e.g. `"@alice"`), or
|
|
29
|
+
* `null` to omit the label entirely. Callers should pass `null` when the
|
|
30
|
+
* label would be redundant with the `role` slot — i.e. assistant rows and
|
|
31
|
+
* user rows with no real Slack displayName. Reaction rows always need a
|
|
32
|
+
* subject, so they receive a role-derived fallback if `null` is passed.
|
|
33
|
+
*/
|
|
34
|
+
senderLabel: string | null;
|
|
35
|
+
/** Fallback sort key for legacy rows; ignored when metadata.channelTs is set. */
|
|
36
|
+
createdAt: number;
|
|
37
|
+
/**
|
|
38
|
+
* Full structured content blocks parsed from the persisted row, when
|
|
39
|
+
* available. Optional so existing fixtures and callers that only need the
|
|
40
|
+
* flattened `content` string continue to compile. `renderSlackTranscript`
|
|
41
|
+
* consumes this field to preserve replayable Anthropic blocks
|
|
42
|
+
* (`tool_use`, `tool_result`, `thinking`, `redacted_thinking`, `image`,
|
|
43
|
+
* `file`) in their original order, emitting the tag line inline at the
|
|
44
|
+
* position of the first `text` block. Non-replayable blocks
|
|
45
|
+
* (`ui_surface`, `server_tool_use`, `web_search_tool_result`, unknown
|
|
46
|
+
* types) are stripped; when stripping empties the row entirely, a
|
|
47
|
+
* fallback tag-line text block is emitted so chronology is preserved.
|
|
48
|
+
*/
|
|
49
|
+
readonly contentBlocks?: readonly ContentBlock[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface RenderOptions {
|
|
53
|
+
/** Reserved for future relative-time rendering; currently unused. */
|
|
54
|
+
now?: Date;
|
|
55
|
+
/** Cap rendered reactions per parent message; default 5. */
|
|
56
|
+
maxReactionsPerMessage?: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const DEFAULT_MAX_REACTIONS = 5;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Replayable Anthropic content-block types that we preserve verbatim from a
|
|
63
|
+
* persisted row when rendering the Slack chronological transcript.
|
|
64
|
+
*
|
|
65
|
+
* `text` is intentionally omitted — text content is subsumed into the tag
|
|
66
|
+
* line (e.g. `[11/14/23 14:25 @alice]: ...`) so callers reading the
|
|
67
|
+
* rendered output see one human-readable line per row rather than a raw
|
|
68
|
+
* text block stripped of thread context.
|
|
69
|
+
*
|
|
70
|
+
* Non-replayable types (`ui_surface`, `server_tool_use`,
|
|
71
|
+
* `web_search_tool_result`, unknown types) are dropped: `ui_surface` blocks
|
|
72
|
+
* are the assistant's ephemeral local UI scaffolding and not meaningful to
|
|
73
|
+
* replay; `server_tool_use` / `web_search_tool_result` carry provider-
|
|
74
|
+
* specific `encrypted_content` that becomes stale and is rejected by the
|
|
75
|
+
* provider on re-send.
|
|
76
|
+
*/
|
|
77
|
+
const REPLAYABLE_BLOCK_TYPES = new Set<ContentBlock["type"]>([
|
|
78
|
+
"tool_use",
|
|
79
|
+
"tool_result",
|
|
80
|
+
"thinking",
|
|
81
|
+
"redacted_thinking",
|
|
82
|
+
"image",
|
|
83
|
+
"file",
|
|
84
|
+
]);
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Compute a short, stable, deterministic alias for a Slack message ts.
|
|
88
|
+
*
|
|
89
|
+
* Used as the "parent label" inside thread-reply tags so the model can
|
|
90
|
+
* cross-reference children with their parent without leaking raw ts values.
|
|
91
|
+
* First 6 hex chars of sha256(channelTs) prefixed with `M`.
|
|
92
|
+
*/
|
|
93
|
+
export function parentAlias(channelTs: string): string {
|
|
94
|
+
const hash = createHash("sha256").update(channelTs).digest("hex");
|
|
95
|
+
return `M${hash.slice(0, 6)}`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Format a Slack ts (`"1700000000.000100"`) as `MM/DD/YY HH:MM` (UTC).
|
|
100
|
+
*
|
|
101
|
+
* Slack ts is `<unix-seconds>.<microseconds>`; we treat it as a unix epoch
|
|
102
|
+
* second value for display purposes. Pure — derives only from the ts string.
|
|
103
|
+
*/
|
|
104
|
+
function formatSlackTs(channelTs: string): string {
|
|
105
|
+
const seconds = Number.parseFloat(channelTs);
|
|
106
|
+
if (!Number.isFinite(seconds)) return "??/??/?? ??:??";
|
|
107
|
+
return formatEpochMs(seconds * 1000);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Format an epoch millisecond timestamp as `MM/DD/YY HH:MM` (UTC).
|
|
112
|
+
*/
|
|
113
|
+
function formatEpochMs(ms: number): string {
|
|
114
|
+
if (!Number.isFinite(ms)) return "??/??/?? ??:??";
|
|
115
|
+
const d = new Date(ms);
|
|
116
|
+
const mo = String(d.getUTCMonth() + 1).padStart(2, "0");
|
|
117
|
+
const da = String(d.getUTCDate()).padStart(2, "0");
|
|
118
|
+
const yy = String(d.getUTCFullYear() % 100).padStart(2, "0");
|
|
119
|
+
const hh = String(d.getUTCHours()).padStart(2, "0");
|
|
120
|
+
const mm = String(d.getUTCMinutes()).padStart(2, "0");
|
|
121
|
+
return `${mo}/${da}/${yy} ${hh}:${mm}`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Sort key for chronological ordering.
|
|
126
|
+
*
|
|
127
|
+
* Post-upgrade rows are ordered by their Slack `channelTs`; legacy rows
|
|
128
|
+
* (metadata === null) fall back to `createdAt`. Both produce a numeric
|
|
129
|
+
* seconds-since-epoch value so they intermix correctly.
|
|
130
|
+
*/
|
|
131
|
+
function sortKey(msg: RenderableSlackMessage): number {
|
|
132
|
+
if (msg.metadata) {
|
|
133
|
+
const n = Number.parseFloat(msg.metadata.channelTs);
|
|
134
|
+
if (Number.isFinite(n)) return n;
|
|
135
|
+
}
|
|
136
|
+
// createdAt is epoch ms; convert to seconds for like-with-like comparison.
|
|
137
|
+
return msg.createdAt / 1000;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Render a single non-reaction message (post-upgrade or legacy) as one
|
|
142
|
+
* tagged line.
|
|
143
|
+
*/
|
|
144
|
+
function renderMessage(msg: RenderableSlackMessage): string {
|
|
145
|
+
const meta = msg.metadata;
|
|
146
|
+
const senderPart = msg.senderLabel ? ` ${msg.senderLabel}` : "";
|
|
147
|
+
if (!meta) {
|
|
148
|
+
// Legacy pre-upgrade row: flat render, no thread tag.
|
|
149
|
+
const time = formatEpochMs(msg.createdAt);
|
|
150
|
+
return `[${time}${senderPart}]: ${msg.content}`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const time = formatSlackTs(meta.channelTs);
|
|
154
|
+
|
|
155
|
+
if (meta.deletedAt !== undefined) {
|
|
156
|
+
const dtime = formatEpochMs(meta.deletedAt);
|
|
157
|
+
return `[${time}${senderPart} — deleted ${dtime}]`;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
let head = `[${time}${senderPart}`;
|
|
161
|
+
if (meta.threadTs && meta.threadTs !== meta.channelTs) {
|
|
162
|
+
head += ` → ${parentAlias(meta.threadTs)}`;
|
|
163
|
+
}
|
|
164
|
+
if (meta.editedAt !== undefined) {
|
|
165
|
+
head += `, edited ${formatEpochMs(meta.editedAt)}`;
|
|
166
|
+
}
|
|
167
|
+
head += `]: ${msg.content}`;
|
|
168
|
+
return head;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Render a single reaction event as one tagged line.
|
|
173
|
+
*
|
|
174
|
+
* `[11/14/23 14:28 @bob reacted 👍 to M1a2b3c]` or
|
|
175
|
+
* `[11/14/23 14:28 @bob removed 👍 from M1a2b3c]`.
|
|
176
|
+
*/
|
|
177
|
+
function renderReaction(msg: RenderableSlackMessage): string | null {
|
|
178
|
+
const meta = msg.metadata;
|
|
179
|
+
if (!meta || meta.eventKind !== "reaction" || !meta.reaction) return null;
|
|
180
|
+
const time = formatSlackTs(meta.channelTs);
|
|
181
|
+
const actor =
|
|
182
|
+
msg.senderLabel ?? (msg.role === "assistant" ? "@assistant" : "@user");
|
|
183
|
+
const verb = meta.reaction.op === "added" ? "reacted" : "removed";
|
|
184
|
+
const prep = meta.reaction.op === "added" ? "to" : "from";
|
|
185
|
+
const target = parentAlias(meta.reaction.targetChannelTs);
|
|
186
|
+
return `[${time} ${actor} ${verb} ${meta.reaction.emoji} ${prep} ${target}]`;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Build the content blocks for a single non-reaction message.
|
|
191
|
+
*
|
|
192
|
+
* Emits the tag line (`[MM/DD/YY HH:MM @sender ...]: body`) inline at the position of
|
|
193
|
+
* the first `text` block in `contentBlocks`, and preserves any replayable
|
|
194
|
+
* blocks (`tool_use`, `tool_result`, `thinking`, `redacted_thinking`,
|
|
195
|
+
* `image`, `file`) in their original order. Non-replayable blocks
|
|
196
|
+
* (`ui_surface`, `server_tool_use`, `web_search_tool_result`, unknown types)
|
|
197
|
+
* are dropped.
|
|
198
|
+
*
|
|
199
|
+
* Special cases:
|
|
200
|
+
* - **Deleted rows**: always emit a single tag-line block; replayable blocks
|
|
201
|
+
* (if any) are discarded because the delete is a logical erasure.
|
|
202
|
+
* - **Legacy rows** (no structured `contentBlocks`, or empty array): fall
|
|
203
|
+
* back to a single tag-line block to preserve pre-plumbing behaviour.
|
|
204
|
+
* - **Pure tool-only rows** (`contentBlocks` present but no `text` block):
|
|
205
|
+
* emit only the replayable blocks — no tag line. Anthropic accepts role-
|
|
206
|
+
* correct messages with only tool blocks.
|
|
207
|
+
* - **All-non-replayable rows** (`contentBlocks` present but every block is
|
|
208
|
+
* filtered out — e.g. a row whose only blocks are `server_tool_use` or
|
|
209
|
+
* `ui_surface`): emit a single fallback tag-line text block annotated
|
|
210
|
+
* with the stripped block types/names. Dropping the row entirely would
|
|
211
|
+
* silently alter chronology and can orphan adjacent tool_result context
|
|
212
|
+
* in later repair/conversion steps.
|
|
213
|
+
*/
|
|
214
|
+
function buildMessageContentBlocks(
|
|
215
|
+
msg: RenderableSlackMessage,
|
|
216
|
+
tagLine: string,
|
|
217
|
+
): ContentBlock[] {
|
|
218
|
+
const isDeleted = msg.metadata?.deletedAt !== undefined;
|
|
219
|
+
const blocks = msg.contentBlocks;
|
|
220
|
+
|
|
221
|
+
// Deleted rows: single tag line, drop any replayable content.
|
|
222
|
+
if (isDeleted) {
|
|
223
|
+
return [{ type: "text", text: tagLine }];
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Legacy / unplumbed rows: fall back to single tag line.
|
|
227
|
+
if (!blocks || blocks.length === 0) {
|
|
228
|
+
return [{ type: "text", text: tagLine }];
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const out: ContentBlock[] = [];
|
|
232
|
+
let tagEmitted = false;
|
|
233
|
+
const strippedLabels: string[] = [];
|
|
234
|
+
for (const block of blocks) {
|
|
235
|
+
if (block.type === "text") {
|
|
236
|
+
if (!tagEmitted) {
|
|
237
|
+
out.push({ type: "text", text: tagLine });
|
|
238
|
+
tagEmitted = true;
|
|
239
|
+
}
|
|
240
|
+
// Subsequent text blocks are already subsumed into the tag line.
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
if (REPLAYABLE_BLOCK_TYPES.has(block.type)) {
|
|
244
|
+
out.push(block);
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
// Non-replayable (ui_surface, server_tool_use, web_search_tool_result,
|
|
248
|
+
// unknown) — drop, but remember what we saw in case we need a fallback.
|
|
249
|
+
if (block.type === "server_tool_use") {
|
|
250
|
+
strippedLabels.push(`server_tool_use(${block.name})`);
|
|
251
|
+
} else {
|
|
252
|
+
strippedLabels.push(block.type);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Non-empty source fully filtered to nothing: emit a fallback tag line so
|
|
257
|
+
// the turn still appears in chronology. Annotate with the stripped block
|
|
258
|
+
// types/names so the model has a hint about what was there.
|
|
259
|
+
if (out.length === 0) {
|
|
260
|
+
const suffix =
|
|
261
|
+
strippedLabels.length > 0
|
|
262
|
+
? ` [stripped non-replayable: ${strippedLabels.join(", ")}]`
|
|
263
|
+
: "";
|
|
264
|
+
return [{ type: "text", text: `${tagLine}${suffix}` }];
|
|
265
|
+
}
|
|
266
|
+
return out;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Render a chronological transcript with compact thread tags.
|
|
271
|
+
*
|
|
272
|
+
* Sort is stable: messages with identical sort keys preserve their input
|
|
273
|
+
* order so callers controlling input ordering can break ties deterministically.
|
|
274
|
+
*
|
|
275
|
+
* Reactions are rendered as their own lines (`[time @actor reacted ... to Mxxx]`),
|
|
276
|
+
* but capped per-target at `opts.maxReactionsPerMessage` (default 5). Excess
|
|
277
|
+
* reactions on the same target are collapsed into a single trailer line
|
|
278
|
+
* (`[…and N more reactions to Mxxx]`, singular `reaction` when N===1), emitted
|
|
279
|
+
* at the point the overflow window closes — i.e. immediately before the next
|
|
280
|
+
* event that is not an overflowing reaction for the same target — so trailers
|
|
281
|
+
* stay in chronological position rather than clustered at the end.
|
|
282
|
+
*/
|
|
283
|
+
export function renderSlackTranscript(
|
|
284
|
+
messages: RenderableSlackMessage[],
|
|
285
|
+
opts?: RenderOptions,
|
|
286
|
+
): Message[] {
|
|
287
|
+
if (messages.length === 0) return [];
|
|
288
|
+
|
|
289
|
+
const maxReactions = Math.max(
|
|
290
|
+
1,
|
|
291
|
+
Math.floor(opts?.maxReactionsPerMessage ?? DEFAULT_MAX_REACTIONS),
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
// Stable sort: decorate-sort-undecorate so equal keys preserve input order.
|
|
295
|
+
const indexed = messages.map((m, i) => ({ m, i, k: sortKey(m) }));
|
|
296
|
+
indexed.sort((a, b) => {
|
|
297
|
+
if (a.k !== b.k) return a.k - b.k;
|
|
298
|
+
return a.i - b.i;
|
|
299
|
+
});
|
|
300
|
+
const sorted = indexed.map((x) => x.m);
|
|
301
|
+
|
|
302
|
+
// Per-target reaction counters used to enforce the cap.
|
|
303
|
+
const reactionCount = new Map<string, number>();
|
|
304
|
+
// Open per-target overflow windows. Excess reactions accumulate here; the
|
|
305
|
+
// window is closed (trailer emitted) as soon as the chronological walk
|
|
306
|
+
// reaches an event that is not an overflowing reaction for that target.
|
|
307
|
+
const overflowAccumulator = new Map<
|
|
308
|
+
string,
|
|
309
|
+
{ excess: number; role: "user" | "assistant" }
|
|
310
|
+
>();
|
|
311
|
+
|
|
312
|
+
const trailerMessage = (
|
|
313
|
+
target: string,
|
|
314
|
+
acc: { excess: number; role: "user" | "assistant" },
|
|
315
|
+
): Message => ({
|
|
316
|
+
role: acc.role,
|
|
317
|
+
content: [
|
|
318
|
+
{
|
|
319
|
+
type: "text" as const,
|
|
320
|
+
text: `[…and ${acc.excess} more ${acc.excess === 1 ? "reaction" : "reactions"} to ${parentAlias(target)}]`,
|
|
321
|
+
},
|
|
322
|
+
],
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
const flushOverflowExcept = (out: Message[], keepTarget: string | null) => {
|
|
326
|
+
for (const target of Array.from(overflowAccumulator.keys())) {
|
|
327
|
+
if (target === keepTarget) continue;
|
|
328
|
+
const acc = overflowAccumulator.get(target)!;
|
|
329
|
+
out.push(trailerMessage(target, acc));
|
|
330
|
+
overflowAccumulator.delete(target);
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
const out: Message[] = [];
|
|
335
|
+
for (const m of sorted) {
|
|
336
|
+
const meta = m.metadata;
|
|
337
|
+
if (meta?.eventKind === "reaction" && meta.reaction) {
|
|
338
|
+
const target = meta.reaction.targetChannelTs;
|
|
339
|
+
const seen = reactionCount.get(target) ?? 0;
|
|
340
|
+
if (seen < maxReactions) {
|
|
341
|
+
// Reaction fits under the cap for `target`. Any open overflow windows
|
|
342
|
+
// for other targets are now behind us chronologically — close them.
|
|
343
|
+
flushOverflowExcept(out, null);
|
|
344
|
+
reactionCount.set(target, seen + 1);
|
|
345
|
+
const line = renderReaction(m);
|
|
346
|
+
if (line !== null) {
|
|
347
|
+
out.push({
|
|
348
|
+
role: m.role,
|
|
349
|
+
content: [{ type: "text" as const, text: line }],
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
} else {
|
|
353
|
+
// Reaction overflows for `target`. Close any other open windows, then
|
|
354
|
+
// extend this target's open window.
|
|
355
|
+
flushOverflowExcept(out, target);
|
|
356
|
+
const acc = overflowAccumulator.get(target) ?? {
|
|
357
|
+
excess: 0,
|
|
358
|
+
role: m.role,
|
|
359
|
+
};
|
|
360
|
+
acc.excess += 1;
|
|
361
|
+
overflowAccumulator.set(target, acc);
|
|
362
|
+
}
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
// Non-reaction event: every open overflow window closes here.
|
|
366
|
+
flushOverflowExcept(out, null);
|
|
367
|
+
const tagLine = renderMessage(m);
|
|
368
|
+
const blocks = buildMessageContentBlocks(m, tagLine);
|
|
369
|
+
if (blocks.length === 0) continue;
|
|
370
|
+
out.push({
|
|
371
|
+
role: m.role,
|
|
372
|
+
content: blocks,
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// End of the walk: flush any still-open overflow windows.
|
|
377
|
+
flushOverflowExcept(out, null);
|
|
378
|
+
|
|
379
|
+
return filterOrphanToolPairs(out);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Final safety pass that drops unpaired `tool_use` ↔ `tool_result` blocks.
|
|
384
|
+
*
|
|
385
|
+
* Anthropic's API requires every `tool_use` in an assistant turn to be
|
|
386
|
+
* matched by a `tool_result` in the following user turn (and vice versa).
|
|
387
|
+
* In normal operation `renderSlackTranscript` emits fully-paired turns
|
|
388
|
+
* because the persisted transcript reflects completed tool exchanges, but
|
|
389
|
+
* edge cases (mid-turn compaction, partial failures, a race between
|
|
390
|
+
* producer persistence and consumer persistence) can leave an orphan in
|
|
391
|
+
* the rendered output. Sending an orphan to the provider hard-fails the
|
|
392
|
+
* entire request, so we defensively prune any unpaired block here.
|
|
393
|
+
*
|
|
394
|
+
* Server-side block types (`server_tool_use`, `web_search_tool_result`) are
|
|
395
|
+
* stripped earlier by `buildMessageContentBlocks` — they carry stale
|
|
396
|
+
* provider-specific `encrypted_content` and are never replayed — so they
|
|
397
|
+
* cannot reach this filter.
|
|
398
|
+
*
|
|
399
|
+
* A message that becomes empty after filtering (e.g. an assistant row that
|
|
400
|
+
* carried only an orphaned `tool_use`) is dropped entirely rather than
|
|
401
|
+
* emitted as `{role, content: []}` — empty-content messages are also
|
|
402
|
+
* rejected by the provider.
|
|
403
|
+
*/
|
|
404
|
+
function filterOrphanToolPairs(messages: Message[]): Message[] {
|
|
405
|
+
const produced = new Set<string>();
|
|
406
|
+
const consumed = new Set<string>();
|
|
407
|
+
for (const msg of messages) {
|
|
408
|
+
for (const b of msg.content) {
|
|
409
|
+
if (b.type === "tool_use") produced.add(b.id);
|
|
410
|
+
else if (b.type === "tool_result" || b.type === "web_search_tool_result")
|
|
411
|
+
consumed.add(b.tool_use_id);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
const out: Message[] = [];
|
|
415
|
+
for (const msg of messages) {
|
|
416
|
+
const kept: ContentBlock[] = [];
|
|
417
|
+
for (const b of msg.content) {
|
|
418
|
+
if (b.type === "tool_use" && !consumed.has(b.id)) continue;
|
|
419
|
+
if (
|
|
420
|
+
(b.type === "tool_result" || b.type === "web_search_tool_result") &&
|
|
421
|
+
!produced.has(b.tool_use_id)
|
|
422
|
+
)
|
|
423
|
+
continue;
|
|
424
|
+
kept.push(b);
|
|
425
|
+
}
|
|
426
|
+
if (kept.length > 0) out.push({ role: msg.role, content: kept });
|
|
427
|
+
}
|
|
428
|
+
return out;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Extract the first text-block text from each rendered message.
|
|
433
|
+
*
|
|
434
|
+
* Used by callers (e.g. the active-thread focus block) that need a flat
|
|
435
|
+
* `string[]` of rendered tag lines rather than the structured `Message[]`
|
|
436
|
+
* output. Messages with no text block yield an empty string.
|
|
437
|
+
*/
|
|
438
|
+
export function extractTagLineTexts(rendered: Message[]): string[] {
|
|
439
|
+
return rendered.map((msg) => {
|
|
440
|
+
const first = msg.content.find((b) => b.type === "text");
|
|
441
|
+
return first && first.type === "text" ? first.text : "";
|
|
442
|
+
});
|
|
443
|
+
}
|
|
@@ -127,7 +127,7 @@ export async function extractStylePatterns(
|
|
|
127
127
|
.map((e, i) => `--- Message ${i + 1} ---\n${e}`)
|
|
128
128
|
.join("\n\n");
|
|
129
129
|
|
|
130
|
-
const provider = await getConfiguredProvider();
|
|
130
|
+
const provider = await getConfiguredProvider("styleAnalyzer");
|
|
131
131
|
if (!provider) {
|
|
132
132
|
return { stylePatterns: [], contactObservations: [] };
|
|
133
133
|
}
|
|
@@ -147,7 +147,10 @@ export async function extractStylePatterns(
|
|
|
147
147
|
promptMessages,
|
|
148
148
|
[storeStyleAnalysisTool],
|
|
149
149
|
STYLE_EXTRACTION_SYSTEM_PROMPT,
|
|
150
|
-
{
|
|
150
|
+
{
|
|
151
|
+
signal: AbortSignal.timeout(30_000),
|
|
152
|
+
config: { callSite: "styleAnalyzer" },
|
|
153
|
+
},
|
|
151
154
|
);
|
|
152
155
|
|
|
153
156
|
const toolBlock = response.content.find((b) => b.type === "tool_use");
|
|
@@ -31,7 +31,7 @@ The candidate set is serialized into a compact `<conversation-candidates>` block
|
|
|
31
31
|
|
|
32
32
|
### 3. Decision
|
|
33
33
|
|
|
34
|
-
The decision engine (`decision-engine.ts`) sends the signal to an LLM (configured via `
|
|
34
|
+
The decision engine (`decision-engine.ts`) sends the signal to an LLM (configured via `llm.callSites.notificationDecision`) along with available channels, the user's preference summary, and the conversation candidate set. The LLM responds with a structured decision: whether to notify, which channels, rendered copy per channel, a deduplication key, and **per-channel conversation actions**.
|
|
35
35
|
|
|
36
36
|
**Conversation actions:** For each selected channel, the LLM decides:
|
|
37
37
|
|
|
@@ -538,10 +538,14 @@ Preferences are sanitized against prompt injection (angle brackets replaced with
|
|
|
538
538
|
|
|
539
539
|
## Configuration
|
|
540
540
|
|
|
541
|
-
|
|
541
|
+
The decision engine and preference extractor pick their per-call LLM config
|
|
542
|
+
from the unified `llm` block. Override defaults by setting either of:
|
|
542
543
|
|
|
543
|
-
| Key
|
|
544
|
-
|
|
|
545
|
-
| `
|
|
544
|
+
| Key | Type | Default | Description |
|
|
545
|
+
| ------------------------------------ | ------- | -------------- | --------------------------------------------------------------------------- |
|
|
546
|
+
| `llm.callSites.notificationDecision` | object | _(unset)_ | Provider/model/effort/etc. override for the decision engine call site |
|
|
547
|
+
| `llm.callSites.preferenceExtraction` | object | _(unset)_ | Provider/model/effort/etc. override for the preference extractor call site |
|
|
548
|
+
|
|
549
|
+
When a call site override is unset, the resolver falls back to `llm.default`.
|
|
546
550
|
|
|
547
551
|
The notification pipeline is always active -- signals are processed and dispatched as soon as the daemon is running. The audit trail (events, decisions, deliveries) is written for every signal.
|
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
import { v4 as uuid } from "uuid";
|
|
13
13
|
|
|
14
14
|
import { getDeliverableChannels } from "../channels/config.js";
|
|
15
|
-
import { getConfig } from "../config/loader.js";
|
|
16
15
|
import { listGuardianChannels } from "../contacts/contact-store.js";
|
|
17
16
|
import { resolveGuardianPersona } from "../prompts/persona-resolver.js";
|
|
18
17
|
import { buildCoreIdentityContext } from "../prompts/system-prompt.js";
|
|
@@ -22,7 +21,7 @@ import {
|
|
|
22
21
|
getConfiguredProvider,
|
|
23
22
|
userMessage,
|
|
24
23
|
} from "../providers/provider-send-message.js";
|
|
25
|
-
import type {
|
|
24
|
+
import type { Provider } from "../providers/types.js";
|
|
26
25
|
import { getLogger } from "../util/logger.js";
|
|
27
26
|
import { truncate } from "../util/truncate.js";
|
|
28
27
|
import {
|
|
@@ -717,9 +716,6 @@ export async function evaluateSignal(
|
|
|
717
716
|
availableChannels: NotificationChannel[],
|
|
718
717
|
preferenceContext?: string,
|
|
719
718
|
): Promise<NotificationDecision> {
|
|
720
|
-
const config = getConfig();
|
|
721
|
-
const decisionModelIntent = config.notifications.decisionModelIntent;
|
|
722
|
-
|
|
723
719
|
// When no explicit preference context is provided, load the user's
|
|
724
720
|
// stored notification preferences from the memory-backed store.
|
|
725
721
|
// Wrapped in try/catch so a DB failure doesn't break the decision path.
|
|
@@ -750,7 +746,7 @@ export async function evaluateSignal(
|
|
|
750
746
|
);
|
|
751
747
|
}
|
|
752
748
|
|
|
753
|
-
const provider = await getConfiguredProvider();
|
|
749
|
+
const provider = await getConfiguredProvider("notificationDecision");
|
|
754
750
|
if (!provider) {
|
|
755
751
|
log.warn(
|
|
756
752
|
"Configured provider unavailable for notification decision, using fallback",
|
|
@@ -774,7 +770,6 @@ export async function evaluateSignal(
|
|
|
774
770
|
signal,
|
|
775
771
|
availableChannels,
|
|
776
772
|
resolvedPreferenceContext,
|
|
777
|
-
decisionModelIntent,
|
|
778
773
|
candidateSet,
|
|
779
774
|
);
|
|
780
775
|
} catch (err) {
|
|
@@ -805,7 +800,6 @@ async function classifyWithLLM(
|
|
|
805
800
|
signal: NotificationSignal,
|
|
806
801
|
availableChannels: NotificationChannel[],
|
|
807
802
|
preferenceContext: string | undefined,
|
|
808
|
-
modelIntent: ModelIntent,
|
|
809
803
|
candidateSet?: ConversationCandidateSet,
|
|
810
804
|
): Promise<NotificationDecision> {
|
|
811
805
|
const { signal: abortSignal, cleanup } = createTimeout(DECISION_TIMEOUT_MS);
|
|
@@ -858,7 +852,7 @@ async function classifyWithLLM(
|
|
|
858
852
|
systemPrompt,
|
|
859
853
|
{
|
|
860
854
|
config: {
|
|
861
|
-
|
|
855
|
+
callSite: "notificationDecision",
|
|
862
856
|
max_tokens: 2048,
|
|
863
857
|
tool_choice: {
|
|
864
858
|
type: "tool" as const,
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
* - "Weeknights after 10pm: only critical notifications"
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import { getConfig } from "../config/loader.js";
|
|
15
14
|
import {
|
|
16
15
|
createTimeout,
|
|
17
16
|
extractToolUse,
|
|
@@ -140,15 +139,12 @@ const EXTRACTION_TOOL = {
|
|
|
140
139
|
export async function extractPreferences(
|
|
141
140
|
message: string,
|
|
142
141
|
): Promise<ExtractionResult> {
|
|
143
|
-
const provider = await getConfiguredProvider();
|
|
142
|
+
const provider = await getConfiguredProvider("preferenceExtraction");
|
|
144
143
|
if (!provider) {
|
|
145
144
|
log.debug("No provider available for preference extraction");
|
|
146
145
|
return { detected: false, preferences: [] };
|
|
147
146
|
}
|
|
148
147
|
|
|
149
|
-
const config = getConfig();
|
|
150
|
-
const modelIntent = config.notifications.decisionModelIntent;
|
|
151
|
-
|
|
152
148
|
const { signal, cleanup } = createTimeout(EXTRACTION_TIMEOUT_MS);
|
|
153
149
|
|
|
154
150
|
try {
|
|
@@ -158,7 +154,7 @@ export async function extractPreferences(
|
|
|
158
154
|
SYSTEM_PROMPT,
|
|
159
155
|
{
|
|
160
156
|
config: {
|
|
161
|
-
|
|
157
|
+
callSite: "preferenceExtraction",
|
|
162
158
|
max_tokens: 1024,
|
|
163
159
|
tool_choice: {
|
|
164
160
|
type: "tool" as const,
|
package/src/oauth/oauth-store.ts
CHANGED
|
@@ -12,6 +12,7 @@ import type { VellumPlatformClient } from "../platform/client.js";
|
|
|
12
12
|
import { BackendError, VellumError } from "../util/errors.js";
|
|
13
13
|
import {
|
|
14
14
|
CredentialRequiredError,
|
|
15
|
+
InsufficientBalanceError,
|
|
15
16
|
PlatformOAuthConnection,
|
|
16
17
|
ProviderUnreachableError,
|
|
17
18
|
} from "./platform-connection.js";
|
|
@@ -199,6 +200,52 @@ describe("PlatformOAuthConnection", () => {
|
|
|
199
200
|
const provErr = new ProviderUnreachableError();
|
|
200
201
|
expect(provErr).toBeInstanceOf(BackendError);
|
|
201
202
|
expect(provErr).toBeInstanceOf(VellumError);
|
|
203
|
+
|
|
204
|
+
const balErr = new InsufficientBalanceError();
|
|
205
|
+
expect(balErr).toBeInstanceOf(BackendError);
|
|
206
|
+
expect(balErr).toBeInstanceOf(VellumError);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test("402 response throws InsufficientBalanceError", async () => {
|
|
210
|
+
const client = makeMockClient(
|
|
211
|
+
mock(
|
|
212
|
+
async () => new Response("", { status: 402 }),
|
|
213
|
+
) as unknown as typeof globalThis.fetch,
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
const conn = new PlatformOAuthConnection({ ...DEFAULT_OPTIONS, client });
|
|
217
|
+
await expect(
|
|
218
|
+
conn.request({ method: "GET", path: "/test" }),
|
|
219
|
+
).rejects.toThrow(InsufficientBalanceError);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test("402 response includes actionable billing message", async () => {
|
|
223
|
+
const client = makeMockClient(
|
|
224
|
+
mock(
|
|
225
|
+
async () => new Response("", { status: 402 }),
|
|
226
|
+
) as unknown as typeof globalThis.fetch,
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
const conn = new PlatformOAuthConnection({ ...DEFAULT_OPTIONS, client });
|
|
230
|
+
await expect(
|
|
231
|
+
conn.request({ method: "GET", path: "/test" }),
|
|
232
|
+
).rejects.toThrow(/add funds/i);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test("does not retry on 402", async () => {
|
|
236
|
+
let callCount = 0;
|
|
237
|
+
const client = makeMockClient(
|
|
238
|
+
mock(async () => {
|
|
239
|
+
callCount++;
|
|
240
|
+
return new Response("", { status: 402 });
|
|
241
|
+
}) as unknown as typeof globalThis.fetch,
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
const conn = new PlatformOAuthConnection({ ...DEFAULT_OPTIONS, client });
|
|
245
|
+
await expect(
|
|
246
|
+
conn.request({ method: "GET", path: "/test" }),
|
|
247
|
+
).rejects.toThrow(InsufficientBalanceError);
|
|
248
|
+
expect(callCount).toBe(1);
|
|
202
249
|
});
|
|
203
250
|
|
|
204
251
|
test("424 response throws CredentialRequiredError", async () => {
|