@vellumai/assistant 0.3.5 → 0.3.7
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 +27 -3
- package/src/config/env-registry.ts +169 -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 +157 -1138
- 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 +254 -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 +74 -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 +321 -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 +62 -0
- package/src/daemon/ipc-contract-inventory.ts +55 -29
- package/src/daemon/ipc-contract.ts +227 -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 +98 -4
- package/src/daemon/session-runtime-assembly.ts +149 -15
- package/src/daemon/session-surfaces.ts +26 -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 +12 -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 +163 -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 +126 -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/assistant-event-hub.ts +3 -1
- 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 +289 -745
- 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 +144 -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 +13 -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 +80 -18
- 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 +25 -18
- 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
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
1
|
import { describe, test, expect, beforeEach, afterEach, mock } from 'bun:test';
|
|
3
2
|
import { mkdtempSync, mkdirSync, rmSync, symlinkSync } from 'node:fs';
|
|
4
3
|
import { tmpdir } from 'node:os';
|
|
5
4
|
import { join } from 'node:path';
|
|
5
|
+
import type { Tool } from '../tools/types.js';
|
|
6
|
+
import type { SandboxBackend } from '../tools/terminal/backends/types.js';
|
|
7
|
+
import type { ShellOutputResult } from '../tools/shared/shell-output.js';
|
|
6
8
|
|
|
7
9
|
// ── Mock modules ────────────────────────────────────────────────────────────
|
|
8
10
|
|
|
@@ -502,7 +504,7 @@ describe('wrapCommand', () => {
|
|
|
502
504
|
describe('Native sandbox backend', () => {
|
|
503
505
|
// We test NativeBackend directly rather than through wrapCommand to avoid
|
|
504
506
|
// platform-dependent sandbox-exec/bwrap availability.
|
|
505
|
-
let NativeBackend:
|
|
507
|
+
let NativeBackend: new () => SandboxBackend;
|
|
506
508
|
|
|
507
509
|
beforeEach(async () => {
|
|
508
510
|
const mod = await import('../tools/terminal/backends/native.js');
|
|
@@ -546,8 +548,8 @@ describe('Native sandbox backend', () => {
|
|
|
546
548
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
547
549
|
|
|
548
550
|
describe('Docker sandbox backend', () => {
|
|
549
|
-
let DockerBackend:
|
|
550
|
-
let _resetDockerChecks:
|
|
551
|
+
let DockerBackend: new (sandboxRoot: string, config?: Record<string, unknown>, uid?: number, gid?: number) => SandboxBackend;
|
|
552
|
+
let _resetDockerChecks: () => void;
|
|
551
553
|
|
|
552
554
|
const sandboxDir = join(testTmpDir, 'docker-sandbox');
|
|
553
555
|
|
|
@@ -628,7 +630,7 @@ describe('Docker sandbox backend', () => {
|
|
|
628
630
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
629
631
|
|
|
630
632
|
describe('Shell tool input validation', () => {
|
|
631
|
-
let shellTool:
|
|
633
|
+
let shellTool: Tool;
|
|
632
634
|
|
|
633
635
|
beforeEach(async () => {
|
|
634
636
|
const mod = await import('../tools/terminal/shell.js');
|
|
@@ -637,6 +639,7 @@ describe('Shell tool input validation', () => {
|
|
|
637
639
|
|
|
638
640
|
const baseContext = {
|
|
639
641
|
workingDir: testTmpDir,
|
|
642
|
+
sessionId: 'test-session-1',
|
|
640
643
|
conversationId: 'test-conv-1',
|
|
641
644
|
onOutput: () => {},
|
|
642
645
|
};
|
|
@@ -700,13 +703,14 @@ describe('Shell tool input validation', () => {
|
|
|
700
703
|
|
|
701
704
|
test('tool definition includes required schema fields', () => {
|
|
702
705
|
const def = shellTool.getDefinition();
|
|
706
|
+
const schema = def.input_schema as { required: string[]; properties: Record<string, unknown> };
|
|
703
707
|
expect(def.name).toBe('bash');
|
|
704
|
-
expect(
|
|
705
|
-
expect(
|
|
706
|
-
expect(
|
|
707
|
-
expect(
|
|
708
|
-
expect(
|
|
709
|
-
expect(
|
|
708
|
+
expect(schema.required).toContain('command');
|
|
709
|
+
expect(schema.required).toContain('reason');
|
|
710
|
+
expect(schema.properties.command).toBeDefined();
|
|
711
|
+
expect(schema.properties.timeout_seconds).toBeDefined();
|
|
712
|
+
expect(schema.properties.network_mode).toBeDefined();
|
|
713
|
+
expect(schema.properties.credential_ids).toBeDefined();
|
|
710
714
|
});
|
|
711
715
|
});
|
|
712
716
|
|
|
@@ -715,7 +719,7 @@ describe('Shell tool input validation', () => {
|
|
|
715
719
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
716
720
|
|
|
717
721
|
describe('formatShellOutput', () => {
|
|
718
|
-
let formatShellOutput:
|
|
722
|
+
let formatShellOutput: (stdout: string, stderr: string, code: number | null, timedOut: boolean, timeoutSec: number) => ShellOutputResult;
|
|
719
723
|
|
|
720
724
|
beforeEach(async () => {
|
|
721
725
|
const mod = await import('../tools/shared/shell-output.js');
|
|
@@ -775,7 +779,7 @@ describe('formatShellOutput', () => {
|
|
|
775
779
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
776
780
|
|
|
777
781
|
describe('EvaluateTypescriptTool input validation', () => {
|
|
778
|
-
let evalTool:
|
|
782
|
+
let evalTool: Tool;
|
|
779
783
|
|
|
780
784
|
beforeEach(async () => {
|
|
781
785
|
const mod = await import('../tools/terminal/evaluate-typescript.js');
|
|
@@ -784,6 +788,7 @@ describe('EvaluateTypescriptTool input validation', () => {
|
|
|
784
788
|
|
|
785
789
|
const baseContext = {
|
|
786
790
|
workingDir: testTmpDir,
|
|
791
|
+
sessionId: 'test-session-1',
|
|
787
792
|
conversationId: 'test-conv-1',
|
|
788
793
|
onOutput: () => {},
|
|
789
794
|
};
|
|
@@ -829,12 +834,13 @@ describe('EvaluateTypescriptTool input validation', () => {
|
|
|
829
834
|
|
|
830
835
|
test('tool definition has correct name and schema', () => {
|
|
831
836
|
const def = evalTool.getDefinition();
|
|
837
|
+
const schema = def.input_schema as { required: string[]; properties: Record<string, unknown> };
|
|
832
838
|
expect(def.name).toBe('evaluate_typescript_code');
|
|
833
|
-
expect(
|
|
834
|
-
expect(
|
|
835
|
-
expect(
|
|
836
|
-
expect(
|
|
837
|
-
expect(
|
|
838
|
-
expect(
|
|
839
|
+
expect(schema.required).toContain('code');
|
|
840
|
+
expect(schema.properties.code).toBeDefined();
|
|
841
|
+
expect(schema.properties.mock_input_json).toBeDefined();
|
|
842
|
+
expect(schema.properties.timeout_seconds).toBeDefined();
|
|
843
|
+
expect(schema.properties.filename).toBeDefined();
|
|
844
|
+
expect(schema.properties.entrypoint).toBeDefined();
|
|
839
845
|
});
|
|
840
846
|
});
|
|
@@ -0,0 +1,545 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests verifying that resources are properly cleaned up when AbortSignal fires
|
|
3
|
+
* during tool execution: shell processes are killed, file operations respect
|
|
4
|
+
* pre-abort signals, and git operations abort via the signal.
|
|
5
|
+
*/
|
|
6
|
+
import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
|
|
7
|
+
import {
|
|
8
|
+
mkdirSync,
|
|
9
|
+
mkdtempSync,
|
|
10
|
+
rmSync,
|
|
11
|
+
existsSync,
|
|
12
|
+
writeFileSync,
|
|
13
|
+
realpathSync,
|
|
14
|
+
} from 'node:fs';
|
|
15
|
+
import { join } from 'node:path';
|
|
16
|
+
import { tmpdir } from 'node:os';
|
|
17
|
+
|
|
18
|
+
// ── Shared mock setup ────────────────────────────────────────────────────────
|
|
19
|
+
// Config mock must be declared before importing tool modules so that the
|
|
20
|
+
// mock.module calls are hoisted above the dynamic imports.
|
|
21
|
+
|
|
22
|
+
import { mock } from 'bun:test';
|
|
23
|
+
|
|
24
|
+
mock.module('../config/loader.js', () => ({
|
|
25
|
+
getConfig: () => ({
|
|
26
|
+
provider: 'anthropic',
|
|
27
|
+
model: 'test',
|
|
28
|
+
apiKeys: {},
|
|
29
|
+
maxTokens: 4096,
|
|
30
|
+
dataDir: '/tmp',
|
|
31
|
+
timeouts: {
|
|
32
|
+
shellDefaultTimeoutSec: 120,
|
|
33
|
+
shellMaxTimeoutSec: 600,
|
|
34
|
+
permissionTimeoutSec: 300,
|
|
35
|
+
},
|
|
36
|
+
sandbox: { enabled: false, backend: 'native' as const },
|
|
37
|
+
rateLimit: { maxRequestsPerMinute: 0, maxTokensPerSession: 0 },
|
|
38
|
+
secretDetection: { enabled: false, action: 'warn' as const, entropyThreshold: 4.0 },
|
|
39
|
+
}),
|
|
40
|
+
loadConfig: () => ({}),
|
|
41
|
+
invalidateConfigCache: () => {},
|
|
42
|
+
saveConfig: () => {},
|
|
43
|
+
loadRawConfig: () => ({}),
|
|
44
|
+
saveRawConfig: () => {},
|
|
45
|
+
}));
|
|
46
|
+
|
|
47
|
+
mock.module('../util/logger.js', () => ({
|
|
48
|
+
getLogger: () => new Proxy({} as Record<string, unknown>, {
|
|
49
|
+
get: () => () => {},
|
|
50
|
+
}),
|
|
51
|
+
}));
|
|
52
|
+
|
|
53
|
+
// shell.ts requires the sandbox wrapper — provide a pass-through stub.
|
|
54
|
+
mock.module('../tools/terminal/sandbox.js', () => ({
|
|
55
|
+
wrapCommand: (command: string, _cwd: string, _cfg: unknown, _opts?: unknown) => ({
|
|
56
|
+
command: 'bash',
|
|
57
|
+
args: ['-c', '--', command],
|
|
58
|
+
sandboxed: false,
|
|
59
|
+
}),
|
|
60
|
+
}));
|
|
61
|
+
|
|
62
|
+
// shell.ts uses the script proxy — stub it to avoid network side-effects.
|
|
63
|
+
mock.module('../tools/network/script-proxy/index.js', () => ({
|
|
64
|
+
getOrStartSession: async () => ({
|
|
65
|
+
session: { id: 'mock-session' },
|
|
66
|
+
}),
|
|
67
|
+
getSessionEnv: () => ({}),
|
|
68
|
+
}));
|
|
69
|
+
|
|
70
|
+
mock.module('../util/platform.js', () => ({
|
|
71
|
+
getDataDir: () => '/tmp',
|
|
72
|
+
getSocketPath: () => '/tmp/vellum.sock',
|
|
73
|
+
}));
|
|
74
|
+
|
|
75
|
+
mock.module('../tools/credentials/resolve.js', () => ({
|
|
76
|
+
resolveCredentialRef: () => null,
|
|
77
|
+
}));
|
|
78
|
+
|
|
79
|
+
mock.module('../tools/network/script-proxy/logging.js', () => ({
|
|
80
|
+
buildCredentialRefTrace: () => ({}),
|
|
81
|
+
}));
|
|
82
|
+
|
|
83
|
+
mock.module('../security/secret-scanner.js', () => ({
|
|
84
|
+
redactSecrets: (s: string) => s,
|
|
85
|
+
scanText: () => [],
|
|
86
|
+
}));
|
|
87
|
+
|
|
88
|
+
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
const testDirs: string[] = [];
|
|
91
|
+
|
|
92
|
+
function makeTempDir(): string {
|
|
93
|
+
const dir = realpathSync(mkdtempSync(join(tmpdir(), 'abort-cleanup-test-')));
|
|
94
|
+
testDirs.push(dir);
|
|
95
|
+
return dir;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function makeToolContext(workingDir: string, signal?: AbortSignal) {
|
|
99
|
+
return {
|
|
100
|
+
workingDir,
|
|
101
|
+
sessionId: 'test-session',
|
|
102
|
+
conversationId: 'test-conv',
|
|
103
|
+
signal,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Restore all module mocks before each test so that this suite is not
|
|
108
|
+
// order-dependent. Without this, stubs installed by one test file can bleed
|
|
109
|
+
// into subsequent files and produce false passes or failures.
|
|
110
|
+
beforeEach(() => {
|
|
111
|
+
mock.restore();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
afterEach(() => {
|
|
115
|
+
for (const dir of testDirs.splice(0)) {
|
|
116
|
+
rmSync(dir, { recursive: true, force: true });
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// ── 1. Shell process cleanup on AbortSignal ─────────────────────────────────
|
|
121
|
+
|
|
122
|
+
describe('shell tool — process cleanup on AbortSignal', () => {
|
|
123
|
+
test('kills the child process when AbortSignal fires mid-execution', async () => {
|
|
124
|
+
const { hostShellTool } = await import('../tools/host-terminal/host-shell.js');
|
|
125
|
+
const dir = makeTempDir();
|
|
126
|
+
const ac = new AbortController();
|
|
127
|
+
|
|
128
|
+
const promise = hostShellTool.execute(
|
|
129
|
+
{ command: 'sleep 30', reason: 'test' },
|
|
130
|
+
makeToolContext(dir, ac.signal),
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
// Allow the child process to start before aborting.
|
|
134
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
135
|
+
ac.abort();
|
|
136
|
+
|
|
137
|
+
const result = await promise;
|
|
138
|
+
|
|
139
|
+
// The process was killed by SIGKILL, so it exits non-zero.
|
|
140
|
+
expect(result.isError).toBe(true);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test('kills the child process immediately when signal is already aborted', async () => {
|
|
144
|
+
const { hostShellTool } = await import('../tools/host-terminal/host-shell.js');
|
|
145
|
+
const dir = makeTempDir();
|
|
146
|
+
const ac = new AbortController();
|
|
147
|
+
ac.abort(); // pre-aborted
|
|
148
|
+
|
|
149
|
+
const result = await hostShellTool.execute(
|
|
150
|
+
{ command: 'sleep 30', reason: 'test' },
|
|
151
|
+
makeToolContext(dir, ac.signal),
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
expect(result.isError).toBe(true);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test('completes normally when abort signal never fires', async () => {
|
|
158
|
+
const { hostShellTool } = await import('../tools/host-terminal/host-shell.js');
|
|
159
|
+
const dir = makeTempDir();
|
|
160
|
+
const ac = new AbortController();
|
|
161
|
+
|
|
162
|
+
const result = await hostShellTool.execute(
|
|
163
|
+
{ command: 'echo completed', reason: 'test' },
|
|
164
|
+
makeToolContext(dir, ac.signal),
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
expect(result.isError).toBe(false);
|
|
168
|
+
expect(result.content).toContain('completed');
|
|
169
|
+
// Ensure aborting after completion doesn't cause errors.
|
|
170
|
+
ac.abort();
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test('removes the abort listener after process exits normally', async () => {
|
|
174
|
+
const { hostShellTool } = await import('../tools/host-terminal/host-shell.js');
|
|
175
|
+
const dir = makeTempDir();
|
|
176
|
+
const ac = new AbortController();
|
|
177
|
+
|
|
178
|
+
await hostShellTool.execute(
|
|
179
|
+
{ command: 'echo done', reason: 'test' },
|
|
180
|
+
makeToolContext(dir, ac.signal),
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
// After the process exits the abort listener should be removed.
|
|
184
|
+
// AbortSignal.eventCounts is not universally available, but we can
|
|
185
|
+
// verify the listener doesn't hold a reference by aborting and
|
|
186
|
+
// confirming no error is thrown from a dangling handler.
|
|
187
|
+
expect(() => ac.abort()).not.toThrow();
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test('abort during long-running output does not leave orphaned listeners', async () => {
|
|
191
|
+
const { hostShellTool } = await import('../tools/host-terminal/host-shell.js');
|
|
192
|
+
const dir = makeTempDir();
|
|
193
|
+
const ac = new AbortController();
|
|
194
|
+
const chunks: string[] = [];
|
|
195
|
+
|
|
196
|
+
const promise = hostShellTool.execute(
|
|
197
|
+
{ command: 'for i in 1 2 3 4 5; do echo $i; sleep 2; done', reason: 'test' },
|
|
198
|
+
{
|
|
199
|
+
...makeToolContext(dir, ac.signal),
|
|
200
|
+
onOutput: (c: string) => chunks.push(c),
|
|
201
|
+
},
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
// Wait for the first chunk to arrive before aborting.
|
|
205
|
+
await new Promise<void>((resolve) => {
|
|
206
|
+
const interval = setInterval(() => {
|
|
207
|
+
if (chunks.length > 0) {
|
|
208
|
+
clearInterval(interval);
|
|
209
|
+
resolve();
|
|
210
|
+
}
|
|
211
|
+
}, 50);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
ac.abort();
|
|
215
|
+
const result = await promise;
|
|
216
|
+
|
|
217
|
+
// Process was killed.
|
|
218
|
+
expect(result.isError).toBe(true);
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// ── 2. File operation abort handling ─────────────────────────────────────────
|
|
223
|
+
//
|
|
224
|
+
// File operations in this codebase use synchronous Node.js APIs
|
|
225
|
+
// (readFileSync / writeFileSync), so there are no long-lived file handles
|
|
226
|
+
// to close. Abort cancellation is handled at the executor level:
|
|
227
|
+
// ToolExecutor.execute() checks signal.aborted before dispatching to the
|
|
228
|
+
// tool and returns a 'Cancelled' error result immediately.
|
|
229
|
+
// The tests below verify that contract at the tool level.
|
|
230
|
+
|
|
231
|
+
describe('file tools — abort signal pre-check', () => {
|
|
232
|
+
test('file_write tool: execute still succeeds when context has no signal', async () => {
|
|
233
|
+
const { getTool } = await import('../tools/registry.js');
|
|
234
|
+
await import('../tools/filesystem/write.js');
|
|
235
|
+
const fileWriteTool = getTool('file_write')!;
|
|
236
|
+
const dir = makeTempDir();
|
|
237
|
+
|
|
238
|
+
const result = await fileWriteTool.execute(
|
|
239
|
+
{ path: 'out.txt', content: 'hello', reason: 'test' },
|
|
240
|
+
makeToolContext(dir),
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
expect(result.isError).toBe(false);
|
|
244
|
+
expect(existsSync(join(dir, 'out.txt'))).toBe(true);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test('file_write tool: execute succeeds with a non-aborted signal', async () => {
|
|
248
|
+
const { getTool } = await import('../tools/registry.js');
|
|
249
|
+
await import('../tools/filesystem/write.js');
|
|
250
|
+
const fileWriteTool = getTool('file_write')!;
|
|
251
|
+
const dir = makeTempDir();
|
|
252
|
+
const ac = new AbortController();
|
|
253
|
+
|
|
254
|
+
const result = await fileWriteTool.execute(
|
|
255
|
+
{ path: 'out2.txt', content: 'world', reason: 'test' },
|
|
256
|
+
makeToolContext(dir, ac.signal),
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
expect(result.isError).toBe(false);
|
|
260
|
+
expect(existsSync(join(dir, 'out2.txt'))).toBe(true);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
test('file_read tool: execute succeeds when context has no signal', async () => {
|
|
264
|
+
const { getTool } = await import('../tools/registry.js');
|
|
265
|
+
await import('../tools/filesystem/read.js');
|
|
266
|
+
const fileReadTool = getTool('file_read')!;
|
|
267
|
+
const dir = makeTempDir();
|
|
268
|
+
writeFileSync(join(dir, 'read-me.txt'), 'content to read');
|
|
269
|
+
|
|
270
|
+
const result = await fileReadTool.execute(
|
|
271
|
+
{ path: 'read-me.txt' },
|
|
272
|
+
makeToolContext(dir),
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
expect(result.isError).toBe(false);
|
|
276
|
+
expect(result.content).toContain('content to read');
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
test('file_read tool: execute succeeds with a non-aborted signal', async () => {
|
|
280
|
+
const { getTool } = await import('../tools/registry.js');
|
|
281
|
+
await import('../tools/filesystem/read.js');
|
|
282
|
+
const fileReadTool = getTool('file_read')!;
|
|
283
|
+
const dir = makeTempDir();
|
|
284
|
+
writeFileSync(join(dir, 'read-me2.txt'), 'readable');
|
|
285
|
+
const ac = new AbortController();
|
|
286
|
+
|
|
287
|
+
const result = await fileReadTool.execute(
|
|
288
|
+
{ path: 'read-me2.txt' },
|
|
289
|
+
makeToolContext(dir, ac.signal),
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
expect(result.isError).toBe(false);
|
|
293
|
+
expect(result.content).toContain('readable');
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
test('file_write tool: synchronous write completes regardless of pre-aborted signal (abort checked at executor level)', async () => {
|
|
297
|
+
// The tool implementations use synchronous Node.js I/O, so they complete
|
|
298
|
+
// regardless of the signal. The ToolExecutor.execute() wrapper is
|
|
299
|
+
// responsible for checking signal.aborted before dispatching; the tool
|
|
300
|
+
// itself is not expected to check it. This test documents that contract.
|
|
301
|
+
const { getTool } = await import('../tools/registry.js');
|
|
302
|
+
await import('../tools/filesystem/write.js');
|
|
303
|
+
|
|
304
|
+
const dir = makeTempDir();
|
|
305
|
+
const ac = new AbortController();
|
|
306
|
+
ac.abort(); // pre-aborted
|
|
307
|
+
|
|
308
|
+
const fileWriteTool = getTool('file_write')!;
|
|
309
|
+
|
|
310
|
+
// When invoked directly (not through ToolExecutor), the sync write runs.
|
|
311
|
+
const result = await fileWriteTool.execute(
|
|
312
|
+
{ path: 'should-write.txt', content: 'test', reason: 'test' },
|
|
313
|
+
makeToolContext(dir, ac.signal),
|
|
314
|
+
);
|
|
315
|
+
// Should succeed — the tool's own execute() doesn't check the signal.
|
|
316
|
+
expect(result.isError).toBe(false);
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
// ── 3. Git operation cleanup on AbortSignal ───────────────────────────────────
|
|
321
|
+
|
|
322
|
+
describe('WorkspaceGitService — abort signal propagation', () => {
|
|
323
|
+
let testDir: string;
|
|
324
|
+
|
|
325
|
+
beforeEach(() => {
|
|
326
|
+
testDir = join(
|
|
327
|
+
tmpdir(),
|
|
328
|
+
`abort-git-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
329
|
+
);
|
|
330
|
+
mkdirSync(testDir, { recursive: true });
|
|
331
|
+
testDirs.push(testDir);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
test('writeNote: AbortSignal cancels the git notes operation', async () => {
|
|
335
|
+
const {
|
|
336
|
+
WorkspaceGitService,
|
|
337
|
+
_resetGitServiceRegistry,
|
|
338
|
+
} = await import('../workspace/git-service.js');
|
|
339
|
+
|
|
340
|
+
_resetGitServiceRegistry();
|
|
341
|
+
const service = new WorkspaceGitService(testDir);
|
|
342
|
+
await service.ensureInitialized();
|
|
343
|
+
|
|
344
|
+
const headHash = await service.getHeadHash();
|
|
345
|
+
|
|
346
|
+
// Abort immediately — writeNote passes the signal to execFileAsync.
|
|
347
|
+
const ac = new AbortController();
|
|
348
|
+
ac.abort();
|
|
349
|
+
|
|
350
|
+
// The operation should throw (or resolve with an error) because the signal
|
|
351
|
+
// is already aborted when execFileAsync receives it.
|
|
352
|
+
let threw = false;
|
|
353
|
+
try {
|
|
354
|
+
await service.writeNote(headHash, 'should-be-cancelled', ac.signal);
|
|
355
|
+
} catch {
|
|
356
|
+
threw = true;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// With a pre-aborted signal, Node's execFileAsync rejects immediately.
|
|
360
|
+
expect(threw).toBe(true);
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
test('writeNote: completes normally without an abort signal', async () => {
|
|
364
|
+
const {
|
|
365
|
+
WorkspaceGitService,
|
|
366
|
+
_resetGitServiceRegistry,
|
|
367
|
+
} = await import('../workspace/git-service.js');
|
|
368
|
+
|
|
369
|
+
_resetGitServiceRegistry();
|
|
370
|
+
const service = new WorkspaceGitService(testDir);
|
|
371
|
+
await service.ensureInitialized();
|
|
372
|
+
|
|
373
|
+
const headHash = await service.getHeadHash();
|
|
374
|
+
|
|
375
|
+
// Should not throw.
|
|
376
|
+
await service.writeNote(headHash, 'note content');
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
test('commitIfDirty: respects deadlineMs to avoid stale commits', async () => {
|
|
380
|
+
const {
|
|
381
|
+
WorkspaceGitService,
|
|
382
|
+
_resetGitServiceRegistry,
|
|
383
|
+
} = await import('../workspace/git-service.js');
|
|
384
|
+
|
|
385
|
+
_resetGitServiceRegistry();
|
|
386
|
+
const service = new WorkspaceGitService(testDir);
|
|
387
|
+
await service.ensureInitialized();
|
|
388
|
+
|
|
389
|
+
writeFileSync(join(testDir, 'dirty.txt'), 'uncommitted');
|
|
390
|
+
|
|
391
|
+
// Deadline already expired.
|
|
392
|
+
const { committed } = await service.commitIfDirty(
|
|
393
|
+
() => ({ message: 'should not commit' }),
|
|
394
|
+
{ deadlineMs: Date.now() - 1000 },
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
expect(committed).toBe(false);
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
test('commitIfDirty: commits when deadline has not expired', async () => {
|
|
401
|
+
const {
|
|
402
|
+
WorkspaceGitService,
|
|
403
|
+
_resetGitServiceRegistry,
|
|
404
|
+
} = await import('../workspace/git-service.js');
|
|
405
|
+
|
|
406
|
+
_resetGitServiceRegistry();
|
|
407
|
+
const service = new WorkspaceGitService(testDir);
|
|
408
|
+
await service.ensureInitialized();
|
|
409
|
+
|
|
410
|
+
writeFileSync(join(testDir, 'commit-me.txt'), 'changes');
|
|
411
|
+
|
|
412
|
+
const { committed } = await service.commitIfDirty(
|
|
413
|
+
() => ({ message: 'test commit' }),
|
|
414
|
+
{ deadlineMs: Date.now() + 30_000 },
|
|
415
|
+
);
|
|
416
|
+
|
|
417
|
+
expect(committed).toBe(true);
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
test('ensureInitialized: concurrent calls resolve without duplicate git inits', async () => {
|
|
421
|
+
const {
|
|
422
|
+
WorkspaceGitService,
|
|
423
|
+
_resetGitServiceRegistry,
|
|
424
|
+
} = await import('../workspace/git-service.js');
|
|
425
|
+
|
|
426
|
+
_resetGitServiceRegistry();
|
|
427
|
+
const service = new WorkspaceGitService(testDir);
|
|
428
|
+
|
|
429
|
+
// Fire multiple concurrent init calls — all should resolve without errors.
|
|
430
|
+
await Promise.all([
|
|
431
|
+
service.ensureInitialized(),
|
|
432
|
+
service.ensureInitialized(),
|
|
433
|
+
service.ensureInitialized(),
|
|
434
|
+
]);
|
|
435
|
+
|
|
436
|
+
expect(service.isInitialized()).toBe(true);
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
test('commitChanges: interleaved with abort-signalled writeNote does not corrupt repo', async () => {
|
|
440
|
+
const {
|
|
441
|
+
WorkspaceGitService,
|
|
442
|
+
_resetGitServiceRegistry,
|
|
443
|
+
} = await import('../workspace/git-service.js');
|
|
444
|
+
|
|
445
|
+
_resetGitServiceRegistry();
|
|
446
|
+
const service = new WorkspaceGitService(testDir);
|
|
447
|
+
await service.ensureInitialized();
|
|
448
|
+
|
|
449
|
+
writeFileSync(join(testDir, 'f.txt'), 'data');
|
|
450
|
+
|
|
451
|
+
// Commit the file.
|
|
452
|
+
await service.commitChanges('add file');
|
|
453
|
+
|
|
454
|
+
const headHash = await service.getHeadHash();
|
|
455
|
+
|
|
456
|
+
// Fire a pre-aborted writeNote alongside another commitChanges.
|
|
457
|
+
const ac = new AbortController();
|
|
458
|
+
ac.abort();
|
|
459
|
+
|
|
460
|
+
const [commitResult] = await Promise.allSettled([
|
|
461
|
+
service.commitChanges('second commit').then(() => 'committed'),
|
|
462
|
+
service.writeNote(headHash, 'note', ac.signal).catch(() => 'note-aborted'),
|
|
463
|
+
]);
|
|
464
|
+
|
|
465
|
+
// The commit itself should succeed — the aborted note does not block it.
|
|
466
|
+
expect(commitResult.status).toBe('fulfilled');
|
|
467
|
+
if (commitResult.status === 'fulfilled') {
|
|
468
|
+
expect(commitResult.value).toBe('committed');
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Repo must still be in a clean, functional state.
|
|
472
|
+
const status = await service.getStatus();
|
|
473
|
+
expect(status.clean).toBe(true);
|
|
474
|
+
});
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
// ── 4. Shell tool (sandboxed) — abort signal ─────────────────────────────────
|
|
478
|
+
//
|
|
479
|
+
// The sandboxed `bash` tool mirrors the host_bash abort handling. Since the
|
|
480
|
+
// sandbox stub is already mocked above, we can drive the sandboxed path too.
|
|
481
|
+
|
|
482
|
+
describe('bash (sandboxed) shell tool — process cleanup on AbortSignal', () => {
|
|
483
|
+
test('kills child process when signal fires mid-execution', async () => {
|
|
484
|
+
// Import shell.ts (registered as 'bash') after mocks are in place.
|
|
485
|
+
const { getTool } = await import('../tools/registry.js');
|
|
486
|
+
await import('../tools/terminal/shell.js');
|
|
487
|
+
|
|
488
|
+
// Assert registration — a missing tool signals a real regression, not a skip.
|
|
489
|
+
expect(getTool('bash')).toBeDefined();
|
|
490
|
+
const bashTool = getTool('bash')!;
|
|
491
|
+
|
|
492
|
+
const dir = makeTempDir();
|
|
493
|
+
const ac = new AbortController();
|
|
494
|
+
|
|
495
|
+
const promise = bashTool.execute(
|
|
496
|
+
{ command: 'sleep 30', reason: 'test' },
|
|
497
|
+
makeToolContext(dir, ac.signal),
|
|
498
|
+
);
|
|
499
|
+
|
|
500
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
501
|
+
ac.abort();
|
|
502
|
+
|
|
503
|
+
const result = await promise;
|
|
504
|
+
expect(result.isError).toBe(true);
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
test('kills child process immediately when signal is pre-aborted', async () => {
|
|
508
|
+
const { getTool } = await import('../tools/registry.js');
|
|
509
|
+
await import('../tools/terminal/shell.js');
|
|
510
|
+
|
|
511
|
+
expect(getTool('bash')).toBeDefined();
|
|
512
|
+
const bashTool = getTool('bash')!;
|
|
513
|
+
|
|
514
|
+
const dir = makeTempDir();
|
|
515
|
+
const ac = new AbortController();
|
|
516
|
+
ac.abort();
|
|
517
|
+
|
|
518
|
+
const result = await bashTool.execute(
|
|
519
|
+
{ command: 'sleep 30', reason: 'test' },
|
|
520
|
+
makeToolContext(dir, ac.signal),
|
|
521
|
+
);
|
|
522
|
+
|
|
523
|
+
expect(result.isError).toBe(true);
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
test('short command completes normally with a non-aborted signal attached', async () => {
|
|
527
|
+
const { getTool } = await import('../tools/registry.js');
|
|
528
|
+
await import('../tools/terminal/shell.js');
|
|
529
|
+
|
|
530
|
+
expect(getTool('bash')).toBeDefined();
|
|
531
|
+
const bashTool = getTool('bash')!;
|
|
532
|
+
|
|
533
|
+
const dir = makeTempDir();
|
|
534
|
+
const ac = new AbortController();
|
|
535
|
+
|
|
536
|
+
const result = await bashTool.execute(
|
|
537
|
+
{ command: 'echo hello-world', reason: 'test' },
|
|
538
|
+
makeToolContext(dir, ac.signal),
|
|
539
|
+
);
|
|
540
|
+
|
|
541
|
+
expect(result.isError).toBe(false);
|
|
542
|
+
expect(result.content).toContain('hello-world');
|
|
543
|
+
ac.abort(); // cleanup after success
|
|
544
|
+
});
|
|
545
|
+
});
|