@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
|
@@ -18,6 +18,14 @@ mock.module("../config/loader.js", () => ({
|
|
|
18
18
|
timezone: null,
|
|
19
19
|
},
|
|
20
20
|
}),
|
|
21
|
+
getConfigReadOnly: () => ({
|
|
22
|
+
llm: {
|
|
23
|
+
profiles: {
|
|
24
|
+
balanced: { model: "test-model" },
|
|
25
|
+
"cost-optimized": { model: "test-model-small" },
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
}),
|
|
21
29
|
invalidateConfigCache: () => {},
|
|
22
30
|
loadRawConfig: () => ({}),
|
|
23
31
|
saveRawConfig: () => {},
|
|
@@ -76,7 +84,7 @@ import { recordUsageEvent } from "../memory/llm-usage-store.js";
|
|
|
76
84
|
import { rawRun } from "../memory/raw-query.js";
|
|
77
85
|
import type { AssistantEvent } from "../runtime/assistant-event.js";
|
|
78
86
|
import { assistantEventHub } from "../runtime/assistant-event-hub.js";
|
|
79
|
-
import { BadRequestError } from "../runtime/routes/errors.js";
|
|
87
|
+
import { BadRequestError, NotFoundError } from "../runtime/routes/errors.js";
|
|
80
88
|
import { ROUTES as HEARTBEAT_ROUTES } from "../runtime/routes/heartbeat-routes.js";
|
|
81
89
|
import { ROUTES as SCHEDULE_ROUTES } from "../runtime/routes/schedule-routes.js";
|
|
82
90
|
import type { RouteDefinition } from "../runtime/routes/types.js";
|
|
@@ -587,6 +595,71 @@ describe("GET /schedules — default defer exclusion", () => {
|
|
|
587
595
|
});
|
|
588
596
|
});
|
|
589
597
|
|
|
598
|
+
// ── GET /schedules/:id ────────────────────────────────────────────────────
|
|
599
|
+
|
|
600
|
+
describe("GET /schedules/:id", () => {
|
|
601
|
+
beforeEach(() => {
|
|
602
|
+
clearTables();
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
test("returns the full schedule by ID", () => {
|
|
606
|
+
const source = createConversation("Schedule source");
|
|
607
|
+
const schedule = createSchedule({
|
|
608
|
+
name: "Single schedule",
|
|
609
|
+
description: "Fetch me by ID",
|
|
610
|
+
cronExpression: "0 9 * * *",
|
|
611
|
+
message: "review queue",
|
|
612
|
+
syntax: "cron",
|
|
613
|
+
createdFromConversationId: source.id,
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
const route = findRoute("schedules/:id", "GET");
|
|
617
|
+
const result = route.handler({ pathParams: { id: schedule.id } }) as {
|
|
618
|
+
schedule: Record<string, unknown>;
|
|
619
|
+
};
|
|
620
|
+
|
|
621
|
+
expect(result.schedule).toMatchObject({
|
|
622
|
+
id: schedule.id,
|
|
623
|
+
name: "Single schedule",
|
|
624
|
+
description: "Fetch me by ID",
|
|
625
|
+
cadenceDescription: "Every day at 9:00 AM",
|
|
626
|
+
message: "review queue",
|
|
627
|
+
mode: "execute",
|
|
628
|
+
enabled: true,
|
|
629
|
+
isOneShot: false,
|
|
630
|
+
createdFromConversationId: source.id,
|
|
631
|
+
createdFromConversationExists: true,
|
|
632
|
+
createdFromConversationArchivedAt: null,
|
|
633
|
+
});
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
test("returns deferred schedules that the list hides by default", () => {
|
|
637
|
+
const deferred = createSchedule({
|
|
638
|
+
name: "Deferred wake",
|
|
639
|
+
cronExpression: "0 9 * * *",
|
|
640
|
+
message: "wake up",
|
|
641
|
+
syntax: "cron",
|
|
642
|
+
createdBy: "defer",
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
const route = findRoute("schedules/:id", "GET");
|
|
646
|
+
const result = route.handler({ pathParams: { id: deferred.id } }) as {
|
|
647
|
+
schedule: { id: string };
|
|
648
|
+
};
|
|
649
|
+
expect(result.schedule.id).toBe(deferred.id);
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
test("throws NotFoundError for a missing schedule", () => {
|
|
653
|
+
const route = findRoute("schedules/:id", "GET");
|
|
654
|
+
expect(() =>
|
|
655
|
+
route.handler({ pathParams: { id: "missing-schedule" } }),
|
|
656
|
+
).toThrow(NotFoundError);
|
|
657
|
+
expect(() =>
|
|
658
|
+
route.handler({ pathParams: { id: "missing-schedule" } }),
|
|
659
|
+
).toThrow("Schedule not found");
|
|
660
|
+
});
|
|
661
|
+
});
|
|
662
|
+
|
|
590
663
|
// ── schedules/:id/runs limit handling ─────────────────────────────────────
|
|
591
664
|
|
|
592
665
|
describe("schedule runs list — limit handling", () => {
|
|
@@ -1076,7 +1149,9 @@ describe("POST /schedules — create", () => {
|
|
|
1076
1149
|
|
|
1077
1150
|
function postCreate(body: Record<string, unknown>) {
|
|
1078
1151
|
const route = findRoute("schedules", "POST");
|
|
1079
|
-
return route.handler({ body }) as {
|
|
1152
|
+
return route.handler({ body }) as {
|
|
1153
|
+
schedules: Array<{ id: string; inferenceProfile: string | null }>;
|
|
1154
|
+
};
|
|
1080
1155
|
}
|
|
1081
1156
|
|
|
1082
1157
|
test("creates a recurring execute schedule with defaults", () => {
|
|
@@ -1156,7 +1231,7 @@ describe("POST /schedules — create", () => {
|
|
|
1156
1231
|
expect(job.description).toBe("Description fallback");
|
|
1157
1232
|
});
|
|
1158
1233
|
|
|
1159
|
-
test("rejects
|
|
1234
|
+
test("rejects modes other than execute/workflow", () => {
|
|
1160
1235
|
expect(() =>
|
|
1161
1236
|
postCreate({
|
|
1162
1237
|
name: "x",
|
|
@@ -1165,7 +1240,37 @@ describe("POST /schedules — create", () => {
|
|
|
1165
1240
|
message: "hi",
|
|
1166
1241
|
mode: "notify",
|
|
1167
1242
|
}),
|
|
1168
|
-
).toThrow("Only 'execute'
|
|
1243
|
+
).toThrow("Only 'execute' and 'workflow' modes are supported");
|
|
1244
|
+
});
|
|
1245
|
+
|
|
1246
|
+
test("rejects workflow-mode creation when the workflows flag is off", () => {
|
|
1247
|
+
// The mocked getConfig returns no feature flags, so `workflows` is off.
|
|
1248
|
+
expect(() =>
|
|
1249
|
+
postCreate({
|
|
1250
|
+
name: "Triage",
|
|
1251
|
+
description: "Triage the inbox every morning",
|
|
1252
|
+
expression: "0 9 * * *",
|
|
1253
|
+
message: "triage inbox",
|
|
1254
|
+
mode: "workflow",
|
|
1255
|
+
workflowName: "triage-inbox",
|
|
1256
|
+
}),
|
|
1257
|
+
).toThrow("Workflows are not enabled");
|
|
1258
|
+
});
|
|
1259
|
+
|
|
1260
|
+
test("rejects PATCH switching to workflow mode when the flag is off", () => {
|
|
1261
|
+
const schedule = createSchedule({
|
|
1262
|
+
name: "Plain execute",
|
|
1263
|
+
cronExpression: "0 9 * * *",
|
|
1264
|
+
message: "hi",
|
|
1265
|
+
syntax: "cron",
|
|
1266
|
+
});
|
|
1267
|
+
const patch = findRoute("schedules/:id", "PATCH");
|
|
1268
|
+
expect(() =>
|
|
1269
|
+
patch.handler({
|
|
1270
|
+
pathParams: { id: schedule.id },
|
|
1271
|
+
body: { mode: "workflow", workflowName: "triage-inbox" },
|
|
1272
|
+
}),
|
|
1273
|
+
).toThrow("Workflows are not enabled");
|
|
1169
1274
|
});
|
|
1170
1275
|
|
|
1171
1276
|
test("rejects an unparseable expression", () => {
|
|
@@ -1190,6 +1295,38 @@ describe("POST /schedules — create", () => {
|
|
|
1190
1295
|
).toThrow();
|
|
1191
1296
|
});
|
|
1192
1297
|
|
|
1298
|
+
test("persists and round-trips workflowName/workflowArgs in the list response", () => {
|
|
1299
|
+
// Store-level round trip: the create route is flag-gated (and the mocked
|
|
1300
|
+
// config disables it), so exercise persistence directly via the store.
|
|
1301
|
+
createSchedule({
|
|
1302
|
+
name: "Workflow schedule",
|
|
1303
|
+
cronExpression: "0 9 * * *",
|
|
1304
|
+
message: "trigger",
|
|
1305
|
+
syntax: "cron",
|
|
1306
|
+
mode: "workflow",
|
|
1307
|
+
workflowName: "triage-inbox",
|
|
1308
|
+
workflowArgs: { folder: "primary", limit: 10 },
|
|
1309
|
+
});
|
|
1310
|
+
|
|
1311
|
+
const route = findRoute("schedules", "GET");
|
|
1312
|
+
const result = route.handler({}) as {
|
|
1313
|
+
schedules: Array<{
|
|
1314
|
+
name: string;
|
|
1315
|
+
mode: string;
|
|
1316
|
+
workflowName: string | null;
|
|
1317
|
+
}>;
|
|
1318
|
+
};
|
|
1319
|
+
const job = result.schedules.find((s) => s.name === "Workflow schedule");
|
|
1320
|
+
expect(job).toBeDefined();
|
|
1321
|
+
expect(job!.mode).toBe("workflow");
|
|
1322
|
+
expect(job!.workflowName).toBe("triage-inbox");
|
|
1323
|
+
|
|
1324
|
+
// workflowArgs is not in the list projection; assert it round-trips at the
|
|
1325
|
+
// store layer (this is what the scheduler reads).
|
|
1326
|
+
const stored = listSchedules().find((s) => s.name === "Workflow schedule")!;
|
|
1327
|
+
expect(stored.workflowArgs).toEqual({ folder: "primary", limit: 10 });
|
|
1328
|
+
});
|
|
1329
|
+
|
|
1193
1330
|
test("respects enabled=false", () => {
|
|
1194
1331
|
postCreate({
|
|
1195
1332
|
name: "Off",
|
|
@@ -1201,6 +1338,52 @@ describe("POST /schedules — create", () => {
|
|
|
1201
1338
|
const job = listSchedules()[0];
|
|
1202
1339
|
expect(job.enabled).toBe(false);
|
|
1203
1340
|
});
|
|
1341
|
+
|
|
1342
|
+
test("persists a valid inferenceProfile and serializes it", () => {
|
|
1343
|
+
const result = postCreate({
|
|
1344
|
+
name: "Cheap digest",
|
|
1345
|
+
description: "High-frequency digest on a cheap model",
|
|
1346
|
+
expression: "0 * * * *",
|
|
1347
|
+
message: "write the digest",
|
|
1348
|
+
inferenceProfile: "cost-optimized",
|
|
1349
|
+
});
|
|
1350
|
+
expect(result.schedules[0].inferenceProfile).toBe("cost-optimized");
|
|
1351
|
+
expect(listSchedules()[0].inferenceProfile).toBe("cost-optimized");
|
|
1352
|
+
});
|
|
1353
|
+
|
|
1354
|
+
test("defaults inferenceProfile to null when omitted", () => {
|
|
1355
|
+
const result = postCreate({
|
|
1356
|
+
name: "Default profile",
|
|
1357
|
+
description: "Runs on the main-agent selection",
|
|
1358
|
+
expression: "0 9 * * *",
|
|
1359
|
+
message: "hi",
|
|
1360
|
+
});
|
|
1361
|
+
expect(result.schedules[0].inferenceProfile).toBeNull();
|
|
1362
|
+
});
|
|
1363
|
+
|
|
1364
|
+
test("rejects an unknown inferenceProfile", () => {
|
|
1365
|
+
expect(() =>
|
|
1366
|
+
postCreate({
|
|
1367
|
+
name: "Bad profile",
|
|
1368
|
+
description: "Unknown profile",
|
|
1369
|
+
expression: "0 9 * * *",
|
|
1370
|
+
message: "hi",
|
|
1371
|
+
inferenceProfile: "does-not-exist",
|
|
1372
|
+
}),
|
|
1373
|
+
).toThrow('Inference profile "does-not-exist" is not defined');
|
|
1374
|
+
});
|
|
1375
|
+
|
|
1376
|
+
test("rejects a non-string inferenceProfile", () => {
|
|
1377
|
+
expect(() =>
|
|
1378
|
+
postCreate({
|
|
1379
|
+
name: "Bad profile type",
|
|
1380
|
+
description: "Wrong type",
|
|
1381
|
+
expression: "0 9 * * *",
|
|
1382
|
+
message: "hi",
|
|
1383
|
+
inferenceProfile: 42,
|
|
1384
|
+
}),
|
|
1385
|
+
).toThrow("inferenceProfile must be a string or null");
|
|
1386
|
+
});
|
|
1204
1387
|
});
|
|
1205
1388
|
|
|
1206
1389
|
// ── PATCH /schedules/:id — description ───────────────────────────────────
|
|
@@ -1233,6 +1416,76 @@ describe("PATCH /schedules/:id — description", () => {
|
|
|
1233
1416
|
});
|
|
1234
1417
|
expect(listSchedules()[0].description).toBe("Updated description");
|
|
1235
1418
|
});
|
|
1419
|
+
|
|
1420
|
+
test("sets, validates, and clears inferenceProfile", () => {
|
|
1421
|
+
const schedule = createSchedule({
|
|
1422
|
+
name: "Profile pin",
|
|
1423
|
+
description: "Pinned profile schedule",
|
|
1424
|
+
cronExpression: "0 9 * * *",
|
|
1425
|
+
message: "hi",
|
|
1426
|
+
syntax: "cron",
|
|
1427
|
+
});
|
|
1428
|
+
const route = findRoute("schedules/:id", "PATCH");
|
|
1429
|
+
|
|
1430
|
+
route.handler({
|
|
1431
|
+
pathParams: { id: schedule.id },
|
|
1432
|
+
body: { inferenceProfile: "balanced" },
|
|
1433
|
+
});
|
|
1434
|
+
expect(listSchedules()[0].inferenceProfile).toBe("balanced");
|
|
1435
|
+
|
|
1436
|
+
expect(() =>
|
|
1437
|
+
route.handler({
|
|
1438
|
+
pathParams: { id: schedule.id },
|
|
1439
|
+
body: { inferenceProfile: "does-not-exist" },
|
|
1440
|
+
}),
|
|
1441
|
+
).toThrow('Inference profile "does-not-exist" is not defined');
|
|
1442
|
+
expect(listSchedules()[0].inferenceProfile).toBe("balanced");
|
|
1443
|
+
|
|
1444
|
+
route.handler({
|
|
1445
|
+
pathParams: { id: schedule.id },
|
|
1446
|
+
body: { inferenceProfile: null },
|
|
1447
|
+
});
|
|
1448
|
+
expect(listSchedules()[0].inferenceProfile).toBeNull();
|
|
1449
|
+
});
|
|
1450
|
+
|
|
1451
|
+
test("re-derives syntax when the expression switches cron to rrule", () => {
|
|
1452
|
+
const schedule = createSchedule({
|
|
1453
|
+
name: "Syntax switch",
|
|
1454
|
+
description: "Cron schedule",
|
|
1455
|
+
cronExpression: "0 9 * * *",
|
|
1456
|
+
message: "hi",
|
|
1457
|
+
syntax: "cron",
|
|
1458
|
+
});
|
|
1459
|
+
|
|
1460
|
+
const route = findRoute("schedules/:id", "PATCH");
|
|
1461
|
+
const rrule = "DTSTART:20260101T000000Z\nRRULE:FREQ=WEEKLY;BYDAY=MO";
|
|
1462
|
+
route.handler({
|
|
1463
|
+
pathParams: { id: schedule.id },
|
|
1464
|
+
body: { expression: rrule },
|
|
1465
|
+
});
|
|
1466
|
+
|
|
1467
|
+
const updated = listSchedules()[0];
|
|
1468
|
+
expect(updated.syntax).toBe("rrule");
|
|
1469
|
+
expect(updated.cronExpression).toBe(rrule);
|
|
1470
|
+
});
|
|
1471
|
+
|
|
1472
|
+
test("rejects an expression that parses as neither cron nor rrule", () => {
|
|
1473
|
+
const schedule = createSchedule({
|
|
1474
|
+
name: "Bad expression",
|
|
1475
|
+
description: "Cron schedule",
|
|
1476
|
+
cronExpression: "0 9 * * *",
|
|
1477
|
+
message: "hi",
|
|
1478
|
+
syntax: "cron",
|
|
1479
|
+
});
|
|
1480
|
+
|
|
1481
|
+
const route = findRoute("schedules/:id", "PATCH");
|
|
1482
|
+
expect(() =>
|
|
1483
|
+
route.handler({
|
|
1484
|
+
pathParams: { id: schedule.id },
|
|
1485
|
+
body: { expression: "not a schedule" },
|
|
1486
|
+
}),
|
|
1487
|
+
).toThrow(BadRequestError);
|
|
1488
|
+
});
|
|
1236
1489
|
});
|
|
1237
1490
|
|
|
1238
1491
|
// ── Wake mode support ─────────────────────────────────────────────────────
|
|
@@ -1170,6 +1170,66 @@ describe("script timeout override", () => {
|
|
|
1170
1170
|
});
|
|
1171
1171
|
});
|
|
1172
1172
|
|
|
1173
|
+
// ── Capability manifest ─────────────────────────────────────────────
|
|
1174
|
+
|
|
1175
|
+
describe("capability manifest", () => {
|
|
1176
|
+
beforeEach(() => {
|
|
1177
|
+
const db = getDb();
|
|
1178
|
+
db.run("DELETE FROM cron_runs");
|
|
1179
|
+
db.run("DELETE FROM cron_jobs");
|
|
1180
|
+
});
|
|
1181
|
+
|
|
1182
|
+
const manifest = {
|
|
1183
|
+
tools: ["file_write"],
|
|
1184
|
+
hostFunctions: [],
|
|
1185
|
+
persona: false,
|
|
1186
|
+
};
|
|
1187
|
+
|
|
1188
|
+
test("persists a capability manifest through create/read", () => {
|
|
1189
|
+
const job = createSchedule({
|
|
1190
|
+
name: "Capable schedule",
|
|
1191
|
+
cronExpression: "0 9 * * *",
|
|
1192
|
+
message: "do scoped work",
|
|
1193
|
+
syntax: "cron",
|
|
1194
|
+
capabilities: manifest,
|
|
1195
|
+
});
|
|
1196
|
+
|
|
1197
|
+
expect(job.capabilities).toEqual(manifest);
|
|
1198
|
+
expect(getSchedule(job.id)!.capabilities).toEqual(manifest);
|
|
1199
|
+
});
|
|
1200
|
+
|
|
1201
|
+
test("defaults capabilities to null when not provided", () => {
|
|
1202
|
+
const job = createSchedule({
|
|
1203
|
+
name: "No manifest",
|
|
1204
|
+
cronExpression: "0 9 * * *",
|
|
1205
|
+
message: "unconstrained",
|
|
1206
|
+
syntax: "cron",
|
|
1207
|
+
});
|
|
1208
|
+
|
|
1209
|
+
expect(job.capabilities).toBeNull();
|
|
1210
|
+
expect(getSchedule(job.id)!.capabilities).toBeNull();
|
|
1211
|
+
});
|
|
1212
|
+
|
|
1213
|
+
test("updateSchedule sets and clears the capability manifest", () => {
|
|
1214
|
+
const job = createSchedule({
|
|
1215
|
+
name: "Update manifest",
|
|
1216
|
+
cronExpression: "0 9 * * *",
|
|
1217
|
+
message: "update manifest",
|
|
1218
|
+
syntax: "cron",
|
|
1219
|
+
});
|
|
1220
|
+
|
|
1221
|
+
expect(job.capabilities).toBeNull();
|
|
1222
|
+
|
|
1223
|
+
const updated = updateSchedule(job.id, { capabilities: manifest });
|
|
1224
|
+
expect(updated!.capabilities).toEqual(manifest);
|
|
1225
|
+
expect(getSchedule(job.id)!.capabilities).toEqual(manifest);
|
|
1226
|
+
|
|
1227
|
+
const cleared = updateSchedule(job.id, { capabilities: null });
|
|
1228
|
+
expect(cleared!.capabilities).toBeNull();
|
|
1229
|
+
expect(getSchedule(job.id)!.capabilities).toBeNull();
|
|
1230
|
+
});
|
|
1231
|
+
});
|
|
1232
|
+
|
|
1173
1233
|
// ── listSchedules filters ───────────────────────────────────────────
|
|
1174
1234
|
|
|
1175
1235
|
describe("listSchedules filters", () => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
1
|
+
import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
2
|
|
|
3
3
|
mock.module("../util/logger.js", () => ({
|
|
4
4
|
getLogger: () =>
|
|
@@ -18,11 +18,18 @@ import type { Database } from "bun:sqlite";
|
|
|
18
18
|
|
|
19
19
|
import { getDb } from "../memory/db-connection.js";
|
|
20
20
|
import { initializeDb } from "../memory/db-init.js";
|
|
21
|
+
import { RiskLevel } from "../permissions/types.js";
|
|
22
|
+
import {
|
|
23
|
+
__clearRegistryForTesting,
|
|
24
|
+
__resetRegistryForTesting,
|
|
25
|
+
registerTool,
|
|
26
|
+
} from "../tools/registry.js";
|
|
21
27
|
import { executeScheduleCreate as rawExecuteScheduleCreate } from "../tools/schedule/create.js";
|
|
22
28
|
import { executeScheduleDelete } from "../tools/schedule/delete.js";
|
|
23
29
|
import { executeScheduleList } from "../tools/schedule/list.js";
|
|
24
30
|
import { executeScheduleUpdate } from "../tools/schedule/update.js";
|
|
25
|
-
import type { ToolContext } from "../tools/types.js";
|
|
31
|
+
import type { Tool, ToolContext } from "../tools/types.js";
|
|
32
|
+
import { setOverridesForTesting } from "./feature-flag-test-helpers.js";
|
|
26
33
|
|
|
27
34
|
initializeDb();
|
|
28
35
|
|
|
@@ -408,6 +415,244 @@ describe("schedule_create with mode and routing", () => {
|
|
|
408
415
|
});
|
|
409
416
|
});
|
|
410
417
|
|
|
418
|
+
// ── schedule_create / schedule_update workflow mode ─────────────────
|
|
419
|
+
|
|
420
|
+
describe("schedule tools — workflow mode", () => {
|
|
421
|
+
beforeEach(() => {
|
|
422
|
+
getRawDb().run("DELETE FROM cron_runs");
|
|
423
|
+
getRawDb().run("DELETE FROM cron_jobs");
|
|
424
|
+
setOverridesForTesting({ workflows: true });
|
|
425
|
+
});
|
|
426
|
+
afterAll(() => {
|
|
427
|
+
setOverridesForTesting({});
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
test("creates a workflow-mode schedule with workflow_name + workflow_args", async () => {
|
|
431
|
+
const result = await executeScheduleCreate(
|
|
432
|
+
{
|
|
433
|
+
name: "Morning triage",
|
|
434
|
+
expression: "0 8 * * *",
|
|
435
|
+
mode: "workflow",
|
|
436
|
+
workflow_name: "inbox-triage",
|
|
437
|
+
workflow_args: { limit: 50 },
|
|
438
|
+
},
|
|
439
|
+
ctx,
|
|
440
|
+
);
|
|
441
|
+
|
|
442
|
+
expect(result.isError).toBe(false);
|
|
443
|
+
expect(result.content).toContain("Mode: workflow");
|
|
444
|
+
|
|
445
|
+
const row = getRawDb()
|
|
446
|
+
.query(
|
|
447
|
+
"SELECT mode, workflow_name, workflow_args_json FROM cron_jobs LIMIT 1",
|
|
448
|
+
)
|
|
449
|
+
.get() as {
|
|
450
|
+
mode: string;
|
|
451
|
+
workflow_name: string;
|
|
452
|
+
workflow_args_json: string;
|
|
453
|
+
};
|
|
454
|
+
expect(row.mode).toBe("workflow");
|
|
455
|
+
expect(row.workflow_name).toBe("inbox-triage");
|
|
456
|
+
expect(JSON.parse(row.workflow_args_json)).toEqual({ limit: 50 });
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
test("rejects workflow mode without workflow_name", async () => {
|
|
460
|
+
const result = await executeScheduleCreate(
|
|
461
|
+
{ name: "No wf name", expression: "0 8 * * *", mode: "workflow" },
|
|
462
|
+
ctx,
|
|
463
|
+
);
|
|
464
|
+
|
|
465
|
+
expect(result.isError).toBe(true);
|
|
466
|
+
expect(result.content).toContain("workflow_name is required");
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
test("rejects workflow mode when the workflows flag is off", async () => {
|
|
470
|
+
setOverridesForTesting({}); // flag off
|
|
471
|
+
const result = await executeScheduleCreate(
|
|
472
|
+
{
|
|
473
|
+
name: "Flag off",
|
|
474
|
+
expression: "0 8 * * *",
|
|
475
|
+
mode: "workflow",
|
|
476
|
+
workflow_name: "inbox-triage",
|
|
477
|
+
},
|
|
478
|
+
ctx,
|
|
479
|
+
);
|
|
480
|
+
|
|
481
|
+
expect(result.isError).toBe(true);
|
|
482
|
+
expect(result.content).toContain("workflows are not enabled");
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
test("update to workflow mode requires a workflow_name", async () => {
|
|
486
|
+
await executeScheduleCreate(
|
|
487
|
+
{ name: "To workflow", expression: "0 9 * * *", message: "test" },
|
|
488
|
+
ctx,
|
|
489
|
+
);
|
|
490
|
+
const { id } = getRawDb()
|
|
491
|
+
.query("SELECT id FROM cron_jobs LIMIT 1")
|
|
492
|
+
.get() as { id: string };
|
|
493
|
+
|
|
494
|
+
const result = await executeScheduleUpdate(
|
|
495
|
+
{ job_id: id, mode: "workflow" },
|
|
496
|
+
ctx,
|
|
497
|
+
);
|
|
498
|
+
|
|
499
|
+
expect(result.isError).toBe(true);
|
|
500
|
+
expect(result.content).toContain("workflow_name is required");
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
test("updates a schedule into workflow mode with a workflow_name", async () => {
|
|
504
|
+
await executeScheduleCreate(
|
|
505
|
+
{ name: "To workflow ok", expression: "0 9 * * *", message: "test" },
|
|
506
|
+
ctx,
|
|
507
|
+
);
|
|
508
|
+
const { id } = getRawDb()
|
|
509
|
+
.query("SELECT id FROM cron_jobs LIMIT 1")
|
|
510
|
+
.get() as { id: string };
|
|
511
|
+
|
|
512
|
+
const result = await executeScheduleUpdate(
|
|
513
|
+
{ job_id: id, mode: "workflow", workflow_name: "nightly-report" },
|
|
514
|
+
ctx,
|
|
515
|
+
);
|
|
516
|
+
|
|
517
|
+
expect(result.isError).toBe(false);
|
|
518
|
+
expect(result.content).toContain("Mode: workflow");
|
|
519
|
+
const dbRow = getRawDb()
|
|
520
|
+
.query("SELECT mode, workflow_name FROM cron_jobs WHERE id = ?")
|
|
521
|
+
.get(id) as { mode: string; workflow_name: string };
|
|
522
|
+
expect(dbRow.mode).toBe("workflow");
|
|
523
|
+
expect(dbRow.workflow_name).toBe("nightly-report");
|
|
524
|
+
});
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
// ── schedule_create workflow capability manifest ────────────────────
|
|
528
|
+
|
|
529
|
+
describe("schedule_create — workflow capability manifest", () => {
|
|
530
|
+
function makeFakeTool(name: string): Tool {
|
|
531
|
+
return {
|
|
532
|
+
name,
|
|
533
|
+
description: `Fake ${name}`,
|
|
534
|
+
category: "test",
|
|
535
|
+
defaultRiskLevel: RiskLevel.Low,
|
|
536
|
+
executionTarget: "sandbox",
|
|
537
|
+
input_schema: { type: "object", properties: {}, required: [] },
|
|
538
|
+
async execute() {
|
|
539
|
+
return { content: "ok", isError: false };
|
|
540
|
+
},
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
beforeEach(() => {
|
|
545
|
+
getRawDb().run("DELETE FROM cron_runs");
|
|
546
|
+
getRawDb().run("DELETE FROM cron_jobs");
|
|
547
|
+
setOverridesForTesting({ workflows: true });
|
|
548
|
+
// Deterministic registry so a declared side-effecting tool resolves.
|
|
549
|
+
__clearRegistryForTesting();
|
|
550
|
+
registerTool(makeFakeTool("file_write"));
|
|
551
|
+
});
|
|
552
|
+
afterAll(() => {
|
|
553
|
+
setOverridesForTesting({});
|
|
554
|
+
__resetRegistryForTesting();
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
test("persists a validated side-effecting manifest verbatim", async () => {
|
|
558
|
+
const result = await executeScheduleCreate(
|
|
559
|
+
{
|
|
560
|
+
name: "Nightly writeback",
|
|
561
|
+
expression: "0 2 * * *",
|
|
562
|
+
mode: "workflow",
|
|
563
|
+
workflow_name: "nightly-report",
|
|
564
|
+
capabilities: { tools: ["file_write"], persona: true },
|
|
565
|
+
},
|
|
566
|
+
ctx,
|
|
567
|
+
);
|
|
568
|
+
|
|
569
|
+
expect(result.isError).toBe(false);
|
|
570
|
+
const row = getRawDb()
|
|
571
|
+
.query("SELECT capabilities_json FROM cron_jobs LIMIT 1")
|
|
572
|
+
.get() as { capabilities_json: string };
|
|
573
|
+
expect(JSON.parse(row.capabilities_json)).toEqual({
|
|
574
|
+
tools: ["file_write"],
|
|
575
|
+
hostFunctions: [],
|
|
576
|
+
persona: true,
|
|
577
|
+
});
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
test("rejects a forbidden manifest at creation", async () => {
|
|
581
|
+
const result = await executeScheduleCreate(
|
|
582
|
+
{
|
|
583
|
+
name: "Recursion vector",
|
|
584
|
+
expression: "0 2 * * *",
|
|
585
|
+
mode: "workflow",
|
|
586
|
+
workflow_name: "nightly-report",
|
|
587
|
+
capabilities: { tools: ["run_workflow"] },
|
|
588
|
+
},
|
|
589
|
+
ctx,
|
|
590
|
+
);
|
|
591
|
+
|
|
592
|
+
expect(result.isError).toBe(true);
|
|
593
|
+
expect(result.content).toContain("invalid capabilities manifest");
|
|
594
|
+
expect(getRawDb().query("SELECT id FROM cron_jobs").all()).toHaveLength(0);
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
test("rejects an unknown tool in the manifest at creation", async () => {
|
|
598
|
+
const result = await executeScheduleCreate(
|
|
599
|
+
{
|
|
600
|
+
name: "Bad tool",
|
|
601
|
+
expression: "0 2 * * *",
|
|
602
|
+
mode: "workflow",
|
|
603
|
+
workflow_name: "nightly-report",
|
|
604
|
+
capabilities: { tools: ["unknown_tool"] },
|
|
605
|
+
},
|
|
606
|
+
ctx,
|
|
607
|
+
);
|
|
608
|
+
|
|
609
|
+
expect(result.isError).toBe(true);
|
|
610
|
+
expect(result.content).toContain("invalid capabilities manifest");
|
|
611
|
+
expect(getRawDb().query("SELECT id FROM cron_jobs").all()).toHaveLength(0);
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
test("a read-only manifest persists as a baseline grant", async () => {
|
|
615
|
+
const result = await executeScheduleCreate(
|
|
616
|
+
{
|
|
617
|
+
name: "Read-only run",
|
|
618
|
+
expression: "0 2 * * *",
|
|
619
|
+
mode: "workflow",
|
|
620
|
+
workflow_name: "nightly-report",
|
|
621
|
+
capabilities: { tools: [], hostFunctions: [], persona: false },
|
|
622
|
+
},
|
|
623
|
+
ctx,
|
|
624
|
+
);
|
|
625
|
+
|
|
626
|
+
expect(result.isError).toBe(false);
|
|
627
|
+
const row = getRawDb()
|
|
628
|
+
.query("SELECT capabilities_json FROM cron_jobs LIMIT 1")
|
|
629
|
+
.get() as { capabilities_json: string };
|
|
630
|
+
expect(JSON.parse(row.capabilities_json)).toEqual({
|
|
631
|
+
tools: [],
|
|
632
|
+
hostFunctions: [],
|
|
633
|
+
persona: false,
|
|
634
|
+
});
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
test("an absent manifest persists no capabilities", async () => {
|
|
638
|
+
const result = await executeScheduleCreate(
|
|
639
|
+
{
|
|
640
|
+
name: "No manifest",
|
|
641
|
+
expression: "0 2 * * *",
|
|
642
|
+
mode: "workflow",
|
|
643
|
+
workflow_name: "nightly-report",
|
|
644
|
+
},
|
|
645
|
+
ctx,
|
|
646
|
+
);
|
|
647
|
+
|
|
648
|
+
expect(result.isError).toBe(false);
|
|
649
|
+
const row = getRawDb()
|
|
650
|
+
.query("SELECT capabilities_json FROM cron_jobs LIMIT 1")
|
|
651
|
+
.get() as { capabilities_json: string | null };
|
|
652
|
+
expect(row.capabilities_json).toBeNull();
|
|
653
|
+
});
|
|
654
|
+
});
|
|
655
|
+
|
|
411
656
|
// ── script timeout override ─────────────────────────────────────────
|
|
412
657
|
|
|
413
658
|
describe("schedule_create / schedule_update timeout_ms", () => {
|