@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,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistent store for always-allowed paired devices.
|
|
3
|
+
*
|
|
4
|
+
* Persisted to ~/.vellum/protected/approved-devices.json using the
|
|
5
|
+
* atomic-write pattern from trust-store.ts (write .tmp → rename → chmod).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { existsSync, readFileSync, writeFileSync, renameSync, chmodSync, mkdirSync } from 'node:fs';
|
|
9
|
+
import { join, dirname } from 'node:path';
|
|
10
|
+
import { createHash } from 'node:crypto';
|
|
11
|
+
import { getRootDir } from '../util/platform.js';
|
|
12
|
+
import { getLogger } from '../util/logger.js';
|
|
13
|
+
|
|
14
|
+
const log = getLogger('approved-devices-store');
|
|
15
|
+
|
|
16
|
+
export interface ApprovedDevice {
|
|
17
|
+
hashedDeviceId: string;
|
|
18
|
+
deviceName: string;
|
|
19
|
+
lastPairedAt: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface ApprovedDevicesFile {
|
|
23
|
+
version: 1;
|
|
24
|
+
devices: ApprovedDevice[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getStorePath(): string {
|
|
28
|
+
return join(getRootDir(), 'protected', 'approved-devices.json');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Hash a raw deviceId for storage. */
|
|
32
|
+
export function hashDeviceId(deviceId: string): string {
|
|
33
|
+
return createHash('sha256').update(deviceId).digest('hex');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let cachedDevices: Map<string, ApprovedDevice> | null = null;
|
|
37
|
+
|
|
38
|
+
function loadFromDisk(): Map<string, ApprovedDevice> {
|
|
39
|
+
const path = getStorePath();
|
|
40
|
+
if (!existsSync(path)) {
|
|
41
|
+
return new Map();
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const raw = readFileSync(path, 'utf-8');
|
|
45
|
+
const data = JSON.parse(raw) as ApprovedDevicesFile;
|
|
46
|
+
if (data.version !== 1 || !Array.isArray(data.devices)) {
|
|
47
|
+
log.warn('Invalid approved-devices.json format, starting fresh');
|
|
48
|
+
return new Map();
|
|
49
|
+
}
|
|
50
|
+
const map = new Map<string, ApprovedDevice>();
|
|
51
|
+
for (const device of data.devices) {
|
|
52
|
+
map.set(device.hashedDeviceId, device);
|
|
53
|
+
}
|
|
54
|
+
return map;
|
|
55
|
+
} catch (err) {
|
|
56
|
+
log.error({ err }, 'Failed to load approved-devices.json');
|
|
57
|
+
return new Map();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function saveToDisk(devices: Map<string, ApprovedDevice>): void {
|
|
62
|
+
const path = getStorePath();
|
|
63
|
+
const dir = dirname(path);
|
|
64
|
+
if (!existsSync(dir)) {
|
|
65
|
+
mkdirSync(dir, { recursive: true });
|
|
66
|
+
}
|
|
67
|
+
const data: ApprovedDevicesFile = {
|
|
68
|
+
version: 1,
|
|
69
|
+
devices: Array.from(devices.values()),
|
|
70
|
+
};
|
|
71
|
+
const tmpPath = path + '.tmp.' + process.pid;
|
|
72
|
+
writeFileSync(tmpPath, JSON.stringify(data, null, 2), { mode: 0o600 });
|
|
73
|
+
renameSync(tmpPath, path);
|
|
74
|
+
chmodSync(path, 0o600);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function getDevices(): Map<string, ApprovedDevice> {
|
|
78
|
+
if (cachedDevices == null) {
|
|
79
|
+
cachedDevices = loadFromDisk();
|
|
80
|
+
}
|
|
81
|
+
return cachedDevices;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Check if a hashed device ID is in the allowlist. */
|
|
85
|
+
export function isDeviceApproved(hashedDeviceId: string): boolean {
|
|
86
|
+
return getDevices().has(hashedDeviceId);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Add or update a device in the allowlist. */
|
|
90
|
+
export function approveDevice(hashedDeviceId: string, deviceName: string): void {
|
|
91
|
+
const devices = getDevices();
|
|
92
|
+
devices.set(hashedDeviceId, {
|
|
93
|
+
hashedDeviceId,
|
|
94
|
+
deviceName,
|
|
95
|
+
lastPairedAt: Date.now(),
|
|
96
|
+
});
|
|
97
|
+
saveToDisk(devices);
|
|
98
|
+
log.info({ hashedDeviceId }, 'Device approved and saved to allowlist');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Update lastPairedAt and deviceName for an existing device (auto-approve refresh). */
|
|
102
|
+
export function refreshDevice(hashedDeviceId: string, deviceName: string): void {
|
|
103
|
+
const devices = getDevices();
|
|
104
|
+
const existing = devices.get(hashedDeviceId);
|
|
105
|
+
if (existing) {
|
|
106
|
+
existing.deviceName = deviceName;
|
|
107
|
+
existing.lastPairedAt = Date.now();
|
|
108
|
+
saveToDisk(devices);
|
|
109
|
+
log.info({ hashedDeviceId }, 'Device metadata refreshed');
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Remove a device from the allowlist. Returns true if removed. */
|
|
114
|
+
export function removeDevice(hashedDeviceId: string): boolean {
|
|
115
|
+
const devices = getDevices();
|
|
116
|
+
const removed = devices.delete(hashedDeviceId);
|
|
117
|
+
if (removed) {
|
|
118
|
+
saveToDisk(devices);
|
|
119
|
+
log.info({ hashedDeviceId }, 'Device removed from allowlist');
|
|
120
|
+
}
|
|
121
|
+
return removed;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Clear all approved devices. */
|
|
125
|
+
export function clearAllDevices(): void {
|
|
126
|
+
const devices = getDevices();
|
|
127
|
+
devices.clear();
|
|
128
|
+
saveToDisk(devices);
|
|
129
|
+
log.info('All approved devices cleared');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** List all approved devices. */
|
|
133
|
+
export function listDevices(): ApprovedDevice[] {
|
|
134
|
+
return Array.from(getDevices().values());
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Reset the in-memory cache (for testing). */
|
|
138
|
+
export function resetCache(): void {
|
|
139
|
+
cachedDevices = null;
|
|
140
|
+
}
|
|
@@ -191,7 +191,7 @@ const ATTR_RE = /(\w+)\s*=\s*"([^"]*)"|(\w+)\s*=\s*'([^']*)'/g;
|
|
|
191
191
|
function parseAttributes(raw: string): Record<string, string> {
|
|
192
192
|
const attrs: Record<string, string> = {};
|
|
193
193
|
let m: RegExpExecArray | null;
|
|
194
|
-
while ((m = ATTR_RE.exec(raw))
|
|
194
|
+
while ((m = ATTR_RE.exec(raw)) != null) {
|
|
195
195
|
const key = m[1] ?? m[3];
|
|
196
196
|
const value = m[2] ?? m[4];
|
|
197
197
|
attrs[key] = value;
|
package/src/daemon/classifier.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { getConfig } from '../config/loader.js';
|
|
1
|
+
import { getConfiguredProvider, createTimeout, extractToolUse, userMessage } from '../providers/provider-send-message.js';
|
|
3
2
|
import { getLogger } from '../util/logger.js';
|
|
4
3
|
|
|
5
4
|
const log = getLogger('classifier');
|
|
@@ -9,7 +8,7 @@ const CLASSIFICATION_TIMEOUT_MS = 5000;
|
|
|
9
8
|
export type InteractionType = 'computer_use' | 'text_qa';
|
|
10
9
|
|
|
11
10
|
/**
|
|
12
|
-
* Classify a user task as computer_use or text_qa using
|
|
11
|
+
* Classify a user task as computer_use or text_qa using an LLM tool-use call,
|
|
13
12
|
* falling back to a heuristic if the API call fails or no API key is available.
|
|
14
13
|
*/
|
|
15
14
|
export async function classifyInteraction(task: string, source?: 'voice' | 'text'): Promise<InteractionType> {
|
|
@@ -18,21 +17,18 @@ export async function classifyInteraction(task: string, source?: 'voice' | 'text
|
|
|
18
17
|
return 'text_qa';
|
|
19
18
|
}
|
|
20
19
|
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
log.warn('No API key available, falling back to heuristic classification');
|
|
20
|
+
const provider = getConfiguredProvider();
|
|
21
|
+
if (!provider) {
|
|
22
|
+
log.warn('No configured provider available, falling back to heuristic classification');
|
|
25
23
|
return classifyHeuristic(task);
|
|
26
24
|
}
|
|
27
25
|
|
|
28
26
|
try {
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
system: 'You are a classifier. Determine whether the user\'s request requires computer use (controlling the GUI — clicking, scrolling, typing into app windows, navigating between apps) or can be handled with local tools (answering questions, running terminal commands, creating/editing/reading files, web searches, writing code). GUI tasks → computer_use. Everything else → text_qa.',
|
|
35
|
-
tools: [{
|
|
27
|
+
const { signal, cleanup } = createTimeout(CLASSIFICATION_TIMEOUT_MS);
|
|
28
|
+
try {
|
|
29
|
+
const response = await provider.sendMessage(
|
|
30
|
+
[userMessage(task)],
|
|
31
|
+
[{
|
|
36
32
|
name: 'classify_interaction',
|
|
37
33
|
description: 'Classify the user interaction type',
|
|
38
34
|
input_schema: {
|
|
@@ -51,34 +47,41 @@ export async function classifyInteraction(task: string, source?: 'voice' | 'text
|
|
|
51
47
|
required: ['interaction_type', 'reasoning'],
|
|
52
48
|
},
|
|
53
49
|
}],
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
50
|
+
'You are a classifier. Determine whether the user\'s request requires computer use (controlling the GUI — clicking, scrolling, typing into app windows, navigating between apps) or can be handled with local tools (answering questions, running terminal commands, creating/editing/reading files, web searches, writing code). GUI tasks → computer_use. Everything else → text_qa.',
|
|
51
|
+
{
|
|
52
|
+
config: {
|
|
53
|
+
modelIntent: 'latency-optimized',
|
|
54
|
+
max_tokens: 128,
|
|
55
|
+
tool_choice: { type: 'tool' as const, name: 'classify_interaction' },
|
|
56
|
+
},
|
|
57
|
+
signal,
|
|
58
|
+
},
|
|
59
|
+
);
|
|
60
|
+
cleanup();
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
62
|
+
const toolBlock = extractToolUse(response);
|
|
63
|
+
if (toolBlock) {
|
|
64
|
+
const input = toolBlock.input as { interaction_type?: string; reasoning?: string };
|
|
65
|
+
const result = input.interaction_type === 'text_qa' ? 'text_qa' : 'computer_use';
|
|
66
|
+
log.info({ result, reasoning: input.reasoning }, 'LLM classification');
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
log.warn('No tool_use block in classification response, falling back to heuristic');
|
|
71
|
+
return classifyHeuristic(task);
|
|
72
|
+
} finally {
|
|
73
|
+
cleanup();
|
|
74
|
+
}
|
|
72
75
|
} catch (err) {
|
|
73
76
|
const message = err instanceof Error ? err.message : String(err);
|
|
74
|
-
log.warn({ err: message }, '
|
|
77
|
+
log.warn({ err: message }, 'LLM classification failed, falling back to heuristic');
|
|
75
78
|
return classifyHeuristic(task);
|
|
76
79
|
}
|
|
77
80
|
}
|
|
78
81
|
|
|
79
82
|
/**
|
|
80
83
|
* Heuristic classifier — direct port of the Swift client's logic.
|
|
81
|
-
* Used as fallback when the
|
|
84
|
+
* Used as fallback when the LLM API call is unavailable or fails.
|
|
82
85
|
*/
|
|
83
86
|
export function classifyHeuristic(task: string): InteractionType {
|
|
84
87
|
const lower = task.toLowerCase().trim();
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { mkdirSync, readFileSync, writeFileSync, unlinkSync, existsSync, openSync, closeSync } from 'node:fs';
|
|
3
|
+
import { join, resolve } from 'node:path';
|
|
4
|
+
import {
|
|
5
|
+
getSocketPath,
|
|
6
|
+
getPidPath,
|
|
7
|
+
getRootDir,
|
|
8
|
+
removeSocketFile,
|
|
9
|
+
} from '../util/platform.js';
|
|
10
|
+
import { getLogger } from '../util/logger.js';
|
|
11
|
+
import { DaemonError } from '../util/errors.js';
|
|
12
|
+
import { getConfig } from '../config/loader.js';
|
|
13
|
+
|
|
14
|
+
const log = getLogger('lifecycle');
|
|
15
|
+
|
|
16
|
+
function isProcessRunning(pid: number): boolean {
|
|
17
|
+
try {
|
|
18
|
+
process.kill(pid, 0);
|
|
19
|
+
return true;
|
|
20
|
+
} catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function readPid(): number | null {
|
|
26
|
+
const pidPath = getPidPath();
|
|
27
|
+
if (!existsSync(pidPath)) return null;
|
|
28
|
+
try {
|
|
29
|
+
const pid = parseInt(readFileSync(pidPath, 'utf-8').trim(), 10);
|
|
30
|
+
return isNaN(pid) ? null : pid;
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function writePid(pid: number): void {
|
|
37
|
+
writeFileSync(getPidPath(), String(pid));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function cleanupPidFile(): void {
|
|
41
|
+
const pidPath = getPidPath();
|
|
42
|
+
if (existsSync(pidPath)) {
|
|
43
|
+
unlinkSync(pidPath);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function isDaemonRunning(): boolean {
|
|
48
|
+
const pid = readPid();
|
|
49
|
+
if (pid == null) return false;
|
|
50
|
+
if (!isProcessRunning(pid)) {
|
|
51
|
+
cleanupPidFile();
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function getDaemonStatus(): { running: boolean; pid?: number } {
|
|
58
|
+
const pid = readPid();
|
|
59
|
+
if (pid == null) return { running: false };
|
|
60
|
+
if (!isProcessRunning(pid)) {
|
|
61
|
+
cleanupPidFile();
|
|
62
|
+
return { running: false };
|
|
63
|
+
}
|
|
64
|
+
return { running: true, pid };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function startDaemon(): Promise<{
|
|
68
|
+
pid: number;
|
|
69
|
+
alreadyRunning: boolean;
|
|
70
|
+
}> {
|
|
71
|
+
const status = getDaemonStatus();
|
|
72
|
+
if (status.running && status.pid) {
|
|
73
|
+
return { pid: status.pid, alreadyRunning: true };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Only create the root dir for socket/PID — the daemon process itself
|
|
77
|
+
// handles migration + full ensureDataDir() in runDaemon(). Calling
|
|
78
|
+
// ensureDataDir() here would pre-create workspace destination dirs
|
|
79
|
+
// and cause migration moves to no-op.
|
|
80
|
+
const rootDir = getRootDir();
|
|
81
|
+
if (!existsSync(rootDir)) {
|
|
82
|
+
mkdirSync(rootDir, { recursive: true });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Clean up stale socket (only if it's actually a Unix socket)
|
|
86
|
+
const socketPath = getSocketPath();
|
|
87
|
+
removeSocketFile(socketPath);
|
|
88
|
+
|
|
89
|
+
// Spawn the daemon as a detached child process
|
|
90
|
+
const mainPath = resolve(
|
|
91
|
+
import.meta.dirname ?? __dirname,
|
|
92
|
+
'main.ts',
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
// Redirect the child's stderr to a file instead of piping it back to the
|
|
96
|
+
// parent. A pipe's read end is destroyed when the parent exits, leaving
|
|
97
|
+
// fd 2 broken in the child. Bun (unlike Node.js) does not ignore SIGPIPE,
|
|
98
|
+
// so any later stderr write would silently kill the daemon.
|
|
99
|
+
const stderrPath = join(rootDir, 'daemon-stderr.log');
|
|
100
|
+
const stderrFd = openSync(stderrPath, 'w');
|
|
101
|
+
|
|
102
|
+
const child = spawn('bun', ['run', mainPath], {
|
|
103
|
+
detached: true,
|
|
104
|
+
stdio: ['ignore', 'ignore', stderrFd],
|
|
105
|
+
env: { ...process.env },
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// The child inherited the fd; close the parent's copy.
|
|
109
|
+
closeSync(stderrFd);
|
|
110
|
+
|
|
111
|
+
let childExited = false;
|
|
112
|
+
let childExitCode: number | null = null;
|
|
113
|
+
child.on('exit', (code) => {
|
|
114
|
+
childExited = true;
|
|
115
|
+
childExitCode = code;
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
child.unref();
|
|
119
|
+
|
|
120
|
+
const pid = child.pid;
|
|
121
|
+
if (!pid) {
|
|
122
|
+
throw new DaemonError('Failed to start daemon: no PID returned');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
writePid(pid);
|
|
126
|
+
|
|
127
|
+
// Wait for socket to appear
|
|
128
|
+
const config = getConfig();
|
|
129
|
+
const maxWait = config.daemon.startupSocketWaitMs;
|
|
130
|
+
const interval = 100;
|
|
131
|
+
let waited = 0;
|
|
132
|
+
while (waited < maxWait) {
|
|
133
|
+
if (existsSync(socketPath)) {
|
|
134
|
+
return { pid, alreadyRunning: false };
|
|
135
|
+
}
|
|
136
|
+
if (childExited) {
|
|
137
|
+
cleanupPidFile();
|
|
138
|
+
const stderr = readFileSync(stderrPath, 'utf-8').trim();
|
|
139
|
+
const detail = stderr
|
|
140
|
+
? `\n${stderr}`
|
|
141
|
+
: `\nCheck logs at ~/.vellum/workspace/data/logs/ for details.`;
|
|
142
|
+
throw new DaemonError(
|
|
143
|
+
`Daemon exited immediately (code ${childExitCode ?? 'unknown'}).${detail}`,
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
await new Promise((r) => setTimeout(r, interval));
|
|
147
|
+
waited += interval;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
throw new DaemonError(
|
|
151
|
+
`Daemon started but socket not available after ${maxWait}ms`,
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export type StopResult =
|
|
156
|
+
| { stopped: true }
|
|
157
|
+
| { stopped: false; reason: 'not_running' | 'stop_failed' };
|
|
158
|
+
|
|
159
|
+
export async function stopDaemon(): Promise<StopResult> {
|
|
160
|
+
const pid = readPid();
|
|
161
|
+
if (pid == null || !isProcessRunning(pid)) {
|
|
162
|
+
cleanupPidFile();
|
|
163
|
+
return { stopped: false, reason: 'not_running' };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
process.kill(pid, 'SIGTERM');
|
|
167
|
+
|
|
168
|
+
const config = getConfig();
|
|
169
|
+
|
|
170
|
+
// Wait for process to exit
|
|
171
|
+
const maxWait = config.daemon.stopTimeoutMs;
|
|
172
|
+
const interval = 100;
|
|
173
|
+
let waited = 0;
|
|
174
|
+
while (waited < maxWait) {
|
|
175
|
+
if (!isProcessRunning(pid)) {
|
|
176
|
+
cleanupPidFile();
|
|
177
|
+
return { stopped: true };
|
|
178
|
+
}
|
|
179
|
+
await new Promise((r) => setTimeout(r, interval));
|
|
180
|
+
waited += interval;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Force kill
|
|
184
|
+
try {
|
|
185
|
+
process.kill(pid, 'SIGKILL');
|
|
186
|
+
} catch (err) {
|
|
187
|
+
log.debug({ err, pid }, 'SIGKILL failed, process already exited');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Wait for the process to actually die after SIGKILL. Without this,
|
|
191
|
+
// startDaemon() can race with the dying process's shutdown handler,
|
|
192
|
+
// which removes the socket file and bricks the new daemon.
|
|
193
|
+
const killMaxWait = config.daemon.sigkillGracePeriodMs;
|
|
194
|
+
let killWaited = 0;
|
|
195
|
+
while (killWaited < killMaxWait && isProcessRunning(pid)) {
|
|
196
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
197
|
+
killWaited += 100;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Only clean up if the process has actually exited.
|
|
201
|
+
// If it's still alive after SIGKILL + timeout, preserve both socket
|
|
202
|
+
// and PID file so isDaemonRunning() still reports true and prevents
|
|
203
|
+
// a duplicate daemon from being spawned.
|
|
204
|
+
if (!isProcessRunning(pid)) {
|
|
205
|
+
removeSocketFile(getSocketPath());
|
|
206
|
+
cleanupPidFile();
|
|
207
|
+
return { stopped: true };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
log.warn({ pid }, 'Daemon process still running after SIGKILL + timeout, leaving socket and PID file intact');
|
|
211
|
+
return { stopped: false, reason: 'stop_failed' };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export async function ensureDaemonRunning(): Promise<void> {
|
|
215
|
+
if (isDaemonRunning()) return;
|
|
216
|
+
await startDaemon();
|
|
217
|
+
}
|
|
@@ -3,6 +3,7 @@ import { v4 as uuid } from 'uuid';
|
|
|
3
3
|
import { existsSync, rmSync, readdirSync, readFileSync } from 'node:fs';
|
|
4
4
|
import { homedir } from 'node:os';
|
|
5
5
|
import { join } from 'node:path';
|
|
6
|
+
import { getRuntimeHttpPort } from '../../config/env.js';
|
|
6
7
|
import { queryAppRecords, createAppRecord, updateAppRecord, deleteAppRecord, listApps, getApp, getAppPreview, createApp, updateApp } from '../../memory/app-store.js';
|
|
7
8
|
import { computeContentId } from '../../util/content-id.js';
|
|
8
9
|
import { packageApp } from '../../bundler/app-bundler.js';
|
|
@@ -365,9 +366,7 @@ export async function handleShareAppCloud(
|
|
|
365
366
|
const bundleData = readFileSync(result.bundlePath);
|
|
366
367
|
const { shareToken } = createSharedAppLink(bundleData, result.manifest);
|
|
367
368
|
|
|
368
|
-
const port =
|
|
369
|
-
? parseInt(process.env.RUNTIME_HTTP_PORT, 10)
|
|
370
|
-
: 7821;
|
|
369
|
+
const port = getRuntimeHttpPort() ?? 7821;
|
|
371
370
|
const shareUrl = `http://localhost:${port}/v1/apps/shared/${shareToken}`;
|
|
372
371
|
|
|
373
372
|
ctx.send(socket, {
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import * as net from 'node:net';
|
|
2
|
+
import { createVerificationChallenge, getGuardianBinding, getPendingChallenge, revokeBinding as revokeGuardianBinding, revokePendingChallenges } from '../../runtime/channel-guardian-service.js';
|
|
3
|
+
import { createReadinessService, type ChannelReadinessService } from '../../runtime/channel-readiness-service.js';
|
|
4
|
+
import * as externalConversationStore from '../../memory/external-conversation-store.js';
|
|
5
|
+
import type {
|
|
6
|
+
GuardianVerificationRequest,
|
|
7
|
+
ChannelReadinessRequest,
|
|
8
|
+
} from '../ipc-protocol.js';
|
|
9
|
+
import { normalizeAssistantId } from '../../util/platform.js';
|
|
10
|
+
import { log, defineHandlers, type HandlerContext } from './shared.js';
|
|
11
|
+
|
|
12
|
+
// Lazy singleton — created on first use so module-load stays lightweight.
|
|
13
|
+
let _readinessService: ChannelReadinessService | undefined;
|
|
14
|
+
export function getReadinessService(): ChannelReadinessService {
|
|
15
|
+
if (!_readinessService) {
|
|
16
|
+
_readinessService = createReadinessService();
|
|
17
|
+
}
|
|
18
|
+
return _readinessService;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function handleGuardianVerification(
|
|
22
|
+
msg: GuardianVerificationRequest,
|
|
23
|
+
socket: net.Socket,
|
|
24
|
+
ctx: HandlerContext,
|
|
25
|
+
): void {
|
|
26
|
+
// Normalize the assistant ID so challenges are always stored under the
|
|
27
|
+
// same key the inbound-call path will use for lookups (typically "self").
|
|
28
|
+
const assistantId = normalizeAssistantId(msg.assistantId ?? 'self');
|
|
29
|
+
const channel = msg.channel ?? 'telegram';
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
if (msg.action === 'create_challenge') {
|
|
33
|
+
const result = createVerificationChallenge(assistantId, channel, msg.sessionId);
|
|
34
|
+
|
|
35
|
+
ctx.send(socket, {
|
|
36
|
+
type: 'guardian_verification_response',
|
|
37
|
+
success: true,
|
|
38
|
+
secret: result.secret,
|
|
39
|
+
instruction: result.instruction,
|
|
40
|
+
channel,
|
|
41
|
+
});
|
|
42
|
+
} else if (msg.action === 'status') {
|
|
43
|
+
const binding = getGuardianBinding(assistantId, channel);
|
|
44
|
+
let guardianUsername: string | undefined;
|
|
45
|
+
let guardianDisplayName: string | undefined;
|
|
46
|
+
if (binding?.metadataJson) {
|
|
47
|
+
try {
|
|
48
|
+
const parsed = JSON.parse(binding.metadataJson) as Record<string, unknown>;
|
|
49
|
+
if (typeof parsed.username === 'string' && parsed.username.trim().length > 0) {
|
|
50
|
+
guardianUsername = parsed.username.trim();
|
|
51
|
+
}
|
|
52
|
+
if (typeof parsed.displayName === 'string' && parsed.displayName.trim().length > 0) {
|
|
53
|
+
guardianDisplayName = parsed.displayName.trim();
|
|
54
|
+
}
|
|
55
|
+
} catch {
|
|
56
|
+
// ignore malformed metadata
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (binding?.guardianDeliveryChatId && (!guardianUsername || !guardianDisplayName)) {
|
|
60
|
+
const ext = externalConversationStore.getBindingByChannelChat(
|
|
61
|
+
channel,
|
|
62
|
+
binding.guardianDeliveryChatId,
|
|
63
|
+
);
|
|
64
|
+
if (!guardianUsername && ext?.username) {
|
|
65
|
+
guardianUsername = ext.username;
|
|
66
|
+
}
|
|
67
|
+
if (!guardianDisplayName && ext?.displayName) {
|
|
68
|
+
guardianDisplayName = ext.displayName;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const hasPendingChallenge = getPendingChallenge(assistantId, channel) != null;
|
|
72
|
+
ctx.send(socket, {
|
|
73
|
+
type: 'guardian_verification_response',
|
|
74
|
+
success: true,
|
|
75
|
+
bound: binding != null,
|
|
76
|
+
guardianExternalUserId: binding?.guardianExternalUserId,
|
|
77
|
+
guardianUsername,
|
|
78
|
+
guardianDisplayName,
|
|
79
|
+
channel,
|
|
80
|
+
assistantId,
|
|
81
|
+
guardianDeliveryChatId: binding?.guardianDeliveryChatId,
|
|
82
|
+
hasPendingChallenge,
|
|
83
|
+
});
|
|
84
|
+
} else if (msg.action === 'revoke') {
|
|
85
|
+
revokeGuardianBinding(assistantId, channel);
|
|
86
|
+
revokePendingChallenges(assistantId, channel);
|
|
87
|
+
ctx.send(socket, {
|
|
88
|
+
type: 'guardian_verification_response',
|
|
89
|
+
success: true,
|
|
90
|
+
bound: false,
|
|
91
|
+
channel,
|
|
92
|
+
});
|
|
93
|
+
} else {
|
|
94
|
+
ctx.send(socket, {
|
|
95
|
+
type: 'guardian_verification_response',
|
|
96
|
+
success: false,
|
|
97
|
+
error: `Unknown action: ${String(msg.action)}`,
|
|
98
|
+
channel,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
} catch (err) {
|
|
102
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
103
|
+
log.error({ err }, 'Failed to handle guardian verification');
|
|
104
|
+
ctx.send(socket, {
|
|
105
|
+
type: 'guardian_verification_response',
|
|
106
|
+
success: false,
|
|
107
|
+
error: message,
|
|
108
|
+
channel,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export async function handleChannelReadiness(
|
|
114
|
+
msg: ChannelReadinessRequest,
|
|
115
|
+
socket: net.Socket,
|
|
116
|
+
ctx: HandlerContext,
|
|
117
|
+
): Promise<void> {
|
|
118
|
+
try {
|
|
119
|
+
const service = getReadinessService();
|
|
120
|
+
|
|
121
|
+
if (msg.action === 'refresh') {
|
|
122
|
+
if (msg.channel) {
|
|
123
|
+
service.invalidateChannel(msg.channel, msg.assistantId);
|
|
124
|
+
} else {
|
|
125
|
+
service.invalidateAll();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const snapshots = await service.getReadiness(msg.channel, msg.includeRemote, msg.assistantId);
|
|
130
|
+
|
|
131
|
+
ctx.send(socket, {
|
|
132
|
+
type: 'channel_readiness_response',
|
|
133
|
+
success: true,
|
|
134
|
+
snapshots: snapshots.map((s) => ({
|
|
135
|
+
channel: s.channel,
|
|
136
|
+
ready: s.ready,
|
|
137
|
+
checkedAt: s.checkedAt,
|
|
138
|
+
stale: s.stale,
|
|
139
|
+
reasons: s.reasons,
|
|
140
|
+
localChecks: s.localChecks,
|
|
141
|
+
remoteChecks: s.remoteChecks,
|
|
142
|
+
})),
|
|
143
|
+
});
|
|
144
|
+
} catch (err) {
|
|
145
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
146
|
+
log.error({ err }, 'Failed to handle channel readiness');
|
|
147
|
+
ctx.send(socket, {
|
|
148
|
+
type: 'channel_readiness_response',
|
|
149
|
+
success: false,
|
|
150
|
+
error: message,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export const channelHandlers = defineHandlers({
|
|
156
|
+
channel_readiness: handleChannelReadiness,
|
|
157
|
+
guardian_verification: handleGuardianVerification,
|
|
158
|
+
});
|