@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,1076 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional HTTP server that exposes the canonical runtime API.
|
|
3
|
+
*
|
|
4
|
+
* Runs in the same process as the daemon. Started only when
|
|
5
|
+
* `RUNTIME_HTTP_PORT` is set (default: disabled).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { existsSync, readFileSync, statSync, statfsSync } from 'node:fs';
|
|
9
|
+
import { resolve, join, dirname } from 'node:path';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
11
|
+
import { timingSafeEqual } from 'node:crypto';
|
|
12
|
+
import { ConfigError, IngressBlockedError } from '../util/errors.js';
|
|
13
|
+
import { getLogger } from '../util/logger.js';
|
|
14
|
+
import { getWorkspacePromptPath } from '../util/platform.js';
|
|
15
|
+
import { TwilioConversationRelayProvider } from '../calls/twilio-provider.js';
|
|
16
|
+
import { loadConfig } from '../config/loader.js';
|
|
17
|
+
import { getPublicBaseUrl } from '../inbound/public-ingress-urls.js';
|
|
18
|
+
import type { RunOrchestrator } from './run-orchestrator.js';
|
|
19
|
+
|
|
20
|
+
// Route handlers — grouped by domain
|
|
21
|
+
import {
|
|
22
|
+
handleListMessages,
|
|
23
|
+
handleSendMessage,
|
|
24
|
+
handleGetSuggestion,
|
|
25
|
+
} from './routes/conversation-routes.js';
|
|
26
|
+
import {
|
|
27
|
+
handleUploadAttachment,
|
|
28
|
+
handleDeleteAttachment,
|
|
29
|
+
handleGetAttachment,
|
|
30
|
+
} from './routes/attachment-routes.js';
|
|
31
|
+
import {
|
|
32
|
+
handleCreateRun,
|
|
33
|
+
handleGetRun,
|
|
34
|
+
handleRunDecision,
|
|
35
|
+
handleRunSecret,
|
|
36
|
+
handleAddTrustRule,
|
|
37
|
+
} from './routes/run-routes.js';
|
|
38
|
+
import {
|
|
39
|
+
handleDeleteConversation,
|
|
40
|
+
handleChannelInbound,
|
|
41
|
+
handleChannelDeliveryAck,
|
|
42
|
+
handleListDeadLetters,
|
|
43
|
+
handleReplayDeadLetters,
|
|
44
|
+
} from './routes/channel-routes.js';
|
|
45
|
+
import * as channelDeliveryStore from '../memory/channel-delivery-store.js';
|
|
46
|
+
import * as conversationStore from '../memory/conversation-store.js';
|
|
47
|
+
import * as externalConversationStore from '../memory/external-conversation-store.js';
|
|
48
|
+
import * as attachmentsStore from '../memory/attachments-store.js';
|
|
49
|
+
import { renderHistoryContent } from '../daemon/handlers.js';
|
|
50
|
+
import { deliverChannelReply } from './gateway-client.js';
|
|
51
|
+
import {
|
|
52
|
+
handleServePage,
|
|
53
|
+
handleShareApp,
|
|
54
|
+
handleDownloadSharedApp,
|
|
55
|
+
handleGetSharedAppMetadata,
|
|
56
|
+
handleDeleteSharedApp,
|
|
57
|
+
} from './routes/app-routes.js';
|
|
58
|
+
import { handleAddSecret } from './routes/secret-routes.js';
|
|
59
|
+
import {
|
|
60
|
+
handleStartCall,
|
|
61
|
+
handleGetCallStatus,
|
|
62
|
+
handleCancelCall,
|
|
63
|
+
handleAnswerCall,
|
|
64
|
+
handleInstructionCall,
|
|
65
|
+
} from './routes/call-routes.js';
|
|
66
|
+
import {
|
|
67
|
+
handleVoiceWebhook,
|
|
68
|
+
handleStatusCallback,
|
|
69
|
+
handleConnectAction,
|
|
70
|
+
} from '../calls/twilio-routes.js';
|
|
71
|
+
import { RelayConnection, activeRelayConnections } from '../calls/relay-server.js';
|
|
72
|
+
import type { RelayWebSocketData } from '../calls/relay-server.js';
|
|
73
|
+
import { handleSubscribeAssistantEvents } from './routes/events-routes.js';
|
|
74
|
+
import { consumeCallback, consumeCallbackError } from '../security/oauth-callback-registry.js';
|
|
75
|
+
|
|
76
|
+
// Re-export shared types so existing consumers don't need to update imports
|
|
77
|
+
export type {
|
|
78
|
+
RuntimeMessageSessionOptions,
|
|
79
|
+
MessageProcessor,
|
|
80
|
+
NonBlockingMessageProcessor,
|
|
81
|
+
RuntimeHttpServerOptions,
|
|
82
|
+
RuntimeAttachmentMetadata,
|
|
83
|
+
} from './http-types.js';
|
|
84
|
+
|
|
85
|
+
import type {
|
|
86
|
+
MessageProcessor,
|
|
87
|
+
NonBlockingMessageProcessor,
|
|
88
|
+
RuntimeHttpServerOptions,
|
|
89
|
+
} from './http-types.js';
|
|
90
|
+
|
|
91
|
+
const log = getLogger('runtime-http');
|
|
92
|
+
|
|
93
|
+
const DEFAULT_PORT = 7821;
|
|
94
|
+
const DEFAULT_HOSTNAME = '127.0.0.1';
|
|
95
|
+
|
|
96
|
+
/** Global hard cap on request body size (50 MB). Bun rejects larger payloads before they reach handlers. */
|
|
97
|
+
const MAX_REQUEST_BODY_BYTES = 50 * 1024 * 1024;
|
|
98
|
+
|
|
99
|
+
interface DiskSpaceInfo {
|
|
100
|
+
path: string;
|
|
101
|
+
totalMb: number;
|
|
102
|
+
usedMb: number;
|
|
103
|
+
freeMb: number;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function getDiskSpaceInfo(): DiskSpaceInfo | null {
|
|
107
|
+
try {
|
|
108
|
+
const baseDataDir = process.env.BASE_DATA_DIR?.trim();
|
|
109
|
+
const diskPath = baseDataDir && existsSync(baseDataDir) ? baseDataDir : '/';
|
|
110
|
+
const stats = statfsSync(diskPath);
|
|
111
|
+
const totalBytes = stats.bsize * stats.blocks;
|
|
112
|
+
const freeBytes = stats.bsize * stats.bavail;
|
|
113
|
+
const bytesToMb = (b: number) => Math.round((b / (1024 * 1024)) * 100) / 100;
|
|
114
|
+
return {
|
|
115
|
+
path: diskPath,
|
|
116
|
+
totalMb: bytesToMb(totalBytes),
|
|
117
|
+
usedMb: bytesToMb(totalBytes - freeBytes),
|
|
118
|
+
freeMb: bytesToMb(freeBytes),
|
|
119
|
+
};
|
|
120
|
+
} catch {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Regex to extract the Twilio webhook subpath from both top-level and
|
|
127
|
+
* assistant-scoped route shapes:
|
|
128
|
+
* /v1/calls/twilio/<subpath>
|
|
129
|
+
* /v1/assistants/<id>/calls/twilio/<subpath>
|
|
130
|
+
*/
|
|
131
|
+
const TWILIO_WEBHOOK_RE = /^\/v1\/(?:assistants\/[^/]+\/)?calls\/twilio\/(.+)$/;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Gateway-compatible Twilio webhook paths:
|
|
135
|
+
* /webhooks/twilio/<subpath>
|
|
136
|
+
*
|
|
137
|
+
* Maps gateway path segments to the internal subpath names used by the
|
|
138
|
+
* dispatcher below (e.g. "voice" -> "voice-webhook").
|
|
139
|
+
*/
|
|
140
|
+
const TWILIO_GATEWAY_WEBHOOK_RE = /^\/webhooks\/twilio\/(.+)$/;
|
|
141
|
+
const GATEWAY_SUBPATH_MAP: Record<string, string> = {
|
|
142
|
+
voice: 'voice-webhook',
|
|
143
|
+
status: 'status',
|
|
144
|
+
'connect-action': 'connect-action',
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Direct Twilio webhook subpaths that are blocked in gateway_only mode.
|
|
149
|
+
* Internal forwarding endpoints (gateway→runtime) are unaffected.
|
|
150
|
+
*/
|
|
151
|
+
const GATEWAY_ONLY_BLOCKED_SUBPATHS = new Set(['voice-webhook', 'status', 'connect-action']);
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Check if a request origin is from a private/internal network address.
|
|
155
|
+
* Extracts the hostname from the Origin header and validates it against
|
|
156
|
+
* isPrivateAddress(), consistent with the isPrivateNetworkPeer check.
|
|
157
|
+
*/
|
|
158
|
+
function isPrivateNetworkOrigin(req: Request): boolean {
|
|
159
|
+
const origin = req.headers.get('origin');
|
|
160
|
+
// No origin header (e.g., server-initiated or same-origin) — allow
|
|
161
|
+
if (!origin) return true;
|
|
162
|
+
try {
|
|
163
|
+
const url = new URL(origin);
|
|
164
|
+
const host = url.hostname;
|
|
165
|
+
if (host === 'localhost') return true;
|
|
166
|
+
// URL.hostname wraps IPv6 addresses in brackets (e.g. "[::1]") — strip them
|
|
167
|
+
const rawHost = host.startsWith('[') && host.endsWith(']') ? host.slice(1, -1) : host;
|
|
168
|
+
return isPrivateAddress(rawHost);
|
|
169
|
+
} catch {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Check if a hostname is a loopback address.
|
|
176
|
+
*/
|
|
177
|
+
function isLoopbackHost(hostname: string): boolean {
|
|
178
|
+
return hostname === '127.0.0.1' || hostname === '::1' || hostname === 'localhost';
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Check if the actual peer/remote address of a connection is from a
|
|
183
|
+
* private/internal network. Uses Bun's server.requestIP() to get the
|
|
184
|
+
* real peer address, which cannot be spoofed unlike the Origin header.
|
|
185
|
+
*
|
|
186
|
+
* Accepts loopback, RFC 1918 private IPv4, link-local, and RFC 4193
|
|
187
|
+
* unique-local IPv6 — including their IPv4-mapped IPv6 forms. This
|
|
188
|
+
* supports container/pod deployments (e.g. Kubernetes sidecars) where
|
|
189
|
+
* gateway and runtime communicate over pod-internal private IPs.
|
|
190
|
+
*/
|
|
191
|
+
function isPrivateNetworkPeer(server: { requestIP(req: Request): { address: string; family: string; port: number } | null }, req: Request): boolean {
|
|
192
|
+
const ip = server.requestIP(req);
|
|
193
|
+
if (!ip) return false;
|
|
194
|
+
return isPrivateAddress(ip.address);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* @internal Exported for testing.
|
|
199
|
+
*
|
|
200
|
+
* Determine whether an IP address string belongs to a private/internal
|
|
201
|
+
* network range:
|
|
202
|
+
* - Loopback: 127.0.0.0/8, ::1
|
|
203
|
+
* - RFC 1918: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16
|
|
204
|
+
* - Link-local: 169.254.0.0/16
|
|
205
|
+
* - IPv6 unique local: fc00::/7 (fc00::–fdff::)
|
|
206
|
+
* - IPv4-mapped IPv6 variants of all of the above (::ffff:x.x.x.x)
|
|
207
|
+
*/
|
|
208
|
+
export function isPrivateAddress(addr: string): boolean {
|
|
209
|
+
// Handle IPv4-mapped IPv6 (e.g. ::ffff:10.0.0.1) — extract the IPv4 part
|
|
210
|
+
const v4Mapped = addr.match(/^::ffff:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/i);
|
|
211
|
+
const normalized = v4Mapped ? v4Mapped[1] : addr;
|
|
212
|
+
|
|
213
|
+
// IPv4 checks
|
|
214
|
+
if (normalized.includes('.')) {
|
|
215
|
+
const parts = normalized.split('.').map(Number);
|
|
216
|
+
if (parts.length !== 4 || parts.some(p => isNaN(p) || p < 0 || p > 255)) return false;
|
|
217
|
+
|
|
218
|
+
// Loopback: 127.0.0.0/8
|
|
219
|
+
if (parts[0] === 127) return true;
|
|
220
|
+
// 10.0.0.0/8
|
|
221
|
+
if (parts[0] === 10) return true;
|
|
222
|
+
// 172.16.0.0/12 (172.16.x.x – 172.31.x.x)
|
|
223
|
+
if (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31) return true;
|
|
224
|
+
// 192.168.0.0/16
|
|
225
|
+
if (parts[0] === 192 && parts[1] === 168) return true;
|
|
226
|
+
// Link-local: 169.254.0.0/16
|
|
227
|
+
if (parts[0] === 169 && parts[1] === 254) return true;
|
|
228
|
+
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// IPv6 checks
|
|
233
|
+
const lower = normalized.toLowerCase();
|
|
234
|
+
// Loopback
|
|
235
|
+
if (lower === '::1') return true;
|
|
236
|
+
// Unique local: fc00::/7 (fc00:: through fdff::)
|
|
237
|
+
if (lower.startsWith('fc') || lower.startsWith('fd')) return true;
|
|
238
|
+
// Link-local: fe80::/10
|
|
239
|
+
if (lower.startsWith('fe80')) return true;
|
|
240
|
+
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Validate a Twilio webhook request's X-Twilio-Signature header.
|
|
246
|
+
*
|
|
247
|
+
* Returns the raw body text on success so callers can reconstruct the Request
|
|
248
|
+
* for downstream handlers (which also need to read the body).
|
|
249
|
+
* Returns a 403 Response if signature validation fails.
|
|
250
|
+
*
|
|
251
|
+
* Fail-closed: if the auth token is not configured, the request is rejected
|
|
252
|
+
* with 403 rather than silently skipping validation. An explicit local-dev
|
|
253
|
+
* bypass is available via TWILIO_WEBHOOK_VALIDATION_DISABLED=true.
|
|
254
|
+
*/
|
|
255
|
+
async function validateTwilioWebhook(
|
|
256
|
+
req: Request,
|
|
257
|
+
): Promise<{ body: string } | Response> {
|
|
258
|
+
const rawBody = await req.text();
|
|
259
|
+
|
|
260
|
+
// Allow explicit local-dev bypass — must be exactly "true"
|
|
261
|
+
if (process.env.TWILIO_WEBHOOK_VALIDATION_DISABLED === 'true') {
|
|
262
|
+
log.warn('Twilio webhook signature validation explicitly disabled via TWILIO_WEBHOOK_VALIDATION_DISABLED');
|
|
263
|
+
return { body: rawBody };
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const authToken = TwilioConversationRelayProvider.getAuthToken();
|
|
267
|
+
|
|
268
|
+
// Fail-closed: reject if no auth token is configured
|
|
269
|
+
if (!authToken) {
|
|
270
|
+
log.error('Twilio auth token not configured — rejecting webhook request (fail-closed)');
|
|
271
|
+
return Response.json({ error: 'Forbidden' }, { status: 403 });
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const signature = req.headers.get('x-twilio-signature');
|
|
275
|
+
if (!signature) {
|
|
276
|
+
log.warn('Twilio webhook request missing X-Twilio-Signature header');
|
|
277
|
+
return Response.json({ error: 'Forbidden' }, { status: 403 });
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Parse form-urlencoded body into key-value params for signature computation
|
|
281
|
+
const params: Record<string, string> = {};
|
|
282
|
+
const formData = new URLSearchParams(rawBody);
|
|
283
|
+
for (const [key, value] of formData.entries()) {
|
|
284
|
+
params[key] = value;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Reconstruct the public-facing URL that Twilio signed against.
|
|
288
|
+
// Behind proxies/gateways, req.url is the local server URL (e.g.
|
|
289
|
+
// http://127.0.0.1:7821/...) which differs from the public URL Twilio
|
|
290
|
+
// used to compute the HMAC-SHA1 signature.
|
|
291
|
+
let publicBaseUrl: string | undefined;
|
|
292
|
+
try {
|
|
293
|
+
publicBaseUrl = getPublicBaseUrl(loadConfig());
|
|
294
|
+
} catch {
|
|
295
|
+
// No webhook base URL configured — fall back to using req.url as-is
|
|
296
|
+
}
|
|
297
|
+
const parsedUrl = new URL(req.url);
|
|
298
|
+
const publicUrl = publicBaseUrl
|
|
299
|
+
? publicBaseUrl + parsedUrl.pathname + parsedUrl.search
|
|
300
|
+
: req.url;
|
|
301
|
+
|
|
302
|
+
const isValid = TwilioConversationRelayProvider.verifyWebhookSignature(
|
|
303
|
+
publicUrl,
|
|
304
|
+
params,
|
|
305
|
+
signature,
|
|
306
|
+
authToken,
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
if (!isValid) {
|
|
310
|
+
log.warn('Twilio webhook signature validation failed');
|
|
311
|
+
return Response.json({ error: 'Forbidden' }, { status: 403 });
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return { body: rawBody };
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Re-create a Request with the same method, headers, and URL but with a
|
|
319
|
+
* pre-read body string so downstream handlers can call req.text() again.
|
|
320
|
+
*/
|
|
321
|
+
function cloneRequestWithBody(original: Request, body: string): Request {
|
|
322
|
+
return new Request(original.url, {
|
|
323
|
+
method: original.method,
|
|
324
|
+
headers: original.headers,
|
|
325
|
+
body,
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export class RuntimeHttpServer {
|
|
330
|
+
private server: ReturnType<typeof Bun.serve> | null = null;
|
|
331
|
+
private port: number;
|
|
332
|
+
private hostname: string;
|
|
333
|
+
private bearerToken: string | undefined;
|
|
334
|
+
private processMessage?: MessageProcessor;
|
|
335
|
+
private persistAndProcessMessage?: NonBlockingMessageProcessor;
|
|
336
|
+
private runOrchestrator?: RunOrchestrator;
|
|
337
|
+
private interfacesDir: string | null;
|
|
338
|
+
private suggestionCache = new Map<string, string>();
|
|
339
|
+
private suggestionInFlight = new Map<string, Promise<string | null>>();
|
|
340
|
+
private retrySweepTimer: ReturnType<typeof setInterval> | null = null;
|
|
341
|
+
private sweepInProgress = false;
|
|
342
|
+
|
|
343
|
+
constructor(options: RuntimeHttpServerOptions = {}) {
|
|
344
|
+
this.port = options.port ?? DEFAULT_PORT;
|
|
345
|
+
this.hostname = options.hostname ?? DEFAULT_HOSTNAME;
|
|
346
|
+
this.bearerToken = options.bearerToken;
|
|
347
|
+
this.processMessage = options.processMessage;
|
|
348
|
+
this.persistAndProcessMessage = options.persistAndProcessMessage;
|
|
349
|
+
this.runOrchestrator = options.runOrchestrator;
|
|
350
|
+
this.interfacesDir = options.interfacesDir ?? null;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/** The port the server is actually listening on (resolved after start). */
|
|
354
|
+
get actualPort(): number {
|
|
355
|
+
return this.server?.port ?? this.port;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
async start(): Promise<void> {
|
|
359
|
+
this.server = Bun.serve<RelayWebSocketData>({
|
|
360
|
+
port: this.port,
|
|
361
|
+
hostname: this.hostname,
|
|
362
|
+
maxRequestBodySize: MAX_REQUEST_BODY_BYTES,
|
|
363
|
+
fetch: (req, server) => this.handleRequest(req, server),
|
|
364
|
+
websocket: {
|
|
365
|
+
open(ws) {
|
|
366
|
+
const callSessionId = ws.data?.callSessionId;
|
|
367
|
+
log.info({ callSessionId }, 'ConversationRelay WebSocket opened');
|
|
368
|
+
if (callSessionId) {
|
|
369
|
+
const connection = new RelayConnection(ws, callSessionId);
|
|
370
|
+
activeRelayConnections.set(callSessionId, connection);
|
|
371
|
+
}
|
|
372
|
+
},
|
|
373
|
+
message(ws, message) {
|
|
374
|
+
const callSessionId = ws.data?.callSessionId;
|
|
375
|
+
if (callSessionId) {
|
|
376
|
+
const connection = activeRelayConnections.get(callSessionId);
|
|
377
|
+
connection?.handleMessage(typeof message === 'string' ? message : new TextDecoder().decode(message));
|
|
378
|
+
}
|
|
379
|
+
},
|
|
380
|
+
close(ws, code, reason) {
|
|
381
|
+
const callSessionId = ws.data?.callSessionId;
|
|
382
|
+
log.info({ callSessionId, code, reason: reason?.toString() }, 'ConversationRelay WebSocket closed');
|
|
383
|
+
if (callSessionId) {
|
|
384
|
+
const connection = activeRelayConnections.get(callSessionId);
|
|
385
|
+
connection?.handleTransportClosed(code, reason?.toString());
|
|
386
|
+
connection?.destroy();
|
|
387
|
+
activeRelayConnections.delete(callSessionId);
|
|
388
|
+
}
|
|
389
|
+
},
|
|
390
|
+
},
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
// Sweep failed channel inbound events for retry every 30 seconds
|
|
394
|
+
if (this.processMessage) {
|
|
395
|
+
this.retrySweepTimer = setInterval(() => {
|
|
396
|
+
if (this.sweepInProgress) return;
|
|
397
|
+
this.sweepInProgress = true;
|
|
398
|
+
this.sweepFailedEvents().finally(() => { this.sweepInProgress = false; });
|
|
399
|
+
}, 30_000);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Startup guard: log gateway-only mode warnings
|
|
403
|
+
log.info('Running in gateway-only ingress mode. Direct webhook routes disabled.');
|
|
404
|
+
if (!isLoopbackHost(this.hostname)) {
|
|
405
|
+
log.warn('RUNTIME_HTTP_HOST is not bound to loopback. This may expose the runtime to direct public access.');
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
log.info({ port: this.actualPort, hostname: this.hostname, auth: !!this.bearerToken }, 'Runtime HTTP server listening');
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
async stop(): Promise<void> {
|
|
412
|
+
if (this.retrySweepTimer) {
|
|
413
|
+
clearInterval(this.retrySweepTimer);
|
|
414
|
+
this.retrySweepTimer = null;
|
|
415
|
+
}
|
|
416
|
+
if (this.server) {
|
|
417
|
+
this.server.stop(true);
|
|
418
|
+
this.server = null;
|
|
419
|
+
log.info('Runtime HTTP server stopped');
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Constant-time comparison of two bearer tokens to prevent timing attacks.
|
|
425
|
+
*/
|
|
426
|
+
private verifyToken(provided: string): boolean {
|
|
427
|
+
const expected = this.bearerToken!;
|
|
428
|
+
const a = Buffer.from(provided);
|
|
429
|
+
const b = Buffer.from(expected);
|
|
430
|
+
if (a.length !== b.length) return false;
|
|
431
|
+
return timingSafeEqual(a, b);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
private async handleRequest(req: Request, server: ReturnType<typeof Bun.serve>): Promise<Response> {
|
|
435
|
+
const url = new URL(req.url);
|
|
436
|
+
const path = url.pathname;
|
|
437
|
+
|
|
438
|
+
// Health checks are unauthenticated — they expose no sensitive data.
|
|
439
|
+
if (path === '/healthz' && req.method === 'GET') {
|
|
440
|
+
return this.handleHealth();
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// WebSocket upgrade for ConversationRelay — before auth check because
|
|
444
|
+
// Twilio WebSocket connections don't use bearer tokens.
|
|
445
|
+
if (path.startsWith('/v1/calls/relay') && req.headers.get('upgrade')?.toLowerCase() === 'websocket') {
|
|
446
|
+
// Only allow relay connections from private network peers.
|
|
447
|
+
// Primary check: actual peer address (cannot be spoofed) — accepts loopback
|
|
448
|
+
// and RFC 1918/4193 private addresses to support container deployments.
|
|
449
|
+
// Secondary check: Origin header (defense in depth).
|
|
450
|
+
if (!isPrivateNetworkPeer(server, req) || !isPrivateNetworkOrigin(req)) {
|
|
451
|
+
return Response.json(
|
|
452
|
+
{ error: 'Direct relay access disabled — only private network peers allowed', code: 'GATEWAY_ONLY' },
|
|
453
|
+
{ status: 403 },
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const wsUrl = new URL(req.url);
|
|
458
|
+
const callSessionId = wsUrl.searchParams.get('callSessionId');
|
|
459
|
+
if (!callSessionId) {
|
|
460
|
+
return new Response('Missing callSessionId', { status: 400 });
|
|
461
|
+
}
|
|
462
|
+
const upgraded = server.upgrade(req, { data: { callSessionId } });
|
|
463
|
+
if (!upgraded) {
|
|
464
|
+
return new Response('WebSocket upgrade failed', { status: 500 });
|
|
465
|
+
}
|
|
466
|
+
// Bun handles the response after a successful upgrade.
|
|
467
|
+
// The RelayConnection is created in the websocket.open handler.
|
|
468
|
+
return undefined as unknown as Response;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// ── Twilio webhook endpoints — before auth check because Twilio
|
|
472
|
+
// webhook POSTs don't include bearer tokens.
|
|
473
|
+
// Supports /v1/calls/twilio/*, /v1/assistants/:id/calls/twilio/*,
|
|
474
|
+
// and gateway-compatible /webhooks/twilio/* paths.
|
|
475
|
+
// Validates X-Twilio-Signature to prevent unauthorized access. ──
|
|
476
|
+
const twilioMatch = path.match(TWILIO_WEBHOOK_RE);
|
|
477
|
+
const gatewayTwilioMatch = !twilioMatch ? path.match(TWILIO_GATEWAY_WEBHOOK_RE) : null;
|
|
478
|
+
const resolvedTwilioSubpath = twilioMatch
|
|
479
|
+
? twilioMatch[1]
|
|
480
|
+
: gatewayTwilioMatch
|
|
481
|
+
? GATEWAY_SUBPATH_MAP[gatewayTwilioMatch[1]]
|
|
482
|
+
: null;
|
|
483
|
+
if (resolvedTwilioSubpath && req.method === 'POST') {
|
|
484
|
+
const twilioSubpath = resolvedTwilioSubpath;
|
|
485
|
+
|
|
486
|
+
// Block direct Twilio webhook routes — must go through the gateway
|
|
487
|
+
if (GATEWAY_ONLY_BLOCKED_SUBPATHS.has(twilioSubpath)) {
|
|
488
|
+
return Response.json(
|
|
489
|
+
{ error: 'Direct webhook access disabled. Use the gateway.', code: 'GATEWAY_ONLY' },
|
|
490
|
+
{ status: 410 },
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Validate Twilio request signature before dispatching
|
|
495
|
+
const validation = await validateTwilioWebhook(req);
|
|
496
|
+
if (validation instanceof Response) return validation;
|
|
497
|
+
|
|
498
|
+
// Reconstruct request so handlers can read the body
|
|
499
|
+
const validatedReq = cloneRequestWithBody(req, validation.body);
|
|
500
|
+
|
|
501
|
+
if (twilioSubpath === 'voice-webhook') {
|
|
502
|
+
return await handleVoiceWebhook(validatedReq);
|
|
503
|
+
}
|
|
504
|
+
if (twilioSubpath === 'status') {
|
|
505
|
+
return await handleStatusCallback(validatedReq);
|
|
506
|
+
}
|
|
507
|
+
if (twilioSubpath === 'connect-action') {
|
|
508
|
+
return await handleConnectAction(validatedReq);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// Require bearer token when configured
|
|
513
|
+
if ((process.env.DISABLE_HTTP_AUTH ?? "").toLowerCase() !== "true" && this.bearerToken) {
|
|
514
|
+
const authHeader = req.headers.get('authorization');
|
|
515
|
+
const token = authHeader?.startsWith('Bearer ') ? authHeader.slice(7) : null;
|
|
516
|
+
if (!token || !this.verifyToken(token)) {
|
|
517
|
+
return Response.json({ error: 'Unauthorized' }, { status: 401 });
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Serve shareable app pages
|
|
522
|
+
const pagesMatch = path.match(/^\/pages\/([^/]+)$/);
|
|
523
|
+
if (pagesMatch && req.method === 'GET') {
|
|
524
|
+
try {
|
|
525
|
+
return handleServePage(pagesMatch[1]);
|
|
526
|
+
} catch (err) {
|
|
527
|
+
log.error({ err, appId: pagesMatch[1] }, 'Runtime HTTP handler error serving page');
|
|
528
|
+
return Response.json({ error: 'Internal server error' }, { status: 500 });
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// ── Cloud sharing endpoints ───────────────────────────────────────
|
|
533
|
+
if (path === '/v1/apps/share' && req.method === 'POST') {
|
|
534
|
+
try {
|
|
535
|
+
return await handleShareApp(req);
|
|
536
|
+
} catch (err) {
|
|
537
|
+
log.error({ err }, 'Runtime HTTP handler error sharing app');
|
|
538
|
+
return Response.json({ error: 'Internal server error' }, { status: 500 });
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
const sharedTokenMatch = path.match(/^\/v1\/apps\/shared\/([^/]+)$/);
|
|
543
|
+
if (sharedTokenMatch) {
|
|
544
|
+
const shareToken = sharedTokenMatch[1];
|
|
545
|
+
if (req.method === 'GET') {
|
|
546
|
+
try {
|
|
547
|
+
return handleDownloadSharedApp(shareToken);
|
|
548
|
+
} catch (err) {
|
|
549
|
+
log.error({ err, shareToken }, 'Runtime HTTP handler error downloading shared app');
|
|
550
|
+
return Response.json({ error: 'Internal server error' }, { status: 500 });
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
if (req.method === 'DELETE') {
|
|
554
|
+
try {
|
|
555
|
+
return handleDeleteSharedApp(shareToken);
|
|
556
|
+
} catch (err) {
|
|
557
|
+
log.error({ err, shareToken }, 'Runtime HTTP handler error deleting shared app');
|
|
558
|
+
return Response.json({ error: 'Internal server error' }, { status: 500 });
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
const sharedMetadataMatch = path.match(/^\/v1\/apps\/shared\/([^/]+)\/metadata$/);
|
|
564
|
+
if (sharedMetadataMatch && req.method === 'GET') {
|
|
565
|
+
try {
|
|
566
|
+
return handleGetSharedAppMetadata(sharedMetadataMatch[1]);
|
|
567
|
+
} catch (err) {
|
|
568
|
+
log.error({ err, shareToken: sharedMetadataMatch[1] }, 'Runtime HTTP handler error getting shared app metadata');
|
|
569
|
+
return Response.json({ error: 'Internal server error' }, { status: 500 });
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// ── Secret management endpoint ─────────────────────────────────────
|
|
574
|
+
if (path === '/v1/secrets' && req.method === 'POST') {
|
|
575
|
+
try {
|
|
576
|
+
return await handleAddSecret(req);
|
|
577
|
+
} catch (err) {
|
|
578
|
+
log.error({ err }, 'Runtime HTTP handler error adding secret');
|
|
579
|
+
return Response.json({ error: 'Internal server error' }, { status: 500 });
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// New assistant-less runtime routes: /v1/<endpoint>
|
|
584
|
+
// These supersede the legacy /v1/assistants/:assistantId/... shape.
|
|
585
|
+
// Paths already handled above (/v1/apps/..., /v1/secrets) will never reach here.
|
|
586
|
+
const newRouteMatch = path.match(/^\/v1\/(?!assistants\/)(.+)$/);
|
|
587
|
+
if (newRouteMatch) {
|
|
588
|
+
return this.dispatchEndpoint(newRouteMatch[1], req, url);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// Legacy: /v1/assistants/:assistantId/<endpoint>
|
|
592
|
+
const match = path.match(/^\/v1\/assistants\/([^/]+)\/(.+)$/);
|
|
593
|
+
if (!match) {
|
|
594
|
+
return Response.json({ error: 'Not found', source: 'runtime' }, { status: 404 });
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
const assistantId = match[1];
|
|
598
|
+
const endpoint = match[2];
|
|
599
|
+
log.warn({ endpoint, assistantId }, '[deprecated] /v1/assistants/:assistantId/... route used; migrate to /v1/...');
|
|
600
|
+
return this.dispatchEndpoint(endpoint, req, url);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* Dispatch a request to the appropriate endpoint handler.
|
|
605
|
+
* Used by both the new assistant-less routes (/v1/<endpoint>) and the
|
|
606
|
+
* legacy assistant-scoped routes (/v1/assistants/:assistantId/<endpoint>).
|
|
607
|
+
*/
|
|
608
|
+
private async dispatchEndpoint(
|
|
609
|
+
endpoint: string,
|
|
610
|
+
req: Request,
|
|
611
|
+
url: URL,
|
|
612
|
+
): Promise<Response> {
|
|
613
|
+
try {
|
|
614
|
+
if (endpoint === 'health' && req.method === 'GET') {
|
|
615
|
+
return this.handleHealth();
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
if (endpoint === 'conversations' && req.method === 'GET') {
|
|
619
|
+
const limit = Number(url.searchParams.get('limit') ?? 50);
|
|
620
|
+
const conversations = conversationStore.listConversations(limit);
|
|
621
|
+
const bindings = externalConversationStore.getBindingsForConversations(
|
|
622
|
+
conversations.map((c) => c.id),
|
|
623
|
+
);
|
|
624
|
+
return Response.json({
|
|
625
|
+
sessions: conversations.map((c) => {
|
|
626
|
+
const binding = bindings.get(c.id);
|
|
627
|
+
return {
|
|
628
|
+
id: c.id,
|
|
629
|
+
title: c.title ?? 'Untitled',
|
|
630
|
+
updatedAt: c.updatedAt,
|
|
631
|
+
threadType: c.threadType === 'private' ? 'private' : 'standard',
|
|
632
|
+
...(binding ? {
|
|
633
|
+
channelBinding: {
|
|
634
|
+
sourceChannel: binding.sourceChannel,
|
|
635
|
+
externalChatId: binding.externalChatId,
|
|
636
|
+
externalUserId: binding.externalUserId,
|
|
637
|
+
displayName: binding.displayName,
|
|
638
|
+
username: binding.username,
|
|
639
|
+
},
|
|
640
|
+
} : {}),
|
|
641
|
+
};
|
|
642
|
+
}),
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
if (endpoint === 'messages' && req.method === 'GET') {
|
|
647
|
+
return handleListMessages(url, this.interfacesDir);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
if (endpoint === 'messages' && req.method === 'POST') {
|
|
651
|
+
return await handleSendMessage(req, {
|
|
652
|
+
processMessage: this.processMessage,
|
|
653
|
+
persistAndProcessMessage: this.persistAndProcessMessage,
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
if (endpoint === 'attachments' && req.method === 'POST') {
|
|
658
|
+
return await handleUploadAttachment(req);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
if (endpoint === 'attachments' && req.method === 'DELETE') {
|
|
662
|
+
return await handleDeleteAttachment(req);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// Match attachments/:attachmentId
|
|
666
|
+
const attachmentMatch = endpoint.match(/^attachments\/([^/]+)$/);
|
|
667
|
+
if (attachmentMatch && req.method === 'GET') {
|
|
668
|
+
return handleGetAttachment(attachmentMatch[1]);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
if (endpoint === 'suggestion' && req.method === 'GET') {
|
|
672
|
+
return await handleGetSuggestion(url, {
|
|
673
|
+
suggestionCache: this.suggestionCache,
|
|
674
|
+
suggestionInFlight: this.suggestionInFlight,
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
if (endpoint === 'runs' && req.method === 'POST') {
|
|
679
|
+
if (!this.runOrchestrator) {
|
|
680
|
+
return Response.json({ error: 'Run orchestration not configured' }, { status: 503 });
|
|
681
|
+
}
|
|
682
|
+
return await handleCreateRun(req, this.runOrchestrator);
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// Match runs/:runId, runs/:runId/decision, runs/:runId/trust-rule, runs/:runId/secret
|
|
686
|
+
const runsMatch = endpoint.match(/^runs\/([^/]+)(\/decision|\/trust-rule|\/secret)?$/);
|
|
687
|
+
if (runsMatch) {
|
|
688
|
+
if (!this.runOrchestrator) {
|
|
689
|
+
return Response.json({ error: 'Run orchestration not configured' }, { status: 503 });
|
|
690
|
+
}
|
|
691
|
+
const runId = runsMatch[1];
|
|
692
|
+
if (runsMatch[2] === '/decision' && req.method === 'POST') {
|
|
693
|
+
return await handleRunDecision(runId, req, this.runOrchestrator);
|
|
694
|
+
}
|
|
695
|
+
if (runsMatch[2] === '/secret' && req.method === 'POST') {
|
|
696
|
+
return await handleRunSecret(runId, req, this.runOrchestrator);
|
|
697
|
+
}
|
|
698
|
+
if (runsMatch[2] === '/trust-rule' && req.method === 'POST') {
|
|
699
|
+
const run = this.runOrchestrator.getRun(runId);
|
|
700
|
+
if (!run) {
|
|
701
|
+
return Response.json({ error: 'Run not found' }, { status: 404 });
|
|
702
|
+
}
|
|
703
|
+
return await handleAddTrustRule(runId, req);
|
|
704
|
+
}
|
|
705
|
+
if (req.method === 'GET') {
|
|
706
|
+
return handleGetRun(runId, this.runOrchestrator);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
const interfacesMatch = endpoint.match(/^interfaces\/(.+)$/);
|
|
711
|
+
if (interfacesMatch && req.method === 'GET') {
|
|
712
|
+
return this.handleGetInterface(interfacesMatch[1]);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
if (endpoint === 'channels/conversation' && req.method === 'DELETE') {
|
|
716
|
+
return await handleDeleteConversation(req);
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
if (endpoint === 'channels/inbound' && req.method === 'POST') {
|
|
720
|
+
return await handleChannelInbound(req, this.processMessage, this.bearerToken, this.runOrchestrator);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
if (endpoint === 'channels/delivery-ack' && req.method === 'POST') {
|
|
724
|
+
return await handleChannelDeliveryAck(req);
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
if (endpoint === 'channels/dead-letters' && req.method === 'GET') {
|
|
728
|
+
return handleListDeadLetters();
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
if (endpoint === 'channels/replay' && req.method === 'POST') {
|
|
732
|
+
return await handleReplayDeadLetters(req);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// ── Call API routes ───────────────────────────────────────────
|
|
736
|
+
if (endpoint === 'calls/start' && req.method === 'POST') {
|
|
737
|
+
return await handleStartCall(req);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// Match calls/:callSessionId and calls/:callSessionId/cancel, calls/:callSessionId/answer, calls/:callSessionId/instruction
|
|
741
|
+
const callsMatch = endpoint.match(/^calls\/([^/]+?)(\/cancel|\/answer|\/instruction)?$/);
|
|
742
|
+
if (callsMatch) {
|
|
743
|
+
const callSessionId = callsMatch[1];
|
|
744
|
+
// Skip known sub-paths that are handled elsewhere (twilio, relay)
|
|
745
|
+
if (callSessionId !== 'twilio' && callSessionId !== 'relay' && callSessionId !== 'start') {
|
|
746
|
+
if (callsMatch[2] === '/cancel' && req.method === 'POST') {
|
|
747
|
+
return await handleCancelCall(req, callSessionId);
|
|
748
|
+
}
|
|
749
|
+
if (callsMatch[2] === '/answer' && req.method === 'POST') {
|
|
750
|
+
return await handleAnswerCall(req, callSessionId);
|
|
751
|
+
}
|
|
752
|
+
if (callsMatch[2] === '/instruction' && req.method === 'POST') {
|
|
753
|
+
return await handleInstructionCall(req, callSessionId);
|
|
754
|
+
}
|
|
755
|
+
if (!callsMatch[2] && req.method === 'GET') {
|
|
756
|
+
return handleGetCallStatus(callSessionId);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// ── Internal Twilio forwarding endpoints (gateway → runtime) ──
|
|
762
|
+
// These accept JSON payloads from the gateway (which already validated
|
|
763
|
+
// the Twilio signature) and reconstruct requests for the existing
|
|
764
|
+
// Twilio route handlers.
|
|
765
|
+
if (endpoint === 'internal/twilio/voice-webhook' && req.method === 'POST') {
|
|
766
|
+
const json = await req.json() as { params: Record<string, string>; originalUrl?: string };
|
|
767
|
+
const formBody = new URLSearchParams(json.params).toString();
|
|
768
|
+
// Reconstruct request URL: keep the original URL query string (callSessionId)
|
|
769
|
+
const reconstructedUrl = json.originalUrl ?? req.url;
|
|
770
|
+
const fakeReq = new Request(reconstructedUrl, {
|
|
771
|
+
method: 'POST',
|
|
772
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
773
|
+
body: formBody,
|
|
774
|
+
});
|
|
775
|
+
return await handleVoiceWebhook(fakeReq);
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
if (endpoint === 'internal/twilio/status' && req.method === 'POST') {
|
|
779
|
+
const json = await req.json() as { params: Record<string, string> };
|
|
780
|
+
const formBody = new URLSearchParams(json.params).toString();
|
|
781
|
+
const fakeReq = new Request(req.url, {
|
|
782
|
+
method: 'POST',
|
|
783
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
784
|
+
body: formBody,
|
|
785
|
+
});
|
|
786
|
+
return await handleStatusCallback(fakeReq);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
if (endpoint === 'internal/twilio/connect-action' && req.method === 'POST') {
|
|
790
|
+
const json = await req.json() as { params: Record<string, string> };
|
|
791
|
+
const formBody = new URLSearchParams(json.params).toString();
|
|
792
|
+
const fakeReq = new Request(req.url, {
|
|
793
|
+
method: 'POST',
|
|
794
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
795
|
+
body: formBody,
|
|
796
|
+
});
|
|
797
|
+
return await handleConnectAction(fakeReq);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
if (endpoint === 'identity' && req.method === 'GET') {
|
|
801
|
+
return this.handleGetIdentity();
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
if (endpoint === 'events' && req.method === 'GET') {
|
|
805
|
+
return handleSubscribeAssistantEvents(req, url);
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
// ── Internal OAuth callback endpoint (gateway → runtime) ──
|
|
809
|
+
if (endpoint === 'internal/oauth/callback' && req.method === 'POST') {
|
|
810
|
+
const json = await req.json() as { state: string; code?: string; error?: string };
|
|
811
|
+
if (!json.state) {
|
|
812
|
+
return Response.json({ error: 'Missing state parameter' }, { status: 400 });
|
|
813
|
+
}
|
|
814
|
+
if (json.error) {
|
|
815
|
+
const consumed = consumeCallbackError(json.state, json.error);
|
|
816
|
+
return consumed
|
|
817
|
+
? Response.json({ ok: true })
|
|
818
|
+
: Response.json({ error: 'Unknown state' }, { status: 404 });
|
|
819
|
+
}
|
|
820
|
+
if (json.code) {
|
|
821
|
+
const consumed = consumeCallback(json.state, json.code);
|
|
822
|
+
return consumed
|
|
823
|
+
? Response.json({ ok: true })
|
|
824
|
+
: Response.json({ error: 'Unknown state' }, { status: 404 });
|
|
825
|
+
}
|
|
826
|
+
return Response.json({ error: 'Missing code or error parameter' }, { status: 400 });
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
return Response.json({ error: 'Not found', source: 'runtime' }, { status: 404 });
|
|
830
|
+
} catch (err) {
|
|
831
|
+
if (err instanceof IngressBlockedError) {
|
|
832
|
+
log.warn({ endpoint, detectedTypes: err.detectedTypes }, 'Blocked HTTP request containing secrets');
|
|
833
|
+
return Response.json({ error: err.message, code: err.code }, { status: 422 });
|
|
834
|
+
}
|
|
835
|
+
if (err instanceof ConfigError) {
|
|
836
|
+
log.warn({ err, endpoint }, 'Runtime HTTP config error');
|
|
837
|
+
return Response.json({ error: err.message, code: err.code }, { status: 422 });
|
|
838
|
+
}
|
|
839
|
+
log.error({ err, endpoint }, 'Runtime HTTP handler error');
|
|
840
|
+
const message = err instanceof Error ? err.message : 'Internal server error';
|
|
841
|
+
return Response.json({ error: message }, { status: 500 });
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
/**
|
|
846
|
+
* Periodically retry failed channel inbound events that have passed
|
|
847
|
+
* their exponential backoff delay.
|
|
848
|
+
*/
|
|
849
|
+
private async sweepFailedEvents(): Promise<void> {
|
|
850
|
+
if (!this.processMessage) return;
|
|
851
|
+
|
|
852
|
+
const events = channelDeliveryStore.getRetryableEvents();
|
|
853
|
+
if (events.length === 0) return;
|
|
854
|
+
|
|
855
|
+
log.info({ count: events.length }, 'Retrying failed channel inbound events');
|
|
856
|
+
|
|
857
|
+
for (const event of events) {
|
|
858
|
+
if (!event.rawPayload) {
|
|
859
|
+
// No payload stored — can't replay, move to dead letter
|
|
860
|
+
channelDeliveryStore.recordProcessingFailure(
|
|
861
|
+
event.id,
|
|
862
|
+
new Error('No raw payload stored for replay'),
|
|
863
|
+
);
|
|
864
|
+
continue;
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
let payload: Record<string, unknown>;
|
|
868
|
+
try {
|
|
869
|
+
payload = JSON.parse(event.rawPayload) as Record<string, unknown>;
|
|
870
|
+
} catch {
|
|
871
|
+
channelDeliveryStore.recordProcessingFailure(
|
|
872
|
+
event.id,
|
|
873
|
+
new Error('Failed to parse stored raw payload'),
|
|
874
|
+
);
|
|
875
|
+
continue;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
const content = typeof payload.content === 'string' ? payload.content.trim() : '';
|
|
879
|
+
const attachmentIds = Array.isArray(payload.attachmentIds) ? payload.attachmentIds as string[] : undefined;
|
|
880
|
+
const sourceChannel = payload.sourceChannel as string;
|
|
881
|
+
const sourceMetadata = payload.sourceMetadata as Record<string, unknown> | undefined;
|
|
882
|
+
|
|
883
|
+
const metadataHintsRaw = sourceMetadata?.hints;
|
|
884
|
+
const metadataHints = Array.isArray(metadataHintsRaw)
|
|
885
|
+
? metadataHintsRaw.filter((h): h is string => typeof h === 'string' && h.trim().length > 0)
|
|
886
|
+
: [];
|
|
887
|
+
const metadataUxBrief = typeof sourceMetadata?.uxBrief === 'string' && sourceMetadata.uxBrief.trim().length > 0
|
|
888
|
+
? sourceMetadata.uxBrief.trim()
|
|
889
|
+
: undefined;
|
|
890
|
+
|
|
891
|
+
try {
|
|
892
|
+
const { messageId: userMessageId } = await this.processMessage(
|
|
893
|
+
event.conversationId,
|
|
894
|
+
content,
|
|
895
|
+
attachmentIds,
|
|
896
|
+
{
|
|
897
|
+
transport: {
|
|
898
|
+
channelId: sourceChannel,
|
|
899
|
+
hints: metadataHints.length > 0 ? metadataHints : undefined,
|
|
900
|
+
uxBrief: metadataUxBrief,
|
|
901
|
+
},
|
|
902
|
+
},
|
|
903
|
+
);
|
|
904
|
+
channelDeliveryStore.linkMessage(event.id, userMessageId);
|
|
905
|
+
channelDeliveryStore.markProcessed(event.id);
|
|
906
|
+
log.info({ eventId: event.id }, 'Successfully replayed failed channel event');
|
|
907
|
+
|
|
908
|
+
const replyCallbackUrl = typeof payload.replyCallbackUrl === 'string'
|
|
909
|
+
? payload.replyCallbackUrl
|
|
910
|
+
: undefined;
|
|
911
|
+
if (replyCallbackUrl) {
|
|
912
|
+
const externalChatId = typeof payload.externalChatId === 'string'
|
|
913
|
+
? payload.externalChatId
|
|
914
|
+
: undefined;
|
|
915
|
+
if (externalChatId) {
|
|
916
|
+
await this.deliverReplyViaCallback(event.conversationId, externalChatId, replyCallbackUrl);
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
} catch (err) {
|
|
920
|
+
log.error({ err, eventId: event.id }, 'Retry failed for channel event');
|
|
921
|
+
channelDeliveryStore.recordProcessingFailure(event.id, err);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
private async deliverReplyViaCallback(
|
|
927
|
+
conversationId: string,
|
|
928
|
+
externalChatId: string,
|
|
929
|
+
callbackUrl: string,
|
|
930
|
+
): Promise<void> {
|
|
931
|
+
const msgs = conversationStore.getMessages(conversationId);
|
|
932
|
+
for (let i = msgs.length - 1; i >= 0; i--) {
|
|
933
|
+
if (msgs[i].role === 'assistant') {
|
|
934
|
+
let parsed: unknown;
|
|
935
|
+
try { parsed = JSON.parse(msgs[i].content); } catch { parsed = msgs[i].content; }
|
|
936
|
+
const rendered = renderHistoryContent(parsed);
|
|
937
|
+
|
|
938
|
+
const linked = attachmentsStore.getAttachmentMetadataForMessage(msgs[i].id);
|
|
939
|
+
const replyAttachments = linked.map((a) => ({
|
|
940
|
+
id: a.id,
|
|
941
|
+
filename: a.originalFilename,
|
|
942
|
+
mimeType: a.mimeType,
|
|
943
|
+
sizeBytes: a.sizeBytes,
|
|
944
|
+
kind: a.kind,
|
|
945
|
+
}));
|
|
946
|
+
|
|
947
|
+
if (rendered.text || replyAttachments.length > 0) {
|
|
948
|
+
await deliverChannelReply(callbackUrl, {
|
|
949
|
+
chatId: externalChatId,
|
|
950
|
+
text: rendered.text || undefined,
|
|
951
|
+
attachments: replyAttachments.length > 0 ? replyAttachments : undefined,
|
|
952
|
+
}, this.bearerToken);
|
|
953
|
+
}
|
|
954
|
+
break;
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
private handleGetIdentity(): Response {
|
|
960
|
+
const identityPath = getWorkspacePromptPath('IDENTITY.md');
|
|
961
|
+
if (!existsSync(identityPath)) {
|
|
962
|
+
return Response.json({ error: 'IDENTITY.md not found' }, { status: 404 });
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
const content = readFileSync(identityPath, 'utf-8');
|
|
966
|
+
const fields: Record<string, string> = {};
|
|
967
|
+
for (const line of content.split('\n')) {
|
|
968
|
+
const trimmed = line.trim();
|
|
969
|
+
const lower = trimmed.toLowerCase();
|
|
970
|
+
const extract = (prefix: string): string | null => {
|
|
971
|
+
if (!lower.startsWith(prefix)) return null;
|
|
972
|
+
return trimmed.split(':**').pop()?.trim() ?? null;
|
|
973
|
+
};
|
|
974
|
+
|
|
975
|
+
const name = extract('- **name:**');
|
|
976
|
+
if (name) { fields.name = name; continue; }
|
|
977
|
+
const role = extract('- **role:**');
|
|
978
|
+
if (role) { fields.role = role; continue; }
|
|
979
|
+
const personality = extract('- **personality:**') ?? extract('- **vibe:**');
|
|
980
|
+
if (personality) { fields.personality = personality; continue; }
|
|
981
|
+
const emoji = extract('- **emoji:**');
|
|
982
|
+
if (emoji) { fields.emoji = emoji; continue; }
|
|
983
|
+
const home = extract('- **home:**');
|
|
984
|
+
if (home) { fields.home = home; continue; }
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
// Read version from package.json
|
|
988
|
+
let version: string | undefined;
|
|
989
|
+
try {
|
|
990
|
+
const pkgPath = join(dirname(fileURLToPath(import.meta.url)), '../../package.json');
|
|
991
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
992
|
+
version = pkg.version;
|
|
993
|
+
} catch {
|
|
994
|
+
// ignore
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
// Read createdAt from IDENTITY.md file birthtime
|
|
998
|
+
let createdAt: string | undefined;
|
|
999
|
+
try {
|
|
1000
|
+
const stats = statSync(identityPath);
|
|
1001
|
+
createdAt = stats.birthtime.toISOString();
|
|
1002
|
+
} catch {
|
|
1003
|
+
// ignore
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
// Read lockfile for assistantId, cloud, and originSystem
|
|
1007
|
+
let assistantId: string | undefined;
|
|
1008
|
+
let cloud: string | undefined;
|
|
1009
|
+
let originSystem: string | undefined;
|
|
1010
|
+
try {
|
|
1011
|
+
const homedir = process.env.HOME ?? process.env.USERPROFILE ?? '';
|
|
1012
|
+
const lockfilePaths = [
|
|
1013
|
+
join(homedir, '.vellum.lock.json'),
|
|
1014
|
+
join(homedir, '.vellum.lockfile.json'),
|
|
1015
|
+
];
|
|
1016
|
+
for (const lockPath of lockfilePaths) {
|
|
1017
|
+
if (!existsSync(lockPath)) continue;
|
|
1018
|
+
const lockData = JSON.parse(readFileSync(lockPath, 'utf-8'));
|
|
1019
|
+
const assistants = lockData.assistants as Array<Record<string, unknown>> | undefined;
|
|
1020
|
+
if (assistants && assistants.length > 0) {
|
|
1021
|
+
// Use the most recently hatched assistant
|
|
1022
|
+
const sorted = [...assistants].sort((a, b) => {
|
|
1023
|
+
const dateA = new Date(a.hatchedAt as string || 0).getTime();
|
|
1024
|
+
const dateB = new Date(b.hatchedAt as string || 0).getTime();
|
|
1025
|
+
return dateB - dateA;
|
|
1026
|
+
});
|
|
1027
|
+
const latest = sorted[0];
|
|
1028
|
+
assistantId = latest.assistantId as string | undefined;
|
|
1029
|
+
cloud = latest.cloud as string | undefined;
|
|
1030
|
+
originSystem = cloud === 'local' ? 'local' : cloud;
|
|
1031
|
+
}
|
|
1032
|
+
break;
|
|
1033
|
+
}
|
|
1034
|
+
} catch {
|
|
1035
|
+
// ignore — lockfile may not exist
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
return Response.json({
|
|
1039
|
+
name: fields.name ?? '',
|
|
1040
|
+
role: fields.role ?? '',
|
|
1041
|
+
personality: fields.personality ?? '',
|
|
1042
|
+
emoji: fields.emoji ?? '',
|
|
1043
|
+
home: fields.home ?? '',
|
|
1044
|
+
version,
|
|
1045
|
+
assistantId,
|
|
1046
|
+
createdAt,
|
|
1047
|
+
originSystem,
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
private handleHealth(): Response {
|
|
1052
|
+
return Response.json({
|
|
1053
|
+
status: 'healthy',
|
|
1054
|
+
timestamp: new Date().toISOString(),
|
|
1055
|
+
disk: getDiskSpaceInfo(),
|
|
1056
|
+
});
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
private handleGetInterface(interfacePath: string): Response {
|
|
1060
|
+
if (!this.interfacesDir) {
|
|
1061
|
+
return Response.json({ error: 'Interface not found' }, { status: 404 });
|
|
1062
|
+
}
|
|
1063
|
+
const fullPath = resolve(this.interfacesDir, interfacePath);
|
|
1064
|
+
// Enforce directory boundary so prefix-sibling paths (e.g. "interfaces-other/") are rejected
|
|
1065
|
+
if (
|
|
1066
|
+
(fullPath !== this.interfacesDir && !fullPath.startsWith(this.interfacesDir + '/')) ||
|
|
1067
|
+
!existsSync(fullPath)
|
|
1068
|
+
) {
|
|
1069
|
+
return Response.json({ error: 'Interface not found' }, { status: 404 });
|
|
1070
|
+
}
|
|
1071
|
+
const source = readFileSync(fullPath, 'utf-8');
|
|
1072
|
+
return new Response(source, {
|
|
1073
|
+
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
1076
|
+
}
|