@vellumai/assistant 0.3.4 → 0.3.6
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/Dockerfile +2 -0
- package/README.md +88 -2
- package/eslint.config.mjs +31 -0
- package/package.json +1 -1
- package/scripts/ipc/check-swift-decoder-drift.ts +4 -1
- package/scripts/ipc/generate-swift.ts +31 -2
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +438 -1
- package/src/__tests__/approval-conversation-turn.test.ts +214 -0
- package/src/__tests__/approval-hardcoded-copy-guard.test.ts +41 -0
- package/src/__tests__/approval-message-composer.test.ts +253 -0
- package/src/__tests__/browser-manager.test.ts +1 -0
- package/src/__tests__/call-conversation-messages.test.ts +130 -0
- package/src/__tests__/call-domain.test.ts +12 -2
- package/src/__tests__/call-orchestrator.test.ts +799 -249
- package/src/__tests__/call-pointer-messages.test.ts +148 -0
- package/src/__tests__/call-recovery.test.ts +3 -0
- package/src/__tests__/call-routes-http.test.ts +32 -2
- package/src/__tests__/call-store.test.ts +3 -0
- package/src/__tests__/channel-approval-routes.test.ts +1277 -98
- package/src/__tests__/channel-approval.test.ts +37 -0
- package/src/__tests__/channel-approvals.test.ts +36 -50
- package/src/__tests__/channel-guardian.test.ts +630 -22
- package/src/__tests__/channel-readiness-service.test.ts +324 -0
- package/src/__tests__/checker.test.ts +14 -7
- package/src/__tests__/clarification-resolver.test.ts +44 -24
- package/src/__tests__/commit-message-enrichment-service.test.ts +9 -4
- package/src/__tests__/computer-use-session-working-dir.test.ts +8 -0
- package/src/__tests__/config-schema.test.ts +14 -8
- package/src/__tests__/context-window-manager.test.ts +30 -2
- package/src/__tests__/contradiction-checker.test.ts +20 -5
- package/src/__tests__/credential-security-invariants.test.ts +7 -2
- package/src/__tests__/daemon-lifecycle.test.ts +13 -12
- package/src/__tests__/db-migration-rollback.test.ts +752 -0
- package/src/__tests__/dictation-mode-detection.test.ts +63 -0
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +2 -0
- package/src/__tests__/entity-search.test.ts +615 -0
- package/src/__tests__/fuzzy-match-property.test.ts +5 -5
- package/src/__tests__/guardian-action-store.test.ts +123 -0
- package/src/__tests__/guardian-action-sweep.test.ts +277 -0
- package/src/__tests__/guardian-dispatch.test.ts +389 -0
- package/src/__tests__/guardian-question-copy.test.ts +47 -0
- package/src/__tests__/handlers-telegram-config.test.ts +4 -2
- package/src/__tests__/handlers-twilio-config.test.ts +533 -0
- package/src/__tests__/intent-routing.test.ts +2 -0
- package/src/__tests__/ipc-snapshot.test.ts +291 -1
- package/src/__tests__/memory-upsert-concurrency.test.ts +828 -0
- package/src/__tests__/messaging-send-tool.test.ts +65 -0
- package/src/__tests__/model-intents.test.ts +96 -0
- package/src/__tests__/no-direct-anthropic-sdk-imports.test.ts +42 -0
- package/src/__tests__/oauth2-gateway-transport.test.ts +130 -0
- package/src/__tests__/onboarding-starter-tasks.test.ts +2 -0
- package/src/__tests__/provider-commit-message-generator.test.ts +89 -13
- package/src/__tests__/provider-error-scenarios.test.ts +621 -0
- package/src/__tests__/provider-fail-open-selection.test.ts +119 -0
- package/src/__tests__/qdrant-manager.test.ts +27 -20
- package/src/__tests__/relay-server.test.ts +779 -40
- package/src/__tests__/run-orchestrator-assistant-events.test.ts +6 -0
- package/src/__tests__/run-orchestrator.test.ts +42 -4
- package/src/__tests__/runtime-runs-http.test.ts +17 -1
- package/src/__tests__/runtime-runs.test.ts +16 -0
- package/src/__tests__/schedule-store.test.ts +18 -4
- package/src/__tests__/scheduler-recurrence.test.ts +13 -4
- package/src/__tests__/session-abort-tool-results.test.ts +6 -0
- package/src/__tests__/session-agent-loop.test.ts +857 -0
- package/src/__tests__/session-conflict-gate.test.ts +6 -0
- package/src/__tests__/session-pre-run-repair.test.ts +6 -0
- package/src/__tests__/session-profile-injection.test.ts +6 -0
- package/src/__tests__/session-provider-retry-repair.test.ts +6 -0
- package/src/__tests__/session-queue.test.ts +6 -0
- package/src/__tests__/session-runtime-assembly.test.ts +321 -13
- package/src/__tests__/session-slash-known.test.ts +6 -0
- package/src/__tests__/session-slash-queue.test.ts +6 -0
- package/src/__tests__/session-slash-unknown.test.ts +6 -0
- package/src/__tests__/session-surfaces-task-progress.test.ts +2 -0
- package/src/__tests__/session-tool-setup-app-refresh.test.ts +1 -0
- package/src/__tests__/session-tool-setup-memory-scope.test.ts +1 -0
- package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +1 -0
- package/src/__tests__/session-workspace-injection.test.ts +6 -0
- package/src/__tests__/session-workspace-tool-tracking.test.ts +6 -0
- package/src/__tests__/skills.test.ts +2 -0
- package/src/__tests__/sms-messaging-provider.test.ts +126 -0
- package/src/__tests__/starter-task-flow.test.ts +2 -0
- package/src/__tests__/swarm-dag-pathological.test.ts +535 -0
- package/src/__tests__/system-prompt.test.ts +2 -0
- package/src/__tests__/task-management-tools.test.ts +2 -2
- package/src/__tests__/task-runner.test.ts +14 -4
- package/src/__tests__/terminal-tools.test.ts +25 -19
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +545 -0
- package/src/__tests__/tool-executor-shell-integration.test.ts +11 -11
- package/src/__tests__/tool-executor.test.ts +23 -24
- package/src/__tests__/trust-store.test.ts +3 -3
- package/src/__tests__/twilio-rest.test.ts +29 -0
- package/src/__tests__/twilio-routes-elevenlabs.test.ts +3 -0
- package/src/__tests__/twilio-routes-twiml.test.ts +11 -0
- package/src/__tests__/twilio-routes.test.ts +167 -11
- package/src/__tests__/twitter-cli-error-shaping.test.ts +2 -2
- package/src/__tests__/user-reference.test.ts +2 -0
- package/src/__tests__/voice-quality.test.ts +222 -0
- package/src/__tests__/web-search.test.ts +46 -30
- package/src/__tests__/work-item-output.test.ts +110 -0
- package/src/agent/loop.ts +1 -1
- package/src/agent-heartbeat/agent-heartbeat-service.ts +2 -10
- package/src/amazon/client.ts +1418 -0
- package/src/amazon/request-extractor.ts +135 -0
- package/src/amazon/session.ts +109 -0
- package/src/autonomy/autonomy-store.ts +5 -5
- package/src/browser-extension-relay/client.ts +124 -0
- package/src/browser-extension-relay/protocol.ts +63 -0
- package/src/browser-extension-relay/server.ts +177 -0
- package/src/bundler/app-bundler.ts +3 -3
- package/src/bundler/bundle-signer.ts +1 -1
- package/src/bundler/signature-verifier.ts +1 -1
- package/src/calls/call-conversation-messages.ts +33 -0
- package/src/calls/call-domain.ts +114 -10
- package/src/calls/call-orchestrator.ts +268 -59
- package/src/calls/call-pointer-messages.ts +53 -0
- package/src/calls/call-recovery.ts +3 -8
- package/src/calls/call-store.ts +69 -87
- package/src/calls/elevenlabs-config.ts +3 -2
- package/src/calls/guardian-action-sweep.ts +105 -0
- package/src/calls/guardian-dispatch.ts +203 -0
- package/src/calls/guardian-question-copy.ts +133 -0
- package/src/calls/relay-server.ts +466 -8
- package/src/calls/speaker-identification.ts +1 -1
- package/src/calls/twilio-config.ts +22 -14
- package/src/calls/twilio-provider.ts +6 -4
- package/src/calls/twilio-rest.ts +308 -7
- package/src/calls/twilio-routes.ts +65 -12
- package/src/calls/types.ts +3 -1
- package/src/channels/types.ts +25 -0
- package/src/cli/amazon.ts +815 -0
- package/src/cli/config-commands.ts +2 -2
- package/src/cli/core-commands.ts +4 -3
- package/src/cli/influencer.ts +244 -0
- package/src/cli/map.ts +89 -6
- package/src/cli.ts +1 -1
- package/src/config/agent-schema.ts +171 -0
- package/src/config/bundled-skills/amazon/SKILL.md +127 -0
- package/src/config/bundled-skills/amazon/icon.svg +13 -0
- package/src/config/bundled-skills/api-mapping/SKILL.md +78 -0
- package/src/config/bundled-skills/browser/SKILL.md +1 -0
- package/src/config/bundled-skills/browser/TOOLS.json +17 -0
- package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +25 -0
- package/src/config/bundled-skills/doordash/SKILL.md +51 -51
- package/src/config/bundled-skills/email-setup/SKILL.md +14 -5
- package/src/config/bundled-skills/google-oauth-setup/SKILL.md +183 -0
- package/src/config/bundled-skills/influencer/SKILL.md +144 -0
- package/src/config/bundled-skills/knowledge-graph/SKILL.md +15 -0
- package/src/config/bundled-skills/knowledge-graph/TOOLS.json +56 -0
- package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +185 -0
- package/src/config/bundled-skills/macos-automation/icon.svg +12 -0
- package/src/config/bundled-skills/media-processing/SKILL.md +176 -0
- package/src/config/bundled-skills/media-processing/TOOLS.json +230 -0
- package/src/config/bundled-skills/media-processing/__tests__/concurrency-pool.test.ts +77 -0
- package/src/config/bundled-skills/media-processing/__tests__/cost-tracker.test.ts +69 -0
- package/src/config/bundled-skills/media-processing/__tests__/preprocess.test.ts +303 -0
- package/src/config/bundled-skills/media-processing/services/concurrency-pool.ts +55 -0
- package/src/config/bundled-skills/media-processing/services/cost-tracker.ts +86 -0
- package/src/config/bundled-skills/media-processing/services/gemini-map.ts +339 -0
- package/src/config/bundled-skills/media-processing/services/preprocess.ts +551 -0
- package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +259 -0
- package/src/config/bundled-skills/media-processing/services/reduce.ts +197 -0
- package/src/config/bundled-skills/media-processing/tools/analyze-keyframes.ts +136 -0
- package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +59 -0
- package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +195 -0
- package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +197 -0
- package/src/config/bundled-skills/media-processing/tools/media-diagnostics.ts +143 -0
- package/src/config/bundled-skills/media-processing/tools/media-status.ts +75 -0
- package/src/config/bundled-skills/media-processing/tools/query-media-events.ts +65 -0
- package/src/config/bundled-skills/messaging/SKILL.md +33 -8
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -7
- package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +2 -1
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +5 -1
- package/src/config/bundled-skills/phone-calls/SKILL.md +88 -23
- package/src/config/bundled-skills/twitter/SKILL.md +19 -3
- package/src/config/bundled-skills/twitter/icon.svg +14 -0
- package/src/config/bundled-tool-registry.ts +310 -0
- package/src/config/calls-schema.ts +181 -0
- package/src/config/core-schema.ts +309 -0
- package/src/config/defaults.ts +28 -3
- package/src/config/env-registry.ts +162 -0
- package/src/config/env.ts +175 -0
- package/src/config/loader.ts +6 -6
- package/src/config/memory-schema.ts +528 -0
- package/src/config/sandbox-schema.ts +55 -0
- package/src/config/schema.ts +158 -1133
- package/src/config/skill-state.ts +1 -1
- package/src/config/skills-schema.ts +32 -0
- package/src/config/skills.ts +35 -24
- package/src/config/system-prompt.ts +131 -56
- package/src/config/templates/IDENTITY.md +2 -2
- package/src/config/templates/SOUL.md +1 -1
- package/src/config/types.ts +1 -0
- package/src/config/user-reference.ts +4 -9
- package/src/config/vellum-skills/catalog.json +6 -7
- package/src/config/vellum-skills/chatgpt-import/tools/chatgpt-import.ts +5 -1
- package/src/config/vellum-skills/slack-oauth-setup/SKILL.md +4 -3
- package/src/config/vellum-skills/sms-setup/SKILL.md +216 -0
- package/src/config/vellum-skills/twilio-setup/SKILL.md +40 -8
- package/src/context/window-manager.ts +27 -7
- package/src/daemon/approval-generators.ts +186 -0
- package/src/daemon/approved-devices-store.ts +140 -0
- package/src/daemon/assistant-attachments.ts +1 -1
- package/src/daemon/classifier.ts +35 -32
- package/src/daemon/config-watcher.ts +1 -1
- package/src/daemon/daemon-control.ts +217 -0
- package/src/daemon/handlers/apps.ts +2 -3
- package/src/daemon/handlers/config-channels.ts +158 -0
- package/src/daemon/handlers/config-inbox.ts +540 -0
- package/src/daemon/handlers/config-ingress.ts +231 -0
- package/src/daemon/handlers/config-integrations.ts +258 -0
- package/src/daemon/handlers/config-model.ts +143 -0
- package/src/daemon/handlers/config-parental.ts +163 -0
- package/src/daemon/handlers/config-scheduling.ts +172 -0
- package/src/daemon/handlers/config-slack.ts +92 -0
- package/src/daemon/handlers/config-telegram.ts +301 -0
- package/src/daemon/handlers/config-tools.ts +177 -0
- package/src/daemon/handlers/config-trust.ts +104 -0
- package/src/daemon/handlers/config-twilio.ts +1080 -0
- package/src/daemon/handlers/config.ts +53 -1689
- package/src/daemon/handlers/diagnostics.ts +1 -1
- package/src/daemon/handlers/dictation.ts +180 -0
- package/src/daemon/handlers/documents.ts +18 -32
- package/src/daemon/handlers/identity.ts +14 -23
- package/src/daemon/handlers/index.ts +11 -0
- package/src/daemon/handlers/misc.ts +3 -5
- package/src/daemon/handlers/pairing.ts +98 -0
- package/src/daemon/handlers/sessions.ts +56 -5
- package/src/daemon/handlers/shared.ts +6 -1
- package/src/daemon/handlers/skills.ts +1 -1
- package/src/daemon/handlers/twitter-auth.ts +2 -0
- package/src/daemon/handlers/work-items.ts +17 -9
- package/src/daemon/handlers/workspace-files.ts +4 -3
- package/src/daemon/install-cli-launchers.ts +113 -0
- package/src/daemon/ipc-contract/apps.ts +356 -0
- package/src/daemon/ipc-contract/browser.ts +74 -0
- package/src/daemon/ipc-contract/computer-use.ts +151 -0
- package/src/daemon/ipc-contract/diagnostics.ts +56 -0
- package/src/daemon/ipc-contract/documents.ts +74 -0
- package/src/daemon/ipc-contract/inbox.ts +209 -0
- package/src/daemon/ipc-contract/integrations.ts +284 -0
- package/src/daemon/ipc-contract/memory.ts +48 -0
- package/src/daemon/ipc-contract/messages.ts +211 -0
- package/src/daemon/ipc-contract/pairing.ts +45 -0
- package/src/daemon/ipc-contract/parental-control.ts +95 -0
- package/src/daemon/ipc-contract/schedules.ts +97 -0
- package/src/daemon/ipc-contract/sessions.ts +315 -0
- package/src/daemon/ipc-contract/shared.ts +42 -0
- package/src/daemon/ipc-contract/skills.ts +120 -0
- package/src/daemon/ipc-contract/subagents.ts +58 -0
- package/src/daemon/ipc-contract/surfaces.ts +250 -0
- package/src/daemon/ipc-contract/trust.ts +60 -0
- package/src/daemon/ipc-contract/work-items.ts +225 -0
- package/src/daemon/ipc-contract/workspace.ts +113 -0
- package/src/daemon/ipc-contract-inventory.json +70 -0
- package/src/daemon/ipc-contract-inventory.ts +55 -29
- package/src/daemon/ipc-contract.ts +229 -2426
- package/src/daemon/ipc-protocol.ts +1 -1
- package/src/daemon/ipc-validate.ts +7 -0
- package/src/daemon/lifecycle.ts +97 -377
- package/src/daemon/pairing-store.ts +177 -0
- package/src/daemon/providers-setup.ts +43 -0
- package/src/daemon/ride-shotgun-handler.ts +68 -3
- package/src/daemon/server.ts +66 -46
- package/src/daemon/session-agent-loop-handlers.ts +421 -0
- package/src/daemon/session-agent-loop.ts +117 -275
- package/src/daemon/session-dynamic-profile.ts +1 -1
- package/src/daemon/session-history.ts +1 -1
- package/src/daemon/session-media-retry.ts +1 -1
- package/src/daemon/session-messaging.ts +37 -2
- package/src/daemon/session-notifiers.ts +5 -25
- package/src/daemon/session-process.ts +99 -59
- package/src/daemon/session-queue-manager.ts +96 -4
- package/src/daemon/session-runtime-assembly.ts +199 -10
- package/src/daemon/session-surfaces.ts +19 -4
- package/src/daemon/session-tool-setup.ts +30 -30
- package/src/daemon/session-workspace.ts +1 -1
- package/src/daemon/session.ts +35 -2
- package/src/daemon/shutdown-handlers.ts +122 -0
- package/src/daemon/trace-emitter.ts +1 -1
- package/src/daemon/watch-handler.ts +36 -33
- package/src/doordash/cart-queries.ts +787 -0
- package/src/doordash/client.ts +144 -127
- package/src/doordash/order-queries.ts +85 -0
- package/src/doordash/queries.ts +10 -1308
- package/src/doordash/search-queries.ts +203 -0
- package/src/doordash/session.ts +3 -2
- package/src/doordash/store-queries.ts +246 -0
- package/src/doordash/types.ts +367 -0
- package/src/email/providers/agentmail.ts +2 -1
- package/src/email/providers/index.ts +3 -2
- package/src/email/service.ts +3 -2
- package/src/errors.ts +43 -0
- package/src/home-base/prebuilt/seed.ts +1 -1
- package/src/hooks/cli.ts +6 -5
- package/src/hooks/config.ts +6 -8
- package/src/hooks/discovery.ts +6 -5
- package/src/hooks/manager.ts +4 -3
- package/src/hooks/runner.ts +2 -2
- package/src/hooks/templates.ts +5 -5
- package/src/inbound/public-ingress-urls.ts +6 -4
- package/src/index.ts +4 -2
- package/src/influencer/client.ts +1104 -0
- package/src/instrument.ts +4 -3
- package/src/logfire.ts +4 -3
- package/src/memory/admin.ts +25 -35
- package/src/memory/attachments-store.ts +4 -7
- package/src/memory/channel-delivery-store.ts +30 -1
- package/src/memory/channel-guardian-store.ts +202 -2
- package/src/memory/clarification-resolver.ts +37 -33
- package/src/memory/conflict-store.ts +67 -61
- package/src/memory/contradiction-checker.ts +141 -117
- package/src/memory/conversation-store.ts +335 -51
- package/src/memory/db-connection.ts +27 -4
- package/src/memory/db-init.ts +265 -4
- package/src/memory/db.ts +14 -1
- package/src/memory/embedding-backend.ts +27 -5
- package/src/memory/embedding-ollama.ts +2 -1
- package/src/memory/entity-extractor.ts +38 -35
- package/src/memory/guardian-action-store.ts +430 -0
- package/src/memory/inbox-escalation-projection.ts +59 -0
- package/src/memory/inbox-thread-store.ts +218 -0
- package/src/memory/ingress-invite-store.ts +338 -0
- package/src/memory/ingress-member-store.ts +350 -0
- package/src/memory/items-extractor.ts +91 -97
- package/src/memory/job-handlers/index-maintenance.ts +3 -3
- package/src/memory/job-handlers/media-processing.ts +69 -0
- package/src/memory/job-handlers/summarization.ts +32 -26
- package/src/memory/job-utils.ts +3 -10
- package/src/memory/jobs-store.ts +8 -10
- package/src/memory/jobs-worker.ts +55 -36
- package/src/memory/media-store.ts +759 -0
- package/src/memory/migrations/001-job-deferrals.ts +45 -0
- package/src/memory/migrations/002-tool-invocations-fk.ts +43 -0
- package/src/memory/migrations/003-memory-fts-backfill.ts +24 -0
- package/src/memory/migrations/004-entity-relation-dedup.ts +87 -0
- package/src/memory/migrations/005-fingerprint-scope-unique.ts +80 -0
- package/src/memory/migrations/006-scope-salted-fingerprints.ts +62 -0
- package/src/memory/migrations/007-assistant-id-to-self.ts +254 -0
- package/src/memory/migrations/008-remove-assistant-id-columns.ts +208 -0
- package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +83 -0
- package/src/memory/migrations/010-ext-conv-bindings-channel-chat-unique.ts +56 -0
- package/src/memory/migrations/011-call-sessions-provider-sid-dedup.ts +63 -0
- package/src/memory/migrations/012-call-sessions-add-initiated-from.ts +19 -0
- package/src/memory/migrations/013-guardian-action-tables.ts +68 -0
- package/src/memory/migrations/014-backfill-inbox-thread-state.ts +76 -0
- package/src/memory/migrations/015-drop-active-search-index.ts +27 -0
- package/src/memory/migrations/016-memory-segments-indexes.ts +11 -0
- package/src/memory/migrations/017-memory-items-indexes.ts +10 -0
- package/src/memory/migrations/018-remaining-table-indexes.ts +13 -0
- package/src/memory/migrations/index.ts +24 -0
- package/src/memory/migrations/registry.ts +79 -0
- package/src/memory/migrations/validate-migration-state.ts +69 -0
- package/src/memory/qdrant-manager.ts +49 -8
- package/src/memory/query-builder.ts +1 -1
- package/src/memory/raw-query.ts +119 -0
- package/src/memory/recall-cache.ts +4 -1
- package/src/memory/retriever.ts +165 -47
- package/src/memory/schema-migration.ts +25 -984
- package/src/memory/schema.ts +228 -7
- package/src/memory/search/entity.ts +205 -31
- package/src/memory/search/lexical.ts +81 -52
- package/src/memory/search/ranking.ts +27 -23
- package/src/memory/search/semantic.ts +157 -19
- package/src/memory/search/types.ts +24 -0
- package/src/memory/shared-app-links-store.ts +4 -5
- package/src/memory/validation.ts +19 -0
- package/src/messaging/draft-store.ts +5 -6
- package/src/messaging/provider-types.ts +2 -0
- package/src/messaging/providers/sms/adapter.ts +201 -0
- package/src/messaging/providers/sms/client.ts +93 -0
- package/src/messaging/providers/sms/types.ts +7 -0
- package/src/messaging/providers/telegram-bot/adapter.ts +2 -5
- package/src/messaging/providers/whatsapp/adapter.ts +136 -0
- package/src/messaging/providers/whatsapp/client.ts +67 -0
- package/src/messaging/style-analyzer.ts +5 -4
- package/src/messaging/thread-summarizer.ts +61 -69
- package/src/messaging/triage-engine.ts +62 -71
- package/src/migrations/config-merge.ts +53 -0
- package/src/migrations/data-layout.ts +68 -0
- package/src/migrations/data-merge.ts +33 -0
- package/src/migrations/hooks-merge.ts +90 -0
- package/src/migrations/index.ts +6 -0
- package/src/migrations/log.ts +23 -0
- package/src/migrations/skills-merge.ts +33 -0
- package/src/migrations/workspace-layout.ts +79 -0
- package/src/permissions/checker.ts +133 -11
- package/src/permissions/prompter.ts +14 -0
- package/src/permissions/shell-identity.ts +31 -1
- package/src/permissions/trust-store.ts +21 -1
- package/src/providers/anthropic/client.ts +4 -4
- package/src/providers/failover.ts +2 -2
- package/src/providers/model-intents.ts +70 -0
- package/src/providers/ollama/client.ts +2 -1
- package/src/providers/provider-send-message.ts +176 -0
- package/src/providers/registry.ts +71 -30
- package/src/providers/retry.ts +35 -1
- package/src/providers/types.ts +12 -1
- package/src/runtime/approval-conversation-turn.ts +97 -0
- package/src/runtime/approval-message-composer.ts +253 -0
- package/src/runtime/channel-approval-parser.ts +36 -2
- package/src/runtime/channel-approvals.ts +11 -24
- package/src/runtime/channel-guardian-service.ts +88 -21
- package/src/runtime/channel-readiness-service.ts +418 -0
- package/src/runtime/channel-readiness-types.ts +35 -0
- package/src/runtime/channel-retry-sweep.ts +184 -0
- package/src/runtime/guardian-context-resolver.ts +108 -0
- package/src/runtime/http-server.ts +275 -717
- package/src/runtime/http-types.ts +59 -3
- package/src/runtime/middleware/auth.ts +116 -0
- package/src/runtime/middleware/error-handler.ts +33 -0
- package/src/runtime/middleware/twilio-validation.ts +127 -0
- package/src/runtime/routes/app-routes.ts +1 -1
- package/src/runtime/routes/call-routes.ts +51 -7
- package/src/runtime/routes/channel-delivery-routes.ts +170 -0
- package/src/runtime/routes/channel-guardian-routes.ts +1191 -0
- package/src/runtime/routes/channel-inbound-routes.ts +1152 -0
- package/src/runtime/routes/channel-route-shared.ts +144 -0
- package/src/runtime/routes/channel-routes.ts +32 -1588
- package/src/runtime/routes/conversation-routes.ts +50 -7
- package/src/runtime/routes/events-routes.ts +2 -2
- package/src/runtime/routes/identity-routes.ts +126 -0
- package/src/runtime/routes/pairing-routes.ts +143 -0
- package/src/runtime/routes/run-routes.ts +15 -1
- package/src/runtime/run-orchestrator.ts +86 -35
- package/src/schedule/schedule-store.ts +36 -32
- package/src/schedule/scheduler.ts +3 -3
- package/src/security/encrypted-store.ts +5 -7
- package/src/security/oauth2.ts +45 -15
- package/src/security/parental-control-store.ts +183 -0
- package/src/security/secret-allowlist.ts +4 -3
- package/src/security/secret-scanner.ts +5 -5
- package/src/security/secure-keys.ts +1 -1
- package/src/security/token-manager.ts +3 -2
- package/src/services/vercel-deploy.ts +6 -2
- package/src/skills/tool-manifest.ts +3 -3
- package/src/skills/vellum-catalog-remote.ts +75 -16
- package/src/slack/slack-webhook.ts +2 -1
- package/src/swarm/orchestrator.ts +92 -1
- package/src/swarm/router-planner.ts +6 -9
- package/src/swarm/worker-prompts.ts +9 -12
- package/src/tasks/task-compiler.ts +19 -28
- package/src/tasks/task-runner.ts +1 -1
- package/src/tools/assets/materialize.ts +2 -2
- package/src/tools/assets/search.ts +15 -14
- package/src/tools/browser/__tests__/auth-detector.test.ts +1 -0
- package/src/tools/browser/auto-navigate.ts +1 -0
- package/src/tools/browser/browser-execution.ts +10 -1
- package/src/tools/browser/browser-manager.ts +119 -4
- package/src/tools/browser/network-recorder.ts +5 -0
- package/src/tools/calls/call-start.ts +1 -0
- package/src/tools/credentials/broker.ts +11 -2
- package/src/tools/credentials/metadata-store.ts +18 -14
- package/src/tools/credentials/post-connect-hooks.ts +61 -0
- package/src/tools/credentials/vault.ts +49 -23
- package/src/tools/execution-target.ts +11 -1
- package/src/tools/executor.ts +68 -9
- package/src/tools/host-terminal/cli-discover.ts +1 -1
- package/src/tools/network/script-proxy/http-forwarder.ts +1 -1
- package/src/tools/network/script-proxy/mitm-handler.ts +1 -1
- package/src/tools/network/script-proxy/server.ts +1 -1
- package/src/tools/network/script-proxy/session-manager.ts +6 -5
- package/src/tools/network/web-fetch.ts +18 -2
- package/src/tools/network/web-search.ts +8 -4
- package/src/tools/reminder/reminder-store.ts +14 -15
- package/src/tools/schedule/create.ts +1 -0
- package/src/tools/schedule/list.ts +2 -1
- package/src/tools/shared/filesystem/file-ops-service.ts +5 -7
- package/src/tools/skills/skill-script-runner.ts +24 -9
- package/src/tools/skills/skill-tool-factory.ts +1 -0
- package/src/tools/tasks/work-item-enqueue.ts +2 -2
- package/src/tools/terminal/evaluate-typescript.ts +21 -12
- package/src/tools/terminal/parser.ts +50 -0
- package/src/tools/types.ts +2 -0
- package/src/tools/watcher/delete.ts +6 -0
- package/src/tools/weather/service.ts +1 -1
- package/src/twitter/client.ts +190 -24
- package/src/twitter/router.ts +1 -1
- package/src/twitter/session.ts +4 -3
- package/src/util/clipboard.ts +1 -1
- package/src/util/errors.ts +65 -8
- package/src/util/fs.ts +40 -0
- package/src/util/json.ts +10 -0
- package/src/util/log-redact.ts +189 -0
- package/src/util/logger.ts +19 -17
- package/src/util/object.ts +3 -0
- package/src/util/platform.ts +105 -363
- package/src/util/pricing.ts +1 -1
- package/src/util/promise-guard.ts +1 -1
- package/src/util/retry.ts +19 -0
- package/src/util/row-mapper.ts +79 -0
- package/src/util/silently.ts +21 -0
- package/src/watcher/engine.ts +5 -1
- package/src/watcher/provider-types.ts +20 -0
- package/src/watcher/providers/github.ts +156 -0
- package/src/watcher/providers/gmail.ts +1 -0
- package/src/watcher/providers/google-calendar.ts +1 -0
- package/src/watcher/providers/linear.ts +460 -0
- package/src/watcher/providers/slack.ts +1 -0
- package/src/work-items/work-item-runner.ts +1 -1
- package/src/workspace/git-service.ts +1 -1
- package/src/workspace/provider-commit-message-generator.ts +51 -22
- package/src/__tests__/call-bridge.test.ts +0 -517
- package/src/__tests__/session-process-bridge.test.ts +0 -244
- package/src/calls/call-bridge.ts +0 -168
- package/src/config/vellum-skills/google-oauth-setup/SKILL.md +0 -199
|
@@ -14,7 +14,7 @@ const log = getLogger('session-history');
|
|
|
14
14
|
|
|
15
15
|
function isUndoableUserMessage(message: Message): boolean {
|
|
16
16
|
if (message.role !== 'user') return false;
|
|
17
|
-
if (getSummaryFromContextMessage(message)
|
|
17
|
+
if (getSummaryFromContextMessage(message) != null) return false;
|
|
18
18
|
// A user message is undoable if it contains user-authored content (non-tool_result
|
|
19
19
|
// blocks). Messages that contain ONLY tool_result blocks (e.g. automated tool
|
|
20
20
|
// responses) are not undoable. Messages that have both tool_result and text blocks
|
|
@@ -17,7 +17,7 @@ export function stripMediaPayloadsForRetry(messages: Message[]): { messages: Mes
|
|
|
17
17
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
18
18
|
const msg = messages[i];
|
|
19
19
|
if (msg.role !== 'user') continue;
|
|
20
|
-
if (getSummaryFromContextMessage(msg)
|
|
20
|
+
if (getSummaryFromContextMessage(msg) != null) continue;
|
|
21
21
|
if (isToolResultOnlyMessage(msg)) continue;
|
|
22
22
|
latestUserIndex = i;
|
|
23
23
|
break;
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
|
|
8
8
|
import { v4 as uuid } from 'uuid';
|
|
9
9
|
import type { Message } from '../providers/types.js';
|
|
10
|
+
import type { TurnChannelContext } from '../channels/types.js';
|
|
11
|
+
import { parseChannelId } from '../channels/types.js';
|
|
10
12
|
import type { ServerMessage, UserMessageAttachment } from './ipc-protocol.js';
|
|
11
13
|
import { createUserMessage } from '../agent/message-types.js';
|
|
12
14
|
import * as conversationStore from '../memory/conversation-store.js';
|
|
@@ -25,6 +27,15 @@ export interface MessagingSessionContext {
|
|
|
25
27
|
abortController: AbortController | null;
|
|
26
28
|
currentRequestId?: string;
|
|
27
29
|
readonly queue: MessageQueue;
|
|
30
|
+
getTurnChannelContext(): TurnChannelContext | null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function extractTurnChannelContext(metadata?: Record<string, unknown>): TurnChannelContext | null {
|
|
34
|
+
if (!metadata) return null;
|
|
35
|
+
const userMessageChannel = parseChannelId(metadata.userMessageChannel);
|
|
36
|
+
const assistantMessageChannel = parseChannelId(metadata.assistantMessageChannel);
|
|
37
|
+
if (!userMessageChannel || !assistantMessageChannel) return null;
|
|
38
|
+
return { userMessageChannel, assistantMessageChannel };
|
|
28
39
|
}
|
|
29
40
|
|
|
30
41
|
// ── enqueueMessage ───────────────────────────────────────────────────
|
|
@@ -43,7 +54,18 @@ export function enqueueMessage(
|
|
|
43
54
|
return { queued: false, requestId };
|
|
44
55
|
}
|
|
45
56
|
|
|
46
|
-
const
|
|
57
|
+
const turnChannelContext = extractTurnChannelContext(metadata) ?? ctx.getTurnChannelContext() ?? undefined;
|
|
58
|
+
const pushed = ctx.queue.push({
|
|
59
|
+
content,
|
|
60
|
+
attachments,
|
|
61
|
+
requestId,
|
|
62
|
+
onEvent,
|
|
63
|
+
activeSurfaceId,
|
|
64
|
+
currentPage,
|
|
65
|
+
metadata,
|
|
66
|
+
turnChannelContext,
|
|
67
|
+
queuedAt: Date.now(),
|
|
68
|
+
});
|
|
47
69
|
if (!pushed) {
|
|
48
70
|
return { queued: false, rejected: true, requestId };
|
|
49
71
|
}
|
|
@@ -82,13 +104,26 @@ export function persistUserMessage(
|
|
|
82
104
|
ctx.messages.push(userMessage);
|
|
83
105
|
|
|
84
106
|
try {
|
|
107
|
+
const turnCtx = extractTurnChannelContext(metadata) ?? ctx.getTurnChannelContext();
|
|
108
|
+
const mergedMetadata = turnCtx
|
|
109
|
+
? {
|
|
110
|
+
...(metadata ?? {}),
|
|
111
|
+
userMessageChannel: turnCtx.userMessageChannel,
|
|
112
|
+
assistantMessageChannel: turnCtx.assistantMessageChannel,
|
|
113
|
+
}
|
|
114
|
+
: metadata;
|
|
115
|
+
|
|
85
116
|
const persistedUserMessage = conversationStore.addMessage(
|
|
86
117
|
ctx.conversationId,
|
|
87
118
|
'user',
|
|
88
119
|
JSON.stringify(userMessage.content),
|
|
89
|
-
|
|
120
|
+
mergedMetadata,
|
|
90
121
|
);
|
|
91
122
|
|
|
123
|
+
if (turnCtx) {
|
|
124
|
+
conversationStore.setConversationOriginChannelIfUnset(ctx.conversationId, turnCtx.userMessageChannel);
|
|
125
|
+
}
|
|
126
|
+
|
|
92
127
|
if (!persistedUserMessage.id) {
|
|
93
128
|
throw new Error('Failed to persist user message');
|
|
94
129
|
}
|
|
@@ -30,7 +30,8 @@ import {
|
|
|
30
30
|
registerCallCompletionNotifier,
|
|
31
31
|
unregisterCallCompletionNotifier,
|
|
32
32
|
} from '../calls/call-state.js';
|
|
33
|
-
import { getCallSession
|
|
33
|
+
import { getCallSession } from '../calls/call-store.js';
|
|
34
|
+
import { buildCallCompletionMessage } from '../calls/call-conversation-messages.js';
|
|
34
35
|
|
|
35
36
|
/**
|
|
36
37
|
* Subset of Session state that notifier callbacks need to read at
|
|
@@ -95,12 +96,13 @@ export function registerSessionNotifiers(
|
|
|
95
96
|
registerCallQuestionNotifier(conversationId, (callSessionId: string, question: string) => {
|
|
96
97
|
const callSession = getCallSession(callSessionId);
|
|
97
98
|
const callee = callSession?.toNumber ?? 'the caller';
|
|
98
|
-
const questionText = `**Live call question** (to ${callee}):\n\n${question}\n\
|
|
99
|
+
const questionText = `**Live call question** (to ${callee}):\n\n${question}\n\n_Use the call answer API to respond._`;
|
|
99
100
|
|
|
100
101
|
conversationStore.addMessage(
|
|
101
102
|
conversationId,
|
|
102
103
|
'assistant',
|
|
103
104
|
JSON.stringify([{ type: 'text', text: questionText }]),
|
|
105
|
+
{ userMessageChannel: 'voice', assistantMessageChannel: 'voice' },
|
|
104
106
|
);
|
|
105
107
|
|
|
106
108
|
ctx.messages.push(createAssistantMessage(questionText));
|
|
@@ -122,14 +124,6 @@ export function registerSessionNotifiers(
|
|
|
122
124
|
const speakerLabel = speaker === 'caller' ? 'Caller' : 'Assistant';
|
|
123
125
|
const transcriptText = `**Live call transcript**\n${speakerLabel}: ${text}`;
|
|
124
126
|
|
|
125
|
-
conversationStore.addMessage(
|
|
126
|
-
conversationId,
|
|
127
|
-
'assistant',
|
|
128
|
-
JSON.stringify([{ type: 'text', text: transcriptText }]),
|
|
129
|
-
);
|
|
130
|
-
|
|
131
|
-
ctx.messages.push(createAssistantMessage(transcriptText));
|
|
132
|
-
|
|
133
127
|
ctx.sendToClient({
|
|
134
128
|
type: 'assistant_text_delta',
|
|
135
129
|
text: transcriptText,
|
|
@@ -143,21 +137,7 @@ export function registerSessionNotifiers(
|
|
|
143
137
|
);
|
|
144
138
|
|
|
145
139
|
registerCallCompletionNotifier(conversationId, (callSessionId: string) => {
|
|
146
|
-
const
|
|
147
|
-
const events = getCallEvents(callSessionId);
|
|
148
|
-
const duration = callSession?.endedAt && callSession?.startedAt
|
|
149
|
-
? Math.round((callSession.endedAt - callSession.startedAt) / 1000)
|
|
150
|
-
: null;
|
|
151
|
-
const durationStr = duration !== null ? ` (${duration}s)` : '';
|
|
152
|
-
const summaryText = `**Call completed**${durationStr}. ${events.length} event(s) recorded.`;
|
|
153
|
-
|
|
154
|
-
conversationStore.addMessage(
|
|
155
|
-
conversationId,
|
|
156
|
-
'assistant',
|
|
157
|
-
JSON.stringify([{ type: 'text', text: summaryText }]),
|
|
158
|
-
);
|
|
159
|
-
|
|
160
|
-
ctx.messages.push(createAssistantMessage(summaryText));
|
|
140
|
+
const summaryText = buildCallCompletionMessage(callSessionId);
|
|
161
141
|
|
|
162
142
|
ctx.sendToClient({
|
|
163
143
|
type: 'assistant_text_delta',
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import type { Message } from '../providers/types.js';
|
|
10
|
+
import type { TurnChannelContext } from '../channels/types.js';
|
|
11
|
+
import { parseChannelId } from '../channels/types.js';
|
|
10
12
|
import type { ServerMessage, UserMessageAttachment } from './ipc-protocol.js';
|
|
11
13
|
import type { UsageStats } from './ipc-contract.js';
|
|
12
14
|
import type { MessageQueue } from './session-queue-manager.js';
|
|
@@ -14,10 +16,15 @@ import type { QueueDrainReason } from './session-queue-manager.js';
|
|
|
14
16
|
import type { TraceEmitter } from './trace-emitter.js';
|
|
15
17
|
import { createUserMessage, createAssistantMessage } from '../agent/message-types.js';
|
|
16
18
|
import * as conversationStore from '../memory/conversation-store.js';
|
|
19
|
+
import {
|
|
20
|
+
getPendingDeliveryByConversation,
|
|
21
|
+
getGuardianActionRequest,
|
|
22
|
+
resolveGuardianActionRequest,
|
|
23
|
+
} from '../memory/guardian-action-store.js';
|
|
24
|
+
import { answerCall } from '../calls/call-domain.js';
|
|
17
25
|
import { resolveSlash, type SlashContext } from './session-slash.js';
|
|
18
26
|
import { getConfig } from '../config/loader.js';
|
|
19
27
|
import { getLogger } from '../util/logger.js';
|
|
20
|
-
import { tryRouteCallMessage } from '../calls/call-bridge.js';
|
|
21
28
|
|
|
22
29
|
const log = getLogger('session-process');
|
|
23
30
|
|
|
@@ -68,6 +75,24 @@ export interface ProcessSessionContext {
|
|
|
68
75
|
onEvent: (msg: ServerMessage) => void,
|
|
69
76
|
options?: { skipPreMessageRollback?: boolean },
|
|
70
77
|
): Promise<void>;
|
|
78
|
+
getTurnChannelContext(): TurnChannelContext | null;
|
|
79
|
+
setTurnChannelContext(ctx: TurnChannelContext): void;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function resolveQueuedTurnContext(
|
|
83
|
+
queued: { turnChannelContext?: TurnChannelContext; metadata?: Record<string, unknown> },
|
|
84
|
+
fallback: TurnChannelContext | null,
|
|
85
|
+
): TurnChannelContext | null {
|
|
86
|
+
if (queued.turnChannelContext) return queued.turnChannelContext;
|
|
87
|
+
const metadata = queued.metadata;
|
|
88
|
+
if (metadata) {
|
|
89
|
+
const userMessageChannel = parseChannelId(metadata.userMessageChannel);
|
|
90
|
+
const assistantMessageChannel = parseChannelId(metadata.assistantMessageChannel);
|
|
91
|
+
if (userMessageChannel && assistantMessageChannel) {
|
|
92
|
+
return { userMessageChannel, assistantMessageChannel };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return fallback;
|
|
71
96
|
}
|
|
72
97
|
|
|
73
98
|
/** Build a SlashContext from the current session state and config. */
|
|
@@ -112,6 +137,11 @@ export function drainQueue(session: ProcessSessionContext, reason: QueueDrainRea
|
|
|
112
137
|
requestId: next.requestId,
|
|
113
138
|
});
|
|
114
139
|
|
|
140
|
+
const queuedTurnCtx = resolveQueuedTurnContext(next, session.getTurnChannelContext());
|
|
141
|
+
if (queuedTurnCtx) {
|
|
142
|
+
session.setTurnChannelContext(queuedTurnCtx);
|
|
143
|
+
}
|
|
144
|
+
|
|
115
145
|
// Resolve slash commands for queued messages
|
|
116
146
|
const slashResult = resolveSlash(next.content, buildSlashContext(session));
|
|
117
147
|
|
|
@@ -120,11 +150,15 @@ export function drainQueue(session: ProcessSessionContext, reason: QueueDrainRea
|
|
|
120
150
|
// failed write never leaves an unpersisted message in memory.
|
|
121
151
|
if (slashResult.kind === 'unknown') {
|
|
122
152
|
try {
|
|
153
|
+
const drainChannelMeta = queuedTurnCtx
|
|
154
|
+
? { userMessageChannel: queuedTurnCtx.userMessageChannel, assistantMessageChannel: queuedTurnCtx.assistantMessageChannel }
|
|
155
|
+
: undefined;
|
|
123
156
|
const userMsg = createUserMessage(next.content, next.attachments);
|
|
124
157
|
conversationStore.addMessage(
|
|
125
158
|
session.conversationId,
|
|
126
159
|
'user',
|
|
127
160
|
JSON.stringify(userMsg.content),
|
|
161
|
+
drainChannelMeta,
|
|
128
162
|
);
|
|
129
163
|
session.messages.push(userMsg);
|
|
130
164
|
|
|
@@ -133,6 +167,7 @@ export function drainQueue(session: ProcessSessionContext, reason: QueueDrainRea
|
|
|
133
167
|
session.conversationId,
|
|
134
168
|
'assistant',
|
|
135
169
|
JSON.stringify(assistantMsg.content),
|
|
170
|
+
drainChannelMeta,
|
|
136
171
|
);
|
|
137
172
|
session.messages.push(assistantMsg);
|
|
138
173
|
|
|
@@ -197,49 +232,15 @@ export function drainQueue(session: ProcessSessionContext, reason: QueueDrainRea
|
|
|
197
232
|
session.currentPage = next.currentPage;
|
|
198
233
|
|
|
199
234
|
// Fire-and-forget: persistUserMessage set session.processing = true
|
|
200
|
-
// so subsequent messages will still be enqueued.
|
|
201
|
-
// bridge first — if consumed, skip agent processing and continue draining.
|
|
235
|
+
// so subsequent messages will still be enqueued.
|
|
202
236
|
// runAgentLoop's finally block will call drainQueue when this run completes.
|
|
203
|
-
|
|
237
|
+
session.runAgentLoop(resolvedContent, userMessageId, next.onEvent).catch((err) => {
|
|
204
238
|
const message = err instanceof Error ? err.message : String(err);
|
|
205
239
|
log.error({ err, conversationId: session.conversationId, requestId: next.requestId }, 'Error processing queued message');
|
|
206
240
|
next.onEvent({ type: 'error', message: `Failed to process queued message: ${message}` });
|
|
207
241
|
});
|
|
208
242
|
}
|
|
209
243
|
|
|
210
|
-
/**
|
|
211
|
-
* Try the call bridge first; if not consumed, run the agent loop.
|
|
212
|
-
* Used by drainQueue to handle the async bridge check in fire-and-forget mode.
|
|
213
|
-
*/
|
|
214
|
-
async function routeOrProcess(
|
|
215
|
-
session: ProcessSessionContext,
|
|
216
|
-
content: string,
|
|
217
|
-
userMessageId: string,
|
|
218
|
-
next: { onEvent: (msg: ServerMessage) => void; requestId: string },
|
|
219
|
-
): Promise<void> {
|
|
220
|
-
try {
|
|
221
|
-
const bridgeResult = await tryRouteCallMessage(session.conversationId, content, userMessageId);
|
|
222
|
-
if (bridgeResult.handled) {
|
|
223
|
-
session.preactivatedSkillIds = undefined;
|
|
224
|
-
session.processing = false;
|
|
225
|
-
session.abortController = null;
|
|
226
|
-
session.currentRequestId = undefined;
|
|
227
|
-
log.info({ conversationId: session.conversationId, userMessageId }, 'Queued message consumed by call bridge, skipping agent loop');
|
|
228
|
-
if (bridgeResult.userFacingText) {
|
|
229
|
-
next.onEvent({ type: 'assistant_text_delta', text: bridgeResult.userFacingText });
|
|
230
|
-
}
|
|
231
|
-
next.onEvent({ type: 'message_complete', sessionId: session.conversationId });
|
|
232
|
-
// runAgentLoop never ran so its finally block won't drain — continue manually
|
|
233
|
-
drainQueue(session);
|
|
234
|
-
return;
|
|
235
|
-
}
|
|
236
|
-
} catch (err) {
|
|
237
|
-
log.warn({ err, conversationId: session.conversationId }, 'Call bridge check failed (non-fatal), proceeding with agent loop');
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
await session.runAgentLoop(content, userMessageId, next.onEvent);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
244
|
// ── processMessage ───────────────────────────────────────────────────
|
|
244
245
|
|
|
245
246
|
/**
|
|
@@ -258,6 +259,61 @@ export async function processMessage(
|
|
|
258
259
|
session.currentActiveSurfaceId = activeSurfaceId;
|
|
259
260
|
session.currentPage = currentPage;
|
|
260
261
|
|
|
262
|
+
// ── Guardian action answer interception (mac channel) ──
|
|
263
|
+
// If this conversation has a pending guardian action delivery, treat the
|
|
264
|
+
// user message as the guardian's answer instead of running the agent loop.
|
|
265
|
+
const guardianDelivery = getPendingDeliveryByConversation(session.conversationId);
|
|
266
|
+
if (guardianDelivery) {
|
|
267
|
+
const guardianRequest = getGuardianActionRequest(guardianDelivery.requestId);
|
|
268
|
+
if (guardianRequest && guardianRequest.status === 'pending') {
|
|
269
|
+
const guardianChannelMeta = { userMessageChannel: 'macos' as const, assistantMessageChannel: 'macos' as const };
|
|
270
|
+
const userMsg = createUserMessage(content, attachments);
|
|
271
|
+
const persisted = conversationStore.addMessage(
|
|
272
|
+
session.conversationId,
|
|
273
|
+
'user',
|
|
274
|
+
JSON.stringify(userMsg.content),
|
|
275
|
+
guardianChannelMeta,
|
|
276
|
+
);
|
|
277
|
+
session.messages.push(userMsg);
|
|
278
|
+
|
|
279
|
+
// Attempt to deliver the answer to the call first. Only resolve
|
|
280
|
+
// the guardian action request if answerCall succeeds, so that a
|
|
281
|
+
// failed delivery leaves the request pending for retry from
|
|
282
|
+
// another channel.
|
|
283
|
+
const answerResult = await answerCall({ callSessionId: guardianRequest.callSessionId, answer: content });
|
|
284
|
+
|
|
285
|
+
if ('ok' in answerResult && answerResult.ok) {
|
|
286
|
+
const resolved = resolveGuardianActionRequest(guardianRequest.id, content, 'macos');
|
|
287
|
+
const replyText = resolved
|
|
288
|
+
? 'Your answer has been relayed to the call.'
|
|
289
|
+
: 'This question has already been answered from another channel.';
|
|
290
|
+
const replyMsg = createAssistantMessage(replyText);
|
|
291
|
+
conversationStore.addMessage(
|
|
292
|
+
session.conversationId,
|
|
293
|
+
'assistant',
|
|
294
|
+
JSON.stringify(replyMsg.content),
|
|
295
|
+
guardianChannelMeta,
|
|
296
|
+
);
|
|
297
|
+
session.messages.push(replyMsg);
|
|
298
|
+
onEvent({ type: 'assistant_text_delta', text: replyText });
|
|
299
|
+
} else {
|
|
300
|
+
const errorDetail = 'error' in answerResult ? answerResult.error : 'Unknown error';
|
|
301
|
+
log.warn({ callSessionId: guardianRequest.callSessionId, error: errorDetail }, 'answerCall failed for mac guardian answer');
|
|
302
|
+
const failMsg = createAssistantMessage('Failed to deliver your answer to the call. Please try again.');
|
|
303
|
+
conversationStore.addMessage(
|
|
304
|
+
session.conversationId,
|
|
305
|
+
'assistant',
|
|
306
|
+
JSON.stringify(failMsg.content),
|
|
307
|
+
guardianChannelMeta,
|
|
308
|
+
);
|
|
309
|
+
session.messages.push(failMsg);
|
|
310
|
+
onEvent({ type: 'assistant_text_delta', text: 'Failed to deliver your answer to the call. Please try again.' });
|
|
311
|
+
}
|
|
312
|
+
onEvent({ type: 'message_complete', sessionId: session.conversationId });
|
|
313
|
+
return persisted.id;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
261
317
|
// Resolve slash commands before persistence
|
|
262
318
|
const slashResult = resolveSlash(content, buildSlashContext(session));
|
|
263
319
|
|
|
@@ -265,11 +321,16 @@ export async function processMessage(
|
|
|
265
321
|
// messageId is real. Persist each message before pushing to session.messages
|
|
266
322
|
// so that a failed write never leaves an unpersisted message in memory.
|
|
267
323
|
if (slashResult.kind === 'unknown') {
|
|
324
|
+
const pmTurnCtx = session.getTurnChannelContext();
|
|
325
|
+
const pmChannelMeta = pmTurnCtx
|
|
326
|
+
? { userMessageChannel: pmTurnCtx.userMessageChannel, assistantMessageChannel: pmTurnCtx.assistantMessageChannel }
|
|
327
|
+
: undefined;
|
|
268
328
|
const userMsg = createUserMessage(content, attachments);
|
|
269
329
|
const persisted = conversationStore.addMessage(
|
|
270
330
|
session.conversationId,
|
|
271
331
|
'user',
|
|
272
332
|
JSON.stringify(userMsg.content),
|
|
333
|
+
pmChannelMeta,
|
|
273
334
|
);
|
|
274
335
|
session.messages.push(userMsg);
|
|
275
336
|
|
|
@@ -278,6 +339,7 @@ export async function processMessage(
|
|
|
278
339
|
session.conversationId,
|
|
279
340
|
'assistant',
|
|
280
341
|
JSON.stringify(assistantMsg.content),
|
|
342
|
+
pmChannelMeta,
|
|
281
343
|
);
|
|
282
344
|
session.messages.push(assistantMsg);
|
|
283
345
|
|
|
@@ -313,28 +375,6 @@ export async function processMessage(
|
|
|
313
375
|
return '';
|
|
314
376
|
}
|
|
315
377
|
|
|
316
|
-
// Route through the call bridge before the agent loop. When the bridge
|
|
317
|
-
// consumes the message (answer or instruction), skip agent processing.
|
|
318
|
-
try {
|
|
319
|
-
const bridgeResult = await tryRouteCallMessage(session.conversationId, resolvedContent, userMessageId);
|
|
320
|
-
if (bridgeResult.handled) {
|
|
321
|
-
session.preactivatedSkillIds = undefined;
|
|
322
|
-
session.processing = false;
|
|
323
|
-
session.abortController = null;
|
|
324
|
-
session.currentRequestId = undefined;
|
|
325
|
-
log.info({ conversationId: session.conversationId, userMessageId }, 'IPC message consumed by call bridge, skipping agent loop');
|
|
326
|
-
if (bridgeResult.userFacingText) {
|
|
327
|
-
onEvent({ type: 'assistant_text_delta', text: bridgeResult.userFacingText });
|
|
328
|
-
}
|
|
329
|
-
onEvent({ type: 'message_complete', sessionId: session.conversationId });
|
|
330
|
-
// runAgentLoop never ran so its finally block won't drain — continue manually
|
|
331
|
-
drainQueue(session);
|
|
332
|
-
return userMessageId;
|
|
333
|
-
}
|
|
334
|
-
} catch (err) {
|
|
335
|
-
log.warn({ err, conversationId: session.conversationId }, 'Call bridge check failed (non-fatal), proceeding with agent loop');
|
|
336
|
-
}
|
|
337
|
-
|
|
338
378
|
await session.runAgentLoop(resolvedContent, userMessageId, onEvent);
|
|
339
379
|
return userMessageId;
|
|
340
380
|
}
|
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { ServerMessage, UserMessageAttachment } from './ipc-protocol.js';
|
|
9
|
+
import type { TurnChannelContext } from '../channels/types.js';
|
|
10
|
+
import { getLogger } from '../util/logger.js';
|
|
11
|
+
|
|
12
|
+
const log = getLogger('session-queue');
|
|
9
13
|
|
|
10
14
|
export interface QueuedMessage {
|
|
11
15
|
content: string;
|
|
@@ -15,9 +19,15 @@ export interface QueuedMessage {
|
|
|
15
19
|
activeSurfaceId?: string;
|
|
16
20
|
currentPage?: string;
|
|
17
21
|
metadata?: Record<string, unknown>;
|
|
22
|
+
turnChannelContext?: TurnChannelContext;
|
|
23
|
+
/** Timestamp (ms) when the message was enqueued. */
|
|
24
|
+
queuedAt: number;
|
|
18
25
|
}
|
|
19
26
|
|
|
20
27
|
export const MAX_QUEUE_DEPTH = 10;
|
|
28
|
+
/** Messages older than this (ms) are auto-expired from the queue. */
|
|
29
|
+
export const DEFAULT_MAX_WAIT_MS = 60_000;
|
|
30
|
+
const CAPACITY_WARNING_THRESHOLD = 0.8;
|
|
21
31
|
|
|
22
32
|
/**
|
|
23
33
|
* Describes why a queued message was promoted from the queue.
|
|
@@ -35,27 +45,77 @@ export interface QueuePolicy {
|
|
|
35
45
|
checkpointHandoffEnabled: boolean;
|
|
36
46
|
}
|
|
37
47
|
|
|
48
|
+
export interface QueueMetrics {
|
|
49
|
+
currentDepth: number;
|
|
50
|
+
totalDropped: number;
|
|
51
|
+
totalExpired: number;
|
|
52
|
+
/** Average wait time (ms) of dequeued messages. 0 when no messages have been dequeued. */
|
|
53
|
+
averageWaitMs: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
38
56
|
/**
|
|
39
57
|
* Typed wrapper around the queued-message array.
|
|
40
58
|
*
|
|
41
|
-
* Session owns one instance; the wrapper handles capacity checks
|
|
42
|
-
* iteration so the rest of Session doesn't
|
|
59
|
+
* Session owns one instance; the wrapper handles capacity checks,
|
|
60
|
+
* expiry, metrics, and iteration so the rest of Session doesn't
|
|
61
|
+
* touch the raw array.
|
|
43
62
|
*/
|
|
44
63
|
export class MessageQueue {
|
|
45
64
|
private items: QueuedMessage[] = [];
|
|
65
|
+
private maxWaitMs: number;
|
|
66
|
+
private droppedCount = 0;
|
|
67
|
+
private expiredCount = 0;
|
|
68
|
+
private totalWaitMs = 0;
|
|
69
|
+
private dequeuedCount = 0;
|
|
70
|
+
private capacityWarned = false;
|
|
71
|
+
|
|
72
|
+
constructor(maxWaitMs: number = DEFAULT_MAX_WAIT_MS) {
|
|
73
|
+
this.maxWaitMs = maxWaitMs;
|
|
74
|
+
}
|
|
46
75
|
|
|
47
76
|
push(item: QueuedMessage): boolean {
|
|
48
|
-
|
|
77
|
+
this.expireStale();
|
|
78
|
+
|
|
79
|
+
if (this.items.length >= MAX_QUEUE_DEPTH) {
|
|
80
|
+
this.droppedCount++;
|
|
81
|
+
item.onEvent({
|
|
82
|
+
type: 'error',
|
|
83
|
+
message: 'Message queue is full. Please wait for current messages to be processed.',
|
|
84
|
+
category: 'queue_full',
|
|
85
|
+
});
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
item.queuedAt = Date.now();
|
|
49
90
|
this.items.push(item);
|
|
91
|
+
|
|
92
|
+
const ratio = this.items.length / MAX_QUEUE_DEPTH;
|
|
93
|
+
if (ratio >= CAPACITY_WARNING_THRESHOLD && !this.capacityWarned) {
|
|
94
|
+
this.capacityWarned = true;
|
|
95
|
+
log.warn({ depth: this.items.length, max: MAX_QUEUE_DEPTH }, 'Queue nearing capacity');
|
|
96
|
+
} else if (ratio < CAPACITY_WARNING_THRESHOLD) {
|
|
97
|
+
this.capacityWarned = false;
|
|
98
|
+
}
|
|
99
|
+
|
|
50
100
|
return true;
|
|
51
101
|
}
|
|
52
102
|
|
|
53
103
|
shift(): QueuedMessage | undefined {
|
|
54
|
-
|
|
104
|
+
this.expireStale();
|
|
105
|
+
const item = this.items.shift();
|
|
106
|
+
if (item) {
|
|
107
|
+
this.dequeuedCount++;
|
|
108
|
+
this.totalWaitMs += Date.now() - item.queuedAt;
|
|
109
|
+
}
|
|
110
|
+
if (this.items.length / MAX_QUEUE_DEPTH < CAPACITY_WARNING_THRESHOLD) {
|
|
111
|
+
this.capacityWarned = false;
|
|
112
|
+
}
|
|
113
|
+
return item;
|
|
55
114
|
}
|
|
56
115
|
|
|
57
116
|
clear(): void {
|
|
58
117
|
this.items = [];
|
|
118
|
+
this.capacityWarned = false;
|
|
59
119
|
}
|
|
60
120
|
|
|
61
121
|
get length(): number {
|
|
@@ -76,6 +136,38 @@ export class MessageQueue {
|
|
|
76
136
|
return this.items.splice(idx, 1)[0];
|
|
77
137
|
}
|
|
78
138
|
|
|
139
|
+
getMetrics(): QueueMetrics {
|
|
140
|
+
return {
|
|
141
|
+
currentDepth: this.items.length,
|
|
142
|
+
totalDropped: this.droppedCount,
|
|
143
|
+
totalExpired: this.expiredCount,
|
|
144
|
+
averageWaitMs: this.dequeuedCount > 0 ? this.totalWaitMs / this.dequeuedCount : 0,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Remove messages that have been waiting longer than maxWaitMs. */
|
|
149
|
+
private expireStale(): void {
|
|
150
|
+
const now = Date.now();
|
|
151
|
+
const cutoff = now - this.maxWaitMs;
|
|
152
|
+
const before = this.items.length;
|
|
153
|
+
this.items = this.items.filter((item) => {
|
|
154
|
+
if (item.queuedAt < cutoff) {
|
|
155
|
+
this.expiredCount++;
|
|
156
|
+
log.warn({ requestId: item.requestId, waitMs: now - item.queuedAt }, 'Expiring stale queued message');
|
|
157
|
+
item.onEvent({
|
|
158
|
+
type: 'error',
|
|
159
|
+
message: 'Your queued message was dropped because it waited too long in the queue.',
|
|
160
|
+
category: 'queue_expired',
|
|
161
|
+
});
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
return true;
|
|
165
|
+
});
|
|
166
|
+
if (this.items.length < before && this.items.length / MAX_QUEUE_DEPTH < CAPACITY_WARNING_THRESHOLD) {
|
|
167
|
+
this.capacityWarned = false;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
79
171
|
[Symbol.iterator](): Iterator<QueuedMessage> {
|
|
80
172
|
return this.items[Symbol.iterator]();
|
|
81
173
|
}
|