@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,372 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the default `task-progress-nudge` plugin's `post-tool-use` hook.
|
|
3
|
+
*
|
|
4
|
+
* Covers:
|
|
5
|
+
* - Stays silent below the round threshold; nudges once a multi-step turn
|
|
6
|
+
* reaches it with no task_progress card shown.
|
|
7
|
+
* - Never nudges when a task_progress card was already shown this turn.
|
|
8
|
+
* - Fires at most once per turn (dedupes parallel results / further rounds).
|
|
9
|
+
* - Resets across turns so a later multi-step turn can nudge again.
|
|
10
|
+
* - Gating: fires only for weak models (Kimi/DeepSeek/MiniMax) and skips
|
|
11
|
+
* capable models, non-mainAgent call sites, surface-incapable clients, and
|
|
12
|
+
* subagent conversations.
|
|
13
|
+
* - Appends to (not overwrites) `additionalContext` set by an earlier hook.
|
|
14
|
+
* - The nudge leaves the tool result's `content` untouched.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
18
|
+
|
|
19
|
+
import * as realConversationRegistry from "../daemon/conversation-registry.js";
|
|
20
|
+
|
|
21
|
+
// The hook lazily imports the conversation registry and subagent manager on
|
|
22
|
+
// the would-nudge path; mock both before importing the hook. The registry mock
|
|
23
|
+
// spreads the real module so other consumers in the shared test process keep
|
|
24
|
+
// their exports — only `findConversation` is overridden.
|
|
25
|
+
interface MockConversation {
|
|
26
|
+
currentCallSite?: string;
|
|
27
|
+
currentTurnChannelCapabilities?: {
|
|
28
|
+
channel: string;
|
|
29
|
+
supportsDynamicUi: boolean;
|
|
30
|
+
};
|
|
31
|
+
channelCapabilities?: { channel: string; supportsDynamicUi: boolean };
|
|
32
|
+
}
|
|
33
|
+
let mockConversation: (
|
|
34
|
+
conversationId: string,
|
|
35
|
+
) => MockConversation | undefined = () => ({
|
|
36
|
+
currentCallSite: "mainAgent",
|
|
37
|
+
currentTurnChannelCapabilities: { channel: "web", supportsDynamicUi: true },
|
|
38
|
+
});
|
|
39
|
+
mock.module("../daemon/conversation-registry.js", () => ({
|
|
40
|
+
...realConversationRegistry,
|
|
41
|
+
findConversation: (conversationId: string) =>
|
|
42
|
+
mockConversation(conversationId),
|
|
43
|
+
}));
|
|
44
|
+
|
|
45
|
+
let mockParentInfo: (conversationId: string) => unknown = () => undefined;
|
|
46
|
+
mock.module("../subagent/index.js", () => ({
|
|
47
|
+
getSubagentManager: () => ({
|
|
48
|
+
getParentInfo: (conversationId: string) => mockParentInfo(conversationId),
|
|
49
|
+
}),
|
|
50
|
+
}));
|
|
51
|
+
|
|
52
|
+
import type { PluginLogger, PostToolUseContext } from "../plugin-api/types.js";
|
|
53
|
+
import postToolUse, {
|
|
54
|
+
resetTaskProgressNudgeStateForTests,
|
|
55
|
+
TASK_PROGRESS_NUDGE_ROUND_THRESHOLD,
|
|
56
|
+
TASK_PROGRESS_NUDGE_TEXT,
|
|
57
|
+
} from "../plugins/defaults/task-progress-nudge/hooks/post-tool-use.js";
|
|
58
|
+
import type { Message, ToolResultContent } from "../providers/types.js";
|
|
59
|
+
|
|
60
|
+
const noopLogger: PluginLogger = {
|
|
61
|
+
info: () => {},
|
|
62
|
+
warn: () => {},
|
|
63
|
+
error: () => {},
|
|
64
|
+
debug: () => {},
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
let conversationCounter = 0;
|
|
68
|
+
function freshConversationId(): string {
|
|
69
|
+
conversationCounter++;
|
|
70
|
+
return `conv-nudge-test-${conversationCounter}`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function toolUseTurn(
|
|
74
|
+
id: string,
|
|
75
|
+
name: string,
|
|
76
|
+
input: Record<string, unknown> = {},
|
|
77
|
+
): Message {
|
|
78
|
+
return {
|
|
79
|
+
role: "assistant",
|
|
80
|
+
content: [{ type: "tool_use", id, name, input }],
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function toolResultTurn(toolUseId: string): Message {
|
|
85
|
+
return {
|
|
86
|
+
role: "user",
|
|
87
|
+
content: [
|
|
88
|
+
{
|
|
89
|
+
type: "tool_result",
|
|
90
|
+
tool_use_id: toolUseId,
|
|
91
|
+
content: "ok",
|
|
92
|
+
is_error: false,
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function currentResponse(toolUseId: string): ToolResultContent {
|
|
99
|
+
return {
|
|
100
|
+
type: "tool_result",
|
|
101
|
+
tool_use_id: toolUseId,
|
|
102
|
+
content: "result body",
|
|
103
|
+
is_error: false,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** A weak-model id that matches WEAK_MODEL_PATTERN (the gated population). */
|
|
108
|
+
const WEAK_MODEL = "minimax/minimax-m3";
|
|
109
|
+
|
|
110
|
+
function makeCtx(
|
|
111
|
+
conversationId: string,
|
|
112
|
+
toolResponse: ToolResultContent,
|
|
113
|
+
messages: Message[],
|
|
114
|
+
model: string = WEAK_MODEL,
|
|
115
|
+
): PostToolUseContext {
|
|
116
|
+
return {
|
|
117
|
+
conversationId,
|
|
118
|
+
toolResponse,
|
|
119
|
+
messages,
|
|
120
|
+
additionalContext: null,
|
|
121
|
+
model,
|
|
122
|
+
maxInputTokens: 10_000,
|
|
123
|
+
logger: noopLogger,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Build a turn of `rounds` tool-use rounds. The last round's tool_use has no
|
|
129
|
+
* result in history (it arrives via `ctx.toolResponse`). When `showCardAtRound`
|
|
130
|
+
* is set, that round issues a `ui_show` task_progress card instead of a plain
|
|
131
|
+
* tool.
|
|
132
|
+
*/
|
|
133
|
+
function turnWithRounds(
|
|
134
|
+
rounds: number,
|
|
135
|
+
opts: { showCardAtRound?: number } = {},
|
|
136
|
+
): { messages: Message[]; currentToolUseId: string } {
|
|
137
|
+
const messages: Message[] = [
|
|
138
|
+
{ role: "user", content: [{ type: "text", text: "build the thing" }] },
|
|
139
|
+
];
|
|
140
|
+
for (let r = 1; r <= rounds; r++) {
|
|
141
|
+
const id = `tool-${r}`;
|
|
142
|
+
if (opts.showCardAtRound === r) {
|
|
143
|
+
messages.push(
|
|
144
|
+
toolUseTurn(id, "ui_show", {
|
|
145
|
+
surface_type: "card",
|
|
146
|
+
template: "task_progress",
|
|
147
|
+
templateData: { status: "in_progress", steps: [] },
|
|
148
|
+
}),
|
|
149
|
+
);
|
|
150
|
+
} else {
|
|
151
|
+
messages.push(toolUseTurn(id, "bash", { command: `cmd-${r}` }));
|
|
152
|
+
}
|
|
153
|
+
if (r < rounds) messages.push(toolResultTurn(id));
|
|
154
|
+
}
|
|
155
|
+
return { messages, currentToolUseId: `tool-${rounds}` };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
describe("task-progress-nudge post-tool-use hook", () => {
|
|
159
|
+
beforeEach(() => {
|
|
160
|
+
resetTaskProgressNudgeStateForTests();
|
|
161
|
+
mockConversation = () => ({
|
|
162
|
+
currentCallSite: "mainAgent",
|
|
163
|
+
currentTurnChannelCapabilities: {
|
|
164
|
+
channel: "web",
|
|
165
|
+
supportsDynamicUi: true,
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
mockParentInfo = () => undefined;
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("stays silent below the round threshold", async () => {
|
|
172
|
+
const { messages, currentToolUseId } = turnWithRounds(
|
|
173
|
+
TASK_PROGRESS_NUDGE_ROUND_THRESHOLD - 1,
|
|
174
|
+
);
|
|
175
|
+
const ctx = makeCtx(
|
|
176
|
+
freshConversationId(),
|
|
177
|
+
currentResponse(currentToolUseId),
|
|
178
|
+
messages,
|
|
179
|
+
);
|
|
180
|
+
await postToolUse(ctx);
|
|
181
|
+
expect(ctx.additionalContext).toBeNull();
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test("nudges once the turn reaches the threshold with no card shown", async () => {
|
|
185
|
+
const { messages, currentToolUseId } = turnWithRounds(
|
|
186
|
+
TASK_PROGRESS_NUDGE_ROUND_THRESHOLD,
|
|
187
|
+
);
|
|
188
|
+
const ctx = makeCtx(
|
|
189
|
+
freshConversationId(),
|
|
190
|
+
currentResponse(currentToolUseId),
|
|
191
|
+
messages,
|
|
192
|
+
);
|
|
193
|
+
await postToolUse(ctx);
|
|
194
|
+
expect(ctx.additionalContext).toBe(TASK_PROGRESS_NUDGE_TEXT);
|
|
195
|
+
// tool result content is untouched
|
|
196
|
+
expect(ctx.toolResponse.content).toBe("result body");
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test("never nudges when a task_progress card was already shown this turn", async () => {
|
|
200
|
+
const { messages, currentToolUseId } = turnWithRounds(
|
|
201
|
+
TASK_PROGRESS_NUDGE_ROUND_THRESHOLD + 2,
|
|
202
|
+
{ showCardAtRound: 1 },
|
|
203
|
+
);
|
|
204
|
+
const ctx = makeCtx(
|
|
205
|
+
freshConversationId(),
|
|
206
|
+
currentResponse(currentToolUseId),
|
|
207
|
+
messages,
|
|
208
|
+
);
|
|
209
|
+
await postToolUse(ctx);
|
|
210
|
+
expect(ctx.additionalContext).toBeNull();
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test("fires at most once per turn", async () => {
|
|
214
|
+
const conversationId = freshConversationId();
|
|
215
|
+
const first = turnWithRounds(TASK_PROGRESS_NUDGE_ROUND_THRESHOLD);
|
|
216
|
+
const ctx1 = makeCtx(
|
|
217
|
+
conversationId,
|
|
218
|
+
currentResponse(first.currentToolUseId),
|
|
219
|
+
first.messages,
|
|
220
|
+
);
|
|
221
|
+
await postToolUse(ctx1);
|
|
222
|
+
expect(ctx1.additionalContext).toBe(TASK_PROGRESS_NUDGE_TEXT);
|
|
223
|
+
|
|
224
|
+
// One more round, still no card — must not nudge again this turn.
|
|
225
|
+
const second = turnWithRounds(TASK_PROGRESS_NUDGE_ROUND_THRESHOLD + 1);
|
|
226
|
+
const ctx2 = makeCtx(
|
|
227
|
+
conversationId,
|
|
228
|
+
currentResponse(second.currentToolUseId),
|
|
229
|
+
second.messages,
|
|
230
|
+
);
|
|
231
|
+
await postToolUse(ctx2);
|
|
232
|
+
expect(ctx2.additionalContext).toBeNull();
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test("resets across turns so a later multi-step turn nudges again", async () => {
|
|
236
|
+
const conversationId = freshConversationId();
|
|
237
|
+
const first = turnWithRounds(TASK_PROGRESS_NUDGE_ROUND_THRESHOLD);
|
|
238
|
+
const ctx1 = makeCtx(
|
|
239
|
+
conversationId,
|
|
240
|
+
currentResponse(first.currentToolUseId),
|
|
241
|
+
first.messages,
|
|
242
|
+
);
|
|
243
|
+
await postToolUse(ctx1);
|
|
244
|
+
expect(ctx1.additionalContext).toBe(TASK_PROGRESS_NUDGE_TEXT);
|
|
245
|
+
|
|
246
|
+
// A brand-new turn (counting restarts low) below threshold — resets state.
|
|
247
|
+
const lull = turnWithRounds(1);
|
|
248
|
+
const ctxLull = makeCtx(
|
|
249
|
+
conversationId,
|
|
250
|
+
currentResponse(lull.currentToolUseId),
|
|
251
|
+
lull.messages,
|
|
252
|
+
);
|
|
253
|
+
await postToolUse(ctxLull);
|
|
254
|
+
expect(ctxLull.additionalContext).toBeNull();
|
|
255
|
+
|
|
256
|
+
// Another multi-step turn — should nudge again.
|
|
257
|
+
const third = turnWithRounds(TASK_PROGRESS_NUDGE_ROUND_THRESHOLD);
|
|
258
|
+
const ctx3 = makeCtx(
|
|
259
|
+
conversationId,
|
|
260
|
+
currentResponse(third.currentToolUseId),
|
|
261
|
+
third.messages,
|
|
262
|
+
);
|
|
263
|
+
await postToolUse(ctx3);
|
|
264
|
+
expect(ctx3.additionalContext).toBe(TASK_PROGRESS_NUDGE_TEXT);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
test("fires for weak models (Kimi, DeepSeek, MiniMax)", async () => {
|
|
268
|
+
for (const model of [
|
|
269
|
+
"moonshotai/kimi-k2.6",
|
|
270
|
+
"deepseek/deepseek-chat",
|
|
271
|
+
"accounts/fireworks/models/minimax-m3",
|
|
272
|
+
]) {
|
|
273
|
+
const { messages, currentToolUseId } = turnWithRounds(
|
|
274
|
+
TASK_PROGRESS_NUDGE_ROUND_THRESHOLD,
|
|
275
|
+
);
|
|
276
|
+
const ctx = makeCtx(
|
|
277
|
+
freshConversationId(),
|
|
278
|
+
currentResponse(currentToolUseId),
|
|
279
|
+
messages,
|
|
280
|
+
model,
|
|
281
|
+
);
|
|
282
|
+
await postToolUse(ctx);
|
|
283
|
+
expect(ctx.additionalContext).toBe(TASK_PROGRESS_NUDGE_TEXT);
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
test("skips capable models (not in the weak-model set)", async () => {
|
|
288
|
+
for (const model of ["claude-opus-4-8", "gpt-5.5"]) {
|
|
289
|
+
const { messages, currentToolUseId } = turnWithRounds(
|
|
290
|
+
TASK_PROGRESS_NUDGE_ROUND_THRESHOLD + 2,
|
|
291
|
+
);
|
|
292
|
+
const ctx = makeCtx(
|
|
293
|
+
freshConversationId(),
|
|
294
|
+
currentResponse(currentToolUseId),
|
|
295
|
+
messages,
|
|
296
|
+
model,
|
|
297
|
+
);
|
|
298
|
+
await postToolUse(ctx);
|
|
299
|
+
expect(ctx.additionalContext).toBeNull();
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
test("skips non-mainAgent call sites", async () => {
|
|
304
|
+
mockConversation = () => ({
|
|
305
|
+
currentCallSite: "wake",
|
|
306
|
+
currentTurnChannelCapabilities: {
|
|
307
|
+
channel: "web",
|
|
308
|
+
supportsDynamicUi: true,
|
|
309
|
+
},
|
|
310
|
+
});
|
|
311
|
+
const { messages, currentToolUseId } = turnWithRounds(
|
|
312
|
+
TASK_PROGRESS_NUDGE_ROUND_THRESHOLD,
|
|
313
|
+
);
|
|
314
|
+
const ctx = makeCtx(
|
|
315
|
+
freshConversationId(),
|
|
316
|
+
currentResponse(currentToolUseId),
|
|
317
|
+
messages,
|
|
318
|
+
);
|
|
319
|
+
await postToolUse(ctx);
|
|
320
|
+
expect(ctx.additionalContext).toBeNull();
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
test("skips clients without dynamic UI support", async () => {
|
|
324
|
+
mockConversation = () => ({
|
|
325
|
+
currentCallSite: "mainAgent",
|
|
326
|
+
currentTurnChannelCapabilities: {
|
|
327
|
+
channel: "telegram",
|
|
328
|
+
supportsDynamicUi: false,
|
|
329
|
+
},
|
|
330
|
+
});
|
|
331
|
+
const { messages, currentToolUseId } = turnWithRounds(
|
|
332
|
+
TASK_PROGRESS_NUDGE_ROUND_THRESHOLD,
|
|
333
|
+
);
|
|
334
|
+
const ctx = makeCtx(
|
|
335
|
+
freshConversationId(),
|
|
336
|
+
currentResponse(currentToolUseId),
|
|
337
|
+
messages,
|
|
338
|
+
);
|
|
339
|
+
await postToolUse(ctx);
|
|
340
|
+
expect(ctx.additionalContext).toBeNull();
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
test("skips subagent conversations", async () => {
|
|
344
|
+
mockParentInfo = () => ({ parentConversationId: "parent-1" });
|
|
345
|
+
const { messages, currentToolUseId } = turnWithRounds(
|
|
346
|
+
TASK_PROGRESS_NUDGE_ROUND_THRESHOLD,
|
|
347
|
+
);
|
|
348
|
+
const ctx = makeCtx(
|
|
349
|
+
freshConversationId(),
|
|
350
|
+
currentResponse(currentToolUseId),
|
|
351
|
+
messages,
|
|
352
|
+
);
|
|
353
|
+
await postToolUse(ctx);
|
|
354
|
+
expect(ctx.additionalContext).toBeNull();
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
test("appends to existing additionalContext rather than overwriting", async () => {
|
|
358
|
+
const { messages, currentToolUseId } = turnWithRounds(
|
|
359
|
+
TASK_PROGRESS_NUDGE_ROUND_THRESHOLD,
|
|
360
|
+
);
|
|
361
|
+
const ctx = makeCtx(
|
|
362
|
+
freshConversationId(),
|
|
363
|
+
currentResponse(currentToolUseId),
|
|
364
|
+
messages,
|
|
365
|
+
);
|
|
366
|
+
ctx.additionalContext = "<system_notice>prior coaching</system_notice>";
|
|
367
|
+
await postToolUse(ctx);
|
|
368
|
+
expect(ctx.additionalContext).toBe(
|
|
369
|
+
`<system_notice>prior coaching</system_notice>\n${TASK_PROGRESS_NUDGE_TEXT}`,
|
|
370
|
+
);
|
|
371
|
+
});
|
|
372
|
+
});
|
|
@@ -44,6 +44,22 @@ mock.module("../runtime/background-job-runner.js", () => ({
|
|
|
44
44
|
},
|
|
45
45
|
}));
|
|
46
46
|
|
|
47
|
+
// Capture workflow-mode dispatch. The scheduler's `workflow` branch calls
|
|
48
|
+
// `getWorkflowRunManager().start(...)`; we stub the singleton so each trigger
|
|
49
|
+
// is observable without spinning up the real engine.
|
|
50
|
+
const workflowStartCalls: Array<Record<string, unknown>> = [];
|
|
51
|
+
let workflowStartImpl: (opts: Record<string, unknown>) => { runId: string } = (
|
|
52
|
+
opts,
|
|
53
|
+
) => {
|
|
54
|
+
workflowStartCalls.push(opts);
|
|
55
|
+
return { runId: "wf-run-1" };
|
|
56
|
+
};
|
|
57
|
+
mock.module("../workflows/run-manager.js", () => ({
|
|
58
|
+
getWorkflowRunManager: () => ({
|
|
59
|
+
start: (opts: Record<string, unknown>) => workflowStartImpl(opts),
|
|
60
|
+
}),
|
|
61
|
+
}));
|
|
62
|
+
|
|
47
63
|
// Capture `emitNotificationSignal` calls so tests can assert that scheduled
|
|
48
64
|
// task failures surface via the notification pipeline (home feed + native).
|
|
49
65
|
const emitNotificationCalls: Array<Record<string, unknown>> = [];
|
|
@@ -60,11 +76,21 @@ mock.module("../notifications/emit-signal.js", () => ({
|
|
|
60
76
|
},
|
|
61
77
|
}));
|
|
62
78
|
|
|
79
|
+
// Control the tool-registry readiness gate the scheduler checks before launching
|
|
80
|
+
// workflow schedules. Default ready=true so the existing workflow-mode tests are
|
|
81
|
+
// unaffected; one test flips it to exercise the boot-time deferral path. Only the
|
|
82
|
+
// scheduler consumes registry in this test's graph, so a minimal mock suffices.
|
|
83
|
+
let coreToolsReady = true;
|
|
84
|
+
mock.module("../tools/registry.js", () => ({
|
|
85
|
+
areCoreToolsInitialized: () => coreToolsReady,
|
|
86
|
+
}));
|
|
87
|
+
|
|
63
88
|
import { getDb } from "../memory/db-connection.js";
|
|
64
89
|
import { initializeDb } from "../memory/db-init.js";
|
|
65
90
|
import { recordUsageEvent } from "../memory/llm-usage-store.js";
|
|
66
91
|
import {
|
|
67
92
|
createSchedule,
|
|
93
|
+
deferClaimedSchedule,
|
|
68
94
|
getSchedule,
|
|
69
95
|
getScheduleRuns,
|
|
70
96
|
} from "../schedule/schedule-store.js";
|
|
@@ -460,3 +486,276 @@ describe("scheduler run_task detection", () => {
|
|
|
460
486
|
});
|
|
461
487
|
});
|
|
462
488
|
});
|
|
489
|
+
|
|
490
|
+
// ── Workflow mode dispatch ──────────────────────────────────────────
|
|
491
|
+
|
|
492
|
+
describe("scheduler workflow mode", () => {
|
|
493
|
+
beforeEach(() => {
|
|
494
|
+
const db = getDb();
|
|
495
|
+
db.run("DELETE FROM cron_runs");
|
|
496
|
+
db.run("DELETE FROM cron_jobs");
|
|
497
|
+
workflowStartCalls.length = 0;
|
|
498
|
+
workflowStartImpl = (opts) => {
|
|
499
|
+
workflowStartCalls.push(opts);
|
|
500
|
+
return { runId: "wf-run-1" };
|
|
501
|
+
};
|
|
502
|
+
coreToolsReady = true;
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
test("a due workflow-mode job triggers the run manager with name/args", async () => {
|
|
506
|
+
const schedule = createSchedule({
|
|
507
|
+
name: "Triage inbox",
|
|
508
|
+
cronExpression: "0 9 * * *",
|
|
509
|
+
message: "triage",
|
|
510
|
+
syntax: "cron",
|
|
511
|
+
mode: "workflow",
|
|
512
|
+
workflowName: "triage-inbox",
|
|
513
|
+
workflowArgs: { folder: "primary" },
|
|
514
|
+
wakeConversationId: "conv-origin",
|
|
515
|
+
});
|
|
516
|
+
forceScheduleDue(schedule.id);
|
|
517
|
+
|
|
518
|
+
const result = await runScheduleDueWorkOnce(
|
|
519
|
+
async () => {},
|
|
520
|
+
() => {},
|
|
521
|
+
);
|
|
522
|
+
|
|
523
|
+
expect(result.completed).toBe(1);
|
|
524
|
+
expect(result.failed).toBe(0);
|
|
525
|
+
expect(workflowStartCalls).toHaveLength(1);
|
|
526
|
+
expect(workflowStartCalls[0]).toMatchObject({
|
|
527
|
+
name: "triage-inbox",
|
|
528
|
+
args: { folder: "primary" },
|
|
529
|
+
conversationId: "conv-origin",
|
|
530
|
+
manifest: { tools: [], hostFunctions: [], persona: false },
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
const runs = getScheduleRuns(schedule.id);
|
|
534
|
+
expect(runs).toHaveLength(1);
|
|
535
|
+
expect(runs[0].status).toBe("ok");
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
test("fires with the schedule's stored side-effecting manifest", async () => {
|
|
539
|
+
const schedule = createSchedule({
|
|
540
|
+
name: "Side-effecting workflow",
|
|
541
|
+
cronExpression: "0 9 * * *",
|
|
542
|
+
message: "go",
|
|
543
|
+
syntax: "cron",
|
|
544
|
+
mode: "workflow",
|
|
545
|
+
workflowName: "send-report",
|
|
546
|
+
capabilities: {
|
|
547
|
+
tools: ["file_write"],
|
|
548
|
+
hostFunctions: ["notify"],
|
|
549
|
+
persona: true,
|
|
550
|
+
},
|
|
551
|
+
});
|
|
552
|
+
forceScheduleDue(schedule.id);
|
|
553
|
+
|
|
554
|
+
await runScheduleDueWorkOnce(
|
|
555
|
+
async () => {},
|
|
556
|
+
() => {},
|
|
557
|
+
);
|
|
558
|
+
|
|
559
|
+
expect(workflowStartCalls).toHaveLength(1);
|
|
560
|
+
expect(workflowStartCalls[0]).toMatchObject({
|
|
561
|
+
manifest: {
|
|
562
|
+
tools: ["file_write"],
|
|
563
|
+
hostFunctions: ["notify"],
|
|
564
|
+
persona: true,
|
|
565
|
+
},
|
|
566
|
+
});
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
test("a legacy schedule (null capabilities) fires with the read-only baseline", async () => {
|
|
570
|
+
const schedule = createSchedule({
|
|
571
|
+
name: "Legacy workflow",
|
|
572
|
+
cronExpression: "0 9 * * *",
|
|
573
|
+
message: "go",
|
|
574
|
+
syntax: "cron",
|
|
575
|
+
mode: "workflow",
|
|
576
|
+
workflowName: "digest",
|
|
577
|
+
capabilities: null,
|
|
578
|
+
});
|
|
579
|
+
forceScheduleDue(schedule.id);
|
|
580
|
+
|
|
581
|
+
await runScheduleDueWorkOnce(
|
|
582
|
+
async () => {},
|
|
583
|
+
() => {},
|
|
584
|
+
);
|
|
585
|
+
|
|
586
|
+
expect(workflowStartCalls).toHaveLength(1);
|
|
587
|
+
expect(workflowStartCalls[0]).toMatchObject({
|
|
588
|
+
manifest: { tools: [], hostFunctions: [], persona: false },
|
|
589
|
+
});
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
test("falls back to createdFromConversationId for the completion wake", async () => {
|
|
593
|
+
// Workflow schedules created via schedule_create store the originating
|
|
594
|
+
// conversation as createdFromConversationId and leave wakeConversationId
|
|
595
|
+
// unset; without the fallback the completion summary lands nowhere.
|
|
596
|
+
const schedule = createSchedule({
|
|
597
|
+
name: "Morning digest",
|
|
598
|
+
cronExpression: "0 9 * * *",
|
|
599
|
+
message: "",
|
|
600
|
+
syntax: "cron",
|
|
601
|
+
mode: "workflow",
|
|
602
|
+
workflowName: "digest",
|
|
603
|
+
createdFromConversationId: "conv-creator",
|
|
604
|
+
});
|
|
605
|
+
forceScheduleDue(schedule.id);
|
|
606
|
+
|
|
607
|
+
await runScheduleDueWorkOnce(
|
|
608
|
+
async () => {},
|
|
609
|
+
() => {},
|
|
610
|
+
);
|
|
611
|
+
|
|
612
|
+
expect(workflowStartCalls).toHaveLength(1);
|
|
613
|
+
expect(workflowStartCalls[0]).toMatchObject({
|
|
614
|
+
conversationId: "conv-creator",
|
|
615
|
+
});
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
test("prefers an explicit wakeConversationId over createdFromConversationId", async () => {
|
|
619
|
+
const schedule = createSchedule({
|
|
620
|
+
name: "Both set",
|
|
621
|
+
cronExpression: "0 9 * * *",
|
|
622
|
+
message: "",
|
|
623
|
+
syntax: "cron",
|
|
624
|
+
mode: "workflow",
|
|
625
|
+
workflowName: "digest",
|
|
626
|
+
wakeConversationId: "conv-wake",
|
|
627
|
+
createdFromConversationId: "conv-creator",
|
|
628
|
+
});
|
|
629
|
+
forceScheduleDue(schedule.id);
|
|
630
|
+
|
|
631
|
+
await runScheduleDueWorkOnce(
|
|
632
|
+
async () => {},
|
|
633
|
+
() => {},
|
|
634
|
+
);
|
|
635
|
+
|
|
636
|
+
expect(workflowStartCalls[0]).toMatchObject({
|
|
637
|
+
conversationId: "conv-wake",
|
|
638
|
+
});
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
test("defaults workflowArgs to {} when unset", async () => {
|
|
642
|
+
const schedule = createSchedule({
|
|
643
|
+
name: "No-args workflow",
|
|
644
|
+
cronExpression: "0 9 * * *",
|
|
645
|
+
message: "go",
|
|
646
|
+
syntax: "cron",
|
|
647
|
+
mode: "workflow",
|
|
648
|
+
workflowName: "nightly",
|
|
649
|
+
});
|
|
650
|
+
forceScheduleDue(schedule.id);
|
|
651
|
+
|
|
652
|
+
await runScheduleDueWorkOnce(
|
|
653
|
+
async () => {},
|
|
654
|
+
() => {},
|
|
655
|
+
);
|
|
656
|
+
|
|
657
|
+
expect(workflowStartCalls).toHaveLength(1);
|
|
658
|
+
expect(workflowStartCalls[0].args).toEqual({});
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
test("records an error and skips when start() throws", async () => {
|
|
662
|
+
workflowStartImpl = () => {
|
|
663
|
+
throw new Error("workflows disabled");
|
|
664
|
+
};
|
|
665
|
+
const schedule = createSchedule({
|
|
666
|
+
name: "Failing workflow",
|
|
667
|
+
cronExpression: "0 9 * * *",
|
|
668
|
+
message: "go",
|
|
669
|
+
syntax: "cron",
|
|
670
|
+
mode: "workflow",
|
|
671
|
+
workflowName: "broken",
|
|
672
|
+
});
|
|
673
|
+
forceScheduleDue(schedule.id);
|
|
674
|
+
|
|
675
|
+
const result = await runScheduleDueWorkOnce(
|
|
676
|
+
async () => {},
|
|
677
|
+
() => {},
|
|
678
|
+
);
|
|
679
|
+
|
|
680
|
+
expect(result.failed).toBe(1);
|
|
681
|
+
const runs = getScheduleRuns(schedule.id);
|
|
682
|
+
expect(runs[0].status).toBe("error");
|
|
683
|
+
expect(runs[0].error).toContain("workflows disabled");
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
test("skips a workflow-mode job with no workflowName", async () => {
|
|
687
|
+
const schedule = createSchedule({
|
|
688
|
+
name: "Nameless workflow",
|
|
689
|
+
cronExpression: "0 9 * * *",
|
|
690
|
+
message: "go",
|
|
691
|
+
syntax: "cron",
|
|
692
|
+
mode: "workflow",
|
|
693
|
+
});
|
|
694
|
+
forceScheduleDue(schedule.id);
|
|
695
|
+
|
|
696
|
+
const result = await runScheduleDueWorkOnce(
|
|
697
|
+
async () => {},
|
|
698
|
+
() => {},
|
|
699
|
+
);
|
|
700
|
+
|
|
701
|
+
expect(result.skipped).toBe(1);
|
|
702
|
+
expect(workflowStartCalls).toHaveLength(0);
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
test("defers a due workflow job until tools are registered, then fires it", async () => {
|
|
706
|
+
coreToolsReady = false;
|
|
707
|
+
const schedule = createSchedule({
|
|
708
|
+
name: "Boot workflow",
|
|
709
|
+
cronExpression: "0 9 * * *",
|
|
710
|
+
message: "go",
|
|
711
|
+
syntax: "cron",
|
|
712
|
+
mode: "workflow",
|
|
713
|
+
workflowName: "triage-inbox",
|
|
714
|
+
});
|
|
715
|
+
forceScheduleDue(schedule.id);
|
|
716
|
+
|
|
717
|
+
// Tools not yet registered: the run must be deferred, not launched with an
|
|
718
|
+
// empty baseline. No run-manager start, no run record, marked skipped.
|
|
719
|
+
const result = await runScheduleDueWorkOnce(
|
|
720
|
+
async () => {},
|
|
721
|
+
() => {},
|
|
722
|
+
);
|
|
723
|
+
expect(workflowStartCalls).toHaveLength(0);
|
|
724
|
+
expect(result.skipped).toBe(1);
|
|
725
|
+
expect(getScheduleRuns(schedule.id)).toHaveLength(0);
|
|
726
|
+
|
|
727
|
+
// Re-armed to due (not consumed): once tools are ready, a later tick fires
|
|
728
|
+
// it with the full baseline.
|
|
729
|
+
coreToolsReady = true;
|
|
730
|
+
await runScheduleDueWorkOnce(
|
|
731
|
+
async () => {},
|
|
732
|
+
() => {},
|
|
733
|
+
);
|
|
734
|
+
expect(workflowStartCalls).toHaveLength(1);
|
|
735
|
+
expect(workflowStartCalls[0]).toMatchObject({ name: "triage-inbox" });
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
test("deferring a final/disabled occurrence re-enables it so it isn't lost", async () => {
|
|
739
|
+
// claimDueSchedules disables a row whose claimed occurrence was its last
|
|
740
|
+
// (one-shot / exhausted finite RRULE). The due-claim query requires
|
|
741
|
+
// enabled=true, so a deferred final occurrence must be re-enabled or it is
|
|
742
|
+
// never re-claimed. Simulate that disabled-on-claim state, then defer.
|
|
743
|
+
const schedule = createSchedule({
|
|
744
|
+
name: "Final workflow occurrence",
|
|
745
|
+
cronExpression: "0 9 * * *",
|
|
746
|
+
message: "",
|
|
747
|
+
syntax: "cron",
|
|
748
|
+
mode: "workflow",
|
|
749
|
+
workflowName: "digest",
|
|
750
|
+
enabled: false, // as claimDueSchedules leaves a last occurrence
|
|
751
|
+
});
|
|
752
|
+
expect(getSchedule(schedule.id)?.enabled).toBe(false);
|
|
753
|
+
|
|
754
|
+
deferClaimedSchedule(schedule.id);
|
|
755
|
+
|
|
756
|
+
const after = getSchedule(schedule.id);
|
|
757
|
+
expect(after?.enabled).toBe(true);
|
|
758
|
+
expect(after?.status).toBe("active");
|
|
759
|
+
expect(after?.nextRunAt).toBeLessThanOrEqual(Date.now());
|
|
760
|
+
});
|
|
761
|
+
});
|