@vellumai/assistant 0.11.4 → 0.11.5-staging.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +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__/config-loader-backfill.test.ts +19 -10
- 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__/managed-profile-guard.test.ts +4 -2
- 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/__tests__/voice-scoped-grant-consumer.test.ts +2 -0
- package/src/__tests__/voice-session-bridge.test.ts +42 -0
- 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/calls/__tests__/voice-session-bridge.test.ts +114 -0
- package/src/calls/voice-session-bridge.ts +60 -6
- 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/__tests__/balanced-model-experiment.test.ts +7 -7
- package/src/config/__tests__/default-profile-catalog.test.ts +3 -3
- package/src/config/default-profile-catalog.ts +5 -6
- package/src/config/feature-flag-registry.json +32 -32
- 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
|
@@ -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
|
+
});
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
* - under --json the consent listing, the prompt, and the cancellation line
|
|
17
17
|
* all go to stderr, leaving stdout a pure JSON document
|
|
18
18
|
* - inspect renders the schedules surface block
|
|
19
|
+
* - install prints a setup-skill hint when `skills/setup` or
|
|
20
|
+
* `skills/<name>-setup` is present on the installed tree
|
|
19
21
|
*
|
|
20
22
|
* The installers are replaced by fakes that stage a fixture tree and run the
|
|
21
23
|
* real `confirmStagedOrAbort` gate, so the command's consent callback is
|
|
@@ -76,6 +78,10 @@ let ipcResults: Array<{
|
|
|
76
78
|
|
|
77
79
|
let inspectResult: PluginInspection | null = null;
|
|
78
80
|
|
|
81
|
+
/** On-disk tree returned as the fake install `target` so post-install walks work. */
|
|
82
|
+
let installTarget: string | null = null;
|
|
83
|
+
const installTargetDirs: string[] = [];
|
|
84
|
+
|
|
79
85
|
// ---------------------------------------------------------------------------
|
|
80
86
|
// Mocks
|
|
81
87
|
// ---------------------------------------------------------------------------
|
|
@@ -123,7 +129,7 @@ async function runFakeInstall(
|
|
|
123
129
|
await stageAndConfirm(name, deps?.confirmStaged);
|
|
124
130
|
return {
|
|
125
131
|
name,
|
|
126
|
-
target: `/plugins/${name}`,
|
|
132
|
+
target: installTarget ?? `/plugins/${name}`,
|
|
127
133
|
fileCount: 3,
|
|
128
134
|
ref: "main",
|
|
129
135
|
commit: "a".repeat(40),
|
|
@@ -253,6 +259,7 @@ beforeEach(() => {
|
|
|
253
259
|
upgradePluginCalls = [];
|
|
254
260
|
ipcResults = [];
|
|
255
261
|
inspectResult = null;
|
|
262
|
+
installTarget = null;
|
|
256
263
|
// Platform features on by default, so a plain name install takes the
|
|
257
264
|
// platform-tarball branch.
|
|
258
265
|
delete process.env.VELLUM_DISABLE_PLATFORM;
|
|
@@ -260,6 +267,10 @@ beforeEach(() => {
|
|
|
260
267
|
});
|
|
261
268
|
|
|
262
269
|
afterEach(() => {
|
|
270
|
+
for (const dir of installTargetDirs) {
|
|
271
|
+
rmSync(dir, { recursive: true, force: true });
|
|
272
|
+
}
|
|
273
|
+
installTargetDirs.length = 0;
|
|
263
274
|
if (savedDisablePlatform === undefined) {
|
|
264
275
|
delete process.env.VELLUM_DISABLE_PLATFORM;
|
|
265
276
|
} else {
|
|
@@ -346,6 +357,7 @@ describe("plugins install - declared-schedules consent", () => {
|
|
|
346
357
|
expect(confirmCalls.length).toBe(0);
|
|
347
358
|
expect(r.stdout).not.toContain("declares");
|
|
348
359
|
expect(r.stdout).not.toContain("schedule");
|
|
360
|
+
expect(r.stdout).not.toContain("to help set up this plugin");
|
|
349
361
|
expect(r.stdout).toContain('Installed plugin "example"');
|
|
350
362
|
expect(r.exitCode).toBe(0);
|
|
351
363
|
});
|
|
@@ -690,3 +702,47 @@ describe("plugins inspect - schedules surface", () => {
|
|
|
690
702
|
expect(r.exitCode).toBe(0);
|
|
691
703
|
});
|
|
692
704
|
});
|
|
705
|
+
|
|
706
|
+
describe("plugins install - setup skill hint", () => {
|
|
707
|
+
function writeSetupSkill(skillId: string): void {
|
|
708
|
+
const dir = mkdtempSync(join(tmpdir(), "plugins-cmd-target-"));
|
|
709
|
+
installTargetDirs.push(dir);
|
|
710
|
+
mkdirSync(join(dir, "skills", skillId), { recursive: true });
|
|
711
|
+
writeFileSync(
|
|
712
|
+
join(dir, "skills", skillId, "SKILL.md"),
|
|
713
|
+
"---\nname: setup\ndescription: Set up the plugin.\n---\n",
|
|
714
|
+
);
|
|
715
|
+
installTarget = dir;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
test("points at skills/setup after a successful install", async () => {
|
|
719
|
+
writeSetupSkill("setup");
|
|
720
|
+
|
|
721
|
+
const r = await runCommand(["plugins", "install", "example"]);
|
|
722
|
+
|
|
723
|
+
expect(r.stdout).toContain('Installed plugin "example"');
|
|
724
|
+
expect(r.stdout).toContain(
|
|
725
|
+
"Load the setup skill to help set up this plugin",
|
|
726
|
+
);
|
|
727
|
+
expect(r.exitCode).toBe(0);
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
test("points at skills/<name>-setup when that is the shipped id", async () => {
|
|
731
|
+
writeSetupSkill("imessage-setup");
|
|
732
|
+
|
|
733
|
+
const r = await runCommand(["plugins", "install", "imessage"]);
|
|
734
|
+
|
|
735
|
+
expect(r.stdout).toContain(
|
|
736
|
+
"Load the imessage-setup skill to help set up this plugin",
|
|
737
|
+
);
|
|
738
|
+
expect(r.exitCode).toBe(0);
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
test("prints no setup hint when the plugin ships no setup skill", async () => {
|
|
742
|
+
const r = await runCommand(["plugins", "install", "example"]);
|
|
743
|
+
|
|
744
|
+
expect(r.stdout).toContain('Installed plugin "example"');
|
|
745
|
+
expect(r.stdout).not.toContain("to help set up this plugin");
|
|
746
|
+
expect(r.exitCode).toBe(0);
|
|
747
|
+
});
|
|
748
|
+
});
|
|
@@ -10,15 +10,16 @@ export const channelVerificationSessionsHelp: CliCommandHelp = {
|
|
|
10
10
|
],
|
|
11
11
|
helpText: `
|
|
12
12
|
Verification sessions are used to verify guardian bindings and trusted
|
|
13
|
-
contacts across channels (telegram, phone, slack, email). Three
|
|
13
|
+
contacts across channels (telegram, phone, slack, discord, email). Three
|
|
14
|
+
flows exist:
|
|
14
15
|
|
|
15
16
|
1. Inbound challenge — the assistant generates a secret code and waits
|
|
16
17
|
for the guardian to send it back on the channel. Used when the
|
|
17
18
|
guardian can already message the assistant.
|
|
18
19
|
|
|
19
20
|
2. Outbound verification — the assistant sends a verification code to
|
|
20
|
-
a destination (Telegram handle, phone number, Slack
|
|
21
|
-
waits for confirmation. Used when bootstrapping a new channel.
|
|
21
|
+
a destination (Telegram handle, phone number, Slack or Discord user
|
|
22
|
+
ID) and waits for confirmation. Used when bootstrapping a new channel.
|
|
22
23
|
|
|
23
24
|
3. Trusted contact verification — verifies a contact channel that
|
|
24
25
|
already exists in the contact graph, sending a code to the channel
|
|
@@ -36,7 +37,7 @@ Examples:
|
|
|
36
37
|
options: [
|
|
37
38
|
{
|
|
38
39
|
flags: "--channel <channel>",
|
|
39
|
-
description: "Channel type (telegram, phone, slack, email)",
|
|
40
|
+
description: "Channel type (telegram, phone, slack, discord, email)",
|
|
40
41
|
},
|
|
41
42
|
{
|
|
42
43
|
flags: "--destination <destination>",
|
|
@@ -72,8 +73,8 @@ Routes between three creation modes based on the provided options:
|
|
|
72
73
|
|
|
73
74
|
2. Outbound: --channel <ch> --destination <dest>
|
|
74
75
|
Sends a verification code to the given destination. Supports telegram
|
|
75
|
-
(handle or chat ID), phone (E.164 number), slack (user ID),
|
|
76
|
-
Use --rebind to replace an existing guardian binding.
|
|
76
|
+
(handle or chat ID), phone (E.164 number), slack and discord (user ID),
|
|
77
|
+
and email. Use --rebind to replace an existing guardian binding.
|
|
77
78
|
|
|
78
79
|
3. Inbound: --channel <ch> (no --destination)
|
|
79
80
|
Generates a challenge secret for the guardian to send back on the
|
|
@@ -91,7 +92,8 @@ Examples:
|
|
|
91
92
|
options: [
|
|
92
93
|
{
|
|
93
94
|
flags: "--channel <channel>",
|
|
94
|
-
description:
|
|
95
|
+
description:
|
|
96
|
+
"Channel type (telegram, phone, slack, discord, email). Defaults to telegram.",
|
|
95
97
|
},
|
|
96
98
|
],
|
|
97
99
|
helpText: `
|
|
@@ -113,7 +115,7 @@ Examples:
|
|
|
113
115
|
options: [
|
|
114
116
|
{
|
|
115
117
|
flags: "--channel <channel>",
|
|
116
|
-
description: "Channel type (telegram, phone, slack, email)",
|
|
118
|
+
description: "Channel type (telegram, phone, slack, discord, email)",
|
|
117
119
|
required: true,
|
|
118
120
|
},
|
|
119
121
|
{
|
|
@@ -137,7 +139,7 @@ Examples:
|
|
|
137
139
|
options: [
|
|
138
140
|
{
|
|
139
141
|
flags: "--channel <channel>",
|
|
140
|
-
description: "Channel type (telegram, phone, slack, email)",
|
|
142
|
+
description: "Channel type (telegram, phone, slack, discord, email)",
|
|
141
143
|
required: true,
|
|
142
144
|
},
|
|
143
145
|
],
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
// `cli/no-daemon-internals` forbids hoisting `channels/types.js`, so the CLI
|
|
2
|
+
// reads the vocabulary straight from the contract package that file re-exports.
|
|
3
|
+
// Both sides then validate `--channel` against the same set.
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
CHANNEL_IDS,
|
|
7
|
+
type ChannelId,
|
|
8
|
+
isChannelId,
|
|
9
|
+
} from "@vellumai/service-contracts/channels";
|
|
1
10
|
import type { Command } from "commander";
|
|
2
11
|
|
|
3
12
|
import { cliIpcCall, exitFromIpcResult } from "../../ipc/cli-client.js";
|
|
@@ -6,25 +15,6 @@ import { registerCommand } from "../lib/register-command.js";
|
|
|
6
15
|
import { writeOutput } from "../output.js";
|
|
7
16
|
import { channelVerificationSessionsHelp } from "./channel-verification-sessions.help.js";
|
|
8
17
|
|
|
9
|
-
// ---------------------------------------------------------------------------
|
|
10
|
-
// Local channel validation (replaces daemon-internal channels/types.js import)
|
|
11
|
-
// ---------------------------------------------------------------------------
|
|
12
|
-
|
|
13
|
-
const VALID_CHANNEL_IDS = [
|
|
14
|
-
"telegram",
|
|
15
|
-
"phone",
|
|
16
|
-
"vellum",
|
|
17
|
-
"whatsapp",
|
|
18
|
-
"slack",
|
|
19
|
-
"email",
|
|
20
|
-
"platform",
|
|
21
|
-
] as const;
|
|
22
|
-
type ChannelId = (typeof VALID_CHANNEL_IDS)[number];
|
|
23
|
-
|
|
24
|
-
function isChannelId(raw: string): raw is ChannelId {
|
|
25
|
-
return (VALID_CHANNEL_IDS as readonly string[]).includes(raw);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
18
|
/**
|
|
29
19
|
* Validate the --channel option. Returns the validated ChannelId or writes an
|
|
30
20
|
* error and returns `false`. When `required` is false an absent value is fine
|
|
@@ -49,7 +39,7 @@ function validateChannelOpt(
|
|
|
49
39
|
if (required) {
|
|
50
40
|
writeOutput(cmd, {
|
|
51
41
|
ok: false,
|
|
52
|
-
error: `The "channel" option is required. Valid values: ${
|
|
42
|
+
error: `The "channel" option is required. Valid values: ${CHANNEL_IDS.join(", ")}`,
|
|
53
43
|
});
|
|
54
44
|
process.exitCode = 1;
|
|
55
45
|
return false;
|
|
@@ -59,7 +49,7 @@ function validateChannelOpt(
|
|
|
59
49
|
if (!isChannelId(raw)) {
|
|
60
50
|
writeOutput(cmd, {
|
|
61
51
|
ok: false,
|
|
62
|
-
error: `Invalid channel "${raw}". Valid values: ${
|
|
52
|
+
error: `Invalid channel "${raw}". Valid values: ${CHANNEL_IDS.join(", ")}`,
|
|
63
53
|
});
|
|
64
54
|
process.exitCode = 1;
|
|
65
55
|
return false;
|
|
@@ -12,6 +12,7 @@ running.
|
|
|
12
12
|
|
|
13
13
|
Examples:
|
|
14
14
|
$ assistant conversations list
|
|
15
|
+
$ assistant conversations search "project planning"
|
|
15
16
|
$ assistant conversations new "Project planning"
|
|
16
17
|
$ assistant conversations export
|
|
17
18
|
$ assistant conversations clear`,
|
|
@@ -168,6 +169,39 @@ include them.
|
|
|
168
169
|
Examples:
|
|
169
170
|
$ assistant conversations list
|
|
170
171
|
$ assistant conversations list --include-archived`,
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
name: "search",
|
|
175
|
+
args: "<term>",
|
|
176
|
+
description: "Search conversations by title and message content",
|
|
177
|
+
options: [
|
|
178
|
+
{
|
|
179
|
+
flags: "--limit <n>",
|
|
180
|
+
description: "Maximum number of conversations to return (default 20)",
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
flags: "--json",
|
|
184
|
+
description: "Output result as machine-readable JSON",
|
|
185
|
+
},
|
|
186
|
+
],
|
|
187
|
+
helpText: `
|
|
188
|
+
Arguments:
|
|
189
|
+
term Search query. Quote multi-word terms so the shell keeps them as one
|
|
190
|
+
argument. Matches conversation titles (substring) and message
|
|
191
|
+
content (full-text). Archived conversations are excluded, matching
|
|
192
|
+
'assistant conversations list'.
|
|
193
|
+
|
|
194
|
+
Requires the assistant to be running. Communicates via IPC socket.
|
|
195
|
+
|
|
196
|
+
Results are grouped by conversation, ordered by most recently updated, and
|
|
197
|
+
capped at 20 conversations unless --limit is set. Each result includes the
|
|
198
|
+
conversation ID, title, relative timestamp, and up to three matching
|
|
199
|
+
message excerpts. Use the ID with rename, export, or wake.
|
|
200
|
+
|
|
201
|
+
Examples:
|
|
202
|
+
$ assistant conversations search "project planning"
|
|
203
|
+
$ assistant conversations search flux --limit 5
|
|
204
|
+
$ assistant conversations search "quarterly metrics" --json`,
|
|
171
205
|
},
|
|
172
206
|
{
|
|
173
207
|
name: "new",
|
|
@@ -11,6 +11,7 @@ import { applyCommandHelp, subcommand } from "../lib/cli-command-help.js";
|
|
|
11
11
|
import { registerCommand } from "../lib/register-command.js";
|
|
12
12
|
import { timeAgo } from "../lib/time-ago.js";
|
|
13
13
|
import { log } from "../logger.js";
|
|
14
|
+
import { shouldOutputJson, writeError, writeOutput } from "../output.js";
|
|
14
15
|
import { tryResolveConversationId } from "../utils/conversation-id.js";
|
|
15
16
|
import { conversationsHelp } from "./conversations.help.js";
|
|
16
17
|
import { registerConversationsDeferCommand } from "./conversations-defer.js";
|
|
@@ -29,6 +30,66 @@ type SlackDetachCliResult = {
|
|
|
29
30
|
conversationId?: string;
|
|
30
31
|
};
|
|
31
32
|
|
|
33
|
+
type ConversationSearchMessage = {
|
|
34
|
+
messageId: string;
|
|
35
|
+
role: string;
|
|
36
|
+
excerpt: string;
|
|
37
|
+
createdAt: number;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
type ConversationSearchResult = {
|
|
41
|
+
conversationId: string;
|
|
42
|
+
conversationTitle: string | null;
|
|
43
|
+
conversationUpdatedAt: number;
|
|
44
|
+
matchingMessages: ConversationSearchMessage[];
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
type ConversationSearchResponse = {
|
|
48
|
+
query: string;
|
|
49
|
+
results: ConversationSearchResult[];
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
function parsePositiveLimit(
|
|
53
|
+
raw: string | undefined,
|
|
54
|
+
): { ok: true; value: number | undefined } | { ok: false; error: string } {
|
|
55
|
+
if (raw === undefined) {
|
|
56
|
+
return { ok: true, value: undefined };
|
|
57
|
+
}
|
|
58
|
+
const trimmed = raw.trim();
|
|
59
|
+
if (!/^\d+$/.test(trimmed)) {
|
|
60
|
+
return {
|
|
61
|
+
ok: false,
|
|
62
|
+
error:
|
|
63
|
+
"Invalid --limit value. Must be a positive integer, e.g. --limit 10.",
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
const value = Number(trimmed);
|
|
67
|
+
if (!Number.isSafeInteger(value) || value < 1) {
|
|
68
|
+
return {
|
|
69
|
+
ok: false,
|
|
70
|
+
error:
|
|
71
|
+
"Invalid --limit value. Must be a positive integer, e.g. --limit 10.",
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return { ok: true, value };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function formatSearchResultLines(
|
|
78
|
+
results: ConversationSearchResult[],
|
|
79
|
+
): string[] {
|
|
80
|
+
const lines: string[] = [];
|
|
81
|
+
for (const result of results) {
|
|
82
|
+
lines.push(
|
|
83
|
+
` ${result.conversationId} ${result.conversationTitle ?? "Untitled"} ${timeAgo(result.conversationUpdatedAt)}`,
|
|
84
|
+
);
|
|
85
|
+
for (const message of result.matchingMessages) {
|
|
86
|
+
const excerpt = message.excerpt.replace(/\s+/g, " ").trim();
|
|
87
|
+
lines.push(` ${message.role} ${excerpt}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return lines;
|
|
91
|
+
}
|
|
92
|
+
|
|
32
93
|
function outputSlackDetachError(message: string, json?: boolean): void {
|
|
33
94
|
if (json) {
|
|
34
95
|
process.stdout.write(JSON.stringify({ ok: false, error: message }) + "\n");
|
|
@@ -200,6 +261,70 @@ export function registerConversationsCommand(program: Command): void {
|
|
|
200
261
|
},
|
|
201
262
|
);
|
|
202
263
|
|
|
264
|
+
// -------------------------------------------------------------------
|
|
265
|
+
// search
|
|
266
|
+
// -------------------------------------------------------------------
|
|
267
|
+
|
|
268
|
+
subcommand(conversations, "search").action(
|
|
269
|
+
async (term: string, opts: { limit?: string }, cmd: Command) => {
|
|
270
|
+
const trimmed = term.trim();
|
|
271
|
+
if (!trimmed) {
|
|
272
|
+
writeError(
|
|
273
|
+
cmd,
|
|
274
|
+
'Search term must be a non-empty string. Quote multi-word queries, e.g. assistant conversations search "project planning".',
|
|
275
|
+
);
|
|
276
|
+
process.exitCode = 1;
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const parsedLimit = parsePositiveLimit(opts.limit);
|
|
281
|
+
if (!parsedLimit.ok) {
|
|
282
|
+
writeError(cmd, parsedLimit.error);
|
|
283
|
+
process.exitCode = 1;
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const queryParams: Record<string, string> = { q: trimmed };
|
|
288
|
+
if (parsedLimit.value !== undefined) {
|
|
289
|
+
queryParams.limit = String(parsedLimit.value);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const result = await cliIpcCall<ConversationSearchResponse>(
|
|
293
|
+
"conversations_search",
|
|
294
|
+
{ queryParams },
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
if (!result.ok) {
|
|
298
|
+
if (shouldOutputJson(cmd)) {
|
|
299
|
+
writeOutput(cmd, {
|
|
300
|
+
ok: false,
|
|
301
|
+
error: result.error ?? "Conversation search failed",
|
|
302
|
+
});
|
|
303
|
+
process.exitCode = exitCodeFromIpcResult(result);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
return exitFromIpcResult(result);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const payload = result.result!;
|
|
310
|
+
if (shouldOutputJson(cmd)) {
|
|
311
|
+
writeOutput(cmd, payload);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (payload.results.length === 0) {
|
|
316
|
+
process.stdout.write(
|
|
317
|
+
`No conversations matched "${payload.query}"\n`,
|
|
318
|
+
);
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
process.stdout.write(
|
|
323
|
+
formatSearchResultLines(payload.results).join("\n") + "\n",
|
|
324
|
+
);
|
|
325
|
+
},
|
|
326
|
+
);
|
|
327
|
+
|
|
203
328
|
// -------------------------------------------------------------------
|
|
204
329
|
// new
|
|
205
330
|
// -------------------------------------------------------------------
|
|
@@ -38,6 +38,7 @@ interface ProviderConnection {
|
|
|
38
38
|
name: string;
|
|
39
39
|
provider: string;
|
|
40
40
|
auth: AuthInfo;
|
|
41
|
+
baseUrl?: string | null;
|
|
41
42
|
createdAt: string;
|
|
42
43
|
updatedAt: string;
|
|
43
44
|
}
|
|
@@ -129,10 +130,11 @@ function collectRepeatable(value: string, previous: string[]): string[] {
|
|
|
129
130
|
}
|
|
130
131
|
|
|
131
132
|
/**
|
|
132
|
-
* Build the
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
133
|
+
* Build the custom-provider fields (`base_url`, `models`) from CLI flags,
|
|
134
|
+
* forwarded to the connection route under its exact field names. The daemon
|
|
135
|
+
* stays authoritative on whether they are required or allowed (openai-compatible
|
|
136
|
+
* requires both; ollama accepts an optional base URL); the CLI only
|
|
137
|
+
* shape-forwards what the user passed.
|
|
136
138
|
*/
|
|
137
139
|
function buildCustomProviderFields(opts: {
|
|
138
140
|
baseUrl?: string;
|
|
@@ -219,6 +221,9 @@ function attachGetSubcommand(parent: Command): void {
|
|
|
219
221
|
process.stdout.write(`name: ${conn.name}\n`);
|
|
220
222
|
process.stdout.write(`provider: ${conn.provider}\n`);
|
|
221
223
|
process.stdout.write(`auth: ${formatAuth(conn.auth)}\n`);
|
|
224
|
+
if (conn.baseUrl) {
|
|
225
|
+
process.stdout.write(`base_url: ${conn.baseUrl}\n`);
|
|
226
|
+
}
|
|
222
227
|
process.stdout.write(
|
|
223
228
|
`created: ${new Date(conn.createdAt).toISOString()}\n`,
|
|
224
229
|
);
|