@vellumai/assistant 0.8.12 → 0.9.0-staging.1
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/openapi.yaml +6935 -5545
- 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-dir-path-guard.test.ts +27 -3
- 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-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 +125 -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__/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__/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/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/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 +75 -0
- 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 +1 -1
- 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/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-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-stages/acl-enforcement.ts +26 -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 +122 -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/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 +48 -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
|
@@ -37,11 +37,6 @@ mock.module("../util/logger.js", () => ({
|
|
|
37
37
|
getLogger: () => makeLoggerStub(),
|
|
38
38
|
}));
|
|
39
39
|
|
|
40
|
-
mock.module("../memory/guardian-action-store.js", () => ({
|
|
41
|
-
getGuardianActionRequest: () => null,
|
|
42
|
-
resolveGuardianActionRequest: () => {},
|
|
43
|
-
}));
|
|
44
|
-
|
|
45
40
|
mock.module("../providers/registry.js", () => ({
|
|
46
41
|
getProvider: () => ({ name: "mock-provider" }),
|
|
47
42
|
initializeProviders: async () => {},
|
|
@@ -18,11 +18,6 @@ mock.module("../util/logger.js", () => ({
|
|
|
18
18
|
new Proxy({} as Record<string, unknown>, { get: () => () => {} }),
|
|
19
19
|
}));
|
|
20
20
|
|
|
21
|
-
mock.module("../memory/guardian-action-store.js", () => ({
|
|
22
|
-
getGuardianActionRequest: () => null,
|
|
23
|
-
resolveGuardianActionRequest: () => {},
|
|
24
|
-
}));
|
|
25
|
-
|
|
26
21
|
mock.module("../providers/registry.js", () => ({
|
|
27
22
|
getProvider: () => ({ name: "mock-provider" }),
|
|
28
23
|
initializeProviders: async () => {},
|
|
@@ -14,11 +14,6 @@ mock.module("../util/logger.js", () => ({
|
|
|
14
14
|
new Proxy({} as Record<string, unknown>, { get: () => () => {} }),
|
|
15
15
|
}));
|
|
16
16
|
|
|
17
|
-
mock.module("../memory/guardian-action-store.js", () => ({
|
|
18
|
-
getGuardianActionRequest: () => null,
|
|
19
|
-
resolveGuardianActionRequest: () => {},
|
|
20
|
-
}));
|
|
21
|
-
|
|
22
17
|
mock.module("../providers/registry.js", () => ({
|
|
23
18
|
getProvider: () => ({ name: "mock-provider" }),
|
|
24
19
|
initializeProviders: async () => {},
|
|
@@ -37,11 +37,6 @@ mock.module("../util/logger.js", () => ({
|
|
|
37
37
|
getLogger: () => makeLoggerStub(),
|
|
38
38
|
}));
|
|
39
39
|
|
|
40
|
-
mock.module("../memory/guardian-action-store.js", () => ({
|
|
41
|
-
getGuardianActionRequest: () => null,
|
|
42
|
-
resolveGuardianActionRequest: () => {},
|
|
43
|
-
}));
|
|
44
|
-
|
|
45
40
|
mock.module("../providers/registry.js", () => ({
|
|
46
41
|
getProvider: () => ({ name: "mock-provider" }),
|
|
47
42
|
initializeProviders: async () => {},
|
|
@@ -208,6 +208,73 @@ describe("task_progress surface compatibility", () => {
|
|
|
208
208
|
);
|
|
209
209
|
});
|
|
210
210
|
|
|
211
|
+
test("ui_show fills well-formed templateData for a stepless task_progress card", async () => {
|
|
212
|
+
const sent: ServerMessage[] = [];
|
|
213
|
+
const ctx = makeContext(sent);
|
|
214
|
+
|
|
215
|
+
const result = await surfaceProxyResolver(ctx, "ui_show", {
|
|
216
|
+
surface_type: "card",
|
|
217
|
+
title: "Researching",
|
|
218
|
+
template: "task_progress",
|
|
219
|
+
data: {},
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
expect(result.isError).toBe(false);
|
|
223
|
+
|
|
224
|
+
const showMessage = sent.find(
|
|
225
|
+
(msg): msg is UiSurfaceShow => msg.type === "ui_surface_show",
|
|
226
|
+
);
|
|
227
|
+
expect(showMessage).toBeDefined();
|
|
228
|
+
if (!showMessage || showMessage.surfaceType !== "card") return;
|
|
229
|
+
|
|
230
|
+
const card = showMessage.data as CardSurfaceData;
|
|
231
|
+
expect(card.template).toBe("task_progress");
|
|
232
|
+
const templateData = card.templateData as Record<string, unknown>;
|
|
233
|
+
expect(templateData.status).toBe("in_progress");
|
|
234
|
+
expect(templateData.title).toBe("Researching");
|
|
235
|
+
expect(templateData.steps).toEqual([]);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test("ui_show coerces invalid status and malformed steps on a task_progress card", async () => {
|
|
239
|
+
const sent: ServerMessage[] = [];
|
|
240
|
+
const ctx = makeContext(sent);
|
|
241
|
+
|
|
242
|
+
const result = await surfaceProxyResolver(ctx, "ui_show", {
|
|
243
|
+
surface_type: "card",
|
|
244
|
+
title: "Building",
|
|
245
|
+
template: "task_progress",
|
|
246
|
+
templateData: {
|
|
247
|
+
status: "working",
|
|
248
|
+
steps: [
|
|
249
|
+
{ label: "Valid step" },
|
|
250
|
+
{ title: "Aliased label", status: "completed" },
|
|
251
|
+
{ status: "in_progress" },
|
|
252
|
+
"not an object",
|
|
253
|
+
],
|
|
254
|
+
},
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
expect(result.isError).toBe(false);
|
|
258
|
+
|
|
259
|
+
const showMessage = sent.find(
|
|
260
|
+
(msg): msg is UiSurfaceShow => msg.type === "ui_surface_show",
|
|
261
|
+
);
|
|
262
|
+
expect(showMessage).toBeDefined();
|
|
263
|
+
if (!showMessage || showMessage.surfaceType !== "card") return;
|
|
264
|
+
|
|
265
|
+
const templateData = (showMessage.data as CardSurfaceData)
|
|
266
|
+
.templateData as Record<string, unknown>;
|
|
267
|
+
expect(templateData.status).toBe("in_progress");
|
|
268
|
+
const steps = templateData.steps as Array<Record<string, unknown>>;
|
|
269
|
+
expect(steps).toHaveLength(2);
|
|
270
|
+
expect(steps[0]).toEqual({ label: "Valid step", status: "pending" });
|
|
271
|
+
expect(steps[1]).toEqual({
|
|
272
|
+
title: "Aliased label",
|
|
273
|
+
label: "Aliased label",
|
|
274
|
+
status: "completed",
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
|
|
211
278
|
test("ui_show normalizes top-level dynamic_page fields into data", async () => {
|
|
212
279
|
const sent: ServerMessage[] = [];
|
|
213
280
|
const ctx = makeContext(sent);
|
|
@@ -256,6 +256,8 @@ describe("recordUsage", () => {
|
|
|
256
256
|
conversationId: "conv-usage-1",
|
|
257
257
|
inputTokens: 3_420_218,
|
|
258
258
|
outputTokens: 11_768,
|
|
259
|
+
cacheCreationInputTokens: 373_619,
|
|
260
|
+
cacheReadInputTokens: 3_046_461,
|
|
259
261
|
totalInputTokens: 3_420_218,
|
|
260
262
|
totalOutputTokens: 11_768,
|
|
261
263
|
estimatedCost: expectedPricing.estimatedCostUsd ?? 0,
|
|
@@ -21,11 +21,6 @@ mock.module("../util/logger.js", () => ({
|
|
|
21
21
|
new Proxy({} as Record<string, unknown>, { get: () => () => {} }),
|
|
22
22
|
}));
|
|
23
23
|
|
|
24
|
-
mock.module("../memory/guardian-action-store.js", () => ({
|
|
25
|
-
getGuardianActionRequest: () => null,
|
|
26
|
-
resolveGuardianActionRequest: () => {},
|
|
27
|
-
}));
|
|
28
|
-
|
|
29
24
|
mock.module("../providers/registry.js", () => ({
|
|
30
25
|
getProvider: () => ({ name: "mock-provider" }),
|
|
31
26
|
initializeProviders: async () => {},
|
|
@@ -19,11 +19,6 @@ mock.module("../util/logger.js", () => ({
|
|
|
19
19
|
new Proxy({} as Record<string, unknown>, { get: () => () => {} }),
|
|
20
20
|
}));
|
|
21
21
|
|
|
22
|
-
mock.module("../memory/guardian-action-store.js", () => ({
|
|
23
|
-
getGuardianActionRequest: () => null,
|
|
24
|
-
resolveGuardianActionRequest: () => {},
|
|
25
|
-
}));
|
|
26
|
-
|
|
27
22
|
mock.module("../providers/registry.js", () => ({
|
|
28
23
|
getProvider: () => ({ name: "mock-provider" }),
|
|
29
24
|
initializeProviders: async () => {},
|
|
@@ -461,7 +461,7 @@ describe("CredentialBroker.browserFill", () => {
|
|
|
461
461
|
expect(result.reason).toContain("not allowed");
|
|
462
462
|
});
|
|
463
463
|
|
|
464
|
-
test("denies with empty allowedTools and suggests
|
|
464
|
+
test("denies with empty allowedTools and suggests assistant credentials set", async () => {
|
|
465
465
|
upsertCredentialMetadata("custom", "key", {
|
|
466
466
|
allowedTools: [],
|
|
467
467
|
});
|
|
@@ -478,7 +478,7 @@ describe("CredentialBroker.browserFill", () => {
|
|
|
478
478
|
|
|
479
479
|
expect(result.success).toBe(false);
|
|
480
480
|
expect(result.reason).toContain("No tools are currently allowed");
|
|
481
|
-
expect(result.reason).toContain("
|
|
481
|
+
expect(result.reason).toContain("assistant credentials set");
|
|
482
482
|
});
|
|
483
483
|
});
|
|
484
484
|
|
|
@@ -305,7 +305,7 @@ describe("CredentialBroker.serverUse", () => {
|
|
|
305
305
|
|
|
306
306
|
expect(result.success).toBe(false);
|
|
307
307
|
expect(result.reason).toContain("No tools are currently allowed");
|
|
308
|
-
expect(result.reason).toContain("
|
|
308
|
+
expect(result.reason).toContain("assistant credentials set");
|
|
309
309
|
});
|
|
310
310
|
|
|
311
311
|
test("denies when credential has domain restrictions even if tool matches", async () => {
|
|
@@ -594,7 +594,7 @@ describe("CredentialBroker.serverUseById", () => {
|
|
|
594
594
|
expect(result.success).toBe(false);
|
|
595
595
|
if (result.success) throw new Error("expected denial");
|
|
596
596
|
expect(result.reason).toContain("No tools are currently allowed");
|
|
597
|
-
expect(result.reason).toContain("
|
|
597
|
+
expect(result.reason).toContain("assistant credentials set");
|
|
598
598
|
});
|
|
599
599
|
|
|
600
600
|
test("denies when metadata exists but no stored secret value", async () => {
|
|
@@ -85,7 +85,7 @@ describe("CredentialBroker", () => {
|
|
|
85
85
|
if (!result.authorized) {
|
|
86
86
|
expect(result.reason).toContain("not allowed");
|
|
87
87
|
expect(result.reason).toContain("No tools are currently allowed");
|
|
88
|
-
expect(result.reason).toContain("
|
|
88
|
+
expect(result.reason).toContain("assistant credentials set");
|
|
89
89
|
}
|
|
90
90
|
});
|
|
91
91
|
|
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import type { SlackChannelConfigResult } from "../daemon/handlers/config-slack-channel.js";
|
|
4
|
+
import type { SecretPromptResult } from "../permissions/secret-prompter.js";
|
|
5
|
+
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// Mutable mock state (closed over by the mock factories below)
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
let capturedSecretParams: Record<string, unknown> | undefined;
|
|
11
|
+
let capturedMetadata: Record<string, unknown> | undefined;
|
|
12
|
+
let secretResult: SecretPromptResult = {
|
|
13
|
+
value: "secret-value",
|
|
14
|
+
delivery: "store",
|
|
15
|
+
};
|
|
16
|
+
let allowOneTimeSend = true;
|
|
17
|
+
let existingMetadata: Record<string, unknown> | undefined;
|
|
18
|
+
let slackConfigResult: SlackChannelConfigResult;
|
|
19
|
+
let slackConfigArgs: Array<(string | undefined)[]>;
|
|
20
|
+
let secureKeyWrites: Array<{ key: string; value: string }>;
|
|
21
|
+
let transientInjections: Array<{
|
|
22
|
+
service: string;
|
|
23
|
+
field: string;
|
|
24
|
+
value: string;
|
|
25
|
+
}>;
|
|
26
|
+
let syncedServices: string[];
|
|
27
|
+
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// Mocks for the route's collaborators
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
mock.module("../util/logger.js", () => ({
|
|
33
|
+
getLogger: () =>
|
|
34
|
+
new Proxy({} as Record<string, unknown>, {
|
|
35
|
+
get: () => () => {},
|
|
36
|
+
}),
|
|
37
|
+
}));
|
|
38
|
+
|
|
39
|
+
mock.module("../daemon/handlers/shared.js", () => ({
|
|
40
|
+
requestSecretStandalone: mock(
|
|
41
|
+
async (params: Record<string, unknown>): Promise<SecretPromptResult> => {
|
|
42
|
+
capturedSecretParams = params;
|
|
43
|
+
return secretResult;
|
|
44
|
+
},
|
|
45
|
+
),
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
mock.module("../tools/credentials/metadata-store.js", () => ({
|
|
49
|
+
assertMetadataWritable: () => {},
|
|
50
|
+
getCredentialMetadata: mock(() => existingMetadata),
|
|
51
|
+
upsertCredentialMetadata: mock(
|
|
52
|
+
(service: string, field: string, meta: Record<string, unknown>) => {
|
|
53
|
+
capturedMetadata = { service, field, ...meta };
|
|
54
|
+
},
|
|
55
|
+
),
|
|
56
|
+
}));
|
|
57
|
+
|
|
58
|
+
mock.module("../security/secure-keys.js", () => ({
|
|
59
|
+
setSecureKeyAsync: mock(async (key: string, value: string) => {
|
|
60
|
+
secureKeyWrites.push({ key, value });
|
|
61
|
+
return true;
|
|
62
|
+
}),
|
|
63
|
+
}));
|
|
64
|
+
|
|
65
|
+
mock.module("../security/credential-key.js", () => ({
|
|
66
|
+
credentialKey: (service: string, field: string) => `${service}:${field}`,
|
|
67
|
+
}));
|
|
68
|
+
|
|
69
|
+
mock.module("../oauth/manual-token-connection.js", () => ({
|
|
70
|
+
syncManualTokenConnection: mock(async (service: string) => {
|
|
71
|
+
syncedServices.push(service);
|
|
72
|
+
}),
|
|
73
|
+
}));
|
|
74
|
+
|
|
75
|
+
mock.module("../config/loader.js", () => ({
|
|
76
|
+
getConfig: () => ({ secretDetection: { allowOneTimeSend } }),
|
|
77
|
+
}));
|
|
78
|
+
|
|
79
|
+
mock.module("../daemon/handlers/config-slack-channel.js", () => ({
|
|
80
|
+
setSlackChannelConfig: mock(
|
|
81
|
+
async (
|
|
82
|
+
botToken?: string,
|
|
83
|
+
appToken?: string,
|
|
84
|
+
userToken?: string,
|
|
85
|
+
): Promise<SlackChannelConfigResult> => {
|
|
86
|
+
slackConfigArgs.push([botToken, appToken, userToken]);
|
|
87
|
+
return slackConfigResult;
|
|
88
|
+
},
|
|
89
|
+
),
|
|
90
|
+
}));
|
|
91
|
+
|
|
92
|
+
mock.module("../tools/credentials/broker.js", () => ({
|
|
93
|
+
credentialBroker: {
|
|
94
|
+
injectTransient: mock((service: string, field: string, value: string) => {
|
|
95
|
+
transientInjections.push({ service, field, value });
|
|
96
|
+
}),
|
|
97
|
+
},
|
|
98
|
+
}));
|
|
99
|
+
|
|
100
|
+
import { ROUTES } from "../runtime/routes/credential-prompt-routes.js";
|
|
101
|
+
|
|
102
|
+
const promptRoute = ROUTES.find((r) => r.operationId === "credentials_prompt");
|
|
103
|
+
|
|
104
|
+
function slackResult(
|
|
105
|
+
overrides: Partial<SlackChannelConfigResult>,
|
|
106
|
+
): SlackChannelConfigResult {
|
|
107
|
+
return {
|
|
108
|
+
success: true,
|
|
109
|
+
hasBotToken: false,
|
|
110
|
+
hasAppToken: false,
|
|
111
|
+
hasUserToken: false,
|
|
112
|
+
connected: false,
|
|
113
|
+
...overrides,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
type PromptResponse = {
|
|
118
|
+
ok: boolean;
|
|
119
|
+
error?: string;
|
|
120
|
+
service?: string;
|
|
121
|
+
field?: string;
|
|
122
|
+
message?: string;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
describe("credentials/prompt route", () => {
|
|
126
|
+
beforeEach(() => {
|
|
127
|
+
capturedSecretParams = undefined;
|
|
128
|
+
capturedMetadata = undefined;
|
|
129
|
+
secretResult = { value: "secret-value", delivery: "store" };
|
|
130
|
+
allowOneTimeSend = true;
|
|
131
|
+
existingMetadata = undefined;
|
|
132
|
+
slackConfigResult = slackResult({});
|
|
133
|
+
slackConfigArgs = [];
|
|
134
|
+
secureKeyWrites = [];
|
|
135
|
+
transientInjections = [];
|
|
136
|
+
syncedServices = [];
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("forwards usageDescription as the prompt purpose and to metadata", async () => {
|
|
140
|
+
/**
|
|
141
|
+
* The CLI `credentials prompt` command exposes `--usage-description` to
|
|
142
|
+
* match the credential_store tool's prompt action. The route must thread
|
|
143
|
+
* it to the secure prompt (as `purpose`, shown to the user) and persist it
|
|
144
|
+
* to credential metadata (as `usageDescription`).
|
|
145
|
+
*/
|
|
146
|
+
// GIVEN the credentials/prompt route is registered
|
|
147
|
+
expect(promptRoute).toBeDefined();
|
|
148
|
+
|
|
149
|
+
// WHEN a prompt request carries a usageDescription
|
|
150
|
+
const result = (await promptRoute!.handler({
|
|
151
|
+
body: {
|
|
152
|
+
service: "sentry",
|
|
153
|
+
field: "auth_token",
|
|
154
|
+
label: "Sentry Auth Token",
|
|
155
|
+
usageDescription: "Needed to read issues",
|
|
156
|
+
},
|
|
157
|
+
})) as PromptResponse;
|
|
158
|
+
|
|
159
|
+
// THEN the prompt is issued with the usageDescription as its purpose
|
|
160
|
+
expect(result.ok).toBe(true);
|
|
161
|
+
expect(capturedSecretParams?.purpose).toBe("Needed to read issues");
|
|
162
|
+
|
|
163
|
+
// AND the credential metadata records the usageDescription
|
|
164
|
+
expect(capturedMetadata?.usageDescription).toBe("Needed to read issues");
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("omits purpose when usageDescription is not provided", async () => {
|
|
168
|
+
/**
|
|
169
|
+
* usageDescription is optional, so a prompt without it must not invent a
|
|
170
|
+
* purpose or a metadata usageDescription.
|
|
171
|
+
*/
|
|
172
|
+
// GIVEN a prompt request with no usageDescription
|
|
173
|
+
// WHEN the route handles it
|
|
174
|
+
const result = (await promptRoute!.handler({
|
|
175
|
+
body: {
|
|
176
|
+
service: "github",
|
|
177
|
+
field: "pat",
|
|
178
|
+
label: "GitHub PAT",
|
|
179
|
+
},
|
|
180
|
+
})) as PromptResponse;
|
|
181
|
+
|
|
182
|
+
// THEN no purpose is forwarded to the prompt
|
|
183
|
+
expect(result.ok).toBe(true);
|
|
184
|
+
expect(capturedSecretParams?.purpose).toBeUndefined();
|
|
185
|
+
|
|
186
|
+
// AND no usageDescription is written to metadata
|
|
187
|
+
expect(capturedMetadata?.usageDescription).toBeUndefined();
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test("stores a non-slack credential to secure storage without a message", async () => {
|
|
191
|
+
/**
|
|
192
|
+
* The default delivery persists the value to secure storage and runs the
|
|
193
|
+
* manual-token connection sync, returning no message so the CLI prints its
|
|
194
|
+
* generic "Stored credential" line.
|
|
195
|
+
*/
|
|
196
|
+
// GIVEN a standard store-delivery prompt for a non-slack credential
|
|
197
|
+
// WHEN the route handles it
|
|
198
|
+
const result = (await promptRoute!.handler({
|
|
199
|
+
body: { service: "stripe", field: "api_key", label: "Stripe API Key" },
|
|
200
|
+
})) as PromptResponse;
|
|
201
|
+
|
|
202
|
+
// THEN the value is written to secure storage
|
|
203
|
+
expect(result.ok).toBe(true);
|
|
204
|
+
expect(secureKeyWrites).toEqual([
|
|
205
|
+
{ key: "stripe:api_key", value: "secret-value" },
|
|
206
|
+
]);
|
|
207
|
+
|
|
208
|
+
// AND the manual token connection is synced for the service
|
|
209
|
+
expect(syncedServices).toEqual(["stripe"]);
|
|
210
|
+
|
|
211
|
+
// AND no message is returned (the CLI falls back to its default line)
|
|
212
|
+
expect(result.message).toBeUndefined();
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test("connects the channel when a slack_channel token is provided", async () => {
|
|
216
|
+
/**
|
|
217
|
+
* Slack channel tokens must route through the Slack channel config so the
|
|
218
|
+
* channel actually connects, and the route surfaces the connection status
|
|
219
|
+
* to the CLI via `message`.
|
|
220
|
+
*/
|
|
221
|
+
// GIVEN the Slack channel config reports a successful connection
|
|
222
|
+
slackConfigResult = slackResult({
|
|
223
|
+
connected: true,
|
|
224
|
+
teamName: "Acme",
|
|
225
|
+
botUsername: "acmebot",
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// WHEN a slack_channel bot_token is submitted
|
|
229
|
+
const result = (await promptRoute!.handler({
|
|
230
|
+
body: {
|
|
231
|
+
service: "slack_channel",
|
|
232
|
+
field: "bot_token",
|
|
233
|
+
label: "Slack Bot Token",
|
|
234
|
+
},
|
|
235
|
+
})) as PromptResponse;
|
|
236
|
+
|
|
237
|
+
// THEN the token is routed through the Slack channel config, not secure storage
|
|
238
|
+
expect(slackConfigArgs).toEqual([["secret-value", undefined, undefined]]);
|
|
239
|
+
expect(secureKeyWrites).toEqual([]);
|
|
240
|
+
|
|
241
|
+
// AND the connection status is surfaced as the response message
|
|
242
|
+
expect(result.ok).toBe(true);
|
|
243
|
+
expect(result.message).toBe("Slack channel connected to Acme (@acmebot).");
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test("surfaces the warning when a slack_channel connection is incomplete", async () => {
|
|
247
|
+
/**
|
|
248
|
+
* When only one of the required Slack tokens is present the config returns a
|
|
249
|
+
* warning instead of a connection; the route surfaces it so the user knows
|
|
250
|
+
* to provide the remaining token.
|
|
251
|
+
*/
|
|
252
|
+
// GIVEN the Slack channel config reports an incomplete connection
|
|
253
|
+
slackConfigResult = slackResult({
|
|
254
|
+
warning: "Slack channel connection incomplete; provide the app token.",
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// WHEN a slack_channel app_token is submitted
|
|
258
|
+
const result = (await promptRoute!.handler({
|
|
259
|
+
body: {
|
|
260
|
+
service: "slack_channel",
|
|
261
|
+
field: "app_token",
|
|
262
|
+
label: "Slack App Token",
|
|
263
|
+
},
|
|
264
|
+
})) as PromptResponse;
|
|
265
|
+
|
|
266
|
+
// THEN the warning is surfaced as the response message
|
|
267
|
+
expect(result.ok).toBe(true);
|
|
268
|
+
expect(result.message).toBe(
|
|
269
|
+
"Slack channel connection incomplete; provide the app token.",
|
|
270
|
+
);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
test("fails when the slack_channel config rejects the token", async () => {
|
|
274
|
+
/**
|
|
275
|
+
* A failed Slack configuration (e.g. invalid_auth) must surface as an error
|
|
276
|
+
* rather than silently storing an unusable token.
|
|
277
|
+
*/
|
|
278
|
+
// GIVEN the Slack channel config rejects the token
|
|
279
|
+
slackConfigResult = slackResult({ success: false, error: "invalid_auth" });
|
|
280
|
+
|
|
281
|
+
// WHEN a slack_channel bot_token is submitted
|
|
282
|
+
const result = (await promptRoute!.handler({
|
|
283
|
+
body: {
|
|
284
|
+
service: "slack_channel",
|
|
285
|
+
field: "bot_token",
|
|
286
|
+
label: "Slack Bot Token",
|
|
287
|
+
},
|
|
288
|
+
})) as PromptResponse;
|
|
289
|
+
|
|
290
|
+
// THEN the route returns the configuration error
|
|
291
|
+
expect(result.ok).toBe(false);
|
|
292
|
+
expect(result.error).toBe("invalid_auth");
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
test("injects a one-time credential without persisting it", async () => {
|
|
296
|
+
/**
|
|
297
|
+
* One-time-send delivery hands the value to the broker for the next
|
|
298
|
+
* operation and must never write it to secure storage.
|
|
299
|
+
*/
|
|
300
|
+
// GIVEN one-time send is enabled and the user chose transient delivery
|
|
301
|
+
allowOneTimeSend = true;
|
|
302
|
+
secretResult = { value: "secret-value", delivery: "transient_send" };
|
|
303
|
+
|
|
304
|
+
// WHEN a non-slack credential is submitted for one-time use
|
|
305
|
+
const result = (await promptRoute!.handler({
|
|
306
|
+
body: { service: "stripe", field: "api_key", label: "Stripe API Key" },
|
|
307
|
+
})) as PromptResponse;
|
|
308
|
+
|
|
309
|
+
// THEN the value is injected into the broker, not secure storage
|
|
310
|
+
expect(transientInjections).toEqual([
|
|
311
|
+
{ service: "stripe", field: "api_key", value: "secret-value" },
|
|
312
|
+
]);
|
|
313
|
+
expect(secureKeyWrites).toEqual([]);
|
|
314
|
+
|
|
315
|
+
// AND the response message notes the value was not saved
|
|
316
|
+
expect(result.ok).toBe(true);
|
|
317
|
+
expect(result.message).toContain("One-time credential provided");
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
test("rejects one-time send when it is disabled in config", async () => {
|
|
321
|
+
/**
|
|
322
|
+
* Transient delivery requires the secretDetection.allowOneTimeSend config
|
|
323
|
+
* flag; otherwise the value is rejected rather than injected.
|
|
324
|
+
*/
|
|
325
|
+
// GIVEN one-time send is disabled and the user chose transient delivery
|
|
326
|
+
allowOneTimeSend = false;
|
|
327
|
+
secretResult = { value: "secret-value", delivery: "transient_send" };
|
|
328
|
+
|
|
329
|
+
// WHEN a credential is submitted for one-time use
|
|
330
|
+
const result = (await promptRoute!.handler({
|
|
331
|
+
body: { service: "stripe", field: "api_key", label: "Stripe API Key" },
|
|
332
|
+
})) as PromptResponse;
|
|
333
|
+
|
|
334
|
+
// THEN the request is rejected and nothing is injected
|
|
335
|
+
expect(result.ok).toBe(false);
|
|
336
|
+
expect(result.error).toContain("one-time send is not enabled");
|
|
337
|
+
expect(transientInjections).toEqual([]);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
test("rejects one-time send for slack_channel credentials", async () => {
|
|
341
|
+
/**
|
|
342
|
+
* Slack channel tokens must be saved so the channel can connect; a one-time
|
|
343
|
+
* send for them is rejected.
|
|
344
|
+
*/
|
|
345
|
+
// GIVEN the user chose transient delivery for a slack_channel token
|
|
346
|
+
secretResult = { value: "secret-value", delivery: "transient_send" };
|
|
347
|
+
|
|
348
|
+
// WHEN a slack_channel bot_token is submitted for one-time use
|
|
349
|
+
const result = (await promptRoute!.handler({
|
|
350
|
+
body: {
|
|
351
|
+
service: "slack_channel",
|
|
352
|
+
field: "bot_token",
|
|
353
|
+
label: "Slack Bot Token",
|
|
354
|
+
},
|
|
355
|
+
})) as PromptResponse;
|
|
356
|
+
|
|
357
|
+
// THEN the request is rejected without injecting or connecting
|
|
358
|
+
expect(result.ok).toBe(false);
|
|
359
|
+
expect(result.error).toContain("must be saved to secure storage");
|
|
360
|
+
expect(transientInjections).toEqual([]);
|
|
361
|
+
expect(slackConfigArgs).toEqual([]);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
test("omitting allowed-tools/-domains forwards undefined so an existing policy is preserved", async () => {
|
|
365
|
+
/**
|
|
366
|
+
* Rotating an existing credential without policy flags must not wipe its
|
|
367
|
+
* allowed tools/domains. The route forwards `undefined` (not `[]`) for
|
|
368
|
+
* omitted flags so the metadata store's partial-update path leaves the
|
|
369
|
+
* existing policy untouched.
|
|
370
|
+
*/
|
|
371
|
+
// GIVEN a credential already exists with an allowed-tools policy
|
|
372
|
+
existingMetadata = {
|
|
373
|
+
service: "github",
|
|
374
|
+
field: "pat",
|
|
375
|
+
allowedTools: ["bash"],
|
|
376
|
+
allowedDomains: ["github.com"],
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
// WHEN it is re-prompted without allowed-tools or allowed-domains
|
|
380
|
+
const result = (await promptRoute!.handler({
|
|
381
|
+
body: { service: "github", field: "pat", label: "GitHub PAT" },
|
|
382
|
+
})) as PromptResponse;
|
|
383
|
+
|
|
384
|
+
// THEN the metadata upsert receives undefined for both lists
|
|
385
|
+
expect(result.ok).toBe(true);
|
|
386
|
+
expect(capturedMetadata).toBeDefined();
|
|
387
|
+
expect(capturedMetadata!.allowedTools).toBeUndefined();
|
|
388
|
+
expect(capturedMetadata!.allowedDomains).toBeUndefined();
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
test("forwards provided allowed-tools/-domains to credential metadata", async () => {
|
|
392
|
+
/**
|
|
393
|
+
* When the caller does supply policy flags they must reach the metadata
|
|
394
|
+
* store verbatim, including an explicit empty array used to set a deny-all
|
|
395
|
+
* policy.
|
|
396
|
+
*/
|
|
397
|
+
// GIVEN a prompt request that supplies an allowed-tools list and a
|
|
398
|
+
// deny-all (empty) allowed-domains list
|
|
399
|
+
// WHEN the route handles it
|
|
400
|
+
const result = (await promptRoute!.handler({
|
|
401
|
+
body: {
|
|
402
|
+
service: "stripe",
|
|
403
|
+
field: "api_key",
|
|
404
|
+
label: "Stripe API Key",
|
|
405
|
+
allowedTools: ["make_authenticated_request"],
|
|
406
|
+
allowedDomains: [],
|
|
407
|
+
},
|
|
408
|
+
})) as PromptResponse;
|
|
409
|
+
|
|
410
|
+
// THEN the metadata upsert receives the supplied lists verbatim
|
|
411
|
+
expect(result.ok).toBe(true);
|
|
412
|
+
expect(capturedMetadata!.allowedTools).toEqual([
|
|
413
|
+
"make_authenticated_request",
|
|
414
|
+
]);
|
|
415
|
+
expect(capturedMetadata!.allowedDomains).toEqual([]);
|
|
416
|
+
});
|
|
417
|
+
});
|
|
@@ -166,6 +166,7 @@ describe("Invariant 2: no generic plaintext secret read API", () => {
|
|
|
166
166
|
// Any new import must be reviewed for secret-leak risk and added here.
|
|
167
167
|
const ALLOWED_IMPORTERS = new Set([
|
|
168
168
|
"tools/credentials/vault.ts", // credential store tool
|
|
169
|
+
"credential-execution/prompted-credential.ts", // shared prompt-action persistence (stores secret via setSecureKeyAsync)
|
|
169
170
|
"tools/credentials/broker.ts", // brokered credential access
|
|
170
171
|
"tools/network/web-search.ts", // web search API key lookup
|
|
171
172
|
"daemon/handlers/config-telegram.ts", // Telegram bot token management
|
|
@@ -842,6 +842,43 @@ describe("credential_store tool", () => {
|
|
|
842
842
|
expect(metadata!.allowedDomains).toEqual([]);
|
|
843
843
|
});
|
|
844
844
|
|
|
845
|
+
test("store rotation without policy fields preserves the existing policy", async () => {
|
|
846
|
+
/**
|
|
847
|
+
* Re-storing (rotating) a credential without policy fields must keep the
|
|
848
|
+
* policy it was originally stored with, rather than resetting it to a
|
|
849
|
+
* deny-all empty policy.
|
|
850
|
+
*/
|
|
851
|
+
// GIVEN a credential stored with an allowed-tools/-domains policy
|
|
852
|
+
await credentialStoreTool.execute(
|
|
853
|
+
{
|
|
854
|
+
action: "store",
|
|
855
|
+
service: "rotation-svc",
|
|
856
|
+
field: "token",
|
|
857
|
+
value: "v1",
|
|
858
|
+
allowed_tools: ["browser_fill_credential"],
|
|
859
|
+
allowed_domains: ["example.com"],
|
|
860
|
+
},
|
|
861
|
+
_ctx,
|
|
862
|
+
);
|
|
863
|
+
|
|
864
|
+
// WHEN it is re-stored with a new value but no policy fields
|
|
865
|
+
const result = await credentialStoreTool.execute(
|
|
866
|
+
{
|
|
867
|
+
action: "store",
|
|
868
|
+
service: "rotation-svc",
|
|
869
|
+
field: "token",
|
|
870
|
+
value: "v2",
|
|
871
|
+
},
|
|
872
|
+
_ctx,
|
|
873
|
+
);
|
|
874
|
+
|
|
875
|
+
// THEN the original policy is preserved
|
|
876
|
+
expect(result.isError).toBe(false);
|
|
877
|
+
const metadata = getCredentialMetadata("rotation-svc", "token");
|
|
878
|
+
expect(metadata!.allowedTools).toEqual(["browser_fill_credential"]);
|
|
879
|
+
expect(metadata!.allowedDomains).toEqual(["example.com"]);
|
|
880
|
+
});
|
|
881
|
+
|
|
845
882
|
test("store rejects invalid policy input", async () => {
|
|
846
883
|
const result = await credentialStoreTool.execute(
|
|
847
884
|
{
|