@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,589 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ephemeral leaf runner for the workflow orchestration engine.
|
|
3
|
+
*
|
|
4
|
+
* A workflow script (run in a sandbox by a later PR) fans out to many "leaf"
|
|
5
|
+
* agents; the engine calls {@link runLeaf} once per leaf. A leaf is deliberately
|
|
6
|
+
* cheap and ephemeral — it must NOT create a conversation row, a jsonl mirror, a
|
|
7
|
+
* title-generation job, or broadcast turn lifecycle events. By default a leaf is
|
|
8
|
+
* anonymous: a minimal task-scoped system prompt, no assistant identity, and no
|
|
9
|
+
* memory pipeline.
|
|
10
|
+
*
|
|
11
|
+
* Setting `persona: true` opts a leaf into PERSONA mode: it carries the
|
|
12
|
+
* assistant's identity system prompt (`buildSystemPrompt`) AND runs the same
|
|
13
|
+
* memory-injection pipeline a main-agent turn uses
|
|
14
|
+
* (`ConversationGraphMemory.prepareMemory`), so its output is authentically
|
|
15
|
+
* "the assistant" — e.g. drafting a reply in the assistant's voice. This is the
|
|
16
|
+
* costly path by design. Model resolution mirrors `mainAgent`: with no explicit
|
|
17
|
+
* `profile`, the workspace `activeProfile` is used as the `overrideProfile`.
|
|
18
|
+
* Persona leaves keep the same no-persistence guarantee — no conversation row
|
|
19
|
+
* is ever created.
|
|
20
|
+
*
|
|
21
|
+
* Two leaf shapes are supported:
|
|
22
|
+
*
|
|
23
|
+
* - **Schema path** (`schema` provided, no `tools`): a single forced
|
|
24
|
+
* `tool_choice` provider call whose synthetic tool's input schema is the
|
|
25
|
+
* caller's Zod schema. The returned tool input is Zod-validated and returned
|
|
26
|
+
* as `output`. Copies the proven pattern from `memory/v2/router.ts` and
|
|
27
|
+
* `memory/graph/extraction.ts`.
|
|
28
|
+
* - **Tool path** (`tools` provided, no `schema`): a real {@link AgentLoop} with
|
|
29
|
+
* a minimal task-scoped system prompt and a tool-executor restricted to the
|
|
30
|
+
* supplied registry tools. No conversation is bootstrapped — the loop runs
|
|
31
|
+
* over an in-memory message array and its final text is returned as `output`.
|
|
32
|
+
*
|
|
33
|
+
* Profile overrides are validated up front (a deliberate deviation from the
|
|
34
|
+
* resolver's silent fall-through — an explicit script request must fail loudly)
|
|
35
|
+
* and threaded through as the `overrideProfile` argument so the per-call-site
|
|
36
|
+
* config resolution picks them up. Retry/rate-limit handling is NOT added here:
|
|
37
|
+
* the configured provider is already wrapped by `RetryProvider`/`RateLimitProvider`.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
import { randomUUID } from "node:crypto";
|
|
41
|
+
|
|
42
|
+
import { z } from "zod";
|
|
43
|
+
|
|
44
|
+
import { type AgentEvent, AgentLoop } from "../agent/loop.js";
|
|
45
|
+
import { getConfig } from "../config/loader.js";
|
|
46
|
+
import type { ServerMessage } from "../daemon/message-protocol.js";
|
|
47
|
+
import {
|
|
48
|
+
isPersonalMemoryAllowed,
|
|
49
|
+
type TrustContext,
|
|
50
|
+
} from "../daemon/trust-context.js";
|
|
51
|
+
import { ConversationGraphMemory } from "../memory/graph/conversation-graph-memory.js";
|
|
52
|
+
import { buildSystemPrompt } from "../prompts/system-prompt.js";
|
|
53
|
+
import {
|
|
54
|
+
extractToolUse,
|
|
55
|
+
getConfiguredProvider,
|
|
56
|
+
} from "../providers/provider-send-message.js";
|
|
57
|
+
import type { Message, ToolDefinition } from "../providers/types.js";
|
|
58
|
+
import { broadcastMessage } from "../runtime/assistant-event-hub.js";
|
|
59
|
+
import type { Tool, ToolContext, ToolExecutionResult } from "../tools/types.js";
|
|
60
|
+
import { getLogger } from "../util/logger.js";
|
|
61
|
+
import { getWorkspaceDir } from "../util/platform.js";
|
|
62
|
+
|
|
63
|
+
const log = getLogger("workflow-leaf-runner");
|
|
64
|
+
|
|
65
|
+
/** Call site every leaf provider call resolves through (added in PR 5). */
|
|
66
|
+
const LEAF_CALL_SITE = "workflowLeaf" as const;
|
|
67
|
+
|
|
68
|
+
/** Tool name forced via `tool_choice` on the schema path. */
|
|
69
|
+
const SCHEMA_TOOL_NAME = "emit_result";
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Minimal anonymous system prompt for the tool path. Deliberately carries NO
|
|
73
|
+
* assistant identity, persona, or workspace context — a leaf is a task-scoped
|
|
74
|
+
* worker, not the assistant.
|
|
75
|
+
*/
|
|
76
|
+
const LEAF_SYSTEM_PROMPT =
|
|
77
|
+
"You are a focused task worker. Complete the task described in the user " +
|
|
78
|
+
"message using the available tools, then reply with the final result as " +
|
|
79
|
+
"plain text. Be concise.";
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Resolve the system prompt and the (possibly memory-injected) message array
|
|
83
|
+
* for a leaf. Anonymous leaves use the minimal task prompt and the raw prompt
|
|
84
|
+
* message unchanged. Persona leaves assemble the assistant's identity system
|
|
85
|
+
* prompt and run the main-agent memory-injection pipeline, REUSING
|
|
86
|
+
* `buildSystemPrompt` and `ConversationGraphMemory.prepareMemory` — no fork.
|
|
87
|
+
*
|
|
88
|
+
* The memory pipeline keys off an ephemeral conversation id and is disposed in
|
|
89
|
+
* a finally block; it reads the memory graph and prepends a `<memory>` block to
|
|
90
|
+
* the messages WITHOUT creating any conversation row (consistent with the
|
|
91
|
+
* anonymous path's no-persistence guarantee).
|
|
92
|
+
*/
|
|
93
|
+
async function resolveLeafContext(
|
|
94
|
+
opts: RunLeafOptions,
|
|
95
|
+
): Promise<{ systemPrompt: string; messages: Message[] }> {
|
|
96
|
+
const userMessages: Message[] = [
|
|
97
|
+
{ role: "user", content: [{ type: "text", text: opts.prompt }] },
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
if (!opts.persona) {
|
|
101
|
+
return { systemPrompt: LEAF_SYSTEM_PROMPT, messages: userMessages };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const systemPrompt = buildSystemPrompt({
|
|
105
|
+
trustContext: opts.trustContext,
|
|
106
|
+
// A persona leaf is not an onboarding turn, and it must not emit the
|
|
107
|
+
// first-run bootstrap block: keep it to the assistant's stable identity.
|
|
108
|
+
excludeBootstrap: true,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const messages = await injectPersonaMemory(opts, userMessages);
|
|
112
|
+
return { systemPrompt, messages };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Run the main-agent memory-injection pipeline for a persona leaf and return
|
|
117
|
+
* the messages with the retrieved `<memory>` block prepended. Constructs an
|
|
118
|
+
* ephemeral, non-persisting {@link ConversationGraphMemory} handle keyed by a
|
|
119
|
+
* synthetic id (it registers itself in the live-by-conversation map, so it is
|
|
120
|
+
* disposed in `finally` to avoid leaking the handle). Best-effort: a retrieval
|
|
121
|
+
* failure leaves the messages unchanged rather than failing the leaf.
|
|
122
|
+
*
|
|
123
|
+
* Gated by the personal-memory trust check — THE canonical gate
|
|
124
|
+
* ({@link isPersonalMemoryAllowed}, which folds in `resolveTrustClass` and the
|
|
125
|
+
* HTTP-auth-disabled dev bypass). A persona leaf retrieves the same private
|
|
126
|
+
* user content (`<memory>` graph) a main-agent turn does, so it honors the same
|
|
127
|
+
* gate the normal turn applies before `prepareMemory`
|
|
128
|
+
* (`userPromptSubmitMemoryRetrieval`): an untrusted (non-guardian) requester's
|
|
129
|
+
* persona leaf still gets the assistant's identity system prompt but NO personal
|
|
130
|
+
* memory. Without this, a workflow launched by an untrusted actor whose manifest
|
|
131
|
+
* grants `persona` could exfiltrate private memory in its output.
|
|
132
|
+
*/
|
|
133
|
+
async function injectPersonaMemory(
|
|
134
|
+
opts: RunLeafOptions,
|
|
135
|
+
messages: Message[],
|
|
136
|
+
): Promise<Message[]> {
|
|
137
|
+
if (!isPersonalMemoryAllowed(opts.trustContext)) return messages;
|
|
138
|
+
|
|
139
|
+
const config = getConfig();
|
|
140
|
+
if (config.memory?.enabled === false) return messages;
|
|
141
|
+
|
|
142
|
+
const ephemeralConversationId = `workflow-leaf:${randomUUID()}`;
|
|
143
|
+
const graphMemory = new ConversationGraphMemory(ephemeralConversationId);
|
|
144
|
+
// The memory pipeline broadcasts retrieval progress to the shared event hub
|
|
145
|
+
// (matching the main-agent hook); a leaf has no per-turn event callback.
|
|
146
|
+
const onEvent = (msg: ServerMessage): void => {
|
|
147
|
+
broadcastMessage(msg);
|
|
148
|
+
};
|
|
149
|
+
// `prepareMemory` requires a non-aborting signal; reuse the caller's when
|
|
150
|
+
// present so cancellation propagates.
|
|
151
|
+
const signal = opts.signal ?? new AbortController().signal;
|
|
152
|
+
try {
|
|
153
|
+
const result = await graphMemory.prepareMemory(
|
|
154
|
+
messages,
|
|
155
|
+
config,
|
|
156
|
+
signal,
|
|
157
|
+
onEvent,
|
|
158
|
+
);
|
|
159
|
+
return result.runMessages;
|
|
160
|
+
} catch (err) {
|
|
161
|
+
log.warn({ err }, "Persona leaf memory injection failed (non-fatal)");
|
|
162
|
+
return messages;
|
|
163
|
+
} finally {
|
|
164
|
+
graphMemory.dispose();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Thrown when {@link runLeaf} is asked to use an inference profile that does not
|
|
170
|
+
* exist in `llm.profiles`. A DELIBERATE deviation from the resolver's silent
|
|
171
|
+
* fall-through: an explicit script request must fail loudly rather than be
|
|
172
|
+
* downgraded to the default profile without the author noticing.
|
|
173
|
+
*/
|
|
174
|
+
export class WorkflowUnknownProfileError extends Error {
|
|
175
|
+
constructor(readonly profile: string) {
|
|
176
|
+
super(
|
|
177
|
+
`Workflow leaf requested inference profile "${profile}", which is not ` +
|
|
178
|
+
`defined in llm.profiles.`,
|
|
179
|
+
);
|
|
180
|
+
this.name = "WorkflowUnknownProfileError";
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** Options for a single leaf run. */
|
|
185
|
+
export interface RunLeafOptions {
|
|
186
|
+
/** The task prompt handed to the leaf as the user message. */
|
|
187
|
+
prompt: string;
|
|
188
|
+
/** Optional human-readable label for logging/diagnostics. */
|
|
189
|
+
label?: string;
|
|
190
|
+
/**
|
|
191
|
+
* When provided (and `tools` is absent), runs the schema path: a single
|
|
192
|
+
* forced-tool-choice call whose returned input is validated against this
|
|
193
|
+
* schema and returned as `output`.
|
|
194
|
+
*
|
|
195
|
+
* Accepts EITHER a host-side Zod schema (host callers) OR a plain JSON Schema
|
|
196
|
+
* object. A workflow script runs inside the QuickJS sandbox and cannot hold a
|
|
197
|
+
* Zod object (Zod is host-side), so a script's `leaf(prompt, { schema })`
|
|
198
|
+
* crosses the sandbox→host boundary as a JSON-marshaled JSON Schema. The
|
|
199
|
+
* runner duck-types the input and handles both shapes (see {@link runSchemaLeaf}).
|
|
200
|
+
*/
|
|
201
|
+
schema?: z.ZodType | Record<string, unknown>;
|
|
202
|
+
/**
|
|
203
|
+
* When provided (and `schema` is absent), runs the tool path: an agent loop
|
|
204
|
+
* restricted to exactly these registry tools. These are the resolved
|
|
205
|
+
* {@link Tool} objects from `ResolvedCapabilities.tools`.
|
|
206
|
+
*/
|
|
207
|
+
tools?: Tool[];
|
|
208
|
+
/**
|
|
209
|
+
* Optional inference-profile override. Validated against `llm.profiles`;
|
|
210
|
+
* an unknown profile throws {@link WorkflowUnknownProfileError}. An explicit
|
|
211
|
+
* profile ALWAYS wins over the persona default (see {@link persona}).
|
|
212
|
+
*/
|
|
213
|
+
profile?: string;
|
|
214
|
+
/**
|
|
215
|
+
* Opt-in persona mode. When `true`, the leaf carries the assistant's identity
|
|
216
|
+
* system prompt (`buildSystemPrompt`) AND runs the same memory-injection
|
|
217
|
+
* pipeline a normal main-agent turn uses (`ConversationGraphMemory.prepareMemory`),
|
|
218
|
+
* so its output is authentically "the assistant" (e.g. for drafting replies in
|
|
219
|
+
* the assistant's voice). This is the costly path by design.
|
|
220
|
+
*
|
|
221
|
+
* Model resolution mirrors `mainAgent`: when no explicit {@link profile} is
|
|
222
|
+
* given, the workspace `activeProfile` is used as the `overrideProfile`.
|
|
223
|
+
*
|
|
224
|
+
* When falsy (the default), the leaf stays ANONYMOUS: minimal task prompt, no
|
|
225
|
+
* identity, no memory pipeline. Persona shares the anonymous path's
|
|
226
|
+
* no-persistence guarantee — it never creates a conversation row.
|
|
227
|
+
*/
|
|
228
|
+
persona?: boolean;
|
|
229
|
+
/** Cooperative cancellation signal. */
|
|
230
|
+
signal?: AbortSignal;
|
|
231
|
+
/** Trust/auth context for the run, forwarded to the agent loop. */
|
|
232
|
+
trustContext: TrustContext;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** Result of a single leaf run. */
|
|
236
|
+
export interface LeafResult {
|
|
237
|
+
/**
|
|
238
|
+
* The leaf's output — the Zod-validated object on the schema path, or the
|
|
239
|
+
* final assistant text on the tool path.
|
|
240
|
+
*/
|
|
241
|
+
output: unknown;
|
|
242
|
+
/** Total input tokens across the leaf's provider call(s). */
|
|
243
|
+
inputTokens: number;
|
|
244
|
+
/** Total output tokens across the leaf's provider call(s). */
|
|
245
|
+
outputTokens: number;
|
|
246
|
+
/** Number of tool invocations the leaf made (always 0 on the schema path). */
|
|
247
|
+
toolCallCount: number;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Run a single ephemeral leaf agent. See the module doc for the two shapes and
|
|
252
|
+
* the persistence guarantees (no conversation row, jsonl, title job, or
|
|
253
|
+
* broadcast).
|
|
254
|
+
*/
|
|
255
|
+
export async function runLeaf(opts: RunLeafOptions): Promise<LeafResult> {
|
|
256
|
+
if (opts.schema && opts.tools && opts.tools.length > 0) {
|
|
257
|
+
throw new Error(
|
|
258
|
+
"runLeaf: provide either `schema` (structured output) or `tools` " +
|
|
259
|
+
"(agent loop), not both.",
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const overrideProfile = resolveLeafOverrideProfile(opts);
|
|
264
|
+
|
|
265
|
+
// Dispatch on schema presence, NOT on a non-empty tool set: a schema leaf does
|
|
266
|
+
// forced-tool-choice structured output; everything else runs the agent-loop
|
|
267
|
+
// tool path, which handles an EMPTY tool array as a plain text leaf. Keying off
|
|
268
|
+
// `tools.length > 0` would route a no-schema leaf with an empty resolved
|
|
269
|
+
// toolset (e.g. the read-only baseline is momentarily empty if a schedule
|
|
270
|
+
// fires before `initializeTools()` populates it) into `runSchemaLeaf` with no
|
|
271
|
+
// schema, where `schemaToInputSchema(undefined)` throws.
|
|
272
|
+
if (opts.schema !== undefined) {
|
|
273
|
+
return runSchemaLeaf(opts, overrideProfile);
|
|
274
|
+
}
|
|
275
|
+
return runToolLeaf(opts, overrideProfile);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Resolve the `overrideProfile` forwarded into the per-call-site config
|
|
280
|
+
* resolution for a leaf.
|
|
281
|
+
*
|
|
282
|
+
* An explicit `opts.profile` is validated and always wins. Otherwise, for a
|
|
283
|
+
* persona leaf, the resolution mirrors `mainAgent`: the workspace
|
|
284
|
+
* `activeProfile` is used as the `overrideProfile` (the active profile reflects
|
|
285
|
+
* the user's chat-model selection). Anonymous leaves with no explicit profile
|
|
286
|
+
* resolve to `undefined` (shipped call-site defaults apply).
|
|
287
|
+
*/
|
|
288
|
+
function resolveLeafOverrideProfile(opts: RunLeafOptions): string | undefined {
|
|
289
|
+
if (opts.profile !== undefined) {
|
|
290
|
+
return validateProfile(opts.profile);
|
|
291
|
+
}
|
|
292
|
+
if (opts.persona) {
|
|
293
|
+
// Mirror `mainAgent`: the workspace active profile floats above the
|
|
294
|
+
// call-site default. Unlike an explicit request, a missing/deleted active
|
|
295
|
+
// profile degrades gracefully (it is not statically validated) — a
|
|
296
|
+
// non-existent `activeProfile` falls through to the shipped default rather
|
|
297
|
+
// than throwing, matching the `mainAgent` resolver's tolerance for a stale
|
|
298
|
+
// `activeProfile`.
|
|
299
|
+
const activeProfile = getConfig().llm.activeProfile;
|
|
300
|
+
return activeProfile != null && profileExists(activeProfile)
|
|
301
|
+
? activeProfile
|
|
302
|
+
: undefined;
|
|
303
|
+
}
|
|
304
|
+
return undefined;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Validate an explicit profile request against `llm.profiles`. Throws
|
|
309
|
+
* {@link WorkflowUnknownProfileError} for an unknown profile — an explicit
|
|
310
|
+
* script request must fail loudly rather than be silently downgraded.
|
|
311
|
+
*/
|
|
312
|
+
function validateProfile(profile: string): string {
|
|
313
|
+
if (!profileExists(profile)) {
|
|
314
|
+
throw new WorkflowUnknownProfileError(profile);
|
|
315
|
+
}
|
|
316
|
+
return profile;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function profileExists(profile: string): boolean {
|
|
320
|
+
return profile in (getConfig().llm.profiles ?? {});
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Schema path: one forced-tool-choice provider call returning structured
|
|
325
|
+
* output. Mirrors `runRouterBatch`/`extractGraphDiff`.
|
|
326
|
+
*/
|
|
327
|
+
async function runSchemaLeaf(
|
|
328
|
+
opts: RunLeafOptions,
|
|
329
|
+
overrideProfile: string | undefined,
|
|
330
|
+
): Promise<LeafResult> {
|
|
331
|
+
const schema = opts.schema as z.ZodType | Record<string, unknown>;
|
|
332
|
+
|
|
333
|
+
const { systemPrompt, messages } = await resolveLeafContext(opts);
|
|
334
|
+
|
|
335
|
+
const provider = await getConfiguredProvider(LEAF_CALL_SITE, {
|
|
336
|
+
...(overrideProfile !== undefined ? { overrideProfile } : {}),
|
|
337
|
+
});
|
|
338
|
+
if (!provider) {
|
|
339
|
+
throw new Error(
|
|
340
|
+
`Workflow leaf provider unavailable for call site "${LEAF_CALL_SITE}".`,
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const tool: ToolDefinition = {
|
|
345
|
+
name: SCHEMA_TOOL_NAME,
|
|
346
|
+
description:
|
|
347
|
+
"Return the result for this task. Call this tool exactly once with the " +
|
|
348
|
+
"structured result.",
|
|
349
|
+
input_schema: schemaToInputSchema(schema),
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
const response = await provider.sendMessage(messages, {
|
|
353
|
+
tools: [tool],
|
|
354
|
+
systemPrompt,
|
|
355
|
+
config: {
|
|
356
|
+
callSite: LEAF_CALL_SITE,
|
|
357
|
+
tool_choice: { type: "tool" as const, name: SCHEMA_TOOL_NAME },
|
|
358
|
+
// `provider` is a CallSiteConfiguredProvider holding our overrideProfile,
|
|
359
|
+
// but it only injects that stored override when the per-call config omits
|
|
360
|
+
// `callSite` — and we set `callSite` here to drive resolveCallSiteConfig.
|
|
361
|
+
// So pass overrideProfile through explicitly (mirroring how AgentLoop
|
|
362
|
+
// builds its provider config on the tool path), or an explicit/persona
|
|
363
|
+
// profile selection is silently dropped and the leaf runs under the
|
|
364
|
+
// default workflowLeaf profile.
|
|
365
|
+
...(overrideProfile !== undefined ? { overrideProfile } : {}),
|
|
366
|
+
},
|
|
367
|
+
...(opts.signal ? { signal: opts.signal } : {}),
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
const toolBlock = extractToolUse(response);
|
|
371
|
+
if (!toolBlock || toolBlock.name !== SCHEMA_TOOL_NAME) {
|
|
372
|
+
throw new Error(
|
|
373
|
+
`Workflow leaf "${opts.label ?? "schema"}" returned no ` +
|
|
374
|
+
`${SCHEMA_TOOL_NAME} tool_use block (stopReason: ${response.stopReason}).`,
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const parsed = validateSchemaOutput(schema, toolBlock.input);
|
|
379
|
+
if (!parsed.success) {
|
|
380
|
+
throw new Error(
|
|
381
|
+
`Workflow leaf "${opts.label ?? "schema"}" output failed schema ` +
|
|
382
|
+
`validation: ${parsed.error.message}`,
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return {
|
|
387
|
+
output: parsed.data,
|
|
388
|
+
inputTokens: response.usage.inputTokens,
|
|
389
|
+
outputTokens: response.usage.outputTokens,
|
|
390
|
+
toolCallCount: 0,
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Tool path: a real agent loop with a restricted toolset and no conversation
|
|
396
|
+
* persistence. The loop runs over an in-memory message array; its events are
|
|
397
|
+
* consumed only to accumulate usage/tool-call counts. No DB row, jsonl, title
|
|
398
|
+
* job, or broadcast is created.
|
|
399
|
+
*/
|
|
400
|
+
async function runToolLeaf(
|
|
401
|
+
opts: RunLeafOptions,
|
|
402
|
+
overrideProfile: string | undefined,
|
|
403
|
+
): Promise<LeafResult> {
|
|
404
|
+
const tools = opts.tools ?? [];
|
|
405
|
+
|
|
406
|
+
const { systemPrompt, messages } = await resolveLeafContext(opts);
|
|
407
|
+
|
|
408
|
+
const provider = await getConfiguredProvider(LEAF_CALL_SITE, {
|
|
409
|
+
...(overrideProfile !== undefined ? { overrideProfile } : {}),
|
|
410
|
+
});
|
|
411
|
+
if (!provider) {
|
|
412
|
+
throw new Error(
|
|
413
|
+
`Workflow leaf provider unavailable for call site "${LEAF_CALL_SITE}".`,
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Restrict execution to exactly the supplied tools. The agent loop calls
|
|
418
|
+
// this back per tool_use; anything outside the set is a hard error (the
|
|
419
|
+
// model cannot have been handed an out-of-set tool, so this is defense in
|
|
420
|
+
// depth).
|
|
421
|
+
const toolsByName = new Map<string, Tool>(tools.map((t) => [t.name, t]));
|
|
422
|
+
|
|
423
|
+
// A synthetic conversation id is needed for the loop's internal bookkeeping
|
|
424
|
+
// (compaction circuit breaker keying) but is NEVER persisted — no
|
|
425
|
+
// conversation row is created for it.
|
|
426
|
+
const ephemeralConversationId = `workflow-leaf:${randomUUID()}`;
|
|
427
|
+
|
|
428
|
+
const loop = new AgentLoop({
|
|
429
|
+
provider,
|
|
430
|
+
systemPrompt,
|
|
431
|
+
tools: tools as ToolDefinition[],
|
|
432
|
+
toolExecutor: (name, input, onOutput) =>
|
|
433
|
+
executeLeafTool(toolsByName, name, input, {
|
|
434
|
+
ephemeralConversationId,
|
|
435
|
+
// Bind leaf file tools to the WORKSPACE, not the daemon's cwd: the CLI
|
|
436
|
+
// starts the daemon with `cwd: dirname(daemonBinary)`, so `process.cwd()`
|
|
437
|
+
// is the install dir. File tools resolve relative paths and enforce the
|
|
438
|
+
// sandbox boundary against this dir, so a workspace path would resolve
|
|
439
|
+
// wrong / be rejected as out-of-bounds. Mirrors the scheduled-task path
|
|
440
|
+
// (`runtime/routes/task-routes.ts`), which also uses `getWorkspaceDir()`.
|
|
441
|
+
workingDir: getWorkspaceDir(),
|
|
442
|
+
signal: opts.signal,
|
|
443
|
+
onOutput,
|
|
444
|
+
trustContext: opts.trustContext,
|
|
445
|
+
}),
|
|
446
|
+
conversationId: ephemeralConversationId,
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
let inputTokens = 0;
|
|
450
|
+
let outputTokens = 0;
|
|
451
|
+
let toolCallCount = 0;
|
|
452
|
+
const onEvent = (event: AgentEvent): void => {
|
|
453
|
+
if (event.type === "usage") {
|
|
454
|
+
inputTokens += event.inputTokens;
|
|
455
|
+
outputTokens += event.outputTokens;
|
|
456
|
+
} else if (event.type === "tool_use") {
|
|
457
|
+
toolCallCount += 1;
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
const result = await loop.run({
|
|
462
|
+
messages,
|
|
463
|
+
onEvent,
|
|
464
|
+
requestId: ephemeralConversationId,
|
|
465
|
+
trust: opts.trustContext,
|
|
466
|
+
...(opts.signal ? { signal: opts.signal } : {}),
|
|
467
|
+
...(overrideProfile !== undefined ? { overrideProfile } : {}),
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
return {
|
|
471
|
+
output: finalAssistantText(result.history),
|
|
472
|
+
inputTokens,
|
|
473
|
+
outputTokens,
|
|
474
|
+
toolCallCount,
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Execute one tool invocation for the tool path, restricted to the supplied
|
|
480
|
+
* set. Builds a minimal {@link ToolContext} — the leaf is anonymous, runs in
|
|
481
|
+
* the sandbox, and self-approves as the requested trust class (the workflow
|
|
482
|
+
* manifest is the single consent point, resolved by the engine before the run).
|
|
483
|
+
*/
|
|
484
|
+
async function executeLeafTool(
|
|
485
|
+
toolsByName: Map<string, Tool>,
|
|
486
|
+
name: string,
|
|
487
|
+
input: Record<string, unknown>,
|
|
488
|
+
ctx: {
|
|
489
|
+
ephemeralConversationId: string;
|
|
490
|
+
workingDir: string;
|
|
491
|
+
signal?: AbortSignal;
|
|
492
|
+
onOutput?: (chunk: string) => void;
|
|
493
|
+
trustContext: TrustContext;
|
|
494
|
+
},
|
|
495
|
+
): Promise<ToolExecutionResult> {
|
|
496
|
+
const tool = toolsByName.get(name);
|
|
497
|
+
if (!tool) {
|
|
498
|
+
return {
|
|
499
|
+
content: `Tool "${name}" is not available to this workflow leaf.`,
|
|
500
|
+
isError: true,
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const toolContext: ToolContext = {
|
|
505
|
+
conversationId: ctx.ephemeralConversationId,
|
|
506
|
+
workingDir: ctx.workingDir,
|
|
507
|
+
requestId: ctx.ephemeralConversationId,
|
|
508
|
+
trustClass: ctx.trustContext.trustClass,
|
|
509
|
+
allowedToolNames: new Set(toolsByName.keys()),
|
|
510
|
+
...(ctx.signal ? { signal: ctx.signal } : {}),
|
|
511
|
+
...(ctx.onOutput ? { onOutput: ctx.onOutput } : {}),
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
try {
|
|
515
|
+
return await tool.execute(input, toolContext);
|
|
516
|
+
} catch (err) {
|
|
517
|
+
log.warn({ err, tool: name }, "Workflow leaf tool execution threw");
|
|
518
|
+
return {
|
|
519
|
+
content: `Tool "${name}" failed: ${
|
|
520
|
+
err instanceof Error ? err.message : String(err)
|
|
521
|
+
}`,
|
|
522
|
+
isError: true,
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/** Join the text blocks of the final assistant message in `history`. */
|
|
528
|
+
function finalAssistantText(history: Message[]): string {
|
|
529
|
+
for (let i = history.length - 1; i >= 0; i--) {
|
|
530
|
+
const msg = history[i];
|
|
531
|
+
if (msg.role !== "assistant") continue;
|
|
532
|
+
const text = msg.content
|
|
533
|
+
.filter(
|
|
534
|
+
(b): b is Extract<typeof b, { type: "text" }> => b.type === "text",
|
|
535
|
+
)
|
|
536
|
+
.map((b) => b.text)
|
|
537
|
+
.join("");
|
|
538
|
+
return text.trim();
|
|
539
|
+
}
|
|
540
|
+
return "";
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* A leaf's structured-output schema is EITHER a host-side Zod schema (passed by
|
|
545
|
+
* a host caller) or a plain JSON Schema object (a script's `leaf(prompt,
|
|
546
|
+
* { schema })` is JSON-marshaled across the sandbox→host boundary, since the
|
|
547
|
+
* sandbox can't hold a Zod object). Duck-type the input: a Zod schema exposes a
|
|
548
|
+
* `safeParse` method (and the internal `_zod` marker), a JSON Schema object does
|
|
549
|
+
* not.
|
|
550
|
+
*/
|
|
551
|
+
function isZodSchema(
|
|
552
|
+
schema: z.ZodType | Record<string, unknown>,
|
|
553
|
+
): schema is z.ZodType {
|
|
554
|
+
return typeof (schema as { safeParse?: unknown }).safeParse === "function";
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Build the synthetic forced-tool's `input_schema` from the leaf's schema. A Zod
|
|
559
|
+
* schema is converted via Zod 4's native `z.toJSONSchema` (mirrors
|
|
560
|
+
* `scripts/generate-openapi.ts`), stripping the `$schema` key tool definitions
|
|
561
|
+
* don't carry. A plain JSON Schema object is used directly (same `$schema`
|
|
562
|
+
* strip for parity).
|
|
563
|
+
*/
|
|
564
|
+
function schemaToInputSchema(
|
|
565
|
+
schema: z.ZodType | Record<string, unknown>,
|
|
566
|
+
): Record<string, unknown> {
|
|
567
|
+
const converted = isZodSchema(schema)
|
|
568
|
+
? (z.toJSONSchema(schema, { unrepresentable: "any" }) as Record<
|
|
569
|
+
string,
|
|
570
|
+
unknown
|
|
571
|
+
>)
|
|
572
|
+
: schema;
|
|
573
|
+
const { $schema: _dropped, ...rest } = converted;
|
|
574
|
+
return rest;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* Validate the model's returned tool input against the leaf's schema. A Zod
|
|
579
|
+
* schema validates directly; a plain JSON Schema object is reconstructed into a
|
|
580
|
+
* Zod schema via `z.fromJSONSchema` first. Returns Zod's native `safeParse`
|
|
581
|
+
* result so callers handle both shapes uniformly.
|
|
582
|
+
*/
|
|
583
|
+
function validateSchemaOutput(
|
|
584
|
+
schema: z.ZodType | Record<string, unknown>,
|
|
585
|
+
input: unknown,
|
|
586
|
+
) {
|
|
587
|
+
const zodSchema = isZodSchema(schema) ? schema : z.fromJSONSchema(schema);
|
|
588
|
+
return zodSchema.safeParse(input);
|
|
589
|
+
}
|