@vellumai/assistant 0.8.3 → 0.8.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +2 -2
- package/docker-entrypoint.sh +0 -1
- package/docs/browser-use-architecture-phase2.md +1 -1
- package/knip.json +2 -1
- package/node_modules/@vellumai/gateway-client/src/types.ts +2 -0
- package/openapi.yaml +1492 -100
- package/package.json +1 -1
- package/src/__tests__/agent-loop-exit-reason.test.ts +4 -5
- package/src/__tests__/agent-loop-override-profile.test.ts +1 -1
- package/src/__tests__/agent-loop.test.ts +88 -3
- package/src/__tests__/anthropic-provider.test.ts +302 -33
- package/src/__tests__/approval-cascade.test.ts +1 -1
- package/src/__tests__/assistant-event-hub-self-exclusion.test.ts +293 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +3 -3
- package/src/__tests__/audit-log-rotation.test.ts +70 -16
- package/src/__tests__/background-workers-disk-pressure.test.ts +4 -3
- package/src/__tests__/btw-routes.test.ts +2 -3
- package/src/__tests__/call-controller.test.ts +0 -1
- package/src/__tests__/cancel-resolves-conversation-key.test.ts +1 -1
- package/src/__tests__/channel-delivery-store.test.ts +193 -0
- package/src/__tests__/channel-guardian.test.ts +3 -3
- package/src/__tests__/channel-reply-delivery.test.ts +284 -5
- package/src/__tests__/channel-retry-sweep.test.ts +274 -1
- package/src/__tests__/checker.test.ts +6 -15
- package/src/__tests__/compaction-events.test.ts +2 -1
- package/src/__tests__/compactor-call-site-logging.test.ts +214 -0
- package/src/__tests__/compactor-preserved-tail-count.test.ts +110 -0
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +5 -11
- package/src/__tests__/computer-use-tools.test.ts +2 -4
- package/src/__tests__/config-watcher.test.ts +1 -1
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
- package/src/__tests__/context-token-estimator.test.ts +91 -1
- package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +55 -4
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +228 -8
- package/src/__tests__/conversation-agent-loop.test.ts +188 -129
- package/src/__tests__/conversation-app-control-instantiation.test.ts +2 -5
- package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -1
- package/src/__tests__/conversation-clean-command.test.ts +137 -0
- package/src/__tests__/conversation-clear-safety.test.ts +25 -25
- package/src/__tests__/conversation-confirmation-signals.test.ts +1 -1
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +1 -1
- package/src/__tests__/conversation-disk-view-integration.test.ts +2 -2
- package/src/__tests__/conversation-error.test.ts +31 -0
- package/src/__tests__/conversation-fork-crud.test.ts +324 -0
- package/src/__tests__/conversation-lifecycle.test.ts +53 -12
- package/src/__tests__/conversation-load-history-repair.test.ts +1 -1
- package/src/__tests__/conversation-load-history-stripped.test.ts +279 -0
- package/src/__tests__/conversation-pairing.test.ts +2 -2
- package/src/__tests__/conversation-process-callsite.test.ts +1 -1
- package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -1
- package/src/__tests__/conversation-queue.test.ts +1 -1
- package/src/__tests__/conversation-routes-disk-view.test.ts +109 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +35 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +264 -81
- package/src/__tests__/conversation-seed-composer.test.ts +66 -4
- package/src/__tests__/conversation-skill-tools.test.ts +2 -5
- package/src/__tests__/conversation-slash-commands.test.ts +36 -8
- package/src/__tests__/conversation-slash-queue.test.ts +1 -1
- package/src/__tests__/conversation-slash-unknown.test.ts +1 -1
- package/src/__tests__/conversation-speed-override.test.ts +1 -1
- package/src/__tests__/conversation-store.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +220 -0
- package/src/__tests__/conversation-sync-tags.test.ts +99 -32
- package/src/__tests__/conversation-workspace-cache-state.test.ts +2 -1
- package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
- package/src/__tests__/credential-execution-feature-gates.test.ts +9 -7
- package/src/__tests__/credential-execution-tools.test.ts +6 -6
- package/src/__tests__/credential-security-invariants.test.ts +7 -0
- package/src/__tests__/credential-vault-unit.test.ts +2 -2
- package/src/__tests__/cu-unified-flow.test.ts +10 -1
- package/src/__tests__/dm-backfill.test.ts +64 -0
- package/src/__tests__/dm-persistence.test.ts +33 -0
- package/src/__tests__/document-find-replace.test.ts +501 -0
- package/src/__tests__/dynamic-page-surface.test.ts +2 -2
- package/src/__tests__/email-html-renderer.test.ts +12 -0
- package/src/__tests__/first-greeting.test.ts +23 -2
- package/src/__tests__/gateway-flag-listener.test.ts +237 -0
- package/src/__tests__/gemini-provider.test.ts +78 -0
- package/src/__tests__/guardian-dispatch.test.ts +0 -1
- package/src/__tests__/guardian-outbound-http.test.ts +7 -5
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
- package/src/__tests__/headless-browser-navigate.test.ts +172 -0
- package/src/__tests__/heartbeat-disk-pressure.test.ts +4 -0
- package/src/__tests__/heartbeat-service.test.ts +4 -0
- package/src/__tests__/host-bash-proxy.test.ts +6 -0
- package/src/__tests__/host-browser-proxy.test.ts +10 -0
- package/src/__tests__/host-cu-proxy.test.ts +8 -1
- package/src/__tests__/host-file-proxy.test.ts +8 -1
- package/src/__tests__/host-shell-tool.test.ts +1 -1
- package/src/__tests__/host-transfer-proxy.test.ts +8 -1
- package/src/__tests__/identity-routes.test.ts +57 -0
- package/src/__tests__/inbound-slack-persistence.test.ts +3 -0
- package/src/__tests__/init-feature-flag-overrides.test.ts +5 -6
- package/src/__tests__/injector-chain.test.ts +2 -0
- package/src/__tests__/injector-document-comments.test.ts +378 -0
- package/src/__tests__/injector-pkb-v2-silenced.test.ts +4 -25
- package/src/__tests__/list-messages-attachments.test.ts +21 -17
- package/src/__tests__/list-messages-hidden-metadata.test.ts +217 -0
- package/src/__tests__/list-messages-page-latest.test.ts +130 -14
- package/src/__tests__/list-messages-tool-merge.test.ts +77 -17
- package/src/__tests__/llm-context-normalization.test.ts +0 -2
- package/src/__tests__/llm-request-log-call-site.test.ts +136 -0
- package/src/__tests__/llm-request-log-source-clickhouse.test.ts +26 -0
- package/src/__tests__/llm-resolver.test.ts +161 -9
- package/src/__tests__/llm-usage-store.test.ts +66 -0
- package/src/__tests__/log-export-routes.test.ts +99 -2
- package/src/__tests__/logger.test.ts +89 -0
- package/src/__tests__/mcp-abort-signal.test.ts +2 -2
- package/src/__tests__/media-generate-image.test.ts +31 -0
- package/src/__tests__/memory-v2-static-injector.test.ts +7 -7
- package/src/__tests__/message-queue-steer.test.ts +114 -0
- package/src/__tests__/model-intents.test.ts +2 -4
- package/src/__tests__/notification-guardian-path.test.ts +0 -1
- package/src/__tests__/onboarding-template-contract.test.ts +1 -1
- package/src/__tests__/openai-provider.test.ts +151 -0
- package/src/__tests__/openai-responses-provider.test.ts +118 -16
- package/src/__tests__/outbound-slack-persistence.test.ts +187 -20
- package/src/__tests__/pending-interactions-resolved-event.test.ts +189 -0
- package/src/__tests__/platform-bash-auto-approve.test.ts +2 -2
- package/src/__tests__/platform.test.ts +2 -5
- package/src/__tests__/plugin-api-tool-definition.test.ts +92 -0
- package/src/__tests__/plugin-bootstrap.test.ts +2 -2
- package/src/__tests__/plugin-source-watcher.test.ts +302 -0
- package/src/__tests__/plugin-tool-contribution.test.ts +13 -6
- package/src/__tests__/plugin-types.test.ts +3 -2
- package/src/__tests__/prechat-onboarding-contract.test.ts +131 -98
- package/src/__tests__/pricing.test.ts +12 -0
- package/src/__tests__/process-message-background-slack.test.ts +1 -51
- package/src/__tests__/process-message-display-content.test.ts +21 -16
- package/src/__tests__/prune-jobs-changes-parser.test.ts +61 -0
- package/src/__tests__/registry.test.ts +2 -8
- package/src/__tests__/require-fresh-approval.test.ts +2 -2
- package/src/__tests__/runtime-events-sse-bilingual.test.ts +154 -0
- package/src/__tests__/server-history-render.test.ts +83 -4
- package/src/__tests__/shell-tool-proxy-mode.test.ts +1 -1
- package/src/__tests__/skill-feature-flags.test.ts +2 -2
- package/src/__tests__/skill-projection-feature-flag.test.ts +4 -7
- package/src/__tests__/skill-projection.benchmark.test.ts +2 -6
- package/src/__tests__/skill-tool-factory.test.ts +1 -1
- package/src/__tests__/steer-tool-repair.test.ts +249 -0
- package/src/__tests__/subagent-notify-parent.test.ts +1 -1
- package/src/__tests__/suggestion-routes.test.ts +1 -0
- package/src/__tests__/sync-message-contract.test.ts +59 -0
- package/src/__tests__/system-prompt.test.ts +161 -124
- package/src/__tests__/terminal-tools.test.ts +12 -2
- package/src/__tests__/thinking-block-replay.test.ts +113 -0
- package/src/__tests__/thread-backfill.test.ts +370 -22
- package/src/__tests__/tool-approval-handler.test.ts +1 -5
- package/src/__tests__/tool-execute-pipeline.test.ts +2 -2
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -5
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +15 -5
- package/src/__tests__/tool-executor.test.ts +89 -53
- package/src/__tests__/tool-grant-request-escalation.test.ts +1 -6
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +167 -0
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +1 -6
- package/src/__tests__/trusted-contact-multichannel.test.ts +0 -1
- package/src/__tests__/twilio-routes.test.ts +1 -1
- package/src/__tests__/ui-file-upload-surface.test.ts +2 -2
- package/src/__tests__/usage-routes.test.ts +3 -0
- package/src/__tests__/verification-control-plane-policy.test.ts +2 -2
- package/src/__tests__/web-fetch.test.ts +2 -2
- package/src/__tests__/workspace-git-service.test.ts +94 -10
- package/src/__tests__/workspace-migration-088-deprecate-background-conversation-override.test.ts +158 -0
- package/src/__tests__/workspace-migration-089-move-memory-tree-out-of-v3.test.ts +86 -0
- package/src/acp/__tests__/prepare-agent-env.test.ts +146 -0
- package/src/acp/prepare-agent-env.ts +78 -0
- package/src/acp/session-manager.ts +1 -1
- package/src/agent/attachments.ts +1 -0
- package/src/agent/loop.ts +65 -20
- package/src/api/README.md +5 -0
- package/src/api/index.ts +4 -0
- package/src/api/package.json +10 -0
- package/src/background-wake/background-wake-routes.test.ts +233 -0
- package/src/background-wake/next-wake.test.ts +289 -0
- package/src/background-wake/next-wake.ts +172 -0
- package/src/background-wake/runtime-registry.ts +24 -0
- package/src/browser/operations.ts +15 -0
- package/src/cli/commands/__tests__/browser.test.ts +23 -5
- package/src/cli/commands/__tests__/conversations-slack.test.ts +572 -0
- package/src/cli/commands/__tests__/domain-register.test.ts +110 -0
- package/src/cli/commands/__tests__/domain-status.test.ts +33 -33
- package/src/cli/commands/__tests__/inference-send.test.ts +108 -5
- package/src/cli/commands/__tests__/memory-v2-compare-render.test.ts +98 -0
- package/src/cli/commands/__tests__/memory-v2.test.ts +10 -12
- package/src/cli/commands/__tests__/memory-v3-render.test.ts +340 -0
- package/src/cli/commands/browser.ts +247 -0
- package/src/cli/commands/conversations.ts +128 -1
- package/src/cli/commands/domain.ts +91 -41
- package/src/cli/commands/inference-providers.ts +147 -1
- package/src/cli/commands/inference.ts +93 -40
- package/src/cli/commands/memory-v2-compare-render.ts +115 -0
- package/src/cli/commands/memory-v2.ts +483 -0
- package/src/cli/commands/memory-v3-render.ts +344 -0
- package/src/cli/commands/memory-v3.ts +316 -0
- package/src/cli/commands/notifications.ts +24 -2
- package/src/cli/program.ts +2 -0
- package/src/cli/utils/conversation-id.ts +17 -5
- package/src/config/assistant-feature-flags.ts +21 -9
- package/src/config/bundled-skills/app-builder/SKILL.md +2 -2
- package/src/config/bundled-skills/document-editor/SKILL.md +124 -0
- package/src/config/bundled-skills/document-editor/TOOLS.json +258 -0
- package/src/config/bundled-skills/document-editor/tools/comment-list.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/comment-reply.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/comment-resolve.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/document-find.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/document-open.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/document-replace-text.ts +12 -0
- package/src/config/bundled-skills/image-studio/SKILL.md +4 -0
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +2 -2
- package/src/config/bundled-skills/media-processing/SKILL.md +8 -0
- package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +13 -8
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +10 -3
- package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +16 -14
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +7 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +7 -2
- package/src/config/bundled-skills/schedule/SKILL.md +8 -0
- package/src/config/bundled-tool-registry.ts +24 -12
- package/src/config/call-site-defaults.ts +20 -0
- package/src/config/feature-flag-registry.json +115 -3
- package/src/config/llm-resolver.ts +16 -2
- package/src/config/schemas/__tests__/memory-v2.test.ts +217 -1
- package/src/config/schemas/call-site-catalog.ts +35 -0
- package/src/config/schemas/llm.ts +14 -0
- package/src/config/schemas/memory-v2.ts +294 -1
- package/src/config/schemas/memory.ts +2 -1
- package/src/context/compactor.ts +60 -1
- package/src/context/token-estimator.ts +47 -4
- package/src/context/window-manager.ts +25 -0
- package/src/conversations/__tests__/message-consolidation.test.ts +350 -0
- package/src/conversations/message-consolidation.ts +404 -0
- package/src/credential-health/credential-health-service.ts +34 -19
- package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +1 -1
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -6
- package/src/daemon/__tests__/meet-manifest-loader.test.ts +1 -1
- package/src/daemon/__tests__/native-web-search-metadata.test.ts +357 -0
- package/src/daemon/__tests__/web-search-status-text.test.ts +287 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +155 -36
- package/src/daemon/conversation-agent-loop.ts +307 -88
- package/src/daemon/conversation-error.ts +31 -1
- package/src/daemon/conversation-lifecycle.ts +149 -118
- package/src/daemon/conversation-messaging.ts +3 -0
- package/src/daemon/conversation-process.ts +273 -0
- package/src/daemon/conversation-queue-manager.ts +14 -0
- package/src/daemon/conversation-runtime-assembly.ts +145 -84
- package/src/daemon/conversation-slash.ts +37 -5
- package/src/daemon/conversation-surfaces.ts +45 -2
- package/src/daemon/conversation-tool-setup.ts +70 -3
- package/src/daemon/conversation-usage.ts +2 -0
- package/src/daemon/conversation.ts +54 -32
- package/src/daemon/disk-pressure-guard.ts +14 -2
- package/src/daemon/first-greeting.ts +10 -0
- package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +498 -0
- package/src/daemon/handlers/config-a2a.ts +160 -0
- package/src/daemon/handlers/config-model.test.ts +2 -0
- package/src/daemon/handlers/conversations.ts +90 -3
- package/src/daemon/handlers/shared.ts +92 -29
- package/src/daemon/host-bash-proxy.ts +1 -1
- package/src/daemon/host-browser-proxy.ts +5 -5
- package/src/daemon/host-cu-proxy.ts +5 -5
- package/src/daemon/host-file-proxy.ts +5 -5
- package/src/daemon/host-proxy-base.ts +4 -4
- package/src/daemon/host-transfer-proxy.ts +11 -11
- package/src/daemon/lifecycle.ts +40 -23
- package/src/daemon/meet-manifest-loader.ts +1 -7
- package/src/daemon/message-protocol.ts +4 -0
- package/src/daemon/message-types/conversations.ts +14 -9
- package/src/daemon/message-types/document-comments.ts +50 -0
- package/src/daemon/message-types/home.ts +1 -13
- package/src/daemon/message-types/messages.ts +66 -7
- package/src/daemon/message-types/surfaces.ts +3 -1
- package/src/daemon/message-types/sync.ts +14 -0
- package/src/daemon/message-types/web-activity.ts +57 -0
- package/src/daemon/plugin-source-watcher.ts +135 -3
- package/src/daemon/process-message.ts +69 -12
- package/src/daemon/shutdown-handlers.ts +24 -5
- package/src/daemon/switch-inference-profile-tool.ts +52 -0
- package/src/daemon/tool-setup-types.ts +13 -0
- package/src/daemon/trust-context.ts +6 -0
- package/src/documents/document-comments-store.test.ts +338 -0
- package/src/documents/document-comments-store.ts +237 -0
- package/src/documents/document-store.ts +202 -0
- package/src/events/relationship-state-updated.ts +25 -0
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +1 -2
- package/src/heartbeat/heartbeat-service.ts +1 -0
- package/src/home/__tests__/suggested-prompts.test.ts +33 -2
- package/src/home/feed-types.ts +6 -1
- package/src/home/home-content-refresh.ts +52 -0
- package/src/home/home-greeting-cache.ts +69 -0
- package/src/home/home-greeting.ts +85 -0
- package/src/home/suggested-prompts.ts +168 -9
- package/src/ipc/gateway-flag-listener.ts +123 -0
- package/src/ipc/skill-routes/registries.ts +8 -12
- package/src/memory/__tests__/db-async-query.test.ts +165 -0
- package/src/memory/__tests__/db-maintenance.test.ts +115 -0
- package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +241 -0
- package/src/memory/__tests__/jobs-store-job-classes.test.ts +28 -1
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +135 -2
- package/src/memory/__tests__/memory-retrospective-job.test.ts +327 -6
- package/src/memory/auto-analysis-enqueue.ts +5 -1
- package/src/memory/conversation-crud.ts +191 -100
- package/src/memory/conversation-starters-cadence.ts +3 -1
- package/src/memory/conversation-title-service.ts +19 -3
- package/src/memory/db-async-query.ts +214 -0
- package/src/memory/db-init.ts +26 -0
- package/src/memory/db-maintenance.ts +30 -21
- package/src/memory/delivery-crud.ts +41 -0
- package/src/memory/delivery-status.ts +141 -15
- package/src/memory/external-conversation-store.ts +32 -1
- package/src/memory/graph/bootstrap.ts +8 -1
- package/src/memory/graph/capability-seed.ts +7 -3
- package/src/memory/graph/conversation-graph-memory.ts +100 -17
- package/src/memory/graph/extraction.ts +1 -5
- package/src/memory/graph/graph-search.ts +7 -1
- package/src/memory/indexer.ts +28 -18
- package/src/memory/job-handlers/cleanup.ts +76 -18
- package/src/memory/job-handlers/conversation-starters.ts +1 -4
- package/src/memory/jobs/embed-pkb-file.ts +6 -1
- package/src/memory/jobs-store.ts +14 -0
- package/src/memory/jobs-worker.ts +68 -15
- package/src/memory/llm-request-log-source-clickhouse.ts +42 -2
- package/src/memory/llm-request-log-source-local.ts +7 -0
- package/src/memory/llm-request-log-source.ts +9 -2
- package/src/memory/llm-request-log-store.ts +43 -1
- package/src/memory/llm-usage-store.ts +24 -0
- package/src/memory/memory-retrospective-constants.ts +28 -0
- package/src/memory/memory-retrospective-enqueue.ts +11 -3
- package/src/memory/memory-retrospective-job.ts +413 -18
- package/src/memory/memory-retrospective-startup-cleanup.ts +3 -3
- package/src/memory/memory-v2-activation-log-store.ts +41 -14
- package/src/memory/migrations/100-core-tables.ts +1 -0
- package/src/memory/migrations/109-external-conversation-bindings.ts +1 -0
- package/src/memory/migrations/253-conversation-last-notified-profile.ts +15 -0
- package/src/memory/migrations/253-document-comments.ts +47 -0
- package/src/memory/migrations/254-external-conversation-binding-chat-name.ts +43 -0
- package/src/memory/migrations/255-channel-inbound-delivery-attempts.ts +24 -0
- package/src/memory/migrations/256-memory-v2-injection-events.ts +113 -0
- package/src/memory/migrations/257-strip-base-url-non-openai-compatible.ts +22 -0
- package/src/memory/migrations/258-onboarding-events-prior-assistants.ts +13 -0
- package/src/memory/migrations/259-conversation-cleaned-at.ts +33 -0
- package/src/memory/migrations/260-rename-cleaned-at.ts +44 -0
- package/src/memory/migrations/261-llm-usage-add-raw-usage.ts +36 -0
- package/src/memory/migrations/262-memory-v3-coactivation.ts +57 -0
- package/src/memory/migrations/263-memory-v3-auto-edges.ts +50 -0
- package/src/memory/migrations/264-llm-request-log-call-site.ts +29 -0
- package/src/memory/migrations/index.ts +34 -0
- package/src/memory/migrations/registry.ts +58 -0
- package/src/memory/onboarding-events-store.ts +7 -0
- package/src/memory/schema/calls.ts +1 -0
- package/src/memory/schema/conversations.ts +3 -0
- package/src/memory/schema/infrastructure.ts +22 -0
- package/src/memory/tool-usage-store.ts +36 -8
- package/src/memory/v2/__tests__/consolidation-job.test.ts +1 -0
- package/src/memory/v2/__tests__/harness-compare.test.ts +186 -0
- package/src/memory/v2/__tests__/harness-metrics.test.ts +74 -0
- package/src/memory/v2/__tests__/harness-oracle.test.ts +257 -0
- package/src/memory/v2/__tests__/harness-replay-input.test.ts +225 -0
- package/src/memory/v2/__tests__/harness-runner.test.ts +109 -0
- package/src/memory/v2/__tests__/injection-events.test.ts +318 -0
- package/src/memory/v2/__tests__/injection.test.ts +158 -112
- package/src/memory/v2/__tests__/page-index.test.ts +365 -1
- package/src/memory/v2/__tests__/qdrant.test.ts +36 -0
- package/src/memory/v2/__tests__/router.test.ts +660 -4
- package/src/memory/v2/consolidation-job.ts +14 -0
- package/src/memory/v2/harness/compare.ts +57 -0
- package/src/memory/v2/harness/metrics.ts +124 -0
- package/src/memory/v2/harness/oracle.ts +145 -0
- package/src/memory/v2/harness/replay-input.ts +224 -0
- package/src/memory/v2/harness/retriever.ts +74 -0
- package/src/memory/v2/harness/router-retriever.ts +43 -0
- package/src/memory/v2/harness/runner.ts +106 -0
- package/src/memory/v2/harness/trace.ts +58 -0
- package/src/memory/v2/injection-events.ts +101 -0
- package/src/memory/v2/injection.ts +42 -25
- package/src/memory/v2/page-index.ts +209 -7
- package/src/memory/v2/page-store.ts +18 -0
- package/src/memory/v2/prompts/router.ts +26 -1
- package/src/memory/v2/qdrant.ts +14 -2
- package/src/memory/v2/router.ts +369 -62
- package/src/memory/v3/__tests__/coactivation-store.test.ts +422 -0
- package/src/memory/v3/__tests__/consolidation-job.test.ts +468 -0
- package/src/memory/v3/__tests__/edge-learning-job.test.ts +324 -0
- package/src/memory/v3/__tests__/edges.test.ts +563 -0
- package/src/memory/v3/__tests__/filter.test.ts +512 -0
- package/src/memory/v3/__tests__/gate.test.ts +574 -0
- package/src/memory/v3/__tests__/index-composition.test.ts +233 -0
- package/src/memory/v3/__tests__/loop.test.ts +530 -0
- package/src/memory/v3/__tests__/retriever.test.ts +226 -0
- package/src/memory/v3/__tests__/scouts.test.ts +440 -0
- package/src/memory/v3/__tests__/shadow-middleware.test.ts +312 -0
- package/src/memory/v3/__tests__/system-prompts.test.ts +154 -0
- package/src/memory/v3/__tests__/traversal.test.ts +469 -0
- package/src/memory/v3/__tests__/tree-index.test.ts +280 -0
- package/src/memory/v3/__tests__/tree-store.test.ts +529 -0
- package/src/memory/v3/__tests__/tree-walk.test.ts +707 -0
- package/src/memory/v3/__tests__/validate.test.ts +245 -0
- package/src/memory/v3/auto-edges.ts +223 -0
- package/src/memory/v3/coactivation-store.ts +124 -0
- package/src/memory/v3/consolidation-job.ts +323 -0
- package/src/memory/v3/edge-learning-job.ts +160 -0
- package/src/memory/v3/edges.ts +249 -0
- package/src/memory/v3/filter.ts +281 -0
- package/src/memory/v3/gate.ts +334 -0
- package/src/memory/v3/index-composition.ts +113 -0
- package/src/memory/v3/llm-capture.ts +46 -0
- package/src/memory/v3/loop.ts +382 -0
- package/src/memory/v3/maintenance.ts +144 -0
- package/src/memory/v3/prompt-context.ts +33 -0
- package/src/memory/v3/prompts/consolidation.ts +458 -0
- package/src/memory/v3/prompts/system-prompts.ts +196 -0
- package/src/memory/v3/retriever.ts +33 -0
- package/src/memory/v3/scouts.ts +420 -0
- package/src/memory/v3/shadow-middleware.ts +305 -0
- package/src/memory/v3/traversal.ts +206 -0
- package/src/memory/v3/tree-index.ts +237 -0
- package/src/memory/v3/tree-store.ts +394 -0
- package/src/memory/v3/tree-walk.ts +351 -0
- package/src/memory/v3/types.ts +65 -0
- package/src/memory/v3/validate.ts +300 -0
- package/src/messaging/providers/index.ts +7 -1
- package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +329 -3
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +34 -1
- package/src/messaging/providers/slack/adapter.ts +178 -25
- package/src/messaging/providers/slack/api.test.ts +54 -0
- package/src/messaging/providers/slack/api.ts +119 -3
- package/src/messaging/providers/slack/client.ts +12 -0
- package/src/messaging/providers/slack/deep-link.ts +20 -1
- package/src/messaging/providers/slack/message-metadata.test.ts +48 -0
- package/src/messaging/providers/slack/message-metadata.ts +156 -0
- package/src/messaging/providers/slack/render-transcript.test.ts +107 -75
- package/src/messaging/providers/slack/render-transcript.ts +176 -49
- package/src/messaging/providers/slack/send.test.ts +77 -0
- package/src/messaging/providers/slack/send.ts +8 -2
- package/src/messaging/providers/slack/types.ts +14 -0
- package/src/notifications/__tests__/emit-signal-home-feed.test.ts +4 -1
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +116 -54
- package/src/notifications/adapters/macos.ts +18 -1
- package/src/notifications/adapters/platform.ts +1 -1
- package/src/notifications/conversation-seed-composer.ts +14 -2
- package/src/notifications/decision-engine.ts +1 -4
- package/src/notifications/deferred-emit.ts +135 -0
- package/src/notifications/emit-signal.ts +38 -50
- package/src/notifications/home-feed-side-effect.ts +60 -30
- package/src/oauth/connect-orchestrator.ts +3 -0
- package/src/oauth/credential-token-resolver.ts +2 -0
- package/src/oauth/manual-token-connection.ts +19 -0
- package/src/oauth/oauth-store.ts +12 -0
- package/src/oauth/seed-providers.ts +22 -0
- package/src/permissions/prompter.ts +8 -5
- package/src/permissions/question-prompter.ts +5 -2
- package/src/permissions/secret-prompter.ts +6 -3
- package/src/plugin-api/index.ts +4 -0
- package/src/plugin-api/types.ts +7 -33
- package/src/plugins/defaults/index.ts +6 -0
- package/src/plugins/defaults/injectors.ts +100 -20
- package/src/plugins/external-plugin-loader.ts +5 -68
- package/src/plugins/types.ts +11 -16
- package/src/proactive-artifact/aux-message-injector.ts +17 -4
- package/src/prompts/__tests__/system-prompt.test.ts +46 -2
- package/src/prompts/__tests__/task-progress-hint-section.test.ts +3 -9
- package/src/prompts/normalize-onboarding.ts +40 -0
- package/src/prompts/persona-resolver.ts +36 -21
- package/src/prompts/sections.ts +69 -19
- package/src/prompts/system-prompt.ts +118 -216
- package/src/prompts/template-detection.ts +37 -0
- package/src/prompts/templates/BOOTSTRAP-CONTENT-AUTOMATION.md +141 -0
- package/src/prompts/templates/BOOTSTRAP.md +10 -2
- package/src/prompts/templates/VOICE.md +3 -0
- package/src/prompts/templates/system-sections.ts +281 -9
- package/src/providers/__tests__/connection-model-compat.test.ts +234 -0
- package/src/providers/__tests__/retry-callsite.test.ts +85 -5
- package/src/providers/anthropic/client.ts +159 -66
- package/src/providers/call-site-routing.ts +14 -2
- package/src/providers/connection-model-compat.ts +38 -0
- package/src/providers/connection-resolution.ts +16 -2
- package/src/providers/fireworks/client.ts +20 -2
- package/src/providers/gemini/client.ts +49 -6
- package/src/providers/inference/__tests__/base-url-route-validation.test.ts +342 -0
- package/src/providers/inference/__tests__/base-url-security.test.ts +189 -0
- package/src/providers/inference/__tests__/codex-token-refresh.test.ts +254 -0
- package/src/providers/inference/adapter-factory.ts +18 -1
- package/src/providers/inference/auth.ts +3 -3
- package/src/providers/inference/codex-token-refresh.ts +128 -0
- package/src/providers/inference/resolve-auth.ts +49 -6
- package/src/providers/minimax/client.ts +106 -0
- package/src/providers/model-catalog.ts +91 -1
- package/src/providers/model-intents.ts +1 -1
- package/src/providers/openai/chat-completions-provider.ts +63 -23
- package/src/providers/openai/codex-models.ts +18 -0
- package/src/providers/openai/responses-provider.ts +86 -23
- package/src/providers/openrouter/client.ts +5 -1
- package/src/providers/provider-send-message.ts +7 -1
- package/src/providers/retry.ts +34 -3
- package/src/providers/thinking-config.ts +26 -1
- package/src/providers/types.ts +25 -0
- package/src/providers/usage-tracking.ts +2 -0
- package/src/runtime/AGENTS.md +2 -2
- package/src/runtime/__tests__/agent-wake.test.ts +214 -0
- package/src/runtime/__tests__/background-job-runner.test.ts +128 -0
- package/src/runtime/agent-wake.ts +152 -56
- package/src/runtime/assistant-event-hub.ts +76 -6
- package/src/runtime/auth/route-policy.ts +43 -3
- package/src/runtime/background-job-runner.ts +26 -0
- package/src/runtime/btw-sidechain.ts +0 -6
- package/src/runtime/channel-reply-delivery.ts +182 -47
- package/src/runtime/channel-retry-sweep.ts +141 -16
- package/src/runtime/http-types.ts +7 -6
- package/src/runtime/migrations/vbundle-builder.ts +10 -3
- package/src/runtime/pending-interactions.ts +50 -8
- package/src/runtime/routes/__tests__/content-source-routes.test.ts +162 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +161 -1
- package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +14 -0
- package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +290 -0
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +512 -0
- package/src/runtime/routes/__tests__/sanity-routes.test.ts +280 -0
- package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +266 -0
- package/src/runtime/routes/acp-routes.test.ts +255 -6
- package/src/runtime/routes/acp-routes.ts +8 -1
- package/src/runtime/routes/approval-routes.ts +4 -1
- package/src/runtime/routes/avatar-routes.ts +10 -10
- package/src/runtime/routes/background-wake-routes.ts +188 -0
- package/src/runtime/routes/browser-tabs-routes.ts +200 -0
- package/src/runtime/routes/btw-routes.ts +0 -6
- package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +246 -0
- package/src/runtime/routes/content-source-routes.ts +78 -0
- package/src/runtime/routes/conversation-cli-routes.ts +147 -2
- package/src/runtime/routes/conversation-list-routes.ts +12 -4
- package/src/runtime/routes/conversation-management-routes.ts +77 -20
- package/src/runtime/routes/conversation-query-routes.ts +196 -31
- package/src/runtime/routes/conversation-routes.ts +472 -425
- package/src/runtime/routes/conversation-starter-routes.ts +6 -3
- package/src/runtime/routes/disk-pressure-routes.ts +1 -1
- package/src/runtime/routes/document-comments-routes.ts +287 -0
- package/src/runtime/routes/documents-routes.ts +33 -0
- package/src/runtime/routes/domain-routes.ts +60 -10
- package/src/runtime/routes/email-routes.ts +5 -2
- package/src/runtime/routes/events-routes.ts +54 -10
- package/src/runtime/routes/group-routes.ts +24 -8
- package/src/runtime/routes/home-feed-routes.ts +6 -3
- package/src/runtime/routes/host-app-control-routes.ts +1 -1
- package/src/runtime/routes/host-browser-routes.ts +17 -2
- package/src/runtime/routes/host-cu-routes.ts +2 -2
- package/src/runtime/routes/identity-routes.ts +21 -0
- package/src/runtime/routes/inbound-message-handler.ts +288 -58
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +96 -3
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +365 -6
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +283 -82
- package/src/runtime/routes/index.ts +20 -4
- package/src/runtime/routes/inference-profile-session-handler.ts +22 -12
- package/src/runtime/routes/inference-profile-session-routes.ts +7 -1
- package/src/runtime/routes/inference-provider-connection-routes.ts +63 -7
- package/src/runtime/routes/integrations/a2a.ts +60 -1
- package/src/runtime/routes/llm-call-sites-routes.ts +32 -5
- package/src/runtime/routes/log-export-routes.ts +39 -0
- package/src/runtime/routes/memory-item-routes.ts +8 -3
- package/src/runtime/routes/memory-v2-routes.ts +427 -0
- package/src/runtime/routes/memory-v3-routes.ts +316 -0
- package/src/runtime/routes/migration-routes.ts +21 -24
- package/src/runtime/routes/notification-routes.ts +19 -2
- package/src/runtime/routes/plugins-routes.ts +337 -0
- package/src/runtime/routes/question-routes.ts +4 -1
- package/src/runtime/routes/rename-conversation-routes.ts +6 -2
- package/src/runtime/routes/sanity-routes.ts +159 -0
- package/src/runtime/routes/secret-routes.ts +25 -5
- package/src/runtime/routes/settings-routes.ts +12 -11
- package/src/runtime/routes/slack-channel-routes.ts +188 -0
- package/src/runtime/routes/workspace-routes.ts +25 -10
- package/src/runtime/services/conversation-serializer.ts +30 -4
- package/src/runtime/sync/resource-sync-events.ts +106 -38
- package/src/runtime/sync/sync-publisher.test.ts +49 -0
- package/src/runtime/sync/sync-publisher.ts +2 -1
- package/src/runtime/verification-outbound-actions.ts +73 -1
- package/src/schedule/integration-status.ts +3 -1
- package/src/security/__tests__/oauth2-device-code.test.ts +479 -0
- package/src/security/oauth2-device-code.ts +307 -0
- package/src/security/oauth2.ts +26 -9
- package/src/security/secure-keys.ts +5 -0
- package/src/skills/catalog-install.ts +6 -2
- package/src/telemetry/types.ts +12 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +48 -0
- package/src/telemetry/usage-telemetry-reporter.ts +1 -0
- package/src/tools/acp/spawn.test.ts +119 -0
- package/src/tools/acp/spawn.ts +15 -2
- package/src/tools/apps/definitions.ts +2 -8
- package/src/tools/ask-question/ask-question-tool.test.ts +3 -3
- package/src/tools/ask-question/ask-question-tool.ts +38 -45
- package/src/tools/browser/__tests__/pinned-tabs.test.ts +150 -0
- package/src/tools/browser/browser-execution.ts +106 -0
- package/src/tools/browser/cdp-client/__tests__/browser-tabs-factory.test.ts +402 -0
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +28 -0
- package/src/tools/browser/cdp-client/__tests__/types.test.ts +4 -0
- package/src/tools/browser/cdp-client/cdp-inspect-client.ts +22 -0
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +42 -2
- package/src/tools/browser/cdp-client/factory.ts +171 -4
- package/src/tools/browser/cdp-client/local-cdp-client.ts +21 -0
- package/src/tools/browser/cdp-client/types.ts +101 -0
- package/src/tools/browser/pinned-tabs.ts +146 -0
- package/src/tools/computer-use/definitions.ts +22 -78
- package/src/tools/credential-execution/make-authenticated-request.ts +3 -9
- package/src/tools/credential-execution/manage-secure-command-tool.ts +3 -9
- package/src/tools/credential-execution/run-authenticated-command.ts +3 -9
- package/src/tools/credentials/vault.ts +3 -9
- package/src/tools/document/document-comment-tool.test.ts +379 -0
- package/src/tools/document/document-comment-tool.ts +156 -0
- package/src/tools/document/document-tool.ts +187 -2
- package/src/tools/execution-target.ts +21 -23
- package/src/tools/executor.ts +6 -1
- package/src/tools/filesystem/edit.ts +3 -9
- package/src/tools/filesystem/list.ts +3 -9
- package/src/tools/filesystem/read.ts +3 -9
- package/src/tools/filesystem/write.ts +3 -9
- package/src/tools/host-filesystem/edit.ts +3 -9
- package/src/tools/host-filesystem/read.ts +3 -9
- package/src/tools/host-filesystem/transfer.ts +3 -9
- package/src/tools/host-filesystem/write.ts +3 -9
- package/src/tools/host-terminal/host-shell.ts +3 -9
- package/src/tools/mcp/mcp-tool-factory.ts +1 -8
- package/src/tools/memory/register.test.ts +1 -1
- package/src/tools/memory/register.ts +4 -9
- package/src/tools/network/__tests__/web-fetch-metadata.test.ts +229 -0
- package/src/tools/network/__tests__/web-search-metadata.test.ts +346 -0
- package/src/tools/network/domain-normalize.ts +17 -0
- package/src/tools/network/web-fetch.ts +216 -73
- package/src/tools/network/web-search.ts +216 -98
- package/src/tools/registry.ts +7 -23
- package/src/tools/schema-transforms.ts +1 -1
- package/src/tools/skills/execute.ts +3 -9
- package/src/tools/skills/load.ts +3 -9
- package/src/tools/skills/skill-tool-factory.ts +1 -8
- package/src/tools/subagent/notify-parent.ts +3 -9
- package/src/tools/system/request-permission.ts +3 -9
- package/src/tools/terminal/safe-env.ts +3 -2
- package/src/tools/terminal/shell.ts +3 -9
- package/src/tools/tool-approval-handler.ts +19 -12
- package/src/tools/tool-defaults.ts +94 -0
- package/src/tools/types.ts +31 -98
- package/src/tools/ui-surface/definitions.ts +9 -23
- package/src/types/onboarding-context.ts +4 -0
- package/src/usage/pricing.ts +23 -0
- package/src/usage/types.ts +12 -0
- package/src/util/__tests__/favicon.test.ts +84 -0
- package/src/util/favicon.ts +40 -0
- package/src/util/logger.ts +16 -7
- package/src/util/platform.ts +7 -7
- package/src/util/sqlite3-runtime.ts +65 -0
- package/src/workspace/git-service.ts +75 -4
- package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +1 -0
- package/src/workspace/migrations/088-deprecate-background-conversation-override.ts +103 -0
- package/src/workspace/migrations/089-move-memory-tree-out-of-v3.ts +86 -0
- package/src/workspace/migrations/registry.ts +4 -0
- package/src/__tests__/compaction-strip-metadata-clear.test.ts +0 -206
- package/src/__tests__/message-complete-display-id.test.ts +0 -175
- package/src/config/bundled-skills/document/SKILL.md +0 -54
- package/src/config/bundled-skills/document/TOOLS.json +0 -106
- package/src/daemon/seed-files.ts +0 -18
- package/src/prompts/cache-boundary.ts +0 -8
- package/src/runtime/routes/interface-routes.ts +0 -43
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-create.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-delete.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-list.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-read.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-update.ts +0 -0
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory v3 — Tree structure validator.
|
|
3
|
+
*
|
|
4
|
+
* The v3 tree is hand-authored by a data-migration during the v2 → v3 rollout
|
|
5
|
+
* (nodes reference pages and sub-nodes by `page:`/`node:` refs). Because the
|
|
6
|
+
* structure is authored, not derived, it can drift: a ref can dangle, a page
|
|
7
|
+
* can be left unwired, two nodes can reference each other into a cycle, a
|
|
8
|
+
* parent node's compositional summary can fall behind a freshly-edited child,
|
|
9
|
+
* or a page `edges:` entry can point at a slug with no page.
|
|
10
|
+
*
|
|
11
|
+
* `validateTree` is the read-only report the migration (and any later
|
|
12
|
+
* structure-health probe) runs to surface those defects. It is deliberately
|
|
13
|
+
* **non-throwing**: the migration is in progress, so an incomplete tree is
|
|
14
|
+
* expected — the report is informational, and the caller decides what (if
|
|
15
|
+
* anything) is fatal. It builds the three indices it needs (tree, page, edge),
|
|
16
|
+
* walks the DAG, and returns counts plus the offending ids for each category.
|
|
17
|
+
*
|
|
18
|
+
* Categories:
|
|
19
|
+
* - `danglingChildRefs` — a node `children` entry (`node:`/`page:`) whose
|
|
20
|
+
* target node/page does not exist on disk.
|
|
21
|
+
* - `orphanPages` — concept pages present in the page index but not reachable
|
|
22
|
+
* from the tree root by descending every `node:` child. Informational while
|
|
23
|
+
* the migration is mid-flight (not every page is wired in yet). Synthetic
|
|
24
|
+
* page-index entries (skills, CLI commands) are excluded — they are never
|
|
25
|
+
* tree members.
|
|
26
|
+
* - `cycles` — back-edges found during a full DFS over `node:` adjacency
|
|
27
|
+
* (A → B → A). A cycle would make a naive descent loop forever.
|
|
28
|
+
* - `staleIndex` — a node whose own file mtime predates one of its `node:`
|
|
29
|
+
* children's mtime, hinting its compositional index/summary may be out of
|
|
30
|
+
* date relative to the child it composes.
|
|
31
|
+
* - `unknownEdgeTargets` — page `edges:` targets with no corresponding page
|
|
32
|
+
* index slug, reusing v2's `validateEdgeTargets`.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import { CLI_COMMAND_SLUG_PREFIX } from "../v2/cli-command-store.js";
|
|
36
|
+
import { getEdgeIndex, validateEdgeTargets } from "../v2/edge-index.js";
|
|
37
|
+
import { getPageIndex } from "../v2/page-index.js";
|
|
38
|
+
import { SKILL_SLUG_PREFIX } from "../v2/skill-store.js";
|
|
39
|
+
import { getTreeIndex, type TreeIndex } from "./tree-index.js";
|
|
40
|
+
import { getNodeMtimeMs } from "./tree-store.js";
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* A `node:` child whose mtime is newer than the parent node that composes it.
|
|
44
|
+
* `node` is the parent, `child` the fresher child, and the two `*MtimeMs`
|
|
45
|
+
* fields are their epoch-ms mtimes (parent < child triggers the report).
|
|
46
|
+
*/
|
|
47
|
+
export interface StaleIndexEntry {
|
|
48
|
+
node: string;
|
|
49
|
+
child: string;
|
|
50
|
+
nodeMtimeMs: number;
|
|
51
|
+
childMtimeMs: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Read-only health report over the v3 tree + its referenced pages/edges.
|
|
56
|
+
* Every list is sorted for deterministic output; `*Count` fields mirror the
|
|
57
|
+
* corresponding list length so callers can summarize without re-counting.
|
|
58
|
+
*/
|
|
59
|
+
export interface TreeValidationReport {
|
|
60
|
+
/** `node:`/`page:` children whose target does not exist. */
|
|
61
|
+
danglingChildRefs: Array<{
|
|
62
|
+
node: string;
|
|
63
|
+
ref: string;
|
|
64
|
+
kind: "node" | "page";
|
|
65
|
+
}>;
|
|
66
|
+
danglingChildRefCount: number;
|
|
67
|
+
/** Concept pages not reachable from the root by descending all node children. */
|
|
68
|
+
orphanPages: string[];
|
|
69
|
+
orphanPageCount: number;
|
|
70
|
+
/** Back-edges (`from → to`) closing a cycle during the full DFS descent. */
|
|
71
|
+
cycles: Array<{ from: string; to: string }>;
|
|
72
|
+
cycleCount: number;
|
|
73
|
+
/** Nodes whose mtime predates a child node's mtime. */
|
|
74
|
+
staleIndex: StaleIndexEntry[];
|
|
75
|
+
staleIndexCount: number;
|
|
76
|
+
/** Page `edges:` targets with no corresponding page-index slug. */
|
|
77
|
+
unknownEdgeTargets: Array<{ from: string; to: string }>;
|
|
78
|
+
unknownEdgeTargetCount: number;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** True when a page-index slug is a synthetic (non-concept-page) entry. */
|
|
82
|
+
function isSyntheticSlug(slug: string): boolean {
|
|
83
|
+
return (
|
|
84
|
+
slug.startsWith(SKILL_SLUG_PREFIX) ||
|
|
85
|
+
slug.startsWith(CLI_COMMAND_SLUG_PREFIX)
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Collect dangling `node:`/`page:` child refs: every node child whose target
|
|
91
|
+
* node id is absent from `tree.nodes`, and every page child whose slug is
|
|
92
|
+
* absent from `knownPageSlugs`. Sorted by `(node, kind, ref)`.
|
|
93
|
+
*/
|
|
94
|
+
function collectDanglingChildRefs(
|
|
95
|
+
tree: TreeIndex,
|
|
96
|
+
knownPageSlugs: ReadonlySet<string>,
|
|
97
|
+
): Array<{ node: string; ref: string; kind: "node" | "page" }> {
|
|
98
|
+
const dangling: Array<{ node: string; ref: string; kind: "node" | "page" }> =
|
|
99
|
+
[];
|
|
100
|
+
for (const [nodeId, children] of tree.childrenByNode) {
|
|
101
|
+
for (const child of children) {
|
|
102
|
+
const exists =
|
|
103
|
+
child.kind === "node"
|
|
104
|
+
? tree.nodes.has(child.ref)
|
|
105
|
+
: knownPageSlugs.has(child.ref);
|
|
106
|
+
if (!exists) {
|
|
107
|
+
dangling.push({ node: nodeId, ref: child.ref, kind: child.kind });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
dangling.sort(
|
|
112
|
+
(a, b) =>
|
|
113
|
+
a.node.localeCompare(b.node) ||
|
|
114
|
+
a.kind.localeCompare(b.kind) ||
|
|
115
|
+
a.ref.localeCompare(b.ref),
|
|
116
|
+
);
|
|
117
|
+
return dangling;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Resolve the existing `node:` children of `nodeId`, in `children` order. Refs
|
|
122
|
+
* to absent nodes are skipped (those are reported separately as dangling) so
|
|
123
|
+
* the descent never recurses into a node that isn't on disk.
|
|
124
|
+
*/
|
|
125
|
+
function nodeChildrenOf(tree: TreeIndex, nodeId: string): string[] {
|
|
126
|
+
const children = tree.childrenByNode.get(nodeId) ?? [];
|
|
127
|
+
const out: string[] = [];
|
|
128
|
+
for (const child of children) {
|
|
129
|
+
if (child.kind === "node" && tree.nodes.has(child.ref)) {
|
|
130
|
+
out.push(child.ref);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return out;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Full DFS over `node:` adjacency from `tree.root`. Returns the set of
|
|
138
|
+
* reachable node ids (for orphan-page reachability) and the back-edges that
|
|
139
|
+
* close a cycle. A back-edge is an edge into a node still on the active
|
|
140
|
+
* recursion stack (classic gray-node cycle detection); `visited` (black)
|
|
141
|
+
* prevents re-walking shared DAG sub-nodes.
|
|
142
|
+
*/
|
|
143
|
+
function descend(tree: TreeIndex): {
|
|
144
|
+
reachableNodes: Set<string>;
|
|
145
|
+
cycles: Array<{ from: string; to: string }>;
|
|
146
|
+
} {
|
|
147
|
+
const reachableNodes = new Set<string>();
|
|
148
|
+
const onStack = new Set<string>();
|
|
149
|
+
const cycles: Array<{ from: string; to: string }> = [];
|
|
150
|
+
|
|
151
|
+
// Iterative DFS with an explicit stack so deep trees don't blow the call
|
|
152
|
+
// stack. Each frame tracks its child cursor; we push a child frame, and on
|
|
153
|
+
// exhaustion pop the parent off the recursion stack (`onStack`).
|
|
154
|
+
type Frame = { node: string; children: string[]; cursor: number };
|
|
155
|
+
const stack: Frame[] = [];
|
|
156
|
+
|
|
157
|
+
function enter(nodeId: string): void {
|
|
158
|
+
reachableNodes.add(nodeId);
|
|
159
|
+
onStack.add(nodeId);
|
|
160
|
+
stack.push({
|
|
161
|
+
node: nodeId,
|
|
162
|
+
children: nodeChildrenOf(tree, nodeId),
|
|
163
|
+
cursor: 0,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (tree.nodes.has(tree.root)) {
|
|
168
|
+
enter(tree.root);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
while (stack.length > 0) {
|
|
172
|
+
const frame = stack[stack.length - 1];
|
|
173
|
+
if (frame.cursor >= frame.children.length) {
|
|
174
|
+
onStack.delete(frame.node);
|
|
175
|
+
stack.pop();
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
const child = frame.children[frame.cursor++];
|
|
179
|
+
if (onStack.has(child)) {
|
|
180
|
+
// Edge into an ancestor still on the stack → cycle-closing back-edge.
|
|
181
|
+
cycles.push({ from: frame.node, to: child });
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (reachableNodes.has(child)) {
|
|
185
|
+
// Already fully explored via another parent (shared DAG sub-node).
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
enter(child);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
cycles.sort(
|
|
192
|
+
(a, b) => a.from.localeCompare(b.from) || a.to.localeCompare(b.to),
|
|
193
|
+
);
|
|
194
|
+
return { reachableNodes, cycles };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Concept pages reachable from the tree: every `page:` child of a reachable
|
|
199
|
+
* node. Pages hanging off unreachable nodes are *not* counted reachable — they
|
|
200
|
+
* only become reachable once their parent chain links back to the root.
|
|
201
|
+
*/
|
|
202
|
+
function reachablePages(
|
|
203
|
+
tree: TreeIndex,
|
|
204
|
+
reachableNodes: ReadonlySet<string>,
|
|
205
|
+
): Set<string> {
|
|
206
|
+
const pages = new Set<string>();
|
|
207
|
+
for (const nodeId of reachableNodes) {
|
|
208
|
+
for (const child of tree.childrenByNode.get(nodeId) ?? []) {
|
|
209
|
+
if (child.kind === "page") pages.add(child.ref);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return pages;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Nodes whose own mtime predates one of their `node:` children's mtime. A
|
|
217
|
+
* missing node file reads as mtime 0 (oldest), so the check never flags a
|
|
218
|
+
* parent against an absent child. Sorted by `(node, child)`.
|
|
219
|
+
*/
|
|
220
|
+
async function collectStaleIndex(
|
|
221
|
+
workspaceDir: string,
|
|
222
|
+
tree: TreeIndex,
|
|
223
|
+
): Promise<StaleIndexEntry[]> {
|
|
224
|
+
const ids = [...tree.nodes.keys()];
|
|
225
|
+
const mtimes = new Map<string, number>();
|
|
226
|
+
await Promise.all(
|
|
227
|
+
ids.map(async (id) => {
|
|
228
|
+
mtimes.set(id, await getNodeMtimeMs(workspaceDir, id));
|
|
229
|
+
}),
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
const stale: StaleIndexEntry[] = [];
|
|
233
|
+
for (const node of ids) {
|
|
234
|
+
const nodeMtimeMs = mtimes.get(node) ?? 0;
|
|
235
|
+
for (const child of nodeChildrenOf(tree, node)) {
|
|
236
|
+
const childMtimeMs = mtimes.get(child) ?? 0;
|
|
237
|
+
if (nodeMtimeMs < childMtimeMs) {
|
|
238
|
+
stale.push({ node, child, nodeMtimeMs, childMtimeMs });
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
stale.sort(
|
|
243
|
+
(a, b) => a.node.localeCompare(b.node) || a.child.localeCompare(b.child),
|
|
244
|
+
);
|
|
245
|
+
return stale;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Validate the hand-authored v3 tree structure for `workspaceDir` and return a
|
|
250
|
+
* {@link TreeValidationReport}. Builds the tree, page, and edge indices, walks
|
|
251
|
+
* the DAG from the root, and reports the five defect categories. Never throws —
|
|
252
|
+
* it is a report, not an assertion.
|
|
253
|
+
*/
|
|
254
|
+
export async function validateTree(
|
|
255
|
+
workspaceDir: string,
|
|
256
|
+
): Promise<TreeValidationReport> {
|
|
257
|
+
const [tree, pageIndex, edgeIndex] = await Promise.all([
|
|
258
|
+
getTreeIndex(workspaceDir),
|
|
259
|
+
getPageIndex(workspaceDir),
|
|
260
|
+
getEdgeIndex(workspaceDir),
|
|
261
|
+
]);
|
|
262
|
+
|
|
263
|
+
const knownPageSlugs = new Set(pageIndex.bySlug.keys());
|
|
264
|
+
|
|
265
|
+
// Kick off the stale-index mtime stats up front — it only depends on the
|
|
266
|
+
// tree, not on the DAG walk below — so its filesystem reads overlap the
|
|
267
|
+
// (synchronous) descent rather than running strictly after it.
|
|
268
|
+
const staleIndexPromise = collectStaleIndex(workspaceDir, tree);
|
|
269
|
+
|
|
270
|
+
const danglingChildRefs = collectDanglingChildRefs(tree, knownPageSlugs);
|
|
271
|
+
|
|
272
|
+
const { reachableNodes, cycles } = descend(tree);
|
|
273
|
+
|
|
274
|
+
const reached = reachablePages(tree, reachableNodes);
|
|
275
|
+
const orphanPages = [...knownPageSlugs]
|
|
276
|
+
.filter((slug) => !isSyntheticSlug(slug) && !reached.has(slug))
|
|
277
|
+
.sort();
|
|
278
|
+
|
|
279
|
+
const staleIndex = await staleIndexPromise;
|
|
280
|
+
|
|
281
|
+
// Edge graph is page-only; knownSlugs is the full page-index slug set so an
|
|
282
|
+
// edge pointing at a skill/CLI entry is not spuriously flagged unknown.
|
|
283
|
+
const unknownEdgeTargets = validateEdgeTargets(
|
|
284
|
+
edgeIndex,
|
|
285
|
+
knownPageSlugs,
|
|
286
|
+
).missing;
|
|
287
|
+
|
|
288
|
+
return {
|
|
289
|
+
danglingChildRefs,
|
|
290
|
+
danglingChildRefCount: danglingChildRefs.length,
|
|
291
|
+
orphanPages,
|
|
292
|
+
orphanPageCount: orphanPages.length,
|
|
293
|
+
cycles,
|
|
294
|
+
cycleCount: cycles.length,
|
|
295
|
+
staleIndex,
|
|
296
|
+
staleIndexCount: staleIndex.length,
|
|
297
|
+
unknownEdgeTargets,
|
|
298
|
+
unknownEdgeTargetCount: unknownEdgeTargets.length,
|
|
299
|
+
};
|
|
300
|
+
}
|
|
@@ -174,8 +174,14 @@ async function deliverSlack(
|
|
|
174
174
|
channel,
|
|
175
175
|
threadTs: statusThreadTs,
|
|
176
176
|
status,
|
|
177
|
+
loadingMessages,
|
|
177
178
|
} = payload.assistantThreadStatus;
|
|
178
|
-
await sendSlackAssistantThreadStatus(
|
|
179
|
+
await sendSlackAssistantThreadStatus(
|
|
180
|
+
channel,
|
|
181
|
+
statusThreadTs,
|
|
182
|
+
status,
|
|
183
|
+
loadingMessages,
|
|
184
|
+
);
|
|
179
185
|
return { ok: true };
|
|
180
186
|
}
|
|
181
187
|
|
|
@@ -21,7 +21,7 @@ mock.module("../../../../oauth/oauth-store.js", () => ({
|
|
|
21
21
|
isProviderConnected: async () => false,
|
|
22
22
|
}));
|
|
23
23
|
|
|
24
|
-
const findContactChannelMock = mock(() => undefined);
|
|
24
|
+
const findContactChannelMock = mock((): unknown => undefined);
|
|
25
25
|
const upsertContactChannelMock = mock(() => {});
|
|
26
26
|
mock.module("../../../../contacts/contact-store.js", () => ({
|
|
27
27
|
findContactChannel: findContactChannelMock,
|
|
@@ -30,9 +30,13 @@ mock.module("../../../../contacts/contacts-write.js", () => ({
|
|
|
30
30
|
upsertContactChannel: upsertContactChannelMock,
|
|
31
31
|
}));
|
|
32
32
|
|
|
33
|
-
import {
|
|
33
|
+
import {
|
|
34
|
+
__resetSlackUserInfoCacheForTests,
|
|
35
|
+
slackProvider,
|
|
36
|
+
} from "../adapter.js";
|
|
34
37
|
|
|
35
38
|
const originalFetch = globalThis.fetch;
|
|
39
|
+
let userInfoCalls: string[] = [];
|
|
36
40
|
|
|
37
41
|
function installFetchStub() {
|
|
38
42
|
globalThis.fetch = (async (
|
|
@@ -60,6 +64,73 @@ function fakeSlackResponse(url: string): Record<string, unknown> {
|
|
|
60
64
|
const method = parsed.pathname.split("/").at(-1);
|
|
61
65
|
|
|
62
66
|
if (method === "conversations.history") {
|
|
67
|
+
if (parsed.searchParams.get("channel") === "C_USERINFO_FAIL") {
|
|
68
|
+
return {
|
|
69
|
+
ok: true,
|
|
70
|
+
has_more: false,
|
|
71
|
+
messages: [
|
|
72
|
+
{
|
|
73
|
+
type: "message",
|
|
74
|
+
ts: "1700000006.000700",
|
|
75
|
+
user: "UMISSING",
|
|
76
|
+
text: "Fallback sender message",
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (parsed.searchParams.get("channel") === "C_USERINFO_RETRY") {
|
|
83
|
+
return {
|
|
84
|
+
ok: true,
|
|
85
|
+
has_more: false,
|
|
86
|
+
messages: [
|
|
87
|
+
{
|
|
88
|
+
type: "message",
|
|
89
|
+
ts: "1700000007.000800",
|
|
90
|
+
user: "URETRY",
|
|
91
|
+
text: "Retry sender message",
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (parsed.searchParams.get("channel") === "C_TIMEZONE_CACHE") {
|
|
98
|
+
return {
|
|
99
|
+
ok: true,
|
|
100
|
+
has_more: false,
|
|
101
|
+
messages: [
|
|
102
|
+
{
|
|
103
|
+
type: "message",
|
|
104
|
+
ts: "1700000004.000500",
|
|
105
|
+
user: "USENDER",
|
|
106
|
+
text: "First timezone-bearing message",
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
type: "message",
|
|
110
|
+
ts: "1700000005.000600",
|
|
111
|
+
user: "USENDER",
|
|
112
|
+
text: "Second timezone-bearing message",
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (parsed.searchParams.get("channel") === "C_BOT_HISTORY") {
|
|
119
|
+
return {
|
|
120
|
+
ok: true,
|
|
121
|
+
has_more: false,
|
|
122
|
+
messages: [
|
|
123
|
+
{
|
|
124
|
+
type: "message",
|
|
125
|
+
subtype: "bot_message",
|
|
126
|
+
ts: "1700000003.000400",
|
|
127
|
+
bot_id: "B_ASSISTANT",
|
|
128
|
+
text: "Bot-authored history",
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
63
134
|
return {
|
|
64
135
|
ok: true,
|
|
65
136
|
has_more: false,
|
|
@@ -117,7 +188,9 @@ function fakeSlackResponse(url: string): Record<string, unknown> {
|
|
|
117
188
|
}
|
|
118
189
|
|
|
119
190
|
if (method === "users.info") {
|
|
120
|
-
|
|
191
|
+
const userId = parsed.searchParams.get("user") ?? "";
|
|
192
|
+
userInfoCalls.push(userId);
|
|
193
|
+
return fakeUserInfoResponse(userId);
|
|
121
194
|
}
|
|
122
195
|
|
|
123
196
|
return { ok: true };
|
|
@@ -141,6 +214,9 @@ function fakeUserInfoResponse(userId: string): Record<string, unknown> {
|
|
|
141
214
|
user: {
|
|
142
215
|
id: "USENDER",
|
|
143
216
|
name: "sender",
|
|
217
|
+
tz: "America/New_York",
|
|
218
|
+
tz_label: "Eastern Time",
|
|
219
|
+
tz_offset: -18000,
|
|
144
220
|
profile: { display_name: "Sender" },
|
|
145
221
|
},
|
|
146
222
|
};
|
|
@@ -157,11 +233,93 @@ function fakeUserInfoResponse(userId: string): Record<string, unknown> {
|
|
|
157
233
|
};
|
|
158
234
|
}
|
|
159
235
|
|
|
236
|
+
if (userId === "URETRY") {
|
|
237
|
+
const retryCalls = userInfoCalls.filter((call) => call === userId).length;
|
|
238
|
+
if (retryCalls === 1) {
|
|
239
|
+
return { ok: false, error: "temporarily_unavailable" };
|
|
240
|
+
}
|
|
241
|
+
return {
|
|
242
|
+
ok: true,
|
|
243
|
+
user: {
|
|
244
|
+
id: "URETRY",
|
|
245
|
+
name: "retry_sender",
|
|
246
|
+
tz: "America/Chicago",
|
|
247
|
+
tz_label: "Central Time",
|
|
248
|
+
tz_offset: -21600,
|
|
249
|
+
profile: { display_name: "Retry Sender" },
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
160
254
|
return { ok: false, error: "user_not_found" };
|
|
161
255
|
}
|
|
162
256
|
|
|
257
|
+
function makeOAuthConnection(
|
|
258
|
+
id: string,
|
|
259
|
+
accountInfo: string,
|
|
260
|
+
displayName: string,
|
|
261
|
+
timezone: string,
|
|
262
|
+
timezoneLabel: string,
|
|
263
|
+
timezoneOffsetSeconds: number,
|
|
264
|
+
): OAuthConnection {
|
|
265
|
+
return {
|
|
266
|
+
id,
|
|
267
|
+
provider: "slack",
|
|
268
|
+
accountInfo,
|
|
269
|
+
request: async (req) => {
|
|
270
|
+
if (req.path === "/conversations.history") {
|
|
271
|
+
return {
|
|
272
|
+
status: 200,
|
|
273
|
+
headers: {},
|
|
274
|
+
body: {
|
|
275
|
+
ok: true,
|
|
276
|
+
has_more: false,
|
|
277
|
+
messages: [
|
|
278
|
+
{
|
|
279
|
+
type: "message",
|
|
280
|
+
ts: "1700000008.000900",
|
|
281
|
+
user: "USAME",
|
|
282
|
+
text: "Account scoped sender",
|
|
283
|
+
},
|
|
284
|
+
],
|
|
285
|
+
},
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
if (req.path === "/users.info") {
|
|
289
|
+
const userId = req.query?.user;
|
|
290
|
+
userInfoCalls.push(`${id}:${userId}`);
|
|
291
|
+
return {
|
|
292
|
+
status: 200,
|
|
293
|
+
headers: {},
|
|
294
|
+
body: {
|
|
295
|
+
ok: true,
|
|
296
|
+
user: {
|
|
297
|
+
id: "USAME",
|
|
298
|
+
name: displayName.toLowerCase().replaceAll(" ", "_"),
|
|
299
|
+
tz: timezone,
|
|
300
|
+
tz_label: timezoneLabel,
|
|
301
|
+
tz_offset: timezoneOffsetSeconds,
|
|
302
|
+
profile: { display_name: displayName },
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
return {
|
|
308
|
+
status: 200,
|
|
309
|
+
headers: {},
|
|
310
|
+
body: { ok: true },
|
|
311
|
+
};
|
|
312
|
+
},
|
|
313
|
+
withToken: async <T>(_fn: (token: string) => Promise<T>): Promise<T> => {
|
|
314
|
+
throw new Error("withToken was not expected");
|
|
315
|
+
},
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
163
319
|
describe("Slack adapter mention rendering", () => {
|
|
164
320
|
beforeEach(async () => {
|
|
321
|
+
__resetSlackUserInfoCacheForTests();
|
|
322
|
+
userInfoCalls = [];
|
|
165
323
|
getSecureKeyAsyncMock.mockReset();
|
|
166
324
|
getSecureKeyAsyncMock.mockImplementation(async (key: string) => {
|
|
167
325
|
if (key === credentialKey("slack_channel", "bot_token")) {
|
|
@@ -190,6 +348,174 @@ describe("Slack adapter mention rendering", () => {
|
|
|
190
348
|
expect(messages[0].reactions).toEqual([{ name: "eyes", count: 1 }]);
|
|
191
349
|
});
|
|
192
350
|
|
|
351
|
+
test("getHistory preserves bot ids for bot-authored Slack messages", async () => {
|
|
352
|
+
const messages = await slackProvider.getHistory(undefined, "C_BOT_HISTORY");
|
|
353
|
+
|
|
354
|
+
expect(messages).toHaveLength(1);
|
|
355
|
+
expect(messages[0].sender).toEqual({ id: "B_ASSISTANT", name: "unknown" });
|
|
356
|
+
expect(messages[0].metadata).toEqual({
|
|
357
|
+
isBot: true,
|
|
358
|
+
slackBotId: "B_ASSISTANT",
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
test("getHistory caches Slack user info and maps timezone metadata", async () => {
|
|
363
|
+
const messages = await slackProvider.getHistory(
|
|
364
|
+
undefined,
|
|
365
|
+
"C_TIMEZONE_CACHE",
|
|
366
|
+
);
|
|
367
|
+
|
|
368
|
+
expect(messages).toHaveLength(2);
|
|
369
|
+
expect(userInfoCalls.filter((userId) => userId === "USENDER")).toHaveLength(
|
|
370
|
+
1,
|
|
371
|
+
);
|
|
372
|
+
expect(messages.map((message) => message.sender)).toEqual([
|
|
373
|
+
{ id: "USENDER", name: "Sender" },
|
|
374
|
+
{ id: "USENDER", name: "Sender" },
|
|
375
|
+
]);
|
|
376
|
+
expect(messages.map((message) => message.metadata)).toEqual([
|
|
377
|
+
{
|
|
378
|
+
actorTimezone: "America/New_York",
|
|
379
|
+
actorTimezoneLabel: "Eastern Time",
|
|
380
|
+
actorTimezoneOffsetSeconds: -18000,
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
actorTimezone: "America/New_York",
|
|
384
|
+
actorTimezoneLabel: "Eastern Time",
|
|
385
|
+
actorTimezoneOffsetSeconds: -18000,
|
|
386
|
+
},
|
|
387
|
+
]);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
test("getHistory prefers contact display names while still fetching Slack timezone facts", async () => {
|
|
391
|
+
findContactChannelMock.mockImplementationOnce(() => ({
|
|
392
|
+
contact: { displayName: "Saved Sender" },
|
|
393
|
+
}));
|
|
394
|
+
|
|
395
|
+
const messages = await slackProvider.getHistory(
|
|
396
|
+
undefined,
|
|
397
|
+
"C_TIMEZONE_CACHE",
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
expect(userInfoCalls.filter((userId) => userId === "USENDER")).toHaveLength(
|
|
401
|
+
1,
|
|
402
|
+
);
|
|
403
|
+
expect(messages[0].sender).toEqual({
|
|
404
|
+
id: "USENDER",
|
|
405
|
+
name: "Saved Sender",
|
|
406
|
+
});
|
|
407
|
+
expect(messages[0].metadata).toEqual({
|
|
408
|
+
actorTimezone: "America/New_York",
|
|
409
|
+
actorTimezoneLabel: "Eastern Time",
|
|
410
|
+
actorTimezoneOffsetSeconds: -18000,
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
test("getHistory caches fallback user info for permanent users.info failures", async () => {
|
|
415
|
+
const firstMessages = await slackProvider.getHistory(
|
|
416
|
+
undefined,
|
|
417
|
+
"C_USERINFO_FAIL",
|
|
418
|
+
);
|
|
419
|
+
const secondMessages = await slackProvider.getHistory(
|
|
420
|
+
undefined,
|
|
421
|
+
"C_USERINFO_FAIL",
|
|
422
|
+
);
|
|
423
|
+
|
|
424
|
+
expect(firstMessages).toHaveLength(1);
|
|
425
|
+
expect(secondMessages).toHaveLength(1);
|
|
426
|
+
expect(
|
|
427
|
+
userInfoCalls.filter((userId) => userId === "UMISSING"),
|
|
428
|
+
).toHaveLength(1);
|
|
429
|
+
expect(firstMessages[0].sender).toEqual({
|
|
430
|
+
id: "UMISSING",
|
|
431
|
+
name: "UMISSING",
|
|
432
|
+
});
|
|
433
|
+
expect(secondMessages[0].sender).toEqual({
|
|
434
|
+
id: "UMISSING",
|
|
435
|
+
name: "UMISSING",
|
|
436
|
+
});
|
|
437
|
+
expect(firstMessages[0].metadata).toBeUndefined();
|
|
438
|
+
expect(secondMessages[0].metadata).toBeUndefined();
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
test("getHistory does not cache fallback user info after transient users.info failures", async () => {
|
|
442
|
+
const firstMessages = await slackProvider.getHistory(
|
|
443
|
+
undefined,
|
|
444
|
+
"C_USERINFO_RETRY",
|
|
445
|
+
);
|
|
446
|
+
const secondMessages = await slackProvider.getHistory(
|
|
447
|
+
undefined,
|
|
448
|
+
"C_USERINFO_RETRY",
|
|
449
|
+
);
|
|
450
|
+
|
|
451
|
+
expect(userInfoCalls.filter((userId) => userId === "URETRY")).toHaveLength(
|
|
452
|
+
2,
|
|
453
|
+
);
|
|
454
|
+
expect(firstMessages[0].sender).toEqual({
|
|
455
|
+
id: "URETRY",
|
|
456
|
+
name: "URETRY",
|
|
457
|
+
});
|
|
458
|
+
expect(firstMessages[0].metadata).toBeUndefined();
|
|
459
|
+
expect(secondMessages[0].sender).toEqual({
|
|
460
|
+
id: "URETRY",
|
|
461
|
+
name: "Retry Sender",
|
|
462
|
+
});
|
|
463
|
+
expect(secondMessages[0].metadata).toEqual({
|
|
464
|
+
actorTimezone: "America/Chicago",
|
|
465
|
+
actorTimezoneLabel: "Central Time",
|
|
466
|
+
actorTimezoneOffsetSeconds: -21600,
|
|
467
|
+
});
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
test("getHistory scopes Slack user info cache by OAuth connection", async () => {
|
|
471
|
+
const workspaceA = makeOAuthConnection(
|
|
472
|
+
"conn-workspace-a",
|
|
473
|
+
"workspace-a",
|
|
474
|
+
"Workspace A Sender",
|
|
475
|
+
"America/Los_Angeles",
|
|
476
|
+
"Pacific Time",
|
|
477
|
+
-28800,
|
|
478
|
+
);
|
|
479
|
+
const workspaceB = makeOAuthConnection(
|
|
480
|
+
"conn-workspace-b",
|
|
481
|
+
"workspace-b",
|
|
482
|
+
"Workspace B Sender",
|
|
483
|
+
"Europe/London",
|
|
484
|
+
"Greenwich Mean Time",
|
|
485
|
+
0,
|
|
486
|
+
);
|
|
487
|
+
|
|
488
|
+
const messagesA = await slackProvider.getHistory(
|
|
489
|
+
workspaceA,
|
|
490
|
+
"C_ACCOUNT_SCOPE",
|
|
491
|
+
);
|
|
492
|
+
const messagesB = await slackProvider.getHistory(
|
|
493
|
+
workspaceB,
|
|
494
|
+
"C_ACCOUNT_SCOPE",
|
|
495
|
+
);
|
|
496
|
+
|
|
497
|
+
expect(userInfoCalls).toContain("conn-workspace-a:USAME");
|
|
498
|
+
expect(userInfoCalls).toContain("conn-workspace-b:USAME");
|
|
499
|
+
expect(messagesA[0].sender).toEqual({
|
|
500
|
+
id: "USAME",
|
|
501
|
+
name: "Workspace A Sender",
|
|
502
|
+
});
|
|
503
|
+
expect(messagesA[0].metadata).toEqual({
|
|
504
|
+
actorTimezone: "America/Los_Angeles",
|
|
505
|
+
actorTimezoneLabel: "Pacific Time",
|
|
506
|
+
actorTimezoneOffsetSeconds: -28800,
|
|
507
|
+
});
|
|
508
|
+
expect(messagesB[0].sender).toEqual({
|
|
509
|
+
id: "USAME",
|
|
510
|
+
name: "Workspace B Sender",
|
|
511
|
+
});
|
|
512
|
+
expect(messagesB[0].metadata).toEqual({
|
|
513
|
+
actorTimezone: "Europe/London",
|
|
514
|
+
actorTimezoneLabel: "Greenwich Mean Time",
|
|
515
|
+
actorTimezoneOffsetSeconds: 0,
|
|
516
|
+
});
|
|
517
|
+
});
|
|
518
|
+
|
|
193
519
|
test("getThreadReplies renders Slack user mentions for model-facing text without changing sender identity", async () => {
|
|
194
520
|
const messages = await slackProvider.getThreadReplies!(
|
|
195
521
|
undefined,
|