@vellumai/assistant 0.7.0 → 0.7.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 +38 -56
- package/Dockerfile +2 -0
- package/README.md +3 -4
- package/__tests__/permissions/gateway-threshold-reader.test.ts +88 -142
- package/bun.lock +29 -26
- package/docs/architecture/security.md +38 -16
- package/docs/plugins.md +7 -9
- package/knip.json +2 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +1 -0
- package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +39 -1
- package/node_modules/@vellumai/gateway-client/src/types.ts +11 -0
- package/node_modules/@vellumai/service-contracts/package.json +2 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/contracts.test.ts +4 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +107 -0
- package/node_modules/@vellumai/service-contracts/src/index.ts +5 -1
- package/node_modules/@vellumai/service-contracts/src/ingress.ts +24 -0
- package/node_modules/@vellumai/service-contracts/src/twilio-ingress.ts +84 -0
- package/node_modules/@vellumai/skill-host-contracts/__tests__/client.test.ts +1 -5
- package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +9 -5
- package/node_modules/@vellumai/skill-host-contracts/src/client.ts +10 -16
- package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +1 -9
- package/node_modules/@vellumai/skill-host-contracts/src/tool-types.ts +12 -12
- package/node_modules/@vellumai/slack-text/bun.lock +24 -0
- package/node_modules/@vellumai/slack-text/package.json +18 -0
- package/node_modules/@vellumai/slack-text/src/index.test.ts +153 -0
- package/node_modules/@vellumai/slack-text/src/index.ts +235 -0
- package/node_modules/@vellumai/slack-text/tsconfig.json +20 -0
- package/node_modules/@vellumai/twilio-client/bun.lock +24 -0
- package/node_modules/@vellumai/twilio-client/package.json +18 -0
- package/node_modules/@vellumai/twilio-client/src/__tests__/twilio-client.test.ts +128 -0
- package/node_modules/@vellumai/twilio-client/src/index.ts +179 -0
- package/node_modules/@vellumai/twilio-client/tsconfig.json +20 -0
- package/openapi.yaml +869 -129
- package/package.json +8 -3
- package/scripts/generate-openapi.ts +16 -111
- package/src/__tests__/agent-wake-override-profile.test.ts +23 -1
- package/src/__tests__/anthropic-provider.test.ts +56 -13
- package/src/__tests__/app-builder-tool-scripts.test.ts +3 -3
- package/src/__tests__/app-bundler.test.ts +170 -1
- package/src/__tests__/app-control-flow.test.ts +374 -0
- package/src/__tests__/app-control-no-global-cgevent.test.ts +98 -0
- package/src/__tests__/app-control-tool-schemas.test.ts +621 -0
- package/src/__tests__/app-conversation-ids-backfill.test.ts +278 -0
- package/src/__tests__/app-conversation-ids.test.ts +151 -0
- package/src/__tests__/app-executors.test.ts +30 -43
- package/src/__tests__/approval-cascade.test.ts +0 -15
- package/src/__tests__/approval-routes-http.test.ts +29 -23
- package/src/__tests__/assistant-event-hub-machine-name.test.ts +146 -0
- package/src/__tests__/assistant-event-hub-targeted.test.ts +257 -0
- package/src/__tests__/assistant-event-hub.test.ts +235 -79
- package/src/__tests__/assistant-event.test.ts +10 -5
- package/src/__tests__/assistant-events-sse-hardening.test.ts +44 -17
- package/src/__tests__/assistant-feature-flags-integration.test.ts +11 -36
- package/src/__tests__/background-shell-host-bash.test.ts +46 -56
- package/src/__tests__/bootstrap-turn-cleanup.test.ts +44 -0
- package/src/__tests__/btw-routes.test.ts +13 -4
- package/src/__tests__/call-controller.test.ts +50 -2
- package/src/__tests__/call-domain.test.ts +0 -2
- package/src/__tests__/call-routes-http.test.ts +0 -2
- package/src/__tests__/call-site-routing-provider.test.ts +193 -0
- package/src/__tests__/channel-approval-routes.test.ts +10 -296
- package/src/__tests__/channel-approvals.test.ts +25 -17
- package/src/__tests__/channel-guardian.test.ts +100 -146
- package/src/__tests__/channel-readiness-service.test.ts +59 -1
- package/src/__tests__/checker.test.ts +23 -38
- package/src/__tests__/compact-event-conversation-id-guard.test.ts +50 -0
- package/src/__tests__/compaction-events.test.ts +2 -0
- package/src/__tests__/config-loader-backfill.test.ts +90 -155
- package/src/__tests__/config-loader-platform-defaults.test.ts +196 -0
- package/src/__tests__/config-schema-cmd.test.ts +0 -1
- package/src/__tests__/config-schema.test.ts +6 -48
- package/src/__tests__/config-set-platform-guard.test.ts +48 -4
- package/src/__tests__/config-watcher-cleanup-throttle.test.ts +2 -2
- package/src/__tests__/config-watcher.test.ts +14 -2
- package/src/__tests__/connection-policy.test.ts +1 -52
- package/src/__tests__/contacts-write.test.ts +2 -64
- package/src/__tests__/context-image-dimensions.test.ts +1 -1
- package/src/__tests__/context-search-memory-source.test.ts +120 -1
- package/src/__tests__/context-search-memory-v2-source.test.ts +383 -0
- package/src/__tests__/context-search-pkb-source.test.ts +49 -0
- package/src/__tests__/context-search-workspace-source.test.ts +9 -22
- package/src/__tests__/context-window-manager.test.ts +46 -0
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +102 -29
- package/src/__tests__/conversation-agent-loop.test.ts +980 -13
- package/src/__tests__/conversation-analysis-routes.test.ts +12 -10
- package/src/__tests__/conversation-app-control-instantiation.test.ts +392 -0
- package/src/__tests__/conversation-app-control-lifecycle.test.ts +237 -0
- package/src/__tests__/conversation-attention-telegram.test.ts +11 -3
- package/src/__tests__/conversation-confirmation-signals.test.ts +0 -291
- package/src/__tests__/conversation-history-web-search.test.ts +4 -3
- package/src/__tests__/conversation-inference-profile-route.test.ts +12 -23
- package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
- package/src/__tests__/conversation-lifecycle.test.ts +40 -4
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +283 -0
- package/src/__tests__/conversation-process-callsite.test.ts +79 -2
- package/src/__tests__/conversation-queue.test.ts +3 -8
- package/src/__tests__/conversation-routes-disk-view.test.ts +7 -161
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +120 -104
- package/src/__tests__/conversation-routes-slash-commands.test.ts +76 -66
- package/src/__tests__/conversation-runtime-assembly.test.ts +257 -3
- package/src/__tests__/conversation-slash-commands.test.ts +24 -8
- package/src/__tests__/conversation-slash-queue.test.ts +2 -0
- package/src/__tests__/conversation-speed-override.test.ts +0 -3
- package/src/__tests__/conversation-starter-routes.test.ts +79 -2
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +202 -0
- package/src/__tests__/conversation-surfaces-app-control.test.ts +317 -0
- package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +12 -5
- package/src/__tests__/conversation-surfaces-standalone.test.ts +18 -14
- package/src/__tests__/conversation-surfaces-state-update.test.ts +3 -2
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +8 -46
- package/src/__tests__/conversation-usage.test.ts +253 -3
- package/src/__tests__/credential-execution-feature-gates.test.ts +5 -12
- package/src/__tests__/credential-execution-managed-contract.test.ts +3 -131
- package/src/__tests__/credential-execution-shell-lockdown.test.ts +0 -39
- package/src/__tests__/credential-health-service.test.ts +68 -0
- package/src/__tests__/credential-security-e2e.test.ts +4 -3
- package/src/__tests__/credential-security-invariants.test.ts +1 -5
- package/src/__tests__/credential-token-resolver.test.ts +180 -0
- package/src/__tests__/credentials-cli.test.ts +5 -12
- package/src/__tests__/cu-unified-flow.test.ts +206 -27
- package/src/__tests__/daemon-assistant-events.test.ts +34 -21
- package/src/__tests__/daemon-credential-client.test.ts +102 -17
- package/src/__tests__/db-connection-isolation.test.ts +125 -0
- package/src/__tests__/db-migration-rollback.test.ts +101 -0
- package/src/__tests__/db-schedule-syntax-migration.test.ts +2 -0
- package/src/__tests__/db-slack-compaction-watermark-migration.test.ts +169 -0
- package/src/__tests__/deterministic-verification-control-plane.test.ts +7 -80
- package/src/__tests__/document-conversations.test.ts +332 -0
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
- package/src/__tests__/embedding-managed-proxy-selection.test.ts +2 -2
- package/src/__tests__/emit-event-signal.test.ts +4 -6
- package/src/__tests__/events-client-registration.test.ts +193 -49
- package/src/__tests__/filing-service.test.ts +58 -7
- package/src/__tests__/first-greeting.test.ts +156 -150
- package/src/__tests__/fixtures/mock-chrome-extension.ts +108 -66
- package/src/__tests__/gateway-only-enforcement.test.ts +0 -1
- package/src/__tests__/get-skill-detail-audit.test.ts +3 -8
- package/src/__tests__/guardian-binding-drift-heal.test.ts +1 -1
- package/src/__tests__/guardian-dispatch.test.ts +1 -1
- package/src/__tests__/guardian-grant-minting.test.ts +7 -2
- package/src/__tests__/guardian-routing-invariants.test.ts +7 -2
- package/src/__tests__/guardian-routing-state.test.ts +1 -1
- package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -2
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +30 -11
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +2 -84
- package/src/__tests__/headless-browser-mode.test.ts +4 -9
- package/src/__tests__/headless-browser-navigate.test.ts +21 -20
- package/src/__tests__/heartbeat-service.test.ts +1007 -8
- package/src/__tests__/helpers/call-route-handler.ts +7 -1
- package/src/__tests__/helpers/channel-test-adapter.ts +2 -2
- package/src/__tests__/helpers/create-guardian-binding.ts +91 -0
- package/src/__tests__/host-app-control-proxy.test.ts +602 -0
- package/src/__tests__/host-app-control-routes.test.ts +263 -0
- package/src/__tests__/host-bash-proxy.test.ts +270 -147
- package/src/__tests__/host-bash-routes.test.ts +294 -0
- package/src/__tests__/host-browser-proxy.test.ts +126 -198
- package/src/__tests__/host-browser-routes.test.ts +50 -54
- package/src/__tests__/host-cu-proxy.test.ts +78 -144
- package/src/__tests__/host-cu-routes-targeted.test.ts +300 -0
- package/src/__tests__/host-file-edit-tool.test.ts +47 -1
- package/src/__tests__/host-file-proxy-targeted.test.ts +339 -0
- package/src/__tests__/host-file-proxy.test.ts +62 -122
- package/src/__tests__/host-file-read-tool.test.ts +59 -21
- package/src/__tests__/host-file-routes-targeted.test.ts +262 -0
- package/src/__tests__/host-file-write-tool.test.ts +42 -1
- package/src/__tests__/host-proxy-base.test.ts +312 -0
- package/src/__tests__/host-shell-tool.test.ts +53 -70
- package/src/__tests__/host-transfer-pending-interactions.test.ts +2 -18
- package/src/__tests__/host-transfer-proxy-targeted.test.ts +583 -0
- package/src/__tests__/host-transfer-proxy.test.ts +145 -56
- package/src/__tests__/host-transfer-routes-targeted.test.ts +447 -0
- package/src/__tests__/http-user-message-parity.test.ts +1 -6
- package/src/__tests__/identity-intro-cache.test.ts +29 -0
- package/src/__tests__/identity-routes.test.ts +103 -1
- package/src/__tests__/inbound-slack-persistence.test.ts +31 -0
- package/src/__tests__/init-feature-flag-overrides.test.ts +26 -3
- package/src/__tests__/injector-chain.test.ts +10 -5
- package/src/__tests__/injector-pkb-v2-silenced.test.ts +124 -0
- package/src/__tests__/inline-command-runner.test.ts +0 -67
- package/src/__tests__/inline-skill-load-permissions.test.ts +5 -13
- package/src/__tests__/install-skill-routing.test.ts +1 -13
- package/src/__tests__/integration-status.test.ts +85 -5
- package/src/__tests__/intent-routing.test.ts +0 -1
- package/src/__tests__/jobs-store-qdrant-breaker.test.ts +95 -5
- package/src/__tests__/lifecycle-memory-v2-seed.test.ts +17 -0
- package/src/__tests__/llm-callsite-catalog.test.ts +34 -0
- package/src/__tests__/llm-catalog-parity.test.ts +90 -0
- package/src/__tests__/llm-context-resolution.test.ts +180 -0
- package/src/__tests__/llm-resolver.test.ts +80 -12
- package/src/__tests__/llm-usage-store.test.ts +269 -4
- package/src/__tests__/log-export-routes.test.ts +89 -0
- package/src/__tests__/managed-profile-guard.test.ts +225 -0
- package/src/__tests__/managed-skill-lifecycle.test.ts +0 -11
- package/src/__tests__/manual-token-reconciliation.test.ts +334 -0
- package/src/__tests__/mcp-auth-routes.test.ts +197 -0
- package/src/__tests__/mcp-cli.test.ts +338 -2
- package/src/__tests__/memory-jobs-worker-lanes.test.ts +188 -0
- package/src/__tests__/memory-v2-static-injector.test.ts +95 -0
- package/src/__tests__/migration-cross-version-compatibility.test.ts +197 -291
- package/src/__tests__/migration-export-http.test.ts +33 -26
- package/src/__tests__/migration-export-streaming.test.ts +18 -10
- package/src/__tests__/migration-export-to-gcs.test.ts +49 -9
- package/src/__tests__/migration-import-commit-http.test.ts +172 -21
- package/src/__tests__/migration-import-from-gcs.test.ts +50 -9
- package/src/__tests__/migration-import-from-url.test.ts +20 -6
- package/src/__tests__/migration-import-preflight-http.test.ts +95 -95
- package/src/__tests__/migration-parity-persistence.test.ts +62 -25
- package/src/__tests__/migration-transport.test.ts +115 -23
- package/src/__tests__/migration-validate-http.test.ts +105 -80
- package/src/__tests__/migration-wizard.test.ts +133 -27
- package/src/__tests__/mock-gateway-ipc.ts +1 -0
- package/src/__tests__/non-member-access-request.test.ts +1 -1
- package/src/__tests__/notification-guardian-path.test.ts +1 -1
- package/src/__tests__/oauth-cli.test.ts +0 -2
- package/src/__tests__/oauth-store.test.ts +19 -0
- package/src/__tests__/oauth2-gateway-transport.test.ts +0 -1
- package/src/__tests__/persistence-secret-redaction.test.ts +299 -0
- package/src/__tests__/platform-bash-auto-approve.test.ts +26 -21
- package/src/__tests__/prechat-onboarding-contract.test.ts +34 -8
- package/src/__tests__/pricing.test.ts +68 -4
- package/src/__tests__/process-message-background-slack.test.ts +333 -0
- package/src/__tests__/provider-commit-message-generator.test.ts +0 -1
- package/src/__tests__/provider-managed-proxy-integration.test.ts +153 -17
- package/src/__tests__/provider-send-message-override-profile.test.ts +50 -0
- package/src/__tests__/provider-usage-tracking.test.ts +208 -0
- package/src/__tests__/public-ingress-urls.test.ts +97 -0
- package/src/__tests__/reaction-persistence.test.ts +9 -6
- package/src/__tests__/rebind-secrets-screen.test.ts +53 -16
- package/src/__tests__/recording-handler.test.ts +64 -81
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +4 -3
- package/src/__tests__/relay-server.test.ts +18 -13
- package/src/__tests__/require-fresh-approval.test.ts +13 -23
- package/src/__tests__/retry-backoff.test.ts +87 -0
- package/src/__tests__/runtime-attachment-metadata.test.ts +1 -1
- package/src/__tests__/runtime-events-sse-parity.test.ts +3 -4
- package/src/__tests__/runtime-events-sse.test.ts +13 -18
- package/src/__tests__/sanitize-config-for-transfer.test.ts +24 -2
- package/src/__tests__/schedule-retry.test.ts +715 -0
- package/src/__tests__/script-proxy-mitm-handler.test.ts +1 -1
- package/src/__tests__/search-skills-unified.test.ts +9 -15
- package/src/__tests__/secret-ingress-cli.test.ts +2 -5
- package/src/__tests__/secret-ingress-http.test.ts +1 -4
- package/src/__tests__/secret-onetime-send.test.ts +4 -2
- package/src/__tests__/secret-prompt-log-hygiene.test.ts +24 -7
- package/src/__tests__/secret-prompter-channel-fallback.test.ts +42 -47
- package/src/__tests__/secret-response-routing.test.ts +29 -15
- package/src/__tests__/secret-routes-managed-proxy.test.ts +5 -1
- package/src/__tests__/secret-scanner.test.ts +2 -545
- package/src/__tests__/send-endpoint-busy.test.ts +12 -24
- package/src/__tests__/settings-routes.test.ts +1 -1
- package/src/__tests__/shell-credential-ref.test.ts +0 -8
- package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -57
- package/src/__tests__/skill-feature-flags.test.ts +43 -41
- package/src/__tests__/skill-load-feature-flag.test.ts +13 -14
- package/src/__tests__/skill-load-inline-command.test.ts +0 -51
- package/src/__tests__/skill-load-inline-includes.test.ts +0 -43
- package/src/__tests__/skill-projection.benchmark.test.ts +0 -1
- package/src/__tests__/skill-script-runner-sandbox.test.ts +0 -12
- package/src/__tests__/skill-tool-factory.test.ts +97 -0
- package/src/__tests__/skills-file-content-endpoint.test.ts +9 -30
- package/src/__tests__/skills-files-catalog-fallback.test.ts +11 -17
- package/src/__tests__/slack-channel-config.test.ts +9 -14
- package/src/__tests__/slack-inbound-verification.test.ts +1 -62
- package/src/__tests__/subagent-fork-notifications.test.ts +57 -47
- package/src/__tests__/subagent-manager-notify.test.ts +70 -70
- package/src/__tests__/subagent-notify-parent.test.ts +80 -83
- package/src/__tests__/system-prompt-ask-mode.test.ts +0 -1
- package/src/__tests__/system-prompt.test.ts +115 -14
- package/src/__tests__/telegram-config.test.ts +0 -1
- package/src/__tests__/terminal-tools.test.ts +0 -89
- package/src/__tests__/test-preload.ts +8 -0
- package/src/__tests__/thread-backfill.test.ts +945 -31
- package/src/__tests__/tool-approval-handler.test.ts +3 -4
- package/src/__tests__/tool-audit-listener.test.ts +48 -0
- package/src/__tests__/tool-domain-event-publisher.test.ts +0 -36
- package/src/__tests__/tool-execute-pipeline.test.ts +0 -7
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +0 -17
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +9 -19
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +4 -8
- package/src/__tests__/tool-executor.test.ts +12 -20
- package/src/__tests__/tool-metrics-listener.test.ts +0 -35
- package/src/__tests__/tool-side-effects-slack-dm.test.ts +1 -0
- package/src/__tests__/tool-trace-listener.test.ts +0 -17
- package/src/__tests__/transfer-progress-screen.test.ts +63 -26
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +2 -149
- package/src/__tests__/trusted-contact-multichannel.test.ts +2 -4
- package/src/__tests__/trusted-contact-verification.test.ts +1 -1
- package/src/__tests__/tts-catalog-parity.test.ts +16 -5
- package/src/__tests__/twilio-config.test.ts +3 -16
- package/src/__tests__/twilio-routes.test.ts +3 -5
- package/src/__tests__/twilio-validation.test.ts +93 -0
- package/src/__tests__/usage-attribution.test.ts +247 -0
- package/src/__tests__/usage-cli.test.ts +143 -0
- package/src/__tests__/usage-grouped-buckets.test.ts +155 -0
- package/src/__tests__/usage-routes.test.ts +150 -0
- package/src/__tests__/validation-results-screen.test.ts +39 -16
- package/src/__tests__/vbundle-pax-and-symlink.test.ts +12 -3
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +47 -138
- package/src/__tests__/verification-control-plane-policy.test.ts +6 -11
- package/src/__tests__/voice-ingress-preflight.test.ts +19 -0
- package/src/__tests__/voice-session-bridge.test.ts +5 -5
- package/src/__tests__/workspace-migration-006-services-config.test.ts +3 -2
- package/src/__tests__/workspace-migration-062-drop-memory-v2-edges-json.test.ts +103 -0
- package/src/__tests__/workspace-migration-063-release-notes-dynamic-model-context.test.ts +77 -0
- package/src/__tests__/workspace-migration-064-unwind-main-agent-opus-seed.test.ts +225 -0
- package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +1 -5
- package/src/__tests__/workspace-migration-down-functions.test.ts +8 -8
- package/src/__tests__/workspace-migration-memory-v2-init.test.ts +8 -30
- package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +10 -6
- package/src/acp/index.ts +0 -15
- package/src/acp/session-manager.ts +37 -34
- package/src/agent/loop.ts +16 -1
- package/src/approvals/AGENTS.md +4 -0
- package/src/approvals/__tests__/guardian-feed-event.test.ts +10 -3
- package/src/approvals/guardian-request-resolvers.ts +10 -2
- package/src/backup/__tests__/paths.test.ts +0 -22
- package/src/backup/__tests__/restore.test.ts +94 -177
- package/src/backup/paths.ts +2 -15
- package/src/backup/restore.ts +107 -231
- package/src/browser-session/events.ts +0 -9
- package/src/bundler/app-bundler.ts +51 -3
- package/src/calls/call-store.ts +1 -34
- package/src/calls/guardian-question-copy.ts +0 -108
- package/src/calls/relay-server.ts +4 -68
- package/src/calls/twilio-config.ts +2 -17
- package/src/calls/twilio-rest.ts +31 -141
- package/src/calls/twilio-routes.ts +12 -13
- package/src/calls/voice-session-bridge.ts +7 -38
- package/src/channels/types.ts +8 -42
- package/src/cli/commands/__tests__/backup.test.ts +6 -277
- package/src/cli/commands/__tests__/cache.test.ts +152 -5
- package/src/cli/commands/__tests__/gateway.test.ts +288 -0
- package/src/cli/commands/__tests__/memory-v2.test.ts +18 -28
- package/src/cli/commands/__tests__/trust.test.ts +21 -387
- package/src/cli/commands/__tests__/webhooks.test.ts +0 -1
- package/src/cli/commands/backup.ts +6 -331
- package/src/cli/commands/cache-fs.ts +8 -0
- package/src/cli/commands/cache.ts +153 -82
- package/src/cli/commands/clients.ts +64 -7
- package/src/cli/commands/completions.ts +3 -3
- package/src/cli/commands/contacts.ts +304 -76
- package/src/cli/commands/conversations.ts +2 -5
- package/src/cli/commands/credentials.ts +15 -7
- package/src/cli/commands/domain.ts +66 -15
- package/src/cli/commands/gateway.ts +183 -0
- package/src/cli/commands/keys.ts +13 -7
- package/src/cli/commands/mcp.ts +116 -156
- package/src/cli/commands/memory-v2.ts +320 -53
- package/src/cli/commands/oauth/shared.ts +2 -29
- package/src/cli/commands/pending.ts +102 -0
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -1
- package/src/cli/commands/platform/__tests__/connect.test.ts +0 -2
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +0 -2
- package/src/cli/commands/platform/__tests__/status.test.ts +13 -15
- package/src/cli/commands/platform/disconnect.ts +5 -4
- package/src/cli/commands/platform/index.ts +0 -18
- package/src/cli/commands/skills.ts +77 -35
- package/src/cli/commands/trust.ts +70 -430
- package/src/cli/commands/usage.ts +25 -16
- package/src/cli/lib/daemon-credential-client.ts +115 -19
- package/src/cli/program.ts +4 -0
- package/src/cli.ts +0 -21
- package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
- package/src/config/assistant-feature-flags.ts +67 -10
- package/src/config/bundled-skills/acp/SKILL.md +6 -0
- package/src/config/bundled-skills/acp/TOOLS.json +1 -22
- package/src/config/bundled-skills/app-builder/SKILL.md +14 -109
- package/src/config/bundled-skills/app-builder/TOOLS.json +1 -28
- package/src/config/bundled-skills/app-builder/tools/app-create.ts +1 -10
- package/src/config/bundled-skills/app-control/SKILL.md +75 -0
- package/src/config/bundled-skills/app-control/TOOLS.json +299 -0
- package/src/config/bundled-skills/app-control/tools/app-control-click.ts +12 -0
- package/src/config/bundled-skills/app-control/tools/app-control-combo.ts +12 -0
- package/src/config/bundled-skills/app-control/tools/app-control-drag.ts +12 -0
- package/src/config/bundled-skills/app-control/tools/app-control-observe.ts +12 -0
- package/src/config/bundled-skills/app-control/tools/app-control-press.ts +12 -0
- package/src/config/bundled-skills/app-control/tools/app-control-sequence.ts +12 -0
- package/src/config/bundled-skills/app-control/tools/app-control-start.ts +12 -0
- package/src/config/bundled-skills/app-control/tools/app-control-stop.ts +12 -0
- package/src/config/bundled-skills/app-control/tools/app-control-type.ts +12 -0
- package/src/config/bundled-skills/computer-use/SKILL.md +6 -0
- package/src/config/bundled-skills/computer-use/TOOLS.json +67 -43
- package/src/config/bundled-skills/contacts/TOOLS.json +0 -16
- package/src/config/bundled-skills/document/TOOLS.json +0 -8
- package/src/config/bundled-skills/followups/TOOLS.json +0 -12
- package/src/config/bundled-skills/image-studio/SKILL.md +4 -0
- package/src/config/bundled-skills/image-studio/TOOLS.json +0 -4
- package/src/config/bundled-skills/media-processing/TOOLS.json +0 -24
- package/src/config/bundled-skills/messaging/TOOLS.json +14 -44
- package/src/config/bundled-skills/phone-calls/TOOLS.json +0 -12
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +19 -4
- package/src/config/bundled-skills/playbooks/TOOLS.json +0 -16
- package/src/config/bundled-skills/schedule/TOOLS.json +14 -14
- package/src/config/bundled-skills/sequences/TOOLS.json +0 -36
- package/src/config/bundled-skills/settings/SKILL.md +4 -0
- package/src/config/bundled-skills/settings/TOOLS.json +0 -12
- package/src/config/bundled-skills/skill-management/SKILL.md +6 -0
- package/src/config/bundled-skills/skill-management/TOOLS.json +0 -8
- package/src/config/bundled-skills/subagent/SKILL.md +6 -2
- package/src/config/bundled-skills/subagent/TOOLS.json +0 -20
- package/src/config/bundled-skills/transcribe/SKILL.md +4 -0
- package/src/config/bundled-skills/transcribe/TOOLS.json +0 -4
- package/src/config/bundled-tool-registry.ts +21 -0
- package/src/config/env-registry.ts +12 -4
- package/src/config/env.ts +22 -26
- package/src/config/feature-flag-registry.json +40 -152
- package/src/config/llm-callsite-catalog.ts +12 -0
- package/src/config/llm-context-resolution.ts +80 -0
- package/src/config/llm-resolver.ts +58 -22
- package/src/config/loader.ts +76 -102
- package/src/config/sanitize-for-transfer.ts +2 -0
- package/src/config/schema.ts +2 -158
- package/src/config/schemas/__tests__/memory-lifecycle.test.ts +80 -0
- package/src/config/schemas/__tests__/memory-v2.test.ts +8 -4
- package/src/config/schemas/call-site-catalog.ts +271 -0
- package/src/config/schemas/calls.ts +5 -14
- package/src/config/schemas/heartbeat.ts +63 -0
- package/src/config/schemas/inference.ts +1 -1
- package/src/config/schemas/ingress.ts +11 -7
- package/src/config/schemas/llm.ts +34 -11
- package/src/config/schemas/memory-lifecycle.ts +77 -24
- package/src/config/schemas/memory-retrieval.ts +2 -2
- package/src/config/schemas/memory-v2.ts +57 -4
- package/src/config/schemas/platform.ts +6 -0
- package/src/config/schemas/security.ts +1 -42
- package/src/config/schemas/services.ts +7 -21
- package/src/config/schemas/skills.ts +5 -11
- package/src/config/schemas/tts.ts +1 -1
- package/src/config/seed-inference-profiles.ts +117 -0
- package/src/config/skills.ts +0 -90
- package/src/config/types.ts +3 -6
- package/src/contacts/contact-store.ts +0 -47
- package/src/contacts/contacts-write.ts +1 -132
- package/src/context/window-manager.ts +43 -5
- package/src/credential-execution/feature-gates.ts +10 -10
- package/src/credential-execution/process-manager.ts +46 -51
- package/src/credential-health/credential-health-service.ts +21 -16
- package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +75 -82
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +126 -5
- package/src/daemon/__tests__/daemon-skill-host.test.ts +2 -9
- package/src/daemon/bootstrap-turn-cleanup.ts +45 -0
- package/src/daemon/config-watcher.ts +4 -3
- package/src/daemon/connection-policy.ts +1 -26
- package/src/daemon/conversation-agent-loop-handlers.ts +74 -7
- package/src/daemon/conversation-agent-loop.ts +309 -64
- package/src/daemon/conversation-history.ts +8 -8
- package/src/daemon/conversation-launch.ts +20 -135
- package/src/daemon/conversation-lifecycle.ts +8 -1
- package/src/daemon/conversation-messaging.ts +1 -0
- package/src/daemon/conversation-process.ts +97 -172
- package/src/daemon/conversation-runtime-assembly.ts +219 -76
- package/src/daemon/conversation-slash.ts +47 -5
- package/src/daemon/conversation-store.ts +7 -31
- package/src/daemon/conversation-surfaces.ts +144 -29
- package/src/daemon/conversation-tool-setup.ts +18 -87
- package/src/daemon/conversation-usage.ts +36 -0
- package/src/daemon/conversation.ts +134 -231
- package/src/daemon/daemon-control.ts +3 -71
- package/src/daemon/daemon-skill-host.ts +8 -11
- package/src/daemon/dictation-profile-store.ts +2 -26
- package/src/daemon/doordash-steps.ts +1 -1
- package/src/daemon/first-greeting.ts +44 -156
- package/src/daemon/handlers/config-channels.ts +12 -12
- package/src/daemon/handlers/config-ingress.ts +4 -165
- package/src/daemon/handlers/config-model.ts +1 -1
- package/src/daemon/handlers/config-voice.ts +0 -42
- package/src/daemon/handlers/conversations.ts +11 -190
- package/src/daemon/handlers/recording.ts +26 -158
- package/src/daemon/handlers/shared.ts +27 -72
- package/src/daemon/handlers/skills.ts +42 -93
- package/src/daemon/host-app-control-proxy.ts +293 -0
- package/src/daemon/host-bash-proxy.ts +124 -92
- package/src/daemon/host-browser-proxy.ts +111 -88
- package/src/daemon/host-cu-proxy.ts +100 -104
- package/src/daemon/host-file-proxy.ts +136 -91
- package/src/daemon/host-proxy-base.ts +294 -0
- package/src/daemon/host-proxy-preactivation.ts +82 -0
- package/src/daemon/host-transfer-proxy.ts +303 -147
- package/src/daemon/lifecycle.ts +164 -132
- package/src/daemon/message-protocol.ts +3 -8
- package/src/daemon/message-types/contacts.ts +23 -1
- package/src/daemon/message-types/conversations.ts +18 -8
- package/src/daemon/message-types/host-app-control.ts +150 -0
- package/src/daemon/message-types/host-bash.ts +5 -0
- package/src/daemon/message-types/host-cu.ts +3 -0
- package/src/daemon/message-types/host-file.ts +5 -0
- package/src/daemon/message-types/host-transfer.ts +4 -0
- package/src/daemon/message-types/messages.ts +10 -9
- package/src/daemon/message-types/schedules.ts +8 -3
- package/src/daemon/message-types/skills.ts +2 -2
- package/src/daemon/message-types/workspace.ts +1 -1
- package/src/daemon/process-message.ts +119 -239
- package/src/daemon/server.ts +13 -462
- package/src/daemon/shutdown-handlers.ts +2 -5
- package/src/daemon/tool-setup-types.ts +51 -0
- package/src/daemon/tool-side-effects.ts +126 -108
- package/src/daemon/trust-context.ts +13 -0
- package/src/daemon/wake-target-adapter.ts +4 -9
- package/src/events/domain-events.ts +0 -8
- package/src/events/tool-audit-listener.ts +5 -2
- package/src/events/tool-domain-event-publisher.ts +0 -10
- package/src/events/tool-metrics-listener.ts +0 -17
- package/src/events/tool-trace-listener.ts +0 -14
- package/src/filing/filing-service.ts +13 -1
- package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +21 -9
- package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +216 -0
- package/src/heartbeat/heartbeat-run-store.ts +236 -0
- package/src/heartbeat/heartbeat-service.ts +303 -54
- package/src/home/__tests__/feed-writer.test.ts +0 -4
- package/src/home/__tests__/post-connect-feed.test.ts +99 -0
- package/src/home/__tests__/relationship-state-writer.test.ts +41 -9
- package/src/home/__tests__/suggested-prompts.test.ts +89 -0
- package/src/home/feed-writer.ts +1 -2
- package/src/home/post-connect-feed.ts +68 -0
- package/src/home/relationship-state-writer.ts +33 -95
- package/src/home/suggested-prompts.ts +46 -10
- package/src/inbound/public-ingress-urls.ts +32 -34
- package/src/ipc/__tests__/browser-ipc.test.ts +2 -12
- package/src/ipc/__tests__/route-error-envelope.test.ts +80 -0
- package/src/ipc/__tests__/skill-server-bidirectional.test.ts +0 -1
- package/src/ipc/assistant-server.ts +17 -11
- package/src/ipc/cli-client.ts +32 -1
- package/src/ipc/routes/__tests__/memory-v2-backfill.test.ts +39 -20
- package/src/ipc/routes/route-adapter.ts +1 -1
- package/src/ipc/routes/trust-rules.test.ts +0 -95
- package/src/ipc/skill-ipc-types.ts +41 -0
- package/src/ipc/skill-routes/__tests__/events-ipc.test.ts +13 -27
- package/src/ipc/skill-routes/__tests__/identity.test.ts +4 -23
- package/src/ipc/skill-routes/events.ts +12 -23
- package/src/ipc/skill-routes/identity.ts +4 -17
- package/src/ipc/skill-routes/index.ts +1 -1
- package/src/ipc/skill-server.ts +6 -39
- package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +0 -8
- package/src/live-voice/live-voice-metrics.ts +10 -10
- package/src/live-voice/protocol.ts +4 -13
- package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +304 -0
- package/src/mcp/manager.ts +0 -5
- package/src/mcp/mcp-auth-orchestrator.ts +213 -0
- package/src/mcp/mcp-auth-state.ts +133 -0
- package/src/mcp/mcp-oauth-provider.ts +19 -0
- package/src/memory/__tests__/fixtures/memory-v2-activation-fixtures.ts +55 -0
- package/src/memory/__tests__/jobs-store-job-classes.test.ts +24 -0
- package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +127 -0
- package/src/memory/__tests__/qdrant-client-sentinel.test.ts +49 -0
- package/src/memory/__tests__/sparse-tokenize.test.ts +66 -0
- package/src/memory/anisotropy.test.ts +247 -0
- package/src/memory/anisotropy.ts +443 -0
- package/src/memory/app-git-service.ts +0 -32
- package/src/memory/app-store.ts +154 -0
- package/src/memory/attachments-store.ts +6 -0
- package/src/memory/auto-analysis-constants.ts +17 -0
- package/src/memory/auto-analysis-guard.ts +5 -15
- package/src/memory/canonical-guardian-store.ts +7 -7
- package/src/memory/context-search/__tests__/agent-runner-redaction.test.ts +122 -0
- package/src/memory/context-search/agent-protocol.ts +6 -6
- package/src/memory/context-search/agent-runner.ts +32 -7
- package/src/memory/context-search/sources/memory-v2.ts +590 -0
- package/src/memory/context-search/sources/memory.ts +5 -0
- package/src/memory/context-search/sources/pkb.ts +10 -1
- package/src/memory/context-search/sources/workspace.ts +3 -2
- package/src/memory/conversation-crud.ts +30 -5
- package/src/memory/conversation-disk-view.ts +1 -5
- package/src/memory/conversation-key-store.ts +2 -15
- package/src/memory/conversation-starter-checkpoints.ts +63 -0
- package/src/memory/db-connection.ts +62 -0
- package/src/memory/db-init.ts +18 -0
- package/src/memory/embedding-backend.ts +12 -42
- package/src/memory/embedding-gemini.ts +0 -2
- package/src/memory/embedding-local.ts +6 -6
- package/src/memory/embedding-ollama.ts +6 -6
- package/src/memory/embedding-openai.ts +6 -6
- package/src/memory/embedding-types.ts +21 -0
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +49 -8
- package/src/memory/graph/conversation-graph-memory.ts +35 -36
- package/src/memory/graph/graph-search.ts +8 -0
- package/src/memory/graph/injection.test.ts +2 -2
- package/src/memory/graph/injection.ts +1 -1
- package/src/memory/graph/retriever.ts +28 -0
- package/src/memory/graph/tools.ts +1 -1
- package/src/memory/guardian-action-store.ts +0 -83
- package/src/memory/guardian-approvals.ts +0 -48
- package/src/memory/indexer.ts +1 -15
- package/src/memory/job-handlers/conversation-starters.ts +36 -53
- package/src/memory/job-utils.ts +0 -6
- package/src/memory/jobs/__tests__/embed-concept-page.test.ts +8 -2
- package/src/memory/jobs/embed-concept-page.ts +28 -2
- package/src/memory/jobs/embed-pkb-file.test.ts +2 -2
- package/src/memory/jobs-store.ts +66 -23
- package/src/memory/jobs-worker.ts +114 -79
- package/src/memory/llm-request-log-store.ts +0 -41
- package/src/memory/llm-usage-store.ts +129 -43
- package/src/memory/memory-v2-activation-log-store.ts +115 -0
- package/src/memory/migrations/233-document-conversations.ts +54 -0
- package/src/memory/migrations/234-memory-v2-activation-logs.ts +55 -0
- package/src/memory/migrations/235-llm-usage-attribution.ts +31 -0
- package/src/memory/migrations/235-slack-compaction-watermark.ts +44 -0
- package/src/memory/migrations/236-tool-invocations-matched-rule-id.ts +26 -0
- package/src/memory/migrations/237-heartbeat-runs.ts +45 -0
- package/src/memory/migrations/238-schedule-retry-policy.ts +20 -0
- package/src/memory/migrations/__tests__/234-memory-v2-activation-logs.test.ts +182 -0
- package/src/memory/migrations/index.ts +19 -0
- package/src/memory/migrations/registry.ts +32 -0
- package/src/memory/pkb/pkb-search.ts +7 -0
- package/src/memory/qdrant-client.ts +50 -20
- package/src/memory/raw-query.ts +2 -68
- package/src/memory/schema/conversations.ts +7 -0
- package/src/memory/schema/infrastructure.ts +40 -0
- package/src/memory/search/semantic.ts +12 -16
- package/src/memory/sparse-tokenize.ts +49 -0
- package/src/memory/tool-usage-store.ts +2 -0
- package/src/memory/usage-buckets.ts +40 -1
- package/src/memory/usage-grouped-buckets.ts +127 -0
- package/src/memory/v2/__tests__/activation.test.ts +361 -180
- package/src/memory/v2/__tests__/backfill-jobs.test.ts +2 -129
- package/src/memory/v2/__tests__/consolidation-job.test.ts +28 -11
- package/src/memory/v2/__tests__/edge-index.test.ts +278 -0
- package/src/memory/v2/__tests__/injection.test.ts +424 -33
- package/src/memory/v2/__tests__/migration.test.ts +64 -36
- package/src/memory/v2/__tests__/page-store.test.ts +191 -8
- package/src/memory/v2/__tests__/prompts-consolidation.test.ts +181 -0
- package/src/memory/v2/__tests__/sim.test.ts +166 -6
- package/src/memory/v2/__tests__/skill-store.test.ts +115 -3
- package/src/memory/v2/__tests__/sparse-bm25.test.ts +292 -0
- package/src/memory/v2/__tests__/static-context.test.ts +152 -0
- package/src/memory/v2/activation.ts +215 -163
- package/src/memory/v2/backfill-jobs.ts +15 -100
- package/src/memory/v2/consolidation-job.ts +17 -17
- package/src/memory/v2/constants.ts +7 -0
- package/src/memory/v2/edge-index.ts +191 -0
- package/src/memory/v2/injection.ts +241 -84
- package/src/memory/v2/migration.ts +57 -64
- package/src/memory/v2/now-text.ts +2 -3
- package/src/memory/v2/page-store.ts +168 -31
- package/src/memory/v2/prompts/consolidation.ts +385 -88
- package/src/memory/v2/prompts/sweep.ts +3 -3
- package/src/memory/v2/qdrant.ts +99 -1
- package/src/memory/v2/sim.ts +126 -16
- package/src/memory/v2/skill-qdrant.ts +12 -3
- package/src/memory/v2/skill-store.ts +71 -8
- package/src/memory/v2/sparse-bm25.ts +245 -0
- package/src/memory/v2/static-context.ts +63 -0
- package/src/memory/v2/types.ts +10 -20
- package/src/memory/validation.ts +0 -11
- package/src/messaging/draft-store.ts +0 -6
- package/src/messaging/provider-types.ts +8 -0
- package/src/messaging/provider.ts +7 -0
- package/src/messaging/providers/gmail/client.ts +1 -121
- package/src/messaging/providers/gmail/types.ts +0 -49
- package/src/messaging/providers/outlook/client.ts +0 -73
- package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +226 -0
- package/src/messaging/providers/slack/adapter.ts +123 -52
- package/src/messaging/providers/slack/backfill.test.ts +95 -6
- package/src/messaging/providers/slack/backfill.ts +89 -11
- package/src/messaging/providers/slack/client.ts +10 -124
- package/src/messaging/providers/slack/message-metadata.ts +12 -2
- package/src/messaging/providers/slack/render-transcript.test.ts +56 -0
- package/src/messaging/providers/slack/render-transcript.ts +126 -25
- package/src/messaging/providers/slack/types.ts +1 -32
- package/src/notifications/README.md +10 -10
- package/src/notifications/broadcaster.ts +1 -1
- package/src/notifications/guardian-question-mode.ts +5 -5
- package/src/oauth/connect-orchestrator.ts +4 -0
- package/src/oauth/connection-resolver.test.ts +8 -0
- package/src/oauth/connection-resolver.ts +8 -16
- package/src/oauth/credential-token-resolver.ts +95 -0
- package/src/oauth/manual-token-connection.ts +26 -34
- package/src/oauth/oauth-store.ts +6 -4
- package/src/outbound-proxy/certs.ts +0 -7
- package/src/outbound-proxy/index.ts +1 -59
- package/src/outbound-proxy/logging.ts +1 -1
- package/src/outbound-proxy/policy.ts +6 -5
- package/src/outbound-proxy/router.ts +2 -1
- package/src/permissions/approval-policy.test.ts +6 -275
- package/src/permissions/approval-policy.ts +0 -51
- package/src/permissions/approval-provenance.test.ts +184 -0
- package/src/permissions/approval-provenance.ts +70 -0
- package/src/permissions/checker.test.ts +0 -1
- package/src/permissions/checker.ts +7 -18
- package/src/permissions/gateway-threshold-reader.ts +6 -1
- package/src/permissions/prompter.ts +43 -3
- package/src/permissions/secret-prompter.ts +25 -48
- package/src/permissions/types.ts +33 -0
- package/src/permissions/workspace-policy.ts +0 -5
- package/src/platform/sync-identity.ts +0 -8
- package/src/plugins/defaults/injectors.ts +69 -2
- package/src/plugins/defaults/overflow-reduce.ts +3 -2
- package/src/plugins/types.ts +8 -0
- package/src/prompts/bootstrap-cleanup.ts +27 -0
- package/src/prompts/system-prompt.ts +37 -88
- package/src/prompts/templates/BOOTSTRAP.md +52 -6
- package/src/prompts/templates/SOUL.md +13 -1
- package/src/prompts/update-bulletin-job.ts +2 -0
- package/src/providers/__tests__/retry-callsite.test.ts +138 -1
- package/src/providers/anthropic/client.ts +72 -33
- package/src/providers/call-site-routing.ts +42 -3
- package/src/providers/gemini/client.ts +18 -2
- package/src/providers/managed-proxy/context.ts +0 -5
- package/src/providers/model-catalog.ts +105 -19
- package/src/providers/openai/chat-completions-provider.ts +6 -0
- package/src/providers/openai/responses-provider.ts +7 -1
- package/src/providers/provider-send-message.ts +45 -2
- package/src/providers/ratelimit.ts +7 -2
- package/src/providers/registry.ts +14 -9
- package/src/providers/retry.ts +96 -8
- package/src/providers/speech-to-text/provider-catalog.ts +7 -8
- package/src/providers/types.ts +13 -0
- package/src/providers/usage-tracking.ts +96 -0
- package/src/runtime/AGENTS.md +10 -6
- package/src/runtime/__tests__/agent-wake.test.ts +89 -0
- package/src/runtime/agent-wake.ts +39 -2
- package/src/runtime/assistant-event-hub.ts +570 -52
- package/src/runtime/assistant-event.ts +2 -6
- package/src/runtime/auth/__tests__/middleware.test.ts +11 -56
- package/src/runtime/auth/context.ts +0 -9
- package/src/runtime/auth/middleware.ts +1 -97
- package/src/runtime/auth/route-policy.ts +30 -9
- package/src/runtime/auth/token-service.ts +0 -11
- package/src/runtime/btw-sidechain.ts +2 -3
- package/src/runtime/channel-approvals.ts +6 -2
- package/src/runtime/channel-invite-transport.ts +2 -48
- package/src/runtime/channel-invite-transports/email.ts +1 -1
- package/src/runtime/channel-invite-transports/slack.ts +1 -1
- package/src/runtime/channel-invite-transports/telegram.ts +1 -1
- package/src/runtime/channel-invite-transports/voice.ts +1 -1
- package/src/runtime/channel-invite-transports/whatsapp.ts +1 -1
- package/src/runtime/channel-invite-types.ts +54 -0
- package/src/runtime/channel-readiness-service.ts +32 -13
- package/src/runtime/channel-verification-service.ts +3 -5
- package/src/runtime/http-errors.ts +0 -34
- package/src/runtime/http-router.ts +6 -3
- package/src/runtime/http-server.ts +16 -402
- package/src/runtime/http-types.ts +5 -5
- package/src/runtime/interactive-ui.ts +0 -1
- package/src/runtime/middleware/auth.ts +0 -20
- package/src/runtime/migrations/__tests__/v1-test-helpers.ts +112 -0
- package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +11 -4
- package/src/runtime/migrations/__tests__/vbundle-builder-v1-shape.test.ts +253 -0
- package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +19 -6
- package/src/runtime/migrations/__tests__/vbundle-import-parity.test.ts +413 -0
- package/src/runtime/migrations/__tests__/vbundle-import-policy.test.ts +260 -0
- package/src/runtime/migrations/__tests__/vbundle-import-version-compat.test.ts +189 -0
- package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +71 -27
- package/src/runtime/migrations/__tests__/vbundle-metadata-merge-integration.test.ts +41 -2
- package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +296 -80
- package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +143 -23
- package/src/runtime/migrations/__tests__/vbundle-symlink-importer.test.ts +451 -0
- package/src/runtime/migrations/__tests__/vbundle-symlink-streaming-importer.test.ts +0 -0
- package/src/runtime/migrations/__tests__/vbundle-symlink-streaming.test.ts +515 -0
- package/src/runtime/migrations/__tests__/vbundle-symlink-tar.test.ts +437 -0
- package/src/runtime/migrations/__tests__/vbundle-symlink-walker.test.ts +319 -0
- package/src/runtime/migrations/__tests__/vbundle-tar-stream.test.ts +2 -2
- package/src/runtime/migrations/__tests__/vbundle-validator-v1-schema.test.ts +421 -0
- package/src/runtime/migrations/migration-transport.ts +49 -16
- package/src/runtime/migrations/migration-wizard.ts +2 -2
- package/src/runtime/migrations/origin-mode.ts +40 -0
- package/src/runtime/migrations/vbundle-builder.ts +457 -136
- package/src/runtime/migrations/vbundle-import-analyzer.ts +13 -11
- package/src/runtime/migrations/vbundle-import-policy.ts +172 -0
- package/src/runtime/migrations/vbundle-importer.ts +251 -74
- package/src/runtime/migrations/vbundle-metadata-merge.ts +1 -1
- package/src/runtime/migrations/vbundle-streaming-importer.ts +329 -38
- package/src/runtime/migrations/vbundle-streaming-validator.ts +203 -28
- package/src/runtime/migrations/vbundle-tar-stream.ts +15 -6
- package/src/runtime/migrations/vbundle-validator.ts +328 -41
- package/src/runtime/pending-interactions.ts +48 -13
- package/src/runtime/routes/__tests__/acp-routes.test.ts +0 -1
- package/src/runtime/routes/__tests__/backup-routes.test.ts +49 -168
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +333 -0
- package/src/runtime/routes/__tests__/gateway-log-routes.test.ts +242 -0
- package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +112 -0
- package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +58 -0
- package/src/runtime/routes/__tests__/migration-export-secrets-redacted.test.ts +54 -0
- package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +19 -6
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +7 -7
- package/src/runtime/routes/acp-routes.test.ts +0 -3
- package/src/runtime/routes/acp-routes.ts +3 -7
- package/src/runtime/routes/app-management-routes.ts +18 -9
- package/src/runtime/routes/approval-interception-types.ts +13 -0
- package/src/runtime/routes/approval-routes.ts +55 -14
- package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +1 -1
- package/src/runtime/routes/avatar-routes.ts +3 -5
- package/src/runtime/routes/backup-routes.ts +15 -38
- package/src/runtime/routes/browser-routes.ts +1 -15
- package/src/runtime/routes/btw-routes.ts +14 -37
- package/src/runtime/routes/channel-guardian-routes.ts +1 -5
- package/src/runtime/routes/channel-readiness-routes.ts +3 -7
- package/src/runtime/routes/channel-route-shared.ts +2 -28
- package/src/runtime/routes/client-routes.ts +46 -12
- package/src/runtime/routes/consolidation-routes.ts +115 -0
- package/src/runtime/routes/contact-prompt-routes.ts +183 -0
- package/src/runtime/routes/conversation-list-routes.ts +12 -29
- package/src/runtime/routes/conversation-management-routes.ts +14 -51
- package/src/runtime/routes/conversation-query-routes.ts +156 -9
- package/src/runtime/routes/conversation-routes.ts +72 -539
- package/src/runtime/routes/conversation-starter-routes.ts +19 -40
- package/src/runtime/routes/document-pdf-renderer.ts +165 -0
- package/src/runtime/routes/documents-routes.ts +83 -18
- package/src/runtime/routes/errors.ts +19 -4
- package/src/runtime/routes/events-routes.ts +68 -94
- package/src/runtime/routes/filing-routes.ts +18 -1
- package/src/runtime/routes/gateway-log-routes.ts +79 -0
- package/src/runtime/routes/guardian-action-routes.ts +4 -9
- package/src/runtime/routes/guardian-approval-interception.ts +2 -8
- package/src/runtime/routes/heartbeat-routes.ts +103 -38
- package/src/runtime/routes/host-app-control-routes.ts +134 -0
- package/src/runtime/routes/host-bash-routes.ts +37 -6
- package/src/runtime/routes/host-browser-routes.ts +96 -25
- package/src/runtime/routes/host-cu-routes.ts +48 -13
- package/src/runtime/routes/host-file-routes.ts +35 -11
- package/src/runtime/routes/host-transfer-routes.ts +73 -37
- package/src/runtime/routes/http-adapter.ts +1 -0
- package/src/runtime/routes/identity-intro-cache.ts +30 -0
- package/src/runtime/routes/identity-routes.ts +93 -49
- package/src/runtime/routes/inbound-message-handler.ts +581 -146
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +2 -95
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +3 -0
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +0 -8
- package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +0 -20
- package/src/runtime/routes/inbound-stages/transcribe-audio.ts +5 -13
- package/src/runtime/routes/index.ts +12 -0
- package/src/runtime/routes/integrations/slack/channel.ts +0 -24
- package/src/runtime/routes/llm-call-sites-routes.ts +22 -0
- package/src/runtime/routes/mcp-auth-routes.ts +132 -0
- package/src/runtime/routes/memory-item-routes.ts +10 -12
- package/src/runtime/routes/memory-v2-routes.ts +451 -16
- package/src/runtime/routes/migration-routes.ts +284 -31
- package/src/runtime/routes/playground/guard.ts +1 -1
- package/src/runtime/routes/playground/index.ts +0 -2
- package/src/runtime/routes/recording-routes.ts +4 -24
- package/src/runtime/routes/rename-conversation-routes.ts +2 -6
- package/src/runtime/routes/schedule-routes.ts +10 -6
- package/src/runtime/routes/secret-routes.ts +87 -18
- package/src/runtime/routes/settings-routes.ts +29 -28
- package/src/runtime/routes/skills-routes.ts +12 -31
- package/src/runtime/routes/suggest-trust-rule-routes.ts +32 -1
- package/src/runtime/routes/task-routes.ts +6 -6
- package/src/runtime/routes/trust-rules-routes.ts +3 -94
- package/src/runtime/routes/types.ts +4 -4
- package/src/runtime/routes/upgrade-broadcast-routes.ts +3 -10
- package/src/runtime/routes/usage-routes.ts +87 -10
- package/src/runtime/routes/user-routes.ts +17 -31
- package/src/runtime/routes/work-items-routes.ts +1 -4
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +2 -2
- package/src/runtime/services/analyze-conversation.ts +7 -17
- package/src/runtime/services/conversation-serializer.ts +2 -4
- package/src/runtime/verification-outbound-actions.ts +1 -1
- package/src/runtime/verification-rate-limiter.ts +1 -1
- package/src/runtime/verification-templates.ts +4 -7
- package/src/schedule/integration-status.ts +66 -2
- package/src/schedule/recurrence-engine.ts +4 -1
- package/src/schedule/retry-backoff.ts +18 -0
- package/src/schedule/retry-policy.ts +82 -0
- package/src/schedule/schedule-recovery.ts +64 -0
- package/src/schedule/schedule-store.ts +106 -18
- package/src/schedule/scheduler-types.ts +25 -0
- package/src/schedule/scheduler.ts +63 -38
- package/src/security/oauth-callback-registry.ts +8 -0
- package/src/security/secret-scanner.ts +14 -547
- package/src/security/secure-keys.ts +31 -11
- package/src/security/token-manager.ts +7 -3
- package/src/sequence/analytics.ts +5 -5
- package/src/sequence/engine.ts +1 -1
- package/src/signals/cancel.ts +16 -25
- package/src/signals/conversation-undo.ts +2 -27
- package/src/signals/emit-event.ts +1 -2
- package/src/signals/user-message.ts +108 -22
- package/src/skills/catalog-files.ts +2 -8
- package/src/skills/catalog-install.ts +1 -0
- package/src/skills/clawhub.ts +2 -2
- package/src/skills/include-graph.ts +5 -5
- package/src/skills/inline-command-runner.ts +1 -7
- package/src/skills/remote-skill-policy.ts +5 -5
- package/src/skills/skill-file-provider.ts +1 -1
- package/src/skills/skill-file-types.ts +13 -0
- package/src/skills/skillssh-audit-types.ts +28 -0
- package/src/skills/skillssh-registry.ts +8 -21
- package/src/subagent/manager.ts +67 -84
- package/src/tasks/task-store.ts +1 -28
- package/src/telemetry/types.ts +8 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +59 -15
- package/src/telemetry/usage-telemetry-reporter.ts +4 -5
- package/src/tools/acp/spawn.test.ts +1 -2
- package/src/tools/acp/steer.test.ts +1 -2
- package/src/tools/app-control/skill-proxy-bridge.ts +28 -0
- package/src/tools/apps/executors.ts +56 -69
- package/src/tools/browser/__tests__/browser-status.test.ts +55 -135
- package/src/tools/browser/browser-execution.ts +31 -147
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +145 -70
- package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +12 -6
- package/src/tools/browser/cdp-client/factory.ts +62 -91
- package/src/tools/browser/cdp-client/index.ts +1 -27
- package/src/tools/computer-use/definitions.ts +42 -20
- package/src/tools/executor.ts +46 -31
- package/src/tools/host-filesystem/edit.ts +29 -2
- package/src/tools/host-filesystem/read.ts +29 -2
- package/src/tools/host-filesystem/transfer.test.ts +45 -42
- package/src/tools/host-filesystem/transfer.ts +35 -4
- package/src/tools/host-filesystem/write.ts +29 -2
- package/src/tools/host-terminal/host-shell.ts +62 -3
- package/src/tools/network/script-proxy/index.ts +1 -10
- package/src/tools/permission-checker.ts +66 -1
- package/src/tools/schedule/create.ts +6 -0
- package/src/tools/schedule/list.ts +2 -0
- package/src/tools/schedule/update.ts +10 -0
- package/src/tools/shared/filesystem/file-ops-service.ts +2 -0
- package/src/tools/shared/filesystem/path-policy.ts +25 -1
- package/src/tools/skills/load.ts +0 -32
- package/src/tools/skills/sandbox-runner.ts +1 -6
- package/src/tools/skills/skill-tool-factory.ts +32 -0
- package/src/tools/terminal/safe-env.ts +1 -0
- package/src/tools/terminal/shell.ts +2 -78
- package/src/tools/tool-approval-handler.ts +1 -5
- package/src/tools/types.ts +16 -39
- package/src/tts/__tests__/provider-catalog.test.ts +2 -2
- package/src/tts/provider-catalog.ts +1 -1
- package/src/usage/actors.ts +2 -1
- package/src/usage/attribution.ts +185 -0
- package/src/usage/pricing.ts +166 -0
- package/src/usage/types.ts +14 -0
- package/src/util/json.ts +13 -0
- package/src/util/logger.ts +3 -3
- package/src/util/pricing.ts +50 -3
- package/src/work-items/work-item-runner.ts +15 -42
- package/src/workspace/hatched-date.ts +86 -0
- package/src/workspace/migrations/003-seed-device-id.ts +1 -1
- package/src/workspace/migrations/006-services-config.ts +8 -5
- package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +3 -9
- package/src/workspace/migrations/021-move-signals-to-workspace.ts +4 -10
- package/src/workspace/migrations/022-move-hooks-to-workspace.ts +4 -10
- package/src/workspace/migrations/023-move-config-files-to-workspace.ts +4 -11
- package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +3 -10
- package/src/workspace/migrations/040-seed-latency-callsite-defaults.ts +3 -2
- package/src/workspace/migrations/050-seed-main-agent-opus-callsite.ts +6 -4
- package/src/workspace/migrations/052-seed-default-inference-profiles.ts +3 -3
- package/src/workspace/migrations/059-move-pid-to-workspace.ts +3 -8
- package/src/workspace/migrations/060-memory-v2-init.ts +2 -18
- package/src/workspace/migrations/061-move-backup-key-to-workspace.ts +54 -0
- package/src/workspace/migrations/062-drop-memory-v2-edges-json.ts +27 -0
- package/src/workspace/migrations/063-release-notes-dynamic-model-context.ts +70 -0
- package/src/workspace/migrations/064-unwind-main-agent-opus-seed.ts +64 -0
- package/src/workspace/migrations/AGENTS.md +1 -1
- package/src/workspace/migrations/migrate-to-workspace-volume.ts +4 -10
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/workspace/migrations/utils.ts +21 -0
- package/src/workspace/provider-commit-message-generator.ts +3 -3
- package/src/__tests__/host-browser-e2e-cloud.test.ts +0 -904
- package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +0 -296
- package/src/__tests__/host-browser-ws-events-e2e.test.ts +0 -431
- package/src/__tests__/sandbox-diagnostics.test.ts +0 -138
- package/src/__tests__/sandbox-host-parity.test.ts +0 -1024
- package/src/__tests__/secret-detection-handler.test.ts +0 -67
- package/src/__tests__/secret-scanner-executor.test.ts +0 -450
- package/src/__tests__/tcc-sandbox-deny.test.ts +0 -198
- package/src/__tests__/terminal-sandbox.test.ts +0 -374
- package/src/__tests__/tool-notification-listener.test.ts +0 -65
- package/src/__tests__/twilio-rest.test.ts +0 -34
- package/src/backup/__tests__/backup-key.test.ts +0 -152
- package/src/backup/__tests__/backup-worker.test.ts +0 -754
- package/src/backup/__tests__/offsite-writer.test.ts +0 -641
- package/src/backup/__tests__/stream-crypt.test.ts +0 -228
- package/src/backup/backup-key.ts +0 -137
- package/src/backup/backup-worker.ts +0 -438
- package/src/backup/offsite-writer.ts +0 -222
- package/src/backup/stream-crypt.ts +0 -263
- package/src/context/__tests__/microcompact.test.ts +0 -805
- package/src/context/microcompact.ts +0 -443
- package/src/daemon/handlers/slack-channel-oauth-install.ts +0 -197
- package/src/daemon/message-types/pairing.ts +0 -58
- package/src/events/tool-notification-listener.ts +0 -17
- package/src/ipc/routes/__tests__/memory-v2-validate.test.ts +0 -219
- package/src/memory/v2/__tests__/edges.test.ts +0 -435
- package/src/memory/v2/edges.ts +0 -217
- package/src/outbound-proxy/config.ts +0 -94
- package/src/outbound-proxy/health.ts +0 -62
- package/src/outbound-proxy/types.ts +0 -150
- package/src/prompts/__tests__/system-prompt-memory-v2.test.ts +0 -197
- package/src/runtime/__tests__/chrome-extension-registry.test.ts +0 -518
- package/src/runtime/__tests__/client-registry.test.ts +0 -271
- package/src/runtime/capability-tokens.ts +0 -190
- package/src/runtime/chrome-extension-registry.ts +0 -368
- package/src/runtime/client-registry.ts +0 -254
- package/src/runtime/routes/inbound-stages/verification-intercept.ts +0 -329
- package/src/signals/mcp-reload.ts +0 -18
- package/src/tools/secret-detection-handler.ts +0 -269
- package/src/tools/terminal/backends/native.ts +0 -327
- package/src/tools/terminal/backends/types.ts +0 -37
- package/src/tools/terminal/sandbox-diagnostics.ts +0 -87
- package/src/tools/terminal/sandbox.ts +0 -40
|
@@ -11,7 +11,6 @@ import {
|
|
|
11
11
|
isChannelId,
|
|
12
12
|
parseInterfaceId,
|
|
13
13
|
} from "../../channels/types.js";
|
|
14
|
-
import { touchContactInteraction } from "../../contacts/contacts-write.js";
|
|
15
14
|
import {
|
|
16
15
|
createApprovalConversationGenerator,
|
|
17
16
|
createApprovalCopyGenerator,
|
|
@@ -45,11 +44,13 @@ import { upsertBinding } from "../../memory/external-conversation-store.js";
|
|
|
45
44
|
import type { Message as ProviderMessage } from "../../messaging/provider-types.js";
|
|
46
45
|
import {
|
|
47
46
|
backfillDm,
|
|
48
|
-
|
|
47
|
+
backfillThreadWindowPage,
|
|
48
|
+
type SlackBackfillWindowPage,
|
|
49
49
|
} from "../../messaging/providers/slack/backfill.js";
|
|
50
50
|
import {
|
|
51
51
|
mergeSlackMetadata,
|
|
52
52
|
readSlackMetadata,
|
|
53
|
+
type SlackFileMetadata,
|
|
53
54
|
type SlackMessageMetadata,
|
|
54
55
|
writeSlackMetadata,
|
|
55
56
|
} from "../../messaging/providers/slack/message-metadata.js";
|
|
@@ -71,7 +72,6 @@ import { handleGuardianActivationIntercept } from "./inbound-stages/guardian-act
|
|
|
71
72
|
import { handleGuardianReplyIntercept } from "./inbound-stages/guardian-reply-intercept.js";
|
|
72
73
|
import { runSecretIngressCheck } from "./inbound-stages/secret-ingress-check.js";
|
|
73
74
|
import { tryTranscribeAudioAttachments } from "./inbound-stages/transcribe-audio.js";
|
|
74
|
-
import { handleVerificationIntercept } from "./inbound-stages/verification-intercept.js";
|
|
75
75
|
import type { RouteHandlerArgs } from "./types.js";
|
|
76
76
|
|
|
77
77
|
const log = getLogger("runtime-http");
|
|
@@ -263,7 +263,7 @@ export async function handleChannelInbound({
|
|
|
263
263
|
externalMessageId,
|
|
264
264
|
});
|
|
265
265
|
if (aclResult.earlyResponse) return aclResult.earlyResponse;
|
|
266
|
-
const { resolvedMember
|
|
266
|
+
const { resolvedMember } = aclResult;
|
|
267
267
|
|
|
268
268
|
// ── Slack delete propagation ──
|
|
269
269
|
// Slack message_deleted events are forwarded by the gateway with the
|
|
@@ -286,7 +286,7 @@ export async function handleChannelInbound({
|
|
|
286
286
|
{ conversationExternalId },
|
|
287
287
|
"Slack message_deleted event missing sourceMetadata.messageId; ignoring",
|
|
288
288
|
);
|
|
289
|
-
return
|
|
289
|
+
return { accepted: true, deleted: false };
|
|
290
290
|
}
|
|
291
291
|
|
|
292
292
|
// Look up the stored message via the existing channel-event lookup.
|
|
@@ -327,7 +327,7 @@ export async function handleChannelInbound({
|
|
|
327
327
|
{ conversationExternalId, deletedMessageTs },
|
|
328
328
|
"No stored message found for Slack delete after retries; ignoring",
|
|
329
329
|
);
|
|
330
|
-
return
|
|
330
|
+
return { accepted: true, deleted: false };
|
|
331
331
|
}
|
|
332
332
|
|
|
333
333
|
// Merge deletedAt into the existing slackMeta sub-key. If the row has
|
|
@@ -345,7 +345,7 @@ export async function handleChannelInbound({
|
|
|
345
345
|
},
|
|
346
346
|
"Stored Slack message has no metadata; skipping delete marker",
|
|
347
347
|
);
|
|
348
|
-
return
|
|
348
|
+
return { accepted: true, deleted: false };
|
|
349
349
|
}
|
|
350
350
|
|
|
351
351
|
let parentMetadata: Record<string, unknown>;
|
|
@@ -365,7 +365,7 @@ export async function handleChannelInbound({
|
|
|
365
365
|
},
|
|
366
366
|
"Failed to parse stored metadata; skipping delete marker",
|
|
367
367
|
);
|
|
368
|
-
return
|
|
368
|
+
return { accepted: true, deleted: false };
|
|
369
369
|
}
|
|
370
370
|
|
|
371
371
|
const existingSlackMeta =
|
|
@@ -382,7 +382,7 @@ export async function handleChannelInbound({
|
|
|
382
382
|
},
|
|
383
383
|
"Stored Slack message has no slackMeta; skipping delete marker",
|
|
384
384
|
);
|
|
385
|
-
return
|
|
385
|
+
return { accepted: true, deleted: false };
|
|
386
386
|
}
|
|
387
387
|
|
|
388
388
|
const updatedSlackMeta = mergeSlackMetadata(existingSlackMeta, {
|
|
@@ -404,11 +404,11 @@ export async function handleChannelInbound({
|
|
|
404
404
|
"Marked Slack message as deleted",
|
|
405
405
|
);
|
|
406
406
|
|
|
407
|
-
return
|
|
407
|
+
return {
|
|
408
408
|
accepted: true,
|
|
409
409
|
deleted: true,
|
|
410
410
|
messageId: original.messageId,
|
|
411
|
-
}
|
|
411
|
+
};
|
|
412
412
|
}
|
|
413
413
|
|
|
414
414
|
if (hasAttachments) {
|
|
@@ -416,7 +416,9 @@ export async function handleChannelInbound({
|
|
|
416
416
|
if (resolved.length !== attachmentIds.length) {
|
|
417
417
|
const resolvedIds = new Set(resolved.map((a) => a.id));
|
|
418
418
|
const missing = attachmentIds.filter((id) => !resolvedIds.has(id));
|
|
419
|
-
throw new BadRequestError(
|
|
419
|
+
throw new BadRequestError(
|
|
420
|
+
`Attachment IDs not found: ${missing.join(", ")}`,
|
|
421
|
+
);
|
|
420
422
|
}
|
|
421
423
|
}
|
|
422
424
|
|
|
@@ -439,7 +441,7 @@ export async function handleChannelInbound({
|
|
|
439
441
|
`[Voice message received — ${transcribeResult.reason}]` +
|
|
440
442
|
(trimmedContent ? `\n\n${trimmedContent}` : "");
|
|
441
443
|
break;
|
|
442
|
-
// "no_audio"
|
|
444
|
+
// "no_audio" — no action needed
|
|
443
445
|
}
|
|
444
446
|
}
|
|
445
447
|
|
|
@@ -500,21 +502,14 @@ export async function handleChannelInbound({
|
|
|
500
502
|
"Retry of pending verification reply failed; will retry on next duplicate",
|
|
501
503
|
);
|
|
502
504
|
}
|
|
503
|
-
return
|
|
505
|
+
return {
|
|
504
506
|
accepted: true,
|
|
505
507
|
duplicate: true,
|
|
506
508
|
eventId: result.eventId,
|
|
507
|
-
}
|
|
509
|
+
};
|
|
508
510
|
}
|
|
509
511
|
}
|
|
510
512
|
|
|
511
|
-
// Track contact interaction only for genuinely new messages (not webhook
|
|
512
|
-
// retries). This was previously in ACL enforcement which runs before dedup,
|
|
513
|
-
// causing retries to inflate interaction counts.
|
|
514
|
-
if (!result.duplicate && resolvedMember) {
|
|
515
|
-
touchContactInteraction(resolvedMember.channel.id);
|
|
516
|
-
}
|
|
517
|
-
|
|
518
513
|
// external_conversation_bindings is assistant-agnostic. Restrict writes to
|
|
519
514
|
// self so assistant-scoped legacy routes do not overwrite each other's
|
|
520
515
|
// channel binding metadata for the same chat.
|
|
@@ -541,10 +536,10 @@ export async function handleChannelInbound({
|
|
|
541
536
|
// guardian approval reactions have no transcript representation.
|
|
542
537
|
// 2. All other reactions (non-guardian, no pending approval, stale,
|
|
543
538
|
// and any `reaction_removed:` event regardless of actor) fall
|
|
544
|
-
// through to `persistSlackReactionAsMessage` so
|
|
545
|
-
//
|
|
546
|
-
//
|
|
547
|
-
//
|
|
539
|
+
// through to `persistSlackReactionAsMessage` so Slack transcript
|
|
540
|
+
// rendering can surface them inline. Reactions never trigger an
|
|
541
|
+
// agent response, so we short-circuit before escalation and
|
|
542
|
+
// agent-loop dispatch in both cases.
|
|
548
543
|
if (isSlackReactionEvent(body)) {
|
|
549
544
|
// Approval interception runs only for reactions (added) — `reaction_removed`
|
|
550
545
|
// never expresses an approval intent, so un-reacting is left as a pure
|
|
@@ -586,12 +581,12 @@ export async function handleChannelInbound({
|
|
|
586
581
|
// transcript line. All other interception outcomes (stale_ignored,
|
|
587
582
|
// non-guardian, no pending approval) fall through to persistence.
|
|
588
583
|
if (reactionApprovalResult.type === "guardian_decision_applied") {
|
|
589
|
-
return
|
|
584
|
+
return {
|
|
590
585
|
accepted: true,
|
|
591
586
|
duplicate: false,
|
|
592
587
|
eventId: result.eventId,
|
|
593
588
|
approval: reactionApprovalResult.type,
|
|
594
|
-
}
|
|
589
|
+
};
|
|
595
590
|
}
|
|
596
591
|
}
|
|
597
592
|
|
|
@@ -604,11 +599,11 @@ export async function handleChannelInbound({
|
|
|
604
599
|
{ conversationId: result.conversationId, eventId: result.eventId },
|
|
605
600
|
"Skipping reaction persistence: missing sourceMetadata.messageId",
|
|
606
601
|
);
|
|
607
|
-
return
|
|
602
|
+
return {
|
|
608
603
|
accepted: result.accepted,
|
|
609
604
|
duplicate: result.duplicate,
|
|
610
605
|
eventId: result.eventId,
|
|
611
|
-
}
|
|
606
|
+
};
|
|
612
607
|
}
|
|
613
608
|
|
|
614
609
|
const threadTs =
|
|
@@ -634,11 +629,11 @@ export async function handleChannelInbound({
|
|
|
634
629
|
);
|
|
635
630
|
}
|
|
636
631
|
|
|
637
|
-
return
|
|
632
|
+
return {
|
|
638
633
|
accepted: result.accepted,
|
|
639
634
|
duplicate: result.duplicate,
|
|
640
635
|
eventId: result.eventId,
|
|
641
|
-
}
|
|
636
|
+
};
|
|
642
637
|
}
|
|
643
638
|
|
|
644
639
|
// ── Ingress escalation ──
|
|
@@ -670,6 +665,7 @@ export async function handleChannelInbound({
|
|
|
670
665
|
typeof hint === "string" && hint.trim().length > 0,
|
|
671
666
|
)
|
|
672
667
|
: [];
|
|
668
|
+
let slackRuntimeContextNotice: string | undefined;
|
|
673
669
|
|
|
674
670
|
// Inject channel-scoped permission hints for Slack channel messages
|
|
675
671
|
if (sourceChannel === "slack") {
|
|
@@ -734,24 +730,6 @@ export async function handleChannelInbound({
|
|
|
734
730
|
});
|
|
735
731
|
if (bootstrapResponse) return bootstrapResponse;
|
|
736
732
|
|
|
737
|
-
// ── Guardian verification code intercept (deterministic) ──
|
|
738
|
-
const verificationResponse = await handleVerificationIntercept({
|
|
739
|
-
isDuplicate: result.duplicate,
|
|
740
|
-
guardianVerifyCode,
|
|
741
|
-
rawSenderId,
|
|
742
|
-
canonicalSenderId,
|
|
743
|
-
canonicalAssistantId,
|
|
744
|
-
sourceChannel,
|
|
745
|
-
conversationExternalId,
|
|
746
|
-
conversationId: result.conversationId,
|
|
747
|
-
eventId: result.eventId,
|
|
748
|
-
replyCallbackUrl,
|
|
749
|
-
assistantId,
|
|
750
|
-
actorDisplayName: body.actorDisplayName,
|
|
751
|
-
actorUsername: body.actorUsername,
|
|
752
|
-
});
|
|
753
|
-
if (verificationResponse) return verificationResponse;
|
|
754
|
-
|
|
755
733
|
// Legacy voice guardian action interception removed — all guardian reply
|
|
756
734
|
// routing now flows through the canonical router below (routeGuardianReply),
|
|
757
735
|
// which handles request code matching, callback parsing, and NL classification
|
|
@@ -862,12 +840,12 @@ export async function handleChannelInbound({
|
|
|
862
840
|
}
|
|
863
841
|
}
|
|
864
842
|
|
|
865
|
-
return
|
|
843
|
+
return {
|
|
866
844
|
accepted: true,
|
|
867
845
|
duplicate: false,
|
|
868
846
|
eventId: result.eventId,
|
|
869
847
|
approval: approvalResult.type,
|
|
870
|
-
}
|
|
848
|
+
};
|
|
871
849
|
}
|
|
872
850
|
|
|
873
851
|
// When a callback payload was not handled by approval interception, it's
|
|
@@ -922,12 +900,12 @@ export async function handleChannelInbound({
|
|
|
922
900
|
});
|
|
923
901
|
}
|
|
924
902
|
|
|
925
|
-
return
|
|
903
|
+
return {
|
|
926
904
|
accepted: true,
|
|
927
905
|
duplicate: false,
|
|
928
906
|
eventId: result.eventId,
|
|
929
907
|
approval: "stale_ignored",
|
|
930
|
-
}
|
|
908
|
+
};
|
|
931
909
|
}
|
|
932
910
|
}
|
|
933
911
|
|
|
@@ -1032,25 +1010,27 @@ export async function handleChannelInbound({
|
|
|
1032
1010
|
});
|
|
1033
1011
|
}
|
|
1034
1012
|
|
|
1035
|
-
// ── Thread
|
|
1036
|
-
// When a Slack reply arrives
|
|
1037
|
-
//
|
|
1038
|
-
//
|
|
1039
|
-
//
|
|
1040
|
-
//
|
|
1041
|
-
//
|
|
1042
|
-
//
|
|
1043
|
-
//
|
|
1044
|
-
//
|
|
1045
|
-
// swallowed inside the helper so they never block dispatch.
|
|
1013
|
+
// ── Thread gap/delta backfill ──
|
|
1014
|
+
// When a Slack thread reply arrives, compare the stored thread state
|
|
1015
|
+
// with the inbound message's ts and fetch only the bounded unseen
|
|
1016
|
+
// window. Initial late-join turns hydrate the earliest thread messages
|
|
1017
|
+
// plus a recent window adjacent to the inbound reply; later turns use
|
|
1018
|
+
// a delta window after the latest stored thread ts and before the
|
|
1019
|
+
// inbound ts. Awaited (mirrors the DM cold-start path above) so the
|
|
1020
|
+
// agent loop dispatched immediately afterwards observes hydrated
|
|
1021
|
+
// context. A late-join notice is added only to the current turn's
|
|
1022
|
+
// runtime context, not persisted as durable Slack metadata. Failures
|
|
1023
|
+
// are swallowed inside the helper so they never block dispatch.
|
|
1046
1024
|
if (slackThreadTs) {
|
|
1047
|
-
await triggerSlackThreadBackfillIfNeeded({
|
|
1025
|
+
const backfillResult = await triggerSlackThreadBackfillIfNeeded({
|
|
1048
1026
|
conversationId: result.conversationId,
|
|
1049
1027
|
channelId: conversationExternalId,
|
|
1050
1028
|
threadTs: slackThreadTs,
|
|
1051
1029
|
excludeChannelTs: slackInbound?.channelTs,
|
|
1052
1030
|
account: slackAccount,
|
|
1053
1031
|
});
|
|
1032
|
+
const lateJoinNotice = buildSlackLateJoinNotice(backfillResult);
|
|
1033
|
+
if (lateJoinNotice) slackRuntimeContextNotice = lateJoinNotice;
|
|
1054
1034
|
}
|
|
1055
1035
|
|
|
1056
1036
|
// Wrap non-guardian inbound content in external_content boundaries so
|
|
@@ -1078,6 +1058,7 @@ export async function handleChannelInbound({
|
|
|
1078
1058
|
externalChatId: conversationExternalId,
|
|
1079
1059
|
trustCtx,
|
|
1080
1060
|
metadataHints,
|
|
1061
|
+
slackRuntimeContextNotice,
|
|
1081
1062
|
metadataUxBrief,
|
|
1082
1063
|
commandIntent,
|
|
1083
1064
|
sourceLanguageCode,
|
|
@@ -1090,11 +1071,11 @@ export async function handleChannelInbound({
|
|
|
1090
1071
|
}
|
|
1091
1072
|
}
|
|
1092
1073
|
|
|
1093
|
-
return
|
|
1074
|
+
return {
|
|
1094
1075
|
accepted: result.accepted,
|
|
1095
1076
|
duplicate: result.duplicate,
|
|
1096
1077
|
eventId: result.eventId,
|
|
1097
|
-
}
|
|
1078
|
+
};
|
|
1098
1079
|
}
|
|
1099
1080
|
|
|
1100
1081
|
/**
|
|
@@ -1193,8 +1174,8 @@ async function persistSlackReactionAsMessage(params: {
|
|
|
1193
1174
|
},
|
|
1194
1175
|
};
|
|
1195
1176
|
|
|
1196
|
-
// Sentinel content —
|
|
1197
|
-
// reaction line; the literal text is never displayed to the model.
|
|
1177
|
+
// Sentinel content — Slack transcript renderers read `slackMeta` to format
|
|
1178
|
+
// the reaction line; the literal text is never displayed to the model.
|
|
1198
1179
|
const persisted = await addMessage(
|
|
1199
1180
|
params.conversationId,
|
|
1200
1181
|
"user",
|
|
@@ -1273,19 +1254,7 @@ function countSlackMetaMessages(conversationId: string): number {
|
|
|
1273
1254
|
);
|
|
1274
1255
|
if (candidates.length === 0) return count;
|
|
1275
1256
|
for (const raw of candidates) {
|
|
1276
|
-
|
|
1277
|
-
try {
|
|
1278
|
-
const parsed = JSON.parse(raw) as unknown;
|
|
1279
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
1280
|
-
parent = parsed as Record<string, unknown>;
|
|
1281
|
-
}
|
|
1282
|
-
} catch {
|
|
1283
|
-
continue;
|
|
1284
|
-
}
|
|
1285
|
-
if (!parent) continue;
|
|
1286
|
-
const inner = parent.slackMeta;
|
|
1287
|
-
if (typeof inner !== "string") continue;
|
|
1288
|
-
if (readSlackMetadata(inner)) {
|
|
1257
|
+
if (readSlackMetadataFromMessageMetadata(raw)) {
|
|
1289
1258
|
count++;
|
|
1290
1259
|
if (count >= SLACK_DM_BACKFILL_WARM_THRESHOLD) return count;
|
|
1291
1260
|
}
|
|
@@ -1296,44 +1265,110 @@ function countSlackMetaMessages(conversationId: string): number {
|
|
|
1296
1265
|
return count;
|
|
1297
1266
|
}
|
|
1298
1267
|
|
|
1268
|
+
function readSlackMetadataFromMessageMetadata(
|
|
1269
|
+
metadata: string | null | undefined,
|
|
1270
|
+
): SlackMessageMetadata | null {
|
|
1271
|
+
if (!metadata) return null;
|
|
1272
|
+
let parent: Record<string, unknown> | null = null;
|
|
1273
|
+
try {
|
|
1274
|
+
const parsed = JSON.parse(metadata) as unknown;
|
|
1275
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
1276
|
+
parent = parsed as Record<string, unknown>;
|
|
1277
|
+
}
|
|
1278
|
+
} catch {
|
|
1279
|
+
return null;
|
|
1280
|
+
}
|
|
1281
|
+
if (!parent) return null;
|
|
1282
|
+
const raw = parent.slackMeta;
|
|
1283
|
+
if (typeof raw !== "string") return null;
|
|
1284
|
+
return readSlackMetadata(raw);
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1299
1287
|
/**
|
|
1300
1288
|
* Build the set of `slackMeta.channelTs` values already stored on a
|
|
1301
|
-
* conversation. Used by both DM cold-start backfill and thread
|
|
1289
|
+
* conversation. Used by both DM cold-start backfill and thread gap/delta
|
|
1302
1290
|
* backfill to dedupe rows so a partial prior backfill (or a single message
|
|
1303
1291
|
* that was already persisted via the live ingress path) does not double-write.
|
|
1304
1292
|
*/
|
|
1305
1293
|
function readStoredSlackChannelTs(conversationId: string): Set<string> {
|
|
1306
1294
|
const seen = new Set<string>();
|
|
1307
1295
|
for (const row of getMessages(conversationId)) {
|
|
1308
|
-
|
|
1309
|
-
let parent: Record<string, unknown> | null = null;
|
|
1310
|
-
try {
|
|
1311
|
-
const parsed = JSON.parse(row.metadata) as unknown;
|
|
1312
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
1313
|
-
parent = parsed as Record<string, unknown>;
|
|
1314
|
-
}
|
|
1315
|
-
} catch {
|
|
1316
|
-
continue;
|
|
1317
|
-
}
|
|
1318
|
-
if (!parent) continue;
|
|
1319
|
-
const raw = parent.slackMeta;
|
|
1320
|
-
if (typeof raw !== "string") continue;
|
|
1321
|
-
const meta = readSlackMetadata(raw);
|
|
1296
|
+
const meta = readSlackMetadataFromMessageMetadata(row.metadata);
|
|
1322
1297
|
// Only message rows represent stored Slack messages. Reaction rows carry
|
|
1323
1298
|
// `channelTs` equal to the target message's ts, so including them would
|
|
1324
|
-
// make a reaction on a thread parent wrongly short-circuit
|
|
1299
|
+
// make a reaction on a thread parent wrongly short-circuit thread
|
|
1325
1300
|
// backfill (the parent itself may still be unseen).
|
|
1326
1301
|
if (meta && meta.eventKind === "message") seen.add(meta.channelTs);
|
|
1327
1302
|
}
|
|
1328
1303
|
return seen;
|
|
1329
1304
|
}
|
|
1330
1305
|
|
|
1306
|
+
interface ParsedSlackTimestamp {
|
|
1307
|
+
seconds: bigint;
|
|
1308
|
+
micros: bigint;
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
function parseSlackTimestamp(
|
|
1312
|
+
ts: string | undefined,
|
|
1313
|
+
): ParsedSlackTimestamp | null {
|
|
1314
|
+
if (!ts) return null;
|
|
1315
|
+
const match = /^(\d+)\.(\d{1,6})$/.exec(ts);
|
|
1316
|
+
if (!match) return null;
|
|
1317
|
+
const micros = BigInt(match[2]);
|
|
1318
|
+
if (micros > 999_999n) return null;
|
|
1319
|
+
return {
|
|
1320
|
+
seconds: BigInt(match[1]),
|
|
1321
|
+
micros,
|
|
1322
|
+
};
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
function compareSlackTimestamps(left: string, right: string): number | null {
|
|
1326
|
+
const parsedLeft = parseSlackTimestamp(left);
|
|
1327
|
+
const parsedRight = parseSlackTimestamp(right);
|
|
1328
|
+
if (!parsedLeft || !parsedRight) return null;
|
|
1329
|
+
if (parsedLeft.seconds < parsedRight.seconds) return -1;
|
|
1330
|
+
if (parsedLeft.seconds > parsedRight.seconds) return 1;
|
|
1331
|
+
if (parsedLeft.micros < parsedRight.micros) return -1;
|
|
1332
|
+
if (parsedLeft.micros > parsedRight.micros) return 1;
|
|
1333
|
+
return 0;
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
interface StoredSlackThreadState {
|
|
1337
|
+
storedChannelTs: Set<string>;
|
|
1338
|
+
latestStoredThreadTs: string | undefined;
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
function readStoredSlackThreadState(
|
|
1342
|
+
conversationId: string,
|
|
1343
|
+
threadTs: string,
|
|
1344
|
+
): StoredSlackThreadState {
|
|
1345
|
+
const storedChannelTs = new Set<string>();
|
|
1346
|
+
let latestStoredThreadTs: string | undefined;
|
|
1347
|
+
|
|
1348
|
+
for (const row of getMessages(conversationId)) {
|
|
1349
|
+
const meta = readSlackMetadataFromMessageMetadata(row.metadata);
|
|
1350
|
+
if (!meta || meta.eventKind !== "message") continue;
|
|
1351
|
+
if (meta.channelTs !== threadTs && meta.threadTs !== threadTs) continue;
|
|
1352
|
+
|
|
1353
|
+
storedChannelTs.add(meta.channelTs);
|
|
1354
|
+
if (!parseSlackTimestamp(meta.channelTs)) continue;
|
|
1355
|
+
if (
|
|
1356
|
+
latestStoredThreadTs === undefined ||
|
|
1357
|
+
compareSlackTimestamps(meta.channelTs, latestStoredThreadTs) === 1
|
|
1358
|
+
) {
|
|
1359
|
+
latestStoredThreadTs = meta.channelTs;
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
return { storedChannelTs, latestStoredThreadTs };
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1331
1366
|
/**
|
|
1332
1367
|
* Persist a single backfilled Slack message as a `messages` row with a
|
|
1333
1368
|
* `slackMeta` envelope.
|
|
1334
1369
|
*
|
|
1335
1370
|
* Shared insertion point for any path that hydrates Slack history lazily
|
|
1336
|
-
* (DM cold-start backfill, thread
|
|
1371
|
+
* (DM cold-start backfill, thread gap/delta backfill, etc.). Role is derived
|
|
1337
1372
|
* from `message.metadata.isBot` — bot-authored rows map to `"assistant"` so
|
|
1338
1373
|
* our own prior replies (and any other bot traffic) are not rehydrated as
|
|
1339
1374
|
* user turns, which would otherwise corrupt speaker attribution and make
|
|
@@ -1347,6 +1382,7 @@ async function persistBackfilledSlackMessage(params: {
|
|
|
1347
1382
|
message: ProviderMessage;
|
|
1348
1383
|
}): Promise<void> {
|
|
1349
1384
|
const { message } = params;
|
|
1385
|
+
const slackFiles = readSlackFilesFromProviderMetadata(message.metadata);
|
|
1350
1386
|
const slackMeta: SlackMessageMetadata = {
|
|
1351
1387
|
source: "slack",
|
|
1352
1388
|
channelId: params.channelId,
|
|
@@ -1354,6 +1390,7 @@ async function persistBackfilledSlackMessage(params: {
|
|
|
1354
1390
|
eventKind: "message",
|
|
1355
1391
|
...(message.threadId ? { threadTs: message.threadId } : {}),
|
|
1356
1392
|
...(message.sender?.name ? { displayName: message.sender.name } : {}),
|
|
1393
|
+
...(slackFiles.length > 0 ? { slackFiles } : {}),
|
|
1357
1394
|
};
|
|
1358
1395
|
const role = message.metadata?.isBot === true ? "assistant" : "user";
|
|
1359
1396
|
await addMessage(params.conversationId, role, message.text ?? "", {
|
|
@@ -1361,6 +1398,32 @@ async function persistBackfilledSlackMessage(params: {
|
|
|
1361
1398
|
});
|
|
1362
1399
|
}
|
|
1363
1400
|
|
|
1401
|
+
function readSlackFilesFromProviderMetadata(
|
|
1402
|
+
metadata: Record<string, unknown> | undefined,
|
|
1403
|
+
): SlackFileMetadata[] {
|
|
1404
|
+
const raw = metadata?.slackFiles;
|
|
1405
|
+
if (!Array.isArray(raw)) return [];
|
|
1406
|
+
const files: SlackFileMetadata[] = [];
|
|
1407
|
+
for (const item of raw) {
|
|
1408
|
+
if (item === null || typeof item !== "object" || Array.isArray(item)) {
|
|
1409
|
+
continue;
|
|
1410
|
+
}
|
|
1411
|
+
const record = item as Record<string, unknown>;
|
|
1412
|
+
const name = typeof record.name === "string" ? record.name.trim() : "";
|
|
1413
|
+
if (!name) continue;
|
|
1414
|
+
files.push({
|
|
1415
|
+
...(typeof record.id === "string" && record.id.length > 0
|
|
1416
|
+
? { id: record.id }
|
|
1417
|
+
: {}),
|
|
1418
|
+
name,
|
|
1419
|
+
...(typeof record.mimetype === "string" && record.mimetype.length > 0
|
|
1420
|
+
? { mimetype: record.mimetype }
|
|
1421
|
+
: {}),
|
|
1422
|
+
});
|
|
1423
|
+
}
|
|
1424
|
+
return files;
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1364
1427
|
/**
|
|
1365
1428
|
* In-memory map of in-flight DM cold-start backfills keyed by conversationId.
|
|
1366
1429
|
* Concurrent inbound DMs to the same cold conversation share a single
|
|
@@ -1503,13 +1566,11 @@ async function runBackfillSlackDmIfCold(params: {
|
|
|
1503
1566
|
// ---------------------------------------------------------------------------
|
|
1504
1567
|
|
|
1505
1568
|
/**
|
|
1506
|
-
* In-memory TTL cache keyed by
|
|
1507
|
-
*
|
|
1508
|
-
* thread
|
|
1509
|
-
*
|
|
1510
|
-
*
|
|
1511
|
-
* backfill becomes a cheap "are the parents already stored?" DB lookup
|
|
1512
|
-
* that short-circuits before the Slack API is touched.
|
|
1569
|
+
* In-memory TTL cache keyed by
|
|
1570
|
+
* `<conversationId>:<threadTs>:<lowerBoundTs>:<upperBoundTs>`. Tracks recent
|
|
1571
|
+
* thread-backfill windows so repeated triggers for the same Slack gap do not
|
|
1572
|
+
* re-fetch identical rows while later replies in the same thread can still
|
|
1573
|
+
* request newer unseen windows.
|
|
1513
1574
|
*
|
|
1514
1575
|
* Exported only for tests; production callers should use
|
|
1515
1576
|
* {@link triggerSlackThreadBackfillIfNeeded}.
|
|
@@ -1518,6 +1579,39 @@ export const _backfillTriggerCache = new Map<string, number>();
|
|
|
1518
1579
|
|
|
1519
1580
|
const BACKFILL_TRIGGER_TTL_MS = 10 * 60 * 1000; // 10 minutes
|
|
1520
1581
|
const BACKFILL_TRIGGER_CACHE_MAX = 1_000;
|
|
1582
|
+
const SLACK_THREAD_INITIAL_EARLY_LIMIT = 25;
|
|
1583
|
+
const SLACK_THREAD_INITIAL_RECENT_LIMIT = 50;
|
|
1584
|
+
const SLACK_THREAD_INITIAL_RECENT_MAX_PAGES = 5;
|
|
1585
|
+
const SLACK_THREAD_DELTA_LIMIT = 50;
|
|
1586
|
+
const SLACK_THREAD_UPPER_ADJACENT_MAX_ATTEMPTS = 5;
|
|
1587
|
+
const MICROS_PER_SECOND = 1_000_000n;
|
|
1588
|
+
const SLACK_UPPER_ADJACENT_EXPANDING_WINDOWS_MICROS = [
|
|
1589
|
+
5n * 60n * MICROS_PER_SECOND,
|
|
1590
|
+
60n * 60n * MICROS_PER_SECOND,
|
|
1591
|
+
24n * 60n * 60n * MICROS_PER_SECOND,
|
|
1592
|
+
7n * 24n * 60n * 60n * MICROS_PER_SECOND,
|
|
1593
|
+
30n * 24n * 60n * 60n * MICROS_PER_SECOND,
|
|
1594
|
+
];
|
|
1595
|
+
const SLACK_UPPER_ADJACENT_SHRINKING_WINDOWS_MICROS = [
|
|
1596
|
+
60n * MICROS_PER_SECOND,
|
|
1597
|
+
10n * MICROS_PER_SECOND,
|
|
1598
|
+
MICROS_PER_SECOND,
|
|
1599
|
+
100_000n,
|
|
1600
|
+
1_000n,
|
|
1601
|
+
];
|
|
1602
|
+
|
|
1603
|
+
export interface SlackThreadBackfillResult {
|
|
1604
|
+
fetched: number;
|
|
1605
|
+
persisted: number;
|
|
1606
|
+
reason?: SlackBackfillReason;
|
|
1607
|
+
omittedMiddle: boolean;
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
type SlackBackfillReason = "thread_late_join" | "thread_delta";
|
|
1611
|
+
|
|
1612
|
+
function emptySlackThreadBackfillResult(): SlackThreadBackfillResult {
|
|
1613
|
+
return { fetched: 0, persisted: 0, omittedMiddle: false };
|
|
1614
|
+
}
|
|
1521
1615
|
|
|
1522
1616
|
function pruneBackfillCacheIfNeeded(): void {
|
|
1523
1617
|
if (_backfillTriggerCache.size < BACKFILL_TRIGGER_CACHE_MAX) return;
|
|
@@ -1552,23 +1646,315 @@ function isBackfillRecentlyTriggered(cacheKey: string): boolean {
|
|
|
1552
1646
|
return true;
|
|
1553
1647
|
}
|
|
1554
1648
|
|
|
1649
|
+
interface SlackInitialThreadWindowsResult {
|
|
1650
|
+
messages: ProviderMessage[];
|
|
1651
|
+
omittedMiddle: boolean;
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
interface SlackUpperAdjacentWindowResult {
|
|
1655
|
+
messages: ProviderMessage[];
|
|
1656
|
+
omittedEarlierContent: boolean;
|
|
1657
|
+
truncatedBeforeUpperBound: boolean;
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
function slackPageHasMore(page: SlackBackfillWindowPage): boolean {
|
|
1661
|
+
return page.hasMore || page.nextCursor !== undefined;
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
function minSlackMessageTs(messages: ProviderMessage[]): string | undefined {
|
|
1665
|
+
return sortSlackProviderMessages(messages)[0]?.id;
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
function maxSlackMessageTs(messages: ProviderMessage[]): string | undefined {
|
|
1669
|
+
const sorted = sortSlackProviderMessages(messages);
|
|
1670
|
+
return sorted[sorted.length - 1]?.id;
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
function slackTimestampToMicros(ts: string | undefined): bigint | null {
|
|
1674
|
+
const parsed = parseSlackTimestamp(ts);
|
|
1675
|
+
if (!parsed) return null;
|
|
1676
|
+
return parsed.seconds * MICROS_PER_SECOND + parsed.micros;
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
function slackTimestampFromMicros(totalMicros: bigint): string | undefined {
|
|
1680
|
+
if (totalMicros < 0n) return undefined;
|
|
1681
|
+
const seconds = totalMicros / MICROS_PER_SECOND;
|
|
1682
|
+
const micros = totalMicros % MICROS_PER_SECOND;
|
|
1683
|
+
return `${seconds.toString()}.${micros.toString().padStart(6, "0")}`;
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
function didInitialWindowsLeaveGap(params: {
|
|
1687
|
+
early: SlackBackfillWindowPage;
|
|
1688
|
+
recent: SlackBackfillWindowPage;
|
|
1689
|
+
recentScanTruncated: boolean;
|
|
1690
|
+
}): boolean {
|
|
1691
|
+
if (params.recentScanTruncated) return true;
|
|
1692
|
+
if (!slackPageHasMore(params.early)) return false;
|
|
1693
|
+
const earlyMax = maxSlackMessageTs(params.early.messages);
|
|
1694
|
+
const recentMin = minSlackMessageTs(params.recent.messages);
|
|
1695
|
+
if (!earlyMax || !recentMin) return false;
|
|
1696
|
+
const compared = compareSlackTimestamps(earlyMax, recentMin);
|
|
1697
|
+
return compared !== null && compared < 0;
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
async function fetchSlackThreadUpperAdjacentWindow(params: {
|
|
1701
|
+
channelId: string;
|
|
1702
|
+
threadTs: string;
|
|
1703
|
+
upperBoundTs: string;
|
|
1704
|
+
lowerBoundTs?: string;
|
|
1705
|
+
limit: number;
|
|
1706
|
+
account?: string;
|
|
1707
|
+
maxAttempts?: number;
|
|
1708
|
+
}): Promise<SlackUpperAdjacentWindowResult> {
|
|
1709
|
+
// Slack returns bounded conversations.replies pages earliest-first. To keep
|
|
1710
|
+
// the context closest to the inbound mention, narrow by timestamp instead
|
|
1711
|
+
// of cursoring forward from the oldest page in the bounded range.
|
|
1712
|
+
const upperMicros = slackTimestampToMicros(params.upperBoundTs);
|
|
1713
|
+
if (upperMicros === null) {
|
|
1714
|
+
const page = await backfillThreadWindowPage(
|
|
1715
|
+
params.channelId,
|
|
1716
|
+
params.threadTs,
|
|
1717
|
+
{
|
|
1718
|
+
limit: params.limit,
|
|
1719
|
+
account: params.account,
|
|
1720
|
+
before: params.upperBoundTs,
|
|
1721
|
+
...(params.lowerBoundTs !== undefined
|
|
1722
|
+
? { after: params.lowerBoundTs }
|
|
1723
|
+
: {}),
|
|
1724
|
+
},
|
|
1725
|
+
);
|
|
1726
|
+
return {
|
|
1727
|
+
messages: page.messages,
|
|
1728
|
+
omittedEarlierContent: slackPageHasMore(page),
|
|
1729
|
+
truncatedBeforeUpperBound: slackPageHasMore(page),
|
|
1730
|
+
};
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
const lowerMicros = slackTimestampToMicros(params.lowerBoundTs);
|
|
1734
|
+
const maxAttempts =
|
|
1735
|
+
params.maxAttempts ?? SLACK_THREAD_UPPER_ADJACENT_MAX_ATTEMPTS;
|
|
1736
|
+
let attempts = 0;
|
|
1737
|
+
let safePage: SlackBackfillWindowPage | undefined;
|
|
1738
|
+
let safeAfterTs: string | undefined;
|
|
1739
|
+
let truncatedBeforeUpperBound = false;
|
|
1740
|
+
|
|
1741
|
+
const fetchWindow = async (
|
|
1742
|
+
windowMicros: bigint,
|
|
1743
|
+
): Promise<{
|
|
1744
|
+
page: SlackBackfillWindowPage;
|
|
1745
|
+
after?: string;
|
|
1746
|
+
reachedLowerBound: boolean;
|
|
1747
|
+
}> => {
|
|
1748
|
+
let candidateMicros = upperMicros - windowMicros;
|
|
1749
|
+
let reachedLowerBound = false;
|
|
1750
|
+
if (lowerMicros !== null && candidateMicros <= lowerMicros) {
|
|
1751
|
+
candidateMicros = lowerMicros;
|
|
1752
|
+
reachedLowerBound = true;
|
|
1753
|
+
}
|
|
1754
|
+
const after = reachedLowerBound
|
|
1755
|
+
? params.lowerBoundTs
|
|
1756
|
+
: slackTimestampFromMicros(candidateMicros);
|
|
1757
|
+
const page = await backfillThreadWindowPage(
|
|
1758
|
+
params.channelId,
|
|
1759
|
+
params.threadTs,
|
|
1760
|
+
{
|
|
1761
|
+
limit: params.limit,
|
|
1762
|
+
account: params.account,
|
|
1763
|
+
before: params.upperBoundTs,
|
|
1764
|
+
...(after !== undefined ? { after } : {}),
|
|
1765
|
+
},
|
|
1766
|
+
);
|
|
1767
|
+
attempts++;
|
|
1768
|
+
return { page, after, reachedLowerBound };
|
|
1769
|
+
};
|
|
1770
|
+
|
|
1771
|
+
const considerWindow = async (windowMicros: bigint): Promise<boolean> => {
|
|
1772
|
+
const { page, after, reachedLowerBound } = await fetchWindow(windowMicros);
|
|
1773
|
+
if (slackPageHasMore(page)) {
|
|
1774
|
+
truncatedBeforeUpperBound = true;
|
|
1775
|
+
return false;
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
safePage = page;
|
|
1779
|
+
safeAfterTs = after;
|
|
1780
|
+
return page.messages.length < params.limit && !reachedLowerBound;
|
|
1781
|
+
};
|
|
1782
|
+
|
|
1783
|
+
for (const windowMicros of SLACK_UPPER_ADJACENT_EXPANDING_WINDOWS_MICROS) {
|
|
1784
|
+
if (attempts >= maxAttempts) break;
|
|
1785
|
+
const shouldExpand = await considerWindow(windowMicros);
|
|
1786
|
+
if (!shouldExpand) break;
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
if (truncatedBeforeUpperBound && !safePage && attempts < maxAttempts) {
|
|
1790
|
+
for (const windowMicros of SLACK_UPPER_ADJACENT_SHRINKING_WINDOWS_MICROS) {
|
|
1791
|
+
if (attempts >= maxAttempts) break;
|
|
1792
|
+
await considerWindow(windowMicros);
|
|
1793
|
+
if (safePage) break;
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
if (!safePage) {
|
|
1798
|
+
const after = slackTimestampFromMicros(upperMicros - 2n);
|
|
1799
|
+
const page = await backfillThreadWindowPage(
|
|
1800
|
+
params.channelId,
|
|
1801
|
+
params.threadTs,
|
|
1802
|
+
{
|
|
1803
|
+
limit: params.limit,
|
|
1804
|
+
account: params.account,
|
|
1805
|
+
before: params.upperBoundTs,
|
|
1806
|
+
...(after !== undefined ? { after } : {}),
|
|
1807
|
+
},
|
|
1808
|
+
);
|
|
1809
|
+
safePage = page;
|
|
1810
|
+
safeAfterTs = after;
|
|
1811
|
+
truncatedBeforeUpperBound =
|
|
1812
|
+
truncatedBeforeUpperBound || slackPageHasMore(page);
|
|
1813
|
+
}
|
|
1814
|
+
if (!safePage) {
|
|
1815
|
+
return {
|
|
1816
|
+
messages: [],
|
|
1817
|
+
omittedEarlierContent: true,
|
|
1818
|
+
truncatedBeforeUpperBound: true,
|
|
1819
|
+
};
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
let omittedEarlierContent = truncatedBeforeUpperBound;
|
|
1823
|
+
if (
|
|
1824
|
+
!omittedEarlierContent &&
|
|
1825
|
+
params.lowerBoundTs !== undefined &&
|
|
1826
|
+
safeAfterTs !== undefined &&
|
|
1827
|
+
compareSlackTimestamps(params.lowerBoundTs, safeAfterTs) === -1
|
|
1828
|
+
) {
|
|
1829
|
+
const coverageProbe = await backfillThreadWindowPage(
|
|
1830
|
+
params.channelId,
|
|
1831
|
+
params.threadTs,
|
|
1832
|
+
{
|
|
1833
|
+
limit: 1,
|
|
1834
|
+
account: params.account,
|
|
1835
|
+
after: params.lowerBoundTs,
|
|
1836
|
+
before: safeAfterTs,
|
|
1837
|
+
},
|
|
1838
|
+
);
|
|
1839
|
+
omittedEarlierContent =
|
|
1840
|
+
coverageProbe.messages.length > 0 || slackPageHasMore(coverageProbe);
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
return {
|
|
1844
|
+
messages: safePage.messages,
|
|
1845
|
+
omittedEarlierContent,
|
|
1846
|
+
truncatedBeforeUpperBound,
|
|
1847
|
+
};
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
async function fetchInitialSlackThreadWindows(params: {
|
|
1851
|
+
channelId: string;
|
|
1852
|
+
threadTs: string;
|
|
1853
|
+
upperBoundTs?: string;
|
|
1854
|
+
account?: string;
|
|
1855
|
+
}): Promise<SlackInitialThreadWindowsResult> {
|
|
1856
|
+
if (!params.upperBoundTs) {
|
|
1857
|
+
const early = await backfillThreadWindowPage(
|
|
1858
|
+
params.channelId,
|
|
1859
|
+
params.threadTs,
|
|
1860
|
+
{
|
|
1861
|
+
limit: SLACK_THREAD_INITIAL_EARLY_LIMIT,
|
|
1862
|
+
account: params.account,
|
|
1863
|
+
},
|
|
1864
|
+
);
|
|
1865
|
+
return {
|
|
1866
|
+
messages: sortSlackProviderMessages(
|
|
1867
|
+
dedupeSlackProviderMessages(early.messages),
|
|
1868
|
+
),
|
|
1869
|
+
omittedMiddle: slackPageHasMore(early),
|
|
1870
|
+
};
|
|
1871
|
+
}
|
|
1872
|
+
const [early, recentResult] = await Promise.all([
|
|
1873
|
+
backfillThreadWindowPage(params.channelId, params.threadTs, {
|
|
1874
|
+
limit: SLACK_THREAD_INITIAL_EARLY_LIMIT,
|
|
1875
|
+
account: params.account,
|
|
1876
|
+
}),
|
|
1877
|
+
fetchSlackThreadUpperAdjacentWindow({
|
|
1878
|
+
channelId: params.channelId,
|
|
1879
|
+
threadTs: params.threadTs,
|
|
1880
|
+
account: params.account,
|
|
1881
|
+
upperBoundTs: params.upperBoundTs,
|
|
1882
|
+
limit: SLACK_THREAD_INITIAL_RECENT_LIMIT,
|
|
1883
|
+
maxAttempts: SLACK_THREAD_INITIAL_RECENT_MAX_PAGES,
|
|
1884
|
+
}),
|
|
1885
|
+
]);
|
|
1886
|
+
const recent: SlackBackfillWindowPage = {
|
|
1887
|
+
messages: recentResult.messages,
|
|
1888
|
+
hasMore: recentResult.truncatedBeforeUpperBound,
|
|
1889
|
+
};
|
|
1890
|
+
return {
|
|
1891
|
+
messages: sortSlackProviderMessages(
|
|
1892
|
+
dedupeSlackProviderMessages([...early.messages, ...recent.messages]),
|
|
1893
|
+
),
|
|
1894
|
+
omittedMiddle:
|
|
1895
|
+
recentResult.omittedEarlierContent ||
|
|
1896
|
+
didInitialWindowsLeaveGap({
|
|
1897
|
+
early,
|
|
1898
|
+
recent,
|
|
1899
|
+
recentScanTruncated: recentResult.truncatedBeforeUpperBound,
|
|
1900
|
+
}),
|
|
1901
|
+
};
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
function dedupeSlackProviderMessages(
|
|
1905
|
+
messages: ProviderMessage[],
|
|
1906
|
+
): ProviderMessage[] {
|
|
1907
|
+
const byTs = new Map<string, ProviderMessage>();
|
|
1908
|
+
for (const message of messages) {
|
|
1909
|
+
if (!message.id || byTs.has(message.id)) continue;
|
|
1910
|
+
byTs.set(message.id, message);
|
|
1911
|
+
}
|
|
1912
|
+
return [...byTs.values()];
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
function sortSlackProviderMessages(
|
|
1916
|
+
messages: ProviderMessage[],
|
|
1917
|
+
): ProviderMessage[] {
|
|
1918
|
+
return [...messages].sort((left, right) => {
|
|
1919
|
+
const compared = compareSlackTimestamps(left.id, right.id);
|
|
1920
|
+
if (compared !== null) return compared;
|
|
1921
|
+
return left.id.localeCompare(right.id);
|
|
1922
|
+
});
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
function buildSlackLateJoinNotice(
|
|
1926
|
+
result: SlackThreadBackfillResult,
|
|
1927
|
+
): string | null {
|
|
1928
|
+
if (result.reason !== "thread_late_join" || result.persisted === 0) {
|
|
1929
|
+
return null;
|
|
1930
|
+
}
|
|
1931
|
+
const omitted = result.omittedMiddle
|
|
1932
|
+
? " Some middle thread messages were intentionally omitted from this turn's hydrated context to keep latency bounded."
|
|
1933
|
+
: "";
|
|
1934
|
+
return `Slack context note: this turn joined an existing thread. ${result.persisted} earlier thread message${result.persisted === 1 ? " was" : "s were"} backfilled before the current message.${omitted}`;
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1555
1937
|
/**
|
|
1556
|
-
* Lazily backfill
|
|
1938
|
+
* Lazily backfill Slack thread gaps for an inbound thread reply.
|
|
1557
1939
|
*
|
|
1558
|
-
* When a reply arrives for a thread
|
|
1559
|
-
*
|
|
1560
|
-
*
|
|
1561
|
-
*
|
|
1562
|
-
*
|
|
1563
|
-
* appears in the conversation.
|
|
1940
|
+
* When a reply arrives for a thread with unseen Slack history, the assistant
|
|
1941
|
+
* fetches bounded `conversations.replies` pages via
|
|
1942
|
+
* {@link backfillThreadWindowPage}, persists each unseen message as a
|
|
1943
|
+
* `messages` row with a `slackMeta` envelope, and skips duplicates whose `ts`
|
|
1944
|
+
* already appears in the conversation.
|
|
1564
1945
|
*
|
|
1565
1946
|
* Behavior contracts:
|
|
1566
|
-
* - **
|
|
1567
|
-
*
|
|
1568
|
-
*
|
|
1569
|
-
*
|
|
1570
|
-
*
|
|
1571
|
-
*
|
|
1947
|
+
* - **Thread-state gap detection.** Looks up stored Slack message rows for
|
|
1948
|
+
* the same thread, excluding reactions, then fetches only the unseen
|
|
1949
|
+
* `(latestStoredThreadTs, excludeChannelTs)` window when the inbound Slack
|
|
1950
|
+
* timestamp is newer than local state.
|
|
1951
|
+
* - **Upper-bound windows.** Initial late-join backfill combines an early
|
|
1952
|
+
* thread page with a recent page adjacent to the inbound ts; delta backfill
|
|
1953
|
+
* fetches the page nearest the inbound upper bound so the current turn sees
|
|
1954
|
+
* the most relevant context while keeping latency bounded.
|
|
1955
|
+
* - **Exact-window TTL cache.** A 10-minute in-memory cache prevents repeated
|
|
1956
|
+
* fetches for the same exact lower/upper bounded window, without
|
|
1957
|
+
* suppressing later unseen windows in the same thread.
|
|
1572
1958
|
* - **Failure-tolerant.** Any error (Slack API failure, DB error, malformed
|
|
1573
1959
|
* payload) is logged at `warn` and swallowed — the inbound turn must
|
|
1574
1960
|
* never block on backfill.
|
|
@@ -1583,65 +1969,106 @@ export async function triggerSlackThreadBackfillIfNeeded(params: {
|
|
|
1583
1969
|
* `conversations.replies` returns it in the thread window. Necessary
|
|
1584
1970
|
* because thread backfill runs concurrently with
|
|
1585
1971
|
* `processChannelMessageInBackground`, so the inbound row may not yet be
|
|
1586
|
-
* in the DB when
|
|
1972
|
+
* in the DB when the thread-state scan snapshots the conversation.
|
|
1587
1973
|
*/
|
|
1588
1974
|
excludeChannelTs?: string;
|
|
1589
1975
|
/**
|
|
1590
1976
|
* OAuth account identifier used to disambiguate which Slack workspace the
|
|
1591
1977
|
* backfill should read from in multi-account setups. Passed through to
|
|
1592
|
-
* `
|
|
1593
|
-
* resolver falls back to the default-active
|
|
1978
|
+
* `backfillThreadWindowPage` page requests and then `resolveConnection`.
|
|
1979
|
+
* Best-effort: if omitted, the resolver falls back to the default-active
|
|
1980
|
+
* connection.
|
|
1594
1981
|
*/
|
|
1595
1982
|
account?: string;
|
|
1596
|
-
}): Promise<
|
|
1983
|
+
}): Promise<SlackThreadBackfillResult> {
|
|
1597
1984
|
const { conversationId, channelId, threadTs, excludeChannelTs, account } =
|
|
1598
1985
|
params;
|
|
1599
|
-
const cacheKey = `${conversationId}:${threadTs}`;
|
|
1600
1986
|
|
|
1601
1987
|
try {
|
|
1602
|
-
|
|
1603
|
-
|
|
1988
|
+
const upperBoundTs = parseSlackTimestamp(excludeChannelTs)
|
|
1989
|
+
? excludeChannelTs
|
|
1990
|
+
: undefined;
|
|
1991
|
+
const threadState = readStoredSlackThreadState(conversationId, threadTs);
|
|
1992
|
+
const lowerBoundTs = threadState.latestStoredThreadTs;
|
|
1993
|
+
|
|
1994
|
+
// Pre-seed only after computing lowerBoundTs. The current inbound row
|
|
1995
|
+
// may not have reached the DB yet, and treating it as stored state would
|
|
1996
|
+
// hide the gap we need to fetch.
|
|
1997
|
+
if (excludeChannelTs) threadState.storedChannelTs.add(excludeChannelTs);
|
|
1998
|
+
|
|
1999
|
+
if (upperBoundTs && lowerBoundTs) {
|
|
2000
|
+
const lowerVsUpper = compareSlackTimestamps(lowerBoundTs, upperBoundTs);
|
|
2001
|
+
if (lowerVsUpper !== null && lowerVsUpper >= 0) {
|
|
2002
|
+
return emptySlackThreadBackfillResult();
|
|
2003
|
+
}
|
|
2004
|
+
} else if (!upperBoundTs && lowerBoundTs) {
|
|
2005
|
+
return emptySlackThreadBackfillResult();
|
|
1604
2006
|
}
|
|
1605
2007
|
|
|
1606
|
-
const
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
_backfillTriggerCache.set(cacheKey, Date.now());
|
|
1612
|
-
pruneBackfillCacheIfNeeded();
|
|
1613
|
-
return;
|
|
2008
|
+
const cacheKey = `${conversationId}:${threadTs}:${
|
|
2009
|
+
lowerBoundTs ?? "none"
|
|
2010
|
+
}:${upperBoundTs ?? "unbounded"}`;
|
|
2011
|
+
if (isBackfillRecentlyTriggered(cacheKey)) {
|
|
2012
|
+
return emptySlackThreadBackfillResult();
|
|
1614
2013
|
}
|
|
1615
2014
|
|
|
1616
2015
|
// Mark the trigger before issuing the network call. Doing this first
|
|
1617
|
-
// means a second concurrent
|
|
1618
|
-
// immediately even while the first call is still awaiting the Slack
|
|
1619
|
-
//
|
|
1620
|
-
//
|
|
1621
|
-
//
|
|
2016
|
+
// means a second concurrent request for the same window short-circuits
|
|
2017
|
+
// immediately even while the first call is still awaiting the Slack API.
|
|
2018
|
+
// The cost is a slightly larger window where a transient Slack failure
|
|
2019
|
+
// suppresses a retry, which the next reply outside the TTL (or a daemon
|
|
2020
|
+
// restart) will re-attempt anyway.
|
|
1622
2021
|
_backfillTriggerCache.set(cacheKey, Date.now());
|
|
1623
2022
|
pruneBackfillCacheIfNeeded();
|
|
1624
2023
|
|
|
1625
|
-
const
|
|
2024
|
+
const isInitialLateJoin =
|
|
2025
|
+
lowerBoundTs === undefined &&
|
|
2026
|
+
threadState.storedChannelTs.size === (excludeChannelTs ? 1 : 0);
|
|
2027
|
+
const reason: SlackBackfillReason = isInitialLateJoin
|
|
2028
|
+
? "thread_late_join"
|
|
2029
|
+
: "thread_delta";
|
|
2030
|
+
let omittedMiddle = false;
|
|
2031
|
+
let fetched: ProviderMessage[];
|
|
2032
|
+
if (isInitialLateJoin) {
|
|
2033
|
+
const initial = await fetchInitialSlackThreadWindows({
|
|
2034
|
+
channelId,
|
|
2035
|
+
threadTs,
|
|
2036
|
+
upperBoundTs,
|
|
2037
|
+
account,
|
|
2038
|
+
});
|
|
2039
|
+
fetched = initial.messages;
|
|
2040
|
+
omittedMiddle = initial.omittedMiddle;
|
|
2041
|
+
} else {
|
|
2042
|
+
const window = await fetchSlackThreadUpperAdjacentWindow({
|
|
2043
|
+
channelId,
|
|
2044
|
+
threadTs,
|
|
2045
|
+
limit: SLACK_THREAD_DELTA_LIMIT,
|
|
2046
|
+
account,
|
|
2047
|
+
...(lowerBoundTs !== undefined ? { lowerBoundTs } : {}),
|
|
2048
|
+
upperBoundTs: upperBoundTs ?? threadTs,
|
|
2049
|
+
});
|
|
2050
|
+
fetched = window.messages;
|
|
2051
|
+
omittedMiddle = window.omittedEarlierContent;
|
|
2052
|
+
}
|
|
1626
2053
|
if (fetched.length === 0) {
|
|
1627
2054
|
log.debug(
|
|
1628
2055
|
{ conversationId, channelId, threadTs },
|
|
1629
2056
|
"Slack thread backfill returned no messages",
|
|
1630
2057
|
);
|
|
1631
|
-
return;
|
|
2058
|
+
return emptySlackThreadBackfillResult();
|
|
1632
2059
|
}
|
|
1633
2060
|
|
|
1634
2061
|
let persisted = 0;
|
|
1635
2062
|
for (const message of fetched) {
|
|
1636
2063
|
if (!message.id) continue;
|
|
1637
|
-
if (storedChannelTs.has(message.id)) continue;
|
|
2064
|
+
if (threadState.storedChannelTs.has(message.id)) continue;
|
|
1638
2065
|
try {
|
|
1639
2066
|
await persistBackfilledSlackMessage({
|
|
1640
2067
|
conversationId,
|
|
1641
2068
|
channelId,
|
|
1642
2069
|
message,
|
|
1643
2070
|
});
|
|
1644
|
-
storedChannelTs.add(message.id);
|
|
2071
|
+
threadState.storedChannelTs.add(message.id);
|
|
1645
2072
|
persisted++;
|
|
1646
2073
|
} catch (err) {
|
|
1647
2074
|
log.warn(
|
|
@@ -1658,15 +2085,22 @@ export async function triggerSlackThreadBackfillIfNeeded(params: {
|
|
|
1658
2085
|
threadTs,
|
|
1659
2086
|
persisted,
|
|
1660
2087
|
fetched: fetched.length,
|
|
2088
|
+
omittedMiddle,
|
|
1661
2089
|
},
|
|
1662
|
-
"Slack thread backfill persisted
|
|
2090
|
+
"Slack thread backfill persisted thread messages",
|
|
1663
2091
|
);
|
|
2092
|
+
return {
|
|
2093
|
+
fetched: fetched.length,
|
|
2094
|
+
persisted,
|
|
2095
|
+
reason,
|
|
2096
|
+
omittedMiddle,
|
|
2097
|
+
};
|
|
1664
2098
|
} catch (err) {
|
|
1665
2099
|
// `channel_not_found` almost always means the resolved connection is
|
|
1666
2100
|
// pointing at the wrong Slack workspace (a real config bug), so log it
|
|
1667
2101
|
// at ERROR to match backfill's rethrow contract. Other failures
|
|
1668
2102
|
// (timeout, auth, ratelimited, …) stay at WARN — they're expected
|
|
1669
|
-
// transient blips and dispatch proceeds without the
|
|
2103
|
+
// transient blips and dispatch proceeds without the backfilled thread rows.
|
|
1670
2104
|
const channelNotFound =
|
|
1671
2105
|
err instanceof Error && /channel_not_found/i.test(err.message);
|
|
1672
2106
|
const payload = { err, conversationId, channelId, threadTs, account };
|
|
@@ -1678,5 +2112,6 @@ export async function triggerSlackThreadBackfillIfNeeded(params: {
|
|
|
1678
2112
|
} else {
|
|
1679
2113
|
log.warn(payload, "Slack thread backfill failed; proceeding without it");
|
|
1680
2114
|
}
|
|
2115
|
+
return emptySlackThreadBackfillResult();
|
|
1681
2116
|
}
|
|
1682
2117
|
}
|