@vellumai/assistant 0.6.4 → 0.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.prettierignore +5 -0
- package/ARCHITECTURE.md +32 -36
- package/Dockerfile +12 -0
- package/README.md +3 -4
- package/bun.lock +8 -3
- package/docs/architecture/integrations.md +1 -20
- package/docs/architecture/security.md +16 -16
- package/docs/error-handling.md +111 -0
- package/docs/skills.md +10 -10
- package/docs/stt-provider-onboarding.md +2 -1
- package/knip.json +9 -2
- package/node_modules/@vellumai/ces-contracts/package.json +2 -1
- package/node_modules/@vellumai/ces-contracts/src/__tests__/trust-rules.test.ts +471 -0
- package/node_modules/@vellumai/ces-contracts/src/trust-rules.ts +398 -4
- package/node_modules/@vellumai/credential-storage/bun.lock +2 -2
- package/node_modules/@vellumai/credential-storage/package.json +2 -2
- package/node_modules/@vellumai/credential-storage/src/oauth-runtime.ts +20 -2
- package/node_modules/@vellumai/egress-proxy/bun.lock +2 -2
- package/node_modules/@vellumai/egress-proxy/package.json +2 -2
- package/openapi.yaml +123 -11
- package/package.json +6 -3
- package/scripts/generate-openapi.ts +50 -11
- package/src/__tests__/agent-loop-callsite-precedence.test.ts +318 -0
- package/src/__tests__/agent-loop-sentry-hygiene.test.ts +137 -0
- package/src/__tests__/agent-loop.test.ts +112 -1
- package/src/__tests__/anthropic-error-formatting.test.ts +98 -0
- package/src/__tests__/anthropic-provider.test.ts +171 -2
- package/src/__tests__/approval-cascade.test.ts +31 -10
- package/src/__tests__/approval-routes-http.test.ts +134 -10
- package/src/__tests__/assistant-attachments.test.ts +44 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +29 -0
- package/src/__tests__/browser-fill-credential.test.ts +1 -1
- package/src/__tests__/browser-identifier-parity-guard.test.ts +53 -0
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +23 -33
- package/src/__tests__/browser-skill-endstate.test.ts +51 -182
- package/src/__tests__/btw-routes.test.ts +47 -1
- package/src/__tests__/call-controller.test.ts +1 -2
- package/src/__tests__/call-site-routing-provider.test.ts +214 -0
- package/src/__tests__/catalog-cache.test.ts +27 -4
- package/src/__tests__/channel-approval-routes.test.ts +4 -4
- package/src/__tests__/channel-reply-delivery.test.ts +300 -2
- package/src/__tests__/checker.test.ts +428 -501
- package/src/__tests__/cli-command-risk-guard.test.ts +30 -33
- package/src/__tests__/compaction-circuit-breaker.test.ts +336 -0
- package/src/__tests__/compaction.benchmark.test.ts +1 -1
- package/src/__tests__/config-analysis.test.ts +11 -28
- package/src/__tests__/config-loader-backfill.test.ts +174 -0
- package/src/__tests__/config-loader-corrupt.test.ts +183 -0
- package/src/__tests__/config-loader-quarantine-bulletin.test.ts +202 -0
- package/src/__tests__/config-schema-cmd.test.ts +11 -5
- package/src/__tests__/config-schema.test.ts +427 -114
- package/src/__tests__/config-watcher.test.ts +2 -2
- package/src/__tests__/contact-store-user-file.test.ts +72 -73
- package/src/__tests__/contacts-write.test.ts +4 -4
- package/src/__tests__/context-token-estimator.test.ts +191 -1
- package/src/__tests__/context-window-manager.test.ts +530 -2
- package/src/__tests__/conversation-abort-tool-results.test.ts +30 -16
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +61 -17
- package/src/__tests__/conversation-agent-loop.test.ts +412 -82
- package/src/__tests__/conversation-attachments.test.ts +1 -1
- package/src/__tests__/conversation-confirmation-signals.test.ts +30 -9
- package/src/__tests__/conversation-error.test.ts +37 -6
- package/src/__tests__/conversation-history-web-search.test.ts +6 -0
- package/src/__tests__/conversation-init.benchmark.test.ts +36 -0
- package/src/__tests__/conversation-lifecycle.test.ts +336 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +27 -10
- package/src/__tests__/conversation-pre-run-repair.test.ts +30 -16
- package/src/__tests__/conversation-process-callsite.test.ts +306 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +30 -16
- package/src/__tests__/conversation-queue.test.ts +41 -26
- package/src/__tests__/conversation-routes-disk-view.test.ts +29 -1
- package/src/__tests__/conversation-routes-slash-commands.test.ts +31 -3
- package/src/__tests__/conversation-runtime-assembly.test.ts +2735 -55
- package/src/__tests__/conversation-runtime-workspace.test.ts +12 -12
- package/src/__tests__/conversation-skill-tools.test.ts +12 -146
- package/src/__tests__/conversation-slash-queue.test.ts +34 -19
- package/src/__tests__/conversation-slash-unknown.test.ts +30 -16
- package/src/__tests__/conversation-speed-override.test.ts +30 -11
- package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +1035 -0
- package/src/__tests__/conversation-surfaces-standalone.test.ts +630 -0
- package/src/__tests__/conversation-title-service.test.ts +2 -2
- package/src/__tests__/conversation-tool-setup-batch-authorized.test.ts +1 -1
- package/src/__tests__/conversation-unread-route.test.ts +2 -2
- package/src/__tests__/conversation-usage.test.ts +3 -1
- package/src/__tests__/conversation-workspace-cache-state.test.ts +31 -10
- package/src/__tests__/conversation-workspace-injection.test.ts +43 -15
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +44 -16
- package/src/__tests__/credential-broker-browser-fill.test.ts +110 -0
- package/src/__tests__/credential-security-invariants.test.ts +3 -0
- package/src/__tests__/credential-storage-oauth-compat.test.ts +18 -0
- package/src/__tests__/credential-storage-static-compat.test.ts +28 -0
- package/src/__tests__/credential-vault-unit.test.ts +135 -19
- package/src/__tests__/credentials-cli.test.ts +1 -9
- package/src/__tests__/cross-provider-web-search.test.ts +84 -0
- package/src/__tests__/daemon-server-persist-and-process-callsite.test.ts +92 -0
- package/src/__tests__/delete-propagation.test.ts +437 -0
- package/src/__tests__/dm-backfill.test.ts +417 -0
- package/src/__tests__/dm-persistence.test.ts +227 -0
- package/src/__tests__/edit-propagation.test.ts +280 -0
- package/src/__tests__/ephemeral-permissions.test.ts +93 -3
- package/src/__tests__/estimator-calibration-integration.test.ts +208 -0
- package/src/__tests__/estimator-calibration.test.ts +213 -0
- package/src/__tests__/extension-id-sync-guard.test.ts +26 -7
- package/src/__tests__/file-write-tool.test.ts +151 -1
- package/src/__tests__/filing-service.test.ts +255 -0
- package/src/__tests__/gemini-provider.test.ts +0 -3
- package/src/__tests__/guardian-grant-minting.test.ts +8 -0
- package/src/__tests__/headless-browser-interactions.test.ts +1 -1
- package/src/__tests__/heartbeat-service.test.ts +96 -15
- package/src/__tests__/host-shell-tool.test.ts +124 -18
- package/src/__tests__/http-user-message-parity.test.ts +29 -1
- package/src/__tests__/inbound-slack-persistence.test.ts +340 -0
- package/src/__tests__/intent-routing.test.ts +1 -40
- package/src/__tests__/llm-catalog-parity.test.ts +174 -0
- package/src/__tests__/llm-context-normalization.test.ts +121 -0
- package/src/__tests__/llm-resolver.test.ts +214 -0
- package/src/__tests__/llm-schema.test.ts +223 -0
- package/src/__tests__/managed-proxy-context.test.ts +6 -2
- package/src/__tests__/messaging-skill-split.test.ts +3 -34
- package/src/__tests__/migration-import-from-url.test.ts +684 -0
- package/src/__tests__/model-intents.test.ts +9 -83
- package/src/__tests__/notification-decision-fallback.test.ts +0 -10
- package/src/__tests__/notification-decision-identity.test.ts +0 -9
- package/src/__tests__/notification-decision-recipient-context.test.ts +0 -9
- package/src/__tests__/oauth-store.test.ts +10 -7
- package/src/__tests__/oauth2-gateway-transport.test.ts +8 -3
- package/src/__tests__/oauth2-refresh-retry.test.ts +279 -0
- package/src/__tests__/openai-provider.test.ts +7 -0
- package/src/__tests__/openai-responses-provider.test.ts +396 -0
- package/src/__tests__/openrouter-provider-only.test.ts +135 -0
- package/src/__tests__/outbound-slack-persistence.test.ts +293 -0
- package/src/__tests__/permission-checker-host-gate.test.ts +1 -1
- package/src/__tests__/permission-mode.test.ts +16 -0
- package/src/__tests__/permission-types.test.ts +0 -1
- package/src/__tests__/persona-resolver.test.ts +13 -13
- package/src/__tests__/pkb-autoinject.test.ts +37 -1
- package/src/__tests__/platform-bash-auto-approve.test.ts +1 -1
- package/src/__tests__/pricing.test.ts +50 -3
- package/src/__tests__/profiler-routes.test.ts +1 -1
- package/src/__tests__/provider-commit-message-generator.test.ts +14 -84
- package/src/__tests__/provider-env-vars-scope.test.ts +52 -0
- package/src/__tests__/provider-error-scenarios.test.ts +135 -6
- package/src/__tests__/provider-managed-proxy-integration.test.ts +42 -11
- package/src/__tests__/provider-registry-ollama.test.ts +1 -2
- package/src/__tests__/proxy-approval-callback.test.ts +0 -1
- package/src/__tests__/reaction-persistence.test.ts +560 -0
- package/src/__tests__/relay-server.test.ts +1 -1
- package/src/__tests__/require-fresh-approval.test.ts +1 -1
- package/src/__tests__/retry-openrouter-only-normalization.test.ts +136 -0
- package/src/__tests__/retry-thinking-tool-choice.test.ts +226 -0
- package/src/__tests__/risk-classifier-parity.test.ts +230 -0
- package/src/__tests__/sanitize-config-for-transfer.test.ts +78 -1
- package/src/__tests__/secret-ingress-http.test.ts +28 -0
- package/src/__tests__/secret-prompter-channel-fallback.test.ts +125 -0
- package/src/__tests__/secret-routes-managed-proxy.test.ts +2 -3
- package/src/__tests__/secret-scanner-executor.test.ts +1 -1
- package/src/__tests__/send-endpoint-busy.test.ts +29 -1
- package/src/__tests__/server-history-render.test.ts +31 -0
- package/src/__tests__/shell-parser-property.test.ts +13 -13
- package/src/__tests__/skill-cache-store.test.ts +182 -0
- package/src/__tests__/skills.test.ts +19 -33
- package/src/__tests__/slack-app-setup-skill-regression.test.ts +3 -1
- package/src/__tests__/slack-skill.test.ts +3 -8
- package/src/__tests__/starter-bundle.test.ts +35 -0
- package/src/__tests__/subagent-call-site-routing.test.ts +280 -0
- package/src/__tests__/suggestion-routes.test.ts +160 -3
- package/src/__tests__/system-prompt.test.ts +22 -35
- package/src/__tests__/task-runner.test.ts +3 -1
- package/src/__tests__/tcc-sandbox-deny.test.ts +198 -0
- package/src/__tests__/terminal-tools.test.ts +8 -0
- package/src/__tests__/test-support/browser-skill-harness.ts +2 -52
- package/src/__tests__/thread-backfill.test.ts +941 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -2
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +2 -2
- package/src/__tests__/tool-executor.test.ts +60 -94
- package/src/__tests__/trust-store.test.ts +442 -109
- package/src/__tests__/update-bulletin-job.test.ts +389 -0
- package/src/__tests__/usage-cache-backfill-migration.test.ts +3 -1
- package/src/__tests__/verification-control-plane-policy.test.ts +1 -22
- package/src/__tests__/voice-session-bridge.test.ts +39 -0
- package/src/__tests__/volume-security-guard.test.ts +3 -2
- package/src/__tests__/web-search-history.test.ts +337 -0
- package/src/__tests__/workspace-migration-039-drop-legacy-llm-keys.test.ts +343 -0
- package/src/__tests__/workspace-migration-043-release-notes-latex-rendering.test.ts +202 -0
- package/src/__tests__/workspace-migration-045-release-notes-meet-avatar.test.ts +210 -0
- package/src/__tests__/workspace-migration-drop-user-md.test.ts +11 -11
- package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +841 -0
- package/src/__tests__/workspace-policy.test.ts +1 -13
- package/src/acp/client-handler.ts +1 -2
- package/src/agent/loop.ts +209 -17
- package/src/avatar/resvg-lazy.test.ts +136 -0
- package/src/avatar/resvg-lazy.ts +82 -9
- package/src/avatar/traits-png-sync.ts +21 -1
- package/src/browser/__tests__/operations.test.ts +163 -0
- package/src/browser/identifiers.ts +51 -0
- package/src/browser/operations.ts +660 -0
- package/src/browser/types.ts +81 -0
- package/src/calls/guardian-question-copy.ts +2 -2
- package/src/calls/telephony-stt-routing.ts +1 -1
- package/src/calls/voice-session-bridge.ts +1 -0
- package/src/cli/AGENTS.md +1 -1
- package/src/cli/commands/__tests__/attachment.test.ts +438 -0
- package/src/cli/commands/__tests__/browser.test.ts +554 -0
- package/src/cli/commands/__tests__/cache.test.ts +623 -0
- package/src/cli/commands/__tests__/email-list.test.ts +6 -0
- package/src/cli/commands/__tests__/email-send.test.ts +93 -1
- package/src/cli/commands/__tests__/image-generation.test.ts +666 -0
- package/src/cli/commands/__tests__/inference-send.test.ts +451 -0
- package/src/cli/commands/__tests__/stt-transcribe.test.ts +454 -0
- package/src/cli/commands/__tests__/task.test.ts +913 -0
- package/src/cli/commands/__tests__/tts-synthesize.test.ts +594 -0
- package/src/cli/commands/__tests__/ui-confirm.test.ts +650 -0
- package/src/cli/commands/__tests__/ui.test.ts +1215 -0
- package/src/cli/commands/__tests__/watchers.test.ts +716 -0
- package/src/cli/commands/attachment.ts +182 -0
- package/src/cli/commands/browser.ts +350 -0
- package/src/cli/commands/cache.ts +341 -0
- package/src/cli/commands/completions.ts +0 -3
- package/src/cli/commands/config.ts +6 -6
- package/src/cli/commands/conversations-import.ts +347 -0
- package/src/cli/commands/conversations.ts +14 -1
- package/src/cli/commands/email.ts +234 -194
- package/src/cli/commands/image-generation.ts +300 -0
- package/src/cli/commands/inference.ts +200 -0
- package/src/cli/commands/memory.ts +127 -17
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -1
- package/src/cli/commands/platform/__tests__/connect.test.ts +0 -1
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +0 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +0 -1
- package/src/cli/commands/stt.ts +339 -0
- package/src/cli/commands/task.ts +795 -0
- package/src/cli/commands/trust.ts +50 -19
- package/src/cli/commands/tts.ts +273 -0
- package/src/cli/commands/ui.ts +670 -0
- package/src/cli/commands/watchers.ts +509 -0
- package/src/cli/lib/daemon-credential-client.ts +0 -19
- package/src/cli/program.ts +23 -4
- package/src/cli.ts +0 -37
- package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +23 -1
- package/src/config/bundled-skills/media-processing/services/reduce.ts +1 -1
- package/src/config/bundled-skills/messaging/SKILL.md +2 -2
- package/src/config/bundled-skills/messaging/TOOLS.json +4 -0
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +8 -1
- package/src/config/bundled-skills/messaging/tools/messaging-read.ts +15 -1
- package/src/config/bundled-skills/messaging/tools/messaging-search.ts +21 -1
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +11 -12
- package/src/config/bundled-skills/phone-calls/references/CONFIG.md +9 -8
- package/src/config/bundled-skills/settings/TOOLS.json +3 -3
- package/src/config/bundled-tool-registry.ts +0 -175
- package/src/config/env.ts +7 -2
- package/src/config/feature-flag-registry.json +25 -9
- package/src/config/llm-resolver.ts +128 -0
- package/src/config/loader.ts +194 -10
- package/src/config/raw-config-utils.ts +30 -2
- package/src/config/sanitize-for-transfer.ts +35 -0
- package/src/config/schema.ts +30 -41
- package/src/config/schemas/analysis.ts +3 -22
- package/src/config/schemas/calls.ts +0 -4
- package/src/config/schemas/filing.ts +2 -7
- package/src/config/schemas/heartbeat.ts +0 -5
- package/src/config/schemas/inference.ts +3 -23
- package/src/config/schemas/llm.ts +318 -0
- package/src/config/schemas/memory-processing.ts +1 -9
- package/src/config/schemas/notifications.ts +4 -11
- package/src/config/schemas/platform.ts +3 -9
- package/src/config/schemas/security.ts +33 -0
- package/src/config/schemas/services.ts +9 -4
- package/src/config/schemas/stt.ts +1 -0
- package/src/config/schemas/tts.ts +53 -0
- package/src/config/schemas/updates.ts +1 -1
- package/src/config/schemas/workspace-git.ts +3 -40
- package/src/config/skills.ts +2 -2
- package/src/context/__tests__/compact-prompt.test.ts +45 -0
- package/src/context/__tests__/microcompact.test.ts +805 -0
- package/src/context/estimator-calibration.ts +136 -0
- package/src/context/microcompact.ts +443 -0
- package/src/context/prompts/compact.md +12 -0
- package/src/context/token-estimator.ts +61 -3
- package/src/context/window-manager.ts +229 -25
- package/src/credential-execution/approval-bridge.ts +0 -1
- package/src/credential-execution/executable-discovery.ts +19 -8
- package/src/credential-execution/process-manager.test.ts +109 -0
- package/src/credential-execution/process-manager.ts +65 -2
- package/src/daemon/approval-generators.ts +29 -4
- package/src/daemon/assistant-attachments.ts +24 -13
- package/src/daemon/classifier.ts +2 -2
- package/src/daemon/config-watcher.ts +0 -1
- package/src/daemon/context-overflow-reducer.ts +4 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +79 -12
- package/src/daemon/conversation-agent-loop.ts +462 -80
- package/src/daemon/conversation-attachments.ts +2 -6
- package/src/daemon/conversation-error.ts +36 -1
- package/src/daemon/conversation-lifecycle.ts +30 -6
- package/src/daemon/conversation-messaging.ts +73 -4
- package/src/daemon/conversation-process.ts +10 -4
- package/src/daemon/conversation-queue-manager.ts +3 -0
- package/src/daemon/conversation-runtime-assembly.ts +760 -29
- package/src/daemon/conversation-slash.ts +2 -2
- package/src/daemon/conversation-surfaces.ts +389 -1
- package/src/daemon/conversation-tool-setup.ts +10 -5
- package/src/daemon/conversation-usage.ts +1 -1
- package/src/daemon/conversation.ts +118 -30
- package/src/daemon/external-skills-bootstrap.ts +41 -0
- package/src/daemon/guardian-action-generators.ts +34 -14
- package/src/daemon/handlers/config-model.test.ts +86 -0
- package/src/daemon/handlers/config-model.ts +54 -12
- package/src/daemon/handlers/conversations.ts +9 -2
- package/src/daemon/handlers/shared.ts +39 -11
- package/src/daemon/handlers/skills.ts +2 -2
- package/src/daemon/handlers/slack-channel-oauth-install.ts +197 -0
- package/src/daemon/lifecycle.ts +76 -14
- package/src/daemon/message-types/conversations.ts +14 -0
- package/src/daemon/message-types/messages.ts +9 -1
- package/src/daemon/message-types/trust.ts +0 -2
- package/src/daemon/parse-actual-tokens-from-error.test.ts +57 -1
- package/src/daemon/parse-actual-tokens-from-error.ts +66 -0
- package/src/daemon/pkb-context-tracker.test.ts +169 -0
- package/src/daemon/pkb-context-tracker.ts +125 -0
- package/src/daemon/pkb-reminder-builder.test.ts +70 -0
- package/src/daemon/pkb-reminder-builder.ts +31 -0
- package/src/daemon/providers-setup.ts +6 -0
- package/src/daemon/server.ts +117 -9
- package/src/daemon/tool-side-effects.ts +0 -9
- package/src/daemon/watch-handler.ts +4 -4
- package/src/daemon/web-search-history.ts +126 -0
- package/src/events/domain-events.ts +0 -1
- package/src/filing/filing-service.ts +9 -10
- package/src/heartbeat/heartbeat-service.ts +76 -28
- package/src/home/__tests__/feed-scheduler.test.ts +39 -11
- package/src/home/__tests__/rollup-producer.test.ts +44 -0
- package/src/home/assistant-feed-authoring.ts +4 -0
- package/src/home/emit-feed-event.ts +4 -0
- package/src/home/feed-scheduler.ts +20 -4
- package/src/home/feed-types.ts +56 -2
- package/src/home/relationship-state-writer.ts +2 -2
- package/src/home/rollup-producer.ts +34 -5
- package/src/home/suggested-prompts.ts +101 -0
- package/src/ipc/__tests__/attachment-ipc.test.ts +213 -0
- package/src/ipc/__tests__/browser-ipc.test.ts +339 -0
- package/src/ipc/__tests__/cache-ipc.test.ts +266 -0
- package/src/ipc/__tests__/socket-path.test.ts +73 -0
- package/src/ipc/__tests__/task-ipc.test.ts +577 -0
- package/src/ipc/__tests__/ui-request-route.test.ts +495 -0
- package/src/ipc/__tests__/watcher-ipc.test.ts +295 -0
- package/src/ipc/cli-client.ts +2 -1
- package/src/ipc/cli-server.ts +26 -8
- package/src/ipc/gateway-client.ts +4 -4
- package/src/ipc/routes/attachment.ts +114 -0
- package/src/ipc/routes/browser-context.ts +61 -0
- package/src/ipc/routes/browser.ts +96 -0
- package/src/ipc/routes/cache.ts +96 -0
- package/src/ipc/routes/index.ts +17 -1
- package/src/ipc/routes/task-queue.ts +226 -0
- package/src/ipc/routes/task.ts +173 -0
- package/src/ipc/routes/ui-request.ts +50 -0
- package/src/ipc/routes/watcher.ts +203 -0
- package/src/ipc/socket-path.ts +100 -0
- package/src/memory/__tests__/conversation-analyze-job.test.ts +9 -8
- package/src/memory/__tests__/conversation-group-migration.test.ts +99 -0
- package/src/memory/admin.ts +18 -0
- package/src/memory/conversation-analyze-job.ts +14 -13
- package/src/memory/conversation-attention-store.ts +13 -6
- package/src/memory/conversation-crud.ts +103 -3
- package/src/memory/conversation-group-migration.ts +38 -6
- package/src/memory/conversation-title-service.ts +7 -4
- package/src/memory/db-init.ts +2 -0
- package/src/memory/embedding-backend.ts +1 -1
- package/src/memory/graph/compaction.ts +299 -0
- package/src/memory/graph/consolidation.ts +4 -4
- package/src/memory/graph/conversation-graph-memory.ts +89 -29
- package/src/memory/graph/extraction.test.ts +272 -2
- package/src/memory/graph/extraction.ts +173 -51
- package/src/memory/graph/graph-search.test.ts +92 -0
- package/src/memory/graph/graph-search.ts +4 -1
- package/src/memory/graph/narrative.ts +2 -2
- package/src/memory/graph/pattern-scan.ts +2 -2
- package/src/memory/graph/retriever.test.ts +459 -0
- package/src/memory/graph/retriever.ts +230 -48
- package/src/memory/graph/store.ts +41 -0
- package/src/memory/graph/tool-handlers.ts +27 -0
- package/src/memory/graph/tools.ts +6 -1
- package/src/memory/indexer.ts +5 -5
- package/src/memory/job-handlers/conversation-starters.ts +23 -20
- package/src/memory/job-handlers/summarization.ts +2 -2
- package/src/memory/job-utils.ts +7 -1
- package/src/memory/jobs/embed-pkb-file.test.ts +168 -0
- package/src/memory/jobs/embed-pkb-file.ts +54 -0
- package/src/memory/jobs-store.ts +44 -3
- package/src/memory/jobs-worker.ts +4 -0
- package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +1 -1
- package/src/memory/migrations/220-normalize-user-file-by-principal.ts +2 -2
- package/src/memory/migrations/222-strip-placeholder-sentinels-from-messages.ts +82 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/pkb/pkb-index.test.ts +368 -0
- package/src/memory/pkb/pkb-index.ts +255 -0
- package/src/memory/pkb/pkb-reconcile.test.ts +251 -0
- package/src/memory/pkb/pkb-reconcile.ts +148 -0
- package/src/memory/pkb/pkb-search.test.ts +438 -0
- package/src/memory/pkb/pkb-search.ts +137 -0
- package/src/memory/pkb/types.ts +53 -0
- package/src/memory/qdrant-client.ts +122 -1
- package/src/memory/slack-thread-store.ts +37 -0
- package/src/messaging/providers/gmail/adapter.ts +6 -16
- package/src/messaging/providers/gmail/client.ts +22 -0
- package/src/messaging/providers/gmail/types.ts +7 -0
- package/src/messaging/providers/slack/adapter.ts +14 -2
- package/src/messaging/providers/slack/backfill.test.ts +257 -0
- package/src/messaging/providers/slack/backfill.ts +101 -0
- package/src/messaging/providers/slack/message-metadata.test.ts +316 -0
- package/src/messaging/providers/slack/message-metadata.ts +123 -0
- package/src/messaging/providers/slack/render-transcript.test.ts +1373 -0
- package/src/messaging/providers/slack/render-transcript.ts +443 -0
- package/src/messaging/style-analyzer.ts +5 -2
- package/src/notifications/README.md +9 -5
- package/src/notifications/decision-engine.ts +3 -9
- package/src/notifications/preference-extractor.ts +2 -6
- package/src/oauth/oauth-store.ts +1 -0
- package/src/oauth/platform-connection.test.ts +47 -0
- package/src/oauth/platform-connection.ts +15 -5
- package/src/oauth/seed-providers.ts +4 -2
- package/src/permissions/approval-policy.test.ts +948 -0
- package/src/permissions/approval-policy.ts +257 -0
- package/src/permissions/bash-risk-classifier.test.ts +1208 -0
- package/src/permissions/bash-risk-classifier.ts +707 -0
- package/src/permissions/checker.ts +217 -708
- package/src/permissions/command-registry.test.ts +535 -0
- package/src/permissions/command-registry.ts +825 -0
- package/src/permissions/defaults.ts +26 -78
- package/src/permissions/file-risk-classifier.test.ts +535 -0
- package/src/permissions/file-risk-classifier.ts +274 -0
- package/src/permissions/risk-types.ts +205 -0
- package/src/permissions/secret-prompter.ts +53 -2
- package/src/permissions/skill-risk-classifier.test.ts +311 -0
- package/src/permissions/skill-risk-classifier.ts +214 -0
- package/src/permissions/trust-client.ts +52 -25
- package/src/permissions/trust-store-interface.ts +1 -6
- package/src/permissions/trust-store.ts +161 -62
- package/src/permissions/types.ts +23 -14
- package/src/permissions/web-risk-classifier.test.ts +170 -0
- package/src/permissions/web-risk-classifier.ts +89 -0
- package/src/permissions/workspace-policy.ts +1 -16
- package/src/platform/client.ts +19 -1
- package/src/prompts/persona-resolver.ts +3 -3
- package/src/prompts/system-prompt.ts +19 -20
- package/src/prompts/templates/SOUL.md +2 -2
- package/src/prompts/update-bulletin-job.ts +190 -0
- package/src/providers/__tests__/context-overflow-error.test.ts +328 -0
- package/src/providers/__tests__/provider-env-vars.test.ts +102 -0
- package/src/providers/__tests__/retry-callsite.test.ts +424 -0
- package/src/providers/anthropic/client.ts +183 -14
- package/src/providers/call-site-routing.ts +71 -0
- package/src/providers/gemini/client.ts +65 -2
- package/src/providers/managed-proxy/constants.ts +2 -1
- package/src/providers/model-catalog.ts +501 -33
- package/src/providers/model-intents.ts +4 -4
- package/src/providers/openai/chat-completions-provider.ts +57 -1
- package/src/providers/openai/responses-provider.ts +86 -9
- package/src/providers/openrouter/client.ts +76 -9
- package/src/providers/provider-env-vars.ts +56 -0
- package/src/providers/provider-send-message.ts +22 -5
- package/src/providers/ratelimit.ts +4 -0
- package/src/providers/registry.ts +19 -8
- package/src/providers/retry.ts +174 -39
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +55 -0
- package/src/providers/speech-to-text/google-gemini-live-stream.ts +4 -4
- package/src/providers/speech-to-text/provider-catalog.ts +17 -0
- package/src/providers/speech-to-text/resolve.ts +7 -0
- package/src/providers/speech-to-text/xai-realtime.test.ts +578 -0
- package/src/providers/speech-to-text/xai-realtime.ts +796 -0
- package/src/providers/speech-to-text/xai.test.ts +155 -0
- package/src/providers/speech-to-text/xai.ts +97 -0
- package/src/providers/types.ts +93 -3
- package/src/runtime/AGENTS.md +2 -2
- package/src/runtime/__tests__/agent-wake.test.ts +43 -2
- package/src/runtime/__tests__/interactive-ui.test.ts +673 -0
- package/src/runtime/agent-wake.ts +63 -22
- package/src/runtime/auth/route-policy.ts +4 -0
- package/src/runtime/btw-sidechain.ts +13 -3
- package/src/runtime/channel-reply-delivery.ts +106 -2
- package/src/runtime/decision-token.ts +116 -0
- package/src/runtime/gateway-client.ts +2 -2
- package/src/runtime/http-router.ts +32 -0
- package/src/runtime/http-server.ts +52 -1
- package/src/runtime/http-types.ts +23 -1
- package/src/runtime/interactive-ui.ts +362 -0
- package/src/runtime/invite-instruction-generator.ts +2 -2
- package/src/runtime/migrations/__tests__/gcs-signed-url.test.ts +176 -0
- package/src/runtime/migrations/__tests__/vbundle-metadata-merge-integration.test.ts +390 -0
- package/src/runtime/migrations/__tests__/vbundle-metadata-merge.test.ts +221 -0
- package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +1540 -0
- package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +453 -0
- package/src/runtime/migrations/__tests__/vbundle-tar-stream.test.ts +222 -0
- package/src/runtime/migrations/gcs-signed-url.ts +162 -0
- package/src/runtime/migrations/vbundle-importer.ts +154 -9
- package/src/runtime/migrations/vbundle-metadata-merge.ts +124 -0
- package/src/runtime/migrations/vbundle-streaming-importer.ts +2522 -0
- package/src/runtime/migrations/vbundle-streaming-validator.ts +244 -0
- package/src/runtime/migrations/vbundle-tar-stream.ts +217 -0
- package/src/runtime/migrations/vbundle-validator.ts +15 -6
- package/src/runtime/routes/__tests__/home-feed-routes.test.ts +111 -0
- package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +114 -75
- package/src/runtime/routes/__tests__/migration-vellum-metadata-reconcile.test.ts +246 -0
- package/src/runtime/routes/approval-prompt-ts-tracker.ts +58 -0
- package/src/runtime/routes/approval-routes.ts +12 -17
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +9 -0
- package/src/runtime/routes/avatar-routes.ts +20 -4
- package/src/runtime/routes/btw-routes.ts +1 -4
- package/src/runtime/routes/conversation-management-routes.ts +20 -2
- package/src/runtime/routes/conversation-routes.ts +133 -27
- package/src/runtime/routes/debug-routes.ts +1 -1
- package/src/runtime/routes/diagnostics-routes.ts +6 -4
- package/src/runtime/routes/events-routes.ts +16 -0
- package/src/runtime/routes/guardian-approval-interception.ts +33 -3
- package/src/runtime/routes/guardian-approval-prompt.ts +13 -3
- package/src/runtime/routes/home-feed-routes.ts +120 -2
- package/src/runtime/routes/inbound-message-handler.ts +912 -2
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +113 -2
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +61 -3
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +129 -6
- package/src/runtime/routes/integrations/slack/channel.ts +25 -3
- package/src/runtime/routes/llm-context-normalization.ts +23 -1
- package/src/runtime/routes/migration-routes.ts +720 -124
- package/src/runtime/routes/settings-routes.ts +4 -2
- package/src/runtime/routes/trust-rules-routes.ts +30 -14
- package/src/runtime/routes/work-items-routes.test.ts +1 -1
- package/src/runtime/routes/work-items-routes.ts +3 -2
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +25 -43
- package/src/runtime/services/analyze-conversation.ts +12 -16
- package/src/runtime/skill-route-registry.ts +28 -6
- package/src/schedule/scheduler.ts +8 -0
- package/src/security/__tests__/provider-key-env-fallback.test.ts +119 -0
- package/src/security/__tests__/untrusted-content.test.ts +109 -0
- package/src/security/oauth2.ts +98 -35
- package/src/security/secure-keys.ts +7 -8
- package/src/security/token-manager.ts +27 -13
- package/src/security/untrusted-content.ts +102 -0
- package/src/skills/catalog-cache.ts +26 -7
- package/src/skills/catalog-install.ts +31 -3
- package/src/skills/skill-cache-store.ts +97 -0
- package/src/stt/__tests__/daemon-batch-transcriber.test.ts +76 -0
- package/src/stt/daemon-batch-transcriber.ts +33 -0
- package/src/stt/stt-stream-session.ts +8 -1
- package/src/stt/types.ts +5 -1
- package/src/subagent/manager.ts +41 -13
- package/src/tasks/ephemeral-permissions.ts +9 -4
- package/src/telemetry/usage-telemetry-reporter.ts +27 -5
- package/src/tools/browser/__tests__/browser-status.test.ts +45 -2
- package/src/tools/browser/browser-execution.ts +65 -38
- package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +22 -0
- package/src/tools/credentials/tool-policy.ts +39 -5
- package/src/tools/credentials/vault.ts +9 -4
- package/src/tools/executor.ts +4 -0
- package/src/tools/filesystem/write.ts +52 -0
- package/src/tools/host-terminal/host-shell.ts +45 -5
- package/src/tools/memory/register.test.ts +185 -0
- package/src/tools/memory/register.ts +3 -1
- package/src/tools/network/web-fetch.ts +20 -10
- package/src/tools/network/web-search.ts +19 -4
- package/src/tools/permission-checker.ts +36 -15
- package/src/tools/policy-context.ts +25 -8
- package/src/tools/registry.ts +55 -3
- package/src/tools/side-effects.ts +0 -11
- package/src/tools/skills/execute.ts +2 -2
- package/src/tools/skills/sandbox-runner.ts +5 -2
- package/src/tools/terminal/backends/native.ts +51 -2
- package/src/tools/terminal/safe-env.ts +3 -2
- package/src/tools/terminal/shell.ts +1 -0
- package/src/tools/tool-manifest.ts +6 -21
- package/src/tools/types.ts +12 -3
- package/src/tools/verification-control-plane-policy.ts +1 -1
- package/src/tts/__tests__/provider-adapters.test.ts +240 -13
- package/src/tts/provider-catalog.ts +18 -0
- package/src/tts/providers/index.ts +2 -0
- package/src/tts/providers/xai-provider.ts +224 -0
- package/src/tts/types.ts +46 -0
- package/src/types/tar-stream.d.ts +66 -0
- package/src/util/json.ts +17 -0
- package/src/util/platform.ts +2 -2
- package/src/util/pricing.ts +15 -5
- package/src/watcher/engine.ts +1 -1
- package/src/watcher/providers/google-calendar.ts +134 -8
- package/src/watcher/providers/outlook-calendar.ts +42 -2
- package/src/workspace/git-service.ts +23 -4
- package/src/workspace/migrations/038-unify-llm-callsite-configs.ts +516 -0
- package/src/workspace/migrations/039-drop-legacy-llm-keys.ts +171 -0
- package/src/workspace/migrations/040-seed-latency-callsite-defaults.ts +154 -0
- package/src/workspace/migrations/041-backfill-google-gmail-settings-scope.ts +57 -0
- package/src/workspace/migrations/042-fix-backfill-google-gmail-settings-scope.ts +70 -0
- package/src/workspace/migrations/043-release-notes-latex-rendering.ts +75 -0
- package/src/workspace/migrations/044-bump-stale-provider-stream-timeout.ts +51 -0
- package/src/workspace/migrations/045-release-notes-meet-avatar.ts +130 -0
- package/src/workspace/migrations/AGENTS.md +1 -1
- package/src/workspace/migrations/registry.ts +16 -0
- package/src/workspace/provider-commit-message-generator.ts +19 -38
- package/src/__tests__/gmail-archive-fallback.test.ts +0 -193
- package/src/__tests__/gmail-archive-gate.test.ts +0 -246
- package/src/__tests__/gmail-preferences.test.ts +0 -117
- package/src/__tests__/outlook-attachments.test.ts +0 -301
- package/src/__tests__/outlook-automation-tools.test.ts +0 -425
- package/src/__tests__/outlook-categories.test.ts +0 -212
- package/src/__tests__/outlook-compose-tools.test.ts +0 -325
- package/src/__tests__/outlook-declutter-tools.test.ts +0 -585
- package/src/__tests__/outlook-follow-up.test.ts +0 -196
- package/src/__tests__/outlook-trash.test.ts +0 -77
- package/src/__tests__/outlook-unsubscribe.test.ts +0 -279
- package/src/__tests__/update-bulletin-format.test.ts +0 -181
- package/src/__tests__/update-bulletin-state.test.ts +0 -135
- package/src/__tests__/update-bulletin.test.ts +0 -478
- package/src/__tests__/update-template-contract.test.ts +0 -29
- package/src/cli/commands/doctor.ts +0 -341
- package/src/config/bundled-skills/browser/SKILL.md +0 -88
- package/src/config/bundled-skills/browser/TOOLS.json +0 -516
- package/src/config/bundled-skills/browser/tools/browser-attach.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-click.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-close.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-detach.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-extract.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-fill-credential.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-hover.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-navigate.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-press-key.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-screenshot.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-scroll.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-select-option.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-snapshot.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-status.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-type.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +0 -49
- package/src/config/bundled-skills/browser/tools/browser-wait-for.ts +0 -12
- package/src/config/bundled-skills/chatgpt-import/SKILL.md +0 -27
- package/src/config/bundled-skills/chatgpt-import/TOOLS.json +0 -27
- package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +0 -378
- package/src/config/bundled-skills/gmail/SKILL.md +0 -221
- package/src/config/bundled-skills/gmail/TOOLS.json +0 -588
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +0 -256
- package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +0 -112
- package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +0 -44
- package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +0 -81
- package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +0 -108
- package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +0 -146
- package/src/config/bundled-skills/gmail/tools/gmail-label.ts +0 -53
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +0 -347
- package/src/config/bundled-skills/gmail/tools/gmail-preferences-tool.ts +0 -59
- package/src/config/bundled-skills/gmail/tools/gmail-preferences.ts +0 -82
- package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +0 -26
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +0 -347
- package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +0 -29
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +0 -122
- package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +0 -67
- package/src/config/bundled-skills/gmail/tools/scan-result-store.ts +0 -100
- package/src/config/bundled-skills/gmail/tools/shared.ts +0 -47
- package/src/config/bundled-skills/google-calendar/SKILL.md +0 -51
- package/src/config/bundled-skills/google-calendar/TOOLS.json +0 -226
- package/src/config/bundled-skills/google-calendar/calendar-client.ts +0 -223
- package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +0 -27
- package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +0 -48
- package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +0 -19
- package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +0 -36
- package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +0 -58
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +0 -17
- package/src/config/bundled-skills/google-calendar/types.ts +0 -97
- package/src/config/bundled-skills/outlook/SKILL.md +0 -196
- package/src/config/bundled-skills/outlook/TOOLS.json +0 -530
- package/src/config/bundled-skills/outlook/tools/outlook-attachments.ts +0 -85
- package/src/config/bundled-skills/outlook/tools/outlook-categories.ts +0 -77
- package/src/config/bundled-skills/outlook/tools/outlook-draft.ts +0 -84
- package/src/config/bundled-skills/outlook/tools/outlook-follow-up.ts +0 -94
- package/src/config/bundled-skills/outlook/tools/outlook-forward.ts +0 -49
- package/src/config/bundled-skills/outlook/tools/outlook-outreach-scan.ts +0 -237
- package/src/config/bundled-skills/outlook/tools/outlook-rules.ts +0 -161
- package/src/config/bundled-skills/outlook/tools/outlook-send-draft.ts +0 -32
- package/src/config/bundled-skills/outlook/tools/outlook-sender-digest.ts +0 -272
- package/src/config/bundled-skills/outlook/tools/outlook-trash.ts +0 -29
- package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +0 -129
- package/src/config/bundled-skills/outlook/tools/outlook-vacation.ts +0 -87
- package/src/config/bundled-skills/outlook/tools/shared.ts +0 -20
- package/src/config/bundled-skills/outlook-calendar/SKILL.md +0 -51
- package/src/config/bundled-skills/outlook-calendar/TOOLS.json +0 -221
- package/src/config/bundled-skills/outlook-calendar/calendar-client.ts +0 -252
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-check-availability.ts +0 -53
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-create-event.ts +0 -74
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-get-event.ts +0 -18
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-list-events.ts +0 -46
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-rsvp.ts +0 -36
- package/src/config/bundled-skills/outlook-calendar/tools/shared.ts +0 -17
- package/src/config/bundled-skills/outlook-calendar/types.ts +0 -120
- package/src/config/bundled-skills/slack/SKILL.md +0 -108
- package/src/config/bundled-skills/tasks/SKILL.md +0 -37
- package/src/config/bundled-skills/tasks/TOOLS.json +0 -353
- package/src/config/bundled-skills/tasks/icon.svg +0 -34
- package/src/config/bundled-skills/tasks/tools/task-delete.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list-add.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list-show.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list-update.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-queue-run.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-run.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-save.ts +0 -12
- package/src/config/bundled-skills/watcher/SKILL.md +0 -31
- package/src/config/bundled-skills/watcher/TOOLS.json +0 -167
- package/src/config/bundled-skills/watcher/tools/watcher-create.ts +0 -12
- package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +0 -12
- package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +0 -12
- package/src/config/bundled-skills/watcher/tools/watcher-list.ts +0 -12
- package/src/config/bundled-skills/watcher/tools/watcher-update.ts +0 -12
- package/src/prompts/templates/UPDATES.md +0 -50
- package/src/prompts/update-bulletin-format.ts +0 -85
- package/src/prompts/update-bulletin-state.ts +0 -58
- package/src/prompts/update-bulletin-template-path.ts +0 -13
- package/src/prompts/update-bulletin.ts +0 -139
- package/src/shared/provider-env-vars.ts +0 -19
- package/src/tools/watcher/create.ts +0 -86
- package/src/tools/watcher/delete.ts +0 -36
- package/src/tools/watcher/digest.ts +0 -54
- package/src/tools/watcher/list.ts +0 -83
- package/src/tools/watcher/update.ts +0 -71
|
@@ -10,14 +10,43 @@ import { join, resolve } from "node:path";
|
|
|
10
10
|
|
|
11
11
|
import { type ChannelId, parseInterfaceId } from "../channels/types.js";
|
|
12
12
|
import { getAppDirPath, listAppFiles } from "../memory/app-store.js";
|
|
13
|
+
import {
|
|
14
|
+
getMessages as defaultGetMessages,
|
|
15
|
+
type MessageRow,
|
|
16
|
+
} from "../memory/conversation-crud.js";
|
|
17
|
+
import {
|
|
18
|
+
countMemoryPrefixBlocks,
|
|
19
|
+
extractMemoryPrefixBlocks,
|
|
20
|
+
} from "../memory/graph/conversation-graph-memory.js";
|
|
21
|
+
import { searchPkbFiles } from "../memory/pkb/pkb-search.js";
|
|
22
|
+
import type { QdrantSparseVector } from "../memory/qdrant-client.js";
|
|
23
|
+
import { readSlackMetadata } from "../messaging/providers/slack/message-metadata.js";
|
|
24
|
+
import {
|
|
25
|
+
extractTagLineTexts,
|
|
26
|
+
type RenderableSlackMessage,
|
|
27
|
+
renderSlackTranscript,
|
|
28
|
+
} from "../messaging/providers/slack/render-transcript.js";
|
|
13
29
|
import { isPermissionControlsV2Enabled } from "../permissions/v2-consent-policy.js";
|
|
14
|
-
import type { Message } from "../providers/types.js";
|
|
15
|
-
import
|
|
30
|
+
import type { ContentBlock, Message } from "../providers/types.js";
|
|
31
|
+
import {
|
|
32
|
+
type ActorTrustContext,
|
|
33
|
+
isUntrustedTrustClass,
|
|
34
|
+
type TrustClass,
|
|
35
|
+
} from "../runtime/actor-trust-resolver.js";
|
|
16
36
|
import { channelStatusToMemberStatus } from "../runtime/routes/inbound-stages/acl-enforcement.js";
|
|
17
37
|
import type { SubagentState } from "../subagent/types.js";
|
|
18
38
|
import { TERMINAL_STATUSES } from "../subagent/types.js";
|
|
39
|
+
import { getLogger } from "../util/logger.js";
|
|
19
40
|
import { getWorkspaceDir, getWorkspacePromptPath } from "../util/platform.js";
|
|
20
41
|
import { stripCommentLines } from "../util/strip-comment-lines.js";
|
|
42
|
+
import { filterMessagesForUntrustedActor } from "./conversation-lifecycle.js";
|
|
43
|
+
import {
|
|
44
|
+
getInContextPkbPaths,
|
|
45
|
+
type PkbContextConversation,
|
|
46
|
+
} from "./pkb-context-tracker.js";
|
|
47
|
+
import { buildPkbReminder } from "./pkb-reminder-builder.js";
|
|
48
|
+
|
|
49
|
+
const pkbReminderLog = getLogger("pkb-reminder");
|
|
21
50
|
|
|
22
51
|
/**
|
|
23
52
|
* Describes the capabilities of the channel through which the user is
|
|
@@ -557,7 +586,7 @@ export function injectNowScratchpad(
|
|
|
557
586
|
): Message {
|
|
558
587
|
const scratchpadBlock = {
|
|
559
588
|
type: "text" as const,
|
|
560
|
-
text: `<NOW.md Always keep this up to date>\n${content}\n</NOW.md>`,
|
|
589
|
+
text: `<NOW.md Always keep this up to date; keep under 10 lines>\n${content}\n</NOW.md>`,
|
|
561
590
|
};
|
|
562
591
|
|
|
563
592
|
// Find insertion point: skip any leading injected-context text blocks
|
|
@@ -586,7 +615,9 @@ export function injectNowScratchpad(
|
|
|
586
615
|
/** Strip `<NOW.md>` blocks injected by `injectNowScratchpad`. */
|
|
587
616
|
export function stripNowScratchpad(messages: Message[]): Message[] {
|
|
588
617
|
return stripUserTextBlocksByPrefix(messages, [
|
|
589
|
-
|
|
618
|
+
// Shared prefix catches both the current tag and any pre-line-limit
|
|
619
|
+
// variant that may linger in in-flight histories during a rolling deploy.
|
|
620
|
+
"<NOW.md Always keep this up to date",
|
|
590
621
|
"<now_scratchpad>", // backward-compat: strip legacy blocks from pre-rename history
|
|
591
622
|
]);
|
|
592
623
|
}
|
|
@@ -607,13 +638,15 @@ const AUTOINJECT_FILENAME = "_autoinject.md";
|
|
|
607
638
|
/** Max buffer.md lines injected into prompts — keeps context bounded even when filing is off. */
|
|
608
639
|
const MAX_BUFFER_LINES = 50;
|
|
609
640
|
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
641
|
+
/** Minimum hybrid-search score for a PKB path to surface as an injection hint. */
|
|
642
|
+
const PKB_HINT_THRESHOLD = 0.5;
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* Stricter hint threshold for PKB entries under `archive/`. Archive files are
|
|
646
|
+
* date-indexed dumps of older notes — they match loosely and are rarely the
|
|
647
|
+
* most relevant read, so require a higher bar before recommending them.
|
|
648
|
+
*/
|
|
649
|
+
const PKB_HINT_ARCHIVE_THRESHOLD = 0.7;
|
|
617
650
|
|
|
618
651
|
/**
|
|
619
652
|
* Read `_autoinject.md` from the PKB directory and return the list of
|
|
@@ -639,6 +672,21 @@ export function readAutoinjectList(pkbDir: string): string[] | null {
|
|
|
639
672
|
}
|
|
640
673
|
}
|
|
641
674
|
|
|
675
|
+
/**
|
|
676
|
+
* Resolve the effective list of auto-inject filenames for a PKB directory.
|
|
677
|
+
*
|
|
678
|
+
* This is the single source of truth used both by `readPkbContext` (which
|
|
679
|
+
* actually injects the files) and by the PKB reminder-hint tracker in
|
|
680
|
+
* `conversation-agent-loop.ts` (which needs to know what's already in
|
|
681
|
+
* context so it doesn't redundantly recommend those files).
|
|
682
|
+
*
|
|
683
|
+
* Returns `PKB_DEFAULT_FILES` when `_autoinject.md` is missing/unreadable,
|
|
684
|
+
* or the parsed list (possibly empty) when it is present.
|
|
685
|
+
*/
|
|
686
|
+
export function getPkbAutoInjectList(pkbRoot: string): string[] {
|
|
687
|
+
return readAutoinjectList(pkbRoot) ?? PKB_DEFAULT_FILES;
|
|
688
|
+
}
|
|
689
|
+
|
|
642
690
|
/**
|
|
643
691
|
* Read the always-loaded PKB files and append a nudge encouraging the
|
|
644
692
|
* assistant to proactively read topic files and use `remember` aggressively.
|
|
@@ -653,7 +701,7 @@ export function readPkbContext(): string | null {
|
|
|
653
701
|
const pkbDir = join(getWorkspaceDir(), "pkb");
|
|
654
702
|
if (!existsSync(pkbDir)) return null;
|
|
655
703
|
|
|
656
|
-
const filesToInject =
|
|
704
|
+
const filesToInject = getPkbAutoInjectList(pkbDir);
|
|
657
705
|
|
|
658
706
|
const parts: string[] = [];
|
|
659
707
|
for (const file of filesToInject) {
|
|
@@ -686,10 +734,13 @@ export function readPkbContext(): string | null {
|
|
|
686
734
|
*/
|
|
687
735
|
export function injectPkbContext(message: Message, content: string): Message {
|
|
688
736
|
// Escape closing tags that could break out of the XML wrapper
|
|
689
|
-
const escaped = content.replace(
|
|
737
|
+
const escaped = content.replace(
|
|
738
|
+
/<\/knowledge_base\s*>/gi,
|
|
739
|
+
"</knowledge_base>",
|
|
740
|
+
);
|
|
690
741
|
const pkbBlock = {
|
|
691
742
|
type: "text" as const,
|
|
692
|
-
text: `<
|
|
743
|
+
text: `<knowledge_base>\n${escaped}\n</knowledge_base>`,
|
|
693
744
|
};
|
|
694
745
|
|
|
695
746
|
// Find insertion point: skip any leading memory/image blocks
|
|
@@ -721,9 +772,12 @@ export function injectPkbContext(message: Message, content: string): Message {
|
|
|
721
772
|
};
|
|
722
773
|
}
|
|
723
774
|
|
|
724
|
-
/** Strip `<
|
|
775
|
+
/** Strip `<knowledge_base>` blocks injected by `injectPkbContext`. */
|
|
725
776
|
export function stripPkbContext(messages: Message[]): Message[] {
|
|
726
|
-
return stripUserTextBlocksByPrefix(messages, [
|
|
777
|
+
return stripUserTextBlocksByPrefix(messages, [
|
|
778
|
+
"<knowledge_base>",
|
|
779
|
+
"<pkb>", // backward-compat: strip legacy blocks from pre-rename history
|
|
780
|
+
]);
|
|
727
781
|
}
|
|
728
782
|
|
|
729
783
|
/**
|
|
@@ -909,6 +963,10 @@ export function buildUnifiedTurnContextBlock(
|
|
|
909
963
|
.replace(/[\r\n\u0085\u2028\u2029]+/g, " ")
|
|
910
964
|
// Replace remaining ASCII C0/C1 control characters and DEL.
|
|
911
965
|
.replace(/[\x00-\x1F\x7F-\x9F]/g, " ")
|
|
966
|
+
// Escape XML special characters to prevent turn_context breakout.
|
|
967
|
+
.replace(/&/g, "&")
|
|
968
|
+
.replace(/</g, "<")
|
|
969
|
+
.replace(/>/g, ">")
|
|
912
970
|
.trim();
|
|
913
971
|
return singleLine.length > 0 ? singleLine : "unknown";
|
|
914
972
|
};
|
|
@@ -1138,6 +1196,433 @@ export function stripTransportHints(messages: Message[]): Message[] {
|
|
|
1138
1196
|
return stripUserTextBlocksByPrefix(messages, ["<transport_hints>"]);
|
|
1139
1197
|
}
|
|
1140
1198
|
|
|
1199
|
+
// ---------------------------------------------------------------------------
|
|
1200
|
+
// Slack chronological transcript assembly
|
|
1201
|
+
// ---------------------------------------------------------------------------
|
|
1202
|
+
|
|
1203
|
+
/**
|
|
1204
|
+
* True when the channel capabilities describe a Slack non-DM conversation
|
|
1205
|
+
* (group/channel/mpim). Used to gate thread-only behavior such as the
|
|
1206
|
+
* `<active_thread>` focus block. DMs are excluded because they have no
|
|
1207
|
+
* threads.
|
|
1208
|
+
*
|
|
1209
|
+
* The gateway normalizer sets `chatType: "channel"` for every non-DM Slack
|
|
1210
|
+
* conversation (public, private, and mpim alike — see
|
|
1211
|
+
* `gateway/src/slack/normalize.ts`) and omits the field entirely for DMs.
|
|
1212
|
+
* We therefore accept only `chatType === "channel"` — when the gateway
|
|
1213
|
+
* omits `chatType` (as it does for DMs), the check correctly returns
|
|
1214
|
+
* `false`.
|
|
1215
|
+
*
|
|
1216
|
+
* The chronological-transcript override applies to ALL Slack
|
|
1217
|
+
* conversations (channels and DMs) — gate that on
|
|
1218
|
+
* `channelCapabilities.channel === "slack"` rather than this helper.
|
|
1219
|
+
*/
|
|
1220
|
+
export function isSlackChannelConversation(
|
|
1221
|
+
channelCapabilities?: ChannelCapabilities | null,
|
|
1222
|
+
): boolean {
|
|
1223
|
+
return (
|
|
1224
|
+
channelCapabilities?.channel === "slack" &&
|
|
1225
|
+
channelCapabilities.chatType === "channel"
|
|
1226
|
+
);
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
/**
|
|
1230
|
+
* Minimal structural shape of a persisted message row used by the Slack
|
|
1231
|
+
* chronological-transcript assembly path. Decouples the assembly logic from
|
|
1232
|
+
* the DB-row type so it can be unit-tested with plain literals.
|
|
1233
|
+
*/
|
|
1234
|
+
export interface SlackTranscriptInputRow {
|
|
1235
|
+
role: "user" | "assistant";
|
|
1236
|
+
/** Raw persisted content column. JSON-encoded `ContentBlock[]` in production. */
|
|
1237
|
+
content: string;
|
|
1238
|
+
/** Epoch ms when the row was created. */
|
|
1239
|
+
createdAt: number;
|
|
1240
|
+
/** Raw `metadata` column value (JSON string with optional `slackMeta` sub-key). */
|
|
1241
|
+
metadata: string | null;
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
/**
|
|
1245
|
+
* Extract the user-facing plain text from an already-parsed `ContentBlock[]`.
|
|
1246
|
+
* Only `text` blocks contribute to the rendered transcript line. Tool-use /
|
|
1247
|
+
* tool-result / thinking blocks are intentionally elided — they would clutter
|
|
1248
|
+
* the Slack-style transcript and the model can already recall them from the
|
|
1249
|
+
* surrounding turn structure.
|
|
1250
|
+
*
|
|
1251
|
+
* Rows with no text blocks (e.g. images, file uploads, pure tool turns) would
|
|
1252
|
+
* otherwise render as an empty transcript line like `[14:25 @alice]: `;
|
|
1253
|
+
* surface the attachment/tool context instead so the model can tell something
|
|
1254
|
+
* was actually said on that turn.
|
|
1255
|
+
*/
|
|
1256
|
+
function extractPlainTextFromBlocks(blocks: ContentBlock[]): string {
|
|
1257
|
+
const parts: string[] = [];
|
|
1258
|
+
const placeholderLabels: string[] = [];
|
|
1259
|
+
for (const block of blocks) {
|
|
1260
|
+
if (!block || typeof block !== "object") continue;
|
|
1261
|
+
if (block.type === "text") {
|
|
1262
|
+
parts.push(block.text);
|
|
1263
|
+
continue;
|
|
1264
|
+
}
|
|
1265
|
+
const label = placeholderForBlockType(block.type);
|
|
1266
|
+
if (label && !placeholderLabels.includes(label)) {
|
|
1267
|
+
placeholderLabels.push(label);
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
if (parts.length > 0) {
|
|
1271
|
+
return parts.join("\n");
|
|
1272
|
+
}
|
|
1273
|
+
return placeholderLabels.join(" ");
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
function placeholderForBlockType(type: ContentBlock["type"]): string | null {
|
|
1277
|
+
switch (type) {
|
|
1278
|
+
case "image":
|
|
1279
|
+
return "[image]";
|
|
1280
|
+
case "file":
|
|
1281
|
+
return "[file]";
|
|
1282
|
+
case "tool_use":
|
|
1283
|
+
case "server_tool_use":
|
|
1284
|
+
return "[tool call]";
|
|
1285
|
+
case "tool_result":
|
|
1286
|
+
case "web_search_tool_result":
|
|
1287
|
+
return "[tool result]";
|
|
1288
|
+
case "thinking":
|
|
1289
|
+
case "redacted_thinking":
|
|
1290
|
+
case "text":
|
|
1291
|
+
return null;
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
/**
|
|
1296
|
+
* Convert a persisted row into the {@link RenderableSlackMessage} shape
|
|
1297
|
+
* consumed by `renderSlackTranscript`.
|
|
1298
|
+
*
|
|
1299
|
+
* Legacy pre-upgrade rows (no `slackMeta` sub-key, malformed metadata, etc.)
|
|
1300
|
+
* yield `metadata: null`; the renderer then takes its flat-render fallback
|
|
1301
|
+
* path and the row stays in chronological order via `createdAt`.
|
|
1302
|
+
*
|
|
1303
|
+
* Sender labels are emitted only when they add information beyond the role
|
|
1304
|
+
* slot:
|
|
1305
|
+
* - Reaction rows: always labeled — `@assistant` for the assistant, the real
|
|
1306
|
+
* `slackMeta.displayName` for a known user, or `@user` as a last-resort
|
|
1307
|
+
* subject so the rendered `[time X reacted ...]` line still parses.
|
|
1308
|
+
* - Assistant message rows: `null` — the role slot already says "assistant".
|
|
1309
|
+
* - User message rows: real `slackMeta.displayName` when available (to
|
|
1310
|
+
* disambiguate speakers in multi-party channels); `null` otherwise so the
|
|
1311
|
+
* renderer drops the redundant `@user` placeholder.
|
|
1312
|
+
*/
|
|
1313
|
+
function rowToRenderable(row: SlackTranscriptInputRow): RenderableSlackMessage {
|
|
1314
|
+
let slackMeta: ReturnType<typeof readSlackMetadata> = null;
|
|
1315
|
+
if (row.metadata) {
|
|
1316
|
+
try {
|
|
1317
|
+
const outer = JSON.parse(row.metadata) as { slackMeta?: unknown };
|
|
1318
|
+
if (typeof outer.slackMeta === "string") {
|
|
1319
|
+
slackMeta = readSlackMetadata(outer.slackMeta);
|
|
1320
|
+
}
|
|
1321
|
+
} catch {
|
|
1322
|
+
// Malformed metadata — fall through to legacy/null treatment.
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
const isReaction = slackMeta?.eventKind === "reaction";
|
|
1327
|
+
let senderLabel: string | null;
|
|
1328
|
+
if (isReaction) {
|
|
1329
|
+
senderLabel =
|
|
1330
|
+
row.role === "assistant"
|
|
1331
|
+
? "@assistant"
|
|
1332
|
+
: (slackMeta?.displayName ?? "@user");
|
|
1333
|
+
} else if (row.role === "assistant") {
|
|
1334
|
+
senderLabel = null;
|
|
1335
|
+
} else {
|
|
1336
|
+
senderLabel = slackMeta?.displayName ?? null;
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
// Parse `row.content` once and derive both the structured `contentBlocks`
|
|
1340
|
+
// view (for downstream tool-block preservation) and the flattened
|
|
1341
|
+
// `plainText` view (used for tag-line rendering) from the same parsed
|
|
1342
|
+
// result. Large Slack histories with many tool payloads would otherwise
|
|
1343
|
+
// pay a double JSON-parse cost per row.
|
|
1344
|
+
let contentBlocks: ContentBlock[] = [];
|
|
1345
|
+
let plainText: string;
|
|
1346
|
+
try {
|
|
1347
|
+
const parsed = JSON.parse(row.content);
|
|
1348
|
+
if (Array.isArray(parsed)) {
|
|
1349
|
+
contentBlocks = parsed as ContentBlock[];
|
|
1350
|
+
plainText = extractPlainTextFromBlocks(contentBlocks);
|
|
1351
|
+
} else if (typeof parsed === "string") {
|
|
1352
|
+
plainText = parsed;
|
|
1353
|
+
} else {
|
|
1354
|
+
plainText = row.content;
|
|
1355
|
+
}
|
|
1356
|
+
} catch {
|
|
1357
|
+
// Plain string row (legacy) — no structured blocks to preserve.
|
|
1358
|
+
plainText = row.content;
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
// Attachment-only rows (images, files) carry no text block, so the
|
|
1362
|
+
// transcript renderer would normally emit them *without* a tag line —
|
|
1363
|
+
// the model sees the image but loses sender/timestamp attribution.
|
|
1364
|
+
// Synthesize a leading text block carrying the placeholder so the
|
|
1365
|
+
// renderer emits `[14:25 @alice]: [image]` and then the image itself.
|
|
1366
|
+
// Pure tool-only rows (tool_use / tool_result) are intentionally
|
|
1367
|
+
// excluded — those are synthetic turn continuations that should stay
|
|
1368
|
+
// tag-line-free, matching the documented behaviour in
|
|
1369
|
+
// `buildMessageContentBlocks`.
|
|
1370
|
+
const hasTextBlock = contentBlocks.some((b) => b?.type === "text");
|
|
1371
|
+
const hasAttachmentBlock = contentBlocks.some(
|
|
1372
|
+
(b) => b?.type === "image" || b?.type === "file",
|
|
1373
|
+
);
|
|
1374
|
+
if (!hasTextBlock && hasAttachmentBlock && plainText !== "") {
|
|
1375
|
+
contentBlocks = [{ type: "text", text: plainText }, ...contentBlocks];
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
return {
|
|
1379
|
+
role: row.role,
|
|
1380
|
+
content: plainText,
|
|
1381
|
+
metadata: slackMeta,
|
|
1382
|
+
senderLabel,
|
|
1383
|
+
createdAt: row.createdAt,
|
|
1384
|
+
contentBlocks,
|
|
1385
|
+
};
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
/**
|
|
1389
|
+
* Build a chronological Slack transcript for Slack conversations (both DMs
|
|
1390
|
+
* and group/channel/mpim) and project it onto the LLM-facing `Message[]`
|
|
1391
|
+
* shape.
|
|
1392
|
+
*
|
|
1393
|
+
* Returns `null` when the channel is not Slack (caller should fall through
|
|
1394
|
+
* to the default message history). Legacy pre-upgrade rows without
|
|
1395
|
+
* `slackMeta` are tolerated: the renderer's flat fallback orders them by
|
|
1396
|
+
* `createdAt` alongside post-upgrade rows.
|
|
1397
|
+
*
|
|
1398
|
+
* For ALL Slack conversations (channels and DMs), `<transport_hints>`
|
|
1399
|
+
* injection is suppressed by `applyRuntimeInjections` so the model sees
|
|
1400
|
+
* one consistent persisted view instead of a duplicated gateway hint.
|
|
1401
|
+
*/
|
|
1402
|
+
export function assembleSlackChronologicalMessages(
|
|
1403
|
+
rows: SlackTranscriptInputRow[],
|
|
1404
|
+
capabilities: ChannelCapabilities,
|
|
1405
|
+
): Message[] | null {
|
|
1406
|
+
if (capabilities.channel !== "slack") {
|
|
1407
|
+
return null;
|
|
1408
|
+
}
|
|
1409
|
+
const renderable = rows.map(rowToRenderable);
|
|
1410
|
+
return renderSlackTranscript(renderable);
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
/**
|
|
1414
|
+
* Load DB rows for a Slack conversation and project them onto the
|
|
1415
|
+
* chronological transcript shape.
|
|
1416
|
+
*
|
|
1417
|
+
* Convenience wrapper over `getMessages` + `assembleSlackChronologicalMessages`.
|
|
1418
|
+
* The loader is exposed as a parameter so tests can substitute a stub. In
|
|
1419
|
+
* production it defaults to `getMessages` from `conversation-crud.ts`.
|
|
1420
|
+
*
|
|
1421
|
+
* When `trustClass` identifies an untrusted actor (guardian-scoped rows
|
|
1422
|
+
* must not leak into the model context), rows are passed through
|
|
1423
|
+
* `filterMessagesForUntrustedActor` before assembly — mirroring the
|
|
1424
|
+
* filtering applied in `loadFromDb` so the chronological transcript
|
|
1425
|
+
* respects the same per-actor scoping as the default history path.
|
|
1426
|
+
*
|
|
1427
|
+
* Returns `null` when the channel is not Slack — callers should fall
|
|
1428
|
+
* through to the default in-memory message history.
|
|
1429
|
+
*/
|
|
1430
|
+
export function loadSlackChronologicalMessages(
|
|
1431
|
+
conversationId: string,
|
|
1432
|
+
capabilities: ChannelCapabilities,
|
|
1433
|
+
options: {
|
|
1434
|
+
loader?: (id: string) => MessageRow[];
|
|
1435
|
+
trustClass?: TrustClass;
|
|
1436
|
+
} = {},
|
|
1437
|
+
): Message[] | null {
|
|
1438
|
+
if (capabilities.channel !== "slack") {
|
|
1439
|
+
return null;
|
|
1440
|
+
}
|
|
1441
|
+
const loader = options.loader ?? defaultGetMessages;
|
|
1442
|
+
const allRows = loader(conversationId);
|
|
1443
|
+
const scopedRows = isUntrustedTrustClass(options.trustClass)
|
|
1444
|
+
? filterMessagesForUntrustedActor(allRows)
|
|
1445
|
+
: allRows;
|
|
1446
|
+
// Coerce MessageRow.role (string) to the structural row's stricter union.
|
|
1447
|
+
const rows: SlackTranscriptInputRow[] = scopedRows.map((row) => ({
|
|
1448
|
+
role: row.role === "assistant" ? "assistant" : "user",
|
|
1449
|
+
content: row.content,
|
|
1450
|
+
createdAt: row.createdAt,
|
|
1451
|
+
metadata: row.metadata,
|
|
1452
|
+
}));
|
|
1453
|
+
return assembleSlackChronologicalMessages(rows, capabilities);
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
// ---------------------------------------------------------------------------
|
|
1457
|
+
// Active-thread focus block (non-persisted; appended to current user turn)
|
|
1458
|
+
// ---------------------------------------------------------------------------
|
|
1459
|
+
|
|
1460
|
+
/**
|
|
1461
|
+
* Detect the "active" Slack thread ts for the current turn.
|
|
1462
|
+
*
|
|
1463
|
+
* The active thread is the thread the current inbound user message belongs
|
|
1464
|
+
* to: scan from newest to oldest and return the `slackMeta.threadTs` of the
|
|
1465
|
+
* most recent user row that carries one. Returns `null` when no recent user
|
|
1466
|
+
* row sits inside a thread (e.g. the inbound was a top-level channel post,
|
|
1467
|
+
* or the conversation has no Slack-tagged user rows yet).
|
|
1468
|
+
*
|
|
1469
|
+
* Pure: takes pre-mapped renderable rows and returns the ts string only.
|
|
1470
|
+
*/
|
|
1471
|
+
function detectActiveThreadTs(rows: RenderableSlackMessage[]): string | null {
|
|
1472
|
+
for (let i = rows.length - 1; i >= 0; i--) {
|
|
1473
|
+
const row = rows[i];
|
|
1474
|
+
if (row.role !== "user") continue;
|
|
1475
|
+
const meta = row.metadata;
|
|
1476
|
+
if (!meta) continue;
|
|
1477
|
+
if (meta.eventKind !== "message") continue;
|
|
1478
|
+
if (typeof meta.threadTs === "string" && meta.threadTs.length > 0) {
|
|
1479
|
+
return meta.threadTs;
|
|
1480
|
+
}
|
|
1481
|
+
// First non-thread user row wins: the inbound is top-level, no active
|
|
1482
|
+
// thread to focus on.
|
|
1483
|
+
return null;
|
|
1484
|
+
}
|
|
1485
|
+
return null;
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
/**
|
|
1489
|
+
* Build a focus block listing every message belonging to the active thread:
|
|
1490
|
+
* the parent (whose `channelTs` equals `activeThreadTs`) plus every reply
|
|
1491
|
+
* (whose `threadTs` equals `activeThreadTs`). Reactions targeting any of
|
|
1492
|
+
* those messages are also pulled in via their `targetChannelTs`. Edits and
|
|
1493
|
+
* deletions surface through the existing renderer markers.
|
|
1494
|
+
*
|
|
1495
|
+
* Returns `null` when no rows match (e.g. parent backfill hasn't run yet
|
|
1496
|
+
* AND the thread has no replies in storage either) so the caller can skip
|
|
1497
|
+
* the empty block. Otherwise returns the rendered XML block ready to append
|
|
1498
|
+
* to the user's tail message.
|
|
1499
|
+
*
|
|
1500
|
+
* Pure: takes pre-mapped renderable rows + a thread ts, returns text only.
|
|
1501
|
+
*/
|
|
1502
|
+
function buildActiveThreadBlockFromRenderable(
|
|
1503
|
+
rows: RenderableSlackMessage[],
|
|
1504
|
+
activeThreadTs: string,
|
|
1505
|
+
): string | null {
|
|
1506
|
+
const members: RenderableSlackMessage[] = [];
|
|
1507
|
+
for (const row of rows) {
|
|
1508
|
+
const meta = row.metadata;
|
|
1509
|
+
if (!meta) continue;
|
|
1510
|
+
if (meta.eventKind === "message") {
|
|
1511
|
+
if (
|
|
1512
|
+
meta.channelTs === activeThreadTs ||
|
|
1513
|
+
meta.threadTs === activeThreadTs
|
|
1514
|
+
) {
|
|
1515
|
+
members.push(row);
|
|
1516
|
+
}
|
|
1517
|
+
continue;
|
|
1518
|
+
}
|
|
1519
|
+
if (
|
|
1520
|
+
meta.eventKind === "reaction" &&
|
|
1521
|
+
meta.reaction &&
|
|
1522
|
+
meta.reaction.targetChannelTs === activeThreadTs
|
|
1523
|
+
) {
|
|
1524
|
+
members.push(row);
|
|
1525
|
+
continue;
|
|
1526
|
+
}
|
|
1527
|
+
// Reactions targeting a reply within the thread also belong in the
|
|
1528
|
+
// focus block — collect them by checking the reaction target against
|
|
1529
|
+
// any thread reply's channelTs we've already accepted. We do this in a
|
|
1530
|
+
// second pass below to avoid an O(n^2) inner scan here.
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
// Second pass: pull in reactions whose target is one of the already-
|
|
1534
|
+
// collected reply messages. Using a Set keeps this O(n).
|
|
1535
|
+
const memberChannelTs = new Set(
|
|
1536
|
+
members
|
|
1537
|
+
.map((m) => m.metadata?.channelTs)
|
|
1538
|
+
.filter((v): v is string => typeof v === "string"),
|
|
1539
|
+
);
|
|
1540
|
+
for (const row of rows) {
|
|
1541
|
+
const meta = row.metadata;
|
|
1542
|
+
if (!meta || meta.eventKind !== "reaction" || !meta.reaction) continue;
|
|
1543
|
+
if (meta.reaction.targetChannelTs === activeThreadTs) continue; // already added
|
|
1544
|
+
if (memberChannelTs.has(meta.reaction.targetChannelTs)) {
|
|
1545
|
+
members.push(row);
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
if (members.length === 0) return null;
|
|
1550
|
+
|
|
1551
|
+
// The active-thread block is flattened to plain text below, which discards
|
|
1552
|
+
// `Message.role`. Force a role-derived sender label on any member whose
|
|
1553
|
+
// `rowToRenderable` emitted `null` (assistant rows, user rows without a
|
|
1554
|
+
// real Slack displayName) so speaker attribution survives the flattening.
|
|
1555
|
+
const labeledMembers = members.map((m) =>
|
|
1556
|
+
m.senderLabel
|
|
1557
|
+
? m
|
|
1558
|
+
: {
|
|
1559
|
+
...m,
|
|
1560
|
+
senderLabel: m.role === "assistant" ? "@assistant" : "@user",
|
|
1561
|
+
},
|
|
1562
|
+
);
|
|
1563
|
+
|
|
1564
|
+
const rendered = renderSlackTranscript(labeledMembers);
|
|
1565
|
+
if (rendered.length === 0) return null;
|
|
1566
|
+
const lines = extractTagLineTexts(rendered).join("\n");
|
|
1567
|
+
return `<active_thread>\n${lines}\n</active_thread>`;
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
/**
|
|
1571
|
+
* Build the Slack active-thread focus block from raw rows.
|
|
1572
|
+
*
|
|
1573
|
+
* Pure assembly entrypoint mirroring `assembleSlackChronologicalMessages`.
|
|
1574
|
+
* Returns the rendered `<active_thread>` block as a string, or `null` when:
|
|
1575
|
+
* - the channel is not Slack, OR
|
|
1576
|
+
* - the channel is a Slack DM (DMs do not have threads), OR
|
|
1577
|
+
* - the latest user row is top-level (not in a thread), OR
|
|
1578
|
+
* - no rows belong to the active thread.
|
|
1579
|
+
*/
|
|
1580
|
+
export function assembleSlackActiveThreadFocusBlock(
|
|
1581
|
+
rows: SlackTranscriptInputRow[],
|
|
1582
|
+
capabilities: ChannelCapabilities,
|
|
1583
|
+
): string | null {
|
|
1584
|
+
if (capabilities.channel !== "slack") return null;
|
|
1585
|
+
// DMs do not have threads, so the focus block is always a no-op.
|
|
1586
|
+
// The gateway sets `chatType: "channel"` for every non-DM Slack
|
|
1587
|
+
// conversation and omits the field for DMs, so gate the focus block
|
|
1588
|
+
// on the positive `"channel"` match.
|
|
1589
|
+
if (capabilities.chatType !== "channel") return null;
|
|
1590
|
+
const renderable = rows.map(rowToRenderable);
|
|
1591
|
+
const activeThreadTs = detectActiveThreadTs(renderable);
|
|
1592
|
+
if (!activeThreadTs) return null;
|
|
1593
|
+
return buildActiveThreadBlockFromRenderable(renderable, activeThreadTs);
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
/**
|
|
1597
|
+
* Loader convenience over `assembleSlackActiveThreadFocusBlock` mirroring
|
|
1598
|
+
* `loadSlackChronologicalMessages`. Returns `null` when the channel is not
|
|
1599
|
+
* Slack, or when it is a Slack DM (DMs have no threads), so callers can
|
|
1600
|
+
* skip the injection entirely without paying for a DB read.
|
|
1601
|
+
*/
|
|
1602
|
+
export function loadSlackActiveThreadFocusBlock(
|
|
1603
|
+
conversationId: string,
|
|
1604
|
+
capabilities: ChannelCapabilities,
|
|
1605
|
+
options: {
|
|
1606
|
+
loader?: (id: string) => MessageRow[];
|
|
1607
|
+
trustClass?: TrustClass;
|
|
1608
|
+
} = {},
|
|
1609
|
+
): string | null {
|
|
1610
|
+
if (capabilities.channel !== "slack") return null;
|
|
1611
|
+
if (capabilities.chatType !== "channel") return null;
|
|
1612
|
+
const loader = options.loader ?? defaultGetMessages;
|
|
1613
|
+
const allRows = loader(conversationId);
|
|
1614
|
+
const scopedRows = isUntrustedTrustClass(options.trustClass)
|
|
1615
|
+
? filterMessagesForUntrustedActor(allRows)
|
|
1616
|
+
: allRows;
|
|
1617
|
+
const rows: SlackTranscriptInputRow[] = scopedRows.map((row) => ({
|
|
1618
|
+
role: row.role === "assistant" ? "assistant" : "user",
|
|
1619
|
+
content: row.content,
|
|
1620
|
+
createdAt: row.createdAt,
|
|
1621
|
+
metadata: row.metadata,
|
|
1622
|
+
}));
|
|
1623
|
+
return assembleSlackActiveThreadFocusBlock(rows, capabilities);
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1141
1626
|
/** Prefixes stripped by the pipeline (order doesn't matter — single pass). */
|
|
1142
1627
|
const RUNTIME_INJECTION_PREFIXES = [
|
|
1143
1628
|
"<channel_capabilities>",
|
|
@@ -1164,11 +1649,18 @@ const RUNTIME_INJECTION_PREFIXES = [
|
|
|
1164
1649
|
"<active_workspace>",
|
|
1165
1650
|
"<active_dynamic_page>",
|
|
1166
1651
|
"<non_interactive_context>",
|
|
1167
|
-
|
|
1652
|
+
// Shared prefix catches both the current NOW.md tag and any pre-line-limit
|
|
1653
|
+
// variant that may linger in in-flight histories during a rolling deploy.
|
|
1654
|
+
"<NOW.md Always keep this up to date",
|
|
1168
1655
|
"<now_scratchpad>", // backward-compat: strip legacy blocks from pre-rename history
|
|
1169
|
-
"<
|
|
1656
|
+
"<knowledge_base>",
|
|
1657
|
+
"<pkb>", // backward-compat: strip legacy tag from pre-rename history
|
|
1170
1658
|
"<system_reminder>",
|
|
1171
1659
|
"<transport_hints>",
|
|
1660
|
+
// The Slack active-thread focus block is non-persisted and injected on
|
|
1661
|
+
// the FINAL user turn only. Strip it here so re-assembly during compaction
|
|
1662
|
+
// and overflow recovery does not duplicate it across turns.
|
|
1663
|
+
"<active_thread>",
|
|
1172
1664
|
"<system_notice>One or more tool calls returned an error.",
|
|
1173
1665
|
];
|
|
1174
1666
|
|
|
@@ -1189,16 +1681,23 @@ export function stripInjectionsForCompaction(messages: Message[]): Message[] {
|
|
|
1189
1681
|
* Returns null if no NOW.md injection is found.
|
|
1190
1682
|
*/
|
|
1191
1683
|
export function findLastInjectedNowContent(messages: Message[]): string | null {
|
|
1192
|
-
|
|
1684
|
+
// Matches every NOW.md opening tag we emit (the tag text may evolve over
|
|
1685
|
+
// time, e.g. adding a line-limit hint), so in-flight histories with older
|
|
1686
|
+
// tag variants remain discoverable during a rolling deploy.
|
|
1687
|
+
const openTagPrefix = "<NOW.md Always keep this up to date";
|
|
1193
1688
|
const suffix = "\n</NOW.md>";
|
|
1194
1689
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1195
1690
|
const msg = messages[i];
|
|
1196
1691
|
if (msg.role !== "user") continue;
|
|
1197
1692
|
for (const block of msg.content) {
|
|
1198
|
-
if (block.type
|
|
1199
|
-
|
|
1200
|
-
if (end > prefix.length) return block.text.slice(prefix.length, end);
|
|
1693
|
+
if (block.type !== "text" || !block.text.startsWith(openTagPrefix)) {
|
|
1694
|
+
continue;
|
|
1201
1695
|
}
|
|
1696
|
+
const tagEnd = block.text.indexOf(">\n");
|
|
1697
|
+
if (tagEnd < 0) continue;
|
|
1698
|
+
const contentStart = tagEnd + ">\n".length;
|
|
1699
|
+
const end = block.text.lastIndexOf(suffix);
|
|
1700
|
+
if (end > contentStart) return block.text.slice(contentStart, end);
|
|
1202
1701
|
}
|
|
1203
1702
|
}
|
|
1204
1703
|
return null;
|
|
@@ -1215,13 +1714,29 @@ export function findLastInjectedNowContent(messages: Message[]): string | null {
|
|
|
1215
1714
|
*/
|
|
1216
1715
|
export type InjectionMode = "full" | "minimal";
|
|
1217
1716
|
|
|
1717
|
+
/**
|
|
1718
|
+
* Per-turn injection bytes captured for later persistence to message
|
|
1719
|
+
* metadata. Empty in this PR — later PRs capture `<turn_context>` and
|
|
1720
|
+
* `<system_reminder>` bodies so they survive daemon restarts.
|
|
1721
|
+
*/
|
|
1722
|
+
export interface RuntimeInjectionBlocks {
|
|
1723
|
+
unifiedTurnContext?: string;
|
|
1724
|
+
pkbSystemReminder?: string;
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
export interface RuntimeInjectionResult {
|
|
1728
|
+
messages: Message[];
|
|
1729
|
+
blocks: RuntimeInjectionBlocks;
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1218
1732
|
/**
|
|
1219
1733
|
* Apply a chain of user-message injections to `runMessages`.
|
|
1220
1734
|
*
|
|
1221
1735
|
* Each injection is optional — pass `null`/`undefined` to skip it.
|
|
1222
|
-
* Returns the final message array ready for the provider
|
|
1736
|
+
* Returns the final message array ready for the provider, along with a
|
|
1737
|
+
* `blocks` object reserved for captured injection bytes (currently empty).
|
|
1223
1738
|
*/
|
|
1224
|
-
export function applyRuntimeInjections(
|
|
1739
|
+
export async function applyRuntimeInjections(
|
|
1225
1740
|
runMessages: Message[],
|
|
1226
1741
|
options: {
|
|
1227
1742
|
activeSurface?: ActiveSurfaceContext | null;
|
|
@@ -1232,15 +1747,114 @@ export function applyRuntimeInjections(
|
|
|
1232
1747
|
voiceCallControlPrompt?: string | null;
|
|
1233
1748
|
pkbContext?: string | null;
|
|
1234
1749
|
pkbActive?: boolean;
|
|
1750
|
+
/**
|
|
1751
|
+
* Dense query vector surfaced from the graph memory retriever (PR 3).
|
|
1752
|
+
* When present together with `pkbActive`, used to run `searchPkbFiles`
|
|
1753
|
+
* to surface relevance hints in the PKB system reminder. When missing,
|
|
1754
|
+
* the reminder falls back to the flat static text.
|
|
1755
|
+
*/
|
|
1756
|
+
pkbQueryVector?: number[];
|
|
1757
|
+
/** Optional sparse vector accompanying `pkbQueryVector`. */
|
|
1758
|
+
pkbSparseVector?: QdrantSparseVector;
|
|
1759
|
+
/** Memory scope id used to filter PKB search results. */
|
|
1760
|
+
pkbScopeId?: string;
|
|
1761
|
+
/**
|
|
1762
|
+
* The live conversation (or a minimal shape containing `messages`) used
|
|
1763
|
+
* to compute which PKB paths are already "in context" and therefore
|
|
1764
|
+
* suppressed from hint suggestions.
|
|
1765
|
+
*/
|
|
1766
|
+
pkbConversation?: PkbContextConversation;
|
|
1767
|
+
/** Auto-injected PKB filenames (resolved relative to `pkbRoot`). */
|
|
1768
|
+
pkbAutoInjectList?: string[];
|
|
1769
|
+
/** Absolute path to the PKB directory (e.g. `<workspace>/pkb`). */
|
|
1770
|
+
pkbRoot?: string;
|
|
1771
|
+
/**
|
|
1772
|
+
* Working directory against which relative `file_read` tool paths
|
|
1773
|
+
* resolve, used to detect workspace-relative reads like
|
|
1774
|
+
* `pkb/threads.md`. Falls back to `pkbRoot` when omitted.
|
|
1775
|
+
*/
|
|
1776
|
+
pkbWorkingDir?: string;
|
|
1235
1777
|
nowScratchpad?: string | null;
|
|
1236
1778
|
subagentStatusBlock?: string | null;
|
|
1237
1779
|
isNonInteractive?: boolean;
|
|
1238
1780
|
transportHints?: string[] | null;
|
|
1781
|
+
/**
|
|
1782
|
+
* Pre-rendered Slack chronological transcript that replaces the
|
|
1783
|
+
* default `runMessages` history for any Slack conversation (channels
|
|
1784
|
+
* and DMs alike).
|
|
1785
|
+
*
|
|
1786
|
+
* When `channelCapabilities` describes a Slack conversation and this
|
|
1787
|
+
* array is non-empty, it overrides `runMessages` so the model sees one
|
|
1788
|
+
* chronologically-ordered transcript built from the stored Slack
|
|
1789
|
+
* metadata. Channel renders include sibling-thread tags; DM renders
|
|
1790
|
+
* are flat (DMs have no threads). The `transportHints` pipeline is
|
|
1791
|
+
* skipped for any Slack conversation so the persisted view isn't
|
|
1792
|
+
* duplicated by gateway-side hints.
|
|
1793
|
+
*
|
|
1794
|
+
* Callers build this via `loadSlackChronologicalMessages` (or the
|
|
1795
|
+
* underlying `assembleSlackChronologicalMessages`) before invoking
|
|
1796
|
+
* this function so the assembly path stays free of direct DB calls
|
|
1797
|
+
* and remains easy to test.
|
|
1798
|
+
*/
|
|
1799
|
+
slackChronologicalMessages?: Message[] | null;
|
|
1800
|
+
/**
|
|
1801
|
+
* Pre-rendered `<active_thread>` focus block listing the messages of
|
|
1802
|
+
* the thread the current inbound user message belongs to.
|
|
1803
|
+
*
|
|
1804
|
+
* Appended (tail-block) to the FINAL user message ONLY when
|
|
1805
|
+
* `channelCapabilities` describes a Slack non-DM channel. The block is
|
|
1806
|
+
* non-persisted: history rebuilds re-derive it from storage on each
|
|
1807
|
+
* turn, and `RUNTIME_INJECTION_PREFIXES` strips any `<active_thread>`
|
|
1808
|
+
* blocks from prior turns so they do not accumulate.
|
|
1809
|
+
*
|
|
1810
|
+
* Callers build this via `loadSlackActiveThreadFocusBlock` (or the
|
|
1811
|
+
* underlying `assembleSlackActiveThreadFocusBlock`). Pass `null` /
|
|
1812
|
+
* `undefined` when the inbound is a top-level (non-thread) post.
|
|
1813
|
+
*/
|
|
1814
|
+
slackActiveThreadFocusBlock?: string | null;
|
|
1239
1815
|
mode?: InjectionMode;
|
|
1240
1816
|
},
|
|
1241
|
-
):
|
|
1817
|
+
): Promise<RuntimeInjectionResult> {
|
|
1242
1818
|
const mode = options.mode ?? "full";
|
|
1819
|
+
let pkbSystemReminderCaptured: string | undefined;
|
|
1820
|
+
const slackChannel = isSlackChannelConversation(options.channelCapabilities);
|
|
1821
|
+
// Slack DMs and channels both assemble context from persisted message
|
|
1822
|
+
// rows, so suppress hint injection for any Slack conversation. Other
|
|
1823
|
+
// channels (telegram, email, etc.) keep the generic hint pipeline.
|
|
1824
|
+
const slackConversation = options.channelCapabilities?.channel === "slack";
|
|
1825
|
+
let turnContextCaptured: string | undefined;
|
|
1243
1826
|
let result = runMessages;
|
|
1827
|
+
// Slack channels AND DMs both override `runMessages` with a pre-rendered
|
|
1828
|
+
// chronological transcript built from persisted message rows. The shared
|
|
1829
|
+
// assembler (`assembleSlackChronologicalMessages`) renders thread tags
|
|
1830
|
+
// for channels and a flat sequence for DMs, so the same branch handles
|
|
1831
|
+
// both. The active-thread focus block below stays gated on `slackChannel`
|
|
1832
|
+
// since DMs do not have threads.
|
|
1833
|
+
if (
|
|
1834
|
+
slackConversation &&
|
|
1835
|
+
options.slackChronologicalMessages &&
|
|
1836
|
+
options.slackChronologicalMessages.length > 0
|
|
1837
|
+
) {
|
|
1838
|
+
// `graphMemory.prepareMemory` prepends a `<memory __injected>` block
|
|
1839
|
+
// (and any memory-image groups) to the last user message before
|
|
1840
|
+
// runtime assembly runs. The Slack transcript is freshly rendered
|
|
1841
|
+
// from persisted rows and has no such prefix, so swap it in and then
|
|
1842
|
+
// re-prepend the captured prefix onto the new tail user message.
|
|
1843
|
+
const carriedMemoryBlocks = extractMemoryPrefixBlocks(runMessages);
|
|
1844
|
+
result = options.slackChronologicalMessages;
|
|
1845
|
+
if (carriedMemoryBlocks.length > 0) {
|
|
1846
|
+
const slackTail = result[result.length - 1];
|
|
1847
|
+
if (slackTail && slackTail.role === "user") {
|
|
1848
|
+
result = [
|
|
1849
|
+
...result.slice(0, -1),
|
|
1850
|
+
{
|
|
1851
|
+
...slackTail,
|
|
1852
|
+
content: [...carriedMemoryBlocks, ...slackTail.content],
|
|
1853
|
+
},
|
|
1854
|
+
];
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1244
1858
|
|
|
1245
1859
|
// For non-interactive conversations (scheduled jobs, work items), instruct the
|
|
1246
1860
|
// model to never ask for clarification — there is no human present to answer.
|
|
@@ -1284,17 +1898,90 @@ export function applyRuntimeInjections(
|
|
|
1284
1898
|
}
|
|
1285
1899
|
|
|
1286
1900
|
// PKB behavioral nudge — injected on every turn when PKB is active so
|
|
1287
|
-
// the model keeps reading topic files and calling `remember`.
|
|
1901
|
+
// the model keeps reading topic files and calling `remember`. When a
|
|
1902
|
+
// query vector is available from the graph memory retriever, run a
|
|
1903
|
+
// hybrid PKB search to surface up to three relevance hints; fall back
|
|
1904
|
+
// to the flat static reminder on empty results or any error.
|
|
1288
1905
|
if (mode === "full" && options.pkbActive) {
|
|
1289
1906
|
const userTail = result[result.length - 1];
|
|
1290
1907
|
if (userTail && userTail.role === "user") {
|
|
1908
|
+
let hints: string[] = [];
|
|
1909
|
+
const queryVector = options.pkbQueryVector;
|
|
1910
|
+
if (
|
|
1911
|
+
queryVector &&
|
|
1912
|
+
queryVector.length > 0 &&
|
|
1913
|
+
options.pkbScopeId &&
|
|
1914
|
+
options.pkbConversation &&
|
|
1915
|
+
options.pkbRoot
|
|
1916
|
+
) {
|
|
1917
|
+
try {
|
|
1918
|
+
const results = await searchPkbFiles(
|
|
1919
|
+
queryVector,
|
|
1920
|
+
options.pkbSparseVector,
|
|
1921
|
+
8,
|
|
1922
|
+
[options.pkbScopeId],
|
|
1923
|
+
);
|
|
1924
|
+
const workingDir = options.pkbWorkingDir ?? options.pkbRoot;
|
|
1925
|
+
const inContext = getInContextPkbPaths(
|
|
1926
|
+
options.pkbConversation,
|
|
1927
|
+
options.pkbAutoInjectList ?? [],
|
|
1928
|
+
options.pkbRoot,
|
|
1929
|
+
workingDir,
|
|
1930
|
+
);
|
|
1931
|
+
const pkbRoot = options.pkbRoot;
|
|
1932
|
+
// Gate on `denseScore` (cosine, [0, 1]) so the quality bar is stable
|
|
1933
|
+
// regardless of whether sparse was provided. Rank by `hybridScore`
|
|
1934
|
+
// (RRF) when available — that captures the sparse signal for
|
|
1935
|
+
// re-ordering eligible hits. hybridScore and denseScore live on
|
|
1936
|
+
// different scales, so items with hybridScore are ordered together
|
|
1937
|
+
// and placed ahead of items that only have denseScore.
|
|
1938
|
+
hints = results
|
|
1939
|
+
.filter((r) => {
|
|
1940
|
+
const abs = resolve(pkbRoot, r.path);
|
|
1941
|
+
if (inContext.has(abs)) return false;
|
|
1942
|
+
const threshold = r.path
|
|
1943
|
+
.replace(/\\/g, "/")
|
|
1944
|
+
.startsWith("archive/")
|
|
1945
|
+
? PKB_HINT_ARCHIVE_THRESHOLD
|
|
1946
|
+
: PKB_HINT_THRESHOLD;
|
|
1947
|
+
return r.denseScore >= threshold;
|
|
1948
|
+
})
|
|
1949
|
+
.sort((a, b) => {
|
|
1950
|
+
const aHasHybrid = a.hybridScore !== undefined;
|
|
1951
|
+
const bHasHybrid = b.hybridScore !== undefined;
|
|
1952
|
+
if (aHasHybrid && !bHasHybrid) return -1;
|
|
1953
|
+
if (!aHasHybrid && bHasHybrid) return 1;
|
|
1954
|
+
if (aHasHybrid && bHasHybrid) {
|
|
1955
|
+
return b.hybridScore! - a.hybridScore!;
|
|
1956
|
+
}
|
|
1957
|
+
return b.denseScore - a.denseScore;
|
|
1958
|
+
})
|
|
1959
|
+
.slice(0, 3)
|
|
1960
|
+
.map((r) => r.path);
|
|
1961
|
+
} catch (err) {
|
|
1962
|
+
pkbReminderLog.warn(
|
|
1963
|
+
{ err: err instanceof Error ? err.message : String(err) },
|
|
1964
|
+
"PKB hint search failed — falling back to flat reminder",
|
|
1965
|
+
);
|
|
1966
|
+
hints = [];
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
const reminder = buildPkbReminder(hints);
|
|
1971
|
+
pkbSystemReminderCaptured = reminder;
|
|
1972
|
+
// Splice the reminder in right after the memory prefix blocks so it
|
|
1973
|
+
// lands above the user's typed text, producing the tail shape
|
|
1974
|
+
// `[<turn_context>, <memory __injected>, <system_reminder>, ...your_text, ...later_appends]`
|
|
1975
|
+
// after `unifiedTurnContext` later prepends `<turn_context>` at index 0.
|
|
1976
|
+
const memoryPrefixCount = countMemoryPrefixBlocks(userTail.content);
|
|
1291
1977
|
result = [
|
|
1292
1978
|
...result.slice(0, -1),
|
|
1293
1979
|
{
|
|
1294
1980
|
...userTail,
|
|
1295
1981
|
content: [
|
|
1296
|
-
...userTail.content,
|
|
1297
|
-
{ type: "text" as const, text:
|
|
1982
|
+
...userTail.content.slice(0, memoryPrefixCount),
|
|
1983
|
+
{ type: "text" as const, text: reminder },
|
|
1984
|
+
...userTail.content.slice(memoryPrefixCount),
|
|
1298
1985
|
],
|
|
1299
1986
|
},
|
|
1300
1987
|
];
|
|
@@ -1354,6 +2041,7 @@ export function applyRuntimeInjections(
|
|
|
1354
2041
|
if (options.unifiedTurnContext) {
|
|
1355
2042
|
const userTail = result[result.length - 1];
|
|
1356
2043
|
if (userTail && userTail.role === "user") {
|
|
2044
|
+
turnContextCaptured = options.unifiedTurnContext;
|
|
1357
2045
|
result = [
|
|
1358
2046
|
...result.slice(0, -1),
|
|
1359
2047
|
{
|
|
@@ -1367,8 +2055,15 @@ export function applyRuntimeInjections(
|
|
|
1367
2055
|
}
|
|
1368
2056
|
}
|
|
1369
2057
|
|
|
2058
|
+
// Slack conversations (both channels and DMs) build their own
|
|
2059
|
+
// chronological transcript from persisted messages and intentionally do
|
|
2060
|
+
// not receive the per-turn `<transport_hints>` block — the rendered
|
|
2061
|
+
// history already covers the active thread / DM, so duplicating it
|
|
2062
|
+
// would confuse the model. Other channels (telegram, email, etc.) keep
|
|
2063
|
+
// the existing injection.
|
|
1370
2064
|
if (
|
|
1371
2065
|
mode === "full" &&
|
|
2066
|
+
!slackConversation &&
|
|
1372
2067
|
options.transportHints &&
|
|
1373
2068
|
options.transportHints.length > 0
|
|
1374
2069
|
) {
|
|
@@ -1381,6 +2076,36 @@ export function applyRuntimeInjections(
|
|
|
1381
2076
|
}
|
|
1382
2077
|
}
|
|
1383
2078
|
|
|
2079
|
+
// Slack active-thread focus block: when the inbound user message lives
|
|
2080
|
+
// inside a thread, append a non-persisted `<active_thread>` tail block
|
|
2081
|
+
// listing that thread's parent + replies so the model can orient even
|
|
2082
|
+
// when the channel-wide chronological transcript is long and
|
|
2083
|
+
// interleaved. Stripped on subsequent rebuilds via the
|
|
2084
|
+
// `RUNTIME_INJECTION_PREFIXES` list so focus blocks never accumulate.
|
|
2085
|
+
if (
|
|
2086
|
+
mode === "full" &&
|
|
2087
|
+
slackChannel &&
|
|
2088
|
+
typeof options.slackActiveThreadFocusBlock === "string" &&
|
|
2089
|
+
options.slackActiveThreadFocusBlock.length > 0
|
|
2090
|
+
) {
|
|
2091
|
+
const userTail = result[result.length - 1];
|
|
2092
|
+
if (userTail && userTail.role === "user") {
|
|
2093
|
+
result = [
|
|
2094
|
+
...result.slice(0, -1),
|
|
2095
|
+
{
|
|
2096
|
+
...userTail,
|
|
2097
|
+
content: [
|
|
2098
|
+
...userTail.content,
|
|
2099
|
+
{
|
|
2100
|
+
type: "text" as const,
|
|
2101
|
+
text: options.slackActiveThreadFocusBlock,
|
|
2102
|
+
},
|
|
2103
|
+
],
|
|
2104
|
+
},
|
|
2105
|
+
];
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
|
|
1384
2109
|
// Workspace top-level context is injected last so it appears first
|
|
1385
2110
|
// (prepended) in the user message content, keeping cache breakpoints
|
|
1386
2111
|
// anchored to the trailing blocks.
|
|
@@ -1397,5 +2122,11 @@ export function applyRuntimeInjections(
|
|
|
1397
2122
|
}
|
|
1398
2123
|
}
|
|
1399
2124
|
|
|
1400
|
-
return
|
|
2125
|
+
return {
|
|
2126
|
+
messages: result,
|
|
2127
|
+
blocks: {
|
|
2128
|
+
unifiedTurnContext: turnContextCaptured,
|
|
2129
|
+
pkbSystemReminder: pkbSystemReminderCaptured,
|
|
2130
|
+
},
|
|
2131
|
+
};
|
|
1401
2132
|
}
|