@vellumai/assistant 0.11.4 → 0.11.5-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 +2 -2
- package/ARCHITECTURE.md +29 -4
- package/README.md +1 -1
- package/docs/architecture/memory.md +17 -5
- package/docs/architecture/security.md +96 -152
- package/docs/guardian-request-flow.md +13 -2
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +166 -1
- package/node_modules/@vellumai/gateway-client/src/http-delivery.ts +7 -14
- package/node_modules/@vellumai/gateway-client/src/index.ts +2 -0
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +29 -31
- package/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
- package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
- package/openapi.yaml +193 -3
- package/package.json +1 -1
- package/src/__tests__/anthropic-provider.test.ts +10 -3
- package/src/__tests__/approval-interception-trust-gates.test.ts +40 -0
- package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
- package/src/__tests__/channel-approval-routes.test.ts +4 -0
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -6
- package/src/__tests__/channel-reply-delivery.test.ts +26 -13
- package/src/__tests__/checker.test.ts +20 -314
- package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +6 -2
- package/src/__tests__/client-os-metadata-persistence.test.ts +16 -0
- package/src/__tests__/compactor-retained-image-validation.test.ts +142 -0
- package/src/__tests__/container-cpu-sampler.test.ts +153 -0
- package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
- package/src/__tests__/conversation-attachments.test.ts +0 -1
- package/src/__tests__/conversation-error.test.ts +17 -0
- package/src/__tests__/conversation-lifecycle.test.ts +20 -26
- package/src/__tests__/conversation-pairing.test.ts +186 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +21 -2
- package/src/__tests__/credential-broker-server-use.test.ts +24 -5
- package/src/__tests__/credential-routes.test.ts +22 -3
- package/src/__tests__/credential-security-invariants.test.ts +2 -1
- package/src/__tests__/daemon-credential-client.test.ts +88 -0
- package/src/__tests__/default-plugin-names-guard.test.ts +12 -1
- package/src/__tests__/dm-backfill.test.ts +63 -0
- package/src/__tests__/dynamic-skill-background-guard.test.ts +0 -2
- package/src/__tests__/edit-propagation.test.ts +107 -4
- package/src/__tests__/events-client-registration.test.ts +28 -0
- package/src/__tests__/file-write-tool.test.ts +4 -2
- package/src/__tests__/guardian-prompt-notice-privacy.test.ts +133 -0
- package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +143 -97
- package/src/__tests__/helpers/gateway-classify-mock.ts +27 -6
- package/src/__tests__/host-proxy-interface.test.ts +11 -1
- package/src/__tests__/host-shell-tool.test.ts +23 -4
- package/src/__tests__/image-conversion.test.ts +143 -1
- package/src/__tests__/inline-skill-load-permissions.test.ts +47 -36
- package/src/__tests__/input-repairs.test.ts +207 -0
- package/src/__tests__/live-workspace-guard.test.ts +75 -0
- package/src/__tests__/mcp-abort-signal.test.ts +1 -1
- package/src/__tests__/mcp-client-auth.test.ts +1 -1
- package/src/__tests__/mcp-tool-annotations-risk.test.ts +1 -1
- package/src/__tests__/media-resolve-image-validation.test.ts +310 -0
- package/src/__tests__/mtime-cache.test.ts +2 -0
- package/src/__tests__/notification-vellum-adapter.test.ts +124 -1
- package/src/__tests__/openai-provider.test.ts +22 -0
- package/src/__tests__/personal-memory-auth-bypass.test.ts +22 -6
- package/src/__tests__/platform-bash-auto-approve.test.ts +0 -4
- package/src/__tests__/platform.test.ts +16 -1
- package/src/__tests__/plugin-api-resolve-credential.test.ts +22 -13
- package/src/__tests__/plugin-api-shim.test.ts +5 -0
- package/src/__tests__/plugin-api-store-credential.test.ts +268 -0
- package/src/__tests__/plugin-config-data-migration.test.ts +8 -1
- package/src/__tests__/plugin-disabled-state.test.ts +2 -0
- package/src/__tests__/plugin-effective-enabled-set.test.ts +55 -3
- package/src/__tests__/plugin-execution-context.test.ts +73 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +0 -1
- package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +0 -1
- package/src/__tests__/reaction-persistence.test.ts +200 -7
- package/src/__tests__/require-fresh-approval.test.ts +0 -4
- package/src/__tests__/resource-pressure-guard.test.ts +428 -0
- package/src/__tests__/resource-pressure-routes.test.ts +113 -0
- package/src/__tests__/risk-classification-boundary-guard.test.ts +115 -0
- package/src/__tests__/run-conversation-turn-persistence.test.ts +27 -1
- package/src/__tests__/secret-routes-platform-proxy.test.ts +7 -0
- package/src/__tests__/skill-tool-factory.test.ts +161 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +132 -5
- package/src/__tests__/tool-executor.test.ts +63 -38
- package/src/__tests__/tool-policy.test.ts +97 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +7 -4
- package/src/__tests__/user-plugin-loader.test.ts +2 -0
- package/src/__tests__/validate-input.test.ts +243 -7
- package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
- package/src/acp/__tests__/acp-claude-oauth.test.ts +97 -29
- package/src/acp/__tests__/prepare-agent-env.test.ts +376 -153
- package/src/acp/acp-claude-oauth.ts +38 -21
- package/src/acp/prepare-agent-env.ts +146 -55
- package/src/agent/loop-tool-dedup.test.ts +161 -0
- package/src/agent/loop.ts +68 -22
- package/src/api/constants/app-tools.ts +34 -0
- package/src/api/events/resource-pressure-status-changed.ts +53 -0
- package/src/api/index.ts +19 -0
- package/src/api/responses/resource-pressure-status.ts +25 -0
- package/src/approvals/guardian-channel-delivery.ts +70 -6
- package/src/approvals/guardian-request-resolvers.ts +32 -66
- package/src/channels/__tests__/message-audience.test.ts +49 -0
- package/src/channels/__tests__/types.test.ts +17 -3
- package/src/channels/message-audience.ts +40 -0
- package/src/channels/types.ts +16 -12
- package/src/cli/__tests__/catalog-search-help.test.ts +50 -0
- package/src/cli/commands/__tests__/channel-verification-sessions.test.ts +31 -0
- package/src/cli/commands/__tests__/conversations-search.test.ts +289 -0
- package/src/cli/commands/__tests__/conversations-slack.test.ts +1 -0
- package/src/cli/commands/__tests__/inference-providers.test.ts +68 -0
- package/src/cli/commands/__tests__/keys.test.ts +89 -8
- package/src/cli/commands/__tests__/plugins.test.ts +57 -1
- package/src/cli/commands/channel-verification-sessions.help.ts +11 -9
- package/src/cli/commands/channel-verification-sessions.ts +11 -21
- package/src/cli/commands/conversations.help.ts +34 -0
- package/src/cli/commands/conversations.ts +125 -0
- package/src/cli/commands/inference-providers.ts +9 -4
- package/src/cli/commands/inference.help.ts +9 -4
- package/src/cli/commands/keys.help.ts +13 -1
- package/src/cli/commands/keys.ts +26 -15
- package/src/cli/commands/memory/__tests__/memory-v2.test.ts +57 -7
- package/src/cli/commands/memory/index.help.ts +41 -27
- package/src/cli/commands/memory/index.ts +2 -0
- package/src/cli/commands/memory/memory-v2.ts +58 -54
- package/src/cli/commands/memory/memory-validate.ts +18 -0
- package/src/cli/commands/monitoring.ts +1 -0
- package/src/cli/commands/plugins.help.ts +28 -31
- package/src/cli/commands/plugins.ts +10 -0
- package/src/cli/commands/skills.help.ts +13 -16
- package/src/cli/commands/trust.ts +3 -14
- package/src/cli/lib/__tests__/install-from-github.test.ts +38 -0
- package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +34 -0
- package/src/cli/lib/__tests__/plugin-surfaces.test.ts +32 -1
- package/src/cli/lib/__tests__/toggle-plugin.test.ts +2 -1
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +64 -0
- package/src/cli/lib/bundled-marketplace.json +13 -0
- package/src/cli/lib/daemon-credential-client.ts +25 -3
- package/src/cli/lib/install-from-github.ts +35 -24
- package/src/cli/lib/merge-plugin-tree.ts +22 -4
- package/src/cli/lib/plugin-surfaces.ts +27 -0
- package/src/config/feature-flag-registry.json +35 -19
- package/src/config/webhook-routing.ts +8 -0
- package/src/context/compactor.ts +31 -2
- package/src/daemon/__tests__/provider-rejection-log-fields.test.ts +272 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +4 -1
- package/src/daemon/conversation-error.ts +13 -0
- package/src/daemon/conversation-messaging.ts +22 -2
- package/src/daemon/conversation-process.ts +11 -0
- package/src/daemon/conversation-store.ts +10 -0
- package/src/daemon/conversation-surfaces.ts +21 -8
- package/src/daemon/conversation.ts +12 -10
- package/src/daemon/lifecycle.ts +6 -2
- package/src/daemon/message-types/conversations.ts +5 -7
- package/src/daemon/provider-rejection-log-fields.ts +124 -0
- package/src/daemon/resource-pressure-guard-lifecycle.ts +66 -0
- package/src/daemon/resource-pressure-guard.ts +387 -0
- package/src/daemon/shutdown-handlers.ts +2 -0
- package/src/daemon/startup-error.ts +16 -0
- package/src/daemon/trust-context.ts +14 -10
- package/src/daemon/unsendable-image-notice.ts +76 -0
- package/src/hooks/registry.ts +65 -51
- package/src/ipc/__tests__/socket-path.test.ts +15 -7
- package/src/ipc/gateway-client.test.ts +1 -1
- package/src/ipc/gateway-client.ts +14 -21
- package/src/ipc/socket-cleanup.ts +2 -11
- package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +1 -1
- package/src/messaging/provider-message-metadata.ts +128 -0
- package/src/messaging/providers/__tests__/transport-dispatch.test.ts +116 -68
- package/src/messaging/providers/channel-transport.ts +90 -13
- package/src/messaging/providers/discord/send.ts +16 -0
- package/src/messaging/providers/discord/transport.ts +11 -1
- package/src/messaging/providers/index.ts +109 -16
- package/src/messaging/providers/slack/message-metadata.ts +45 -0
- package/src/messaging/providers/slack/render-transcript.ts +1 -4
- package/src/messaging/providers/slack/send.test.ts +6 -9
- package/src/messaging/providers/slack/send.ts +40 -28
- package/src/messaging/providers/slack/transport.ts +22 -26
- package/src/messaging/providers/telegram-bot/send.test.ts +2 -8
- package/src/messaging/providers/telegram-bot/transport.ts +3 -6
- package/src/messaging/read-provider-metadata.test.ts +157 -0
- package/src/messaging/read-provider-metadata.ts +51 -0
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +94 -0
- package/src/notifications/__tests__/edit-notification.test.ts +174 -2
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +536 -4
- package/src/notifications/adapters/macos.ts +55 -0
- package/src/notifications/adapters/slack.ts +10 -5
- package/src/notifications/assistant-reply-producer.ts +37 -4
- package/src/notifications/conversation-pairing.ts +111 -21
- package/src/notifications/edit-notification.ts +38 -8
- package/src/notifications/emit-signal.ts +12 -14
- package/src/notifications/home-feed-side-effect.ts +218 -15
- package/src/notifications/types.ts +5 -0
- package/src/permissions/AGENTS.md +16 -0
- package/src/permissions/checker.test.ts +75 -122
- package/src/permissions/checker.ts +44 -560
- package/src/permissions/confirmation-guardian-request.test.ts +28 -3
- package/src/permissions/confirmation-guardian-request.ts +5 -1
- package/src/persistence/conversation-crud.ts +23 -20
- package/src/persistence/conversation-queries.ts +14 -1
- package/src/persistence/conversation-types.ts +22 -0
- package/src/persistence/delivery-crud.ts +122 -15
- package/src/plugin-api/__tests__/import-graph-partial-mock.test.ts +67 -0
- package/src/plugin-api/conversation-turn.ts +27 -0
- package/src/plugin-api/credential-scope.test.ts +15 -0
- package/src/plugin-api/credential-scope.ts +14 -0
- package/src/plugin-api/index.ts +19 -1
- package/src/plugin-api/resolve-credential.ts +15 -10
- package/src/plugin-api/store-credential.ts +148 -0
- package/src/plugin-api/system-card.ts +36 -0
- package/src/plugin-api/vision-support.test.ts +82 -0
- package/src/plugin-api/vision-support.ts +14 -2
- package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +361 -110
- package/src/plugins/defaults/image-fallback/__tests__/vision-recovery.test.ts +4 -0
- package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +58 -0
- package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +64 -23
- package/src/plugins/defaults/image-recovery/detect.ts +24 -1
- package/src/plugins/defaults/main.ts +15 -0
- package/src/plugins/defaults/memory/AGENTS.md +11 -3
- package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +0 -1
- package/src/plugins/defaults/memory/graph-topology/pending-buffer.ts +9 -12
- package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +1 -1
- package/src/plugins/defaults/memory/src/memory-v2-routes.ts +12 -10
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +114 -0
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +8 -0
- package/src/plugins/defaults/memory/substrate/__tests__/edge-index.test.ts +0 -30
- package/src/plugins/defaults/memory/substrate/__tests__/ingest.test.ts +73 -1
- package/src/plugins/defaults/memory/substrate/__tests__/page-index.test.ts +37 -0
- package/src/plugins/defaults/memory/substrate/__tests__/page-links.test.ts +208 -0
- package/src/plugins/defaults/memory/substrate/__tests__/prompts-consolidation.test.ts +158 -1
- package/src/plugins/defaults/memory/substrate/__tests__/static-context.test.ts +1 -61
- package/src/plugins/defaults/memory/substrate/consolidation-job.ts +68 -6
- package/src/plugins/defaults/memory/substrate/edge-index.ts +0 -31
- package/src/plugins/defaults/memory/substrate/ingest.ts +59 -0
- package/src/plugins/defaults/memory/substrate/page-index.ts +35 -8
- package/src/plugins/defaults/memory/substrate/page-links.ts +133 -0
- package/src/plugins/defaults/memory/substrate/page-store.ts +10 -0
- package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +124 -34
- package/src/plugins/defaults/memory/substrate/static-context.ts +0 -29
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +3 -1
- package/src/plugins/defaults/memory/v3/card.ts +9 -9
- package/src/plugins/defaults/memory/v3/core-set.test.ts +7 -0
- package/src/plugins/defaults/memory/v3/core-set.ts +5 -1
- package/src/plugins/defaults/memory/v3/edge.ts +13 -57
- package/src/plugins/mtime-cache.ts +1 -1
- package/src/plugins/pipeline.ts +14 -7
- package/src/plugins/plugin-execution-context.ts +35 -11
- package/src/plugins/plugin-tree-walk.ts +63 -4
- package/src/providers/anthropic/__tests__/pause-turn-continuation.test.ts +170 -0
- package/src/providers/anthropic/client.ts +332 -241
- package/src/providers/connection-resolution.ts +2 -1
- package/src/providers/content-blocks.ts +22 -0
- package/src/providers/gemini/client.ts +5 -2
- package/src/providers/inference/__tests__/adapter-factory-litellm.test.ts +47 -1
- package/src/providers/inference/__tests__/adapter-factory-ollama.test.ts +83 -0
- package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +98 -1
- package/src/providers/inference/__tests__/base-url-route-validation.test.ts +38 -0
- package/src/providers/inference/__tests__/base-url-security.test.ts +10 -0
- package/src/providers/inference/__tests__/connections-ollama.test.ts +90 -0
- package/src/providers/inference/__tests__/missing-credential-guard.test.ts +117 -0
- package/src/providers/inference/adapter-factory.ts +33 -2
- package/src/providers/inference/auth.ts +13 -2
- package/src/providers/inference/credential-usage.ts +37 -0
- package/src/providers/inference/missing-credential-guard.ts +110 -0
- package/src/providers/inference/resolve-auth.ts +7 -7
- package/src/providers/media-resolve.ts +176 -15
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +576 -46
- package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +125 -2
- package/src/providers/openai/chat-completions-provider.ts +322 -42
- package/src/routes/route-host-protocol.ts +7 -0
- package/src/routes/worker.ts +31 -10
- package/src/runtime/AGENTS.md +35 -0
- package/src/runtime/__tests__/runtime-http-port-collision.test.ts +131 -0
- package/src/runtime/__tests__/web-presence.test.ts +234 -0
- package/src/runtime/assistant-event-hub.ts +38 -0
- package/src/runtime/channel-reply-delivery.ts +54 -31
- package/src/runtime/channel-retry-sweep.ts +6 -4
- package/src/runtime/effective-capabilities.test.ts +19 -0
- package/src/runtime/effective-capabilities.ts +25 -0
- package/src/runtime/guardian-reply-router.ts +6 -2
- package/src/runtime/http-errors.ts +1 -0
- package/src/runtime/http-server.ts +20 -5
- package/src/runtime/routes/__tests__/client-routes.test.ts +150 -0
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +133 -0
- package/src/runtime/routes/__tests__/credential-delete-in-use.test.ts +155 -0
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +52 -1
- package/src/runtime/routes/__tests__/monitoring-routes.test.ts +1 -0
- package/src/runtime/routes/__tests__/pending-interactions-route.test.ts +175 -0
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +88 -35
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +55 -6
- package/src/runtime/routes/__tests__/system-card-turn-termination.test.ts +107 -0
- package/src/runtime/routes/__tests__/user-route-dispatcher-host.test.ts +52 -1
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +75 -4
- package/src/runtime/routes/approval-routes.ts +37 -4
- package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +16 -22
- package/src/runtime/routes/canned-message-complete.ts +68 -29
- package/src/runtime/routes/client-routes.ts +113 -0
- package/src/runtime/routes/conversation-list-routes.ts +22 -7
- package/src/runtime/routes/conversation-management-routes.ts +15 -9
- package/src/runtime/routes/conversation-routes.ts +36 -22
- package/src/runtime/routes/credential-in-use.ts +85 -0
- package/src/runtime/routes/credential-routes.ts +55 -85
- package/src/runtime/routes/guardian-approval-interception.ts +22 -22
- package/src/runtime/routes/guardian-approval-reply-helpers.ts +14 -12
- package/src/runtime/routes/identity-routes.ts +5 -121
- package/src/runtime/routes/inbound-message-handler.ts +71 -27
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +16 -17
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +77 -50
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +61 -97
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +101 -77
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +17 -8
- package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +91 -7
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +72 -70
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/inference-provider-connection-routes.ts +9 -7
- package/src/runtime/routes/monitoring-routes.ts +9 -1
- package/src/runtime/routes/plugins-routes.ts +17 -5
- package/src/runtime/routes/resource-pressure-routes.ts +22 -0
- package/src/runtime/routes/secret-routes.ts +39 -8
- package/src/runtime/routes/settings-routes.ts +6 -6
- package/src/runtime/routes/surface-action-routes.ts +20 -19
- package/src/runtime/routes/user-route-dispatcher.ts +31 -2
- package/src/runtime/routes/user-route-resolution.ts +18 -1
- package/src/runtime/slack-reply-session.test.ts +23 -13
- package/src/runtime/slack-reply-session.ts +38 -55
- package/src/runtime/web-presence.ts +90 -0
- package/src/skills/validate-input.ts +177 -40
- package/src/subagent/__tests__/consult-context-gating.test.ts +6 -10
- package/src/subagent/consult-context.ts +7 -23
- package/src/tools/credentials/broker.ts +24 -79
- package/src/tools/credentials/ref-parse.ts +35 -0
- package/src/tools/credentials/resolve.ts +4 -10
- package/src/tools/credentials/store.ts +168 -0
- package/src/tools/credentials/tool-policy.ts +67 -0
- package/src/tools/executor.ts +53 -15
- package/src/tools/network/__tests__/web-search.test.ts +41 -1
- package/src/tools/network/url-safety.ts +9 -16
- package/src/tools/network/web-search.ts +21 -3
- package/src/tools/permission-checker.ts +43 -52
- package/src/tools/schema-transforms.ts +40 -5
- package/src/tools/shared/input-repairs.ts +160 -0
- package/src/tools/skills/skill-tool-factory.ts +18 -4
- package/src/tools/subagent/spawn.ts +0 -1
- package/src/tools/tool-approval-handler.ts +16 -10
- package/src/tools/tool-types.ts +26 -13
- package/src/tools/types.ts +6 -6
- package/src/util/__tests__/cgroup-memory.test.ts +3 -0
- package/src/util/cgroup-memory.ts +3 -0
- package/src/util/container-cpu-sampler.ts +250 -0
- package/src/util/image-conversion.ts +178 -14
- package/src/util/platform.ts +93 -10
- package/src/permissions/ipc-risk-types.ts +0 -143
- package/src/permissions/risk-types.ts +0 -76
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory injection indexes CLI help via {@link buildCliCommandHelpContent}.
|
|
3
|
+
* Setup prompts like "Setup <app> for me" should retrieve `plugins search` /
|
|
4
|
+
* `skills search` before the model falls back to web search.
|
|
5
|
+
*
|
|
6
|
+
* These assertions lock the retrieval phrases in the indexed blob so a later
|
|
7
|
+
* wording change cannot silently drop them.
|
|
8
|
+
*/
|
|
9
|
+
import { describe, expect, test } from "bun:test";
|
|
10
|
+
|
|
11
|
+
import { buildCliCommandHelpContent } from "../../plugins/defaults/memory/substrate/cli-command-content.js";
|
|
12
|
+
import { pluginsHelp } from "../commands/plugins.help.js";
|
|
13
|
+
import { skillsHelp } from "../commands/skills.help.js";
|
|
14
|
+
|
|
15
|
+
function searchHelp(
|
|
16
|
+
help: { subcommands?: Array<{ name: string; helpText?: string }> },
|
|
17
|
+
): { name: string; helpText?: string } {
|
|
18
|
+
const search = help.subcommands?.find((sub) => sub.name === "search");
|
|
19
|
+
if (!search) {
|
|
20
|
+
throw new Error("expected a search subcommand");
|
|
21
|
+
}
|
|
22
|
+
return search;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe("catalog search help for setup-intent retrieval", () => {
|
|
26
|
+
test("plugins help indexes setup, superpowers, and web-search fallback", () => {
|
|
27
|
+
const indexed = buildCliCommandHelpContent(pluginsHelp);
|
|
28
|
+
const search = searchHelp(pluginsHelp);
|
|
29
|
+
|
|
30
|
+
expect(search.helpText).toBeDefined();
|
|
31
|
+
expect(indexed.toLowerCase()).toContain("superpowers");
|
|
32
|
+
expect(indexed).toContain("Setup <app> for me");
|
|
33
|
+
expect(indexed.toLowerCase()).toContain("before searching the web");
|
|
34
|
+
expect(indexed).toContain("assistant plugins search");
|
|
35
|
+
expect(indexed).toContain("assistant skills search");
|
|
36
|
+
expect(indexed.toLowerCase()).not.toContain("empty query");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("skills help indexes setup as the second stop before web search", () => {
|
|
40
|
+
const indexed = buildCliCommandHelpContent(skillsHelp);
|
|
41
|
+
const search = searchHelp(skillsHelp);
|
|
42
|
+
|
|
43
|
+
expect(search.helpText).toBeDefined();
|
|
44
|
+
expect(indexed).toContain("Second stop after plugin search");
|
|
45
|
+
expect(indexed).toContain("Setup <app> for me");
|
|
46
|
+
expect(indexed.toLowerCase()).toContain("before searching the web");
|
|
47
|
+
expect(indexed).toContain("assistant skills search");
|
|
48
|
+
expect(indexed).toContain("try web search");
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -13,10 +13,12 @@
|
|
|
13
13
|
* - `revoke` with no flags (channel: undefined)
|
|
14
14
|
* - `revoke --channel phone` sends channel param
|
|
15
15
|
* - `create --channel fax` → invalid channel, exits with code 1, IPC not called
|
|
16
|
+
* - the accepted `--channel` set is the canonical CHANNEL_IDS vocabulary
|
|
16
17
|
*/
|
|
17
18
|
|
|
18
19
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
19
20
|
|
|
21
|
+
import { CHANNEL_IDS } from "@vellumai/service-contracts/channels";
|
|
20
22
|
import { Command } from "commander";
|
|
21
23
|
|
|
22
24
|
// ---------------------------------------------------------------------------
|
|
@@ -242,6 +244,35 @@ describe("channel-verification-sessions create", () => {
|
|
|
242
244
|
expect(lastIpcCall).toBeNull();
|
|
243
245
|
});
|
|
244
246
|
|
|
247
|
+
test("--channel discord reaches IPC (canonical vocabulary, not a local copy)", async () => {
|
|
248
|
+
const { exitCode } = await runCommand([
|
|
249
|
+
"channel-verification-sessions",
|
|
250
|
+
"create",
|
|
251
|
+
"--channel",
|
|
252
|
+
"discord",
|
|
253
|
+
]);
|
|
254
|
+
|
|
255
|
+
expect(exitCode).toBe(0);
|
|
256
|
+
expect(lastIpcCall!.method).toBe("channel_verification_sessions_create");
|
|
257
|
+
expect(lastIpcCall!.params).toMatchObject({
|
|
258
|
+
body: { channel: "discord", purpose: "guardian" },
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test("every canonical channel id is accepted", async () => {
|
|
263
|
+
for (const channel of CHANNEL_IDS) {
|
|
264
|
+
const { exitCode } = await runCommand([
|
|
265
|
+
"channel-verification-sessions",
|
|
266
|
+
"create",
|
|
267
|
+
"--channel",
|
|
268
|
+
channel,
|
|
269
|
+
]);
|
|
270
|
+
|
|
271
|
+
expect(exitCode).toBe(0);
|
|
272
|
+
expect(lastIpcCall!.params).toMatchObject({ body: { channel } });
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
|
|
245
276
|
test("IPC error results in exit code 1", async () => {
|
|
246
277
|
mockIpcResult = { ok: false, error: "Could not connect" };
|
|
247
278
|
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for `assistant conversations search`.
|
|
3
|
+
*
|
|
4
|
+
* Validates IPC plumbing onto `conversations_search`, human vs `--json`
|
|
5
|
+
* output, empty-result copy, and validation of the search term / --limit.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as nodeFs from "node:fs";
|
|
9
|
+
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
10
|
+
|
|
11
|
+
import { Command } from "commander";
|
|
12
|
+
|
|
13
|
+
let lastIpcCall: { method: string; params?: Record<string, unknown> } | null =
|
|
14
|
+
null;
|
|
15
|
+
|
|
16
|
+
let mockIpcResult: {
|
|
17
|
+
ok: boolean;
|
|
18
|
+
result?: unknown;
|
|
19
|
+
error?: string;
|
|
20
|
+
statusCode?: number;
|
|
21
|
+
} = { ok: true, result: { query: "flux", results: [] } };
|
|
22
|
+
|
|
23
|
+
mock.module("../../../ipc/cli-client.js", () => ({
|
|
24
|
+
cliIpcCall: async (method: string, params?: Record<string, unknown>) => {
|
|
25
|
+
lastIpcCall = { method, params };
|
|
26
|
+
return mockIpcResult;
|
|
27
|
+
},
|
|
28
|
+
exitCodeFromIpcResult: (r: { statusCode?: number }) =>
|
|
29
|
+
r.statusCode === undefined
|
|
30
|
+
? 10
|
|
31
|
+
: r.statusCode >= 500
|
|
32
|
+
? 3
|
|
33
|
+
: r.statusCode >= 400
|
|
34
|
+
? 2
|
|
35
|
+
: 1,
|
|
36
|
+
exitFromIpcResult: () => {
|
|
37
|
+
process.exitCode = 1;
|
|
38
|
+
},
|
|
39
|
+
}));
|
|
40
|
+
|
|
41
|
+
const fakeLogger = {
|
|
42
|
+
info: () => {},
|
|
43
|
+
warn: () => {},
|
|
44
|
+
error: () => {},
|
|
45
|
+
debug: () => {},
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
mock.module("../../../util/logger.js", () => ({
|
|
49
|
+
getLogger: () => fakeLogger,
|
|
50
|
+
getCliLogger: () => fakeLogger,
|
|
51
|
+
initLogger: () => {},
|
|
52
|
+
truncateForLog: (v: string) => v,
|
|
53
|
+
pruneOldLogFiles: () => 0,
|
|
54
|
+
LOG_FILE_PATTERN: /^assistant-(\d{4}-\d{2}-\d{2})\.log$/,
|
|
55
|
+
getCurrentLogFilePath: () => "/tmp/test-assistant.log",
|
|
56
|
+
}));
|
|
57
|
+
|
|
58
|
+
const realFs = { ...nodeFs };
|
|
59
|
+
mock.module("node:fs", () => ({ ...realFs }));
|
|
60
|
+
|
|
61
|
+
const { registerConversationsCommand } = await import("../conversations.js");
|
|
62
|
+
|
|
63
|
+
const THREE_HOURS_AGO = Date.now() - 3 * 60 * 60 * 1000;
|
|
64
|
+
|
|
65
|
+
const matchingResult = {
|
|
66
|
+
conversationId: "conv-xyz",
|
|
67
|
+
conversationTitle: "Quarterly metrics",
|
|
68
|
+
conversationUpdatedAt: THREE_HOURS_AGO,
|
|
69
|
+
matchingMessages: [
|
|
70
|
+
{
|
|
71
|
+
messageId: "msg-1",
|
|
72
|
+
role: "user",
|
|
73
|
+
excerpt: "let's review the flux capacitor numbers",
|
|
74
|
+
createdAt: THREE_HOURS_AGO,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
messageId: "msg-2",
|
|
78
|
+
role: "assistant",
|
|
79
|
+
excerpt: "the Q3 flux capacitor spend is on track",
|
|
80
|
+
createdAt: THREE_HOURS_AGO,
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
async function runSearch(
|
|
86
|
+
args: string[],
|
|
87
|
+
): Promise<{ code: number; stdout: string; stderr: string }> {
|
|
88
|
+
const stdoutChunks: string[] = [];
|
|
89
|
+
const stderrChunks: string[] = [];
|
|
90
|
+
const originalStdoutWrite = process.stdout.write.bind(process.stdout);
|
|
91
|
+
const originalStderrWrite = process.stderr.write.bind(process.stderr);
|
|
92
|
+
process.stdout.write = ((chunk: unknown) => {
|
|
93
|
+
stdoutChunks.push(typeof chunk === "string" ? chunk : String(chunk));
|
|
94
|
+
return true;
|
|
95
|
+
}) as typeof process.stdout.write;
|
|
96
|
+
process.stderr.write = ((chunk: unknown) => {
|
|
97
|
+
stderrChunks.push(typeof chunk === "string" ? chunk : String(chunk));
|
|
98
|
+
return true;
|
|
99
|
+
}) as typeof process.stderr.write;
|
|
100
|
+
|
|
101
|
+
const program = new Command();
|
|
102
|
+
program.exitOverride();
|
|
103
|
+
program.configureOutput({
|
|
104
|
+
writeErr: (s) => {
|
|
105
|
+
stderrChunks.push(s);
|
|
106
|
+
},
|
|
107
|
+
writeOut: (s) => {
|
|
108
|
+
stdoutChunks.push(s);
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
registerConversationsCommand(program);
|
|
112
|
+
try {
|
|
113
|
+
await program.parseAsync(["node", "assistant", "conversations", ...args]);
|
|
114
|
+
} catch (err) {
|
|
115
|
+
const isHelp =
|
|
116
|
+
typeof err === "object" &&
|
|
117
|
+
err !== null &&
|
|
118
|
+
"code" in err &&
|
|
119
|
+
(err as { code?: string }).code === "commander.helpDisplayed";
|
|
120
|
+
if (!isHelp && (process.exitCode === 0 || process.exitCode === undefined)) {
|
|
121
|
+
process.exitCode = 1;
|
|
122
|
+
}
|
|
123
|
+
if (
|
|
124
|
+
!isHelp &&
|
|
125
|
+
err instanceof Error &&
|
|
126
|
+
!stderrChunks.some((s) => s.includes(err.message))
|
|
127
|
+
) {
|
|
128
|
+
stderrChunks.push(err.message);
|
|
129
|
+
}
|
|
130
|
+
} finally {
|
|
131
|
+
process.stdout.write = originalStdoutWrite;
|
|
132
|
+
process.stderr.write = originalStderrWrite;
|
|
133
|
+
}
|
|
134
|
+
const code = Number(process.exitCode ?? 0);
|
|
135
|
+
process.exitCode = 0;
|
|
136
|
+
return {
|
|
137
|
+
code,
|
|
138
|
+
stdout: stdoutChunks.join(""),
|
|
139
|
+
stderr: stderrChunks.join(""),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
beforeEach(() => {
|
|
144
|
+
lastIpcCall = null;
|
|
145
|
+
process.exitCode = 0;
|
|
146
|
+
mockIpcResult = { ok: true, result: { query: "flux", results: [] } };
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
afterEach(() => {
|
|
150
|
+
process.exitCode = 0;
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe("conversations search", () => {
|
|
154
|
+
test("forwards the term to conversations_search as queryParams.q", async () => {
|
|
155
|
+
mockIpcResult = {
|
|
156
|
+
ok: true,
|
|
157
|
+
result: { query: "flux capacitor", results: [matchingResult] },
|
|
158
|
+
};
|
|
159
|
+
const { code } = await runSearch(["search", "flux capacitor"]);
|
|
160
|
+
expect(code).toBe(0);
|
|
161
|
+
expect(lastIpcCall?.method).toBe("conversations_search");
|
|
162
|
+
expect(lastIpcCall?.params).toEqual({
|
|
163
|
+
queryParams: { q: "flux capacitor" },
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("passes --limit as a query param string", async () => {
|
|
168
|
+
mockIpcResult = {
|
|
169
|
+
ok: true,
|
|
170
|
+
result: { query: "flux", results: [] },
|
|
171
|
+
};
|
|
172
|
+
const { code } = await runSearch(["search", "flux", "--limit", "5"]);
|
|
173
|
+
expect(code).toBe(0);
|
|
174
|
+
expect(lastIpcCall?.params).toEqual({
|
|
175
|
+
queryParams: { q: "flux", limit: "5" },
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("prints conversation rows and excerpts in human mode", async () => {
|
|
180
|
+
mockIpcResult = {
|
|
181
|
+
ok: true,
|
|
182
|
+
result: { query: "flux", results: [matchingResult] },
|
|
183
|
+
};
|
|
184
|
+
const { code, stdout } = await runSearch(["search", "flux"]);
|
|
185
|
+
expect(code).toBe(0);
|
|
186
|
+
expect(stdout).toContain("conv-xyz");
|
|
187
|
+
expect(stdout).toContain("Quarterly metrics");
|
|
188
|
+
expect(stdout).toContain("3h ago");
|
|
189
|
+
expect(stdout).toContain("user let's review the flux capacitor numbers");
|
|
190
|
+
expect(stdout).toContain(
|
|
191
|
+
"assistant the Q3 flux capacitor spend is on track",
|
|
192
|
+
);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test("prints Untitled when the conversation title is null", async () => {
|
|
196
|
+
mockIpcResult = {
|
|
197
|
+
ok: true,
|
|
198
|
+
result: {
|
|
199
|
+
query: "flux",
|
|
200
|
+
results: [
|
|
201
|
+
{
|
|
202
|
+
...matchingResult,
|
|
203
|
+
conversationTitle: null,
|
|
204
|
+
matchingMessages: [],
|
|
205
|
+
},
|
|
206
|
+
],
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
const { code, stdout } = await runSearch(["search", "flux"]);
|
|
210
|
+
expect(code).toBe(0);
|
|
211
|
+
expect(stdout).toContain("Untitled");
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test("prints a no-match line when the daemon returns no results", async () => {
|
|
215
|
+
mockIpcResult = {
|
|
216
|
+
ok: true,
|
|
217
|
+
result: { query: "no-such-term", results: [] },
|
|
218
|
+
};
|
|
219
|
+
const { code, stdout } = await runSearch(["search", "no-such-term"]);
|
|
220
|
+
expect(code).toBe(0);
|
|
221
|
+
expect(lastIpcCall?.method).toBe("conversations_search");
|
|
222
|
+
expect(stdout).toBe('No conversations matched "no-such-term"\n');
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test("--json emits the daemon payload", async () => {
|
|
226
|
+
mockIpcResult = {
|
|
227
|
+
ok: true,
|
|
228
|
+
result: { query: "flux", results: [matchingResult] },
|
|
229
|
+
};
|
|
230
|
+
const { code, stdout } = await runSearch(["search", "flux", "--json"]);
|
|
231
|
+
expect(code).toBe(0);
|
|
232
|
+
expect(JSON.parse(stdout)).toEqual({
|
|
233
|
+
query: "flux",
|
|
234
|
+
results: [matchingResult],
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test("--json reports IPC failure as { ok: false } and exits non-zero", async () => {
|
|
239
|
+
mockIpcResult = { ok: false, error: "assistant not running" };
|
|
240
|
+
const { code, stdout } = await runSearch(["search", "flux", "--json"]);
|
|
241
|
+
expect(code).toBe(10);
|
|
242
|
+
expect(JSON.parse(stdout)).toEqual({
|
|
243
|
+
ok: false,
|
|
244
|
+
error: "assistant not running",
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
test("rejects a whitespace-only term before IPC", async () => {
|
|
249
|
+
const { code, stderr } = await runSearch(["search", " "]);
|
|
250
|
+
expect(code).toBe(1);
|
|
251
|
+
expect(lastIpcCall).toBeNull();
|
|
252
|
+
expect(stderr).toContain("Search term must be a non-empty string");
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
test("--json rejects a whitespace-only term as { ok: false }", async () => {
|
|
256
|
+
const { code, stdout } = await runSearch(["search", " ", "--json"]);
|
|
257
|
+
expect(code).toBe(1);
|
|
258
|
+
expect(lastIpcCall).toBeNull();
|
|
259
|
+
expect(JSON.parse(stdout)).toMatchObject({
|
|
260
|
+
ok: false,
|
|
261
|
+
error: expect.stringContaining("Search term must be a non-empty string"),
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
test("rejects a non-numeric --limit before IPC", async () => {
|
|
266
|
+
const { code, stderr } = await runSearch([
|
|
267
|
+
"search",
|
|
268
|
+
"flux",
|
|
269
|
+
"--limit",
|
|
270
|
+
"all",
|
|
271
|
+
]);
|
|
272
|
+
expect(code).toBe(1);
|
|
273
|
+
expect(lastIpcCall).toBeNull();
|
|
274
|
+
expect(stderr).toContain("Invalid --limit value");
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
test("search --help documents the term, --limit, and examples", async () => {
|
|
278
|
+
const { code, stdout } = await runSearch(["search", "--help"]);
|
|
279
|
+
expect(code).toBe(0);
|
|
280
|
+
expect(stdout).toContain(
|
|
281
|
+
"Search conversations by title and message content",
|
|
282
|
+
);
|
|
283
|
+
expect(stdout).toContain("--limit");
|
|
284
|
+
expect(stdout).toContain(
|
|
285
|
+
'assistant conversations search "project planning"',
|
|
286
|
+
);
|
|
287
|
+
expect(stdout).toContain("--json");
|
|
288
|
+
});
|
|
289
|
+
});
|
|
@@ -98,6 +98,7 @@ mock.module("../../../ipc/gateway-client.js", () => ({
|
|
|
98
98
|
}));
|
|
99
99
|
|
|
100
100
|
mock.module("../../../messaging/providers/slack/send.js", () => ({
|
|
101
|
+
updateSlackMessage: async () => ({ ok: true, ts: "ts" }),
|
|
101
102
|
sendSlackReply: async (
|
|
102
103
|
chatId: string,
|
|
103
104
|
text: string,
|
|
@@ -120,6 +120,24 @@ describe("providers create — derived auth", () => {
|
|
|
120
120
|
});
|
|
121
121
|
});
|
|
122
122
|
|
|
123
|
+
test("forwards optional --base-url for ollama create", async () => {
|
|
124
|
+
await run([
|
|
125
|
+
"providers",
|
|
126
|
+
"create",
|
|
127
|
+
"ollama-home",
|
|
128
|
+
"--provider",
|
|
129
|
+
"ollama",
|
|
130
|
+
"--base-url",
|
|
131
|
+
"http://192.168.1.50:11434/v1",
|
|
132
|
+
]);
|
|
133
|
+
expect(lastIpcCall()?.params?.body).toEqual({
|
|
134
|
+
name: "ollama-home",
|
|
135
|
+
provider: "ollama",
|
|
136
|
+
auth: { type: "none" },
|
|
137
|
+
base_url: "http://192.168.1.50:11434/v1",
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
123
141
|
test("rejects a keyed provider without --credential before calling the daemon", async () => {
|
|
124
142
|
const { exitCode } = await run([
|
|
125
143
|
"providers",
|
|
@@ -332,6 +350,56 @@ describe("providers update", () => {
|
|
|
332
350
|
base_url: "http://localhost:5678/v1",
|
|
333
351
|
});
|
|
334
352
|
});
|
|
353
|
+
|
|
354
|
+
test("ollama update forwards --base-url after GET", async () => {
|
|
355
|
+
mockIpcResult = {
|
|
356
|
+
ok: true,
|
|
357
|
+
result: {
|
|
358
|
+
...CONNECTION_RESULT,
|
|
359
|
+
name: "ollama",
|
|
360
|
+
provider: "ollama",
|
|
361
|
+
auth: { type: "none" },
|
|
362
|
+
},
|
|
363
|
+
};
|
|
364
|
+
await run([
|
|
365
|
+
"providers",
|
|
366
|
+
"update",
|
|
367
|
+
"ollama",
|
|
368
|
+
"--base-url",
|
|
369
|
+
"http://192.168.1.50:11434/v1",
|
|
370
|
+
]);
|
|
371
|
+
expect(ipcCalls.map((c) => c.method)).toEqual([
|
|
372
|
+
"inference_provider_connections_get",
|
|
373
|
+
"inference_provider_connections_update",
|
|
374
|
+
]);
|
|
375
|
+
expect(lastIpcCall()?.params).toEqual({
|
|
376
|
+
pathParams: { name: "ollama" },
|
|
377
|
+
body: {
|
|
378
|
+
auth: { type: "none" },
|
|
379
|
+
base_url: "http://192.168.1.50:11434/v1",
|
|
380
|
+
},
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
describe("providers get output", () => {
|
|
386
|
+
test("prints base_url when the connection stores one", async () => {
|
|
387
|
+
mockIpcResult = {
|
|
388
|
+
ok: true,
|
|
389
|
+
result: {
|
|
390
|
+
name: "ollama",
|
|
391
|
+
provider: "ollama",
|
|
392
|
+
auth: { type: "none" },
|
|
393
|
+
baseUrl: "http://192.168.1.50:11434/v1",
|
|
394
|
+
createdAt: 0,
|
|
395
|
+
updatedAt: 0,
|
|
396
|
+
},
|
|
397
|
+
};
|
|
398
|
+
const { stdout } = await run(["providers", "get", "ollama"]);
|
|
399
|
+
expect(stdout).toContain("name: ollama");
|
|
400
|
+
expect(stdout).toContain("provider: ollama");
|
|
401
|
+
expect(stdout).toContain("base_url: http://192.168.1.50:11434/v1");
|
|
402
|
+
});
|
|
335
403
|
});
|
|
336
404
|
|
|
337
405
|
describe("providers list output", () => {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Tests for the keys
|
|
2
|
+
* Tests for the keys CLI subcommands: the set agent-shell guard and the
|
|
3
|
+
* delete force flag.
|
|
3
4
|
*
|
|
4
5
|
* Validates:
|
|
5
6
|
* - inline set from an agent shell (__CONVERSATION_ID) is refused with a
|
|
@@ -8,6 +9,8 @@
|
|
|
8
9
|
* - --generated bypasses the guard and stores via the daemon client
|
|
9
10
|
* - a plain user-terminal invocation (no markers) stores via the daemon
|
|
10
11
|
* client
|
|
12
|
+
* - delete forwards the caller's force choice to the daemon client
|
|
13
|
+
* - an in-use refusal points at the --force escape hatch
|
|
11
14
|
*/
|
|
12
15
|
|
|
13
16
|
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
@@ -18,7 +21,16 @@ import { Command } from "commander";
|
|
|
18
21
|
// Mock state
|
|
19
22
|
// ---------------------------------------------------------------------------
|
|
20
23
|
|
|
24
|
+
interface DeleteResultStub {
|
|
25
|
+
result: "deleted" | "not-found" | "error";
|
|
26
|
+
error?: string;
|
|
27
|
+
code?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
21
30
|
let mockSetSecureKeyViaDaemon = mock(() => Promise.resolve({ ok: true }));
|
|
31
|
+
let mockDeleteSecureKeyViaDaemon = mock(
|
|
32
|
+
(): Promise<DeleteResultStub> => Promise.resolve({ result: "deleted" }),
|
|
33
|
+
);
|
|
22
34
|
|
|
23
35
|
// ---------------------------------------------------------------------------
|
|
24
36
|
// Mocks — must be declared before importing the module under test
|
|
@@ -26,11 +38,11 @@ let mockSetSecureKeyViaDaemon = mock(() => Promise.resolve({ ok: true }));
|
|
|
26
38
|
|
|
27
39
|
mock.module("../../lib/daemon-credential-client.js", () => ({
|
|
28
40
|
setSecureKeyViaDaemon: mockSetSecureKeyViaDaemon,
|
|
29
|
-
deleteSecureKeyViaDaemon:
|
|
30
|
-
Promise.resolve({ result: "deleted" as const }),
|
|
31
|
-
),
|
|
41
|
+
deleteSecureKeyViaDaemon: mockDeleteSecureKeyViaDaemon,
|
|
32
42
|
}));
|
|
33
43
|
|
|
44
|
+
const loggedErrors: string[] = [];
|
|
45
|
+
|
|
34
46
|
mock.module("../../../util/logger.js", () => ({
|
|
35
47
|
getLogger: () => ({
|
|
36
48
|
info: () => {},
|
|
@@ -41,7 +53,9 @@ mock.module("../../../util/logger.js", () => ({
|
|
|
41
53
|
getCliLogger: () => ({
|
|
42
54
|
info: () => {},
|
|
43
55
|
warn: () => {},
|
|
44
|
-
error: () => {
|
|
56
|
+
error: (message: unknown) => {
|
|
57
|
+
loggedErrors.push(String(message));
|
|
58
|
+
},
|
|
45
59
|
debug: () => {},
|
|
46
60
|
}),
|
|
47
61
|
}));
|
|
@@ -55,6 +69,10 @@ let savedSkillContextJson: string | undefined;
|
|
|
55
69
|
|
|
56
70
|
beforeEach(() => {
|
|
57
71
|
mockSetSecureKeyViaDaemon = mock(() => Promise.resolve({ ok: true }));
|
|
72
|
+
mockDeleteSecureKeyViaDaemon = mock(
|
|
73
|
+
(): Promise<DeleteResultStub> => Promise.resolve({ result: "deleted" }),
|
|
74
|
+
);
|
|
75
|
+
loggedErrors.length = 0;
|
|
58
76
|
savedConversationId = process.env.__CONVERSATION_ID;
|
|
59
77
|
savedSkillContextJson = process.env.__SKILL_CONTEXT_JSON;
|
|
60
78
|
delete process.env.__CONVERSATION_ID;
|
|
@@ -83,9 +101,7 @@ afterEach(() => {
|
|
|
83
101
|
async function runKeysCommand(...args: string[]) {
|
|
84
102
|
mock.module("../../lib/daemon-credential-client.js", () => ({
|
|
85
103
|
setSecureKeyViaDaemon: mockSetSecureKeyViaDaemon,
|
|
86
|
-
deleteSecureKeyViaDaemon:
|
|
87
|
-
Promise.resolve({ result: "deleted" as const }),
|
|
88
|
-
),
|
|
104
|
+
deleteSecureKeyViaDaemon: mockDeleteSecureKeyViaDaemon,
|
|
89
105
|
}));
|
|
90
106
|
|
|
91
107
|
const { registerKeysCommand } = await import("../keys.js");
|
|
@@ -187,3 +203,68 @@ describe("assistant keys set (agent-shell guard)", () => {
|
|
|
187
203
|
expect(process.exitCode).toBe(0);
|
|
188
204
|
});
|
|
189
205
|
});
|
|
206
|
+
|
|
207
|
+
// ---------------------------------------------------------------------------
|
|
208
|
+
// delete — in-use guard and --force
|
|
209
|
+
// ---------------------------------------------------------------------------
|
|
210
|
+
|
|
211
|
+
describe("assistant keys delete (in-use guard)", () => {
|
|
212
|
+
test("deletes without force by default", async () => {
|
|
213
|
+
// GIVEN a stored key no connection depends on
|
|
214
|
+
// WHEN the key is deleted without --force
|
|
215
|
+
await runKeysCommand("keys", "delete", "acme");
|
|
216
|
+
|
|
217
|
+
// THEN the daemon is asked for a non-forced delete
|
|
218
|
+
expect(mockDeleteSecureKeyViaDaemon).toHaveBeenCalledTimes(1);
|
|
219
|
+
const [type, provider, force] = mockDeleteSecureKeyViaDaemon.mock
|
|
220
|
+
.calls[0] as unknown as [string, string, boolean];
|
|
221
|
+
expect(type).toBe("api_key");
|
|
222
|
+
expect(provider).toBe("acme");
|
|
223
|
+
expect(force).toBe(false);
|
|
224
|
+
expect(process.exitCode).toBe(0);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test("--force carries explicit intent to the daemon", async () => {
|
|
228
|
+
// GIVEN a caller who accepts breaking dependent connections
|
|
229
|
+
// WHEN --force is passed
|
|
230
|
+
await runKeysCommand("keys", "delete", "acme", "--force");
|
|
231
|
+
|
|
232
|
+
// THEN the daemon receives the force flag
|
|
233
|
+
const [, , force] = mockDeleteSecureKeyViaDaemon.mock
|
|
234
|
+
.calls[0] as unknown as [string, string, boolean];
|
|
235
|
+
expect(force).toBe(true);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test("names dependent connections and points at --force when refused", async () => {
|
|
239
|
+
// GIVEN a key an LLM provider connection resolves its auth through
|
|
240
|
+
mockDeleteSecureKeyViaDaemon = mock(
|
|
241
|
+
(): Promise<DeleteResultStub> =>
|
|
242
|
+
Promise.resolve({
|
|
243
|
+
result: "error",
|
|
244
|
+
code: "CREDENTIAL_IN_USE",
|
|
245
|
+
error:
|
|
246
|
+
'Credential credential/acme/api_key is in use by connection "acme-router".',
|
|
247
|
+
}),
|
|
248
|
+
);
|
|
249
|
+
const exitCalls: number[] = [];
|
|
250
|
+
const origExit = process.exit;
|
|
251
|
+
process.exit = ((code?: number) => {
|
|
252
|
+
exitCalls.push(code ?? 0);
|
|
253
|
+
throw new Error("__exit__");
|
|
254
|
+
}) as typeof process.exit;
|
|
255
|
+
|
|
256
|
+
// WHEN the delete is attempted without --force
|
|
257
|
+
try {
|
|
258
|
+
await runKeysCommand("keys", "delete", "acme");
|
|
259
|
+
} catch (err) {
|
|
260
|
+
expect((err as Error).message).toBe("__exit__");
|
|
261
|
+
} finally {
|
|
262
|
+
process.exit = origExit;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// THEN the refusal names the connection and suggests the escape hatch
|
|
266
|
+
expect(exitCalls).toEqual([1]);
|
|
267
|
+
expect(loggedErrors.join("\n")).toContain('connection "acme-router"');
|
|
268
|
+
expect(loggedErrors.join("\n")).toContain("keys delete acme --force");
|
|
269
|
+
});
|
|
270
|
+
});
|