@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
|
@@ -68,6 +68,13 @@ let riskOverride: string = "high";
|
|
|
68
68
|
/** Scope options override — controls whether persistent decisions are offered. */
|
|
69
69
|
let scopeOptionsOverride: ScopeOption[] | undefined;
|
|
70
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Auto-approve threshold returned to the permission checker. "high" is the
|
|
73
|
+
* full-access posture under which the requireFreshApproval promotion is
|
|
74
|
+
* skipped; every lower value still promotes allow → prompt.
|
|
75
|
+
*/
|
|
76
|
+
let thresholdOverride: "none" | "low" | "medium" | "high" = "medium";
|
|
77
|
+
|
|
71
78
|
mock.module("../config/loader.js", () => ({
|
|
72
79
|
getConfig: () => mockConfig,
|
|
73
80
|
loadConfig: () => mockConfig,
|
|
@@ -124,13 +131,26 @@ mock.module("../tools/registry.js", () => ({
|
|
|
124
131
|
}));
|
|
125
132
|
|
|
126
133
|
mock.module("../permissions/gateway-threshold-reader.js", () => ({
|
|
127
|
-
getAutoApproveThreshold: async () =>
|
|
134
|
+
getAutoApproveThreshold: async () => thresholdOverride,
|
|
128
135
|
// Refresh failure ("null") keeps the original decision — these tests
|
|
129
136
|
// exercise the cached-threshold paths only.
|
|
130
137
|
refreshAutoApproveThreshold: async () => null,
|
|
131
138
|
_clearGlobalCacheForTesting: () => {},
|
|
132
139
|
}));
|
|
133
140
|
|
|
141
|
+
// Stub the workflow run manager so the `manage_workflows` resume gate can read a
|
|
142
|
+
// target run's STORED capabilities without a real journal/DB. Tests set
|
|
143
|
+
// `fakeWorkflowRun` to control the stored manifest (or null for "not found").
|
|
144
|
+
let fakeWorkflowRun: {
|
|
145
|
+
capabilities: unknown;
|
|
146
|
+
conversationId: string | null;
|
|
147
|
+
} | null = null;
|
|
148
|
+
mock.module("../workflows/run-manager.js", () => ({
|
|
149
|
+
getWorkflowRunManager: () => ({
|
|
150
|
+
status: (_runId: string) => fakeWorkflowRun,
|
|
151
|
+
}),
|
|
152
|
+
}));
|
|
153
|
+
|
|
134
154
|
mock.module("../tools/shared/filesystem/path-policy.js", () => ({
|
|
135
155
|
sandboxPolicy: () => ({ ok: false }),
|
|
136
156
|
hostPolicy: () => ({ ok: false }),
|
|
@@ -173,6 +193,7 @@ describe("requireFreshApproval: non-interactive guardian denial", () => {
|
|
|
173
193
|
checkResultOverride = undefined;
|
|
174
194
|
scopeOptionsOverride = undefined;
|
|
175
195
|
riskOverride = "high";
|
|
196
|
+
thresholdOverride = "medium";
|
|
176
197
|
});
|
|
177
198
|
|
|
178
199
|
afterEach(() => {});
|
|
@@ -322,6 +343,7 @@ describe("requireFreshApproval: persistent decisions disabled", () => {
|
|
|
322
343
|
checkResultOverride = undefined;
|
|
323
344
|
scopeOptionsOverride = undefined;
|
|
324
345
|
riskOverride = "high";
|
|
346
|
+
thresholdOverride = "medium";
|
|
325
347
|
});
|
|
326
348
|
|
|
327
349
|
afterEach(() => {});
|
|
@@ -385,6 +407,7 @@ describe("requireFreshApproval: grant-consumed does not skip permission check",
|
|
|
385
407
|
checkResultOverride = undefined;
|
|
386
408
|
scopeOptionsOverride = undefined;
|
|
387
409
|
riskOverride = "high";
|
|
410
|
+
thresholdOverride = "medium";
|
|
388
411
|
});
|
|
389
412
|
|
|
390
413
|
afterEach(() => {});
|
|
@@ -437,6 +460,7 @@ describe("requireFreshApproval: context flag propagation", () => {
|
|
|
437
460
|
checkResultOverride = undefined;
|
|
438
461
|
scopeOptionsOverride = undefined;
|
|
439
462
|
riskOverride = "high";
|
|
463
|
+
thresholdOverride = "medium";
|
|
440
464
|
});
|
|
441
465
|
|
|
442
466
|
afterEach(() => {});
|
|
@@ -476,3 +500,403 @@ describe("requireFreshApproval: context flag propagation", () => {
|
|
|
476
500
|
expect(ctx.requireFreshApproval).toBeUndefined();
|
|
477
501
|
});
|
|
478
502
|
});
|
|
503
|
+
|
|
504
|
+
// ---------------------------------------------------------------------------
|
|
505
|
+
// Workflow launch consent: a run_workflow whose capability manifest grants
|
|
506
|
+
// side-effecting tools/host functions must prompt at LAUNCH (the manifest is
|
|
507
|
+
// model-declared and leaves execute granted tools directly, with no per-call
|
|
508
|
+
// check). A read-only run launches silently.
|
|
509
|
+
// ---------------------------------------------------------------------------
|
|
510
|
+
|
|
511
|
+
describe("requireFreshApproval: workflow capability grants", () => {
|
|
512
|
+
beforeEach(() => {
|
|
513
|
+
fakeToolResult = { content: "ok", isError: false };
|
|
514
|
+
checkResultOverride = undefined;
|
|
515
|
+
scopeOptionsOverride = undefined;
|
|
516
|
+
riskOverride = "low";
|
|
517
|
+
thresholdOverride = "medium";
|
|
518
|
+
fakeWorkflowRun = null;
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
afterEach(() => {});
|
|
522
|
+
|
|
523
|
+
function trackingPrompter(state: { prompted: boolean }): PermissionPrompter {
|
|
524
|
+
return {
|
|
525
|
+
prompt: async () => {
|
|
526
|
+
state.prompted = true;
|
|
527
|
+
return { decision: "allow" as const };
|
|
528
|
+
},
|
|
529
|
+
resolveConfirmation: () => {},
|
|
530
|
+
updateSender: () => {},
|
|
531
|
+
dispose: () => {},
|
|
532
|
+
} as unknown as PermissionPrompter;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
test("run_workflow granting side-effecting tools prompts at launch", async () => {
|
|
536
|
+
// A grant of `bash` would normally auto-allow (mocked check() returns
|
|
537
|
+
// allow); requireFreshApproval promotes it to an interactive prompt.
|
|
538
|
+
checkResultOverride = { decision: "allow", reason: "allowed" };
|
|
539
|
+
const state = { prompted: false };
|
|
540
|
+
const executor = new ToolExecutor(trackingPrompter(state));
|
|
541
|
+
const ctx = makeContext();
|
|
542
|
+
|
|
543
|
+
const result = await executor.execute(
|
|
544
|
+
"run_workflow",
|
|
545
|
+
{ script: "export const meta={};", capabilities: { tools: ["bash"] } },
|
|
546
|
+
ctx,
|
|
547
|
+
);
|
|
548
|
+
|
|
549
|
+
expect(ctx.requireFreshApproval).toBe(true);
|
|
550
|
+
expect(state.prompted).toBe(true);
|
|
551
|
+
expect(result.isError).toBe(false);
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
test.each(["none", "low", "medium"] as const)(
|
|
555
|
+
"run_workflow side-effecting launch prompts at the %s threshold (non-full-access posture)",
|
|
556
|
+
async (threshold) => {
|
|
557
|
+
checkResultOverride = { decision: "allow", reason: "allowed" };
|
|
558
|
+
thresholdOverride = threshold;
|
|
559
|
+
const state = { prompted: false };
|
|
560
|
+
const executor = new ToolExecutor(trackingPrompter(state));
|
|
561
|
+
const ctx = makeContext();
|
|
562
|
+
|
|
563
|
+
const result = await executor.execute(
|
|
564
|
+
"run_workflow",
|
|
565
|
+
{ script: "export const meta={};", capabilities: { tools: ["bash"] } },
|
|
566
|
+
ctx,
|
|
567
|
+
);
|
|
568
|
+
|
|
569
|
+
expect(ctx.requireFreshApproval).toBe(true);
|
|
570
|
+
expect(state.prompted).toBe(true);
|
|
571
|
+
expect(result.isError).toBe(false);
|
|
572
|
+
},
|
|
573
|
+
);
|
|
574
|
+
|
|
575
|
+
test("run_workflow side-effecting launch does NOT prompt at full access (high)", async () => {
|
|
576
|
+
// Full-access posture auto-approves even high-risk tools, so the
|
|
577
|
+
// requireFreshApproval promotion is skipped — the grant auto-allows.
|
|
578
|
+
checkResultOverride = { decision: "allow", reason: "allowed" };
|
|
579
|
+
thresholdOverride = "high";
|
|
580
|
+
const state = { prompted: false };
|
|
581
|
+
const executor = new ToolExecutor(trackingPrompter(state));
|
|
582
|
+
const ctx = makeContext();
|
|
583
|
+
|
|
584
|
+
const result = await executor.execute(
|
|
585
|
+
"run_workflow",
|
|
586
|
+
{ script: "export const meta={};", capabilities: { tools: ["bash"] } },
|
|
587
|
+
ctx,
|
|
588
|
+
);
|
|
589
|
+
|
|
590
|
+
// requireFreshApproval is still SET on the context (launch is
|
|
591
|
+
// side-effecting); only the allow → prompt promotion is suppressed.
|
|
592
|
+
expect(ctx.requireFreshApproval).toBe(true);
|
|
593
|
+
expect(state.prompted).toBe(false);
|
|
594
|
+
expect(result.isError).toBe(false);
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
test("a deny rule still blocks a side-effecting launch at full access (high)", async () => {
|
|
598
|
+
// Full access skips the fresh-approval promotion but never overrides an
|
|
599
|
+
// explicit deny — deny rules win regardless of posture.
|
|
600
|
+
checkResultOverride = { decision: "deny", reason: "blocked by rule" };
|
|
601
|
+
thresholdOverride = "high";
|
|
602
|
+
const state = { prompted: false };
|
|
603
|
+
const executor = new ToolExecutor(trackingPrompter(state));
|
|
604
|
+
const ctx = makeContext();
|
|
605
|
+
|
|
606
|
+
const result = await executor.execute(
|
|
607
|
+
"run_workflow",
|
|
608
|
+
{ script: "export const meta={};", capabilities: { tools: ["bash"] } },
|
|
609
|
+
ctx,
|
|
610
|
+
);
|
|
611
|
+
|
|
612
|
+
expect(state.prompted).toBe(false);
|
|
613
|
+
expect(result.isError).toBe(true);
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
test("a deny rule still blocks a side-effecting launch at a normal threshold", async () => {
|
|
617
|
+
checkResultOverride = { decision: "deny", reason: "blocked by rule" };
|
|
618
|
+
thresholdOverride = "medium";
|
|
619
|
+
const state = { prompted: false };
|
|
620
|
+
const executor = new ToolExecutor(trackingPrompter(state));
|
|
621
|
+
const ctx = makeContext();
|
|
622
|
+
|
|
623
|
+
const result = await executor.execute(
|
|
624
|
+
"run_workflow",
|
|
625
|
+
{ script: "export const meta={};", capabilities: { tools: ["bash"] } },
|
|
626
|
+
ctx,
|
|
627
|
+
);
|
|
628
|
+
|
|
629
|
+
expect(state.prompted).toBe(false);
|
|
630
|
+
expect(result.isError).toBe(true);
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
test("a read-only launch (no requireFreshApproval) is unaffected by a normal threshold", async () => {
|
|
634
|
+
// No requireFreshApproval means the allow stands untouched at any posture.
|
|
635
|
+
checkResultOverride = { decision: "allow", reason: "allowed" };
|
|
636
|
+
thresholdOverride = "medium";
|
|
637
|
+
const state = { prompted: false };
|
|
638
|
+
const executor = new ToolExecutor(trackingPrompter(state));
|
|
639
|
+
const ctx = makeContext();
|
|
640
|
+
|
|
641
|
+
const result = await executor.execute(
|
|
642
|
+
"run_workflow",
|
|
643
|
+
{ script: "export const meta={};", capabilities: {} },
|
|
644
|
+
ctx,
|
|
645
|
+
);
|
|
646
|
+
|
|
647
|
+
expect(ctx.requireFreshApproval).toBeUndefined();
|
|
648
|
+
expect(state.prompted).toBe(false);
|
|
649
|
+
expect(result.isError).toBe(false);
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
test("run_workflow granting host functions prompts at launch", async () => {
|
|
653
|
+
checkResultOverride = { decision: "allow", reason: "allowed" };
|
|
654
|
+
const state = { prompted: false };
|
|
655
|
+
const executor = new ToolExecutor(trackingPrompter(state));
|
|
656
|
+
const ctx = makeContext();
|
|
657
|
+
|
|
658
|
+
await executor.execute(
|
|
659
|
+
"run_workflow",
|
|
660
|
+
{
|
|
661
|
+
script: "export const meta={};",
|
|
662
|
+
capabilities: { hostFunctions: ["notify"] },
|
|
663
|
+
},
|
|
664
|
+
ctx,
|
|
665
|
+
);
|
|
666
|
+
|
|
667
|
+
expect(ctx.requireFreshApproval).toBe(true);
|
|
668
|
+
expect(state.prompted).toBe(true);
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
test("read-only run_workflow launches silently (no fresh approval, no prompt)", async () => {
|
|
672
|
+
checkResultOverride = { decision: "allow", reason: "allowed" };
|
|
673
|
+
const state = { prompted: false };
|
|
674
|
+
const executor = new ToolExecutor(trackingPrompter(state));
|
|
675
|
+
const ctx = makeContext();
|
|
676
|
+
|
|
677
|
+
const result = await executor.execute(
|
|
678
|
+
"run_workflow",
|
|
679
|
+
{ script: "export const meta={};", capabilities: {} },
|
|
680
|
+
ctx,
|
|
681
|
+
);
|
|
682
|
+
|
|
683
|
+
expect(ctx.requireFreshApproval).toBeUndefined();
|
|
684
|
+
expect(state.prompted).toBe(false);
|
|
685
|
+
expect(result.isError).toBe(false);
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
test("workflow-mode schedule_create granting side-effecting tools prompts at creation", async () => {
|
|
689
|
+
checkResultOverride = { decision: "allow", reason: "allowed" };
|
|
690
|
+
const state = { prompted: false };
|
|
691
|
+
const executor = new ToolExecutor(trackingPrompter(state));
|
|
692
|
+
const ctx = makeContext();
|
|
693
|
+
|
|
694
|
+
const result = await executor.execute(
|
|
695
|
+
"schedule_create",
|
|
696
|
+
{
|
|
697
|
+
name: "Nightly writeback",
|
|
698
|
+
mode: "workflow",
|
|
699
|
+
workflow_name: "nightly-report",
|
|
700
|
+
capabilities: { tools: ["bash"] },
|
|
701
|
+
},
|
|
702
|
+
ctx,
|
|
703
|
+
);
|
|
704
|
+
|
|
705
|
+
expect(ctx.requireFreshApproval).toBe(true);
|
|
706
|
+
expect(state.prompted).toBe(true);
|
|
707
|
+
expect(result.isError).toBe(false);
|
|
708
|
+
});
|
|
709
|
+
|
|
710
|
+
test("workflow-mode schedule_create granting host functions prompts at creation", async () => {
|
|
711
|
+
checkResultOverride = { decision: "allow", reason: "allowed" };
|
|
712
|
+
const state = { prompted: false };
|
|
713
|
+
const executor = new ToolExecutor(trackingPrompter(state));
|
|
714
|
+
const ctx = makeContext();
|
|
715
|
+
|
|
716
|
+
await executor.execute(
|
|
717
|
+
"schedule_create",
|
|
718
|
+
{
|
|
719
|
+
name: "Nightly notify",
|
|
720
|
+
mode: "workflow",
|
|
721
|
+
workflow_name: "nightly-report",
|
|
722
|
+
capabilities: { hostFunctions: ["notify"] },
|
|
723
|
+
},
|
|
724
|
+
ctx,
|
|
725
|
+
);
|
|
726
|
+
|
|
727
|
+
expect(ctx.requireFreshApproval).toBe(true);
|
|
728
|
+
expect(state.prompted).toBe(true);
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
test("workflow-mode schedule_create with a read-only manifest stays silent", async () => {
|
|
732
|
+
checkResultOverride = { decision: "allow", reason: "allowed" };
|
|
733
|
+
const state = { prompted: false };
|
|
734
|
+
const executor = new ToolExecutor(trackingPrompter(state));
|
|
735
|
+
const ctx = makeContext();
|
|
736
|
+
|
|
737
|
+
const result = await executor.execute(
|
|
738
|
+
"schedule_create",
|
|
739
|
+
{
|
|
740
|
+
name: "Read-only schedule",
|
|
741
|
+
mode: "workflow",
|
|
742
|
+
workflow_name: "nightly-report",
|
|
743
|
+
capabilities: {},
|
|
744
|
+
},
|
|
745
|
+
ctx,
|
|
746
|
+
);
|
|
747
|
+
|
|
748
|
+
expect(ctx.requireFreshApproval).toBeUndefined();
|
|
749
|
+
expect(state.prompted).toBe(false);
|
|
750
|
+
expect(result.isError).toBe(false);
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
test("non-workflow schedule_create never gates even with a side-effecting capabilities blob", async () => {
|
|
754
|
+
// The capabilities field is workflow-mode only; an execute-mode schedule
|
|
755
|
+
// must not be promoted to a fresh approval by a stray capabilities object.
|
|
756
|
+
checkResultOverride = { decision: "allow", reason: "allowed" };
|
|
757
|
+
const state = { prompted: false };
|
|
758
|
+
const executor = new ToolExecutor(trackingPrompter(state));
|
|
759
|
+
const ctx = makeContext();
|
|
760
|
+
|
|
761
|
+
await executor.execute(
|
|
762
|
+
"schedule_create",
|
|
763
|
+
{
|
|
764
|
+
name: "Execute schedule",
|
|
765
|
+
mode: "execute",
|
|
766
|
+
message: "do the thing",
|
|
767
|
+
capabilities: { tools: ["bash"] },
|
|
768
|
+
},
|
|
769
|
+
ctx,
|
|
770
|
+
);
|
|
771
|
+
|
|
772
|
+
expect(ctx.requireFreshApproval).toBeUndefined();
|
|
773
|
+
expect(state.prompted).toBe(false);
|
|
774
|
+
});
|
|
775
|
+
|
|
776
|
+
test("manage_workflows resume of a side-effecting run requires fresh approval", async () => {
|
|
777
|
+
// The target run's STORED manifest granted tools, so resuming it (which
|
|
778
|
+
// restarts unfinished side-effecting leaves) must re-prompt.
|
|
779
|
+
fakeWorkflowRun = {
|
|
780
|
+
capabilities: { tools: ["bash"] },
|
|
781
|
+
conversationId: "conversation-1",
|
|
782
|
+
};
|
|
783
|
+
checkResultOverride = { decision: "allow", reason: "allowed" };
|
|
784
|
+
const state = { prompted: false };
|
|
785
|
+
const executor = new ToolExecutor(trackingPrompter(state));
|
|
786
|
+
const ctx = makeContext();
|
|
787
|
+
|
|
788
|
+
await executor.execute(
|
|
789
|
+
"manage_workflows",
|
|
790
|
+
{ action: "resume", run_id: "wf-run-1" },
|
|
791
|
+
ctx,
|
|
792
|
+
);
|
|
793
|
+
|
|
794
|
+
expect(ctx.requireFreshApproval).toBe(true);
|
|
795
|
+
expect(state.prompted).toBe(true);
|
|
796
|
+
});
|
|
797
|
+
|
|
798
|
+
test("manage_workflows resume of a read-only run stays silent", async () => {
|
|
799
|
+
fakeWorkflowRun = { capabilities: {}, conversationId: "conversation-1" };
|
|
800
|
+
checkResultOverride = { decision: "allow", reason: "allowed" };
|
|
801
|
+
const state = { prompted: false };
|
|
802
|
+
const executor = new ToolExecutor(trackingPrompter(state));
|
|
803
|
+
const ctx = makeContext();
|
|
804
|
+
|
|
805
|
+
await executor.execute(
|
|
806
|
+
"manage_workflows",
|
|
807
|
+
{ action: "resume", run_id: "wf-run-1" },
|
|
808
|
+
ctx,
|
|
809
|
+
);
|
|
810
|
+
|
|
811
|
+
expect(ctx.requireFreshApproval).toBeUndefined();
|
|
812
|
+
expect(state.prompted).toBe(false);
|
|
813
|
+
});
|
|
814
|
+
|
|
815
|
+
test("manage_workflows non-resume actions never gate (status of a side-effecting run)", async () => {
|
|
816
|
+
// Even though the run granted tools, status/abort/list_runs are pure
|
|
817
|
+
// control/reads and must stay low-risk and silent.
|
|
818
|
+
fakeWorkflowRun = {
|
|
819
|
+
capabilities: { tools: ["bash"] },
|
|
820
|
+
conversationId: "conversation-1",
|
|
821
|
+
};
|
|
822
|
+
checkResultOverride = { decision: "allow", reason: "allowed" };
|
|
823
|
+
const state = { prompted: false };
|
|
824
|
+
const executor = new ToolExecutor(trackingPrompter(state));
|
|
825
|
+
const ctx = makeContext();
|
|
826
|
+
|
|
827
|
+
await executor.execute(
|
|
828
|
+
"manage_workflows",
|
|
829
|
+
{ action: "status", run_id: "wf-run-1" },
|
|
830
|
+
ctx,
|
|
831
|
+
);
|
|
832
|
+
|
|
833
|
+
expect(ctx.requireFreshApproval).toBeUndefined();
|
|
834
|
+
expect(state.prompted).toBe(false);
|
|
835
|
+
});
|
|
836
|
+
|
|
837
|
+
test("manage_workflows resume of an unknown run does not gate (run not found)", async () => {
|
|
838
|
+
fakeWorkflowRun = null;
|
|
839
|
+
checkResultOverride = { decision: "allow", reason: "allowed" };
|
|
840
|
+
const state = { prompted: false };
|
|
841
|
+
const executor = new ToolExecutor(trackingPrompter(state));
|
|
842
|
+
const ctx = makeContext();
|
|
843
|
+
|
|
844
|
+
await executor.execute(
|
|
845
|
+
"manage_workflows",
|
|
846
|
+
{ action: "resume", run_id: "missing" },
|
|
847
|
+
ctx,
|
|
848
|
+
);
|
|
849
|
+
|
|
850
|
+
expect(ctx.requireFreshApproval).toBeUndefined();
|
|
851
|
+
expect(state.prompted).toBe(false);
|
|
852
|
+
});
|
|
853
|
+
|
|
854
|
+
test("a non-owner resuming another conversation's side-effecting run does NOT gate (no prompt, no existence leak)", async () => {
|
|
855
|
+
// The run is side-effecting but belongs to a different conversation. The
|
|
856
|
+
// tool will hide it as not-found, so the gate must not prompt — otherwise it
|
|
857
|
+
// leaks that the run exists and nags the guardian for a no-op resume.
|
|
858
|
+
fakeWorkflowRun = {
|
|
859
|
+
capabilities: { tools: ["bash"] },
|
|
860
|
+
conversationId: "other-conversation",
|
|
861
|
+
};
|
|
862
|
+
checkResultOverride = { decision: "allow", reason: "allowed" };
|
|
863
|
+
const state = { prompted: false };
|
|
864
|
+
const executor = new ToolExecutor(trackingPrompter(state));
|
|
865
|
+
const ctx = makeContext({
|
|
866
|
+
trustClass: "trusted_contact",
|
|
867
|
+
conversationId: "conversation-1",
|
|
868
|
+
});
|
|
869
|
+
|
|
870
|
+
await executor.execute(
|
|
871
|
+
"manage_workflows",
|
|
872
|
+
{ action: "resume", run_id: "wf-run-1" },
|
|
873
|
+
ctx,
|
|
874
|
+
);
|
|
875
|
+
|
|
876
|
+
expect(ctx.requireFreshApproval).toBeUndefined();
|
|
877
|
+
expect(state.prompted).toBe(false);
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
test("a non-owner resuming its OWN side-effecting run still gates", async () => {
|
|
881
|
+
fakeWorkflowRun = {
|
|
882
|
+
capabilities: { tools: ["bash"] },
|
|
883
|
+
conversationId: "conversation-1",
|
|
884
|
+
};
|
|
885
|
+
checkResultOverride = { decision: "allow", reason: "allowed" };
|
|
886
|
+
const state = { prompted: false };
|
|
887
|
+
const executor = new ToolExecutor(trackingPrompter(state));
|
|
888
|
+
const ctx = makeContext({
|
|
889
|
+
trustClass: "trusted_contact",
|
|
890
|
+
conversationId: "conversation-1",
|
|
891
|
+
});
|
|
892
|
+
|
|
893
|
+
await executor.execute(
|
|
894
|
+
"manage_workflows",
|
|
895
|
+
{ action: "resume", run_id: "wf-run-1" },
|
|
896
|
+
ctx,
|
|
897
|
+
);
|
|
898
|
+
|
|
899
|
+
expect(ctx.requireFreshApproval).toBe(true);
|
|
900
|
+
expect(state.prompted).toBe(true);
|
|
901
|
+
});
|
|
902
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import type { AppDefinition } from "../memory/app-store.js";
|
|
4
|
+
|
|
5
|
+
let appsByConversation: AppDefinition[] = [];
|
|
6
|
+
|
|
7
|
+
const realStore = await import("../memory/app-store.js");
|
|
8
|
+
mock.module("../memory/app-store.js", () => ({
|
|
9
|
+
...realStore,
|
|
10
|
+
listAppsByConversation: (_conversationId: string) => appsByConversation,
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
const { resolveAppId, missingAppIdError } =
|
|
14
|
+
await import("../tools/apps/resolve-app-id.js");
|
|
15
|
+
|
|
16
|
+
function makeApp(id: string, updatedAt: number): AppDefinition {
|
|
17
|
+
return {
|
|
18
|
+
id,
|
|
19
|
+
name: id,
|
|
20
|
+
schemaJson: "{}",
|
|
21
|
+
htmlDefinition: "",
|
|
22
|
+
createdAt: updatedAt,
|
|
23
|
+
updatedAt,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
describe("resolveAppId", () => {
|
|
28
|
+
test("returns an explicit non-empty app_id unchanged", () => {
|
|
29
|
+
appsByConversation = [makeApp("other", 1)];
|
|
30
|
+
expect(resolveAppId({ app_id: "explicit" }, "conv-1")).toBe("explicit");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("falls back to the most-recently-updated conversation app when missing", () => {
|
|
34
|
+
// listAppsByConversation inherits listApps' updatedAt-descending order.
|
|
35
|
+
appsByConversation = [makeApp("newest", 30), makeApp("older", 10)];
|
|
36
|
+
expect(resolveAppId({}, "conv-1")).toBe("newest");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("treats a blank app_id as missing", () => {
|
|
40
|
+
appsByConversation = [makeApp("active", 5)];
|
|
41
|
+
expect(resolveAppId({ app_id: " " }, "conv-1")).toBe("active");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("returns null when no app_id is given and the conversation has no app", () => {
|
|
45
|
+
appsByConversation = [];
|
|
46
|
+
expect(resolveAppId({}, "conv-1")).toBeNull();
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe("missingAppIdError", () => {
|
|
51
|
+
test("is an actionable error result", () => {
|
|
52
|
+
const result = missingAppIdError();
|
|
53
|
+
expect(result.isError).toBe(true);
|
|
54
|
+
expect(JSON.parse(result.content).error).toContain("app_create");
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -138,6 +138,7 @@ describe("SSE HTTP parity — streaming/delta message types", () => {
|
|
|
138
138
|
type: "assistant_thinking_delta" as const,
|
|
139
139
|
thinking: "Let me reason through this...",
|
|
140
140
|
conversationId: "conv-thinking-test",
|
|
141
|
+
timestampMs: 1_700_000_000_000,
|
|
141
142
|
};
|
|
142
143
|
const event = await publishAndReadFrame("parity-thinking-delta", msg);
|
|
143
144
|
|
|
@@ -145,6 +146,7 @@ describe("SSE HTTP parity — streaming/delta message types", () => {
|
|
|
145
146
|
const m = event.message as typeof msg;
|
|
146
147
|
expect(m.thinking).toBe("Let me reason through this...");
|
|
147
148
|
expect(m.conversationId).toBe("conv-thinking-test");
|
|
149
|
+
expect(m.timestampMs).toBe(1_700_000_000_000);
|
|
148
150
|
});
|
|
149
151
|
|
|
150
152
|
// ── tool_input_delta ─────────────────────────────────────────────────────
|