@vellumai/assistant 0.8.12 → 0.9.0-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/AGENTS.md +0 -14
- package/ARCHITECTURE.md +45 -45
- package/README.md +1 -1
- package/bun.lock +200 -154
- package/docs/architecture/integrations.md +3 -3
- package/docs/architecture/memory.md +2 -2
- package/docs/architecture/security.md +10 -10
- package/docs/runbook-trusted-contacts.md +12 -12
- package/docs/skills.md +6 -6
- package/docs/workflows-testing.md +221 -0
- package/docs/workflows.md +510 -0
- package/examples/plugins/echo/README.md +5 -5
- package/knip.json +2 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +105 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +12 -0
- package/openapi.yaml +7197 -5708
- package/package.json +8 -4
- package/scripts/generate-openapi.ts +66 -114
- package/src/__tests__/access-request-seed-content-blocks.test.ts +213 -0
- package/src/__tests__/adaptive-thinking-repair.test.ts +32 -3
- package/src/__tests__/agent-loop-output-hooks.test.ts +183 -0
- package/src/__tests__/agent-loop-regrowth-guard.test.ts +506 -0
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
- package/src/__tests__/agent-wake-override-profile.test.ts +77 -0
- package/src/__tests__/app-compiler.test.ts +7 -1
- package/src/__tests__/app-dir-path-guard.test.ts +27 -3
- package/src/__tests__/app-executors.test.ts +43 -0
- package/src/__tests__/approval-cascade.test.ts +0 -5
- package/src/__tests__/approval-routes-http.test.ts +91 -0
- package/src/__tests__/assistant-stream-state.test.ts +107 -0
- package/src/__tests__/browser-fill-credential.test.ts +3 -3
- package/src/__tests__/bundled-skill-retrieval-guard.test.ts +1 -1
- package/src/__tests__/compaction-events.test.ts +63 -7
- package/src/__tests__/compaction-trail-store.test.ts +74 -1
- package/src/__tests__/compaction.benchmark.test.ts +63 -41
- package/src/__tests__/compactor-low-watermark-cut.test.ts +349 -0
- package/src/__tests__/context-window-manager-compact-retry.test.ts +64 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +0 -5
- package/src/__tests__/conversation-confirmation-signals.test.ts +0 -5
- package/src/__tests__/conversation-history-web-search.test.ts +7 -0
- package/src/__tests__/conversation-process-callsite.test.ts +0 -5
- package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -5
- package/src/__tests__/conversation-queue.test.ts +0 -5
- package/src/__tests__/conversation-slash-queue.test.ts +0 -5
- package/src/__tests__/conversation-slash-unknown.test.ts +0 -5
- package/src/__tests__/conversation-speed-override.test.ts +0 -5
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +97 -0
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
- package/src/__tests__/conversation-usage.test.ts +2 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +0 -5
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -5
- package/src/__tests__/credential-broker-browser-fill.test.ts +2 -2
- package/src/__tests__/credential-broker-server-use.test.ts +2 -2
- package/src/__tests__/credential-broker.test.ts +1 -1
- package/src/__tests__/credential-prompt-route.test.ts +417 -0
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/credential-vault.test.ts +37 -0
- package/src/__tests__/db-schedule-syntax-migration.test.ts +24 -0
- package/src/__tests__/dynamic-page-surface.test.ts +219 -0
- package/src/__tests__/empty-state-greeting-cache.test.ts +94 -0
- package/src/__tests__/gateway-flag-listener.test.ts +24 -7
- package/src/__tests__/guardian-action-sweep.test.ts +56 -219
- package/src/__tests__/guardian-routing-invariants.test.ts +138 -0
- package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
- package/src/__tests__/list-messages-hidden-metadata.test.ts +99 -0
- package/src/__tests__/llm-request-log-source-clickhouse.test.ts +87 -1
- package/src/__tests__/llm-resolver.test.ts +115 -0
- package/src/__tests__/managed-profile-guard.test.ts +6 -5
- package/src/__tests__/max-tokens-continue-hook.test.ts +184 -0
- package/src/__tests__/media-generate-image.test.ts +20 -9
- package/src/__tests__/mock-gateway-ipc.ts +23 -0
- package/src/__tests__/model-intents.test.ts +1 -1
- package/src/__tests__/normalize-onboarding.test.ts +26 -0
- package/src/__tests__/notification-decision-strategy.test.ts +4 -2
- package/src/__tests__/notification-telegram-adapter.test.ts +21 -3
- package/src/__tests__/pending-interactions-resolved-event.test.ts +62 -0
- package/src/__tests__/post-turn-tool-result-truncation.test.ts +72 -18
- package/src/__tests__/require-fresh-approval.test.ts +425 -1
- package/src/__tests__/resolve-app-id.test.ts +56 -0
- package/src/__tests__/runtime-events-sse-parity.test.ts +2 -0
- package/src/__tests__/schedule-routes-workflow-validation.test.ts +408 -0
- package/src/__tests__/schedule-routes.test.ts +257 -4
- package/src/__tests__/schedule-store.test.ts +60 -0
- package/src/__tests__/schedule-tools.test.ts +247 -2
- package/src/__tests__/skill-execute-input.test.ts +85 -0
- package/src/__tests__/skill-secret-handling-guard.test.ts +21 -20
- package/src/__tests__/skills.test.ts +3 -3
- package/src/__tests__/slack-app-setup-skill-regression.test.ts +1 -1
- package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
- package/src/__tests__/subagent-tool-gate-mode.test.ts +547 -0
- package/src/__tests__/system-prompt.test.ts +1 -1
- package/src/__tests__/task-progress-nudge-hook.test.ts +372 -0
- package/src/__tests__/task-scheduler.test.ts +299 -0
- package/src/__tests__/tool-approval-seed-content-blocks.test.ts +209 -0
- package/src/__tests__/tool-result-spool.test.ts +3 -1
- package/src/__tests__/workspace-migration-102-preserve-heartbeat-enabled-for-existing-workspaces.test.ts +181 -0
- package/src/__tests__/workspace-migration-103-upgrade-quality-profile-to-opus-4-8.test.ts +174 -0
- package/src/agent/compaction-circuit.ts +11 -0
- package/src/agent/loop.ts +181 -12
- package/src/api/constants/call-sites.ts +12 -0
- package/src/api/events/assistant-thinking-delta.ts +10 -0
- package/src/api/events/usage-update.ts +7 -0
- package/src/api/index.ts +4 -1
- package/src/api/responses/memory-v3-selection-log.ts +18 -11
- package/src/approvals/approval-primitive.ts +2 -2
- package/src/background-wake/background-wake-routes.test.ts +5 -2
- package/src/bundler/compiler-tools.ts +1 -1
- package/src/bundler/package-resolver.ts +0 -1
- package/src/calls/call-domain.ts +1 -1
- package/src/calls/guardian-action-sweep.ts +16 -93
- package/src/cli/AGENTS.md +4 -0
- package/src/cli/commands/__tests__/schedules.test.ts +430 -1
- package/src/cli/commands/credentials.ts +28 -24
- package/src/cli/commands/image-generation.ts +23 -9
- package/src/cli/commands/notifications.ts +1 -1
- package/src/cli/commands/plugins.ts +89 -46
- package/src/cli/commands/schedules.ts +384 -11
- package/src/cli/lib/__tests__/inspect-plugin.test.ts +69 -5
- package/src/cli/lib/__tests__/install-from-github.test.ts +15 -0
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +81 -4
- package/src/cli/lib/inspect-plugin.ts +62 -1
- package/src/cli/lib/install-from-github.ts +52 -5
- package/src/cli/lib/upgrade-plugin.ts +18 -0
- package/src/config/__tests__/workflows-schema.test.ts +60 -0
- package/src/config/bundled-skills/acp/SKILL.md +2 -2
- package/src/config/bundled-skills/app-builder/SKILL.md +1 -1
- package/src/config/bundled-skills/app-builder/tools/app-create.ts +6 -1
- package/src/config/bundled-skills/app-builder/tools/app-generate-icon.ts +7 -1
- package/src/config/bundled-skills/app-builder/tools/app-refresh.ts +7 -1
- package/src/config/bundled-skills/app-builder/tools/app-update.ts +10 -1
- package/src/config/bundled-skills/image-studio/SKILL.md +66 -19
- package/src/config/bundled-skills/image-studio/TOOLS.json +1 -6
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +22 -3
- package/src/config/bundled-skills/personal-page/SKILL.md +57 -0
- package/src/config/bundled-skills/personal-page/TOOLS.json +27 -0
- package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +17 -0
- package/src/config/bundled-skills/schedule/SKILL.md +7 -2
- package/src/config/bundled-skills/schedule/TOOLS.json +48 -4
- package/src/config/bundled-skills/workflows/SKILL.md +214 -0
- package/src/config/bundled-skills/workflows/TOOLS.json +84 -0
- package/src/config/bundled-skills/workflows/tools/manage-workflows.ts +12 -0
- package/src/config/bundled-skills/workflows/tools/run-workflow.ts +12 -0
- package/src/config/bundled-tool-registry.ts +14 -2
- package/src/config/call-site-defaults.ts +5 -0
- package/src/config/feature-flag-registry.json +12 -4
- package/src/config/llm-context-resolution.ts +8 -0
- package/src/config/llm-resolver.ts +30 -0
- package/src/config/preloaded-apps/personal-page/src/components/About.tsx +22 -0
- package/src/config/preloaded-apps/personal-page/src/components/App.tsx +16 -0
- package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +77 -0
- package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +57 -0
- package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +28 -0
- package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +234 -0
- package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +48 -0
- package/src/config/preloaded-apps/personal-page/src/components/media.ts +16 -0
- package/src/config/preloaded-apps/personal-page/src/index.html +20 -0
- package/src/config/preloaded-apps/personal-page/src/main.tsx +7 -0
- package/src/config/preloaded-apps/personal-page/src/profile-data.ts +82 -0
- package/src/config/preloaded-apps/personal-page/src/styles.css +759 -0
- package/src/config/schema.ts +2 -0
- package/src/config/schemas/call-site-catalog.ts +7 -0
- package/src/config/schemas/heartbeat.ts +4 -1
- package/src/config/schemas/llm.ts +33 -26
- package/src/config/schemas/memory-retrospective.ts +19 -0
- package/src/config/schemas/platform.ts +8 -0
- package/src/config/schemas/services.ts +5 -2
- package/src/config/schemas/workflows.ts +42 -0
- package/src/config/skills.ts +3 -3
- package/src/context/compactor.ts +273 -39
- package/src/context/post-turn-tool-result-truncation.ts +23 -6
- package/src/context/tool-result-spool.ts +12 -17
- package/src/credential-execution/executable-discovery.ts +1 -1
- package/src/credential-execution/process-manager.ts +37 -3
- package/src/credential-execution/prompted-credential.ts +205 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +14 -0
- package/src/daemon/conversation-process.ts +11 -2
- package/src/daemon/conversation-surfaces.ts +167 -3
- package/src/daemon/conversation-tool-setup.ts +103 -26
- package/src/daemon/conversation-usage.ts +2 -0
- package/src/daemon/conversation.ts +115 -11
- package/src/daemon/handlers/shared.ts +26 -14
- package/src/daemon/host-cu-proxy.ts +15 -12
- package/src/daemon/host-file-proxy.ts +15 -12
- package/src/daemon/host-transfer-proxy.ts +30 -24
- package/src/daemon/lifecycle.ts +40 -3
- package/src/daemon/message-protocol.ts +3 -0
- package/src/daemon/message-types/messages.ts +2 -10
- package/src/daemon/message-types/workflows.ts +49 -0
- package/src/daemon/parse-actual-tokens-from-error.test.ts +62 -1
- package/src/daemon/parse-actual-tokens-from-error.ts +43 -4
- package/src/daemon/process-message.ts +6 -0
- package/src/daemon/tool-setup-types.ts +57 -0
- package/src/daemon/wake-conversation-ops.ts +18 -0
- package/src/heartbeat/heartbeat-run-store.ts +8 -2
- package/src/home/feed-types.ts +1 -1
- package/src/ipc/gateway-flag-listener.ts +28 -6
- package/src/mcp/mcp-auth-state.ts +8 -20
- package/src/media/__tests__/image-models.test.ts +57 -0
- package/src/media/image-models.ts +66 -0
- package/src/memory/__tests__/auto-analysis-enqueue.test.ts +38 -0
- package/src/memory/__tests__/find-most-recent-retrospective-for.test.ts +12 -2
- package/src/memory/__tests__/memory-retrospective-job.test.ts +911 -34
- package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +227 -5
- package/src/memory/__tests__/memory-retrospective-state.test.ts +195 -0
- package/src/memory/__tests__/preloaded-apps.test.ts +85 -0
- package/src/memory/auto-analysis-enqueue.ts +14 -1
- package/src/memory/compaction-log-store-clickhouse.ts +6 -4
- package/src/memory/conversation-crud.ts +9 -2
- package/src/memory/conversation-disk-view.ts +1 -1
- package/src/memory/conversation-queries.ts +22 -7
- package/src/memory/db-init.ts +20 -0
- package/src/memory/db-maintenance.ts +16 -0
- package/src/memory/embedding-runtime-manager.ts +1 -1
- package/src/memory/llm-request-log-source-clickhouse.ts +112 -14
- package/src/memory/llm-request-log-source-local.ts +19 -1
- package/src/memory/llm-request-log-source.ts +35 -6
- package/src/memory/llm-request-log-store.ts +90 -2
- package/src/memory/memory-retrospective-constants.ts +9 -0
- package/src/memory/memory-retrospective-enqueue.ts +3 -6
- package/src/memory/memory-retrospective-fork-boundary.ts +94 -0
- package/src/memory/memory-retrospective-job.ts +500 -208
- package/src/memory/memory-retrospective-startup-cleanup.ts +97 -19
- package/src/memory/memory-retrospective-state.ts +85 -2
- package/src/memory/migrations/281-memory-retrospective-remembered-log.ts +40 -0
- package/src/memory/migrations/282-schedule-inference-profile.test.ts +77 -0
- package/src/memory/migrations/282-schedule-inference-profile.ts +26 -0
- package/src/memory/migrations/283-memory-v3-selections-message-id-and-sections.test.ts +102 -0
- package/src/memory/migrations/283-memory-v3-selections-message-id-and-sections.ts +53 -0
- package/src/memory/migrations/284-workflow-runs.ts +51 -0
- package/src/memory/migrations/285-schedule-workflow-mode.ts +26 -0
- package/src/memory/migrations/286-workflow-run-trust.ts +27 -0
- package/src/memory/migrations/287-conversation-origin-channel-index.ts +15 -0
- package/src/memory/migrations/288-backfill-origin-channel-from-bindings.ts +43 -0
- package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +115 -0
- package/src/memory/migrations/290-schedule-capabilities.test.ts +77 -0
- package/src/memory/migrations/290-schedule-capabilities.ts +25 -0
- package/src/memory/migrations/__tests__/281-memory-retrospective-remembered-log.test.ts +96 -0
- package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +571 -0
- package/src/memory/migrations/index.ts +10 -0
- package/src/memory/preloaded-apps.ts +116 -0
- package/src/memory/schema/infrastructure.ts +4 -0
- package/src/memory/schema/memory-core.ts +4 -0
- package/src/memory/v2/__tests__/concept-page-frontmatter-schema.test.ts +45 -0
- package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +11 -7
- package/src/memory/v2/__tests__/page-store.test.ts +13 -2
- package/src/memory/v2/__tests__/qdrant.test.ts +24 -0
- package/src/memory/v2/frontmatter-sweep.ts +7 -6
- package/src/memory/v2/page-store.ts +4 -3
- package/src/memory/v2/qdrant.ts +42 -3
- package/src/memory/v2/types.ts +16 -10
- package/src/messaging/draft-store.ts +1 -1
- package/src/notifications/access-request-copy.ts +200 -113
- package/src/notifications/adapters/slack.ts +250 -111
- package/src/notifications/adapters/telegram.ts +7 -44
- package/src/notifications/approval-card-builder.ts +93 -0
- package/src/notifications/broadcaster.ts +74 -0
- package/src/notifications/conversation-pairing.ts +8 -6
- package/src/notifications/copy-composer.ts +32 -26
- package/src/notifications/decision-engine.ts +59 -7
- package/src/notifications/guardian-question-mode.ts +145 -155
- package/src/notifications/home-feed-side-effect.ts +28 -11
- package/src/notifications/notification-utils.ts +66 -0
- package/src/notifications/signal.ts +6 -0
- package/src/notifications/tool-approval-copy.ts +142 -0
- package/src/notifications/types.ts +19 -0
- package/src/permissions/threshold.ts +11 -0
- package/src/plugin-api/types.ts +16 -4
- package/src/plugins/defaults/compaction/window-manager.ts +44 -0
- package/src/plugins/defaults/index.ts +46 -0
- package/src/plugins/defaults/max-tokens-continue/continue-state-store.ts +53 -0
- package/src/plugins/defaults/max-tokens-continue/hooks/post-model-call.ts +80 -0
- package/src/plugins/defaults/max-tokens-continue/hooks/stop.ts +20 -0
- package/src/plugins/defaults/max-tokens-continue/package.json +14 -0
- package/src/plugins/defaults/memory-retrieval/hooks/__tests__/user-prompt-submit.test.ts +37 -0
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +29 -1
- package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +8 -3
- package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +4 -2
- package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +28 -18
- package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +67 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +122 -22
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +2 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +63 -1
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +61 -18
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +1 -1
- package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +39 -10
- package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +34 -1
- package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +112 -47
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +78 -15
- package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +206 -0
- package/src/plugins/defaults/task-progress-nudge/package.json +15 -0
- package/src/prompts/__tests__/system-prompt.test.ts +100 -1
- package/src/prompts/__tests__/task-progress-hint-section.test.ts +5 -7
- package/src/prompts/normalize-onboarding.ts +2 -0
- package/src/prompts/persona-resolver.ts +3 -0
- package/src/prompts/system-prompt.ts +51 -2
- package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +3 -1
- package/src/prompts/templates/system-sections.ts +8 -3
- package/src/providers/call-site-routing.ts +6 -3
- package/src/providers/fireworks/client.ts +3 -0
- package/src/providers/inference/auth.ts +52 -46
- package/src/providers/model-intents.ts +2 -2
- package/src/providers/openai/__tests__/coerce-object-args.test.ts +105 -0
- package/src/providers/openai/chat-completions-provider.ts +47 -9
- package/src/providers/openai/coerce-object-args.ts +104 -0
- package/src/providers/retry.ts +8 -5
- package/src/providers/types.ts +10 -0
- package/src/runtime/__tests__/agent-wake.test.ts +629 -7
- package/src/runtime/access-request-helper.ts +16 -9
- package/src/runtime/agent-wake.ts +302 -51
- package/src/runtime/assistant-stream-state.ts +141 -8
- package/src/runtime/background-job-runner.ts +9 -0
- package/src/runtime/channel-approval-types.ts +1 -0
- package/src/runtime/channel-invite-transports/telegram.ts +6 -5
- package/src/runtime/channel-invite-transports/voice.ts +2 -2
- package/src/runtime/channel-invite-types.ts +4 -2
- package/src/runtime/channel-retry-sweep.ts +19 -41
- package/src/runtime/finalize-event-delivery.ts +72 -0
- package/src/runtime/guardian-action-message-composer.ts +0 -54
- package/src/runtime/http-server.ts +6 -14
- package/src/runtime/http-types.ts +0 -1
- package/src/runtime/message-composer-types.ts +0 -9
- package/src/runtime/middleware/__tests__/rate-limiter.test.ts +63 -0
- package/src/runtime/middleware/auth.ts +27 -3
- package/src/runtime/middleware/rate-limiter.ts +28 -1
- package/src/runtime/migrations/vbundle-builder.ts +6 -5
- package/src/runtime/pending-interactions.ts +20 -1
- package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +232 -173
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +18 -0
- package/src/runtime/routes/__tests__/retrospective-routes.test.ts +436 -0
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +11 -0
- package/src/runtime/routes/approval-routes.ts +35 -8
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -1
- package/src/runtime/routes/btw-routes.ts +37 -1
- package/src/runtime/routes/channel-delivery-routes.ts +11 -7
- package/src/runtime/routes/channel-route-definitions.ts +3 -0
- package/src/runtime/routes/channel-route-shared.ts +3 -1
- package/src/runtime/routes/consolidation-routes.ts +17 -13
- package/src/runtime/routes/conversation-compaction-routes.ts +159 -119
- package/src/runtime/routes/conversation-list-routes.ts +41 -4
- package/src/runtime/routes/conversation-query-routes.ts +196 -11
- package/src/runtime/routes/conversation-routes.ts +15 -1
- package/src/runtime/routes/credential-prompt-routes.ts +39 -17
- package/src/runtime/routes/empty-state-greeting-cache.ts +65 -0
- package/src/runtime/routes/heartbeat-routes.ts +18 -13
- package/src/runtime/routes/image-generation-routes.ts +20 -2
- package/src/runtime/routes/inbound-message-handler.ts +13 -12
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +32 -31
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +7 -5
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +7 -21
- package/src/runtime/routes/inbound-stages/escalation-intercept.ts +5 -5
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +6 -15
- package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +1 -1
- package/src/runtime/routes/index.ts +6 -0
- package/src/runtime/routes/log-export/AGENTS.md +1 -1
- package/src/runtime/routes/log-export/workspace-allowlist.ts +1 -1
- package/src/runtime/routes/migration-routes.ts +5 -9
- package/src/runtime/routes/plugins-routes.ts +26 -0
- package/src/runtime/routes/ps-routes.ts +10 -8
- package/src/runtime/routes/retrospective-routes.ts +235 -0
- package/src/runtime/routes/runs-pagination.ts +75 -0
- package/src/runtime/routes/schedule-routes.ts +367 -59
- package/src/runtime/routes/sounds-config-routes.ts +239 -0
- package/src/runtime/routes/surface-action-routes.ts +84 -4
- package/src/runtime/routes/workflow-routes.test.ts +372 -0
- package/src/runtime/routes/workflow-routes.ts +363 -0
- package/src/runtime/routes/workspace-routes.test.ts +61 -1
- package/src/runtime/routes/workspace-routes.ts +26 -1
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +38 -0
- package/src/runtime/services/analyze-conversation.ts +26 -13
- package/src/schedule/inference-profile.ts +28 -0
- package/src/schedule/schedule-store.ts +152 -4
- package/src/schedule/scheduler-types.ts +6 -0
- package/src/schedule/scheduler.ts +96 -0
- package/src/security/secret-allowlist.ts +1 -1
- package/src/skills/path-classifier.ts +1 -1
- package/src/tools/apps/executors.ts +23 -0
- package/src/tools/apps/resolve-app-id.ts +42 -0
- package/src/tools/browser/browser-execution.ts +9 -11
- package/src/tools/credentials/broker.ts +4 -4
- package/src/tools/credentials/vault.ts +26 -137
- package/src/tools/executor.ts +69 -0
- package/src/tools/flag-gated-tools.test.ts +76 -0
- package/src/tools/permission-checker.ts +8 -1
- package/src/tools/registry.ts +51 -0
- package/src/tools/schedule/create.ts +77 -1
- package/src/tools/schedule/list.ts +1 -0
- package/src/tools/schedule/update.ts +73 -1
- package/src/tools/skills/execute.ts +56 -0
- package/src/tools/terminal/shell.ts +1 -1
- package/src/tools/ui-surface/definitions.ts +91 -2
- package/src/tools/workflows/manage-workflows.ts +183 -0
- package/src/tools/workflows/run-workflow.test.ts +442 -0
- package/src/tools/workflows/run-workflow.ts +88 -0
- package/src/types/onboarding-context.ts +2 -0
- package/src/usage/attribution.ts +24 -0
- package/src/util/canonicalize-identity.ts +12 -3
- package/src/util/platform.ts +17 -17
- package/src/watcher/__tests__/engine.test.ts +24 -0
- package/src/watcher/__tests__/telemetry.test.ts +135 -0
- package/src/watcher/engine.ts +7 -0
- package/src/watcher/telemetry.ts +74 -0
- package/src/workflows/capabilities.test.ts +365 -0
- package/src/workflows/capabilities.ts +359 -0
- package/src/workflows/deterministic-stringify.ts +27 -0
- package/src/workflows/engine-integration.test.ts +656 -0
- package/src/workflows/engine.test.ts +1144 -0
- package/src/workflows/engine.ts +1078 -0
- package/src/workflows/fanout-load.test.ts +168 -0
- package/src/workflows/journal-store.test.ts +369 -0
- package/src/workflows/journal-store.ts +470 -0
- package/src/workflows/leaf-runner.test.ts +704 -0
- package/src/workflows/leaf-runner.ts +589 -0
- package/src/workflows/library.test.ts +134 -0
- package/src/workflows/library.ts +124 -0
- package/src/workflows/run-manager.test.ts +711 -0
- package/src/workflows/run-manager.ts +593 -0
- package/src/workflows/sandbox-escape.test.ts +339 -0
- package/src/workflows/sandbox.test.ts +251 -0
- package/src/workflows/sandbox.ts +447 -0
- package/src/workspace/adaptive-thinking-repair.ts +33 -11
- package/src/workspace/migrations/021-move-signals-to-workspace.ts +1 -1
- package/src/workspace/migrations/022-move-hooks-to-workspace.ts +1 -1
- package/src/workspace/migrations/026-backfill-install-meta.ts +1 -1
- package/src/workspace/migrations/030-seed-pkb-autoinject.ts +2 -1
- package/src/workspace/migrations/031-drop-user-md.ts +1 -4
- package/src/workspace/migrations/048-remove-workspace-hooks.ts +1 -1
- package/src/workspace/migrations/056-release-notes-inference-profile-reordering.ts +5 -2
- package/src/workspace/migrations/061-move-backup-key-to-workspace.ts +1 -1
- package/src/workspace/migrations/082-backfill-managed-profile-labels.ts +8 -2
- package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +41 -14
- package/src/workspace/migrations/102-preserve-heartbeat-enabled-for-existing-workspaces.ts +69 -0
- package/src/workspace/migrations/103-upgrade-quality-profile-to-opus-4-8.ts +83 -0
- package/src/workspace/migrations/104-recheck-adaptive-thinking-model-implied-anthropic.ts +133 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/src/workspace/migrations/runner.ts +1 -1
- package/tsconfig.json +1 -1
- package/src/__tests__/guardian-action-copy-generator.test.ts +0 -200
- package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -579
- package/src/__tests__/guardian-action-store.test.ts +0 -106
- package/src/daemon/guardian-action-generators.ts +0 -71
- package/src/memory/guardian-action-store.ts +0 -484
- package/src/runtime/guardian-action-grant-minter.ts +0 -150
|
@@ -0,0 +1,593 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lifecycle layer for the workflow orchestration engine.
|
|
3
|
+
*
|
|
4
|
+
* The {@link WorkflowRunManager} is the surface the workflow tool, scheduler,
|
|
5
|
+
* and routes (later PRs) drive. It owns everything the raw {@link executeWorkflow}
|
|
6
|
+
* engine deliberately does NOT:
|
|
7
|
+
*
|
|
8
|
+
* - **Feature-flag gate.** `start` hard-fails with {@link WorkflowsDisabledError}
|
|
9
|
+
* when the `workflows` flag is off, BEFORE any engine code path is reachable.
|
|
10
|
+
* - **Concurrent-run cap.** At most `config.workflows.maxConcurrentRuns` runs
|
|
11
|
+
* may be in flight; the (N+1)th `start` throws {@link WorkflowRunCapError}.
|
|
12
|
+
* - **Async launch.** `start` resolves capabilities, creates the journal run
|
|
13
|
+
* row, kicks off {@link executeWorkflow} WITHOUT awaiting it, and returns the
|
|
14
|
+
* `runId` immediately. Progress and completion are surfaced out-of-band.
|
|
15
|
+
* - **Progress + completion events.** The engine's `onProgress` callback is
|
|
16
|
+
* republished as `workflow_progress` server messages; on completion a
|
|
17
|
+
* `workflow_completed` message is published. Both go through the shared
|
|
18
|
+
* `broadcastMessage` / event hub.
|
|
19
|
+
* - **Completion injection.** On completion, a human-readable summary is
|
|
20
|
+
* surfaced back into the originating conversation via
|
|
21
|
+
* {@link wakeAgentForOpportunity} — the same mechanism scheduled tasks and
|
|
22
|
+
* background shell jobs use to report finished background work to a
|
|
23
|
+
* conversation.
|
|
24
|
+
*
|
|
25
|
+
* `status` and `list` are thin reads over the journal store (the engine flushes
|
|
26
|
+
* live counts to the run row, so `getRun` is the single source of truth).
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
30
|
+
|
|
31
|
+
import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
|
|
32
|
+
import { getConfig } from "../config/loader.js";
|
|
33
|
+
import type { AssistantConfig } from "../config/schema.js";
|
|
34
|
+
import {
|
|
35
|
+
FALLBACK_TURN_TRUST,
|
|
36
|
+
type TrustContext,
|
|
37
|
+
} from "../daemon/trust-context.js";
|
|
38
|
+
import { wakeAgentForOpportunity } from "../runtime/agent-wake.js";
|
|
39
|
+
import { broadcastMessage } from "../runtime/assistant-event-hub.js";
|
|
40
|
+
import { getLogger } from "../util/logger.js";
|
|
41
|
+
import type { CapabilityManifest } from "./capabilities.js";
|
|
42
|
+
import {
|
|
43
|
+
normalizeCapabilityManifest,
|
|
44
|
+
resolveCapabilities,
|
|
45
|
+
} from "./capabilities.js";
|
|
46
|
+
import {
|
|
47
|
+
executeWorkflow,
|
|
48
|
+
extractWorkflowMeta,
|
|
49
|
+
WorkflowNotFoundError,
|
|
50
|
+
} from "./engine.js";
|
|
51
|
+
import type { WorkflowRun } from "./journal-store.js";
|
|
52
|
+
import * as journalStore from "./journal-store.js";
|
|
53
|
+
import { runLeaf } from "./leaf-runner.js";
|
|
54
|
+
import * as library from "./library.js";
|
|
55
|
+
|
|
56
|
+
const log = getLogger("workflow-run-manager");
|
|
57
|
+
|
|
58
|
+
/** Source tag for the completion wake (shows up in the wake's structured log). */
|
|
59
|
+
const WORKFLOW_WAKE_SOURCE = "workflow_completed";
|
|
60
|
+
|
|
61
|
+
/** Thrown by `start` when the `workflows` feature flag is disabled. */
|
|
62
|
+
export class WorkflowsDisabledError extends Error {
|
|
63
|
+
readonly code = "workflows_disabled" as const;
|
|
64
|
+
constructor() {
|
|
65
|
+
super("Workflows are not enabled.");
|
|
66
|
+
this.name = "WorkflowsDisabledError";
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Thrown by `start` when the concurrent-run cap is already reached. */
|
|
71
|
+
export class WorkflowRunCapError extends Error {
|
|
72
|
+
readonly code = "workflow_run_cap_exceeded" as const;
|
|
73
|
+
constructor(readonly limit: number) {
|
|
74
|
+
super(
|
|
75
|
+
`Cannot start workflow run: ${limit} run(s) are already in flight ` +
|
|
76
|
+
`(maxConcurrentRuns).`,
|
|
77
|
+
);
|
|
78
|
+
this.name = "WorkflowRunCapError";
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Thrown by `resume` when the target run cannot be resumed: it does not exist,
|
|
84
|
+
* is already in a terminal-but-not-resumable state, or is currently in flight.
|
|
85
|
+
* Only an `interrupted` run (a `running` row reconciled at startup after a
|
|
86
|
+
* crash) is resumable.
|
|
87
|
+
*/
|
|
88
|
+
export class WorkflowResumeNotPossibleError extends Error {
|
|
89
|
+
readonly code = "workflow_resume_not_possible" as const;
|
|
90
|
+
constructor(
|
|
91
|
+
readonly runId: string,
|
|
92
|
+
readonly reason: "not_found" | "not_interrupted" | "in_flight",
|
|
93
|
+
readonly status?: WorkflowRun["status"],
|
|
94
|
+
) {
|
|
95
|
+
super(
|
|
96
|
+
reason === "not_found"
|
|
97
|
+
? `Workflow run ${runId} not found.`
|
|
98
|
+
: reason === "in_flight"
|
|
99
|
+
? `Workflow run ${runId} is already in flight.`
|
|
100
|
+
: `Workflow run ${runId} is not resumable (status: ${status}). ` +
|
|
101
|
+
`Only interrupted runs can be resumed.`,
|
|
102
|
+
);
|
|
103
|
+
this.name = "WorkflowResumeNotPossibleError";
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Start a run from EITHER an inline `scriptSource` OR a saved workflow `name`
|
|
109
|
+
* (exactly one). When `name` is given the source is resolved from the saved
|
|
110
|
+
* workflow library; an unknown name throws {@link WorkflowNotFoundError}.
|
|
111
|
+
*/
|
|
112
|
+
export type StartWorkflowOptions = StartWorkflowCommon &
|
|
113
|
+
(
|
|
114
|
+
| { scriptSource: string; name?: undefined }
|
|
115
|
+
| { name: string; scriptSource?: undefined }
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
interface StartWorkflowCommon {
|
|
119
|
+
/** Verbatim run input, exposed to the script as `args`. */
|
|
120
|
+
args: unknown;
|
|
121
|
+
/** Per-run capability declaration (the single consent point). */
|
|
122
|
+
manifest: CapabilityManifest;
|
|
123
|
+
/**
|
|
124
|
+
* Conversation that originated the run; receives the completion summary via
|
|
125
|
+
* an agent wake. Omit for runs with no originating conversation (e.g. a
|
|
126
|
+
* scheduled run with no chat surface) — the summary is then events-only.
|
|
127
|
+
*/
|
|
128
|
+
conversationId?: string;
|
|
129
|
+
/** Human-readable label for display; defaults to the run id. */
|
|
130
|
+
label?: string;
|
|
131
|
+
/** Trust/auth context forwarded to every leaf. */
|
|
132
|
+
trustContext: TrustContext;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Dependencies injected for testing. Production callers omit this entirely and
|
|
137
|
+
* rely on the real engine, leaf runner, journal store, config, and wake.
|
|
138
|
+
*/
|
|
139
|
+
export interface WorkflowRunManagerDeps {
|
|
140
|
+
executeWorkflow: typeof executeWorkflow;
|
|
141
|
+
leafRunner: typeof runLeaf;
|
|
142
|
+
journal: typeof journalStore;
|
|
143
|
+
getConfig: () => AssistantConfig;
|
|
144
|
+
isFlagEnabled: (config: AssistantConfig) => boolean;
|
|
145
|
+
wake: typeof wakeAgentForOpportunity;
|
|
146
|
+
broadcast: typeof broadcastMessage;
|
|
147
|
+
newRunId: () => string;
|
|
148
|
+
/** Resolve a saved workflow's source by name (for `start({ name })`). */
|
|
149
|
+
getWorkflow: typeof library.getWorkflow;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function defaultDeps(): WorkflowRunManagerDeps {
|
|
153
|
+
return {
|
|
154
|
+
executeWorkflow,
|
|
155
|
+
leafRunner: runLeaf,
|
|
156
|
+
journal: journalStore,
|
|
157
|
+
getConfig,
|
|
158
|
+
isFlagEnabled: (config) =>
|
|
159
|
+
isAssistantFeatureFlagEnabled("workflows", config),
|
|
160
|
+
wake: wakeAgentForOpportunity,
|
|
161
|
+
broadcast: broadcastMessage,
|
|
162
|
+
newRunId: () => randomUUID(),
|
|
163
|
+
getWorkflow: library.getWorkflow,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Owns the lifecycle of workflow runs. One instance per daemon (see
|
|
169
|
+
* {@link getWorkflowRunManager}); tests construct their own with injected deps.
|
|
170
|
+
*/
|
|
171
|
+
export class WorkflowRunManager {
|
|
172
|
+
private readonly deps: WorkflowRunManagerDeps;
|
|
173
|
+
/** runId → AbortController for every currently in-flight run. */
|
|
174
|
+
private readonly inflight = new Map<string, AbortController>();
|
|
175
|
+
|
|
176
|
+
constructor(deps?: Partial<WorkflowRunManagerDeps>) {
|
|
177
|
+
this.deps = { ...defaultDeps(), ...deps };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Launch a workflow run. Gates on the `workflows` flag and the concurrent-run
|
|
182
|
+
* cap (both throw before any engine code is reachable), resolves capabilities,
|
|
183
|
+
* creates the journal run row, and kicks off {@link executeWorkflow}
|
|
184
|
+
* asynchronously. Returns the `runId` immediately — completion is surfaced via
|
|
185
|
+
* events and a conversation wake.
|
|
186
|
+
*/
|
|
187
|
+
start(opts: StartWorkflowOptions): { runId: string } {
|
|
188
|
+
const config = this.deps.getConfig();
|
|
189
|
+
if (!this.deps.isFlagEnabled(config)) {
|
|
190
|
+
throw new WorkflowsDisabledError();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const limit = config.workflows.maxConcurrentRuns;
|
|
194
|
+
if (this.inflight.size >= limit) {
|
|
195
|
+
throw new WorkflowRunCapError(limit);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Resolve the script source: either inline (`scriptSource`) or by name from
|
|
199
|
+
// the saved-workflow library (`name`). An unknown saved name throws
|
|
200
|
+
// synchronously, leaving no orphaned `running` row behind.
|
|
201
|
+
const scriptSource = this.resolveScriptSource(opts);
|
|
202
|
+
|
|
203
|
+
// Resolve capabilities and extract meta BEFORE creating the run row so a
|
|
204
|
+
// manifest authoring error (unknown/forbidden tool) or a malformed script
|
|
205
|
+
// `meta` surfaces synchronously to the caller and leaves no orphaned
|
|
206
|
+
// `running` row behind.
|
|
207
|
+
const capabilities = resolveCapabilities(opts.manifest);
|
|
208
|
+
const meta = extractWorkflowMeta(scriptSource);
|
|
209
|
+
|
|
210
|
+
const runId = this.deps.newRunId();
|
|
211
|
+
const label = opts.label ?? meta.name;
|
|
212
|
+
|
|
213
|
+
// Create the row up front (so `status`/`list` see it immediately) with the
|
|
214
|
+
// same name + script hash the engine would compute, so its idempotent
|
|
215
|
+
// re-open of the existing row is a true no-op rather than a divergent
|
|
216
|
+
// overwrite. We persist the MANIFEST (a plain serializable grant), not the
|
|
217
|
+
// resolved capabilities (which carry live Tool objects that don't survive a
|
|
218
|
+
// round-trip), so a later `resume` can re-resolve it deterministically. We
|
|
219
|
+
// also persist the originating TRUST CONTEXT (trust metadata, no secret
|
|
220
|
+
// material) so a crash-orphaned `resume` reconstructs the exact trust class
|
|
221
|
+
// the run started under — NEVER escalating to guardian on resume.
|
|
222
|
+
this.deps.journal.createRun({
|
|
223
|
+
id: runId,
|
|
224
|
+
name: meta.name,
|
|
225
|
+
scriptSource,
|
|
226
|
+
scriptHash: createHash("sha256").update(scriptSource).digest("hex"),
|
|
227
|
+
args: opts.args,
|
|
228
|
+
capabilities: opts.manifest,
|
|
229
|
+
status: "running",
|
|
230
|
+
conversationId: opts.conversationId ?? null,
|
|
231
|
+
trust: opts.trustContext,
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
const controller = new AbortController();
|
|
235
|
+
this.inflight.set(runId, controller);
|
|
236
|
+
|
|
237
|
+
// Fire-and-forget: the engine owns its own try/catch and always finishes
|
|
238
|
+
// the run row. We never await it — `start` must return synchronously.
|
|
239
|
+
void this.runToCompletion(
|
|
240
|
+
runId,
|
|
241
|
+
label,
|
|
242
|
+
scriptSource,
|
|
243
|
+
capabilities,
|
|
244
|
+
controller,
|
|
245
|
+
{
|
|
246
|
+
args: opts.args,
|
|
247
|
+
conversationId: opts.conversationId,
|
|
248
|
+
trustContext: opts.trustContext,
|
|
249
|
+
},
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
return { runId };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Mark every `running` run `interrupted` at daemon startup. A row left in
|
|
257
|
+
* `running` means the process died mid-run (the engine always finishes its
|
|
258
|
+
* row on exit, so nothing else leaves one `running`). This makes the run
|
|
259
|
+
* eligible for an explicit {@link resume}; it does NOT auto-resume (that would
|
|
260
|
+
* be surprising and possibly unsafe). STATUS ONLY — accounting counters are
|
|
261
|
+
* never touched (see {@link journalStore.markRunningAsInterrupted}). Returns
|
|
262
|
+
* the number of runs reconciled.
|
|
263
|
+
*/
|
|
264
|
+
reconcileOrphanedRuns(): number {
|
|
265
|
+
return this.deps.journal.markRunningAsInterrupted();
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Resume an `interrupted` run with the SAME `runId`, re-invoking the engine so
|
|
270
|
+
* it replays the journaled prefix and continues from the first changed/new
|
|
271
|
+
* leaf. Reconstructs the run's inputs from the persisted row (script source,
|
|
272
|
+
* args, capability manifest, conversation) and the trust context from the
|
|
273
|
+
* persisted `trust` snapshot — so a resumed run NEVER runs with more trust
|
|
274
|
+
* than it started under. Legacy rows with no persisted trust fall back to the
|
|
275
|
+
* low-trust {@link FALLBACK_TURN_TRUST} (never guardian). Enforces the
|
|
276
|
+
* `workflows` flag and the concurrent-run cap (a resume occupies a run slot).
|
|
277
|
+
* Returns the `runId` immediately — completion is surfaced via events and a
|
|
278
|
+
* conversation wake exactly like {@link start}.
|
|
279
|
+
*
|
|
280
|
+
* Throws {@link WorkflowResumeNotPossibleError} when the run is missing, not
|
|
281
|
+
* `interrupted`, or already in flight.
|
|
282
|
+
*/
|
|
283
|
+
resume(runId: string): { runId: string } {
|
|
284
|
+
const config = this.deps.getConfig();
|
|
285
|
+
if (!this.deps.isFlagEnabled(config)) {
|
|
286
|
+
throw new WorkflowsDisabledError();
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (this.inflight.has(runId)) {
|
|
290
|
+
throw new WorkflowResumeNotPossibleError(runId, "in_flight");
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const run = this.deps.journal.getRun(runId);
|
|
294
|
+
if (!run) {
|
|
295
|
+
throw new WorkflowResumeNotPossibleError(runId, "not_found");
|
|
296
|
+
}
|
|
297
|
+
// Gate to `interrupted`: only a startup-reconciled crash leaves a resumable
|
|
298
|
+
// row. A `running` row that is NOT in our in-flight map is a stale row from
|
|
299
|
+
// a still-booting/other process — refuse rather than racing it.
|
|
300
|
+
if (run.status !== "interrupted") {
|
|
301
|
+
throw new WorkflowResumeNotPossibleError(
|
|
302
|
+
runId,
|
|
303
|
+
"not_interrupted",
|
|
304
|
+
run.status,
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const limit = config.workflows.maxConcurrentRuns;
|
|
309
|
+
if (this.inflight.size >= limit) {
|
|
310
|
+
throw new WorkflowRunCapError(limit);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Re-resolve capabilities from the persisted manifest (re-resolution throws
|
|
314
|
+
// synchronously on an unknown/forbidden tool, leaving the row interrupted).
|
|
315
|
+
// The engine re-validates the script's `meta` on re-invoke, so we don't
|
|
316
|
+
// re-extract it here — the persisted `name` is the display label.
|
|
317
|
+
const capabilities = resolveCapabilities(
|
|
318
|
+
normalizeCapabilityManifest(run.capabilities),
|
|
319
|
+
);
|
|
320
|
+
const label = run.name ?? runId;
|
|
321
|
+
const trustContext = reconstructResumeTrustContext(run.trust);
|
|
322
|
+
|
|
323
|
+
// Flip back to `running` before launching so `status`/`list` reflect the
|
|
324
|
+
// in-flight resume; the engine re-opens the same row idempotently.
|
|
325
|
+
this.deps.journal.updateRun(runId, { status: "running" });
|
|
326
|
+
|
|
327
|
+
const controller = new AbortController();
|
|
328
|
+
this.inflight.set(runId, controller);
|
|
329
|
+
|
|
330
|
+
void this.runToCompletion(
|
|
331
|
+
runId,
|
|
332
|
+
label,
|
|
333
|
+
run.scriptSource,
|
|
334
|
+
capabilities,
|
|
335
|
+
controller,
|
|
336
|
+
{
|
|
337
|
+
args: run.args,
|
|
338
|
+
conversationId: run.conversationId ?? undefined,
|
|
339
|
+
trustContext,
|
|
340
|
+
},
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
return { runId };
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Resolve the run's script source from `StartWorkflowOptions`: returns
|
|
348
|
+
* `opts.scriptSource` verbatim, or — when `name` is given — the source of the
|
|
349
|
+
* matching saved workflow. Throws {@link WorkflowNotFoundError} if the name
|
|
350
|
+
* does not resolve.
|
|
351
|
+
*/
|
|
352
|
+
private resolveScriptSource(opts: StartWorkflowOptions): string {
|
|
353
|
+
if (opts.scriptSource !== undefined) return opts.scriptSource;
|
|
354
|
+
const saved = this.deps.getWorkflow(opts.name);
|
|
355
|
+
if (!saved) throw new WorkflowNotFoundError(opts.name);
|
|
356
|
+
return saved.source;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/** Abort an in-flight run by signalling its {@link AbortController}. No-op for unknown/finished runs. */
|
|
360
|
+
abort(runId: string): void {
|
|
361
|
+
this.inflight.get(runId)?.abort();
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/** Read the current run row (live counts + status), or null if unknown. */
|
|
365
|
+
status(runId: string): WorkflowRun | null {
|
|
366
|
+
return this.deps.journal.getRun(runId);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/** List runs newest-first, optionally filtered by status. */
|
|
370
|
+
list(options?: {
|
|
371
|
+
limit?: number;
|
|
372
|
+
status?: WorkflowRun["status"];
|
|
373
|
+
}): WorkflowRun[] {
|
|
374
|
+
return this.deps.journal.listRuns({
|
|
375
|
+
limit: options?.limit ?? 50,
|
|
376
|
+
...(options?.status ? { status: options.status } : {}),
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/** Number of currently in-flight runs (for tests / diagnostics). */
|
|
381
|
+
inflightCount(): number {
|
|
382
|
+
return this.inflight.size;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Drive a single run to completion: stream progress events, await the engine,
|
|
387
|
+
* publish completion, and inject the summary into the originating
|
|
388
|
+
* conversation. Always clears the in-flight slot in a finally block so a cap
|
|
389
|
+
* slot is never leaked even if the engine throws (it shouldn't — it owns its
|
|
390
|
+
* own error handling — but defense in depth).
|
|
391
|
+
*/
|
|
392
|
+
private async runToCompletion(
|
|
393
|
+
runId: string,
|
|
394
|
+
label: string,
|
|
395
|
+
scriptSource: string,
|
|
396
|
+
capabilities: ReturnType<typeof resolveCapabilities>,
|
|
397
|
+
controller: AbortController,
|
|
398
|
+
ctx: RunContext,
|
|
399
|
+
): Promise<void> {
|
|
400
|
+
const config = this.deps.getConfig();
|
|
401
|
+
try {
|
|
402
|
+
const result = await this.deps.executeWorkflow({
|
|
403
|
+
runId,
|
|
404
|
+
scriptSource,
|
|
405
|
+
args: ctx.args,
|
|
406
|
+
capabilities,
|
|
407
|
+
config: config.workflows,
|
|
408
|
+
journal: this.deps.journal,
|
|
409
|
+
leafRunner: this.deps.leafRunner,
|
|
410
|
+
trustContext: ctx.trustContext,
|
|
411
|
+
signal: controller.signal,
|
|
412
|
+
onProgress: (event) => {
|
|
413
|
+
const run = this.deps.journal.getRun(runId);
|
|
414
|
+
const agentsSpawned = run?.agentsSpawned ?? 0;
|
|
415
|
+
this.deps.broadcast({
|
|
416
|
+
type: "workflow_progress",
|
|
417
|
+
runId,
|
|
418
|
+
label,
|
|
419
|
+
agentsSpawned,
|
|
420
|
+
...(event.type === "phase"
|
|
421
|
+
? { phase: event.title }
|
|
422
|
+
: { message: event.message }),
|
|
423
|
+
});
|
|
424
|
+
},
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
const summary = buildCompletionSummary(
|
|
428
|
+
runId,
|
|
429
|
+
label,
|
|
430
|
+
result,
|
|
431
|
+
this.deps.journal.getRun(runId),
|
|
432
|
+
);
|
|
433
|
+
|
|
434
|
+
this.deps.broadcast({
|
|
435
|
+
type: "workflow_completed",
|
|
436
|
+
runId,
|
|
437
|
+
status: result.status,
|
|
438
|
+
agentsSpawned: result.agentsSpawned,
|
|
439
|
+
inputTokens: result.inputTokens,
|
|
440
|
+
outputTokens: result.outputTokens,
|
|
441
|
+
summary,
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
await this.injectCompletionSummary(ctx, summary);
|
|
445
|
+
} catch (err) {
|
|
446
|
+
// The engine finishes the run row even on internal errors, so this only
|
|
447
|
+
// fires for an unexpected throw (e.g. a bug in the manager glue). Log and
|
|
448
|
+
// move on — the in-flight slot is released in finally.
|
|
449
|
+
log.error({ err, runId }, "Workflow run manager: run threw unexpectedly");
|
|
450
|
+
} finally {
|
|
451
|
+
this.inflight.delete(runId);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Surface the completion summary back into the originating conversation,
|
|
457
|
+
* REUSING {@link wakeAgentForOpportunity} — the canonical "background job
|
|
458
|
+
* finished, tell this conversation" path (shared with scheduled tasks and
|
|
459
|
+
* background shell tools). Best-effort: a wake failure is logged, never
|
|
460
|
+
* thrown (the run already completed; events already fired).
|
|
461
|
+
*/
|
|
462
|
+
private async injectCompletionSummary(
|
|
463
|
+
ctx: RunContext,
|
|
464
|
+
summary: string,
|
|
465
|
+
): Promise<void> {
|
|
466
|
+
if (!ctx.conversationId) return;
|
|
467
|
+
try {
|
|
468
|
+
await this.deps.wake({
|
|
469
|
+
conversationId: ctx.conversationId,
|
|
470
|
+
hint: summary,
|
|
471
|
+
source: WORKFLOW_WAKE_SOURCE,
|
|
472
|
+
trustContext: ctx.trustContext,
|
|
473
|
+
});
|
|
474
|
+
} catch (err) {
|
|
475
|
+
log.warn(
|
|
476
|
+
{ err, conversationId: ctx.conversationId },
|
|
477
|
+
"Workflow run manager: completion wake failed (non-fatal)",
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* The per-run inputs `runToCompletion` needs, decoupled from how the run was
|
|
485
|
+
* launched. Both {@link WorkflowRunManager.start} (from `StartWorkflowOptions`)
|
|
486
|
+
* and {@link WorkflowRunManager.resume} (from the persisted run row) build one.
|
|
487
|
+
*/
|
|
488
|
+
interface RunContext {
|
|
489
|
+
args: unknown;
|
|
490
|
+
conversationId: string | undefined;
|
|
491
|
+
trustContext: TrustContext;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/** The trust classes a persisted trust snapshot may legitimately carry. */
|
|
495
|
+
const VALID_TRUST_CLASSES: ReadonlySet<TrustContext["trustClass"]> = new Set([
|
|
496
|
+
"guardian",
|
|
497
|
+
"trusted_contact",
|
|
498
|
+
"unknown",
|
|
499
|
+
]);
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Reconstruct the {@link TrustContext} for a resumed run from its persisted
|
|
503
|
+
* `trust` snapshot. This is the security boundary on resume: a run must NEVER
|
|
504
|
+
* resume with more trust than it started under. When the snapshot is absent
|
|
505
|
+
* (legacy rows written before the `trust_json` column existed) or unusable
|
|
506
|
+
* (unparseable JSON, or a missing/unrecognized `trustClass`), we fall back to
|
|
507
|
+
* the low-trust {@link FALLBACK_TURN_TRUST} — biased to `unknown` — matching the
|
|
508
|
+
* start path's no-elevation discipline. We deliberately do NOT fall back to the
|
|
509
|
+
* internal guardian context, which would clear the side-effect approval gate.
|
|
510
|
+
*/
|
|
511
|
+
function reconstructResumeTrustContext(persisted: unknown): TrustContext {
|
|
512
|
+
if (persisted && typeof persisted === "object") {
|
|
513
|
+
const candidate = persisted as Partial<TrustContext>;
|
|
514
|
+
if (
|
|
515
|
+
typeof candidate.trustClass === "string" &&
|
|
516
|
+
VALID_TRUST_CLASSES.has(candidate.trustClass)
|
|
517
|
+
) {
|
|
518
|
+
// The snapshot is a well-formed trust context — replay it verbatim.
|
|
519
|
+
return candidate as TrustContext;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
return FALLBACK_TURN_TRUST;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* Build the human-readable completion summary injected into the originating
|
|
527
|
+
* conversation and carried on the `workflow_completed` event. Reports the run's
|
|
528
|
+
* terminal status, agent/token counts, and a result-or-error tail.
|
|
529
|
+
*/
|
|
530
|
+
function buildCompletionSummary(
|
|
531
|
+
runId: string,
|
|
532
|
+
label: string,
|
|
533
|
+
result: Awaited<ReturnType<typeof executeWorkflow>>,
|
|
534
|
+
run: WorkflowRun | null,
|
|
535
|
+
): string {
|
|
536
|
+
const lines = [
|
|
537
|
+
`[workflow "${label}" ${result.status}]`,
|
|
538
|
+
`Run ${runId} finished with status: ${result.status}.`,
|
|
539
|
+
`Agents spawned: ${result.agentsSpawned}. ` +
|
|
540
|
+
`Tokens: ${result.inputTokens} in / ${result.outputTokens} out.`,
|
|
541
|
+
];
|
|
542
|
+
if (result.status === "completed") {
|
|
543
|
+
lines.push(
|
|
544
|
+
`Result: ${truncateForSummary(stringifyResult(result.result), runId)}`,
|
|
545
|
+
);
|
|
546
|
+
} else {
|
|
547
|
+
lines.push(`Outcome: ${run?.error ?? result.status}`);
|
|
548
|
+
}
|
|
549
|
+
return lines.join("\n");
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Max characters of a workflow result echoed into the completion summary. The
|
|
554
|
+
* summary rides on the `workflow_completed` SSE event AND is injected into the
|
|
555
|
+
* originating conversation via a wake, so an unbounded fan-out result (a large
|
|
556
|
+
* array/object/string) could blow the event or prompt size limit. The full,
|
|
557
|
+
* untruncated result is retained on the durable run row.
|
|
558
|
+
*/
|
|
559
|
+
const MAX_SUMMARY_RESULT_CHARS = 2000;
|
|
560
|
+
|
|
561
|
+
/** Bound a result tail for the summary; the full value stays on the run row. */
|
|
562
|
+
function truncateForSummary(s: string, runId: string): string {
|
|
563
|
+
if (s.length <= MAX_SUMMARY_RESULT_CHARS) return s;
|
|
564
|
+
const omitted = s.length - MAX_SUMMARY_RESULT_CHARS;
|
|
565
|
+
return (
|
|
566
|
+
`${s.slice(0, MAX_SUMMARY_RESULT_CHARS)}… ` +
|
|
567
|
+
`[truncated ${omitted} chars — fetch the full result with manage_workflows ` +
|
|
568
|
+
`{ action: "get_result", run_id: "${runId}" }]`
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/** Compact a workflow result for the summary — JSON for objects, string otherwise. */
|
|
573
|
+
function stringifyResult(value: unknown): string {
|
|
574
|
+
if (value == null) return "(no result)";
|
|
575
|
+
if (typeof value === "string") return value;
|
|
576
|
+
try {
|
|
577
|
+
return JSON.stringify(value);
|
|
578
|
+
} catch {
|
|
579
|
+
return String(value);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// ── Process-level singleton ───────────────────────────────────────────────
|
|
584
|
+
|
|
585
|
+
let _instance: WorkflowRunManager | null = null;
|
|
586
|
+
|
|
587
|
+
/** Singleton {@link WorkflowRunManager} shared across the daemon. */
|
|
588
|
+
export function getWorkflowRunManager(): WorkflowRunManager {
|
|
589
|
+
if (!_instance) {
|
|
590
|
+
_instance = new WorkflowRunManager();
|
|
591
|
+
}
|
|
592
|
+
return _instance;
|
|
593
|
+
}
|