@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,7 +1,19 @@
|
|
|
1
1
|
import { Cron } from "croner";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
and,
|
|
4
|
+
asc,
|
|
5
|
+
desc,
|
|
6
|
+
eq,
|
|
7
|
+
inArray,
|
|
8
|
+
isNotNull,
|
|
9
|
+
isNull,
|
|
10
|
+
lt,
|
|
11
|
+
lte,
|
|
12
|
+
sql,
|
|
13
|
+
} from "drizzle-orm";
|
|
3
14
|
import { v4 as uuid } from "uuid";
|
|
4
15
|
|
|
16
|
+
import { getConversation } from "../persistence/conversation-crud.js";
|
|
5
17
|
import { getDb } from "../persistence/db-connection.js";
|
|
6
18
|
import { getGroup } from "../persistence/group-crud.js";
|
|
7
19
|
import { isLifecycleQuiesced } from "../persistence/lifecycle-quiesce.js";
|
|
@@ -17,6 +29,11 @@ import {
|
|
|
17
29
|
LEGACY_DEFER_CREATED_BY,
|
|
18
30
|
OWNER_DEFER_CREATED_BY,
|
|
19
31
|
} from "./defer-provenance.js";
|
|
32
|
+
import {
|
|
33
|
+
resolveDefaultScheduleInferenceProfile,
|
|
34
|
+
resolveWakeScheduleInferenceProfile,
|
|
35
|
+
} from "./inference-profile.js";
|
|
36
|
+
import { declarationExistsOnDisk } from "./plugin-schedule-declarations.js";
|
|
20
37
|
import {
|
|
21
38
|
computeNextRunAt as computeNextRunAtEngine,
|
|
22
39
|
isValidScheduleExpression,
|
|
@@ -77,7 +94,11 @@ export interface ScheduleJob {
|
|
|
77
94
|
timeoutMs: number | null;
|
|
78
95
|
/**
|
|
79
96
|
* Inference profile (`llm.profiles` key) applied to the schedule's
|
|
80
|
-
* LLM-executed runs
|
|
97
|
+
* LLM-executed runs. Pinned at creation from the caller's choice or a
|
|
98
|
+
* snapshot of the resolved default, so the schedule's model (and cost) does
|
|
99
|
+
* not move when the global default changes. Null only where no named profile
|
|
100
|
+
* resolves at all, in which case runs follow the `mainAgent` call-site
|
|
101
|
+
* configuration.
|
|
81
102
|
*/
|
|
82
103
|
inferenceProfile: string | null;
|
|
83
104
|
/**
|
|
@@ -89,6 +110,24 @@ export interface ScheduleJob {
|
|
|
89
110
|
groupId: string | null;
|
|
90
111
|
createdFromConversationId: string | null;
|
|
91
112
|
createdBy: string;
|
|
113
|
+
/**
|
|
114
|
+
* Plugin declaration this row was reconciled from
|
|
115
|
+
* (`plugin:<pluginName>/<scheduleName>`); null = imperative schedule.
|
|
116
|
+
* Sourced rows are read-only through {@link updateSchedule} and
|
|
117
|
+
* {@link deleteSchedule}: the declaration file is the source of truth for
|
|
118
|
+
* definition columns, and the reconciler is their only writer.
|
|
119
|
+
*/
|
|
120
|
+
sourceKey: string | null;
|
|
121
|
+
/**
|
|
122
|
+
* Reconciler change detector over the plugin's declaration files; null on
|
|
123
|
+
* imperative rows.
|
|
124
|
+
*/
|
|
125
|
+
definitionHash: string | null;
|
|
126
|
+
/**
|
|
127
|
+
* User enable/disable override for a sourced row; null = the declaration's
|
|
128
|
+
* own enabled value applies. Always null on imperative rows.
|
|
129
|
+
*/
|
|
130
|
+
userEnabled: boolean | null;
|
|
92
131
|
mode: ScheduleMode;
|
|
93
132
|
routingIntent: RoutingIntent;
|
|
94
133
|
routingHints: Record<string, unknown>;
|
|
@@ -156,6 +195,8 @@ interface InsertScheduleParams {
|
|
|
156
195
|
inferenceProfile?: string | null;
|
|
157
196
|
groupId?: string | null;
|
|
158
197
|
createdFromConversationId?: string | null;
|
|
198
|
+
sourceKey?: string | null;
|
|
199
|
+
definitionHash?: string | null;
|
|
159
200
|
}
|
|
160
201
|
|
|
161
202
|
/**
|
|
@@ -172,11 +213,36 @@ export type OrdinaryScheduleCreator =
|
|
|
172
213
|
| "user"
|
|
173
214
|
| typeof LEGACY_DEFER_CREATED_BY;
|
|
174
215
|
|
|
175
|
-
/**
|
|
176
|
-
|
|
216
|
+
/**
|
|
217
|
+
* Parameters for ordinary schedule creation. `sourceKey` and `definitionHash`
|
|
218
|
+
* are excluded for the same structural reason `createdBy` is constrained:
|
|
219
|
+
* plugin-declaration provenance is minted only by
|
|
220
|
+
* {@link upsertDeclaredSchedule}, so an ordinary create cannot produce a row
|
|
221
|
+
* the reconciler believes it owns.
|
|
222
|
+
*/
|
|
223
|
+
export type CreateScheduleParams = Omit<
|
|
224
|
+
InsertScheduleParams,
|
|
225
|
+
"createdBy" | "sourceKey" | "definitionHash"
|
|
226
|
+
> & {
|
|
177
227
|
createdBy?: OrdinaryScheduleCreator;
|
|
178
228
|
};
|
|
179
229
|
|
|
230
|
+
/**
|
|
231
|
+
* Timezone stored for a recurring schedule. A wall-clock expression with no
|
|
232
|
+
* zone otherwise evaluates in the host clock (UTC on managed containers), so
|
|
233
|
+
* it defaults to the user's configured/detected zone; expressions that carry
|
|
234
|
+
* their own zone keep the caller's value verbatim.
|
|
235
|
+
*/
|
|
236
|
+
function resolveStoredTimezone(
|
|
237
|
+
syntax: ScheduleSyntax,
|
|
238
|
+
expression: string | null,
|
|
239
|
+
timezone: string | null | undefined,
|
|
240
|
+
): string | null {
|
|
241
|
+
return expressionCarriesOwnTimezone(syntax, expression)
|
|
242
|
+
? (timezone ?? null)
|
|
243
|
+
: resolveScheduleTimezone(timezone);
|
|
244
|
+
}
|
|
245
|
+
|
|
180
246
|
async function insertSchedule(
|
|
181
247
|
params: InsertScheduleParams,
|
|
182
248
|
): Promise<ScheduleJob> {
|
|
@@ -206,14 +272,10 @@ async function insertSchedule(
|
|
|
206
272
|
const id = uuid();
|
|
207
273
|
const now = Date.now();
|
|
208
274
|
const enabled = params.enabled ?? true;
|
|
209
|
-
//
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
const timezone =
|
|
214
|
-
isOneShot || expressionCarriesOwnTimezone(syntax, expression)
|
|
215
|
-
? (params.timezone ?? null)
|
|
216
|
-
: resolveScheduleTimezone(params.timezone);
|
|
275
|
+
// One-shot schedules (absolute epoch) keep the caller's value verbatim.
|
|
276
|
+
const timezone = isOneShot
|
|
277
|
+
? (params.timezone ?? null)
|
|
278
|
+
: resolveStoredTimezone(syntax, expression, params.timezone);
|
|
217
279
|
const mode = params.mode ?? "execute";
|
|
218
280
|
const routingIntent = params.routingIntent ?? "all_channels";
|
|
219
281
|
const routingHints = params.routingHints ?? {};
|
|
@@ -222,7 +284,25 @@ async function insertSchedule(
|
|
|
222
284
|
const maxRetries = params.maxRetries ?? 3;
|
|
223
285
|
const retryBackoffMs = params.retryBackoffMs ?? 60000;
|
|
224
286
|
const timeoutMs = params.timeoutMs ?? null;
|
|
225
|
-
|
|
287
|
+
// The single chokepoint that pins every schedule to a concrete profile: a
|
|
288
|
+
// caller that names none gets a snapshot of what that row resolves today, so
|
|
289
|
+
// the schedule's cost stays put when the user changes their global default
|
|
290
|
+
// profile.
|
|
291
|
+
//
|
|
292
|
+
// A wake row snapshots its target conversation's durable pin instead of the
|
|
293
|
+
// global default, because `buildWakeScheduleOptions` forces the row's pin as
|
|
294
|
+
// the woken turn's override: an unpinned wake resolves the target's own
|
|
295
|
+
// choice live, so seeding anything else would silently re-point it. Seeding
|
|
296
|
+
// here rather than in `createOwnerDeferredWake` makes the rule structural —
|
|
297
|
+
// it holds for every wake row however it was minted — and matches what
|
|
298
|
+
// migration 363 backfills onto the rows that predate the pin.
|
|
299
|
+
const inferenceProfile =
|
|
300
|
+
params.inferenceProfile ??
|
|
301
|
+
(mode === "wake"
|
|
302
|
+
? resolveWakeScheduleInferenceProfile(
|
|
303
|
+
getConversation(params.wakeConversationId!) ?? null,
|
|
304
|
+
)
|
|
305
|
+
: resolveDefaultScheduleInferenceProfile());
|
|
226
306
|
const groupId = params.groupId ?? null;
|
|
227
307
|
const createdFromConversationId = params.createdFromConversationId ?? null;
|
|
228
308
|
const description = normalizeDescription(
|
|
@@ -268,6 +348,9 @@ async function insertSchedule(
|
|
|
268
348
|
groupId,
|
|
269
349
|
createdFromConversationId,
|
|
270
350
|
createdBy: params.createdBy ?? "agent",
|
|
351
|
+
sourceKey: params.sourceKey ?? null,
|
|
352
|
+
definitionHash: params.definitionHash ?? null,
|
|
353
|
+
userEnabled: null as boolean | null,
|
|
271
354
|
mode,
|
|
272
355
|
routingIntent,
|
|
273
356
|
routingHintsJson: JSON.stringify(routingHints),
|
|
@@ -320,6 +403,15 @@ export async function createSchedule(
|
|
|
320
403
|
*
|
|
321
404
|
* Callers must have established owner authority first; `defer/create` is the
|
|
322
405
|
* only production caller and is gated accordingly.
|
|
406
|
+
*
|
|
407
|
+
* The pin seeds from the source conversation's durable choice rather than the
|
|
408
|
+
* global default. A defer resumes the very conversation it was created in, and
|
|
409
|
+
* `buildWakeScheduleOptions` forces the row's pin as the woken turn's
|
|
410
|
+
* override, so a defer created inside a conversation the user pinned to a
|
|
411
|
+
* specific profile must fire on that profile. That seeding lives in
|
|
412
|
+
* `insertSchedule`, keyed on `mode: "wake"` rather than on defer provenance,
|
|
413
|
+
* so no wake row can be minted that overrides its target's pin with the global
|
|
414
|
+
* default.
|
|
323
415
|
*/
|
|
324
416
|
export async function createOwnerDeferredWake(params: {
|
|
325
417
|
conversationId: string;
|
|
@@ -337,9 +429,7 @@ export async function createOwnerDeferredWake(params: {
|
|
|
337
429
|
createdBy: OWNER_DEFER_CREATED_BY,
|
|
338
430
|
nextRunAt: params.fireAt,
|
|
339
431
|
quiet: true,
|
|
340
|
-
|
|
341
|
-
? { inferenceProfile: params.inferenceProfile }
|
|
342
|
-
: {}),
|
|
432
|
+
inferenceProfile: params.inferenceProfile ?? null,
|
|
343
433
|
});
|
|
344
434
|
}
|
|
345
435
|
|
|
@@ -390,6 +480,7 @@ export function listSchedules(options?: {
|
|
|
390
480
|
mode?: ScheduleMode;
|
|
391
481
|
createdBy?: string | readonly string[];
|
|
392
482
|
conversationId?: string;
|
|
483
|
+
inferenceProfile?: string;
|
|
393
484
|
}): ScheduleJob[] {
|
|
394
485
|
const db = getDb();
|
|
395
486
|
const conditions = [];
|
|
@@ -418,6 +509,11 @@ export function listSchedules(options?: {
|
|
|
418
509
|
eq(scheduleJobs.wakeConversationId, options.conversationId),
|
|
419
510
|
);
|
|
420
511
|
}
|
|
512
|
+
if (options?.inferenceProfile) {
|
|
513
|
+
conditions.push(
|
|
514
|
+
eq(scheduleJobs.inferenceProfile, options.inferenceProfile),
|
|
515
|
+
);
|
|
516
|
+
}
|
|
421
517
|
const where = conditions.length > 0 ? and(...conditions) : undefined;
|
|
422
518
|
const rows = db
|
|
423
519
|
.select()
|
|
@@ -478,6 +574,16 @@ export async function updateSchedule(
|
|
|
478
574
|
return null;
|
|
479
575
|
}
|
|
480
576
|
|
|
577
|
+
// Plugin-sourced rows are read-only here: the plugin's schedule file is the
|
|
578
|
+
// source of truth for every definition column, and the reconciler would
|
|
579
|
+
// overwrite any edit on its next pass. Enable/disable goes through
|
|
580
|
+
// `setUserEnabled`; every other change is an edit to the declaration file.
|
|
581
|
+
if (existing.sourceKey != null) {
|
|
582
|
+
throw new UserError(
|
|
583
|
+
"This schedule is managed by a plugin. Edit the plugin's schedule file instead.",
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
|
|
481
587
|
// Owner-defer provenance certifies that the assistant's owner chose both the
|
|
482
588
|
// conversation this wake resumes and the text it carries. Rewriting either
|
|
483
589
|
// would leave the marker standing over content it does not describe, so on
|
|
@@ -610,7 +716,12 @@ export async function updateSchedule(
|
|
|
610
716
|
set.timeoutMs = updates.timeoutMs;
|
|
611
717
|
}
|
|
612
718
|
if (updates.inferenceProfile !== undefined) {
|
|
613
|
-
|
|
719
|
+
// Null re-snapshots the currently resolved default instead of unpinning:
|
|
720
|
+
// "follow whatever my global default happens to be" is not a state a
|
|
721
|
+
// schedule can rest in, since it would let a later default change move the
|
|
722
|
+
// schedule's model and price without the user touching the schedule.
|
|
723
|
+
set.inferenceProfile =
|
|
724
|
+
updates.inferenceProfile ?? resolveDefaultScheduleInferenceProfile();
|
|
614
725
|
}
|
|
615
726
|
if (updates.groupId !== undefined) {
|
|
616
727
|
set.groupId = updates.groupId;
|
|
@@ -642,8 +753,27 @@ export async function updateSchedule(
|
|
|
642
753
|
return getSchedule(id);
|
|
643
754
|
}
|
|
644
755
|
|
|
756
|
+
/** `source_key` of the row, or null when the row is absent or imperative. */
|
|
757
|
+
function getRowSourceKey(id: string): string | null {
|
|
758
|
+
const row = getDb()
|
|
759
|
+
.select({ sourceKey: scheduleJobs.sourceKey })
|
|
760
|
+
.from(scheduleJobs)
|
|
761
|
+
.where(eq(scheduleJobs.id, id))
|
|
762
|
+
.get();
|
|
763
|
+
return row?.sourceKey ?? null;
|
|
764
|
+
}
|
|
765
|
+
|
|
645
766
|
export async function deleteSchedule(id: string): Promise<boolean> {
|
|
646
767
|
const db = getDb();
|
|
768
|
+
// Plugin-sourced rows keep their identity and run history: deleting one
|
|
769
|
+
// would just have the reconciler recreate it from the declaration on its
|
|
770
|
+
// next pass. Disable is the supported action; removal means removing the
|
|
771
|
+
// plugin's schedule file.
|
|
772
|
+
if (getRowSourceKey(id) != null) {
|
|
773
|
+
throw new UserError(
|
|
774
|
+
"This schedule is managed by a plugin and cannot be deleted. Disable it instead, or remove the plugin's schedule file.",
|
|
775
|
+
);
|
|
776
|
+
}
|
|
647
777
|
// Capture rawChanges() inside the awaited closure: reading it after the
|
|
648
778
|
// await would race other async DB work on the shared connection.
|
|
649
779
|
const deleted = await withSqliteRetry(
|
|
@@ -659,6 +789,296 @@ export async function deleteSchedule(id: string): Promise<boolean> {
|
|
|
659
789
|
return deleted;
|
|
660
790
|
}
|
|
661
791
|
|
|
792
|
+
// ── Plugin-declared schedules ───────────────────────────────────────
|
|
793
|
+
//
|
|
794
|
+
// Rows with a non-null `source_key` mirror a declaration in a plugin's
|
|
795
|
+
// `schedules/` directory. The reconciler owns their definition columns (via
|
|
796
|
+
// the functions below, the only writers of `source_key`, `definition_hash`,
|
|
797
|
+
// and the definition side of `enabled`), the engine owns the runtime columns,
|
|
798
|
+
// and the user owns `user_enabled`. Rows with a null `source_key` are
|
|
799
|
+
// imperative schedules and none of this section touches them.
|
|
800
|
+
|
|
801
|
+
/**
|
|
802
|
+
* Definition of a plugin-declared schedule, as parsed from the plugin's
|
|
803
|
+
* declaration files. `enabled` is the declaration's own value; the stored
|
|
804
|
+
* row's `enabled` is the effective value, `user_enabled ?? enabled`.
|
|
805
|
+
*/
|
|
806
|
+
export interface DeclaredScheduleDefinition {
|
|
807
|
+
name: string;
|
|
808
|
+
description?: string;
|
|
809
|
+
syntax: ScheduleSyntax;
|
|
810
|
+
expression: string;
|
|
811
|
+
timezone?: string | null;
|
|
812
|
+
message: string;
|
|
813
|
+
script?: string | null;
|
|
814
|
+
mode: Extract<ScheduleMode, "execute" | "script">;
|
|
815
|
+
maxRetries?: number;
|
|
816
|
+
retryBackoffMs?: number;
|
|
817
|
+
quiet?: boolean;
|
|
818
|
+
inferenceProfile?: string | null;
|
|
819
|
+
timeoutMs?: number | null;
|
|
820
|
+
enabled: boolean;
|
|
821
|
+
definitionHash: string;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
/** All plugin-sourced schedule rows, including disarmed ones. */
|
|
825
|
+
export function listDeclaredSchedules(): ScheduleJob[] {
|
|
826
|
+
const db = getDb();
|
|
827
|
+
const rows = db
|
|
828
|
+
.select()
|
|
829
|
+
.from(scheduleJobs)
|
|
830
|
+
.where(isNotNull(scheduleJobs.sourceKey))
|
|
831
|
+
.orderBy(asc(scheduleJobs.nextRunAt))
|
|
832
|
+
.all();
|
|
833
|
+
return rows.map(parseJobRow);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
/**
|
|
837
|
+
* True when the engine has latched the row: a fired or cancelled one-shot, or
|
|
838
|
+
* a recurrence the claim path exhausted. The exhaust path stamps `lastRunAt`
|
|
839
|
+
* as it writes `nextRunAt = 0` and disables the row, so `nextRunAt = 0` with
|
|
840
|
+
* a null `lastRunAt` is a declared schedule inserted disabled, not an engine
|
|
841
|
+
* latch, and stays re-armable.
|
|
842
|
+
*/
|
|
843
|
+
function isEngineLatched(row: {
|
|
844
|
+
status: string;
|
|
845
|
+
nextRunAt: number;
|
|
846
|
+
lastRunAt: number | null;
|
|
847
|
+
}): boolean {
|
|
848
|
+
return (
|
|
849
|
+
row.status === "fired" ||
|
|
850
|
+
row.status === "cancelled" ||
|
|
851
|
+
(row.nextRunAt === 0 && row.lastRunAt != null)
|
|
852
|
+
);
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
/**
|
|
856
|
+
* Insert or update the row for a plugin declaration, keyed by `sourceKey`.
|
|
857
|
+
*
|
|
858
|
+
* Effective `enabled` (`user_enabled ?? definition.enabled`) is recomputed on
|
|
859
|
+
* every call, so a row disarmed while its plugin was disabled re-arms once
|
|
860
|
+
* the declaration is back in the desired set. `nextRunAt` is recomputed only
|
|
861
|
+
* when the timing changed or the row transitions to enabled; a matching hash
|
|
862
|
+
* with matching script path and effective `enabled` is a no-op. A script
|
|
863
|
+
* path that moved on a matching hash (the invocation embeds the absolute
|
|
864
|
+
* entrypoint path, which moves with the workspace while the hash covers only
|
|
865
|
+
* schedules/-relative paths and contents) is rewritten without touching
|
|
866
|
+
* timing. Rows the engine has latched are returned untouched.
|
|
867
|
+
*/
|
|
868
|
+
export async function upsertDeclaredSchedule(
|
|
869
|
+
sourceKey: string,
|
|
870
|
+
definition: DeclaredScheduleDefinition,
|
|
871
|
+
): Promise<ScheduleJob> {
|
|
872
|
+
const db = getDb();
|
|
873
|
+
const existing = db
|
|
874
|
+
.select()
|
|
875
|
+
.from(scheduleJobs)
|
|
876
|
+
.where(eq(scheduleJobs.sourceKey, sourceKey))
|
|
877
|
+
.get();
|
|
878
|
+
|
|
879
|
+
if (!existing) {
|
|
880
|
+
return insertSchedule({ ...definition, createdBy: "agent", sourceKey });
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
if (isEngineLatched(existing)) {
|
|
884
|
+
return parseJobRow(existing);
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
// Same timezone defaulting as insert, so change detection compares the
|
|
888
|
+
// value that would actually be stored.
|
|
889
|
+
const timezone = resolveStoredTimezone(
|
|
890
|
+
definition.syntax,
|
|
891
|
+
definition.expression,
|
|
892
|
+
definition.timezone,
|
|
893
|
+
);
|
|
894
|
+
|
|
895
|
+
const definitionChanged =
|
|
896
|
+
existing.definitionHash !== definition.definitionHash;
|
|
897
|
+
const scriptChanged = (definition.script ?? null) !== existing.script;
|
|
898
|
+
const effectiveEnabled = existing.userEnabled ?? definition.enabled;
|
|
899
|
+
if (
|
|
900
|
+
!definitionChanged &&
|
|
901
|
+
!scriptChanged &&
|
|
902
|
+
effectiveEnabled === existing.enabled
|
|
903
|
+
) {
|
|
904
|
+
return parseJobRow(existing);
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
const spec = {
|
|
908
|
+
syntax: definition.syntax,
|
|
909
|
+
expression: definition.expression,
|
|
910
|
+
timezone,
|
|
911
|
+
};
|
|
912
|
+
if (definitionChanged && !isValidScheduleExpression(spec)) {
|
|
913
|
+
throw new Error(
|
|
914
|
+
`Invalid ${definition.syntax} expression: "${definition.expression}"`,
|
|
915
|
+
);
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
const timingChanged =
|
|
919
|
+
definition.expression !== existing.cronExpression ||
|
|
920
|
+
definition.syntax !== existing.scheduleSyntax ||
|
|
921
|
+
timezone !== existing.timezone;
|
|
922
|
+
|
|
923
|
+
const set: Record<string, unknown> = {
|
|
924
|
+
enabled: effectiveEnabled,
|
|
925
|
+
updatedAt: Date.now(),
|
|
926
|
+
};
|
|
927
|
+
if (definitionChanged) {
|
|
928
|
+
set.name = definition.name;
|
|
929
|
+
set.description = normalizeDescription(
|
|
930
|
+
definition.description,
|
|
931
|
+
definition.name,
|
|
932
|
+
);
|
|
933
|
+
set.cronExpression = definition.expression;
|
|
934
|
+
set.scheduleSyntax = definition.syntax;
|
|
935
|
+
set.timezone = timezone;
|
|
936
|
+
set.message = definition.message;
|
|
937
|
+
set.script = definition.script ?? null;
|
|
938
|
+
set.mode = definition.mode;
|
|
939
|
+
set.maxRetries = definition.maxRetries ?? 3;
|
|
940
|
+
set.retryBackoffMs = definition.retryBackoffMs ?? 60000;
|
|
941
|
+
set.quiet = definition.quiet ?? false;
|
|
942
|
+
set.inferenceProfile = definition.inferenceProfile ?? null;
|
|
943
|
+
set.timeoutMs = definition.timeoutMs ?? null;
|
|
944
|
+
set.definitionHash = definition.definitionHash;
|
|
945
|
+
} else if (scriptChanged) {
|
|
946
|
+
set.script = definition.script ?? null;
|
|
947
|
+
}
|
|
948
|
+
// While disabled, `nextRunAt` is left alone so a stale value never reads as
|
|
949
|
+
// the engine's exhaust latch (`nextRunAt = 0` with `lastRunAt` set).
|
|
950
|
+
if (
|
|
951
|
+
effectiveEnabled &&
|
|
952
|
+
(timingChanged || !existing.enabled || existing.nextRunAt === 0)
|
|
953
|
+
) {
|
|
954
|
+
set.nextRunAt = computeNextRunAtEngine(spec);
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
await withSqliteRetry(
|
|
958
|
+
() =>
|
|
959
|
+
db
|
|
960
|
+
.update(scheduleJobs)
|
|
961
|
+
.set(set)
|
|
962
|
+
.where(eq(scheduleJobs.id, existing.id))
|
|
963
|
+
.run(),
|
|
964
|
+
{ op: "upsertDeclaredSchedule", context: { scheduleId: existing.id } },
|
|
965
|
+
);
|
|
966
|
+
notifySchedulesChanged();
|
|
967
|
+
|
|
968
|
+
const updated = getSchedule(existing.id);
|
|
969
|
+
if (!updated) {
|
|
970
|
+
throw new Error(`Declared schedule ${existing.id} vanished during upsert`);
|
|
971
|
+
}
|
|
972
|
+
return updated;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
/**
|
|
976
|
+
* Pause a sourced row whose declaration is gone or whose plugin is disabled.
|
|
977
|
+
* The row and its runs are kept so a reinstall re-links by `source_key` and
|
|
978
|
+
* `user_enabled` survives. `nextRunAt` is left alone: a disabled row never
|
|
979
|
+
* fires, and `nextRunAt = 0` is reserved for the engine's exhaust latch.
|
|
980
|
+
*
|
|
981
|
+
* Resolves `true` when this call took an armed row off, and `false` when
|
|
982
|
+
* there was nothing to do because the row is gone or already disabled. A
|
|
983
|
+
* caller reporting the pause to the user needs that distinction: a row the
|
|
984
|
+
* user or the engine already turned off is not something this call paused.
|
|
985
|
+
*/
|
|
986
|
+
export async function disarmDeclaredSchedule(id: string): Promise<boolean> {
|
|
987
|
+
const db = getDb();
|
|
988
|
+
const existing = db
|
|
989
|
+
.select()
|
|
990
|
+
.from(scheduleJobs)
|
|
991
|
+
.where(eq(scheduleJobs.id, id))
|
|
992
|
+
.get();
|
|
993
|
+
if (!existing) {
|
|
994
|
+
return false;
|
|
995
|
+
}
|
|
996
|
+
if (existing.sourceKey == null) {
|
|
997
|
+
throw new Error(
|
|
998
|
+
"disarmDeclaredSchedule applies only to plugin-sourced schedules",
|
|
999
|
+
);
|
|
1000
|
+
}
|
|
1001
|
+
if (!existing.enabled) {
|
|
1002
|
+
return false;
|
|
1003
|
+
}
|
|
1004
|
+
await withSqliteRetry(
|
|
1005
|
+
() =>
|
|
1006
|
+
db
|
|
1007
|
+
.update(scheduleJobs)
|
|
1008
|
+
.set({ enabled: false, updatedAt: Date.now() })
|
|
1009
|
+
.where(eq(scheduleJobs.id, id))
|
|
1010
|
+
.run(),
|
|
1011
|
+
{ op: "disarmDeclaredSchedule", context: { scheduleId: id } },
|
|
1012
|
+
);
|
|
1013
|
+
notifySchedulesChanged();
|
|
1014
|
+
return true;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
/**
|
|
1018
|
+
* The user's side of the enabled toggle.
|
|
1019
|
+
*
|
|
1020
|
+
* On an imperative row this is exactly the existing toggle
|
|
1021
|
+
* (`updateSchedule(id, { enabled })`) and `user_enabled` stays null. On a
|
|
1022
|
+
* plugin-sourced row it records the override in `user_enabled` and applies
|
|
1023
|
+
* it to the effective `enabled` on the spot. Engine-latched rows record the
|
|
1024
|
+
* override without being re-armed.
|
|
1025
|
+
*/
|
|
1026
|
+
export async function setUserEnabled(
|
|
1027
|
+
id: string,
|
|
1028
|
+
value: boolean,
|
|
1029
|
+
): Promise<ScheduleJob | null> {
|
|
1030
|
+
const db = getDb();
|
|
1031
|
+
const existing = db
|
|
1032
|
+
.select()
|
|
1033
|
+
.from(scheduleJobs)
|
|
1034
|
+
.where(eq(scheduleJobs.id, id))
|
|
1035
|
+
.get();
|
|
1036
|
+
if (!existing) {
|
|
1037
|
+
return null;
|
|
1038
|
+
}
|
|
1039
|
+
if (existing.sourceKey == null) {
|
|
1040
|
+
return updateSchedule(id, { enabled: value });
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
const set: Record<string, unknown> = {
|
|
1044
|
+
userEnabled: value,
|
|
1045
|
+
updatedAt: Date.now(),
|
|
1046
|
+
};
|
|
1047
|
+
if (value !== existing.enabled && !isEngineLatched(existing)) {
|
|
1048
|
+
// Enabling a row whose declaration is no longer sourceable (plugin
|
|
1049
|
+
// uninstalled or disabled, manifest broken, schedule file removed) would
|
|
1050
|
+
// let it fire an orphaned run before the next reconcile pass disarms it
|
|
1051
|
+
// again. The reconciler is the authority on the declaration set; this
|
|
1052
|
+
// probe only closes that fire-before-next-sweep window. The override
|
|
1053
|
+
// itself is still recorded, so it applies if the declaration returns.
|
|
1054
|
+
if (value && !(await declarationExistsOnDisk(existing.sourceKey))) {
|
|
1055
|
+
logger.info(
|
|
1056
|
+
{ scheduleId: id, sourceKey: existing.sourceKey },
|
|
1057
|
+
"Enable override recorded without re-arming: declaration missing on disk",
|
|
1058
|
+
);
|
|
1059
|
+
} else {
|
|
1060
|
+
set.enabled = value;
|
|
1061
|
+
// Re-arming needs a fresh occurrence: the stored one went stale while
|
|
1062
|
+
// the row was disabled. Disabling keeps `nextRunAt`; see the disarm
|
|
1063
|
+
// note.
|
|
1064
|
+
if (value && existing.cronExpression != null) {
|
|
1065
|
+
set.nextRunAt = computeNextRunAtEngine({
|
|
1066
|
+
syntax: existing.scheduleSyntax as ScheduleSyntax,
|
|
1067
|
+
expression: existing.cronExpression,
|
|
1068
|
+
timezone: existing.timezone,
|
|
1069
|
+
});
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
await withSqliteRetry(
|
|
1075
|
+
() => db.update(scheduleJobs).set(set).where(eq(scheduleJobs.id, id)).run(),
|
|
1076
|
+
{ op: "setUserEnabled", context: { scheduleId: id } },
|
|
1077
|
+
);
|
|
1078
|
+
notifySchedulesChanged();
|
|
1079
|
+
return getSchedule(id);
|
|
1080
|
+
}
|
|
1081
|
+
|
|
662
1082
|
/**
|
|
663
1083
|
* Claim due schedules atomically. Handles both recurring and one-shot schedules.
|
|
664
1084
|
*
|
|
@@ -930,6 +1350,15 @@ export async function failOneShotPermanently(id: string): Promise<void> {
|
|
|
930
1350
|
*/
|
|
931
1351
|
export async function cancelSchedule(id: string): Promise<boolean> {
|
|
932
1352
|
const db = getDb();
|
|
1353
|
+
// Cancelled is a permanent latch on a sourced row: the reconciler, the
|
|
1354
|
+
// enabled toggle, and delete all skip it, so cancelling would brick the
|
|
1355
|
+
// plugin's schedule even across reinstalls. Disable is the supported
|
|
1356
|
+
// action, as with update and delete.
|
|
1357
|
+
if (getRowSourceKey(id) != null) {
|
|
1358
|
+
throw new UserError(
|
|
1359
|
+
"This schedule is managed by a plugin and cannot be cancelled. Disable it instead via the enabled toggle.",
|
|
1360
|
+
);
|
|
1361
|
+
}
|
|
933
1362
|
const now = Date.now();
|
|
934
1363
|
const cancelled = await withSqliteRetry(
|
|
935
1364
|
() => {
|
|
@@ -1017,10 +1446,15 @@ export async function setScheduleRunConversationId(
|
|
|
1017
1446
|
);
|
|
1018
1447
|
}
|
|
1019
1448
|
|
|
1020
|
-
|
|
1449
|
+
/**
|
|
1450
|
+
* Close out a run row, leaving the parent job alone. Returns the job the run
|
|
1451
|
+
* belongs to and the timestamp the row was finished at, or null when the run
|
|
1452
|
+
* no longer exists.
|
|
1453
|
+
*/
|
|
1454
|
+
async function finishScheduleRunRow(
|
|
1021
1455
|
runId: string,
|
|
1022
1456
|
result: { status: "ok" | "error"; output?: string; error?: string },
|
|
1023
|
-
): Promise<
|
|
1457
|
+
): Promise<{ jobId: string; now: number } | null> {
|
|
1024
1458
|
const db = getDb();
|
|
1025
1459
|
const now = Date.now();
|
|
1026
1460
|
|
|
@@ -1030,7 +1464,7 @@ export async function completeScheduleRun(
|
|
|
1030
1464
|
.where(eq(scheduleRuns.id, runId))
|
|
1031
1465
|
.get();
|
|
1032
1466
|
if (!run) {
|
|
1033
|
-
return;
|
|
1467
|
+
return null;
|
|
1034
1468
|
}
|
|
1035
1469
|
|
|
1036
1470
|
const durationMs = now - run.startedAt;
|
|
@@ -1048,16 +1482,73 @@ export async function completeScheduleRun(
|
|
|
1048
1482
|
})
|
|
1049
1483
|
.where(eq(scheduleRuns.id, runId))
|
|
1050
1484
|
.run(),
|
|
1051
|
-
{ op: "
|
|
1485
|
+
{ op: "finishScheduleRunRow", context: { runId } },
|
|
1052
1486
|
);
|
|
1053
1487
|
|
|
1488
|
+
return { jobId: run.jobId, now };
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
/**
|
|
1492
|
+
* Record a run the scheduler declined to start, without charging it to the
|
|
1493
|
+
* schedule's retry budget.
|
|
1494
|
+
*
|
|
1495
|
+
* An administrative skip (the plugin behind a sourced row is disabled,
|
|
1496
|
+
* uninstalled, or broken) is not a failed attempt: nothing ran, so there is
|
|
1497
|
+
* nothing to retry. `retryCount` is therefore left where it is, and no retry
|
|
1498
|
+
* is scheduled, which leaves a recurring row on the next occurrence its claim
|
|
1499
|
+
* advanced it to rather than on retry-backoff cadence. `lastStatus` still
|
|
1500
|
+
* moves to `error` and the run row is still written, so the skip is visible
|
|
1501
|
+
* in the schedule's history.
|
|
1502
|
+
*/
|
|
1503
|
+
export async function recordAdministrativeSkipRun(
|
|
1504
|
+
jobId: string,
|
|
1505
|
+
reason: string,
|
|
1506
|
+
): Promise<void> {
|
|
1507
|
+
const db = getDb();
|
|
1508
|
+
const runId = await createScheduleRun(jobId, null);
|
|
1509
|
+
const finished = await finishScheduleRunRow(runId, {
|
|
1510
|
+
status: "error",
|
|
1511
|
+
error: reason,
|
|
1512
|
+
});
|
|
1513
|
+
if (!finished) {
|
|
1514
|
+
return;
|
|
1515
|
+
}
|
|
1516
|
+
const changed = await withSqliteRetry(
|
|
1517
|
+
() => {
|
|
1518
|
+
db.update(scheduleJobs)
|
|
1519
|
+
.set({ lastStatus: "error", updatedAt: finished.now })
|
|
1520
|
+
.where(eq(scheduleJobs.id, jobId))
|
|
1521
|
+
.run();
|
|
1522
|
+
return rawChanges() > 0;
|
|
1523
|
+
},
|
|
1524
|
+
{
|
|
1525
|
+
op: "recordAdministrativeSkipRun.job",
|
|
1526
|
+
context: { scheduleId: jobId },
|
|
1527
|
+
},
|
|
1528
|
+
);
|
|
1529
|
+
if (changed) {
|
|
1530
|
+
notifySchedulesChanged();
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
export async function completeScheduleRun(
|
|
1535
|
+
runId: string,
|
|
1536
|
+
result: { status: "ok" | "error"; output?: string; error?: string },
|
|
1537
|
+
): Promise<void> {
|
|
1538
|
+
const db = getDb();
|
|
1539
|
+
const finished = await finishScheduleRunRow(runId, result);
|
|
1540
|
+
if (!finished) {
|
|
1541
|
+
return;
|
|
1542
|
+
}
|
|
1543
|
+
const { jobId, now } = finished;
|
|
1544
|
+
|
|
1054
1545
|
// Update the parent job's lastStatus and retryCount
|
|
1055
1546
|
if (result.status === "error") {
|
|
1056
1547
|
// Increment retry count
|
|
1057
1548
|
const job = db
|
|
1058
1549
|
.select()
|
|
1059
1550
|
.from(scheduleJobs)
|
|
1060
|
-
.where(eq(scheduleJobs.id,
|
|
1551
|
+
.where(eq(scheduleJobs.id, jobId))
|
|
1061
1552
|
.get();
|
|
1062
1553
|
if (job) {
|
|
1063
1554
|
const changed = await withSqliteRetry(
|
|
@@ -1068,13 +1559,13 @@ export async function completeScheduleRun(
|
|
|
1068
1559
|
retryCount: job.retryCount + 1,
|
|
1069
1560
|
updatedAt: now,
|
|
1070
1561
|
})
|
|
1071
|
-
.where(eq(scheduleJobs.id,
|
|
1562
|
+
.where(eq(scheduleJobs.id, jobId))
|
|
1072
1563
|
.run();
|
|
1073
1564
|
return rawChanges() > 0;
|
|
1074
1565
|
},
|
|
1075
1566
|
{
|
|
1076
1567
|
op: "completeScheduleRun.jobError",
|
|
1077
|
-
context: { scheduleId:
|
|
1568
|
+
context: { scheduleId: jobId },
|
|
1078
1569
|
},
|
|
1079
1570
|
);
|
|
1080
1571
|
if (changed) {
|
|
@@ -1086,11 +1577,11 @@ export async function completeScheduleRun(
|
|
|
1086
1577
|
() => {
|
|
1087
1578
|
db.update(scheduleJobs)
|
|
1088
1579
|
.set({ lastStatus: "ok", retryCount: 0, updatedAt: now })
|
|
1089
|
-
.where(eq(scheduleJobs.id,
|
|
1580
|
+
.where(eq(scheduleJobs.id, jobId))
|
|
1090
1581
|
.run();
|
|
1091
1582
|
return rawChanges() > 0;
|
|
1092
1583
|
},
|
|
1093
|
-
{ op: "completeScheduleRun.jobOk", context: { scheduleId:
|
|
1584
|
+
{ op: "completeScheduleRun.jobOk", context: { scheduleId: jobId } },
|
|
1094
1585
|
);
|
|
1095
1586
|
if (changed) {
|
|
1096
1587
|
notifySchedulesChanged();
|
|
@@ -1537,6 +2028,9 @@ function parseJobRow(row: typeof scheduleJobs.$inferSelect): ScheduleJob {
|
|
|
1537
2028
|
groupId: row.groupId ?? null,
|
|
1538
2029
|
createdFromConversationId: row.createdFromConversationId ?? null,
|
|
1539
2030
|
createdBy: row.createdBy,
|
|
2031
|
+
sourceKey: row.sourceKey ?? null,
|
|
2032
|
+
definitionHash: row.definitionHash ?? null,
|
|
2033
|
+
userEnabled: row.userEnabled ?? null,
|
|
1540
2034
|
mode: (row.mode ?? "execute") as ScheduleMode,
|
|
1541
2035
|
routingIntent: (row.routingIntent ?? "all_channels") as RoutingIntent,
|
|
1542
2036
|
routingHints: safeParseJson(row.routingHintsJson),
|