@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,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OPT-IN, real-provider fan-out load & cost guard for the workflow engine.
|
|
3
|
+
*
|
|
4
|
+
* ─── Manual / opt-in only ───────────────────────────────────────────────────
|
|
5
|
+
* This suite is SKIPPED unless `ANTHROPIC_API_KEY` is set. When a key IS
|
|
6
|
+
* present it makes ~200 REAL leaf provider calls (one forced-schema
|
|
7
|
+
* `workflowLeaf` call per leaf), so it is never run in CI. Run it by hand
|
|
8
|
+
* when you want to validate the engine against the live provider:
|
|
9
|
+
*
|
|
10
|
+
* ANTHROPIC_API_KEY=sk-... bun test src/workflows/fanout-load.test.ts
|
|
11
|
+
*
|
|
12
|
+
* Cost: each leaf is a tiny Haiku-class (cost-optimized) structured-output
|
|
13
|
+
* call with a small per-leaf context. One full run is ~200 such calls and
|
|
14
|
+
* typically costs a few US cents (well under $0.25). The assertion band below
|
|
15
|
+
* caps it at $1 as a blunt runaway-cost tripwire — if a run ever approaches
|
|
16
|
+
* that, something is mis-resolved (wrong model/profile) and the guard should
|
|
17
|
+
* fail loudly.
|
|
18
|
+
*
|
|
19
|
+
* ─── What it guards against ─────────────────────────────────────────────────
|
|
20
|
+
* It reproduces the shape that previously caused rate-limit collapse: many
|
|
21
|
+
* concurrent leaves with non-trivial per-leaf context. The historical failure
|
|
22
|
+
* mode was a `StructuredOutput` fast-fail cluster — leaves returning null /
|
|
23
|
+
* zero output tokens en masse, sub-minute "completion", because the token/min
|
|
24
|
+
* ceiling was blown by an over-wide fan-out. At the default
|
|
25
|
+
* `maxConcurrentLeaves: 6` the engine throttles the fan-out, so the collapse
|
|
26
|
+
* must NOT recur. This test asserts that: every leaf returns real non-zero
|
|
27
|
+
* output, the run completes (not `cap_exceeded` / `failed`), wall-clock lands
|
|
28
|
+
* in a generous band, and summed cost stays within a sane envelope.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { describe, expect, test } from "bun:test";
|
|
32
|
+
|
|
33
|
+
import { z } from "zod";
|
|
34
|
+
|
|
35
|
+
import { getConfig } from "../config/loader.js";
|
|
36
|
+
import type { TrustContext } from "../daemon/trust-context.js";
|
|
37
|
+
import { initializeDb } from "../memory/db-init.js";
|
|
38
|
+
import { listUsageEvents } from "../memory/llm-usage-store.js";
|
|
39
|
+
import { resolveCapabilities } from "./capabilities.js";
|
|
40
|
+
import { executeWorkflow } from "./engine.js";
|
|
41
|
+
import * as journalStore from "./journal-store.js";
|
|
42
|
+
import { runLeaf } from "./leaf-runner.js";
|
|
43
|
+
|
|
44
|
+
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
45
|
+
|
|
46
|
+
// Number of concurrent leaves the historical collapse fanned out over; the
|
|
47
|
+
// guard runs the same shape at the engine's default concurrency.
|
|
48
|
+
const LEAF_COUNT = 200;
|
|
49
|
+
|
|
50
|
+
// Generous wall-clock ceiling. At concurrency 6 with ~200 short leaves this
|
|
51
|
+
// is well under a couple minutes in practice; the band only fails on a true
|
|
52
|
+
// stall, not on normal provider jitter.
|
|
53
|
+
const MAX_WALL_CLOCK_MS = 8 * 60_000;
|
|
54
|
+
|
|
55
|
+
// Runaway-cost tripwire (see header). A healthy run is a few cents.
|
|
56
|
+
const MAX_COST_USD = 1;
|
|
57
|
+
|
|
58
|
+
const TRUST: TrustContext = { sourceChannel: "vellum", trustClass: "guardian" };
|
|
59
|
+
|
|
60
|
+
describe.skipIf(!apiKey)(
|
|
61
|
+
"Workflow fan-out load & cost guard — real provider",
|
|
62
|
+
() => {
|
|
63
|
+
test(
|
|
64
|
+
"200-leaf fan-out at default concurrency does not rate-limit-collapse",
|
|
65
|
+
async () => {
|
|
66
|
+
initializeDb();
|
|
67
|
+
|
|
68
|
+
// Non-trivial per-leaf context mirrors the real fan-out shape: each
|
|
69
|
+
// leaf judges a distinct synthetic record, not a one-token prompt.
|
|
70
|
+
const items = Array.from({ length: LEAF_COUNT }, (_, i) => ({
|
|
71
|
+
id: i,
|
|
72
|
+
subject: `synthetic-record-${i}`,
|
|
73
|
+
body:
|
|
74
|
+
`Incident report #${i}. A background job processed batch ${i % 7} ` +
|
|
75
|
+
`with status code ${200 + (i % 5)} and emitted ${3 + (i % 11)} ` +
|
|
76
|
+
`warnings about resource contention on shard ${i % 13}. The ` +
|
|
77
|
+
`operator noted latency drift of ${(i % 9) * 4}ms over the window.`,
|
|
78
|
+
}));
|
|
79
|
+
|
|
80
|
+
// Schema-forced judgement per leaf: a small structured output that
|
|
81
|
+
// forces a real `workflowLeaf` provider call and a non-empty result.
|
|
82
|
+
const judgement = z.object({
|
|
83
|
+
severity: z.enum(["low", "medium", "high"]),
|
|
84
|
+
summary: z.string(),
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const scriptSource = `
|
|
88
|
+
export const meta = {
|
|
89
|
+
name: "fanout-load-guard",
|
|
90
|
+
description: "200-leaf real-provider fan-out load & cost guard",
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const results = map(args.items, (item) =>
|
|
94
|
+
leaf(
|
|
95
|
+
"Classify the severity of this incident and summarize it in one short " +
|
|
96
|
+
"sentence.\\n\\n" +
|
|
97
|
+
"Subject: " + item.subject + "\\n" +
|
|
98
|
+
"Body: " + item.body,
|
|
99
|
+
{ schema: args.schema, label: "judge-" + item.id },
|
|
100
|
+
),
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
return results;
|
|
104
|
+
`;
|
|
105
|
+
|
|
106
|
+
const runId = `fanout-load-${Date.now()}`;
|
|
107
|
+
const startedAt = Date.now();
|
|
108
|
+
|
|
109
|
+
const result = await executeWorkflow({
|
|
110
|
+
runId,
|
|
111
|
+
scriptSource,
|
|
112
|
+
args: { items, schema: judgement },
|
|
113
|
+
capabilities: resolveCapabilities({
|
|
114
|
+
tools: [],
|
|
115
|
+
hostFunctions: [],
|
|
116
|
+
persona: false,
|
|
117
|
+
}),
|
|
118
|
+
config: getConfig().workflows,
|
|
119
|
+
journal: journalStore,
|
|
120
|
+
leafRunner: runLeaf,
|
|
121
|
+
trustContext: TRUST,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const wallClockMs = Date.now() - startedAt;
|
|
125
|
+
|
|
126
|
+
// The engine must have throttled, not collapsed: the run completes.
|
|
127
|
+
expect(result.status).toBe("completed");
|
|
128
|
+
expect(result.agentsSpawned).toBe(LEAF_COUNT);
|
|
129
|
+
|
|
130
|
+
// No `StructuredOutput` fast-fail cluster: every leaf returns a real,
|
|
131
|
+
// schema-valid, non-null result (the collapse manifested as all-null).
|
|
132
|
+
const leafResults = result.result as Array<z.infer<
|
|
133
|
+
typeof judgement
|
|
134
|
+
> | null>;
|
|
135
|
+
expect(Array.isArray(leafResults)).toBe(true);
|
|
136
|
+
expect(leafResults).toHaveLength(LEAF_COUNT);
|
|
137
|
+
const nulls = leafResults.filter((r) => r == null);
|
|
138
|
+
expect(nulls).toHaveLength(0);
|
|
139
|
+
const invalid = leafResults.filter(
|
|
140
|
+
(r) => !judgement.safeParse(r).success,
|
|
141
|
+
);
|
|
142
|
+
expect(invalid).toHaveLength(0);
|
|
143
|
+
|
|
144
|
+
// Real, non-zero output tokens in aggregate (the collapse zeroed these
|
|
145
|
+
// out as leaves fast-failed before producing any tokens).
|
|
146
|
+
expect(result.outputTokens).toBeGreaterThan(0);
|
|
147
|
+
expect(result.inputTokens).toBeGreaterThan(0);
|
|
148
|
+
|
|
149
|
+
// Wall-clock within a generous band: not a sub-second mass-fail, and
|
|
150
|
+
// not an indefinite stall.
|
|
151
|
+
expect(wallClockMs).toBeLessThan(MAX_WALL_CLOCK_MS);
|
|
152
|
+
|
|
153
|
+
// Summed cost over this run's `workflowLeaf` usage events stays within
|
|
154
|
+
// a sane envelope. Filter by call site + the run's start time so we
|
|
155
|
+
// only sum events this run produced (the table is shared).
|
|
156
|
+
const events = listUsageEvents({ limit: 10_000 });
|
|
157
|
+
const runCostUsd = events
|
|
158
|
+
.filter(
|
|
159
|
+
(e) => e.callSite === "workflowLeaf" && e.createdAt >= startedAt,
|
|
160
|
+
)
|
|
161
|
+
.reduce((sum, e) => sum + (e.estimatedCostUsd ?? 0), 0);
|
|
162
|
+
expect(runCostUsd).toBeGreaterThan(0);
|
|
163
|
+
expect(runCostUsd).toBeLessThan(MAX_COST_USD);
|
|
164
|
+
},
|
|
165
|
+
MAX_WALL_CLOCK_MS + 60_000,
|
|
166
|
+
);
|
|
167
|
+
},
|
|
168
|
+
);
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
// Silence the logger.
|
|
4
|
+
mock.module("../util/logger.js", () => ({
|
|
5
|
+
getLogger: () =>
|
|
6
|
+
new Proxy({} as Record<string, unknown>, {
|
|
7
|
+
get: () => () => {},
|
|
8
|
+
}),
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
import { getDb, getSqlite } from "../memory/db-connection.js";
|
|
12
|
+
import { initializeDb } from "../memory/db-init.js";
|
|
13
|
+
import { migrateWorkflowRuns } from "../memory/migrations/284-workflow-runs.js";
|
|
14
|
+
import {
|
|
15
|
+
appendJournalEntry,
|
|
16
|
+
createRun,
|
|
17
|
+
finishRun,
|
|
18
|
+
getJournal,
|
|
19
|
+
getRun,
|
|
20
|
+
listRuns,
|
|
21
|
+
pruneRuns,
|
|
22
|
+
updateRun,
|
|
23
|
+
} from "./journal-store.js";
|
|
24
|
+
|
|
25
|
+
initializeDb();
|
|
26
|
+
|
|
27
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
28
|
+
|
|
29
|
+
function resetTables(): void {
|
|
30
|
+
getSqlite().exec("DELETE FROM workflow_journal");
|
|
31
|
+
getSqlite().exec("DELETE FROM workflow_runs");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe("workflow journal store", () => {
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
resetTables();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("the migration is idempotent (re-running is a no-op)", () => {
|
|
40
|
+
// initializeDb (called at module load) already ran the migration once.
|
|
41
|
+
// Running it directly a second and third time must not throw and must
|
|
42
|
+
// leave the tables intact and usable.
|
|
43
|
+
expect(() => migrateWorkflowRuns(getDb())).not.toThrow();
|
|
44
|
+
expect(() => migrateWorkflowRuns(getDb())).not.toThrow();
|
|
45
|
+
|
|
46
|
+
createRun({
|
|
47
|
+
id: "wf-idem",
|
|
48
|
+
scriptSource: "console.log(1)",
|
|
49
|
+
scriptHash: "h1",
|
|
50
|
+
});
|
|
51
|
+
expect(getRun("wf-idem")?.id).toBe("wf-idem");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("createRun round-trips all fields with sane defaults", () => {
|
|
55
|
+
const run = createRun({
|
|
56
|
+
id: "wf-1",
|
|
57
|
+
name: "nightly digest",
|
|
58
|
+
scriptSource: "await agent('x')",
|
|
59
|
+
scriptHash: "hash-abc",
|
|
60
|
+
args: { topic: "ai" },
|
|
61
|
+
capabilities: ["search", "email"],
|
|
62
|
+
conversationId: "conv-xyz",
|
|
63
|
+
trust: { sourceChannel: "slack", trustClass: "unknown" },
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
expect(run).toMatchObject({
|
|
67
|
+
id: "wf-1",
|
|
68
|
+
name: "nightly digest",
|
|
69
|
+
scriptSource: "await agent('x')",
|
|
70
|
+
scriptHash: "hash-abc",
|
|
71
|
+
args: { topic: "ai" },
|
|
72
|
+
capabilities: ["search", "email"],
|
|
73
|
+
status: "running",
|
|
74
|
+
conversationId: "conv-xyz",
|
|
75
|
+
trust: { sourceChannel: "slack", trustClass: "unknown" },
|
|
76
|
+
agentsSpawned: 0,
|
|
77
|
+
inputTokens: 0,
|
|
78
|
+
outputTokens: 0,
|
|
79
|
+
result: null,
|
|
80
|
+
error: null,
|
|
81
|
+
finishedAt: null,
|
|
82
|
+
});
|
|
83
|
+
expect(run.createdAt).toBeGreaterThan(0);
|
|
84
|
+
expect(run.updatedAt).toBeGreaterThan(0);
|
|
85
|
+
|
|
86
|
+
expect(getRun("wf-1")).toEqual(run);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("optional fields default to null", () => {
|
|
90
|
+
const run = createRun({
|
|
91
|
+
id: "wf-min",
|
|
92
|
+
scriptSource: "noop",
|
|
93
|
+
scriptHash: "h",
|
|
94
|
+
});
|
|
95
|
+
expect(run).toMatchObject({
|
|
96
|
+
name: null,
|
|
97
|
+
args: null,
|
|
98
|
+
capabilities: null,
|
|
99
|
+
conversationId: null,
|
|
100
|
+
trust: null,
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("updateRun patches only provided fields and bumps updated_at", () => {
|
|
105
|
+
const created = createRun({
|
|
106
|
+
id: "wf-2",
|
|
107
|
+
scriptSource: "s",
|
|
108
|
+
scriptHash: "h",
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const updated = updateRun("wf-2", {
|
|
112
|
+
status: "running",
|
|
113
|
+
agentsSpawned: 3,
|
|
114
|
+
inputTokens: 100,
|
|
115
|
+
outputTokens: 50,
|
|
116
|
+
conversationId: "conv-new",
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
expect(updated).toMatchObject({
|
|
120
|
+
agentsSpawned: 3,
|
|
121
|
+
inputTokens: 100,
|
|
122
|
+
outputTokens: 50,
|
|
123
|
+
conversationId: "conv-new",
|
|
124
|
+
// Untouched fields preserved.
|
|
125
|
+
scriptSource: "s",
|
|
126
|
+
scriptHash: "h",
|
|
127
|
+
});
|
|
128
|
+
expect(updated!.updatedAt!).toBeGreaterThanOrEqual(created.updatedAt!);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("updateRun returns null for an unknown run", () => {
|
|
132
|
+
expect(updateRun("nope", { status: "failed" })).toBeNull();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("finishRun stamps terminal status, result, and finished_at", () => {
|
|
136
|
+
createRun({ id: "wf-3", scriptSource: "s", scriptHash: "h" });
|
|
137
|
+
|
|
138
|
+
const finished = finishRun("wf-3", {
|
|
139
|
+
status: "completed",
|
|
140
|
+
result: { ok: true, count: 2 },
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
expect(finished).toMatchObject({
|
|
144
|
+
status: "completed",
|
|
145
|
+
result: { ok: true, count: 2 },
|
|
146
|
+
error: null,
|
|
147
|
+
});
|
|
148
|
+
expect(finished!.finishedAt).toBeGreaterThan(0);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("finishRun records failure error text", () => {
|
|
152
|
+
createRun({ id: "wf-fail", scriptSource: "s", scriptHash: "h" });
|
|
153
|
+
const finished = finishRun("wf-fail", {
|
|
154
|
+
status: "failed",
|
|
155
|
+
error: "boom",
|
|
156
|
+
});
|
|
157
|
+
expect(finished).toMatchObject({ status: "failed", error: "boom" });
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("appendJournalEntry + getJournal round-trips in seq order", () => {
|
|
161
|
+
createRun({ id: "wf-j", scriptSource: "s", scriptHash: "h" });
|
|
162
|
+
|
|
163
|
+
appendJournalEntry({
|
|
164
|
+
runId: "wf-j",
|
|
165
|
+
seq: 1,
|
|
166
|
+
callHash: "c1",
|
|
167
|
+
kind: "agent",
|
|
168
|
+
request: { prompt: "a" },
|
|
169
|
+
result: { text: "ra" },
|
|
170
|
+
status: "completed",
|
|
171
|
+
});
|
|
172
|
+
appendJournalEntry({
|
|
173
|
+
runId: "wf-j",
|
|
174
|
+
seq: 0,
|
|
175
|
+
callHash: "c0",
|
|
176
|
+
kind: "agent",
|
|
177
|
+
request: { prompt: "b" },
|
|
178
|
+
result: 123,
|
|
179
|
+
status: "completed",
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const journal = getJournal("wf-j");
|
|
183
|
+
expect(journal.map((e) => e.seq)).toEqual([0, 1]);
|
|
184
|
+
expect(journal[0]).toMatchObject({
|
|
185
|
+
runId: "wf-j",
|
|
186
|
+
seq: 0,
|
|
187
|
+
callHash: "c0",
|
|
188
|
+
kind: "agent",
|
|
189
|
+
request: { prompt: "b" },
|
|
190
|
+
result: 123,
|
|
191
|
+
status: "completed",
|
|
192
|
+
});
|
|
193
|
+
expect(journal[1]).toMatchObject({
|
|
194
|
+
kind: "agent",
|
|
195
|
+
request: { prompt: "a" },
|
|
196
|
+
result: { text: "ra" },
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test("appendJournalEntry is idempotent on (run_id, seq)", () => {
|
|
201
|
+
createRun({ id: "wf-dup", scriptSource: "s", scriptHash: "h" });
|
|
202
|
+
|
|
203
|
+
appendJournalEntry({
|
|
204
|
+
runId: "wf-dup",
|
|
205
|
+
seq: 0,
|
|
206
|
+
callHash: "c0",
|
|
207
|
+
kind: "agent",
|
|
208
|
+
status: "completed",
|
|
209
|
+
});
|
|
210
|
+
// Replayed append for the same seq must not double-insert.
|
|
211
|
+
appendJournalEntry({
|
|
212
|
+
runId: "wf-dup",
|
|
213
|
+
seq: 0,
|
|
214
|
+
callHash: "c0",
|
|
215
|
+
kind: "agent",
|
|
216
|
+
status: "completed",
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
expect(getJournal("wf-dup")).toHaveLength(1);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test("appendJournalEntry upserts a changed-hash re-run at the same seq", () => {
|
|
223
|
+
createRun({ id: "wf-resume", scriptSource: "s", scriptHash: "h" });
|
|
224
|
+
|
|
225
|
+
// First run: leaf at seq 0 produces one (hash, result).
|
|
226
|
+
appendJournalEntry({
|
|
227
|
+
runId: "wf-resume",
|
|
228
|
+
seq: 0,
|
|
229
|
+
callHash: "hash-old",
|
|
230
|
+
kind: "agent",
|
|
231
|
+
request: { prompt: "old" },
|
|
232
|
+
result: { text: "old-result" },
|
|
233
|
+
status: "completed",
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// Resume: the leaf's input CHANGED, so it re-runs and re-appends a new
|
|
237
|
+
// (hash, result) at the SAME seq. The stale row must be overwritten.
|
|
238
|
+
appendJournalEntry({
|
|
239
|
+
runId: "wf-resume",
|
|
240
|
+
seq: 0,
|
|
241
|
+
callHash: "hash-new",
|
|
242
|
+
kind: "agent",
|
|
243
|
+
request: { prompt: "new" },
|
|
244
|
+
result: { text: "new-result" },
|
|
245
|
+
status: "completed",
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
const journal = getJournal("wf-resume");
|
|
249
|
+
// Still exactly one row at seq 0 (no duplicate).
|
|
250
|
+
expect(journal).toHaveLength(1);
|
|
251
|
+
expect(journal[0]).toMatchObject({
|
|
252
|
+
seq: 0,
|
|
253
|
+
callHash: "hash-new",
|
|
254
|
+
request: { prompt: "new" },
|
|
255
|
+
result: { text: "new-result" },
|
|
256
|
+
status: "completed",
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
test("listRuns returns newest-first and honors limit + status filter", () => {
|
|
261
|
+
// created_at is wall-clock; force distinct ordering via direct timestamps.
|
|
262
|
+
createRun({ id: "old", scriptSource: "s", scriptHash: "h" });
|
|
263
|
+
getSqlite().exec(
|
|
264
|
+
"UPDATE workflow_runs SET created_at = 1000 WHERE id = 'old'",
|
|
265
|
+
);
|
|
266
|
+
createRun({ id: "mid", scriptSource: "s", scriptHash: "h" });
|
|
267
|
+
getSqlite().exec(
|
|
268
|
+
"UPDATE workflow_runs SET created_at = 2000 WHERE id = 'mid'",
|
|
269
|
+
);
|
|
270
|
+
createRun({ id: "new", scriptSource: "s", scriptHash: "h" });
|
|
271
|
+
getSqlite().exec(
|
|
272
|
+
"UPDATE workflow_runs SET created_at = 3000 WHERE id = 'new'",
|
|
273
|
+
);
|
|
274
|
+
finishRun("new", { status: "completed" });
|
|
275
|
+
|
|
276
|
+
expect(listRuns({ limit: 10 }).map((r) => r.id)).toEqual([
|
|
277
|
+
"new",
|
|
278
|
+
"mid",
|
|
279
|
+
"old",
|
|
280
|
+
]);
|
|
281
|
+
expect(listRuns({ limit: 1 }).map((r) => r.id)).toEqual(["new"]);
|
|
282
|
+
expect(
|
|
283
|
+
listRuns({ limit: 10, status: "completed" }).map((r) => r.id),
|
|
284
|
+
).toEqual(["new"]);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
test("pruneRuns deletes finished runs past retention but keeps running ones", () => {
|
|
288
|
+
const now = Date.now();
|
|
289
|
+
|
|
290
|
+
// Old finished run — should be pruned.
|
|
291
|
+
createRun({ id: "old-done", scriptSource: "s", scriptHash: "h" });
|
|
292
|
+
finishRun("old-done", { status: "completed" });
|
|
293
|
+
appendJournalEntry({
|
|
294
|
+
runId: "old-done",
|
|
295
|
+
seq: 0,
|
|
296
|
+
callHash: "c",
|
|
297
|
+
kind: "agent",
|
|
298
|
+
status: "completed",
|
|
299
|
+
});
|
|
300
|
+
getSqlite().exec(
|
|
301
|
+
`UPDATE workflow_runs SET created_at = ${now - 10 * DAY_MS} WHERE id = 'old-done'`,
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
// Old but still running — must be kept.
|
|
305
|
+
createRun({ id: "old-running", scriptSource: "s", scriptHash: "h" });
|
|
306
|
+
getSqlite().exec(
|
|
307
|
+
`UPDATE workflow_runs SET created_at = ${now - 10 * DAY_MS} WHERE id = 'old-running'`,
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
// Recent finished run — within retention, kept.
|
|
311
|
+
createRun({ id: "recent-done", scriptSource: "s", scriptHash: "h" });
|
|
312
|
+
finishRun("recent-done", { status: "completed" });
|
|
313
|
+
|
|
314
|
+
const deleted = pruneRuns(7);
|
|
315
|
+
expect(deleted).toBe(1);
|
|
316
|
+
expect(getRun("old-done")).toBeNull();
|
|
317
|
+
expect(getJournal("old-done")).toHaveLength(0);
|
|
318
|
+
expect(getRun("old-running")).not.toBeNull();
|
|
319
|
+
expect(getRun("recent-done")).not.toBeNull();
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
test("pruneRuns never reaps a resumable 'interrupted' run, even past retention", () => {
|
|
323
|
+
const now = Date.now();
|
|
324
|
+
|
|
325
|
+
// Old interrupted run (crash-orphaned, awaiting resume) with a journal —
|
|
326
|
+
// pruning it would destroy resumability, so it MUST be kept.
|
|
327
|
+
createRun({ id: "old-interrupted", scriptSource: "s", scriptHash: "h" });
|
|
328
|
+
updateRun("old-interrupted", { status: "interrupted" });
|
|
329
|
+
appendJournalEntry({
|
|
330
|
+
runId: "old-interrupted",
|
|
331
|
+
seq: 0,
|
|
332
|
+
callHash: "c",
|
|
333
|
+
kind: "agent",
|
|
334
|
+
status: "completed",
|
|
335
|
+
});
|
|
336
|
+
getSqlite().exec(
|
|
337
|
+
`UPDATE workflow_runs SET created_at = ${now - 100 * DAY_MS} WHERE id = 'old-interrupted'`,
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
const deleted = pruneRuns(7);
|
|
341
|
+
expect(deleted).toBe(0);
|
|
342
|
+
expect(getRun("old-interrupted")).not.toBeNull();
|
|
343
|
+
// Its journal survives too, so resume can still replay.
|
|
344
|
+
expect(getJournal("old-interrupted")).toHaveLength(1);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
test("pruneRuns reaps every TERMINAL status past retention", () => {
|
|
348
|
+
const now = Date.now();
|
|
349
|
+
const terminal = [
|
|
350
|
+
"completed",
|
|
351
|
+
"failed",
|
|
352
|
+
"aborted",
|
|
353
|
+
"cap_exceeded",
|
|
354
|
+
] as const;
|
|
355
|
+
for (const status of terminal) {
|
|
356
|
+
createRun({ id: `old-${status}`, scriptSource: "s", scriptHash: "h" });
|
|
357
|
+
finishRun(`old-${status}`, { status });
|
|
358
|
+
getSqlite().exec(
|
|
359
|
+
`UPDATE workflow_runs SET created_at = ${now - 10 * DAY_MS} WHERE id = 'old-${status}'`,
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const deleted = pruneRuns(7);
|
|
364
|
+
expect(deleted).toBe(terminal.length);
|
|
365
|
+
for (const status of terminal) {
|
|
366
|
+
expect(getRun(`old-${status}`)).toBeNull();
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
});
|