agentwheel 0.16.4 → 0.16.5
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/README.md +18 -10
- package/dist/index.js +334 -47
- package/install.md +15 -4
- package/openpack.json +1 -1
- package/package.json +2 -1
- package/skills/agentwheel/SKILL.md +18 -14
- package/skills/agentwheel-discovery/SKILL.md +75 -0
package/README.md
CHANGED
|
@@ -54,13 +54,15 @@ install the companion skill, and show the right catalogue flow for your runtime.
|
|
|
54
54
|
curl -fsSL https://raw.githubusercontent.com/NestDevLab/agentwheel/main/install.md
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
-
**Companion
|
|
57
|
+
**Companion skills**
|
|
58
58
|
|
|
59
|
-
The
|
|
59
|
+
The management skill keeps Agentwheel commands and safety rules inside the runtime you are using.
|
|
60
|
+
Install the discovery skill separately when you also want proactive, non-installing suggestions:
|
|
60
61
|
|
|
61
62
|
```bash
|
|
62
63
|
agentwheel doctor --adapter codex --local
|
|
63
64
|
agentwheel install github:NestDevLab/agentwheel --adapter codex --local --skill agentwheel
|
|
65
|
+
agentwheel install github:NestDevLab/agentwheel --adapter codex --local --skill agentwheel-discovery
|
|
64
66
|
```
|
|
65
67
|
|
|
66
68
|
> **Status: early (v0.12).** The public CLI vocabulary is package-manager style:
|
|
@@ -167,10 +169,10 @@ Use `--scope registry`, `--scope enriched`, or `--scope vercel` to restrict a qu
|
|
|
167
169
|
`--scope all` combines every source, deduplicates equivalent artifacts, and reports every
|
|
168
170
|
provenance plus the safe installation route.
|
|
169
171
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
configuration by
|
|
172
|
+
The optional `agentwheel-discovery` skill notices capability gaps during normal work, uses the
|
|
173
|
+
verified semantic catalogue index, reranks results against the original request, and suggests at
|
|
174
|
+
most three artifacts. It can read one instruction skill with `agentwheel try` before installation.
|
|
175
|
+
Search and trial never install or change configuration by themselves.
|
|
174
176
|
|
|
175
177
|
Registry maintenance remains available through `agentwheel registry update` and
|
|
176
178
|
`agentwheel registry list`. Registry short names continue to resolve during add/install.
|
|
@@ -243,10 +245,14 @@ stderr warning when an update is available. Disable it with `--no-update-check`
|
|
|
243
245
|
|
|
244
246
|
## Companion Skill Doctor
|
|
245
247
|
|
|
246
|
-
Agentwheel ships
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
248
|
+
Agentwheel ships two optional companion skills in `github:NestDevLab/agentwheel`:
|
|
249
|
+
|
|
250
|
+
- `skills/agentwheel` provides explicit search, setup guidance, safety rules, and artifact management.
|
|
251
|
+
- `skills/agentwheel-discovery` proactively notices capability gaps, suggests up to three matches,
|
|
252
|
+
and offers a read-only skill trial before installation.
|
|
253
|
+
|
|
254
|
+
Install only the management skill when proactive recommendations are unwanted. Install both when
|
|
255
|
+
you want Agentwheel to surface reusable capabilities during normal work.
|
|
250
256
|
|
|
251
257
|
The CLI never installs the companion skill silently into runtime folders. Use `doctor` to check the
|
|
252
258
|
selected runtime and print the exact preview and install commands when a skill is missing:
|
|
@@ -255,6 +261,8 @@ selected runtime and print the exact preview and install commands when a skill i
|
|
|
255
261
|
agentwheel doctor --adapter copilot --user
|
|
256
262
|
agentwheel install github:NestDevLab/agentwheel --adapter copilot --user --skill agentwheel --dry-run
|
|
257
263
|
agentwheel install github:NestDevLab/agentwheel --adapter copilot --user --skill agentwheel
|
|
264
|
+
agentwheel install github:NestDevLab/agentwheel --adapter copilot --user --skill agentwheel-discovery --dry-run
|
|
265
|
+
agentwheel install github:NestDevLab/agentwheel --adapter copilot --user --skill agentwheel-discovery
|
|
258
266
|
```
|
|
259
267
|
|
|
260
268
|
`doctor` also accepts explicit skill checks and machine-readable output. In Syncwheel-managed
|
package/dist/index.js
CHANGED
|
@@ -9,11 +9,11 @@ import {
|
|
|
9
9
|
} from "./chunk-PKAPR55N.js";
|
|
10
10
|
|
|
11
11
|
// src/cli/index.ts
|
|
12
|
-
import { createHash as
|
|
12
|
+
import { createHash as createHash14 } from "crypto";
|
|
13
13
|
import { existsSync } from "fs";
|
|
14
14
|
import { mkdir as mkdir23, rm as rm12, writeFile as writeFile22 } from "fs/promises";
|
|
15
|
-
import { homedir as
|
|
16
|
-
import { dirname as dirname32, join as
|
|
15
|
+
import { homedir as homedir12 } from "os";
|
|
16
|
+
import { dirname as dirname32, join as join48, resolve as resolve22 } from "path";
|
|
17
17
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
18
18
|
import { Command } from "commander";
|
|
19
19
|
|
|
@@ -1469,15 +1469,15 @@ function appendMcpServers(content, servers) {
|
|
|
1469
1469
|
`;
|
|
1470
1470
|
}
|
|
1471
1471
|
function formatMcpServer(name, server) {
|
|
1472
|
-
const
|
|
1472
|
+
const env2 = isRecord2(server.env) ? server.env : void 0;
|
|
1473
1473
|
const lines = [`[mcp_servers.${quoteTomlKey(name)}]`];
|
|
1474
1474
|
for (const [key, value] of Object.entries(server)) {
|
|
1475
1475
|
if (key === "env" || value === void 0) continue;
|
|
1476
1476
|
lines.push(`${quoteTomlKey(key)} = ${formatTomlValue(value)}`);
|
|
1477
1477
|
}
|
|
1478
|
-
if (
|
|
1478
|
+
if (env2 && Object.keys(env2).length > 0) {
|
|
1479
1479
|
lines.push("", `[mcp_servers.${quoteTomlKey(name)}.env]`);
|
|
1480
|
-
for (const [key, value] of Object.entries(
|
|
1480
|
+
for (const [key, value] of Object.entries(env2)) {
|
|
1481
1481
|
lines.push(`${quoteTomlKey(key)} = ${formatTomlValue(value)}`);
|
|
1482
1482
|
}
|
|
1483
1483
|
}
|
|
@@ -10581,9 +10581,9 @@ async function maybeCheckForUpdate(options) {
|
|
|
10581
10581
|
}
|
|
10582
10582
|
}
|
|
10583
10583
|
function isDisabled(options) {
|
|
10584
|
-
const
|
|
10585
|
-
if (
|
|
10586
|
-
if (
|
|
10584
|
+
const env2 = options.env ?? process.env;
|
|
10585
|
+
if (env2.AGENTWHEEL_NO_UPDATE_CHECK === "1" || env2.AGENTWHEEL_NO_UPDATE_CHECK === "true") return true;
|
|
10586
|
+
if (env2.CI) return true;
|
|
10587
10587
|
if (options.argv?.includes("--no-update-check")) return true;
|
|
10588
10588
|
if (options.argv?.includes("--offline")) return true;
|
|
10589
10589
|
const isTTY = options.isTTY ?? process.stderr.isTTY === true;
|
|
@@ -11228,7 +11228,7 @@ async function invokeMemberStatus(member, parentWorkspace, chain, options, cliEn
|
|
|
11228
11228
|
const args = ["--no-update-check", "status", "--profile", member.profile, "--json"];
|
|
11229
11229
|
if (options.refresh) args.push("--refresh");
|
|
11230
11230
|
if (options.offline) args.push("--offline");
|
|
11231
|
-
const
|
|
11231
|
+
const env2 = { ...process.env, AGENTWHEEL_COMPOSITE_CHAIN: JSON.stringify(chain) };
|
|
11232
11232
|
let stdout = "";
|
|
11233
11233
|
let stderr = "";
|
|
11234
11234
|
try {
|
|
@@ -11236,7 +11236,7 @@ async function invokeMemberStatus(member, parentWorkspace, chain, options, cliEn
|
|
|
11236
11236
|
const workspace = resolve21(parentWorkspace, member.workspace);
|
|
11237
11237
|
const result = await execFileAsync6(process.execPath, [cliEntry, ...args], {
|
|
11238
11238
|
cwd: workspace,
|
|
11239
|
-
env,
|
|
11239
|
+
env: env2,
|
|
11240
11240
|
maxBuffer: 20 * 1024 * 1024
|
|
11241
11241
|
});
|
|
11242
11242
|
stdout = result.stdout;
|
|
@@ -11251,7 +11251,7 @@ async function invokeMemberStatus(member, parentWorkspace, chain, options, cliEn
|
|
|
11251
11251
|
...args.map(shellQuote2)
|
|
11252
11252
|
];
|
|
11253
11253
|
const result = await execFileAsync6("ssh", [...sshArgs, remoteArgs.join(" ")], {
|
|
11254
|
-
env,
|
|
11254
|
+
env: env2,
|
|
11255
11255
|
maxBuffer: 20 * 1024 * 1024
|
|
11256
11256
|
});
|
|
11257
11257
|
stdout = result.stdout;
|
|
@@ -11272,7 +11272,7 @@ async function invokeMemberStatus(member, parentWorkspace, chain, options, cliEn
|
|
|
11272
11272
|
}
|
|
11273
11273
|
}
|
|
11274
11274
|
async function runMemberAgentwheel(member, parentWorkspace, args, chain) {
|
|
11275
|
-
const
|
|
11275
|
+
const env2 = { ...process.env, AGENTWHEEL_COMPOSITE_CHAIN: JSON.stringify(chain) };
|
|
11276
11276
|
try {
|
|
11277
11277
|
if (member.transport === "local") {
|
|
11278
11278
|
const result2 = await execFileAsync6(
|
|
@@ -11280,7 +11280,7 @@ async function runMemberAgentwheel(member, parentWorkspace, args, chain) {
|
|
|
11280
11280
|
[process.argv[1], "--no-update-check", ...args],
|
|
11281
11281
|
{
|
|
11282
11282
|
cwd: resolve21(parentWorkspace, member.workspace),
|
|
11283
|
-
env,
|
|
11283
|
+
env: env2,
|
|
11284
11284
|
maxBuffer: 20 * 1024 * 1024
|
|
11285
11285
|
}
|
|
11286
11286
|
);
|
|
@@ -11295,7 +11295,7 @@ async function runMemberAgentwheel(member, parentWorkspace, args, chain) {
|
|
|
11295
11295
|
...args.map(shellQuote2)
|
|
11296
11296
|
];
|
|
11297
11297
|
const result = await execFileAsync6("ssh", [...sshArguments(member), remoteArgs.join(" ")], {
|
|
11298
|
-
env,
|
|
11298
|
+
env: env2,
|
|
11299
11299
|
maxBuffer: 20 * 1024 * 1024
|
|
11300
11300
|
});
|
|
11301
11301
|
return { stdout: result.stdout, stderr: result.stderr };
|
|
@@ -11521,13 +11521,21 @@ var catalogueCacheSchema = z13.object({
|
|
|
11521
11521
|
fetchedAt: z13.string().datetime(),
|
|
11522
11522
|
sources: z13.tuple([z13.string().url(), z13.string().url()]),
|
|
11523
11523
|
enriched: enrichedCatalogueSchema,
|
|
11524
|
-
vercel: vercelCatalogueSchema
|
|
11524
|
+
vercel: vercelCatalogueSchema,
|
|
11525
|
+
sourceDigests: z13.object({
|
|
11526
|
+
enriched: z13.string().regex(/^[a-f0-9]{64}$/),
|
|
11527
|
+
vercel: z13.string().regex(/^[a-f0-9]{64}$/)
|
|
11528
|
+
}).optional()
|
|
11525
11529
|
});
|
|
11526
11530
|
var catalogueCacheEnvelopeSchema = z13.object({
|
|
11527
11531
|
version: z13.literal(1),
|
|
11528
11532
|
fetchedAt: z13.string().datetime(),
|
|
11529
11533
|
sources: z13.tuple([z13.string().url(), z13.string().url()]),
|
|
11530
11534
|
contentHash: z13.string().regex(/^[a-f0-9]{64}$/).optional(),
|
|
11535
|
+
sourceDigests: z13.object({
|
|
11536
|
+
enriched: z13.string().regex(/^[a-f0-9]{64}$/),
|
|
11537
|
+
vercel: z13.string().regex(/^[a-f0-9]{64}$/)
|
|
11538
|
+
}).optional(),
|
|
11531
11539
|
enriched: z13.unknown(),
|
|
11532
11540
|
vercel: z13.unknown()
|
|
11533
11541
|
});
|
|
@@ -11545,13 +11553,15 @@ var searchResultSchema = z13.object({
|
|
|
11545
11553
|
installability: installabilitySchema,
|
|
11546
11554
|
provenances: z13.array(catalogueProvenanceSchema).min(1),
|
|
11547
11555
|
score: z13.number().int().nonnegative(),
|
|
11548
|
-
matchedFields: z13.array(z13.string())
|
|
11556
|
+
matchedFields: z13.array(z13.string()),
|
|
11557
|
+
semanticScore: z13.number().finite().optional()
|
|
11549
11558
|
});
|
|
11550
11559
|
var searchResponseSchema = z13.object({
|
|
11551
11560
|
schemaVersion: z13.literal(1),
|
|
11552
11561
|
query: z13.string(),
|
|
11553
11562
|
scope: searchScopeSchema,
|
|
11554
11563
|
fromCache: z13.boolean(),
|
|
11564
|
+
searchMode: z13.enum(["lexical", "semantic"]).optional(),
|
|
11555
11565
|
results: z13.array(searchResultSchema)
|
|
11556
11566
|
});
|
|
11557
11567
|
|
|
@@ -11594,24 +11604,35 @@ var CatalogueClient = class {
|
|
|
11594
11604
|
return this.fromCache(usableCache, false);
|
|
11595
11605
|
}
|
|
11596
11606
|
try {
|
|
11597
|
-
const [
|
|
11607
|
+
const [enrichedPayload, vercelPayload] = await Promise.all([
|
|
11598
11608
|
this.fetchJson(this.sources[0], enrichedCatalogueSchema),
|
|
11599
11609
|
this.fetchJson(this.sources[1], vercelCatalogueSchema)
|
|
11600
11610
|
]);
|
|
11611
|
+
const { value: enriched, digest: enrichedDigest } = enrichedPayload;
|
|
11612
|
+
const { value: vercel, digest: vercelDigest } = vercelPayload;
|
|
11601
11613
|
const fetchedAt = this.now().toISOString();
|
|
11602
11614
|
const cache = {
|
|
11603
11615
|
version: 1,
|
|
11604
11616
|
fetchedAt,
|
|
11605
11617
|
sources: this.sources,
|
|
11606
11618
|
enriched,
|
|
11607
|
-
vercel
|
|
11619
|
+
vercel,
|
|
11620
|
+
sourceDigests: { enriched: enrichedDigest, vercel: vercelDigest }
|
|
11608
11621
|
};
|
|
11609
11622
|
const cacheFile = {
|
|
11610
11623
|
...cache,
|
|
11611
11624
|
contentHash: catalogueContentHash(enriched, vercel)
|
|
11612
11625
|
};
|
|
11613
11626
|
await writeJsonAtomic(this.cachePath, cacheFile);
|
|
11614
|
-
return {
|
|
11627
|
+
return {
|
|
11628
|
+
enriched,
|
|
11629
|
+
vercel,
|
|
11630
|
+
sources: this.sources,
|
|
11631
|
+
fetchedAt,
|
|
11632
|
+
fromCache: false,
|
|
11633
|
+
stale: false,
|
|
11634
|
+
sourceDigests: cache.sourceDigests
|
|
11635
|
+
};
|
|
11615
11636
|
} catch (error) {
|
|
11616
11637
|
if (!usableCache) throw error;
|
|
11617
11638
|
const reason = error instanceof Error ? error.message : String(error);
|
|
@@ -11652,7 +11673,8 @@ var CatalogueClient = class {
|
|
|
11652
11673
|
sources: cache.sources,
|
|
11653
11674
|
fetchedAt: cache.fetchedAt,
|
|
11654
11675
|
fromCache: true,
|
|
11655
|
-
stale
|
|
11676
|
+
stale,
|
|
11677
|
+
sourceDigests: cache.sourceDigests
|
|
11656
11678
|
};
|
|
11657
11679
|
}
|
|
11658
11680
|
async fetchJson(source, schema) {
|
|
@@ -11662,8 +11684,8 @@ var CatalogueClient = class {
|
|
|
11662
11684
|
}
|
|
11663
11685
|
const declaredLength = response.headers.get("content-length");
|
|
11664
11686
|
if (declaredLength !== null) {
|
|
11665
|
-
const
|
|
11666
|
-
if (Number.isFinite(
|
|
11687
|
+
const bytes2 = Number(declaredLength);
|
|
11688
|
+
if (Number.isFinite(bytes2) && bytes2 > MAX_CATALOGUE_PAYLOAD_BYTES) {
|
|
11667
11689
|
throw new Error(`Catalogue payload exceeds 32 MiB limit: ${source}`);
|
|
11668
11690
|
}
|
|
11669
11691
|
}
|
|
@@ -11671,13 +11693,17 @@ var CatalogueClient = class {
|
|
|
11671
11693
|
if (payload.byteLength > MAX_CATALOGUE_PAYLOAD_BYTES) {
|
|
11672
11694
|
throw new Error(`Catalogue payload exceeds 32 MiB limit: ${source}`);
|
|
11673
11695
|
}
|
|
11696
|
+
const bytes = new Uint8Array(payload);
|
|
11674
11697
|
let value;
|
|
11675
11698
|
try {
|
|
11676
11699
|
value = JSON.parse(new TextDecoder().decode(payload));
|
|
11677
11700
|
} catch {
|
|
11678
11701
|
throw new Error(`Catalogue source returned invalid JSON: ${source}`);
|
|
11679
11702
|
}
|
|
11680
|
-
return
|
|
11703
|
+
return {
|
|
11704
|
+
value: schema.parse(value),
|
|
11705
|
+
digest: createHash11("sha256").update(bytes).digest("hex")
|
|
11706
|
+
};
|
|
11681
11707
|
}
|
|
11682
11708
|
};
|
|
11683
11709
|
function defaultCatalogueCachePath() {
|
|
@@ -12048,6 +12074,235 @@ function includesToken(normalizedText, token) {
|
|
|
12048
12074
|
return normalizedText === token || normalizedText.startsWith(`${token} `) || normalizedText.endsWith(` ${token}`) || normalizedText.includes(` ${token} `);
|
|
12049
12075
|
}
|
|
12050
12076
|
|
|
12077
|
+
// src/semantic/index.ts
|
|
12078
|
+
import { createHash as createHash12 } from "crypto";
|
|
12079
|
+
import { homedir as homedir11 } from "os";
|
|
12080
|
+
import { join as join46 } from "path";
|
|
12081
|
+
import { env, pipeline } from "@huggingface/transformers";
|
|
12082
|
+
var DEFAULT_SEMANTIC_INDEX_URL = "https://raw.githubusercontent.com/NestDevLab/agentwheel-registry/main/catalogue-semantic-index/gte-v1/";
|
|
12083
|
+
var CONTRACT = {
|
|
12084
|
+
schemaVersion: 1,
|
|
12085
|
+
textSchemaVersion: 1,
|
|
12086
|
+
model: {
|
|
12087
|
+
id: "Xenova/gte-small",
|
|
12088
|
+
revision: "5927d1727bb12db490052a1b33265ad78058de08",
|
|
12089
|
+
dimensions: 384,
|
|
12090
|
+
dtype: "q8",
|
|
12091
|
+
pooling: "mean",
|
|
12092
|
+
normalize: true,
|
|
12093
|
+
queryPrefix: "",
|
|
12094
|
+
documentPrefix: ""
|
|
12095
|
+
},
|
|
12096
|
+
vectorFormat: "signed-int8-per-vector-scaled",
|
|
12097
|
+
normFormat: "float32-little-endian"
|
|
12098
|
+
};
|
|
12099
|
+
var SemanticSearchClient = class {
|
|
12100
|
+
constructor(options = {}) {
|
|
12101
|
+
this.options = options;
|
|
12102
|
+
this.fetchImpl = options.fetch ?? fetch;
|
|
12103
|
+
this.indexUrl = ensureTrailingSlash(options.indexUrl ?? DEFAULT_SEMANTIC_INDEX_URL);
|
|
12104
|
+
}
|
|
12105
|
+
options;
|
|
12106
|
+
fetchImpl;
|
|
12107
|
+
indexUrl;
|
|
12108
|
+
async search(request) {
|
|
12109
|
+
if (!request.catalogueDigests) {
|
|
12110
|
+
throw new Error("Semantic search needs a catalogue cache with source checksums. Run again online with --refresh.");
|
|
12111
|
+
}
|
|
12112
|
+
const metadata = await this.fetchMetadata();
|
|
12113
|
+
validateMetadata(metadata, request.catalogueDigests);
|
|
12114
|
+
const [ids, vectors, norms] = await Promise.all([
|
|
12115
|
+
this.fetchIds(metadata.files.ids),
|
|
12116
|
+
this.fetchBinary(metadata.files.vectors),
|
|
12117
|
+
this.fetchBinary(metadata.files.norms)
|
|
12118
|
+
]);
|
|
12119
|
+
const decodedNorms = decodeFloat32LittleEndian(norms);
|
|
12120
|
+
validateIndexFiles(metadata, ids, vectors, decodedNorms);
|
|
12121
|
+
const query = await (this.options.embed ?? embedQuery)(request.query);
|
|
12122
|
+
if (query.length !== metadata.dimensions) {
|
|
12123
|
+
throw new Error(`Semantic model returned ${query.length} dimensions; expected ${metadata.dimensions}.`);
|
|
12124
|
+
}
|
|
12125
|
+
const ranked = searchInt8Index(vectors, decodedNorms, metadata.dimensions, query, Math.max(100, request.limit * 10));
|
|
12126
|
+
const entries = new Map(request.entries.map((entry) => [entry.id, entry]));
|
|
12127
|
+
const results = [];
|
|
12128
|
+
for (const candidate of ranked) {
|
|
12129
|
+
const entry = entries.get(ids[candidate.row]);
|
|
12130
|
+
if (!entry || !request.includeArchived && entry.archived) continue;
|
|
12131
|
+
if (request.type && entry.type !== request.type) continue;
|
|
12132
|
+
if (request.ecosystem && entry.ecosystem !== request.ecosystem) continue;
|
|
12133
|
+
results.push(toSearchResult(entry, candidate.score, results.length));
|
|
12134
|
+
if (results.length === request.limit) break;
|
|
12135
|
+
}
|
|
12136
|
+
if (results.length === 0) this.options.warn?.("Semantic index had no candidates matching the selected filters.");
|
|
12137
|
+
return results;
|
|
12138
|
+
}
|
|
12139
|
+
async fetchMetadata() {
|
|
12140
|
+
const response = await this.fetchImpl(new URL("metadata.json", this.indexUrl));
|
|
12141
|
+
if (!response.ok) throw new Error(`Semantic index metadata failed (${response.status}).`);
|
|
12142
|
+
let metadata;
|
|
12143
|
+
try {
|
|
12144
|
+
metadata = JSON.parse(await response.text());
|
|
12145
|
+
} catch {
|
|
12146
|
+
throw new Error("Semantic index metadata is not valid JSON.");
|
|
12147
|
+
}
|
|
12148
|
+
return metadata;
|
|
12149
|
+
}
|
|
12150
|
+
async fetchIds(descriptor) {
|
|
12151
|
+
const bytes = await this.fetchBinary(descriptor);
|
|
12152
|
+
let ids;
|
|
12153
|
+
try {
|
|
12154
|
+
ids = JSON.parse(new TextDecoder().decode(bytes));
|
|
12155
|
+
} catch {
|
|
12156
|
+
throw new Error("Semantic index IDs are not valid JSON.");
|
|
12157
|
+
}
|
|
12158
|
+
if (!Array.isArray(ids) || ids.some((id) => typeof id !== "string" || !id)) {
|
|
12159
|
+
throw new Error("Semantic index IDs are invalid.");
|
|
12160
|
+
}
|
|
12161
|
+
return ids;
|
|
12162
|
+
}
|
|
12163
|
+
async fetchBinary(descriptor) {
|
|
12164
|
+
const response = await this.fetchImpl(new URL(descriptor.path, this.indexUrl));
|
|
12165
|
+
if (!response.ok) throw new Error(`Semantic index file failed (${response.status}): ${descriptor.path}`);
|
|
12166
|
+
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
12167
|
+
if (bytes.byteLength !== descriptor.bytes) throw new Error(`Semantic index file size does not match: ${descriptor.path}`);
|
|
12168
|
+
const digest = createHash12("sha256").update(bytes).digest("hex");
|
|
12169
|
+
if (digest !== descriptor.sha256) throw new Error(`Semantic index checksum does not match: ${descriptor.path}`);
|
|
12170
|
+
return bytes;
|
|
12171
|
+
}
|
|
12172
|
+
};
|
|
12173
|
+
async function embedQuery(query) {
|
|
12174
|
+
env.cacheDir = join46(homedir11(), ".agentwheel", "semantic-models");
|
|
12175
|
+
const extractor = await pipeline("feature-extraction", CONTRACT.model.id, {
|
|
12176
|
+
revision: CONTRACT.model.revision,
|
|
12177
|
+
dtype: CONTRACT.model.dtype,
|
|
12178
|
+
session_options: { intraOpNumThreads: 1, interOpNumThreads: 1 }
|
|
12179
|
+
});
|
|
12180
|
+
const output = await extractor(query, { pooling: CONTRACT.model.pooling, normalize: CONTRACT.model.normalize });
|
|
12181
|
+
return Float32Array.from(output.data);
|
|
12182
|
+
}
|
|
12183
|
+
function validateMetadata(metadata, digests) {
|
|
12184
|
+
if (metadata.schemaVersion !== CONTRACT.schemaVersion || metadata.textSchemaVersion !== CONTRACT.textSchemaVersion) {
|
|
12185
|
+
throw new Error("Unsupported semantic index schema.");
|
|
12186
|
+
}
|
|
12187
|
+
if (!Number.isInteger(metadata.count) || metadata.count < 1 || metadata.dimensions !== CONTRACT.model.dimensions) {
|
|
12188
|
+
throw new Error("Semantic index dimensions or count are invalid.");
|
|
12189
|
+
}
|
|
12190
|
+
if (metadata.vectorFormat !== CONTRACT.vectorFormat || metadata.normFormat !== CONTRACT.normFormat) {
|
|
12191
|
+
throw new Error("Semantic index binary format is unsupported.");
|
|
12192
|
+
}
|
|
12193
|
+
for (const [field, value] of Object.entries(CONTRACT.model)) {
|
|
12194
|
+
if (metadata.model?.[field] !== value) throw new Error(`Semantic model ${field} does not match.`);
|
|
12195
|
+
}
|
|
12196
|
+
for (const source of ["enriched", "vercel"]) {
|
|
12197
|
+
if (metadata.catalogue?.[source]?.sha256 !== digests[source]) {
|
|
12198
|
+
throw new Error(`Semantic index catalogue checksum does not match ${source}.`);
|
|
12199
|
+
}
|
|
12200
|
+
}
|
|
12201
|
+
for (const key of ["ids", "vectors", "norms"]) {
|
|
12202
|
+
const descriptor = metadata.files?.[key];
|
|
12203
|
+
if (!descriptor || typeof descriptor.path !== "string" || !Number.isInteger(descriptor.bytes) || descriptor.bytes < 1 || !/^[a-f0-9]{64}$/u.test(descriptor.sha256)) {
|
|
12204
|
+
throw new Error(`Semantic index ${key} descriptor is invalid.`);
|
|
12205
|
+
}
|
|
12206
|
+
}
|
|
12207
|
+
}
|
|
12208
|
+
function validateIndexFiles(metadata, ids, vectors, norms) {
|
|
12209
|
+
if (ids.length !== metadata.count || vectors.length !== metadata.count * metadata.dimensions || norms.length !== metadata.count) {
|
|
12210
|
+
throw new Error("Semantic index files do not match metadata.");
|
|
12211
|
+
}
|
|
12212
|
+
}
|
|
12213
|
+
function decodeFloat32LittleEndian(bytes) {
|
|
12214
|
+
if (bytes.byteLength % Float32Array.BYTES_PER_ELEMENT !== 0) throw new Error("Semantic norm file has an invalid length.");
|
|
12215
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
12216
|
+
const result = new Float32Array(bytes.byteLength / Float32Array.BYTES_PER_ELEMENT);
|
|
12217
|
+
for (let index = 0; index < result.length; index += 1) result[index] = view.getFloat32(index * 4, true);
|
|
12218
|
+
return result;
|
|
12219
|
+
}
|
|
12220
|
+
function searchInt8Index(vectors, norms, dimensions, query, limit) {
|
|
12221
|
+
const results = [];
|
|
12222
|
+
const signed = new Int8Array(vectors.buffer, vectors.byteOffset, vectors.byteLength);
|
|
12223
|
+
for (let row = 0; row < norms.length; row += 1) {
|
|
12224
|
+
let dot = 0;
|
|
12225
|
+
const offset = row * dimensions;
|
|
12226
|
+
for (let column = 0; column < dimensions; column += 1) dot += signed[offset + column] * query[column];
|
|
12227
|
+
const candidate = { row, score: norms[row] > 0 ? dot / norms[row] : 0 };
|
|
12228
|
+
const index = results.findIndex((item) => candidate.score > item.score);
|
|
12229
|
+
results.splice(index === -1 ? results.length : index, 0, candidate);
|
|
12230
|
+
if (results.length > limit) results.pop();
|
|
12231
|
+
}
|
|
12232
|
+
return results;
|
|
12233
|
+
}
|
|
12234
|
+
function toSearchResult(entry, semanticScore, rank) {
|
|
12235
|
+
return {
|
|
12236
|
+
id: entry.id,
|
|
12237
|
+
name: entry.name,
|
|
12238
|
+
description: entry.description,
|
|
12239
|
+
type: entry.type,
|
|
12240
|
+
...entry.ecosystem ? { ecosystem: entry.ecosystem } : {},
|
|
12241
|
+
tags: entry.tags,
|
|
12242
|
+
provides: entry.provides,
|
|
12243
|
+
...entry.source ? { source: entry.source } : {},
|
|
12244
|
+
...entry.repoUrl ? { repoUrl: entry.repoUrl } : {},
|
|
12245
|
+
...entry.installCommand ? { installCommand: entry.installCommand } : {},
|
|
12246
|
+
installability: entry.installability,
|
|
12247
|
+
provenances: entry.provenances,
|
|
12248
|
+
score: 1e5 - rank,
|
|
12249
|
+
matchedFields: ["semantic"],
|
|
12250
|
+
semanticScore: Number(semanticScore.toFixed(6))
|
|
12251
|
+
};
|
|
12252
|
+
}
|
|
12253
|
+
function ensureTrailingSlash(value) {
|
|
12254
|
+
return value.endsWith("/") ? value : `${value}/`;
|
|
12255
|
+
}
|
|
12256
|
+
|
|
12257
|
+
// src/trial/skill.ts
|
|
12258
|
+
import { createHash as createHash13 } from "crypto";
|
|
12259
|
+
import { readFile as readFile34, stat as stat12 } from "fs/promises";
|
|
12260
|
+
import { join as join47 } from "path";
|
|
12261
|
+
import { parse as parseYaml2 } from "yaml";
|
|
12262
|
+
var MAX_TRIAL_SKILL_BYTES = 512 * 1024;
|
|
12263
|
+
async function createSkillTrial(driver, resolved, selectors) {
|
|
12264
|
+
const scan = await driver.scan(resolved);
|
|
12265
|
+
if (!scan.ok) throw new Error("Skill trial blocked by source scan findings.");
|
|
12266
|
+
const selected = filterArtifactsBySelection(await driver.list(resolved), selectors);
|
|
12267
|
+
const skills = selected.filter((artifact2) => artifact2.type === "skills");
|
|
12268
|
+
if (skills.length !== 1) {
|
|
12269
|
+
throw new Error("Skill trial requires exactly one selected skill. Use --skill <name> or --select skills/<name>.");
|
|
12270
|
+
}
|
|
12271
|
+
const artifact = skills[0];
|
|
12272
|
+
const path = artifact.kind === "dir" ? join47(artifact.sourcePath, "SKILL.md") : artifact.sourcePath;
|
|
12273
|
+
const info = await stat12(path);
|
|
12274
|
+
if (info.size > MAX_TRIAL_SKILL_BYTES) {
|
|
12275
|
+
throw new Error(`Skill trial exceeds the ${MAX_TRIAL_SKILL_BYTES / 1024} KiB content limit.`);
|
|
12276
|
+
}
|
|
12277
|
+
const content = await readFile34(path, "utf8");
|
|
12278
|
+
const frontmatter = readSkillFrontmatter(content, artifact);
|
|
12279
|
+
return {
|
|
12280
|
+
schemaVersion: 1,
|
|
12281
|
+
mode: "read-only",
|
|
12282
|
+
source: resolved.source,
|
|
12283
|
+
...resolved.resolvedCommit ? { resolvedCommit: resolved.resolvedCommit } : {},
|
|
12284
|
+
findings: scan.findings,
|
|
12285
|
+
skill: {
|
|
12286
|
+
name: artifact.name,
|
|
12287
|
+
relativePath: artifact.relativePath,
|
|
12288
|
+
sha256: createHash13("sha256").update(content).digest("hex"),
|
|
12289
|
+
frontmatter,
|
|
12290
|
+
content
|
|
12291
|
+
}
|
|
12292
|
+
};
|
|
12293
|
+
}
|
|
12294
|
+
function readSkillFrontmatter(content, artifact) {
|
|
12295
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/);
|
|
12296
|
+
if (!match) throw new Error(`Skill trial requires YAML frontmatter: ${artifact.relativePath}`);
|
|
12297
|
+
const parsed = parseYaml2(match[1]);
|
|
12298
|
+
if (!parsed || typeof parsed !== "object") throw new Error(`Skill trial frontmatter is invalid: ${artifact.relativePath}`);
|
|
12299
|
+
const value = parsed;
|
|
12300
|
+
if (typeof value.name !== "string" || !value.name.trim() || typeof value.description !== "string" || !value.description.trim()) {
|
|
12301
|
+
throw new Error(`Skill trial frontmatter needs name and description: ${artifact.relativePath}`);
|
|
12302
|
+
}
|
|
12303
|
+
return { name: value.name, description: value.description };
|
|
12304
|
+
}
|
|
12305
|
+
|
|
12051
12306
|
// src/cli/index.ts
|
|
12052
12307
|
var CLI_VERSION = resolveCliVersion();
|
|
12053
12308
|
var COMPANION_SKILL_SOURCE = "github:NestDevLab/agentwheel";
|
|
@@ -12091,13 +12346,13 @@ program.command("list").description("list artifacts exposed by a package source"
|
|
|
12091
12346
|
const resolvedInput = await resolvePackageSource(source, targetRoot);
|
|
12092
12347
|
const selectedArtifacts = selectedArtifactsFromOptionsOrRegistry(options, resolvedInput.registryEntry);
|
|
12093
12348
|
const driver = getSourceDriver(options.driver ?? inferSourceDriverName(resolvedInput.source));
|
|
12094
|
-
const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot:
|
|
12349
|
+
const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot: join48(targetRoot, ".agentwheel", "cache") }))));
|
|
12095
12350
|
const artifacts = filterArtifactsBySelection(await driver.list(resolved), selectedArtifacts);
|
|
12096
12351
|
for (const artifact of artifacts) {
|
|
12097
12352
|
console.log(`${artifact.type} ${artifact.name} ${artifact.relativePath}`);
|
|
12098
12353
|
}
|
|
12099
12354
|
});
|
|
12100
|
-
program.command("search").description("search registry and public catalogue artifacts").argument("<query>", "search query").option("--json", "print the versioned search response as JSON", false).option("--scope <scope>", "search scope: all, registry, enriched, or vercel", "all").option("--type <type>", "artifact type: package, skill, plugin, mcp, or adapter").option("--ecosystem <ecosystem>", "ecosystem: official, openpack, mcp-registry, clawhub, skillkit, or vercel").option("--limit <n>", "maximum number of results (1-100)", "20").option("--include-archived", "include archived catalogue entries", false).option("--refresh", "refresh registry and catalogue caches", false).option("--offline", "use compatible local caches without network access", false).option("-t, --target-root <path>", "workspace root", process.cwd()).action(async (query, options) => {
|
|
12355
|
+
program.command("search").description("search registry and public catalogue artifacts").argument("<query>", "search query").option("--json", "print the versioned search response as JSON", false).option("--scope <scope>", "search scope: all, registry, enriched, or vercel", "all").option("--type <type>", "artifact type: package, skill, plugin, mcp, or adapter").option("--ecosystem <ecosystem>", "ecosystem: official, openpack, mcp-registry, clawhub, skillkit, or vercel").option("--limit <n>", "maximum number of results (1-100)", "20").option("--include-archived", "include archived catalogue entries", false).option("--refresh", "refresh registry and catalogue caches", false).option("--offline", "use compatible local caches without network access", false).option("--semantic", "rank published catalogue entries with the verified semantic index", false).option("-t, --target-root <path>", "workspace root", process.cwd()).action(async (query, options) => {
|
|
12101
12356
|
const trimmedQuery = query.trim();
|
|
12102
12357
|
if (!trimmedQuery) {
|
|
12103
12358
|
throw new Error("Search query must not be empty.");
|
|
@@ -12109,17 +12364,28 @@ program.command("search").description("search registry and public catalogue arti
|
|
|
12109
12364
|
if (options.refresh && options.offline) {
|
|
12110
12365
|
throw new Error("--refresh cannot be used with --offline.");
|
|
12111
12366
|
}
|
|
12367
|
+
if (options.semantic && scope === "registry") {
|
|
12368
|
+
throw new Error("--semantic requires a catalogue scope: all, enriched, or vercel.");
|
|
12369
|
+
}
|
|
12112
12370
|
const warning = (message) => console.error(message);
|
|
12113
12371
|
const targetRoot = normalizeTargetRoot(options.targetRoot);
|
|
12114
12372
|
const registryRequest = scope === "all" || scope === "registry" ? new RegistryClient({ workspaceRoot: targetRoot, offline: options.offline, warn: warning }).getIndex({ refresh: options.refresh }) : void 0;
|
|
12115
|
-
const catalogueRequest = scope === "all" || scope === "enriched" || scope === "vercel" ? new CatalogueClient({ offline: options.offline, warn: warning }).getIndex({ refresh: options.refresh }) : void 0;
|
|
12373
|
+
const catalogueRequest = scope === "all" || scope === "enriched" || scope === "vercel" ? new CatalogueClient({ offline: options.offline, warn: warning }).getIndex({ refresh: options.refresh || options.semantic && !options.offline }) : void 0;
|
|
12116
12374
|
const [registryIndex, catalogueIndex] = await Promise.all([registryRequest, catalogueRequest]);
|
|
12117
12375
|
const entries = buildSearchEntries({
|
|
12118
12376
|
registry: registryIndex?.entries,
|
|
12119
12377
|
enriched: scope === "all" || scope === "enriched" ? catalogueIndex?.enriched : void 0,
|
|
12120
12378
|
vercel: scope === "all" || scope === "vercel" ? catalogueIndex?.vercel : void 0
|
|
12121
12379
|
});
|
|
12122
|
-
const results =
|
|
12380
|
+
const results = options.semantic ? await new SemanticSearchClient({ warn: warning }).search({
|
|
12381
|
+
query: trimmedQuery,
|
|
12382
|
+
entries,
|
|
12383
|
+
catalogueDigests: catalogueIndex?.sourceDigests,
|
|
12384
|
+
type,
|
|
12385
|
+
ecosystem,
|
|
12386
|
+
limit,
|
|
12387
|
+
includeArchived: options.includeArchived
|
|
12388
|
+
}) : searchEntries(entries, trimmedQuery, {
|
|
12123
12389
|
type,
|
|
12124
12390
|
ecosystem,
|
|
12125
12391
|
limit,
|
|
@@ -12131,7 +12397,8 @@ program.command("search").description("search registry and public catalogue arti
|
|
|
12131
12397
|
query: trimmedQuery,
|
|
12132
12398
|
scope,
|
|
12133
12399
|
fromCache: loadedIndexes.every((index) => index.fromCache),
|
|
12134
|
-
results
|
|
12400
|
+
results,
|
|
12401
|
+
...options.semantic ? { searchMode: "semantic" } : {}
|
|
12135
12402
|
};
|
|
12136
12403
|
if (options.json) {
|
|
12137
12404
|
console.log(JSON.stringify(response, null, 2));
|
|
@@ -12139,11 +12406,30 @@ program.command("search").description("search registry and public catalogue arti
|
|
|
12139
12406
|
}
|
|
12140
12407
|
printSearchResults(trimmedQuery, results);
|
|
12141
12408
|
});
|
|
12409
|
+
program.command("try").description("read and validate one skill for the current task without installing it").argument("<source>", "package source").option("--driver <driver>", "source driver").option("--json", "print the read-only skill trial as JSON", false).option("-t, --target-root <path>", "workspace root", process.cwd()).option("--select <type/name>", "select exactly one skill artifact", collectSelectOption, []).option("--skill <name>", "select exactly one skill by name", collectSkillOption, []).action(async (source, options) => {
|
|
12410
|
+
const targetRoot = normalizeTargetRoot(options.targetRoot);
|
|
12411
|
+
const resolvedInput = await resolvePackageSource(source, targetRoot);
|
|
12412
|
+
const driver = getSourceDriver(options.driver ?? inferSourceDriverName(resolvedInput.source));
|
|
12413
|
+
const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, {
|
|
12414
|
+
cacheRoot: join48(targetRoot, ".agentwheel", "cache")
|
|
12415
|
+
}))));
|
|
12416
|
+
const trial = await createSkillTrial(driver, resolved, selectedArtifactsFromOptionsOrRegistry(options, resolvedInput.registryEntry));
|
|
12417
|
+
if (options.json) {
|
|
12418
|
+
console.log(JSON.stringify(trial, null, 2));
|
|
12419
|
+
return;
|
|
12420
|
+
}
|
|
12421
|
+
console.log(`Read-only skill trial: ${trial.skill.name}`);
|
|
12422
|
+
console.log(`Source: ${trial.source}`);
|
|
12423
|
+
console.log(`Description: ${trial.skill.frontmatter.description}`);
|
|
12424
|
+
console.log("No configuration or runtime files were changed.");
|
|
12425
|
+
console.log("\n--- SKILL.md ---\n");
|
|
12426
|
+
console.log(trial.skill.content);
|
|
12427
|
+
});
|
|
12142
12428
|
program.command("scan").description("scan a package source for validation findings").argument("<source>", "package source").option("--driver <driver>", "source driver").option("-t, --target-root <path>", "workspace root", process.cwd()).action(async (source, options) => {
|
|
12143
12429
|
const targetRoot = normalizeTargetRoot(options.targetRoot);
|
|
12144
12430
|
const resolvedInput = await resolvePackageSource(source, targetRoot);
|
|
12145
12431
|
const driver = getSourceDriver(options.driver ?? inferSourceDriverName(resolvedInput.source));
|
|
12146
|
-
const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot:
|
|
12432
|
+
const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot: join48(targetRoot, ".agentwheel", "cache") }))));
|
|
12147
12433
|
const result = await driver.scan(resolved);
|
|
12148
12434
|
if (result.findings.length === 0) {
|
|
12149
12435
|
console.log("Scan ok: no findings");
|
|
@@ -12411,7 +12697,7 @@ journalCommand.command("list").description("show pending apply journals for reso
|
|
|
12411
12697
|
if (!journal) continue;
|
|
12412
12698
|
pending += 1;
|
|
12413
12699
|
console.log(`PENDING ${state.adapter.name}/${state.installationType} at ${state.installRoot}`);
|
|
12414
|
-
console.log(` journal: ${
|
|
12700
|
+
console.log(` journal: ${join48(state.installRoot, ".agentwheel", `${state.state.stateKey}.apply-journal.json`)}`);
|
|
12415
12701
|
console.log(` stateKey: ${state.state.stateKey}`);
|
|
12416
12702
|
console.log(` createdAt: ${journal.createdAt}`);
|
|
12417
12703
|
console.log(` updatedAt: ${journal.updatedAt}`);
|
|
@@ -12714,7 +13000,7 @@ async function packageEntryFromSource(source, targetRoot, options) {
|
|
|
12714
13000
|
const bundle = await stageSource(driver, resolvedSource, {
|
|
12715
13001
|
workspaceRoot: targetRoot,
|
|
12716
13002
|
adapter,
|
|
12717
|
-
cacheRoot:
|
|
13003
|
+
cacheRoot: join48(targetRoot, ".agentwheel", "cache"),
|
|
12718
13004
|
mode: options.mode,
|
|
12719
13005
|
ref: initialVersion?.ref,
|
|
12720
13006
|
frozenLock: lockMode,
|
|
@@ -13108,7 +13394,7 @@ function scopeUpdatePlanToDependencies(result, selectors, previousLock, manifest
|
|
|
13108
13394
|
selectedPreviousNodeIds,
|
|
13109
13395
|
selectedRootIds
|
|
13110
13396
|
);
|
|
13111
|
-
const graphLockDigest =
|
|
13397
|
+
const graphLockDigest = createHash14("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
|
|
13112
13398
|
return {
|
|
13113
13399
|
...result,
|
|
13114
13400
|
bundle: { ...result.bundle, graphLock },
|
|
@@ -13276,7 +13562,7 @@ function scopeUpdatePlanToRoot(result, rootId, previousLock, manifest) {
|
|
|
13276
13562
|
selectedPreviousNodeIds,
|
|
13277
13563
|
/* @__PURE__ */ new Set([rootId])
|
|
13278
13564
|
);
|
|
13279
|
-
const graphLockDigest =
|
|
13565
|
+
const graphLockDigest = createHash14("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
|
|
13280
13566
|
return {
|
|
13281
13567
|
...scoped,
|
|
13282
13568
|
bundle: { ...scoped.bundle, graphLock },
|
|
@@ -13348,7 +13634,7 @@ function keepManifestEntryOperation(entry, targetRoot, scopeDescription, operati
|
|
|
13348
13634
|
artifactType: entry.artifactType,
|
|
13349
13635
|
artifactName: entry.artifactName,
|
|
13350
13636
|
kind: entry.kind,
|
|
13351
|
-
destPath: operation?.destPath ??
|
|
13637
|
+
destPath: operation?.destPath ?? join48(targetRoot, entry.path),
|
|
13352
13638
|
relativeDestPath: entry.path,
|
|
13353
13639
|
desiredHash: entry.sourceHash,
|
|
13354
13640
|
currentHash: operation?.currentHash ?? entry.hash,
|
|
@@ -13933,12 +14219,12 @@ async function printDoctor(target, options) {
|
|
|
13933
14219
|
const requestedSkills = doctorSkillRequests(target, options);
|
|
13934
14220
|
const skills = [];
|
|
13935
14221
|
for (const request of requestedSkills) {
|
|
13936
|
-
const skillPath =
|
|
14222
|
+
const skillPath = join48(state.installRoot, targetMapping.dest, request.name);
|
|
13937
14223
|
const exists = await pathExists(skillPath);
|
|
13938
14224
|
const manifestEntry = manifest?.entries.find((entry) => {
|
|
13939
14225
|
if (entry.artifactType !== "skills") return false;
|
|
13940
14226
|
const legacyInstallName = "installName" in entry && typeof entry.installName === "string" ? entry.installName : void 0;
|
|
13941
|
-
return entry.artifactName === request.name || legacyInstallName === request.name || entry.path ===
|
|
14227
|
+
return entry.artifactName === request.name || legacyInstallName === request.name || entry.path === join48(targetMapping.dest, request.name);
|
|
13942
14228
|
});
|
|
13943
14229
|
const status = manifestEntry ? "managed" : exists ? "present-unmanaged" : "missing";
|
|
13944
14230
|
skills.push({
|
|
@@ -14018,7 +14304,7 @@ function doctorSkillLabel(name) {
|
|
|
14018
14304
|
return `${name} skill`;
|
|
14019
14305
|
}
|
|
14020
14306
|
function isSyncwheelWorkspace(targetRoot) {
|
|
14021
|
-
return existsSync(
|
|
14307
|
+
return existsSync(join48(targetRoot, ".syncwheel", "manifest.json"));
|
|
14022
14308
|
}
|
|
14023
14309
|
function skillInstallCommand(adapter, installationType, options, skill, behavior = {}) {
|
|
14024
14310
|
const args = [
|
|
@@ -14086,7 +14372,7 @@ function normalizeRuntimeScopeOptions(options, behavior = {}) {
|
|
|
14086
14372
|
}
|
|
14087
14373
|
const canDefaultTargetRoot = !options.agent && !options.all && !options.allDetected && !options.profile;
|
|
14088
14374
|
if (!targetRoot && canDefaultTargetRoot && (options.user || installationType === "user" || behavior.defaultUser)) {
|
|
14089
|
-
targetRoot =
|
|
14375
|
+
targetRoot = homedir12();
|
|
14090
14376
|
}
|
|
14091
14377
|
if (!installationType && behavior.defaultUser) {
|
|
14092
14378
|
installationType = "user";
|
|
@@ -14106,12 +14392,12 @@ function looksLikeSourceSpecifier(value) {
|
|
|
14106
14392
|
return value.includes(":") || value.startsWith("/") || value.startsWith("./") || value.startsWith("../") || value === "~" || value.startsWith("~/");
|
|
14107
14393
|
}
|
|
14108
14394
|
function normalizeCliPath(value) {
|
|
14109
|
-
if (value === "~") return
|
|
14110
|
-
if (value.startsWith("~/")) return resolve22(
|
|
14395
|
+
if (value === "~") return homedir12();
|
|
14396
|
+
if (value.startsWith("~/")) return resolve22(homedir12(), value.slice(2));
|
|
14111
14397
|
return resolve22(value);
|
|
14112
14398
|
}
|
|
14113
14399
|
function isHomePath(path) {
|
|
14114
|
-
return resolve22(path) === resolve22(
|
|
14400
|
+
return resolve22(path) === resolve22(homedir12());
|
|
14115
14401
|
}
|
|
14116
14402
|
function adapterListFromOption(adapter) {
|
|
14117
14403
|
if (!adapter) return [];
|
|
@@ -14166,10 +14452,10 @@ function filterUninstallPlanBySelection(plan, selected) {
|
|
|
14166
14452
|
};
|
|
14167
14453
|
}
|
|
14168
14454
|
async function initPackage(root) {
|
|
14169
|
-
await mkdir23(
|
|
14170
|
-
await mkdir23(
|
|
14171
|
-
await mkdir23(
|
|
14172
|
-
const manifestPath =
|
|
14455
|
+
await mkdir23(join48(root, "instructions"), { recursive: true });
|
|
14456
|
+
await mkdir23(join48(root, "rules"), { recursive: true });
|
|
14457
|
+
await mkdir23(join48(root, "skills"), { recursive: true });
|
|
14458
|
+
const manifestPath = join48(root, "openpack.json");
|
|
14173
14459
|
const manifest = {
|
|
14174
14460
|
schemaVersion: 2,
|
|
14175
14461
|
name: "example/agentwheel-package",
|
|
@@ -14182,7 +14468,7 @@ async function initPackage(root) {
|
|
|
14182
14468
|
};
|
|
14183
14469
|
await writeFile22(manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
14184
14470
|
`, "utf8");
|
|
14185
|
-
await writeFile22(
|
|
14471
|
+
await writeFile22(join48(root, "instructions", "AGENTS.md"), "# Agent Instructions\n", "utf8");
|
|
14186
14472
|
}
|
|
14187
14473
|
async function defaultBootstrapPackage(_root) {
|
|
14188
14474
|
const packageRoot = await findAgentwheelPackageRoot(dirname32(fileURLToPath3(import.meta.url)));
|
|
@@ -14287,6 +14573,7 @@ function printSearchResults(query, results) {
|
|
|
14287
14573
|
`${index + 1}. ${result.name} [type=${result.type}; ecosystem=${ecosystem}; installability=${result.installability}; provenance=${provenances}]`
|
|
14288
14574
|
);
|
|
14289
14575
|
console.log(` ${result.description || "(no description)"}`);
|
|
14576
|
+
if (result.semanticScore !== void 0) console.log(` Semantic score: ${result.semanticScore}`);
|
|
14290
14577
|
if (result.installCommand) {
|
|
14291
14578
|
console.log(` Install: ${result.installCommand}`);
|
|
14292
14579
|
} else if (result.source) {
|
package/install.md
CHANGED
|
@@ -11,8 +11,9 @@ commands, subagents, MCP, hooks, settings, plugins, and fragments.
|
|
|
11
11
|
1. Inspect the environment.
|
|
12
12
|
2. Install the `agentwheel` CLI if it is missing or too old.
|
|
13
13
|
3. Verify the CLI.
|
|
14
|
-
4. Install the Agentwheel
|
|
15
|
-
5.
|
|
14
|
+
4. Install the Agentwheel management skill into the user's active runtime when requested.
|
|
15
|
+
5. Offer the separate proactive discovery skill when the user wants automatic capability suggestions.
|
|
16
|
+
6. Show the user how to browse or install catalogue resources.
|
|
16
17
|
|
|
17
18
|
If the user explicitly asked you to install Agentwheel, you may run the installation commands below.
|
|
18
19
|
Ask before destructive operations, `--force`, remote publishing, local adapter code execution, or
|
|
@@ -46,7 +47,7 @@ agentwheel --version
|
|
|
46
47
|
agentwheel doctor --help
|
|
47
48
|
```
|
|
48
49
|
|
|
49
|
-
## Install The Companion
|
|
50
|
+
## Install The Companion Skills
|
|
50
51
|
|
|
51
52
|
Pick the adapter that matches the active runtime:
|
|
52
53
|
|
|
@@ -70,6 +71,15 @@ agentwheel doctor --adapter claude --user
|
|
|
70
71
|
agentwheel install github:NestDevLab/agentwheel --adapter claude --user --skill agentwheel
|
|
71
72
|
```
|
|
72
73
|
|
|
74
|
+
The `agentwheel` skill is not proactive. If the user wants the agent to notice capability gaps,
|
|
75
|
+
suggest up to three semantic matches, and offer read-only trials, preview and install the separate
|
|
76
|
+
discovery skill:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
agentwheel install github:NestDevLab/agentwheel --adapter claude --user --skill agentwheel-discovery --dry-run
|
|
80
|
+
agentwheel install github:NestDevLab/agentwheel --adapter claude --user --skill agentwheel-discovery
|
|
81
|
+
```
|
|
82
|
+
|
|
73
83
|
If the user wants to preview first, add `--dry-run` to the `install` command.
|
|
74
84
|
|
|
75
85
|
## Use The Catalogue
|
|
@@ -115,6 +125,7 @@ npx agentwheel@latest registry publish https://github.com/owner/repo
|
|
|
115
125
|
|
|
116
126
|
- `agentwheel --version` works.
|
|
117
127
|
- `agentwheel doctor` runs for the selected adapter.
|
|
118
|
-
- The
|
|
128
|
+
- The management skill is installed if the user requested it.
|
|
129
|
+
- The discovery skill is installed only if the user requested proactive suggestions.
|
|
119
130
|
- Any catalogue resource install uses the adapter and installation type the user intended.
|
|
120
131
|
- Catalogue submissions use `agentwheel registry publish` unless the user explicitly wants a manual registry PR.
|
package/openpack.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentwheel",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.5",
|
|
4
4
|
"description": "Weave skills, rules, and instructions across every AI agent.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
"typecheck": "tsc --noEmit"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
+
"@huggingface/transformers": "4.2.0",
|
|
52
53
|
"@skillkit/core": "1.24.0",
|
|
53
54
|
"commander": "14.0.2",
|
|
54
55
|
"jsonc-parser": "3.3.1",
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: agentwheel
|
|
3
|
-
description:
|
|
3
|
+
description: Manage and explicitly inspect reusable agent artifacts with Agentwheel. Use when the user asks to search, add, install, update, customize, eject, or uninstall skills, integrations, workflows, or other OpenPack artifacts across runtimes. For automatic capability suggestions, install the separate agentwheel-discovery skill.
|
|
4
4
|
allowed-tools: [Bash]
|
|
5
5
|
license: MIT
|
|
6
6
|
metadata:
|
|
7
7
|
author: NestDevLab
|
|
8
|
-
version: "0.16.
|
|
8
|
+
version: "0.16.5"
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
# agentwheel
|
|
@@ -73,30 +73,34 @@ agentwheel init package
|
|
|
73
73
|
|
|
74
74
|
`agentwheel init package` creates `openpack.json`, `instructions/`, `rules/`, `skills/`, and `instructions/AGENTS.md`.
|
|
75
75
|
|
|
76
|
-
## Discovery
|
|
76
|
+
## Explicit Discovery
|
|
77
77
|
|
|
78
78
|
Search configured registries and public catalogue sources:
|
|
79
79
|
|
|
80
80
|
```bash
|
|
81
81
|
agentwheel search "<query>"
|
|
82
82
|
agentwheel search "<query>" --json --limit 10
|
|
83
|
+
agentwheel search "<query>" --semantic --json --limit 10
|
|
83
84
|
```
|
|
84
85
|
|
|
85
|
-
Search
|
|
86
|
+
Search when the user explicitly asks to find, compare, or evaluate an artifact. Search results are
|
|
87
|
+
proposals, not approval, and never change desired state by themselves.
|
|
86
88
|
|
|
87
|
-
|
|
89
|
+
### Semantic catalogue search
|
|
88
90
|
|
|
89
|
-
|
|
90
|
-
2. Generate one to four short lexical queries using capability terms, synonyms, runtime names, and artifact types. Prefer English catalogue terms when the request uses another language.
|
|
91
|
-
3. Run one `agentwheel search "<query>" --json --limit 10` per variant. Stop after four calls; do not recursively refine without new user requirements.
|
|
92
|
-
4. Merge results by stable `id`. Treat CLI scores as retrieval signals, not semantic confidence.
|
|
93
|
-
5. Rerank against the original request using capabilities, runtime or ecosystem, artifact type, description, tags, `provides`, and installability. Do not infer capabilities absent from result metadata.
|
|
94
|
-
6. Suggest zero to three distinct artifacts. For each, give its name or source, one evidence-based match reason, installability, and a safe next command.
|
|
95
|
-
7. Wait for explicit approval before `add`, `install`, plugin execution, or configuration changes.
|
|
91
|
+
Use `--semantic` for a capability request whose wording is unlikely to match catalogue labels, or after bounded lexical search returns only weak matches. It queries the same published catalogue vector index used by the website and validates its checksums against the loaded catalogue before ranking. It is opt-in because first use may download the model and index assets.
|
|
96
92
|
|
|
97
|
-
|
|
93
|
+
```bash
|
|
94
|
+
agentwheel search "remember corrections from earlier conversations" --semantic --json --limit 10
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Do not use `--semantic` for a registry-only search, and never describe a semantic score as proof
|
|
98
|
+
that an artifact implements a capability.
|
|
99
|
+
|
|
100
|
+
Use a trial only for instruction skills. Plugins, MCP servers, hooks, commands, and settings are not trialled because reading them is not equivalent to safely executing them in an isolated runtime.
|
|
98
101
|
|
|
99
|
-
|
|
102
|
+
Install `skills/agentwheel-discovery` separately when the user wants proactive capability-gap
|
|
103
|
+
detection, bounded semantic recommendations, and read-only trial suggestions during unrelated work.
|
|
100
104
|
|
|
101
105
|
Registry maintenance remains explicit:
|
|
102
106
|
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agentwheel-discovery
|
|
3
|
+
description: Proactively discover reusable skills, integrations, and workflows when a user's request exposes a missing capability, repeated manual work, or unavailable integration. Use Agentwheel semantic search to suggest up to three evidence-backed matches and offer a read-only trial without installing or changing anything.
|
|
4
|
+
allowed-tools: [Bash]
|
|
5
|
+
license: MIT
|
|
6
|
+
metadata:
|
|
7
|
+
author: NestDevLab
|
|
8
|
+
version: "0.16.5"
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Agentwheel Discovery
|
|
12
|
+
|
|
13
|
+
Find reusable capabilities during normal work without silently changing the user's environment.
|
|
14
|
+
This skill is optional: install it only when proactive recommendations are wanted. The separate
|
|
15
|
+
`agentwheel` skill owns artifact management and explicit installation workflows.
|
|
16
|
+
|
|
17
|
+
## Safety
|
|
18
|
+
|
|
19
|
+
- Inspect the skills and tools already available in the current session before searching.
|
|
20
|
+
- Search and trial are read-only. Never add, install, enable, execute, or change configuration
|
|
21
|
+
without explicit approval for the artifact and target scope.
|
|
22
|
+
- Treat retrieval scores as ranking signals, not proof that an artifact implements a capability.
|
|
23
|
+
- Continue useful work while searching when possible; do not interrupt solely to advertise weak
|
|
24
|
+
or marginal matches.
|
|
25
|
+
|
|
26
|
+
## When To Search
|
|
27
|
+
|
|
28
|
+
Search automatically when substantive work exposes a missing capability, repeated manual workflow,
|
|
29
|
+
or unavailable integration. This is a per-turn judgment, not a background monitor. Delegated agents
|
|
30
|
+
follow the same trigger when this skill is available.
|
|
31
|
+
|
|
32
|
+
Skip discovery when the user explicitly wants a custom implementation, has already selected an
|
|
33
|
+
artifact, an installed artifact clearly satisfies the request, candidates are only weak lexical
|
|
34
|
+
matches, or the same suggestion was declined or shown without new evidence.
|
|
35
|
+
|
|
36
|
+
## Discovery Workflow
|
|
37
|
+
|
|
38
|
+
1. Extract the capability and constraints from the complete request.
|
|
39
|
+
2. Run one fast semantic search using the full capability request:
|
|
40
|
+
`agentwheel search "<query>" --semantic --json --limit 10`.
|
|
41
|
+
Use exact lexical search first only when the user supplied an artifact name, source, or registry
|
|
42
|
+
identifier.
|
|
43
|
+
3. If semantic candidates are weak, absent, or need a precise runtime or type constraint, generate
|
|
44
|
+
up to three short lexical variants using capability terms, synonyms, runtime names, and artifact
|
|
45
|
+
types. Prefer English catalogue terms for non-English requests. Stop after four total searches;
|
|
46
|
+
do not recursively refine without new user requirements.
|
|
47
|
+
4. Merge results by stable `id`. Treat CLI scores as retrieval signals, not semantic confidence.
|
|
48
|
+
5. Rerank against the original request using capabilities, runtime or ecosystem, artifact type,
|
|
49
|
+
description, tags, `provides`, and installability. Do not infer capabilities absent from result
|
|
50
|
+
metadata.
|
|
51
|
+
6. Suggest zero to three distinct artifacts. For each, give its name or source, one evidence-based
|
|
52
|
+
match reason, installability, and a read-only trial command before any installation command.
|
|
53
|
+
7. Search once per distinct capability gap. Do not repeat a recommendation without new evidence.
|
|
54
|
+
|
|
55
|
+
Do not use `--semantic` for a registry-only search. The semantic path queries the same published
|
|
56
|
+
catalogue vector index used by the website and validates its checksums against the loaded catalogue.
|
|
57
|
+
First use may download the model and index assets.
|
|
58
|
+
|
|
59
|
+
## Read-Only Trial
|
|
60
|
+
|
|
61
|
+
When the user wants to evaluate one instruction skill, run:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
agentwheel try <source> --skill <name> --json
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
A trial fetches, scans, validates, and reads exactly one `SKILL.md` for the current task. It does not
|
|
68
|
+
add a package, change configuration, write runtime files, or execute code. Do not trial plugins,
|
|
69
|
+
MCP servers, hooks, commands, or settings because reading their metadata is not safe execution.
|
|
70
|
+
|
|
71
|
+
## Recommendation Contract
|
|
72
|
+
|
|
73
|
+
Search recommendations are conversational only: they do not select OpenPack `suggests`, mutate
|
|
74
|
+
desired state, or imply installation approval. Wait for explicit approval before `add`, `install`,
|
|
75
|
+
plugin execution, or configuration changes.
|