@vellumai/assistant 0.12.2 → 0.12.3-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/.env.example +2 -0
- package/AGENTS.md +1 -1
- package/docs/architecture/integrations.md +7 -0
- package/docs/architecture/memory.md +17 -2
- package/docs/credential-execution-service.md +1 -1
- package/docs/desktop-browser-cli.md +7 -3
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/secret-detection.test.ts +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/secret-detection.ts +4 -1
- package/node_modules/@vellumai/environments/src/shell.test.ts +21 -0
- package/node_modules/@vellumai/environments/src/shell.ts +24 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/secret-detection.test.ts +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/secret-detection.ts +4 -1
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +8 -2
- package/node_modules/@vellumai/service-contracts/src/__tests__/secret-detection.test.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/secret-detection.ts +4 -1
- package/openapi.yaml +54 -5
- package/package.json +1 -1
- package/scripts/smoke-desktop-browser-cli.ts +1 -0
- package/scripts/sync-llm-catalog.ts +3 -0
- package/scripts/voice-ttft-spike.ts +2 -2
- package/src/__tests__/agent-loop.test.ts +259 -0
- package/src/__tests__/agent-wake-delegation-prompt.test.ts +64 -2
- package/src/__tests__/anthropic-provider.test.ts +55 -0
- package/src/__tests__/approval-interception-trust-gates.test.ts +40 -0
- package/src/__tests__/attachments-store.test.ts +22 -3
- package/src/__tests__/channel-approval.test.ts +9 -14
- package/src/__tests__/channel-reply-delivery.test.ts +56 -0
- package/src/__tests__/chat-credential-redaction.test.ts +23 -0
- package/src/__tests__/computer-use-screenshot-attachments.test.ts +411 -0
- package/src/__tests__/computer-use-screenshot-selection.test.ts +50 -0
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop.test.ts +257 -6
- package/src/__tests__/conversation-attachments.test.ts +106 -0
- package/src/__tests__/conversation-fork-crud.test.ts +191 -0
- package/src/__tests__/conversation-rendered-delegation-state.test.ts +276 -0
- package/src/__tests__/credential-execution-client.test.ts +70 -53
- package/src/__tests__/db-conversation-tool-surface.test.ts +221 -0
- package/src/__tests__/events-tail-route.test.ts +33 -0
- package/src/__tests__/history-repair.test.ts +53 -0
- package/src/__tests__/inference-profile-session-handler.test.ts +26 -0
- package/src/__tests__/list-messages-tool-merge.test.ts +85 -1
- package/src/__tests__/llm-catalog-parity.test.ts +27 -4
- package/src/__tests__/oauth-apps-routes.test.ts +1 -0
- package/src/__tests__/oauth-commands-routes.test.ts +114 -101
- package/src/__tests__/oauth-connect-orchestrator.test.ts +2 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +1 -0
- package/src/__tests__/oauth-providers-routes.test.ts +2 -0
- package/src/__tests__/persist-media-references.test.ts +50 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +0 -1
- package/src/__tests__/run-conversation-turn-persistence.test.ts +138 -1
- package/src/__tests__/schedule-routes.test.ts +20 -0
- package/src/__tests__/scheduler-result-notification.test.ts +23 -4
- package/src/__tests__/script-proxy-certs.test.ts +1 -1
- package/src/__tests__/secret-routes-platform-proxy.test.ts +86 -31
- package/src/__tests__/secret-routes-scrub.test.ts +22 -27
- package/src/__tests__/secret-scanner.test.ts +20 -0
- package/src/__tests__/secure-keys-managed-failover.test.ts +8 -0
- package/src/__tests__/secure-keys.test.ts +7 -3
- package/src/__tests__/server-tool-pairing.test.ts +107 -0
- package/src/__tests__/skills.test.ts +5 -4
- package/src/__tests__/subagent-tool-gate-mode.test.ts +226 -0
- package/src/__tests__/terminal-tools.test.ts +8 -0
- package/src/__tests__/tool-result-follow-up.test.ts +99 -0
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +63 -0
- package/src/__tests__/unicode.test.ts +36 -0
- package/src/agent/loop.ts +37 -12
- package/src/agent/tool-result-follow-up.ts +70 -0
- package/src/api/attachment-provenance.test.ts +68 -0
- package/src/api/computer-use-tool.test.ts +55 -0
- package/src/api/computer-use-tool.ts +27 -0
- package/src/api/events/assistant-outbound-attachment.ts +3 -0
- package/src/api/events/desktop-activity-changed.ts +10 -0
- package/src/api/events/question-request.ts +1 -0
- package/src/api/index.ts +11 -0
- package/src/api/responses/conversation-message.ts +3 -0
- package/src/approvals/approval-primitive.ts +5 -2
- package/src/approvals/scoped-approval-grants.ts +6 -2
- package/src/browser/virtual-desktop-target.ts +1 -2
- package/src/cli/commands/__tests__/cli-test-harness.ts +21 -3
- package/src/cli/commands/__tests__/plugins.test.ts +20 -1
- package/src/cli/commands/__tests__/schedules.test.ts +14 -0
- package/src/cli/commands/bash.help.ts +4 -3
- package/src/cli/commands/browser.help.ts +5 -1
- package/src/cli/commands/credentials.help.ts +3 -3
- package/src/cli/commands/oauth/index.help.ts +10 -0
- package/src/cli/commands/oauth/providers.ts +12 -0
- package/src/cli/commands/oauth/request.test.ts +179 -2
- package/src/cli/commands/oauth/request.ts +41 -10
- package/src/cli/commands/plugins.ts +12 -5
- package/src/cli/commands/schedules.ts +2 -0
- package/src/cli/lib/__tests__/inspect-plugin.test.ts +54 -0
- package/src/cli/lib/__tests__/install-from-github.test.ts +41 -0
- package/src/cli/lib/__tests__/local-plugin-upgrade.test.ts +133 -51
- package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +8 -2
- package/src/cli/lib/bundled-marketplace.json +855 -0
- package/src/cli/lib/bundled-plugin-packages.json +783 -1
- package/src/cli/lib/inspect-plugin.ts +11 -4
- package/src/cli/lib/upgrade-plugin.ts +19 -11
- package/src/cli/output.ts +18 -4
- package/src/config/__tests__/default-provider.test.ts +4 -0
- package/src/config/bundled-skills/schedule/SKILL.md +6 -6
- package/src/config/loader.ts +9 -17
- package/src/config/platform-identity.ts +8 -8
- package/src/config/profile-text-generation.test.ts +51 -0
- package/src/config/profile-text-generation.ts +51 -0
- package/src/config/schemas/__tests__/memory-v3.test.ts +12 -0
- package/src/config/schemas/llm.ts +7 -2
- package/src/config/schemas/mcp.ts +5 -1
- package/src/config/schemas/memory-v3.ts +12 -0
- package/src/credential-execution/ces-runtime.ts +2 -2
- package/src/credential-execution/executable-discovery.ts +36 -134
- package/src/credential-execution/process-manager.test.ts +20 -24
- package/src/credential-execution/process-manager.ts +24 -19
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +43 -0
- package/src/daemon/__tests__/turn-tail-deleted-conversation.test.ts +36 -0
- package/src/daemon/assistant-attachments.ts +20 -12
- package/src/daemon/chat-credential-redaction.ts +16 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +74 -3
- package/src/daemon/conversation-agent-loop.ts +12 -0
- package/src/daemon/conversation-attachments.ts +93 -12
- package/src/daemon/conversation-tool-setup.ts +71 -3
- package/src/daemon/conversation-turn-finalize.ts +26 -13
- package/src/daemon/conversation.ts +72 -17
- package/src/daemon/daemon-control.ts +3 -7
- package/src/daemon/lifecycle.ts +4 -4
- package/src/daemon/mcp-reload-service.ts +1 -1
- package/src/daemon/message-types/shared.ts +1 -0
- package/src/daemon/orphan-reaper.ts +4 -3
- package/src/daemon/persist-media-references.ts +20 -3
- package/src/daemon/tool-setup-types.ts +6 -0
- package/src/daemon/wake-conversation-ops.ts +50 -15
- package/src/desktop/desktop-automation-lease.test.ts +230 -0
- package/src/desktop/desktop-automation-lease.ts +90 -6
- package/src/desktop/desktop-help.ts +15 -0
- package/src/desktop/desktop-stream-bridge.test.ts +65 -10
- package/src/desktop/desktop-stream-bridge.ts +3 -3
- package/src/desktop/desktop-wallpaper-renderer.ts +132 -0
- package/src/desktop/desktop-wallpaper-worker.ts +17 -0
- package/src/desktop/desktop-wallpaper.test.ts +48 -7
- package/src/desktop/desktop-wallpaper.ts +34 -102
- package/src/desktop/virtual-desktop-feature.ts +1 -1
- package/src/mcp/__tests__/manager-tool-caps.test.ts +111 -0
- package/src/mcp/__tests__/startup.test.ts +30 -7
- package/src/mcp/__tests__/tool-caps.test.ts +107 -0
- package/src/mcp/manager.ts +168 -101
- package/src/mcp/startup.ts +33 -13
- package/src/mcp/tool-caps.ts +176 -0
- package/src/messaging/provider-message-metadata.ts +3 -3
- package/src/notifications/__tests__/copy-composer.test.ts +70 -0
- package/src/notifications/copy-composer.ts +11 -3
- package/src/notifications/schedule-result-producer.ts +4 -6
- package/src/oauth/AGENTS.md +2 -0
- package/src/oauth/__tests__/identity-verifier.test.ts +36 -1
- package/src/oauth/identity-verifier.ts +24 -0
- package/src/oauth/oauth-store.ts +11 -1
- package/src/oauth/provider-serializer.ts +1 -0
- package/src/oauth/seed-providers.ts +12 -0
- package/src/permissions/confirmation-guardian-request.test.ts +16 -0
- package/src/permissions/confirmation-guardian-request.ts +2 -3
- package/src/permissions/question-prompter.test.ts +31 -0
- package/src/permissions/question-prompter.ts +2 -0
- package/src/persistence/attachments-store.ts +84 -44
- package/src/persistence/conversation-crud.ts +111 -20
- package/src/persistence/conversation-plugin-facade.ts +30 -0
- package/src/persistence/conversation-tool-surface.ts +135 -0
- package/src/persistence/conversation-types.test.ts +32 -0
- package/src/persistence/conversation-types.ts +26 -4
- package/src/persistence/migrations/378-create-conversation-tool-surfaces.test.ts +78 -0
- package/src/persistence/migrations/378-create-conversation-tool-surfaces.ts +29 -0
- package/src/persistence/migrations/379-oauth-providers-response-ok-field.test.ts +92 -0
- package/src/persistence/migrations/379-oauth-providers-response-ok-field.ts +32 -0
- package/src/persistence/migrations/380-conversation-tool-surfaces-delegate-independent-tasks.test.ts +90 -0
- package/src/persistence/migrations/380-conversation-tool-surfaces-delegate-independent-tasks.ts +34 -0
- package/src/persistence/schema/conversation-tool-surfaces.ts +32 -0
- package/src/persistence/schema/index.ts +1 -0
- package/src/persistence/schema/oauth.ts +1 -0
- package/src/persistence/steps.ts +24 -3
- package/src/plugin-api/conversation-turn.ts +31 -7
- package/src/plugin-api/index.ts +11 -1
- package/src/plugin-api/plugin-channel-turn-trust.test.ts +133 -0
- package/src/plugin-api/plugin-channel-turn-trust.ts +71 -0
- package/src/plugins/defaults/memory/AGENTS.md +39 -5
- package/src/plugins/defaults/memory/__tests__/buffer-file.test.ts +365 -0
- package/src/plugins/defaults/memory/__tests__/buffer-format.test.ts +43 -0
- package/src/plugins/defaults/memory/__tests__/conversation-memory-purge.test.ts +1 -0
- package/src/plugins/defaults/memory/__tests__/db-memory-attach.test.ts +2 -0
- package/src/plugins/defaults/memory/__tests__/fixtures/buffer-appender.ts +17 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +74 -0
- package/src/plugins/defaults/memory/__tests__/memory-run-evidence.test.ts +161 -0
- package/src/plugins/defaults/memory/__tests__/relocated-memory-test-rows.ts +10 -0
- package/src/plugins/defaults/memory/buffer-file.ts +354 -0
- package/src/plugins/defaults/memory/buffer-format.ts +40 -0
- package/src/plugins/defaults/memory/context-search/agent-runner.ts +1 -2
- package/src/plugins/defaults/memory/context-search/format.ts +2 -1
- package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +2 -1
- package/src/plugins/defaults/memory/context-search/sources/workspace.ts +2 -1
- package/src/plugins/defaults/memory/conversation-memory-purge.ts +4 -0
- package/src/plugins/defaults/memory/graph/capability-seed.ts +1 -2
- package/src/plugins/defaults/memory/graph/tool-handlers.ts +1 -42
- package/src/plugins/defaults/memory/host-utils.ts +0 -10
- package/src/plugins/defaults/memory/injectors.ts +4 -3
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +72 -183
- package/src/plugins/defaults/memory/memory-run-evidence.ts +213 -0
- package/src/plugins/defaults/memory/src/memory-item-routes.test.ts +1 -1
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +407 -99
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +10 -0
- package/src/plugins/defaults/memory/substrate/__tests__/prompts-consolidation.test.ts +107 -7
- package/src/plugins/defaults/memory/substrate/consolidation-job.ts +307 -86
- package/src/plugins/defaults/memory/substrate/consolidation-tool-surface.ts +34 -0
- package/src/plugins/defaults/memory/substrate/page-index.ts +2 -1
- package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +89 -49
- package/src/plugins/defaults/memory/substrate/sweep-job.ts +1 -1
- package/src/plugins/defaults/memory/tools.ts +1 -1
- package/src/plugins/defaults/memory/v1/graph/consolidation.ts +2 -2
- package/src/plugins/defaults/memory/v1/graph/extraction.ts +2 -1
- package/src/plugins/defaults/memory/v1/graph/retriever.ts +1 -1
- package/src/plugins/defaults/memory/v2/__tests__/migration.test.ts +5 -0
- package/src/plugins/defaults/memory/v2/__tests__/reranker.test.ts +5 -2
- package/src/plugins/defaults/memory/v2/reranker.ts +2 -1
- package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +81 -1
- package/src/plugins/defaults/memory/v3/__tests__/orchestrate.test.ts +87 -0
- package/src/plugins/defaults/memory/v3/__tests__/plugin-schema.test.ts +12 -0
- package/src/plugins/defaults/memory/v3/__tests__/pool-log-store.test.ts +183 -4
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +103 -2
- package/src/plugins/defaults/memory/v3/card.ts +2 -1
- package/src/plugins/defaults/memory/v3/injector.ts +212 -178
- package/src/plugins/defaults/memory/v3/orchestrate.ts +112 -25
- package/src/plugins/defaults/memory/v3/plugin-schema.ts +54 -4
- package/src/plugins/defaults/memory/v3/pool-log-store.ts +253 -1
- package/src/plugins/defaults/memory/v3/pool-select.test.ts +35 -2
- package/src/plugins/defaults/memory/v3/pool-select.ts +31 -19
- package/src/plugins/defaults/memory/v3/sections.ts +2 -1
- package/src/plugins/defaults/memory/v3/shadow-plugin.ts +41 -14
- package/src/plugins/defaults/tool-error/hooks/post-tool-use.ts +4 -1
- package/src/plugins/defaults/tool-result-truncate/terminal.ts +1 -46
- package/src/prompts/__tests__/parallel-tasks-section.test.ts +25 -0
- package/src/prompts/delegation-gate.ts +57 -0
- package/src/prompts/system-prompt.ts +14 -32
- package/src/providers/inference/adapter-factory.ts +6 -0
- package/src/providers/jev/client.test.ts +260 -0
- package/src/providers/jev/client.ts +518 -0
- package/src/providers/model-catalog.ts +58 -3
- package/src/providers/server-tool-pairing.ts +16 -7
- package/src/runtime/AGENTS.md +2 -2
- package/src/runtime/__tests__/agent-wake.test.ts +94 -1
- package/src/runtime/agent-wake.ts +31 -4
- package/src/runtime/guardian-action-service.ts +2 -17
- package/src/runtime/guardian-reply-router.ts +1 -8
- package/src/runtime/http-server.ts +2 -2
- package/src/runtime/migrations/__tests__/vbundle-import-policy.test.ts +89 -0
- package/src/runtime/migrations/vbundle-import-policy.ts +33 -11
- package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +24 -2
- package/src/runtime/routes/channel-route-shared.ts +1 -9
- package/src/runtime/routes/conversation-routes.ts +29 -4
- package/src/runtime/routes/desktop-setup-routes.test.ts +2 -2
- package/src/runtime/routes/desktop-setup-routes.ts +8 -5
- package/src/runtime/routes/guardian-approval-interception.ts +24 -0
- package/src/runtime/routes/inbound-message-handler.ts +2 -3
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +1 -1
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +7 -4
- package/src/runtime/routes/inference-profile-session-handler.ts +11 -1
- package/src/runtime/routes/inference-profiles-routes.ts +9 -0
- package/src/runtime/routes/oauth-commands-routes.ts +57 -54
- package/src/runtime/routes/oauth-providers.ts +4 -0
- package/src/runtime/routes/oauth-request-hints.test.ts +246 -0
- package/src/runtime/routes/oauth-request-hints.ts +149 -0
- package/src/runtime/routes/schedule-routes.ts +2 -0
- package/src/runtime/routes/secret-routes.ts +99 -119
- package/src/schedule/__tests__/worker-mcp-bootstrap.test.ts +107 -0
- package/src/schedule/__tests__/worker-mcp-readiness.test.ts +173 -0
- package/src/schedule/__tests__/worker-mcp-tools.test.ts +13 -47
- package/src/schedule/run-script.ts +2 -2
- package/src/schedule/scheduler.ts +47 -11
- package/src/schedule/tool-surface-readiness.ts +65 -0
- package/src/schedule/worker-mcp.ts +85 -0
- package/src/schedule/worker.ts +9 -31
- package/src/security/secure-keys.ts +60 -48
- package/src/tools/ask-question/ask-question-tool.test.ts +167 -5
- package/src/tools/ask-question/ask-question-tool.ts +119 -23
- package/src/tools/browser/browser-execution.ts +7 -1
- package/src/tools/host-terminal/host-shell.ts +12 -6
- package/src/tools/shared/filesystem/file-ops-service.ts +1 -31
- package/src/tools/shared/shell-output.test.ts +10 -0
- package/src/tools/shared/shell-output.ts +14 -2
- package/src/tools/skills/sandbox-runner.ts +13 -2
- package/src/tools/skills/scaffold-managed.ts +2 -1
- package/src/tools/terminal/__tests__/safe-env.test.ts +33 -4
- package/src/tools/terminal/__tests__/sanitized-bash.test.ts +0 -14
- package/src/tools/terminal/safe-env.ts +35 -24
- package/src/tools/terminal/sanitized-bash.ts +15 -2
- package/src/tools/terminal/shell-launch.test.ts +162 -0
- package/src/tools/terminal/shell.test.ts +29 -0
- package/src/tools/terminal/shell.ts +13 -7
- package/src/util/browser-human-verification.ts +14 -0
- package/src/util/host-process.test.ts +17 -1
- package/src/util/host-process.ts +24 -0
- package/src/util/unicode.ts +29 -0
|
@@ -17,6 +17,7 @@ let handleRequestResult: {
|
|
|
17
17
|
body: unknown;
|
|
18
18
|
bodyEncoding?: "base64";
|
|
19
19
|
account?: string | null;
|
|
20
|
+
hint?: string;
|
|
20
21
|
} = {
|
|
21
22
|
ok: true,
|
|
22
23
|
status: 200,
|
|
@@ -71,18 +72,71 @@ afterEach(() => {
|
|
|
71
72
|
process.exitCode = 0;
|
|
72
73
|
});
|
|
73
74
|
|
|
74
|
-
|
|
75
|
+
/**
|
|
76
|
+
* Runs the command with both streams captured and `process.exit` recorded.
|
|
77
|
+
* The stream stubs honour a write callback the way the real streams do, so an
|
|
78
|
+
* exit that rides the last write lands in `exitCalls`, and only after every
|
|
79
|
+
* chunk written before it has been captured. `holdStderr` keeps stderr
|
|
80
|
+
* callbacks back and returns them, for the case where stderr is still in
|
|
81
|
+
* flight when stdout has drained.
|
|
82
|
+
*/
|
|
83
|
+
async function runRequestCommand(
|
|
84
|
+
args: string[],
|
|
85
|
+
options: { holdStderr?: boolean } = {},
|
|
86
|
+
): Promise<{
|
|
75
87
|
stdout: Buffer;
|
|
88
|
+
stderr: string;
|
|
76
89
|
exitCode: number;
|
|
90
|
+
exitCalls: number[];
|
|
91
|
+
heldStderrCallbacks: Array<() => void>;
|
|
77
92
|
}> {
|
|
78
93
|
const chunks: Buffer[] = [];
|
|
94
|
+
const stderrChunks: string[] = [];
|
|
95
|
+
const exitCalls: number[] = [];
|
|
96
|
+
const heldStderrCallbacks: Array<() => void> = [];
|
|
79
97
|
const originalWrite = process.stdout.write;
|
|
80
|
-
process.
|
|
98
|
+
const originalStderrWrite = process.stderr.write;
|
|
99
|
+
const originalExit = process.exit;
|
|
100
|
+
const callbackOf = (rest: unknown[]): (() => void) | undefined =>
|
|
101
|
+
rest.find((arg) => typeof arg === "function") as (() => void) | undefined;
|
|
102
|
+
process.stdout.write = ((chunk: string | Uint8Array, ...rest: unknown[]) => {
|
|
81
103
|
chunks.push(
|
|
82
104
|
typeof chunk === "string" ? Buffer.from(chunk) : Buffer.from(chunk),
|
|
83
105
|
);
|
|
106
|
+
callbackOf(rest)?.();
|
|
84
107
|
return true;
|
|
85
108
|
}) as typeof process.stdout.write;
|
|
109
|
+
process.stderr.write = ((chunk: string | Uint8Array, ...rest: unknown[]) => {
|
|
110
|
+
stderrChunks.push(String(chunk));
|
|
111
|
+
const callback = callbackOf(rest);
|
|
112
|
+
if (callback) {
|
|
113
|
+
if (options.holdStderr) {
|
|
114
|
+
heldStderrCallbacks.push(holdCallback(callback));
|
|
115
|
+
} else {
|
|
116
|
+
callback();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return true;
|
|
120
|
+
}) as typeof process.stderr.write;
|
|
121
|
+
const recordExit = ((code?: number) => {
|
|
122
|
+
exitCalls.push(code ?? Number(process.exitCode ?? 0));
|
|
123
|
+
}) as typeof process.exit;
|
|
124
|
+
process.exit = recordExit;
|
|
125
|
+
// A held callback runs after this function has restored the real
|
|
126
|
+
// `process.exit`, so it reinstalls the recorder for the duration of the
|
|
127
|
+
// call; the exit it triggers must land in `exitCalls`, never end the test
|
|
128
|
+
// process.
|
|
129
|
+
const holdCallback = (callback: () => void): (() => void) => {
|
|
130
|
+
return () => {
|
|
131
|
+
const current = process.exit;
|
|
132
|
+
process.exit = recordExit;
|
|
133
|
+
try {
|
|
134
|
+
callback();
|
|
135
|
+
} finally {
|
|
136
|
+
process.exit = current;
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
};
|
|
86
140
|
|
|
87
141
|
try {
|
|
88
142
|
const program = new Command();
|
|
@@ -101,11 +155,16 @@ async function runRequestCommand(args: string[]): Promise<{
|
|
|
101
155
|
// Commander may throw under exitOverride for parse errors.
|
|
102
156
|
} finally {
|
|
103
157
|
process.stdout.write = originalWrite;
|
|
158
|
+
process.stderr.write = originalStderrWrite;
|
|
159
|
+
process.exit = originalExit;
|
|
104
160
|
}
|
|
105
161
|
|
|
106
162
|
return {
|
|
107
163
|
stdout: Buffer.concat(chunks),
|
|
164
|
+
stderr: stderrChunks.join(""),
|
|
108
165
|
exitCode: Number(process.exitCode ?? 0),
|
|
166
|
+
exitCalls,
|
|
167
|
+
heldStderrCallbacks,
|
|
109
168
|
};
|
|
110
169
|
}
|
|
111
170
|
|
|
@@ -308,6 +367,124 @@ describe("oauth request body output", () => {
|
|
|
308
367
|
expect(parsed.bodyEncoding).toBe("base64");
|
|
309
368
|
});
|
|
310
369
|
|
|
370
|
+
test("exits non-zero when the route reports failure inside a 2xx", async () => {
|
|
371
|
+
// The exit code follows the route's verdict, so a caller that only checks
|
|
372
|
+
// the exit code sees a refused call as a failure; the body still prints,
|
|
373
|
+
// since it carries the provider's own error code.
|
|
374
|
+
handleRequestResult = {
|
|
375
|
+
ok: false,
|
|
376
|
+
status: 200,
|
|
377
|
+
headers: { "content-type": "application/json" },
|
|
378
|
+
body: { ok: false, error: "not_in_channel" },
|
|
379
|
+
account: "user@example.com",
|
|
380
|
+
};
|
|
381
|
+
const { stdout, exitCode } = await runRequestCommand([
|
|
382
|
+
"--provider",
|
|
383
|
+
"slack_channel",
|
|
384
|
+
"-s",
|
|
385
|
+
"/chat.postMessage",
|
|
386
|
+
]);
|
|
387
|
+
|
|
388
|
+
expect(exitCode).toBe(1);
|
|
389
|
+
expect(stdout.toString("utf8")).toContain("not_in_channel");
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
test("exits itself once the whole body has drained", async () => {
|
|
393
|
+
// The in-process route leaves handles open, so the command has to exit;
|
|
394
|
+
// and it must do so on the write callback, because a bare exit after a
|
|
395
|
+
// large piped write drops everything past the first 64 KB.
|
|
396
|
+
const body = "x".repeat(200_000);
|
|
397
|
+
handleRequestResult = {
|
|
398
|
+
ok: true,
|
|
399
|
+
status: 200,
|
|
400
|
+
headers: { "content-type": "text/plain" },
|
|
401
|
+
body,
|
|
402
|
+
account: "user@example.com",
|
|
403
|
+
};
|
|
404
|
+
const { stdout, exitCode, exitCalls } = await runRequestCommand([
|
|
405
|
+
"--provider",
|
|
406
|
+
"google",
|
|
407
|
+
"-s",
|
|
408
|
+
"https://api.google.com/v1/big",
|
|
409
|
+
]);
|
|
410
|
+
|
|
411
|
+
expect(stdout.toString("utf8")).toBe(body + "\n");
|
|
412
|
+
expect(exitCode).toBe(0);
|
|
413
|
+
expect(exitCalls).toEqual([0]);
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
test("exits after writing an empty body to a file", async () => {
|
|
417
|
+
const target = join(tempDir, "empty.bin");
|
|
418
|
+
handleRequestResult = {
|
|
419
|
+
ok: true,
|
|
420
|
+
status: 204,
|
|
421
|
+
headers: {},
|
|
422
|
+
body: null,
|
|
423
|
+
account: "user@example.com",
|
|
424
|
+
};
|
|
425
|
+
const { exitCalls } = await runRequestCommand([
|
|
426
|
+
"--provider",
|
|
427
|
+
"google",
|
|
428
|
+
"-s",
|
|
429
|
+
"-o",
|
|
430
|
+
target,
|
|
431
|
+
"https://api.google.com/v1/none",
|
|
432
|
+
]);
|
|
433
|
+
|
|
434
|
+
expect(readFileSync(target)).toHaveLength(0);
|
|
435
|
+
expect(exitCalls).toEqual([0]);
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
test("waits for stderr to drain before exiting when stdout carried nothing", async () => {
|
|
439
|
+
// With `-o`, stdout carries nothing, so the exit cannot ride a stdout
|
|
440
|
+
// write, and the diagnostics on stderr (the account line, a hint) may
|
|
441
|
+
// still be in flight. The exit waits for the last of them.
|
|
442
|
+
const target = join(tempDir, "body.json");
|
|
443
|
+
handleRequestResult = {
|
|
444
|
+
ok: false,
|
|
445
|
+
status: 200,
|
|
446
|
+
headers: { "content-type": "application/json" },
|
|
447
|
+
body: { ok: false, error: "not_in_channel" },
|
|
448
|
+
account: "user@example.com",
|
|
449
|
+
hint: "slack_channel answered HTTP 200 but reported ok: false",
|
|
450
|
+
};
|
|
451
|
+
const { stderr, exitCalls, heldStderrCallbacks } = await runRequestCommand(
|
|
452
|
+
["--provider", "slack_channel", "-o", target, "/chat.postMessage"],
|
|
453
|
+
{ holdStderr: true },
|
|
454
|
+
);
|
|
455
|
+
|
|
456
|
+
expect(stderr).toContain("Account: user@example.com");
|
|
457
|
+
expect(stderr).toContain("ok: false");
|
|
458
|
+
expect(heldStderrCallbacks).toHaveLength(2);
|
|
459
|
+
expect(exitCalls).toEqual([]);
|
|
460
|
+
|
|
461
|
+
heldStderrCallbacks[0]();
|
|
462
|
+
expect(exitCalls).toEqual([]);
|
|
463
|
+
heldStderrCallbacks[1]();
|
|
464
|
+
expect(exitCalls).toEqual([1]);
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
test("a stdout body waits for stderr diagnostics written before it", async () => {
|
|
468
|
+
handleRequestResult = {
|
|
469
|
+
ok: true,
|
|
470
|
+
status: 200,
|
|
471
|
+
headers: { "content-type": "application/json" },
|
|
472
|
+
body: { hello: "world" },
|
|
473
|
+
account: "user@example.com",
|
|
474
|
+
};
|
|
475
|
+
const { stdout, exitCalls, heldStderrCallbacks } = await runRequestCommand(
|
|
476
|
+
["--provider", "google", "https://api.google.com/v1/me"],
|
|
477
|
+
{ holdStderr: true },
|
|
478
|
+
);
|
|
479
|
+
|
|
480
|
+
expect(stdout.toString("utf8")).toContain("hello");
|
|
481
|
+
expect(heldStderrCallbacks).toHaveLength(1);
|
|
482
|
+
expect(exitCalls).toEqual([]);
|
|
483
|
+
|
|
484
|
+
heldStderrCallbacks[0]();
|
|
485
|
+
expect(exitCalls).toEqual([0]);
|
|
486
|
+
});
|
|
487
|
+
|
|
311
488
|
test("writes text bodies as UTF-8 and appends a newline on stdout", async () => {
|
|
312
489
|
handleRequestResult = {
|
|
313
490
|
ok: true,
|
|
@@ -142,11 +142,38 @@ export async function runAuthenticatedRequest(params: {
|
|
|
142
142
|
const { providerKey, url, opts, cmd } = params;
|
|
143
143
|
const jsonMode = shouldOutputJson(cmd);
|
|
144
144
|
|
|
145
|
+
// The command exits itself, once its last write on either stream has
|
|
146
|
+
// drained. Running the route in-process leaves handles open that the event
|
|
147
|
+
// loop waits on (the lazy CES connection the credential resolves through is
|
|
148
|
+
// never closed by the CLI), so returning from the action would leave the
|
|
149
|
+
// process alive after a complete response. A bare `process.exit()` is not
|
|
150
|
+
// the answer either: with a pipe on the other end, Bun flushes what the
|
|
151
|
+
// pipe accepts and drops the rest, so the exit rides write callbacks, which
|
|
152
|
+
// fire when the bytes are out. Stdout's last write carries `exit` itself;
|
|
153
|
+
// stderr's writes are counted, so an exit requested while one is still in
|
|
154
|
+
// flight waits for it. The pipe's capacity is not a bound to lean on: a
|
|
155
|
+
// busy host can shrink an unprivileged pipe to a single page.
|
|
156
|
+
let stderrInFlight = 0;
|
|
157
|
+
let exitRequested = false;
|
|
158
|
+
const exit = (): void => {
|
|
159
|
+
exitRequested = true;
|
|
160
|
+
if (stderrInFlight === 0) {
|
|
161
|
+
process.exit(Number(process.exitCode ?? 0));
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
145
165
|
// Helper: write info to stderr (respects -s)
|
|
146
166
|
const writeInfo = (msg: string): void => {
|
|
147
|
-
if (
|
|
148
|
-
|
|
167
|
+
if (opts.silent) {
|
|
168
|
+
return;
|
|
149
169
|
}
|
|
170
|
+
stderrInFlight += 1;
|
|
171
|
+
process.stderr.write(msg + "\n", () => {
|
|
172
|
+
stderrInFlight -= 1;
|
|
173
|
+
if (exitRequested) {
|
|
174
|
+
exit();
|
|
175
|
+
}
|
|
176
|
+
});
|
|
150
177
|
};
|
|
151
178
|
|
|
152
179
|
try {
|
|
@@ -232,17 +259,18 @@ export async function runAuthenticatedRequest(params: {
|
|
|
232
259
|
// reported the way every other failure here is, so `--json` gets its
|
|
233
260
|
// envelope and the caller's diagnostics hint is not lost; only the
|
|
234
261
|
// exit code comes from the route's status.
|
|
235
|
-
writeError(cmd, `${err.message}\n\n${params.diagnosticsHint}`);
|
|
236
262
|
process.exitCode = exitCodeFromIpcResult({
|
|
237
263
|
statusCode: err.statusCode,
|
|
238
264
|
});
|
|
265
|
+
writeError(cmd, `${err.message}\n\n${params.diagnosticsHint}`, exit);
|
|
239
266
|
return;
|
|
240
267
|
}
|
|
241
268
|
throw err;
|
|
242
269
|
}
|
|
243
270
|
|
|
244
|
-
//
|
|
245
|
-
|
|
271
|
+
// The route's verdict, not the status code: a provider that reports
|
|
272
|
+
// failure inside a 2xx (Slack's `ok: false`) is a failed request.
|
|
273
|
+
if (!result.ok) {
|
|
246
274
|
process.exitCode = 1;
|
|
247
275
|
}
|
|
248
276
|
|
|
@@ -261,7 +289,7 @@ export async function runAuthenticatedRequest(params: {
|
|
|
261
289
|
|
|
262
290
|
// JSON output mode
|
|
263
291
|
if (jsonMode) {
|
|
264
|
-
writeOutput(cmd, result);
|
|
292
|
+
writeOutput(cmd, result, exit);
|
|
265
293
|
return;
|
|
266
294
|
}
|
|
267
295
|
|
|
@@ -282,20 +310,23 @@ export async function runAuthenticatedRequest(params: {
|
|
|
282
310
|
if (output) {
|
|
283
311
|
if (opts.output) {
|
|
284
312
|
writeFileSync(opts.output, output.bytes);
|
|
313
|
+
} else if (output.isBinary) {
|
|
314
|
+
process.stdout.write(output.bytes, exit);
|
|
315
|
+
return;
|
|
285
316
|
} else {
|
|
286
317
|
process.stdout.write(output.bytes);
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
}
|
|
318
|
+
process.stdout.write("\n", exit);
|
|
319
|
+
return;
|
|
290
320
|
}
|
|
291
321
|
}
|
|
292
322
|
} else if (opts.output) {
|
|
293
323
|
writeFileSync(opts.output, Buffer.alloc(0));
|
|
294
324
|
}
|
|
325
|
+
exit();
|
|
295
326
|
} catch (err) {
|
|
296
327
|
const message = err instanceof Error ? err.message : String(err);
|
|
297
|
-
writeError(cmd, `${message}\n\n${params.diagnosticsHint}`);
|
|
298
328
|
process.exitCode = 1;
|
|
329
|
+
writeError(cmd, `${message}\n\n${params.diagnosticsHint}`, exit);
|
|
299
330
|
}
|
|
300
331
|
}
|
|
301
332
|
|
|
@@ -238,11 +238,18 @@ export function registerPluginsCommand(program: Command): void {
|
|
|
238
238
|
const platformEnabled =
|
|
239
239
|
libs.catalogLocal.arePlatformFeaturesEnabled();
|
|
240
240
|
if (platformEnabled) {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
241
|
+
let match;
|
|
242
|
+
try {
|
|
243
|
+
match = (
|
|
244
|
+
await libs.catalogCache.getPluginCatalog(
|
|
245
|
+
DEFAULT_PLUGIN_REF,
|
|
246
|
+
{ fetch: globalThis.fetch.bind(globalThis) },
|
|
247
|
+
)
|
|
248
|
+
).matches.find((candidate) => candidate.name === nameOrUrl);
|
|
249
|
+
} catch {
|
|
250
|
+
// The install endpoint remains authoritative and can succeed
|
|
251
|
+
// independently when catalog discovery is unavailable.
|
|
252
|
+
}
|
|
246
253
|
if (match?.source.kind === "local") {
|
|
247
254
|
result = await libs.installGitHub.installPlugin(
|
|
248
255
|
{
|
|
@@ -35,6 +35,7 @@ interface ScheduleRecord {
|
|
|
35
35
|
mode: string;
|
|
36
36
|
status: string;
|
|
37
37
|
routingIntent: string;
|
|
38
|
+
quiet: boolean;
|
|
38
39
|
reuseConversation: boolean;
|
|
39
40
|
wakeConversationId: string | null;
|
|
40
41
|
sourceKey: string | null;
|
|
@@ -268,6 +269,7 @@ export function registerSchedulesCommand(program: Command): void {
|
|
|
268
269
|
formatScheduleInferenceProfile(schedule.inferenceProfile),
|
|
269
270
|
],
|
|
270
271
|
["Routing intent", schedule.routingIntent],
|
|
272
|
+
["Quiet", schedule.quiet ? "yes" : "no"],
|
|
271
273
|
["Reuse conversation", schedule.reuseConversation ? "yes" : "no"],
|
|
272
274
|
["Wake conversation", schedule.wakeConversationId ?? "—"],
|
|
273
275
|
["Retry count", String(schedule.retryCount)],
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
PluginInspectNotFoundError,
|
|
20
20
|
} from "../inspect-plugin.js";
|
|
21
21
|
import { computeFingerprint } from "../plugin-fingerprint.js";
|
|
22
|
+
import type { PluginCatalog } from "../search-plugins.js";
|
|
22
23
|
|
|
23
24
|
const SHA_A = "a".repeat(40);
|
|
24
25
|
const SHA_B = "b".repeat(40);
|
|
@@ -305,6 +306,59 @@ describe("inspectPlugin", () => {
|
|
|
305
306
|
expect(result.remote?.commit).toBe(SHA_B);
|
|
306
307
|
});
|
|
307
308
|
|
|
309
|
+
test("previews an uninstalled bundled provider offline without fetching GitHub", async () => {
|
|
310
|
+
const savedDisablePlatform = process.env.VELLUM_DISABLE_PLATFORM;
|
|
311
|
+
const savedIsPlatform = process.env.IS_PLATFORM;
|
|
312
|
+
process.env.VELLUM_DISABLE_PLATFORM = "true";
|
|
313
|
+
delete process.env.IS_PLATFORM;
|
|
314
|
+
let fetchCalls = 0;
|
|
315
|
+
const fetch = (async () => {
|
|
316
|
+
fetchCalls += 1;
|
|
317
|
+
throw new Error("offline");
|
|
318
|
+
}) as FetchLike;
|
|
319
|
+
const localCatalog: PluginCatalog = {
|
|
320
|
+
ref: "bundled",
|
|
321
|
+
matches: [
|
|
322
|
+
{
|
|
323
|
+
name: "fathom",
|
|
324
|
+
path: "local:plugins/mcp-catalog/fathom@1.0.0",
|
|
325
|
+
category: "productivity",
|
|
326
|
+
source: {
|
|
327
|
+
kind: "local",
|
|
328
|
+
path: "plugins/mcp-catalog/fathom",
|
|
329
|
+
version: "1.0.0",
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
],
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
try {
|
|
336
|
+
const result = await inspectPlugin(
|
|
337
|
+
{ name: "fathom" },
|
|
338
|
+
{ fetch, workspacePluginsDir: workspace, localCatalog },
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
expect(result.status).toBe("not-installed");
|
|
342
|
+
expect(result.remote).toMatchObject({
|
|
343
|
+
kind: "local",
|
|
344
|
+
path: "plugins/mcp-catalog/fathom",
|
|
345
|
+
version: "1.0.0",
|
|
346
|
+
});
|
|
347
|
+
expect(fetchCalls).toBe(0);
|
|
348
|
+
} finally {
|
|
349
|
+
if (savedDisablePlatform === undefined) {
|
|
350
|
+
delete process.env.VELLUM_DISABLE_PLATFORM;
|
|
351
|
+
} else {
|
|
352
|
+
process.env.VELLUM_DISABLE_PLATFORM = savedDisablePlatform;
|
|
353
|
+
}
|
|
354
|
+
if (savedIsPlatform === undefined) {
|
|
355
|
+
delete process.env.IS_PLATFORM;
|
|
356
|
+
} else {
|
|
357
|
+
process.env.IS_PLATFORM = savedIsPlatform;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
|
|
308
362
|
test("reports remote-unavailable when the marketplace cannot be reached", async () => {
|
|
309
363
|
// GIVEN an installed plugin and a failing marketplace fetch
|
|
310
364
|
installPlugin(workspace, "level-up", { sidecar: { commit: SHA_A } });
|
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
resolveTreeRefPath,
|
|
40
40
|
sanitizePluginName,
|
|
41
41
|
} from "../install-from-github.js";
|
|
42
|
+
import { readValidatedPluginIcon } from "../plugin-icon-file.js";
|
|
42
43
|
|
|
43
44
|
const CANON_REPO = "vellum-ai/vellum-assistant";
|
|
44
45
|
/** Synthetic host the fixtures use for Contents API `download_url`s. */
|
|
@@ -320,6 +321,46 @@ describe("installPlugin — install lifecycle", () => {
|
|
|
320
321
|
]);
|
|
321
322
|
});
|
|
322
323
|
|
|
324
|
+
test("installs the reviewed Fathom package from the generated bundle", async () => {
|
|
325
|
+
const result = await installPlugin(
|
|
326
|
+
{
|
|
327
|
+
name: "fathom",
|
|
328
|
+
trustedSource: {
|
|
329
|
+
kind: "local",
|
|
330
|
+
path: "plugins/mcp-catalog/fathom",
|
|
331
|
+
version: "1.0.2",
|
|
332
|
+
},
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
fetch: (async () => {
|
|
336
|
+
throw new Error("local package install must not fetch");
|
|
337
|
+
}) as FetchLike,
|
|
338
|
+
runGit: unusedGitRunner,
|
|
339
|
+
workspacePluginsDir: pluginsDir,
|
|
340
|
+
},
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
expect(result).toMatchObject({
|
|
344
|
+
name: "fathom",
|
|
345
|
+
ref: "1.0.2",
|
|
346
|
+
commit: null,
|
|
347
|
+
});
|
|
348
|
+
expect(readValidatedPluginIcon(result.target).hasIcon).toBe(true);
|
|
349
|
+
expect(
|
|
350
|
+
readPluginMcpServers({ workspacePluginsDir: pluginsDir }).servers,
|
|
351
|
+
).toEqual([
|
|
352
|
+
expect.objectContaining({
|
|
353
|
+
pluginName: "fathom",
|
|
354
|
+
serverKey: "fathom",
|
|
355
|
+
config: expect.objectContaining({
|
|
356
|
+
transport: expect.objectContaining({
|
|
357
|
+
url: "https://api.fathom.ai/mcp",
|
|
358
|
+
}),
|
|
359
|
+
}),
|
|
360
|
+
}),
|
|
361
|
+
]);
|
|
362
|
+
});
|
|
363
|
+
|
|
323
364
|
test("refuses to overwrite an existing install without --force", async () => {
|
|
324
365
|
// GIVEN a plugin already installed at <pluginsDir>/caveman
|
|
325
366
|
const target = join(pluginsDir, "caveman");
|