@vellumai/assistant 0.8.9-staging.2 → 0.8.9-staging.4
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/docs/activation-funnel-telemetry.md +310 -0
- package/openapi.yaml +16 -115
- package/package.json +1 -1
- package/src/__tests__/activation-early-marking.test.ts +120 -0
- package/src/__tests__/agent-loop-output-hooks.test.ts +13 -13
- package/src/__tests__/anthropic-provider.test.ts +23 -8
- package/src/__tests__/approval-cascade.test.ts +1 -1
- package/src/__tests__/compaction-direct.test.ts +32 -18
- package/src/__tests__/compaction-events.test.ts +2 -2
- package/src/__tests__/compaction.benchmark.test.ts +1 -1
- package/src/__tests__/context-overflow-reducer.test.ts +5 -5
- package/src/__tests__/context-window-manager-compact-retry.test.ts +121 -15
- package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
- package/src/__tests__/conversation-confirmation-signals.test.ts +1 -1
- package/src/__tests__/conversation-error.test.ts +15 -1
- package/src/__tests__/conversation-history-web-search.test.ts +5 -0
- package/src/__tests__/conversation-media-retry.test.ts +1 -1
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +40 -0
- package/src/__tests__/conversation-process-callsite.test.ts +1 -1
- package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -1
- package/src/__tests__/conversation-queue.test.ts +1 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +71 -0
- package/src/__tests__/conversation-slash-queue.test.ts +1 -1
- package/src/__tests__/conversation-slash-unknown.test.ts +1 -1
- package/src/__tests__/conversation-speed-override.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-activation-emit.test.ts +395 -0
- package/src/__tests__/conversation-surfaces-app-control.test.ts +44 -0
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +73 -4
- package/src/__tests__/conversation-undo.test.ts +2 -2
- package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -1
- package/src/__tests__/conversation-workspace-injection.test.ts +1 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/cu-unified-flow.test.ts +36 -0
- package/src/__tests__/history-repair-hook.test.ts +2 -0
- package/src/__tests__/llm-resolver.test.ts +73 -0
- package/src/__tests__/memory-retrieval-hook.test.ts +1 -2
- package/src/__tests__/persist-unsendable-image-downscale.test.ts +145 -0
- package/src/__tests__/persist-unsendable-image.test.ts +97 -1
- package/src/__tests__/plugin-external-api.test.ts +68 -0
- package/src/__tests__/post-turn-tool-result-truncation.test.ts +69 -0
- package/src/__tests__/published-app-updater.test.ts +138 -0
- package/src/__tests__/skill-feature-flags-integration.test.ts +5 -7
- package/src/__tests__/title-generate-hook.test.ts +2 -0
- package/src/__tests__/web-fetch.test.ts +45 -0
- package/src/acp/__tests__/helpers/acp-config-stub.ts +0 -2
- package/src/acp/resolve-agent.test.ts +0 -56
- package/src/acp/resolve-agent.ts +10 -38
- package/src/agent/loop.ts +13 -27
- package/src/api/responses/memory-v3-selection-log.ts +19 -10
- package/src/cli/commands/__tests__/memory-v3.test.ts +191 -210
- package/src/cli/commands/memory-v3.ts +57 -199
- package/src/cli/lib/__tests__/install-from-github.test.ts +232 -29
- package/src/cli/lib/__tests__/plugin-details.test.ts +28 -19
- package/src/cli/lib/__tests__/plugin-marketplace.test.ts +57 -7
- package/src/cli/lib/__tests__/search-plugins.test.ts +17 -10
- package/src/cli/lib/install-from-github.ts +258 -41
- package/src/cli/lib/plugin-details.ts +20 -13
- package/src/cli/lib/plugin-marketplace.ts +23 -5
- package/src/cli/lib/search-plugins.ts +14 -8
- package/src/config/acp-defaults.ts +3 -3
- package/src/config/acp-schema.ts +1 -7
- package/src/config/bundled-skills/acp/SKILL.md +4 -17
- package/src/config/bundled-skills/acp/TOOLS.json +2 -2
- package/src/config/call-site-defaults.ts +0 -1
- package/src/config/feature-flag-registry.json +3 -18
- package/src/config/llm-resolver.ts +39 -7
- package/src/config/schemas/__tests__/memory-v3.test.ts +25 -9
- package/src/config/schemas/call-site-catalog.ts +0 -7
- package/src/config/schemas/llm.ts +17 -1
- package/src/config/schemas/memory-v3.ts +58 -8
- package/src/config/seed-inference-profiles.ts +18 -0
- package/src/context/post-turn-tool-result-truncation.ts +39 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +8 -1
- package/src/daemon/conversation-agent-loop.ts +87 -95
- package/src/daemon/conversation-error.ts +31 -4
- package/src/daemon/conversation-history.ts +1 -1
- package/src/daemon/conversation-media-retry.ts +19 -6
- package/src/daemon/conversation-messaging.ts +17 -0
- package/src/daemon/conversation-process.ts +14 -5
- package/src/daemon/conversation-queue-manager.ts +8 -0
- package/src/daemon/conversation-runtime-assembly.ts +37 -1
- package/src/daemon/conversation-surfaces.ts +141 -3
- package/src/daemon/conversation.ts +48 -13
- package/src/daemon/external-plugins-bootstrap.ts +8 -3
- package/src/daemon/persist-unsendable-image.ts +62 -25
- package/src/daemon/process-message.ts +1 -1
- package/src/daemon/tool-side-effects.ts +15 -0
- package/src/memory/__tests__/activation-session-store.test.ts +41 -0
- package/src/memory/__tests__/onboarding-events-store.test.ts +80 -0
- package/src/memory/activation-session-store.ts +43 -0
- package/src/memory/db-init.ts +4 -0
- package/src/memory/migrations/273-onboarding-events-funnel-columns.ts +46 -0
- package/src/memory/migrations/274-create-activation-sessions.ts +15 -0
- package/src/memory/migrations/index.ts +2 -0
- package/src/memory/onboarding-events-store.ts +66 -18
- package/src/memory/schema/infrastructure.ts +13 -0
- package/src/memory/v2/__tests__/consolidation-job.test.ts +4 -97
- package/src/memory/v2/__tests__/page-store.test.ts +22 -0
- package/src/memory/v2/consolidation-job.ts +2 -72
- package/src/memory/v2/types.ts +5 -0
- package/src/messaging/providers/telegram-bot/api.ts +14 -5
- package/src/notifications/adapters/telegram.ts +7 -1
- package/src/plugin-api/constants.ts +2 -2
- package/src/plugin-api/index.ts +2 -2
- package/src/plugin-api/types.ts +19 -5
- package/src/plugins/defaults/compaction/compact.ts +24 -15
- package/src/plugins/defaults/compaction/context-overflow-reducer.ts +4 -4
- package/src/plugins/defaults/compaction/manager-store.ts +1 -1
- package/src/{context → plugins/defaults/compaction}/window-manager.ts +68 -12
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +12 -18
- package/src/plugins/defaults/memory-v3-shadow/__tests__/capabilities.test.ts +19 -66
- package/src/plugins/defaults/memory-v3-shadow/__tests__/dense.test.ts +181 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/edge.test.ts +247 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/live-integration.test.ts +139 -129
- package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +332 -164
- package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +516 -293
- package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +306 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/render-injection.test.ts +51 -21
- package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +402 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/section-needle.test.ts +135 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/sections.test.ts +125 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +66 -11
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +446 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +271 -110
- package/src/plugins/defaults/memory-v3-shadow/__tests__/types.test.ts +0 -22
- package/src/plugins/defaults/memory-v3-shadow/capabilities.ts +26 -62
- package/src/plugins/defaults/memory-v3-shadow/dense.ts +97 -0
- package/src/plugins/defaults/memory-v3-shadow/edge.ts +252 -0
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +3 -2
- package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +415 -181
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +196 -56
- package/src/plugins/defaults/memory-v3-shadow/page-content.ts +39 -2
- package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +204 -0
- package/src/plugins/defaults/memory-v3-shadow/render-injection.ts +15 -7
- package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +236 -0
- package/src/plugins/defaults/memory-v3-shadow/section-needle.ts +200 -0
- package/src/plugins/defaults/memory-v3-shadow/sections.ts +115 -0
- package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +75 -3
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +111 -78
- package/src/plugins/defaults/memory-v3-shadow/types.ts +52 -19
- package/src/plugins/defaults/memory-v3-shadow/working-set.ts +4 -1
- package/src/plugins/external-api.ts +114 -0
- package/src/prompts/system-prompt.ts +61 -10
- package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +37 -2
- package/src/providers/anthropic/client.ts +9 -10
- package/src/providers/inference/kimi-cjk-token-ids.ts +493 -0
- package/src/providers/inference/logit-bias.ts +55 -0
- package/src/providers/openai/__tests__/vision-not-supported.test.ts +75 -0
- package/src/providers/openai/chat-completions-provider.ts +44 -1
- package/src/providers/retry.ts +22 -0
- package/src/providers/types.ts +6 -0
- package/src/runtime/routes/__tests__/stt-routes.test.ts +112 -0
- package/src/runtime/routes/acp-routes.test.ts +3 -20
- package/src/runtime/routes/app-management-routes.ts +3 -0
- package/src/runtime/routes/conversation-routes.ts +2 -0
- package/src/runtime/routes/memory-v3-routes.ts +64 -314
- package/src/runtime/routes/playground/__tests__/force-compact.test.ts +1 -1
- package/src/runtime/routes/stt-routes.ts +45 -12
- package/src/runtime/routes/workspace-routes.ts +50 -15
- package/src/services/published-app-updater.ts +30 -8
- package/src/telemetry/__tests__/activation-funnel.test.ts +95 -0
- package/src/telemetry/activation-funnel.ts +167 -0
- package/src/telemetry/types.ts +13 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +154 -0
- package/src/telemetry/usage-telemetry-reporter.ts +26 -1
- package/src/tools/acp/list-agents.test.ts +2 -18
- package/src/tools/acp/list-agents.ts +3 -15
- package/src/tools/acp/spawn.test.ts +0 -10
- package/src/tools/browser/browser-execution.ts +12 -2
- package/src/tools/network/web-fetch.ts +65 -24
- package/src/tools/skills/load.ts +1 -1
- package/src/tools/ui-surface/definitions.ts +7 -0
- package/src/acp/feature-gate.test.ts +0 -48
- package/src/acp/feature-gate.ts +0 -34
- package/src/plugins/defaults/memory-v3-shadow/__tests__/assign.test.ts +0 -242
- package/src/plugins/defaults/memory-v3-shadow/__tests__/core.test.ts +0 -39
- package/src/plugins/defaults/memory-v3-shadow/__tests__/health.test.ts +0 -219
- package/src/plugins/defaults/memory-v3-shadow/__tests__/needle.test.ts +0 -107
- package/src/plugins/defaults/memory-v3-shadow/__tests__/provider-blocks.test.ts +0 -13
- package/src/plugins/defaults/memory-v3-shadow/__tests__/reconcile.test.ts +0 -274
- package/src/plugins/defaults/memory-v3-shadow/__tests__/router.test.ts +0 -337
- package/src/plugins/defaults/memory-v3-shadow/__tests__/selector.test.ts +0 -470
- package/src/plugins/defaults/memory-v3-shadow/__tests__/snapshot.test.ts +0 -168
- package/src/plugins/defaults/memory-v3-shadow/__tests__/tree.test.ts +0 -192
- package/src/plugins/defaults/memory-v3-shadow/assign.ts +0 -272
- package/src/plugins/defaults/memory-v3-shadow/core.ts +0 -26
- package/src/plugins/defaults/memory-v3-shadow/health.ts +0 -0
- package/src/plugins/defaults/memory-v3-shadow/needle.ts +0 -115
- package/src/plugins/defaults/memory-v3-shadow/provider-blocks.ts +0 -26
- package/src/plugins/defaults/memory-v3-shadow/reconcile.ts +0 -527
- package/src/plugins/defaults/memory-v3-shadow/router.ts +0 -190
- package/src/plugins/defaults/memory-v3-shadow/selector.ts +0 -226
- package/src/plugins/defaults/memory-v3-shadow/snapshot.ts +0 -209
- package/src/plugins/defaults/memory-v3-shadow/tree.ts +0 -174
- package/src/util/map-limit.ts +0 -27
|
@@ -31,7 +31,9 @@ import {
|
|
|
31
31
|
InvalidPluginNameError,
|
|
32
32
|
PluginAlreadyInstalledError,
|
|
33
33
|
PluginNotFoundError,
|
|
34
|
+
PluginPostinstallError,
|
|
34
35
|
PluginSourceUnavailableError,
|
|
36
|
+
type PostinstallRunner,
|
|
35
37
|
sanitizePluginName,
|
|
36
38
|
} from "../install-from-github.js";
|
|
37
39
|
|
|
@@ -54,6 +56,13 @@ const DOWNLOAD_HOST = "https://files.test/";
|
|
|
54
56
|
function makeContentsFetch(opts: {
|
|
55
57
|
tree: Record<string, Uint8Array | string | null>;
|
|
56
58
|
manifest?: unknown;
|
|
59
|
+
/**
|
|
60
|
+
* HTTP status to answer the manifest fetch with. Defaults to 200 (when
|
|
61
|
+
* `manifest` is provided) or 404 (when omitted). Set to a transient status
|
|
62
|
+
* (e.g. 500) to simulate a marketplace lookup that fails rather than being
|
|
63
|
+
* absent — exercising the degrade-to-first-party path.
|
|
64
|
+
*/
|
|
65
|
+
manifestStatus?: number;
|
|
57
66
|
}): FetchLike {
|
|
58
67
|
const MANIFEST_URL = `https://api.github.com/repos/${CANON_REPO}/contents/experimental/plugins/marketplace.json`;
|
|
59
68
|
const CONTENTS = `https://api.github.com/repos/${CANON_REPO}/contents/`;
|
|
@@ -90,6 +99,11 @@ function makeContentsFetch(opts: {
|
|
|
90
99
|
const url = typeof input === "string" ? input : input.toString();
|
|
91
100
|
|
|
92
101
|
if (url.startsWith(MANIFEST_URL)) {
|
|
102
|
+
if (opts.manifestStatus !== undefined && opts.manifestStatus !== 200) {
|
|
103
|
+
return new Response("manifest unavailable", {
|
|
104
|
+
status: opts.manifestStatus,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
93
107
|
if (opts.manifest === undefined) {
|
|
94
108
|
return new Response("not found", { status: 404 });
|
|
95
109
|
}
|
|
@@ -182,6 +196,33 @@ const unusedGitRunner: GitRunner = async (args) => {
|
|
|
182
196
|
);
|
|
183
197
|
};
|
|
184
198
|
|
|
199
|
+
/**
|
|
200
|
+
* Read the real, committed caveman adapter stub from the repo so the
|
|
201
|
+
* integration test exercises the adapter that ships rather than a fixture
|
|
202
|
+
* copy that could drift from it. Returns every stub file keyed by its
|
|
203
|
+
* Contents-API path (`experimental/plugins/caveman/<rel>`) so the fetch fake
|
|
204
|
+
* serves the whole stub — package.json, the adapter, and its templates — to
|
|
205
|
+
* the real postinstall runner. Resolves the stub relative to this test file.
|
|
206
|
+
*/
|
|
207
|
+
function readRealCavemanStub(): Record<string, string> {
|
|
208
|
+
const repoRel = "experimental/plugins/caveman";
|
|
209
|
+
const stubDir = join(import.meta.dir, "../../../../../", repoRel);
|
|
210
|
+
const tree: Record<string, string> = {};
|
|
211
|
+
const walk = (relDir: string): void => {
|
|
212
|
+
const absDir = relDir ? join(stubDir, relDir) : stubDir;
|
|
213
|
+
for (const entry of readdirSync(absDir, { withFileTypes: true })) {
|
|
214
|
+
const rel = relDir ? `${relDir}/${entry.name}` : entry.name;
|
|
215
|
+
if (entry.isDirectory()) {
|
|
216
|
+
walk(rel);
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
tree[`${repoRel}/${rel}`] = readFileSync(join(stubDir, rel), "utf-8");
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
walk("");
|
|
223
|
+
return tree;
|
|
224
|
+
}
|
|
225
|
+
|
|
185
226
|
describe("installPlugin — first-party", () => {
|
|
186
227
|
let ws: string;
|
|
187
228
|
let pluginsDir: string;
|
|
@@ -495,7 +536,7 @@ describe("installPlugin — marketplace resolution", () => {
|
|
|
495
536
|
source: {
|
|
496
537
|
source: "github",
|
|
497
538
|
repo: "JuliusBrussee/caveman",
|
|
498
|
-
ref: "
|
|
539
|
+
ref: "63a91ecadbf4c4719a4602a5abb00883f9966034",
|
|
499
540
|
},
|
|
500
541
|
description: "Ultra-compressed communication mode.",
|
|
501
542
|
},
|
|
@@ -503,7 +544,7 @@ describe("installPlugin — marketplace resolution", () => {
|
|
|
503
544
|
};
|
|
504
545
|
|
|
505
546
|
test("installs a whitelisted plugin by shallow-cloning its pinned repo + ref", async () => {
|
|
506
|
-
// GIVEN a marketplace whitelisting caveman at its repo root, pinned to a
|
|
547
|
+
// GIVEN a marketplace whitelisting caveman at its repo root, pinned to a commit SHA
|
|
507
548
|
const fetch = makeContentsFetch({ tree: {}, manifest: CAVEMAN_MANIFEST });
|
|
508
549
|
const calls: string[][] = [];
|
|
509
550
|
const runGit = fakeGitRunner({
|
|
@@ -513,7 +554,7 @@ describe("installPlugin — marketplace resolution", () => {
|
|
|
513
554
|
".claude-plugin": null,
|
|
514
555
|
".claude-plugin/plugin.json": "{}",
|
|
515
556
|
},
|
|
516
|
-
commit: "
|
|
557
|
+
commit: "63a91ecadbf4c4719a4602a5abb00883f9966034",
|
|
517
558
|
calls,
|
|
518
559
|
});
|
|
519
560
|
|
|
@@ -528,8 +569,8 @@ describe("installPlugin — marketplace resolution", () => {
|
|
|
528
569
|
// result reports the pinned ref and resolved commit, and `.git` is dropped
|
|
529
570
|
const target = join(pluginsDir, "caveman");
|
|
530
571
|
expect(result.target).toBe(target);
|
|
531
|
-
expect(result.ref).toBe("
|
|
532
|
-
expect(result.commit).toBe("
|
|
572
|
+
expect(result.ref).toBe("63a91ecadbf4c4719a4602a5abb00883f9966034");
|
|
573
|
+
expect(result.commit).toBe("63a91ecadbf4c4719a4602a5abb00883f9966034");
|
|
533
574
|
expect(result.fileCount).toBe(3);
|
|
534
575
|
expect(readFileSync(join(target, "package.json"), "utf-8")).toBe(
|
|
535
576
|
'{"name":"caveman"}',
|
|
@@ -541,7 +582,7 @@ describe("installPlugin — marketplace resolution", () => {
|
|
|
541
582
|
|
|
542
583
|
// AND the clone fetched the pinned ref from the pinned repo URL.
|
|
543
584
|
const fetchCall = calls.find((c) => c[0] === "fetch");
|
|
544
|
-
expect(fetchCall).toContain("
|
|
585
|
+
expect(fetchCall).toContain("63a91ecadbf4c4719a4602a5abb00883f9966034");
|
|
545
586
|
const remoteCall = calls.find((c) => c[0] === "remote");
|
|
546
587
|
expect(remoteCall?.at(-1)).toBe(
|
|
547
588
|
"https://github.com/JuliusBrussee/caveman.git",
|
|
@@ -553,8 +594,31 @@ describe("installPlugin — marketplace resolution", () => {
|
|
|
553
594
|
);
|
|
554
595
|
expect(manifest.source.kind).toBe("external");
|
|
555
596
|
expect(manifest.source.owner).toBe("JuliusBrussee");
|
|
556
|
-
expect(manifest.source.ref).toBe(
|
|
557
|
-
|
|
597
|
+
expect(manifest.source.ref).toBe(
|
|
598
|
+
"63a91ecadbf4c4719a4602a5abb00883f9966034",
|
|
599
|
+
);
|
|
600
|
+
expect(manifest.commit).toBe("63a91ecadbf4c4719a4602a5abb00883f9966034");
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
test("refuses to install when the checked-out commit differs from the pinned SHA", async () => {
|
|
604
|
+
// GIVEN a clone whose resolved HEAD does not match the manifest's pinned
|
|
605
|
+
// commit SHA — i.e. the upstream object served something unexpected
|
|
606
|
+
const fetch = makeContentsFetch({ tree: {}, manifest: CAVEMAN_MANIFEST });
|
|
607
|
+
const runGit = fakeGitRunner({
|
|
608
|
+
tree: { "package.json": '{"name":"caveman"}' },
|
|
609
|
+
commit: "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
// WHEN we install
|
|
613
|
+
// THEN the integrity check fails closed rather than materializing and
|
|
614
|
+
// later `import()`-ing code from an unexpected revision, and nothing lands
|
|
615
|
+
await expect(
|
|
616
|
+
installPlugin(
|
|
617
|
+
{ name: "caveman", ref: "main" },
|
|
618
|
+
{ fetch, runGit, workspacePluginsDir: pluginsDir },
|
|
619
|
+
),
|
|
620
|
+
).rejects.toBeInstanceOf(PluginSourceUnavailableError);
|
|
621
|
+
expect(readdirSync(pluginsDir)).toEqual([]);
|
|
558
622
|
});
|
|
559
623
|
|
|
560
624
|
test("a missing remote ref surfaces a clean not-found", async () => {
|
|
@@ -563,7 +627,8 @@ describe("installPlugin — marketplace resolution", () => {
|
|
|
563
627
|
const runGit = fakeGitRunner({
|
|
564
628
|
tree: {},
|
|
565
629
|
fetchError: Object.assign(new Error("git fetch failed"), {
|
|
566
|
-
stderr:
|
|
630
|
+
stderr:
|
|
631
|
+
"fatal: couldn't find remote ref 63a91ecadbf4c4719a4602a5abb00883f9966034",
|
|
567
632
|
}),
|
|
568
633
|
});
|
|
569
634
|
|
|
@@ -618,39 +683,177 @@ describe("installPlugin — marketplace resolution", () => {
|
|
|
618
683
|
});
|
|
619
684
|
});
|
|
620
685
|
|
|
621
|
-
test("
|
|
622
|
-
// GIVEN
|
|
623
|
-
// AND
|
|
686
|
+
test("overlays a curated adapter stub and runs its postinstall transform", async () => {
|
|
687
|
+
// GIVEN caveman is whitelisted externally
|
|
688
|
+
// AND we curate an adapter stub for it at experimental/plugins/caveman —
|
|
689
|
+
// the real, committed stub (package.json + postinstall.ts + templates), so
|
|
690
|
+
// this test exercises the adapter that ships, not a fixture copy of it
|
|
624
691
|
const fetch = makeContentsFetch({
|
|
692
|
+
tree: readRealCavemanStub(),
|
|
693
|
+
manifest: CAVEMAN_MANIFEST,
|
|
694
|
+
});
|
|
695
|
+
// AND the upstream clone is a Claude Code plugin: a name-mismatched
|
|
696
|
+
// package.json, the `.claude-plugin/plugin.json` manifest, and the
|
|
697
|
+
// terse-mode ruleset in skills/caveman/SKILL.md
|
|
698
|
+
const runGit = fakeGitRunner({
|
|
625
699
|
tree: {
|
|
626
|
-
"
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
700
|
+
"package.json": '{"name":"caveman-installer"}',
|
|
701
|
+
".claude-plugin/plugin.json": JSON.stringify({
|
|
702
|
+
name: "caveman",
|
|
703
|
+
description: "Ultra-compressed communication mode.",
|
|
704
|
+
}),
|
|
705
|
+
"skills/caveman/SKILL.md":
|
|
706
|
+
"---\nname: caveman\ndescription: terse mode\n---\n\nCAVEMAN MODE. Drop filler words. Keep technical substance.",
|
|
630
707
|
},
|
|
631
|
-
|
|
708
|
+
commit: "63a91ecadbf4c4719a4602a5abb00883f9966034",
|
|
632
709
|
});
|
|
633
710
|
|
|
634
|
-
// WHEN we install
|
|
635
|
-
// plugin is served entirely from the Contents API)
|
|
711
|
+
// WHEN we install (real postinstall runner — no injected stub)
|
|
636
712
|
const result = await installPlugin(
|
|
637
713
|
{ name: "caveman", ref: "main" },
|
|
638
|
-
{ fetch, runGit
|
|
714
|
+
{ fetch, runGit, workspacePluginsDir: pluginsDir },
|
|
639
715
|
);
|
|
640
716
|
|
|
641
|
-
// THEN the
|
|
642
|
-
//
|
|
643
|
-
//
|
|
717
|
+
// THEN the materialized tree is a valid Vellum plugin: package.json `name`
|
|
718
|
+
// matches the directory and declares the @vellumai/plugin-api peer dep
|
|
719
|
+
// (fixing both loader warnings), and the upstream installer name is gone
|
|
644
720
|
const target = join(pluginsDir, "caveman");
|
|
645
|
-
expect(result.
|
|
646
|
-
|
|
647
|
-
expect(
|
|
648
|
-
|
|
721
|
+
expect(result.commit).toBe("63a91ecadbf4c4719a4602a5abb00883f9966034");
|
|
722
|
+
const pkg = JSON.parse(readFileSync(join(target, "package.json"), "utf-8"));
|
|
723
|
+
expect(pkg.name).toBe("caveman");
|
|
724
|
+
expect(pkg.peerDependencies["@vellumai/plugin-api"]).toBeString();
|
|
725
|
+
expect(pkg.scripts?.postinstall).toBeUndefined();
|
|
726
|
+
|
|
727
|
+
// AND a pre-model-call hook is synthesized carrying the upstream ruleset,
|
|
728
|
+
// so caveman actually runs (terse mode injected on the user-facing call)
|
|
729
|
+
const hook = readFileSync(
|
|
730
|
+
join(target, "hooks", "pre-model-call.ts"),
|
|
731
|
+
"utf-8",
|
|
732
|
+
);
|
|
733
|
+
expect(hook).toContain("PreModelCallContext");
|
|
734
|
+
expect(hook).toContain('ctx.callSite !== "mainAgent"');
|
|
735
|
+
expect(hook).toContain("CAVEMAN MODE. Drop filler words.");
|
|
736
|
+
// AND the ruleset is sourced verbatim — the YAML frontmatter is stripped
|
|
737
|
+
expect(hook).not.toContain("description: terse mode");
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
test("does not run a postinstall for a raw clone without an adapter stub", async () => {
|
|
741
|
+
// GIVEN caveman is whitelisted but we curate NO adapter stub for it
|
|
742
|
+
// (the Contents API has no experimental/plugins/caveman directory)
|
|
743
|
+
const fetch = makeContentsFetch({ tree: {}, manifest: CAVEMAN_MANIFEST });
|
|
744
|
+
const runGit = fakeGitRunner({
|
|
745
|
+
tree: { "package.json": '{"name":"caveman"}', "hooks/init.ts": "//" },
|
|
746
|
+
commit: "63a91ecadbf4c4719a4602a5abb00883f9966034",
|
|
747
|
+
});
|
|
748
|
+
// AND a postinstall runner that fails the test if it is ever invoked
|
|
749
|
+
const runPostinstall: PostinstallRunner = async () => {
|
|
750
|
+
throw new Error("postinstall must not run for a stubless raw clone");
|
|
751
|
+
};
|
|
752
|
+
|
|
753
|
+
// WHEN we install
|
|
754
|
+
await installPlugin(
|
|
755
|
+
{ name: "caveman", ref: "main" },
|
|
756
|
+
{ fetch, runGit, runPostinstall, workspacePluginsDir: pluginsDir },
|
|
649
757
|
);
|
|
650
|
-
|
|
651
|
-
|
|
758
|
+
|
|
759
|
+
// THEN the clone is installed verbatim — no transform ran
|
|
760
|
+
const target = join(pluginsDir, "caveman");
|
|
761
|
+
expect(readFileSync(join(target, "package.json"), "utf-8")).toBe(
|
|
762
|
+
'{"name":"caveman"}',
|
|
652
763
|
);
|
|
653
764
|
});
|
|
765
|
+
|
|
766
|
+
test("aborts and rolls back when the adapter's postinstall fails", async () => {
|
|
767
|
+
// GIVEN a whitelisted plugin with a curated stub declaring a postinstall
|
|
768
|
+
const fetch = makeContentsFetch({
|
|
769
|
+
tree: {
|
|
770
|
+
"experimental/plugins/caveman/package.json": JSON.stringify({
|
|
771
|
+
name: "caveman",
|
|
772
|
+
scripts: { postinstall: "bun ./postinstall.ts" },
|
|
773
|
+
}),
|
|
774
|
+
"experimental/plugins/caveman/postinstall.ts": "// adapter",
|
|
775
|
+
},
|
|
776
|
+
manifest: CAVEMAN_MANIFEST,
|
|
777
|
+
});
|
|
778
|
+
const runGit = fakeGitRunner({
|
|
779
|
+
tree: { "package.json": '{"name":"caveman-installer"}' },
|
|
780
|
+
commit: "63a91ecadbf4c4719a4602a5abb00883f9966034",
|
|
781
|
+
});
|
|
782
|
+
// AND its adapter exits non-zero
|
|
783
|
+
const runPostinstall: PostinstallRunner = async () => {
|
|
784
|
+
throw new Error("adapter blew up");
|
|
785
|
+
};
|
|
786
|
+
|
|
787
|
+
// WHEN we install
|
|
788
|
+
// THEN it surfaces a PluginPostinstallError and nothing is materialized —
|
|
789
|
+
// better to fail loudly than ship a half-transformed plugin
|
|
790
|
+
await expect(
|
|
791
|
+
installPlugin(
|
|
792
|
+
{ name: "caveman", ref: "main" },
|
|
793
|
+
{ fetch, runGit, runPostinstall, workspacePluginsDir: pluginsDir },
|
|
794
|
+
),
|
|
795
|
+
).rejects.toBeInstanceOf(PluginPostinstallError);
|
|
796
|
+
expect(readdirSync(pluginsDir)).toEqual([]);
|
|
797
|
+
});
|
|
798
|
+
|
|
799
|
+
test("rejects a stub whose postinstall is not a single bun invocation", async () => {
|
|
800
|
+
// GIVEN a curated stub whose postinstall is an arbitrary shell command
|
|
801
|
+
// rather than the supported `bun <script>` adapter convention
|
|
802
|
+
const fetch = makeContentsFetch({
|
|
803
|
+
tree: {
|
|
804
|
+
"experimental/plugins/caveman/package.json": JSON.stringify({
|
|
805
|
+
name: "caveman",
|
|
806
|
+
scripts: { postinstall: "rm -rf /" },
|
|
807
|
+
}),
|
|
808
|
+
},
|
|
809
|
+
manifest: CAVEMAN_MANIFEST,
|
|
810
|
+
});
|
|
811
|
+
const runGit = fakeGitRunner({
|
|
812
|
+
tree: { "package.json": '{"name":"caveman-installer"}' },
|
|
813
|
+
commit: "63a91ecadbf4c4719a4602a5abb00883f9966034",
|
|
814
|
+
});
|
|
815
|
+
|
|
816
|
+
// WHEN we install (the default runner is never reached — resolution rejects
|
|
817
|
+
// the command before anything executes)
|
|
818
|
+
// THEN it surfaces a PluginPostinstallError and nothing is materialized
|
|
819
|
+
await expect(
|
|
820
|
+
installPlugin(
|
|
821
|
+
{ name: "caveman", ref: "main" },
|
|
822
|
+
{ fetch, runGit, workspacePluginsDir: pluginsDir },
|
|
823
|
+
),
|
|
824
|
+
).rejects.toBeInstanceOf(PluginPostinstallError);
|
|
825
|
+
expect(readdirSync(pluginsDir)).toEqual([]);
|
|
826
|
+
});
|
|
827
|
+
|
|
828
|
+
test("refuses to install an adapter stub as first-party when the marketplace lookup fails", async () => {
|
|
829
|
+
// GIVEN the marketplace lookup fails transiently (e.g. rate-limit / 5xx)
|
|
830
|
+
// rather than being absent, so resolution degrades to the first-party path
|
|
831
|
+
// AND the same-named in-repo directory is an adapter stub (a package.json
|
|
832
|
+
// with a postinstall + adapter script, but no hooks/tools of its own)
|
|
833
|
+
const fetch = makeContentsFetch({
|
|
834
|
+
tree: {
|
|
835
|
+
"experimental/plugins/caveman/package.json": JSON.stringify({
|
|
836
|
+
name: "caveman",
|
|
837
|
+
scripts: { postinstall: "bun ./postinstall.ts" },
|
|
838
|
+
}),
|
|
839
|
+
"experimental/plugins/caveman/postinstall.ts": "// adapter",
|
|
840
|
+
},
|
|
841
|
+
manifestStatus: 500,
|
|
842
|
+
});
|
|
843
|
+
|
|
844
|
+
// WHEN we install the name (git never runs — there is no external source to
|
|
845
|
+
// clone because the marketplace that would have named it was unreadable)
|
|
846
|
+
// THEN it fails loudly with a PluginPostinstallError instead of
|
|
847
|
+
// materializing the bare stub as a non-functional standalone plugin, and
|
|
848
|
+
// nothing is left behind on disk
|
|
849
|
+
await expect(
|
|
850
|
+
installPlugin(
|
|
851
|
+
{ name: "caveman", ref: "main" },
|
|
852
|
+
{ fetch, runGit: unusedGitRunner, workspacePluginsDir: pluginsDir },
|
|
853
|
+
),
|
|
854
|
+
).rejects.toBeInstanceOf(PluginPostinstallError);
|
|
855
|
+
expect(readdirSync(pluginsDir)).toEqual([]);
|
|
856
|
+
});
|
|
654
857
|
});
|
|
655
858
|
|
|
656
859
|
describe("sanitizePluginName", () => {
|
|
@@ -116,7 +116,7 @@ describe("getPluginDetails", () => {
|
|
|
116
116
|
source: {
|
|
117
117
|
source: "github",
|
|
118
118
|
repo: "example-org/caveman",
|
|
119
|
-
ref: "
|
|
119
|
+
ref: "1111111111111111111111111111111111111111",
|
|
120
120
|
},
|
|
121
121
|
description: "manifest description",
|
|
122
122
|
homepage: "https://example.com/caveman",
|
|
@@ -152,7 +152,7 @@ describe("getPluginDetails", () => {
|
|
|
152
152
|
expect(details.source).toEqual({
|
|
153
153
|
kind: "github",
|
|
154
154
|
repo: "example-org/caveman",
|
|
155
|
-
ref: "
|
|
155
|
+
ref: "1111111111111111111111111111111111111111",
|
|
156
156
|
});
|
|
157
157
|
expect(details.installed).toBe(false);
|
|
158
158
|
expect(details.readme).toContain("Grug brain plugin");
|
|
@@ -176,7 +176,7 @@ describe("getPluginDetails", () => {
|
|
|
176
176
|
source: {
|
|
177
177
|
source: "github",
|
|
178
178
|
repo: "example-org/caveman",
|
|
179
|
-
ref: "
|
|
179
|
+
ref: "2222222222222222222222222222222222222222",
|
|
180
180
|
},
|
|
181
181
|
},
|
|
182
182
|
],
|
|
@@ -215,16 +215,20 @@ describe("getPluginDetails", () => {
|
|
|
215
215
|
);
|
|
216
216
|
|
|
217
217
|
// THEN the external repo is read at the plugin's pinned ref, not `main`
|
|
218
|
-
expect(contentsRefs.get("external")).toBe(
|
|
219
|
-
|
|
220
|
-
|
|
218
|
+
expect(contentsRefs.get("external")).toBe(
|
|
219
|
+
"2222222222222222222222222222222222222222",
|
|
220
|
+
);
|
|
221
|
+
// AND the marketplace entry owns the name, so the first-party directory is
|
|
222
|
+
// never probed (its same-named in-repo dir would be the adapter stub)
|
|
223
|
+
expect(contentsRefs.has("first-party")).toBe(false);
|
|
221
224
|
// AND the README from the pinned ref is surfaced
|
|
222
225
|
expect(details.readme).toContain("Caveman");
|
|
223
226
|
});
|
|
224
227
|
|
|
225
|
-
test("
|
|
226
|
-
// GIVEN
|
|
227
|
-
//
|
|
228
|
+
test("a marketplace entry owns a name shared by an in-repo stub dir", async () => {
|
|
229
|
+
// GIVEN a marketplace entry for "simple-memory"
|
|
230
|
+
// AND a same-named in-repo directory — caveman-style, this is the plugin's
|
|
231
|
+
// adapter stub, not a standalone first-party plugin
|
|
228
232
|
const fetch = makeFetch({
|
|
229
233
|
marketplace: {
|
|
230
234
|
name: "vellum",
|
|
@@ -234,17 +238,17 @@ describe("getPluginDetails", () => {
|
|
|
234
238
|
source: {
|
|
235
239
|
source: "github",
|
|
236
240
|
repo: "example-org/simple-memory",
|
|
237
|
-
ref: "
|
|
241
|
+
ref: "9999999999999999999999999999999999999999",
|
|
238
242
|
},
|
|
239
243
|
},
|
|
240
244
|
],
|
|
241
245
|
},
|
|
242
246
|
listings: {
|
|
243
|
-
"
|
|
244
|
-
fileEntry("README.md", "raw://
|
|
247
|
+
"example-org/simple-memory": [
|
|
248
|
+
fileEntry("README.md", "raw://ext/readme"),
|
|
245
249
|
],
|
|
246
250
|
},
|
|
247
|
-
raw: { "raw://
|
|
251
|
+
raw: { "raw://ext/readme": "# Simple Memory (external)" },
|
|
248
252
|
});
|
|
249
253
|
|
|
250
254
|
// WHEN we resolve the detail view
|
|
@@ -253,9 +257,14 @@ describe("getPluginDetails", () => {
|
|
|
253
257
|
{ fetch, workspacePluginsDir: workspace },
|
|
254
258
|
);
|
|
255
259
|
|
|
256
|
-
// THEN the
|
|
257
|
-
|
|
258
|
-
expect(details.
|
|
260
|
+
// THEN the external marketplace source is used — the stub dir is an
|
|
261
|
+
// adapter overlay for the clone, so it does not override the claim
|
|
262
|
+
expect(details.source).toEqual({
|
|
263
|
+
kind: "github",
|
|
264
|
+
repo: "example-org/simple-memory",
|
|
265
|
+
ref: "9999999999999999999999999999999999999999",
|
|
266
|
+
});
|
|
267
|
+
expect(details.readme).toContain("external");
|
|
259
268
|
});
|
|
260
269
|
|
|
261
270
|
test("prefers an installed copy's README and package.json over the repo", async () => {
|
|
@@ -278,7 +287,7 @@ describe("getPluginDetails", () => {
|
|
|
278
287
|
source: {
|
|
279
288
|
source: "github",
|
|
280
289
|
repo: "example-org/caveman",
|
|
281
|
-
ref: "
|
|
290
|
+
ref: "1111111111111111111111111111111111111111",
|
|
282
291
|
},
|
|
283
292
|
description: "manifest description",
|
|
284
293
|
},
|
|
@@ -330,7 +339,7 @@ describe("getPluginDetails", () => {
|
|
|
330
339
|
source: {
|
|
331
340
|
source: "github",
|
|
332
341
|
repo: "example-org/caveman",
|
|
333
|
-
ref: "
|
|
342
|
+
ref: "1111111111111111111111111111111111111111",
|
|
334
343
|
},
|
|
335
344
|
description: "manifest description",
|
|
336
345
|
license: "MIT",
|
|
@@ -353,7 +362,7 @@ describe("getPluginDetails", () => {
|
|
|
353
362
|
expect(details.source).toEqual({
|
|
354
363
|
kind: "github",
|
|
355
364
|
repo: "example-org/caveman",
|
|
356
|
-
ref: "
|
|
365
|
+
ref: "1111111111111111111111111111111111111111",
|
|
357
366
|
});
|
|
358
367
|
});
|
|
359
368
|
|
|
@@ -18,6 +18,11 @@ import {
|
|
|
18
18
|
const MANIFEST_URL_PREFIX =
|
|
19
19
|
"https://api.github.com/repos/vellum-ai/vellum-assistant/contents/experimental/plugins/marketplace.json";
|
|
20
20
|
|
|
21
|
+
// External marketplace refs must be full commit SHAs (immutable). Fixtures use
|
|
22
|
+
// realistic 40-char hex object names rather than tags/branches.
|
|
23
|
+
const CAVEMAN_SHA = "63a91ecadbf4c4719a4602a5abb00883f9966034";
|
|
24
|
+
const NESTED_SHA = "0123456789abcdef0123456789abcdef01234567";
|
|
25
|
+
|
|
21
26
|
/** Serve `body` (any value) as the raw manifest file at the manifest URL. */
|
|
22
27
|
function manifestFetch(body: unknown, status = 200, raw?: string): FetchLike {
|
|
23
28
|
return (async (input: RequestInfo | URL) => {
|
|
@@ -41,7 +46,7 @@ const VALID_MANIFEST = {
|
|
|
41
46
|
source: {
|
|
42
47
|
source: "github",
|
|
43
48
|
repo: "JuliusBrussee/caveman",
|
|
44
|
-
ref:
|
|
49
|
+
ref: CAVEMAN_SHA,
|
|
45
50
|
},
|
|
46
51
|
description: "Ultra-compressed communication mode.",
|
|
47
52
|
category: "productivity",
|
|
@@ -52,7 +57,7 @@ const VALID_MANIFEST = {
|
|
|
52
57
|
source: "github",
|
|
53
58
|
repo: "acme/monorepo",
|
|
54
59
|
path: "packages/nested",
|
|
55
|
-
ref:
|
|
60
|
+
ref: NESTED_SHA,
|
|
56
61
|
},
|
|
57
62
|
},
|
|
58
63
|
],
|
|
@@ -71,7 +76,7 @@ describe("fetchMarketplaceEntries", () => {
|
|
|
71
76
|
expect(entries[0]!.source).toEqual({
|
|
72
77
|
source: "github",
|
|
73
78
|
repo: "JuliusBrussee/caveman",
|
|
74
|
-
ref:
|
|
79
|
+
ref: CAVEMAN_SHA,
|
|
75
80
|
});
|
|
76
81
|
expect(entries[1]!.source.path).toBe("packages/nested");
|
|
77
82
|
});
|
|
@@ -123,6 +128,51 @@ describe("fetchMarketplaceEntries", () => {
|
|
|
123
128
|
).rejects.toBeInstanceOf(MarketplaceFetchError);
|
|
124
129
|
});
|
|
125
130
|
|
|
131
|
+
test("rejects a mutable tag or branch ref (must be a full commit SHA)", async () => {
|
|
132
|
+
// GIVEN an entry whose source pins a mutable tag rather than a commit SHA
|
|
133
|
+
const fetch = manifestFetch({
|
|
134
|
+
name: "x",
|
|
135
|
+
plugins: [
|
|
136
|
+
{
|
|
137
|
+
name: "caveman",
|
|
138
|
+
source: {
|
|
139
|
+
source: "github",
|
|
140
|
+
repo: "JuliusBrussee/caveman",
|
|
141
|
+
ref: "v1.8.2",
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// WHEN / THEN the mutable ref is rejected: a retag could repoint it at
|
|
148
|
+
// attacker code the daemon later dynamically imports.
|
|
149
|
+
await expect(
|
|
150
|
+
fetchMarketplaceEntries({ fetch }, { ref: "main" }),
|
|
151
|
+
).rejects.toBeInstanceOf(MarketplaceFetchError);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("rejects an abbreviated commit SHA", async () => {
|
|
155
|
+
// GIVEN an entry whose source uses a short (ambiguous, non-pinning) SHA
|
|
156
|
+
const fetch = manifestFetch({
|
|
157
|
+
name: "x",
|
|
158
|
+
plugins: [
|
|
159
|
+
{
|
|
160
|
+
name: "caveman",
|
|
161
|
+
source: {
|
|
162
|
+
source: "github",
|
|
163
|
+
repo: "JuliusBrussee/caveman",
|
|
164
|
+
ref: "63a91ec",
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
],
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// WHEN / THEN only a full object name pins the install immutably
|
|
171
|
+
await expect(
|
|
172
|
+
fetchMarketplaceEntries({ fetch }, { ref: "main" }),
|
|
173
|
+
).rejects.toBeInstanceOf(MarketplaceFetchError);
|
|
174
|
+
});
|
|
175
|
+
|
|
126
176
|
test("rejects a manifest entry missing a pinned ref", async () => {
|
|
127
177
|
// GIVEN an entry whose source omits the required ref
|
|
128
178
|
const fetch = manifestFetch({
|
|
@@ -169,7 +219,7 @@ describe("resolveMarketplaceSource", () => {
|
|
|
169
219
|
source: {
|
|
170
220
|
source: "github",
|
|
171
221
|
repo: "JuliusBrussee/caveman",
|
|
172
|
-
ref:
|
|
222
|
+
ref: CAVEMAN_SHA,
|
|
173
223
|
},
|
|
174
224
|
},
|
|
175
225
|
{
|
|
@@ -178,7 +228,7 @@ describe("resolveMarketplaceSource", () => {
|
|
|
178
228
|
source: "github",
|
|
179
229
|
repo: "acme/monorepo",
|
|
180
230
|
path: "packages/nested",
|
|
181
|
-
ref:
|
|
231
|
+
ref: NESTED_SHA,
|
|
182
232
|
},
|
|
183
233
|
},
|
|
184
234
|
];
|
|
@@ -193,7 +243,7 @@ describe("resolveMarketplaceSource", () => {
|
|
|
193
243
|
owner: "JuliusBrussee",
|
|
194
244
|
repo: "caveman",
|
|
195
245
|
path: "",
|
|
196
|
-
ref:
|
|
246
|
+
ref: CAVEMAN_SHA,
|
|
197
247
|
});
|
|
198
248
|
});
|
|
199
249
|
|
|
@@ -207,7 +257,7 @@ describe("resolveMarketplaceSource", () => {
|
|
|
207
257
|
owner: "acme",
|
|
208
258
|
repo: "monorepo",
|
|
209
259
|
path: "packages/nested",
|
|
210
|
-
ref:
|
|
260
|
+
ref: NESTED_SHA,
|
|
211
261
|
});
|
|
212
262
|
});
|
|
213
263
|
|