@vellumai/assistant 0.11.2 → 0.11.3-staging.2
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/ARCHITECTURE.md +58 -3
- package/docs/trusted-contact-access.md +9 -5
- package/docs/vellum-doctor.md +195 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +39 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/ingress.ts +10 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +6 -0
- package/node_modules/@vellumai/ces-client/src/index.ts +1 -0
- package/node_modules/@vellumai/ces-client/src/rpc-client.ts +19 -2
- package/node_modules/@vellumai/environments/package.json +2 -1
- package/node_modules/@vellumai/environments/src/__tests__/cloud-assistant-hub-url.test.ts +38 -0
- package/node_modules/@vellumai/environments/src/__tests__/install-layout.test.ts +98 -0
- package/node_modules/@vellumai/environments/src/__tests__/package-boundary.test.ts +5 -5
- package/node_modules/@vellumai/environments/src/index.ts +17 -6
- package/node_modules/@vellumai/environments/src/install-layout.ts +49 -0
- package/node_modules/@vellumai/environments/src/seeds.ts +29 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +39 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/ingress.ts +10 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +6 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +4 -0
- package/node_modules/@vellumai/gateway-client/src/verification-session-contract.ts +91 -6
- package/node_modules/@vellumai/ipc-server-utils/src/endpoint.test.ts +36 -0
- package/node_modules/@vellumai/ipc-server-utils/src/endpoint.ts +142 -0
- package/node_modules/@vellumai/ipc-server-utils/src/index.ts +2 -0
- package/node_modules/@vellumai/ipc-server-utils/src/listen-options.ts +3 -0
- package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.test.ts +15 -1
- package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.ts +17 -2
- package/node_modules/@vellumai/service-contracts/src/channels.ts +39 -0
- package/node_modules/@vellumai/service-contracts/src/ingress.ts +10 -0
- package/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +6 -0
- package/node_modules/@vellumai/slack-text/src/index.ts +16 -2
- package/openapi.yaml +589 -30
- package/package.json +1 -1
- package/src/__tests__/agent-image-optimize.test.ts +4 -4
- package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +113 -66
- package/src/__tests__/agent-loop-override-profile.test.ts +38 -48
- package/src/__tests__/agent-loop-subagent-billing-headers.test.ts +166 -0
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +1 -0
- package/src/__tests__/agent-wake-override-profile.test.ts +1 -0
- package/src/__tests__/answered-question-persistence.test.ts +240 -0
- package/src/__tests__/anthropic-provider.test.ts +165 -30
- package/src/__tests__/app-control-flow.test.ts +9 -8
- package/src/__tests__/assistant-event-hub.test.ts +48 -0
- package/src/__tests__/attachments-store-heic-normalize.test.ts +16 -12
- package/src/__tests__/attachments-store.test.ts +130 -68
- package/src/__tests__/attachments.test.ts +36 -36
- package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
- package/src/__tests__/call-start-guardian-guard.test.ts +18 -4
- package/src/__tests__/canned-reply-release.test.ts +86 -0
- package/src/__tests__/channel-guardian.test.ts +59 -29
- package/src/__tests__/channel-readiness-telegram-remote.test.ts +154 -0
- package/src/__tests__/channel-setup-panel-ack.test.ts +6 -5
- package/src/__tests__/client-os-metadata-persistence.test.ts +103 -4
- package/src/__tests__/compactor-image-manifest-trust.test.ts +1 -1
- package/src/__tests__/consult-deadline.test.ts +65 -0
- package/src/__tests__/conversation-agent-loop.test.ts +438 -2
- package/src/__tests__/conversation-disk-view-integration.test.ts +5 -1
- package/src/__tests__/conversation-disk-view.test.ts +4 -4
- package/src/__tests__/conversation-error.test.ts +22 -3
- package/src/__tests__/conversation-fork-crud.test.ts +5 -1
- package/src/__tests__/conversation-fork-referential.test.ts +352 -0
- package/src/__tests__/conversation-fork-retrospective.test.ts +5 -1
- package/src/__tests__/conversation-group-tools.test.ts +32 -2
- package/src/__tests__/conversation-inference-profile-route.test.ts +27 -1
- package/src/__tests__/conversation-initial-prompt.test.ts +51 -22
- package/src/__tests__/conversation-key-store-bootstrap-cleanup.test.ts +119 -0
- package/src/__tests__/conversation-key-store-origin.test.ts +105 -0
- package/src/__tests__/conversation-list-recency-ordering.test.ts +104 -0
- package/src/__tests__/conversation-origin-at-creation.test.ts +153 -0
- package/src/__tests__/conversation-origin-channel-filter.test.ts +95 -0
- package/src/__tests__/conversation-placement-promotion.test.ts +172 -0
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +123 -0
- package/src/__tests__/conversation-queue.test.ts +433 -1
- package/src/__tests__/conversation-retry-route.test.ts +3 -1
- package/src/__tests__/conversation-routes-hidden-queue.test.ts +64 -1
- package/src/__tests__/conversation-store-ephemeral.test.ts +4 -0
- package/src/__tests__/conversation-store.test.ts +26 -10
- package/src/__tests__/conversation-summarize-route.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +15 -6
- package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -5
- package/src/__tests__/conversation-surfaces-app-control.test.ts +12 -11
- package/src/__tests__/conversation-surfaces-app-open.test.ts +11 -5
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +5 -4
- package/src/__tests__/conversation-surfaces-history-restored-completion.test.ts +967 -0
- package/src/__tests__/conversation-surfaces-persisted-info.test.ts +312 -0
- package/src/__tests__/conversation-surfaces-queued-emit.test.ts +6 -5
- package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +12 -8
- package/src/__tests__/conversation-surfaces-standalone.test.ts +21 -11
- package/src/__tests__/conversation-surfaces-state-update.test.ts +7 -11
- package/src/__tests__/conversation-surfaces-table-action.test.ts +7 -6
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +11 -6
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +9 -6
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +70 -7
- package/src/__tests__/conversation-wait-for-idle.test.ts +6 -11
- package/src/__tests__/credential-prompt-route.test.ts +57 -7
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/cu-unified-flow.test.ts +89 -28
- package/src/__tests__/custom-group-listing-visibility.test.ts +1 -1
- package/src/__tests__/db-schedule-syntax-migration.test.ts +4 -1
- package/src/__tests__/discord-access-request-privacy.test.ts +366 -0
- package/src/__tests__/discord-requester-notice-privacy.test.ts +87 -0
- package/src/__tests__/disk-pressure-guard.test.ts +55 -55
- package/src/__tests__/disk-pressure-routes.test.ts +10 -10
- package/src/__tests__/disk-pressure-tools.test.ts +2 -2
- package/src/__tests__/disk-usage.test.ts +35 -20
- package/src/__tests__/document-sync-tags.test.ts +379 -0
- package/src/__tests__/document-update-default-surface.test.ts +28 -0
- package/src/__tests__/dynamic-skill-background-guard.test.ts +2 -2
- package/src/__tests__/emit-signal-routing-intent.test.ts +3 -4
- package/src/__tests__/ensure-conversation-exists.test.ts +8 -5
- package/src/__tests__/events-dev-bypass-actor.test.ts +183 -1
- package/src/__tests__/evict-conversations-for-reload.test.ts +90 -0
- package/src/__tests__/file-list-tool.test.ts +12 -10
- package/src/__tests__/file-ops-service.test.ts +151 -44
- package/src/__tests__/file-read-container-boundary.test.ts +137 -0
- package/src/__tests__/file-read-tool.test.ts +21 -9
- package/src/__tests__/file-url-path-guard.test.ts +83 -0
- package/src/__tests__/filesystem-tools.test.ts +82 -66
- package/src/__tests__/guardian-outbound-http.test.ts +8 -8
- package/src/__tests__/helpers/gateway-verification-sessions-stub.ts +14 -1
- package/src/__tests__/helpers/hub-clients.ts +42 -0
- package/src/__tests__/helpers/mock-conversation.ts +46 -0
- package/src/__tests__/helpers/verification-sessions-ipc-sim.ts +61 -57
- package/src/__tests__/history-repair.test.ts +112 -12
- package/src/__tests__/host-bash-proxy.test.ts +10 -5
- package/src/__tests__/host-bash-routes.test.ts +67 -0
- package/src/__tests__/host-browser-proxy.test.ts +2 -2
- package/src/__tests__/host-cu-proxy.test.ts +3 -7
- package/src/__tests__/host-file-proxy.test.ts +3 -7
- package/src/__tests__/host-file-read-tool.test.ts +5 -1
- package/src/__tests__/host-transfer-proxy-targeted.test.ts +6 -4
- package/src/__tests__/http-conversation-lineage.test.ts +2 -5
- package/src/__tests__/http-user-message-parity.test.ts +44 -0
- package/src/__tests__/identity-routes.test.ts +13 -13
- package/src/__tests__/image-conversion.test.ts +29 -26
- package/src/__tests__/image-source-path-reinject.test.ts +2 -2
- package/src/__tests__/inference-profile-session-handler.test.ts +95 -0
- package/src/__tests__/inference-profile-session-ipc.test.ts +25 -0
- package/src/__tests__/introduction-card-resolver.test.ts +6 -1
- package/src/__tests__/list-messages-attachments.test.ts +56 -32
- package/src/__tests__/list-messages-background-tool-completion.test.ts +4 -4
- package/src/__tests__/list-messages-client-message-id.test.ts +2 -2
- package/src/__tests__/list-messages-hidden-metadata.test.ts +16 -16
- package/src/__tests__/list-messages-page-latest.test.ts +61 -61
- package/src/__tests__/list-messages-provider-error.test.ts +6 -6
- package/src/__tests__/list-messages-queued.test.ts +49 -6
- package/src/__tests__/list-messages-system-card.test.ts +4 -4
- package/src/__tests__/list-messages-tool-merge.test.ts +9 -9
- package/src/__tests__/llm-resolver-override-or-default.test.ts +141 -0
- package/src/__tests__/llm-usage-store.test.ts +55 -0
- package/src/__tests__/manual-token-reconciliation.test.ts +45 -0
- package/src/__tests__/mcp-tool-annotations-risk.test.ts +133 -0
- package/src/__tests__/media-stream-server-integration.test.ts +36 -0
- package/src/__tests__/migration-export-http.test.ts +1 -1
- package/src/__tests__/migration-import-commit-http.test.ts +1 -1
- package/src/__tests__/migration-import-preflight-http.test.ts +1 -1
- package/src/__tests__/migration-validate-http.test.ts +1 -1
- package/src/__tests__/mtime-cache.test.ts +65 -3
- package/src/__tests__/notification-platform-adapter.test.ts +104 -0
- package/src/__tests__/notification-schedule-notify-dedup.test.ts +1 -1
- package/src/__tests__/oauth-provider-profiles.test.ts +4 -2
- package/src/__tests__/oauth-provider-seed-logos.test.ts +65 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +17 -0
- package/src/__tests__/oauth-providers-routes.test.ts +1 -0
- package/src/__tests__/openai-responses-prompt-cache.test.ts +5 -2
- package/src/__tests__/path-policy.test.ts +164 -1
- package/src/__tests__/persist-media-references.test.ts +2 -2
- package/src/__tests__/persist-unsendable-image-downscale.test.ts +5 -5
- package/src/__tests__/persist-unsendable-image.test.ts +23 -23
- package/src/__tests__/persona-resolver.test.ts +154 -0
- package/src/__tests__/plugin-api-webhook-url.test.ts +170 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +6 -0
- package/src/__tests__/processing-flag-persist-failure.test.ts +15 -10
- package/src/__tests__/profile-availability-incomplete.test.ts +113 -0
- package/src/__tests__/profiler-routes.test.ts +13 -13
- package/src/__tests__/prompt-cache-cross-turn-stability.test.ts +593 -0
- package/src/__tests__/proxy-approval-callback.test.ts +3 -3
- package/src/__tests__/queued-message-cancel-and-steer.test.ts +189 -0
- package/src/__tests__/queued-message-steer-actor-scoping.test.ts +164 -0
- package/src/__tests__/resolve-trust-class.test.ts +20 -0
- package/src/__tests__/run-conversation-turn-persistence.test.ts +1 -1
- package/src/__tests__/run-due-schedules.test.ts +174 -0
- package/src/__tests__/runtime-attachment-metadata.test.ts +15 -7
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +166 -0
- package/src/__tests__/schedule-routes.test.ts +730 -8
- package/src/__tests__/schedule-store.test.ts +540 -0
- package/src/__tests__/schedule-tools.test.ts +103 -0
- package/src/__tests__/scheduler-wake.test.ts +5 -0
- package/src/__tests__/secret-routes-platform-proxy.test.ts +29 -1
- package/src/__tests__/secure-keys.test.ts +91 -0
- package/src/__tests__/server-history-render.test.ts +97 -7
- package/src/__tests__/size-guard.test.ts +8 -8
- package/src/__tests__/skill-docs-sync-guard.test.ts +3 -2
- package/src/__tests__/skill-tool-factory.test.ts +247 -0
- package/src/__tests__/skills-install-staging.test.ts +15 -6
- package/src/__tests__/slack-edit-ordering-characterization.test.ts +103 -0
- package/src/__tests__/slack-inbound-verification.test.ts +4 -1
- package/src/__tests__/slack-mention-binding-invariance.test.ts +103 -0
- package/src/__tests__/slack-mention-provider-leak-audit.test.ts +135 -0
- package/src/__tests__/starter-task-flow.test.ts +5 -4
- package/src/__tests__/steer-on-enqueue-question.test.ts +3 -2
- package/src/__tests__/stt-language-catalog-parity.test.ts +38 -0
- package/src/__tests__/subagent-allowlist-validation.test.ts +26 -4
- package/src/__tests__/subagent-cron-run-attribution.test.ts +301 -0
- package/src/__tests__/subagent-disposal.test.ts +10 -0
- package/src/__tests__/subagent-fork-notifications.test.ts +10 -0
- package/src/__tests__/subagent-fork-prompt-role.test.ts +85 -11
- package/src/__tests__/subagent-fork-spawn.test.ts +15 -7
- package/src/__tests__/subagent-manager-notify.test.ts +148 -0
- package/src/__tests__/subagent-notify-parent.test.ts +1 -1
- package/src/__tests__/subagent-persistence.test.ts +141 -0
- package/src/__tests__/subagent-role-registry.test.ts +172 -63
- package/src/__tests__/subagent-role-resolution.test.ts +88 -0
- package/src/__tests__/subagent-spawn-and-await.test.ts +48 -0
- package/src/__tests__/subagent-spawn-tool-fork.test.ts +111 -10
- package/src/__tests__/subagent-terminal-message.test.ts +88 -1
- package/src/__tests__/subagent-tool-gate-mode.test.ts +225 -10
- package/src/__tests__/subagent-tools.test.ts +1901 -108
- package/src/__tests__/surface-completion-compaction-boundary.test.ts +124 -0
- package/src/__tests__/surface-completion-in-flight-snapshot.test.ts +298 -0
- package/src/__tests__/system-prompt.test.ts +82 -7
- package/src/__tests__/tool-grant-request-escalation.test.ts +40 -1
- package/src/__tests__/tool-result-spool.test.ts +74 -18
- package/src/__tests__/tool-side-effects-documents.test.ts +174 -0
- package/src/__tests__/tool-side-effects-hook-failures.test.ts +129 -0
- package/src/__tests__/tools-audio-read.test.ts +10 -10
- package/src/__tests__/tools-get-route.test.ts +85 -12
- package/src/__tests__/tools-list-cli.test.ts +171 -0
- package/src/__tests__/top-level-renderer.test.ts +54 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +68 -1
- package/src/__tests__/trusted-contact-multichannel.test.ts +4 -1
- package/src/__tests__/turn-tail-chain.test.ts +135 -0
- package/src/__tests__/ui-choice-copy-surfaces.test.ts +6 -4
- package/src/__tests__/ui-visual-surface.test.ts +5 -4
- package/src/__tests__/ui-work-result-surface.test.ts +5 -4
- package/src/__tests__/unified-turn-context-visible-app.test.ts +7 -8
- package/src/__tests__/usage-routes.test.ts +62 -0
- package/src/__tests__/verification-outbound-delivery.test.ts +176 -0
- package/src/__tests__/visible-app-context.test.ts +20 -4
- package/src/__tests__/web-search-history.test.ts +96 -0
- package/src/__tests__/workspace-migration-139-clear-renamed-cost-profile-label.test.ts +6 -6
- package/src/__tests__/workspace-migration-140-repair-seed-pinned-memory-v3-live.test.ts +330 -0
- package/src/acp/failure-error.ts +3 -5
- package/src/agent/attachments.ts +26 -24
- package/src/agent/history-repair/history-repair.ts +27 -26
- package/src/agent/image-optimize.ts +6 -6
- package/src/agent/loop.ts +40 -22
- package/src/agent/message-types.ts +3 -3
- package/src/api/constants/document-tools.ts +55 -0
- package/src/api/events/question-answered.ts +53 -0
- package/src/api/events/tool-result.ts +7 -0
- package/src/api/index.ts +13 -0
- package/src/api/responses/conversation-message.ts +11 -0
- package/src/api/surfaces-oauth-connect.test.ts +96 -0
- package/src/api/surfaces.ts +36 -8
- package/src/approvals/guardian-channel-delivery.ts +83 -0
- package/src/approvals/guardian-expiry-notifier.ts +13 -11
- package/src/approvals/guardian-request-resolvers.ts +222 -135
- package/src/calls/__tests__/voice-session-bridge.test.ts +50 -0
- package/src/calls/media-stream-server.ts +9 -1
- package/src/calls/voice-session-bridge.ts +38 -0
- package/src/channels/__tests__/gateway-verification-sessions.test.ts +3 -3
- package/src/channels/config.ts +7 -2
- package/src/channels/gateway-verification-sessions.ts +7 -2
- package/src/channels/types.ts +7 -1
- package/src/cli/commands/__tests__/cache.test.ts +7 -4
- package/src/cli/commands/__tests__/cli-test-harness.ts +82 -0
- package/src/cli/commands/__tests__/conversations-wake.test.ts +51 -6
- package/src/cli/commands/__tests__/inference-models.test.ts +14 -0
- package/src/cli/commands/__tests__/inference-profiles.test.ts +44 -1
- package/src/cli/commands/__tests__/inference-providers.test.ts +7 -0
- package/src/cli/commands/__tests__/plugins.test.ts +692 -0
- package/src/cli/commands/__tests__/schedules.test.ts +136 -37
- package/src/cli/commands/conversations.ts +7 -3
- package/src/cli/commands/credentials.help.ts +7 -0
- package/src/cli/commands/inference-profiles.ts +9 -3
- package/src/cli/commands/inference.help.ts +16 -2
- package/src/cli/commands/mcp.help.ts +4 -2
- package/src/cli/commands/memory/memory-retrospective.ts +18 -0
- package/src/cli/commands/notifications.ts +3 -6
- package/src/cli/commands/oauth/connect-surface-guidance.test.ts +16 -0
- package/src/cli/commands/oauth/connect-surface-guidance.ts +25 -7
- package/src/cli/commands/oauth/connect.test.ts +26 -1
- package/src/cli/commands/oauth/connect.ts +4 -1
- package/src/cli/commands/oauth/index.help.ts +3 -1
- package/src/cli/commands/oauth/providers.ts +3 -0
- package/src/cli/commands/plugins.help.ts +25 -2
- package/src/cli/commands/plugins.ts +289 -10
- package/src/cli/commands/schedules.help.ts +14 -12
- package/src/cli/commands/schedules.ts +19 -5
- package/src/cli/commands/tools.help.ts +4 -2
- package/src/cli/commands/tools.ts +38 -0
- package/src/cli/lib/__tests__/inspect-plugin.test.ts +1 -0
- package/src/cli/lib/__tests__/install-from-github.test.ts +79 -0
- package/src/cli/lib/__tests__/install-from-platform.test.ts +47 -1
- package/src/cli/lib/__tests__/plugin-surfaces.test.ts +86 -7
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +122 -0
- package/src/cli/lib/install-from-github.ts +61 -0
- package/src/cli/lib/install-from-platform.ts +13 -2
- package/src/cli/lib/plugin-surfaces.ts +124 -4
- package/src/cli/lib/upgrade-plugin.ts +18 -0
- package/src/cli.ts +1 -1
- package/src/config/__tests__/deployment-context-defaults.test.ts +73 -0
- package/src/config/__tests__/memory-retrospective-schema.test.ts +40 -0
- package/src/config/__tests__/plugin-updates-schema.test.ts +43 -0
- package/src/config/__tests__/profile-tool-support.test.ts +131 -0
- package/src/config/bundled-skills/AGENTS.md +1 -1
- package/src/config/bundled-skills/app-builder/SKILL.md +1 -1
- package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +3 -3
- package/src/config/bundled-skills/schedule/SKILL.md +5 -1
- package/src/config/bundled-skills/schedule/TOOLS.json +2 -2
- package/src/config/bundled-skills/settings/TOOLS.json +2 -2
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +1 -0
- package/src/config/bundled-skills/subagent/SKILL.md +53 -22
- package/src/config/bundled-skills/subagent/TOOLS.json +14 -13
- package/src/config/bundled-skills/visualize/SKILL.md +1 -1
- package/src/config/default-profile-catalog.ts +6 -2
- package/src/config/feature-flag-registry.json +25 -17
- package/src/config/llm-resolver.ts +45 -15
- package/src/config/loader.ts +70 -1
- package/src/config/profile-tool-support.ts +110 -0
- package/src/config/sanitize-for-transfer.ts +11 -0
- package/src/config/schema.ts +4 -0
- package/src/config/schemas/__tests__/stt.test.ts +20 -5
- package/src/config/schemas/channels.ts +13 -0
- package/src/config/schemas/memory-retrospective.ts +25 -0
- package/src/config/schemas/plugin-updates.ts +55 -0
- package/src/config/schemas/services.ts +8 -4
- package/src/config/schemas/stt.ts +17 -5
- package/src/config/schemas/timeouts.ts +1 -1
- package/src/contacts/__tests__/guardian-delivery-reader.test.ts +16 -0
- package/src/contacts/guardian-delivery-reader.ts +29 -2
- package/src/context/compactor.ts +4 -4
- package/src/context/post-turn-tool-result-truncation.ts +32 -16
- package/src/context/strip-injections.ts +42 -0
- package/src/context/tool-result-spool.ts +32 -18
- package/src/credential-execution/ces-runtime.ts +3 -0
- package/src/credential-execution/client.ts +22 -1
- package/src/credential-execution/process-manager.test.ts +99 -0
- package/src/credential-execution/process-manager.ts +62 -16
- package/src/daemon/__tests__/abort-null-controller.test.ts +141 -5
- package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +8 -12
- package/src/daemon/__tests__/install-assistant-command.test.ts +234 -0
- package/src/daemon/__tests__/turn-tail-assistant-reply-notify.test.ts +2 -4
- package/src/daemon/__tests__/turn-tail-deleted-conversation.test.ts +20 -29
- package/src/daemon/channel-ui-capability.ts +4 -4
- package/src/daemon/conversation-agent-loop-handlers.ts +117 -61
- package/src/daemon/conversation-agent-loop.ts +318 -148
- package/src/daemon/conversation-attachments.ts +1 -1
- package/src/daemon/conversation-error.ts +8 -1
- package/src/daemon/conversation-initial-prompt.ts +16 -38
- package/src/daemon/conversation-lifecycle.ts +154 -32
- package/src/daemon/conversation-messaging.ts +106 -43
- package/src/daemon/conversation-process.ts +131 -33
- package/src/daemon/conversation-queue-manager.ts +10 -0
- package/src/daemon/conversation-runtime-assembly.ts +9 -4
- package/src/daemon/conversation-store.ts +52 -3
- package/src/daemon/conversation-surface-state.ts +9 -0
- package/src/daemon/conversation-surfaces.ts +403 -210
- package/src/daemon/conversation-tool-setup.ts +56 -7
- package/src/daemon/conversation-turn-finalize.ts +134 -89
- package/src/daemon/conversation.ts +111 -27
- package/src/daemon/disk-pressure-guard-lifecycle.ts +2 -2
- package/src/daemon/disk-pressure-guard.ts +3 -3
- package/src/daemon/doordash-steps.ts +5 -5
- package/src/daemon/external-plugins-bootstrap.ts +7 -1
- package/src/daemon/handlers/config-channels.ts +48 -5
- package/src/daemon/handlers/conversation-history.ts +1 -1
- package/src/daemon/handlers/conversations.ts +62 -17
- package/src/daemon/handlers/shared.ts +12 -0
- package/src/daemon/handlers/skills.ts +11 -1
- package/src/daemon/identity-helpers.ts +38 -10
- package/src/daemon/install-assistant-command.ts +322 -0
- package/src/daemon/lifecycle.ts +14 -3
- package/src/daemon/message-types/conversations.ts +6 -7
- package/src/daemon/message-types/sync.ts +1 -0
- package/src/daemon/persist-media-references.ts +23 -14
- package/src/daemon/process-message.ts +6 -6
- package/src/daemon/tool-setup-types.ts +19 -122
- package/src/daemon/tool-side-effects.ts +17 -94
- package/src/daemon/trust-context-types.ts +39 -0
- package/src/daemon/trust-context.ts +14 -6
- package/src/daemon/turn-tail-chain.ts +73 -0
- package/src/daemon/web-search-history.ts +47 -33
- package/src/heartbeat/heartbeat-service.ts +6 -6
- package/src/ipc/__tests__/socket-path.test.ts +21 -0
- package/src/ipc/assistant-server.ts +17 -10
- package/src/ipc/cli-client.ts +10 -1
- package/src/ipc/routes/__tests__/documents-sync-ipc-routes.test.ts +56 -0
- package/src/ipc/routes/documents-sync-ipc-routes.ts +42 -0
- package/src/ipc/socket-cleanup.ts +9 -7
- package/src/ipc/socket-path.ts +4 -96
- package/src/live-voice/__tests__/live-voice-archive.test.ts +16 -16
- package/src/live-voice/__tests__/live-voice-attach-image.test.ts +209 -0
- package/src/live-voice/__tests__/live-voice-connection.test.ts +20 -0
- package/src/live-voice/__tests__/live-voice-integration.test.ts +7 -5
- package/src/live-voice/__tests__/live-voice-session-telemetry.test.ts +265 -0
- package/src/live-voice/__tests__/live-voice-stt.test.ts +6 -1
- package/src/live-voice/__tests__/live-voice-vad.test.ts +35 -15
- package/src/live-voice/__tests__/protocol.test.ts +52 -0
- package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +3 -0
- package/src/live-voice/live-voice-archive.ts +13 -13
- package/src/live-voice/live-voice-connection.ts +7 -1
- package/src/live-voice/live-voice-photo.ts +169 -0
- package/src/live-voice/live-voice-session.ts +108 -4
- package/src/live-voice/protocol.ts +89 -1
- package/src/mcp/client.ts +10 -0
- package/src/messaging/providers/__tests__/callback-routing.test.ts +5 -1
- package/src/messaging/providers/__tests__/transport-dispatch.test.ts +66 -4
- package/src/messaging/providers/callback-routing.ts +1 -0
- package/src/messaging/providers/discord/api.ts +312 -0
- package/src/messaging/providers/discord/dm-delivery.test.ts +204 -0
- package/src/messaging/providers/discord/render.test.ts +204 -0
- package/src/messaging/providers/discord/render.ts +195 -0
- package/src/messaging/providers/discord/send.test.ts +179 -0
- package/src/messaging/providers/discord/send.ts +212 -0
- package/src/messaging/providers/discord/transport.ts +81 -0
- package/src/messaging/providers/index.ts +3 -1
- package/src/messaging/providers/retry-policy.test.ts +247 -0
- package/src/messaging/providers/retry-policy.ts +206 -0
- package/src/messaging/providers/slack/mention-source.test.ts +285 -0
- package/src/messaging/providers/slack/mention-source.ts +287 -0
- package/src/messaging/providers/telegram-bot/api.ts +47 -130
- package/src/messaging/providers/whatsapp/api.ts +47 -136
- package/src/monitoring/__tests__/db-integrity-sample.test.ts +4 -1
- package/src/monitoring/__tests__/plugin-auto-update.test.ts +332 -0
- package/src/monitoring/db-integrity-sample.ts +6 -1
- package/src/monitoring/plugin-auto-update.ts +359 -0
- package/src/monitoring/resource-sampler.ts +21 -5
- package/src/monitoring/worker.ts +13 -0
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +375 -2
- package/src/notifications/__tests__/broadcaster.test.ts +37 -1
- package/src/notifications/__tests__/copy-composer.test.ts +165 -0
- package/src/notifications/__tests__/emit-signal-pipeline-failure.test.ts +205 -0
- package/src/notifications/__tests__/notification-utils.test.ts +249 -0
- package/src/notifications/adapters/platform.ts +31 -2
- package/src/notifications/assistant-reply-producer.ts +79 -32
- package/src/notifications/broadcaster.ts +30 -10
- package/src/notifications/copy-composer.ts +69 -0
- package/src/notifications/emit-signal.ts +82 -15
- package/src/notifications/events-store.ts +10 -2
- package/src/notifications/home-feed-side-effect.ts +6 -3
- package/src/notifications/notification-utils.ts +173 -5
- package/src/notifications/signal.ts +12 -0
- package/src/oauth/AGENTS.md +4 -2
- package/src/oauth/byo-connection.ts +6 -1
- package/src/oauth/credential-token-resolver.ts +3 -10
- package/src/oauth/manual-token-connection.ts +29 -64
- package/src/oauth/manual-token-providers.ts +62 -0
- package/src/oauth/provider-serializer.ts +13 -0
- package/src/oauth/seed-providers.ts +62 -11
- package/src/onboarding/onboarding-events-store.ts +91 -1
- package/src/permissions/question-prompter.test.ts +4 -0
- package/src/permissions/question-prompter.ts +21 -4
- package/src/persistence/__tests__/conversation-lineage.test.ts +248 -0
- package/src/persistence/attachments-store.ts +15 -15
- package/src/persistence/conversation-attention-store.ts +30 -10
- package/src/persistence/conversation-bootstrap.ts +17 -0
- package/src/persistence/conversation-crud.ts +363 -97
- package/src/persistence/conversation-group-migration.ts +1 -1
- package/src/persistence/conversation-key-store.ts +38 -10
- package/src/persistence/conversation-lineage.ts +226 -0
- package/src/persistence/conversation-plugin-facade.ts +28 -3
- package/src/persistence/conversation-queries.ts +207 -33
- package/src/persistence/conversation-types.ts +107 -1
- package/src/persistence/jobs-store.ts +32 -8
- package/src/persistence/llm-usage-store.ts +22 -0
- package/src/persistence/migrations/361-normalize-managed-connection-rows.test.ts +168 -0
- package/src/persistence/migrations/361-normalize-managed-connection-rows.ts +76 -0
- package/src/persistence/migrations/362-add-conversation-subagent-kind.test.ts +187 -0
- package/src/persistence/migrations/362-add-conversation-subagent-kind.ts +93 -0
- package/src/persistence/migrations/363-backfill-schedule-inference-profile.test.ts +229 -0
- package/src/persistence/migrations/363-backfill-schedule-inference-profile.ts +127 -0
- package/src/persistence/migrations/364-add-schedule-source-key.test.ts +120 -0
- package/src/persistence/migrations/364-add-schedule-source-key.ts +43 -0
- package/src/persistence/migrations/365-add-conversation-fork-strategy.ts +27 -0
- package/src/persistence/schema/conversations.ts +32 -0
- package/src/persistence/schema/infrastructure.ts +3 -0
- package/src/persistence/steps.ts +30 -0
- package/src/persistence/subagent-store.ts +148 -6
- package/src/platform/managed-speech.test.ts +20 -0
- package/src/platform/managed-speech.ts +9 -0
- package/src/plugin-api/index.ts +8 -0
- package/src/plugin-api/webhook-url.ts +151 -0
- package/src/plugins/__tests__/installed-plugin-dirs.test.ts +82 -0
- package/src/plugins/__tests__/source-fingerprint.test.ts +5 -1
- package/src/plugins/collect-source-versions.ts +4 -27
- package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +5 -5
- package/src/plugins/defaults/exploration-drift/package.json +1 -1
- package/src/plugins/defaults/image-recovery/hooks/post-model-call.ts +4 -2
- package/src/plugins/defaults/image-recovery/recover.ts +67 -57
- package/src/plugins/defaults/index.ts +1 -1
- package/src/plugins/defaults/memory/__tests__/conversation-queries.test.ts +64 -18
- package/src/plugins/defaults/memory/__tests__/jobs-worker-outcome-contract.test.ts +242 -0
- package/src/plugins/defaults/memory/__tests__/jobs-worker-retrospective-sweep.test.ts +24 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +48 -3
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +511 -19
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +39 -3
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +493 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-wake-chain.test.ts +5 -4
- package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +5 -1
- package/src/plugins/defaults/memory/job-handlers.ts +87 -5
- package/src/plugins/defaults/memory/jobs-worker.ts +60 -6
- package/src/plugins/defaults/memory/memory-retrospective-constants.ts +11 -0
- package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +29 -0
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +342 -60
- package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +23 -3
- package/src/plugins/defaults/memory/substrate/consolidation-lock.ts +9 -4
- package/src/plugins/defaults/memory/substrate/sweep-job.ts +7 -0
- package/src/plugins/defaults/memory/v1/graph/injection.ts +1 -1
- package/src/plugins/defaults/memory/v2/router.ts +8 -0
- package/src/plugins/defaults/turn-context/unified-turn-context.ts +1 -2
- package/src/plugins/external-plugin-loader.ts +17 -8
- package/src/plugins/installed-plugin-dirs.ts +101 -0
- package/src/plugins/mtime-cache.ts +34 -22
- package/src/plugins/types.ts +69 -8
- package/src/prompts/persona-resolver.ts +6 -2
- package/src/prompts/system-prompt.ts +72 -17
- package/src/providers/__tests__/inference.test.ts +40 -0
- package/src/providers/__tests__/preflight-resolved-config.test.ts +58 -1
- package/src/providers/__tests__/retry-callsite.test.ts +67 -0
- package/src/providers/__tests__/vellum-connection-routing.test.ts +42 -11
- package/src/providers/anthropic/client.ts +112 -95
- package/src/providers/connection-resolution.ts +35 -1
- package/src/providers/gemini/client.ts +5 -5
- package/src/providers/inference/adapter-factory.ts +16 -10
- package/src/providers/inference/auth.ts +16 -0
- package/src/providers/inference/connection-availability.ts +164 -12
- package/src/providers/inference/connections.ts +25 -1
- package/src/providers/media-resolve.ts +21 -11
- package/src/providers/model-catalog.ts +15 -0
- package/src/providers/openai/chat-completions-provider.ts +4 -4
- package/src/providers/openai/responses-provider.ts +12 -25
- package/src/providers/registry.ts +9 -4
- package/src/providers/retry.ts +25 -0
- package/src/providers/server-tool-pairing.ts +134 -0
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +187 -6
- package/src/providers/speech-to-text/deepgram.ts +1 -0
- package/src/providers/speech-to-text/resolve.ts +80 -10
- package/src/providers/speech-to-text/vellum-managed.ts +7 -1
- package/src/providers/speech-to-text/vellum-speech-relay-connection.ts +3 -1
- package/src/providers/types.ts +26 -6
- package/src/providers/vellum-model-routing.ts +6 -7
- package/src/runtime/AGENTS.md +2 -0
- package/src/runtime/__tests__/agent-wake.test.ts +16 -16
- package/src/runtime/__tests__/background-job-runner.test.ts +0 -144
- package/src/runtime/__tests__/desktop-presence.test.ts +162 -0
- package/src/runtime/agent-wake.ts +63 -24
- package/src/runtime/assistant-event-hub.ts +87 -12
- package/src/runtime/auth/__tests__/guard-tests.test.ts +2 -0
- package/src/runtime/auth/__tests__/scopes.test.ts +20 -0
- package/src/runtime/auth/same-actor.ts +56 -8
- package/src/runtime/auth/scopes.ts +12 -0
- package/src/runtime/auth/types.ts +2 -0
- package/src/runtime/background-job-runner.ts +5 -27
- package/src/runtime/channel-readiness-service.ts +59 -3
- package/src/runtime/channel-readiness-types.ts +18 -0
- package/src/runtime/desktop-presence.ts +69 -0
- package/src/runtime/http-server.ts +12 -0
- package/src/runtime/local-actor-identity.ts +11 -9
- package/src/runtime/migrations/__tests__/vbundle-symlink-streaming-importer.test.ts +0 -0
- package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/client-routes.test.ts +186 -14
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +547 -1
- package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +162 -7
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +14 -10
- package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +340 -3
- package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +28 -1
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +173 -1
- package/src/runtime/routes/__tests__/question-routes.test.ts +64 -0
- package/src/runtime/routes/__tests__/retrospective-routes.test.ts +20 -0
- package/src/runtime/routes/__tests__/skill-history-route.test.ts +116 -0
- package/src/runtime/routes/__tests__/wake-conversation-routes.test.ts +60 -2
- package/src/runtime/routes/archive-utils.ts +16 -12
- package/src/runtime/routes/attachment-routes.ts +13 -6
- package/src/runtime/routes/canned-reply-release.ts +54 -0
- package/src/runtime/routes/channel-verification-routes.ts +2 -27
- package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +5 -0
- package/src/runtime/routes/client-routes.ts +114 -2
- package/src/runtime/routes/conversation-cli-routes.ts +10 -12
- package/src/runtime/routes/conversation-list-routes.ts +59 -10
- package/src/runtime/routes/conversation-management-routes.ts +47 -18
- package/src/runtime/routes/conversation-query-routes.ts +32 -53
- package/src/runtime/routes/conversation-routes.ts +360 -293
- package/src/runtime/routes/credential-prompt-routes.ts +23 -2
- package/src/runtime/routes/debug-routes.ts +4 -1
- package/src/runtime/routes/default-provider-routes.ts +7 -11
- package/src/runtime/routes/documents-routes.ts +44 -1
- package/src/runtime/routes/events-routes.ts +28 -1
- package/src/runtime/routes/host-app-control-routes.ts +2 -0
- package/src/runtime/routes/host-bash-routes.ts +2 -0
- package/src/runtime/routes/host-browser-routes.ts +2 -0
- package/src/runtime/routes/host-cu-routes.ts +2 -0
- package/src/runtime/routes/host-file-routes.ts +2 -0
- package/src/runtime/routes/host-transfer-routes.ts +4 -0
- package/src/runtime/routes/identity-routes.ts +6 -6
- package/src/runtime/routes/inbound-message-handler.ts +5 -5
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +7 -3
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +14 -0
- package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +2 -2
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +14 -11
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +14 -17
- package/src/runtime/routes/inference-profile-availability-guard.ts +171 -0
- package/src/runtime/routes/inference-profile-session-handler.ts +26 -2
- package/src/runtime/routes/inference-profile-session-routes.ts +2 -1
- package/src/runtime/routes/inference-profiles-routes.ts +152 -73
- package/src/runtime/routes/inference-provider-connection-routes.ts +0 -16
- package/src/runtime/routes/llm-call-sites-routes.ts +13 -0
- package/src/runtime/routes/log-export-routes.ts +1 -1
- package/src/runtime/routes/notification-routes.ts +0 -14
- package/src/runtime/routes/oauth-providers.ts +1 -0
- package/src/runtime/routes/plugins-routes.ts +106 -1
- package/src/runtime/routes/profiler-routes.ts +4 -2
- package/src/runtime/routes/question-routes.ts +28 -10
- package/src/runtime/routes/retrospective-routes.ts +9 -3
- package/src/runtime/routes/schedule-routes.ts +210 -12
- package/src/runtime/routes/secret-routes.ts +40 -23
- package/src/runtime/routes/settings-routes.ts +41 -13
- package/src/runtime/routes/skills-routes.ts +79 -0
- package/src/runtime/routes/ui-snapshot-routes.ts +1 -0
- package/src/runtime/routes/wake-conversation-routes.ts +23 -0
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +1 -0
- package/src/runtime/services/conversation-serializer.ts +24 -9
- package/src/runtime/sync/documents-sidecar-publish.test.ts +101 -0
- package/src/runtime/sync/resource-sync-events.ts +79 -1
- package/src/runtime/sync/worker-daemon-notify.test.ts +34 -0
- package/src/runtime/sync/worker-daemon-notify.ts +44 -6
- package/src/runtime/verification-outbound-actions.ts +250 -222
- package/src/runtime/verification-templates.ts +41 -30
- package/src/schedule/__tests__/plugin-schedule-declarations.test.ts +614 -0
- package/src/schedule/__tests__/plugin-schedule-reconciler.test.ts +1066 -0
- package/src/schedule/inference-profile.ts +64 -3
- package/src/schedule/plugin-schedule-declarations.ts +518 -0
- package/src/schedule/plugin-schedule-reconciler.ts +605 -0
- package/src/schedule/plugin-schedules-gate.ts +23 -0
- package/src/schedule/schedule-store.ts +520 -26
- package/src/schedule/scheduler.ts +38 -0
- package/src/security/credential-backend.ts +51 -4
- package/src/security/encrypted-store.ts +64 -42
- package/src/security/secure-keys.ts +55 -7
- package/src/skills/catalog-install.ts +20 -7
- package/src/skills/inline-command-runner.ts +2 -7
- package/src/skills/skill-history.test.ts +269 -0
- package/src/skills/skill-history.ts +250 -0
- package/src/skills/skillssh-registry.ts +1 -1
- package/src/stt/daemon-batch-transcriber.ts +8 -6
- package/src/subagent/__tests__/consult-prompt.test.ts +25 -0
- package/src/subagent/consult-prompt.ts +3 -1
- package/src/subagent/consult-transcript.ts +1 -1
- package/src/subagent/manager.ts +304 -44
- package/src/subagent/progress-events.ts +35 -0
- package/src/subagent/role-resolution.ts +88 -0
- package/src/subagent/types.ts +302 -67
- package/src/subagent/validate-allowlists.ts +3 -4
- package/src/telegram/__tests__/webhook-health.test.ts +73 -1
- package/src/telegram/webhook-health.ts +101 -3
- package/src/telemetry/__tests__/telemetry-event-fixtures.ts +2 -0
- package/src/telemetry/activation-funnel.ts +6 -1
- package/src/telemetry/live-voice-funnel.ts +75 -0
- package/src/telemetry/telemetry-event-sources.ts +6 -0
- package/src/telemetry/telemetry-wire-source.json +1 -1
- package/src/telemetry/telemetry-wire.generated.ts +2 -0
- package/src/telemetry/types.ts +23 -4
- package/src/telemetry/usage-telemetry-reporter.test.ts +4 -0
- package/src/tools/__tests__/subagent-workflow-tool-input-schemas.test.ts +1 -1
- package/src/tools/ask-question/ask-question-tool.test.ts +118 -3
- package/src/tools/ask-question/ask-question-tool.ts +56 -2
- package/src/tools/browser/browser-execution.ts +1 -1
- package/src/tools/calls/call-start.ts +8 -5
- package/src/tools/conversation-groups/move_to_group.ts +10 -12
- package/src/tools/document/document-tool.ts +11 -4
- package/src/tools/execution-timeout.ts +9 -2
- package/src/tools/executor.ts +7 -6
- package/src/tools/filesystem/edit.ts +1 -1
- package/src/tools/filesystem/list.ts +1 -1
- package/src/tools/filesystem/read.ts +7 -13
- package/src/tools/filesystem/search.ts +23 -21
- package/src/tools/filesystem/write.ts +4 -1
- package/src/tools/host-filesystem/edit.ts +7 -2
- package/src/tools/host-filesystem/read.ts +21 -8
- package/src/tools/host-filesystem/write.ts +10 -2
- package/src/tools/host-terminal/host-shell.ts +2 -2
- package/src/tools/mcp/mcp-tool-factory.ts +25 -1
- package/src/tools/permission-checker.ts +8 -3
- package/src/tools/schedule/delete.ts +13 -1
- package/src/tools/schedule/list.ts +14 -3
- package/src/tools/schedule/update.ts +56 -33
- package/src/tools/shared/filesystem/audio-read.ts +6 -4
- package/src/tools/shared/filesystem/file-ops-service.ts +176 -116
- package/src/tools/shared/filesystem/image-read.ts +12 -12
- package/src/tools/shared/filesystem/path-policy.ts +70 -5
- package/src/tools/shared/filesystem/size-guard.ts +6 -6
- package/src/tools/shared/input-misuse.ts +72 -0
- package/src/tools/skills/scaffold-managed.ts +82 -0
- package/src/tools/skills/skill-tool-factory.ts +14 -1
- package/src/tools/subagent/consult-deadline.ts +12 -7
- package/src/tools/subagent/message.ts +3 -1
- package/src/tools/subagent/read.ts +66 -4
- package/src/tools/subagent/spawn.ts +500 -78
- package/src/tools/tool-approval-handler.ts +45 -4
- package/src/tools/types.ts +22 -3
- package/src/tools/ui-surface/surface-shape-docs.ts +1 -1
- package/src/tools/ui-surface/visual-validation.ts +2 -0
- package/src/usage/__tests__/subagent-attribution.test.ts +109 -0
- package/src/usage/subagent-attribution.ts +108 -0
- package/src/util/__tests__/sqlite-retry.test.ts +112 -0
- package/src/util/abort-reasons.ts +16 -0
- package/src/util/ansi.ts +44 -0
- package/src/util/disk-usage.ts +17 -13
- package/src/util/image-conversion.ts +20 -13
- package/src/util/schedule-source-key.ts +23 -0
- package/src/util/worker-process.ts +7 -1
- package/src/workspace/git-service.ts +33 -9
- package/src/workspace/migrations/140-repair-seed-pinned-memory-v3-live.ts +260 -0
- package/src/workspace/migrations/141-stt-english-default-to-multilingual.ts +111 -0
- package/src/workspace/migrations/__tests__/141-stt-english-default-to-multilingual.test.ts +126 -0
- package/src/workspace/migrations/registry.ts +4 -0
- package/src/workspace/top-level-renderer.ts +15 -2
- package/src/__tests__/tool-side-effects-slack-dm.test.ts +0 -357
- package/src/daemon/install-symlink.ts +0 -207
- package/src/notifications/deferred-emit.ts +0 -147
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
1
3
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
4
|
|
|
3
5
|
mock.module("../heartbeat/heartbeat-service.js", () => ({
|
|
@@ -58,12 +60,17 @@ import { BadRequestError, NotFoundError } from "../runtime/routes/errors.js";
|
|
|
58
60
|
import { ROUTES as HEARTBEAT_ROUTES } from "../runtime/routes/heartbeat-routes.js";
|
|
59
61
|
import { ROUTES as SCHEDULE_ROUTES } from "../runtime/routes/schedule-routes.js";
|
|
60
62
|
import type { RouteDefinition } from "../runtime/routes/types.js";
|
|
63
|
+
import { resolveDefaultScheduleInferenceProfile } from "../schedule/inference-profile.js";
|
|
61
64
|
import {
|
|
62
65
|
completeScheduleRun,
|
|
63
66
|
createSchedule,
|
|
64
67
|
createScheduleRun,
|
|
68
|
+
type DeclaredScheduleDefinition,
|
|
65
69
|
listSchedules,
|
|
70
|
+
upsertDeclaredSchedule,
|
|
66
71
|
} from "../schedule/schedule-store.js";
|
|
72
|
+
import { getWorkspacePluginsDir } from "../util/platform.js";
|
|
73
|
+
import { setOverridesForTesting } from "./feature-flag-test-helpers.js";
|
|
67
74
|
|
|
68
75
|
await initializeDb();
|
|
69
76
|
|
|
@@ -1230,14 +1237,50 @@ describe("POST /schedules — create", () => {
|
|
|
1230
1237
|
expect(listSchedules()[0].inferenceProfile).toBe("cost-optimized");
|
|
1231
1238
|
});
|
|
1232
1239
|
|
|
1233
|
-
|
|
1240
|
+
// The endpoint validates inferenceProfile once, before branching on mode, so
|
|
1241
|
+
// every mode it accepts has to honour the value it just validated.
|
|
1242
|
+
test("persists a valid inferenceProfile on a workflow-mode create", async () => {
|
|
1243
|
+
const result = await postCreate({
|
|
1244
|
+
name: "Cheap triage",
|
|
1245
|
+
description: "Workflow run on a cheap model",
|
|
1246
|
+
expression: "0 * * * *",
|
|
1247
|
+
message: "trigger",
|
|
1248
|
+
mode: "workflow",
|
|
1249
|
+
workflowName: "triage-inbox",
|
|
1250
|
+
inferenceProfile: "cost-optimized",
|
|
1251
|
+
});
|
|
1252
|
+
expect(result.schedule.inferenceProfile).toBe("cost-optimized");
|
|
1253
|
+
expect(listSchedules()[0].inferenceProfile).toBe("cost-optimized");
|
|
1254
|
+
});
|
|
1255
|
+
|
|
1256
|
+
test("persists a valid inferenceProfile on a script-mode create", async () => {
|
|
1257
|
+
const result = await postCreate({
|
|
1258
|
+
name: "Cheap script",
|
|
1259
|
+
description: "Script run on a cheap model",
|
|
1260
|
+
expression: "0 * * * *",
|
|
1261
|
+
message: "trigger",
|
|
1262
|
+
mode: "script",
|
|
1263
|
+
script: "console.log('hi')",
|
|
1264
|
+
inferenceProfile: "cost-optimized",
|
|
1265
|
+
});
|
|
1266
|
+
expect(result.schedule.inferenceProfile).toBe("cost-optimized");
|
|
1267
|
+
expect(listSchedules()[0].inferenceProfile).toBe("cost-optimized");
|
|
1268
|
+
});
|
|
1269
|
+
|
|
1270
|
+
test("pins inferenceProfile to the resolved default when omitted", async () => {
|
|
1271
|
+
const expected = resolveDefaultScheduleInferenceProfile();
|
|
1272
|
+
// A schedule that followed the global default would change model whenever
|
|
1273
|
+
// the user changed that default, so creation snapshots it instead.
|
|
1274
|
+
expect(expected).not.toBeNull();
|
|
1275
|
+
|
|
1234
1276
|
const result = await postCreate({
|
|
1235
1277
|
name: "Default profile",
|
|
1236
|
-
description: "Runs on the
|
|
1278
|
+
description: "Runs on the resolved default profile",
|
|
1237
1279
|
expression: "0 9 * * *",
|
|
1238
1280
|
message: "hi",
|
|
1239
1281
|
});
|
|
1240
|
-
expect(result.schedule.inferenceProfile).
|
|
1282
|
+
expect(result.schedule.inferenceProfile).toBe(expected!);
|
|
1283
|
+
expect(listSchedules()[0].inferenceProfile).toBe(expected!);
|
|
1241
1284
|
});
|
|
1242
1285
|
|
|
1243
1286
|
test("rejects an unknown inferenceProfile", async () => {
|
|
@@ -1296,7 +1339,10 @@ describe("PATCH /schedules/:id — description", () => {
|
|
|
1296
1339
|
expect(listSchedules()[0].description).toBe("Updated description");
|
|
1297
1340
|
});
|
|
1298
1341
|
|
|
1299
|
-
test("sets, validates, and
|
|
1342
|
+
test("sets, validates, and re-snapshots inferenceProfile", async () => {
|
|
1343
|
+
const resolvedDefault = resolveDefaultScheduleInferenceProfile();
|
|
1344
|
+
expect(resolvedDefault).not.toBe("cost-optimized");
|
|
1345
|
+
|
|
1300
1346
|
const schedule = await createSchedule({
|
|
1301
1347
|
name: "Profile pin",
|
|
1302
1348
|
description: "Pinned profile schedule",
|
|
@@ -1308,9 +1354,9 @@ describe("PATCH /schedules/:id — description", () => {
|
|
|
1308
1354
|
|
|
1309
1355
|
await route.handler({
|
|
1310
1356
|
pathParams: { id: schedule.id },
|
|
1311
|
-
body: { inferenceProfile: "
|
|
1357
|
+
body: { inferenceProfile: "cost-optimized" },
|
|
1312
1358
|
});
|
|
1313
|
-
expect(listSchedules()[0].inferenceProfile).toBe("
|
|
1359
|
+
expect(listSchedules()[0].inferenceProfile).toBe("cost-optimized");
|
|
1314
1360
|
|
|
1315
1361
|
await expect(
|
|
1316
1362
|
route.handler({
|
|
@@ -1318,13 +1364,14 @@ describe("PATCH /schedules/:id — description", () => {
|
|
|
1318
1364
|
body: { inferenceProfile: "does-not-exist" },
|
|
1319
1365
|
}),
|
|
1320
1366
|
).rejects.toThrow('Inference profile "does-not-exist" is not defined');
|
|
1321
|
-
expect(listSchedules()[0].inferenceProfile).toBe("
|
|
1367
|
+
expect(listSchedules()[0].inferenceProfile).toBe("cost-optimized");
|
|
1322
1368
|
|
|
1369
|
+
// Null is a reset to the current default, not an unpin.
|
|
1323
1370
|
await route.handler({
|
|
1324
1371
|
pathParams: { id: schedule.id },
|
|
1325
1372
|
body: { inferenceProfile: null },
|
|
1326
1373
|
});
|
|
1327
|
-
expect(listSchedules()[0].inferenceProfile).
|
|
1374
|
+
expect(listSchedules()[0].inferenceProfile).toBe(resolvedDefault);
|
|
1328
1375
|
});
|
|
1329
1376
|
|
|
1330
1377
|
test("re-derives syntax when the expression switches cron to rrule", async () => {
|
|
@@ -1367,6 +1414,242 @@ describe("PATCH /schedules/:id — description", () => {
|
|
|
1367
1414
|
});
|
|
1368
1415
|
});
|
|
1369
1416
|
|
|
1417
|
+
// ── Plugin-sourced schedules ──────────────────────────────────────────────
|
|
1418
|
+
|
|
1419
|
+
describe("plugin-sourced schedules over routes", () => {
|
|
1420
|
+
const SOURCE_KEY = "plugin:example-plugin/daily-digest";
|
|
1421
|
+
|
|
1422
|
+
// The store's enable path probes the declaration's on-disk presence and its
|
|
1423
|
+
// plugin's manifest before re-arming, so the suite keeps a declaration
|
|
1424
|
+
// directory and a valid package.json in place.
|
|
1425
|
+
beforeEach(() => {
|
|
1426
|
+
clearTables();
|
|
1427
|
+
const pluginDir = join(getWorkspacePluginsDir(), "example-plugin");
|
|
1428
|
+
rmSync(pluginDir, { recursive: true, force: true });
|
|
1429
|
+
const declarationDir = join(pluginDir, "schedules", "daily-digest");
|
|
1430
|
+
mkdirSync(declarationDir, { recursive: true });
|
|
1431
|
+
writeFileSync(
|
|
1432
|
+
join(declarationDir, "config.json"),
|
|
1433
|
+
JSON.stringify({ expression: "0 9 * * *" }),
|
|
1434
|
+
);
|
|
1435
|
+
writeFileSync(join(declarationDir, "index.md"), "produce the digest\n");
|
|
1436
|
+
writeFileSync(
|
|
1437
|
+
join(pluginDir, "package.json"),
|
|
1438
|
+
JSON.stringify({ name: "example-plugin", version: "1.0.0" }),
|
|
1439
|
+
);
|
|
1440
|
+
// The feature ships off, so the cases below run under an explicit flag.
|
|
1441
|
+
setOverridesForTesting({ "plugin-schedules": true });
|
|
1442
|
+
});
|
|
1443
|
+
|
|
1444
|
+
function seedSourcedSchedule(
|
|
1445
|
+
overrides: Partial<DeclaredScheduleDefinition> = {},
|
|
1446
|
+
) {
|
|
1447
|
+
return upsertDeclaredSchedule(SOURCE_KEY, {
|
|
1448
|
+
name: "Daily digest",
|
|
1449
|
+
description: "Summarize the day",
|
|
1450
|
+
syntax: "cron",
|
|
1451
|
+
expression: "0 9 * * *",
|
|
1452
|
+
message: "produce the digest",
|
|
1453
|
+
mode: "execute",
|
|
1454
|
+
enabled: true,
|
|
1455
|
+
definitionHash: "hash-1",
|
|
1456
|
+
...overrides,
|
|
1457
|
+
});
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
type SerializedEntry = {
|
|
1461
|
+
id: string;
|
|
1462
|
+
enabled: boolean;
|
|
1463
|
+
sourceKey: string | null;
|
|
1464
|
+
userEnabled: boolean | null;
|
|
1465
|
+
};
|
|
1466
|
+
|
|
1467
|
+
test("list serializes sourceKey and userEnabled for sourced and imperative rows", async () => {
|
|
1468
|
+
const sourced = await seedSourcedSchedule();
|
|
1469
|
+
const imperative = await createSchedule({
|
|
1470
|
+
name: "Imperative schedule",
|
|
1471
|
+
cronExpression: "* * * * *",
|
|
1472
|
+
message: "hi",
|
|
1473
|
+
syntax: "cron",
|
|
1474
|
+
});
|
|
1475
|
+
|
|
1476
|
+
const route = findRoute("schedules", "GET");
|
|
1477
|
+
const result = (await route.handler({})) as {
|
|
1478
|
+
schedules: SerializedEntry[];
|
|
1479
|
+
};
|
|
1480
|
+
const byId = new Map(result.schedules.map((s) => [s.id, s]));
|
|
1481
|
+
|
|
1482
|
+
expect(byId.get(sourced.id)).toMatchObject({
|
|
1483
|
+
sourceKey: SOURCE_KEY,
|
|
1484
|
+
userEnabled: null,
|
|
1485
|
+
});
|
|
1486
|
+
expect(byId.get(imperative.id)).toMatchObject({
|
|
1487
|
+
sourceKey: null,
|
|
1488
|
+
userEnabled: null,
|
|
1489
|
+
});
|
|
1490
|
+
});
|
|
1491
|
+
|
|
1492
|
+
test("toggle on a sourced row records and round-trips the user override", async () => {
|
|
1493
|
+
const sourced = await seedSourcedSchedule();
|
|
1494
|
+
const route = findRoute("schedules/:id/toggle", "POST");
|
|
1495
|
+
|
|
1496
|
+
const disabled = (await route.handler({
|
|
1497
|
+
pathParams: { id: sourced.id },
|
|
1498
|
+
body: { enabled: false },
|
|
1499
|
+
})) as { schedules: SerializedEntry[] };
|
|
1500
|
+
expect(disabled.schedules.find((s) => s.id === sourced.id)).toMatchObject({
|
|
1501
|
+
enabled: false,
|
|
1502
|
+
userEnabled: false,
|
|
1503
|
+
});
|
|
1504
|
+
|
|
1505
|
+
const enabled = (await route.handler({
|
|
1506
|
+
pathParams: { id: sourced.id },
|
|
1507
|
+
body: { enabled: true },
|
|
1508
|
+
})) as { schedules: SerializedEntry[] };
|
|
1509
|
+
expect(enabled.schedules.find((s) => s.id === sourced.id)).toMatchObject({
|
|
1510
|
+
enabled: true,
|
|
1511
|
+
userEnabled: true,
|
|
1512
|
+
});
|
|
1513
|
+
});
|
|
1514
|
+
|
|
1515
|
+
test("toggle on an imperative row keeps userEnabled null", async () => {
|
|
1516
|
+
const imperative = await createSchedule({
|
|
1517
|
+
name: "Imperative toggle",
|
|
1518
|
+
cronExpression: "* * * * *",
|
|
1519
|
+
message: "hi",
|
|
1520
|
+
syntax: "cron",
|
|
1521
|
+
});
|
|
1522
|
+
|
|
1523
|
+
const route = findRoute("schedules/:id/toggle", "POST");
|
|
1524
|
+
const result = (await route.handler({
|
|
1525
|
+
pathParams: { id: imperative.id },
|
|
1526
|
+
body: { enabled: false },
|
|
1527
|
+
})) as { schedules: SerializedEntry[] };
|
|
1528
|
+
|
|
1529
|
+
expect(result.schedules.find((s) => s.id === imperative.id)).toMatchObject({
|
|
1530
|
+
enabled: false,
|
|
1531
|
+
userEnabled: null,
|
|
1532
|
+
});
|
|
1533
|
+
});
|
|
1534
|
+
|
|
1535
|
+
test("PATCH on a sourced row surfaces the store refusal as a 400", async () => {
|
|
1536
|
+
const sourced = await seedSourcedSchedule();
|
|
1537
|
+
const route = findRoute("schedules/:id", "PATCH");
|
|
1538
|
+
const patch = () =>
|
|
1539
|
+
route.handler({
|
|
1540
|
+
pathParams: { id: sourced.id },
|
|
1541
|
+
body: { name: "Renamed" },
|
|
1542
|
+
});
|
|
1543
|
+
|
|
1544
|
+
await expect(patch()).rejects.toThrow(BadRequestError);
|
|
1545
|
+
await expect(patch()).rejects.toThrow(
|
|
1546
|
+
"This schedule is managed by a plugin. Edit the plugin's schedule file instead.",
|
|
1547
|
+
);
|
|
1548
|
+
});
|
|
1549
|
+
|
|
1550
|
+
test("DELETE on a sourced row surfaces the store refusal as a 400", async () => {
|
|
1551
|
+
const sourced = await seedSourcedSchedule();
|
|
1552
|
+
const route = findRoute("schedules/:id", "DELETE");
|
|
1553
|
+
const remove = () => route.handler({ pathParams: { id: sourced.id } });
|
|
1554
|
+
|
|
1555
|
+
await expect(remove()).rejects.toThrow(BadRequestError);
|
|
1556
|
+
await expect(remove()).rejects.toThrow(
|
|
1557
|
+
"This schedule is managed by a plugin and cannot be deleted. Disable it instead, or remove the plugin's schedule file.",
|
|
1558
|
+
);
|
|
1559
|
+
});
|
|
1560
|
+
|
|
1561
|
+
test("cancel on a sourced row surfaces the store refusal as a 400", async () => {
|
|
1562
|
+
const sourced = await seedSourcedSchedule();
|
|
1563
|
+
const route = findRoute("schedules/:id/cancel", "POST");
|
|
1564
|
+
const cancel = () => route.handler({ pathParams: { id: sourced.id } });
|
|
1565
|
+
|
|
1566
|
+
await expect(cancel()).rejects.toThrow(BadRequestError);
|
|
1567
|
+
await expect(cancel()).rejects.toThrow(
|
|
1568
|
+
"This schedule is managed by a plugin and cannot be cancelled. Disable it instead via the enabled toggle.",
|
|
1569
|
+
);
|
|
1570
|
+
});
|
|
1571
|
+
|
|
1572
|
+
describe("run now", () => {
|
|
1573
|
+
const RUN_MARKER = join(getWorkspacePluginsDir(), "run-now-marker.txt");
|
|
1574
|
+
|
|
1575
|
+
beforeEach(() => {
|
|
1576
|
+
rmSync(RUN_MARKER, { force: true });
|
|
1577
|
+
});
|
|
1578
|
+
|
|
1579
|
+
function seedSourcedScript() {
|
|
1580
|
+
return seedSourcedSchedule({
|
|
1581
|
+
mode: "script",
|
|
1582
|
+
script: `touch ${RUN_MARKER}`,
|
|
1583
|
+
message: "",
|
|
1584
|
+
});
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
async function runNow(id: string) {
|
|
1588
|
+
return findRoute("schedules/:id/run", "POST").handler({
|
|
1589
|
+
pathParams: { id },
|
|
1590
|
+
});
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
test("refuses a sourced row whose plugin is disabled", async () => {
|
|
1594
|
+
const sourced = await seedSourcedScript();
|
|
1595
|
+
writeFileSync(
|
|
1596
|
+
join(getWorkspacePluginsDir(), "example-plugin", ".disabled"),
|
|
1597
|
+
"",
|
|
1598
|
+
);
|
|
1599
|
+
|
|
1600
|
+
await expect(runNow(sourced.id)).rejects.toThrow(BadRequestError);
|
|
1601
|
+
// Refused before execution: the plugin's script never ran.
|
|
1602
|
+
expect(existsSync(RUN_MARKER)).toBe(false);
|
|
1603
|
+
});
|
|
1604
|
+
|
|
1605
|
+
test("refuses a sourced row while the feature flag is off", async () => {
|
|
1606
|
+
const sourced = await seedSourcedScript();
|
|
1607
|
+
setOverridesForTesting({ "plugin-schedules": false });
|
|
1608
|
+
|
|
1609
|
+
await expect(runNow(sourced.id)).rejects.toThrow(BadRequestError);
|
|
1610
|
+
expect(existsSync(RUN_MARKER)).toBe(false);
|
|
1611
|
+
});
|
|
1612
|
+
|
|
1613
|
+
test("refuses a sourced row whose declaration is gone", async () => {
|
|
1614
|
+
const sourced = await seedSourcedScript();
|
|
1615
|
+
rmSync(
|
|
1616
|
+
join(
|
|
1617
|
+
getWorkspacePluginsDir(),
|
|
1618
|
+
"example-plugin",
|
|
1619
|
+
"schedules",
|
|
1620
|
+
"daily-digest",
|
|
1621
|
+
),
|
|
1622
|
+
{ recursive: true, force: true },
|
|
1623
|
+
);
|
|
1624
|
+
|
|
1625
|
+
await expect(runNow(sourced.id)).rejects.toThrow(BadRequestError);
|
|
1626
|
+
expect(existsSync(RUN_MARKER)).toBe(false);
|
|
1627
|
+
});
|
|
1628
|
+
|
|
1629
|
+
test("runs a sourced row whose plugin is healthy", async () => {
|
|
1630
|
+
const sourced = await seedSourcedScript();
|
|
1631
|
+
|
|
1632
|
+
await runNow(sourced.id);
|
|
1633
|
+
|
|
1634
|
+
expect(existsSync(RUN_MARKER)).toBe(true);
|
|
1635
|
+
});
|
|
1636
|
+
|
|
1637
|
+
test("runs an imperative row", async () => {
|
|
1638
|
+
const imperative = await createSchedule({
|
|
1639
|
+
name: "Imperative script",
|
|
1640
|
+
cronExpression: "* * * * *",
|
|
1641
|
+
message: "",
|
|
1642
|
+
mode: "script",
|
|
1643
|
+
script: `touch ${RUN_MARKER}`,
|
|
1644
|
+
});
|
|
1645
|
+
|
|
1646
|
+
await runNow(imperative.id);
|
|
1647
|
+
|
|
1648
|
+
expect(existsSync(RUN_MARKER)).toBe(true);
|
|
1649
|
+
});
|
|
1650
|
+
});
|
|
1651
|
+
});
|
|
1652
|
+
|
|
1370
1653
|
// ── Wake mode support ─────────────────────────────────────────────────────
|
|
1371
1654
|
|
|
1372
1655
|
describe("wake mode in schedule routes", () => {
|
|
@@ -1492,3 +1775,442 @@ describe("PATCH /schedules/:id — timeout override", () => {
|
|
|
1492
1775
|
expect(listOne().timeoutMs).toBeNull();
|
|
1493
1776
|
});
|
|
1494
1777
|
});
|
|
1778
|
+
|
|
1779
|
+
// ── GET /schedules with the inference_profile filter ────────────────────────────
|
|
1780
|
+
|
|
1781
|
+
describe("GET /schedules - inference_profile filter", () => {
|
|
1782
|
+
beforeEach(() => {
|
|
1783
|
+
clearTables();
|
|
1784
|
+
});
|
|
1785
|
+
|
|
1786
|
+
test("narrows the list to schedules pinned to that profile", async () => {
|
|
1787
|
+
const resolvedDefault = resolveDefaultScheduleInferenceProfile();
|
|
1788
|
+
expect(resolvedDefault).not.toBe("cost-optimized");
|
|
1789
|
+
|
|
1790
|
+
await createSchedule({
|
|
1791
|
+
name: "Cheap digest",
|
|
1792
|
+
description: "Cheap digest",
|
|
1793
|
+
cronExpression: "0 * * * *",
|
|
1794
|
+
message: "digest",
|
|
1795
|
+
syntax: "cron",
|
|
1796
|
+
inferenceProfile: "cost-optimized",
|
|
1797
|
+
});
|
|
1798
|
+
await createSchedule({
|
|
1799
|
+
name: "Default schedule",
|
|
1800
|
+
description: "Default schedule",
|
|
1801
|
+
cronExpression: "0 9 * * *",
|
|
1802
|
+
message: "hi",
|
|
1803
|
+
syntax: "cron",
|
|
1804
|
+
});
|
|
1805
|
+
|
|
1806
|
+
const route = findRoute("schedules", "GET");
|
|
1807
|
+
const filtered = (await route.handler({
|
|
1808
|
+
queryParams: { inference_profile: "cost-optimized" },
|
|
1809
|
+
})) as { schedules: Array<{ name: string; inferenceProfile: string }> };
|
|
1810
|
+
expect(filtered.schedules.map((s) => s.name)).toEqual(["Cheap digest"]);
|
|
1811
|
+
|
|
1812
|
+
const unfiltered = (await route.handler({})) as {
|
|
1813
|
+
schedules: Array<{ name: string }>;
|
|
1814
|
+
};
|
|
1815
|
+
expect(unfiltered.schedules).toHaveLength(2);
|
|
1816
|
+
});
|
|
1817
|
+
|
|
1818
|
+
test("flags deferred rows so a caller can count them without naming them", async () => {
|
|
1819
|
+
await createSchedule({
|
|
1820
|
+
name: "Cheap digest",
|
|
1821
|
+
description: "Cheap digest",
|
|
1822
|
+
cronExpression: "0 * * * *",
|
|
1823
|
+
message: "digest",
|
|
1824
|
+
syntax: "cron",
|
|
1825
|
+
inferenceProfile: "cost-optimized",
|
|
1826
|
+
});
|
|
1827
|
+
await createSchedule({
|
|
1828
|
+
name: "Deferred wake",
|
|
1829
|
+
description: "Deferred wake",
|
|
1830
|
+
message: "wake up",
|
|
1831
|
+
nextRunAt: Date.now() + 60_000,
|
|
1832
|
+
mode: "wake",
|
|
1833
|
+
wakeConversationId: "conv-abc",
|
|
1834
|
+
createdBy: "defer",
|
|
1835
|
+
inferenceProfile: "cost-optimized",
|
|
1836
|
+
});
|
|
1837
|
+
|
|
1838
|
+
const route = findRoute("schedules", "GET");
|
|
1839
|
+
const result = (await route.handler({
|
|
1840
|
+
queryParams: { inference_profile: "cost-optimized", include_all: "true" },
|
|
1841
|
+
})) as { schedules: Array<{ name: string; isDeferred: boolean }> };
|
|
1842
|
+
|
|
1843
|
+
// The count a delete warning shows must match what the reassign moves, so
|
|
1844
|
+
// the deferred row has to be visible AND separable from the named ones.
|
|
1845
|
+
expect(result.schedules).toHaveLength(2);
|
|
1846
|
+
expect(
|
|
1847
|
+
result.schedules.filter((s) => !s.isDeferred).map((s) => s.name),
|
|
1848
|
+
).toEqual(["Cheap digest"]);
|
|
1849
|
+
expect(result.schedules.filter((s) => s.isDeferred)).toHaveLength(1);
|
|
1850
|
+
|
|
1851
|
+
const reassign = findRoute("schedules/reassign-profile", "POST");
|
|
1852
|
+
const moved = (await reassign.handler({
|
|
1853
|
+
body: {
|
|
1854
|
+
from: "cost-optimized",
|
|
1855
|
+
to: resolveDefaultScheduleInferenceProfile()!,
|
|
1856
|
+
},
|
|
1857
|
+
headers: { "x-vellum-principal-type": "local" },
|
|
1858
|
+
})) as { reassigned: number };
|
|
1859
|
+
expect(moved.reassigned).toBe(result.schedules.length);
|
|
1860
|
+
});
|
|
1861
|
+
|
|
1862
|
+
test("returns nothing for a profile no schedule is pinned to", async () => {
|
|
1863
|
+
await createSchedule({
|
|
1864
|
+
name: "Default schedule",
|
|
1865
|
+
description: "Default schedule",
|
|
1866
|
+
cronExpression: "0 9 * * *",
|
|
1867
|
+
message: "hi",
|
|
1868
|
+
syntax: "cron",
|
|
1869
|
+
});
|
|
1870
|
+
|
|
1871
|
+
const route = findRoute("schedules", "GET");
|
|
1872
|
+
const result = (await route.handler({
|
|
1873
|
+
queryParams: { inference_profile: "cost-optimized" },
|
|
1874
|
+
})) as { schedules: unknown[] };
|
|
1875
|
+
expect(result.schedules).toEqual([]);
|
|
1876
|
+
});
|
|
1877
|
+
});
|
|
1878
|
+
|
|
1879
|
+
// ── POST /schedules/reassign-profile ─────────────────────────────────────
|
|
1880
|
+
|
|
1881
|
+
describe("POST /schedules/reassign-profile", () => {
|
|
1882
|
+
beforeEach(() => {
|
|
1883
|
+
clearTables();
|
|
1884
|
+
});
|
|
1885
|
+
|
|
1886
|
+
function reassignRoute(): RouteDefinition {
|
|
1887
|
+
return findRoute("schedules/reassign-profile", "POST");
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
test("moves every schedule pinned to the source profile and leaves the rest alone", async () => {
|
|
1891
|
+
const resolvedDefault = resolveDefaultScheduleInferenceProfile();
|
|
1892
|
+
expect(resolvedDefault).not.toBe("cost-optimized");
|
|
1893
|
+
|
|
1894
|
+
await createSchedule({
|
|
1895
|
+
name: "Cheap digest",
|
|
1896
|
+
description: "Cheap digest",
|
|
1897
|
+
cronExpression: "0 * * * *",
|
|
1898
|
+
message: "digest",
|
|
1899
|
+
syntax: "cron",
|
|
1900
|
+
inferenceProfile: "cost-optimized",
|
|
1901
|
+
});
|
|
1902
|
+
await createSchedule({
|
|
1903
|
+
name: "Cheap sweep",
|
|
1904
|
+
description: "Cheap sweep",
|
|
1905
|
+
cronExpression: "30 * * * *",
|
|
1906
|
+
message: "sweep",
|
|
1907
|
+
syntax: "cron",
|
|
1908
|
+
inferenceProfile: "cost-optimized",
|
|
1909
|
+
});
|
|
1910
|
+
await createSchedule({
|
|
1911
|
+
name: "Untouched",
|
|
1912
|
+
description: "Untouched",
|
|
1913
|
+
cronExpression: "0 9 * * *",
|
|
1914
|
+
message: "hi",
|
|
1915
|
+
syntax: "cron",
|
|
1916
|
+
});
|
|
1917
|
+
|
|
1918
|
+
const result = (await reassignRoute().handler({
|
|
1919
|
+
body: { from: "cost-optimized", to: resolvedDefault! },
|
|
1920
|
+
})) as { reassigned: number };
|
|
1921
|
+
|
|
1922
|
+
expect(result.reassigned).toBe(2);
|
|
1923
|
+
expect(listSchedules({ inferenceProfile: "cost-optimized" })).toEqual([]);
|
|
1924
|
+
expect(
|
|
1925
|
+
listSchedules()
|
|
1926
|
+
.map((j) => j.inferenceProfile)
|
|
1927
|
+
.every((p) => p === resolvedDefault),
|
|
1928
|
+
).toBe(true);
|
|
1929
|
+
});
|
|
1930
|
+
|
|
1931
|
+
test("moves deferred wake rows too when the caller holds owner authority", async () => {
|
|
1932
|
+
const resolvedDefault = resolveDefaultScheduleInferenceProfile();
|
|
1933
|
+
|
|
1934
|
+
await createSchedule({
|
|
1935
|
+
name: "Deferred wake",
|
|
1936
|
+
description: "Deferred wake",
|
|
1937
|
+
message: "wake up",
|
|
1938
|
+
nextRunAt: Date.now() + 60_000,
|
|
1939
|
+
mode: "wake",
|
|
1940
|
+
wakeConversationId: "conv-abc",
|
|
1941
|
+
createdBy: "defer",
|
|
1942
|
+
inferenceProfile: "cost-optimized",
|
|
1943
|
+
});
|
|
1944
|
+
|
|
1945
|
+
const result = (await reassignRoute().handler({
|
|
1946
|
+
body: { from: "cost-optimized", to: resolvedDefault! },
|
|
1947
|
+
headers: { "x-vellum-principal-type": "local" },
|
|
1948
|
+
})) as { reassigned: number };
|
|
1949
|
+
|
|
1950
|
+
expect(result.reassigned).toBe(1);
|
|
1951
|
+
expect(listSchedules()[0].inferenceProfile).toBe(resolvedDefault!);
|
|
1952
|
+
});
|
|
1953
|
+
|
|
1954
|
+
test("refuses the whole move when a wake row is in scope and the caller is not the owner", async () => {
|
|
1955
|
+
const resolvedDefault = resolveDefaultScheduleInferenceProfile();
|
|
1956
|
+
await createSchedule({
|
|
1957
|
+
name: "Cheap digest",
|
|
1958
|
+
description: "Cheap digest",
|
|
1959
|
+
cronExpression: "0 * * * *",
|
|
1960
|
+
message: "digest",
|
|
1961
|
+
syntax: "cron",
|
|
1962
|
+
inferenceProfile: "cost-optimized",
|
|
1963
|
+
});
|
|
1964
|
+
await createSchedule({
|
|
1965
|
+
name: "Deferred wake",
|
|
1966
|
+
description: "Deferred wake",
|
|
1967
|
+
message: "wake up",
|
|
1968
|
+
nextRunAt: Date.now() + 60_000,
|
|
1969
|
+
mode: "wake",
|
|
1970
|
+
wakeConversationId: "conv-abc",
|
|
1971
|
+
createdBy: "defer",
|
|
1972
|
+
inferenceProfile: "cost-optimized",
|
|
1973
|
+
});
|
|
1974
|
+
|
|
1975
|
+
await expect(
|
|
1976
|
+
reassignRoute().handler({
|
|
1977
|
+
body: { from: "cost-optimized", to: resolvedDefault! },
|
|
1978
|
+
headers: { "x-vellum-principal-type": "actor" },
|
|
1979
|
+
}),
|
|
1980
|
+
).rejects.toThrow(
|
|
1981
|
+
"Deferred wake schedules can only be changed by the assistant's owner",
|
|
1982
|
+
);
|
|
1983
|
+
// All-or-nothing: the ordinary schedule sharing the profile does not move
|
|
1984
|
+
// either.
|
|
1985
|
+
expect(
|
|
1986
|
+
listSchedules().every((j) => j.inferenceProfile === "cost-optimized"),
|
|
1987
|
+
).toBe(true);
|
|
1988
|
+
});
|
|
1989
|
+
|
|
1990
|
+
test("rejects an unknown target profile without touching any schedule", async () => {
|
|
1991
|
+
await createSchedule({
|
|
1992
|
+
name: "Cheap digest",
|
|
1993
|
+
description: "Cheap digest",
|
|
1994
|
+
cronExpression: "0 * * * *",
|
|
1995
|
+
message: "digest",
|
|
1996
|
+
syntax: "cron",
|
|
1997
|
+
inferenceProfile: "cost-optimized",
|
|
1998
|
+
});
|
|
1999
|
+
|
|
2000
|
+
await expect(
|
|
2001
|
+
reassignRoute().handler({
|
|
2002
|
+
body: { from: "cost-optimized", to: "does-not-exist" },
|
|
2003
|
+
}),
|
|
2004
|
+
).rejects.toThrow('Inference profile "does-not-exist" is not defined');
|
|
2005
|
+
expect(listSchedules()[0].inferenceProfile).toBe("cost-optimized");
|
|
2006
|
+
});
|
|
2007
|
+
|
|
2008
|
+
test("requires to, and rejects an empty from", async () => {
|
|
2009
|
+
await expect(
|
|
2010
|
+
reassignRoute().handler({ body: { from: "cost-optimized" } }),
|
|
2011
|
+
).rejects.toThrow("to is required");
|
|
2012
|
+
await expect(
|
|
2013
|
+
reassignRoute().handler({ body: { from: " ", to: "balanced" } }),
|
|
2014
|
+
).rejects.toThrow("from must name an inference profile when provided");
|
|
2015
|
+
});
|
|
2016
|
+
|
|
2017
|
+
test("is a no-op when nothing is pinned to the source profile", async () => {
|
|
2018
|
+
const resolvedDefault = resolveDefaultScheduleInferenceProfile();
|
|
2019
|
+
const schedule = await createSchedule({
|
|
2020
|
+
name: "Default schedule",
|
|
2021
|
+
description: "Default schedule",
|
|
2022
|
+
cronExpression: "0 9 * * *",
|
|
2023
|
+
message: "hi",
|
|
2024
|
+
syntax: "cron",
|
|
2025
|
+
});
|
|
2026
|
+
const updatedAtBefore = listSchedules()[0].updatedAt;
|
|
2027
|
+
|
|
2028
|
+
const result = (await reassignRoute().handler({
|
|
2029
|
+
body: { from: "cost-optimized", to: resolvedDefault! },
|
|
2030
|
+
})) as { reassigned: number };
|
|
2031
|
+
|
|
2032
|
+
expect(result.reassigned).toBe(0);
|
|
2033
|
+
expect(listSchedules()[0].id).toBe(schedule.id);
|
|
2034
|
+
expect(listSchedules()[0].updatedAt).toBe(updatedAtBefore);
|
|
2035
|
+
});
|
|
2036
|
+
|
|
2037
|
+
test("sweeps up pins left behind by an already-deleted profile", async () => {
|
|
2038
|
+
const resolvedDefault = resolveDefaultScheduleInferenceProfile();
|
|
2039
|
+
const schedule = await createSchedule({
|
|
2040
|
+
name: "Dangling pin",
|
|
2041
|
+
description: "Dangling pin",
|
|
2042
|
+
cronExpression: "0 9 * * *",
|
|
2043
|
+
message: "hi",
|
|
2044
|
+
syntax: "cron",
|
|
2045
|
+
});
|
|
2046
|
+
// A profile deleted out from under its schedules leaves rows naming a key
|
|
2047
|
+
// that no longer resolves; the sweep must still be able to move them.
|
|
2048
|
+
rawRun(
|
|
2049
|
+
"test:setDanglingInferenceProfile",
|
|
2050
|
+
"UPDATE cron_jobs SET inference_profile = ? WHERE id = ?",
|
|
2051
|
+
"deleted-profile",
|
|
2052
|
+
schedule.id,
|
|
2053
|
+
);
|
|
2054
|
+
|
|
2055
|
+
const result = (await reassignRoute().handler({
|
|
2056
|
+
body: { from: "deleted-profile", to: resolvedDefault! },
|
|
2057
|
+
})) as { reassigned: number };
|
|
2058
|
+
|
|
2059
|
+
expect(result.reassigned).toBe(1);
|
|
2060
|
+
expect(listSchedules()[0].inferenceProfile).toBe(resolvedDefault!);
|
|
2061
|
+
});
|
|
2062
|
+
|
|
2063
|
+
test("omitting from moves every schedule and skips the ones already there", async () => {
|
|
2064
|
+
const resolvedDefault = resolveDefaultScheduleInferenceProfile();
|
|
2065
|
+
expect(resolvedDefault).not.toBe("cost-optimized");
|
|
2066
|
+
|
|
2067
|
+
await createSchedule({
|
|
2068
|
+
name: "Cheap digest",
|
|
2069
|
+
description: "Cheap digest",
|
|
2070
|
+
cronExpression: "0 * * * *",
|
|
2071
|
+
message: "digest",
|
|
2072
|
+
syntax: "cron",
|
|
2073
|
+
inferenceProfile: "cost-optimized",
|
|
2074
|
+
});
|
|
2075
|
+
const dangling = await createSchedule({
|
|
2076
|
+
name: "Dangling pin",
|
|
2077
|
+
description: "Dangling pin",
|
|
2078
|
+
cronExpression: "15 * * * *",
|
|
2079
|
+
message: "hi",
|
|
2080
|
+
syntax: "cron",
|
|
2081
|
+
});
|
|
2082
|
+
rawRun(
|
|
2083
|
+
"test:setDanglingInferenceProfile",
|
|
2084
|
+
"UPDATE cron_jobs SET inference_profile = ? WHERE id = ?",
|
|
2085
|
+
"deleted-profile",
|
|
2086
|
+
dangling.id,
|
|
2087
|
+
);
|
|
2088
|
+
// Already on the target: counted as untouched, and its updatedAt proves it.
|
|
2089
|
+
await createSchedule({
|
|
2090
|
+
name: "Already default",
|
|
2091
|
+
description: "Already default",
|
|
2092
|
+
cronExpression: "30 * * * *",
|
|
2093
|
+
message: "hi",
|
|
2094
|
+
syntax: "cron",
|
|
2095
|
+
inferenceProfile: resolvedDefault!,
|
|
2096
|
+
});
|
|
2097
|
+
const untouchedBefore = listSchedules().find(
|
|
2098
|
+
(job) => job.name === "Already default",
|
|
2099
|
+
)!.updatedAt;
|
|
2100
|
+
|
|
2101
|
+
const result = (await reassignRoute().handler({
|
|
2102
|
+
body: { to: resolvedDefault! },
|
|
2103
|
+
})) as { reassigned: number };
|
|
2104
|
+
|
|
2105
|
+
// Two moved (the cheap one and the dangling one); the third was already
|
|
2106
|
+
// on the target, so it is neither counted nor rewritten.
|
|
2107
|
+
expect(result.reassigned).toBe(2);
|
|
2108
|
+
expect(
|
|
2109
|
+
listSchedules().every((job) => job.inferenceProfile === resolvedDefault),
|
|
2110
|
+
).toBe(true);
|
|
2111
|
+
expect(
|
|
2112
|
+
listSchedules().find((job) => job.name === "Already default")!.updatedAt,
|
|
2113
|
+
).toBe(untouchedBefore);
|
|
2114
|
+
});
|
|
2115
|
+
|
|
2116
|
+
test("omitting from leaves fired and cancelled one-shots alone", async () => {
|
|
2117
|
+
const resolvedDefault = resolveDefaultScheduleInferenceProfile();
|
|
2118
|
+
expect(resolvedDefault).not.toBe("cost-optimized");
|
|
2119
|
+
|
|
2120
|
+
const fired = await createSchedule({
|
|
2121
|
+
name: "Already fired",
|
|
2122
|
+
description: "Already fired",
|
|
2123
|
+
message: "hi",
|
|
2124
|
+
nextRunAt: Date.now() - 60_000,
|
|
2125
|
+
inferenceProfile: "cost-optimized",
|
|
2126
|
+
});
|
|
2127
|
+
const cancelled = await createSchedule({
|
|
2128
|
+
name: "Cancelled",
|
|
2129
|
+
description: "Cancelled",
|
|
2130
|
+
message: "hi",
|
|
2131
|
+
nextRunAt: Date.now() + 60_000,
|
|
2132
|
+
inferenceProfile: "cost-optimized",
|
|
2133
|
+
});
|
|
2134
|
+
rawRun(
|
|
2135
|
+
"test:setTerminalScheduleStatuses",
|
|
2136
|
+
"UPDATE cron_jobs SET status = CASE id WHEN ? THEN 'fired' ELSE 'cancelled' END WHERE id IN (?, ?)",
|
|
2137
|
+
fired.id,
|
|
2138
|
+
fired.id,
|
|
2139
|
+
cancelled.id,
|
|
2140
|
+
);
|
|
2141
|
+
const live = await createSchedule({
|
|
2142
|
+
name: "Still running",
|
|
2143
|
+
description: "Still running",
|
|
2144
|
+
cronExpression: "0 * * * *",
|
|
2145
|
+
message: "digest",
|
|
2146
|
+
syntax: "cron",
|
|
2147
|
+
inferenceProfile: "cost-optimized",
|
|
2148
|
+
});
|
|
2149
|
+
const byId = new Map(listSchedules().map((job) => [job.id, job]));
|
|
2150
|
+
const firedBefore = byId.get(fired.id)!.updatedAt;
|
|
2151
|
+
const cancelledBefore = byId.get(cancelled.id)!.updatedAt;
|
|
2152
|
+
|
|
2153
|
+
const result = (await reassignRoute().handler({
|
|
2154
|
+
body: { to: resolvedDefault! },
|
|
2155
|
+
})) as { reassigned: number };
|
|
2156
|
+
|
|
2157
|
+
// Their profile is history, so counting them would report a bigger move
|
|
2158
|
+
// than the user was shown.
|
|
2159
|
+
expect(result.reassigned).toBe(1);
|
|
2160
|
+
const after = new Map(listSchedules().map((job) => [job.id, job]));
|
|
2161
|
+
expect(after.get(live.id)!.inferenceProfile).toBe(resolvedDefault!);
|
|
2162
|
+
expect(after.get(fired.id)!.inferenceProfile).toBe("cost-optimized");
|
|
2163
|
+
expect(after.get(cancelled.id)!.inferenceProfile).toBe("cost-optimized");
|
|
2164
|
+
expect(after.get(fired.id)!.updatedAt).toBe(firedBefore);
|
|
2165
|
+
expect(after.get(cancelled.id)!.updatedAt).toBe(cancelledBefore);
|
|
2166
|
+
});
|
|
2167
|
+
|
|
2168
|
+
test("an explicit from still sweeps a fired one-shot's dangling pin", async () => {
|
|
2169
|
+
const resolvedDefault = resolveDefaultScheduleInferenceProfile();
|
|
2170
|
+
const fired = await createSchedule({
|
|
2171
|
+
name: "Already fired",
|
|
2172
|
+
description: "Already fired",
|
|
2173
|
+
message: "hi",
|
|
2174
|
+
nextRunAt: Date.now() - 60_000,
|
|
2175
|
+
inferenceProfile: "cost-optimized",
|
|
2176
|
+
});
|
|
2177
|
+
rawRun(
|
|
2178
|
+
"test:setFiredScheduleStatus",
|
|
2179
|
+
"UPDATE cron_jobs SET status = 'fired' WHERE id = ?",
|
|
2180
|
+
fired.id,
|
|
2181
|
+
);
|
|
2182
|
+
|
|
2183
|
+
// Deleting a profile has to clear every row naming it, including rows that
|
|
2184
|
+
// will never fire again: the pin is what the delete is removing.
|
|
2185
|
+
const result = (await reassignRoute().handler({
|
|
2186
|
+
body: { from: "cost-optimized", to: resolvedDefault! },
|
|
2187
|
+
})) as { reassigned: number };
|
|
2188
|
+
|
|
2189
|
+
expect(result.reassigned).toBe(1);
|
|
2190
|
+
expect(listSchedules()[0].inferenceProfile).toBe(resolvedDefault!);
|
|
2191
|
+
});
|
|
2192
|
+
|
|
2193
|
+
test("omitting from still refuses a non-owner caller when a wake row would move", async () => {
|
|
2194
|
+
const resolvedDefault = resolveDefaultScheduleInferenceProfile();
|
|
2195
|
+
await createSchedule({
|
|
2196
|
+
name: "Deferred wake",
|
|
2197
|
+
description: "Deferred wake",
|
|
2198
|
+
message: "wake up",
|
|
2199
|
+
nextRunAt: Date.now() + 60_000,
|
|
2200
|
+
mode: "wake",
|
|
2201
|
+
wakeConversationId: "conv-abc",
|
|
2202
|
+
createdBy: "defer",
|
|
2203
|
+
inferenceProfile: "cost-optimized",
|
|
2204
|
+
});
|
|
2205
|
+
|
|
2206
|
+
await expect(
|
|
2207
|
+
reassignRoute().handler({
|
|
2208
|
+
body: { to: resolvedDefault! },
|
|
2209
|
+
headers: { "x-vellum-principal-type": "actor" },
|
|
2210
|
+
}),
|
|
2211
|
+
).rejects.toThrow(
|
|
2212
|
+
"Deferred wake schedules can only be changed by the assistant's owner",
|
|
2213
|
+
);
|
|
2214
|
+
expect(listSchedules()[0].inferenceProfile).toBe("cost-optimized");
|
|
2215
|
+
});
|
|
2216
|
+
});
|