@vellumai/assistant 0.8.2 → 0.8.3
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/ARCHITECTURE.md +11 -12
- package/docker-entrypoint.sh +13 -1
- package/docker-init-apt-root.sh +79 -6
- package/openapi.yaml +336 -21
- package/package.json +1 -1
- package/src/__tests__/agent-loop-exit-reason.test.ts +272 -0
- package/src/__tests__/agent-loop-provider-error-recording.test.ts +195 -0
- package/src/__tests__/compactor-tail-resolution.test.ts +107 -1
- package/src/__tests__/config-get-vision-flag.test.ts +136 -0
- package/src/__tests__/config-loader-backfill.test.ts +115 -18
- package/src/__tests__/context-token-estimator.test.ts +30 -65
- package/src/__tests__/conversation-agent-loop.test.ts +57 -1
- package/src/__tests__/conversation-media-retry.test.ts +19 -8
- package/src/__tests__/conversation-runtime-assembly.test.ts +26 -4
- package/src/__tests__/date-context.test.ts +45 -0
- package/src/__tests__/external-plugin-loader.test.ts +91 -19
- package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +0 -1
- package/src/__tests__/guardian-dispatch.test.ts +1 -0
- package/src/__tests__/heartbeat-service.test.ts +24 -164
- package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
- package/src/__tests__/host-app-control-proxy.test.ts +241 -0
- package/src/__tests__/host-proxy-preactivation.test.ts +200 -13
- package/src/__tests__/injector-background-turn.test.ts +153 -0
- package/src/__tests__/injector-chain.test.ts +5 -0
- package/src/__tests__/lifecycle-memory-v2-seed.test.ts +9 -2
- package/src/__tests__/llm-callsite-catalog.test.ts +25 -0
- package/src/__tests__/llm-catalog-parity.test.ts +3 -0
- package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +116 -0
- package/src/__tests__/llm-request-log-error-payload.test.ts +138 -0
- package/src/__tests__/llm-request-log-source-clickhouse.test.ts +2 -0
- package/src/__tests__/llm-resolver.test.ts +255 -2
- package/src/__tests__/managed-profile-guard.test.ts +10 -0
- package/src/__tests__/notification-decision-fallback.test.ts +0 -91
- package/src/__tests__/notification-decision-strategy.test.ts +14 -31
- package/src/__tests__/notification-deep-link.test.ts +15 -0
- package/src/__tests__/notification-guardian-path.test.ts +1 -2
- package/src/__tests__/notification-platform-adapter.test.ts +5 -4
- package/src/__tests__/notification-telegram-adapter.test.ts +1 -0
- package/src/__tests__/notification-vellum-adapter.test.ts +113 -0
- package/src/__tests__/openai-provider.test.ts +218 -3
- package/src/__tests__/openai-responses-cutover-guard.test.ts +3 -3
- package/src/__tests__/openrouter-provider-only.test.ts +51 -3
- package/src/__tests__/openrouter-token-estimation.test.ts +34 -25
- package/src/__tests__/platform-proxy-context.test.ts +6 -1
- package/src/__tests__/plugin-tool-contribution.test.ts +3 -3
- package/src/__tests__/plugin-types.test.ts +2 -2
- package/src/__tests__/provider-catalog-visibility.test.ts +16 -0
- package/src/__tests__/provider-platform-proxy-integration.test.ts +27 -25
- package/src/__tests__/secret-routes-platform-proxy.test.ts +1 -1
- package/src/__tests__/system-prompt.test.ts +6 -73
- package/src/__tests__/workspace-migration-087-memory-router-balanced-profile.test.ts +228 -0
- package/src/a2a/__tests__/agent-card.test.ts +98 -0
- package/src/a2a/__tests__/e2e-a2a-channel.test.ts +597 -0
- package/src/a2a/__tests__/protocol-helpers.test.ts +113 -0
- package/src/a2a/__tests__/task-store.test.ts +246 -0
- package/src/a2a/agent-card.ts +58 -0
- package/src/a2a/feature-gate.ts +8 -0
- package/src/a2a/protocol-constants.ts +21 -0
- package/src/a2a/protocol-errors.ts +50 -0
- package/src/a2a/protocol-types.ts +162 -0
- package/src/a2a/task-store.ts +168 -0
- package/src/agent/loop.ts +167 -18
- package/src/channels/config.ts +9 -0
- package/src/channels/types.ts +14 -0
- package/src/cli/{__tests__ → commands/__tests__}/notifications.test.ts +201 -28
- package/src/cli/commands/__tests__/schedules.test.ts +469 -0
- package/src/cli/commands/notifications.ts +65 -35
- package/src/cli/commands/plugins.ts +67 -0
- package/src/cli/commands/schedules.ts +297 -5
- package/src/cli/lib/__tests__/search-plugins.test.ts +261 -0
- package/src/cli/lib/install-from-github.ts +8 -9
- package/src/cli/lib/search-plugins.ts +163 -0
- package/src/cli/program.ts +14 -0
- package/src/config/assistant-feature-flags.ts +24 -54
- package/src/config/bundled-skills/app-builder/SKILL.md +117 -1
- package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
- package/src/config/call-site-defaults.ts +105 -0
- package/src/config/feature-flag-registry.json +21 -29
- package/src/config/llm-resolver.ts +52 -1
- package/src/config/schema.ts +2 -0
- package/src/config/schemas/__tests__/memory-v2.test.ts +3 -3
- package/src/config/schemas/channels.ts +9 -0
- package/src/config/schemas/conversations.ts +10 -0
- package/src/config/schemas/heartbeat.ts +14 -0
- package/src/config/schemas/llm.ts +1 -3
- package/src/config/schemas/memory-retrospective.ts +1 -1
- package/src/config/schemas/memory-v2.ts +4 -4
- package/src/config/schemas/memory.ts +3 -1
- package/src/config/seed-inference-profiles.ts +99 -29
- package/src/context/compactor.ts +72 -12
- package/src/context/token-estimator.ts +32 -34
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -22
- package/src/daemon/conversation-agent-loop-handlers.ts +78 -0
- package/src/daemon/conversation-agent-loop.ts +29 -2
- package/src/daemon/conversation-runtime-assembly.ts +9 -0
- package/src/daemon/conversation.ts +0 -7
- package/src/daemon/date-context.ts +40 -0
- package/src/daemon/guardian-action-generators.ts +1 -125
- package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +248 -0
- package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +154 -0
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +133 -0
- package/src/daemon/handlers/__tests__/config-a2a.test.ts +95 -0
- package/src/daemon/handlers/config-a2a.ts +289 -0
- package/src/daemon/handlers/conversations.ts +1 -0
- package/src/daemon/host-app-control-proxy.ts +69 -18
- package/src/daemon/host-proxy-preactivation.ts +85 -18
- package/src/daemon/lifecycle.ts +49 -61
- package/src/daemon/memory-v2-startup.ts +49 -13
- package/src/daemon/message-types/notifications.ts +21 -0
- package/src/daemon/pkb-reminder-builder.test.ts +10 -53
- package/src/daemon/pkb-reminder-builder.ts +4 -19
- package/src/daemon/process-message.ts +3 -0
- package/src/daemon/skill-memory-refresh.ts +5 -1
- package/src/daemon/wake-target-adapter.ts +2 -0
- package/src/export/__tests__/transcript-formatter.test.ts +121 -0
- package/src/export/transcript-formatter.ts +54 -20
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +44 -0
- package/src/heartbeat/heartbeat-service.ts +34 -191
- package/src/home/__tests__/feed-types.test.ts +40 -0
- package/src/home/feed-types.ts +14 -2
- package/src/ipc/cli-client.ts +147 -45
- package/src/memory/__tests__/conversation-queries.test.ts +220 -0
- package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +2 -50
- package/src/memory/__tests__/memory-retrospective-job.test.ts +87 -4
- package/src/memory/conversation-queries.ts +87 -1
- package/src/memory/conversation-title-service.ts +26 -4
- package/src/memory/db-init.ts +6 -0
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +84 -3
- package/src/memory/graph/conversation-graph-memory.ts +18 -6
- package/src/memory/graph/tools.ts +6 -37
- package/src/memory/invite-store.ts +53 -0
- package/src/memory/llm-request-log-source-clickhouse.ts +7 -2
- package/src/memory/llm-request-log-store.ts +92 -1
- package/src/memory/memory-retrospective-enqueue.ts +1 -20
- package/src/memory/memory-retrospective-job.ts +33 -6
- package/src/memory/migrations/250-provider-connection-base-url-and-models.ts +28 -0
- package/src/memory/migrations/251-a2a-tasks.ts +49 -0
- package/src/memory/migrations/252-llm-request-log-agent-loop-exit-reason.ts +32 -0
- package/src/memory/migrations/index.ts +3 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/schema/a2a.ts +15 -0
- package/src/memory/schema/index.ts +1 -0
- package/src/memory/schema/inference.ts +2 -0
- package/src/memory/schema/infrastructure.ts +1 -0
- package/src/memory/v2/__tests__/activation-store.test.ts +25 -23
- package/src/memory/v2/__tests__/cli-command-store.test.ts +404 -0
- package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +25 -4
- package/src/memory/v2/__tests__/injection.test.ts +190 -3
- package/src/memory/v2/__tests__/static-context.test.ts +12 -1
- package/src/memory/v2/activation-store.ts +14 -16
- package/src/memory/v2/cli-command-content.ts +19 -0
- package/src/memory/v2/cli-command-store.ts +304 -0
- package/src/memory/v2/frontmatter-sweep.ts +7 -1
- package/src/memory/v2/injection.ts +49 -20
- package/src/memory/v2/page-index.ts +38 -13
- package/src/memory/v2/static-context.ts +4 -4
- package/src/memory/v2/types.ts +23 -0
- package/src/messaging/providers/a2a/__tests__/deliver.test.ts +274 -0
- package/src/messaging/providers/a2a/deliver.ts +156 -0
- package/src/messaging/providers/gmail/client.ts +9 -2
- package/src/messaging/providers/index.ts +11 -2
- package/src/notifications/__tests__/broadcaster.test.ts +203 -0
- package/src/notifications/__tests__/decision-engine.test.ts +283 -0
- package/src/notifications/__tests__/deterministic-checks.test.ts +286 -0
- package/src/notifications/__tests__/emit-signal-home-feed.test.ts +1 -0
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +430 -7
- package/src/notifications/adapters/macos.ts +12 -2
- package/src/notifications/broadcaster.ts +29 -4
- package/src/notifications/copy-composer.ts +17 -64
- package/src/notifications/decision-engine.ts +111 -44
- package/src/notifications/deterministic-checks.ts +96 -0
- package/src/notifications/emit-signal.ts +1 -0
- package/src/notifications/home-feed-side-effect.ts +85 -6
- package/src/notifications/signal.ts +0 -4
- package/src/notifications/types.ts +8 -0
- package/src/oauth/platform-connection.test.ts +43 -3
- package/src/oauth/platform-connection.ts +13 -4
- package/src/plugins/defaults/injectors.ts +38 -19
- package/src/plugins/external-plugin-loader.ts +82 -10
- package/src/plugins/types.ts +16 -7
- package/src/prompts/__tests__/system-prompt.test.ts +6 -51
- package/src/prompts/__tests__/task-progress-hint-section.test.ts +4 -8
- package/src/prompts/system-prompt.ts +0 -8
- package/src/prompts/templates/BOOTSTRAP.md +5 -5
- package/src/prompts/templates/system-sections.ts +0 -9
- package/src/providers/__tests__/inference.test.ts +2 -0
- package/src/providers/call-site-routing.ts +24 -6
- package/src/providers/connection-resolution.ts +63 -13
- package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +74 -0
- package/src/providers/inference/__tests__/connections-openai-compatible.test.ts +175 -0
- package/src/providers/inference/__tests__/connections-status-label.test.ts +15 -0
- package/src/providers/inference/adapter-factory.ts +9 -20
- package/src/providers/inference/auth.ts +12 -0
- package/src/providers/inference/backfill.ts +14 -1
- package/src/providers/inference/connections.ts +85 -5
- package/src/providers/inference/resolve-auth.ts +2 -0
- package/src/providers/model-catalog.ts +199 -244
- package/src/providers/model-intents.ts +3 -3
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +235 -0
- package/src/providers/openai/chat-completions-provider.ts +159 -6
- package/src/providers/openrouter/client.ts +42 -4
- package/src/providers/platform-proxy/constants.ts +3 -4
- package/src/providers/provider-catalog-visibility.ts +3 -1
- package/src/providers/provider-send-message.ts +27 -12
- package/src/providers/registry.ts +30 -1
- package/src/runtime/agent-wake.ts +61 -1
- package/src/runtime/auth/route-policy.ts +13 -0
- package/src/runtime/http-server.ts +7 -16
- package/src/runtime/http-types.ts +0 -47
- package/src/runtime/routes/__tests__/consolidation-routes.test.ts +258 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +66 -4
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +275 -44
- package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +12 -0
- package/src/runtime/routes/channel-availability-routes.ts +5 -0
- package/src/runtime/routes/consolidation-routes.ts +100 -0
- package/src/runtime/routes/conversation-query-routes.ts +70 -11
- package/src/runtime/routes/conversation-routes.ts +7 -0
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/inference-provider-connection-routes.ts +134 -1
- package/src/runtime/routes/integrations/a2a.ts +235 -0
- package/src/runtime/routes/llm-call-sites-routes.ts +11 -1
- package/src/runtime/routes/subagents-routes.ts +41 -0
- package/src/subagent/manager.ts +2 -0
- package/src/tools/memory/register.ts +1 -9
- package/src/tools/registry.ts +2 -2
- package/src/tools/types.ts +37 -2
- package/src/workspace/migrations/087-memory-router-balanced-profile.ts +91 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -441
- package/src/memory/graph/__tests__/remember-description.test.ts +0 -55
- package/src/runtime/guardian-action-conversation-turn.ts +0 -99
|
@@ -19,6 +19,10 @@ import {
|
|
|
19
19
|
} from "../lib/install-from-github.js";
|
|
20
20
|
import { listInstalledPlugins } from "../lib/list-installed-plugins.js";
|
|
21
21
|
import { registerCommand } from "../lib/register-command.js";
|
|
22
|
+
import {
|
|
23
|
+
InvalidSearchPatternError,
|
|
24
|
+
searchPlugins,
|
|
25
|
+
} from "../lib/search-plugins.js";
|
|
22
26
|
import {
|
|
23
27
|
PluginNotInstalledError,
|
|
24
28
|
uninstallPlugin,
|
|
@@ -42,6 +46,9 @@ Examples:
|
|
|
42
46
|
$ assistant plugins install simple-memory --ref my-feature-branch
|
|
43
47
|
$ assistant plugins list
|
|
44
48
|
$ assistant plugins list --json
|
|
49
|
+
$ assistant plugins search memory
|
|
50
|
+
$ assistant plugins search "^simple"
|
|
51
|
+
$ assistant plugins search memory --json
|
|
45
52
|
$ assistant plugins uninstall simple-memory`,
|
|
46
53
|
);
|
|
47
54
|
|
|
@@ -134,6 +141,66 @@ Examples:
|
|
|
134
141
|
);
|
|
135
142
|
});
|
|
136
143
|
|
|
144
|
+
plugins
|
|
145
|
+
.command("search <query>")
|
|
146
|
+
.description(
|
|
147
|
+
"Search vellum-ai/vellum-assistant/experimental/plugins for plugin names matching <query> (case-insensitive regex)",
|
|
148
|
+
)
|
|
149
|
+
.option("--json", "Emit machine-readable JSON instead of a table")
|
|
150
|
+
.action(async (query: string, opts: { json?: boolean }) => {
|
|
151
|
+
try {
|
|
152
|
+
const result = await searchPlugins(
|
|
153
|
+
{ query },
|
|
154
|
+
{ fetch: globalThis.fetch.bind(globalThis) },
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
// Log on every success path — JSON output, empty results, and
|
|
158
|
+
// populated tables alike — so observability doesn't depend on
|
|
159
|
+
// which formatting branch the caller landed in.
|
|
160
|
+
log.info(
|
|
161
|
+
{
|
|
162
|
+
query: result.query,
|
|
163
|
+
ref: result.ref,
|
|
164
|
+
matchCount: result.matches.length,
|
|
165
|
+
},
|
|
166
|
+
"external plugin search",
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
if (opts.json) {
|
|
170
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (result.matches.length === 0) {
|
|
175
|
+
console.log(`No plugins matched "${result.query}".`);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const nameW = Math.max(
|
|
180
|
+
4,
|
|
181
|
+
...result.matches.map((m) => m.name.length),
|
|
182
|
+
);
|
|
183
|
+
const pad = (s: string, w: number) => s + " ".repeat(w - s.length);
|
|
184
|
+
console.log(`${pad("NAME", nameW)} PATH`);
|
|
185
|
+
for (const m of result.matches) {
|
|
186
|
+
console.log(`${pad(m.name, nameW)} ${m.path}`);
|
|
187
|
+
}
|
|
188
|
+
console.log("");
|
|
189
|
+
console.log(
|
|
190
|
+
`${result.matches.length} match${result.matches.length === 1 ? "" : "es"} for "${result.query}".`,
|
|
191
|
+
);
|
|
192
|
+
} catch (err) {
|
|
193
|
+
if (err instanceof InvalidSearchPatternError) {
|
|
194
|
+
console.error(err.message);
|
|
195
|
+
process.exitCode = 1;
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
199
|
+
console.error(`Plugin search failed: ${message}`);
|
|
200
|
+
process.exitCode = 1;
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
|
|
137
204
|
plugins
|
|
138
205
|
.command("uninstall <name>")
|
|
139
206
|
.description("Remove a plugin from <workspaceDir>/plugins/<name>/")
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Command } from "commander";
|
|
2
2
|
|
|
3
3
|
import { cliIpcCall, exitFromIpcResult } from "../../ipc/cli-client.js";
|
|
4
|
+
import { confirmPrompt } from "../lib/confirm-prompt.js";
|
|
4
5
|
import { registerCommand } from "../lib/register-command.js";
|
|
5
6
|
import { log } from "../logger.js";
|
|
6
7
|
import { writeOutput } from "../output.js";
|
|
@@ -60,18 +61,21 @@ export function registerSchedulesCommand(program: Command): void {
|
|
|
60
61
|
schedules.addHelpText(
|
|
61
62
|
"after",
|
|
62
63
|
`
|
|
63
|
-
Schedules are recurring or one-shot jobs run by the assistant
|
|
64
|
+
Schedules are recurring or one-shot jobs run by the assistant.
|
|
64
65
|
|
|
65
66
|
This CLI namespace is intentionally landing incrementally. Today it supports
|
|
66
|
-
listing schedules, viewing recent run history,
|
|
67
|
-
|
|
68
|
-
separate slices.
|
|
67
|
+
listing schedules, viewing recent run history, enabling/disabling schedules,
|
|
68
|
+
manually executing a schedule one time, and cancelling pending one-shot schedules;
|
|
69
|
+
create, delete, and run inspection will follow as separate slices.
|
|
69
70
|
|
|
70
71
|
Examples:
|
|
71
72
|
$ assistant schedules list
|
|
72
73
|
$ assistant schedules list --all
|
|
73
74
|
$ assistant schedules runs <schedule-id>
|
|
74
75
|
$ assistant schedules runs <schedule-id> --limit 25 --json
|
|
76
|
+
$ assistant schedules disable <schedule-id>
|
|
77
|
+
$ assistant schedules enable <schedule-id>
|
|
78
|
+
$ assistant schedules cancel <schedule-id>
|
|
75
79
|
$ assistant schedules execute <schedule-id>`,
|
|
76
80
|
);
|
|
77
81
|
|
|
@@ -203,7 +207,7 @@ Examples:
|
|
|
203
207
|
"after",
|
|
204
208
|
`
|
|
205
209
|
Options:
|
|
206
|
-
--limit <count> Max runs to return. The
|
|
210
|
+
--limit <count> Max runs to return. The assistant clamps values to 1-100.
|
|
207
211
|
--json Output the raw run list as compact JSON.
|
|
208
212
|
|
|
209
213
|
Arguments:
|
|
@@ -307,6 +311,271 @@ Examples:
|
|
|
307
311
|
},
|
|
308
312
|
);
|
|
309
313
|
|
|
314
|
+
schedules
|
|
315
|
+
.command("create <name>")
|
|
316
|
+
.description("Create a new recurring schedule")
|
|
317
|
+
.requiredOption(
|
|
318
|
+
"-e, --expression <expr>",
|
|
319
|
+
"Cron or RRULE expression that schedules the fire times",
|
|
320
|
+
)
|
|
321
|
+
.requiredOption(
|
|
322
|
+
"-m, --message <text>",
|
|
323
|
+
"Message body sent to the assistant on each fire",
|
|
324
|
+
)
|
|
325
|
+
.option(
|
|
326
|
+
"-t, --timezone <tz>",
|
|
327
|
+
"IANA timezone for the expression (e.g. America/New_York)",
|
|
328
|
+
)
|
|
329
|
+
.option("--no-enabled", "Create the schedule in a disabled state")
|
|
330
|
+
.option("--json", "Machine-readable compact JSON output")
|
|
331
|
+
.addHelpText(
|
|
332
|
+
"after",
|
|
333
|
+
`
|
|
334
|
+
Options:
|
|
335
|
+
-e, --expression <expr> Cron (e.g. '*/30 * * * *') or RRULE expression.
|
|
336
|
+
-m, --message <text> Message body sent on each fire.
|
|
337
|
+
-t, --timezone <tz> IANA timezone applied to the expression.
|
|
338
|
+
--no-enabled Create the schedule disabled. Defaults to enabled.
|
|
339
|
+
--json Output the updated schedule list as compact JSON.
|
|
340
|
+
|
|
341
|
+
Arguments:
|
|
342
|
+
<name> Display name for the schedule.
|
|
343
|
+
|
|
344
|
+
Behavior:
|
|
345
|
+
Creates a recurring schedule in 'execute' mode. The IPC endpoint is
|
|
346
|
+
currently locked to execute mode; notify/script/wake schedules remain
|
|
347
|
+
reachable only through the in-assistant schedule_create LLM tool.
|
|
348
|
+
|
|
349
|
+
Examples:
|
|
350
|
+
$ assistant schedules create "Heartbeat" \\
|
|
351
|
+
--expression '*/30 * * * *' \\
|
|
352
|
+
--message 'run heartbeat'
|
|
353
|
+
$ assistant schedules create "Morning summary" \\
|
|
354
|
+
--expression '0 9 * * MON-FRI' \\
|
|
355
|
+
--timezone America/New_York \\
|
|
356
|
+
--message 'write the morning summary'
|
|
357
|
+
$ assistant schedules create "Drafted" \\
|
|
358
|
+
--expression '0 0 * * *' \\
|
|
359
|
+
--message 'placeholder' \\
|
|
360
|
+
--no-enabled --json`,
|
|
361
|
+
)
|
|
362
|
+
.action(
|
|
363
|
+
async (
|
|
364
|
+
name: string,
|
|
365
|
+
opts: {
|
|
366
|
+
expression: string;
|
|
367
|
+
message: string;
|
|
368
|
+
timezone?: string;
|
|
369
|
+
enabled: boolean;
|
|
370
|
+
json?: boolean;
|
|
371
|
+
},
|
|
372
|
+
cmd: Command,
|
|
373
|
+
) => {
|
|
374
|
+
const scheduleName = name.trim();
|
|
375
|
+
if (!scheduleName) {
|
|
376
|
+
const error = "name is required";
|
|
377
|
+
if (opts.json) {
|
|
378
|
+
writeOutput(cmd, { ok: false, error });
|
|
379
|
+
} else {
|
|
380
|
+
log.error(error);
|
|
381
|
+
}
|
|
382
|
+
process.exitCode = 1;
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const body: Record<string, unknown> = {
|
|
387
|
+
name: scheduleName,
|
|
388
|
+
expression: opts.expression,
|
|
389
|
+
message: opts.message,
|
|
390
|
+
enabled: opts.enabled,
|
|
391
|
+
};
|
|
392
|
+
if (opts.timezone != null) body.timezone = opts.timezone;
|
|
393
|
+
|
|
394
|
+
const result = await cliIpcCall<ListSchedulesResponse>(
|
|
395
|
+
"createSchedule",
|
|
396
|
+
{ body },
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
if (!result.ok) return exitFromIpcResult(result, cmd);
|
|
400
|
+
|
|
401
|
+
const response = result.result ?? { schedules: [] };
|
|
402
|
+
if (opts.json) {
|
|
403
|
+
writeOutput(cmd, response);
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
log.info(`Created schedule: ${scheduleName}`);
|
|
408
|
+
},
|
|
409
|
+
);
|
|
410
|
+
|
|
411
|
+
schedules
|
|
412
|
+
.command("enable <id>")
|
|
413
|
+
.description("Enable a schedule")
|
|
414
|
+
.option("--json", "Machine-readable compact JSON output")
|
|
415
|
+
.addHelpText(
|
|
416
|
+
"after",
|
|
417
|
+
`
|
|
418
|
+
Options:
|
|
419
|
+
--json Output the updated schedule list as compact JSON.
|
|
420
|
+
|
|
421
|
+
Arguments:
|
|
422
|
+
<id> Schedule ID (UUID) — run 'assistant schedules list --all' to find it.
|
|
423
|
+
|
|
424
|
+
Behavior:
|
|
425
|
+
Enables the schedule so it can run on future matching times. This does not
|
|
426
|
+
execute the schedule immediately; use 'assistant schedules execute <id>' for
|
|
427
|
+
manual run-now behavior.
|
|
428
|
+
|
|
429
|
+
Examples:
|
|
430
|
+
$ assistant schedules enable 9f2c4f3a-3f1a-41e4-88e7-abc123
|
|
431
|
+
$ assistant schedules enable 9f2c4f3a-3f1a-41e4-88e7-abc123 --json`,
|
|
432
|
+
)
|
|
433
|
+
.action(async (id: string, opts: { json?: boolean }, cmd: Command) => {
|
|
434
|
+
await toggleScheduleEnabled(id, true, opts, cmd);
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
schedules
|
|
438
|
+
.command("disable <id>")
|
|
439
|
+
.description("Disable a schedule")
|
|
440
|
+
.option("--json", "Machine-readable compact JSON output")
|
|
441
|
+
.addHelpText(
|
|
442
|
+
"after",
|
|
443
|
+
`
|
|
444
|
+
Options:
|
|
445
|
+
--json Output the updated schedule list as compact JSON.
|
|
446
|
+
|
|
447
|
+
Arguments:
|
|
448
|
+
<id> Schedule ID (UUID) — run 'assistant schedules list --all' to find it.
|
|
449
|
+
|
|
450
|
+
Behavior:
|
|
451
|
+
Disables the schedule so future scheduled fires are skipped until it is
|
|
452
|
+
enabled again. Existing run history is preserved.
|
|
453
|
+
|
|
454
|
+
Examples:
|
|
455
|
+
$ assistant schedules disable 9f2c4f3a-3f1a-41e4-88e7-abc123
|
|
456
|
+
$ assistant schedules disable 9f2c4f3a-3f1a-41e4-88e7-abc123 --json`,
|
|
457
|
+
)
|
|
458
|
+
.action(async (id: string, opts: { json?: boolean }, cmd: Command) => {
|
|
459
|
+
await toggleScheduleEnabled(id, false, opts, cmd);
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
schedules
|
|
463
|
+
.command("cancel <id>")
|
|
464
|
+
.description("Cancel a pending one-shot schedule")
|
|
465
|
+
.option("--json", "Machine-readable compact JSON output")
|
|
466
|
+
.addHelpText(
|
|
467
|
+
"after",
|
|
468
|
+
`
|
|
469
|
+
Options:
|
|
470
|
+
--json Output the updated schedule list as compact JSON.
|
|
471
|
+
|
|
472
|
+
Arguments:
|
|
473
|
+
<id> Schedule ID (UUID) — run 'assistant schedules list --all' to find
|
|
474
|
+
pending one-shot/deferred schedules.
|
|
475
|
+
|
|
476
|
+
Behavior:
|
|
477
|
+
Cancels a pending one-shot schedule. Recurring schedules are not cancellable;
|
|
478
|
+
use 'assistant schedules disable <id>' to pause recurring schedules.
|
|
479
|
+
|
|
480
|
+
Examples:
|
|
481
|
+
$ assistant schedules cancel 9f2c4f3a-3f1a-41e4-88e7-abc123
|
|
482
|
+
$ assistant schedules cancel 9f2c4f3a-3f1a-41e4-88e7-abc123 --json`,
|
|
483
|
+
)
|
|
484
|
+
.action(async (id: string, opts: { json?: boolean }, cmd: Command) => {
|
|
485
|
+
const scheduleId = id.trim();
|
|
486
|
+
const result = await cliIpcCall<ListSchedulesResponse>(
|
|
487
|
+
"cancelSchedule",
|
|
488
|
+
{ pathParams: { id: scheduleId } },
|
|
489
|
+
);
|
|
490
|
+
|
|
491
|
+
if (!result.ok) return exitFromIpcResult(result, cmd);
|
|
492
|
+
|
|
493
|
+
const response = result.result ?? { schedules: [] };
|
|
494
|
+
if (opts.json) {
|
|
495
|
+
writeOutput(cmd, response);
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
log.info(`Cancelled schedule: ${scheduleId}`);
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
schedules
|
|
503
|
+
.command("delete <id>")
|
|
504
|
+
.description("Permanently delete a schedule and its run history")
|
|
505
|
+
.option("--force", "Skip the confirmation prompt")
|
|
506
|
+
.option("--json", "Machine-readable compact JSON output")
|
|
507
|
+
.addHelpText(
|
|
508
|
+
"after",
|
|
509
|
+
`
|
|
510
|
+
Options:
|
|
511
|
+
--force Skip the destructive y/N confirmation prompt. Required when stdin
|
|
512
|
+
is not a TTY (e.g. in scripts and CI).
|
|
513
|
+
--json Output the updated schedule list as compact JSON.
|
|
514
|
+
|
|
515
|
+
Arguments:
|
|
516
|
+
<id> Schedule ID (UUID) — run 'assistant schedules list --all' to find it.
|
|
517
|
+
|
|
518
|
+
Behavior:
|
|
519
|
+
Permanently removes the schedule and its run history. This cannot be undone.
|
|
520
|
+
To temporarily pause a recurring schedule, use
|
|
521
|
+
'assistant schedules disable <id>' instead.
|
|
522
|
+
|
|
523
|
+
Examples:
|
|
524
|
+
$ assistant schedules delete 9f2c4f3a-3f1a-41e4-88e7-abc123
|
|
525
|
+
$ assistant schedules delete 9f2c4f3a-3f1a-41e4-88e7-abc123 --force
|
|
526
|
+
$ assistant schedules delete 9f2c4f3a-3f1a-41e4-88e7-abc123 --force --json`,
|
|
527
|
+
)
|
|
528
|
+
.action(
|
|
529
|
+
async (
|
|
530
|
+
id: string,
|
|
531
|
+
opts: { force?: boolean; json?: boolean },
|
|
532
|
+
cmd: Command,
|
|
533
|
+
) => {
|
|
534
|
+
const scheduleId = id.trim();
|
|
535
|
+
if (!scheduleId) {
|
|
536
|
+
const error = "Schedule ID is required";
|
|
537
|
+
if (opts.json) {
|
|
538
|
+
writeOutput(cmd, { ok: false, error });
|
|
539
|
+
} else {
|
|
540
|
+
log.error(error);
|
|
541
|
+
}
|
|
542
|
+
process.exitCode = 1;
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
if (!opts.force) {
|
|
547
|
+
const decision = await confirmPrompt({
|
|
548
|
+
question: `Delete schedule "${scheduleId}"? [y/N] `,
|
|
549
|
+
isTTY: Boolean(process.stdin.isTTY),
|
|
550
|
+
refuseNonInteractiveMessage: `Refusing to delete schedule "${scheduleId}" non-interactively. Pass --force to confirm.`,
|
|
551
|
+
});
|
|
552
|
+
if (decision === "non-interactive") {
|
|
553
|
+
process.exitCode = 1;
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
if (decision === "denied") {
|
|
557
|
+
log.info("Delete cancelled.");
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
const result = await cliIpcCall<ListSchedulesResponse>(
|
|
563
|
+
"deleteSchedule",
|
|
564
|
+
{ pathParams: { id: scheduleId } },
|
|
565
|
+
);
|
|
566
|
+
|
|
567
|
+
if (!result.ok) return exitFromIpcResult(result, cmd);
|
|
568
|
+
|
|
569
|
+
const response = result.result ?? { schedules: [] };
|
|
570
|
+
if (opts.json) {
|
|
571
|
+
writeOutput(cmd, response);
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
log.info(`Deleted schedule: ${scheduleId}`);
|
|
576
|
+
},
|
|
577
|
+
);
|
|
578
|
+
|
|
310
579
|
schedules
|
|
311
580
|
.command("execute <id>")
|
|
312
581
|
.description("Execute a schedule one time immediately")
|
|
@@ -368,6 +637,29 @@ Examples:
|
|
|
368
637
|
});
|
|
369
638
|
}
|
|
370
639
|
|
|
640
|
+
async function toggleScheduleEnabled(
|
|
641
|
+
id: string,
|
|
642
|
+
enabled: boolean,
|
|
643
|
+
opts: { json?: boolean },
|
|
644
|
+
cmd: Command,
|
|
645
|
+
): Promise<void> {
|
|
646
|
+
const scheduleId = id.trim();
|
|
647
|
+
const result = await cliIpcCall<ListSchedulesResponse>("toggleSchedule", {
|
|
648
|
+
pathParams: { id: scheduleId },
|
|
649
|
+
body: { enabled },
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
if (!result.ok) return exitFromIpcResult(result, cmd);
|
|
653
|
+
|
|
654
|
+
const response = result.result ?? { schedules: [] };
|
|
655
|
+
if (opts.json) {
|
|
656
|
+
writeOutput(cmd, response);
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
log.info(`${enabled ? "Enabled" : "Disabled"} schedule: ${scheduleId}`);
|
|
661
|
+
}
|
|
662
|
+
|
|
371
663
|
function describeSchedule(schedule: ScheduleRecord): string {
|
|
372
664
|
if (schedule.isOneShot) return "one-shot";
|
|
373
665
|
const expression = schedule.description ?? schedule.expression ?? "—";
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for {@link searchPlugins}.
|
|
3
|
+
*
|
|
4
|
+
* Network is replaced with an in-memory fixture passed via the `fetch`
|
|
5
|
+
* dependency — no globals are monkey-patched and no `--test-hook` exports
|
|
6
|
+
* leak into production code.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, expect, test } from "bun:test";
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
type FetchLike,
|
|
13
|
+
InvalidSearchPatternError,
|
|
14
|
+
searchPlugins,
|
|
15
|
+
} from "../search-plugins.js";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Build a GitHub Contents API fixture from an in-memory directory listing.
|
|
19
|
+
*
|
|
20
|
+
* `entries` maps each name under `experimental/plugins/` to its `type`. The
|
|
21
|
+
* fixture answers GET requests against
|
|
22
|
+
* - `https://api.github.com/repos/vellum-ai/vellum-assistant/contents/experimental/plugins...`
|
|
23
|
+
* and returns 500 for anything else (forces test bugs to surface loudly).
|
|
24
|
+
*/
|
|
25
|
+
function fixtureFetch(
|
|
26
|
+
entries: Record<string, "dir" | "file" | "symlink" | "submodule">,
|
|
27
|
+
): FetchLike {
|
|
28
|
+
const PREFIX_API =
|
|
29
|
+
"https://api.github.com/repos/vellum-ai/vellum-assistant/contents/experimental/plugins";
|
|
30
|
+
|
|
31
|
+
return (async (input: RequestInfo | URL) => {
|
|
32
|
+
const url = typeof input === "string" ? input : input.toString();
|
|
33
|
+
if (!url.startsWith(PREFIX_API)) {
|
|
34
|
+
return new Response("unexpected url: " + url, { status: 500 });
|
|
35
|
+
}
|
|
36
|
+
const body = Object.entries(entries).map(([name, type]) => ({
|
|
37
|
+
name,
|
|
38
|
+
path: `experimental/plugins/${name}`,
|
|
39
|
+
type,
|
|
40
|
+
size: type === "file" ? 1 : 0,
|
|
41
|
+
download_url:
|
|
42
|
+
type === "file"
|
|
43
|
+
? `https://raw.githubusercontent.com/vellum-ai/vellum-assistant/main/experimental/plugins/${name}`
|
|
44
|
+
: null,
|
|
45
|
+
}));
|
|
46
|
+
return new Response(JSON.stringify(body), {
|
|
47
|
+
status: 200,
|
|
48
|
+
headers: { "content-type": "application/json" },
|
|
49
|
+
});
|
|
50
|
+
}) as FetchLike;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
describe("searchPlugins", () => {
|
|
54
|
+
test("matches the query as a case-insensitive regex against directory names", async () => {
|
|
55
|
+
const result = await searchPlugins(
|
|
56
|
+
{ query: "memory" },
|
|
57
|
+
{
|
|
58
|
+
fetch: fixtureFetch({
|
|
59
|
+
"simple-memory": "dir",
|
|
60
|
+
"memory-graph": "dir",
|
|
61
|
+
"git-tools": "dir",
|
|
62
|
+
}),
|
|
63
|
+
},
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
expect(result.matches.map((m) => m.name)).toEqual([
|
|
67
|
+
"memory-graph",
|
|
68
|
+
"simple-memory",
|
|
69
|
+
]);
|
|
70
|
+
expect(result.matches[0]!.path).toBe("experimental/plugins/memory-graph");
|
|
71
|
+
expect(result.query).toBe("memory");
|
|
72
|
+
expect(result.ref).toBe("main");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("matches regardless of query casing (case-insensitive)", async () => {
|
|
76
|
+
const result = await searchPlugins(
|
|
77
|
+
{ query: "MEMORY" },
|
|
78
|
+
{ fetch: fixtureFetch({ "simple-memory": "dir" }) },
|
|
79
|
+
);
|
|
80
|
+
expect(result.matches.map((m) => m.name)).toEqual(["simple-memory"]);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("anchored patterns work without escaping", async () => {
|
|
84
|
+
const result = await searchPlugins(
|
|
85
|
+
{ query: "^memory-" },
|
|
86
|
+
{
|
|
87
|
+
fetch: fixtureFetch({
|
|
88
|
+
"memory-graph": "dir",
|
|
89
|
+
"simple-memory": "dir",
|
|
90
|
+
}),
|
|
91
|
+
},
|
|
92
|
+
);
|
|
93
|
+
expect(result.matches.map((m) => m.name)).toEqual(["memory-graph"]);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("empty query matches all directories", async () => {
|
|
97
|
+
const result = await searchPlugins(
|
|
98
|
+
{ query: "" },
|
|
99
|
+
{
|
|
100
|
+
fetch: fixtureFetch({
|
|
101
|
+
"simple-memory": "dir",
|
|
102
|
+
"memory-graph": "dir",
|
|
103
|
+
"git-tools": "dir",
|
|
104
|
+
}),
|
|
105
|
+
},
|
|
106
|
+
);
|
|
107
|
+
expect(result.matches.map((m) => m.name)).toEqual([
|
|
108
|
+
"git-tools",
|
|
109
|
+
"memory-graph",
|
|
110
|
+
"simple-memory",
|
|
111
|
+
]);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("skips entries that are not directories", async () => {
|
|
115
|
+
const result = await searchPlugins(
|
|
116
|
+
{ query: "" },
|
|
117
|
+
{
|
|
118
|
+
fetch: fixtureFetch({
|
|
119
|
+
"simple-memory": "dir",
|
|
120
|
+
"README.md": "file",
|
|
121
|
+
"broken-symlink": "symlink",
|
|
122
|
+
"old-plugin": "submodule",
|
|
123
|
+
}),
|
|
124
|
+
},
|
|
125
|
+
);
|
|
126
|
+
expect(result.matches.map((m) => m.name)).toEqual(["simple-memory"]);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("rejects invalid regex patterns up front (no network call)", async () => {
|
|
130
|
+
let fetchCalled = false;
|
|
131
|
+
const fetch: FetchLike = (async () => {
|
|
132
|
+
fetchCalled = true;
|
|
133
|
+
return new Response("", { status: 200 });
|
|
134
|
+
}) as FetchLike;
|
|
135
|
+
|
|
136
|
+
await expect(
|
|
137
|
+
searchPlugins({ query: "(unterminated" }, { fetch }),
|
|
138
|
+
).rejects.toBeInstanceOf(InvalidSearchPatternError);
|
|
139
|
+
expect(fetchCalled).toBe(false);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("empty result set on no matches", async () => {
|
|
143
|
+
const result = await searchPlugins(
|
|
144
|
+
{ query: "nothing-matches" },
|
|
145
|
+
{ fetch: fixtureFetch({ "simple-memory": "dir" }) },
|
|
146
|
+
);
|
|
147
|
+
expect(result.matches).toEqual([]);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("respects `ref` option by forwarding to GitHub", async () => {
|
|
151
|
+
// The CLI does not surface a `--ref` flag (the source-path convention
|
|
152
|
+
// may change), but the underlying function keeps `ref` for test
|
|
153
|
+
// injection and future internal callers.
|
|
154
|
+
let seenRef: string | undefined;
|
|
155
|
+
const result = await searchPlugins(
|
|
156
|
+
{ query: "memory", ref: "feat-branch" },
|
|
157
|
+
{
|
|
158
|
+
fetch: (async (input: RequestInfo | URL) => {
|
|
159
|
+
const url = typeof input === "string" ? input : input.toString();
|
|
160
|
+
const m = /[?&]ref=([^&]+)/.exec(url);
|
|
161
|
+
seenRef = m ? decodeURIComponent(m[1]!) : undefined;
|
|
162
|
+
return new Response(
|
|
163
|
+
JSON.stringify([
|
|
164
|
+
{
|
|
165
|
+
name: "simple-memory",
|
|
166
|
+
path: "experimental/plugins/simple-memory",
|
|
167
|
+
type: "dir",
|
|
168
|
+
size: 0,
|
|
169
|
+
download_url: null,
|
|
170
|
+
},
|
|
171
|
+
]),
|
|
172
|
+
{ status: 200, headers: { "content-type": "application/json" } },
|
|
173
|
+
);
|
|
174
|
+
}) as FetchLike,
|
|
175
|
+
},
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
expect(seenRef).toBe("feat-branch");
|
|
179
|
+
expect(result.ref).toBe("feat-branch");
|
|
180
|
+
expect(result.matches.map((m) => m.name)).toEqual(["simple-memory"]);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test("HTTP 5xx from GitHub propagates with the status code", async () => {
|
|
184
|
+
await expect(
|
|
185
|
+
searchPlugins(
|
|
186
|
+
{ query: "memory" },
|
|
187
|
+
{
|
|
188
|
+
fetch: (async () =>
|
|
189
|
+
new Response("upstream broken", { status: 503 })) as FetchLike,
|
|
190
|
+
},
|
|
191
|
+
),
|
|
192
|
+
).rejects.toThrow(/HTTP 503/);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test("HTTP 403 (rate-limited / forbidden) surfaces as an error", async () => {
|
|
196
|
+
await expect(
|
|
197
|
+
searchPlugins(
|
|
198
|
+
{ query: "memory" },
|
|
199
|
+
{
|
|
200
|
+
fetch: (async () =>
|
|
201
|
+
new Response("rate limit exceeded", { status: 403 })) as FetchLike,
|
|
202
|
+
},
|
|
203
|
+
),
|
|
204
|
+
).rejects.toThrow(/HTTP 403/);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test("404 on the plugins prefix surfaces as an error (not silently empty)", async () => {
|
|
208
|
+
// Distinct from `installPlugin`, where 404 on a specific plugin name is
|
|
209
|
+
// normal "not found". For the search, 404 on the prefix means the
|
|
210
|
+
// canonical source path itself is gone — that's an upstream problem
|
|
211
|
+
// worth surfacing, not a clean empty result.
|
|
212
|
+
await expect(
|
|
213
|
+
searchPlugins(
|
|
214
|
+
{ query: "memory" },
|
|
215
|
+
{
|
|
216
|
+
fetch: (async () =>
|
|
217
|
+
new Response("not found", { status: 404 })) as FetchLike,
|
|
218
|
+
},
|
|
219
|
+
),
|
|
220
|
+
).rejects.toThrow(/HTTP 404/);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test("returns matches sorted by name", async () => {
|
|
224
|
+
const result = await searchPlugins(
|
|
225
|
+
{ query: "" },
|
|
226
|
+
{
|
|
227
|
+
fetch: fixtureFetch({
|
|
228
|
+
"zeta-plugin": "dir",
|
|
229
|
+
"alpha-plugin": "dir",
|
|
230
|
+
"mu-plugin": "dir",
|
|
231
|
+
}),
|
|
232
|
+
},
|
|
233
|
+
);
|
|
234
|
+
expect(result.matches.map((m) => m.name)).toEqual([
|
|
235
|
+
"alpha-plugin",
|
|
236
|
+
"mu-plugin",
|
|
237
|
+
"zeta-plugin",
|
|
238
|
+
]);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
test("sends no Authorization header (canonical source is a public repo)", async () => {
|
|
242
|
+
let seenAuth: string | undefined;
|
|
243
|
+
let seenUserAgent: string | undefined;
|
|
244
|
+
await searchPlugins(
|
|
245
|
+
{ query: "memory" },
|
|
246
|
+
{
|
|
247
|
+
fetch: (async (_input: RequestInfo | URL, init?: RequestInit) => {
|
|
248
|
+
const headers = init?.headers as Record<string, string> | undefined;
|
|
249
|
+
seenAuth = headers?.Authorization;
|
|
250
|
+
seenUserAgent = headers?.["User-Agent"];
|
|
251
|
+
return new Response("[]", {
|
|
252
|
+
status: 200,
|
|
253
|
+
headers: { "content-type": "application/json" },
|
|
254
|
+
});
|
|
255
|
+
}) as FetchLike,
|
|
256
|
+
},
|
|
257
|
+
);
|
|
258
|
+
expect(seenAuth).toBeUndefined();
|
|
259
|
+
expect(seenUserAgent).toBe("vellum-assistant-cli");
|
|
260
|
+
});
|
|
261
|
+
});
|