@vellumai/assistant 0.12.2-staging.5 → 0.12.2-staging.6
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/Dockerfile +7 -7
- package/openapi.yaml +151 -53
- package/package.json +5 -4
- package/scripts/bundled-plugin-packages.ts +154 -0
- package/scripts/generate-bundled-plugin-packages.ts +29 -0
- package/scripts/test.ts +15 -13
- package/src/__tests__/managed-store.test.ts +121 -0
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +88 -0
- package/src/calls/__tests__/voice-control-protocol.test.ts +24 -2
- package/src/calls/__tests__/voice-session-bridge.test.ts +22 -2
- package/src/calls/voice-control-protocol.ts +13 -4
- package/src/calls/voice-session-bridge.ts +27 -6
- package/src/cli/commands/__tests__/plugins.test.ts +66 -0
- package/src/cli/commands/plugins.ts +50 -18
- package/src/cli/lib/__tests__/install-from-github.test.ts +67 -0
- package/src/cli/lib/__tests__/local-plugin-upgrade.test.ts +169 -0
- package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +57 -0
- package/src/cli/lib/__tests__/plugin-catalog-platform.test.ts +14 -0
- package/src/cli/lib/__tests__/plugin-catalog-resolve.test.ts +27 -1
- package/src/cli/lib/__tests__/plugin-details.test.ts +9 -2
- package/src/cli/lib/__tests__/plugin-marketplace.test.ts +20 -0
- package/src/cli/lib/__tests__/plugins-install-offline.test.ts +43 -0
- package/src/cli/lib/__tests__/search-plugins.test.ts +31 -0
- package/src/cli/lib/bundled-plugin-packages.json +4 -0
- package/src/cli/lib/bundled-plugin-packages.ts +87 -0
- package/src/cli/lib/diff-plugin.ts +1 -1
- package/src/cli/lib/inspect-plugin.ts +80 -12
- package/src/cli/lib/install-from-github.ts +133 -76
- package/src/cli/lib/plugin-catalog-cache.ts +22 -3
- package/src/cli/lib/plugin-catalog-local.ts +13 -3
- package/src/cli/lib/plugin-catalog-platform.ts +6 -1
- package/src/cli/lib/plugin-catalog-resolve.ts +20 -0
- package/src/cli/lib/plugin-details.ts +12 -0
- package/src/cli/lib/plugin-marketplace.ts +121 -21
- package/src/cli/lib/plugin-pin-history.ts +5 -2
- package/src/cli/lib/search-plugins.ts +58 -16
- package/src/cli/lib/upgrade-plugin.ts +48 -3
- package/src/config/bundled-skills/skill-management/SKILL.md +1 -1
- package/src/config/bundled-skills/skill-management/TOOLS.json +6 -6
- package/src/daemon/conversation-tool-setup.ts +4 -22
- package/src/live-voice/__tests__/live-voice-vad.test.ts +624 -3
- package/src/live-voice/__tests__/session-controls.test.ts +18 -0
- package/src/live-voice/live-voice-session.ts +575 -53
- package/src/live-voice/session-controls.ts +7 -3
- package/src/monitoring/plugin-auto-update.ts +6 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +5 -0
- package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +1 -1
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +102 -0
- package/src/runtime/routes/plugins-routes.ts +69 -49
- package/src/skills/managed-store.ts +98 -32
- package/src/tools/skills/find-similar-skills.test.ts +3 -0
- package/src/tools/skills/resolve-execute-invocation.ts +24 -0
- package/src/tools/skills/scaffold-managed.ts +16 -13
package/Dockerfile
CHANGED
|
@@ -130,13 +130,13 @@ RUN bun run scripts/smoke-container-workspace-dependencies.ts
|
|
|
130
130
|
# The repo-root .dockerignore decides which skills (and files) ship.
|
|
131
131
|
COPY skills /app/skills
|
|
132
132
|
|
|
133
|
-
# Materialize the git-ignored offline
|
|
134
|
-
#
|
|
135
|
-
#
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
133
|
+
# Materialize the git-ignored offline marketplace and reviewed local packages.
|
|
134
|
+
# The build installs with --ignore-scripts, so the postinstall sync that creates
|
|
135
|
+
# these static imports in development never runs here.
|
|
136
|
+
COPY plugins /app/plugins
|
|
137
|
+
COPY meta/sync-bundled-copies.ts /app/meta/
|
|
138
|
+
RUN cd /app && bun run meta/sync-bundled-copies.ts --plugins-only && \
|
|
139
|
+
bun run assistant/scripts/generate-bundled-plugin-packages.ts
|
|
140
140
|
|
|
141
141
|
# Install assistant CLI launcher backed by the bundled assistant package
|
|
142
142
|
RUN printf '#!/usr/bin/env sh\nexec bun run /app/assistant/src/index.ts "$@"\n' > /usr/local/bin/assistant && \
|
package/openapi.yaml
CHANGED
|
@@ -26622,26 +26622,44 @@ paths:
|
|
|
26622
26622
|
description: Resolved version (installed copy first, then repo `package.json`).
|
|
26623
26623
|
source:
|
|
26624
26624
|
anyOf:
|
|
26625
|
-
-
|
|
26626
|
-
|
|
26627
|
-
|
|
26628
|
-
|
|
26629
|
-
|
|
26630
|
-
|
|
26631
|
-
|
|
26632
|
-
|
|
26633
|
-
|
|
26634
|
-
|
|
26635
|
-
|
|
26636
|
-
|
|
26637
|
-
|
|
26638
|
-
|
|
26639
|
-
|
|
26640
|
-
|
|
26641
|
-
|
|
26642
|
-
|
|
26643
|
-
|
|
26644
|
-
|
|
26625
|
+
- oneOf:
|
|
26626
|
+
- type: object
|
|
26627
|
+
properties:
|
|
26628
|
+
kind:
|
|
26629
|
+
type: string
|
|
26630
|
+
const: github
|
|
26631
|
+
repo:
|
|
26632
|
+
type: string
|
|
26633
|
+
description: "`owner/repo` of the external plugin repository."
|
|
26634
|
+
path:
|
|
26635
|
+
description: Directory within the repo, when the plugin is not at the root.
|
|
26636
|
+
type: string
|
|
26637
|
+
ref:
|
|
26638
|
+
type: string
|
|
26639
|
+
description: Pinned git ref the plugin is fetched from.
|
|
26640
|
+
required:
|
|
26641
|
+
- kind
|
|
26642
|
+
- repo
|
|
26643
|
+
- ref
|
|
26644
|
+
additionalProperties: false
|
|
26645
|
+
- type: object
|
|
26646
|
+
properties:
|
|
26647
|
+
kind:
|
|
26648
|
+
type: string
|
|
26649
|
+
const: local
|
|
26650
|
+
path:
|
|
26651
|
+
type: string
|
|
26652
|
+
description: Package key in the assistant bundle.
|
|
26653
|
+
version:
|
|
26654
|
+
type: string
|
|
26655
|
+
description: Bundled package version.
|
|
26656
|
+
required:
|
|
26657
|
+
- kind
|
|
26658
|
+
- path
|
|
26659
|
+
- version
|
|
26660
|
+
additionalProperties: false
|
|
26661
|
+
description: Reviewed source of the marketplace entry.
|
|
26662
|
+
type: object
|
|
26645
26663
|
- type: "null"
|
|
26646
26664
|
description: Pinned origin from the marketplace entry, or null when an installed copy has no catalog entry.
|
|
26647
26665
|
readme:
|
|
@@ -26996,28 +27014,42 @@ paths:
|
|
|
26996
27014
|
description: ISO-8601 install timestamp from the sidecar; null when absent.
|
|
26997
27015
|
source:
|
|
26998
27016
|
anyOf:
|
|
26999
|
-
-
|
|
27000
|
-
|
|
27001
|
-
|
|
27002
|
-
|
|
27003
|
-
|
|
27004
|
-
|
|
27005
|
-
|
|
27006
|
-
|
|
27007
|
-
|
|
27008
|
-
|
|
27009
|
-
|
|
27010
|
-
|
|
27011
|
-
|
|
27012
|
-
|
|
27013
|
-
|
|
27014
|
-
|
|
27015
|
-
|
|
27016
|
-
|
|
27017
|
-
|
|
27018
|
-
|
|
27019
|
-
|
|
27017
|
+
- oneOf:
|
|
27018
|
+
- type: object
|
|
27019
|
+
properties:
|
|
27020
|
+
kind:
|
|
27021
|
+
type: string
|
|
27022
|
+
const: github
|
|
27023
|
+
owner:
|
|
27024
|
+
type: string
|
|
27025
|
+
repo:
|
|
27026
|
+
type: string
|
|
27027
|
+
path:
|
|
27028
|
+
type: string
|
|
27029
|
+
ref:
|
|
27030
|
+
type: string
|
|
27031
|
+
required:
|
|
27032
|
+
- kind
|
|
27033
|
+
- owner
|
|
27034
|
+
- repo
|
|
27035
|
+
- ref
|
|
27036
|
+
additionalProperties: false
|
|
27037
|
+
- type: object
|
|
27038
|
+
properties:
|
|
27039
|
+
kind:
|
|
27040
|
+
type: string
|
|
27041
|
+
const: local
|
|
27042
|
+
path:
|
|
27043
|
+
type: string
|
|
27044
|
+
version:
|
|
27045
|
+
type: string
|
|
27046
|
+
required:
|
|
27047
|
+
- kind
|
|
27048
|
+
- path
|
|
27049
|
+
- version
|
|
27050
|
+
additionalProperties: false
|
|
27020
27051
|
description: Source coordinates recorded in the install-time provenance sidecar.
|
|
27052
|
+
type: object
|
|
27021
27053
|
- type: "null"
|
|
27022
27054
|
description: Source recorded at install time; null when no sidecar exists.
|
|
27023
27055
|
localChanges:
|
|
@@ -27076,6 +27108,9 @@ paths:
|
|
|
27076
27108
|
anyOf:
|
|
27077
27109
|
- type: object
|
|
27078
27110
|
properties:
|
|
27111
|
+
kind:
|
|
27112
|
+
type: string
|
|
27113
|
+
const: local
|
|
27079
27114
|
repo:
|
|
27080
27115
|
type: string
|
|
27081
27116
|
description: "`owner/repo` of the external plugin repository."
|
|
@@ -27111,6 +27146,9 @@ paths:
|
|
|
27111
27146
|
marketplaceRef:
|
|
27112
27147
|
type: string
|
|
27113
27148
|
description: Ref of the canonical repo the marketplace manifest was read from.
|
|
27149
|
+
version:
|
|
27150
|
+
description: Bundled package version.
|
|
27151
|
+
type: string
|
|
27114
27152
|
required:
|
|
27115
27153
|
- repo
|
|
27116
27154
|
- path
|
|
@@ -27547,7 +27585,7 @@ paths:
|
|
|
27547
27585
|
description: Install name. Matches `assistant plugins install <name>`.
|
|
27548
27586
|
path:
|
|
27549
27587
|
type: string
|
|
27550
|
-
description:
|
|
27588
|
+
description: Human-readable catalog origin locator.
|
|
27551
27589
|
description:
|
|
27552
27590
|
description: Short description, when known (external entries only today).
|
|
27553
27591
|
type: string
|
|
@@ -27561,27 +27599,87 @@ paths:
|
|
|
27561
27599
|
- type: string
|
|
27562
27600
|
- type: "null"
|
|
27563
27601
|
description: Marketplace category slug (Skills taxonomy); null when the entry declares none.
|
|
27564
|
-
|
|
27602
|
+
integration:
|
|
27565
27603
|
type: object
|
|
27566
27604
|
properties:
|
|
27567
27605
|
kind:
|
|
27568
27606
|
type: string
|
|
27569
|
-
const:
|
|
27570
|
-
|
|
27607
|
+
const: mcp
|
|
27608
|
+
displayName:
|
|
27609
|
+
type: string
|
|
27610
|
+
documentationUrl:
|
|
27571
27611
|
type: string
|
|
27572
|
-
|
|
27573
|
-
|
|
27574
|
-
|
|
27612
|
+
verifiedAt:
|
|
27613
|
+
type: string
|
|
27614
|
+
verification:
|
|
27615
|
+
type: string
|
|
27616
|
+
const: documentation-only
|
|
27617
|
+
setup:
|
|
27618
|
+
type: object
|
|
27619
|
+
properties:
|
|
27620
|
+
mode:
|
|
27621
|
+
type: string
|
|
27622
|
+
enum:
|
|
27623
|
+
- oauth
|
|
27624
|
+
- manual
|
|
27625
|
+
instructions:
|
|
27626
|
+
type: string
|
|
27627
|
+
required:
|
|
27628
|
+
- mode
|
|
27629
|
+
- instructions
|
|
27630
|
+
additionalProperties: false
|
|
27631
|
+
logo:
|
|
27575
27632
|
type: string
|
|
27576
|
-
|
|
27633
|
+
oauthProvider:
|
|
27577
27634
|
type: string
|
|
27578
|
-
description: Pinned git ref the plugin is fetched from.
|
|
27579
27635
|
required:
|
|
27580
27636
|
- kind
|
|
27581
|
-
-
|
|
27582
|
-
-
|
|
27637
|
+
- displayName
|
|
27638
|
+
- documentationUrl
|
|
27639
|
+
- verifiedAt
|
|
27640
|
+
- verification
|
|
27641
|
+
- setup
|
|
27642
|
+
- logo
|
|
27583
27643
|
additionalProperties: false
|
|
27584
|
-
|
|
27644
|
+
source:
|
|
27645
|
+
oneOf:
|
|
27646
|
+
- type: object
|
|
27647
|
+
properties:
|
|
27648
|
+
kind:
|
|
27649
|
+
type: string
|
|
27650
|
+
const: github
|
|
27651
|
+
repo:
|
|
27652
|
+
type: string
|
|
27653
|
+
description: "`owner/repo` of the external plugin repository."
|
|
27654
|
+
path:
|
|
27655
|
+
description: Directory within the repo, when the plugin is not at the root.
|
|
27656
|
+
type: string
|
|
27657
|
+
ref:
|
|
27658
|
+
type: string
|
|
27659
|
+
description: Pinned git ref the plugin is fetched from.
|
|
27660
|
+
required:
|
|
27661
|
+
- kind
|
|
27662
|
+
- repo
|
|
27663
|
+
- ref
|
|
27664
|
+
additionalProperties: false
|
|
27665
|
+
- type: object
|
|
27666
|
+
properties:
|
|
27667
|
+
kind:
|
|
27668
|
+
type: string
|
|
27669
|
+
const: local
|
|
27670
|
+
path:
|
|
27671
|
+
type: string
|
|
27672
|
+
description: Package key in the assistant bundle.
|
|
27673
|
+
version:
|
|
27674
|
+
type: string
|
|
27675
|
+
description: Bundled package version.
|
|
27676
|
+
required:
|
|
27677
|
+
- kind
|
|
27678
|
+
- path
|
|
27679
|
+
- version
|
|
27680
|
+
additionalProperties: false
|
|
27681
|
+
description: Reviewed source of the marketplace entry.
|
|
27682
|
+
type: object
|
|
27585
27683
|
required:
|
|
27586
27684
|
- name
|
|
27587
27685
|
- path
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vellumai/assistant",
|
|
3
|
-
"version": "0.12.2-staging.
|
|
3
|
+
"version": "0.12.2-staging.6",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -24,16 +24,17 @@
|
|
|
24
24
|
"lint:unused": "knip --include files,dependencies,unlisted",
|
|
25
25
|
"lint:circular": "bun run scripts/check-circular-deps.ts",
|
|
26
26
|
"lint:unused:production": "knip --production --include exports",
|
|
27
|
-
"
|
|
27
|
+
"sync:bundled-catalog": "cd .. && bun run meta/sync-bundled-copies.ts && bun run assistant/scripts/generate-bundled-plugin-packages.ts",
|
|
28
|
+
"pretypecheck": "bun run sync:bundled-catalog",
|
|
28
29
|
"typecheck": "bunx tsc --noEmit",
|
|
29
|
-
"pretypecheck:fast": "
|
|
30
|
+
"pretypecheck:fast": "bun run sync:bundled-catalog",
|
|
30
31
|
"typecheck:fast": "bunx tsgo --noEmit",
|
|
31
32
|
"test": "bun run scripts/test.ts",
|
|
32
33
|
"test:coverage": "COVERAGE=true bun run scripts/test.ts",
|
|
33
34
|
"test:stable": "EXCLUDE_EXPERIMENTAL=true bun run scripts/test.ts",
|
|
34
35
|
"test:bench": "find src -type f -name '*.benchmark.test.ts' -print0 | xargs -0 -P 1 -I {} bun test {}",
|
|
35
36
|
"test:filesystem-tools": "bash scripts/test-filesystem-tools.sh",
|
|
36
|
-
"postinstall": "cd .. && (git config core.hooksPath || git config core.hooksPath .githooks 2>/dev/null || true) && ([ -f meta/sync-bundled-copies.ts ] && bun run meta/sync-bundled-copies.ts 2>/dev/null || true)"
|
|
37
|
+
"postinstall": "cd .. && (git config core.hooksPath || git config core.hooksPath .githooks 2>/dev/null || true) && ([ -f meta/sync-bundled-copies.ts ] && (bun run meta/sync-bundled-copies.ts && bun run assistant/scripts/generate-bundled-plugin-packages.ts) 2>/dev/null || true)"
|
|
37
38
|
},
|
|
38
39
|
"dependencies": {
|
|
39
40
|
"@agentclientprotocol/sdk": "0.25.0",
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { lstatSync, readdirSync, readFileSync, realpathSync } from "node:fs";
|
|
2
|
+
import { join, relative, resolve, sep } from "node:path";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
AGENT_PLUGINS_MCP_SCHEMA_URL,
|
|
6
|
+
SpecMcpDocumentSchema,
|
|
7
|
+
SpecMcpServerSchema,
|
|
8
|
+
} from "../src/mcp/spec-schema.js";
|
|
9
|
+
import {
|
|
10
|
+
readPluginManifest,
|
|
11
|
+
STANDARD_PLUGIN_MANIFEST,
|
|
12
|
+
} from "../src/util/plugin-manifest.js";
|
|
13
|
+
|
|
14
|
+
export interface BundledPackageFile {
|
|
15
|
+
path: string;
|
|
16
|
+
contentBase64: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface BundledPackage {
|
|
20
|
+
version: string;
|
|
21
|
+
files: BundledPackageFile[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Validate and embed every local source in the canonical marketplace. */
|
|
25
|
+
export function buildBundledPluginPackages(
|
|
26
|
+
repoRoot: string,
|
|
27
|
+
): Record<string, BundledPackage> {
|
|
28
|
+
const marketplacePath = join(repoRoot, "plugins", "marketplace.json");
|
|
29
|
+
const manifest = JSON.parse(readFileSync(marketplacePath, "utf8")) as {
|
|
30
|
+
plugins?: unknown;
|
|
31
|
+
};
|
|
32
|
+
if (!Array.isArray(manifest.plugins)) {
|
|
33
|
+
throw new Error("plugins/marketplace.json is missing its plugins array");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const pluginsRoot = realpathSync(join(repoRoot, "plugins"));
|
|
37
|
+
const packages: Record<string, BundledPackage> = {};
|
|
38
|
+
for (const rawEntry of manifest.plugins) {
|
|
39
|
+
if (typeof rawEntry !== "object" || rawEntry === null) {
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
const entry = rawEntry as Record<string, unknown>;
|
|
43
|
+
const rawSource = entry.source;
|
|
44
|
+
if (typeof rawSource !== "object" || rawSource === null) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const source = rawSource as Record<string, unknown>;
|
|
48
|
+
if (source.source !== "local") {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (
|
|
52
|
+
typeof entry.name !== "string" ||
|
|
53
|
+
typeof source.path !== "string" ||
|
|
54
|
+
typeof source.version !== "string"
|
|
55
|
+
) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
"local marketplace entries require name, path, and version",
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const pathSegments = source.path.split("/");
|
|
62
|
+
if (
|
|
63
|
+
!source.path.startsWith("plugins/") ||
|
|
64
|
+
source.path.includes("\\") ||
|
|
65
|
+
pathSegments.some(
|
|
66
|
+
(segment) => segment === "" || segment === "." || segment === "..",
|
|
67
|
+
)
|
|
68
|
+
) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
`local marketplace package path is not canonical: ${source.path}`,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
let packageRoot = resolve(repoRoot);
|
|
74
|
+
for (const segment of pathSegments) {
|
|
75
|
+
packageRoot = join(packageRoot, segment);
|
|
76
|
+
if (lstatSync(packageRoot).isSymbolicLink()) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
`local marketplace package path may not contain symlinks: ${source.path}`,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const packageRealpath = realpathSync(packageRoot);
|
|
83
|
+
const relativeToPlugins = relative(pluginsRoot, packageRealpath);
|
|
84
|
+
if (
|
|
85
|
+
relativeToPlugins === "" ||
|
|
86
|
+
relativeToPlugins === ".." ||
|
|
87
|
+
relativeToPlugins.startsWith(`..${sep}`)
|
|
88
|
+
) {
|
|
89
|
+
throw new Error(
|
|
90
|
+
`local marketplace package escapes plugins/: ${source.path}`,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const pluginManifest = readPluginManifest(packageRealpath);
|
|
95
|
+
if (
|
|
96
|
+
pluginManifest.source !== STANDARD_PLUGIN_MANIFEST ||
|
|
97
|
+
pluginManifest.name !== entry.name ||
|
|
98
|
+
pluginManifest.version !== source.version
|
|
99
|
+
) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
`local marketplace package ${source.path} must match entry name and version`,
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
const rawMcp = JSON.parse(
|
|
105
|
+
readFileSync(join(packageRealpath, "mcp.json"), "utf8"),
|
|
106
|
+
) as unknown;
|
|
107
|
+
const mcp = SpecMcpDocumentSchema.parse(rawMcp);
|
|
108
|
+
if (mcp.$schema !== AGENT_PLUGINS_MCP_SCHEMA_URL) {
|
|
109
|
+
throw new Error(
|
|
110
|
+
`local marketplace package ${source.path} must use the Agent Plugins MCP schema`,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
if (Object.keys(mcp.mcpServers).length === 0) {
|
|
114
|
+
throw new Error(
|
|
115
|
+
`local marketplace package ${source.path} must declare an MCP server`,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
for (const server of Object.values(mcp.mcpServers)) {
|
|
119
|
+
SpecMcpServerSchema.parse(server);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const files: BundledPackageFile[] = [];
|
|
123
|
+
const visit = (directory: string): void => {
|
|
124
|
+
for (const child of readdirSync(directory, { withFileTypes: true })) {
|
|
125
|
+
const childPath = join(directory, child.name);
|
|
126
|
+
if (child.isSymbolicLink() || lstatSync(childPath).isSymbolicLink()) {
|
|
127
|
+
throw new Error(
|
|
128
|
+
`local marketplace packages may not contain symlinks: ${source.path}/${child.name}`,
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
if (child.isDirectory()) {
|
|
132
|
+
visit(childPath);
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (!child.isFile()) {
|
|
136
|
+
throw new Error(
|
|
137
|
+
`local marketplace package contains an unsupported entry: ${childPath}`,
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
files.push({
|
|
141
|
+
path: relative(packageRealpath, childPath).split(sep).join("/"),
|
|
142
|
+
contentBase64: readFileSync(childPath).toString("base64"),
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
visit(packageRealpath);
|
|
147
|
+
files.sort((a, b) => a.path.localeCompare(b.path));
|
|
148
|
+
if (packages[source.path]) {
|
|
149
|
+
throw new Error(`duplicate local marketplace package: ${source.path}`);
|
|
150
|
+
}
|
|
151
|
+
packages[source.path] = { version: source.version, files };
|
|
152
|
+
}
|
|
153
|
+
return packages;
|
|
154
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { writeFileSync } from "node:fs";
|
|
4
|
+
import { join, resolve } from "node:path";
|
|
5
|
+
|
|
6
|
+
import { buildBundledPluginPackages } from "./bundled-plugin-packages.js";
|
|
7
|
+
|
|
8
|
+
const repoRoot = resolve(import.meta.dir, "../..");
|
|
9
|
+
const output = {
|
|
10
|
+
version: 1,
|
|
11
|
+
packages: buildBundledPluginPackages(repoRoot),
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
if (process.argv.includes("--check")) {
|
|
15
|
+
console.log("✓ Local marketplace packages are valid");
|
|
16
|
+
} else {
|
|
17
|
+
writeFileSync(
|
|
18
|
+
join(
|
|
19
|
+
repoRoot,
|
|
20
|
+
"assistant",
|
|
21
|
+
"src",
|
|
22
|
+
"cli",
|
|
23
|
+
"lib",
|
|
24
|
+
"bundled-plugin-packages.json",
|
|
25
|
+
),
|
|
26
|
+
`${JSON.stringify(output, null, 2)}\n`,
|
|
27
|
+
);
|
|
28
|
+
console.log("✓ Generated bundled local plugin packages");
|
|
29
|
+
}
|
package/scripts/test.ts
CHANGED
|
@@ -62,26 +62,28 @@ const EXPERIMENTAL_FILES = new Set([
|
|
|
62
62
|
const KNOWN_BROKEN_FILES = new Set<string>([]);
|
|
63
63
|
|
|
64
64
|
// ---------------------------------------------------------------------------
|
|
65
|
-
//
|
|
65
|
+
// Bundled catalog sync
|
|
66
66
|
// ---------------------------------------------------------------------------
|
|
67
67
|
|
|
68
|
-
// Ensure
|
|
69
|
-
//
|
|
70
|
-
//
|
|
71
|
-
|
|
72
|
-
// (e.g. worktrees) postinstall never fires, so the bundled copy can be missing
|
|
73
|
-
// and feature-flag-registry-bundled.test.ts fails. Running the sync here is
|
|
74
|
-
// idempotent and cheap (two file copies).
|
|
75
|
-
function syncFeatureFlagRegistry(): void {
|
|
68
|
+
// Ensure generated catalog inputs exist before tests. Locally, postinstall may
|
|
69
|
+
// not run in worktrees that share node_modules, so the static JSON imports can
|
|
70
|
+
// otherwise be missing.
|
|
71
|
+
function syncBundledCatalog(): void {
|
|
76
72
|
const syncScript = join("..", "meta", "sync-bundled-copies.ts");
|
|
77
73
|
if (!existsSync(syncScript)) {
|
|
78
74
|
return;
|
|
79
75
|
}
|
|
80
|
-
Bun.spawnSync(["bun", "run", "
|
|
81
|
-
cwd:
|
|
76
|
+
const result = Bun.spawnSync(["bun", "run", "sync:bundled-catalog"], {
|
|
77
|
+
cwd: process.cwd(),
|
|
82
78
|
stdout: "ignore",
|
|
83
|
-
stderr: "
|
|
79
|
+
stderr: "pipe",
|
|
80
|
+
windowsHide: true,
|
|
84
81
|
});
|
|
82
|
+
if (result.exitCode !== 0) {
|
|
83
|
+
throw new Error(
|
|
84
|
+
`Failed to generate bundled catalog inputs: ${result.stderr.toString()}`,
|
|
85
|
+
);
|
|
86
|
+
}
|
|
85
87
|
}
|
|
86
88
|
|
|
87
89
|
// ---------------------------------------------------------------------------
|
|
@@ -514,7 +516,7 @@ async function buildMigratedFixture(outWorkspaceDir: string): Promise<void> {
|
|
|
514
516
|
// ---------------------------------------------------------------------------
|
|
515
517
|
|
|
516
518
|
async function main(): Promise<void> {
|
|
517
|
-
|
|
519
|
+
syncBundledCatalog();
|
|
518
520
|
|
|
519
521
|
let testFiles = collectTestFiles();
|
|
520
522
|
if (testFiles.length === 0) {
|
|
@@ -332,6 +332,127 @@ describe("createManagedSkill", () => {
|
|
|
332
332
|
expect(content).toContain("Overwritten");
|
|
333
333
|
});
|
|
334
334
|
|
|
335
|
+
test("an overwrite keeps the frontmatter the call omits and replaces the body", () => {
|
|
336
|
+
createManagedSkill({
|
|
337
|
+
id: "kept",
|
|
338
|
+
name: "Kept",
|
|
339
|
+
description: "V1",
|
|
340
|
+
bodyMarkdown: "Old body.",
|
|
341
|
+
emoji: "📊",
|
|
342
|
+
includes: ["csv-basics"],
|
|
343
|
+
activationHints: ["user asks for the weekly report"],
|
|
344
|
+
avoidWhen: ["the report is monthly"],
|
|
345
|
+
category: "productivity",
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
const result = createManagedSkill({
|
|
349
|
+
id: "kept",
|
|
350
|
+
name: "Kept",
|
|
351
|
+
description: "V2",
|
|
352
|
+
bodyMarkdown: "New body.",
|
|
353
|
+
overwrite: true,
|
|
354
|
+
});
|
|
355
|
+
expect(result.created).toBe(true);
|
|
356
|
+
|
|
357
|
+
const catalog = loadSkillCatalog(undefined, [join(TEST_DIR, "skills")]);
|
|
358
|
+
const skill = catalog.find((s) => s.id === "kept")!;
|
|
359
|
+
expect(skill.description).toBe("V2");
|
|
360
|
+
expect(skill.emoji).toBe("📊");
|
|
361
|
+
expect(skill.includes).toEqual(["csv-basics"]);
|
|
362
|
+
expect(skill.activationHints).toEqual(["user asks for the weekly report"]);
|
|
363
|
+
expect(skill.avoidWhen).toEqual(["the report is monthly"]);
|
|
364
|
+
expect(skill.category).toBe("productivity");
|
|
365
|
+
expect(readFileSync(result.path, "utf-8")).toContain("New body.");
|
|
366
|
+
expect(readFileSync(result.path, "utf-8")).not.toContain("Old body.");
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
test("an overwrite carries through frontmatter the tool does not own", () => {
|
|
370
|
+
const dir = join(TEST_DIR, "skills", "hand-edited");
|
|
371
|
+
mkdirSync(dir, { recursive: true });
|
|
372
|
+
writeFileSync(
|
|
373
|
+
join(dir, "SKILL.md"),
|
|
374
|
+
[
|
|
375
|
+
"---",
|
|
376
|
+
'name: "Hand Edited"',
|
|
377
|
+
'description: "Has fields the tool never writes"',
|
|
378
|
+
"metadata:",
|
|
379
|
+
" custom: kept",
|
|
380
|
+
" vellum:",
|
|
381
|
+
' display-name: "Hand Edited Skill"',
|
|
382
|
+
" platforms:",
|
|
383
|
+
" - macos",
|
|
384
|
+
" always-candidate: true",
|
|
385
|
+
' emoji: "📊"',
|
|
386
|
+
"---",
|
|
387
|
+
"",
|
|
388
|
+
"Old body.",
|
|
389
|
+
"",
|
|
390
|
+
].join("\n"),
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
const result = createManagedSkill({
|
|
394
|
+
id: "hand-edited",
|
|
395
|
+
name: "Hand Edited",
|
|
396
|
+
description: "V2",
|
|
397
|
+
bodyMarkdown: "New body.",
|
|
398
|
+
overwrite: true,
|
|
399
|
+
category: "productivity",
|
|
400
|
+
});
|
|
401
|
+
expect(result.created).toBe(true);
|
|
402
|
+
|
|
403
|
+
const fm = parseYaml(
|
|
404
|
+
readFileSync(result.path, "utf-8").match(/^---\n([\s\S]*?)\n---/)![1],
|
|
405
|
+
);
|
|
406
|
+
expect(fm.metadata.custom).toBe("kept");
|
|
407
|
+
expect(fm.metadata.vellum["display-name"]).toBe("Hand Edited Skill");
|
|
408
|
+
expect(fm.metadata.vellum.platforms).toEqual(["macos"]);
|
|
409
|
+
expect(fm.metadata.vellum["always-candidate"]).toBe(true);
|
|
410
|
+
expect(fm.metadata.vellum.emoji).toBe("📊");
|
|
411
|
+
expect(fm.metadata.vellum.category).toBe("productivity");
|
|
412
|
+
const skill = loadSkillCatalog(undefined, [join(TEST_DIR, "skills")]).find(
|
|
413
|
+
(s) => s.id === "hand-edited",
|
|
414
|
+
)!;
|
|
415
|
+
expect(skill.displayName).toBe("Hand Edited Skill");
|
|
416
|
+
expect(skill.platforms).toEqual(["macos"]);
|
|
417
|
+
expect(skill.alwaysCandidate).toBe(true);
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
test("an overwrite replaces a field it passes and clears one passed empty", () => {
|
|
421
|
+
createManagedSkill({
|
|
422
|
+
id: "patched",
|
|
423
|
+
name: "Patched",
|
|
424
|
+
description: "V1",
|
|
425
|
+
bodyMarkdown: "Body.",
|
|
426
|
+
emoji: "📊",
|
|
427
|
+
includes: ["csv-basics"],
|
|
428
|
+
activationHints: ["old hint"],
|
|
429
|
+
avoidWhen: ["the report is monthly"],
|
|
430
|
+
category: "productivity",
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
createManagedSkill({
|
|
434
|
+
id: "patched",
|
|
435
|
+
name: "Patched",
|
|
436
|
+
description: "V2",
|
|
437
|
+
bodyMarkdown: "Body.",
|
|
438
|
+
overwrite: true,
|
|
439
|
+
emoji: "🚀",
|
|
440
|
+
activationHints: ["new hint"],
|
|
441
|
+
avoidWhen: [],
|
|
442
|
+
category: "",
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
const catalog = loadSkillCatalog(undefined, [join(TEST_DIR, "skills")]);
|
|
446
|
+
const skill = catalog.find((s) => s.id === "patched")!;
|
|
447
|
+
expect(skill.emoji).toBe("🚀");
|
|
448
|
+
expect(skill.activationHints).toEqual(["new hint"]);
|
|
449
|
+
// Omitted: kept.
|
|
450
|
+
expect(skill.includes).toEqual(["csv-basics"]);
|
|
451
|
+
// Passed empty: cleared.
|
|
452
|
+
expect(skill.avoidWhen).toBeUndefined();
|
|
453
|
+
expect(skill.category).toBeUndefined();
|
|
454
|
+
});
|
|
455
|
+
|
|
335
456
|
test("writes category to frontmatter and round-trips through catalog load", () => {
|
|
336
457
|
createManagedSkill({
|
|
337
458
|
id: "categorized-skill",
|