@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
|
@@ -440,4 +440,38 @@ describe("mergePluginTree", () => {
|
|
|
440
440
|
expect(result.conflicts).toEqual(["f.txt"]);
|
|
441
441
|
expect(result.binaryConflicts).toEqual([]);
|
|
442
442
|
});
|
|
443
|
+
|
|
444
|
+
test("copies live config.json, data/, and .disabled from ours, ignoring the pin", async () => {
|
|
445
|
+
// GIVEN user-owned state on the live install and different defaults at the pin
|
|
446
|
+
writeTree(baseDir, { "package.json": '{"name":"p"}\n' });
|
|
447
|
+
writeTree(oursDir, {
|
|
448
|
+
"package.json": '{"name":"p"}\n',
|
|
449
|
+
"config.json": '{"provider":"photon","ingressMode":"live"}\n',
|
|
450
|
+
"data/cursor.json": '{"n":1}\n',
|
|
451
|
+
".disabled": "",
|
|
452
|
+
});
|
|
453
|
+
writeTree(theirsDir, {
|
|
454
|
+
"package.json": '{"name":"p"}\n',
|
|
455
|
+
"config.json": '{"provider":"comms","ingressMode":"webhook"}\n',
|
|
456
|
+
"data/cursor.json": '{"n":0}\n',
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
// WHEN merged with theirs (the strategy that would otherwise take the pin)
|
|
460
|
+
const result = await mergePluginTree({
|
|
461
|
+
baseDir,
|
|
462
|
+
oursDir,
|
|
463
|
+
theirsDir,
|
|
464
|
+
destDir,
|
|
465
|
+
strategy: "theirs",
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
// THEN user-owned state is the live copy, not the pin, and is not counted
|
|
469
|
+
// as a merged source file
|
|
470
|
+
expect(read("config.json")).toBe(
|
|
471
|
+
'{"provider":"photon","ingressMode":"live"}\n',
|
|
472
|
+
);
|
|
473
|
+
expect(read("data/cursor.json")).toBe('{"n":1}\n');
|
|
474
|
+
expect(read(".disabled")).toBe("");
|
|
475
|
+
expect(result.fileCount).toBe(1);
|
|
476
|
+
});
|
|
443
477
|
});
|
|
@@ -17,7 +17,11 @@ import { tmpdir } from "node:os";
|
|
|
17
17
|
import { join } from "node:path";
|
|
18
18
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
19
19
|
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
detectPluginSurfaces,
|
|
22
|
+
findPluginSetupSkill,
|
|
23
|
+
formatPluginSetupHint,
|
|
24
|
+
} from "../plugin-surfaces.js";
|
|
21
25
|
|
|
22
26
|
let pluginDir: string;
|
|
23
27
|
|
|
@@ -188,3 +192,30 @@ describe("detectPluginSurfaces", () => {
|
|
|
188
192
|
expect(detectPluginSurfaces(pluginDir).schedules).toEqual([]);
|
|
189
193
|
});
|
|
190
194
|
});
|
|
195
|
+
|
|
196
|
+
describe("findPluginSetupSkill", () => {
|
|
197
|
+
test("prefers the generic setup id when both conventions are present", () => {
|
|
198
|
+
expect(
|
|
199
|
+
findPluginSetupSkill("imessage", ["setup", "imessage-setup", "other"]),
|
|
200
|
+
).toBe("setup");
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test("falls back to <plugin-name>-setup", () => {
|
|
204
|
+
expect(findPluginSetupSkill("imessage", ["imessage-setup"])).toBe(
|
|
205
|
+
"imessage-setup",
|
|
206
|
+
);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test("returns undefined when the plugin ships no setup skill", () => {
|
|
210
|
+
expect(findPluginSetupSkill("imessage", ["imessage-send"])).toBeUndefined();
|
|
211
|
+
expect(findPluginSetupSkill("imessage", [])).toBeUndefined();
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
describe("formatPluginSetupHint", () => {
|
|
216
|
+
test("names the skill in the install-command copy", () => {
|
|
217
|
+
expect(formatPluginSetupHint("imessage-setup")).toBe(
|
|
218
|
+
"Load the imessage-setup skill to help set up this plugin",
|
|
219
|
+
);
|
|
220
|
+
});
|
|
221
|
+
});
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
rmSync,
|
|
6
6
|
writeFileSync,
|
|
7
7
|
} from "node:fs";
|
|
8
|
+
import { tmpdir } from "node:os";
|
|
8
9
|
import { join } from "node:path";
|
|
9
10
|
import { afterEach, beforeEach, describe, expect, it } from "bun:test";
|
|
10
11
|
|
|
@@ -18,7 +19,7 @@ import {
|
|
|
18
19
|
|
|
19
20
|
// VELLUM_WORKSPACE_DIR is set to this; getWorkspacePluginsDir() returns
|
|
20
21
|
// <workspace>/plugins, so the actual plugins directory is TMP_WS_DIR/plugins.
|
|
21
|
-
const TMP_WS_DIR = join(
|
|
22
|
+
const TMP_WS_DIR = join(tmpdir(), `toggle-plugin-test-${process.pid}`);
|
|
22
23
|
const TMP_PLUGINS_DIR = join(TMP_WS_DIR, "plugins");
|
|
23
24
|
|
|
24
25
|
beforeEach(() => {
|
|
@@ -920,6 +920,70 @@ describe("upgradePlugin --strategy", () => {
|
|
|
920
920
|
expect(installedFile("level-up", "local-only.txt")).toBeNull();
|
|
921
921
|
});
|
|
922
922
|
|
|
923
|
+
test("keeps live config.json, data/, and .disabled under overwrite, theirs, and ours", async () => {
|
|
924
|
+
// GIVEN user-owned state on the live install, and a pin that ships defaults
|
|
925
|
+
const userConfig = '{"provider":"photon","ingressMode":"live"}\n';
|
|
926
|
+
const pinConfig = '{"provider":"comms","ingressMode":"webhook"}\n';
|
|
927
|
+
const liveOurs: Tree = {
|
|
928
|
+
...OURS,
|
|
929
|
+
"config.json": userConfig,
|
|
930
|
+
"data/cursor.json": '{"n":1}\n',
|
|
931
|
+
".disabled": "",
|
|
932
|
+
};
|
|
933
|
+
const pinTheirs: Tree = {
|
|
934
|
+
...THEIRS,
|
|
935
|
+
"config.json": pinConfig,
|
|
936
|
+
"data/cursor.json": '{"n":0}\n',
|
|
937
|
+
};
|
|
938
|
+
|
|
939
|
+
for (const strategy of ["overwrite", "theirs", "ours"] as const) {
|
|
940
|
+
rmSync(join(pluginsDir, "level-up"), { recursive: true, force: true });
|
|
941
|
+
installMergeCopy("level-up", liveOurs, SHA_A, BASE);
|
|
942
|
+
const fetch = makeFetch({ manifest: manifestWith("level-up", SHA_B) });
|
|
943
|
+
const runGit = treeGitRunner({
|
|
944
|
+
[SHA_A]: BASE,
|
|
945
|
+
[SHA_B]: pinTheirs,
|
|
946
|
+
});
|
|
947
|
+
|
|
948
|
+
// WHEN the plugin is upgraded
|
|
949
|
+
const result = await upgradePlugin(
|
|
950
|
+
{ name: "level-up", strategy },
|
|
951
|
+
{ fetch, runGit, workspacePluginsDir: pluginsDir },
|
|
952
|
+
);
|
|
953
|
+
|
|
954
|
+
// THEN user-owned state is unchanged, including under `theirs` and
|
|
955
|
+
// overwrite, which would otherwise take the pin wholesale
|
|
956
|
+
expect(result.outcome).toBe("upgraded");
|
|
957
|
+
expect(installedFile("level-up", "config.json")).toBe(userConfig);
|
|
958
|
+
expect(installedFile("level-up", "data/cursor.json")).toBe('{"n":1}\n');
|
|
959
|
+
expect(existsSync(join(pluginsDir, "level-up", ".disabled"))).toBe(true);
|
|
960
|
+
}
|
|
961
|
+
});
|
|
962
|
+
|
|
963
|
+
test("does not seed config.json from the pin when the live install has none", async () => {
|
|
964
|
+
// GIVEN an install with no config.json, and a pin that ships defaults
|
|
965
|
+
installMergeCopy("level-up", OURS, SHA_A, BASE);
|
|
966
|
+
const fetch = makeFetch({ manifest: manifestWith("level-up", SHA_B) });
|
|
967
|
+
const runGit = treeGitRunner({
|
|
968
|
+
[SHA_A]: BASE,
|
|
969
|
+
[SHA_B]: {
|
|
970
|
+
...THEIRS,
|
|
971
|
+
"config.json": '{"provider":"comms","ingressMode":"webhook"}\n',
|
|
972
|
+
},
|
|
973
|
+
});
|
|
974
|
+
|
|
975
|
+
// WHEN upgraded (overwrite materializes the pin tree directly)
|
|
976
|
+
const result = await upgradePlugin(
|
|
977
|
+
{ name: "level-up", strategy: "overwrite" },
|
|
978
|
+
{ fetch, runGit, workspacePluginsDir: pluginsDir },
|
|
979
|
+
);
|
|
980
|
+
|
|
981
|
+
// THEN the pin's config.json does not land: host-owned config is created
|
|
982
|
+
// by the plugin at runtime, not by the installer
|
|
983
|
+
expect(result.outcome).toBe("upgraded");
|
|
984
|
+
expect(installedFile("level-up", "config.json")).toBeNull();
|
|
985
|
+
});
|
|
986
|
+
|
|
923
987
|
test("defaults to overwrite when no strategy is given", async () => {
|
|
924
988
|
// GIVEN an install with a local-only file and an advanced pin
|
|
925
989
|
installMergeCopy("level-up", OURS, SHA_A, BASE);
|
|
@@ -292,6 +292,19 @@
|
|
|
292
292
|
"license": "MIT",
|
|
293
293
|
"icon": "🎬"
|
|
294
294
|
},
|
|
295
|
+
{
|
|
296
|
+
"name": "natural",
|
|
297
|
+
"source": {
|
|
298
|
+
"source": "github",
|
|
299
|
+
"repo": "vellum-ai/natural",
|
|
300
|
+
"ref": "3cfc8c97c14df311fc64992ab8b172c1b2d64eb0"
|
|
301
|
+
},
|
|
302
|
+
"description": "Hosted MCP access to Natural payment operations for Vellum assistants.",
|
|
303
|
+
"category": "finance",
|
|
304
|
+
"homepage": "https://www.natural.com",
|
|
305
|
+
"license": "MIT",
|
|
306
|
+
"icon": "💳"
|
|
307
|
+
},
|
|
295
308
|
{
|
|
296
309
|
"name": "originkit",
|
|
297
310
|
"source": {
|
|
@@ -31,6 +31,8 @@ export interface DeleteSecureKeyResult {
|
|
|
31
31
|
result: DeleteResult;
|
|
32
32
|
/** Human-readable error reason when result="error". */
|
|
33
33
|
error?: string;
|
|
34
|
+
/** Daemon error code when result="error" (e.g. `CREDENTIAL_IN_USE`). */
|
|
35
|
+
code?: string;
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
// ---------------------------------------------------------------------------
|
|
@@ -104,13 +106,18 @@ export async function setSecureKeyViaDaemon(
|
|
|
104
106
|
/**
|
|
105
107
|
* Delete a secret via the daemon IPC socket. Falls back to direct
|
|
106
108
|
* `deleteSecureKeyAsync()` when the daemon is not running.
|
|
109
|
+
*
|
|
110
|
+
* `force` carries the caller's confirmation past the daemon's in-use guard,
|
|
111
|
+
* which refuses a delete while an LLM provider connection resolves its auth
|
|
112
|
+
* through the credential.
|
|
107
113
|
*/
|
|
108
114
|
export async function deleteSecureKeyViaDaemon(
|
|
109
115
|
type: string,
|
|
110
116
|
name: string,
|
|
117
|
+
force = false,
|
|
111
118
|
): Promise<DeleteSecureKeyResult> {
|
|
112
119
|
const ipc = await cliIpcCall<{ success: boolean }>("secrets_delete", {
|
|
113
|
-
body: { type, name },
|
|
120
|
+
body: force ? { type, name, force } : { type, name },
|
|
114
121
|
});
|
|
115
122
|
|
|
116
123
|
if (ipc.ok && ipc.result?.success) {
|
|
@@ -122,7 +129,9 @@ export async function deleteSecureKeyViaDaemon(
|
|
|
122
129
|
if (ipc.error.includes("not found") || ipc.error.includes("404")) {
|
|
123
130
|
return { result: "not-found" };
|
|
124
131
|
}
|
|
125
|
-
return
|
|
132
|
+
return ipc.errorCode !== undefined
|
|
133
|
+
? { result: "error", error: ipc.error, code: ipc.errorCode }
|
|
134
|
+
: { result: "error", error: ipc.error };
|
|
126
135
|
}
|
|
127
136
|
|
|
128
137
|
// Daemon returned success=false
|
|
@@ -133,7 +142,20 @@ export async function deleteSecureKeyViaDaemon(
|
|
|
133
142
|
};
|
|
134
143
|
}
|
|
135
144
|
|
|
136
|
-
// Daemon unreachable
|
|
145
|
+
// Daemon unreachable. The in-use guard runs daemon-side, next to the
|
|
146
|
+
// provider connection records it reads, so a direct delete here would
|
|
147
|
+
// remove a credential an LLM connection depends on with no check at all.
|
|
148
|
+
// The delete is refused until the caller either starts the assistant or
|
|
149
|
+
// states the intent explicitly.
|
|
150
|
+
if (!force) {
|
|
151
|
+
return {
|
|
152
|
+
result: "error",
|
|
153
|
+
code: "IN_USE_CHECK_UNAVAILABLE",
|
|
154
|
+
error:
|
|
155
|
+
"Cannot check whether LLM provider connections depend on this credential while the assistant is not running. Start the assistant, or re-run with --force to delete without the check.",
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
137
159
|
if (type === "api_key") {
|
|
138
160
|
// Delete from both locations; during migration overlap both may exist.
|
|
139
161
|
const credResult = await deleteSecureKeyAsync(
|
|
@@ -26,7 +26,6 @@
|
|
|
26
26
|
import { execFile } from "node:child_process";
|
|
27
27
|
import {
|
|
28
28
|
copyFileSync,
|
|
29
|
-
cpSync,
|
|
30
29
|
existsSync,
|
|
31
30
|
mkdirSync,
|
|
32
31
|
readdirSync,
|
|
@@ -40,7 +39,12 @@ import { tmpdir } from "node:os";
|
|
|
40
39
|
import { dirname, join, resolve, sep } from "node:path";
|
|
41
40
|
import { promisify } from "node:util";
|
|
42
41
|
|
|
43
|
-
import {
|
|
42
|
+
import {
|
|
43
|
+
copyPreservedUserState,
|
|
44
|
+
isPluginUserStateEntry,
|
|
45
|
+
PRESERVED_ENTRIES,
|
|
46
|
+
stripPreservedUserState,
|
|
47
|
+
} from "../../plugins/plugin-tree-walk.js";
|
|
44
48
|
import { ensureBun } from "../../util/bun-runtime.js";
|
|
45
49
|
import { getWorkspacePluginsDir } from "../../util/platform.js";
|
|
46
50
|
import type { FetchLike } from "./fetch-like.js";
|
|
@@ -687,27 +691,13 @@ export async function finalizeStagedInstall(
|
|
|
687
691
|
}
|
|
688
692
|
}
|
|
689
693
|
|
|
690
|
-
//
|
|
691
|
-
//
|
|
692
|
-
//
|
|
693
|
-
//
|
|
694
|
+
// User-owned state (config.json, data/, .disabled) is not plugin source.
|
|
695
|
+
// Drop any copy the pin materialized into staging, then copy the live
|
|
696
|
+
// install's bytes over so an upgrade cannot reset user config to pin
|
|
697
|
+
// defaults. Without the live copy, the rm+rename below would destroy it.
|
|
698
|
+
stripPreservedUserState(stagingDir);
|
|
694
699
|
if (existsSync(target)) {
|
|
695
|
-
|
|
696
|
-
if (entry === INSTALL_META_FILENAME) {
|
|
697
|
-
continue;
|
|
698
|
-
} // sidecar is rewritten above
|
|
699
|
-
const src = join(target, entry);
|
|
700
|
-
if (!existsSync(src)) {
|
|
701
|
-
continue;
|
|
702
|
-
}
|
|
703
|
-
const dest = join(stagingDir, entry);
|
|
704
|
-
const stat = statSync(src);
|
|
705
|
-
if (stat.isDirectory()) {
|
|
706
|
-
cpSync(src, dest, { recursive: true });
|
|
707
|
-
} else {
|
|
708
|
-
copyFileSync(src, dest);
|
|
709
|
-
}
|
|
710
|
-
}
|
|
700
|
+
copyPreservedUserState(target, stagingDir);
|
|
711
701
|
rmSync(target, { recursive: true, force: true });
|
|
712
702
|
}
|
|
713
703
|
renameSync(stagingDir, target);
|
|
@@ -1287,7 +1277,8 @@ function pluginPostinstallEnv(bun: string): NodeJS.ProcessEnv {
|
|
|
1287
1277
|
|
|
1288
1278
|
/**
|
|
1289
1279
|
* Recursively copy regular files from `srcRoot` into `destDir`, skipping the
|
|
1290
|
-
* top-level `.git` directory, a top-level `bunfig.toml` (see below),
|
|
1280
|
+
* top-level `.git` directory, a top-level `bunfig.toml` (see below), top-level
|
|
1281
|
+
* user-state entries (`config.json`, `data/`, `.disabled`), and any
|
|
1291
1282
|
* symlinks. Returns the file count.
|
|
1292
1283
|
*/
|
|
1293
1284
|
function copyTreeSkippingGit(srcRoot: string, destDir: string): number {
|
|
@@ -1300,6 +1291,12 @@ function copyTreeSkippingGit(srcRoot: string, destDir: string): number {
|
|
|
1300
1291
|
if (relDir === "" && entry.name === ".git") {
|
|
1301
1292
|
continue;
|
|
1302
1293
|
}
|
|
1294
|
+
// Runtime-owned state is not plugin source. A pin that ships
|
|
1295
|
+
// `config.json` or `data/` must not land in the staged tree, or an
|
|
1296
|
+
// upgrade would reset user config to whatever the revision committed.
|
|
1297
|
+
if (relDir === "" && isPluginUserStateEntry(entry.name)) {
|
|
1298
|
+
continue;
|
|
1299
|
+
}
|
|
1303
1300
|
// Drop a top-level `bunfig.toml`. The adapter postinstall runs `bun` with
|
|
1304
1301
|
// its cwd at the staged root, and Bun auto-loads `$cwd/bunfig.toml` as
|
|
1305
1302
|
// project config — including a `preload` list it executes before the
|
|
@@ -1712,6 +1709,7 @@ async function copyDir(
|
|
|
1712
1709
|
ref: string,
|
|
1713
1710
|
destDir: string,
|
|
1714
1711
|
fetchFn: FetchLike,
|
|
1712
|
+
atPluginRoot = true,
|
|
1715
1713
|
): Promise<number> {
|
|
1716
1714
|
const entries = await listDir(owner, repo, apiPath, ref, fetchFn);
|
|
1717
1715
|
if (entries === null) {
|
|
@@ -1725,11 +1723,24 @@ async function copyDir(
|
|
|
1725
1723
|
continue;
|
|
1726
1724
|
}
|
|
1727
1725
|
assertSafeFilename("entry name", entry.name);
|
|
1726
|
+
// Adapter stubs overlay the plugin root. User-state names at that root
|
|
1727
|
+
// are host-owned, not adapter input.
|
|
1728
|
+
if (atPluginRoot && isPluginUserStateEntry(entry.name)) {
|
|
1729
|
+
continue;
|
|
1730
|
+
}
|
|
1728
1731
|
|
|
1729
1732
|
if (entry.type === "dir") {
|
|
1730
1733
|
const subDest = join(destDir, entry.name);
|
|
1731
1734
|
mkdirSync(subDest, { recursive: true });
|
|
1732
|
-
count += await copyDir(
|
|
1735
|
+
count += await copyDir(
|
|
1736
|
+
owner,
|
|
1737
|
+
repo,
|
|
1738
|
+
entry.path,
|
|
1739
|
+
ref,
|
|
1740
|
+
subDest,
|
|
1741
|
+
fetchFn,
|
|
1742
|
+
false,
|
|
1743
|
+
);
|
|
1733
1744
|
continue;
|
|
1734
1745
|
}
|
|
1735
1746
|
|
|
@@ -23,6 +23,13 @@
|
|
|
23
23
|
* one side) is resolved here before any line merge, since `git merge-file`
|
|
24
24
|
* operates on three existing blobs.
|
|
25
25
|
*
|
|
26
|
+
* Runtime-owned user state (`config.json`, `data/`, `.disabled`) is never
|
|
27
|
+
* merged. Those paths are excluded from the fingerprints and copied from the
|
|
28
|
+
* live install (`ours`) into the destination after the source merge, so a
|
|
29
|
+
* pin that ships default config cannot reset user settings. The provenance
|
|
30
|
+
* sidecar is excluded on every side and rewritten by the caller after the swap.
|
|
31
|
+
*
|
|
32
|
+
*
|
|
26
33
|
* Binary files cannot be line-merged, so a binary file that diverged on both
|
|
27
34
|
* sides is resolved whole-file by the strategy (`ours`/`theirs`); under
|
|
28
35
|
* `assistant` the local copy is kept and the path is reported as a binary
|
|
@@ -44,7 +51,10 @@ import { tmpdir } from "node:os";
|
|
|
44
51
|
import { dirname, join } from "node:path";
|
|
45
52
|
import { promisify } from "node:util";
|
|
46
53
|
|
|
47
|
-
import {
|
|
54
|
+
import {
|
|
55
|
+
copyPreservedUserState,
|
|
56
|
+
PRESERVED_ENTRIES,
|
|
57
|
+
} from "../../plugins/plugin-tree-walk.js";
|
|
48
58
|
import { computeFingerprint } from "./plugin-fingerprint.js";
|
|
49
59
|
|
|
50
60
|
const execFileAsync = promisify(execFile);
|
|
@@ -230,9 +240,11 @@ async function threeWayMergeFile(
|
|
|
230
240
|
|
|
231
241
|
/**
|
|
232
242
|
* Three-way merge `oursDir`/`theirsDir` against `baseDir` into `destDir` per
|
|
233
|
-
* `strategy`.
|
|
234
|
-
*
|
|
235
|
-
*
|
|
243
|
+
* `strategy`. User-owned state (`config.json`, `data/`, `.disabled`) is
|
|
244
|
+
* excluded from the line merge and copied from `oursDir` afterward, so the
|
|
245
|
+
* pin cannot reset it. The provenance sidecar is excluded on every side — it
|
|
246
|
+
* is rewritten by the caller after the swap and must not be carried through
|
|
247
|
+
* the merge.
|
|
236
248
|
*
|
|
237
249
|
* Returns the file count plus, for the `assistant` strategy, the paths left for
|
|
238
250
|
* the assistant to resolve (text files with conflict markers and modify/delete
|
|
@@ -332,5 +344,11 @@ export async function mergePluginTree({
|
|
|
332
344
|
// Present only in the base: removed on both sides, so it stays removed.
|
|
333
345
|
}
|
|
334
346
|
|
|
347
|
+
// User-owned state is not plugin source, so it is never a merge input.
|
|
348
|
+
// Copy it from the live install so `--strategy theirs` cannot take a
|
|
349
|
+
// pin-shipped `config.json` (or drop an untracked one the fingerprint
|
|
350
|
+
// walk never saw).
|
|
351
|
+
copyPreservedUserState(oursDir, destDir);
|
|
352
|
+
|
|
335
353
|
return { fileCount, conflicts, binaryConflicts };
|
|
336
354
|
}
|
|
@@ -216,6 +216,33 @@ function listSchedules(schedulesDir: string): PluginScheduleSurface[] {
|
|
|
216
216
|
);
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
+
/**
|
|
220
|
+
* Skill id a plugin ships for first-run setup, if any.
|
|
221
|
+
*
|
|
222
|
+
* Convention: `skills/setup/SKILL.md` or `skills/<plugin-name>-setup/SKILL.md`.
|
|
223
|
+
* The directory name is the id `skill_load` uses. `setup` wins when both exist
|
|
224
|
+
* so the generic name is the standard; `<plugin-name>-setup` covers plugins
|
|
225
|
+
* that already ship that form.
|
|
226
|
+
*/
|
|
227
|
+
export function findPluginSetupSkill(
|
|
228
|
+
pluginName: string,
|
|
229
|
+
skillIds: readonly string[],
|
|
230
|
+
): string | undefined {
|
|
231
|
+
if (skillIds.includes("setup")) {
|
|
232
|
+
return "setup";
|
|
233
|
+
}
|
|
234
|
+
const named = `${pluginName}-setup`;
|
|
235
|
+
if (skillIds.includes(named)) {
|
|
236
|
+
return named;
|
|
237
|
+
}
|
|
238
|
+
return undefined;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** Install-command copy pointing at a plugin's setup skill. */
|
|
242
|
+
export function formatPluginSetupHint(skillId: string): string {
|
|
243
|
+
return `Load the ${skillId} skill to help set up this plugin`;
|
|
244
|
+
}
|
|
245
|
+
|
|
219
246
|
/**
|
|
220
247
|
* Detect the {@link PluginSurfaces} an installed plugin contributes by walking
|
|
221
248
|
* its install tree at `pluginDir`. Surface types with no contributions come
|
|
@@ -92,14 +92,6 @@
|
|
|
92
92
|
"description": "Enable the Quick Input popover on right-click of the menu bar icon",
|
|
93
93
|
"defaultEnabled": false
|
|
94
94
|
},
|
|
95
|
-
{
|
|
96
|
-
"id": "companion-surface",
|
|
97
|
-
"scope": "client",
|
|
98
|
-
"key": "companion-surface",
|
|
99
|
-
"label": "Companion Surface",
|
|
100
|
-
"description": "Show the macOS companion surface: the floating avatar that carries Talk, Type and the running call, and its tray item. Read in the Electron main process, which decides whether the window opens at all",
|
|
101
|
-
"defaultEnabled": false
|
|
102
|
-
},
|
|
103
95
|
{
|
|
104
96
|
"id": "expand-completed-steps",
|
|
105
97
|
"scope": "client",
|
|
@@ -108,14 +100,6 @@
|
|
|
108
100
|
"description": "Auto-expand completed tool call step groups instead of showing them collapsed",
|
|
109
101
|
"defaultEnabled": false
|
|
110
102
|
},
|
|
111
|
-
{
|
|
112
|
-
"id": "collapse-assistant-intermediates",
|
|
113
|
-
"scope": "client",
|
|
114
|
-
"key": "collapse-assistant-intermediates",
|
|
115
|
-
"label": "Collapse Assistant Intermediates",
|
|
116
|
-
"description": "Collapse completed intermediary assistant activity behind Earlier activity while keeping the final response visible",
|
|
117
|
-
"defaultEnabled": false
|
|
118
|
-
},
|
|
119
103
|
{
|
|
120
104
|
"id": "backward-releases",
|
|
121
105
|
"scope": "assistant",
|
|
@@ -152,8 +136,16 @@
|
|
|
152
136
|
"id": "fork-from-message",
|
|
153
137
|
"scope": "client",
|
|
154
138
|
"key": "fork-from-message",
|
|
155
|
-
"label": "
|
|
156
|
-
"description": "
|
|
139
|
+
"label": "Internal Thread Actions (legacy key)",
|
|
140
|
+
"description": "Legacy key for `internal-thread-actions`. The client grants the internal thread affordances when either key is enabled, so the platform may serve the gate under either name. Declared here so the flag mapper keeps the value instead of dropping it as undeclared.",
|
|
141
|
+
"defaultEnabled": false
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"id": "internal-thread-actions",
|
|
145
|
+
"scope": "client",
|
|
146
|
+
"key": "internal-thread-actions",
|
|
147
|
+
"label": "Internal Thread Actions",
|
|
148
|
+
"description": "Show the internal thread affordances: 'Fork from here' on messages, 'Fork Conversation', 'Analyze Conversation', and 'Copy conversation ID' in the thread menu, plus the LLM inspector surfaces. The flag is the sole gate: any session that enables it gets the affordances, including local sessions with no platform identity.",
|
|
157
149
|
"defaultEnabled": false
|
|
158
150
|
},
|
|
159
151
|
{
|
|
@@ -244,6 +236,14 @@
|
|
|
244
236
|
"description": "Show the pair-a-device card in web settings. Visibility only; pairing itself is always available.",
|
|
245
237
|
"defaultEnabled": false
|
|
246
238
|
},
|
|
239
|
+
{
|
|
240
|
+
"id": "paired-devices-ui",
|
|
241
|
+
"scope": "client",
|
|
242
|
+
"key": "paired-devices-ui",
|
|
243
|
+
"label": "Paired Devices UI",
|
|
244
|
+
"description": "Show the paired-devices list and revoke controls inside the pair-a-device settings card. Visibility only; `vellum devices` on the host stays available.",
|
|
245
|
+
"defaultEnabled": false
|
|
246
|
+
},
|
|
247
247
|
{
|
|
248
248
|
"id": "settings-sleep-policy",
|
|
249
249
|
"scope": "assistant",
|
|
@@ -356,13 +356,21 @@
|
|
|
356
356
|
"description": "Gates whether a `chat.assistant_reply` push is suppressed while a Mac desktop client reports itself attended. On by default; turn it off to restore unconditional reply pushes.",
|
|
357
357
|
"defaultEnabled": true
|
|
358
358
|
},
|
|
359
|
+
{
|
|
360
|
+
"id": "web-presence-suppression",
|
|
361
|
+
"scope": "assistant",
|
|
362
|
+
"key": "web-presence-suppression",
|
|
363
|
+
"label": "Web Presence Suppression",
|
|
364
|
+
"description": "Gates whether a `chat.assistant_reply` push is suppressed while a browser tab reports itself visible on that conversation. On by default; turn it off to restore unconditional reply pushes for web-visible conversations.",
|
|
365
|
+
"defaultEnabled": true
|
|
366
|
+
},
|
|
359
367
|
{
|
|
360
368
|
"id": "plugin-schedules",
|
|
361
369
|
"scope": "assistant",
|
|
362
370
|
"key": "plugin-schedules",
|
|
363
371
|
"label": "Plugin Schedules",
|
|
364
372
|
"description": "Plugins declare recurring schedules as files that register and run automatically",
|
|
365
|
-
"defaultEnabled":
|
|
373
|
+
"defaultEnabled": true
|
|
366
374
|
},
|
|
367
375
|
{
|
|
368
376
|
"id": "experiment-balanced-model-2026-08-06",
|
|
@@ -372,6 +380,14 @@
|
|
|
372
380
|
"description": "Multivariate experiment on the model the managed Balanced inference profile resolves to. control = gpt-5.6-luna (the shipped pin); terra = gpt-5.6-terra; glm-5p2 = accounts/fireworks/models/glm-5p2. Only the managed (vellum) implementation moves: ChatGPT-subscription and BYOK installs run the provider the user chose and are outside the experiment. Rollout and targeting are managed in the LaunchDarkly dashboard; any value that is not an arm name resolves to control.",
|
|
373
381
|
"defaultEnabled": "control",
|
|
374
382
|
"values": ["control", "terra", "glm-5p2"]
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
"id": "obscure-credits",
|
|
386
|
+
"scope": "client",
|
|
387
|
+
"key": "obscure-credits",
|
|
388
|
+
"label": "Obscure Credits",
|
|
389
|
+
"description": "Replaces the dollar amounts on the billing Plan section with a usage-relative view: the current-plan tile trades its price footer for a Usage Balance bar (managed spend this billing cycle against the package's included credits) and the credits spec chip reads \"<Package> usage, reset monthly\" instead of naming a dollar bundle. Spec chips wrap horizontally to fit the shorter labels. Off leaves the Plan section exactly as it ships today.",
|
|
390
|
+
"defaultEnabled": false
|
|
375
391
|
}
|
|
376
392
|
]
|
|
377
393
|
}
|
|
@@ -83,6 +83,14 @@ function isIngressExplicitlyDisabled(): boolean {
|
|
|
83
83
|
*
|
|
84
84
|
* `allowManagedCallbacks` gates both platform tiers: channels that can only be
|
|
85
85
|
* served by a self-hosted ingress pass `false` and never see them.
|
|
86
|
+
*
|
|
87
|
+
* The gateway's Telegram webhook reconciler implements this same resolution
|
|
88
|
+
* order when deciding what URL to hand Telegram's setWebhook
|
|
89
|
+
* (`resolveExpectedTelegramWebhookUrl` in
|
|
90
|
+
* `gateway/src/telegram/webhook-manager.ts`). The two must stay in agreement:
|
|
91
|
+
* a tier this derivation reports as configured but the reconciler declines
|
|
92
|
+
* leaves Telegram setup reporting success while no webhook is ever
|
|
93
|
+
* registered. Change the tiers in both places or not at all.
|
|
86
94
|
*/
|
|
87
95
|
export async function hasWebhookRoutingConfigured(
|
|
88
96
|
allowManagedCallbacks = false,
|
package/src/context/compactor.ts
CHANGED
|
@@ -39,6 +39,10 @@ import type {
|
|
|
39
39
|
} from "../providers/types.js";
|
|
40
40
|
import { type TrustClass } from "../runtime/actor-trust-resolver.js";
|
|
41
41
|
import { resolveCapabilities } from "../runtime/capabilities.js";
|
|
42
|
+
import {
|
|
43
|
+
hasValidJpegStructure,
|
|
44
|
+
sniffImageMimeType,
|
|
45
|
+
} from "../util/image-conversion.js";
|
|
42
46
|
import { getLogger } from "../util/logger.js";
|
|
43
47
|
import { preModelCallSanitize } from "./outbound-sanitize.js";
|
|
44
48
|
import { stripInjectionsForCompaction } from "./strip-injections.js";
|
|
@@ -865,7 +869,8 @@ function resolveTailFloorIndex(messages: Message[], tailIndex: number): number {
|
|
|
865
869
|
// Retained-image hydration
|
|
866
870
|
// ---------------------------------------------------------------------------
|
|
867
871
|
|
|
868
|
-
|
|
872
|
+
/** Exported for unit testing (the corrupt-bytes drop gate). */
|
|
873
|
+
export async function buildRetainedImageBlocks(
|
|
869
874
|
filenames: string[],
|
|
870
875
|
manifest: ManifestEntry[],
|
|
871
876
|
): Promise<{ blocks: ImageContent[]; resolved: string[]; missing: string[] }> {
|
|
@@ -883,7 +888,11 @@ async function buildRetainedImageBlocks(
|
|
|
883
888
|
missing.push(name);
|
|
884
889
|
continue;
|
|
885
890
|
}
|
|
886
|
-
|
|
891
|
+
// Sniff the declared MIME from the stored bytes rather than the filename:
|
|
892
|
+
// clients derive extensions from user input, and providers reject an image
|
|
893
|
+
// whose bytes disagree with the declared media type.
|
|
894
|
+
const sourceMime =
|
|
895
|
+
sniffImageMimeType(content) ?? guessMimeFromFilename(name);
|
|
887
896
|
// Run the same downscale pass the agent uses when first sending an
|
|
888
897
|
// image. Without this, attachments that exceed the provider's per-image
|
|
889
898
|
// byte limit (Anthropic: 5 MB) crash the next turn after compaction.
|
|
@@ -891,6 +900,26 @@ async function buildRetainedImageBlocks(
|
|
|
891
900
|
content.toString("base64"),
|
|
892
901
|
sourceMime,
|
|
893
902
|
);
|
|
903
|
+
// Last-line gate before the bytes are baked into the rebuilt history: a
|
|
904
|
+
// corrupt payload would be rejected by the provider with a 400 on EVERY
|
|
905
|
+
// subsequent turn, wedging the conversation. Dropping the image loses a
|
|
906
|
+
// retained picture; shipping it loses the conversation. Format sniffing
|
|
907
|
+
// alone is not enough for JPEG: a truncated JPEG (e.g. persisted from a
|
|
908
|
+
// torn conversion-cache read) keeps its SOI header, so JPEG payloads must
|
|
909
|
+
// also walk to a terminal EOI marker.
|
|
910
|
+
const optimizedBytes = Buffer.from(optimized.data, "base64");
|
|
911
|
+
const optimizedFormat = sniffImageMimeType(optimizedBytes);
|
|
912
|
+
const bytesAreValidImage =
|
|
913
|
+
optimizedFormat != null &&
|
|
914
|
+
(optimizedFormat !== "image/jpeg" ||
|
|
915
|
+
hasValidJpegStructure(optimizedBytes));
|
|
916
|
+
if (!bytesAreValidImage) {
|
|
917
|
+
log.warn(
|
|
918
|
+
{ filename: name, attachmentId: entry.attachmentId },
|
|
919
|
+
"Retained image bytes are not a valid image after transport optimization; dropping",
|
|
920
|
+
);
|
|
921
|
+
continue;
|
|
922
|
+
}
|
|
894
923
|
blocks.push({
|
|
895
924
|
type: "image",
|
|
896
925
|
source: {
|