@vellumai/assistant 0.3.0
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/.dockerignore +27 -0
- package/.env.example +22 -0
- package/Dockerfile +99 -0
- package/Dockerfile.sandbox +5 -0
- package/README.md +248 -0
- package/bun.lock +1723 -0
- package/bunfig.toml +2 -0
- package/docs/skills.md +158 -0
- package/drizzle/0000_dizzy_maggott.sql +301 -0
- package/drizzle/meta/0000_snapshot.json +1999 -0
- package/drizzle/meta/_journal.json +13 -0
- package/drizzle.config.ts +7 -0
- package/eslint.config.mjs +17 -0
- package/hook-templates/debug-prompt-logger/hook.json +7 -0
- package/hook-templates/debug-prompt-logger/run.sh +68 -0
- package/knip.json +9 -0
- package/package.json +70 -0
- package/scripts/capture-x-graphql.ts +545 -0
- package/scripts/ipc/check-contract-inventory.ts +104 -0
- package/scripts/ipc/check-swift-decoder-drift.ts +166 -0
- package/scripts/ipc/generate-swift.ts +492 -0
- package/scripts/test-filesystem-tools.sh +48 -0
- package/scripts/test.sh +127 -0
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +2485 -0
- package/src/__tests__/account-registry.test.ts +245 -0
- package/src/__tests__/active-skill-tools.test.ts +378 -0
- package/src/__tests__/agent-heartbeat-service.test.ts +250 -0
- package/src/__tests__/agent-loop-thinking.test.ts +81 -0
- package/src/__tests__/agent-loop.test.ts +1135 -0
- package/src/__tests__/anthropic-provider.test.ts +778 -0
- package/src/__tests__/app-builder-tool-scripts.test.ts +290 -0
- package/src/__tests__/app-bundler.test.ts +292 -0
- package/src/__tests__/app-executors.test.ts +613 -0
- package/src/__tests__/app-git-history.test.ts +176 -0
- package/src/__tests__/app-git-service.test.ts +169 -0
- package/src/__tests__/app-open-proxy.test.ts +62 -0
- package/src/__tests__/asset-materialize-tool.test.ts +452 -0
- package/src/__tests__/asset-search-tool.test.ts +477 -0
- package/src/__tests__/assistant-attachment-directive.test.ts +401 -0
- package/src/__tests__/assistant-attachments.test.ts +437 -0
- package/src/__tests__/assistant-event-hub.test.ts +226 -0
- package/src/__tests__/assistant-event.test.ts +123 -0
- package/src/__tests__/assistant-events-sse-hardening.test.ts +315 -0
- package/src/__tests__/attachments-store.test.ts +476 -0
- package/src/__tests__/attachments.test.ts +134 -0
- package/src/__tests__/audit-log-rotation.test.ts +154 -0
- package/src/__tests__/browser-fill-credential.test.ts +309 -0
- package/src/__tests__/browser-manager.test.ts +203 -0
- package/src/__tests__/browser-runtime-check.test.ts +55 -0
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +68 -0
- package/src/__tests__/browser-skill-endstate.test.ts +195 -0
- package/src/__tests__/bundle-scanner.test.ts +313 -0
- package/src/__tests__/call-bridge.test.ts +517 -0
- package/src/__tests__/call-constants.test.ts +40 -0
- package/src/__tests__/call-domain.test.ts +163 -0
- package/src/__tests__/call-orchestrator.test.ts +625 -0
- package/src/__tests__/call-recovery.test.ts +518 -0
- package/src/__tests__/call-routes-http.test.ts +699 -0
- package/src/__tests__/call-state-machine.test.ts +143 -0
- package/src/__tests__/call-state.test.ts +174 -0
- package/src/__tests__/call-store.test.ts +691 -0
- package/src/__tests__/channel-approval-routes.test.ts +2356 -0
- package/src/__tests__/channel-approval.test.ts +299 -0
- package/src/__tests__/channel-approvals.test.ts +521 -0
- package/src/__tests__/channel-delivery-store.test.ts +447 -0
- package/src/__tests__/channel-guardian.test.ts +1005 -0
- package/src/__tests__/checker.test.ts +3519 -0
- package/src/__tests__/clarification-resolver.test.ts +159 -0
- package/src/__tests__/classifier.test.ts +67 -0
- package/src/__tests__/claude-code-skill-regression.test.ts +127 -0
- package/src/__tests__/claude-code-tool-profiles.test.ts +88 -0
- package/src/__tests__/cli-discover.test.ts +85 -0
- package/src/__tests__/cli.test.ts +26 -0
- package/src/__tests__/clipboard.test.ts +80 -0
- package/src/__tests__/commit-guarantee.test.ts +335 -0
- package/src/__tests__/commit-message-enrichment-service.test.ts +550 -0
- package/src/__tests__/compaction.benchmark.test.ts +176 -0
- package/src/__tests__/computer-use-session-compaction.test.ts +132 -0
- package/src/__tests__/computer-use-session-lifecycle.test.ts +293 -0
- package/src/__tests__/computer-use-session-working-dir.test.ts +117 -0
- package/src/__tests__/computer-use-skill-baseline.test.ts +74 -0
- package/src/__tests__/computer-use-skill-endstate.test.ts +89 -0
- package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +217 -0
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +107 -0
- package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +54 -0
- package/src/__tests__/computer-use-tools.test.ts +250 -0
- package/src/__tests__/config-schema.test.ts +1462 -0
- package/src/__tests__/conflict-intent-tokenization.test.ts +141 -0
- package/src/__tests__/conflict-policy.test.ts +121 -0
- package/src/__tests__/conflict-store.test.ts +332 -0
- package/src/__tests__/connection-policy.test.ts +102 -0
- package/src/__tests__/contacts-tools.test.ts +331 -0
- package/src/__tests__/context-memory-e2e.test.ts +434 -0
- package/src/__tests__/context-token-estimator.test.ts +135 -0
- package/src/__tests__/context-window-manager.test.ts +376 -0
- package/src/__tests__/contradiction-checker.test.ts +314 -0
- package/src/__tests__/conversation-store.test.ts +612 -0
- package/src/__tests__/credential-broker-browser-fill.test.ts +517 -0
- package/src/__tests__/credential-broker-server-use.test.ts +554 -0
- package/src/__tests__/credential-broker.test.ts +167 -0
- package/src/__tests__/credential-host-pattern-match.test.ts +104 -0
- package/src/__tests__/credential-metadata-store.test.ts +779 -0
- package/src/__tests__/credential-policy-validate.test.ts +121 -0
- package/src/__tests__/credential-resolve.test.ts +328 -0
- package/src/__tests__/credential-security-e2e.test.ts +352 -0
- package/src/__tests__/credential-security-invariants.test.ts +583 -0
- package/src/__tests__/credential-selection.test.ts +354 -0
- package/src/__tests__/credential-vault-unit.test.ts +780 -0
- package/src/__tests__/credential-vault.test.ts +852 -0
- package/src/__tests__/daemon-assistant-events.test.ts +164 -0
- package/src/__tests__/daemon-server-session-init.test.ts +522 -0
- package/src/__tests__/date-context.test.ts +373 -0
- package/src/__tests__/db-schedule-syntax-migration.test.ts +129 -0
- package/src/__tests__/delete-managed-skill-tool.test.ts +97 -0
- package/src/__tests__/diff.test.ts +121 -0
- package/src/__tests__/domain-normalize.test.ts +112 -0
- package/src/__tests__/domain-policy.test.ts +124 -0
- package/src/__tests__/doordash-client.test.ts +186 -0
- package/src/__tests__/doordash-session.test.ts +152 -0
- package/src/__tests__/dynamic-page-surface.test.ts +91 -0
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +132 -0
- package/src/__tests__/edit-engine.test.ts +180 -0
- package/src/__tests__/elevenlabs-client.test.ts +271 -0
- package/src/__tests__/email-cli.test.ts +283 -0
- package/src/__tests__/encrypted-store.test.ts +332 -0
- package/src/__tests__/entity-extractor.test.ts +190 -0
- package/src/__tests__/ephemeral-permissions.test.ts +362 -0
- package/src/__tests__/evaluate-typescript-tool.test.ts +286 -0
- package/src/__tests__/event-bus.test.ts +222 -0
- package/src/__tests__/file-edit-tool.test.ts +122 -0
- package/src/__tests__/file-ops-service.test.ts +330 -0
- package/src/__tests__/file-read-tool.test.ts +75 -0
- package/src/__tests__/file-write-tool.test.ts +113 -0
- package/src/__tests__/filesystem-tools.test.ts +579 -0
- package/src/__tests__/fixtures/credential-security-fixtures.ts +181 -0
- package/src/__tests__/fixtures/media-reuse-fixtures.ts +126 -0
- package/src/__tests__/fixtures/mock-signup-server.ts +387 -0
- package/src/__tests__/fixtures/proxy-fixtures.ts +147 -0
- package/src/__tests__/followup-tools.test.ts +303 -0
- package/src/__tests__/forbidden-legacy-symbols.test.ts +71 -0
- package/src/__tests__/fuzzy-match-property.test.ts +216 -0
- package/src/__tests__/fuzzy-match.test.ts +138 -0
- package/src/__tests__/gateway-only-enforcement.test.ts +631 -0
- package/src/__tests__/gemini-image-service.test.ts +261 -0
- package/src/__tests__/gemini-provider.test.ts +651 -0
- package/src/__tests__/get-weather.test.ts +318 -0
- package/src/__tests__/gmail-integration.test.ts +73 -0
- package/src/__tests__/handlers-add-trust-rule-metadata.test.ts +202 -0
- package/src/__tests__/handlers-cu-observation-blob.test.ts +352 -0
- package/src/__tests__/handlers-ipc-blob-probe.test.ts +191 -0
- package/src/__tests__/handlers-slack-config.test.ts +200 -0
- package/src/__tests__/handlers-task-submit-slash.test.ts +38 -0
- package/src/__tests__/handlers-telegram-config.test.ts +968 -0
- package/src/__tests__/handlers-twilio-config.test.ts +659 -0
- package/src/__tests__/handlers-twitter-config.test.ts +858 -0
- package/src/__tests__/headless-browser-interactions.test.ts +536 -0
- package/src/__tests__/headless-browser-navigate.test.ts +211 -0
- package/src/__tests__/headless-browser-read-tools.test.ts +261 -0
- package/src/__tests__/headless-browser-snapshot.test.ts +185 -0
- package/src/__tests__/history-repair-observability.test.ts +56 -0
- package/src/__tests__/history-repair.test.ts +510 -0
- package/src/__tests__/home-base-bootstrap.test.ts +82 -0
- package/src/__tests__/hooks-blocking.test.ts +128 -0
- package/src/__tests__/hooks-cli.test.ts +144 -0
- package/src/__tests__/hooks-config.test.ts +93 -0
- package/src/__tests__/hooks-discovery.test.ts +199 -0
- package/src/__tests__/hooks-integration.test.ts +189 -0
- package/src/__tests__/hooks-manager.test.ts +187 -0
- package/src/__tests__/hooks-runner.test.ts +182 -0
- package/src/__tests__/hooks-settings.test.ts +154 -0
- package/src/__tests__/hooks-templates.test.ts +137 -0
- package/src/__tests__/hooks-ts-runner.test.ts +125 -0
- package/src/__tests__/hooks-watch.test.ts +100 -0
- package/src/__tests__/host-file-edit-tool.test.ts +228 -0
- package/src/__tests__/host-file-read-tool.test.ts +123 -0
- package/src/__tests__/host-file-write-tool.test.ts +136 -0
- package/src/__tests__/host-shell-tool.test.ts +562 -0
- package/src/__tests__/ingress-reconcile.test.ts +581 -0
- package/src/__tests__/ingress-url-consistency.test.ts +214 -0
- package/src/__tests__/intent-routing.test.ts +259 -0
- package/src/__tests__/ipc-blob-store.test.ts +315 -0
- package/src/__tests__/ipc-contract-inventory.test.ts +54 -0
- package/src/__tests__/ipc-contract.test.ts +74 -0
- package/src/__tests__/ipc-protocol.test.ts +113 -0
- package/src/__tests__/ipc-roundtrip.benchmark.test.ts +237 -0
- package/src/__tests__/ipc-snapshot.test.ts +1769 -0
- package/src/__tests__/ipc-validate.test.ts +407 -0
- package/src/__tests__/key-migration.test.ts +206 -0
- package/src/__tests__/keychain.test.ts +258 -0
- package/src/__tests__/llm-usage-store.test.ts +221 -0
- package/src/__tests__/managed-skill-lifecycle.test.ts +257 -0
- package/src/__tests__/managed-store.test.ts +608 -0
- package/src/__tests__/media-generate-image.test.ts +238 -0
- package/src/__tests__/media-reuse-story.e2e.test.ts +676 -0
- package/src/__tests__/media-visibility-policy.test.ts +141 -0
- package/src/__tests__/memory-context-benchmark.benchmark.test.ts +235 -0
- package/src/__tests__/memory-lifecycle-e2e.test.ts +481 -0
- package/src/__tests__/memory-query-builder.test.ts +59 -0
- package/src/__tests__/memory-recall-quality.test.ts +846 -0
- package/src/__tests__/memory-regressions.experimental.test.ts +538 -0
- package/src/__tests__/memory-regressions.test.ts +4435 -0
- package/src/__tests__/memory-retrieval-budget.test.ts +49 -0
- package/src/__tests__/memory-retrieval.benchmark.test.ts +430 -0
- package/src/__tests__/migration-cli-flows.test.ts +169 -0
- package/src/__tests__/migration-ordering.test.ts +249 -0
- package/src/__tests__/mock-signup-server.test.ts +528 -0
- package/src/__tests__/oauth-callback-registry.test.ts +92 -0
- package/src/__tests__/oauth2-gateway-transport.test.ts +285 -0
- package/src/__tests__/onboarding-starter-tasks.test.ts +176 -0
- package/src/__tests__/onboarding-template-contract.test.ts +58 -0
- package/src/__tests__/openai-provider.test.ts +753 -0
- package/src/__tests__/parallel-tool.benchmark.test.ts +294 -0
- package/src/__tests__/parser.test.ts +472 -0
- package/src/__tests__/path-classifier.test.ts +73 -0
- package/src/__tests__/path-policy.test.ts +435 -0
- package/src/__tests__/platform-move-helper.test.ts +99 -0
- package/src/__tests__/platform-socket-path.test.ts +52 -0
- package/src/__tests__/platform-workspace-migration.test.ts +1000 -0
- package/src/__tests__/platform.test.ts +131 -0
- package/src/__tests__/playbook-execution.test.ts +502 -0
- package/src/__tests__/playbook-tools.test.ts +340 -0
- package/src/__tests__/prebuilt-home-base-seed.test.ts +75 -0
- package/src/__tests__/pricing.test.ts +256 -0
- package/src/__tests__/profile-compiler.test.ts +374 -0
- package/src/__tests__/provider-commit-message-generator.test.ts +342 -0
- package/src/__tests__/provider-registry-ollama.test.ts +16 -0
- package/src/__tests__/provider-streaming.benchmark.test.ts +773 -0
- package/src/__tests__/proxy-approval-callback.test.ts +601 -0
- package/src/__tests__/public-ingress-urls.test.ts +256 -0
- package/src/__tests__/qdrant-manager.test.ts +267 -0
- package/src/__tests__/ratelimit.test.ts +297 -0
- package/src/__tests__/recurrence-engine-rruleset.test.ts +175 -0
- package/src/__tests__/recurrence-engine.test.ts +78 -0
- package/src/__tests__/recurrence-types.test.ts +79 -0
- package/src/__tests__/registry.test.ts +494 -0
- package/src/__tests__/relay-server.test.ts +688 -0
- package/src/__tests__/reminder-store.test.ts +223 -0
- package/src/__tests__/reminder.test.ts +229 -0
- package/src/__tests__/request-file-tool.test.ts +158 -0
- package/src/__tests__/run-orchestrator-assistant-events.test.ts +227 -0
- package/src/__tests__/run-orchestrator.test.ts +425 -0
- package/src/__tests__/runtime-attachment-metadata.test.ts +189 -0
- package/src/__tests__/runtime-events-sse-parity.test.ts +343 -0
- package/src/__tests__/runtime-events-sse.test.ts +162 -0
- package/src/__tests__/runtime-runs-http.test.ts +438 -0
- package/src/__tests__/runtime-runs.test.ts +260 -0
- package/src/__tests__/sandbox-diagnostics.test.ts +408 -0
- package/src/__tests__/sandbox-host-parity.test.ts +950 -0
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +253 -0
- package/src/__tests__/schedule-store.test.ts +484 -0
- package/src/__tests__/schedule-tools.test.ts +783 -0
- package/src/__tests__/scheduler-recurrence.test.ts +430 -0
- package/src/__tests__/script-proxy-certs.test.ts +90 -0
- package/src/__tests__/script-proxy-connect-tunnel.test.ts +177 -0
- package/src/__tests__/script-proxy-decision-trace.test.ts +156 -0
- package/src/__tests__/script-proxy-http-forwarder.test.ts +281 -0
- package/src/__tests__/script-proxy-injection-runtime.test.ts +401 -0
- package/src/__tests__/script-proxy-mitm-handler.test.ts +407 -0
- package/src/__tests__/script-proxy-policy-runtime.test.ts +287 -0
- package/src/__tests__/script-proxy-policy.test.ts +310 -0
- package/src/__tests__/script-proxy-rewrite-specificity.test.ts +135 -0
- package/src/__tests__/script-proxy-router.test.ts +180 -0
- package/src/__tests__/script-proxy-session-manager.test.ts +382 -0
- package/src/__tests__/script-proxy-session-runtime.test.ts +113 -0
- package/src/__tests__/secret-allowlist.test.ts +230 -0
- package/src/__tests__/secret-ingress-handler.test.ts +110 -0
- package/src/__tests__/secret-onetime-send.test.ts +130 -0
- package/src/__tests__/secret-prompt-log-hygiene.test.ts +106 -0
- package/src/__tests__/secret-response-routing.test.ts +93 -0
- package/src/__tests__/secret-scanner-executor.test.ts +348 -0
- package/src/__tests__/secret-scanner.test.ts +900 -0
- package/src/__tests__/secure-keys.test.ts +323 -0
- package/src/__tests__/server-history-render.test.ts +431 -0
- package/src/__tests__/session-abort-tool-results.test.ts +240 -0
- package/src/__tests__/session-conflict-gate.test.ts +1136 -0
- package/src/__tests__/session-error.test.ts +369 -0
- package/src/__tests__/session-evictor.test.ts +188 -0
- package/src/__tests__/session-init.benchmark.test.ts +465 -0
- package/src/__tests__/session-load-history-repair.test.ts +222 -0
- package/src/__tests__/session-pre-run-repair.test.ts +213 -0
- package/src/__tests__/session-process-bridge.test.ts +242 -0
- package/src/__tests__/session-profile-injection.test.ts +444 -0
- package/src/__tests__/session-provider-retry-repair.test.ts +306 -0
- package/src/__tests__/session-queue.test.ts +1535 -0
- package/src/__tests__/session-runtime-assembly.test.ts +476 -0
- package/src/__tests__/session-runtime-workspace.test.ts +183 -0
- package/src/__tests__/session-skill-tools.test.ts +2431 -0
- package/src/__tests__/session-slash-known.test.ts +368 -0
- package/src/__tests__/session-slash-queue.test.ts +288 -0
- package/src/__tests__/session-slash-unknown.test.ts +271 -0
- package/src/__tests__/session-surfaces-task-progress.test.ts +104 -0
- package/src/__tests__/session-tool-setup-app-refresh.test.ts +473 -0
- package/src/__tests__/session-tool-setup-memory-scope.test.ts +140 -0
- package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +140 -0
- package/src/__tests__/session-undo.test.ts +75 -0
- package/src/__tests__/session-workspace-cache-state.test.ts +246 -0
- package/src/__tests__/session-workspace-injection.test.ts +327 -0
- package/src/__tests__/session-workspace-tool-tracking.test.ts +240 -0
- package/src/__tests__/shared-filesystem-errors.test.ts +78 -0
- package/src/__tests__/shell-credential-ref.test.ts +187 -0
- package/src/__tests__/shell-identity.test.ts +256 -0
- package/src/__tests__/shell-parser-fuzz.test.ts +544 -0
- package/src/__tests__/shell-parser-property.test.ts +433 -0
- package/src/__tests__/shell-tool-proxy-mode.test.ts +272 -0
- package/src/__tests__/signup-e2e.test.ts +353 -0
- package/src/__tests__/size-guard.test.ts +117 -0
- package/src/__tests__/skill-include-graph.test.ts +303 -0
- package/src/__tests__/skill-load-tool.test.ts +409 -0
- package/src/__tests__/skill-projection.benchmark.test.ts +338 -0
- package/src/__tests__/skill-script-runner-host.test.ts +489 -0
- package/src/__tests__/skill-script-runner-sandbox.test.ts +349 -0
- package/src/__tests__/skill-script-runner.test.ts +159 -0
- package/src/__tests__/skill-tool-factory.test.ts +252 -0
- package/src/__tests__/skill-tool-manifest.test.ts +658 -0
- package/src/__tests__/skill-version-hash.test.ts +182 -0
- package/src/__tests__/skills.test.ts +680 -0
- package/src/__tests__/slash-commands-catalog.test.ts +86 -0
- package/src/__tests__/slash-commands-parser.test.ts +119 -0
- package/src/__tests__/slash-commands-resolver.test.ts +193 -0
- package/src/__tests__/slash-commands-rewrite.test.ts +39 -0
- package/src/__tests__/speaker-identification.test.ts +52 -0
- package/src/__tests__/starter-bundle.test.ts +136 -0
- package/src/__tests__/starter-task-flow.test.ts +143 -0
- package/src/__tests__/subagent-manager-notify.test.ts +404 -0
- package/src/__tests__/subagent-tools.test.ts +801 -0
- package/src/__tests__/subagent-types.test.ts +78 -0
- package/src/__tests__/swarm-orchestrator.test.ts +428 -0
- package/src/__tests__/swarm-plan-validator.test.ts +330 -0
- package/src/__tests__/swarm-recursion.test.ts +165 -0
- package/src/__tests__/swarm-router-planner.test.ts +208 -0
- package/src/__tests__/swarm-session-integration.test.ts +274 -0
- package/src/__tests__/swarm-tool.test.ts +145 -0
- package/src/__tests__/swarm-worker-backend.test.ts +129 -0
- package/src/__tests__/swarm-worker-runner.test.ts +272 -0
- package/src/__tests__/system-prompt.test.ts +439 -0
- package/src/__tests__/task-compiler.test.ts +284 -0
- package/src/__tests__/task-management-tools.test.ts +936 -0
- package/src/__tests__/task-runner.test.ts +216 -0
- package/src/__tests__/task-scheduler.test.ts +217 -0
- package/src/__tests__/task-tools.test.ts +595 -0
- package/src/__tests__/terminal-sandbox-docker.test.ts +1064 -0
- package/src/__tests__/terminal-sandbox.integration.test.ts +178 -0
- package/src/__tests__/terminal-sandbox.test.ts +202 -0
- package/src/__tests__/terminal-tools.test.ts +840 -0
- package/src/__tests__/test-support/browser-skill-harness.ts +90 -0
- package/src/__tests__/test-support/computer-use-skill-harness.ts +45 -0
- package/src/__tests__/tool-audit-listener.test.ts +113 -0
- package/src/__tests__/tool-domain-event-publisher.test.ts +253 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +500 -0
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +516 -0
- package/src/__tests__/tool-executor-redaction.test.ts +289 -0
- package/src/__tests__/tool-executor-shell-integration.test.ts +301 -0
- package/src/__tests__/tool-executor.test.ts +1989 -0
- package/src/__tests__/tool-metrics-listener.test.ts +225 -0
- package/src/__tests__/tool-notification-listener.test.ts +49 -0
- package/src/__tests__/tool-permission-simulate-handler.test.ts +336 -0
- package/src/__tests__/tool-policy.test.ts +54 -0
- package/src/__tests__/tool-profiling-listener.test.ts +268 -0
- package/src/__tests__/tool-result-truncation.test.ts +217 -0
- package/src/__tests__/tool-trace-listener.test.ts +226 -0
- package/src/__tests__/top-level-renderer.test.ts +121 -0
- package/src/__tests__/top-level-scanner.test.ts +141 -0
- package/src/__tests__/trace-emitter.test.ts +173 -0
- package/src/__tests__/trust-store.test.ts +1605 -0
- package/src/__tests__/turn-commit.test.ts +554 -0
- package/src/__tests__/twilio-provider.test.ts +329 -0
- package/src/__tests__/twilio-routes-elevenlabs.test.ts +375 -0
- package/src/__tests__/twilio-routes-twiml.test.ts +127 -0
- package/src/__tests__/twilio-routes.test.ts +577 -0
- package/src/__tests__/twitter-auth-handler.test.ts +667 -0
- package/src/__tests__/twitter-cli-error-shaping.test.ts +208 -0
- package/src/__tests__/twitter-cli-routing.test.ts +252 -0
- package/src/__tests__/twitter-oauth-client.test.ts +209 -0
- package/src/__tests__/url-safety.test.ts +418 -0
- package/src/__tests__/view-image-tool.test.ts +217 -0
- package/src/__tests__/weather-skill-regression.test.ts +225 -0
- package/src/__tests__/web-fetch.test.ts +869 -0
- package/src/__tests__/web-search.test.ts +584 -0
- package/src/__tests__/workspace-git-service.test.ts +1153 -0
- package/src/__tests__/workspace-heartbeat-service.test.ts +486 -0
- package/src/__tests__/workspace-lifecycle.test.ts +292 -0
- package/src/__tests__/workspace-policy.test.ts +213 -0
- package/src/agent/attachments.ts +35 -0
- package/src/agent/loop.ts +500 -0
- package/src/agent/message-types.ts +17 -0
- package/src/agent-heartbeat/agent-heartbeat-service.ts +155 -0
- package/src/autonomy/autonomy-resolver.ts +60 -0
- package/src/autonomy/autonomy-store.ts +122 -0
- package/src/autonomy/disposition-mapper.ts +31 -0
- package/src/autonomy/index.ts +11 -0
- package/src/autonomy/types.ts +39 -0
- package/src/bundler/app-bundler.ts +295 -0
- package/src/bundler/bundle-scanner.ts +535 -0
- package/src/bundler/bundle-signer.ts +124 -0
- package/src/bundler/manifest.ts +21 -0
- package/src/bundler/signature-verifier.ts +184 -0
- package/src/calls/call-bridge.ts +168 -0
- package/src/calls/call-constants.ts +48 -0
- package/src/calls/call-domain.ts +430 -0
- package/src/calls/call-orchestrator.ts +498 -0
- package/src/calls/call-recovery.ts +207 -0
- package/src/calls/call-state-machine.ts +68 -0
- package/src/calls/call-state.ts +87 -0
- package/src/calls/call-store.ts +422 -0
- package/src/calls/elevenlabs-client.ts +97 -0
- package/src/calls/elevenlabs-config.ts +31 -0
- package/src/calls/relay-server.ts +390 -0
- package/src/calls/speaker-identification.ts +213 -0
- package/src/calls/twilio-config.ts +45 -0
- package/src/calls/twilio-provider.ts +263 -0
- package/src/calls/twilio-rest.ts +156 -0
- package/src/calls/twilio-routes.ts +311 -0
- package/src/calls/types.ts +39 -0
- package/src/calls/voice-provider.ts +14 -0
- package/src/calls/voice-quality.ts +114 -0
- package/src/cli/autonomy.ts +188 -0
- package/src/cli/config-commands.ts +334 -0
- package/src/cli/contacts.ts +149 -0
- package/src/cli/core-commands.ts +784 -0
- package/src/cli/doordash.ts +1055 -0
- package/src/cli/email-guardrails.ts +200 -0
- package/src/cli/email.ts +405 -0
- package/src/cli/ipc-client.ts +82 -0
- package/src/cli/main-screen.tsx +53 -0
- package/src/cli/map.ts +270 -0
- package/src/cli/twitter.ts +754 -0
- package/src/cli.ts +918 -0
- package/src/commands/__tests__/cc-command-registry.test.ts +319 -0
- package/src/commands/cc-command-registry.ts +209 -0
- package/src/config/bundled-skills/.gitkeep +0 -0
- package/src/config/bundled-skills/agentmail/SKILL.md +128 -0
- package/src/config/bundled-skills/agentmail/icon.svg +21 -0
- package/src/config/bundled-skills/app-builder/SKILL.md +1404 -0
- package/src/config/bundled-skills/app-builder/TOOLS.json +279 -0
- package/src/config/bundled-skills/app-builder/icon.svg +9 -0
- package/src/config/bundled-skills/app-builder/tools/app-create.ts +15 -0
- package/src/config/bundled-skills/app-builder/tools/app-delete.ts +10 -0
- package/src/config/bundled-skills/app-builder/tools/app-file-edit.ts +11 -0
- package/src/config/bundled-skills/app-builder/tools/app-file-list.ts +10 -0
- package/src/config/bundled-skills/app-builder/tools/app-file-read.ts +18 -0
- package/src/config/bundled-skills/app-builder/tools/app-file-write.ts +11 -0
- package/src/config/bundled-skills/app-builder/tools/app-list.ts +10 -0
- package/src/config/bundled-skills/app-builder/tools/app-query.ts +10 -0
- package/src/config/bundled-skills/app-builder/tools/app-update.ts +20 -0
- package/src/config/bundled-skills/browser/SKILL.md +28 -0
- package/src/config/bundled-skills/browser/TOOLS.json +234 -0
- package/src/config/bundled-skills/browser/tools/browser-click.ts +9 -0
- package/src/config/bundled-skills/browser/tools/browser-close.ts +9 -0
- package/src/config/bundled-skills/browser/tools/browser-extract.ts +9 -0
- package/src/config/bundled-skills/browser/tools/browser-fill-credential.ts +9 -0
- package/src/config/bundled-skills/browser/tools/browser-navigate.ts +9 -0
- package/src/config/bundled-skills/browser/tools/browser-press-key.ts +9 -0
- package/src/config/bundled-skills/browser/tools/browser-screenshot.ts +9 -0
- package/src/config/bundled-skills/browser/tools/browser-snapshot.ts +9 -0
- package/src/config/bundled-skills/browser/tools/browser-type.ts +9 -0
- package/src/config/bundled-skills/browser/tools/browser-wait-for.ts +9 -0
- package/src/config/bundled-skills/claude-code/SKILL.md +50 -0
- package/src/config/bundled-skills/claude-code/TOOLS.json +40 -0
- package/src/config/bundled-skills/claude-code/tools/claude-code.ts +9 -0
- package/src/config/bundled-skills/computer-use/SKILL.md +17 -0
- package/src/config/bundled-skills/computer-use/TOOLS.json +326 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-click.ts +9 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-done.ts +9 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-double-click.ts +9 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-drag.ts +9 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-key.ts +9 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-open-app.ts +9 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-request-control.ts +9 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-respond.ts +9 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-right-click.ts +9 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-run-applescript.ts +9 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-scroll.ts +9 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-type-text.ts +9 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-wait.ts +9 -0
- package/src/config/bundled-skills/contacts/SKILL.md +39 -0
- package/src/config/bundled-skills/contacts/TOOLS.json +122 -0
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +57 -0
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +60 -0
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +66 -0
- package/src/config/bundled-skills/document/SKILL.md +26 -0
- package/src/config/bundled-skills/document/TOOLS.json +53 -0
- package/src/config/bundled-skills/document/tools/document-create.ts +9 -0
- package/src/config/bundled-skills/document/tools/document-update.ts +9 -0
- package/src/config/bundled-skills/doordash/SKILL.md +163 -0
- package/src/config/bundled-skills/followups/SKILL.md +32 -0
- package/src/config/bundled-skills/followups/TOOLS.json +100 -0
- package/src/config/bundled-skills/followups/icon.svg +24 -0
- package/src/config/bundled-skills/followups/tools/followup-create.ts +9 -0
- package/src/config/bundled-skills/followups/tools/followup-list.ts +9 -0
- package/src/config/bundled-skills/followups/tools/followup-resolve.ts +9 -0
- package/src/config/bundled-skills/google-calendar/SKILL.md +51 -0
- package/src/config/bundled-skills/google-calendar/TOOLS.json +108 -0
- package/src/config/bundled-skills/google-calendar/calendar-client.ts +165 -0
- package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +21 -0
- package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +42 -0
- package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +13 -0
- package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +30 -0
- package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +41 -0
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +18 -0
- package/src/config/bundled-skills/google-calendar/types.ts +97 -0
- package/src/config/bundled-skills/image-studio/SKILL.md +32 -0
- package/src/config/bundled-skills/image-studio/TOOLS.json +42 -0
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +115 -0
- package/src/config/bundled-skills/macos-automation/SKILL.md +66 -0
- package/src/config/bundled-skills/messaging/SKILL.md +153 -0
- package/src/config/bundled-skills/messaging/TOOLS.json +357 -0
- package/src/config/bundled-skills/messaging/tools/gmail-archive.ts +23 -0
- package/src/config/bundled-skills/messaging/tools/gmail-batch-archive.ts +23 -0
- package/src/config/bundled-skills/messaging/tools/gmail-batch-label.ts +25 -0
- package/src/config/bundled-skills/messaging/tools/gmail-draft.ts +26 -0
- package/src/config/bundled-skills/messaging/tools/gmail-label.ts +25 -0
- package/src/config/bundled-skills/messaging/tools/gmail-trash.ts +23 -0
- package/src/config/bundled-skills/messaging/tools/gmail-unsubscribe.ts +84 -0
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-activity.ts +18 -0
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +125 -0
- package/src/config/bundled-skills/messaging/tools/messaging-auth-test.ts +16 -0
- package/src/config/bundled-skills/messaging/tools/messaging-draft.ts +49 -0
- package/src/config/bundled-skills/messaging/tools/messaging-list-conversations.ts +21 -0
- package/src/config/bundled-skills/messaging/tools/messaging-mark-read.ts +25 -0
- package/src/config/bundled-skills/messaging/tools/messaging-read.ts +28 -0
- package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +32 -0
- package/src/config/bundled-skills/messaging/tools/messaging-search.ts +22 -0
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +31 -0
- package/src/config/bundled-skills/messaging/tools/shared.ts +76 -0
- package/src/config/bundled-skills/messaging/tools/slack-add-reaction.ts +25 -0
- package/src/config/bundled-skills/messaging/tools/slack-leave-channel.ts +23 -0
- package/src/config/bundled-skills/phone-calls/SKILL.md +533 -0
- package/src/config/bundled-skills/playbooks/SKILL.md +31 -0
- package/src/config/bundled-skills/playbooks/TOOLS.json +126 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +98 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +54 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +76 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +113 -0
- package/src/config/bundled-skills/public-ingress/SKILL.md +200 -0
- package/src/config/bundled-skills/reminder/SKILL.md +20 -0
- package/src/config/bundled-skills/reminder/TOOLS.json +67 -0
- package/src/config/bundled-skills/reminder/tools/reminder-cancel.ts +9 -0
- package/src/config/bundled-skills/reminder/tools/reminder-create.ts +9 -0
- package/src/config/bundled-skills/reminder/tools/reminder-list.ts +9 -0
- package/src/config/bundled-skills/schedule/SKILL.md +74 -0
- package/src/config/bundled-skills/schedule/TOOLS.json +135 -0
- package/src/config/bundled-skills/schedule/tools/schedule-create.ts +9 -0
- package/src/config/bundled-skills/schedule/tools/schedule-delete.ts +9 -0
- package/src/config/bundled-skills/schedule/tools/schedule-list.ts +9 -0
- package/src/config/bundled-skills/schedule/tools/schedule-update.ts +9 -0
- package/src/config/bundled-skills/self-upgrade/SKILL.md +68 -0
- package/src/config/bundled-skills/start-the-day/SKILL.md +70 -0
- package/src/config/bundled-skills/start-the-day/icon.svg +13 -0
- package/src/config/bundled-skills/subagent/SKILL.md +25 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +107 -0
- package/src/config/bundled-skills/subagent/tools/subagent-abort.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-message.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-read.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-spawn.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-status.ts +9 -0
- package/src/config/bundled-skills/tasks/SKILL.md +28 -0
- package/src/config/bundled-skills/tasks/TOOLS.json +281 -0
- package/src/config/bundled-skills/tasks/tools/task-delete.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-add.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-show.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-update.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-queue-run.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-run.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-save.ts +9 -0
- package/src/config/bundled-skills/transcribe/SKILL.md +25 -0
- package/src/config/bundled-skills/transcribe/TOOLS.json +32 -0
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +370 -0
- package/src/config/bundled-skills/twitter/SKILL.md +220 -0
- package/src/config/bundled-skills/watcher/SKILL.md +27 -0
- package/src/config/bundled-skills/watcher/TOOLS.json +147 -0
- package/src/config/bundled-skills/watcher/tools/watcher-create.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-list.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-update.ts +9 -0
- package/src/config/bundled-skills/weather/SKILL.md +37 -0
- package/src/config/bundled-skills/weather/TOOLS.json +32 -0
- package/src/config/bundled-skills/weather/icon.svg +24 -0
- package/src/config/bundled-skills/weather/tools/get-weather.ts +9 -0
- package/src/config/computer-use-prompt.ts +97 -0
- package/src/config/defaults.ts +263 -0
- package/src/config/loader.ts +339 -0
- package/src/config/schema.ts +1436 -0
- package/src/config/skill-state.ts +95 -0
- package/src/config/skills.ts +972 -0
- package/src/config/system-prompt.ts +675 -0
- package/src/config/templates/BOOTSTRAP.md +70 -0
- package/src/config/templates/IDENTITY.md +25 -0
- package/src/config/templates/LOOKS.md +25 -0
- package/src/config/templates/SOUL.md +37 -0
- package/src/config/templates/USER.md +19 -0
- package/src/config/types.ts +42 -0
- package/src/config/vellum-skills/chatgpt-import/SKILL.md +24 -0
- package/src/config/vellum-skills/chatgpt-import/TOOLS.json +23 -0
- package/src/config/vellum-skills/chatgpt-import/tools/chatgpt-import.ts +284 -0
- package/src/config/vellum-skills/deploy-fullstack-vercel/SKILL.md +179 -0
- package/src/config/vellum-skills/document-writer/SKILL.md +195 -0
- package/src/config/vellum-skills/google-oauth-setup/SKILL.md +199 -0
- package/src/config/vellum-skills/slack-oauth-setup/SKILL.md +153 -0
- package/src/config/vellum-skills/telegram-setup/SKILL.md +143 -0
- package/src/config/vellum-skills/twilio-setup/SKILL.md +213 -0
- package/src/contacts/contact-store.ts +410 -0
- package/src/contacts/index.ts +11 -0
- package/src/contacts/types.ts +28 -0
- package/src/context/token-estimator.ts +108 -0
- package/src/context/tool-result-truncation.ts +128 -0
- package/src/context/window-manager.ts +531 -0
- package/src/daemon/assistant-attachments.ts +691 -0
- package/src/daemon/classifier.ts +110 -0
- package/src/daemon/computer-use-session.ts +903 -0
- package/src/daemon/connection-policy.ts +41 -0
- package/src/daemon/date-context.ts +136 -0
- package/src/daemon/handlers/apps.ts +530 -0
- package/src/daemon/handlers/browser.ts +54 -0
- package/src/daemon/handlers/computer-use.ts +187 -0
- package/src/daemon/handlers/config.ts +1517 -0
- package/src/daemon/handlers/diagnostics.ts +338 -0
- package/src/daemon/handlers/documents.ts +173 -0
- package/src/daemon/handlers/home-base.ts +78 -0
- package/src/daemon/handlers/identity.ts +127 -0
- package/src/daemon/handlers/index.ts +129 -0
- package/src/daemon/handlers/misc.ts +331 -0
- package/src/daemon/handlers/open-bundle-handler.ts +80 -0
- package/src/daemon/handlers/publish.ts +187 -0
- package/src/daemon/handlers/sessions.ts +555 -0
- package/src/daemon/handlers/shared.ts +570 -0
- package/src/daemon/handlers/signing.ts +37 -0
- package/src/daemon/handlers/skills.ts +486 -0
- package/src/daemon/handlers/subagents.ts +210 -0
- package/src/daemon/handlers/twitter-auth.ts +198 -0
- package/src/daemon/handlers/work-items.ts +632 -0
- package/src/daemon/handlers/workspace-files.ts +75 -0
- package/src/daemon/handlers.ts +17 -0
- package/src/daemon/history-repair.ts +214 -0
- package/src/daemon/ipc-blob-store.ts +231 -0
- package/src/daemon/ipc-contract-inventory.json +495 -0
- package/src/daemon/ipc-contract-inventory.ts +126 -0
- package/src/daemon/ipc-contract.ts +2551 -0
- package/src/daemon/ipc-protocol.ts +75 -0
- package/src/daemon/ipc-validate.ts +188 -0
- package/src/daemon/lifecycle.ts +582 -0
- package/src/daemon/main.ts +21 -0
- package/src/daemon/media-visibility-policy.ts +57 -0
- package/src/daemon/ride-shotgun-handler.ts +309 -0
- package/src/daemon/server.ts +1215 -0
- package/src/daemon/session-agent-loop.ts +922 -0
- package/src/daemon/session-attachments.ts +196 -0
- package/src/daemon/session-conflict-gate.ts +184 -0
- package/src/daemon/session-dynamic-profile.ts +63 -0
- package/src/daemon/session-error.ts +290 -0
- package/src/daemon/session-evictor.ts +196 -0
- package/src/daemon/session-history.ts +437 -0
- package/src/daemon/session-lifecycle.ts +147 -0
- package/src/daemon/session-media-retry.ts +147 -0
- package/src/daemon/session-memory.ts +212 -0
- package/src/daemon/session-messaging.ts +145 -0
- package/src/daemon/session-notifiers.ts +193 -0
- package/src/daemon/session-process.ts +323 -0
- package/src/daemon/session-queue-manager.ts +82 -0
- package/src/daemon/session-runtime-assembly.ts +447 -0
- package/src/daemon/session-skill-tools.ts +356 -0
- package/src/daemon/session-slash.ts +305 -0
- package/src/daemon/session-surfaces.ts +702 -0
- package/src/daemon/session-tool-setup.ts +523 -0
- package/src/daemon/session-usage.ts +72 -0
- package/src/daemon/session-workspace.ts +19 -0
- package/src/daemon/session.ts +400 -0
- package/src/daemon/tls-certs.ts +189 -0
- package/src/daemon/trace-emitter.ts +82 -0
- package/src/daemon/video-thumbnail.ts +62 -0
- package/src/daemon/watch-handler.ts +274 -0
- package/src/doordash/client.ts +999 -0
- package/src/doordash/queries.ts +1311 -0
- package/src/doordash/query-extractor.ts +93 -0
- package/src/doordash/session.ts +82 -0
- package/src/email/provider.ts +117 -0
- package/src/email/providers/agentmail.ts +317 -0
- package/src/email/providers/index.ts +58 -0
- package/src/email/service.ts +303 -0
- package/src/email/types.ts +126 -0
- package/src/events/bus.ts +157 -0
- package/src/events/domain-events.ts +83 -0
- package/src/events/index.ts +18 -0
- package/src/events/tool-audit-listener.ts +80 -0
- package/src/events/tool-domain-event-publisher.ts +111 -0
- package/src/events/tool-metrics-listener.ts +159 -0
- package/src/events/tool-notification-listener.ts +17 -0
- package/src/events/tool-profiling-listener.ts +158 -0
- package/src/events/tool-trace-listener.ts +75 -0
- package/src/export/formatter.ts +98 -0
- package/src/followups/followup-store.ts +168 -0
- package/src/followups/index.ts +10 -0
- package/src/followups/types.ts +29 -0
- package/src/gallery/default-gallery.ts +795 -0
- package/src/gallery/gallery-manifest.ts +24 -0
- package/src/home-base/app-link-store.ts +82 -0
- package/src/home-base/bootstrap.ts +68 -0
- package/src/home-base/prebuilt/index.html +662 -0
- package/src/home-base/prebuilt/seed-metadata.json +21 -0
- package/src/home-base/prebuilt/seed.ts +112 -0
- package/src/home-base/prebuilt-home-base-updater.ts +30 -0
- package/src/hooks/cli.ts +163 -0
- package/src/hooks/config.ts +88 -0
- package/src/hooks/discovery.ts +110 -0
- package/src/hooks/manager.ts +124 -0
- package/src/hooks/runner.ts +123 -0
- package/src/hooks/templates.ts +52 -0
- package/src/hooks/types.ts +72 -0
- package/src/inbound/public-ingress-urls.ts +123 -0
- package/src/index.ts +81 -0
- package/src/instrument.ts +60 -0
- package/src/logfire.ts +99 -0
- package/src/media/gemini-image-service.ts +136 -0
- package/src/memory/account-store.ts +108 -0
- package/src/memory/admin.ts +211 -0
- package/src/memory/app-git-service.ts +295 -0
- package/src/memory/app-store.ts +577 -0
- package/src/memory/attachments-store.ts +397 -0
- package/src/memory/channel-delivery-store.ts +353 -0
- package/src/memory/channel-guardian-store.ts +669 -0
- package/src/memory/checkpoints.ts +52 -0
- package/src/memory/clarification-resolver.ts +298 -0
- package/src/memory/conflict-intent.ts +157 -0
- package/src/memory/conflict-policy.ts +73 -0
- package/src/memory/conflict-store.ts +350 -0
- package/src/memory/contradiction-checker.ts +358 -0
- package/src/memory/conversation-key-store.ts +122 -0
- package/src/memory/conversation-store.ts +470 -0
- package/src/memory/db.ts +1991 -0
- package/src/memory/embedding-backend.ts +229 -0
- package/src/memory/embedding-gemini.ts +52 -0
- package/src/memory/embedding-local.ts +65 -0
- package/src/memory/embedding-ollama.ts +55 -0
- package/src/memory/embedding-openai.ts +25 -0
- package/src/memory/entity-extractor.ts +474 -0
- package/src/memory/external-conversation-store.ts +234 -0
- package/src/memory/fingerprint.ts +20 -0
- package/src/memory/indexer.ts +156 -0
- package/src/memory/items-extractor.ts +461 -0
- package/src/memory/job-handlers/backfill.ts +139 -0
- package/src/memory/job-handlers/cleanup.ts +58 -0
- package/src/memory/job-handlers/conflict.ts +141 -0
- package/src/memory/job-handlers/embedding.ts +61 -0
- package/src/memory/job-handlers/extraction.ts +123 -0
- package/src/memory/job-handlers/index-maintenance.ts +54 -0
- package/src/memory/job-handlers/summarization.ts +286 -0
- package/src/memory/job-utils.ts +170 -0
- package/src/memory/jobs-store.ts +401 -0
- package/src/memory/jobs-worker.ts +313 -0
- package/src/memory/llm-request-log-store.ts +45 -0
- package/src/memory/llm-usage-store.ts +60 -0
- package/src/memory/message-content.ts +54 -0
- package/src/memory/profile-compiler.ts +160 -0
- package/src/memory/published-pages-store.ts +137 -0
- package/src/memory/qdrant-client.ts +366 -0
- package/src/memory/qdrant-manager.ts +242 -0
- package/src/memory/query-builder.ts +45 -0
- package/src/memory/retrieval-budget.ts +30 -0
- package/src/memory/retriever.ts +653 -0
- package/src/memory/runs-store.ts +305 -0
- package/src/memory/schema.ts +677 -0
- package/src/memory/search/entity.ts +298 -0
- package/src/memory/search/formatting.ts +207 -0
- package/src/memory/search/lexical.ts +227 -0
- package/src/memory/search/ranking.ts +401 -0
- package/src/memory/search/semantic.ts +121 -0
- package/src/memory/search/types.ts +137 -0
- package/src/memory/segmenter.ts +68 -0
- package/src/memory/shared-app-links-store.ts +138 -0
- package/src/memory/tool-usage-store.ts +62 -0
- package/src/messaging/activity-analyzer.ts +76 -0
- package/src/messaging/draft-store.ts +88 -0
- package/src/messaging/index.ts +3 -0
- package/src/messaging/provider-types.ts +80 -0
- package/src/messaging/provider.ts +52 -0
- package/src/messaging/providers/gmail/adapter.ts +193 -0
- package/src/messaging/providers/gmail/client.ts +204 -0
- package/src/messaging/providers/gmail/types.ts +90 -0
- package/src/messaging/providers/slack/adapter.ts +202 -0
- package/src/messaging/providers/slack/client.ts +198 -0
- package/src/messaging/providers/slack/types.ts +119 -0
- package/src/messaging/providers/telegram-bot/adapter.ts +162 -0
- package/src/messaging/providers/telegram-bot/client.ts +104 -0
- package/src/messaging/providers/telegram-bot/types.ts +15 -0
- package/src/messaging/registry.ts +35 -0
- package/src/messaging/style-analyzer.ts +159 -0
- package/src/messaging/thread-summarizer.ts +306 -0
- package/src/messaging/triage-engine.ts +323 -0
- package/src/messaging/types.ts +55 -0
- package/src/permissions/checker.ts +640 -0
- package/src/permissions/defaults.ts +254 -0
- package/src/permissions/prompter.ts +98 -0
- package/src/permissions/secret-prompter.ts +114 -0
- package/src/permissions/shell-identity.ts +227 -0
- package/src/permissions/trust-store.ts +607 -0
- package/src/permissions/types.ts +43 -0
- package/src/permissions/workspace-policy.ts +114 -0
- package/src/playbooks/index.ts +2 -0
- package/src/playbooks/playbook-compiler.ts +90 -0
- package/src/playbooks/types.ts +55 -0
- package/src/providers/anthropic/client.ts +751 -0
- package/src/providers/failover.ts +129 -0
- package/src/providers/fireworks/client.ts +20 -0
- package/src/providers/gemini/client.ts +285 -0
- package/src/providers/ollama/client.ts +30 -0
- package/src/providers/openai/client.ts +337 -0
- package/src/providers/openrouter/client.ts +20 -0
- package/src/providers/ratelimit.ts +93 -0
- package/src/providers/registry.ts +146 -0
- package/src/providers/retry.ts +81 -0
- package/src/providers/stream-timeout.ts +38 -0
- package/src/providers/types.ts +109 -0
- package/src/runtime/assistant-event-hub.ts +157 -0
- package/src/runtime/assistant-event.ts +82 -0
- package/src/runtime/channel-approval-parser.ts +60 -0
- package/src/runtime/channel-approval-types.ts +73 -0
- package/src/runtime/channel-approvals.ts +206 -0
- package/src/runtime/channel-guardian-service.ts +212 -0
- package/src/runtime/gateway-client.ts +58 -0
- package/src/runtime/http-server.ts +1076 -0
- package/src/runtime/http-types.ts +66 -0
- package/src/runtime/routes/app-routes.ts +174 -0
- package/src/runtime/routes/attachment-routes.ts +133 -0
- package/src/runtime/routes/call-routes.ts +190 -0
- package/src/runtime/routes/channel-routes.ts +1404 -0
- package/src/runtime/routes/conversation-routes.ts +352 -0
- package/src/runtime/routes/events-routes.ts +148 -0
- package/src/runtime/routes/run-routes.ts +257 -0
- package/src/runtime/routes/secret-routes.ts +76 -0
- package/src/runtime/run-orchestrator.ts +330 -0
- package/src/schedule/recurrence-engine.ts +162 -0
- package/src/schedule/recurrence-types.ts +67 -0
- package/src/schedule/schedule-store.ts +506 -0
- package/src/schedule/scheduler.ts +171 -0
- package/src/security/encrypted-store.ts +238 -0
- package/src/security/keychain.ts +252 -0
- package/src/security/oauth-callback-registry.ts +66 -0
- package/src/security/oauth2.ts +274 -0
- package/src/security/redaction.ts +89 -0
- package/src/security/secret-allowlist.ts +164 -0
- package/src/security/secret-ingress.ts +57 -0
- package/src/security/secret-scanner.ts +550 -0
- package/src/security/secure-keys.ts +180 -0
- package/src/security/token-manager.ts +141 -0
- package/src/services/published-app-updater.ts +69 -0
- package/src/services/vercel-deploy.ts +73 -0
- package/src/skills/active-skill-tools.ts +81 -0
- package/src/skills/clawhub.ts +414 -0
- package/src/skills/include-graph.ts +146 -0
- package/src/skills/managed-store.ts +233 -0
- package/src/skills/path-classifier.ts +128 -0
- package/src/skills/slash-commands.ts +174 -0
- package/src/skills/tool-manifest.ts +165 -0
- package/src/skills/version-hash.ts +110 -0
- package/src/slack/slack-webhook.ts +61 -0
- package/src/subagent/index.ts +19 -0
- package/src/subagent/manager.ts +511 -0
- package/src/subagent/types.ts +69 -0
- package/src/swarm/backend-claude-code.ts +145 -0
- package/src/swarm/index.ts +44 -0
- package/src/swarm/limits.ts +37 -0
- package/src/swarm/orchestrator.ts +279 -0
- package/src/swarm/plan-validator.ts +151 -0
- package/src/swarm/router-planner.ts +100 -0
- package/src/swarm/router-prompts.ts +36 -0
- package/src/swarm/synthesizer.ts +62 -0
- package/src/swarm/types.ts +62 -0
- package/src/swarm/worker-backend.ts +121 -0
- package/src/swarm/worker-prompts.ts +79 -0
- package/src/swarm/worker-runner.ts +164 -0
- package/src/tasks/SPEC.md +139 -0
- package/src/tasks/candidate-store.ts +86 -0
- package/src/tasks/ephemeral-permissions.ts +48 -0
- package/src/tasks/task-compiler.ts +199 -0
- package/src/tasks/task-runner.ts +90 -0
- package/src/tasks/task-scheduler.ts +21 -0
- package/src/tasks/task-store.ts +127 -0
- package/src/tasks/tool-sanitizer.ts +36 -0
- package/src/tools/apps/definitions.ts +59 -0
- package/src/tools/apps/executors.ts +313 -0
- package/src/tools/apps/open-proxy.ts +43 -0
- package/src/tools/apps/registry.ts +16 -0
- package/src/tools/assets/materialize.ts +218 -0
- package/src/tools/assets/search.ts +361 -0
- package/src/tools/browser/__tests__/auth-cache.test.ts +219 -0
- package/src/tools/browser/__tests__/auth-detector.test.ts +362 -0
- package/src/tools/browser/__tests__/jit-auth.test.ts +189 -0
- package/src/tools/browser/api-map.ts +293 -0
- package/src/tools/browser/auth-cache.ts +149 -0
- package/src/tools/browser/auth-detector.ts +347 -0
- package/src/tools/browser/auto-navigate.ts +270 -0
- package/src/tools/browser/browser-execution.ts +980 -0
- package/src/tools/browser/browser-handoff.ts +79 -0
- package/src/tools/browser/browser-manager.ts +715 -0
- package/src/tools/browser/browser-screencast.ts +217 -0
- package/src/tools/browser/headless-browser.ts +450 -0
- package/src/tools/browser/jit-auth.ts +51 -0
- package/src/tools/browser/network-recorder.ts +349 -0
- package/src/tools/browser/network-recording-types.ts +49 -0
- package/src/tools/browser/recording-store.ts +49 -0
- package/src/tools/browser/runtime-check.ts +43 -0
- package/src/tools/browser/x-auto-navigate.ts +207 -0
- package/src/tools/calls/call-end.ts +67 -0
- package/src/tools/calls/call-start.ts +81 -0
- package/src/tools/calls/call-status.ts +81 -0
- package/src/tools/claude-code/claude-code.ts +428 -0
- package/src/tools/computer-use/definitions.ts +443 -0
- package/src/tools/computer-use/registry.ts +22 -0
- package/src/tools/computer-use/request-computer-control.ts +53 -0
- package/src/tools/computer-use/skill-proxy-bridge.ts +28 -0
- package/src/tools/credentials/account-registry.ts +127 -0
- package/src/tools/credentials/broker-types.ts +107 -0
- package/src/tools/credentials/broker.ts +372 -0
- package/src/tools/credentials/domain-policy.ts +51 -0
- package/src/tools/credentials/host-pattern-match.ts +60 -0
- package/src/tools/credentials/metadata-store.ts +335 -0
- package/src/tools/credentials/policy-types.ts +52 -0
- package/src/tools/credentials/policy-validate.ts +80 -0
- package/src/tools/credentials/resolve.ts +122 -0
- package/src/tools/credentials/selection.ts +159 -0
- package/src/tools/credentials/tool-policy.ts +25 -0
- package/src/tools/credentials/vault.ts +657 -0
- package/src/tools/document/document-tool.ts +92 -0
- package/src/tools/document/editor-template.ts +237 -0
- package/src/tools/execution-target.ts +21 -0
- package/src/tools/execution-timeout.ts +49 -0
- package/src/tools/executor.ts +815 -0
- package/src/tools/filesystem/edit.ts +127 -0
- package/src/tools/filesystem/fuzzy-match.ts +202 -0
- package/src/tools/filesystem/read.ts +71 -0
- package/src/tools/filesystem/view-image.ts +199 -0
- package/src/tools/filesystem/write.ts +79 -0
- package/src/tools/followups/followup_create.ts +76 -0
- package/src/tools/followups/followup_list.ts +60 -0
- package/src/tools/followups/followup_resolve.ts +56 -0
- package/src/tools/host-filesystem/edit.ts +125 -0
- package/src/tools/host-filesystem/read.ts +80 -0
- package/src/tools/host-filesystem/write.ts +76 -0
- package/src/tools/host-terminal/cli-discover.ts +180 -0
- package/src/tools/host-terminal/host-shell.ts +191 -0
- package/src/tools/memory/definitions.ts +69 -0
- package/src/tools/memory/handlers.ts +246 -0
- package/src/tools/memory/register.ts +66 -0
- package/src/tools/network/__tests__/web-search.test.ts +427 -0
- package/src/tools/network/domain-normalize.ts +85 -0
- package/src/tools/network/script-proxy/__tests__/logging.test.ts +248 -0
- package/src/tools/network/script-proxy/__tests__/policy.test.ts +234 -0
- package/src/tools/network/script-proxy/__tests__/router.test.ts +76 -0
- package/src/tools/network/script-proxy/certs.ts +237 -0
- package/src/tools/network/script-proxy/connect-tunnel.ts +82 -0
- package/src/tools/network/script-proxy/http-forwarder.ts +151 -0
- package/src/tools/network/script-proxy/index.ts +28 -0
- package/src/tools/network/script-proxy/logging.ts +196 -0
- package/src/tools/network/script-proxy/mitm-handler.ts +269 -0
- package/src/tools/network/script-proxy/policy.ts +152 -0
- package/src/tools/network/script-proxy/router.ts +60 -0
- package/src/tools/network/script-proxy/server.ts +136 -0
- package/src/tools/network/script-proxy/session-manager.ts +534 -0
- package/src/tools/network/script-proxy/types.ts +125 -0
- package/src/tools/network/url-safety.ts +227 -0
- package/src/tools/network/web-fetch.ts +713 -0
- package/src/tools/network/web-search.ts +296 -0
- package/src/tools/policy-context.ts +29 -0
- package/src/tools/registry.ts +295 -0
- package/src/tools/reminder/reminder-store.ts +148 -0
- package/src/tools/reminder/reminder.ts +80 -0
- package/src/tools/schedule/create.ts +81 -0
- package/src/tools/schedule/delete.ts +28 -0
- package/src/tools/schedule/list.ts +69 -0
- package/src/tools/schedule/update.ts +97 -0
- package/src/tools/shared/filesystem/edit-engine.ts +56 -0
- package/src/tools/shared/filesystem/errors.ts +85 -0
- package/src/tools/shared/filesystem/file-ops-service.ts +215 -0
- package/src/tools/shared/filesystem/format-diff.ts +35 -0
- package/src/tools/shared/filesystem/path-policy.ts +125 -0
- package/src/tools/shared/filesystem/size-guard.ts +41 -0
- package/src/tools/shared/filesystem/types.ts +80 -0
- package/src/tools/shared/shell-output.ts +52 -0
- package/src/tools/skills/delete-managed.ts +60 -0
- package/src/tools/skills/load.ts +139 -0
- package/src/tools/skills/sandbox-runner.ts +279 -0
- package/src/tools/skills/scaffold-managed.ts +150 -0
- package/src/tools/skills/script-contract.ts +6 -0
- package/src/tools/skills/skill-script-runner.ts +86 -0
- package/src/tools/skills/skill-tool-factory.ts +64 -0
- package/src/tools/skills/vellum-catalog.ts +217 -0
- package/src/tools/subagent/abort.ts +33 -0
- package/src/tools/subagent/message.ts +39 -0
- package/src/tools/subagent/read.ts +67 -0
- package/src/tools/subagent/spawn.ts +46 -0
- package/src/tools/subagent/status.ts +45 -0
- package/src/tools/swarm/delegate.ts +183 -0
- package/src/tools/system/request-permission.ts +98 -0
- package/src/tools/system/version.ts +43 -0
- package/src/tools/tasks/index.ts +27 -0
- package/src/tools/tasks/task-delete.ts +82 -0
- package/src/tools/tasks/task-list.ts +44 -0
- package/src/tools/tasks/task-run.ts +97 -0
- package/src/tools/tasks/task-save.ts +47 -0
- package/src/tools/tasks/work-item-enqueue.ts +234 -0
- package/src/tools/tasks/work-item-list.ts +55 -0
- package/src/tools/tasks/work-item-remove.ts +60 -0
- package/src/tools/tasks/work-item-run.ts +78 -0
- package/src/tools/tasks/work-item-update.ts +114 -0
- package/src/tools/terminal/backends/docker.ts +372 -0
- package/src/tools/terminal/backends/native.ts +190 -0
- package/src/tools/terminal/backends/types.ts +26 -0
- package/src/tools/terminal/evaluate-typescript.ts +275 -0
- package/src/tools/terminal/parser.ts +413 -0
- package/src/tools/terminal/safe-env.ts +37 -0
- package/src/tools/terminal/sandbox-diagnostics.ts +149 -0
- package/src/tools/terminal/sandbox.ts +44 -0
- package/src/tools/terminal/shell.ts +257 -0
- package/src/tools/tool-manifest.ts +198 -0
- package/src/tools/types.ts +176 -0
- package/src/tools/ui-surface/definitions.ts +244 -0
- package/src/tools/ui-surface/registry.ts +14 -0
- package/src/tools/watch/screen-watch.ts +130 -0
- package/src/tools/watch/watch-state.ts +119 -0
- package/src/tools/watcher/create.ts +64 -0
- package/src/tools/watcher/delete.ts +27 -0
- package/src/tools/watcher/digest.ts +50 -0
- package/src/tools/watcher/list.ts +60 -0
- package/src/tools/watcher/update.ts +56 -0
- package/src/tools/weather/service.ts +551 -0
- package/src/twitter/client.ts +690 -0
- package/src/twitter/oauth-client.ts +102 -0
- package/src/twitter/router.ts +101 -0
- package/src/twitter/session.ts +91 -0
- package/src/usage/actors.ts +24 -0
- package/src/usage/types.ts +37 -0
- package/src/util/clipboard.ts +33 -0
- package/src/util/content-id.ts +16 -0
- package/src/util/debounce.ts +88 -0
- package/src/util/diff.ts +181 -0
- package/src/util/errors.ts +129 -0
- package/src/util/logger.ts +243 -0
- package/src/util/network-info.ts +47 -0
- package/src/util/platform.ts +632 -0
- package/src/util/pricing.ts +150 -0
- package/src/util/promise-guard.ts +37 -0
- package/src/util/retry.ts +98 -0
- package/src/util/spinner.ts +51 -0
- package/src/util/time.ts +16 -0
- package/src/util/truncate.ts +6 -0
- package/src/util/xml.ts +4 -0
- package/src/version.ts +3 -0
- package/src/watcher/constants.ts +11 -0
- package/src/watcher/engine.ts +199 -0
- package/src/watcher/provider-registry.ts +15 -0
- package/src/watcher/provider-types.ts +48 -0
- package/src/watcher/providers/gmail.ts +198 -0
- package/src/watcher/providers/google-calendar.ts +228 -0
- package/src/watcher/providers/slack.ts +129 -0
- package/src/watcher/watcher-store.ts +419 -0
- package/src/work-items/work-item-runner.ts +171 -0
- package/src/work-items/work-item-store.ts +325 -0
- package/src/workspace/commit-message-enrichment-service.ts +284 -0
- package/src/workspace/commit-message-provider.ts +95 -0
- package/src/workspace/git-service.ts +857 -0
- package/src/workspace/heartbeat-service.ts +345 -0
- package/src/workspace/provider-commit-message-generator.ts +285 -0
- package/src/workspace/top-level-renderer.ts +19 -0
- package/src/workspace/top-level-scanner.ts +41 -0
- package/src/workspace/turn-commit.ts +175 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,1404 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route handlers for channel inbound messages, delivery acks, and
|
|
3
|
+
* conversation deletion.
|
|
4
|
+
*/
|
|
5
|
+
import { timingSafeEqual } from 'node:crypto';
|
|
6
|
+
import { deleteConversationKey } from '../../memory/conversation-key-store.js';
|
|
7
|
+
import * as conversationStore from '../../memory/conversation-store.js';
|
|
8
|
+
import * as attachmentsStore from '../../memory/attachments-store.js';
|
|
9
|
+
import * as channelDeliveryStore from '../../memory/channel-delivery-store.js';
|
|
10
|
+
import * as externalConversationStore from '../../memory/external-conversation-store.js';
|
|
11
|
+
import { getPendingConfirmationsByConversation } from '../../memory/runs-store.js';
|
|
12
|
+
import { renderHistoryContent } from '../../daemon/handlers.js';
|
|
13
|
+
import { checkIngressForSecrets } from '../../security/secret-ingress.js';
|
|
14
|
+
import { IngressBlockedError } from '../../util/errors.js';
|
|
15
|
+
import { getLogger } from '../../util/logger.js';
|
|
16
|
+
import {
|
|
17
|
+
getGuardianBinding,
|
|
18
|
+
isGuardian,
|
|
19
|
+
validateAndConsumeChallenge,
|
|
20
|
+
} from '../channel-guardian-service.js';
|
|
21
|
+
import {
|
|
22
|
+
createApprovalRequest,
|
|
23
|
+
getPendingApprovalByGuardianChat,
|
|
24
|
+
getPendingApprovalByRunAndGuardianChat,
|
|
25
|
+
getAllPendingApprovalsByGuardianChat,
|
|
26
|
+
getPendingApprovalForRun,
|
|
27
|
+
getUnresolvedApprovalForRun,
|
|
28
|
+
getExpiredPendingApprovals,
|
|
29
|
+
updateApprovalDecision,
|
|
30
|
+
} from '../../memory/channel-guardian-store.js';
|
|
31
|
+
import { deliverChannelReply, deliverApprovalPrompt } from '../gateway-client.js';
|
|
32
|
+
import { parseApprovalDecision } from '../channel-approval-parser.js';
|
|
33
|
+
import {
|
|
34
|
+
getChannelApprovalPrompt,
|
|
35
|
+
buildApprovalUIMetadata,
|
|
36
|
+
buildGuardianApprovalPrompt,
|
|
37
|
+
handleChannelDecision,
|
|
38
|
+
buildReminderPrompt,
|
|
39
|
+
channelSupportsRichApprovalUI,
|
|
40
|
+
} from '../channel-approvals.js';
|
|
41
|
+
import type { ApprovalAction, ApprovalDecisionResult } from '../channel-approval-types.js';
|
|
42
|
+
import type { RunOrchestrator } from '../run-orchestrator.js';
|
|
43
|
+
import type {
|
|
44
|
+
MessageProcessor,
|
|
45
|
+
RuntimeAttachmentMetadata,
|
|
46
|
+
} from '../http-types.js';
|
|
47
|
+
|
|
48
|
+
const log = getLogger('runtime-http');
|
|
49
|
+
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
// Gateway-origin proof
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Header name used by the gateway to prove a request originated from it.
|
|
56
|
+
* The gateway sets this to the shared bearer token; the runtime validates
|
|
57
|
+
* it using constant-time comparison. Requests to `/channels/inbound`
|
|
58
|
+
* that lack a valid gateway-origin proof are rejected with 403.
|
|
59
|
+
*/
|
|
60
|
+
export const GATEWAY_ORIGIN_HEADER = 'X-Gateway-Origin';
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Validate that the request carries a valid gateway-origin proof.
|
|
64
|
+
* Returns true when the header value matches the expected bearer token
|
|
65
|
+
* using constant-time comparison to prevent timing attacks.
|
|
66
|
+
*
|
|
67
|
+
* When no bearer token is configured (e.g., local dev without auth),
|
|
68
|
+
* gateway-origin validation is skipped — the server is already
|
|
69
|
+
* unauthenticated, so there is no shared secret to verify against.
|
|
70
|
+
*/
|
|
71
|
+
export function verifyGatewayOrigin(req: Request, bearerToken?: string): boolean {
|
|
72
|
+
if (!bearerToken) return true; // No shared secret configured — skip validation
|
|
73
|
+
const provided = req.headers.get(GATEWAY_ORIGIN_HEADER);
|
|
74
|
+
if (!provided) return false;
|
|
75
|
+
const a = Buffer.from(provided);
|
|
76
|
+
const b = Buffer.from(bearerToken);
|
|
77
|
+
if (a.length !== b.length) return false;
|
|
78
|
+
return timingSafeEqual(a, b);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
// Actor role
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
|
|
85
|
+
export type ActorRole = 'guardian' | 'non-guardian' | 'unverified_channel';
|
|
86
|
+
|
|
87
|
+
export interface GuardianContext {
|
|
88
|
+
actorRole: ActorRole;
|
|
89
|
+
/** The guardian's delivery chat ID (from the guardian binding). */
|
|
90
|
+
guardianChatId?: string;
|
|
91
|
+
/** The guardian's external user ID. */
|
|
92
|
+
guardianExternalUserId?: string;
|
|
93
|
+
/** Display identifier for the requester (username or external user ID). */
|
|
94
|
+
requesterIdentifier?: string;
|
|
95
|
+
/** The requester's external user ID. */
|
|
96
|
+
requesterExternalUserId?: string;
|
|
97
|
+
/** The requester's chat ID. */
|
|
98
|
+
requesterChatId?: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Guardian approval request expiry (30 minutes). */
|
|
102
|
+
const GUARDIAN_APPROVAL_TTL_MS = 30 * 60 * 1000;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Return the effective prompt text for an approval prompt, appending the
|
|
106
|
+
* plainTextFallback instructions when the channel does not support rich
|
|
107
|
+
* inline approval UI (e.g. Telegram inline keyboards).
|
|
108
|
+
*/
|
|
109
|
+
function effectivePromptText(
|
|
110
|
+
promptText: string,
|
|
111
|
+
plainTextFallback: string,
|
|
112
|
+
channel: string,
|
|
113
|
+
): string {
|
|
114
|
+
if (channelSupportsRichApprovalUI(channel)) return promptText;
|
|
115
|
+
return plainTextFallback;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
// Feature flag
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
|
|
122
|
+
export function isChannelApprovalsEnabled(): boolean {
|
|
123
|
+
return process.env.CHANNEL_APPROVALS_ENABLED === 'true';
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
// Callback data parser — format: "apr:<runId>:<action>"
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
|
|
130
|
+
const VALID_ACTIONS: ReadonlySet<string> = new Set<string>([
|
|
131
|
+
'approve_once',
|
|
132
|
+
'approve_always',
|
|
133
|
+
'reject',
|
|
134
|
+
]);
|
|
135
|
+
|
|
136
|
+
function parseCallbackData(data: string): ApprovalDecisionResult | null {
|
|
137
|
+
const parts = data.split(':');
|
|
138
|
+
if (parts.length < 3 || parts[0] !== 'apr') return null;
|
|
139
|
+
const runId = parts[1];
|
|
140
|
+
const action = parts.slice(2).join(':');
|
|
141
|
+
if (!runId || !VALID_ACTIONS.has(action)) return null;
|
|
142
|
+
return { action: action as ApprovalAction, source: 'telegram_button', runId };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export async function handleDeleteConversation(req: Request): Promise<Response> {
|
|
146
|
+
const body = await req.json() as {
|
|
147
|
+
sourceChannel?: string;
|
|
148
|
+
externalChatId?: string;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const { sourceChannel, externalChatId } = body;
|
|
152
|
+
|
|
153
|
+
if (!sourceChannel || typeof sourceChannel !== 'string') {
|
|
154
|
+
return Response.json({ error: 'sourceChannel is required' }, { status: 400 });
|
|
155
|
+
}
|
|
156
|
+
if (!externalChatId || typeof externalChatId !== 'string') {
|
|
157
|
+
return Response.json({ error: 'externalChatId is required' }, { status: 400 });
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const conversationKey = `${sourceChannel}:${externalChatId}`;
|
|
161
|
+
deleteConversationKey(conversationKey);
|
|
162
|
+
externalConversationStore.deleteBindingByChannelChat(sourceChannel, externalChatId);
|
|
163
|
+
|
|
164
|
+
return Response.json({ ok: true });
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export async function handleChannelInbound(
|
|
168
|
+
req: Request,
|
|
169
|
+
processMessage?: MessageProcessor,
|
|
170
|
+
bearerToken?: string,
|
|
171
|
+
runOrchestrator?: RunOrchestrator,
|
|
172
|
+
): Promise<Response> {
|
|
173
|
+
// Reject requests that lack valid gateway-origin proof. This ensures
|
|
174
|
+
// channel inbound messages can only arrive via the gateway (which
|
|
175
|
+
// performs webhook-level verification) and not via direct HTTP calls.
|
|
176
|
+
if (!verifyGatewayOrigin(req, bearerToken)) {
|
|
177
|
+
log.warn('Rejected channel inbound request: missing or invalid gateway-origin proof');
|
|
178
|
+
return Response.json(
|
|
179
|
+
{ error: 'Forbidden: missing gateway-origin proof', code: 'GATEWAY_ORIGIN_REQUIRED' },
|
|
180
|
+
{ status: 403 },
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const body = await req.json() as {
|
|
185
|
+
sourceChannel?: string;
|
|
186
|
+
externalChatId?: string;
|
|
187
|
+
externalMessageId?: string;
|
|
188
|
+
content?: string;
|
|
189
|
+
isEdit?: boolean;
|
|
190
|
+
senderName?: string;
|
|
191
|
+
attachmentIds?: string[];
|
|
192
|
+
senderExternalUserId?: string;
|
|
193
|
+
senderUsername?: string;
|
|
194
|
+
sourceMetadata?: Record<string, unknown>;
|
|
195
|
+
replyCallbackUrl?: string;
|
|
196
|
+
callbackQueryId?: string;
|
|
197
|
+
callbackData?: string;
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const {
|
|
201
|
+
sourceChannel,
|
|
202
|
+
externalChatId,
|
|
203
|
+
externalMessageId,
|
|
204
|
+
content,
|
|
205
|
+
isEdit,
|
|
206
|
+
attachmentIds,
|
|
207
|
+
sourceMetadata,
|
|
208
|
+
} = body;
|
|
209
|
+
|
|
210
|
+
if (!sourceChannel || typeof sourceChannel !== 'string') {
|
|
211
|
+
return Response.json({ error: 'sourceChannel is required' }, { status: 400 });
|
|
212
|
+
}
|
|
213
|
+
if (!externalChatId || typeof externalChatId !== 'string') {
|
|
214
|
+
return Response.json({ error: 'externalChatId is required' }, { status: 400 });
|
|
215
|
+
}
|
|
216
|
+
if (!externalMessageId || typeof externalMessageId !== 'string') {
|
|
217
|
+
return Response.json({ error: 'externalMessageId is required' }, { status: 400 });
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Reject non-string content regardless of whether attachments are present.
|
|
221
|
+
if (content !== undefined && content !== null && typeof content !== 'string') {
|
|
222
|
+
return Response.json({ error: 'content must be a string' }, { status: 400 });
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const trimmedContent = typeof content === 'string' ? content.trim() : '';
|
|
226
|
+
const hasAttachments = Array.isArray(attachmentIds) && attachmentIds.length > 0;
|
|
227
|
+
|
|
228
|
+
const hasCallbackData = typeof body.callbackData === 'string' && body.callbackData.length > 0;
|
|
229
|
+
|
|
230
|
+
if (trimmedContent.length === 0 && !hasAttachments && !isEdit && !hasCallbackData) {
|
|
231
|
+
return Response.json({ error: 'content or attachmentIds is required' }, { status: 400 });
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (hasAttachments) {
|
|
235
|
+
const resolved = attachmentsStore.getAttachmentsByIds(attachmentIds);
|
|
236
|
+
if (resolved.length !== attachmentIds.length) {
|
|
237
|
+
const resolvedIds = new Set(resolved.map((a) => a.id));
|
|
238
|
+
const missing = attachmentIds.filter((id) => !resolvedIds.has(id));
|
|
239
|
+
return Response.json(
|
|
240
|
+
{ error: `Attachment IDs not found: ${missing.join(', ')}` },
|
|
241
|
+
{ status: 400 },
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const sourceMessageId = typeof sourceMetadata?.messageId === 'string'
|
|
247
|
+
? sourceMetadata.messageId
|
|
248
|
+
: undefined;
|
|
249
|
+
|
|
250
|
+
if (isEdit && !sourceMessageId) {
|
|
251
|
+
return Response.json({ error: 'sourceMetadata.messageId is required for edits' }, { status: 400 });
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ── Edit path: update existing message content, no new agent loop ──
|
|
255
|
+
if (isEdit && sourceMessageId) {
|
|
256
|
+
// Dedup the edit event itself (retried edited_message webhooks)
|
|
257
|
+
const editResult = channelDeliveryStore.recordInbound(
|
|
258
|
+
sourceChannel,
|
|
259
|
+
externalChatId,
|
|
260
|
+
externalMessageId,
|
|
261
|
+
{ sourceMessageId },
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
if (editResult.duplicate) {
|
|
265
|
+
return Response.json({
|
|
266
|
+
accepted: true,
|
|
267
|
+
duplicate: true,
|
|
268
|
+
eventId: editResult.eventId,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Retry lookup a few times — the original message may still be processing
|
|
273
|
+
// (linkMessage hasn't been called yet). Short backoff avoids losing edits
|
|
274
|
+
// that arrive while the original agent loop is in progress.
|
|
275
|
+
const EDIT_LOOKUP_RETRIES = 5;
|
|
276
|
+
const EDIT_LOOKUP_DELAY_MS = 2000;
|
|
277
|
+
|
|
278
|
+
let original: { messageId: string; conversationId: string } | null = null;
|
|
279
|
+
for (let attempt = 0; attempt <= EDIT_LOOKUP_RETRIES; attempt++) {
|
|
280
|
+
original = channelDeliveryStore.findMessageBySourceId(
|
|
281
|
+
sourceChannel,
|
|
282
|
+
externalChatId,
|
|
283
|
+
sourceMessageId,
|
|
284
|
+
);
|
|
285
|
+
if (original) break;
|
|
286
|
+
if (attempt < EDIT_LOOKUP_RETRIES) {
|
|
287
|
+
log.info(
|
|
288
|
+
{ assistantId: "self", sourceMessageId, attempt: attempt + 1, maxAttempts: EDIT_LOOKUP_RETRIES },
|
|
289
|
+
'Original message not linked yet, retrying edit lookup',
|
|
290
|
+
);
|
|
291
|
+
await new Promise((resolve) => setTimeout(resolve, EDIT_LOOKUP_DELAY_MS));
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (original) {
|
|
296
|
+
conversationStore.updateMessageContent(original.messageId, content ?? '');
|
|
297
|
+
log.info(
|
|
298
|
+
{ assistantId: "self", sourceMessageId, messageId: original.messageId },
|
|
299
|
+
'Updated message content from edited_message',
|
|
300
|
+
);
|
|
301
|
+
} else {
|
|
302
|
+
log.warn(
|
|
303
|
+
{ assistantId: "self", sourceChannel, externalChatId, sourceMessageId },
|
|
304
|
+
'Could not find original message for edit after retries, ignoring',
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return Response.json({
|
|
309
|
+
accepted: true,
|
|
310
|
+
duplicate: false,
|
|
311
|
+
eventId: editResult.eventId,
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// ── New message path ──
|
|
316
|
+
const result = channelDeliveryStore.recordInbound(
|
|
317
|
+
sourceChannel,
|
|
318
|
+
externalChatId,
|
|
319
|
+
externalMessageId,
|
|
320
|
+
{ sourceMessageId },
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
// Upsert external conversation binding with sender metadata
|
|
324
|
+
externalConversationStore.upsertBinding({
|
|
325
|
+
conversationId: result.conversationId,
|
|
326
|
+
sourceChannel,
|
|
327
|
+
externalChatId,
|
|
328
|
+
externalUserId: body.senderExternalUserId ?? null,
|
|
329
|
+
displayName: body.senderName ?? null,
|
|
330
|
+
username: body.senderUsername ?? null,
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
const metadataHintsRaw = sourceMetadata?.hints;
|
|
334
|
+
const metadataHints = Array.isArray(metadataHintsRaw)
|
|
335
|
+
? metadataHintsRaw.filter((hint): hint is string => typeof hint === 'string' && hint.trim().length > 0)
|
|
336
|
+
: [];
|
|
337
|
+
const metadataUxBrief = typeof sourceMetadata?.uxBrief === 'string' && sourceMetadata.uxBrief.trim().length > 0
|
|
338
|
+
? sourceMetadata.uxBrief.trim()
|
|
339
|
+
: undefined;
|
|
340
|
+
|
|
341
|
+
const replyCallbackUrl = body.replyCallbackUrl;
|
|
342
|
+
|
|
343
|
+
// ── Guardian verification command intercept ──
|
|
344
|
+
// Handled before normal message processing so it never enters the agent loop.
|
|
345
|
+
if (
|
|
346
|
+
!result.duplicate &&
|
|
347
|
+
trimmedContent.startsWith('/guardian_verify ') &&
|
|
348
|
+
replyCallbackUrl &&
|
|
349
|
+
body.senderExternalUserId
|
|
350
|
+
) {
|
|
351
|
+
const token = trimmedContent.slice('/guardian_verify '.length).trim();
|
|
352
|
+
if (token.length > 0) {
|
|
353
|
+
const verifyResult = validateAndConsumeChallenge(
|
|
354
|
+
'self',
|
|
355
|
+
sourceChannel,
|
|
356
|
+
token,
|
|
357
|
+
body.senderExternalUserId,
|
|
358
|
+
externalChatId,
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
const replyText = verifyResult.success
|
|
362
|
+
? 'Guardian verified successfully. Your identity is now linked to this bot.'
|
|
363
|
+
: 'Verification failed. Please try again later.';
|
|
364
|
+
|
|
365
|
+
try {
|
|
366
|
+
await deliverChannelReply(replyCallbackUrl, {
|
|
367
|
+
chatId: externalChatId,
|
|
368
|
+
text: replyText,
|
|
369
|
+
}, bearerToken);
|
|
370
|
+
} catch (err) {
|
|
371
|
+
log.error({ err, externalChatId }, 'Failed to deliver guardian verification reply');
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return Response.json({
|
|
375
|
+
accepted: true,
|
|
376
|
+
duplicate: false,
|
|
377
|
+
eventId: result.eventId,
|
|
378
|
+
guardianVerification: verifyResult.success ? 'verified' : 'failed',
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// ── Actor role resolution ──
|
|
384
|
+
// Determine whether the sender is the guardian for this channel.
|
|
385
|
+
// When a guardian binding exists, non-guardian actors get stricter
|
|
386
|
+
// side-effect controls and their approvals route to the guardian's chat.
|
|
387
|
+
let guardianCtx: GuardianContext = { actorRole: 'guardian' };
|
|
388
|
+
if (isChannelApprovalsEnabled() && body.senderExternalUserId) {
|
|
389
|
+
const senderIsGuardian = isGuardian('self', sourceChannel, body.senderExternalUserId);
|
|
390
|
+
if (!senderIsGuardian) {
|
|
391
|
+
const binding = getGuardianBinding('self', sourceChannel);
|
|
392
|
+
if (binding) {
|
|
393
|
+
const requesterLabel = body.senderUsername
|
|
394
|
+
? `@${body.senderUsername}`
|
|
395
|
+
: body.senderExternalUserId;
|
|
396
|
+
guardianCtx = {
|
|
397
|
+
actorRole: 'non-guardian',
|
|
398
|
+
guardianChatId: binding.guardianDeliveryChatId,
|
|
399
|
+
guardianExternalUserId: binding.guardianExternalUserId,
|
|
400
|
+
requesterIdentifier: requesterLabel,
|
|
401
|
+
requesterExternalUserId: body.senderExternalUserId,
|
|
402
|
+
requesterChatId: externalChatId,
|
|
403
|
+
};
|
|
404
|
+
} else {
|
|
405
|
+
// No guardian binding configured for this channel — the sender is
|
|
406
|
+
// unverified. Sensitive actions will be auto-denied (fail-closed).
|
|
407
|
+
guardianCtx = {
|
|
408
|
+
actorRole: 'unverified_channel',
|
|
409
|
+
requesterExternalUserId: body.senderExternalUserId,
|
|
410
|
+
requesterChatId: externalChatId,
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// ── Approval interception (gated behind feature flag) ──
|
|
417
|
+
if (
|
|
418
|
+
isChannelApprovalsEnabled() &&
|
|
419
|
+
runOrchestrator &&
|
|
420
|
+
replyCallbackUrl &&
|
|
421
|
+
!result.duplicate
|
|
422
|
+
) {
|
|
423
|
+
const approvalResult = await handleApprovalInterception({
|
|
424
|
+
conversationId: result.conversationId,
|
|
425
|
+
callbackData: body.callbackData,
|
|
426
|
+
content: trimmedContent,
|
|
427
|
+
externalChatId,
|
|
428
|
+
sourceChannel,
|
|
429
|
+
senderExternalUserId: body.senderExternalUserId,
|
|
430
|
+
replyCallbackUrl,
|
|
431
|
+
bearerToken,
|
|
432
|
+
orchestrator: runOrchestrator,
|
|
433
|
+
guardianCtx,
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
if (approvalResult.handled) {
|
|
437
|
+
return Response.json({
|
|
438
|
+
accepted: true,
|
|
439
|
+
duplicate: false,
|
|
440
|
+
eventId: result.eventId,
|
|
441
|
+
approval: approvalResult.type,
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// When a callback payload was not handled by approval interception, it's
|
|
446
|
+
// a stale button press with no pending approval. Return early regardless
|
|
447
|
+
// of whether content/attachments are present — callback payloads always
|
|
448
|
+
// have non-empty content (normalize.ts sets message.content to cbq.data),
|
|
449
|
+
// so checking for empty content alone would miss stale callbacks.
|
|
450
|
+
if (hasCallbackData) {
|
|
451
|
+
return Response.json({
|
|
452
|
+
accepted: true,
|
|
453
|
+
duplicate: false,
|
|
454
|
+
eventId: result.eventId,
|
|
455
|
+
approval: 'stale_ignored',
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// For new (non-duplicate) messages, run the secret ingress check
|
|
461
|
+
// synchronously, then fire off the agent loop in the background.
|
|
462
|
+
if (!result.duplicate && processMessage) {
|
|
463
|
+
// Persist the raw payload first so dead-lettered events can always be
|
|
464
|
+
// replayed. If the ingress check later detects secrets we clear it
|
|
465
|
+
// before throwing, so secret-bearing content is never left on disk.
|
|
466
|
+
channelDeliveryStore.storePayload(result.eventId, {
|
|
467
|
+
sourceChannel, externalChatId, externalMessageId, content,
|
|
468
|
+
attachmentIds, sourceMetadata: body.sourceMetadata,
|
|
469
|
+
senderName: body.senderName,
|
|
470
|
+
senderExternalUserId: body.senderExternalUserId,
|
|
471
|
+
senderUsername: body.senderUsername,
|
|
472
|
+
replyCallbackUrl,
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
const contentToCheck = content ?? '';
|
|
476
|
+
let ingressCheck: ReturnType<typeof checkIngressForSecrets>;
|
|
477
|
+
try {
|
|
478
|
+
ingressCheck = checkIngressForSecrets(contentToCheck);
|
|
479
|
+
} catch (checkErr) {
|
|
480
|
+
channelDeliveryStore.clearPayload(result.eventId);
|
|
481
|
+
throw checkErr;
|
|
482
|
+
}
|
|
483
|
+
if (ingressCheck.blocked) {
|
|
484
|
+
channelDeliveryStore.clearPayload(result.eventId);
|
|
485
|
+
throw new IngressBlockedError(ingressCheck.userNotice!, ingressCheck.detectedTypes);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// When approval flow is enabled and we have an orchestrator, use the
|
|
489
|
+
// orchestrator-backed path which properly intercepts confirmation_request
|
|
490
|
+
// events and sends proactive approval prompts to the channel.
|
|
491
|
+
const useApprovalPath =
|
|
492
|
+
isChannelApprovalsEnabled() && runOrchestrator && replyCallbackUrl;
|
|
493
|
+
|
|
494
|
+
if (useApprovalPath) {
|
|
495
|
+
processChannelMessageWithApprovals({
|
|
496
|
+
orchestrator: runOrchestrator,
|
|
497
|
+
conversationId: result.conversationId,
|
|
498
|
+
eventId: result.eventId,
|
|
499
|
+
content: content ?? '',
|
|
500
|
+
attachmentIds: hasAttachments ? attachmentIds : undefined,
|
|
501
|
+
externalChatId,
|
|
502
|
+
sourceChannel,
|
|
503
|
+
replyCallbackUrl,
|
|
504
|
+
bearerToken,
|
|
505
|
+
guardianCtx,
|
|
506
|
+
});
|
|
507
|
+
} else {
|
|
508
|
+
// Fire-and-forget: process the message and deliver the reply in the background.
|
|
509
|
+
// The HTTP response returns immediately so the gateway webhook is not blocked.
|
|
510
|
+
processChannelMessageInBackground({
|
|
511
|
+
processMessage,
|
|
512
|
+
conversationId: result.conversationId,
|
|
513
|
+
eventId: result.eventId,
|
|
514
|
+
content: content ?? '',
|
|
515
|
+
attachmentIds: hasAttachments ? attachmentIds : undefined,
|
|
516
|
+
sourceChannel,
|
|
517
|
+
externalChatId,
|
|
518
|
+
metadataHints,
|
|
519
|
+
metadataUxBrief,
|
|
520
|
+
replyCallbackUrl,
|
|
521
|
+
bearerToken,
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
return Response.json({
|
|
527
|
+
accepted: result.accepted,
|
|
528
|
+
duplicate: result.duplicate,
|
|
529
|
+
eventId: result.eventId,
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
interface BackgroundProcessingParams {
|
|
534
|
+
processMessage: MessageProcessor;
|
|
535
|
+
conversationId: string;
|
|
536
|
+
eventId: string;
|
|
537
|
+
content: string;
|
|
538
|
+
attachmentIds?: string[];
|
|
539
|
+
sourceChannel: string;
|
|
540
|
+
externalChatId: string;
|
|
541
|
+
metadataHints: string[];
|
|
542
|
+
metadataUxBrief?: string;
|
|
543
|
+
replyCallbackUrl?: string;
|
|
544
|
+
bearerToken?: string;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function processChannelMessageInBackground(params: BackgroundProcessingParams): void {
|
|
548
|
+
const {
|
|
549
|
+
processMessage,
|
|
550
|
+
conversationId,
|
|
551
|
+
eventId,
|
|
552
|
+
content,
|
|
553
|
+
attachmentIds,
|
|
554
|
+
sourceChannel,
|
|
555
|
+
externalChatId,
|
|
556
|
+
metadataHints,
|
|
557
|
+
metadataUxBrief,
|
|
558
|
+
replyCallbackUrl,
|
|
559
|
+
bearerToken,
|
|
560
|
+
} = params;
|
|
561
|
+
|
|
562
|
+
(async () => {
|
|
563
|
+
try {
|
|
564
|
+
const { messageId: userMessageId } = await processMessage(
|
|
565
|
+
conversationId,
|
|
566
|
+
content,
|
|
567
|
+
attachmentIds,
|
|
568
|
+
{
|
|
569
|
+
transport: {
|
|
570
|
+
channelId: sourceChannel,
|
|
571
|
+
hints: metadataHints.length > 0 ? metadataHints : undefined,
|
|
572
|
+
uxBrief: metadataUxBrief,
|
|
573
|
+
},
|
|
574
|
+
},
|
|
575
|
+
sourceChannel,
|
|
576
|
+
);
|
|
577
|
+
channelDeliveryStore.linkMessage(eventId, userMessageId);
|
|
578
|
+
channelDeliveryStore.markProcessed(eventId);
|
|
579
|
+
|
|
580
|
+
if (replyCallbackUrl) {
|
|
581
|
+
await deliverReplyViaCallback(conversationId, externalChatId, replyCallbackUrl, bearerToken);
|
|
582
|
+
}
|
|
583
|
+
} catch (err) {
|
|
584
|
+
log.error({ err, conversationId }, 'Background channel message processing failed');
|
|
585
|
+
channelDeliveryStore.recordProcessingFailure(eventId, err);
|
|
586
|
+
}
|
|
587
|
+
})();
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// ---------------------------------------------------------------------------
|
|
591
|
+
// Orchestrator-backed channel processing with approval prompt delivery
|
|
592
|
+
// ---------------------------------------------------------------------------
|
|
593
|
+
|
|
594
|
+
const RUN_POLL_INTERVAL_MS = 500;
|
|
595
|
+
const RUN_POLL_MAX_WAIT_MS = 300_000; // 5 minutes
|
|
596
|
+
|
|
597
|
+
/** Post-decision delivery poll: uses the same budget as the main poll since
|
|
598
|
+
* this is the only delivery path for late approvals after the main poll exits. */
|
|
599
|
+
const POST_DECISION_POLL_INTERVAL_MS = 500;
|
|
600
|
+
const POST_DECISION_POLL_MAX_WAIT_MS = RUN_POLL_MAX_WAIT_MS;
|
|
601
|
+
|
|
602
|
+
interface ApprovalProcessingParams {
|
|
603
|
+
orchestrator: RunOrchestrator;
|
|
604
|
+
conversationId: string;
|
|
605
|
+
eventId: string;
|
|
606
|
+
content: string;
|
|
607
|
+
attachmentIds?: string[];
|
|
608
|
+
externalChatId: string;
|
|
609
|
+
sourceChannel: string;
|
|
610
|
+
replyCallbackUrl: string;
|
|
611
|
+
bearerToken?: string;
|
|
612
|
+
guardianCtx: GuardianContext;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* Process a channel message using the run orchestrator so that
|
|
617
|
+
* `confirmation_request` events are intercepted and written to the
|
|
618
|
+
* runs store. Polls the run until it reaches a terminal state,
|
|
619
|
+
* sending approval prompts when `needs_confirmation` is detected.
|
|
620
|
+
*
|
|
621
|
+
* When the actor is a non-guardian:
|
|
622
|
+
* - `forceStrictSideEffects` is set on the run so all side-effect tools
|
|
623
|
+
* trigger the confirmation flow
|
|
624
|
+
* - Approval prompts are routed to the guardian's chat
|
|
625
|
+
* - A `channelGuardianApprovalRequest` record is created
|
|
626
|
+
*/
|
|
627
|
+
function processChannelMessageWithApprovals(params: ApprovalProcessingParams): void {
|
|
628
|
+
const {
|
|
629
|
+
orchestrator,
|
|
630
|
+
conversationId,
|
|
631
|
+
eventId,
|
|
632
|
+
content,
|
|
633
|
+
attachmentIds,
|
|
634
|
+
externalChatId,
|
|
635
|
+
sourceChannel,
|
|
636
|
+
replyCallbackUrl,
|
|
637
|
+
bearerToken,
|
|
638
|
+
guardianCtx,
|
|
639
|
+
} = params;
|
|
640
|
+
|
|
641
|
+
const isNonGuardian = guardianCtx.actorRole === 'non-guardian';
|
|
642
|
+
const isUnverifiedChannel = guardianCtx.actorRole === 'unverified_channel';
|
|
643
|
+
|
|
644
|
+
(async () => {
|
|
645
|
+
try {
|
|
646
|
+
const run = await orchestrator.startRun(
|
|
647
|
+
conversationId,
|
|
648
|
+
content,
|
|
649
|
+
attachmentIds,
|
|
650
|
+
{
|
|
651
|
+
...((isNonGuardian || isUnverifiedChannel) ? { forceStrictSideEffects: true } : {}),
|
|
652
|
+
sourceChannel,
|
|
653
|
+
},
|
|
654
|
+
);
|
|
655
|
+
|
|
656
|
+
// Poll the run until it reaches a terminal state, delivering approval
|
|
657
|
+
// prompts when it transitions to needs_confirmation.
|
|
658
|
+
const startTime = Date.now();
|
|
659
|
+
let lastStatus = run.status;
|
|
660
|
+
|
|
661
|
+
while (Date.now() - startTime < RUN_POLL_MAX_WAIT_MS) {
|
|
662
|
+
await new Promise((resolve) => setTimeout(resolve, RUN_POLL_INTERVAL_MS));
|
|
663
|
+
|
|
664
|
+
const current = orchestrator.getRun(run.id);
|
|
665
|
+
if (!current) break;
|
|
666
|
+
|
|
667
|
+
if (current.status === 'needs_confirmation' && lastStatus !== 'needs_confirmation') {
|
|
668
|
+
const pending = getPendingConfirmationsByConversation(conversationId);
|
|
669
|
+
|
|
670
|
+
if (isUnverifiedChannel && pending.length > 0) {
|
|
671
|
+
// No guardian binding — auto-deny the sensitive action (fail-closed).
|
|
672
|
+
handleChannelDecision(conversationId, { action: 'reject', source: 'plain_text' }, orchestrator);
|
|
673
|
+
try {
|
|
674
|
+
await deliverChannelReply(replyCallbackUrl, {
|
|
675
|
+
chatId: externalChatId,
|
|
676
|
+
text: `The action "${pending[0].toolName}" requires guardian approval, but no guardian has been set up for this channel. The action has been denied. Please ask an administrator to configure a guardian.`,
|
|
677
|
+
}, bearerToken);
|
|
678
|
+
} catch (err) {
|
|
679
|
+
log.error({ err, runId: run.id }, 'Failed to deliver unverified-channel denial notice');
|
|
680
|
+
}
|
|
681
|
+
} else if (isNonGuardian && guardianCtx.guardianChatId && pending.length > 0) {
|
|
682
|
+
// Non-guardian actor: route the approval prompt to the guardian's chat
|
|
683
|
+
const guardianPrompt = buildGuardianApprovalPrompt(
|
|
684
|
+
pending[0],
|
|
685
|
+
guardianCtx.requesterIdentifier ?? 'Unknown user',
|
|
686
|
+
);
|
|
687
|
+
const uiMetadata = buildApprovalUIMetadata(guardianPrompt, pending[0]);
|
|
688
|
+
|
|
689
|
+
// Persist the guardian approval request so we can look it up when
|
|
690
|
+
// the guardian responds from their chat.
|
|
691
|
+
const approvalReqRecord = createApprovalRequest({
|
|
692
|
+
runId: run.id,
|
|
693
|
+
conversationId,
|
|
694
|
+
channel: sourceChannel,
|
|
695
|
+
requesterExternalUserId: guardianCtx.requesterExternalUserId ?? '',
|
|
696
|
+
requesterChatId: guardianCtx.requesterChatId ?? externalChatId,
|
|
697
|
+
guardianExternalUserId: guardianCtx.guardianExternalUserId ?? '',
|
|
698
|
+
guardianChatId: guardianCtx.guardianChatId,
|
|
699
|
+
toolName: pending[0].toolName,
|
|
700
|
+
riskLevel: pending[0].riskLevel,
|
|
701
|
+
expiresAt: Date.now() + GUARDIAN_APPROVAL_TTL_MS,
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
let guardianNotified = false;
|
|
705
|
+
try {
|
|
706
|
+
const guardianText = effectivePromptText(
|
|
707
|
+
guardianPrompt.promptText,
|
|
708
|
+
guardianPrompt.plainTextFallback,
|
|
709
|
+
sourceChannel,
|
|
710
|
+
);
|
|
711
|
+
await deliverApprovalPrompt(
|
|
712
|
+
replyCallbackUrl,
|
|
713
|
+
guardianCtx.guardianChatId,
|
|
714
|
+
guardianText,
|
|
715
|
+
uiMetadata,
|
|
716
|
+
bearerToken,
|
|
717
|
+
);
|
|
718
|
+
guardianNotified = true;
|
|
719
|
+
} catch (err) {
|
|
720
|
+
log.error({ err, runId: run.id }, 'Failed to deliver guardian approval prompt');
|
|
721
|
+
// Deny the approval and the underlying run — fail-closed. If
|
|
722
|
+
// the prompt could not be delivered, the guardian will never see
|
|
723
|
+
// it, so the safest action is to deny rather than cancel (which
|
|
724
|
+
// would allow requester fallback).
|
|
725
|
+
updateApprovalDecision(approvalReqRecord.id, { status: 'denied' });
|
|
726
|
+
handleChannelDecision(conversationId, { action: 'reject', source: 'plain_text' }, orchestrator);
|
|
727
|
+
try {
|
|
728
|
+
await deliverChannelReply(replyCallbackUrl, {
|
|
729
|
+
chatId: guardianCtx.requesterChatId ?? externalChatId,
|
|
730
|
+
text: `Your request to run "${pending[0].toolName}" could not be sent to the guardian for approval. The action has been denied.`,
|
|
731
|
+
}, bearerToken);
|
|
732
|
+
} catch (notifyErr) {
|
|
733
|
+
log.error({ err: notifyErr, runId: run.id }, 'Failed to notify requester of guardian delivery failure');
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// Only notify the requester if the guardian prompt was actually delivered
|
|
738
|
+
if (guardianNotified) {
|
|
739
|
+
try {
|
|
740
|
+
await deliverChannelReply(replyCallbackUrl, {
|
|
741
|
+
chatId: guardianCtx.requesterChatId ?? externalChatId,
|
|
742
|
+
text: `Your request to run "${pending[0].toolName}" has been sent to the guardian for approval.`,
|
|
743
|
+
}, bearerToken);
|
|
744
|
+
} catch (err) {
|
|
745
|
+
log.error({ err, runId: run.id }, 'Failed to notify requester of pending guardian approval');
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
} else {
|
|
749
|
+
// Guardian actor or no guardian binding: standard approval prompt
|
|
750
|
+
// sent to the requester's own chat.
|
|
751
|
+
const prompt = getChannelApprovalPrompt(conversationId);
|
|
752
|
+
if (prompt && pending.length > 0) {
|
|
753
|
+
const uiMetadata = buildApprovalUIMetadata(prompt, pending[0]);
|
|
754
|
+
try {
|
|
755
|
+
const promptTextForChannel = effectivePromptText(
|
|
756
|
+
prompt.promptText,
|
|
757
|
+
prompt.plainTextFallback,
|
|
758
|
+
sourceChannel,
|
|
759
|
+
);
|
|
760
|
+
await deliverApprovalPrompt(
|
|
761
|
+
replyCallbackUrl,
|
|
762
|
+
externalChatId,
|
|
763
|
+
promptTextForChannel,
|
|
764
|
+
uiMetadata,
|
|
765
|
+
bearerToken,
|
|
766
|
+
);
|
|
767
|
+
} catch (err) {
|
|
768
|
+
log.error({ err, runId: run.id }, 'Failed to deliver approval prompt for channel run');
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
lastStatus = current.status;
|
|
775
|
+
|
|
776
|
+
if (current.status === 'completed' || current.status === 'failed') {
|
|
777
|
+
break;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// Only mark processed and deliver the final reply when the run has
|
|
782
|
+
// actually reached a terminal state.
|
|
783
|
+
const finalRun = orchestrator.getRun(run.id);
|
|
784
|
+
const isTerminal = finalRun?.status === 'completed' || finalRun?.status === 'failed';
|
|
785
|
+
|
|
786
|
+
if (isTerminal) {
|
|
787
|
+
// Link the inbound event to the user message created by the run so
|
|
788
|
+
// that edit lookups and dead letter replay work correctly.
|
|
789
|
+
if (run.messageId) {
|
|
790
|
+
channelDeliveryStore.linkMessage(eventId, run.messageId);
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
channelDeliveryStore.markProcessed(eventId);
|
|
794
|
+
|
|
795
|
+
// Deliver the final assistant reply to the requester's chat
|
|
796
|
+
await deliverReplyViaCallback(conversationId, externalChatId, replyCallbackUrl, bearerToken);
|
|
797
|
+
|
|
798
|
+
// If this was a non-guardian run that went through guardian approval,
|
|
799
|
+
// also notify the guardian's chat about the outcome.
|
|
800
|
+
if (isNonGuardian && guardianCtx.guardianChatId) {
|
|
801
|
+
const approvalReq = getPendingApprovalForRun(run.id);
|
|
802
|
+
if (approvalReq) {
|
|
803
|
+
// The approval was resolved (run completed or failed) — mark it
|
|
804
|
+
const outcomeStatus = finalRun?.status === 'completed' ? 'approved' as const : 'denied' as const;
|
|
805
|
+
updateApprovalDecision(approvalReq.id, { status: outcomeStatus });
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
} else if (finalRun?.status === 'needs_confirmation') {
|
|
809
|
+
// The run is waiting for an approval decision but the poll window has
|
|
810
|
+
// elapsed. Mark the event as processed rather than failed — the run
|
|
811
|
+
// will resume when the user clicks approve/reject, and
|
|
812
|
+
// `handleApprovalInterception` will deliver the reply via its own
|
|
813
|
+
// post-decision poll. Marking it failed would cause the generic retry
|
|
814
|
+
// sweep to replay through `processMessage`, which throws "Session is
|
|
815
|
+
// already processing a message" and dead-letters a valid conversation.
|
|
816
|
+
log.warn(
|
|
817
|
+
{ runId: run.id, status: finalRun.status, conversationId },
|
|
818
|
+
'Approval-path poll loop timed out while run awaits approval decision; marking event as processed',
|
|
819
|
+
);
|
|
820
|
+
channelDeliveryStore.markProcessed(eventId);
|
|
821
|
+
} else {
|
|
822
|
+
// The run is in a non-terminal, non-approval state (e.g. running,
|
|
823
|
+
// needs_secret, or disappeared). Record a processing failure so the
|
|
824
|
+
// retry/dead-letter machinery can handle it.
|
|
825
|
+
const timeoutErr = new Error(
|
|
826
|
+
`Approval poll timeout: run did not reach terminal state within ${RUN_POLL_MAX_WAIT_MS}ms (status: ${finalRun?.status ?? 'null'})`,
|
|
827
|
+
);
|
|
828
|
+
log.warn(
|
|
829
|
+
{ runId: run.id, status: finalRun?.status, conversationId },
|
|
830
|
+
'Approval-path poll loop timed out before run reached terminal state',
|
|
831
|
+
);
|
|
832
|
+
channelDeliveryStore.recordProcessingFailure(eventId, timeoutErr);
|
|
833
|
+
}
|
|
834
|
+
} catch (err) {
|
|
835
|
+
log.error({ err, conversationId }, 'Approval-aware channel message processing failed');
|
|
836
|
+
channelDeliveryStore.recordProcessingFailure(eventId, err);
|
|
837
|
+
}
|
|
838
|
+
})();
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
// ---------------------------------------------------------------------------
|
|
842
|
+
// Approval interception
|
|
843
|
+
// ---------------------------------------------------------------------------
|
|
844
|
+
|
|
845
|
+
interface ApprovalInterceptionParams {
|
|
846
|
+
conversationId: string;
|
|
847
|
+
callbackData?: string;
|
|
848
|
+
content: string;
|
|
849
|
+
externalChatId: string;
|
|
850
|
+
sourceChannel: string;
|
|
851
|
+
senderExternalUserId?: string;
|
|
852
|
+
replyCallbackUrl: string;
|
|
853
|
+
bearerToken?: string;
|
|
854
|
+
orchestrator: RunOrchestrator;
|
|
855
|
+
guardianCtx: GuardianContext;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
interface ApprovalInterceptionResult {
|
|
859
|
+
handled: boolean;
|
|
860
|
+
type?: 'decision_applied' | 'reminder_sent' | 'guardian_decision_applied' | 'stale_ignored';
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
/**
|
|
864
|
+
* Check for pending approvals and handle inbound messages accordingly.
|
|
865
|
+
*
|
|
866
|
+
* Returns `{ handled: true }` when the message was consumed by the approval
|
|
867
|
+
* flow (either as a decision or a reminder), so the caller should NOT proceed
|
|
868
|
+
* to normal message processing.
|
|
869
|
+
*
|
|
870
|
+
* When the sender is a guardian responding from their chat, also checks for
|
|
871
|
+
* pending guardian approval requests and routes the decision accordingly.
|
|
872
|
+
*/
|
|
873
|
+
async function handleApprovalInterception(
|
|
874
|
+
params: ApprovalInterceptionParams,
|
|
875
|
+
): Promise<ApprovalInterceptionResult> {
|
|
876
|
+
const {
|
|
877
|
+
conversationId,
|
|
878
|
+
callbackData,
|
|
879
|
+
content,
|
|
880
|
+
externalChatId,
|
|
881
|
+
sourceChannel,
|
|
882
|
+
senderExternalUserId,
|
|
883
|
+
replyCallbackUrl,
|
|
884
|
+
bearerToken,
|
|
885
|
+
orchestrator,
|
|
886
|
+
guardianCtx,
|
|
887
|
+
} = params;
|
|
888
|
+
|
|
889
|
+
// ── Guardian approval decision path ──
|
|
890
|
+
// When the sender is the guardian and there's a pending guardian approval
|
|
891
|
+
// request targeting this chat, the message might be a decision on behalf
|
|
892
|
+
// of a non-guardian requester.
|
|
893
|
+
if (
|
|
894
|
+
guardianCtx.actorRole === 'guardian' &&
|
|
895
|
+
senderExternalUserId
|
|
896
|
+
) {
|
|
897
|
+
// First, try to parse the inbound payload to determine if it carries
|
|
898
|
+
// a run ID (callback button) or is plain text. This governs how we
|
|
899
|
+
// look up the target approval request.
|
|
900
|
+
let decision: ApprovalDecisionResult | null = null;
|
|
901
|
+
if (callbackData) {
|
|
902
|
+
decision = parseCallbackData(callbackData);
|
|
903
|
+
}
|
|
904
|
+
if (!decision && content) {
|
|
905
|
+
decision = parseApprovalDecision(content);
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
// When a callback button provides a run ID, use the scoped lookup so
|
|
909
|
+
// the decision resolves to exactly the right approval even when
|
|
910
|
+
// multiple approvals target the same guardian chat.
|
|
911
|
+
let guardianApproval = decision?.runId
|
|
912
|
+
? getPendingApprovalByRunAndGuardianChat(decision.runId, sourceChannel, externalChatId)
|
|
913
|
+
: null;
|
|
914
|
+
|
|
915
|
+
// For plain-text decisions (no run ID), check how many pending
|
|
916
|
+
// approvals exist for this guardian chat. If there are multiple,
|
|
917
|
+
// the guardian must use buttons to disambiguate.
|
|
918
|
+
if (!guardianApproval && decision && !decision.runId) {
|
|
919
|
+
const allPending = getAllPendingApprovalsByGuardianChat(sourceChannel, externalChatId);
|
|
920
|
+
if (allPending.length > 1) {
|
|
921
|
+
try {
|
|
922
|
+
await deliverChannelReply(replyCallbackUrl, {
|
|
923
|
+
chatId: externalChatId,
|
|
924
|
+
text: `You have ${allPending.length} pending approval requests. Please use the approval buttons to respond to a specific request.`,
|
|
925
|
+
}, bearerToken);
|
|
926
|
+
} catch (err) {
|
|
927
|
+
log.error({ err, externalChatId }, 'Failed to deliver disambiguation notice');
|
|
928
|
+
}
|
|
929
|
+
return { handled: true, type: 'guardian_decision_applied' };
|
|
930
|
+
}
|
|
931
|
+
if (allPending.length === 1) {
|
|
932
|
+
guardianApproval = allPending[0];
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
// Fall back to the single-result lookup for non-decision messages
|
|
937
|
+
// (reminder path) or when the scoped lookup found nothing.
|
|
938
|
+
if (!guardianApproval && !decision) {
|
|
939
|
+
guardianApproval = getPendingApprovalByGuardianChat(sourceChannel, externalChatId);
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
if (guardianApproval) {
|
|
943
|
+
// Validate that the sender is the specific guardian who was assigned
|
|
944
|
+
// this approval request. This is a defense-in-depth check — the
|
|
945
|
+
// actorRole check above already verifies the sender is a guardian,
|
|
946
|
+
// but this catches edge cases like binding rotation between request
|
|
947
|
+
// creation and decision.
|
|
948
|
+
if (senderExternalUserId !== guardianApproval.guardianExternalUserId) {
|
|
949
|
+
log.warn(
|
|
950
|
+
{ externalChatId, senderExternalUserId, expectedGuardian: guardianApproval.guardianExternalUserId },
|
|
951
|
+
'Non-guardian sender attempted to act on guardian approval request',
|
|
952
|
+
);
|
|
953
|
+
try {
|
|
954
|
+
await deliverChannelReply(replyCallbackUrl, {
|
|
955
|
+
chatId: externalChatId,
|
|
956
|
+
text: 'Only the verified guardian can approve or deny this request.',
|
|
957
|
+
}, bearerToken);
|
|
958
|
+
} catch (err) {
|
|
959
|
+
log.error({ err, externalChatId }, 'Failed to deliver guardian identity rejection notice');
|
|
960
|
+
}
|
|
961
|
+
return { handled: true, type: 'guardian_decision_applied' };
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
if (decision) {
|
|
965
|
+
// approve_always is not available for guardian approvals — guardians
|
|
966
|
+
// should not be able to permanently allowlist tools on behalf of the
|
|
967
|
+
// requester. Downgrade to approve_once.
|
|
968
|
+
if (decision.action === 'approve_always') {
|
|
969
|
+
decision = { ...decision, action: 'approve_once' };
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
// Apply the decision to the underlying run using the requester's
|
|
973
|
+
// conversation context
|
|
974
|
+
const result = handleChannelDecision(
|
|
975
|
+
guardianApproval.conversationId,
|
|
976
|
+
decision,
|
|
977
|
+
orchestrator,
|
|
978
|
+
);
|
|
979
|
+
|
|
980
|
+
// Update the guardian approval request record
|
|
981
|
+
const approvalStatus = decision.action === 'reject' ? 'denied' as const : 'approved' as const;
|
|
982
|
+
updateApprovalDecision(guardianApproval.id, {
|
|
983
|
+
status: approvalStatus,
|
|
984
|
+
decidedByExternalUserId: senderExternalUserId,
|
|
985
|
+
});
|
|
986
|
+
|
|
987
|
+
if (result.applied) {
|
|
988
|
+
// Notify the requester's chat about the outcome with the tool name
|
|
989
|
+
const toolLabel = guardianApproval.toolName;
|
|
990
|
+
const outcomeText = decision.action === 'reject'
|
|
991
|
+
? `Your request to run "${toolLabel}" was denied by the guardian.`
|
|
992
|
+
: `Your request to run "${toolLabel}" was approved by the guardian.`;
|
|
993
|
+
try {
|
|
994
|
+
await deliverChannelReply(replyCallbackUrl, {
|
|
995
|
+
chatId: guardianApproval.requesterChatId,
|
|
996
|
+
text: outcomeText,
|
|
997
|
+
}, bearerToken);
|
|
998
|
+
} catch (err) {
|
|
999
|
+
log.error({ err, conversationId: guardianApproval.conversationId }, 'Failed to notify requester of guardian decision');
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// Schedule post-decision delivery to the requester's chat in case
|
|
1003
|
+
// the original poll has already exited.
|
|
1004
|
+
if (result.runId) {
|
|
1005
|
+
schedulePostDecisionDelivery(
|
|
1006
|
+
orchestrator,
|
|
1007
|
+
result.runId,
|
|
1008
|
+
guardianApproval.conversationId,
|
|
1009
|
+
guardianApproval.requesterChatId,
|
|
1010
|
+
replyCallbackUrl,
|
|
1011
|
+
bearerToken,
|
|
1012
|
+
);
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
return { handled: true, type: 'guardian_decision_applied' };
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
// Non-decision message from guardian while approval is pending — remind them
|
|
1020
|
+
const pendingInfo = getPendingConfirmationsByConversation(guardianApproval.conversationId);
|
|
1021
|
+
if (pendingInfo.length > 0) {
|
|
1022
|
+
const guardianPrompt = buildGuardianApprovalPrompt(
|
|
1023
|
+
pendingInfo[0],
|
|
1024
|
+
`user ${guardianApproval.requesterExternalUserId}`,
|
|
1025
|
+
);
|
|
1026
|
+
const reminder = buildReminderPrompt(guardianPrompt);
|
|
1027
|
+
const uiMetadata = buildApprovalUIMetadata(reminder, pendingInfo[0]);
|
|
1028
|
+
try {
|
|
1029
|
+
const reminderText = effectivePromptText(
|
|
1030
|
+
reminder.promptText,
|
|
1031
|
+
reminder.plainTextFallback,
|
|
1032
|
+
sourceChannel,
|
|
1033
|
+
);
|
|
1034
|
+
await deliverApprovalPrompt(
|
|
1035
|
+
replyCallbackUrl,
|
|
1036
|
+
externalChatId,
|
|
1037
|
+
reminderText,
|
|
1038
|
+
uiMetadata,
|
|
1039
|
+
bearerToken,
|
|
1040
|
+
);
|
|
1041
|
+
} catch (err) {
|
|
1042
|
+
log.error({ err, conversationId: guardianApproval.conversationId }, 'Failed to deliver guardian approval reminder');
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
return { handled: true, type: 'reminder_sent' };
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
// Callback with a run ID that no longer has a pending approval — stale button
|
|
1050
|
+
if (decision?.runId) {
|
|
1051
|
+
return { handled: true, type: 'stale_ignored' };
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
// ── Standard approval interception (existing flow) ──
|
|
1056
|
+
const pendingPrompt = getChannelApprovalPrompt(conversationId);
|
|
1057
|
+
if (!pendingPrompt) return { handled: false };
|
|
1058
|
+
|
|
1059
|
+
// When the sender is from an unverified channel (no guardian binding),
|
|
1060
|
+
// auto-deny any pending confirmation and block self-approval.
|
|
1061
|
+
if (guardianCtx.actorRole === 'unverified_channel') {
|
|
1062
|
+
const pending = getPendingConfirmationsByConversation(conversationId);
|
|
1063
|
+
if (pending.length > 0) {
|
|
1064
|
+
handleChannelDecision(conversationId, { action: 'reject', source: 'plain_text' }, orchestrator);
|
|
1065
|
+
try {
|
|
1066
|
+
await deliverChannelReply(replyCallbackUrl, {
|
|
1067
|
+
chatId: externalChatId,
|
|
1068
|
+
text: `The action "${pending[0].toolName}" requires guardian approval, but no guardian has been set up for this channel. The action has been denied.`,
|
|
1069
|
+
}, bearerToken);
|
|
1070
|
+
} catch (err) {
|
|
1071
|
+
log.error({ err, conversationId }, 'Failed to deliver unverified-channel denial notice during interception');
|
|
1072
|
+
}
|
|
1073
|
+
return { handled: true, type: 'decision_applied' };
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
// When the sender is a non-guardian and there's a pending guardian approval
|
|
1078
|
+
// for this conversation's run, block self-approval. The non-guardian must
|
|
1079
|
+
// wait for the guardian to decide.
|
|
1080
|
+
if (guardianCtx.actorRole === 'non-guardian') {
|
|
1081
|
+
const pending = getPendingConfirmationsByConversation(conversationId);
|
|
1082
|
+
if (pending.length > 0) {
|
|
1083
|
+
const guardianApprovalForRun = getPendingApprovalForRun(pending[0].runId);
|
|
1084
|
+
if (guardianApprovalForRun) {
|
|
1085
|
+
try {
|
|
1086
|
+
await deliverChannelReply(replyCallbackUrl, {
|
|
1087
|
+
chatId: externalChatId,
|
|
1088
|
+
text: 'Your request is pending guardian approval. Only the verified guardian can approve or deny this request.',
|
|
1089
|
+
}, bearerToken);
|
|
1090
|
+
} catch (err) {
|
|
1091
|
+
log.error({ err, conversationId }, 'Failed to deliver guardian-pending notice to requester');
|
|
1092
|
+
}
|
|
1093
|
+
return { handled: true, type: 'reminder_sent' };
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
// Check for an expired-but-unresolved guardian approval. If the approval
|
|
1097
|
+
// expired without a guardian decision, auto-deny the run and transition
|
|
1098
|
+
// the approval to 'expired'. Without this, the requester could bypass
|
|
1099
|
+
// guardian-only controls by simply waiting for the TTL to elapse.
|
|
1100
|
+
const unresolvedApproval = getUnresolvedApprovalForRun(pending[0].runId);
|
|
1101
|
+
if (unresolvedApproval) {
|
|
1102
|
+
updateApprovalDecision(unresolvedApproval.id, { status: 'expired' });
|
|
1103
|
+
|
|
1104
|
+
// Auto-deny the underlying run so it does not remain actionable
|
|
1105
|
+
const expiredDecision: ApprovalDecisionResult = {
|
|
1106
|
+
action: 'reject',
|
|
1107
|
+
source: 'plain_text',
|
|
1108
|
+
};
|
|
1109
|
+
handleChannelDecision(conversationId, expiredDecision, orchestrator);
|
|
1110
|
+
|
|
1111
|
+
try {
|
|
1112
|
+
await deliverChannelReply(replyCallbackUrl, {
|
|
1113
|
+
chatId: externalChatId,
|
|
1114
|
+
text: 'Your guardian approval request has expired and the action has been denied. Please try again.',
|
|
1115
|
+
}, bearerToken);
|
|
1116
|
+
} catch (err) {
|
|
1117
|
+
log.error({ err, conversationId }, 'Failed to deliver guardian-expiry notice to requester');
|
|
1118
|
+
}
|
|
1119
|
+
return { handled: true, type: 'decision_applied' };
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
// Try to extract a decision from callback data (button press) first,
|
|
1125
|
+
// then fall back to plain-text parsing.
|
|
1126
|
+
let decision: ApprovalDecisionResult | null = null;
|
|
1127
|
+
|
|
1128
|
+
if (callbackData) {
|
|
1129
|
+
decision = parseCallbackData(callbackData);
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
if (!decision && content) {
|
|
1133
|
+
decision = parseApprovalDecision(content);
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
if (decision) {
|
|
1137
|
+
// When the decision came from a callback button, validate that the embedded
|
|
1138
|
+
// run ID matches the currently pending run. A stale button (from a previous
|
|
1139
|
+
// approval prompt) must not apply to a different pending run.
|
|
1140
|
+
if (decision.runId) {
|
|
1141
|
+
const pending = getPendingConfirmationsByConversation(conversationId);
|
|
1142
|
+
if (pending.length === 0 || pending[0].runId !== decision.runId) {
|
|
1143
|
+
log.warn(
|
|
1144
|
+
{ conversationId, callbackRunId: decision.runId, pendingRunId: pending[0]?.runId },
|
|
1145
|
+
'Callback run ID does not match pending run, ignoring stale button press',
|
|
1146
|
+
);
|
|
1147
|
+
return { handled: true, type: 'stale_ignored' };
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
const result = handleChannelDecision(conversationId, decision, orchestrator);
|
|
1152
|
+
|
|
1153
|
+
// Schedule a background poll for run terminal state and deliver the reply.
|
|
1154
|
+
// This handles the case where the original poll in
|
|
1155
|
+
// processChannelMessageWithApprovals has already exited due to timeout.
|
|
1156
|
+
// If the original poll is still running and delivers first, the duplicate
|
|
1157
|
+
// delivery is acceptable (gateway deduplicates or user sees a repeat).
|
|
1158
|
+
if (result.applied && result.runId) {
|
|
1159
|
+
schedulePostDecisionDelivery(
|
|
1160
|
+
orchestrator,
|
|
1161
|
+
result.runId,
|
|
1162
|
+
conversationId,
|
|
1163
|
+
externalChatId,
|
|
1164
|
+
replyCallbackUrl,
|
|
1165
|
+
bearerToken,
|
|
1166
|
+
);
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
return { handled: true, type: 'decision_applied' };
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
// The message is not a decision — send a reminder with the approval buttons.
|
|
1173
|
+
const reminder = buildReminderPrompt(pendingPrompt);
|
|
1174
|
+
const pending = getPendingConfirmationsByConversation(conversationId);
|
|
1175
|
+
if (pending.length > 0) {
|
|
1176
|
+
const uiMetadata = buildApprovalUIMetadata(reminder, pending[0]);
|
|
1177
|
+
try {
|
|
1178
|
+
const reminderText = effectivePromptText(
|
|
1179
|
+
reminder.promptText,
|
|
1180
|
+
reminder.plainTextFallback,
|
|
1181
|
+
sourceChannel,
|
|
1182
|
+
);
|
|
1183
|
+
await deliverApprovalPrompt(
|
|
1184
|
+
replyCallbackUrl,
|
|
1185
|
+
externalChatId,
|
|
1186
|
+
reminderText,
|
|
1187
|
+
uiMetadata,
|
|
1188
|
+
bearerToken,
|
|
1189
|
+
);
|
|
1190
|
+
} catch (err) {
|
|
1191
|
+
log.error({ err, conversationId }, 'Failed to deliver approval reminder');
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
return { handled: true, type: 'reminder_sent' };
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
/**
|
|
1199
|
+
* Fire-and-forget: after a decision is applied via `handleApprovalInterception`,
|
|
1200
|
+
* poll the run briefly for terminal state and deliver the final reply. This
|
|
1201
|
+
* handles the case where the original poll in `processChannelMessageWithApprovals`
|
|
1202
|
+
* has already exited due to the 5-minute timeout.
|
|
1203
|
+
*
|
|
1204
|
+
* If the original poll already delivered the reply, delivering it again is
|
|
1205
|
+
* acceptable — the gateway will deduplicate or the user sees a duplicate
|
|
1206
|
+
* (better than seeing nothing).
|
|
1207
|
+
*/
|
|
1208
|
+
function schedulePostDecisionDelivery(
|
|
1209
|
+
orchestrator: RunOrchestrator,
|
|
1210
|
+
runId: string,
|
|
1211
|
+
conversationId: string,
|
|
1212
|
+
externalChatId: string,
|
|
1213
|
+
replyCallbackUrl: string,
|
|
1214
|
+
bearerToken?: string,
|
|
1215
|
+
): void {
|
|
1216
|
+
(async () => {
|
|
1217
|
+
try {
|
|
1218
|
+
const startTime = Date.now();
|
|
1219
|
+
while (Date.now() - startTime < POST_DECISION_POLL_MAX_WAIT_MS) {
|
|
1220
|
+
await new Promise((resolve) => setTimeout(resolve, POST_DECISION_POLL_INTERVAL_MS));
|
|
1221
|
+
const current = orchestrator.getRun(runId);
|
|
1222
|
+
if (!current) break;
|
|
1223
|
+
if (current.status === 'completed' || current.status === 'failed') {
|
|
1224
|
+
await deliverReplyViaCallback(conversationId, externalChatId, replyCallbackUrl, bearerToken);
|
|
1225
|
+
return;
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
log.warn(
|
|
1229
|
+
{ runId, conversationId },
|
|
1230
|
+
'Post-decision delivery poll timed out without run reaching terminal state',
|
|
1231
|
+
);
|
|
1232
|
+
} catch (err) {
|
|
1233
|
+
log.error({ err, runId, conversationId }, 'Post-decision delivery failed');
|
|
1234
|
+
}
|
|
1235
|
+
})();
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
async function deliverReplyViaCallback(
|
|
1239
|
+
conversationId: string,
|
|
1240
|
+
externalChatId: string,
|
|
1241
|
+
callbackUrl: string,
|
|
1242
|
+
bearerToken?: string,
|
|
1243
|
+
): Promise<void> {
|
|
1244
|
+
const msgs = conversationStore.getMessages(conversationId);
|
|
1245
|
+
for (let i = msgs.length - 1; i >= 0; i--) {
|
|
1246
|
+
if (msgs[i].role === 'assistant') {
|
|
1247
|
+
let parsed: unknown;
|
|
1248
|
+
try { parsed = JSON.parse(msgs[i].content); } catch { parsed = msgs[i].content; }
|
|
1249
|
+
const rendered = renderHistoryContent(parsed);
|
|
1250
|
+
|
|
1251
|
+
const linked = attachmentsStore.getAttachmentMetadataForMessage(msgs[i].id);
|
|
1252
|
+
const replyAttachments: RuntimeAttachmentMetadata[] = linked.map((a) => ({
|
|
1253
|
+
id: a.id,
|
|
1254
|
+
filename: a.originalFilename,
|
|
1255
|
+
mimeType: a.mimeType,
|
|
1256
|
+
sizeBytes: a.sizeBytes,
|
|
1257
|
+
kind: a.kind,
|
|
1258
|
+
}));
|
|
1259
|
+
|
|
1260
|
+
if (rendered.text || replyAttachments.length > 0) {
|
|
1261
|
+
await deliverChannelReply(callbackUrl, {
|
|
1262
|
+
chatId: externalChatId,
|
|
1263
|
+
text: rendered.text || undefined,
|
|
1264
|
+
attachments: replyAttachments.length > 0 ? replyAttachments : undefined,
|
|
1265
|
+
}, bearerToken);
|
|
1266
|
+
}
|
|
1267
|
+
break;
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
export function handleListDeadLetters(): Response {
|
|
1273
|
+
const events = channelDeliveryStore.getDeadLetterEvents();
|
|
1274
|
+
return Response.json({ events });
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
export async function handleReplayDeadLetters(req: Request): Promise<Response> {
|
|
1278
|
+
const body = await req.json() as { eventIds?: string[] };
|
|
1279
|
+
const eventIds = body.eventIds;
|
|
1280
|
+
|
|
1281
|
+
if (!Array.isArray(eventIds) || eventIds.length === 0) {
|
|
1282
|
+
return Response.json({ error: 'eventIds array is required' }, { status: 400 });
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
const replayed = channelDeliveryStore.replayDeadLetters(eventIds);
|
|
1286
|
+
return Response.json({ replayed });
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
export async function handleChannelDeliveryAck(req: Request): Promise<Response> {
|
|
1290
|
+
const body = await req.json() as {
|
|
1291
|
+
sourceChannel?: string;
|
|
1292
|
+
externalChatId?: string;
|
|
1293
|
+
externalMessageId?: string;
|
|
1294
|
+
};
|
|
1295
|
+
|
|
1296
|
+
const { sourceChannel, externalChatId, externalMessageId } = body;
|
|
1297
|
+
|
|
1298
|
+
if (!sourceChannel || typeof sourceChannel !== 'string') {
|
|
1299
|
+
return Response.json({ error: 'sourceChannel is required' }, { status: 400 });
|
|
1300
|
+
}
|
|
1301
|
+
if (!externalChatId || typeof externalChatId !== 'string') {
|
|
1302
|
+
return Response.json({ error: 'externalChatId is required' }, { status: 400 });
|
|
1303
|
+
}
|
|
1304
|
+
if (!externalMessageId || typeof externalMessageId !== 'string') {
|
|
1305
|
+
return Response.json({ error: 'externalMessageId is required' }, { status: 400 });
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
const acked = channelDeliveryStore.acknowledgeDelivery(
|
|
1309
|
+
sourceChannel,
|
|
1310
|
+
externalChatId,
|
|
1311
|
+
externalMessageId,
|
|
1312
|
+
);
|
|
1313
|
+
|
|
1314
|
+
if (!acked) {
|
|
1315
|
+
return Response.json({ error: 'Inbound event not found' }, { status: 404 });
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
return new Response(null, { status: 204 });
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
// ---------------------------------------------------------------------------
|
|
1322
|
+
// Proactive guardian approval expiry sweep
|
|
1323
|
+
// ---------------------------------------------------------------------------
|
|
1324
|
+
|
|
1325
|
+
/** Interval at which the expiry sweep runs (60 seconds). */
|
|
1326
|
+
const GUARDIAN_EXPIRY_SWEEP_INTERVAL_MS = 60_000;
|
|
1327
|
+
|
|
1328
|
+
/** Timer handle for the expiry sweep so it can be stopped in tests. */
|
|
1329
|
+
let expirySweepTimer: ReturnType<typeof setInterval> | null = null;
|
|
1330
|
+
|
|
1331
|
+
/**
|
|
1332
|
+
* Sweep expired guardian approval requests, auto-deny the underlying runs,
|
|
1333
|
+
* and notify both the requester and guardian. This runs proactively on a
|
|
1334
|
+
* timer so expired approvals are closed without waiting for follow-up
|
|
1335
|
+
* traffic from either party.
|
|
1336
|
+
*/
|
|
1337
|
+
export function sweepExpiredGuardianApprovals(
|
|
1338
|
+
orchestrator: RunOrchestrator,
|
|
1339
|
+
replyCallbackUrl: string,
|
|
1340
|
+
bearerToken?: string,
|
|
1341
|
+
): void {
|
|
1342
|
+
const expired = getExpiredPendingApprovals();
|
|
1343
|
+
for (const approval of expired) {
|
|
1344
|
+
// Mark the approval as expired
|
|
1345
|
+
updateApprovalDecision(approval.id, { status: 'expired' });
|
|
1346
|
+
|
|
1347
|
+
// Auto-deny the underlying run
|
|
1348
|
+
const expiredDecision: ApprovalDecisionResult = {
|
|
1349
|
+
action: 'reject',
|
|
1350
|
+
source: 'plain_text',
|
|
1351
|
+
};
|
|
1352
|
+
handleChannelDecision(approval.conversationId, expiredDecision, orchestrator);
|
|
1353
|
+
|
|
1354
|
+
// Notify the requester that the approval expired
|
|
1355
|
+
deliverChannelReply(replyCallbackUrl, {
|
|
1356
|
+
chatId: approval.requesterChatId,
|
|
1357
|
+
text: `Your guardian approval request for "${approval.toolName}" has expired and the action has been denied. Please try again.`,
|
|
1358
|
+
}, bearerToken).catch((err) => {
|
|
1359
|
+
log.error({ err, runId: approval.runId }, 'Failed to notify requester of guardian approval expiry');
|
|
1360
|
+
});
|
|
1361
|
+
|
|
1362
|
+
// Notify the guardian that the approval expired
|
|
1363
|
+
deliverChannelReply(replyCallbackUrl, {
|
|
1364
|
+
chatId: approval.guardianChatId,
|
|
1365
|
+
text: `The approval request for "${approval.toolName}" from user ${approval.requesterExternalUserId} has expired and was automatically denied.`,
|
|
1366
|
+
}, bearerToken).catch((err) => {
|
|
1367
|
+
log.error({ err, runId: approval.runId }, 'Failed to notify guardian of approval expiry');
|
|
1368
|
+
});
|
|
1369
|
+
|
|
1370
|
+
log.info(
|
|
1371
|
+
{ runId: approval.runId, approvalId: approval.id },
|
|
1372
|
+
'Auto-denied expired guardian approval request',
|
|
1373
|
+
);
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
/**
|
|
1378
|
+
* Start the periodic expiry sweep. Idempotent — calling it multiple times
|
|
1379
|
+
* re-uses the same timer.
|
|
1380
|
+
*/
|
|
1381
|
+
export function startGuardianExpirySweep(
|
|
1382
|
+
orchestrator: RunOrchestrator,
|
|
1383
|
+
replyCallbackUrl: string,
|
|
1384
|
+
bearerToken?: string,
|
|
1385
|
+
): void {
|
|
1386
|
+
if (expirySweepTimer) return;
|
|
1387
|
+
expirySweepTimer = setInterval(() => {
|
|
1388
|
+
try {
|
|
1389
|
+
sweepExpiredGuardianApprovals(orchestrator, replyCallbackUrl, bearerToken);
|
|
1390
|
+
} catch (err) {
|
|
1391
|
+
log.error({ err }, 'Guardian expiry sweep failed');
|
|
1392
|
+
}
|
|
1393
|
+
}, GUARDIAN_EXPIRY_SWEEP_INTERVAL_MS);
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
/**
|
|
1397
|
+
* Stop the periodic expiry sweep. Used in tests and shutdown.
|
|
1398
|
+
*/
|
|
1399
|
+
export function stopGuardianExpirySweep(): void {
|
|
1400
|
+
if (expirySweepTimer) {
|
|
1401
|
+
clearInterval(expirySweepTimer);
|
|
1402
|
+
expirySweepTimer = null;
|
|
1403
|
+
}
|
|
1404
|
+
}
|