@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
|
@@ -2,6 +2,7 @@ import { and, asc, desc, eq, lte } from 'drizzle-orm';
|
|
|
2
2
|
import { v4 as uuid } from 'uuid';
|
|
3
3
|
import { Cron } from 'croner';
|
|
4
4
|
import { getDb } from '../memory/db.js';
|
|
5
|
+
import { rawChanges } from '../memory/raw-query.js';
|
|
5
6
|
import { scheduleJobs, scheduleRuns } from '../memory/schema.js';
|
|
6
7
|
import { computeNextRunAt as computeNextRunAtEngine, isValidScheduleExpression } from './recurrence-engine.js';
|
|
7
8
|
import { getLogger } from '../util/logger.js';
|
|
@@ -189,8 +190,8 @@ export function updateSchedule(
|
|
|
189
190
|
|
|
190
191
|
export function deleteSchedule(id: string): boolean {
|
|
191
192
|
const db = getDb();
|
|
192
|
-
|
|
193
|
-
return (
|
|
193
|
+
db.delete(scheduleJobs).where(eq(scheduleJobs.id, id)).run();
|
|
194
|
+
return rawChanges() > 0;
|
|
194
195
|
}
|
|
195
196
|
|
|
196
197
|
/**
|
|
@@ -244,13 +245,13 @@ export function claimDueSchedules(now: number): ScheduleJob[] {
|
|
|
244
245
|
updates.nextRunAt = newNextRunAt!;
|
|
245
246
|
}
|
|
246
247
|
|
|
247
|
-
|
|
248
|
+
db
|
|
248
249
|
.update(scheduleJobs)
|
|
249
250
|
.set(updates)
|
|
250
251
|
.where(and(eq(scheduleJobs.id, row.id), eq(scheduleJobs.nextRunAt, row.nextRunAt)))
|
|
251
|
-
.run()
|
|
252
|
+
.run();
|
|
252
253
|
|
|
253
|
-
if ((
|
|
254
|
+
if (rawChanges() === 0) continue;
|
|
254
255
|
|
|
255
256
|
claimed.push(parseJobRow({
|
|
256
257
|
...row,
|
|
@@ -359,7 +360,14 @@ export function formatLocalDate(timestamp: number): string {
|
|
|
359
360
|
export function describeCronExpression(expr: string): string {
|
|
360
361
|
try {
|
|
361
362
|
const cron = new Cron(expr, { maxRuns: 0 });
|
|
362
|
-
|
|
363
|
+
// Access Croner internal state to extract the parsed cron pattern.
|
|
364
|
+
// This is fragile but necessary — Croner doesn't expose a public API for this.
|
|
365
|
+
const cronInternal = cron as unknown as Record<string, unknown>;
|
|
366
|
+
const states = cronInternal._states;
|
|
367
|
+
if (!states || typeof states !== 'object') return expr;
|
|
368
|
+
const p = (states as Record<string, unknown>).pattern;
|
|
369
|
+
if (!p || typeof p !== 'object') return expr;
|
|
370
|
+
const pattern = p as {
|
|
363
371
|
minute: number[];
|
|
364
372
|
hour: number[];
|
|
365
373
|
day: number[];
|
|
@@ -367,20 +375,28 @@ export function describeCronExpression(expr: string): string {
|
|
|
367
375
|
dayOfWeek: number[];
|
|
368
376
|
starDOM: boolean;
|
|
369
377
|
starDOW: boolean;
|
|
370
|
-
}
|
|
378
|
+
};
|
|
371
379
|
|
|
372
|
-
const activeMinutes =
|
|
373
|
-
const activeHours =
|
|
374
|
-
const activeDays =
|
|
375
|
-
const activeDOW =
|
|
376
|
-
const activeMonths =
|
|
380
|
+
const activeMinutes = pattern.minute.reduce<number[]>((acc, v, i) => { if (v) acc.push(i); return acc; }, []);
|
|
381
|
+
const activeHours = pattern.hour.reduce<number[]>((acc, v, i) => { if (v) acc.push(i); return acc; }, []);
|
|
382
|
+
const activeDays = pattern.day.reduce<number[]>((acc, v, i) => { if (v) acc.push(i + 1); return acc; }, []);
|
|
383
|
+
const activeDOW = pattern.dayOfWeek.reduce<number[]>((acc, v, i) => { if (v) acc.push(i); return acc; }, []);
|
|
384
|
+
const activeMonths = pattern.month.reduce<number[]>((acc, v, i) => { if (v) acc.push(i + 1); return acc; }, []);
|
|
377
385
|
|
|
378
386
|
const allMinutes = activeMinutes.length === 60;
|
|
379
387
|
const allHours = activeHours.length === 24;
|
|
380
|
-
const allDays =
|
|
381
|
-
const allDOW =
|
|
388
|
+
const allDays = pattern.starDOM;
|
|
389
|
+
const allDOW = pattern.starDOW;
|
|
382
390
|
const allMonths = activeMonths.length === 12;
|
|
383
391
|
|
|
392
|
+
const fixedMinute = activeMinutes.length === 1;
|
|
393
|
+
const fixedHour = activeHours.length === 1;
|
|
394
|
+
const fixedTime = fixedMinute && fixedHour;
|
|
395
|
+
const steppedMinutes = !allMinutes && activeMinutes.length > 1;
|
|
396
|
+
const steppedHours = !allHours && activeHours.length > 1;
|
|
397
|
+
const anyDay = allDays && allDOW;
|
|
398
|
+
const anyDayAndMonth = anyDay && allMonths;
|
|
399
|
+
|
|
384
400
|
// Format time as 12-hour clock
|
|
385
401
|
function formatTime(hour: number, minute: number): string {
|
|
386
402
|
const period = hour >= 12 ? 'PM' : 'AM';
|
|
@@ -396,14 +412,11 @@ export function describeCronExpression(expr: string): string {
|
|
|
396
412
|
return n + (s[(v - 20) % 10] || s[v] || s[0]);
|
|
397
413
|
}
|
|
398
414
|
|
|
399
|
-
|
|
400
|
-
if (allMinutes && allHours && allDays && allDOW && allMonths) {
|
|
415
|
+
if (allMinutes && allHours && anyDayAndMonth) {
|
|
401
416
|
return 'Every minute';
|
|
402
417
|
}
|
|
403
418
|
|
|
404
|
-
|
|
405
|
-
if (!allMinutes && activeMinutes.length > 1 && allHours && allDays && allDOW && allMonths) {
|
|
406
|
-
// Check if it's a regular step pattern (e.g. */5)
|
|
419
|
+
if (steppedMinutes && allHours && anyDayAndMonth) {
|
|
407
420
|
if (activeMinutes.length >= 2 && activeMinutes[0] === 0) {
|
|
408
421
|
const step = activeMinutes[1] - activeMinutes[0];
|
|
409
422
|
const isRegularStep = activeMinutes.every((v, i) => v === i * step);
|
|
@@ -413,16 +426,14 @@ export function describeCronExpression(expr: string): string {
|
|
|
413
426
|
}
|
|
414
427
|
}
|
|
415
428
|
|
|
416
|
-
|
|
417
|
-
if (activeMinutes.length === 1 && allHours && allDays && allDOW && allMonths) {
|
|
429
|
+
if (fixedMinute && allHours && anyDayAndMonth) {
|
|
418
430
|
if (activeMinutes[0] === 0) {
|
|
419
431
|
return 'Every hour';
|
|
420
432
|
}
|
|
421
433
|
return `Every hour at minute ${activeMinutes[0]}`;
|
|
422
434
|
}
|
|
423
435
|
|
|
424
|
-
|
|
425
|
-
if (activeMinutes.length === 1 && !allHours && activeHours.length > 1 && allDays && allDOW && allMonths) {
|
|
436
|
+
if (fixedMinute && steppedHours && anyDayAndMonth) {
|
|
426
437
|
if (activeHours.length >= 2 && activeHours[0] === 0) {
|
|
427
438
|
const step = activeHours[1] - activeHours[0];
|
|
428
439
|
const isRegularStep = activeHours.every((v, i) => v === i * step);
|
|
@@ -432,33 +443,26 @@ export function describeCronExpression(expr: string): string {
|
|
|
432
443
|
}
|
|
433
444
|
}
|
|
434
445
|
|
|
435
|
-
|
|
436
|
-
if (activeMinutes.length === 1 && activeHours.length === 1 && allMonths) {
|
|
446
|
+
if (fixedTime && allMonths) {
|
|
437
447
|
const timeStr = formatTime(activeHours[0], activeMinutes[0]);
|
|
438
448
|
|
|
439
|
-
// Check day-of-week constraints
|
|
440
449
|
if (allDays && !allDOW) {
|
|
441
|
-
// Weekdays: Mon-Fri (1-5)
|
|
442
450
|
if (activeDOW.length === 5 && activeDOW.every((d) => d >= 1 && d <= 5)) {
|
|
443
451
|
return `Every weekday at ${timeStr}`;
|
|
444
452
|
}
|
|
445
|
-
// Weekends: Sat, Sun (0, 6)
|
|
446
453
|
if (activeDOW.length === 2 && activeDOW.includes(0) && activeDOW.includes(6)) {
|
|
447
454
|
return `Every weekend at ${timeStr}`;
|
|
448
455
|
}
|
|
449
|
-
// Specific days of week
|
|
450
456
|
const dayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
451
457
|
const names = activeDOW.map((d) => dayNames[d]);
|
|
452
458
|
return `Every ${names.join(', ')} at ${timeStr}`;
|
|
453
459
|
}
|
|
454
460
|
|
|
455
|
-
// Specific day of month
|
|
456
461
|
if (!allDays && allDOW && activeDays.length === 1) {
|
|
457
462
|
return `On the ${ordinal(activeDays[0])} of every month at ${timeStr}`;
|
|
458
463
|
}
|
|
459
464
|
|
|
460
|
-
|
|
461
|
-
if (allDays && allDOW) {
|
|
465
|
+
if (anyDay) {
|
|
462
466
|
return `Every day at ${timeStr}`;
|
|
463
467
|
}
|
|
464
468
|
}
|
|
@@ -103,14 +103,14 @@ async function runScheduleOnce(
|
|
|
103
103
|
const message = err instanceof Error ? err.message : String(err);
|
|
104
104
|
log.warn({ err, jobId: job.id, name: job.name, taskId, syntax: job.syntax, expression: job.expression, isRruleSet }, 'Scheduled task execution failed');
|
|
105
105
|
// Create a fallback conversation for the schedule run record
|
|
106
|
-
const fallbackConversation = createConversation(`Schedule: ${job.name}
|
|
106
|
+
const fallbackConversation = createConversation({ title: `Schedule: ${job.name}`, source: 'schedule' });
|
|
107
107
|
const runId = createScheduleRun(job.id, fallbackConversation.id);
|
|
108
108
|
completeScheduleRun(runId, { status: 'error', error: message });
|
|
109
109
|
}
|
|
110
110
|
continue;
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
const conversation = createConversation(`Schedule: ${job.name}
|
|
113
|
+
const conversation = createConversation({ title: `Schedule: ${job.name}`, source: 'schedule' });
|
|
114
114
|
const runId = createScheduleRun(job.id, conversation.id);
|
|
115
115
|
const isRruleSetMsg = job.syntax === 'rrule' && hasSetConstructs(job.expression);
|
|
116
116
|
|
|
@@ -131,7 +131,7 @@ async function runScheduleOnce(
|
|
|
131
131
|
const dueReminders = claimDueReminders(now);
|
|
132
132
|
for (const reminder of dueReminders) {
|
|
133
133
|
if (reminder.mode === 'execute') {
|
|
134
|
-
const conversation = createConversation(`Reminder: ${reminder.label}
|
|
134
|
+
const conversation = createConversation({ title: `Reminder: ${reminder.label}`, source: 'reminder' });
|
|
135
135
|
setReminderConversationId(reminder.id, conversation.id);
|
|
136
136
|
try {
|
|
137
137
|
log.info({ reminderId: reminder.id, label: reminder.label, conversationId: conversation.id }, 'Executing reminder');
|
|
@@ -17,8 +17,9 @@ import {
|
|
|
17
17
|
createCipheriv,
|
|
18
18
|
createDecipheriv,
|
|
19
19
|
} from 'node:crypto';
|
|
20
|
-
import { readFileSync, writeFileSync,
|
|
20
|
+
import { readFileSync, writeFileSync, chmodSync } from 'node:fs';
|
|
21
21
|
import { join, dirname } from 'node:path';
|
|
22
|
+
import { pathExists, ensureDir } from '../util/fs.js';
|
|
22
23
|
import { hostname, userInfo } from 'node:os';
|
|
23
24
|
import { getRootDir, getPlatformName } from '../util/platform.js';
|
|
24
25
|
import { getLogger } from '../util/logger.js';
|
|
@@ -98,7 +99,7 @@ function deriveKey(salt: Buffer): Buffer {
|
|
|
98
99
|
*/
|
|
99
100
|
function readStore(): StoreFile | null {
|
|
100
101
|
const path = getStorePath();
|
|
101
|
-
if (!
|
|
102
|
+
if (!pathExists(path)) return null;
|
|
102
103
|
|
|
103
104
|
const raw = readFileSync(path, 'utf-8');
|
|
104
105
|
const parsed = JSON.parse(raw);
|
|
@@ -114,10 +115,7 @@ function readStore(): StoreFile | null {
|
|
|
114
115
|
|
|
115
116
|
function writeStore(store: StoreFile): void {
|
|
116
117
|
const path = getStorePath();
|
|
117
|
-
|
|
118
|
-
if (!existsSync(dir)) {
|
|
119
|
-
mkdirSync(dir, { recursive: true });
|
|
120
|
-
}
|
|
118
|
+
ensureDir(dirname(path));
|
|
121
119
|
writeFileSync(path, JSON.stringify(store, null, 2), { mode: 0o600 });
|
|
122
120
|
// Enforce 0600 even if the file already existed with permissive bits
|
|
123
121
|
chmodSync(path, 0o600);
|
|
@@ -125,7 +123,7 @@ function writeStore(store: StoreFile): void {
|
|
|
125
123
|
|
|
126
124
|
function getOrCreateStore(): StoreFile {
|
|
127
125
|
const path = getStorePath();
|
|
128
|
-
if (
|
|
126
|
+
if (pathExists(path)) {
|
|
129
127
|
// File exists — must be parseable, otherwise fail to prevent data loss
|
|
130
128
|
return readStore()!;
|
|
131
129
|
}
|
package/src/security/oauth2.ts
CHANGED
|
@@ -18,16 +18,25 @@ const log = getLogger('oauth2');
|
|
|
18
18
|
// Types
|
|
19
19
|
// ---------------------------------------------------------------------------
|
|
20
20
|
|
|
21
|
+
export type TokenEndpointAuthMethod = 'client_secret_basic' | 'client_secret_post';
|
|
22
|
+
|
|
21
23
|
export interface OAuth2Config {
|
|
22
24
|
authUrl: string;
|
|
23
25
|
tokenUrl: string;
|
|
24
26
|
scopes: string[];
|
|
25
27
|
clientId: string;
|
|
26
|
-
/** Client secret for providers that require it (e.g. Slack).
|
|
28
|
+
/** Client secret for providers that require it (e.g. Slack). PKCE is always used regardless. */
|
|
27
29
|
clientSecret?: string;
|
|
28
30
|
extraParams?: Record<string, string>;
|
|
29
31
|
/** URL to fetch user identity info after OAuth. If omitted, account info is not fetched. */
|
|
30
32
|
userinfoUrl?: string;
|
|
33
|
+
/**
|
|
34
|
+
* How the client authenticates at the token endpoint when a clientSecret is present.
|
|
35
|
+
* - `client_secret_post`: Send client_id and client_secret in the POST body (default).
|
|
36
|
+
* - `client_secret_basic`: Send an HTTP Basic Auth header with base64(client_id:client_secret).
|
|
37
|
+
* Defaults to `client_secret_post` for backward compatibility.
|
|
38
|
+
*/
|
|
39
|
+
tokenEndpointAuthMethod?: TokenEndpointAuthMethod;
|
|
31
40
|
}
|
|
32
41
|
|
|
33
42
|
export interface OAuth2TokenResult {
|
|
@@ -80,24 +89,32 @@ async function exchangeCodeForTokens(
|
|
|
80
89
|
redirectUri: string,
|
|
81
90
|
codeVerifier: string,
|
|
82
91
|
): Promise<OAuth2FlowResult> {
|
|
83
|
-
const
|
|
92
|
+
const authMethod = config.tokenEndpointAuthMethod ?? 'client_secret_post';
|
|
84
93
|
|
|
85
94
|
const tokenBody: Record<string, string> = {
|
|
86
95
|
grant_type: 'authorization_code',
|
|
87
96
|
code,
|
|
88
97
|
redirect_uri: redirectUri,
|
|
89
|
-
|
|
98
|
+
code_verifier: codeVerifier,
|
|
90
99
|
};
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
100
|
+
|
|
101
|
+
const headers: Record<string, string> = {
|
|
102
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
if (config.clientSecret && authMethod === 'client_secret_basic') {
|
|
106
|
+
const credentials = Buffer.from(`${config.clientId}:${config.clientSecret}`).toString('base64');
|
|
107
|
+
headers['Authorization'] = `Basic ${credentials}`;
|
|
108
|
+
} else {
|
|
109
|
+
tokenBody.client_id = config.clientId;
|
|
110
|
+
if (config.clientSecret) {
|
|
111
|
+
tokenBody.client_secret = config.clientSecret;
|
|
112
|
+
}
|
|
96
113
|
}
|
|
97
114
|
|
|
98
115
|
const tokenResp = await fetch(config.tokenUrl, {
|
|
99
116
|
method: 'POST',
|
|
100
|
-
headers
|
|
117
|
+
headers,
|
|
101
118
|
body: new URLSearchParams(tokenBody),
|
|
102
119
|
});
|
|
103
120
|
|
|
@@ -160,7 +177,6 @@ async function runGatewayFlow(
|
|
|
160
177
|
registerPendingCallback(state, resolve, reject);
|
|
161
178
|
});
|
|
162
179
|
|
|
163
|
-
const usePKCE = !config.clientSecret;
|
|
164
180
|
const authParams = new URLSearchParams({
|
|
165
181
|
...config.extraParams,
|
|
166
182
|
client_id: config.clientId,
|
|
@@ -168,7 +184,8 @@ async function runGatewayFlow(
|
|
|
168
184
|
response_type: 'code',
|
|
169
185
|
scope: config.scopes.join(' '),
|
|
170
186
|
state,
|
|
171
|
-
|
|
187
|
+
code_challenge: codeChallenge,
|
|
188
|
+
code_challenge_method: 'S256',
|
|
172
189
|
});
|
|
173
190
|
|
|
174
191
|
const authUrl = `${config.authUrl}?${authParams}`;
|
|
@@ -230,19 +247,32 @@ export async function refreshOAuth2Token(
|
|
|
230
247
|
clientId: string,
|
|
231
248
|
refreshToken: string,
|
|
232
249
|
clientSecret?: string,
|
|
250
|
+
tokenEndpointAuthMethod?: TokenEndpointAuthMethod,
|
|
233
251
|
): Promise<OAuth2TokenResult> {
|
|
252
|
+
const authMethod = tokenEndpointAuthMethod ?? 'client_secret_post';
|
|
253
|
+
|
|
234
254
|
const body: Record<string, string> = {
|
|
235
255
|
grant_type: 'refresh_token',
|
|
236
256
|
refresh_token: refreshToken,
|
|
237
|
-
client_id: clientId,
|
|
238
257
|
};
|
|
239
|
-
|
|
240
|
-
|
|
258
|
+
|
|
259
|
+
const headers: Record<string, string> = {
|
|
260
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
if (clientSecret && authMethod === 'client_secret_basic') {
|
|
264
|
+
const credentials = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');
|
|
265
|
+
headers['Authorization'] = `Basic ${credentials}`;
|
|
266
|
+
} else {
|
|
267
|
+
body.client_id = clientId;
|
|
268
|
+
if (clientSecret) {
|
|
269
|
+
body.client_secret = clientSecret;
|
|
270
|
+
}
|
|
241
271
|
}
|
|
242
272
|
|
|
243
273
|
const resp = await fetch(tokenUrl, {
|
|
244
274
|
method: 'POST',
|
|
245
|
-
headers
|
|
275
|
+
headers,
|
|
246
276
|
body: new URLSearchParams(body),
|
|
247
277
|
});
|
|
248
278
|
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parental control settings and PIN management.
|
|
3
|
+
*
|
|
4
|
+
* Non-secret settings (enabled state, content restrictions, blocked tool
|
|
5
|
+
* categories) are persisted to `~/.vellum/parental-control.json`.
|
|
6
|
+
*
|
|
7
|
+
* The PIN hash and salt are stored in the encrypted key store under the
|
|
8
|
+
* account `parental:pin` as the hex string `"<salt>:<hash>"`.
|
|
9
|
+
*
|
|
10
|
+
* PIN hashing uses SHA-256 with a random 16-byte salt to prevent offline
|
|
11
|
+
* dictionary attacks. Comparison uses timingSafeEqual to avoid timing leaks.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { createHash, randomBytes, timingSafeEqual } from 'node:crypto';
|
|
15
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
16
|
+
import { join, dirname } from 'node:path';
|
|
17
|
+
import { pathExists, ensureDir } from '../util/fs.js';
|
|
18
|
+
import { getRootDir } from '../util/platform.js';
|
|
19
|
+
import { getKey, setKey, deleteKey } from './encrypted-store.js';
|
|
20
|
+
import { getLogger } from '../util/logger.js';
|
|
21
|
+
import type { ParentalContentTopic, ParentalToolCategory } from '../daemon/ipc-contract/parental-control.js';
|
|
22
|
+
|
|
23
|
+
const log = getLogger('parental-control');
|
|
24
|
+
|
|
25
|
+
const PIN_ACCOUNT = 'parental:pin';
|
|
26
|
+
|
|
27
|
+
export type { ParentalContentTopic, ParentalToolCategory };
|
|
28
|
+
|
|
29
|
+
export interface ParentalControlSettings {
|
|
30
|
+
enabled: boolean;
|
|
31
|
+
contentRestrictions: ParentalContentTopic[];
|
|
32
|
+
blockedToolCategories: ParentalToolCategory[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const DEFAULT_SETTINGS: ParentalControlSettings = {
|
|
36
|
+
enabled: false,
|
|
37
|
+
contentRestrictions: [],
|
|
38
|
+
blockedToolCategories: [],
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
function getSettingsPath(): string {
|
|
42
|
+
return join(getRootDir(), 'parental-control.json');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Settings I/O
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
export function getParentalControlSettings(): ParentalControlSettings {
|
|
50
|
+
try {
|
|
51
|
+
const file = getSettingsPath();
|
|
52
|
+
if (!pathExists(file)) return { ...DEFAULT_SETTINGS };
|
|
53
|
+
const raw = readFileSync(file, 'utf-8');
|
|
54
|
+
const parsed = JSON.parse(raw) as Partial<ParentalControlSettings>;
|
|
55
|
+
return {
|
|
56
|
+
enabled: typeof parsed.enabled === 'boolean' ? parsed.enabled : false,
|
|
57
|
+
contentRestrictions: Array.isArray(parsed.contentRestrictions) ? parsed.contentRestrictions : [],
|
|
58
|
+
blockedToolCategories: Array.isArray(parsed.blockedToolCategories) ? parsed.blockedToolCategories : [],
|
|
59
|
+
};
|
|
60
|
+
} catch {
|
|
61
|
+
return { ...DEFAULT_SETTINGS };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function saveSettings(settings: ParentalControlSettings): void {
|
|
66
|
+
const file = getSettingsPath();
|
|
67
|
+
ensureDir(dirname(file));
|
|
68
|
+
writeFileSync(file, JSON.stringify(settings, null, 2), { encoding: 'utf-8' });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function updateParentalControlSettings(
|
|
72
|
+
patch: Partial<ParentalControlSettings>,
|
|
73
|
+
): ParentalControlSettings {
|
|
74
|
+
const current = getParentalControlSettings();
|
|
75
|
+
const next: ParentalControlSettings = {
|
|
76
|
+
enabled: patch.enabled !== undefined ? patch.enabled : current.enabled,
|
|
77
|
+
contentRestrictions: patch.contentRestrictions !== undefined
|
|
78
|
+
? patch.contentRestrictions
|
|
79
|
+
: current.contentRestrictions,
|
|
80
|
+
blockedToolCategories: patch.blockedToolCategories !== undefined
|
|
81
|
+
? patch.blockedToolCategories
|
|
82
|
+
: current.blockedToolCategories,
|
|
83
|
+
};
|
|
84
|
+
saveSettings(next);
|
|
85
|
+
return next;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
// PIN management
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
/** Returns true if a parental control PIN has been configured. */
|
|
93
|
+
export function hasPIN(): boolean {
|
|
94
|
+
return getKey(PIN_ACCOUNT) !== undefined;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function hashPIN(pin: string, salt: Buffer): Buffer {
|
|
98
|
+
return createHash('sha256').update(salt).update(pin).digest();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Set a new PIN. Rejects if `pin` is not exactly 6 ASCII digits.
|
|
103
|
+
* Throws if the store write fails.
|
|
104
|
+
*/
|
|
105
|
+
export function setPIN(pin: string): void {
|
|
106
|
+
if (!/^\d{6}$/.test(pin)) {
|
|
107
|
+
throw new Error('PIN must be exactly 6 digits');
|
|
108
|
+
}
|
|
109
|
+
const salt = randomBytes(16);
|
|
110
|
+
const hash = hashPIN(pin, salt);
|
|
111
|
+
const stored = `${salt.toString('hex')}:${hash.toString('hex')}`;
|
|
112
|
+
if (!setKey(PIN_ACCOUNT, stored)) {
|
|
113
|
+
throw new Error('Failed to persist PIN — encrypted store write error');
|
|
114
|
+
}
|
|
115
|
+
log.info('Parental control PIN set');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Verify a PIN attempt. Returns true on match, false on mismatch or if no
|
|
120
|
+
* PIN has been configured. Uses constant-time comparison to prevent timing
|
|
121
|
+
* attacks.
|
|
122
|
+
*/
|
|
123
|
+
export function verifyPIN(pin: string): boolean {
|
|
124
|
+
if (!/^\d{6}$/.test(pin)) return false;
|
|
125
|
+
const stored = getKey(PIN_ACCOUNT);
|
|
126
|
+
if (!stored) return false;
|
|
127
|
+
|
|
128
|
+
const colonIdx = stored.indexOf(':');
|
|
129
|
+
if (colonIdx === -1) return false;
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
const salt = Buffer.from(stored.slice(0, colonIdx), 'hex');
|
|
133
|
+
const expectedHash = Buffer.from(stored.slice(colonIdx + 1), 'hex');
|
|
134
|
+
const actualHash = hashPIN(pin, salt);
|
|
135
|
+
if (actualHash.length !== expectedHash.length) return false;
|
|
136
|
+
return timingSafeEqual(actualHash, expectedHash);
|
|
137
|
+
} catch {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Remove the PIN. The caller is responsible for requiring PIN verification
|
|
144
|
+
* before calling this.
|
|
145
|
+
*/
|
|
146
|
+
export function clearPIN(): void {
|
|
147
|
+
deleteKey(PIN_ACCOUNT);
|
|
148
|
+
log.info('Parental control PIN cleared');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ---------------------------------------------------------------------------
|
|
152
|
+
// Tool category → tool name mapping
|
|
153
|
+
// ---------------------------------------------------------------------------
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Tool name prefixes that belong to each blocked category.
|
|
157
|
+
* A tool is considered blocked if its name starts with any of the listed
|
|
158
|
+
* prefixes (case-sensitive).
|
|
159
|
+
*/
|
|
160
|
+
export const TOOL_CATEGORY_PREFIXES: Record<ParentalToolCategory, string[]> = {
|
|
161
|
+
computer_use: ['cu_', 'computer_use', 'screenshot', 'accessibility_'],
|
|
162
|
+
network: ['web_fetch', 'web_search', 'browser_'],
|
|
163
|
+
shell: ['bash', 'terminal', 'host_shell'],
|
|
164
|
+
file_write: ['file_write', 'file_edit', 'multi_edit', 'file_delete', 'git'],
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Returns true if the given tool name falls within any of the currently
|
|
169
|
+
* blocked tool categories.
|
|
170
|
+
*/
|
|
171
|
+
export function isToolBlocked(toolName: string): boolean {
|
|
172
|
+
const { enabled, blockedToolCategories } = getParentalControlSettings();
|
|
173
|
+
if (!enabled || blockedToolCategories.length === 0) return false;
|
|
174
|
+
|
|
175
|
+
for (const category of blockedToolCategories) {
|
|
176
|
+
const prefixes = TOOL_CATEGORY_PREFIXES[category];
|
|
177
|
+
// Guard against unknown categories that may appear after deserialization of
|
|
178
|
+
// settings written by a newer client version — skip rather than throw.
|
|
179
|
+
if (!prefixes) continue;
|
|
180
|
+
if (prefixes.some((p) => toolName.startsWith(p))) return true;
|
|
181
|
+
}
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
@@ -13,9 +13,10 @@
|
|
|
13
13
|
* }
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import { readFileSync
|
|
16
|
+
import { readFileSync } from 'node:fs';
|
|
17
17
|
import { join } from 'node:path';
|
|
18
18
|
import { getRootDir } from '../util/platform.js';
|
|
19
|
+
import { pathExists } from '../util/fs.js';
|
|
19
20
|
import { getLogger } from '../util/logger.js';
|
|
20
21
|
|
|
21
22
|
const log = getLogger('secret-allowlist');
|
|
@@ -44,7 +45,7 @@ export function loadAllowlist(): void {
|
|
|
44
45
|
if (loaded || fileChecked) return;
|
|
45
46
|
|
|
46
47
|
const filePath = join(getRootDir(), 'protected', 'secret-allowlist.json');
|
|
47
|
-
if (!
|
|
48
|
+
if (!pathExists(filePath)) {
|
|
48
49
|
fileChecked = true;
|
|
49
50
|
return;
|
|
50
51
|
}
|
|
@@ -143,7 +144,7 @@ export function validateAllowlist(config: AllowlistConfig): AllowlistValidationE
|
|
|
143
144
|
*/
|
|
144
145
|
export function validateAllowlistFile(): AllowlistValidationError[] | null {
|
|
145
146
|
const filePath = join(getRootDir(), 'protected', 'secret-allowlist.json');
|
|
146
|
-
if (!
|
|
147
|
+
if (!pathExists(filePath)) return null;
|
|
147
148
|
|
|
148
149
|
const raw = readFileSync(filePath, 'utf-8');
|
|
149
150
|
const config: AllowlistConfig = JSON.parse(raw);
|
|
@@ -393,7 +393,7 @@ function scanEntropy(
|
|
|
393
393
|
// Scan hex tokens
|
|
394
394
|
HEX_TOKEN_RE.lastIndex = 0;
|
|
395
395
|
let m: RegExpExecArray | null;
|
|
396
|
-
while ((m = HEX_TOKEN_RE.exec(text))
|
|
396
|
+
while ((m = HEX_TOKEN_RE.exec(text)) != null) {
|
|
397
397
|
const value = m[1];
|
|
398
398
|
if (value.length < config.minLength) continue;
|
|
399
399
|
const startIndex = m.index;
|
|
@@ -424,7 +424,7 @@ function scanEntropy(
|
|
|
424
424
|
|
|
425
425
|
// Scan base64 tokens
|
|
426
426
|
BASE64_TOKEN_RE.lastIndex = 0;
|
|
427
|
-
while ((m = BASE64_TOKEN_RE.exec(text))
|
|
427
|
+
while ((m = BASE64_TOKEN_RE.exec(text)) != null) {
|
|
428
428
|
const value = m[1];
|
|
429
429
|
if (value.length < config.minLength) continue;
|
|
430
430
|
// Must look like base64 (not pure alphanumeric) or pure hex
|
|
@@ -603,7 +603,7 @@ function scanEncoded(
|
|
|
603
603
|
for (const pattern of PATTERNS) {
|
|
604
604
|
pattern.regex.lastIndex = 0;
|
|
605
605
|
let pm: RegExpExecArray | null;
|
|
606
|
-
while ((pm = pattern.regex.exec(decoded))
|
|
606
|
+
while ((pm = pattern.regex.exec(decoded)) != null) {
|
|
607
607
|
const value = pm[1] ?? pm[0];
|
|
608
608
|
if (isPlaceholder(value)) continue;
|
|
609
609
|
if (isAllowlisted(value)) continue;
|
|
@@ -649,7 +649,7 @@ function scanEncoded(
|
|
|
649
649
|
if (quickCheck && !quickCheck(text)) continue;
|
|
650
650
|
regex.lastIndex = 0;
|
|
651
651
|
let m: RegExpExecArray | null;
|
|
652
|
-
while ((m = regex.exec(text))
|
|
652
|
+
while ((m = regex.exec(text)) != null) {
|
|
653
653
|
const encoded = m[1] ?? m[0];
|
|
654
654
|
if (encoded.length > 1000) continue;
|
|
655
655
|
const startIndex = m.index + (m[0].indexOf(encoded));
|
|
@@ -688,7 +688,7 @@ export function scanText(text: string, entropyConfig?: Partial<EntropyConfig>):
|
|
|
688
688
|
// Reset lastIndex for global regexes
|
|
689
689
|
pattern.regex.lastIndex = 0;
|
|
690
690
|
let m: RegExpExecArray | null;
|
|
691
|
-
while ((m = pattern.regex.exec(text))
|
|
691
|
+
while ((m = pattern.regex.exec(text)) != null) {
|
|
692
692
|
// Use first capturing group if present, otherwise full match
|
|
693
693
|
const value = m[1] ?? m[0];
|
|
694
694
|
const startIndex = m.index + (m[0].indexOf(value));
|
|
@@ -123,7 +123,7 @@ export function deleteSecureKey(account: string): boolean {
|
|
|
123
123
|
// backend — saveConfig routinely deletes keys for unset providers.
|
|
124
124
|
// getKey now returns null for "not found" and throws on runtime errors.
|
|
125
125
|
try {
|
|
126
|
-
if (keychain.getKey(account)
|
|
126
|
+
if (keychain.getKey(account) == null) {
|
|
127
127
|
return false;
|
|
128
128
|
}
|
|
129
129
|
} catch {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { getSecureKey, setSecureKey } from './secure-keys.js';
|
|
10
10
|
import { getCredentialMetadata, upsertCredentialMetadata } from '../tools/credentials/metadata-store.js';
|
|
11
|
-
import { refreshOAuth2Token } from './oauth2.js';
|
|
11
|
+
import { refreshOAuth2Token, type TokenEndpointAuthMethod } from './oauth2.js';
|
|
12
12
|
import { getLogger } from '../util/logger.js';
|
|
13
13
|
|
|
14
14
|
const log = getLogger('token-manager');
|
|
@@ -66,11 +66,12 @@ async function doRefresh(service: string): Promise<string> {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
const clientSecret = meta?.oauth2ClientSecret as string | undefined;
|
|
69
|
+
const authMethod = meta?.oauth2TokenEndpointAuthMethod as TokenEndpointAuthMethod | undefined;
|
|
69
70
|
const resolvedTokenUrl = tokenUrl;
|
|
70
71
|
|
|
71
72
|
log.info({ service }, 'Refreshing OAuth2 access token');
|
|
72
73
|
|
|
73
|
-
const result = await refreshOAuth2Token(resolvedTokenUrl, clientId, refreshToken, clientSecret);
|
|
74
|
+
const result = await refreshOAuth2Token(resolvedTokenUrl, clientId, refreshToken, clientSecret, authMethod);
|
|
74
75
|
|
|
75
76
|
if (!setSecureKey(`credential:${service}:access_token`, result.accessToken)) {
|
|
76
77
|
throw new Error(`Failed to store refreshed access token for "${service}"`);
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* Thin wrapper around the Vercel REST API for deploying static HTML pages.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { ProviderError } from '../util/errors.js';
|
|
6
|
+
|
|
5
7
|
export async function deployHtmlToVercel(opts: {
|
|
6
8
|
html: string;
|
|
7
9
|
name: string;
|
|
@@ -37,7 +39,7 @@ export async function deployHtmlToVercel(opts: {
|
|
|
37
39
|
|
|
38
40
|
if (!response.ok) {
|
|
39
41
|
const text = await response.text();
|
|
40
|
-
throw new
|
|
42
|
+
throw new ProviderError(`Vercel deploy failed (${response.status}): ${text}`, 'vercel', response.status);
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
const data = (await response.json()) as { url: string; id: string };
|
|
@@ -66,8 +68,10 @@ export async function deleteVercelDeployment(
|
|
|
66
68
|
|
|
67
69
|
if (!response.ok) {
|
|
68
70
|
const text = await response.text();
|
|
69
|
-
throw new
|
|
71
|
+
throw new ProviderError(
|
|
70
72
|
`Vercel delete deployment failed (${response.status}): ${text}`,
|
|
73
|
+
'vercel',
|
|
74
|
+
response.status,
|
|
71
75
|
);
|
|
72
76
|
}
|
|
73
77
|
}
|