@fitlab-ai/agent-infra 0.11.8 → 0.11.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/lib/agent-clients/custom-tool-invocations.js +92 -0
- package/dist/lib/agent-clients/next-steps.js +1 -1
- package/dist/lib/agent-clients/reconcile.js +3 -3
- package/dist/lib/agent-clients/schema.js +18 -10
- package/dist/lib/init.js +2 -2
- package/dist/lib/internal/agent-client.js +3 -3
- package/dist/lib/sandbox/recovery.js +1 -22
- package/dist/lib/sandbox/tools.js +2 -0
- package/dist/lib/task/finalization.js +90 -93
- package/dist/lib/task/verification.js +0 -1
- package/dist/lib/update.js +2 -2
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/custom-tool-invocations.ts +129 -0
- package/lib/agent-clients/next-steps.ts +3 -3
- package/lib/agent-clients/reconcile.ts +3 -3
- package/lib/agent-clients/schema.ts +18 -10
- package/lib/init.ts +2 -2
- package/lib/internal/agent-client.ts +3 -3
- package/lib/sandbox/recovery.ts +0 -25
- package/lib/sandbox/tool-types.ts +1 -0
- package/lib/sandbox/tools.ts +2 -0
- package/lib/task/finalization.ts +91 -88
- package/lib/task/verification.ts +1 -2
- package/lib/update.ts +2 -3
- package/package.json +1 -1
- package/templates/.agents/README.en.md +2 -50
- package/templates/.agents/README.zh-CN.md +2 -50
- package/templates/.agents/rules/next-step-output.en.md +1 -1
- package/templates/.agents/rules/next-step-output.zh-CN.md +1 -1
- package/templates/.agents/scripts/lib/agent-infra-package.js +0 -7
- package/templates/.agents/skills/commit/SKILL.en.md +1 -1
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +1 -1
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +1 -1
- package/templates/.agents/skills/review-code/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-code/reference/output-templates.en.md +15 -6
- package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +15 -6
- package/templates/.agents/skills/update-agent-infra/SKILL.en.md +1 -1
- package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +2 -268
- package/dist/lib/agent-clients/custom-tuis.js +0 -99
- package/lib/agent-clients/custom-tuis.ts +0 -149
|
@@ -225,17 +225,6 @@ const AGENT_CLIENT_MANIFEST = [
|
|
|
225
225
|
}
|
|
226
226
|
}
|
|
227
227
|
];
|
|
228
|
-
const CUSTOM_TUI_CONTRACT = {
|
|
229
|
-
"requiredFields": [
|
|
230
|
-
"name",
|
|
231
|
-
"dir",
|
|
232
|
-
"invoke"
|
|
233
|
-
],
|
|
234
|
-
"allowedPlaceholders": [
|
|
235
|
-
"skillName",
|
|
236
|
-
"projectName"
|
|
237
|
-
]
|
|
238
|
-
};
|
|
239
228
|
// Add a new identifier here only after shipping matching .{platform}. template variants.
|
|
240
229
|
const KNOWN_PLATFORMS = new Set(['github', 'none']);
|
|
241
230
|
const KNOWN_LANGUAGES = new Set(['en', 'zh-CN']);
|
|
@@ -444,10 +433,6 @@ function trustedBaseline(value) {
|
|
|
444
433
|
return typeof value === 'string' && /^sha256:[a-f0-9]{64}$/.test(value) ? value : null;
|
|
445
434
|
}
|
|
446
435
|
|
|
447
|
-
function normDir(p) {
|
|
448
|
-
return norm(p).replace(/^\.\//, '').replace(/\/+$/, '');
|
|
449
|
-
}
|
|
450
|
-
|
|
451
436
|
function isInsideProject(projectRoot, relativePath) {
|
|
452
437
|
if (typeof relativePath !== 'string' || relativePath.trim() === '' || path.isAbsolute(relativePath)) {
|
|
453
438
|
return false;
|
|
@@ -607,14 +592,6 @@ function isCustomProtected(targetPath, customSkills, customCommandTargets) {
|
|
|
607
592
|
)) || customCommandTargets.has(normalized);
|
|
608
593
|
}
|
|
609
594
|
|
|
610
|
-
function recordCustomTUISkipped(report, entry) {
|
|
611
|
-
report?.custom?.customTUIs?.skipped?.push(entry);
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
function recordCustomTUISkippedRef(report, entry) {
|
|
615
|
-
report?.custom?.customTUIs?.skippedRefs?.push(entry);
|
|
616
|
-
}
|
|
617
|
-
|
|
618
595
|
function expandHome(inputPath) {
|
|
619
596
|
if (inputPath === '~') return os.homedir();
|
|
620
597
|
if (inputPath.startsWith('~/')) {
|
|
@@ -856,204 +833,6 @@ function generateAgentClientCommand(skill, lang, descriptor) {
|
|
|
856
833
|
return `${lines.join('\n')}\n`;
|
|
857
834
|
}
|
|
858
835
|
|
|
859
|
-
function validateCustomTUIs(projectRoot, customTUIs, report) {
|
|
860
|
-
if (customTUIs === undefined) return [];
|
|
861
|
-
if (!Array.isArray(customTUIs)) {
|
|
862
|
-
recordCustomTUISkipped(report, {
|
|
863
|
-
index: -1,
|
|
864
|
-
name: '',
|
|
865
|
-
dir: '',
|
|
866
|
-
reason: 'customTUIs must be an array'
|
|
867
|
-
});
|
|
868
|
-
return [];
|
|
869
|
-
}
|
|
870
|
-
const tools = customTUIs;
|
|
871
|
-
return tools
|
|
872
|
-
.map((tool, index) => {
|
|
873
|
-
if (
|
|
874
|
-
typeof tool !== 'object'
|
|
875
|
-
|| tool === null
|
|
876
|
-
|| Array.isArray(tool)
|
|
877
|
-
) {
|
|
878
|
-
recordCustomTUISkipped(report, {
|
|
879
|
-
index,
|
|
880
|
-
name: '',
|
|
881
|
-
dir: '',
|
|
882
|
-
reason: 'invalid custom TUI'
|
|
883
|
-
});
|
|
884
|
-
return null;
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
for (const field of CUSTOM_TUI_CONTRACT.requiredFields) {
|
|
888
|
-
if (
|
|
889
|
-
typeof tool[field] !== 'string'
|
|
890
|
-
|| tool[field].trim() === ''
|
|
891
|
-
|| /[\r\n]/.test(tool[field])
|
|
892
|
-
) {
|
|
893
|
-
recordCustomTUISkipped(report, {
|
|
894
|
-
index,
|
|
895
|
-
name: String(tool.name || ''),
|
|
896
|
-
dir: String(tool.dir || ''),
|
|
897
|
-
reason: `invalid ${field}`
|
|
898
|
-
});
|
|
899
|
-
return null;
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
if (typeof tool?.dir !== 'string' || tool.dir.trim() === '') {
|
|
904
|
-
recordCustomTUISkipped(report, {
|
|
905
|
-
index,
|
|
906
|
-
name: String(tool?.name || ''),
|
|
907
|
-
dir: String(tool?.dir || ''),
|
|
908
|
-
reason: 'invalid dir'
|
|
909
|
-
});
|
|
910
|
-
return null;
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
if (tool.dir.includes('\\')) {
|
|
914
|
-
recordCustomTUISkipped(report, {
|
|
915
|
-
index,
|
|
916
|
-
name: tool.name,
|
|
917
|
-
dir: tool.dir,
|
|
918
|
-
reason: 'dir must use POSIX separators'
|
|
919
|
-
});
|
|
920
|
-
return null;
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
if (!isInsideProject(projectRoot, tool.dir)) {
|
|
924
|
-
recordCustomTUISkipped(report, {
|
|
925
|
-
index,
|
|
926
|
-
name: String(tool?.name || ''),
|
|
927
|
-
dir: tool.dir,
|
|
928
|
-
reason: 'dir must be a relative path inside the project root'
|
|
929
|
-
});
|
|
930
|
-
return null;
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
const placeholders = [...tool.invoke.matchAll(/\$\{([^}]+)\}/g)]
|
|
934
|
-
.map((match) => match[1]);
|
|
935
|
-
if (
|
|
936
|
-
!placeholders.includes('skillName')
|
|
937
|
-
|| placeholders.some((placeholder) =>
|
|
938
|
-
!CUSTOM_TUI_CONTRACT.allowedPlaceholders.includes(placeholder)
|
|
939
|
-
)
|
|
940
|
-
|| tool.invoke
|
|
941
|
-
.replaceAll('${skillName}', '')
|
|
942
|
-
.replaceAll('${projectName}', '')
|
|
943
|
-
.includes('${')
|
|
944
|
-
) {
|
|
945
|
-
recordCustomTUISkipped(report, {
|
|
946
|
-
index,
|
|
947
|
-
name: tool.name,
|
|
948
|
-
dir: tool.dir,
|
|
949
|
-
reason: 'invalid invoke placeholders'
|
|
950
|
-
});
|
|
951
|
-
return null;
|
|
952
|
-
}
|
|
953
|
-
|
|
954
|
-
return { ...tool, index, dir: normDir(tool.dir) };
|
|
955
|
-
})
|
|
956
|
-
.filter(Boolean);
|
|
957
|
-
}
|
|
958
|
-
|
|
959
|
-
function customTUITargetPath(tool, refFile, refSkillName, skillName) {
|
|
960
|
-
const targetFile = refFile.includes(refSkillName)
|
|
961
|
-
? refFile.replaceAll(refSkillName, skillName)
|
|
962
|
-
: `${skillName}${path.extname(refFile)}`;
|
|
963
|
-
return norm(path.join(tool.dir, targetFile));
|
|
964
|
-
}
|
|
965
|
-
|
|
966
|
-
function findCustomTUIReference(projectRoot, tool, templateSkillNames, report, logSkipped = false) {
|
|
967
|
-
const cmdDir = path.join(projectRoot, tool.dir);
|
|
968
|
-
if (!fs.existsSync(cmdDir) || !fs.statSync(cmdDir).isDirectory()) {
|
|
969
|
-
if (logSkipped) {
|
|
970
|
-
recordCustomTUISkipped(report, {
|
|
971
|
-
index: tool.index,
|
|
972
|
-
name: String(tool.name || ''),
|
|
973
|
-
dir: tool.dir,
|
|
974
|
-
reason: 'directory not found'
|
|
975
|
-
});
|
|
976
|
-
}
|
|
977
|
-
return null;
|
|
978
|
-
}
|
|
979
|
-
|
|
980
|
-
const cmdFiles = fs.readdirSync(cmdDir)
|
|
981
|
-
.filter((file) => fs.statSync(path.join(cmdDir, file)).isFile())
|
|
982
|
-
.sort((left, right) => left.localeCompare(right));
|
|
983
|
-
if (cmdFiles.length === 0) {
|
|
984
|
-
if (logSkipped) {
|
|
985
|
-
recordCustomTUISkipped(report, {
|
|
986
|
-
index: tool.index,
|
|
987
|
-
name: String(tool.name || ''),
|
|
988
|
-
dir: tool.dir,
|
|
989
|
-
reason: 'no command files'
|
|
990
|
-
});
|
|
991
|
-
}
|
|
992
|
-
return null;
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
let sawKnownSkillReference = false;
|
|
996
|
-
|
|
997
|
-
for (const file of cmdFiles) {
|
|
998
|
-
const content = fs.readFileSync(path.join(cmdDir, file), 'utf8');
|
|
999
|
-
const match = content.match(/\.agents\/skills\/([^/]+)\/SKILL\.md/);
|
|
1000
|
-
if (!match) continue;
|
|
1001
|
-
|
|
1002
|
-
const skillName = match[1];
|
|
1003
|
-
if (!templateSkillNames.has(skillName)) continue;
|
|
1004
|
-
|
|
1005
|
-
const skillMd = path.join(projectRoot, '.agents/skills', skillName, 'SKILL.md');
|
|
1006
|
-
if (!fs.existsSync(skillMd)) continue;
|
|
1007
|
-
|
|
1008
|
-
const meta = parseSkillFrontmatter(skillMd);
|
|
1009
|
-
if (!meta.description) continue;
|
|
1010
|
-
|
|
1011
|
-
sawKnownSkillReference = true;
|
|
1012
|
-
if (!content.includes(meta.description)) {
|
|
1013
|
-
if (logSkipped) {
|
|
1014
|
-
recordCustomTUISkippedRef(report, {
|
|
1015
|
-
index: tool.index,
|
|
1016
|
-
name: String(tool.name || ''),
|
|
1017
|
-
dir: tool.dir,
|
|
1018
|
-
file,
|
|
1019
|
-
skill: skillName,
|
|
1020
|
-
reason: 'description not found in reference command file'
|
|
1021
|
-
});
|
|
1022
|
-
}
|
|
1023
|
-
continue;
|
|
1024
|
-
}
|
|
1025
|
-
|
|
1026
|
-
return { content, file, skillName, skillDesc: meta.description };
|
|
1027
|
-
}
|
|
1028
|
-
|
|
1029
|
-
if (logSkipped) {
|
|
1030
|
-
recordCustomTUISkipped(report, {
|
|
1031
|
-
index: tool.index,
|
|
1032
|
-
name: String(tool.name || ''),
|
|
1033
|
-
dir: tool.dir,
|
|
1034
|
-
reason: sawKnownSkillReference
|
|
1035
|
-
? 'no reference command file with matching description'
|
|
1036
|
-
: 'no usable reference command file'
|
|
1037
|
-
});
|
|
1038
|
-
}
|
|
1039
|
-
|
|
1040
|
-
return null;
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
function buildCustomTUICommandTargets(projectRoot, customSkills, customTUIs, templateSkillNames) {
|
|
1044
|
-
const targets = new Set();
|
|
1045
|
-
for (const tool of customTUIs) {
|
|
1046
|
-
const ref = findCustomTUIReference(projectRoot, tool, templateSkillNames, null, false);
|
|
1047
|
-
if (!ref) continue;
|
|
1048
|
-
|
|
1049
|
-
for (const skill of customSkills) {
|
|
1050
|
-
targets.add(customTUITargetPath(tool, ref.file, ref.skillName, skill.dirName));
|
|
1051
|
-
}
|
|
1052
|
-
}
|
|
1053
|
-
|
|
1054
|
-
return targets;
|
|
1055
|
-
}
|
|
1056
|
-
|
|
1057
836
|
function buildBuiltinCustomCommandTargets(customSkills, enabledTUIs) {
|
|
1058
837
|
return new Set(AGENT_CLIENT_MANIFEST.flatMap((adapter) => {
|
|
1059
838
|
if (!enabledTUIs.has(adapter.id) || !adapter.customCommand) return [];
|
|
@@ -1063,33 +842,10 @@ function buildBuiltinCustomCommandTargets(customSkills, enabledTUIs) {
|
|
|
1063
842
|
}));
|
|
1064
843
|
}
|
|
1065
844
|
|
|
1066
|
-
function learnAndGenerateCommands(projectRoot, customSkills, tool, templateSkillNames, report) {
|
|
1067
|
-
const ref = findCustomTUIReference(projectRoot, tool, templateSkillNames, report, true);
|
|
1068
|
-
if (!ref) return;
|
|
1069
|
-
|
|
1070
|
-
for (const skill of customSkills) {
|
|
1071
|
-
const descToken = '__AGENT_INFRA_CUSTOM_SKILL_DESCRIPTION__';
|
|
1072
|
-
const generated = ref.content
|
|
1073
|
-
.replaceAll(ref.skillDesc, descToken)
|
|
1074
|
-
.replaceAll(ref.skillName, skill.dirName)
|
|
1075
|
-
.replaceAll(descToken, skill.description);
|
|
1076
|
-
|
|
1077
|
-
writeIfChanged(
|
|
1078
|
-
projectRoot,
|
|
1079
|
-
customTUITargetPath(tool, ref.file, ref.skillName, skill.dirName),
|
|
1080
|
-
generated,
|
|
1081
|
-
report.custom.commands
|
|
1082
|
-
);
|
|
1083
|
-
}
|
|
1084
|
-
}
|
|
1085
|
-
|
|
1086
845
|
function generateCustomCommands(
|
|
1087
|
-
projectRoot,
|
|
1088
846
|
customSkills,
|
|
1089
847
|
lang,
|
|
1090
848
|
report,
|
|
1091
|
-
customTUIs,
|
|
1092
|
-
templateSkillNames,
|
|
1093
849
|
enabledTUIs,
|
|
1094
850
|
managedWriter
|
|
1095
851
|
) {
|
|
@@ -1107,11 +863,6 @@ function generateCustomCommands(
|
|
|
1107
863
|
);
|
|
1108
864
|
}
|
|
1109
865
|
}
|
|
1110
|
-
|
|
1111
|
-
const tools = Array.isArray(customTUIs) ? customTUIs : [];
|
|
1112
|
-
for (const tool of tools) {
|
|
1113
|
-
learnAndGenerateCommands(projectRoot, customSkills, tool, templateSkillNames, report);
|
|
1114
|
-
}
|
|
1115
866
|
}
|
|
1116
867
|
|
|
1117
868
|
function matchesAny(rel, patterns) {
|
|
@@ -1277,9 +1028,7 @@ function syncTemplates(projectRoot, templateRootOverride) {
|
|
|
1277
1028
|
const configPathRel = norm(path.relative(projectRoot, cfgPath));
|
|
1278
1029
|
let templateRoot = templateRootOverride;
|
|
1279
1030
|
if (!templateRoot) {
|
|
1280
|
-
const packageResolution = resolveAgentInfraPackage(
|
|
1281
|
-
startPath: path.join(projectRoot, '.agents', 'scripts', 'lib', 'agent-infra-package.js')
|
|
1282
|
-
});
|
|
1031
|
+
const packageResolution = resolveAgentInfraPackage();
|
|
1283
1032
|
if (packageResolution.templateRoot && fs.existsSync(packageResolution.templateRoot)) {
|
|
1284
1033
|
templateRoot = packageResolution.templateRoot;
|
|
1285
1034
|
} else {
|
|
@@ -1303,7 +1052,6 @@ function syncTemplates(projectRoot, templateRootOverride) {
|
|
|
1303
1052
|
AGENT_CLIENT_IDS.filter((id) => enabledResolution.state[id].enabled)
|
|
1304
1053
|
);
|
|
1305
1054
|
materializeAgentClientConfig(cfg, enabledResolution);
|
|
1306
|
-
const customTUIsConfig = Array.isArray(cfg.customTUIs) ? cfg.customTUIs : [];
|
|
1307
1055
|
const vars = { project, org };
|
|
1308
1056
|
const templateSkillNames = listTemplateSkillNames(templateRoot);
|
|
1309
1057
|
const protectedCustomSkills = detectCustomSkills(projectRoot, templateSkillNames);
|
|
@@ -1390,24 +1138,13 @@ function syncTemplates(projectRoot, templateRootOverride) {
|
|
|
1390
1138
|
unchanged: [],
|
|
1391
1139
|
removed: [],
|
|
1392
1140
|
sourceErrors: [],
|
|
1393
|
-
customTUIs: { skipped: [], skippedRefs: [] },
|
|
1394
1141
|
commands: { generated: [], updated: [], unchanged: [] }
|
|
1395
1142
|
},
|
|
1396
1143
|
ejected: { created: [], skipped: [] },
|
|
1397
1144
|
merged: { pending: [] },
|
|
1398
1145
|
configUpdated: false
|
|
1399
1146
|
};
|
|
1400
|
-
const
|
|
1401
|
-
const customTUICommandTargets = buildCustomTUICommandTargets(
|
|
1402
|
-
projectRoot,
|
|
1403
|
-
protectedCustomSkills,
|
|
1404
|
-
customTUIs,
|
|
1405
|
-
templateSkillNames
|
|
1406
|
-
);
|
|
1407
|
-
const customCommandTargets = new Set([
|
|
1408
|
-
...buildBuiltinCustomCommandTargets(protectedCustomSkills, enabledTUIs),
|
|
1409
|
-
...customTUICommandTargets
|
|
1410
|
-
]);
|
|
1147
|
+
const customCommandTargets = buildBuiltinCustomCommandTargets(protectedCustomSkills, enabledTUIs);
|
|
1411
1148
|
|
|
1412
1149
|
for (const category of ['managed', 'merged', 'ejected']) {
|
|
1413
1150
|
const before = currentRegistry[category];
|
|
@@ -1896,12 +1633,9 @@ function syncTemplates(projectRoot, templateRootOverride) {
|
|
|
1896
1633
|
const customSkills = detectCustomSkills(projectRoot, templateSkillNames);
|
|
1897
1634
|
report.custom.detected = customSkills.map((skill) => skill.dirName);
|
|
1898
1635
|
generateCustomCommands(
|
|
1899
|
-
projectRoot,
|
|
1900
1636
|
customSkills,
|
|
1901
1637
|
lang,
|
|
1902
1638
|
report,
|
|
1903
|
-
customTUIs,
|
|
1904
|
-
templateSkillNames,
|
|
1905
1639
|
enabledTUIs,
|
|
1906
1640
|
writeProtectedManaged
|
|
1907
1641
|
);
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
const CUSTOM_TUI_CONTRACT = Object.freeze({
|
|
3
|
-
requiredFields: Object.freeze(['name', 'dir', 'invoke']),
|
|
4
|
-
allowedPlaceholders: Object.freeze(['skillName', 'projectName'])
|
|
5
|
-
});
|
|
6
|
-
function isRecord(value) {
|
|
7
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
8
|
-
}
|
|
9
|
-
function isNonEmptySingleLine(value) {
|
|
10
|
-
return typeof value === 'string'
|
|
11
|
-
&& value.trim() !== ''
|
|
12
|
-
&& !/[\r\n]/.test(value);
|
|
13
|
-
}
|
|
14
|
-
function normalizeDir(projectRoot, value) {
|
|
15
|
-
if (!isNonEmptySingleLine(value)
|
|
16
|
-
|| value.includes('\\')
|
|
17
|
-
|| path.isAbsolute(value)
|
|
18
|
-
|| /^[a-zA-Z]:/.test(value)) {
|
|
19
|
-
return null;
|
|
20
|
-
}
|
|
21
|
-
const normalized = path.posix.normalize(value);
|
|
22
|
-
if (normalized === '.'
|
|
23
|
-
|| normalized === '..'
|
|
24
|
-
|| normalized.startsWith('../')
|
|
25
|
-
|| normalized.split('/').includes('..')) {
|
|
26
|
-
return null;
|
|
27
|
-
}
|
|
28
|
-
const relative = path.relative(path.resolve(projectRoot), path.resolve(projectRoot, normalized));
|
|
29
|
-
return relative === ''
|
|
30
|
-
|| path.isAbsolute(relative)
|
|
31
|
-
|| relative === '..'
|
|
32
|
-
|| relative.startsWith(`..${path.sep}`)
|
|
33
|
-
? null
|
|
34
|
-
: normalized;
|
|
35
|
-
}
|
|
36
|
-
function hasValidPlaceholders(invocation) {
|
|
37
|
-
const placeholders = [
|
|
38
|
-
...invocation.matchAll(/\$\{([^}]+)\}/g)
|
|
39
|
-
].map((match) => match[1]);
|
|
40
|
-
return placeholders.includes('skillName')
|
|
41
|
-
&& placeholders.every((placeholder) => CUSTOM_TUI_CONTRACT.allowedPlaceholders.includes(placeholder))
|
|
42
|
-
&& !invocation
|
|
43
|
-
.replaceAll('${skillName}', '')
|
|
44
|
-
.replaceAll('${projectName}', '')
|
|
45
|
-
.includes('${');
|
|
46
|
-
}
|
|
47
|
-
function normalizeCustomTUIs(projectRoot, input) {
|
|
48
|
-
const items = [];
|
|
49
|
-
const diagnostics = [];
|
|
50
|
-
if (!Array.isArray(input)) {
|
|
51
|
-
return Object.freeze({
|
|
52
|
-
items: Object.freeze([]),
|
|
53
|
-
diagnostics: Object.freeze([
|
|
54
|
-
Object.freeze({
|
|
55
|
-
code: 'INVALID_CUSTOM_TUIS',
|
|
56
|
-
path: 'customTUIs'
|
|
57
|
-
})
|
|
58
|
-
])
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
for (const [index, candidate] of input.entries()) {
|
|
62
|
-
const base = `customTUIs[${index}]`;
|
|
63
|
-
if (!isRecord(candidate)) {
|
|
64
|
-
diagnostics.push({ code: 'INVALID_CUSTOM_TUI', path: base });
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
-
if (!isNonEmptySingleLine(candidate.name)) {
|
|
68
|
-
diagnostics.push({ code: 'INVALID_CUSTOM_TUI', path: `${base}.name` });
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
const dir = normalizeDir(projectRoot, candidate.dir);
|
|
72
|
-
if (!dir) {
|
|
73
|
-
diagnostics.push({ code: 'INVALID_CUSTOM_TUI', path: `${base}.dir` });
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
if (!isNonEmptySingleLine(candidate.invoke)) {
|
|
77
|
-
diagnostics.push({ code: 'INVALID_CUSTOM_TUI', path: `${base}.invoke` });
|
|
78
|
-
continue;
|
|
79
|
-
}
|
|
80
|
-
if (!hasValidPlaceholders(candidate.invoke)) {
|
|
81
|
-
diagnostics.push({
|
|
82
|
-
code: 'INVALID_CUSTOM_TUI_PLACEHOLDER',
|
|
83
|
-
path: `${base}.invoke`
|
|
84
|
-
});
|
|
85
|
-
continue;
|
|
86
|
-
}
|
|
87
|
-
items.push(Object.freeze({
|
|
88
|
-
name: candidate.name,
|
|
89
|
-
dir,
|
|
90
|
-
invocation: candidate.invoke
|
|
91
|
-
}));
|
|
92
|
-
}
|
|
93
|
-
return Object.freeze({
|
|
94
|
-
items: Object.freeze(items),
|
|
95
|
-
diagnostics: Object.freeze(diagnostics.map((diagnostic) => Object.freeze(diagnostic)))
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
export { CUSTOM_TUI_CONTRACT, normalizeCustomTUIs };
|
|
99
|
-
//# sourceMappingURL=custom-tuis.js.map
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
|
|
3
|
-
type CustomTUI = Readonly<{
|
|
4
|
-
name: string;
|
|
5
|
-
dir: string;
|
|
6
|
-
invocation: string;
|
|
7
|
-
}>;
|
|
8
|
-
|
|
9
|
-
type CustomTUIDiagnosticCode =
|
|
10
|
-
| 'INVALID_CUSTOM_TUIS'
|
|
11
|
-
| 'INVALID_CUSTOM_TUI'
|
|
12
|
-
| 'INVALID_CUSTOM_TUI_PLACEHOLDER';
|
|
13
|
-
|
|
14
|
-
type CustomTUIDiagnostic = Readonly<{
|
|
15
|
-
code: CustomTUIDiagnosticCode;
|
|
16
|
-
path: string;
|
|
17
|
-
}>;
|
|
18
|
-
|
|
19
|
-
type NormalizeCustomTUIsResult = Readonly<{
|
|
20
|
-
items: readonly CustomTUI[];
|
|
21
|
-
diagnostics: readonly CustomTUIDiagnostic[];
|
|
22
|
-
}>;
|
|
23
|
-
|
|
24
|
-
const CUSTOM_TUI_CONTRACT = Object.freeze({
|
|
25
|
-
requiredFields: Object.freeze(['name', 'dir', 'invoke']),
|
|
26
|
-
allowedPlaceholders: Object.freeze(['skillName', 'projectName'])
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
30
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function isNonEmptySingleLine(value: unknown): value is string {
|
|
34
|
-
return typeof value === 'string'
|
|
35
|
-
&& value.trim() !== ''
|
|
36
|
-
&& !/[\r\n]/.test(value);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function normalizeDir(projectRoot: string, value: unknown): string | null {
|
|
40
|
-
if (
|
|
41
|
-
!isNonEmptySingleLine(value)
|
|
42
|
-
|| value.includes('\\')
|
|
43
|
-
|| path.isAbsolute(value)
|
|
44
|
-
|| /^[a-zA-Z]:/.test(value)
|
|
45
|
-
) {
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
const normalized = path.posix.normalize(value);
|
|
49
|
-
if (
|
|
50
|
-
normalized === '.'
|
|
51
|
-
|| normalized === '..'
|
|
52
|
-
|| normalized.startsWith('../')
|
|
53
|
-
|| normalized.split('/').includes('..')
|
|
54
|
-
) {
|
|
55
|
-
return null;
|
|
56
|
-
}
|
|
57
|
-
const relative = path.relative(
|
|
58
|
-
path.resolve(projectRoot),
|
|
59
|
-
path.resolve(projectRoot, normalized)
|
|
60
|
-
);
|
|
61
|
-
return relative === ''
|
|
62
|
-
|| path.isAbsolute(relative)
|
|
63
|
-
|| relative === '..'
|
|
64
|
-
|| relative.startsWith(`..${path.sep}`)
|
|
65
|
-
? null
|
|
66
|
-
: normalized;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function hasValidPlaceholders(invocation: string): boolean {
|
|
70
|
-
const placeholders = [
|
|
71
|
-
...invocation.matchAll(/\$\{([^}]+)\}/g)
|
|
72
|
-
].map((match) => match[1]!);
|
|
73
|
-
return placeholders.includes('skillName')
|
|
74
|
-
&& placeholders.every((placeholder) =>
|
|
75
|
-
CUSTOM_TUI_CONTRACT.allowedPlaceholders.includes(placeholder)
|
|
76
|
-
)
|
|
77
|
-
&& !invocation
|
|
78
|
-
.replaceAll('${skillName}', '')
|
|
79
|
-
.replaceAll('${projectName}', '')
|
|
80
|
-
.includes('${');
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function normalizeCustomTUIs(
|
|
84
|
-
projectRoot: string,
|
|
85
|
-
input: unknown
|
|
86
|
-
): NormalizeCustomTUIsResult {
|
|
87
|
-
const items: CustomTUI[] = [];
|
|
88
|
-
const diagnostics: CustomTUIDiagnostic[] = [];
|
|
89
|
-
|
|
90
|
-
if (!Array.isArray(input)) {
|
|
91
|
-
return Object.freeze({
|
|
92
|
-
items: Object.freeze([]),
|
|
93
|
-
diagnostics: Object.freeze([
|
|
94
|
-
Object.freeze({
|
|
95
|
-
code: 'INVALID_CUSTOM_TUIS' as const,
|
|
96
|
-
path: 'customTUIs'
|
|
97
|
-
})
|
|
98
|
-
])
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
for (const [index, candidate] of input.entries()) {
|
|
103
|
-
const base = `customTUIs[${index}]`;
|
|
104
|
-
if (!isRecord(candidate)) {
|
|
105
|
-
diagnostics.push({ code: 'INVALID_CUSTOM_TUI', path: base });
|
|
106
|
-
continue;
|
|
107
|
-
}
|
|
108
|
-
if (!isNonEmptySingleLine(candidate.name)) {
|
|
109
|
-
diagnostics.push({ code: 'INVALID_CUSTOM_TUI', path: `${base}.name` });
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
|
-
const dir = normalizeDir(projectRoot, candidate.dir);
|
|
113
|
-
if (!dir) {
|
|
114
|
-
diagnostics.push({ code: 'INVALID_CUSTOM_TUI', path: `${base}.dir` });
|
|
115
|
-
continue;
|
|
116
|
-
}
|
|
117
|
-
if (!isNonEmptySingleLine(candidate.invoke)) {
|
|
118
|
-
diagnostics.push({ code: 'INVALID_CUSTOM_TUI', path: `${base}.invoke` });
|
|
119
|
-
continue;
|
|
120
|
-
}
|
|
121
|
-
if (!hasValidPlaceholders(candidate.invoke)) {
|
|
122
|
-
diagnostics.push({
|
|
123
|
-
code: 'INVALID_CUSTOM_TUI_PLACEHOLDER',
|
|
124
|
-
path: `${base}.invoke`
|
|
125
|
-
});
|
|
126
|
-
continue;
|
|
127
|
-
}
|
|
128
|
-
items.push(Object.freeze({
|
|
129
|
-
name: candidate.name,
|
|
130
|
-
dir,
|
|
131
|
-
invocation: candidate.invoke
|
|
132
|
-
}));
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
return Object.freeze({
|
|
136
|
-
items: Object.freeze(items),
|
|
137
|
-
diagnostics: Object.freeze(
|
|
138
|
-
diagnostics.map((diagnostic) => Object.freeze(diagnostic))
|
|
139
|
-
)
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
export { CUSTOM_TUI_CONTRACT, normalizeCustomTUIs };
|
|
144
|
-
export type {
|
|
145
|
-
CustomTUI,
|
|
146
|
-
CustomTUIDiagnostic,
|
|
147
|
-
CustomTUIDiagnosticCode,
|
|
148
|
-
NormalizeCustomTUIsResult
|
|
149
|
-
};
|