@codemieai/cdk 0.1.543 → 0.1.544
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/cli/index.js +421 -126
- package/dist/index.d.ts +2 -0
- package/dist/index.js +356 -61
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import * as crypto from 'crypto';
|
|
4
|
+
import { DataSourceType, CodeMieClient, SkillVisibility } from 'codemie-sdk';
|
|
2
5
|
import * as fs6 from 'fs';
|
|
3
6
|
import * as path6 from 'path';
|
|
4
7
|
import path6__default from 'path';
|
|
5
|
-
import ora from 'ora';
|
|
6
|
-
import * as crypto2 from 'crypto';
|
|
7
|
-
import { DataSourceType, CodeMieClient } from 'codemie-sdk';
|
|
8
8
|
import * as yaml5 from 'yaml';
|
|
9
9
|
import { program } from '@commander-js/extra-typings';
|
|
10
10
|
import { z } from 'zod';
|
|
@@ -21,66 +21,6 @@ var __export = (target, all) => {
|
|
|
21
21
|
for (var name in all)
|
|
22
22
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
23
23
|
};
|
|
24
|
-
function sanitizeFileName(name, maxLength = 255) {
|
|
25
|
-
const nameWithHyphens = name.replaceAll(/[/\\]/g, "-");
|
|
26
|
-
const sanitized = nameWithHyphens.toLowerCase().replaceAll(/[^a-z0-9]+/g, "-").replaceAll(/^-+|-+$/g, "").slice(0, maxLength);
|
|
27
|
-
if (!sanitized) {
|
|
28
|
-
throw new Error(`Sanitized filename is empty for input: "${name}"`);
|
|
29
|
-
}
|
|
30
|
-
return sanitized;
|
|
31
|
-
}
|
|
32
|
-
function validateBackupDirectory(backupDir, minSpaceGB = 1) {
|
|
33
|
-
try {
|
|
34
|
-
const parentDir = path6.dirname(backupDir);
|
|
35
|
-
if (!fs6.existsSync(parentDir)) {
|
|
36
|
-
fs6.mkdirSync(parentDir, { recursive: true });
|
|
37
|
-
}
|
|
38
|
-
const testFile = path6.join(parentDir, ".write-test");
|
|
39
|
-
fs6.writeFileSync(testFile, "test");
|
|
40
|
-
fs6.unlinkSync(testFile);
|
|
41
|
-
try {
|
|
42
|
-
const stats = fs6.statfsSync(parentDir);
|
|
43
|
-
const availableGB = stats.bavail * stats.bsize / BYTES_IN_GB;
|
|
44
|
-
if (availableGB < minSpaceGB) {
|
|
45
|
-
throw new Error(`Insufficient disk space: ${availableGB.toFixed(2)}GB available, need ${minSpaceGB}GB`);
|
|
46
|
-
}
|
|
47
|
-
} catch (error) {
|
|
48
|
-
if (error.code !== "ERR_METHOD_NOT_SUPPORTED") {
|
|
49
|
-
throw error;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
} catch (error) {
|
|
53
|
-
throw new Error(`Cannot write to backup directory: ${error instanceof Error ? error.message : String(error)}`);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
function moveAtomically(tempPath, finalPath) {
|
|
57
|
-
try {
|
|
58
|
-
fs6.renameSync(tempPath, finalPath);
|
|
59
|
-
} catch (error) {
|
|
60
|
-
const err = error;
|
|
61
|
-
if (err.code === "EEXIST") {
|
|
62
|
-
throw new Error(`Destination already exists: ${finalPath}`);
|
|
63
|
-
}
|
|
64
|
-
throw error;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
function cleanupDirectory(dirPath) {
|
|
68
|
-
if (fs6.existsSync(dirPath)) {
|
|
69
|
-
fs6.rmSync(dirPath, { recursive: true, force: true });
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
function ensureDirectoryExists(filePath) {
|
|
73
|
-
const dir = path6.dirname(filePath);
|
|
74
|
-
if (!fs6.existsSync(dir)) {
|
|
75
|
-
fs6.mkdirSync(dir, { recursive: true });
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
var BYTES_IN_GB;
|
|
79
|
-
var init_fileUtils = __esm({
|
|
80
|
-
"src/lib/fileUtils.ts"() {
|
|
81
|
-
BYTES_IN_GB = 1024 ** 3;
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
24
|
function formatDuration(durationMs) {
|
|
85
25
|
if (durationMs < 1e3) {
|
|
86
26
|
return `${durationMs}ms`;
|
|
@@ -354,7 +294,7 @@ function calculateChecksum(content) {
|
|
|
354
294
|
if (content.length === 0) {
|
|
355
295
|
logger.warn("\u26A0\uFE0F Calculating checksum of empty string");
|
|
356
296
|
}
|
|
357
|
-
return
|
|
297
|
+
return crypto.createHash("sha256").update(content, "utf8").digest("hex");
|
|
358
298
|
}
|
|
359
299
|
function normalizeAssistantConfig(assistant, buildConfig = null) {
|
|
360
300
|
return {
|
|
@@ -750,12 +690,84 @@ var init_converters = __esm({
|
|
|
750
690
|
FILE_SUMMARY_ALIASES = /* @__PURE__ */ new Set(["file_summary", "file-summary", "file-summay"]);
|
|
751
691
|
}
|
|
752
692
|
});
|
|
693
|
+
function sanitizeFileName(name, maxLength = 255) {
|
|
694
|
+
const nameWithHyphens = name.replaceAll(/[/\\]/g, "-");
|
|
695
|
+
const sanitized = nameWithHyphens.toLowerCase().replaceAll(/[^a-z0-9]+/g, "-").replaceAll(/^-+|-+$/g, "").slice(0, maxLength);
|
|
696
|
+
if (!sanitized) {
|
|
697
|
+
throw new Error(`Sanitized filename is empty for input: "${name}"`);
|
|
698
|
+
}
|
|
699
|
+
return sanitized;
|
|
700
|
+
}
|
|
701
|
+
function validateBackupDirectory(backupDir, minSpaceGB = 1) {
|
|
702
|
+
try {
|
|
703
|
+
const parentDir = path6.dirname(backupDir);
|
|
704
|
+
if (!fs6.existsSync(parentDir)) {
|
|
705
|
+
fs6.mkdirSync(parentDir, { recursive: true });
|
|
706
|
+
}
|
|
707
|
+
const testFile = path6.join(parentDir, ".write-test");
|
|
708
|
+
fs6.writeFileSync(testFile, "test");
|
|
709
|
+
fs6.unlinkSync(testFile);
|
|
710
|
+
try {
|
|
711
|
+
const stats = fs6.statfsSync(parentDir);
|
|
712
|
+
const availableGB = stats.bavail * stats.bsize / BYTES_IN_GB;
|
|
713
|
+
if (availableGB < minSpaceGB) {
|
|
714
|
+
throw new Error(`Insufficient disk space: ${availableGB.toFixed(2)}GB available, need ${minSpaceGB}GB`);
|
|
715
|
+
}
|
|
716
|
+
} catch (error) {
|
|
717
|
+
if (error.code !== "ERR_METHOD_NOT_SUPPORTED") {
|
|
718
|
+
throw error;
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
} catch (error) {
|
|
722
|
+
throw new Error(`Cannot write to backup directory: ${error instanceof Error ? error.message : String(error)}`);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
function moveAtomically(tempPath, finalPath) {
|
|
726
|
+
try {
|
|
727
|
+
fs6.renameSync(tempPath, finalPath);
|
|
728
|
+
} catch (error) {
|
|
729
|
+
const err = error;
|
|
730
|
+
if (err.code === "EEXIST") {
|
|
731
|
+
throw new Error(`Destination already exists: ${finalPath}`);
|
|
732
|
+
}
|
|
733
|
+
throw error;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
function cleanupDirectory(dirPath) {
|
|
737
|
+
if (fs6.existsSync(dirPath)) {
|
|
738
|
+
fs6.rmSync(dirPath, { recursive: true, force: true });
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
function ensureDirectoryExists(filePath) {
|
|
742
|
+
const dir = path6.dirname(filePath);
|
|
743
|
+
if (!fs6.existsSync(dir)) {
|
|
744
|
+
fs6.mkdirSync(dir, { recursive: true });
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
var BYTES_IN_GB;
|
|
748
|
+
var init_fileUtils = __esm({
|
|
749
|
+
"src/lib/fileUtils.ts"() {
|
|
750
|
+
BYTES_IN_GB = 1024 ** 3;
|
|
751
|
+
}
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
// src/lib/skillUtils.ts
|
|
755
|
+
function isProjectOwnedSkill(skill, projectName) {
|
|
756
|
+
return skill.project.trim().toLowerCase() === projectName.trim().toLowerCase();
|
|
757
|
+
}
|
|
758
|
+
var init_skillUtils = __esm({
|
|
759
|
+
"src/lib/skillUtils.ts"() {
|
|
760
|
+
}
|
|
761
|
+
});
|
|
753
762
|
|
|
754
763
|
// src/lib/backupTransformers.ts
|
|
755
764
|
var backupTransformers_exports = {};
|
|
756
765
|
__export(backupTransformers_exports, {
|
|
766
|
+
collectReferencedSkillIds: () => collectReferencedSkillIds,
|
|
767
|
+
partitionSkillsForBackupYaml: () => partitionSkillsForBackupYaml,
|
|
757
768
|
prepareAssistantForYaml: () => prepareAssistantForYaml,
|
|
758
769
|
prepareDatasourceForYaml: () => prepareDatasourceForYaml,
|
|
770
|
+
prepareImportedSkillForYaml: () => prepareImportedSkillForYaml,
|
|
759
771
|
prepareSkillForYaml: () => prepareSkillForYaml,
|
|
760
772
|
prepareWorkflowForYaml: () => prepareWorkflowForYaml,
|
|
761
773
|
transformMcpServer: () => transformMcpServer,
|
|
@@ -912,6 +924,36 @@ function prepareWorkflowForYaml(workflow, state, assistants, backupDir) {
|
|
|
912
924
|
};
|
|
913
925
|
return resource;
|
|
914
926
|
}
|
|
927
|
+
function collectReferencedSkillIds(assistants) {
|
|
928
|
+
const referencedSkillIds = /* @__PURE__ */ new Set();
|
|
929
|
+
for (const assistant of assistants) {
|
|
930
|
+
const assistantData = assistant;
|
|
931
|
+
for (const skillId of assistantData.skill_ids || []) {
|
|
932
|
+
referencedSkillIds.add(skillId);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
return referencedSkillIds;
|
|
936
|
+
}
|
|
937
|
+
function partitionSkillsForBackupYaml(skills, projectName, referencedSkillIds) {
|
|
938
|
+
const ownedSkills = [];
|
|
939
|
+
const importedSkills = [];
|
|
940
|
+
for (const skill of skills) {
|
|
941
|
+
if (isProjectOwnedSkill(skill, projectName)) {
|
|
942
|
+
ownedSkills.push(skill);
|
|
943
|
+
continue;
|
|
944
|
+
}
|
|
945
|
+
if (referencedSkillIds.has(skill.id)) {
|
|
946
|
+
importedSkills.push(skill);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
return { ownedSkills, importedSkills };
|
|
950
|
+
}
|
|
951
|
+
function prepareImportedSkillForYaml(skill) {
|
|
952
|
+
return {
|
|
953
|
+
name: skill.name,
|
|
954
|
+
id: skill.id
|
|
955
|
+
};
|
|
956
|
+
}
|
|
915
957
|
function prepareSkillForYaml(skill, state) {
|
|
916
958
|
const resource = skillResponseToResource(skill);
|
|
917
959
|
if (!state.resources.skills) {
|
|
@@ -932,13 +974,14 @@ var init_backupTransformers = __esm({
|
|
|
932
974
|
init_converters();
|
|
933
975
|
init_fileUtils();
|
|
934
976
|
init_logger();
|
|
977
|
+
init_skillUtils();
|
|
935
978
|
init_typeGuards();
|
|
936
979
|
}
|
|
937
980
|
});
|
|
938
981
|
|
|
939
982
|
// package.json
|
|
940
983
|
var package_default = {
|
|
941
|
-
version: "0.1.
|
|
984
|
+
version: "0.1.544"};
|
|
942
985
|
var appConfigSchema = z.object({
|
|
943
986
|
rootDir: z.string(),
|
|
944
987
|
codemieConfig: z.string(),
|
|
@@ -999,6 +1042,10 @@ function removeEmptyFields(userConfig) {
|
|
|
999
1042
|
);
|
|
1000
1043
|
}
|
|
1001
1044
|
|
|
1045
|
+
// src/backup.ts
|
|
1046
|
+
init_backupTransformers();
|
|
1047
|
+
init_skillUtils();
|
|
1048
|
+
|
|
1002
1049
|
// src/lib/constants.ts
|
|
1003
1050
|
var PAGINATION = {
|
|
1004
1051
|
DEFAULT_PAGE_SIZE: 100};
|
|
@@ -1101,7 +1148,7 @@ var BackupTransaction = class {
|
|
|
1101
1148
|
}
|
|
1102
1149
|
}
|
|
1103
1150
|
generateTransactionId() {
|
|
1104
|
-
return
|
|
1151
|
+
return crypto.randomBytes(8).toString("hex");
|
|
1105
1152
|
}
|
|
1106
1153
|
/**
|
|
1107
1154
|
* Save transaction state to disk (checkpoint) with timeout protection
|
|
@@ -1258,6 +1305,12 @@ function generateCodemieYaml(backup, projectName, backupDir, integrationSpecPath
|
|
|
1258
1305
|
const integrationIdToAlias = /* @__PURE__ */ new Map();
|
|
1259
1306
|
const integrationArray = [];
|
|
1260
1307
|
const skillIdToName = new Map(backup.resources.skills.map((skill) => [skill.id, skill.name]));
|
|
1308
|
+
const referencedSkillIds = collectReferencedSkillIds(backup.resources.assistants);
|
|
1309
|
+
const { ownedSkills, importedSkills } = partitionSkillsForBackupYaml(
|
|
1310
|
+
backup.resources.skills,
|
|
1311
|
+
projectName,
|
|
1312
|
+
referencedSkillIds
|
|
1313
|
+
);
|
|
1261
1314
|
for (const integration of backup.resources.integrations) {
|
|
1262
1315
|
const credentialType = integration.credential_type || "integration";
|
|
1263
1316
|
const alias = integration.alias || `${String(credentialType).toLowerCase()}_${integration.id.slice(0, 8)}`;
|
|
@@ -1303,6 +1356,7 @@ function generateCodemieYaml(backup, projectName, backupDir, integrationSpecPath
|
|
|
1303
1356
|
imported: {
|
|
1304
1357
|
assistants: [],
|
|
1305
1358
|
datasources: [],
|
|
1359
|
+
skills: importedSkills.map((skill) => prepareImportedSkillForYaml(skill)),
|
|
1306
1360
|
integrations: integrationArray
|
|
1307
1361
|
},
|
|
1308
1362
|
datasource_defaults: {
|
|
@@ -1324,8 +1378,8 @@ function generateCodemieYaml(backup, projectName, backupDir, integrationSpecPath
|
|
|
1324
1378
|
workflows: backup.resources.workflows.map(
|
|
1325
1379
|
(workflow) => prepareWorkflowForYaml(workflow, backup.state, backup.resources.assistants, backupDir)
|
|
1326
1380
|
),
|
|
1327
|
-
...
|
|
1328
|
-
skills:
|
|
1381
|
+
...ownedSkills.length > 0 && {
|
|
1382
|
+
skills: ownedSkills.map((skill) => prepareSkillForYaml(skill, backup.state))
|
|
1329
1383
|
}
|
|
1330
1384
|
}
|
|
1331
1385
|
};
|
|
@@ -1405,6 +1459,13 @@ async function createClient(config) {
|
|
|
1405
1459
|
}
|
|
1406
1460
|
return client;
|
|
1407
1461
|
}
|
|
1462
|
+
|
|
1463
|
+
// src/lib/nameUtils.ts
|
|
1464
|
+
function namesEqual(a, b) {
|
|
1465
|
+
return a.toLowerCase() === b.toLowerCase();
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
// src/lib/codemieConfigLoader.ts
|
|
1408
1469
|
var CodemieConfigLoader = class {
|
|
1409
1470
|
appConfig;
|
|
1410
1471
|
constructor(appConfig) {
|
|
@@ -1499,6 +1560,14 @@ var CodemieConfigLoader = class {
|
|
|
1499
1560
|
}
|
|
1500
1561
|
}
|
|
1501
1562
|
}
|
|
1563
|
+
for (const skill of config.imported?.skills || []) {
|
|
1564
|
+
if (!skill.name) {
|
|
1565
|
+
errors.push("Imported skill entry is missing required field: name");
|
|
1566
|
+
}
|
|
1567
|
+
if (!skill.id) {
|
|
1568
|
+
errors.push(`Imported skill "${skill.name || "<unknown>"}" is missing required field: id`);
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1502
1571
|
return {
|
|
1503
1572
|
valid: errors.length === 0,
|
|
1504
1573
|
errors
|
|
@@ -1713,6 +1782,7 @@ Import chain: ${[...visitedFiles].join(" \u2192 ")} \u2192 ${normalizedPath}`
|
|
|
1713
1782
|
* - imported.integrations (array): searches by 'alias' field
|
|
1714
1783
|
* - imported.assistants (array): searches by 'name' field
|
|
1715
1784
|
* - imported.datasources (array): searches by 'name' field
|
|
1785
|
+
* - imported.skills (array): searches by 'name' field
|
|
1716
1786
|
*/
|
|
1717
1787
|
resolveReference(config, ref, context) {
|
|
1718
1788
|
const parts = ref.split(".");
|
|
@@ -1736,9 +1806,16 @@ Import chain: ${[...visitedFiles].join(" \u2192 ")} \u2192 ${normalizedPath}`
|
|
|
1736
1806
|
if (Array.isArray(current) && hasMorePathSegments) {
|
|
1737
1807
|
const nextPathSegment = parts[i + 1];
|
|
1738
1808
|
const searchField = part === "integrations" ? "alias" : "name";
|
|
1739
|
-
const foundItem = current.find(
|
|
1740
|
-
(
|
|
1741
|
-
|
|
1809
|
+
const foundItem = current.find((item) => {
|
|
1810
|
+
if (typeof item !== "object" || item === null) {
|
|
1811
|
+
return false;
|
|
1812
|
+
}
|
|
1813
|
+
const fieldValue = item[searchField];
|
|
1814
|
+
if (typeof fieldValue !== "string") {
|
|
1815
|
+
return false;
|
|
1816
|
+
}
|
|
1817
|
+
return searchField === "alias" ? fieldValue === nextPathSegment : namesEqual(fieldValue, nextPathSegment);
|
|
1818
|
+
});
|
|
1742
1819
|
if (!foundItem) {
|
|
1743
1820
|
throw new Error(
|
|
1744
1821
|
`Reference path "${ref}" not found: no item with ${searchField}="${nextPathSegment}" in "${pathParts.join(".")}". Referenced in ${context}.`
|
|
@@ -1895,6 +1972,27 @@ async function* streamResources(fetchPage, resourceType) {
|
|
|
1895
1972
|
}
|
|
1896
1973
|
}
|
|
1897
1974
|
}
|
|
1975
|
+
function createFallbackImportedSkillDetail(skill) {
|
|
1976
|
+
return {
|
|
1977
|
+
id: skill.id,
|
|
1978
|
+
name: skill.name,
|
|
1979
|
+
description: "",
|
|
1980
|
+
project: skill.project || "",
|
|
1981
|
+
visibility: SkillVisibility.PUBLIC,
|
|
1982
|
+
created_by: null,
|
|
1983
|
+
categories: [],
|
|
1984
|
+
createdDate: "",
|
|
1985
|
+
updatedDate: null,
|
|
1986
|
+
is_attached: true,
|
|
1987
|
+
assistants_count: 0,
|
|
1988
|
+
user_abilities: [],
|
|
1989
|
+
unique_likes_count: 0,
|
|
1990
|
+
unique_dislikes_count: 0,
|
|
1991
|
+
content: "",
|
|
1992
|
+
toolkits: [],
|
|
1993
|
+
mcp_servers: []
|
|
1994
|
+
};
|
|
1995
|
+
}
|
|
1898
1996
|
async function saveAssistantToBackup(assistant, client, backupData, backupDir) {
|
|
1899
1997
|
logger.info(` \u2022 ${assistant.name} (${assistant.id})`);
|
|
1900
1998
|
const full = await withTimeout(
|
|
@@ -2087,16 +2185,24 @@ async function backupIntegrations(client, backupData, projectName) {
|
|
|
2087
2185
|
logger.info(`\u2713 Backed up ${projectIntegrations.length + userIntegrations.length} integration(s)
|
|
2088
2186
|
`);
|
|
2089
2187
|
}
|
|
2090
|
-
async function backupSkills(client, backupData, backupDir, transaction) {
|
|
2188
|
+
async function backupSkills(client, backupData, backupDir, transaction, projectName) {
|
|
2091
2189
|
logger.info("\u{1F9E0} Fetching skills...");
|
|
2190
|
+
const referencedSkillIds = collectReferencedSkillIds(backupData.resources.assistants);
|
|
2092
2191
|
const allSkills = [];
|
|
2093
2192
|
for await (const skill of streamResources((params) => client.skills.list(params), "skills")) {
|
|
2094
2193
|
allSkills.push(skill);
|
|
2095
2194
|
}
|
|
2096
|
-
|
|
2097
|
-
|
|
2195
|
+
const skillsToBackup = allSkills.filter(
|
|
2196
|
+
(skill) => referencedSkillIds.has(skill.id) || !skill.project || isProjectOwnedSkill({ project: skill.project }, projectName)
|
|
2197
|
+
);
|
|
2198
|
+
const skippedExternalSkills = allSkills.length - skillsToBackup.length;
|
|
2199
|
+
logger.info(` Found ${allSkills.length} skill(s), backing up ${skillsToBackup.length}`);
|
|
2200
|
+
if (skippedExternalSkills > 0) {
|
|
2201
|
+
logger.info(` \u21B7 Skipping ${skippedExternalSkills} unreferenced external skill(s)`);
|
|
2202
|
+
}
|
|
2203
|
+
transaction.setTotal("skills", skillsToBackup.length);
|
|
2098
2204
|
const limit = createConcurrentLimiter();
|
|
2099
|
-
for (const skill of
|
|
2205
|
+
for (const skill of skillsToBackup) {
|
|
2100
2206
|
if (transaction.isCompleted("skills", skill.id)) {
|
|
2101
2207
|
logger.info(` \u21B7 Skipping ${skill.name} (already backed up)`);
|
|
2102
2208
|
continue;
|
|
@@ -2105,16 +2211,37 @@ async function backupSkills(client, backupData, backupDir, transaction) {
|
|
|
2105
2211
|
await limit(
|
|
2106
2212
|
() => withRetry(async () => {
|
|
2107
2213
|
logger.info(` \u2022 ${skill.name} (${skill.id})`);
|
|
2108
|
-
const
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2214
|
+
const listedAsProjectOwned = skill.project ? isProjectOwnedSkill({ project: skill.project }, projectName) : false;
|
|
2215
|
+
let full;
|
|
2216
|
+
try {
|
|
2217
|
+
full = await withTimeout(
|
|
2218
|
+
client.skills.get(skill.id),
|
|
2219
|
+
TIMEOUTS_MS.SKILL_FETCH,
|
|
2220
|
+
`Timeout fetching skill ${skill.id}`
|
|
2221
|
+
);
|
|
2222
|
+
} catch (error) {
|
|
2223
|
+
if (listedAsProjectOwned || !referencedSkillIds.has(skill.id)) {
|
|
2224
|
+
throw error;
|
|
2225
|
+
}
|
|
2226
|
+
logger.warn(
|
|
2227
|
+
` \u26A0\uFE0F Failed to fetch imported skill details for ${skill.name}. Using list response as fallback: ${error instanceof Error ? error.message : String(error)}`
|
|
2228
|
+
);
|
|
2229
|
+
full = createFallbackImportedSkillDetail(skill);
|
|
2230
|
+
}
|
|
2231
|
+
const fullIsProjectOwned = isProjectOwnedSkill(full, projectName);
|
|
2232
|
+
if (!fullIsProjectOwned && !referencedSkillIds.has(skill.id)) {
|
|
2233
|
+
logger.info(` \u21B7 Skipping unreferenced external skill ${skill.name} (owner: ${full.project})`);
|
|
2234
|
+
return;
|
|
2235
|
+
}
|
|
2113
2236
|
backupData.resources.skills.push(full);
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2237
|
+
if (fullIsProjectOwned) {
|
|
2238
|
+
const fileName = `${sanitizeFileName(skill.name)}.skill.md`;
|
|
2239
|
+
const filePath = path6.join(backupDir, "skills", fileName);
|
|
2240
|
+
ensureDirectoryExists(filePath);
|
|
2241
|
+
fs6.writeFileSync(filePath, full.content || "", "utf8");
|
|
2242
|
+
} else {
|
|
2243
|
+
logger.info(` \u21B7 Skipping content file for imported skill ${skill.name} (owner: ${full.project})`);
|
|
2244
|
+
}
|
|
2118
2245
|
}, `Backup skill ${skill.name}`)
|
|
2119
2246
|
);
|
|
2120
2247
|
transaction.markCompleted("skills", skill.id);
|
|
@@ -2128,7 +2255,7 @@ async function backupSkills(client, backupData, backupDir, transaction) {
|
|
|
2128
2255
|
transaction.markFailed("skills", skill.id, errorDetails.message);
|
|
2129
2256
|
}
|
|
2130
2257
|
}
|
|
2131
|
-
logger.info(`\u2713 Backed up ${
|
|
2258
|
+
logger.info(`\u2713 Backed up ${backupData.resources.skills.length} skill(s)
|
|
2132
2259
|
`);
|
|
2133
2260
|
}
|
|
2134
2261
|
function getUniqueBackupDir(baseDir, timestamp) {
|
|
@@ -2213,11 +2340,11 @@ async function backupResources(options) {
|
|
|
2213
2340
|
}
|
|
2214
2341
|
}
|
|
2215
2342
|
};
|
|
2216
|
-
await backupSkills(client, backupData, tempBackupDir, transaction);
|
|
2217
2343
|
await backupIntegrations(client, backupData, config.project.name);
|
|
2218
2344
|
await backupDatasources(client, backupData, transaction, config.project.name);
|
|
2219
2345
|
await backupWorkflows(client, backupData, tempBackupDir, transaction);
|
|
2220
2346
|
await backupAssistants(client, backupData, tempBackupDir, transaction);
|
|
2347
|
+
await backupSkills(client, backupData, tempBackupDir, transaction, config.project.name);
|
|
2221
2348
|
const stats = transaction.getData();
|
|
2222
2349
|
const totalFailed = stats.resources.assistants.failed.length + stats.resources.datasources.failed.length + stats.resources.workflows.failed.length + stats.resources.skills.failed.length;
|
|
2223
2350
|
if (totalFailed > 0) {
|
|
@@ -2403,12 +2530,12 @@ var CleanupManager = class {
|
|
|
2403
2530
|
const configAssistantNames = new Set((config.resources.assistants || []).map(({ name }) => name));
|
|
2404
2531
|
const configDatasourceNames = new Set((config.resources.datasources || []).map(({ name }) => name));
|
|
2405
2532
|
const configWorkflowNames = new Set((config.resources.workflows || []).map(({ name }) => name));
|
|
2406
|
-
const configSkillNames = new Set((config.resources.skills || []).map(({ name }) => name));
|
|
2533
|
+
const configSkillNames = new Set((config.resources.skills || []).map(({ name }) => name.toLowerCase()));
|
|
2407
2534
|
return {
|
|
2408
2535
|
assistants: managedResources.assistants.filter((name) => !configAssistantNames.has(name)),
|
|
2409
2536
|
datasources: managedResources.datasources.filter((name) => !configDatasourceNames.has(name)),
|
|
2410
2537
|
workflows: managedResources.workflows.filter((name) => !configWorkflowNames.has(name)),
|
|
2411
|
-
skills: managedResources.skills.filter((name) => !configSkillNames.has(name))
|
|
2538
|
+
skills: managedResources.skills.filter((name) => !configSkillNames.has(name.toLowerCase()))
|
|
2412
2539
|
};
|
|
2413
2540
|
}
|
|
2414
2541
|
/**
|
|
@@ -2595,6 +2722,62 @@ function checkSkillExists(client, name, stateManager) {
|
|
|
2595
2722
|
);
|
|
2596
2723
|
}
|
|
2597
2724
|
|
|
2725
|
+
// src/lib/resourceResolver.ts
|
|
2726
|
+
init_logger();
|
|
2727
|
+
function findSkillStateByName(stateManager, skillName) {
|
|
2728
|
+
const exactMatch = stateManager.getSkillState(skillName);
|
|
2729
|
+
if (exactMatch) {
|
|
2730
|
+
return exactMatch;
|
|
2731
|
+
}
|
|
2732
|
+
for (const name of stateManager.getAllManagedResources().skills) {
|
|
2733
|
+
if (namesEqual(name, skillName)) {
|
|
2734
|
+
return stateManager.getSkillState(name);
|
|
2735
|
+
}
|
|
2736
|
+
}
|
|
2737
|
+
return void 0;
|
|
2738
|
+
}
|
|
2739
|
+
function findConfiguredSkill(config, skillName) {
|
|
2740
|
+
return config.resources.skills?.find((skill) => namesEqual(skill.name, skillName));
|
|
2741
|
+
}
|
|
2742
|
+
function findImportedSkill(config, skillName) {
|
|
2743
|
+
return config.imported?.skills?.find((skill) => namesEqual(skill.name, skillName));
|
|
2744
|
+
}
|
|
2745
|
+
function lookupSkillId(skillName, stateManager, config) {
|
|
2746
|
+
const configuredSkill = findConfiguredSkill(config, skillName);
|
|
2747
|
+
const importedSkill = findImportedSkill(config, skillName);
|
|
2748
|
+
if (configuredSkill && importedSkill) {
|
|
2749
|
+
return void 0;
|
|
2750
|
+
}
|
|
2751
|
+
if (importedSkill) {
|
|
2752
|
+
return importedSkill.id;
|
|
2753
|
+
}
|
|
2754
|
+
if (!configuredSkill) {
|
|
2755
|
+
return void 0;
|
|
2756
|
+
}
|
|
2757
|
+
return findSkillStateByName(stateManager, configuredSkill.name)?.id;
|
|
2758
|
+
}
|
|
2759
|
+
function resolveSkillId(skillName, stateManager, config) {
|
|
2760
|
+
const configuredSkill = findConfiguredSkill(config, skillName);
|
|
2761
|
+
const importedSkill = findImportedSkill(config, skillName);
|
|
2762
|
+
if (configuredSkill && importedSkill) {
|
|
2763
|
+
throw new Error(
|
|
2764
|
+
`Skill "${skillName}" is defined in both resources.skills and imported.skills. Keep project-owned skills in resources.skills or external skills in imported.skills, not both.`
|
|
2765
|
+
);
|
|
2766
|
+
}
|
|
2767
|
+
const skillId = lookupSkillId(skillName, stateManager, config);
|
|
2768
|
+
if (skillId) {
|
|
2769
|
+
const source = importedSkill ? "imported" : "state";
|
|
2770
|
+
logger.info(` \u2713 Resolved${source === "imported" ? " (imported)" : ""} "${skillName}" \u2192 ${skillId}`);
|
|
2771
|
+
return skillId;
|
|
2772
|
+
}
|
|
2773
|
+
throw new Error(
|
|
2774
|
+
`Skill "${skillName}" cannot be resolved. For project-owned skills, define it in resources.skills and deploy it so state.json contains its ID. For external skills, add name and id to imported.skills.`
|
|
2775
|
+
);
|
|
2776
|
+
}
|
|
2777
|
+
function resolveSkillIds(skillNames, stateManager, config) {
|
|
2778
|
+
return skillNames.map((name) => resolveSkillId(name, stateManager, config));
|
|
2779
|
+
}
|
|
2780
|
+
|
|
2598
2781
|
// src/lib/stateManager.ts
|
|
2599
2782
|
init_checksumUtils();
|
|
2600
2783
|
init_codemieConfigChecksums();
|
|
@@ -2990,16 +3173,7 @@ async function deployAssistants(config, client, loader, stateManager) {
|
|
|
2990
3173
|
let resolvedSkillIds = [];
|
|
2991
3174
|
if (assistant.skills && assistant.skills.length > 0) {
|
|
2992
3175
|
logger.info(` Resolving ${assistant.skills.length} skill name(s)...`);
|
|
2993
|
-
|
|
2994
|
-
for (const skillName of assistant.skills) {
|
|
2995
|
-
const skillState = stateManager.getSkillState(skillName);
|
|
2996
|
-
if (!skillState) {
|
|
2997
|
-
throw new Error(`Skill "${skillName}" not found in state. Ensure the skill is deployed first.`);
|
|
2998
|
-
}
|
|
2999
|
-
resolvedIds.push(skillState.id);
|
|
3000
|
-
logger.info(` \u2713 Resolved "${skillName}" \u2192 ${skillState.id}`);
|
|
3001
|
-
}
|
|
3002
|
-
resolvedSkillIds = resolvedIds;
|
|
3176
|
+
resolvedSkillIds = resolveSkillIds(assistant.skills, stateManager, config);
|
|
3003
3177
|
}
|
|
3004
3178
|
const assistantWithResolved = {
|
|
3005
3179
|
...assistant,
|
|
@@ -3119,9 +3293,8 @@ async function deployAssistants(config, client, loader, stateManager) {
|
|
|
3119
3293
|
logger.error(` ${error.message}`);
|
|
3120
3294
|
logger.debug(` Stack:`, error.stack);
|
|
3121
3295
|
if ("statusCode" in error) {
|
|
3122
|
-
|
|
3123
|
-
logger.error(`
|
|
3124
|
-
logger.error(` Data: ${JSON.stringify(apiError.response, null, 2)}`);
|
|
3296
|
+
logger.error(` Status: ${String(error.statusCode)}`);
|
|
3297
|
+
logger.error(` Data: ${JSON.stringify("response" in error ? error.response : void 0, null, 2)}`);
|
|
3125
3298
|
} else if ("response" in error) {
|
|
3126
3299
|
const axiosError = error;
|
|
3127
3300
|
logger.error(` Status: ${axiosError.response?.status}`);
|
|
@@ -3568,6 +3741,13 @@ async function deployResources(options) {
|
|
|
3568
3741
|
}
|
|
3569
3742
|
if (orphaned.skills.length > 0) {
|
|
3570
3743
|
logger.info(` \u2022 ${orphaned.skills.length} skill(s)`);
|
|
3744
|
+
const importedSkillNames = new Set(config.imported?.skills?.map((skill) => skill.name.toLowerCase()) || []);
|
|
3745
|
+
const migratedImportedSkills = orphaned.skills.filter((name) => importedSkillNames.has(name.toLowerCase()));
|
|
3746
|
+
if (migratedImportedSkills.length > 0) {
|
|
3747
|
+
logger.info(
|
|
3748
|
+
` \u26A0\uFE0F ${migratedImportedSkills.length} skill(s) also exist in imported.skills. Run with --prune to remove stale IaC-managed skill state and delete old managed skill(s) from platform.`
|
|
3749
|
+
);
|
|
3750
|
+
}
|
|
3571
3751
|
}
|
|
3572
3752
|
if (process.env.SAMPLE_DEPLOY === "1") {
|
|
3573
3753
|
logger.info("\n\u{1F50E} SAMPLE_DEPLOY=1 -> Skipping orphan deletion (simulation / partial deploy mode)\n");
|
|
@@ -3772,6 +3952,7 @@ async function main3(options) {
|
|
|
3772
3952
|
|
|
3773
3953
|
// src/import.ts
|
|
3774
3954
|
init_converters();
|
|
3955
|
+
init_skillUtils();
|
|
3775
3956
|
init_fileUtils();
|
|
3776
3957
|
init_logger();
|
|
3777
3958
|
init_checksumUtils();
|
|
@@ -3863,7 +4044,14 @@ Please use a more specific name.`);
|
|
|
3863
4044
|
function checkResourceExists2(config, resourceType, name) {
|
|
3864
4045
|
const pluralType = resourceType === "assistant" ? "assistants" : resourceType === "datasource" ? "datasources" : resourceType === "workflow" ? "workflows" : "skills";
|
|
3865
4046
|
const resources = config.resources[pluralType] || [];
|
|
3866
|
-
|
|
4047
|
+
if (resources.some((r) => namesEqual(r.name, name))) {
|
|
4048
|
+
return true;
|
|
4049
|
+
}
|
|
4050
|
+
if (resourceType === "skill") {
|
|
4051
|
+
const importedSkills = config.imported?.skills || [];
|
|
4052
|
+
return importedSkills.some((skill) => namesEqual(skill.name, name));
|
|
4053
|
+
}
|
|
4054
|
+
return false;
|
|
3867
4055
|
}
|
|
3868
4056
|
function writeResourceFiles(rootDir, resourceType, resource, apiResponse) {
|
|
3869
4057
|
const safeName = sanitizeFileName(resource.name);
|
|
@@ -3954,6 +4142,53 @@ function addImportToCodemieYaml(rootDir, codemieConfigPath, resourceType, relati
|
|
|
3954
4142
|
fs6.writeFileSync(configPath, doc.toString(), "utf8");
|
|
3955
4143
|
logger.info(` \u2705 Added $import to ${codemieConfigPath} \u2192 resources.${pluralType}`);
|
|
3956
4144
|
}
|
|
4145
|
+
function addImportedSkillToCodemieYaml(rootDir, codemieConfigPath, skill) {
|
|
4146
|
+
const configPath = path6.join(rootDir, codemieConfigPath);
|
|
4147
|
+
if (!fs6.existsSync(configPath)) {
|
|
4148
|
+
throw new Error(`Configuration file not found: ${configPath}`);
|
|
4149
|
+
}
|
|
4150
|
+
const content = fs6.readFileSync(configPath, "utf8");
|
|
4151
|
+
const doc = yaml5.parseDocument(content);
|
|
4152
|
+
let importedNode = doc.get("imported");
|
|
4153
|
+
if (!importedNode) {
|
|
4154
|
+
importedNode = doc.createNode({ assistants: [], datasources: [], skills: [], integrations: [] });
|
|
4155
|
+
doc.set("imported", importedNode);
|
|
4156
|
+
}
|
|
4157
|
+
let skillsArray = doc.getIn(["imported", "skills"]);
|
|
4158
|
+
if (!skillsArray) {
|
|
4159
|
+
skillsArray = doc.createNode([]);
|
|
4160
|
+
doc.setIn(["imported", "skills"], skillsArray);
|
|
4161
|
+
}
|
|
4162
|
+
const existingSkill = skillsArray.items.find((item) => {
|
|
4163
|
+
if (!yaml5.isMap(item)) {
|
|
4164
|
+
return false;
|
|
4165
|
+
}
|
|
4166
|
+
const existingName = item.get("name");
|
|
4167
|
+
return typeof existingName === "string" && namesEqual(existingName, skill.name);
|
|
4168
|
+
});
|
|
4169
|
+
if (existingSkill) {
|
|
4170
|
+
const existingId = existingSkill.get("id");
|
|
4171
|
+
if (existingId && existingId !== skill.id) {
|
|
4172
|
+
throw new Error(
|
|
4173
|
+
`Skill "${skill.name}" already exists in imported.skills with a different ID (${existingId}). Remove the existing entry or use the intended skill ID.`
|
|
4174
|
+
);
|
|
4175
|
+
}
|
|
4176
|
+
logger.info(` \u21B7 Skill "${skill.name}" already exists in imported.skills`);
|
|
4177
|
+
return;
|
|
4178
|
+
}
|
|
4179
|
+
skillsArray.add(doc.createNode({ name: skill.name, id: skill.id }));
|
|
4180
|
+
fs6.writeFileSync(configPath, doc.toString(), "utf8");
|
|
4181
|
+
logger.info(` \u2705 Added skill to ${codemieConfigPath} \u2192 imported.skills`);
|
|
4182
|
+
}
|
|
4183
|
+
function ensureExternalSkillDoesNotConflictWithManagedSkill(config, skillName) {
|
|
4184
|
+
const managedSkill = config.resources.skills?.find((skill) => namesEqual(skill.name, skillName));
|
|
4185
|
+
if (!managedSkill) {
|
|
4186
|
+
return;
|
|
4187
|
+
}
|
|
4188
|
+
throw new Error(
|
|
4189
|
+
`External skill "${skillName}" is already defined in resources.skills. Keep project-owned skills in resources.skills or external skills in imported.skills, not both.`
|
|
4190
|
+
);
|
|
4191
|
+
}
|
|
3957
4192
|
function updateState(stateManager, resourceType, resource, apiResponse) {
|
|
3958
4193
|
switch (resourceType) {
|
|
3959
4194
|
case "assistant": {
|
|
@@ -4041,16 +4276,22 @@ async function importResource(options) {
|
|
|
4041
4276
|
logger.info(` Resolving ${assistantData.skill_ids.length} skill ID(s)...`);
|
|
4042
4277
|
const skillNames = [];
|
|
4043
4278
|
for (const skillId of assistantData.skill_ids) {
|
|
4279
|
+
let skill;
|
|
4044
4280
|
try {
|
|
4045
|
-
|
|
4281
|
+
skill = await withTimeout(
|
|
4046
4282
|
client.skills.get(skillId),
|
|
4047
4283
|
TIMEOUTS_MS.ASSISTANT_FETCH,
|
|
4048
4284
|
`Timeout fetching skill "${skillId}"`
|
|
4049
4285
|
);
|
|
4050
|
-
skillNames.push(skill.name);
|
|
4051
|
-
logger.info(` \u2713 Resolved skill ${skillId.slice(0, 8)}... \u2192 "${skill.name}"`);
|
|
4052
4286
|
} catch {
|
|
4053
4287
|
logger.warn(` \u26A0\uFE0F Could not resolve skill ID "${skillId}" \u2014 skipping`);
|
|
4288
|
+
continue;
|
|
4289
|
+
}
|
|
4290
|
+
skillNames.push(skill.name);
|
|
4291
|
+
logger.info(` \u2713 Resolved skill ${skillId.slice(0, 8)}... \u2192 "${skill.name}"`);
|
|
4292
|
+
if (!isProjectOwnedSkill(skill, config.project.name)) {
|
|
4293
|
+
ensureExternalSkillDoesNotConflictWithManagedSkill(config, skill.name);
|
|
4294
|
+
addImportedSkillToCodemieYaml(rootDir, appConfig.codemieConfig, { name: skill.name, id: skill.id });
|
|
4054
4295
|
}
|
|
4055
4296
|
}
|
|
4056
4297
|
if (skillNames.length > 0) {
|
|
@@ -4086,9 +4327,22 @@ async function importResource(options) {
|
|
|
4086
4327
|
logger.info(` Searching for skill "${slug}"...`);
|
|
4087
4328
|
const skill = await findSkill(client, slug);
|
|
4088
4329
|
logger.info(` \u2713 Found: ${skill.name} (${skill.id})`);
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4330
|
+
if (skill.name !== slug && checkResourceExists2(config, resourceType, skill.name)) {
|
|
4331
|
+
throw new Error(
|
|
4332
|
+
`A ${resourceType} named "${skill.name}" already exists in the configuration. Remove it first or use a different name.`
|
|
4333
|
+
);
|
|
4334
|
+
}
|
|
4335
|
+
if (isProjectOwnedSkill(skill, config.project.name)) {
|
|
4336
|
+
logger.info(` Project-owned skill \u2014 importing into resources.skills`);
|
|
4337
|
+
apiResponse = skill;
|
|
4338
|
+
resource = skillResponseToResource(skill);
|
|
4339
|
+
break;
|
|
4340
|
+
}
|
|
4341
|
+
addImportedSkillToCodemieYaml(rootDir, appConfig.codemieConfig, { name: skill.name, id: skill.id });
|
|
4342
|
+
logger.info(`
|
|
4343
|
+
\u2705 Successfully imported external skill "${skill.name}" into imported.skills
|
|
4344
|
+
`);
|
|
4345
|
+
return;
|
|
4092
4346
|
}
|
|
4093
4347
|
}
|
|
4094
4348
|
const relativeResourcePath = writeResourceFiles(rootDir, resourceType, resource, apiResponse);
|
|
@@ -4418,7 +4672,7 @@ async function previewSkills(skills, loader, stateManager, client) {
|
|
|
4418
4672
|
}
|
|
4419
4673
|
return changes;
|
|
4420
4674
|
}
|
|
4421
|
-
async function previewAssistants(assistants, loader, stateManager, client) {
|
|
4675
|
+
async function previewAssistants(assistants, loader, stateManager, client, config) {
|
|
4422
4676
|
const changes = [];
|
|
4423
4677
|
for (const assistant of assistants) {
|
|
4424
4678
|
const promptContent = loader.loadPrompt(assistant.prompt);
|
|
@@ -4435,17 +4689,16 @@ async function previewAssistants(assistants, loader, stateManager, client) {
|
|
|
4435
4689
|
async () => {
|
|
4436
4690
|
const existingState = stateManager.getAssistantState(assistant.name);
|
|
4437
4691
|
const configChanged = existingState ? existingState.promptChecksum !== calculateChecksum(promptContent) || existingState.configChecksum !== configChecksum : false;
|
|
4438
|
-
if (
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
if (drifted) {
|
|
4692
|
+
if (assistant.skills !== void 0) {
|
|
4693
|
+
const { ids: desiredSkillIds, unresolved } = resolveDesiredSkillIds(assistant.skills, stateManager, config);
|
|
4694
|
+
if (unresolved.length > 0) {
|
|
4695
|
+
return {
|
|
4696
|
+
hasChanged: true,
|
|
4697
|
+
createDetails: `Unresolved skill reference(s): ${unresolved.join(", ")}`,
|
|
4698
|
+
updateDetails: `Unresolved skill reference(s): ${unresolved.join(", ")}`
|
|
4699
|
+
};
|
|
4700
|
+
}
|
|
4701
|
+
if (existingState?.id && await checkSkillAttachmentDrift(client, existingState.id, desiredSkillIds)) {
|
|
4449
4702
|
return {
|
|
4450
4703
|
hasChanged: true,
|
|
4451
4704
|
createDetails: `Model: ${assistant.model}`,
|
|
@@ -4453,6 +4706,13 @@ async function previewAssistants(assistants, loader, stateManager, client) {
|
|
|
4453
4706
|
};
|
|
4454
4707
|
}
|
|
4455
4708
|
}
|
|
4709
|
+
if (configChanged) {
|
|
4710
|
+
return {
|
|
4711
|
+
hasChanged: true,
|
|
4712
|
+
createDetails: `Model: ${assistant.model}`,
|
|
4713
|
+
updateDetails: "Prompt or configuration changed"
|
|
4714
|
+
};
|
|
4715
|
+
}
|
|
4456
4716
|
return {
|
|
4457
4717
|
hasChanged: false,
|
|
4458
4718
|
createDetails: `Model: ${assistant.model}`
|
|
@@ -4463,15 +4723,18 @@ async function previewAssistants(assistants, loader, stateManager, client) {
|
|
|
4463
4723
|
}
|
|
4464
4724
|
return changes;
|
|
4465
4725
|
}
|
|
4466
|
-
function resolveDesiredSkillIds(skillNames, stateManager) {
|
|
4726
|
+
function resolveDesiredSkillIds(skillNames, stateManager, config) {
|
|
4467
4727
|
const ids = [];
|
|
4728
|
+
const unresolved = [];
|
|
4468
4729
|
for (const name of skillNames) {
|
|
4469
|
-
const
|
|
4470
|
-
if (
|
|
4471
|
-
ids.push(
|
|
4730
|
+
const skillId = lookupSkillId(name, stateManager, config);
|
|
4731
|
+
if (skillId) {
|
|
4732
|
+
ids.push(skillId);
|
|
4733
|
+
} else {
|
|
4734
|
+
unresolved.push(name);
|
|
4472
4735
|
}
|
|
4473
4736
|
}
|
|
4474
|
-
return ids;
|
|
4737
|
+
return { ids, unresolved };
|
|
4475
4738
|
}
|
|
4476
4739
|
async function checkSkillAttachmentDrift(client, assistantId, desiredSkillIds) {
|
|
4477
4740
|
try {
|
|
@@ -4575,7 +4838,7 @@ async function previewChanges(appConfig, existingClient) {
|
|
|
4575
4838
|
changes.push(...skillChanges);
|
|
4576
4839
|
}
|
|
4577
4840
|
if (config.resources.assistants) {
|
|
4578
|
-
const assistantChanges = await previewAssistants(config.resources.assistants, loader, stateManager, client);
|
|
4841
|
+
const assistantChanges = await previewAssistants(config.resources.assistants, loader, stateManager, client, config);
|
|
4579
4842
|
changes.push(...assistantChanges);
|
|
4580
4843
|
}
|
|
4581
4844
|
if (config.resources.datasources) {
|
|
@@ -4809,6 +5072,32 @@ var DependencyValidator = class {
|
|
|
4809
5072
|
}
|
|
4810
5073
|
return errors;
|
|
4811
5074
|
}
|
|
5075
|
+
/**
|
|
5076
|
+
* Validate assistant skill references.
|
|
5077
|
+
* Check that all skill names point to resources.skills or imported.skills entries.
|
|
5078
|
+
*/
|
|
5079
|
+
static validateAssistantSkillReferences(assistants, managedSkills, importedSkills) {
|
|
5080
|
+
const errors = [];
|
|
5081
|
+
for (const managedSkill of managedSkills) {
|
|
5082
|
+
const importedSkill = importedSkills.find((skill) => namesEqual(skill.name, managedSkill.name));
|
|
5083
|
+
if (importedSkill) {
|
|
5084
|
+
errors.push(
|
|
5085
|
+
`Skill "${managedSkill.name}" is defined in both resources.skills and imported.skills. Keep project-owned skills in resources.skills or external skills in imported.skills, not both.`
|
|
5086
|
+
);
|
|
5087
|
+
}
|
|
5088
|
+
}
|
|
5089
|
+
const isSkillAvailable = (skillName) => managedSkills.some((skill) => namesEqual(skill.name, skillName)) || importedSkills.some((skill) => namesEqual(skill.name, skillName));
|
|
5090
|
+
for (const assistant of assistants) {
|
|
5091
|
+
for (const skillName of assistant.skills || []) {
|
|
5092
|
+
if (!isSkillAvailable(skillName)) {
|
|
5093
|
+
errors.push(
|
|
5094
|
+
`Assistant "${assistant.name}": Skill reference "${skillName}" not found in resources.skills or imported.skills`
|
|
5095
|
+
);
|
|
5096
|
+
}
|
|
5097
|
+
}
|
|
5098
|
+
}
|
|
5099
|
+
return errors;
|
|
5100
|
+
}
|
|
4812
5101
|
};
|
|
4813
5102
|
|
|
4814
5103
|
// src/validate.ts
|
|
@@ -4892,6 +5181,12 @@ async function validateConfig(options) {
|
|
|
4892
5181
|
if (config.resources.assistants && config.resources.assistants.length > 0) {
|
|
4893
5182
|
const dependencyErrors = DependencyValidator.validateAssistantDependencies(config.resources.assistants);
|
|
4894
5183
|
errors.push(...dependencyErrors);
|
|
5184
|
+
const skillReferenceErrors = DependencyValidator.validateAssistantSkillReferences(
|
|
5185
|
+
config.resources.assistants,
|
|
5186
|
+
config.resources.skills || [],
|
|
5187
|
+
config.imported?.skills || []
|
|
5188
|
+
);
|
|
5189
|
+
errors.push(...skillReferenceErrors);
|
|
4895
5190
|
}
|
|
4896
5191
|
if (config.resources.workflows && config.resources.workflows.length > 0) {
|
|
4897
5192
|
const workflowsWithContent = config.resources.workflows.map((workflow) => {
|