@vellumai/assistant 0.3.5 → 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/README.md +51 -0
- 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 +18 -2
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +338 -1
- package/src/__tests__/approval-conversation-turn.test.ts +214 -0
- package/src/__tests__/browser-manager.test.ts +1 -0
- package/src/__tests__/call-conversation-messages.test.ts +130 -0
- package/src/__tests__/call-orchestrator.test.ts +752 -271
- 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 +5 -0
- package/src/__tests__/call-store.test.ts +3 -0
- package/src/__tests__/channel-approval-routes.test.ts +1260 -85
- package/src/__tests__/channel-approval.test.ts +37 -0
- package/src/__tests__/channel-approvals.test.ts +4 -65
- package/src/__tests__/channel-guardian.test.ts +556 -0
- package/src/__tests__/channel-readiness-service.test.ts +74 -7
- 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 +12 -7
- 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 +6 -2
- package/src/__tests__/db-migration-rollback.test.ts +752 -0
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +2 -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 +126 -0
- package/src/__tests__/intent-routing.test.ts +2 -0
- package/src/__tests__/ipc-snapshot.test.ts +228 -1
- package/src/__tests__/memory-upsert-concurrency.test.ts +828 -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 +2 -0
- package/src/__tests__/run-orchestrator.test.ts +20 -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 +237 -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 +2 -1
- 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 +141 -21
- 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 +45 -29
- 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 +106 -5
- package/src/calls/call-orchestrator.ts +252 -54
- 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 +7 -5
- package/src/calls/twilio-provider.ts +6 -4
- package/src/calls/twilio-rest.ts +40 -15
- package/src/calls/twilio-routes.ts +60 -45
- 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/macos-automation/icon.svg +12 -0
- package/src/config/bundled-skills/media-processing/SKILL.md +72 -95
- package/src/config/bundled-skills/media-processing/TOOLS.json +57 -147
- 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 +7 -9
- package/src/config/bundled-skills/media-processing/services/reduce.ts +197 -0
- package/src/config/bundled-skills/media-processing/tools/analyze-keyframes.ts +88 -253
- package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +22 -153
- package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +2 -2
- package/src/config/bundled-skills/media-processing/tools/media-diagnostics.ts +28 -51
- package/src/config/bundled-skills/media-processing/tools/query-media-events.ts +35 -270
- package/src/config/bundled-skills/messaging/SKILL.md +12 -2
- 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/phone-calls/SKILL.md +86 -21
- 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 +26 -2
- 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 +156 -1137
- 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 +107 -56
- 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 +0 -7
- package/src/config/vellum-skills/chatgpt-import/tools/chatgpt-import.ts +5 -1
- package/src/config/vellum-skills/slack-oauth-setup/SKILL.md +1 -0
- package/src/config/vellum-skills/sms-setup/SKILL.md +112 -14
- 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 -2463
- package/src/daemon/handlers/diagnostics.ts +1 -1
- package/src/daemon/handlers/dictation.ts +4 -6
- package/src/daemon/handlers/documents.ts +18 -32
- package/src/daemon/handlers/index.ts +9 -0
- package/src/daemon/handlers/misc.ts +3 -5
- package/src/daemon/handlers/pairing.ts +98 -0
- package/src/daemon/handlers/sessions.ts +54 -5
- package/src/daemon/handlers/shared.ts +3 -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 +2 -2
- 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 +60 -0
- package/src/daemon/ipc-contract-inventory.ts +55 -29
- package/src/daemon/ipc-contract.ts +226 -2527
- package/src/daemon/ipc-protocol.ts +1 -1
- package/src/daemon/ipc-validate.ts +7 -0
- package/src/daemon/lifecycle.ts +97 -379
- package/src/daemon/pairing-store.ts +177 -0
- package/src/daemon/providers-setup.ts +43 -0
- package/src/daemon/ride-shotgun-handler.ts +67 -2
- package/src/daemon/server.ts +60 -44
- package/src/daemon/session-agent-loop-handlers.ts +421 -0
- package/src/daemon/session-agent-loop.ts +113 -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 +149 -15
- package/src/daemon/session-surfaces.ts +19 -4
- package/src/daemon/session-tool-setup.ts +28 -30
- package/src/daemon/session-workspace.ts +1 -1
- package/src/daemon/session.ts +24 -1
- 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 +3 -1
- 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 +200 -1
- 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 +121 -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 +11 -42
- package/src/memory/job-handlers/summarization.ts +32 -26
- package/src/memory/job-utils.ts +3 -10
- package/src/memory/jobs-store.ts +6 -9
- package/src/memory/jobs-worker.ts +51 -36
- 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 +160 -47
- package/src/memory/schema-migration.ts +25 -984
- package/src/memory/schema.ts +130 -7
- package/src/memory/search/entity.ts +10 -19
- package/src/memory/search/lexical.ts +81 -52
- package/src/memory/search/ranking.ts +21 -22
- package/src/memory/search/semantic.ts +157 -19
- 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/providers/sms/adapter.ts +3 -6
- 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 +119 -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 +115 -5
- package/src/runtime/channel-approval-parser.ts +36 -2
- package/src/runtime/channel-approvals.ts +0 -21
- package/src/runtime/channel-guardian-service.ts +48 -7
- package/src/runtime/channel-readiness-service.ts +160 -34
- package/src/runtime/channel-readiness-types.ts +10 -4
- 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 -743
- package/src/runtime/http-types.ts +56 -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 +49 -6
- 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 -1634
- 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 +52 -34
- 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/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/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/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 +7 -3
- 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/watcher/delete.ts +6 -0
- package/src/tools/weather/service.ts +1 -1
- package/src/twitter/client.ts +190 -24
- 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 +72 -365
- 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/bundled-skills/media-processing/services/capability-registry.ts +0 -137
- package/src/config/bundled-skills/media-processing/services/event-detection-service.ts +0 -280
- package/src/config/bundled-skills/media-processing/services/feedback-aggregation.ts +0 -144
- package/src/config/bundled-skills/media-processing/services/feedback-store.ts +0 -136
- package/src/config/bundled-skills/media-processing/services/retrieval-service.ts +0 -95
- package/src/config/bundled-skills/media-processing/services/timeline-service.ts +0 -267
- package/src/config/bundled-skills/media-processing/tools/detect-events.ts +0 -110
- package/src/config/bundled-skills/media-processing/tools/recalibrate.ts +0 -235
- package/src/config/bundled-skills/media-processing/tools/select-tracking-profile.ts +0 -142
- package/src/config/bundled-skills/media-processing/tools/submit-feedback.ts +0 -150
- package/src/config/vellum-skills/google-oauth-setup/SKILL.md +0 -199
|
@@ -34,15 +34,35 @@ export interface WatcherProvider {
|
|
|
34
34
|
/**
|
|
35
35
|
* Fetch new events since the given watermark.
|
|
36
36
|
* Returns new items and an updated watermark.
|
|
37
|
+
*
|
|
38
|
+
* `watcherKey` is the unique watcher instance ID (e.g. the DB row UUID).
|
|
39
|
+
* Providers that maintain per-watcher in-process state (like the Linear
|
|
40
|
+
* issue-state cache) must key that state by `watcherKey` — not just
|
|
41
|
+
* `credentialService` — so that multiple watchers sharing the same
|
|
42
|
+
* credential maintain independent baselines.
|
|
37
43
|
*/
|
|
38
44
|
fetchNew(
|
|
39
45
|
credentialService: string,
|
|
40
46
|
watermark: string | null,
|
|
41
47
|
config: Record<string, unknown>,
|
|
48
|
+
watcherKey: string,
|
|
42
49
|
): Promise<FetchResult>;
|
|
43
50
|
|
|
44
51
|
/**
|
|
45
52
|
* Get the initial watermark (start from "now" so we don't replay history).
|
|
46
53
|
*/
|
|
47
54
|
getInitialWatermark(credentialService: string): Promise<string>;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Release any in-process state held for a watcher instance.
|
|
58
|
+
* Called only when a watcher is truly deleted so that providers with
|
|
59
|
+
* per-watcher caches (e.g. the Linear issue-state map) can evict the
|
|
60
|
+
* stale entry and prevent unbounded memory growth.
|
|
61
|
+
*
|
|
62
|
+
* Must NOT be called on circuit-breaker auto-disable — that path is
|
|
63
|
+
* reversible, and clearing state would cause missed events on re-enable.
|
|
64
|
+
*
|
|
65
|
+
* Optional — providers with no per-watcher state need not implement this.
|
|
66
|
+
*/
|
|
67
|
+
cleanup?(watcherKey: string): void;
|
|
48
68
|
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub watcher provider — polls for new PRs, issues, and review requests.
|
|
3
|
+
*
|
|
4
|
+
* Uses the GitHub Notifications API (`GET /notifications`) with a timestamp
|
|
5
|
+
* watermark. On first poll, captures the current time as the watermark so we
|
|
6
|
+
* start from "now" and don't replay historical notifications.
|
|
7
|
+
*
|
|
8
|
+
* The credential service expects a GitHub Personal Access Token (or fine-grained
|
|
9
|
+
* token) stored under `integration:github`. The token needs at minimum the
|
|
10
|
+
* `notifications` scope (classic) or Notification read permission (fine-grained).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { withValidToken } from '../../security/token-manager.js';
|
|
14
|
+
import { truncate } from '../../util/truncate.js';
|
|
15
|
+
import type { WatcherProvider, WatcherItem, FetchResult } from '../provider-types.js';
|
|
16
|
+
import { getLogger } from '../../util/logger.js';
|
|
17
|
+
|
|
18
|
+
const log = getLogger('watcher:github');
|
|
19
|
+
|
|
20
|
+
const GITHUB_API_BASE = 'https://api.github.com';
|
|
21
|
+
|
|
22
|
+
// ── API types ──────────────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
interface GitHubNotification {
|
|
25
|
+
id: string;
|
|
26
|
+
reason: string; // 'assign', 'author', 'comment', 'mention', 'review_requested', 'subscribed', etc.
|
|
27
|
+
unread: boolean;
|
|
28
|
+
updated_at: string;
|
|
29
|
+
subject: {
|
|
30
|
+
title: string;
|
|
31
|
+
url: string | null;
|
|
32
|
+
latest_comment_url: string | null;
|
|
33
|
+
type: 'Issue' | 'PullRequest' | 'Release' | 'Commit' | string;
|
|
34
|
+
};
|
|
35
|
+
repository: {
|
|
36
|
+
full_name: string;
|
|
37
|
+
html_url: string;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ── Helpers ────────────────────────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
/** Map a GitHub notification reason to a watcher event type. */
|
|
44
|
+
function reasonToEventType(reason: string, subjectType: string): string {
|
|
45
|
+
if (reason === 'review_requested') return 'github_review_requested';
|
|
46
|
+
if (reason === 'assign') return subjectType === 'Issue' ? 'github_issue_assigned' : 'github_pr_assigned';
|
|
47
|
+
if (reason === 'mention') return 'github_mention';
|
|
48
|
+
if (subjectType === 'PullRequest') return 'github_pr_activity';
|
|
49
|
+
return 'github_notification';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function notificationToItem(n: GitHubNotification): WatcherItem {
|
|
53
|
+
const eventType = reasonToEventType(n.reason, n.subject.type);
|
|
54
|
+
const repoName = n.repository.full_name;
|
|
55
|
+
const title = n.subject.title;
|
|
56
|
+
const subjectType = n.subject.type;
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
externalId: n.id,
|
|
60
|
+
eventType,
|
|
61
|
+
summary: `GitHub ${subjectType} in ${repoName}: ${truncate(title, 80)}`,
|
|
62
|
+
payload: {
|
|
63
|
+
id: n.id,
|
|
64
|
+
reason: n.reason,
|
|
65
|
+
subjectType: n.subject.type,
|
|
66
|
+
title,
|
|
67
|
+
subjectUrl: n.subject.url,
|
|
68
|
+
repoFullName: repoName,
|
|
69
|
+
repoHtmlUrl: n.repository.html_url,
|
|
70
|
+
updatedAt: n.updated_at,
|
|
71
|
+
},
|
|
72
|
+
timestamp: new Date(n.updated_at).getTime(),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Fetch a single page of notifications since a timestamp. */
|
|
77
|
+
async function fetchNotificationsPage(
|
|
78
|
+
token: string,
|
|
79
|
+
since: string,
|
|
80
|
+
page: number,
|
|
81
|
+
): Promise<{ items: GitHubNotification[]; hasMore: boolean }> {
|
|
82
|
+
const params = new URLSearchParams({
|
|
83
|
+
all: 'false', // only unread
|
|
84
|
+
since,
|
|
85
|
+
per_page: '50',
|
|
86
|
+
page: String(page),
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const resp = await fetch(`${GITHUB_API_BASE}/notifications?${params}`, {
|
|
90
|
+
headers: {
|
|
91
|
+
Authorization: `Bearer ${token}`,
|
|
92
|
+
Accept: 'application/vnd.github+json',
|
|
93
|
+
'X-GitHub-Api-Version': '2022-11-28',
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
if (!resp.ok) {
|
|
98
|
+
const body = await resp.text().catch(() => '');
|
|
99
|
+
throw new Error(`GitHub Notifications API ${resp.status}: ${body}`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const items = (await resp.json()) as GitHubNotification[];
|
|
103
|
+
// GitHub returns 50 per page; if we got a full page there may be more
|
|
104
|
+
const hasMore = items.length === 50;
|
|
105
|
+
return { items, hasMore };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ── Provider ───────────────────────────────────────────────────────────────────
|
|
109
|
+
|
|
110
|
+
export const githubProvider: WatcherProvider = {
|
|
111
|
+
id: 'github',
|
|
112
|
+
displayName: 'GitHub',
|
|
113
|
+
requiredCredentialService: 'integration:github',
|
|
114
|
+
|
|
115
|
+
async getInitialWatermark(_credentialService: string): Promise<string> {
|
|
116
|
+
// Start from "now" so we don't replay all existing notifications
|
|
117
|
+
return new Date().toISOString();
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
async fetchNew(
|
|
121
|
+
credentialService: string,
|
|
122
|
+
watermark: string | null,
|
|
123
|
+
_config: Record<string, unknown>,
|
|
124
|
+
_watcherKey: string,
|
|
125
|
+
): Promise<FetchResult> {
|
|
126
|
+
return withValidToken(credentialService, async (token) => {
|
|
127
|
+
const since = watermark ?? new Date().toISOString();
|
|
128
|
+
const items: WatcherItem[] = [];
|
|
129
|
+
let page = 1;
|
|
130
|
+
|
|
131
|
+
while (true) {
|
|
132
|
+
const { items: pageItems, hasMore } = await fetchNotificationsPage(token, since, page);
|
|
133
|
+
|
|
134
|
+
for (const n of pageItems) {
|
|
135
|
+
// Only surface notifications for reasons that warrant attention
|
|
136
|
+
const relevantReasons = new Set([
|
|
137
|
+
'assign', 'mention', 'review_requested', 'team_mention',
|
|
138
|
+
]);
|
|
139
|
+
if (!relevantReasons.has(n.reason)) continue;
|
|
140
|
+
|
|
141
|
+
items.push(notificationToItem(n));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (!hasMore) break;
|
|
145
|
+
page++;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// New watermark: the time just before we fetched so we don't miss events
|
|
149
|
+
// that arrive between poll cycles.
|
|
150
|
+
const newWatermark = new Date().toISOString();
|
|
151
|
+
|
|
152
|
+
log.info({ count: items.length, watermark: newWatermark }, 'GitHub: fetched new notifications');
|
|
153
|
+
return { items, watermark: newWatermark };
|
|
154
|
+
});
|
|
155
|
+
},
|
|
156
|
+
};
|
|
@@ -113,6 +113,7 @@ export const gmailProvider: WatcherProvider = {
|
|
|
113
113
|
credentialService: string,
|
|
114
114
|
watermark: string | null,
|
|
115
115
|
_config: Record<string, unknown>,
|
|
116
|
+
_watcherKey: string,
|
|
116
117
|
): Promise<FetchResult> {
|
|
117
118
|
return withValidToken(credentialService, async (token) => {
|
|
118
119
|
if (!watermark) {
|
|
@@ -139,6 +139,7 @@ export const googleCalendarProvider: WatcherProvider = {
|
|
|
139
139
|
credentialService: string,
|
|
140
140
|
watermark: string | null,
|
|
141
141
|
_config: Record<string, unknown>,
|
|
142
|
+
_watcherKey: string,
|
|
142
143
|
): Promise<FetchResult> {
|
|
143
144
|
return withValidToken(credentialService, async (token) => {
|
|
144
145
|
if (!watermark) {
|
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Linear watcher provider — polls for assigned issues, status changes, and @mentions.
|
|
3
|
+
*
|
|
4
|
+
* Uses the Linear GraphQL API with a timestamp watermark. On first poll, captures
|
|
5
|
+
* the current time as the watermark so we start from "now" and don't replay history.
|
|
6
|
+
*
|
|
7
|
+
* The watermark is an ISO 8601 timestamp string used in the `updatedAt_gte` filter.
|
|
8
|
+
* We query notifications (which cover assignments and mentions) and issue status changes
|
|
9
|
+
* for issues assigned to the authenticated user.
|
|
10
|
+
*
|
|
11
|
+
* The credential service expects a Linear API key (personal or OAuth access token)
|
|
12
|
+
* stored under `integration:linear`. The token only needs read access to notifications
|
|
13
|
+
* and issues.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { withValidToken } from '../../security/token-manager.js';
|
|
17
|
+
import { truncate } from '../../util/truncate.js';
|
|
18
|
+
import type { WatcherProvider, WatcherItem, FetchResult } from '../provider-types.js';
|
|
19
|
+
import { getLogger } from '../../util/logger.js';
|
|
20
|
+
|
|
21
|
+
const log = getLogger('watcher:linear');
|
|
22
|
+
|
|
23
|
+
const LINEAR_GRAPHQL_URL = 'https://api.linear.app/graphql';
|
|
24
|
+
|
|
25
|
+
// ── GraphQL response types ────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
interface LinearNotification {
|
|
28
|
+
id: string;
|
|
29
|
+
type: string;
|
|
30
|
+
createdAt: string;
|
|
31
|
+
updatedAt: string;
|
|
32
|
+
issue?: {
|
|
33
|
+
id: string;
|
|
34
|
+
identifier: string;
|
|
35
|
+
title: string;
|
|
36
|
+
url: string;
|
|
37
|
+
state?: {
|
|
38
|
+
id: string;
|
|
39
|
+
name: string;
|
|
40
|
+
type: string;
|
|
41
|
+
};
|
|
42
|
+
assignee?: {
|
|
43
|
+
id: string;
|
|
44
|
+
name: string;
|
|
45
|
+
email: string;
|
|
46
|
+
};
|
|
47
|
+
team?: {
|
|
48
|
+
id: string;
|
|
49
|
+
name: string;
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
comment?: {
|
|
53
|
+
id: string;
|
|
54
|
+
body: string;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
interface LinearIssue {
|
|
59
|
+
id: string;
|
|
60
|
+
identifier: string;
|
|
61
|
+
title: string;
|
|
62
|
+
url: string;
|
|
63
|
+
updatedAt: string;
|
|
64
|
+
state: {
|
|
65
|
+
id: string;
|
|
66
|
+
name: string;
|
|
67
|
+
type: string;
|
|
68
|
+
};
|
|
69
|
+
team: {
|
|
70
|
+
id: string;
|
|
71
|
+
name: string;
|
|
72
|
+
};
|
|
73
|
+
assignee?: {
|
|
74
|
+
id: string;
|
|
75
|
+
name: string;
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
interface LinearViewer {
|
|
80
|
+
id: string;
|
|
81
|
+
name: string;
|
|
82
|
+
email: string;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ── GraphQL helpers ───────────────────────────────────────────────────────────
|
|
86
|
+
|
|
87
|
+
async function graphql<T>(token: string, query: string, variables?: Record<string, unknown>): Promise<T> {
|
|
88
|
+
const resp = await fetch(LINEAR_GRAPHQL_URL, {
|
|
89
|
+
method: 'POST',
|
|
90
|
+
headers: {
|
|
91
|
+
// Linear accepts both personal API keys and OAuth tokens; the Bearer scheme
|
|
92
|
+
// is required for all token types per Linear's API docs.
|
|
93
|
+
'Authorization': `Bearer ${token}`,
|
|
94
|
+
'Content-Type': 'application/json',
|
|
95
|
+
},
|
|
96
|
+
body: JSON.stringify({ query, variables }),
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
if (!resp.ok) {
|
|
100
|
+
const body = await resp.text().catch(() => '');
|
|
101
|
+
throw new Error(`Linear API ${resp.status}: ${body}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const result = await resp.json() as { data?: T; errors?: Array<{ message: string }> };
|
|
105
|
+
|
|
106
|
+
if (result.errors?.length) {
|
|
107
|
+
throw new Error(`Linear GraphQL errors: ${result.errors.map((e) => e.message).join(', ')}`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (!result.data) {
|
|
111
|
+
throw new Error('Linear API returned no data');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return result.data;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Fetch the authenticated user's ID and name. */
|
|
118
|
+
async function fetchViewer(token: string): Promise<LinearViewer> {
|
|
119
|
+
const data = await graphql<{ viewer: LinearViewer }>(token, `
|
|
120
|
+
query {
|
|
121
|
+
viewer {
|
|
122
|
+
id
|
|
123
|
+
name
|
|
124
|
+
email
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
`);
|
|
128
|
+
return data.viewer;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Fetch all notifications since a given ISO timestamp, paginating until
|
|
133
|
+
* `pageInfo.hasNextPage` is false so we never miss events when 50+ arrive
|
|
134
|
+
* between polls.
|
|
135
|
+
*/
|
|
136
|
+
async function fetchNotifications(
|
|
137
|
+
token: string,
|
|
138
|
+
since: string,
|
|
139
|
+
): Promise<LinearNotification[]> {
|
|
140
|
+
const allNodes: LinearNotification[] = [];
|
|
141
|
+
let cursor: string | null = null;
|
|
142
|
+
|
|
143
|
+
type NotificationsResponse = {
|
|
144
|
+
notifications: { nodes: LinearNotification[]; pageInfo: { hasNextPage: boolean; endCursor: string } };
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
do {
|
|
148
|
+
const data: NotificationsResponse = await graphql<NotificationsResponse>(token, `
|
|
149
|
+
query FetchNotifications($after: DateTime, $cursor: String) {
|
|
150
|
+
notifications(
|
|
151
|
+
filter: { updatedAt: { gte: $after } }
|
|
152
|
+
orderBy: updatedAt
|
|
153
|
+
first: 50
|
|
154
|
+
after: $cursor
|
|
155
|
+
) {
|
|
156
|
+
nodes {
|
|
157
|
+
id
|
|
158
|
+
type
|
|
159
|
+
createdAt
|
|
160
|
+
updatedAt
|
|
161
|
+
... on IssueNotification {
|
|
162
|
+
issue {
|
|
163
|
+
id
|
|
164
|
+
identifier
|
|
165
|
+
title
|
|
166
|
+
url
|
|
167
|
+
state {
|
|
168
|
+
id
|
|
169
|
+
name
|
|
170
|
+
type
|
|
171
|
+
}
|
|
172
|
+
assignee {
|
|
173
|
+
id
|
|
174
|
+
name
|
|
175
|
+
email
|
|
176
|
+
}
|
|
177
|
+
team {
|
|
178
|
+
id
|
|
179
|
+
name
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
... on IssueCommentMentionNotification {
|
|
184
|
+
issue {
|
|
185
|
+
id
|
|
186
|
+
identifier
|
|
187
|
+
title
|
|
188
|
+
url
|
|
189
|
+
team {
|
|
190
|
+
id
|
|
191
|
+
name
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
comment {
|
|
195
|
+
id
|
|
196
|
+
body
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
pageInfo {
|
|
201
|
+
hasNextPage
|
|
202
|
+
endCursor
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
`, { after: since, cursor });
|
|
207
|
+
|
|
208
|
+
allNodes.push(...data.notifications.nodes);
|
|
209
|
+
cursor = data.notifications.pageInfo.hasNextPage ? data.notifications.pageInfo.endCursor : null;
|
|
210
|
+
} while (cursor != null);
|
|
211
|
+
|
|
212
|
+
return allNodes;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Fetch all assigned issues updated since the watermark, paginating until
|
|
217
|
+
* `pageInfo.hasNextPage` is false so updates beyond the first 50 aren't skipped.
|
|
218
|
+
*/
|
|
219
|
+
async function fetchAssignedIssueUpdates(
|
|
220
|
+
token: string,
|
|
221
|
+
viewerId: string,
|
|
222
|
+
since: string,
|
|
223
|
+
): Promise<LinearIssue[]> {
|
|
224
|
+
const allNodes: LinearIssue[] = [];
|
|
225
|
+
let cursor: string | null = null;
|
|
226
|
+
|
|
227
|
+
type IssuesResponse = {
|
|
228
|
+
issues: { nodes: LinearIssue[]; pageInfo: { hasNextPage: boolean; endCursor: string } };
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
do {
|
|
232
|
+
const data: IssuesResponse = await graphql<IssuesResponse>(token, `
|
|
233
|
+
query FetchAssignedIssues($assigneeId: ID, $after: DateTime, $cursor: String) {
|
|
234
|
+
issues(
|
|
235
|
+
filter: {
|
|
236
|
+
assignee: { id: { eq: $assigneeId } }
|
|
237
|
+
updatedAt: { gte: $after }
|
|
238
|
+
}
|
|
239
|
+
orderBy: updatedAt
|
|
240
|
+
first: 50
|
|
241
|
+
after: $cursor
|
|
242
|
+
) {
|
|
243
|
+
nodes {
|
|
244
|
+
id
|
|
245
|
+
identifier
|
|
246
|
+
title
|
|
247
|
+
url
|
|
248
|
+
updatedAt
|
|
249
|
+
state {
|
|
250
|
+
id
|
|
251
|
+
name
|
|
252
|
+
type
|
|
253
|
+
}
|
|
254
|
+
team {
|
|
255
|
+
id
|
|
256
|
+
name
|
|
257
|
+
}
|
|
258
|
+
assignee {
|
|
259
|
+
id
|
|
260
|
+
name
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
pageInfo {
|
|
264
|
+
hasNextPage
|
|
265
|
+
endCursor
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
`, { assigneeId: viewerId, after: since, cursor });
|
|
270
|
+
|
|
271
|
+
allNodes.push(...data.issues.nodes);
|
|
272
|
+
cursor = data.issues.pageInfo.hasNextPage ? data.issues.pageInfo.endCursor : null;
|
|
273
|
+
} while (cursor != null);
|
|
274
|
+
|
|
275
|
+
return allNodes;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// ── Issue state tracking ──────────────────────────────────────────────────────
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Tracks the last known state ID per issue, scoped by watcher instance key
|
|
282
|
+
* (the watcher's DB UUID) so that multiple Linear watchers in the same process
|
|
283
|
+
* — even when they share the same `credentialService` string — maintain
|
|
284
|
+
* completely independent baselines.
|
|
285
|
+
*
|
|
286
|
+
* Keying by `credentialService` alone is insufficient: `runWatchersOnce` polls
|
|
287
|
+
* watchers sequentially, so watcher-1 would write its post-poll state into the
|
|
288
|
+
* shared map, and watcher-2 would then see already-updated IDs and silently
|
|
289
|
+
* skip emitting valid transitions. Outer key: watcherKey; inner key: issue ID.
|
|
290
|
+
*
|
|
291
|
+
* In-memory only; resets on daemon restart, which is acceptable — the first
|
|
292
|
+
* poll after restart will seed the map without emitting false-positive events.
|
|
293
|
+
*/
|
|
294
|
+
const knownIssueStateIdsByWatcher = new Map<string, Map<string, string>>();
|
|
295
|
+
|
|
296
|
+
/** Get (or lazily create) the per-watcher state map. */
|
|
297
|
+
function getStateCache(watcherKey: string): Map<string, string> {
|
|
298
|
+
let cache = knownIssueStateIdsByWatcher.get(watcherKey);
|
|
299
|
+
if (!cache) {
|
|
300
|
+
cache = new Map<string, string>();
|
|
301
|
+
knownIssueStateIdsByWatcher.set(watcherKey, cache);
|
|
302
|
+
}
|
|
303
|
+
return cache;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Evict the state cache for a watcher that has been deleted or permanently
|
|
308
|
+
* disabled. Prevents unbounded growth of `knownIssueStateIdsByWatcher` in
|
|
309
|
+
* environments that create and delete watchers frequently (watcher churn).
|
|
310
|
+
*/
|
|
311
|
+
export function clearLinearStateCache(watcherKey: string): void {
|
|
312
|
+
knownIssueStateIdsByWatcher.delete(watcherKey);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// ── Event type mapping ────────────────────────────────────────────────────────
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Map a Linear notification type to a watcher event type.
|
|
319
|
+
* Linear notification types include: issueAssignedToYou, issueMentionedYou,
|
|
320
|
+
* issueCommentMentionedYou, issueStatusChanged, etc.
|
|
321
|
+
*/
|
|
322
|
+
function notificationTypeToEventType(type: string): string {
|
|
323
|
+
if (type === 'issueAssignedToYou') return 'linear_issue_assigned';
|
|
324
|
+
if (type === 'issueMentionedYou') return 'linear_mention';
|
|
325
|
+
if (type === 'issueCommentMentionedYou') return 'linear_comment_mention';
|
|
326
|
+
if (type === 'issueStatusChanged') return 'linear_status_changed';
|
|
327
|
+
return 'linear_notification';
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function notificationToItem(n: LinearNotification): WatcherItem {
|
|
331
|
+
const eventType = notificationTypeToEventType(n.type);
|
|
332
|
+
const issue = n.issue;
|
|
333
|
+
const teamName = issue?.team?.name ?? 'Unknown Team';
|
|
334
|
+
const issueRef = issue ? `${issue.identifier}: ${truncate(issue.title, 60)}` : 'Unknown issue';
|
|
335
|
+
|
|
336
|
+
const summary = eventType === 'linear_comment_mention' && n.comment
|
|
337
|
+
? `Linear @mention in ${teamName} / ${issueRef}: ${truncate(n.comment.body, 80)}`
|
|
338
|
+
: `Linear ${n.type.replace(/([A-Z])/g, ' $1').trim()} in ${teamName} / ${issueRef}`;
|
|
339
|
+
|
|
340
|
+
return {
|
|
341
|
+
externalId: n.id,
|
|
342
|
+
eventType,
|
|
343
|
+
summary,
|
|
344
|
+
payload: {
|
|
345
|
+
notificationId: n.id,
|
|
346
|
+
type: n.type,
|
|
347
|
+
issueId: issue?.id,
|
|
348
|
+
issueIdentifier: issue?.identifier,
|
|
349
|
+
issueTitle: issue?.title,
|
|
350
|
+
issueUrl: issue?.url,
|
|
351
|
+
issueStateName: issue?.state?.name,
|
|
352
|
+
issueStateType: issue?.state?.type,
|
|
353
|
+
teamName,
|
|
354
|
+
commentBody: n.comment?.body,
|
|
355
|
+
updatedAt: n.updatedAt,
|
|
356
|
+
},
|
|
357
|
+
timestamp: new Date(n.updatedAt).getTime(),
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function issueToStatusChangeItem(issue: LinearIssue, previousStateId: string): WatcherItem {
|
|
362
|
+
// Composite key encodes both the old and new state so re-polling the same
|
|
363
|
+
// transition doesn't generate a duplicate event via the dedup layer.
|
|
364
|
+
const externalId = `status_change:${issue.id}:${previousStateId}→${issue.state.id}`;
|
|
365
|
+
const teamName = issue.team?.name ?? 'Unknown Team';
|
|
366
|
+
|
|
367
|
+
return {
|
|
368
|
+
externalId,
|
|
369
|
+
eventType: 'linear_status_changed',
|
|
370
|
+
summary: `Linear status → ${issue.state.name} in ${teamName} / ${issue.identifier}: ${truncate(issue.title, 60)}`,
|
|
371
|
+
payload: {
|
|
372
|
+
issueId: issue.id,
|
|
373
|
+
issueIdentifier: issue.identifier,
|
|
374
|
+
issueTitle: issue.title,
|
|
375
|
+
issueUrl: issue.url,
|
|
376
|
+
stateName: issue.state.name,
|
|
377
|
+
stateType: issue.state.type,
|
|
378
|
+
teamName,
|
|
379
|
+
updatedAt: issue.updatedAt,
|
|
380
|
+
},
|
|
381
|
+
timestamp: new Date(issue.updatedAt).getTime(),
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// ── Provider ──────────────────────────────────────────────────────────────────
|
|
386
|
+
|
|
387
|
+
export const linearProvider: WatcherProvider = {
|
|
388
|
+
id: 'linear',
|
|
389
|
+
displayName: 'Linear',
|
|
390
|
+
requiredCredentialService: 'integration:linear',
|
|
391
|
+
|
|
392
|
+
async getInitialWatermark(_credentialService: string): Promise<string> {
|
|
393
|
+
// Start from "now" so we don't replay all existing notifications
|
|
394
|
+
return new Date().toISOString();
|
|
395
|
+
},
|
|
396
|
+
|
|
397
|
+
cleanup(watcherKey: string): void {
|
|
398
|
+
clearLinearStateCache(watcherKey);
|
|
399
|
+
},
|
|
400
|
+
|
|
401
|
+
async fetchNew(
|
|
402
|
+
credentialService: string,
|
|
403
|
+
watermark: string | null,
|
|
404
|
+
_config: Record<string, unknown>,
|
|
405
|
+
watcherKey: string,
|
|
406
|
+
): Promise<FetchResult> {
|
|
407
|
+
return withValidToken(credentialService, async (token) => {
|
|
408
|
+
const since = watermark ?? new Date().toISOString();
|
|
409
|
+
|
|
410
|
+
// Resolve the authenticated viewer's ID once per poll for the assigned-issues query
|
|
411
|
+
const viewer = await fetchViewer(token);
|
|
412
|
+
|
|
413
|
+
// Fetch notifications (assignments, mentions, status changes via notification feed)
|
|
414
|
+
const notifications = await fetchNotifications(token, since);
|
|
415
|
+
|
|
416
|
+
// Only surface notification types that warrant attention
|
|
417
|
+
const relevantTypes = new Set([
|
|
418
|
+
'issueAssignedToYou',
|
|
419
|
+
'issueMentionedYou',
|
|
420
|
+
'issueCommentMentionedYou',
|
|
421
|
+
'issueStatusChanged',
|
|
422
|
+
]);
|
|
423
|
+
|
|
424
|
+
const items: WatcherItem[] = [];
|
|
425
|
+
|
|
426
|
+
for (const n of notifications) {
|
|
427
|
+
if (!relevantTypes.has(n.type)) continue;
|
|
428
|
+
items.push(notificationToItem(n));
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Also poll assigned issues directly for status changes not covered by
|
|
432
|
+
// notifications (e.g., bulk team updates). We only emit an event when the
|
|
433
|
+
// state ID differs from what we recorded on the previous poll — any other
|
|
434
|
+
// field update (title, description, etc.) does not constitute a status change.
|
|
435
|
+
// On first sight of an issue we seed the map without emitting, so we don't
|
|
436
|
+
// fire false-positive events after a daemon restart.
|
|
437
|
+
const assignedIssues = await fetchAssignedIssueUpdates(token, viewer.id, since);
|
|
438
|
+
// Scope the state cache by watcherKey (the watcher's DB UUID) rather than
|
|
439
|
+
// credentialService so that multiple Linear watchers sharing the same credential
|
|
440
|
+
// maintain independent baselines and cannot clobber each other's state.
|
|
441
|
+
const stateCache = getStateCache(watcherKey);
|
|
442
|
+
for (const issue of assignedIssues) {
|
|
443
|
+
const previousStateId = stateCache.get(issue.id);
|
|
444
|
+
if (previousStateId !== undefined && previousStateId !== issue.state.id) {
|
|
445
|
+
items.push(issueToStatusChangeItem(issue, previousStateId));
|
|
446
|
+
}
|
|
447
|
+
// Always update the map so the next poll has an accurate baseline.
|
|
448
|
+
stateCache.set(issue.id, issue.state.id);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const newWatermark = new Date().toISOString();
|
|
452
|
+
log.info(
|
|
453
|
+
{ count: items.length, viewer: viewer.name, watermark: newWatermark },
|
|
454
|
+
'Linear: fetched new notifications',
|
|
455
|
+
);
|
|
456
|
+
|
|
457
|
+
return { items, watermark: newWatermark };
|
|
458
|
+
});
|
|
459
|
+
},
|
|
460
|
+
};
|
|
@@ -49,6 +49,7 @@ export const slackProvider: WatcherProvider = {
|
|
|
49
49
|
credentialService: string,
|
|
50
50
|
watermark: string | null,
|
|
51
51
|
_config: Record<string, unknown>,
|
|
52
|
+
_watcherKey: string,
|
|
52
53
|
): Promise<FetchResult> {
|
|
53
54
|
return withValidToken(credentialService, async (token) => {
|
|
54
55
|
if (!watermark) {
|
|
@@ -89,7 +89,7 @@ export function runWorkItemInBackground(workItemId: string): RunWorkItemResult {
|
|
|
89
89
|
|
|
90
90
|
// Resolve required tools
|
|
91
91
|
let requiredTools: string[];
|
|
92
|
-
if (workItem.requiredTools
|
|
92
|
+
if (workItem.requiredTools != null) {
|
|
93
93
|
requiredTools = sanitizeToolList(JSON.parse(workItem.requiredTools));
|
|
94
94
|
} else {
|
|
95
95
|
requiredTools = task.requiredTools
|
|
@@ -651,7 +651,7 @@ export class WorkspaceGitService {
|
|
|
651
651
|
return;
|
|
652
652
|
}
|
|
653
653
|
|
|
654
|
-
const state = currentBranch
|
|
654
|
+
const state = currentBranch == null ? 'detached HEAD' : `branch '${currentBranch}'`;
|
|
655
655
|
log.warn(
|
|
656
656
|
{ workspaceDir: this.workspaceDir, currentBranch },
|
|
657
657
|
`Workspace repo is on ${state}; auto-switching to main`,
|