@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
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layered approval message composition system.
|
|
3
|
+
*
|
|
4
|
+
* Generates approval prompt text through a priority chain:
|
|
5
|
+
* 1. Assistant preface (macOS parity — reuse existing assistant text)
|
|
6
|
+
* 2. Generator-produced rewrite of deterministic fallback text (when provided by daemon)
|
|
7
|
+
* 3. Deterministic fallback templates (natural, scenario-specific messages)
|
|
8
|
+
*/
|
|
9
|
+
import { getLogger } from '../util/logger.js';
|
|
10
|
+
import type { ApprovalCopyGenerator } from './http-types.js';
|
|
11
|
+
|
|
12
|
+
const log = getLogger('approval-message-composer');
|
|
13
|
+
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Types
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
export type ApprovalMessageScenario =
|
|
19
|
+
| 'standard_prompt'
|
|
20
|
+
| 'guardian_prompt'
|
|
21
|
+
| 'reminder_prompt'
|
|
22
|
+
| 'guardian_delivery_failed'
|
|
23
|
+
| 'guardian_request_forwarded'
|
|
24
|
+
| 'guardian_disambiguation'
|
|
25
|
+
| 'guardian_identity_mismatch'
|
|
26
|
+
| 'request_pending_guardian'
|
|
27
|
+
| 'guardian_decision_outcome'
|
|
28
|
+
| 'guardian_expired_requester'
|
|
29
|
+
| 'guardian_expired_guardian'
|
|
30
|
+
| 'guardian_verify_success'
|
|
31
|
+
| 'guardian_verify_failed'
|
|
32
|
+
| 'guardian_verify_challenge_setup'
|
|
33
|
+
| 'guardian_verify_status_bound'
|
|
34
|
+
| 'guardian_verify_status_unbound'
|
|
35
|
+
| 'guardian_deny_no_identity'
|
|
36
|
+
| 'guardian_deny_no_binding'
|
|
37
|
+
| 'requester_cancel'
|
|
38
|
+
| 'approval_already_resolved';
|
|
39
|
+
|
|
40
|
+
export interface ApprovalMessageContext {
|
|
41
|
+
scenario: ApprovalMessageScenario;
|
|
42
|
+
channel?: string;
|
|
43
|
+
toolName?: string;
|
|
44
|
+
requesterIdentifier?: string;
|
|
45
|
+
guardianIdentifier?: string;
|
|
46
|
+
pendingCount?: number;
|
|
47
|
+
decision?: 'approved' | 'denied';
|
|
48
|
+
richUi?: boolean;
|
|
49
|
+
/** Pre-existing assistant text to reuse (macOS parity). */
|
|
50
|
+
assistantPreface?: string;
|
|
51
|
+
verifyCommand?: string;
|
|
52
|
+
ttlSeconds?: number;
|
|
53
|
+
failureReason?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface ComposeApprovalMessageGenerativeOptions {
|
|
57
|
+
/**
|
|
58
|
+
* Optional fallback message to use when generation fails. If omitted,
|
|
59
|
+
* the deterministic scenario fallback is used.
|
|
60
|
+
*/
|
|
61
|
+
fallbackText?: string;
|
|
62
|
+
/**
|
|
63
|
+
* Require these standalone words in the generated output (case-insensitive).
|
|
64
|
+
* Useful for plain-text decision flows where parser-compatible keywords
|
|
65
|
+
* like yes/no/always must be present.
|
|
66
|
+
*/
|
|
67
|
+
requiredKeywords?: string[];
|
|
68
|
+
timeoutMs?: number;
|
|
69
|
+
maxTokens?: number;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// Public API
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Compose an approval message using layered source selection:
|
|
78
|
+
* 1. If an assistant preface is provided and non-empty, return it directly.
|
|
79
|
+
* 2. Otherwise fall back to a deterministic scenario-specific template.
|
|
80
|
+
*/
|
|
81
|
+
export function composeApprovalMessage(context: ApprovalMessageContext): string {
|
|
82
|
+
if (context.assistantPreface && context.assistantPreface.trim().length > 0) {
|
|
83
|
+
return context.assistantPreface;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return getFallbackMessage(context);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** @internal Exported for use by the daemon-injected generator implementation. */
|
|
90
|
+
export function escapeRegExp(input: string): string {
|
|
91
|
+
return input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** @internal Exported for use by the daemon-injected generator implementation. */
|
|
95
|
+
export function includesRequiredKeywords(text: string, requiredKeywords: string[] | undefined): boolean {
|
|
96
|
+
if (!requiredKeywords || requiredKeywords.length === 0) return true;
|
|
97
|
+
return requiredKeywords.every((keyword) => {
|
|
98
|
+
const re = new RegExp(`\\b${escapeRegExp(keyword)}\\b`, 'i');
|
|
99
|
+
return re.test(text);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** @internal Exported for use by the daemon-injected generator implementation. */
|
|
104
|
+
export function buildGenerationPrompt(
|
|
105
|
+
context: ApprovalMessageContext,
|
|
106
|
+
fallbackText: string,
|
|
107
|
+
requiredKeywords: string[] | undefined,
|
|
108
|
+
): string {
|
|
109
|
+
const keywordClause = requiredKeywords && requiredKeywords.length > 0
|
|
110
|
+
? `Required words to include (as standalone words): ${requiredKeywords.join(', ')}.\n`
|
|
111
|
+
: '';
|
|
112
|
+
return [
|
|
113
|
+
'Rewrite the following approval/guardian message as a natural assistant reply to the user.',
|
|
114
|
+
'Keep the same concrete facts and next-step guidance.',
|
|
115
|
+
keywordClause,
|
|
116
|
+
`Context JSON: ${JSON.stringify(context)}`,
|
|
117
|
+
`Fallback message: ${fallbackText}`,
|
|
118
|
+
].filter(Boolean).join('\n\n');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Constants for the generator implementation (moved to exports for daemon lifecycle). */
|
|
122
|
+
export const APPROVAL_COPY_TIMEOUT_MS = 4_000;
|
|
123
|
+
export const APPROVAL_COPY_MAX_TOKENS = 180;
|
|
124
|
+
export const APPROVAL_COPY_SYSTEM_PROMPT =
|
|
125
|
+
'You are an assistant writing one user-facing message about permissions/approval state. '
|
|
126
|
+
+ 'Keep it concise, natural, and actionable. Preserve factual details exactly. '
|
|
127
|
+
+ 'Do not mention internal systems, scenario IDs, or policy engine details. '
|
|
128
|
+
+ 'Return plain text only.';
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Compose user-facing approval copy using the daemon-injected generator when
|
|
132
|
+
* available, with deterministic fallback for reliability.
|
|
133
|
+
*
|
|
134
|
+
* The generator parameter is the daemon-provided function that knows about
|
|
135
|
+
* providers. When absent (or in test env), only the deterministic fallback
|
|
136
|
+
* is used.
|
|
137
|
+
*/
|
|
138
|
+
export async function composeApprovalMessageGenerative(
|
|
139
|
+
context: ApprovalMessageContext,
|
|
140
|
+
options: ComposeApprovalMessageGenerativeOptions = {},
|
|
141
|
+
generator?: ApprovalCopyGenerator,
|
|
142
|
+
): Promise<string> {
|
|
143
|
+
if (context.assistantPreface && context.assistantPreface.trim().length > 0) {
|
|
144
|
+
return context.assistantPreface;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const fallbackText = options.fallbackText?.trim() || getFallbackMessage(context);
|
|
148
|
+
|
|
149
|
+
if (process.env.NODE_ENV === 'test') {
|
|
150
|
+
return fallbackText;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (generator) {
|
|
154
|
+
try {
|
|
155
|
+
const generated = await generator(context, options);
|
|
156
|
+
if (generated) return generated;
|
|
157
|
+
} catch (err) {
|
|
158
|
+
log.warn({ err, scenario: context.scenario }, 'Failed to generate approval copy, using fallback');
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return fallbackText;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ---------------------------------------------------------------------------
|
|
166
|
+
// Deterministic fallback templates
|
|
167
|
+
// ---------------------------------------------------------------------------
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Return a scenario-specific deterministic fallback message.
|
|
171
|
+
*
|
|
172
|
+
* Each template is slightly more conversational than the old hard-coded
|
|
173
|
+
* strings while preserving all required semantic content (tool name,
|
|
174
|
+
* who must approve, next action, etc.).
|
|
175
|
+
*/
|
|
176
|
+
export function getFallbackMessage(context: ApprovalMessageContext): string {
|
|
177
|
+
switch (context.scenario) {
|
|
178
|
+
case 'standard_prompt':
|
|
179
|
+
return `I'd like to use the tool "${context.toolName ?? 'unknown'}". Would you like to allow this?`;
|
|
180
|
+
|
|
181
|
+
case 'guardian_prompt':
|
|
182
|
+
return `${context.requesterIdentifier ?? 'A user'} is requesting to use "${context.toolName ?? 'unknown'}". Please approve or deny this request.`;
|
|
183
|
+
|
|
184
|
+
case 'reminder_prompt':
|
|
185
|
+
return 'There is a pending approval request. Ask a follow-up question or say approve/deny when you are ready.';
|
|
186
|
+
|
|
187
|
+
case 'guardian_delivery_failed':
|
|
188
|
+
return context.toolName
|
|
189
|
+
? `Your request to run "${context.toolName}" could not be sent to the guardian for approval. The request has been denied for safety.`
|
|
190
|
+
: "I wasn't able to reach the guardian to request approval. The request has been denied for safety.";
|
|
191
|
+
|
|
192
|
+
case 'guardian_request_forwarded':
|
|
193
|
+
return `Your request to use "${context.toolName ?? 'unknown'}" has been forwarded to the guardian for approval. I'll let you know once they decide.`;
|
|
194
|
+
|
|
195
|
+
case 'guardian_disambiguation':
|
|
196
|
+
return `There are ${context.pendingCount ?? 'multiple'} pending approval requests. Please use the approval buttons to specify which request you're responding to.`;
|
|
197
|
+
|
|
198
|
+
case 'guardian_identity_mismatch':
|
|
199
|
+
return 'This approval request can only be handled by the designated guardian.';
|
|
200
|
+
|
|
201
|
+
case 'request_pending_guardian':
|
|
202
|
+
return 'Your request is pending guardian approval. Please wait for the guardian to respond.';
|
|
203
|
+
|
|
204
|
+
case 'guardian_decision_outcome':
|
|
205
|
+
return `The guardian has ${context.decision ?? 'decided on'} your request to use "${context.toolName ?? 'unknown'}".`;
|
|
206
|
+
|
|
207
|
+
case 'guardian_expired_requester':
|
|
208
|
+
return `The approval request for "${context.toolName ?? 'unknown'}" has expired without a guardian response. The request has been denied.`;
|
|
209
|
+
|
|
210
|
+
case 'guardian_expired_guardian':
|
|
211
|
+
return `The approval request from ${context.requesterIdentifier ?? 'the requester'} for "${context.toolName ?? 'unknown'}" has expired.`;
|
|
212
|
+
|
|
213
|
+
case 'guardian_verify_success':
|
|
214
|
+
return 'Guardian verification successful! You are now set as the guardian for this channel.';
|
|
215
|
+
|
|
216
|
+
case 'guardian_verify_failed':
|
|
217
|
+
return `Verification failed. ${context.failureReason ?? 'Please try again.'}`;
|
|
218
|
+
|
|
219
|
+
case 'guardian_verify_challenge_setup':
|
|
220
|
+
if (context.channel === 'voice') {
|
|
221
|
+
// Voice challenges use a six-digit numeric code that can be spoken aloud
|
|
222
|
+
const code = context.verifyCommand?.replace('/guardian_verify ', '') ?? 'the verification code';
|
|
223
|
+
return `To complete guardian verification, speak or enter the six-digit code: ${code}. This code expires in ${Math.round((context.ttlSeconds ?? 600) / 60)} minutes.`;
|
|
224
|
+
}
|
|
225
|
+
return `To complete guardian verification, send ${context.verifyCommand ?? 'the verification command'} within ${context.ttlSeconds ?? 60} seconds.`;
|
|
226
|
+
|
|
227
|
+
case 'guardian_verify_status_bound':
|
|
228
|
+
return 'A guardian is currently active for this channel.';
|
|
229
|
+
|
|
230
|
+
case 'guardian_verify_status_unbound':
|
|
231
|
+
return 'No guardian is currently configured for this channel.';
|
|
232
|
+
|
|
233
|
+
case 'guardian_deny_no_identity':
|
|
234
|
+
return 'This action requires approval, but your identity could not be verified. The request has been denied for safety.';
|
|
235
|
+
|
|
236
|
+
case 'guardian_deny_no_binding':
|
|
237
|
+
return 'This action requires guardian approval, but no guardian has been configured for this channel. The request has been denied for safety.';
|
|
238
|
+
|
|
239
|
+
case 'requester_cancel':
|
|
240
|
+
return context.toolName
|
|
241
|
+
? `Your request to use "${context.toolName}" has been cancelled.`
|
|
242
|
+
: 'Your pending request has been cancelled.';
|
|
243
|
+
|
|
244
|
+
case 'approval_already_resolved':
|
|
245
|
+
return 'This approval request has already been resolved.';
|
|
246
|
+
|
|
247
|
+
default: {
|
|
248
|
+
// Exhaustive check — TypeScript will flag if a scenario is missing.
|
|
249
|
+
const _exhaustive: never = context.scenario;
|
|
250
|
+
return `Approval required. ${String(_exhaustive)}`;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
* Parses inbound user text to determine whether it matches an approval,
|
|
5
5
|
* rejection, or "approve always" intent. This module is transport-agnostic
|
|
6
6
|
* and can be used by any channel adapter (Telegram, SMS, etc.).
|
|
7
|
+
*
|
|
8
|
+
* Both the standard and guardian approval flows now use the conversational
|
|
9
|
+
* approval engine as the primary classifier. This deterministic parser is
|
|
10
|
+
* retained only as a legacy fallback for when the conversational engine is
|
|
11
|
+
* not injected (i.e. approvalConversationGenerator is undefined).
|
|
7
12
|
*/
|
|
8
13
|
|
|
9
14
|
import type { ApprovalAction, ApprovalDecisionResult } from './channel-approval-types.js';
|
|
@@ -44,6 +49,30 @@ const PHRASE_MAP = buildPhraseMap();
|
|
|
44
49
|
// Public API
|
|
45
50
|
// ---------------------------------------------------------------------------
|
|
46
51
|
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// Run-reference tag extraction
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Pattern matching a `[ref:<runId>]` disambiguation tag appended to
|
|
58
|
+
* plain-text approval prompts. Guardians can include this tag in their
|
|
59
|
+
* reply so that `handleApprovalInterception` can resolve the correct
|
|
60
|
+
* pending approval when multiple approvals target the same chat.
|
|
61
|
+
*/
|
|
62
|
+
const REF_TAG_RE = /\[ref:([^\]]+)\]/i;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Extract a run-reference tag from the text and return the cleaned
|
|
66
|
+
* decision text plus the extracted runId (if any).
|
|
67
|
+
*/
|
|
68
|
+
function extractRefTag(text: string): { cleaned: string; runId?: string } {
|
|
69
|
+
const match = REF_TAG_RE.exec(text);
|
|
70
|
+
if (!match) return { cleaned: text };
|
|
71
|
+
const runId = match[1].trim();
|
|
72
|
+
const cleaned = text.replace(REF_TAG_RE, '').trim();
|
|
73
|
+
return { cleaned, runId: runId || undefined };
|
|
74
|
+
}
|
|
75
|
+
|
|
47
76
|
/**
|
|
48
77
|
* Parse a plain-text message into an approval decision.
|
|
49
78
|
*
|
|
@@ -51,10 +80,15 @@ const PHRASE_MAP = buildPhraseMap();
|
|
|
51
80
|
* of the known intent phrases, or `null` if it does not match.
|
|
52
81
|
*
|
|
53
82
|
* Matching is case-insensitive with leading/trailing whitespace trimmed.
|
|
83
|
+
*
|
|
84
|
+
* When the text contains a `[ref:<runId>]` tag (appended by the
|
|
85
|
+
* plain-text fallback path), the extracted runId is included in the
|
|
86
|
+
* result so the caller can disambiguate among multiple pending approvals.
|
|
54
87
|
*/
|
|
55
88
|
export function parseApprovalDecision(text: string): ApprovalDecisionResult | null {
|
|
56
|
-
const
|
|
89
|
+
const { cleaned, runId } = extractRefTag(text);
|
|
90
|
+
const normalised = cleaned.trim().toLowerCase();
|
|
57
91
|
const action = PHRASE_MAP.get(normalised);
|
|
58
92
|
if (!action) return null;
|
|
59
|
-
return { action, source: 'plain_text' };
|
|
93
|
+
return { action, source: 'plain_text', ...(runId ? { runId } : {}) };
|
|
60
94
|
}
|
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
* 1. Detect pending confirmations for a conversation
|
|
8
8
|
* 2. Build human-readable approval prompts with action buttons
|
|
9
9
|
* 3. Consume user decisions and apply them to the underlying run
|
|
10
|
-
* 4. Build reminder prompts when non-decision messages arrive
|
|
11
10
|
*/
|
|
12
11
|
|
|
13
12
|
import { getPendingConfirmationsByConversation, getRun } from '../memory/runs-store.js';
|
|
@@ -21,6 +20,7 @@ import type {
|
|
|
21
20
|
ApprovalUIMetadata,
|
|
22
21
|
ApprovalDecisionResult,
|
|
23
22
|
} from './channel-approval-types.js';
|
|
23
|
+
import { composeApprovalMessage } from './approval-message-composer.js';
|
|
24
24
|
|
|
25
25
|
// ---------------------------------------------------------------------------
|
|
26
26
|
// 1. Detect pending confirmations and build prompt
|
|
@@ -47,13 +47,17 @@ export function getChannelApprovalPrompt(
|
|
|
47
47
|
* Internal helper: turn a PendingRunInfo into a ChannelApprovalPrompt.
|
|
48
48
|
*/
|
|
49
49
|
function buildPromptFromRunInfo(info: PendingRunInfo): ChannelApprovalPrompt {
|
|
50
|
-
const promptText =
|
|
50
|
+
const promptText = composeApprovalMessage({
|
|
51
|
+
scenario: 'standard_prompt',
|
|
52
|
+
toolName: info.toolName,
|
|
53
|
+
});
|
|
51
54
|
|
|
52
55
|
// Hide "approve always" when persistent trust rules are disallowed for this invocation.
|
|
53
56
|
const actions = info.persistentDecisionsAllowed === false
|
|
54
57
|
? DEFAULT_APPROVAL_ACTIONS.filter((a) => a.id !== 'approve_always')
|
|
55
58
|
: [...DEFAULT_APPROVAL_ACTIONS];
|
|
56
59
|
|
|
60
|
+
// Plain-text fallback must remain parser-compatible (contains "yes"/"always"/"no" keywords).
|
|
57
61
|
const plainTextFallback = info.persistentDecisionsAllowed === false
|
|
58
62
|
? `${promptText}\n\nReply "yes" to approve or "no" to reject.`
|
|
59
63
|
: `${promptText}\n\nReply "yes" to approve once, "always" to approve always, or "no" to reject.`;
|
|
@@ -166,8 +170,11 @@ export function buildGuardianApprovalPrompt(
|
|
|
166
170
|
info: PendingRunInfo,
|
|
167
171
|
requesterIdentifier: string,
|
|
168
172
|
): ChannelApprovalPrompt {
|
|
169
|
-
const promptText =
|
|
170
|
-
|
|
173
|
+
const promptText = composeApprovalMessage({
|
|
174
|
+
scenario: 'guardian_prompt',
|
|
175
|
+
toolName: info.toolName,
|
|
176
|
+
requesterIdentifier,
|
|
177
|
+
});
|
|
171
178
|
|
|
172
179
|
// Guardian approvals are always one-time decisions — "approve always"
|
|
173
180
|
// doesn't make sense when the guardian is approving on behalf of someone else.
|
|
@@ -198,23 +205,3 @@ const RICH_APPROVAL_CHANNELS: ReadonlySet<string> = new Set(['telegram']);
|
|
|
198
205
|
export function channelSupportsRichApprovalUI(channel: string): boolean {
|
|
199
206
|
return RICH_APPROVAL_CHANNELS.has(channel);
|
|
200
207
|
}
|
|
201
|
-
|
|
202
|
-
// ---------------------------------------------------------------------------
|
|
203
|
-
// 6. Reminder prompt for non-decision messages
|
|
204
|
-
// ---------------------------------------------------------------------------
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* Build a reminder prompt when the user sends a non-decision message while
|
|
208
|
-
* an approval is pending. Reuses the original actions and fallback text
|
|
209
|
-
* but prefixes the prompt text with a reminder.
|
|
210
|
-
*/
|
|
211
|
-
export function buildReminderPrompt(
|
|
212
|
-
pendingPrompt: ChannelApprovalPrompt,
|
|
213
|
-
): ChannelApprovalPrompt {
|
|
214
|
-
const reminderPrefix = "I'm still waiting for your decision on the previous request.";
|
|
215
|
-
return {
|
|
216
|
-
promptText: `${reminderPrefix}\n\n${pendingPrompt.promptText}`,
|
|
217
|
-
actions: pendingPrompt.actions,
|
|
218
|
-
plainTextFallback: `${reminderPrefix}\n\n${pendingPrompt.plainTextFallback}`,
|
|
219
|
-
};
|
|
220
|
-
}
|
|
@@ -12,14 +12,17 @@ import {
|
|
|
12
12
|
createBinding,
|
|
13
13
|
getActiveBinding,
|
|
14
14
|
revokeBinding as storeRevokeBinding,
|
|
15
|
+
revokePendingChallenges as storeRevokePendingChallenges,
|
|
15
16
|
createChallenge,
|
|
16
17
|
findPendingChallengeByHash,
|
|
18
|
+
findPendingChallengeForChannel,
|
|
17
19
|
consumeChallenge,
|
|
18
20
|
getRateLimit,
|
|
19
21
|
recordInvalidAttempt,
|
|
20
22
|
resetRateLimit,
|
|
21
23
|
} from '../memory/channel-guardian-store.js';
|
|
22
|
-
import type { GuardianBinding } from '../memory/channel-guardian-store.js';
|
|
24
|
+
import type { GuardianBinding, VerificationChallenge } from '../memory/channel-guardian-store.js';
|
|
25
|
+
import { composeApprovalMessage } from './approval-message-composer.js';
|
|
23
26
|
|
|
24
27
|
// ---------------------------------------------------------------------------
|
|
25
28
|
// Constants
|
|
@@ -44,6 +47,8 @@ const RATE_LIMIT_LOCKOUT_MS = 30 * 60 * 1000;
|
|
|
44
47
|
export interface CreateChallengeResult {
|
|
45
48
|
challengeId: string;
|
|
46
49
|
secret: string;
|
|
50
|
+
verifyCommand: string;
|
|
51
|
+
ttlSeconds: number;
|
|
47
52
|
instruction: string;
|
|
48
53
|
}
|
|
49
54
|
|
|
@@ -64,31 +69,32 @@ function hashSecret(secret: string): string {
|
|
|
64
69
|
// ---------------------------------------------------------------------------
|
|
65
70
|
|
|
66
71
|
/**
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* channels get appropriate wording.
|
|
72
|
+
* Generate a six-digit numeric secret for voice channel challenges.
|
|
73
|
+
* Uses cryptographic randomness to pick a number in [100000, 999999].
|
|
70
74
|
*/
|
|
71
|
-
function
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
75
|
+
function generateVoiceSecret(): string {
|
|
76
|
+
const buf = randomBytes(4);
|
|
77
|
+
const num = buf.readUInt32BE(0);
|
|
78
|
+
// Map to the range [100000, 999999] (900000 possible values)
|
|
79
|
+
return String(100000 + (num % 900000));
|
|
77
80
|
}
|
|
78
81
|
|
|
79
82
|
/**
|
|
80
83
|
* Create a new verification challenge for a guardian candidate.
|
|
81
84
|
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
+
* For voice channels, generates a six-digit numeric secret that can be
|
|
86
|
+
* spoken aloud. For all other channels, generates a 32-byte hex secret.
|
|
87
|
+
*
|
|
88
|
+
* Hashes the secret (SHA-256) and stores the challenge record with a
|
|
89
|
+
* 10-minute TTL. The raw secret is returned so it can be displayed to
|
|
90
|
+
* the user; only the hash is persisted.
|
|
85
91
|
*/
|
|
86
92
|
export function createVerificationChallenge(
|
|
87
93
|
assistantId: string,
|
|
88
94
|
channel: string,
|
|
89
95
|
sessionId?: string,
|
|
90
96
|
): CreateChallengeResult {
|
|
91
|
-
const secret = randomBytes(32).toString('hex');
|
|
97
|
+
const secret = channel === 'voice' ? generateVoiceSecret() : randomBytes(32).toString('hex');
|
|
92
98
|
const challengeHash = hashSecret(secret);
|
|
93
99
|
const challengeId = uuid();
|
|
94
100
|
const expiresAt = Date.now() + CHALLENGE_TTL_MS;
|
|
@@ -102,12 +108,20 @@ export function createVerificationChallenge(
|
|
|
102
108
|
createdBySessionId: sessionId,
|
|
103
109
|
});
|
|
104
110
|
|
|
105
|
-
const
|
|
111
|
+
const verifyCommand = `/guardian_verify ${secret}`;
|
|
112
|
+
const ttlSeconds = CHALLENGE_TTL_MS / 1000;
|
|
106
113
|
|
|
107
114
|
return {
|
|
108
115
|
challengeId,
|
|
109
116
|
secret,
|
|
110
|
-
|
|
117
|
+
verifyCommand,
|
|
118
|
+
ttlSeconds,
|
|
119
|
+
instruction: composeApprovalMessage({
|
|
120
|
+
scenario: 'guardian_verify_challenge_setup',
|
|
121
|
+
channel,
|
|
122
|
+
verifyCommand,
|
|
123
|
+
ttlSeconds,
|
|
124
|
+
}),
|
|
111
125
|
};
|
|
112
126
|
}
|
|
113
127
|
|
|
@@ -129,13 +143,21 @@ export function validateAndConsumeChallenge(
|
|
|
129
143
|
secret: string,
|
|
130
144
|
actorExternalUserId: string,
|
|
131
145
|
actorChatId: string,
|
|
146
|
+
actorUsername?: string,
|
|
147
|
+
actorDisplayName?: string,
|
|
132
148
|
): ValidateChallengeResult {
|
|
133
149
|
// ── Rate-limit check ──
|
|
134
150
|
const existing = getRateLimit(assistantId, channel, actorExternalUserId, actorChatId);
|
|
135
|
-
if (existing && existing.lockedUntil
|
|
151
|
+
if (existing && existing.lockedUntil != null && Date.now() < existing.lockedUntil) {
|
|
136
152
|
// Use the same generic failure message to avoid leaking whether the
|
|
137
153
|
// actor is rate-limited vs. the code is genuinely wrong.
|
|
138
|
-
return {
|
|
154
|
+
return {
|
|
155
|
+
success: false,
|
|
156
|
+
reason: composeApprovalMessage({
|
|
157
|
+
scenario: 'guardian_verify_failed',
|
|
158
|
+
failureReason: 'The verification code is invalid or has expired.',
|
|
159
|
+
}),
|
|
160
|
+
};
|
|
139
161
|
}
|
|
140
162
|
|
|
141
163
|
const challengeHash = hashSecret(secret);
|
|
@@ -146,7 +168,13 @@ export function validateAndConsumeChallenge(
|
|
|
146
168
|
assistantId, channel, actorExternalUserId, actorChatId,
|
|
147
169
|
RATE_LIMIT_WINDOW_MS, RATE_LIMIT_MAX_ATTEMPTS, RATE_LIMIT_LOCKOUT_MS,
|
|
148
170
|
);
|
|
149
|
-
return {
|
|
171
|
+
return {
|
|
172
|
+
success: false,
|
|
173
|
+
reason: composeApprovalMessage({
|
|
174
|
+
scenario: 'guardian_verify_failed',
|
|
175
|
+
failureReason: 'The verification code is invalid or has expired.',
|
|
176
|
+
}),
|
|
177
|
+
};
|
|
150
178
|
}
|
|
151
179
|
|
|
152
180
|
if (Date.now() > challenge.expiresAt) {
|
|
@@ -154,7 +182,13 @@ export function validateAndConsumeChallenge(
|
|
|
154
182
|
assistantId, channel, actorExternalUserId, actorChatId,
|
|
155
183
|
RATE_LIMIT_WINDOW_MS, RATE_LIMIT_MAX_ATTEMPTS, RATE_LIMIT_LOCKOUT_MS,
|
|
156
184
|
);
|
|
157
|
-
return {
|
|
185
|
+
return {
|
|
186
|
+
success: false,
|
|
187
|
+
reason: composeApprovalMessage({
|
|
188
|
+
scenario: 'guardian_verify_failed',
|
|
189
|
+
failureReason: 'The verification code is invalid or has expired.',
|
|
190
|
+
}),
|
|
191
|
+
};
|
|
158
192
|
}
|
|
159
193
|
|
|
160
194
|
// Consume the challenge so it cannot be reused
|
|
@@ -166,6 +200,14 @@ export function validateAndConsumeChallenge(
|
|
|
166
200
|
// Revoke any existing active binding before creating a new one
|
|
167
201
|
storeRevokeBinding(assistantId, channel);
|
|
168
202
|
|
|
203
|
+
const metadata: Record<string, string> = {};
|
|
204
|
+
if (actorUsername && actorUsername.trim().length > 0) {
|
|
205
|
+
metadata.username = actorUsername.trim();
|
|
206
|
+
}
|
|
207
|
+
if (actorDisplayName && actorDisplayName.trim().length > 0) {
|
|
208
|
+
metadata.displayName = actorDisplayName.trim();
|
|
209
|
+
}
|
|
210
|
+
|
|
169
211
|
// Create the new guardian binding
|
|
170
212
|
const binding = createBinding({
|
|
171
213
|
assistantId,
|
|
@@ -173,6 +215,7 @@ export function validateAndConsumeChallenge(
|
|
|
173
215
|
guardianExternalUserId: actorExternalUserId,
|
|
174
216
|
guardianDeliveryChatId: actorChatId,
|
|
175
217
|
verifiedVia: 'challenge',
|
|
218
|
+
metadataJson: Object.keys(metadata).length > 0 ? JSON.stringify(metadata) : null,
|
|
176
219
|
});
|
|
177
220
|
|
|
178
221
|
return { success: true, bindingId: binding.id };
|
|
@@ -198,7 +241,7 @@ export function isGuardian(
|
|
|
198
241
|
externalUserId: string,
|
|
199
242
|
): boolean {
|
|
200
243
|
const binding = getActiveBinding(assistantId, channel);
|
|
201
|
-
return binding
|
|
244
|
+
return binding != null && binding.guardianExternalUserId === externalUserId;
|
|
202
245
|
}
|
|
203
246
|
|
|
204
247
|
/**
|
|
@@ -210,3 +253,27 @@ export function revokeBinding(
|
|
|
210
253
|
): boolean {
|
|
211
254
|
return storeRevokeBinding(assistantId, channel);
|
|
212
255
|
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Revoke all pending challenges for a given assistant and channel.
|
|
259
|
+
* Called when the user cancels verification so that stale challenges
|
|
260
|
+
* don't gate inbound calls.
|
|
261
|
+
*/
|
|
262
|
+
export function revokePendingChallenges(
|
|
263
|
+
assistantId: string,
|
|
264
|
+
channel: string,
|
|
265
|
+
): void {
|
|
266
|
+
storeRevokePendingChallenges(assistantId, channel);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Look up a pending (non-expired) verification challenge for a given
|
|
271
|
+
* assistant and channel. Used by relay setup to detect whether an active
|
|
272
|
+
* voice verification session exists.
|
|
273
|
+
*/
|
|
274
|
+
export function getPendingChallenge(
|
|
275
|
+
assistantId: string,
|
|
276
|
+
channel: string,
|
|
277
|
+
): VerificationChallenge | null {
|
|
278
|
+
return findPendingChallengeForChannel(assistantId, channel);
|
|
279
|
+
}
|