@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,363 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route handlers for workflow-run management.
|
|
3
|
+
*
|
|
4
|
+
* All routes are served by both the HTTP server and the IPC server via the
|
|
5
|
+
* shared ROUTES array. They are read/abort surfaces over the workflow run
|
|
6
|
+
* manager and the saved-workflow library — a human (via the `vellum workflows`
|
|
7
|
+
* CLI or the app) can inspect runs and abort an in-flight one.
|
|
8
|
+
*
|
|
9
|
+
* Every handler is gated on the `workflows` feature flag: when it is off, the
|
|
10
|
+
* routes behave as if they do not exist (404), so disabling the flag fully
|
|
11
|
+
* hides the surface.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { z } from "zod";
|
|
15
|
+
|
|
16
|
+
import { isAssistantFeatureFlagEnabled } from "../../config/assistant-feature-flags.js";
|
|
17
|
+
import { getConfig } from "../../config/loader.js";
|
|
18
|
+
import type { AssistantConfig } from "../../config/schema.js";
|
|
19
|
+
import { getAutoApproveThreshold } from "../../permissions/gateway-threshold-reader.js";
|
|
20
|
+
import { isFullAccessThreshold } from "../../permissions/threshold.js";
|
|
21
|
+
import { manifestGrantsSideEffects } from "../../workflows/capabilities.js";
|
|
22
|
+
import type { WorkflowRun } from "../../workflows/journal-store.js";
|
|
23
|
+
import { listWorkflows } from "../../workflows/library.js";
|
|
24
|
+
import {
|
|
25
|
+
getWorkflowRunManager,
|
|
26
|
+
WorkflowResumeNotPossibleError,
|
|
27
|
+
WorkflowRunCapError,
|
|
28
|
+
type WorkflowRunManager,
|
|
29
|
+
} from "../../workflows/run-manager.js";
|
|
30
|
+
import { ACTOR_PRINCIPALS } from "../auth/route-policy.js";
|
|
31
|
+
import {
|
|
32
|
+
ConflictError,
|
|
33
|
+
ForbiddenError,
|
|
34
|
+
NotFoundError,
|
|
35
|
+
TooManyRequestsError,
|
|
36
|
+
} from "./errors.js";
|
|
37
|
+
import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
|
|
38
|
+
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
// Injectable dependencies (production defaults; tests override)
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
export interface WorkflowRoutesDeps {
|
|
44
|
+
getManager: () => Pick<
|
|
45
|
+
WorkflowRunManager,
|
|
46
|
+
"list" | "status" | "abort" | "resume"
|
|
47
|
+
>;
|
|
48
|
+
listWorkflows: typeof listWorkflows;
|
|
49
|
+
getConfig: () => AssistantConfig;
|
|
50
|
+
isFlagEnabled: (config: AssistantConfig) => boolean;
|
|
51
|
+
getAutoApproveThreshold: typeof getAutoApproveThreshold;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function defaultDeps(): WorkflowRoutesDeps {
|
|
55
|
+
return {
|
|
56
|
+
getManager: getWorkflowRunManager,
|
|
57
|
+
listWorkflows,
|
|
58
|
+
getConfig,
|
|
59
|
+
isFlagEnabled: (config) =>
|
|
60
|
+
isAssistantFeatureFlagEnabled("workflows", config),
|
|
61
|
+
getAutoApproveThreshold,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let deps: WorkflowRoutesDeps = defaultDeps();
|
|
66
|
+
|
|
67
|
+
/** Test seam: override the route dependencies. Pass no arg to restore defaults. */
|
|
68
|
+
export function __setWorkflowRoutesDeps(
|
|
69
|
+
overrides?: Partial<WorkflowRoutesDeps>,
|
|
70
|
+
): void {
|
|
71
|
+
deps = overrides ? { ...defaultDeps(), ...overrides } : defaultDeps();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
// Response schemas
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
|
|
78
|
+
const VALID_RUN_STATUSES = [
|
|
79
|
+
"running",
|
|
80
|
+
"completed",
|
|
81
|
+
"failed",
|
|
82
|
+
"aborted",
|
|
83
|
+
"cap_exceeded",
|
|
84
|
+
"interrupted",
|
|
85
|
+
] as const;
|
|
86
|
+
|
|
87
|
+
const workflowRunSchema = z.object({
|
|
88
|
+
id: z.string(),
|
|
89
|
+
name: z.string().nullable(),
|
|
90
|
+
scriptHash: z.string(),
|
|
91
|
+
status: z.enum(VALID_RUN_STATUSES),
|
|
92
|
+
conversationId: z.string().nullable(),
|
|
93
|
+
agentsSpawned: z.number(),
|
|
94
|
+
inputTokens: z.number(),
|
|
95
|
+
outputTokens: z.number(),
|
|
96
|
+
error: z.string().nullable(),
|
|
97
|
+
createdAt: z.number().nullable(),
|
|
98
|
+
updatedAt: z.number().nullable(),
|
|
99
|
+
finishedAt: z.number().nullable(),
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const savedWorkflowSchema = z.object({
|
|
103
|
+
name: z.string(),
|
|
104
|
+
description: z.string(),
|
|
105
|
+
path: z.string(),
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
// Handlers (transport-agnostic)
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Throw a 404 when the `workflows` flag is off, hiding the whole surface. The
|
|
114
|
+
* NotFoundError (rather than a 403) keeps a disabled-flag indistinguishable
|
|
115
|
+
* from a route that does not exist.
|
|
116
|
+
*/
|
|
117
|
+
function assertFlagEnabled(): void {
|
|
118
|
+
if (!deps.isFlagEnabled(deps.getConfig())) {
|
|
119
|
+
throw new NotFoundError("Workflows are not enabled.");
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* The single compact-run wire contract, inferred from {@link workflowRunSchema}.
|
|
125
|
+
* The HTTP routes return this shape; other producers (e.g. the CLI's client-side
|
|
126
|
+
* mirror) should track it so projections can't silently drift.
|
|
127
|
+
*/
|
|
128
|
+
export type WorkflowRunWire = z.infer<typeof workflowRunSchema>;
|
|
129
|
+
|
|
130
|
+
/** Project a stored run into the wire shape (drops bulky source/args/result). */
|
|
131
|
+
export function toWireRun(run: WorkflowRun): WorkflowRunWire {
|
|
132
|
+
return {
|
|
133
|
+
id: run.id,
|
|
134
|
+
name: run.name,
|
|
135
|
+
scriptHash: run.scriptHash,
|
|
136
|
+
status: run.status,
|
|
137
|
+
conversationId: run.conversationId,
|
|
138
|
+
agentsSpawned: run.agentsSpawned,
|
|
139
|
+
inputTokens: run.inputTokens,
|
|
140
|
+
outputTokens: run.outputTokens,
|
|
141
|
+
error: run.error,
|
|
142
|
+
createdAt: run.createdAt,
|
|
143
|
+
updatedAt: run.updatedAt,
|
|
144
|
+
finishedAt: run.finishedAt,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function parseLimit(raw: string | undefined): number | undefined {
|
|
149
|
+
if (raw === undefined) return undefined;
|
|
150
|
+
const n = Number(raw);
|
|
151
|
+
if (!Number.isFinite(n)) return undefined;
|
|
152
|
+
return Math.min(Math.max(Math.floor(n), 1), 200);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function parseStatus(
|
|
156
|
+
raw: string | undefined,
|
|
157
|
+
): WorkflowRun["status"] | undefined {
|
|
158
|
+
if (raw === undefined) return undefined;
|
|
159
|
+
return (VALID_RUN_STATUSES as readonly string[]).includes(raw)
|
|
160
|
+
? (raw as WorkflowRun["status"])
|
|
161
|
+
: undefined;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function handleListRuns(queryParams: Record<string, string>) {
|
|
165
|
+
assertFlagEnabled();
|
|
166
|
+
const limit = parseLimit(queryParams.limit);
|
|
167
|
+
const status = parseStatus(queryParams.status);
|
|
168
|
+
const runs = deps.getManager().list({
|
|
169
|
+
...(limit !== undefined ? { limit } : {}),
|
|
170
|
+
...(status !== undefined ? { status } : {}),
|
|
171
|
+
});
|
|
172
|
+
return { runs: runs.map(toWireRun) };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function handleGetRun(id: string) {
|
|
176
|
+
assertFlagEnabled();
|
|
177
|
+
const run = deps.getManager().status(id);
|
|
178
|
+
if (!run) {
|
|
179
|
+
throw new NotFoundError(`Workflow run ${id} not found`);
|
|
180
|
+
}
|
|
181
|
+
return toWireRun(run);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function handleAbortRun(id: string) {
|
|
185
|
+
assertFlagEnabled();
|
|
186
|
+
// status() is the source of truth for existence; abort() itself is a no-op
|
|
187
|
+
// for unknown/finished runs, so 404 on an unknown id is surfaced here.
|
|
188
|
+
const manager = deps.getManager();
|
|
189
|
+
if (!manager.status(id)) {
|
|
190
|
+
throw new NotFoundError(`Workflow run ${id} not found`);
|
|
191
|
+
}
|
|
192
|
+
manager.abort(id);
|
|
193
|
+
return { ok: true, runId: id };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async function handleResumeRun(id: string) {
|
|
197
|
+
assertFlagEnabled();
|
|
198
|
+
const manager = deps.getManager();
|
|
199
|
+
// status() is the source of truth for existence, so 404 an unknown id here
|
|
200
|
+
// (matching abort) rather than letting resume's "not_found" reach the client
|
|
201
|
+
// as a 409.
|
|
202
|
+
const run = manager.status(id);
|
|
203
|
+
if (!run) {
|
|
204
|
+
throw new NotFoundError(`Workflow run ${id} not found`);
|
|
205
|
+
}
|
|
206
|
+
// Consent gate. Resuming restarts the run's unfinished leaves, which execute
|
|
207
|
+
// the side-effecting tools/host functions the original manifest granted. The
|
|
208
|
+
// conversational `manage_workflows` resume path promotes such resumes to a
|
|
209
|
+
// fresh interactive approval (executor.ts → requireFreshApproval); this
|
|
210
|
+
// HTTP/IPC route (and the `vellum workflows resume` CLI on top of it) has no
|
|
211
|
+
// prompt channel. At full-access posture the user has opted into
|
|
212
|
+
// auto-approving even high-risk tools, so the resume proceeds without a
|
|
213
|
+
// prompt; otherwise it must not silently bypass consent — refuse and direct
|
|
214
|
+
// the caller to the assistant. A read-only run (no declared tools/host
|
|
215
|
+
// functions) resumes freely regardless of posture.
|
|
216
|
+
if (manifestGrantsSideEffects(run.capabilities)) {
|
|
217
|
+
const threshold = await deps.getAutoApproveThreshold(
|
|
218
|
+
run.conversationId ?? undefined,
|
|
219
|
+
"conversation",
|
|
220
|
+
);
|
|
221
|
+
if (!isFullAccessThreshold(threshold)) {
|
|
222
|
+
throw new ForbiddenError(
|
|
223
|
+
`Workflow run ${id} was granted side-effecting capabilities; resuming it ` +
|
|
224
|
+
`can restart steps that perform those side effects. Resume it through ` +
|
|
225
|
+
`the assistant (which will ask for approval) instead of this route.`,
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
try {
|
|
230
|
+
const { runId } = manager.resume(id);
|
|
231
|
+
return { ok: true, runId };
|
|
232
|
+
} catch (err) {
|
|
233
|
+
if (err instanceof WorkflowResumeNotPossibleError) {
|
|
234
|
+
if (err.reason === "not_found") {
|
|
235
|
+
throw new NotFoundError(`Workflow run ${id} not found`);
|
|
236
|
+
}
|
|
237
|
+
// not_interrupted / in_flight — the run exists but isn't resumable now.
|
|
238
|
+
throw new ConflictError(err.message);
|
|
239
|
+
}
|
|
240
|
+
if (err instanceof WorkflowRunCapError) {
|
|
241
|
+
throw new TooManyRequestsError(err.message);
|
|
242
|
+
}
|
|
243
|
+
throw err;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function handleListSavedWorkflows() {
|
|
248
|
+
assertFlagEnabled();
|
|
249
|
+
return { workflows: deps.listWorkflows() };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// ---------------------------------------------------------------------------
|
|
253
|
+
// Shared route definitions (HTTP + IPC)
|
|
254
|
+
// ---------------------------------------------------------------------------
|
|
255
|
+
|
|
256
|
+
export const ROUTES: RouteDefinition[] = [
|
|
257
|
+
{
|
|
258
|
+
operationId: "listWorkflowRuns",
|
|
259
|
+
endpoint: "workflows/runs",
|
|
260
|
+
method: "GET",
|
|
261
|
+
policy: {
|
|
262
|
+
requiredScopes: ["settings.read"],
|
|
263
|
+
allowedPrincipalTypes: ACTOR_PRINCIPALS,
|
|
264
|
+
},
|
|
265
|
+
summary: "List workflow runs",
|
|
266
|
+
description: "Return recent workflow runs, newest first.",
|
|
267
|
+
tags: ["workflows"],
|
|
268
|
+
queryParams: [
|
|
269
|
+
{
|
|
270
|
+
name: "limit",
|
|
271
|
+
schema: { type: "integer" },
|
|
272
|
+
description: "Max runs to return (default 50, max 200)",
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
name: "status",
|
|
276
|
+
schema: { type: "string" },
|
|
277
|
+
description:
|
|
278
|
+
"Filter by run status (running, completed, failed, aborted, cap_exceeded, interrupted).",
|
|
279
|
+
},
|
|
280
|
+
],
|
|
281
|
+
responseBody: z.object({
|
|
282
|
+
runs: z.array(workflowRunSchema).describe("Workflow run objects"),
|
|
283
|
+
}),
|
|
284
|
+
handler: ({ queryParams }: RouteHandlerArgs) =>
|
|
285
|
+
handleListRuns(queryParams ?? {}),
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
operationId: "getWorkflowRun",
|
|
289
|
+
endpoint: "workflows/runs/:id",
|
|
290
|
+
method: "GET",
|
|
291
|
+
policy: {
|
|
292
|
+
requiredScopes: ["settings.read"],
|
|
293
|
+
allowedPrincipalTypes: ACTOR_PRINCIPALS,
|
|
294
|
+
},
|
|
295
|
+
summary: "Get workflow run",
|
|
296
|
+
description: "Return a single workflow run by ID.",
|
|
297
|
+
tags: ["workflows"],
|
|
298
|
+
responseBody: workflowRunSchema,
|
|
299
|
+
additionalResponses: { "404": { description: "Run not found" } },
|
|
300
|
+
handler: ({ pathParams }: RouteHandlerArgs) => handleGetRun(pathParams!.id),
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
operationId: "abortWorkflowRun",
|
|
304
|
+
endpoint: "workflows/runs/:id/abort",
|
|
305
|
+
method: "POST",
|
|
306
|
+
policy: {
|
|
307
|
+
requiredScopes: ["settings.write"],
|
|
308
|
+
allowedPrincipalTypes: ACTOR_PRINCIPALS,
|
|
309
|
+
},
|
|
310
|
+
summary: "Abort workflow run",
|
|
311
|
+
description: "Signal an in-flight workflow run to abort.",
|
|
312
|
+
tags: ["workflows"],
|
|
313
|
+
responseBody: z.object({
|
|
314
|
+
ok: z.boolean(),
|
|
315
|
+
runId: z.string(),
|
|
316
|
+
}),
|
|
317
|
+
additionalResponses: { "404": { description: "Run not found" } },
|
|
318
|
+
handler: ({ pathParams }: RouteHandlerArgs) =>
|
|
319
|
+
handleAbortRun(pathParams!.id),
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
operationId: "resumeWorkflowRun",
|
|
323
|
+
endpoint: "workflows/runs/:id/resume",
|
|
324
|
+
method: "POST",
|
|
325
|
+
policy: {
|
|
326
|
+
requiredScopes: ["settings.write"],
|
|
327
|
+
allowedPrincipalTypes: ACTOR_PRINCIPALS,
|
|
328
|
+
},
|
|
329
|
+
summary: "Resume workflow run",
|
|
330
|
+
description:
|
|
331
|
+
"Resume an interrupted workflow run (one orphaned by an assistant restart), replaying its journaled prefix and continuing from the first unfinished step.",
|
|
332
|
+
tags: ["workflows"],
|
|
333
|
+
responseBody: z.object({
|
|
334
|
+
ok: z.boolean(),
|
|
335
|
+
runId: z.string(),
|
|
336
|
+
}),
|
|
337
|
+
additionalResponses: {
|
|
338
|
+
"404": { description: "Run not found" },
|
|
339
|
+
"409": { description: "Run is not resumable (not interrupted)" },
|
|
340
|
+
"429": { description: "Concurrent-run cap reached" },
|
|
341
|
+
},
|
|
342
|
+
handler: ({ pathParams }: RouteHandlerArgs) =>
|
|
343
|
+
handleResumeRun(pathParams!.id),
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
operationId: "listSavedWorkflows",
|
|
347
|
+
endpoint: "workflows",
|
|
348
|
+
method: "GET",
|
|
349
|
+
policy: {
|
|
350
|
+
requiredScopes: ["settings.read"],
|
|
351
|
+
allowedPrincipalTypes: ACTOR_PRINCIPALS,
|
|
352
|
+
},
|
|
353
|
+
summary: "List saved workflows",
|
|
354
|
+
description: "Return the saved (named) workflows the assistant can run.",
|
|
355
|
+
tags: ["workflows"],
|
|
356
|
+
responseBody: z.object({
|
|
357
|
+
workflows: z
|
|
358
|
+
.array(savedWorkflowSchema)
|
|
359
|
+
.describe("Saved workflow entries"),
|
|
360
|
+
}),
|
|
361
|
+
handler: () => handleListSavedWorkflows(),
|
|
362
|
+
},
|
|
363
|
+
];
|
|
@@ -5,7 +5,15 @@
|
|
|
5
5
|
* directory listing, file metadata, write/mkdir/rename/delete, and raw
|
|
6
6
|
* content serving with range support (HTTP-only).
|
|
7
7
|
*/
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
existsSync,
|
|
10
|
+
linkSync,
|
|
11
|
+
mkdirSync,
|
|
12
|
+
readdirSync,
|
|
13
|
+
readFileSync,
|
|
14
|
+
symlinkSync,
|
|
15
|
+
writeFileSync,
|
|
16
|
+
} from "node:fs";
|
|
9
17
|
import { join } from "node:path";
|
|
10
18
|
import { beforeAll, describe, expect, test } from "bun:test";
|
|
11
19
|
|
|
@@ -676,6 +684,58 @@ describe("POST /v1/workspace/rename", () => {
|
|
|
676
684
|
).toThrow(ConflictError);
|
|
677
685
|
});
|
|
678
686
|
|
|
687
|
+
// Hard links share an inode while being distinct entries, and POSIX
|
|
688
|
+
// rename() between two hard links is a silent no-op — must 409 instead
|
|
689
|
+
// of reporting a successful rename that never happened.
|
|
690
|
+
test("throws ConflictError when renaming a hard link over its sibling link", () => {
|
|
691
|
+
writeFileSync(join(testWorkspaceDir, "hard-a.txt"), "hard");
|
|
692
|
+
linkSync(
|
|
693
|
+
join(testWorkspaceDir, "hard-a.txt"),
|
|
694
|
+
join(testWorkspaceDir, "hard-b.txt"),
|
|
695
|
+
);
|
|
696
|
+
|
|
697
|
+
expect(() =>
|
|
698
|
+
handler({
|
|
699
|
+
body: { oldPath: "hard-a.txt", newPath: "hard-b.txt" },
|
|
700
|
+
}),
|
|
701
|
+
).toThrow(ConflictError);
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
// statSync would follow both links to the shared target and treat them as
|
|
705
|
+
// the same entry, letting the rename clobber a real, separate symlink.
|
|
706
|
+
test("throws ConflictError when renaming a symlink over a sibling symlink to the same target", () => {
|
|
707
|
+
writeFileSync(join(testWorkspaceDir, "link-target.txt"), "target");
|
|
708
|
+
symlinkSync(
|
|
709
|
+
join(testWorkspaceDir, "link-target.txt"),
|
|
710
|
+
join(testWorkspaceDir, "link-a.txt"),
|
|
711
|
+
);
|
|
712
|
+
symlinkSync(
|
|
713
|
+
join(testWorkspaceDir, "link-target.txt"),
|
|
714
|
+
join(testWorkspaceDir, "link-b.txt"),
|
|
715
|
+
);
|
|
716
|
+
|
|
717
|
+
expect(() =>
|
|
718
|
+
handler({
|
|
719
|
+
body: { oldPath: "link-a.txt", newPath: "link-b.txt" },
|
|
720
|
+
}),
|
|
721
|
+
).toThrow(ConflictError);
|
|
722
|
+
});
|
|
723
|
+
|
|
724
|
+
// On case-insensitive filesystems (macOS default) the destination "exists"
|
|
725
|
+
// because it resolves to the source itself — must rename, not conflict.
|
|
726
|
+
test("allows case-only rename of the same file", () => {
|
|
727
|
+
const srcPath = join(testWorkspaceDir, "CaseFile.txt");
|
|
728
|
+
writeFileSync(srcPath, "case test");
|
|
729
|
+
|
|
730
|
+
const result = handler({
|
|
731
|
+
body: { oldPath: "CaseFile.txt", newPath: "casefile.txt" },
|
|
732
|
+
}) as { oldPath: string; newPath: string };
|
|
733
|
+
expect(result.newPath).toBe("casefile.txt");
|
|
734
|
+
const names = readdirSync(testWorkspaceDir);
|
|
735
|
+
expect(names).toContain("casefile.txt");
|
|
736
|
+
expect(names).not.toContain("CaseFile.txt");
|
|
737
|
+
});
|
|
738
|
+
|
|
679
739
|
test("rejects path traversal on oldPath", () => {
|
|
680
740
|
expect(() =>
|
|
681
741
|
handler({
|
|
@@ -6,9 +6,11 @@
|
|
|
6
6
|
import {
|
|
7
7
|
type Dirent,
|
|
8
8
|
existsSync,
|
|
9
|
+
lstatSync,
|
|
9
10
|
mkdirSync,
|
|
10
11
|
readdirSync,
|
|
11
12
|
readFileSync,
|
|
13
|
+
realpathSync,
|
|
12
14
|
renameSync,
|
|
13
15
|
rmSync,
|
|
14
16
|
statSync,
|
|
@@ -448,6 +450,29 @@ function handleWorkspaceMkdir({ body, headers }: RouteHandlerArgs) {
|
|
|
448
450
|
// POST /v1/workspace/rename — rename/move files and directories
|
|
449
451
|
// ---------------------------------------------------------------------------
|
|
450
452
|
|
|
453
|
+
/**
|
|
454
|
+
* On case-insensitive filesystems (macOS/iOS defaults) a case-only rename's
|
|
455
|
+
* destination "exists" because it resolves to the source itself. Treat only
|
|
456
|
+
* true path aliases of one directory entry as the same file:
|
|
457
|
+
* - lstat, not stat, so two symlinks to one target stay distinct entries;
|
|
458
|
+
* - matching inodes alone are not enough — hard links share an inode while
|
|
459
|
+
* being distinct entries, and POSIX rename() between two hard links is a
|
|
460
|
+
* silent no-op. realpath canonicalizes case/normalization aliases of the
|
|
461
|
+
* same entry to one string, while distinct entries keep distinct names.
|
|
462
|
+
*/
|
|
463
|
+
function isSamePathAlias(a: string, b: string): boolean {
|
|
464
|
+
try {
|
|
465
|
+
const statA = lstatSync(a);
|
|
466
|
+
const statB = lstatSync(b);
|
|
467
|
+
if (statA.dev !== statB.dev || statA.ino !== statB.ino) {
|
|
468
|
+
return false;
|
|
469
|
+
}
|
|
470
|
+
return realpathSync(a) === realpathSync(b);
|
|
471
|
+
} catch {
|
|
472
|
+
return false;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
451
476
|
function handleWorkspaceRename({ body, headers }: RouteHandlerArgs) {
|
|
452
477
|
const oldPath = body?.oldPath as string | undefined;
|
|
453
478
|
const newPath = body?.newPath as string | undefined;
|
|
@@ -474,7 +499,7 @@ function handleWorkspaceRename({ body, headers }: RouteHandlerArgs) {
|
|
|
474
499
|
throw new NotFoundError("Source path not found");
|
|
475
500
|
}
|
|
476
501
|
|
|
477
|
-
if (existsSync(resolvedNew)) {
|
|
502
|
+
if (existsSync(resolvedNew) && !isSamePathAlias(resolvedOld, resolvedNew)) {
|
|
478
503
|
throw new ConflictError("Destination already exists");
|
|
479
504
|
}
|
|
480
505
|
|
|
@@ -365,6 +365,44 @@ describe("analyzeConversation", () => {
|
|
|
365
365
|
expect(mockAddMessage).not.toHaveBeenCalled();
|
|
366
366
|
});
|
|
367
367
|
|
|
368
|
+
test.each(["memory-retrospective", "memory-retrospective-fork"])(
|
|
369
|
+
"auto: rejects when the source conversation is a %s conversation",
|
|
370
|
+
async (source) => {
|
|
371
|
+
// Fork-kind retrospective conversations carry a full copy of the
|
|
372
|
+
// source conversation's history; auto-analyzing one would re-process
|
|
373
|
+
// the entire source conversation and double-write memory.
|
|
374
|
+
mockGetConversationSource.mockImplementation(() => source);
|
|
375
|
+
|
|
376
|
+
const result = await analyzeConversation("conv-1", {
|
|
377
|
+
trigger: "auto",
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
expect("error" in result).toBe(true);
|
|
381
|
+
if (!("error" in result)) throw new Error("expected error");
|
|
382
|
+
expect(result.error.kind).toBe("BAD_REQUEST");
|
|
383
|
+
expect(result.error.status).toBe(400);
|
|
384
|
+
|
|
385
|
+
// Nothing downstream of the guard should have fired.
|
|
386
|
+
expect(mockFindAnalysisConversationFor).not.toHaveBeenCalled();
|
|
387
|
+
expect(mockCreateConversation).not.toHaveBeenCalled();
|
|
388
|
+
expect(mockAddMessage).not.toHaveBeenCalled();
|
|
389
|
+
},
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
test("manual: does NOT reject a memory-retrospective conversation (guard is auto-only)", async () => {
|
|
393
|
+
mockGetConversationSource.mockImplementation(
|
|
394
|
+
() => "memory-retrospective-fork",
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
const result = await analyzeConversation("conv-1", {
|
|
398
|
+
trigger: "manual",
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
expect("error" in result).toBe(false);
|
|
402
|
+
if ("error" in result) throw new Error("expected success");
|
|
403
|
+
expect(result.analysisConversationId).toBe("analysis-new");
|
|
404
|
+
});
|
|
405
|
+
|
|
368
406
|
test("auto: routes the agent loop through callSite: 'analyzeConversation'", async () => {
|
|
369
407
|
await analyzeConversation("conv-1", { trigger: "auto" });
|
|
370
408
|
|
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
getMessages,
|
|
35
35
|
} from "../../memory/conversation-crud.js";
|
|
36
36
|
import { resolveConversationId } from "../../memory/conversation-key-store.js";
|
|
37
|
+
import { isMemoryRetrospectiveSource } from "../../memory/memory-retrospective-constants.js";
|
|
37
38
|
import { getLogger } from "../../util/logger.js";
|
|
38
39
|
import { assistantEventHub, broadcastMessage } from "../assistant-event-hub.js";
|
|
39
40
|
import {
|
|
@@ -118,19 +119,31 @@ export async function analyzeConversation(
|
|
|
118
119
|
|
|
119
120
|
// e. Defense-in-depth recursion guard for auto mode: refuse to
|
|
120
121
|
// auto-analyze a conversation that is itself an auto-analysis
|
|
121
|
-
// conversation
|
|
122
|
-
// self-analysis loops
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
) {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
122
|
+
// conversation (prevents job-handler bugs from triggering runaway
|
|
123
|
+
// self-analysis loops) or a memory-retrospective conversation
|
|
124
|
+
// (fork-kind retrospectives carry a full copy of the source history,
|
|
125
|
+
// so auto-analyzing one would re-process the entire source
|
|
126
|
+
// conversation and double-write memory).
|
|
127
|
+
if (opts.trigger === "auto") {
|
|
128
|
+
const source = getConversationSource(resolvedId);
|
|
129
|
+
if (source === AUTO_ANALYSIS_SOURCE) {
|
|
130
|
+
return {
|
|
131
|
+
error: {
|
|
132
|
+
kind: "BAD_REQUEST",
|
|
133
|
+
status: 400,
|
|
134
|
+
message: "Cannot auto-analyze an auto-analysis conversation",
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
if (source !== null && isMemoryRetrospectiveSource(source)) {
|
|
139
|
+
return {
|
|
140
|
+
error: {
|
|
141
|
+
kind: "BAD_REQUEST",
|
|
142
|
+
status: 400,
|
|
143
|
+
message: "Cannot auto-analyze a memory-retrospective conversation",
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
}
|
|
134
147
|
}
|
|
135
148
|
|
|
136
149
|
// f. Build the analysis transcript
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { getConfigReadOnly } from "../config/loader.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Validate a schedule's inference-profile key against the configured
|
|
5
|
+
* `llm.profiles` catalog. Returns a user-facing error message when the key is
|
|
6
|
+
* empty or unknown, or `null` when valid.
|
|
7
|
+
*
|
|
8
|
+
* Only create/update paths validate — a profile deleted after a schedule was
|
|
9
|
+
* created degrades gracefully at run time (the resolver silently drops a
|
|
10
|
+
* missing `overrideProfile` reference and falls through to the defaults).
|
|
11
|
+
*/
|
|
12
|
+
export function validateScheduleInferenceProfile(
|
|
13
|
+
profile: string,
|
|
14
|
+
): string | null {
|
|
15
|
+
if (!profile.trim()) {
|
|
16
|
+
return "inferenceProfile must be a non-empty string";
|
|
17
|
+
}
|
|
18
|
+
const profiles = getConfigReadOnly().llm?.profiles ?? {};
|
|
19
|
+
if (!Object.prototype.hasOwnProperty.call(profiles, profile)) {
|
|
20
|
+
const available = Object.keys(profiles).sort();
|
|
21
|
+
const hint =
|
|
22
|
+
available.length > 0
|
|
23
|
+
? ` Available profiles: ${available.join(", ")}.`
|
|
24
|
+
: " No profiles defined in llm.profiles.";
|
|
25
|
+
return `Inference profile "${profile}" is not defined in llm.profiles.${hint}`;
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
}
|