agent-resource-management 2.1.4 → 2.1.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/dist/main.js +344 -51
- package/package.json +1 -1
- package/src/cmd/agent.ts +320 -19
- package/src/lib/client.ts +43 -8
- package/src/main.ts +54 -14
package/dist/main.js
CHANGED
|
@@ -268,34 +268,42 @@ class ApiClient {
|
|
|
268
268
|
throw new Error(res.msg);
|
|
269
269
|
}
|
|
270
270
|
}
|
|
271
|
-
async bindSkillToAgent(agentId, skillId, config) {
|
|
271
|
+
async bindSkillToAgent(agentId, skillId, version, config) {
|
|
272
272
|
const res = await this.request(`/agents/${agentId}/skills`, {
|
|
273
273
|
method: "POST",
|
|
274
|
-
body: JSON.stringify({ skillId, config })
|
|
274
|
+
body: JSON.stringify({ skillId, version, config })
|
|
275
275
|
});
|
|
276
276
|
if (!res.ok) {
|
|
277
277
|
throw new Error(res.msg);
|
|
278
278
|
}
|
|
279
279
|
}
|
|
280
|
-
async unbindSkillFromAgent(agentId, skillId) {
|
|
281
|
-
|
|
280
|
+
async unbindSkillFromAgent(agentId, skillId, version) {
|
|
281
|
+
let path = `/agents/${agentId}/skills?skillId=${skillId}`;
|
|
282
|
+
if (version) {
|
|
283
|
+
path += `&version=${version}`;
|
|
284
|
+
}
|
|
285
|
+
const res = await this.request(path, {
|
|
282
286
|
method: "DELETE"
|
|
283
287
|
});
|
|
284
288
|
if (!res.ok) {
|
|
285
289
|
throw new Error(res.msg);
|
|
286
290
|
}
|
|
287
291
|
}
|
|
288
|
-
async bindKnowledgeToAgent(agentId, knowledgeId, retrievalConfig) {
|
|
292
|
+
async bindKnowledgeToAgent(agentId, knowledgeId, version, retrievalConfig) {
|
|
289
293
|
const res = await this.request(`/agents/${agentId}/knowledges`, {
|
|
290
294
|
method: "POST",
|
|
291
|
-
body: JSON.stringify({ knowledgeId, retrievalConfig })
|
|
295
|
+
body: JSON.stringify({ knowledgeId, version, retrievalConfig })
|
|
292
296
|
});
|
|
293
297
|
if (!res.ok) {
|
|
294
298
|
throw new Error(res.msg);
|
|
295
299
|
}
|
|
296
300
|
}
|
|
297
|
-
async unbindKnowledgeFromAgent(agentId, knowledgeId) {
|
|
298
|
-
|
|
301
|
+
async unbindKnowledgeFromAgent(agentId, knowledgeId, version) {
|
|
302
|
+
let path = `/agents/${agentId}/knowledges?knowledgeId=${knowledgeId}`;
|
|
303
|
+
if (version) {
|
|
304
|
+
path += `&version=${version}`;
|
|
305
|
+
}
|
|
306
|
+
const res = await this.request(path, {
|
|
299
307
|
method: "DELETE"
|
|
300
308
|
});
|
|
301
309
|
if (!res.ok) {
|
|
@@ -306,6 +314,13 @@ class ApiClient {
|
|
|
306
314
|
const result = await this.listAgents(name, 1, 1);
|
|
307
315
|
return result.agents.find((a) => a.name === name) || null;
|
|
308
316
|
}
|
|
317
|
+
async getAgentBindingsHistory(agentId) {
|
|
318
|
+
const res = await this.request(`/agents/${agentId}/bindings/history`);
|
|
319
|
+
if (!res.ok) {
|
|
320
|
+
throw new Error(res.msg);
|
|
321
|
+
}
|
|
322
|
+
return res.data;
|
|
323
|
+
}
|
|
309
324
|
}
|
|
310
325
|
|
|
311
326
|
// src/lib/storage.ts
|
|
@@ -577,7 +592,7 @@ function setOutputMode(mode) {
|
|
|
577
592
|
}
|
|
578
593
|
|
|
579
594
|
// src/lib/validate.ts
|
|
580
|
-
import { readFileSync as readFileSync2, existsSync as existsSync2, statSync
|
|
595
|
+
import { readFileSync as readFileSync2, existsSync as existsSync2, statSync } from "fs";
|
|
581
596
|
import { execSync } from "child_process";
|
|
582
597
|
import { join as join2, extname } from "path";
|
|
583
598
|
import { mkdtempSync, rmSync } from "fs";
|
|
@@ -597,7 +612,7 @@ function validateZip(filePath) {
|
|
|
597
612
|
result.errors.push("文件必须是 ZIP 格式");
|
|
598
613
|
return result;
|
|
599
614
|
}
|
|
600
|
-
const stats =
|
|
615
|
+
const stats = statSync(filePath);
|
|
601
616
|
if (stats.size === 0) {
|
|
602
617
|
result.valid = false;
|
|
603
618
|
result.errors.push("ZIP 文件为空");
|
|
@@ -809,9 +824,9 @@ function validateAgentDir(dirPath) {
|
|
|
809
824
|
result.errors.push("AGENT.md 缺少 frontmatter (--- 包裹的 YAML)");
|
|
810
825
|
}
|
|
811
826
|
const skillsDir = join2(dirPath, "skills");
|
|
812
|
-
if (existsSync2(skillsDir) &&
|
|
827
|
+
if (existsSync2(skillsDir) && statSync(skillsDir).isDirectory()) {
|
|
813
828
|
const skillDirs = execSync(`ls -1 "${skillsDir}"`, { encoding: "utf-8" }).split(`
|
|
814
|
-
`).filter((l) => l.trim() && existsSync2(join2(skillsDir, l)) &&
|
|
829
|
+
`).filter((l) => l.trim() && existsSync2(join2(skillsDir, l)) && statSync(join2(skillsDir, l)).isDirectory());
|
|
815
830
|
for (const skillDir of skillDirs) {
|
|
816
831
|
const skillMdPath = join2(skillsDir, skillDir, "SKILL.md");
|
|
817
832
|
if (!existsSync2(skillMdPath)) {
|
|
@@ -820,7 +835,7 @@ function validateAgentDir(dirPath) {
|
|
|
820
835
|
}
|
|
821
836
|
}
|
|
822
837
|
const knowledgesDir = join2(dirPath, "knowledges");
|
|
823
|
-
if (existsSync2(knowledgesDir) &&
|
|
838
|
+
if (existsSync2(knowledgesDir) && statSync(knowledgesDir).isDirectory()) {
|
|
824
839
|
const mdFiles = execSync(`ls -1 "${knowledgesDir}"`, { encoding: "utf-8" }).split(`
|
|
825
840
|
`).filter((l) => l.trim().endsWith(".md"));
|
|
826
841
|
if (mdFiles.length === 0) {
|
|
@@ -835,7 +850,7 @@ function validateAgentDir(dirPath) {
|
|
|
835
850
|
}
|
|
836
851
|
|
|
837
852
|
// src/cmd/skill.ts
|
|
838
|
-
import { writeFileSync as writeFileSync2, existsSync as existsSync3, statSync as
|
|
853
|
+
import { writeFileSync as writeFileSync2, existsSync as existsSync3, statSync as statSync2 } from "fs";
|
|
839
854
|
import { join as join3, basename, dirname } from "path";
|
|
840
855
|
import { execSync as execSync2 } from "child_process";
|
|
841
856
|
import { mkdtempSync as mkdtempSync2, rmSync as rmSync2 } from "fs";
|
|
@@ -967,7 +982,7 @@ async function downloadSkill(name, outputDir) {
|
|
|
967
982
|
const nonZipEntries = entries.filter((e) => e !== `${name}.zip`);
|
|
968
983
|
if (nonZipEntries.length === 1) {
|
|
969
984
|
const onlyEntry = join3(tempDir, nonZipEntries[0]);
|
|
970
|
-
if (
|
|
985
|
+
if (statSync2(onlyEntry).isDirectory()) {
|
|
971
986
|
execSync2(`cp -r "${onlyEntry}"/* "${targetDir}/"`, { stdio: "pipe" });
|
|
972
987
|
} else {
|
|
973
988
|
execSync2(`cp -r "${onlyEntry}" "${targetDir}/"`, { stdio: "pipe" });
|
|
@@ -1116,7 +1131,7 @@ async function deleteSkill(name) {
|
|
|
1116
1131
|
}
|
|
1117
1132
|
}
|
|
1118
1133
|
async function validateSkill(filePath) {
|
|
1119
|
-
const isDir = existsSync3(filePath) &&
|
|
1134
|
+
const isDir = existsSync3(filePath) && statSync2(filePath).isDirectory();
|
|
1120
1135
|
const result = isDir ? validateSkillDir(filePath) : validateZip(filePath);
|
|
1121
1136
|
if (shouldOutputJson()) {
|
|
1122
1137
|
outputJson({
|
|
@@ -1172,7 +1187,7 @@ async function validateSkill(filePath) {
|
|
|
1172
1187
|
}
|
|
1173
1188
|
|
|
1174
1189
|
// src/cmd/agent.ts
|
|
1175
|
-
import { writeFileSync as writeFileSync3, existsSync as existsSync4 } from "fs";
|
|
1190
|
+
import { writeFileSync as writeFileSync3, existsSync as existsSync4, readFileSync as readFileSync3, statSync as statSync3 } from "fs";
|
|
1176
1191
|
import { join as join4 } from "path";
|
|
1177
1192
|
import { execSync as execSync3 } from "child_process";
|
|
1178
1193
|
import { mkdtempSync as mkdtempSync3, rmSync as rmSync3 } from "fs";
|
|
@@ -1283,7 +1298,7 @@ async function deleteAgent(id) {
|
|
|
1283
1298
|
process.exit(1);
|
|
1284
1299
|
}
|
|
1285
1300
|
}
|
|
1286
|
-
async function bindSkill(id, skillId, config) {
|
|
1301
|
+
async function bindSkill(id, skillId, version = "1.0.0", config) {
|
|
1287
1302
|
const configStore = loadConfig();
|
|
1288
1303
|
if (!configStore?.token) {
|
|
1289
1304
|
if (shouldOutputJson()) {
|
|
@@ -1296,12 +1311,12 @@ async function bindSkill(id, skillId, config) {
|
|
|
1296
1311
|
const client = new ApiClient(configStore.serverUrl, configStore.token);
|
|
1297
1312
|
try {
|
|
1298
1313
|
const parsedConfig = config ? JSON.parse(config) : undefined;
|
|
1299
|
-
await client.bindSkillToAgent(id, skillId, parsedConfig);
|
|
1314
|
+
await client.bindSkillToAgent(id, skillId, version, parsedConfig);
|
|
1300
1315
|
if (shouldOutputJson()) {
|
|
1301
|
-
outputJson({ success: true, data: { agentId: id, skillId, config: parsedConfig } });
|
|
1316
|
+
outputJson({ success: true, data: { agentId: id, skillId, version, config: parsedConfig } });
|
|
1302
1317
|
return;
|
|
1303
1318
|
}
|
|
1304
|
-
success(`Skill "${skillId}" 已绑定到 Agent "${id}"`);
|
|
1319
|
+
success(`Skill "${skillId}@${version}" 已绑定到 Agent "${id}"`);
|
|
1305
1320
|
} catch (err) {
|
|
1306
1321
|
if (shouldOutputJson()) {
|
|
1307
1322
|
outputJson({ success: false, error: { code: "BIND_FAILED", message: err instanceof Error ? err.message : "未知错误" } });
|
|
@@ -1311,7 +1326,7 @@ async function bindSkill(id, skillId, config) {
|
|
|
1311
1326
|
process.exit(1);
|
|
1312
1327
|
}
|
|
1313
1328
|
}
|
|
1314
|
-
async function unbindSkill(id, skillId) {
|
|
1329
|
+
async function unbindSkill(id, skillId, version) {
|
|
1315
1330
|
const config = loadConfig();
|
|
1316
1331
|
if (!config?.token) {
|
|
1317
1332
|
if (shouldOutputJson()) {
|
|
@@ -1323,12 +1338,12 @@ async function unbindSkill(id, skillId) {
|
|
|
1323
1338
|
}
|
|
1324
1339
|
const client = new ApiClient(config.serverUrl, config.token);
|
|
1325
1340
|
try {
|
|
1326
|
-
await client.unbindSkillFromAgent(id, skillId);
|
|
1341
|
+
await client.unbindSkillFromAgent(id, skillId, version);
|
|
1327
1342
|
if (shouldOutputJson()) {
|
|
1328
|
-
outputJson({ success: true, data: { agentId: id, skillId } });
|
|
1343
|
+
outputJson({ success: true, data: { agentId: id, skillId, version } });
|
|
1329
1344
|
return;
|
|
1330
1345
|
}
|
|
1331
|
-
success(`Skill "${skillId}" 已从 Agent "${id}" 解绑`);
|
|
1346
|
+
success(`Skill "${skillId}${version ? "@" + version : ""}" 已从 Agent "${id}" 解绑`);
|
|
1332
1347
|
} catch (err) {
|
|
1333
1348
|
if (shouldOutputJson()) {
|
|
1334
1349
|
outputJson({ success: false, error: { code: "UNBIND_FAILED", message: err instanceof Error ? err.message : "未知错误" } });
|
|
@@ -1338,7 +1353,7 @@ async function unbindSkill(id, skillId) {
|
|
|
1338
1353
|
process.exit(1);
|
|
1339
1354
|
}
|
|
1340
1355
|
}
|
|
1341
|
-
async function bindKnowledge(id, knowledgeId, retrievalConfig) {
|
|
1356
|
+
async function bindKnowledge(id, knowledgeId, version = "1.0.0", retrievalConfig) {
|
|
1342
1357
|
const configStore = loadConfig();
|
|
1343
1358
|
if (!configStore?.token) {
|
|
1344
1359
|
if (shouldOutputJson()) {
|
|
@@ -1351,12 +1366,12 @@ async function bindKnowledge(id, knowledgeId, retrievalConfig) {
|
|
|
1351
1366
|
const client = new ApiClient(configStore.serverUrl, configStore.token);
|
|
1352
1367
|
try {
|
|
1353
1368
|
const parsedConfig = retrievalConfig ? JSON.parse(retrievalConfig) : undefined;
|
|
1354
|
-
await client.bindKnowledgeToAgent(id, knowledgeId, parsedConfig);
|
|
1369
|
+
await client.bindKnowledgeToAgent(id, knowledgeId, version, parsedConfig);
|
|
1355
1370
|
if (shouldOutputJson()) {
|
|
1356
|
-
outputJson({ success: true, data: { agentId: id, knowledgeId, retrievalConfig: parsedConfig } });
|
|
1371
|
+
outputJson({ success: true, data: { agentId: id, knowledgeId, version, retrievalConfig: parsedConfig } });
|
|
1357
1372
|
return;
|
|
1358
1373
|
}
|
|
1359
|
-
success(`Knowledge "${knowledgeId}" 已绑定到 Agent "${id}"`);
|
|
1374
|
+
success(`Knowledge "${knowledgeId}@${version}" 已绑定到 Agent "${id}"`);
|
|
1360
1375
|
} catch (err) {
|
|
1361
1376
|
if (shouldOutputJson()) {
|
|
1362
1377
|
outputJson({ success: false, error: { code: "BIND_FAILED", message: err instanceof Error ? err.message : "未知错误" } });
|
|
@@ -1366,7 +1381,7 @@ async function bindKnowledge(id, knowledgeId, retrievalConfig) {
|
|
|
1366
1381
|
process.exit(1);
|
|
1367
1382
|
}
|
|
1368
1383
|
}
|
|
1369
|
-
async function unbindKnowledge(id, knowledgeId) {
|
|
1384
|
+
async function unbindKnowledge(id, knowledgeId, version) {
|
|
1370
1385
|
const config = loadConfig();
|
|
1371
1386
|
if (!config?.token) {
|
|
1372
1387
|
if (shouldOutputJson()) {
|
|
@@ -1378,12 +1393,12 @@ async function unbindKnowledge(id, knowledgeId) {
|
|
|
1378
1393
|
}
|
|
1379
1394
|
const client = new ApiClient(config.serverUrl, config.token);
|
|
1380
1395
|
try {
|
|
1381
|
-
await client.unbindKnowledgeFromAgent(id, knowledgeId);
|
|
1396
|
+
await client.unbindKnowledgeFromAgent(id, knowledgeId, version);
|
|
1382
1397
|
if (shouldOutputJson()) {
|
|
1383
|
-
outputJson({ success: true, data: { agentId: id, knowledgeId } });
|
|
1398
|
+
outputJson({ success: true, data: { agentId: id, knowledgeId, version } });
|
|
1384
1399
|
return;
|
|
1385
1400
|
}
|
|
1386
|
-
success(`Knowledge "${knowledgeId}" 已从 Agent "${id}" 解绑`);
|
|
1401
|
+
success(`Knowledge "${knowledgeId}${version ? "@" + version : ""}" 已从 Agent "${id}" 解绑`);
|
|
1387
1402
|
} catch (err) {
|
|
1388
1403
|
if (shouldOutputJson()) {
|
|
1389
1404
|
outputJson({ success: false, error: { code: "UNBIND_FAILED", message: err instanceof Error ? err.message : "未知错误" } });
|
|
@@ -1603,9 +1618,9 @@ async function createAgentFromFolder(folderPath) {
|
|
|
1603
1618
|
const uploadedSkills = [];
|
|
1604
1619
|
const uploadedKnowledges = [];
|
|
1605
1620
|
const skillsDir = join4(folderPath, "skills");
|
|
1606
|
-
if (existsSync4(skillsDir) &&
|
|
1621
|
+
if (existsSync4(skillsDir) && statSync3(skillsDir).isDirectory()) {
|
|
1607
1622
|
const skillDirs = execSync3(`ls -1 "${skillsDir}"`, { encoding: "utf-8" }).split(`
|
|
1608
|
-
`).filter((l) => l.trim() && existsSync4(join4(skillsDir, l)) &&
|
|
1623
|
+
`).filter((l) => l.trim() && existsSync4(join4(skillsDir, l)) && statSync3(join4(skillsDir, l)).isDirectory());
|
|
1609
1624
|
for (const skillDir of skillDirs) {
|
|
1610
1625
|
const skillPath = join4(skillsDir, skillDir);
|
|
1611
1626
|
try {
|
|
@@ -1637,7 +1652,7 @@ async function createAgentFromFolder(folderPath) {
|
|
|
1637
1652
|
}
|
|
1638
1653
|
}
|
|
1639
1654
|
const knowledgesDir = join4(folderPath, "knowledges");
|
|
1640
|
-
if (existsSync4(knowledgesDir) &&
|
|
1655
|
+
if (existsSync4(knowledgesDir) && statSync3(knowledgesDir).isDirectory()) {
|
|
1641
1656
|
const mdFiles = execSync3(`ls -1 "${knowledgesDir}"`, { encoding: "utf-8" }).split(`
|
|
1642
1657
|
`).filter((l) => l.trim().endsWith(".md"));
|
|
1643
1658
|
for (const mdFile of mdFiles) {
|
|
@@ -1680,13 +1695,13 @@ async function createAgentFromFolder(folderPath) {
|
|
|
1680
1695
|
if (shouldOutputJson()) {
|
|
1681
1696
|
info(`绑定技能: ${skill.name}`);
|
|
1682
1697
|
}
|
|
1683
|
-
await client.bindSkillToAgent(agent.id, skill.id);
|
|
1698
|
+
await client.bindSkillToAgent(agent.id, skill.id, undefined);
|
|
1684
1699
|
}
|
|
1685
1700
|
for (const knowledge of uploadedKnowledges) {
|
|
1686
1701
|
if (shouldOutputJson()) {
|
|
1687
1702
|
info(`绑定知识: ${knowledge.title}`);
|
|
1688
1703
|
}
|
|
1689
|
-
await client.bindKnowledgeToAgent(agent.id, knowledge.id);
|
|
1704
|
+
await client.bindKnowledgeToAgent(agent.id, knowledge.id, undefined);
|
|
1690
1705
|
}
|
|
1691
1706
|
if (shouldOutputJson()) {
|
|
1692
1707
|
outputJson({
|
|
@@ -1711,6 +1726,244 @@ async function createAgentFromFolder(folderPath) {
|
|
|
1711
1726
|
process.exit(1);
|
|
1712
1727
|
}
|
|
1713
1728
|
}
|
|
1729
|
+
async function syncAgent(folderPath, options = {}) {
|
|
1730
|
+
const config = loadConfig();
|
|
1731
|
+
if (!config?.token) {
|
|
1732
|
+
if (shouldOutputJson()) {
|
|
1733
|
+
outputJson({ success: false, error: { code: "NOT_LOGGED_IN", message: "未登录,请先运行 arm login" } });
|
|
1734
|
+
process.exit(1);
|
|
1735
|
+
}
|
|
1736
|
+
error("未登录,请先运行 arm login");
|
|
1737
|
+
process.exit(1);
|
|
1738
|
+
}
|
|
1739
|
+
if (!existsSync4(folderPath)) {
|
|
1740
|
+
if (shouldOutputJson()) {
|
|
1741
|
+
outputJson({ success: false, error: { code: "FILE_NOT_FOUND", message: `目录不存在: ${folderPath}` } });
|
|
1742
|
+
process.exit(1);
|
|
1743
|
+
}
|
|
1744
|
+
error(`目录不存在: ${folderPath}`);
|
|
1745
|
+
process.exit(1);
|
|
1746
|
+
}
|
|
1747
|
+
const validation = validateAgentDir(folderPath);
|
|
1748
|
+
if (!validation.valid) {
|
|
1749
|
+
if (shouldOutputJson()) {
|
|
1750
|
+
outputJson({ success: false, error: { code: "VALIDATION_FAILED", message: validation.errors.join(", ") } });
|
|
1751
|
+
process.exit(1);
|
|
1752
|
+
}
|
|
1753
|
+
error(`验证失败: ${validation.errors.join(", ")}`);
|
|
1754
|
+
process.exit(1);
|
|
1755
|
+
}
|
|
1756
|
+
const metadata = validation.metadata;
|
|
1757
|
+
const agentName = metadata.name;
|
|
1758
|
+
const client = new ApiClient(config.serverUrl, config.token);
|
|
1759
|
+
if (shouldOutputJson()) {
|
|
1760
|
+
info(`正在同步 Agent: ${agentName}...`);
|
|
1761
|
+
}
|
|
1762
|
+
try {
|
|
1763
|
+
const result = await client.listAgents(agentName, 1, 1);
|
|
1764
|
+
const existingAgent = result.agents.find((a) => a.name === agentName);
|
|
1765
|
+
if (!existingAgent) {
|
|
1766
|
+
if (shouldOutputJson()) {
|
|
1767
|
+
outputJson({ success: false, error: { code: "NOT_FOUND", message: `Agent "${agentName}" 不存在,请先使用 arm agent create --from 创建` } });
|
|
1768
|
+
process.exit(1);
|
|
1769
|
+
}
|
|
1770
|
+
error(`Agent "${agentName}" 不存在,请先使用 arm agent create --from 创建`);
|
|
1771
|
+
process.exit(1);
|
|
1772
|
+
}
|
|
1773
|
+
const cloudAgent = await client.getAgent(existingAgent.id);
|
|
1774
|
+
const localSkills = await parseLocalSkills(folderPath);
|
|
1775
|
+
const localKnowledges = await parseLocalKnowledges(folderPath);
|
|
1776
|
+
const cloudSkillBindings = cloudAgent.skills || [];
|
|
1777
|
+
const cloudKnowledgeBindings = cloudAgent.knowledges || [];
|
|
1778
|
+
const changes = {
|
|
1779
|
+
metadataChanged: false,
|
|
1780
|
+
newSkills: [],
|
|
1781
|
+
removedSkills: [],
|
|
1782
|
+
newKnowledges: [],
|
|
1783
|
+
removedKnowledges: []
|
|
1784
|
+
};
|
|
1785
|
+
if (cloudAgent.prompt !== metadata.prompt || cloudAgent.description !== metadata.description) {
|
|
1786
|
+
changes.metadataChanged = true;
|
|
1787
|
+
}
|
|
1788
|
+
const cloudSkillNames = new Set(cloudSkillBindings.map((s) => s.skill?.name).filter(Boolean));
|
|
1789
|
+
for (const localSkill of localSkills) {
|
|
1790
|
+
const existingBinding = cloudSkillBindings.find((sb) => sb.skill?.name === localSkill.name && sb.version === localSkill.version);
|
|
1791
|
+
if (!existingBinding) {
|
|
1792
|
+
changes.newSkills.push(localSkill);
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
const localSkillNames = new Set(localSkills.map((s) => s.name));
|
|
1796
|
+
for (const cloudBinding of cloudSkillBindings) {
|
|
1797
|
+
if (cloudBinding.skill?.name && !localSkillNames.has(cloudBinding.skill.name)) {
|
|
1798
|
+
changes.removedSkills.push(cloudBinding.skill.name);
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
const cloudKnowledgeNames = new Set(cloudKnowledgeBindings.map((k) => k.knowledge?.name).filter(Boolean));
|
|
1802
|
+
for (const localKnowledge of localKnowledges) {
|
|
1803
|
+
const existingBinding = cloudKnowledgeBindings.find((kb) => kb.knowledge?.name === localKnowledge.name && kb.version === localKnowledge.version);
|
|
1804
|
+
if (!existingBinding) {
|
|
1805
|
+
changes.newKnowledges.push(localKnowledge);
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
const localKnowledgeNames = new Set(localKnowledges.map((k) => k.name));
|
|
1809
|
+
for (const cloudBinding of cloudKnowledgeBindings) {
|
|
1810
|
+
if (cloudBinding.knowledge?.name && !localKnowledgeNames.has(cloudBinding.knowledge.name)) {
|
|
1811
|
+
changes.removedKnowledges.push(cloudBinding.knowledge.name);
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
if (options.dryRun) {
|
|
1815
|
+
if (shouldOutputJson()) {
|
|
1816
|
+
outputJson({
|
|
1817
|
+
success: true,
|
|
1818
|
+
data: {
|
|
1819
|
+
agentName,
|
|
1820
|
+
agentId: existingAgent.id,
|
|
1821
|
+
changes,
|
|
1822
|
+
dryRun: true
|
|
1823
|
+
}
|
|
1824
|
+
});
|
|
1825
|
+
return;
|
|
1826
|
+
}
|
|
1827
|
+
console.log(`
|
|
1828
|
+
[DRY-RUN] 预览 ${agentName} 的变更:
|
|
1829
|
+
`);
|
|
1830
|
+
if (changes.metadataChanged) {
|
|
1831
|
+
console.log(" 元信息: 将更新 (prompt/description 变更)");
|
|
1832
|
+
}
|
|
1833
|
+
if (changes.newSkills.length > 0) {
|
|
1834
|
+
console.log(` 新增 Skills: ${changes.newSkills.map((s) => `${s.name}@${s.version}`).join(", ")}`);
|
|
1835
|
+
}
|
|
1836
|
+
if (changes.removedSkills.length > 0) {
|
|
1837
|
+
console.log(` 移除 Skills: ${changes.removedSkills.join(", ")}`);
|
|
1838
|
+
}
|
|
1839
|
+
if (changes.newKnowledges.length > 0) {
|
|
1840
|
+
console.log(` 新增 Knowledges: ${changes.newKnowledges.map((k) => `${k.name}@${k.version}`).join(", ")}`);
|
|
1841
|
+
}
|
|
1842
|
+
if (changes.removedKnowledges.length > 0) {
|
|
1843
|
+
console.log(` 移除 Knowledges: ${changes.removedKnowledges.join(", ")}`);
|
|
1844
|
+
}
|
|
1845
|
+
if (!changes.metadataChanged && changes.newSkills.length === 0 && changes.removedSkills.length === 0 && changes.newKnowledges.length === 0 && changes.removedKnowledges.length === 0) {
|
|
1846
|
+
console.log(" 无变更");
|
|
1847
|
+
}
|
|
1848
|
+
return;
|
|
1849
|
+
}
|
|
1850
|
+
if (changes.metadataChanged) {
|
|
1851
|
+
if (shouldOutputJson()) {
|
|
1852
|
+
info("更新 Agent 元信息...");
|
|
1853
|
+
}
|
|
1854
|
+
await client.updateAgent(existingAgent.id, {
|
|
1855
|
+
prompt: metadata.prompt,
|
|
1856
|
+
description: metadata.description
|
|
1857
|
+
});
|
|
1858
|
+
}
|
|
1859
|
+
for (const skill of changes.newSkills) {
|
|
1860
|
+
if (shouldOutputJson()) {
|
|
1861
|
+
info(`上传并绑定新技能: ${skill.name} (版本自动分配)`);
|
|
1862
|
+
}
|
|
1863
|
+
const skillTempDir = mkdtempSync3("/tmp/skill-sync-");
|
|
1864
|
+
const zipPath = join4(skillTempDir, `${skill.name}.zip`);
|
|
1865
|
+
execSync3(`cd "${skill.path}" && zip -r "${zipPath}" . -x ".*"`, { stdio: "pipe" });
|
|
1866
|
+
try {
|
|
1867
|
+
const uploadedSkill = await client.uploadSkill(zipPath);
|
|
1868
|
+
await client.bindSkillToAgent(existingAgent.id, uploadedSkill.id, undefined);
|
|
1869
|
+
} finally {
|
|
1870
|
+
rmSync3(skillTempDir, { recursive: true, force: true });
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
for (const skillName of changes.removedSkills) {
|
|
1874
|
+
const binding = cloudSkillBindings.find((sb) => sb.skill?.name === skillName);
|
|
1875
|
+
if (binding) {
|
|
1876
|
+
if (shouldOutputJson()) {
|
|
1877
|
+
info(`解绑技能: ${skillName}`);
|
|
1878
|
+
}
|
|
1879
|
+
await client.unbindSkillFromAgent(existingAgent.id, binding.skillId, binding.version);
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
for (const knowledge of changes.newKnowledges) {
|
|
1883
|
+
if (shouldOutputJson()) {
|
|
1884
|
+
info(`上传并绑定新知识: ${knowledge.name} (版本自动分配)`);
|
|
1885
|
+
}
|
|
1886
|
+
const uploadedKnowledge = await client.uploadKnowledge(knowledge.path);
|
|
1887
|
+
await client.bindKnowledgeToAgent(existingAgent.id, uploadedKnowledge.id, undefined);
|
|
1888
|
+
}
|
|
1889
|
+
for (const knowledgeName of changes.removedKnowledges) {
|
|
1890
|
+
const binding = cloudKnowledgeBindings.find((kb) => kb.knowledge?.name === knowledgeName);
|
|
1891
|
+
if (binding) {
|
|
1892
|
+
if (shouldOutputJson()) {
|
|
1893
|
+
info(`解绑知识: ${knowledgeName}`);
|
|
1894
|
+
}
|
|
1895
|
+
await client.unbindKnowledgeFromAgent(existingAgent.id, binding.knowledgeId, binding.version);
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
if (shouldOutputJson()) {
|
|
1899
|
+
outputJson({
|
|
1900
|
+
success: true,
|
|
1901
|
+
data: {
|
|
1902
|
+
agentName,
|
|
1903
|
+
agentId: existingAgent.id,
|
|
1904
|
+
changes
|
|
1905
|
+
}
|
|
1906
|
+
});
|
|
1907
|
+
return;
|
|
1908
|
+
}
|
|
1909
|
+
success(`Agent "${agentName}" 同步完成`);
|
|
1910
|
+
const changeCount = (changes.metadataChanged ? 1 : 0) + changes.newSkills.length + changes.removedSkills.length + changes.newKnowledges.length + changes.removedKnowledges.length;
|
|
1911
|
+
if (changeCount === 0) {
|
|
1912
|
+
info("无变更");
|
|
1913
|
+
} else {
|
|
1914
|
+
console.log(` 更新了 ${changeCount} 项`);
|
|
1915
|
+
}
|
|
1916
|
+
} catch (err) {
|
|
1917
|
+
if (shouldOutputJson()) {
|
|
1918
|
+
outputJson({ success: false, error: { code: "SYNC_FAILED", message: err instanceof Error ? err.message : "未知错误" } });
|
|
1919
|
+
process.exit(1);
|
|
1920
|
+
}
|
|
1921
|
+
error(`同步失败: ${err instanceof Error ? err.message : "未知错误"}`);
|
|
1922
|
+
process.exit(1);
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
async function parseLocalSkills(folderPath) {
|
|
1926
|
+
const skills = [];
|
|
1927
|
+
const skillsDir = join4(folderPath, "skills");
|
|
1928
|
+
if (!existsSync4(skillsDir) || !statSync3(skillsDir).isDirectory()) {
|
|
1929
|
+
return skills;
|
|
1930
|
+
}
|
|
1931
|
+
try {
|
|
1932
|
+
const skillDirs = execSync3(`ls -1 "${skillsDir}"`, { encoding: "utf-8" }).split(`
|
|
1933
|
+
`).filter((l) => l.trim() && existsSync4(join4(skillsDir, l)) && statSync3(join4(skillsDir, l)).isDirectory());
|
|
1934
|
+
for (const skillDir of skillDirs) {
|
|
1935
|
+
const skillPath = join4(skillsDir, skillDir);
|
|
1936
|
+
const skillMdPath = join4(skillPath, "SKILL.md");
|
|
1937
|
+
if (existsSync4(skillMdPath)) {
|
|
1938
|
+
const content = readFileSync3(skillMdPath, "utf-8");
|
|
1939
|
+
const versionMatch = content.match(/^---\n[\s\S]*?version:\s*(.+)\n/);
|
|
1940
|
+
const version = versionMatch ? versionMatch[1].trim() : "1.0.0";
|
|
1941
|
+
skills.push({ name: skillDir, path: skillPath, version });
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
} catch {}
|
|
1945
|
+
return skills;
|
|
1946
|
+
}
|
|
1947
|
+
async function parseLocalKnowledges(folderPath) {
|
|
1948
|
+
const knowledges = [];
|
|
1949
|
+
const knowledgesDir = join4(folderPath, "knowledges");
|
|
1950
|
+
if (!existsSync4(knowledgesDir) || !statSync3(knowledgesDir).isDirectory()) {
|
|
1951
|
+
return knowledges;
|
|
1952
|
+
}
|
|
1953
|
+
try {
|
|
1954
|
+
const mdFiles = execSync3(`ls -1 "${knowledgesDir}"`, { encoding: "utf-8" }).split(`
|
|
1955
|
+
`).filter((l) => l.trim().endsWith(".md"));
|
|
1956
|
+
for (const mdFile of mdFiles) {
|
|
1957
|
+
const mdPath = join4(knowledgesDir, mdFile);
|
|
1958
|
+
const name = mdFile.replace(".md", "");
|
|
1959
|
+
const content = readFileSync3(mdPath, "utf-8");
|
|
1960
|
+
const versionMatch = content.match(/^---\n[\s\S]*?version:\s*(.+)\n/);
|
|
1961
|
+
const version = versionMatch ? versionMatch[1].trim() : "1.0.0";
|
|
1962
|
+
knowledges.push({ name, path: mdPath, version });
|
|
1963
|
+
}
|
|
1964
|
+
} catch {}
|
|
1965
|
+
return knowledges;
|
|
1966
|
+
}
|
|
1714
1967
|
|
|
1715
1968
|
// src/cmd/knowledge.ts
|
|
1716
1969
|
import { writeFileSync as writeFileSync4, existsSync as existsSync5, statSync as statSync4 } from "fs";
|
|
@@ -2272,6 +2525,7 @@ async function main() {
|
|
|
2272
2525
|
case "bind":
|
|
2273
2526
|
if (!args[2]) {
|
|
2274
2527
|
console.error("用法: arm agent bind <id> --skill=<skillId> [--skill-config='{...}'] 或 arm agent bind <id> --knowledge=<knowledgeId> [--knowledge-config='{...}'] [--json]");
|
|
2528
|
+
console.error("注意: version 缺省时自动绑定新版本");
|
|
2275
2529
|
process.exit(1);
|
|
2276
2530
|
}
|
|
2277
2531
|
{
|
|
@@ -2293,9 +2547,9 @@ async function main() {
|
|
|
2293
2547
|
}
|
|
2294
2548
|
}
|
|
2295
2549
|
if (skillId) {
|
|
2296
|
-
await bindSkill(id, skillId, skillConfig);
|
|
2550
|
+
await bindSkill(id, skillId, undefined, skillConfig);
|
|
2297
2551
|
} else if (knowledgeId) {
|
|
2298
|
-
await bindKnowledge(id, knowledgeId, knowledgeConfig);
|
|
2552
|
+
await bindKnowledge(id, knowledgeId, undefined, knowledgeConfig);
|
|
2299
2553
|
} else {
|
|
2300
2554
|
console.error("用法: arm agent bind <id> --skill=<skillId> 或 --knowledge=<knowledgeId>");
|
|
2301
2555
|
process.exit(1);
|
|
@@ -2304,31 +2558,67 @@ async function main() {
|
|
|
2304
2558
|
break;
|
|
2305
2559
|
case "unbind":
|
|
2306
2560
|
if (!args[2]) {
|
|
2307
|
-
console.error("用法: arm agent unbind <id> --skill=<skillId> 或 --knowledge=<knowledgeId> [--json]");
|
|
2561
|
+
console.error("用法: arm agent unbind <id> --skill=<skillId> [--version=<ver>] 或 --knowledge=<knowledgeId> [--version=<ver>] [--json]");
|
|
2308
2562
|
process.exit(1);
|
|
2309
2563
|
}
|
|
2310
2564
|
{
|
|
2311
2565
|
const id = args[2];
|
|
2312
2566
|
let skillId;
|
|
2313
2567
|
let knowledgeId;
|
|
2568
|
+
let skillVersion;
|
|
2569
|
+
let knowledgeVersion;
|
|
2314
2570
|
for (let i = 3;i < args.length; i++) {
|
|
2315
2571
|
const arg = args[i];
|
|
2316
2572
|
if (arg.startsWith("--skill=")) {
|
|
2317
2573
|
skillId = arg.split("=").slice(1).join("=");
|
|
2318
2574
|
} else if (arg.startsWith("--knowledge=")) {
|
|
2319
2575
|
knowledgeId = arg.split("=").slice(1).join("=");
|
|
2576
|
+
} else if (arg.startsWith("--version=")) {
|
|
2577
|
+
const ver = arg.split("=").slice(1).join("=");
|
|
2578
|
+
if (skillId)
|
|
2579
|
+
skillVersion = ver;
|
|
2580
|
+
if (knowledgeId)
|
|
2581
|
+
knowledgeVersion = ver;
|
|
2320
2582
|
}
|
|
2321
2583
|
}
|
|
2322
2584
|
if (skillId) {
|
|
2323
|
-
await unbindSkill(id, skillId);
|
|
2585
|
+
await unbindSkill(id, skillId, skillVersion);
|
|
2324
2586
|
} else if (knowledgeId) {
|
|
2325
|
-
await unbindKnowledge(id, knowledgeId);
|
|
2587
|
+
await unbindKnowledge(id, knowledgeId, knowledgeVersion);
|
|
2326
2588
|
} else {
|
|
2327
2589
|
console.error("用法: arm agent unbind <id> --skill=<skillId> 或 --knowledge=<knowledgeId>");
|
|
2328
2590
|
process.exit(1);
|
|
2329
2591
|
}
|
|
2330
2592
|
}
|
|
2331
2593
|
break;
|
|
2594
|
+
case "sync":
|
|
2595
|
+
if (!args[2]) {
|
|
2596
|
+
console.error("用法: arm agent sync <folder> [--dry-run] [--force] [--json]");
|
|
2597
|
+
process.exit(1);
|
|
2598
|
+
}
|
|
2599
|
+
{
|
|
2600
|
+
const folder = args[2];
|
|
2601
|
+
const syncOptions = {};
|
|
2602
|
+
for (let i = 3;i < args.length; i++) {
|
|
2603
|
+
const arg = args[i];
|
|
2604
|
+
if (arg === "--dry-run") {
|
|
2605
|
+
syncOptions.dryRun = true;
|
|
2606
|
+
} else if (arg === "--force") {
|
|
2607
|
+
syncOptions.force = true;
|
|
2608
|
+
}
|
|
2609
|
+
}
|
|
2610
|
+
await syncAgent(folder, syncOptions);
|
|
2611
|
+
}
|
|
2612
|
+
break;
|
|
2613
|
+
case "bindings":
|
|
2614
|
+
if (!args[2]) {
|
|
2615
|
+
console.error("用法: arm agent bindings <name> [--history] [--json]");
|
|
2616
|
+
process.exit(1);
|
|
2617
|
+
}
|
|
2618
|
+
if (subCommand === "history") {
|
|
2619
|
+
console.log("bindings history 需要通过 info 命令查看");
|
|
2620
|
+
}
|
|
2621
|
+
break;
|
|
2332
2622
|
default:
|
|
2333
2623
|
console.log(`
|
|
2334
2624
|
可用命令:
|
|
@@ -2340,10 +2630,11 @@ async function main() {
|
|
|
2340
2630
|
arm agent create --from=<folder> 从本地文件夹创建 Agent
|
|
2341
2631
|
arm agent update <id> 更新 Agent (--name, --description, --prompt, --avatar, --status)
|
|
2342
2632
|
arm agent delete <id> 删除 Agent
|
|
2343
|
-
arm agent bind <id> --skill=<id>
|
|
2344
|
-
arm agent unbind <id> --skill=<id> 解绑 Skill
|
|
2345
|
-
arm agent bind <id> --knowledge=<id> 绑定 Knowledge 到 Agent
|
|
2346
|
-
arm agent unbind <id> --knowledge=<id> 解绑 Knowledge
|
|
2633
|
+
arm agent bind <id> --skill=<id> [--version=<ver>] 绑定 Skill 到 Agent
|
|
2634
|
+
arm agent unbind <id> --skill=<id> [--version=<ver>] 解绑 Skill
|
|
2635
|
+
arm agent bind <id> --knowledge=<id> [--version=<ver>] 绑定 Knowledge 到 Agent
|
|
2636
|
+
arm agent unbind <id> --knowledge=<id> [--version=<ver>] 解绑 Knowledge
|
|
2637
|
+
arm agent sync <folder> [--dry-run] 同步本地文件夹到云端 Agent
|
|
2347
2638
|
所有命令支持 --json 参数获取机器可读输出
|
|
2348
2639
|
`);
|
|
2349
2640
|
}
|
|
@@ -2377,12 +2668,14 @@ Agent Resource Management (arm)
|
|
|
2377
2668
|
arm agent info <name> 查看 Agent 详情
|
|
2378
2669
|
arm agent download <name> [dir] 下载 Agent
|
|
2379
2670
|
arm agent create <name> 创建 Agent
|
|
2671
|
+
arm agent create --from=<folder> 从本地文件夹创建 Agent
|
|
2380
2672
|
arm agent update <id> 更新 Agent
|
|
2381
2673
|
arm agent delete <id> 删除 Agent
|
|
2382
|
-
arm agent bind <id> --skill=<id>
|
|
2383
|
-
arm agent unbind <id> --skill=<id> 解绑 Skill
|
|
2384
|
-
arm agent bind <id> --knowledge=<id> 绑定 Knowledge
|
|
2385
|
-
arm agent unbind <id> --knowledge=<id> 解绑 Knowledge
|
|
2674
|
+
arm agent bind <id> --skill=<id> [--version=<ver>] 绑定 Skill
|
|
2675
|
+
arm agent unbind <id> --skill=<id> [--version=<ver>] 解绑 Skill
|
|
2676
|
+
arm agent bind <id> --knowledge=<id> [--version=<ver>] 绑定 Knowledge
|
|
2677
|
+
arm agent unbind <id> --knowledge=<id> [--version=<ver>] 解绑 Knowledge
|
|
2678
|
+
arm agent sync <folder> [--dry-run] 同步本地文件夹到云端 Agent
|
|
2386
2679
|
arm server 显示当前服务端
|
|
2387
2680
|
arm server set <url> 设置服务端
|
|
2388
2681
|
使用 arm <entity> -h 查看详细帮助
|
package/package.json
CHANGED
package/src/cmd/agent.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { loadConfig } from '../lib/storage';
|
|
|
3
3
|
import { formatAgent, formatAgentDetail, success, error, info } from '../lib/formatter';
|
|
4
4
|
import { shouldOutputJson, outputJson } from '../lib/output';
|
|
5
5
|
import { validateAgentDir } from '../lib/validate';
|
|
6
|
-
import { writeFileSync, existsSync, readFileSync } from 'fs';
|
|
6
|
+
import { writeFileSync, existsSync, readFileSync, statSync } from 'fs';
|
|
7
7
|
import { join, basename } from 'path';
|
|
8
8
|
import { execSync } from 'child_process';
|
|
9
9
|
import { mkdtempSync, rmSync } from 'fs';
|
|
@@ -156,7 +156,7 @@ export async function deleteAgent(id: string): Promise<void> {
|
|
|
156
156
|
}
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
-
export async function bindSkill(id: string, skillId: string, config?: string): Promise<void> {
|
|
159
|
+
export async function bindSkill(id: string, skillId: string, version: string = '1.0.0', config?: string): Promise<void> {
|
|
160
160
|
const configStore = loadConfig();
|
|
161
161
|
if (!configStore?.token) {
|
|
162
162
|
if (shouldOutputJson()) {
|
|
@@ -170,13 +170,13 @@ export async function bindSkill(id: string, skillId: string, config?: string): P
|
|
|
170
170
|
const client = new ApiClient(configStore.serverUrl, configStore.token);
|
|
171
171
|
try {
|
|
172
172
|
const parsedConfig = config ? JSON.parse(config) : undefined;
|
|
173
|
-
await client.bindSkillToAgent(id, skillId, parsedConfig);
|
|
173
|
+
await client.bindSkillToAgent(id, skillId, version, parsedConfig);
|
|
174
174
|
|
|
175
175
|
if (shouldOutputJson()) {
|
|
176
|
-
outputJson({ success: true, data: { agentId: id, skillId, config: parsedConfig } });
|
|
176
|
+
outputJson({ success: true, data: { agentId: id, skillId, version, config: parsedConfig } });
|
|
177
177
|
return;
|
|
178
178
|
}
|
|
179
|
-
success(`Skill "${skillId}" 已绑定到 Agent "${id}"`);
|
|
179
|
+
success(`Skill "${skillId}@${version}" 已绑定到 Agent "${id}"`);
|
|
180
180
|
} catch (err) {
|
|
181
181
|
if (shouldOutputJson()) {
|
|
182
182
|
outputJson({ success: false, error: { code: 'BIND_FAILED', message: err instanceof Error ? err.message : '未知错误' } });
|
|
@@ -187,7 +187,7 @@ export async function bindSkill(id: string, skillId: string, config?: string): P
|
|
|
187
187
|
}
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
-
export async function unbindSkill(id: string, skillId: string): Promise<void> {
|
|
190
|
+
export async function unbindSkill(id: string, skillId: string, version?: string): Promise<void> {
|
|
191
191
|
const config = loadConfig();
|
|
192
192
|
if (!config?.token) {
|
|
193
193
|
if (shouldOutputJson()) {
|
|
@@ -200,13 +200,13 @@ export async function unbindSkill(id: string, skillId: string): Promise<void> {
|
|
|
200
200
|
|
|
201
201
|
const client = new ApiClient(config.serverUrl, config.token);
|
|
202
202
|
try {
|
|
203
|
-
await client.unbindSkillFromAgent(id, skillId);
|
|
203
|
+
await client.unbindSkillFromAgent(id, skillId, version);
|
|
204
204
|
|
|
205
205
|
if (shouldOutputJson()) {
|
|
206
|
-
outputJson({ success: true, data: { agentId: id, skillId } });
|
|
206
|
+
outputJson({ success: true, data: { agentId: id, skillId, version } });
|
|
207
207
|
return;
|
|
208
208
|
}
|
|
209
|
-
success(`Skill "${skillId}" 已从 Agent "${id}" 解绑`);
|
|
209
|
+
success(`Skill "${skillId}${version ? '@' + version : ''}" 已从 Agent "${id}" 解绑`);
|
|
210
210
|
} catch (err) {
|
|
211
211
|
if (shouldOutputJson()) {
|
|
212
212
|
outputJson({ success: false, error: { code: 'UNBIND_FAILED', message: err instanceof Error ? err.message : '未知错误' } });
|
|
@@ -217,7 +217,7 @@ export async function unbindSkill(id: string, skillId: string): Promise<void> {
|
|
|
217
217
|
}
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
-
export async function bindKnowledge(id: string, knowledgeId: string, retrievalConfig?: string): Promise<void> {
|
|
220
|
+
export async function bindKnowledge(id: string, knowledgeId: string, version: string = '1.0.0', retrievalConfig?: string): Promise<void> {
|
|
221
221
|
const configStore = loadConfig();
|
|
222
222
|
if (!configStore?.token) {
|
|
223
223
|
if (shouldOutputJson()) {
|
|
@@ -231,13 +231,13 @@ export async function bindKnowledge(id: string, knowledgeId: string, retrievalCo
|
|
|
231
231
|
const client = new ApiClient(configStore.serverUrl, configStore.token);
|
|
232
232
|
try {
|
|
233
233
|
const parsedConfig = retrievalConfig ? JSON.parse(retrievalConfig) : undefined;
|
|
234
|
-
await client.bindKnowledgeToAgent(id, knowledgeId, parsedConfig);
|
|
234
|
+
await client.bindKnowledgeToAgent(id, knowledgeId, version, parsedConfig);
|
|
235
235
|
|
|
236
236
|
if (shouldOutputJson()) {
|
|
237
|
-
outputJson({ success: true, data: { agentId: id, knowledgeId, retrievalConfig: parsedConfig } });
|
|
237
|
+
outputJson({ success: true, data: { agentId: id, knowledgeId, version, retrievalConfig: parsedConfig } });
|
|
238
238
|
return;
|
|
239
239
|
}
|
|
240
|
-
success(`Knowledge "${knowledgeId}" 已绑定到 Agent "${id}"`);
|
|
240
|
+
success(`Knowledge "${knowledgeId}@${version}" 已绑定到 Agent "${id}"`);
|
|
241
241
|
} catch (err) {
|
|
242
242
|
if (shouldOutputJson()) {
|
|
243
243
|
outputJson({ success: false, error: { code: 'BIND_FAILED', message: err instanceof Error ? err.message : '未知错误' } });
|
|
@@ -248,7 +248,7 @@ export async function bindKnowledge(id: string, knowledgeId: string, retrievalCo
|
|
|
248
248
|
}
|
|
249
249
|
}
|
|
250
250
|
|
|
251
|
-
export async function unbindKnowledge(id: string, knowledgeId: string): Promise<void> {
|
|
251
|
+
export async function unbindKnowledge(id: string, knowledgeId: string, version?: string): Promise<void> {
|
|
252
252
|
const config = loadConfig();
|
|
253
253
|
if (!config?.token) {
|
|
254
254
|
if (shouldOutputJson()) {
|
|
@@ -261,13 +261,13 @@ export async function unbindKnowledge(id: string, knowledgeId: string): Promise<
|
|
|
261
261
|
|
|
262
262
|
const client = new ApiClient(config.serverUrl, config.token);
|
|
263
263
|
try {
|
|
264
|
-
await client.unbindKnowledgeFromAgent(id, knowledgeId);
|
|
264
|
+
await client.unbindKnowledgeFromAgent(id, knowledgeId, version);
|
|
265
265
|
|
|
266
266
|
if (shouldOutputJson()) {
|
|
267
|
-
outputJson({ success: true, data: { agentId: id, knowledgeId } });
|
|
267
|
+
outputJson({ success: true, data: { agentId: id, knowledgeId, version } });
|
|
268
268
|
return;
|
|
269
269
|
}
|
|
270
|
-
success(`Knowledge "${knowledgeId}" 已从 Agent "${id}" 解绑`);
|
|
270
|
+
success(`Knowledge "${knowledgeId}${version ? '@' + version : ''}" 已从 Agent "${id}" 解绑`);
|
|
271
271
|
} catch (err) {
|
|
272
272
|
if (shouldOutputJson()) {
|
|
273
273
|
outputJson({ success: false, error: { code: 'UNBIND_FAILED', message: err instanceof Error ? err.message : '未知错误' } });
|
|
@@ -592,14 +592,14 @@ export async function createAgentFromFolder(folderPath: string): Promise<void> {
|
|
|
592
592
|
if (shouldOutputJson()) {
|
|
593
593
|
info(`绑定技能: ${skill.name}`);
|
|
594
594
|
}
|
|
595
|
-
await client.bindSkillToAgent(agent.id, skill.id);
|
|
595
|
+
await client.bindSkillToAgent(agent.id, skill.id, undefined);
|
|
596
596
|
}
|
|
597
597
|
|
|
598
598
|
for (const knowledge of uploadedKnowledges) {
|
|
599
599
|
if (shouldOutputJson()) {
|
|
600
600
|
info(`绑定知识: ${knowledge.title}`);
|
|
601
601
|
}
|
|
602
|
-
await client.bindKnowledgeToAgent(agent.id, knowledge.id);
|
|
602
|
+
await client.bindKnowledgeToAgent(agent.id, knowledge.id, undefined);
|
|
603
603
|
}
|
|
604
604
|
|
|
605
605
|
if (shouldOutputJson()) {
|
|
@@ -624,4 +624,305 @@ export async function createAgentFromFolder(folderPath: string): Promise<void> {
|
|
|
624
624
|
error(`创建失败: ${err instanceof Error ? err.message : '未知错误'}`);
|
|
625
625
|
process.exit(1);
|
|
626
626
|
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
export interface SyncOptions {
|
|
630
|
+
dryRun?: boolean;
|
|
631
|
+
force?: boolean;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
interface LocalSkill {
|
|
635
|
+
name: string;
|
|
636
|
+
path: string;
|
|
637
|
+
version: string;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
interface LocalKnowledge {
|
|
641
|
+
name: string;
|
|
642
|
+
path: string;
|
|
643
|
+
version: string;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
export async function syncAgent(folderPath: string, options: SyncOptions = {}): Promise<void> {
|
|
647
|
+
const config = loadConfig();
|
|
648
|
+
if (!config?.token) {
|
|
649
|
+
if (shouldOutputJson()) {
|
|
650
|
+
outputJson({ success: false, error: { code: 'NOT_LOGGED_IN', message: '未登录,请先运行 arm login' } });
|
|
651
|
+
process.exit(1);
|
|
652
|
+
}
|
|
653
|
+
error('未登录,请先运行 arm login');
|
|
654
|
+
process.exit(1);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
if (!existsSync(folderPath)) {
|
|
658
|
+
if (shouldOutputJson()) {
|
|
659
|
+
outputJson({ success: false, error: { code: 'FILE_NOT_FOUND', message: `目录不存在: ${folderPath}` } });
|
|
660
|
+
process.exit(1);
|
|
661
|
+
}
|
|
662
|
+
error(`目录不存在: ${folderPath}`);
|
|
663
|
+
process.exit(1);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
const validation = validateAgentDir(folderPath);
|
|
667
|
+
if (!validation.valid) {
|
|
668
|
+
if (shouldOutputJson()) {
|
|
669
|
+
outputJson({ success: false, error: { code: 'VALIDATION_FAILED', message: validation.errors.join(', ') } });
|
|
670
|
+
process.exit(1);
|
|
671
|
+
}
|
|
672
|
+
error(`验证失败: ${validation.errors.join(', ')}`);
|
|
673
|
+
process.exit(1);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
const metadata = validation.metadata!;
|
|
677
|
+
const agentName = metadata.name!;
|
|
678
|
+
const client = new ApiClient(config.serverUrl, config.token);
|
|
679
|
+
|
|
680
|
+
if (shouldOutputJson()) {
|
|
681
|
+
info(`正在同步 Agent: ${agentName}...`);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
try {
|
|
685
|
+
const result = await client.listAgents(agentName, 1, 1);
|
|
686
|
+
const existingAgent = result.agents.find(a => a.name === agentName);
|
|
687
|
+
|
|
688
|
+
if (!existingAgent) {
|
|
689
|
+
if (shouldOutputJson()) {
|
|
690
|
+
outputJson({ success: false, error: { code: 'NOT_FOUND', message: `Agent "${agentName}" 不存在,请先使用 arm agent create --from 创建` } });
|
|
691
|
+
process.exit(1);
|
|
692
|
+
}
|
|
693
|
+
error(`Agent "${agentName}" 不存在,请先使用 arm agent create --from 创建`);
|
|
694
|
+
process.exit(1);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
const cloudAgent = await client.getAgent(existingAgent.id);
|
|
698
|
+
|
|
699
|
+
const localSkills = await parseLocalSkills(folderPath);
|
|
700
|
+
const localKnowledges = await parseLocalKnowledges(folderPath);
|
|
701
|
+
|
|
702
|
+
const cloudSkillBindings = cloudAgent.skills || [];
|
|
703
|
+
const cloudKnowledgeBindings = cloudAgent.knowledges || [];
|
|
704
|
+
|
|
705
|
+
const changes: {
|
|
706
|
+
metadataChanged: boolean;
|
|
707
|
+
newSkills: LocalSkill[];
|
|
708
|
+
removedSkills: string[];
|
|
709
|
+
newKnowledges: LocalKnowledge[];
|
|
710
|
+
removedKnowledges: string[];
|
|
711
|
+
} = {
|
|
712
|
+
metadataChanged: false,
|
|
713
|
+
newSkills: [],
|
|
714
|
+
removedSkills: [],
|
|
715
|
+
newKnowledges: [],
|
|
716
|
+
removedKnowledges: [],
|
|
717
|
+
};
|
|
718
|
+
|
|
719
|
+
if (cloudAgent.prompt !== metadata.prompt || cloudAgent.description !== metadata.description) {
|
|
720
|
+
changes.metadataChanged = true;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
const cloudSkillNames = new Set(cloudSkillBindings.map(s => s.skill?.name).filter(Boolean));
|
|
724
|
+
for (const localSkill of localSkills) {
|
|
725
|
+
const existingBinding = cloudSkillBindings.find(
|
|
726
|
+
sb => sb.skill?.name === localSkill.name && sb.version === localSkill.version
|
|
727
|
+
);
|
|
728
|
+
if (!existingBinding) {
|
|
729
|
+
changes.newSkills.push(localSkill);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
const localSkillNames = new Set(localSkills.map(s => s.name));
|
|
733
|
+
for (const cloudBinding of cloudSkillBindings) {
|
|
734
|
+
if (cloudBinding.skill?.name && !localSkillNames.has(cloudBinding.skill.name)) {
|
|
735
|
+
changes.removedSkills.push(cloudBinding.skill.name);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
const cloudKnowledgeNames = new Set(cloudKnowledgeBindings.map(k => k.knowledge?.name).filter(Boolean));
|
|
740
|
+
for (const localKnowledge of localKnowledges) {
|
|
741
|
+
const existingBinding = cloudKnowledgeBindings.find(
|
|
742
|
+
kb => kb.knowledge?.name === localKnowledge.name && kb.version === localKnowledge.version
|
|
743
|
+
);
|
|
744
|
+
if (!existingBinding) {
|
|
745
|
+
changes.newKnowledges.push(localKnowledge);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
const localKnowledgeNames = new Set(localKnowledges.map(k => k.name));
|
|
749
|
+
for (const cloudBinding of cloudKnowledgeBindings) {
|
|
750
|
+
if (cloudBinding.knowledge?.name && !localKnowledgeNames.has(cloudBinding.knowledge.name)) {
|
|
751
|
+
changes.removedKnowledges.push(cloudBinding.knowledge.name);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
if (options.dryRun) {
|
|
756
|
+
if (shouldOutputJson()) {
|
|
757
|
+
outputJson({
|
|
758
|
+
success: true,
|
|
759
|
+
data: {
|
|
760
|
+
agentName,
|
|
761
|
+
agentId: existingAgent.id,
|
|
762
|
+
changes,
|
|
763
|
+
dryRun: true,
|
|
764
|
+
},
|
|
765
|
+
});
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
console.log(`\n[DRY-RUN] 预览 ${agentName} 的变更:\n`);
|
|
769
|
+
if (changes.metadataChanged) {
|
|
770
|
+
console.log(' 元信息: 将更新 (prompt/description 变更)');
|
|
771
|
+
}
|
|
772
|
+
if (changes.newSkills.length > 0) {
|
|
773
|
+
console.log(` 新增 Skills: ${changes.newSkills.map(s => `${s.name}@${s.version}`).join(', ')}`);
|
|
774
|
+
}
|
|
775
|
+
if (changes.removedSkills.length > 0) {
|
|
776
|
+
console.log(` 移除 Skills: ${changes.removedSkills.join(', ')}`);
|
|
777
|
+
}
|
|
778
|
+
if (changes.newKnowledges.length > 0) {
|
|
779
|
+
console.log(` 新增 Knowledges: ${changes.newKnowledges.map(k => `${k.name}@${k.version}`).join(', ')}`);
|
|
780
|
+
}
|
|
781
|
+
if (changes.removedKnowledges.length > 0) {
|
|
782
|
+
console.log(` 移除 Knowledges: ${changes.removedKnowledges.join(', ')}`);
|
|
783
|
+
}
|
|
784
|
+
if (!changes.metadataChanged && changes.newSkills.length === 0 && changes.removedSkills.length === 0 &&
|
|
785
|
+
changes.newKnowledges.length === 0 && changes.removedKnowledges.length === 0) {
|
|
786
|
+
console.log(' 无变更');
|
|
787
|
+
}
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
if (changes.metadataChanged) {
|
|
792
|
+
if (shouldOutputJson()) {
|
|
793
|
+
info('更新 Agent 元信息...');
|
|
794
|
+
}
|
|
795
|
+
await client.updateAgent(existingAgent.id, {
|
|
796
|
+
prompt: metadata.prompt,
|
|
797
|
+
description: metadata.description,
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
for (const skill of changes.newSkills) {
|
|
802
|
+
if (shouldOutputJson()) {
|
|
803
|
+
info(`上传并绑定新技能: ${skill.name} (版本自动分配)`);
|
|
804
|
+
}
|
|
805
|
+
const skillTempDir = mkdtempSync('/tmp/skill-sync-');
|
|
806
|
+
const zipPath = join(skillTempDir, `${skill.name}.zip`);
|
|
807
|
+
execSync(`cd "${skill.path}" && zip -r "${zipPath}" . -x ".*"`, { stdio: 'pipe' });
|
|
808
|
+
try {
|
|
809
|
+
const uploadedSkill = await client.uploadSkill(zipPath);
|
|
810
|
+
await client.bindSkillToAgent(existingAgent.id, uploadedSkill.id, undefined);
|
|
811
|
+
} finally {
|
|
812
|
+
rmSync(skillTempDir, { recursive: true, force: true });
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
for (const skillName of changes.removedSkills) {
|
|
817
|
+
const binding = cloudSkillBindings.find(sb => sb.skill?.name === skillName);
|
|
818
|
+
if (binding) {
|
|
819
|
+
if (shouldOutputJson()) {
|
|
820
|
+
info(`解绑技能: ${skillName}`);
|
|
821
|
+
}
|
|
822
|
+
await client.unbindSkillFromAgent(existingAgent.id, binding.skillId, binding.version);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
for (const knowledge of changes.newKnowledges) {
|
|
827
|
+
if (shouldOutputJson()) {
|
|
828
|
+
info(`上传并绑定新知识: ${knowledge.name} (版本自动分配)`);
|
|
829
|
+
}
|
|
830
|
+
const uploadedKnowledge = await client.uploadKnowledge(knowledge.path);
|
|
831
|
+
await client.bindKnowledgeToAgent(existingAgent.id, uploadedKnowledge.id, undefined);
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
for (const knowledgeName of changes.removedKnowledges) {
|
|
835
|
+
const binding = cloudKnowledgeBindings.find(kb => kb.knowledge?.name === knowledgeName);
|
|
836
|
+
if (binding) {
|
|
837
|
+
if (shouldOutputJson()) {
|
|
838
|
+
info(`解绑知识: ${knowledgeName}`);
|
|
839
|
+
}
|
|
840
|
+
await client.unbindKnowledgeFromAgent(existingAgent.id, binding.knowledgeId, binding.version);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
if (shouldOutputJson()) {
|
|
845
|
+
outputJson({
|
|
846
|
+
success: true,
|
|
847
|
+
data: {
|
|
848
|
+
agentName,
|
|
849
|
+
agentId: existingAgent.id,
|
|
850
|
+
changes,
|
|
851
|
+
},
|
|
852
|
+
});
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
success(`Agent "${agentName}" 同步完成`);
|
|
856
|
+
const changeCount = (changes.metadataChanged ? 1 : 0) + changes.newSkills.length +
|
|
857
|
+
changes.removedSkills.length + changes.newKnowledges.length + changes.removedKnowledges.length;
|
|
858
|
+
if (changeCount === 0) {
|
|
859
|
+
info('无变更');
|
|
860
|
+
} else {
|
|
861
|
+
console.log(` 更新了 ${changeCount} 项`);
|
|
862
|
+
}
|
|
863
|
+
} catch (err) {
|
|
864
|
+
if (shouldOutputJson()) {
|
|
865
|
+
outputJson({ success: false, error: { code: 'SYNC_FAILED', message: err instanceof Error ? err.message : '未知错误' } });
|
|
866
|
+
process.exit(1);
|
|
867
|
+
}
|
|
868
|
+
error(`同步失败: ${err instanceof Error ? err.message : '未知错误'}`);
|
|
869
|
+
process.exit(1);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
async function parseLocalSkills(folderPath: string): Promise<LocalSkill[]> {
|
|
874
|
+
const skills: LocalSkill[] = [];
|
|
875
|
+
const skillsDir = join(folderPath, 'skills');
|
|
876
|
+
|
|
877
|
+
if (!existsSync(skillsDir) || !statSync(skillsDir).isDirectory()) {
|
|
878
|
+
return skills;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
try {
|
|
882
|
+
const skillDirs = execSync(`ls -1 "${skillsDir}"`, { encoding: 'utf-8' })
|
|
883
|
+
.split('\n')
|
|
884
|
+
.filter(l => l.trim() && existsSync(join(skillsDir, l)) && statSync(join(skillsDir, l)).isDirectory());
|
|
885
|
+
|
|
886
|
+
for (const skillDir of skillDirs) {
|
|
887
|
+
const skillPath = join(skillsDir, skillDir);
|
|
888
|
+
const skillMdPath = join(skillPath, 'SKILL.md');
|
|
889
|
+
|
|
890
|
+
if (existsSync(skillMdPath)) {
|
|
891
|
+
const content = readFileSync(skillMdPath, 'utf-8');
|
|
892
|
+
const versionMatch = content.match(/^---\n[\s\S]*?version:\s*(.+)\n/);
|
|
893
|
+
const version = versionMatch ? versionMatch[1].trim() : '1.0.0';
|
|
894
|
+
skills.push({ name: skillDir, path: skillPath, version });
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
} catch {
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
return skills;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
async function parseLocalKnowledges(folderPath: string): Promise<LocalKnowledge[]> {
|
|
904
|
+
const knowledges: LocalKnowledge[] = [];
|
|
905
|
+
const knowledgesDir = join(folderPath, 'knowledges');
|
|
906
|
+
|
|
907
|
+
if (!existsSync(knowledgesDir) || !statSync(knowledgesDir).isDirectory()) {
|
|
908
|
+
return knowledges;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
try {
|
|
912
|
+
const mdFiles = execSync(`ls -1 "${knowledgesDir}"`, { encoding: 'utf-8' })
|
|
913
|
+
.split('\n')
|
|
914
|
+
.filter(l => l.trim().endsWith('.md'));
|
|
915
|
+
|
|
916
|
+
for (const mdFile of mdFiles) {
|
|
917
|
+
const mdPath = join(knowledgesDir, mdFile);
|
|
918
|
+
const name = mdFile.replace('.md', '');
|
|
919
|
+
const content = readFileSync(mdPath, 'utf-8');
|
|
920
|
+
const versionMatch = content.match(/^---\n[\s\S]*?version:\s*(.+)\n/);
|
|
921
|
+
const version = versionMatch ? versionMatch[1].trim() : '1.0.0';
|
|
922
|
+
knowledges.push({ name, path: mdPath, version });
|
|
923
|
+
}
|
|
924
|
+
} catch {
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
return knowledges;
|
|
627
928
|
}
|
package/src/lib/client.ts
CHANGED
|
@@ -327,18 +327,22 @@ export class ApiClient {
|
|
|
327
327
|
}
|
|
328
328
|
}
|
|
329
329
|
|
|
330
|
-
async bindSkillToAgent(agentId: string, skillId: string, config?: Record<string, unknown>): Promise<void> {
|
|
330
|
+
async bindSkillToAgent(agentId: string, skillId: string, version: string, config?: Record<string, unknown>): Promise<void> {
|
|
331
331
|
const res = await this.request<null>(`/agents/${agentId}/skills`, {
|
|
332
332
|
method: 'POST',
|
|
333
|
-
body: JSON.stringify({ skillId, config }),
|
|
333
|
+
body: JSON.stringify({ skillId, version, config }),
|
|
334
334
|
});
|
|
335
335
|
if (!res.ok) {
|
|
336
336
|
throw new Error(res.msg);
|
|
337
337
|
}
|
|
338
338
|
}
|
|
339
339
|
|
|
340
|
-
async unbindSkillFromAgent(agentId: string, skillId: string): Promise<void> {
|
|
341
|
-
|
|
340
|
+
async unbindSkillFromAgent(agentId: string, skillId: string, version?: string): Promise<void> {
|
|
341
|
+
let path = `/agents/${agentId}/skills?skillId=${skillId}`;
|
|
342
|
+
if (version) {
|
|
343
|
+
path += `&version=${version}`;
|
|
344
|
+
}
|
|
345
|
+
const res = await this.request<null>(path, {
|
|
342
346
|
method: 'DELETE',
|
|
343
347
|
});
|
|
344
348
|
if (!res.ok) {
|
|
@@ -346,18 +350,22 @@ export class ApiClient {
|
|
|
346
350
|
}
|
|
347
351
|
}
|
|
348
352
|
|
|
349
|
-
async bindKnowledgeToAgent(agentId: string, knowledgeId: string, retrievalConfig?: { topK?: number; similarityThreshold?: number }): Promise<void> {
|
|
353
|
+
async bindKnowledgeToAgent(agentId: string, knowledgeId: string, version: string, retrievalConfig?: { topK?: number; similarityThreshold?: number }): Promise<void> {
|
|
350
354
|
const res = await this.request<null>(`/agents/${agentId}/knowledges`, {
|
|
351
355
|
method: 'POST',
|
|
352
|
-
body: JSON.stringify({ knowledgeId, retrievalConfig }),
|
|
356
|
+
body: JSON.stringify({ knowledgeId, version, retrievalConfig }),
|
|
353
357
|
});
|
|
354
358
|
if (!res.ok) {
|
|
355
359
|
throw new Error(res.msg);
|
|
356
360
|
}
|
|
357
361
|
}
|
|
358
362
|
|
|
359
|
-
async unbindKnowledgeFromAgent(agentId: string, knowledgeId: string): Promise<void> {
|
|
360
|
-
|
|
363
|
+
async unbindKnowledgeFromAgent(agentId: string, knowledgeId: string, version?: string): Promise<void> {
|
|
364
|
+
let path = `/agents/${agentId}/knowledges?knowledgeId=${knowledgeId}`;
|
|
365
|
+
if (version) {
|
|
366
|
+
path += `&version=${version}`;
|
|
367
|
+
}
|
|
368
|
+
const res = await this.request<null>(path, {
|
|
361
369
|
method: 'DELETE',
|
|
362
370
|
});
|
|
363
371
|
if (!res.ok) {
|
|
@@ -369,4 +377,31 @@ export class ApiClient {
|
|
|
369
377
|
const result = await this.listAgents(name, 1, 1);
|
|
370
378
|
return result.agents.find(a => a.name === name) || null;
|
|
371
379
|
}
|
|
380
|
+
|
|
381
|
+
async getAgentBindingsHistory(agentId: string): Promise<{
|
|
382
|
+
skillBindings: Array<{
|
|
383
|
+
id: string;
|
|
384
|
+
skillId: string;
|
|
385
|
+
skillName: string;
|
|
386
|
+
version: string;
|
|
387
|
+
config: Record<string, unknown> | null;
|
|
388
|
+
createdAt: string;
|
|
389
|
+
deletedAt: string | null;
|
|
390
|
+
}>;
|
|
391
|
+
knowledgeBindings: Array<{
|
|
392
|
+
id: string;
|
|
393
|
+
knowledgeId: string;
|
|
394
|
+
knowledgeName: string;
|
|
395
|
+
version: string;
|
|
396
|
+
retrievalConfig: Record<string, unknown> | null;
|
|
397
|
+
createdAt: string;
|
|
398
|
+
deletedAt: string | null;
|
|
399
|
+
}>;
|
|
400
|
+
}> {
|
|
401
|
+
const res = await this.request<any>(`/agents/${agentId}/bindings/history`);
|
|
402
|
+
if (!res.ok) {
|
|
403
|
+
throw new Error(res.msg);
|
|
404
|
+
}
|
|
405
|
+
return res.data;
|
|
406
|
+
}
|
|
372
407
|
}
|
package/src/main.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { login, logout, getCurrentUser, register } from './cmd/auth';
|
|
2
2
|
import { listSkills, searchSkills, infoSkill, downloadSkill, uploadSkill, mySkills, deleteSkill, validateSkill } from './cmd/skill';
|
|
3
|
-
import { listAgents, searchAgents, infoAgent, downloadAgent, createAgent, updateAgent, deleteAgent, bindSkill, unbindSkill, bindKnowledge, unbindKnowledge, createAgentFromFolder } from './cmd/agent';
|
|
3
|
+
import { listAgents, searchAgents, infoAgent, downloadAgent, createAgent, updateAgent, deleteAgent, bindSkill, unbindSkill, bindKnowledge, unbindKnowledge, createAgentFromFolder, syncAgent } from './cmd/agent';
|
|
4
4
|
import { listKnowledge, searchKnowledge, infoKnowledge, downloadKnowledge, uploadKnowledge, myKnowledge, deleteKnowledge } from './cmd/knowledge';
|
|
5
5
|
import { showServer, setServer } from './cmd/server';
|
|
6
6
|
import { getOutputMode, setOutputMode } from './lib/output';
|
|
@@ -316,6 +316,7 @@ async function main() {
|
|
|
316
316
|
case 'bind':
|
|
317
317
|
if (!args[2]) {
|
|
318
318
|
console.error('用法: arm agent bind <id> --skill=<skillId> [--skill-config=\'{...}\'] 或 arm agent bind <id> --knowledge=<knowledgeId> [--knowledge-config=\'{...}\'] [--json]');
|
|
319
|
+
console.error('注意: version 缺省时自动绑定新版本');
|
|
319
320
|
process.exit(1);
|
|
320
321
|
}
|
|
321
322
|
{
|
|
@@ -339,9 +340,9 @@ async function main() {
|
|
|
339
340
|
}
|
|
340
341
|
|
|
341
342
|
if (skillId) {
|
|
342
|
-
await bindSkill(id, skillId, skillConfig);
|
|
343
|
+
await bindSkill(id, skillId, undefined, skillConfig);
|
|
343
344
|
} else if (knowledgeId) {
|
|
344
|
-
await bindKnowledge(id, knowledgeId, knowledgeConfig);
|
|
345
|
+
await bindKnowledge(id, knowledgeId, undefined, knowledgeConfig);
|
|
345
346
|
} else {
|
|
346
347
|
console.error('用法: arm agent bind <id> --skill=<skillId> 或 --knowledge=<knowledgeId>');
|
|
347
348
|
process.exit(1);
|
|
@@ -350,13 +351,15 @@ async function main() {
|
|
|
350
351
|
break;
|
|
351
352
|
case 'unbind':
|
|
352
353
|
if (!args[2]) {
|
|
353
|
-
console.error('用法: arm agent unbind <id> --skill=<skillId> 或 --knowledge=<knowledgeId> [--json]');
|
|
354
|
+
console.error('用法: arm agent unbind <id> --skill=<skillId> [--version=<ver>] 或 --knowledge=<knowledgeId> [--version=<ver>] [--json]');
|
|
354
355
|
process.exit(1);
|
|
355
356
|
}
|
|
356
357
|
{
|
|
357
358
|
const id = args[2];
|
|
358
359
|
let skillId: string | undefined;
|
|
359
360
|
let knowledgeId: string | undefined;
|
|
361
|
+
let skillVersion: string | undefined;
|
|
362
|
+
let knowledgeVersion: string | undefined;
|
|
360
363
|
|
|
361
364
|
for (let i = 3; i < args.length; i++) {
|
|
362
365
|
const arg = args[i];
|
|
@@ -364,19 +367,53 @@ async function main() {
|
|
|
364
367
|
skillId = arg.split('=').slice(1).join('=');
|
|
365
368
|
} else if (arg.startsWith('--knowledge=')) {
|
|
366
369
|
knowledgeId = arg.split('=').slice(1).join('=');
|
|
370
|
+
} else if (arg.startsWith('--version=')) {
|
|
371
|
+
const ver = arg.split('=').slice(1).join('=');
|
|
372
|
+
if (skillId) skillVersion = ver;
|
|
373
|
+
if (knowledgeId) knowledgeVersion = ver;
|
|
367
374
|
}
|
|
368
375
|
}
|
|
369
376
|
|
|
370
377
|
if (skillId) {
|
|
371
|
-
await unbindSkill(id, skillId);
|
|
378
|
+
await unbindSkill(id, skillId, skillVersion);
|
|
372
379
|
} else if (knowledgeId) {
|
|
373
|
-
await unbindKnowledge(id, knowledgeId);
|
|
380
|
+
await unbindKnowledge(id, knowledgeId, knowledgeVersion);
|
|
374
381
|
} else {
|
|
375
382
|
console.error('用法: arm agent unbind <id> --skill=<skillId> 或 --knowledge=<knowledgeId>');
|
|
376
383
|
process.exit(1);
|
|
377
384
|
}
|
|
378
385
|
}
|
|
379
386
|
break;
|
|
387
|
+
case 'sync':
|
|
388
|
+
if (!args[2]) {
|
|
389
|
+
console.error('用法: arm agent sync <folder> [--dry-run] [--force] [--json]');
|
|
390
|
+
process.exit(1);
|
|
391
|
+
}
|
|
392
|
+
{
|
|
393
|
+
const folder = args[2];
|
|
394
|
+
const syncOptions: { dryRun?: boolean; force?: boolean } = {};
|
|
395
|
+
|
|
396
|
+
for (let i = 3; i < args.length; i++) {
|
|
397
|
+
const arg = args[i];
|
|
398
|
+
if (arg === '--dry-run') {
|
|
399
|
+
syncOptions.dryRun = true;
|
|
400
|
+
} else if (arg === '--force') {
|
|
401
|
+
syncOptions.force = true;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
await syncAgent(folder, syncOptions);
|
|
406
|
+
}
|
|
407
|
+
break;
|
|
408
|
+
case 'bindings':
|
|
409
|
+
if (!args[2]) {
|
|
410
|
+
console.error('用法: arm agent bindings <name> [--history] [--json]');
|
|
411
|
+
process.exit(1);
|
|
412
|
+
}
|
|
413
|
+
if (subCommand === 'history') {
|
|
414
|
+
console.log('bindings history 需要通过 info 命令查看');
|
|
415
|
+
}
|
|
416
|
+
break;
|
|
380
417
|
default:
|
|
381
418
|
console.log(`
|
|
382
419
|
可用命令:
|
|
@@ -388,10 +425,11 @@ async function main() {
|
|
|
388
425
|
arm agent create --from=<folder> 从本地文件夹创建 Agent
|
|
389
426
|
arm agent update <id> 更新 Agent (--name, --description, --prompt, --avatar, --status)
|
|
390
427
|
arm agent delete <id> 删除 Agent
|
|
391
|
-
arm agent bind <id> --skill=<id>
|
|
392
|
-
arm agent unbind <id> --skill=<id> 解绑 Skill
|
|
393
|
-
arm agent bind <id> --knowledge=<id> 绑定 Knowledge 到 Agent
|
|
394
|
-
arm agent unbind <id> --knowledge=<id> 解绑 Knowledge
|
|
428
|
+
arm agent bind <id> --skill=<id> [--version=<ver>] 绑定 Skill 到 Agent
|
|
429
|
+
arm agent unbind <id> --skill=<id> [--version=<ver>] 解绑 Skill
|
|
430
|
+
arm agent bind <id> --knowledge=<id> [--version=<ver>] 绑定 Knowledge 到 Agent
|
|
431
|
+
arm agent unbind <id> --knowledge=<id> [--version=<ver>] 解绑 Knowledge
|
|
432
|
+
arm agent sync <folder> [--dry-run] 同步本地文件夹到云端 Agent
|
|
395
433
|
所有命令支持 --json 参数获取机器可读输出
|
|
396
434
|
`);
|
|
397
435
|
}
|
|
@@ -426,12 +464,14 @@ Agent Resource Management (arm)
|
|
|
426
464
|
arm agent info <name> 查看 Agent 详情
|
|
427
465
|
arm agent download <name> [dir] 下载 Agent
|
|
428
466
|
arm agent create <name> 创建 Agent
|
|
467
|
+
arm agent create --from=<folder> 从本地文件夹创建 Agent
|
|
429
468
|
arm agent update <id> 更新 Agent
|
|
430
469
|
arm agent delete <id> 删除 Agent
|
|
431
|
-
arm agent bind <id> --skill=<id>
|
|
432
|
-
arm agent unbind <id> --skill=<id> 解绑 Skill
|
|
433
|
-
arm agent bind <id> --knowledge=<id> 绑定 Knowledge
|
|
434
|
-
arm agent unbind <id> --knowledge=<id> 解绑 Knowledge
|
|
470
|
+
arm agent bind <id> --skill=<id> [--version=<ver>] 绑定 Skill
|
|
471
|
+
arm agent unbind <id> --skill=<id> [--version=<ver>] 解绑 Skill
|
|
472
|
+
arm agent bind <id> --knowledge=<id> [--version=<ver>] 绑定 Knowledge
|
|
473
|
+
arm agent unbind <id> --knowledge=<id> [--version=<ver>] 解绑 Knowledge
|
|
474
|
+
arm agent sync <folder> [--dry-run] 同步本地文件夹到云端 Agent
|
|
435
475
|
arm server 显示当前服务端
|
|
436
476
|
arm server set <url> 设置服务端
|
|
437
477
|
使用 arm <entity> -h 查看详细帮助
|