@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,547 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for `subagentToolGateMode` — the execution-layer tool gating mode
|
|
3
|
+
* used by cache-parity wakes (fork-based memory retrospectives).
|
|
4
|
+
*
|
|
5
|
+
* Covers:
|
|
6
|
+
* - Resolver (`createResolveToolsCallback`): in `"execution"` mode the
|
|
7
|
+
* resolved tool definitions are NOT filtered by the subagent allowlist
|
|
8
|
+
* (the wire request keeps the conversation's full tool surface so the
|
|
9
|
+
* provider prompt-cache prefix stays byte-identical); in `"wire"` mode /
|
|
10
|
+
* absent, the definitions are filtered exactly as before (regression).
|
|
11
|
+
* - Executor (`createToolExecutor`): in `"execution"` mode a call to a
|
|
12
|
+
* non-allowlisted tool returns an error tool_result WITHOUT invoking the
|
|
13
|
+
* tool's executor (safety invariant), while allowlisted calls execute
|
|
14
|
+
* normally. The gate also covers the `skill_execute` indirection by
|
|
15
|
+
* gating the resolved inner tool name, and runs BEFORE every in-executor
|
|
16
|
+
* interception — including `switch_inference_profile`, which must not be
|
|
17
|
+
* able to switch `ctx.toolRoutedProfile` mid-wake (allowlist bypass +
|
|
18
|
+
* per-model prompt-cache parity break).
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { describe, expect, mock, test } from "bun:test";
|
|
22
|
+
|
|
23
|
+
import type { SkillProjectionCache } from "../daemon/conversation-skill-tools.js";
|
|
24
|
+
import type { SurfaceData, SurfaceType } from "../daemon/message-protocol.js";
|
|
25
|
+
import type { PermissionPrompter } from "../permissions/prompter.js";
|
|
26
|
+
import type { SecretPrompter } from "../permissions/secret-prompter.js";
|
|
27
|
+
import type { Message, ToolDefinition } from "../providers/types.js";
|
|
28
|
+
import type { ToolExecutor } from "../tools/executor.js";
|
|
29
|
+
import type { ToolContext, ToolExecutionResult } from "../tools/types.js";
|
|
30
|
+
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// Module mocks (must precede the import of the module under test)
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
const baseConfig = {
|
|
36
|
+
tools: { exclude: [] as string[] },
|
|
37
|
+
timeouts: {
|
|
38
|
+
shellDefaultTimeoutSec: 120,
|
|
39
|
+
shellMaxTimeoutSec: 600,
|
|
40
|
+
permissionTimeoutSec: 300,
|
|
41
|
+
toolExecutionTimeoutSec: 600,
|
|
42
|
+
},
|
|
43
|
+
services: {},
|
|
44
|
+
llm: { profiles: { speedy: { label: "Speedy" } } },
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
mock.module("../config/loader.js", () => ({
|
|
48
|
+
getConfig: () => baseConfig,
|
|
49
|
+
loadConfig: () => baseConfig,
|
|
50
|
+
invalidateConfigCache: () => {},
|
|
51
|
+
loadRawConfig: () => ({}),
|
|
52
|
+
saveRawConfig: () => {},
|
|
53
|
+
getNestedValue: () => undefined,
|
|
54
|
+
setNestedValue: () => {},
|
|
55
|
+
}));
|
|
56
|
+
|
|
57
|
+
mock.module("../runtime/assistant-event-hub.js", () => ({
|
|
58
|
+
broadcastMessage: mock(() => {}),
|
|
59
|
+
assistantEventHub: { listClientsByCapability: () => [] },
|
|
60
|
+
}));
|
|
61
|
+
|
|
62
|
+
mock.module("../daemon/conversation-surfaces.js", () => ({
|
|
63
|
+
refreshSurfacesForApp: mock(() => {}),
|
|
64
|
+
surfaceProxyResolver: mock(() =>
|
|
65
|
+
Promise.resolve({ content: "", isError: false }),
|
|
66
|
+
),
|
|
67
|
+
}));
|
|
68
|
+
|
|
69
|
+
mock.module("../services/published-app-updater.js", () => ({
|
|
70
|
+
updatePublishedAppDeployment: mock(() => Promise.resolve()),
|
|
71
|
+
}));
|
|
72
|
+
|
|
73
|
+
mock.module("../tools/browser/browser-screencast.js", () => ({
|
|
74
|
+
registerConversationSender: mock(() => {}),
|
|
75
|
+
}));
|
|
76
|
+
|
|
77
|
+
mock.module("../memory/app-store.js", () => ({
|
|
78
|
+
getApp: mock(() => null),
|
|
79
|
+
getAppDirPath: mock(() => "/tmp/test-apps/dummy"),
|
|
80
|
+
isMultifileApp: mock(() => false),
|
|
81
|
+
getAppsDir: mock(() => "/tmp/test-apps"),
|
|
82
|
+
resolveAppIdByDirName: mock(() => null),
|
|
83
|
+
resolveAppIdFromPath: mock(() => null),
|
|
84
|
+
}));
|
|
85
|
+
|
|
86
|
+
// Controls the skill tools the projection reports per resolver call.
|
|
87
|
+
let projectedSkillToolNames: string[] = [];
|
|
88
|
+
|
|
89
|
+
mock.module("../daemon/conversation-skill-tools.js", () => ({
|
|
90
|
+
projectSkillTools: mock(() => ({
|
|
91
|
+
allowedToolNames: new Set(projectedSkillToolNames),
|
|
92
|
+
toolDefinitions: [],
|
|
93
|
+
})),
|
|
94
|
+
}));
|
|
95
|
+
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// Imports after mocks are in place
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
import {
|
|
101
|
+
createResolveToolsCallback,
|
|
102
|
+
createToolExecutor,
|
|
103
|
+
type SkillProjectionContext,
|
|
104
|
+
type ToolSetupContext,
|
|
105
|
+
} from "../daemon/conversation-tool-setup.js";
|
|
106
|
+
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
// Helpers
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
|
|
111
|
+
const EMPTY_HISTORY: Message[] = [];
|
|
112
|
+
|
|
113
|
+
function makeToolDef(name: string): ToolDefinition {
|
|
114
|
+
return { name, description: `${name} tool`, input_schema: {} };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function makeProjectionCtx(
|
|
118
|
+
overrides: Partial<SkillProjectionContext> = {},
|
|
119
|
+
): SkillProjectionContext {
|
|
120
|
+
return {
|
|
121
|
+
skillProjectionState: new Map(),
|
|
122
|
+
skillProjectionCache: {} as SkillProjectionCache,
|
|
123
|
+
coreToolNames: new Set(["remember", "tool_b"]),
|
|
124
|
+
toolsDisabledDepth: 0,
|
|
125
|
+
...overrides,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function makeSetupCtx(
|
|
130
|
+
overrides: Partial<ToolSetupContext> = {},
|
|
131
|
+
): ToolSetupContext {
|
|
132
|
+
return {
|
|
133
|
+
conversationId: "conv-test",
|
|
134
|
+
currentRequestId: "req-1",
|
|
135
|
+
workingDir: "/tmp/test",
|
|
136
|
+
abortController: null,
|
|
137
|
+
traceEmitter: { emit: () => {} },
|
|
138
|
+
sendToClient: mock(() => {}),
|
|
139
|
+
pendingSurfaceActions: new Map(),
|
|
140
|
+
lastSurfaceAction: new Map(),
|
|
141
|
+
surfaceState: new Map<
|
|
142
|
+
string,
|
|
143
|
+
{ surfaceType: SurfaceType; data: SurfaceData; title?: string }
|
|
144
|
+
>(),
|
|
145
|
+
surfaceUndoStacks: new Map(),
|
|
146
|
+
accumulatedSurfaceState: new Map(),
|
|
147
|
+
surfaceActionRequestIds: new Set<string>(),
|
|
148
|
+
currentTurnSurfaces: [],
|
|
149
|
+
isProcessing: () => false,
|
|
150
|
+
enqueueMessage: () => ({ queued: false, requestId: "r" }),
|
|
151
|
+
getQueueDepth: () => 0,
|
|
152
|
+
processMessage: async () => "",
|
|
153
|
+
withSurface: async <T>(_id: string, fn: () => T | Promise<T>) => fn(),
|
|
154
|
+
...overrides,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Fake ToolExecutor that records every execute() invocation. */
|
|
159
|
+
function makeCapturingExecutor() {
|
|
160
|
+
const calls: Array<{ name: string; input: Record<string, unknown> }> = [];
|
|
161
|
+
const executor = {
|
|
162
|
+
execute: async (
|
|
163
|
+
name: string,
|
|
164
|
+
input: Record<string, unknown>,
|
|
165
|
+
_context: ToolContext,
|
|
166
|
+
): Promise<ToolExecutionResult> => {
|
|
167
|
+
calls.push({ name, input });
|
|
168
|
+
return { content: "ok", isError: false };
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
return { executor: executor as unknown as ToolExecutor, calls };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const noopPrompter = {
|
|
175
|
+
prompt: mock(async () => ({ decision: "allow" as const })),
|
|
176
|
+
} as unknown as PermissionPrompter;
|
|
177
|
+
const noopSecretPrompter = {
|
|
178
|
+
prompt: mock(async () => ({ cancelled: true })),
|
|
179
|
+
} as unknown as SecretPrompter;
|
|
180
|
+
|
|
181
|
+
function makeToolFn(executor: ToolExecutor, ctx: ToolSetupContext) {
|
|
182
|
+
return createToolExecutor(
|
|
183
|
+
executor,
|
|
184
|
+
noopPrompter,
|
|
185
|
+
noopSecretPrompter,
|
|
186
|
+
ctx,
|
|
187
|
+
() => {},
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ---------------------------------------------------------------------------
|
|
192
|
+
// Resolver — wire vs execution gate mode
|
|
193
|
+
// ---------------------------------------------------------------------------
|
|
194
|
+
|
|
195
|
+
describe("createResolveToolsCallback — subagentToolGateMode", () => {
|
|
196
|
+
test("wire mode (default) filters resolved defs to the subagent allowlist (regression)", () => {
|
|
197
|
+
projectedSkillToolNames = ["skill_tool_x"];
|
|
198
|
+
const toolDefs = [makeToolDef("remember"), makeToolDef("tool_b")];
|
|
199
|
+
const ctx = makeProjectionCtx({
|
|
200
|
+
subagentAllowedTools: new Set(["remember"]),
|
|
201
|
+
});
|
|
202
|
+
const resolve = createResolveToolsCallback(toolDefs, ctx)!;
|
|
203
|
+
|
|
204
|
+
const tools = resolve(EMPTY_HISTORY);
|
|
205
|
+
|
|
206
|
+
expect(tools.map((t) => t.name)).toEqual(["remember"]);
|
|
207
|
+
expect(ctx.allowedToolNames).toEqual(new Set(["remember"]));
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test("execution mode keeps the full tool surface on the wire", () => {
|
|
211
|
+
projectedSkillToolNames = ["skill_tool_x"];
|
|
212
|
+
const toolDefs = [makeToolDef("remember"), makeToolDef("tool_b")];
|
|
213
|
+
const ctx = makeProjectionCtx({
|
|
214
|
+
subagentAllowedTools: new Set(["remember"]),
|
|
215
|
+
subagentToolGateMode: "execution",
|
|
216
|
+
});
|
|
217
|
+
const resolve = createResolveToolsCallback(toolDefs, ctx)!;
|
|
218
|
+
|
|
219
|
+
const tools = resolve(EMPTY_HISTORY);
|
|
220
|
+
|
|
221
|
+
// Defs are NOT filtered by the allowlist — byte-identical to a turn
|
|
222
|
+
// without any subagent allowlist.
|
|
223
|
+
expect(tools.map((t) => t.name).sort()).toEqual(["remember", "tool_b"]);
|
|
224
|
+
// Skill-projection availability is also not narrowed: the execution-layer
|
|
225
|
+
// gate in the executor callback owns enforcement.
|
|
226
|
+
expect(ctx.allowedToolNames).toEqual(
|
|
227
|
+
new Set(["remember", "tool_b", "skill_tool_x"]),
|
|
228
|
+
);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test("execution mode resolves the same defs as having no allowlist at all", () => {
|
|
232
|
+
projectedSkillToolNames = [];
|
|
233
|
+
const toolDefs = [makeToolDef("remember"), makeToolDef("tool_b")];
|
|
234
|
+
|
|
235
|
+
const unscoped = createResolveToolsCallback(toolDefs, makeProjectionCtx())!(
|
|
236
|
+
EMPTY_HISTORY,
|
|
237
|
+
);
|
|
238
|
+
const executionScoped = createResolveToolsCallback(
|
|
239
|
+
toolDefs,
|
|
240
|
+
makeProjectionCtx({
|
|
241
|
+
subagentAllowedTools: new Set(["remember"]),
|
|
242
|
+
subagentToolGateMode: "execution",
|
|
243
|
+
}),
|
|
244
|
+
)!(EMPTY_HISTORY);
|
|
245
|
+
|
|
246
|
+
expect(executionScoped).toEqual(unscoped);
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// ---------------------------------------------------------------------------
|
|
251
|
+
// Resolver — toolContextPin (wire tool-surface parity for execution wakes)
|
|
252
|
+
// ---------------------------------------------------------------------------
|
|
253
|
+
|
|
254
|
+
describe("createResolveToolsCallback — toolContextPin", () => {
|
|
255
|
+
// Core defs spanning each client-gated family: host proxy (host_bash),
|
|
256
|
+
// dynamic UI (ui_show), connected-client (app_open), client-platform
|
|
257
|
+
// (request_system_permission), plus the always-on remember.
|
|
258
|
+
const CLIENT_GATED_DEFS = [
|
|
259
|
+
makeToolDef("remember"),
|
|
260
|
+
makeToolDef("host_bash"),
|
|
261
|
+
makeToolDef("ui_show"),
|
|
262
|
+
makeToolDef("app_open"),
|
|
263
|
+
makeToolDef("request_system_permission"),
|
|
264
|
+
];
|
|
265
|
+
|
|
266
|
+
/** Execution-gate ctx shaped like a clientless fork-retrospective wake. */
|
|
267
|
+
function clientlessExecutionCtx(
|
|
268
|
+
overrides: Partial<SkillProjectionContext> = {},
|
|
269
|
+
): SkillProjectionContext {
|
|
270
|
+
return makeProjectionCtx({
|
|
271
|
+
coreToolNames: new Set(CLIENT_GATED_DEFS.map((d) => d.name)),
|
|
272
|
+
hasNoClient: true,
|
|
273
|
+
subagentAllowedTools: new Set(["remember"]),
|
|
274
|
+
subagentToolGateMode: "execution",
|
|
275
|
+
...overrides,
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
test("control: without a pin, a clientless fork drops every client-gated tool from the wire", () => {
|
|
280
|
+
projectedSkillToolNames = [];
|
|
281
|
+
const resolve = createResolveToolsCallback(
|
|
282
|
+
CLIENT_GATED_DEFS,
|
|
283
|
+
clientlessExecutionCtx(),
|
|
284
|
+
)!;
|
|
285
|
+
|
|
286
|
+
expect(resolve(EMPTY_HISTORY).map((t) => t.name)).toEqual(["remember"]);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
test("a desktop-source pin restores the host/UI/client tool defs on the wire", () => {
|
|
290
|
+
projectedSkillToolNames = [];
|
|
291
|
+
const resolve = createResolveToolsCallback(
|
|
292
|
+
CLIENT_GATED_DEFS,
|
|
293
|
+
clientlessExecutionCtx({
|
|
294
|
+
toolContextPin: { hasNoClient: false, transportInterface: "macos" },
|
|
295
|
+
}),
|
|
296
|
+
)!;
|
|
297
|
+
|
|
298
|
+
const names = resolve(EMPTY_HISTORY)
|
|
299
|
+
.map((t) => t.name)
|
|
300
|
+
.sort();
|
|
301
|
+
// request_system_permission stays out: it keys on
|
|
302
|
+
// channelCapabilities.clientOS, which desktop HTTP live turns never set
|
|
303
|
+
// either — exclusion IS parity there.
|
|
304
|
+
expect(names).toEqual(["app_open", "host_bash", "remember", "ui_show"]);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
test("the pin REPLACES the live context — absent pin fields do not fall through", () => {
|
|
308
|
+
projectedSkillToolNames = [];
|
|
309
|
+
// Live ctx claims an interactive macOS client; the pin says clientless.
|
|
310
|
+
// Client-gated tools must drop, proving pinned-undefined beats live.
|
|
311
|
+
const resolve = createResolveToolsCallback(
|
|
312
|
+
CLIENT_GATED_DEFS,
|
|
313
|
+
clientlessExecutionCtx({
|
|
314
|
+
hasNoClient: false,
|
|
315
|
+
transportInterface: "macos",
|
|
316
|
+
toolContextPin: { hasNoClient: true },
|
|
317
|
+
}),
|
|
318
|
+
)!;
|
|
319
|
+
|
|
320
|
+
expect(resolve(EMPTY_HISTORY).map((t) => t.name)).toEqual(["remember"]);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
test("invariant: a pinned-in tool is on the wire but can never execute", async () => {
|
|
324
|
+
projectedSkillToolNames = [];
|
|
325
|
+
const pin = { hasNoClient: false, transportInterface: "macos" as const };
|
|
326
|
+
const resolve = createResolveToolsCallback(
|
|
327
|
+
CLIENT_GATED_DEFS,
|
|
328
|
+
clientlessExecutionCtx({ toolContextPin: pin }),
|
|
329
|
+
)!;
|
|
330
|
+
expect(resolve(EMPTY_HISTORY).map((t) => t.name)).toContain("host_bash");
|
|
331
|
+
|
|
332
|
+
// The pin affects tool-DEFINITION resolution only: the executor-level
|
|
333
|
+
// gate never reads it, so the pinned-in host tool is rejected before
|
|
334
|
+
// any executor dispatch.
|
|
335
|
+
const { executor, calls } = makeCapturingExecutor();
|
|
336
|
+
const toolFn = makeToolFn(
|
|
337
|
+
executor,
|
|
338
|
+
makeSetupCtx({
|
|
339
|
+
hasNoClient: true,
|
|
340
|
+
subagentAllowedTools: new Set(["remember"]),
|
|
341
|
+
subagentToolGateMode: "execution",
|
|
342
|
+
}),
|
|
343
|
+
);
|
|
344
|
+
|
|
345
|
+
const result = await toolFn("host_bash", { command: "echo hi" });
|
|
346
|
+
|
|
347
|
+
expect(result).toEqual({
|
|
348
|
+
content: "This background pass may only use: remember.",
|
|
349
|
+
isError: true,
|
|
350
|
+
});
|
|
351
|
+
expect(calls).toHaveLength(0);
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
// ---------------------------------------------------------------------------
|
|
356
|
+
// Executor — execution-layer allowlist gate
|
|
357
|
+
// ---------------------------------------------------------------------------
|
|
358
|
+
|
|
359
|
+
describe("createToolExecutor — execution-layer allowlist gate", () => {
|
|
360
|
+
test("execution mode: non-allowlisted call returns an error tool_result and never invokes the executor", async () => {
|
|
361
|
+
const { executor, calls } = makeCapturingExecutor();
|
|
362
|
+
const toolFn = makeToolFn(
|
|
363
|
+
executor,
|
|
364
|
+
makeSetupCtx({
|
|
365
|
+
subagentAllowedTools: new Set(["remember"]),
|
|
366
|
+
subagentToolGateMode: "execution",
|
|
367
|
+
}),
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
const result = await toolFn("bash", { command: "echo hi" });
|
|
371
|
+
|
|
372
|
+
expect(result).toEqual({
|
|
373
|
+
content: "This background pass may only use: remember.",
|
|
374
|
+
isError: true,
|
|
375
|
+
});
|
|
376
|
+
// Safety invariant: the non-allowlisted tool's executor must never run.
|
|
377
|
+
expect(calls).toHaveLength(0);
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
test("execution mode: allowlisted call executes normally", async () => {
|
|
381
|
+
const { executor, calls } = makeCapturingExecutor();
|
|
382
|
+
const toolFn = makeToolFn(
|
|
383
|
+
executor,
|
|
384
|
+
makeSetupCtx({
|
|
385
|
+
subagentAllowedTools: new Set(["remember"]),
|
|
386
|
+
subagentToolGateMode: "execution",
|
|
387
|
+
}),
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
const result = await toolFn("remember", { content: "a fact" });
|
|
391
|
+
|
|
392
|
+
expect(result).toEqual({ content: "ok", isError: false });
|
|
393
|
+
expect(calls).toHaveLength(1);
|
|
394
|
+
expect(calls[0]!.name).toBe("remember");
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
test("execution mode: skill_execute gates the resolved inner tool, executor never invoked", async () => {
|
|
398
|
+
const { executor, calls } = makeCapturingExecutor();
|
|
399
|
+
const toolFn = makeToolFn(
|
|
400
|
+
executor,
|
|
401
|
+
makeSetupCtx({
|
|
402
|
+
subagentAllowedTools: new Set(["remember"]),
|
|
403
|
+
subagentToolGateMode: "execution",
|
|
404
|
+
}),
|
|
405
|
+
);
|
|
406
|
+
|
|
407
|
+
const rejected = await toolFn("skill_execute", {
|
|
408
|
+
tool: "bash",
|
|
409
|
+
input: { command: "echo hi" },
|
|
410
|
+
});
|
|
411
|
+
expect(rejected).toEqual({
|
|
412
|
+
content: "This background pass may only use: remember.",
|
|
413
|
+
isError: true,
|
|
414
|
+
});
|
|
415
|
+
expect(calls).toHaveLength(0);
|
|
416
|
+
|
|
417
|
+
const allowed = await toolFn("skill_execute", {
|
|
418
|
+
tool: "remember",
|
|
419
|
+
input: { content: "a fact" },
|
|
420
|
+
});
|
|
421
|
+
expect(allowed).toEqual({ content: "ok", isError: false });
|
|
422
|
+
expect(calls).toHaveLength(1);
|
|
423
|
+
expect(calls[0]!.name).toBe("remember");
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
test("execution mode: multi-entry allowlist renders sorted in the rejection message", async () => {
|
|
427
|
+
const { executor, calls } = makeCapturingExecutor();
|
|
428
|
+
const toolFn = makeToolFn(
|
|
429
|
+
executor,
|
|
430
|
+
makeSetupCtx({
|
|
431
|
+
subagentAllowedTools: new Set(["remember", "file_read"]),
|
|
432
|
+
subagentToolGateMode: "execution",
|
|
433
|
+
}),
|
|
434
|
+
);
|
|
435
|
+
|
|
436
|
+
const result = await toolFn("bash", {});
|
|
437
|
+
|
|
438
|
+
expect(result).toEqual({
|
|
439
|
+
content: "This background pass may only use: file_read, remember.",
|
|
440
|
+
isError: true,
|
|
441
|
+
});
|
|
442
|
+
expect(calls).toHaveLength(0);
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
test("execution mode: switch_inference_profile is rejected BEFORE its interception runs", async () => {
|
|
446
|
+
const { executor, calls } = makeCapturingExecutor();
|
|
447
|
+
const ctx = makeSetupCtx({
|
|
448
|
+
subagentAllowedTools: new Set(["remember"]),
|
|
449
|
+
subagentToolGateMode: "execution",
|
|
450
|
+
});
|
|
451
|
+
const toolFn = makeToolFn(executor, ctx);
|
|
452
|
+
|
|
453
|
+
const result = await toolFn("switch_inference_profile", {
|
|
454
|
+
profile: "speedy",
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
// The gate must fire before the interception: the rejection tool_result
|
|
458
|
+
// comes back (NOT the interception's "Switched to ..." / "not found"
|
|
459
|
+
// responses) and the routed profile is untouched — switching it mid-wake
|
|
460
|
+
// would bypass the allowlist and break per-model prompt-cache parity.
|
|
461
|
+
expect(result).toEqual({
|
|
462
|
+
content: "This background pass may only use: remember.",
|
|
463
|
+
isError: true,
|
|
464
|
+
});
|
|
465
|
+
expect(ctx.toolRoutedProfile).toBeUndefined();
|
|
466
|
+
expect(calls).toHaveLength(0);
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
test("switch_inference_profile interception still works when the gate is inert (regression)", async () => {
|
|
470
|
+
// No execution-mode allowlist (wire mode) — the interception must keep
|
|
471
|
+
// its historical behavior: switch the routed profile without ever
|
|
472
|
+
// touching the tool executor pipeline.
|
|
473
|
+
const { executor, calls } = makeCapturingExecutor();
|
|
474
|
+
const ctx = makeSetupCtx({
|
|
475
|
+
subagentAllowedTools: new Set(["remember"]),
|
|
476
|
+
subagentToolGateMode: "wire",
|
|
477
|
+
});
|
|
478
|
+
const toolFn = makeToolFn(executor, ctx);
|
|
479
|
+
|
|
480
|
+
const result = await toolFn("switch_inference_profile", {
|
|
481
|
+
profile: "speedy",
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
expect(result).toEqual({
|
|
485
|
+
content: "Switched to Speedy profile. Continue with your response.",
|
|
486
|
+
isError: false,
|
|
487
|
+
});
|
|
488
|
+
expect(ctx.toolRoutedProfile).toBe("speedy");
|
|
489
|
+
expect(calls).toHaveLength(0);
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
test("execution mode: allowlisted switch_inference_profile still reaches the interception", async () => {
|
|
493
|
+
// When the orchestrator explicitly allowlists the routing tool, the gate
|
|
494
|
+
// passes and the interception behaves normally.
|
|
495
|
+
const { executor, calls } = makeCapturingExecutor();
|
|
496
|
+
const ctx = makeSetupCtx({
|
|
497
|
+
subagentAllowedTools: new Set(["remember", "switch_inference_profile"]),
|
|
498
|
+
subagentToolGateMode: "execution",
|
|
499
|
+
});
|
|
500
|
+
const toolFn = makeToolFn(executor, ctx);
|
|
501
|
+
|
|
502
|
+
const result = await toolFn("switch_inference_profile", {
|
|
503
|
+
profile: "speedy",
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
expect(result).toEqual({
|
|
507
|
+
content: "Switched to Speedy profile. Continue with your response.",
|
|
508
|
+
isError: false,
|
|
509
|
+
});
|
|
510
|
+
expect(ctx.toolRoutedProfile).toBe("speedy");
|
|
511
|
+
expect(calls).toHaveLength(0);
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
test("wire mode (and absent mode) leaves the executor path unchanged (regression)", async () => {
|
|
515
|
+
// In wire mode the allowlist is enforced by filtering the wire defs (and
|
|
516
|
+
// by the executor pipeline's own allowedToolNames gate) — the new
|
|
517
|
+
// execution-layer rejection must stay inert.
|
|
518
|
+
for (const subagentToolGateMode of ["wire", undefined] as const) {
|
|
519
|
+
const { executor, calls } = makeCapturingExecutor();
|
|
520
|
+
const toolFn = makeToolFn(
|
|
521
|
+
executor,
|
|
522
|
+
makeSetupCtx({
|
|
523
|
+
subagentAllowedTools: new Set(["remember"]),
|
|
524
|
+
subagentToolGateMode,
|
|
525
|
+
}),
|
|
526
|
+
);
|
|
527
|
+
|
|
528
|
+
const result = await toolFn("bash", { command: "echo hi" });
|
|
529
|
+
|
|
530
|
+
expect(result).toEqual({ content: "ok", isError: false });
|
|
531
|
+
expect(calls).toHaveLength(1);
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
test("execution mode without an allowlist gates nothing", async () => {
|
|
536
|
+
const { executor, calls } = makeCapturingExecutor();
|
|
537
|
+
const toolFn = makeToolFn(
|
|
538
|
+
executor,
|
|
539
|
+
makeSetupCtx({ subagentToolGateMode: "execution" }),
|
|
540
|
+
);
|
|
541
|
+
|
|
542
|
+
const result = await toolFn("bash", { command: "echo hi" });
|
|
543
|
+
|
|
544
|
+
expect(result).toEqual({ content: "ok", isError: false });
|
|
545
|
+
expect(calls).toHaveLength(1);
|
|
546
|
+
});
|
|
547
|
+
});
|
|
@@ -1326,7 +1326,7 @@ describe("buildSystemPrompt", () => {
|
|
|
1326
1326
|
const result = buildSystemPrompt();
|
|
1327
1327
|
expect(result).toContain("## Credential Security");
|
|
1328
1328
|
expect(result).toContain("Never ask users to share secrets");
|
|
1329
|
-
expect(result).toContain("`
|
|
1329
|
+
expect(result).toContain("`assistant credentials prompt`");
|
|
1330
1330
|
});
|
|
1331
1331
|
|
|
1332
1332
|
test("renders after the access-preference section to preserve original order", () => {
|