| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107 |
- import fs from 'node:fs';
- import path from 'node:path';
- import crypto from 'node:crypto';
- import {fileURLToPath} from 'node:url';
- const hash=b=>crypto.createHash('sha256').update(b).digest('hex');
- const expectedBefore={
- 'SKILL.md':'41d8fe2cb437525e6418e18e0fc5c95777f27235ead3fd9b0594f27b8efe16ae',
- 'scripts/ksf-secure.mjs':'33074dfaf410debd49382c7b15658ffe720099e62722f1ed981392ccf4c635d1',
- 'scripts/ksf.mjs':'7ef128f7d022bfbc15c0ec813bfd87995c6297c6efe407a563c840f014aba477',
- 'runtime-lock.json':'2605cbe8fd7be43b5969a4991f978dc0768dee5b4d65d18f7af15f75e9004a79',
- 'references/write-tasks.md':'d1564bca70d8dff6eeb3f3465fe88c0abd59c5ef30ecc61603c57985ae4fa2ef'
- };
- const expectedWorkshop={
- ...expectedBefore,
- 'SKILL.md':'09ea66572276896adb3ce8d14146914b67dba353a5b125cf0a5e51bde404cd49',
- 'scripts/ksf-secure.mjs':'d76bf8a28235441b7c7c98fec3319e7a5038c12c34b28dc98da92ad674075fa0',
- 'scripts/ksf-workshop.mjs':'14deb2145ea4714e5842110a37d568e24bcf05b760b46e46d2ccf63e103cc19b',
- 'references/workshop-tasks.md':'0ebd5252dc95e3d249c5d88a59bc55892ea3ed317eb92de410c7aefbdfa7bd8e'
- };
- const expectedTwoAgents={
- ...expectedWorkshop,
- 'scripts/ksf-identity.mjs':'5e592ab775c3b47a08de87f773a4df867f23d5040862f92b8e964142be8b40ae',
- 'scripts/ksf-workshop.mjs':'63851a10688d5363f5cd1bd07196bff03dfb8427b8c9fec83dd9aacf6440dcb0',
- 'SKILL.md':'92e305a5f64dd43d0e15b9ad093228a53b5412a774ffbfb942837f7810f69b73',
- 'scripts/ksf-secure.mjs':'bbbf35568f0f2c315b7ca5e78bc7f41ff94611bffc10f0f240be5a7e22326835',
- 'references/identity.md':'d17086c124577214c7e8156ae4214fde7aada6020d3aafe0f9387b9d2bdd4727',
- 'references/workshop-tasks.md':'d17e5084ce03a634cffd560180ceb0a640a3437d95a1b08e3c5b17f4a728805a'
- };
- const fail=code=>{throw Object.assign(Error(code),{code});};
- const expectedRegistry={...expectedWorkshop,
- 'SKILL.md':'472c17778b72cf17d6346cf479a3d58b37a4bb6fbf9c79850f3a93b901039710',
- 'references/identity.md':'2338220275d1d7dd1f0a6c4198f1b4c66a1026f167c281bd8f495a723a383403',
- 'references/workshop-tasks.md':'93108b19247ea0ef3fdd5871dc8bcc5aaff9d43e8b71ade49ef48eceba690bb9',
- 'scripts/ksf-secure.mjs':'aea216b67b5d077a05aa5ccbf78cc55f4b0975437a39b829f107d56f1eb5a3cf',
- 'scripts/ksf-workshop.mjs':'be38359c8d032cf719424c3cc6723b47011fab28435b21315e3363e3bc8b76bd',
- 'scripts/ksf-identity.mjs':'c73ec1cd5ad3302a24f15aab0ea956f163609a7f1d88d20b04b51e47f9c6ed05'
- };
- const expectedAibot={...expectedRegistry,
- 'SKILL.md':'e71ce988fdcbd8240e37b62c928a5ca0b63c74e5662ca9fc84d34a608e709e3f',
- 'references/identity.md':'19c3c321e4f0a318e062f520bc7b2568a4d2b6dac66c2685a6260999bec6b99f',
- 'references/workshop-tasks.md':'aa165aa0ff0e816fbadd72db1ac04a3ca6794ed0d4b97215ef33e1cefa179611',
- 'scripts/ksf-identity.mjs':'23165b200c1526fcbf4a3c50c95a194466ec337441100a23d956f6b3c4e1eb2b',
- 'scripts/ksf-workshop.mjs':'cc1861ac2c5fa82014447fbb303588ee8e4b595a2d854707a0f85d30d6287f72',
- 'scripts/ksf-secure.mjs':'baa6d82e7a172753efc2a28923628a17184a0aba4ea2b6ebfbc2be9545990a34'
- };
- const expectedCurrentIntegration={"runtime-lock.json":"2605cbe8fd7be43b5969a4991f978dc0768dee5b4d65d18f7af15f75e9004a79","scripts/ksf-identity-bridge.mjs":"6ba3f0cb6527bc4a0a6218a898472ac65aa3dd9f3f57b597d77efcee641a7540","scripts/ksf.mjs":"7ef128f7d022bfbc15c0ec813bfd87995c6297c6efe407a563c840f014aba477","references/identity.md":"8495c6d664fc5d445ab10af90484e39aead98ca2c8bc73e592db46ba277d4449","scripts/ksf-identity.mjs":"9224ef89a96425890d54961fda1f4fcae3403aa7c753fe1d70ac62082f2ff77e","references/workshop-tasks.md":"aa165aa0ff0e816fbadd72db1ac04a3ca6794ed0d4b97215ef33e1cefa179611","scripts/ksf-secure.mjs":"aa25ad0a11e357744fd2aa0d679cfeb3436ef3ca657bdbf6f16173daa1ae65f4","scripts/ksf-workshop.mjs":"cc1861ac2c5fa82014447fbb303588ee8e4b595a2d854707a0f85d30d6287f72","references/write-tasks.md":"d1564bca70d8dff6eeb3f3465fe88c0abd59c5ef30ecc61603c57985ae4fa2ef","SKILL.md":"d334df2c0a4b351ca0635273b669b6b88f4ac5075791fab61419cd29f56e236b"};
- // Checked local integration baseline; never accept arbitrary target edits.
- const expectedUploadBaseline={
- 'SKILL.md':'1d4f41f8541b5f033ece5362e1021e35826217296c68240bd659a05b8ea43df2',
- 'references/identity.md':'8495c6d664fc5d445ab10af90484e39aead98ca2c8bc73e592db46ba277d4449',
- 'references/workshop-tasks.md':'3db07b2d66b6c6338e50dbd01995b6db2eb9a781646e73748f7cc12351cce6b7',
- 'references/write-tasks.md':'197a02db39a78bd14803c27b455a51681b6ec32f8d50e69d6e32486a2e9fe2cc',
- 'scripts/ksf-workshop.mjs':'95d05ac21310b7ccdd204cd5d01b4dcb7fa6944694acda089f1ff32eacda4fc7',
- 'scripts/ksf-secure.mjs':'1fe19bf41e3b57aa78061a15d04d346b3a4c189452d15e98434c35a727bb81c1',
- 'scripts/ksf-identity.mjs':'9224ef89a96425890d54961fda1f4fcae3403aa7c753fe1d70ac62082f2ff77e',
- 'scripts/ksf-identity-bridge.mjs':'6ba3f0cb6527bc4a0a6218a898472ac65aa3dd9f3f57b597d77efcee641a7540',
- 'scripts/ksf.mjs':'7ef128f7d022bfbc15c0ec813bfd87995c6297c6efe407a563c840f014aba477',
- 'runtime-lock.json':'2605cbe8fd7be43b5969a4991f978dc0768dee5b4d65d18f7af15f75e9004a79'
- };
- const expectedEmployeeR1={"references/workshop-tasks.md":"032a2502cd7a74a33f800638fd2a3c3568233b7118f5a9adbfea0446ae33f30d","references/write-tasks.md":"197a02db39a78bd14803c27b455a51681b6ec32f8d50e69d6e32486a2e9fe2cc","scripts/ksf-secure.mjs":"1fe19bf41e3b57aa78061a15d04d346b3a4c189452d15e98434c35a727bb81c1","runtime-lock.json":"2605cbe8fd7be43b5969a4991f978dc0768dee5b4d65d18f7af15f75e9004a79","references/identity.md":"619568567035417f018f25b73ceb32f704274a3c5668d3b591215b41acce5ae3","scripts/ksf-identity-bridge.mjs":"6ba3f0cb6527bc4a0a6218a898472ac65aa3dd9f3f57b597d77efcee641a7540","scripts/ksf.mjs":"7ef128f7d022bfbc15c0ec813bfd87995c6297c6efe407a563c840f014aba477","scripts/ksf-identity.mjs":"9224ef89a96425890d54961fda1f4fcae3403aa7c753fe1d70ac62082f2ff77e","scripts/ksf-workshop.mjs":"95d05ac21310b7ccdd204cd5d01b4dcb7fa6944694acda089f1ff32eacda4fc7","SKILL.md":"b3161c29cf0c7b6c8a6ce1f0308d99c60acab801ca7affc52d39c4ea1c61e27e"};
- function plain(p){const s=fs.lstatSync(p);if(!s.isFile()||s.isSymbolicLink())fail('NOT_PLAIN_FILE');}
- function stateSnapshot(root){if(!fs.existsSync(root))return{};const result={};function walk(dir){if(fs.lstatSync(dir).isSymbolicLink())fail('SYMLINK_STATE_DIRECTORY');for(const e of fs.readdirSync(dir,{withFileTypes:true})){const file=path.join(dir,e.name);if(e.isSymbolicLink())fail('SYMLINK_STATE_DIRECTORY');if(e.isDirectory())walk(file);else{plain(file);result[path.relative(root,file)]=hash(fs.readFileSync(file));}}}walk(root);return result;}
- export function installWorkshopSkill(source,destination,{apply=true}={}){
- source=path.resolve(source);destination=path.resolve(destination);
- if(fs.realpathSync(destination)!==destination)fail('SYMLINK_DESTINATION');
- for(const dir of ['scripts','references']){const stat=fs.lstatSync(path.join(destination,dir));if(!stat.isDirectory()||stat.isSymbolicLink())fail('SYMLINK_SUBDIRECTORY');}
- const binding=path.join(destination,'credential-binding.json');plain(binding);const bindingHash=hash(fs.readFileSync(binding));
- const state=path.resolve(destination,'../../ksf-workshop-state');
- const beforeState=stateSnapshot(state);
- const entries=fs.readFileSync(path.join(source,'FILES.sha256'),'utf8').trim().split(/\r?\n/).map(line=>{
- const m=line.match(/^([a-f0-9]{64}) ([A-Za-z0-9_./-]+)$/);if(!m||m[2].split('/').includes('..')||path.isAbsolute(m[2]))fail('BAD_MANIFEST');
- const origin=path.resolve(source,m[2]);if(!origin.startsWith(source+path.sep))fail('BAD_MANIFEST');plain(origin);if(hash(fs.readFileSync(origin))!==m[1])fail('PACKAGE_HASH_MISMATCH');return{name:m[2],hash:m[1],origin,target:path.join(destination,m[2])};
- });
- const expectedNames=[...Object.keys(expectedWorkshop),'scripts/ksf-identity.mjs','scripts/ksf-identity-bridge.mjs','references/identity.md'].sort();
- if(JSON.stringify(entries.map(x=>x.name).sort())!==JSON.stringify(expectedNames))fail('UNEXPECTED_SKILL_FILES');
- const changes=[];
- for(const e of entries){
- if(fs.existsSync(e.target)){plain(e.target);const before=fs.readFileSync(e.target),current=hash(before);if(current===e.hash)continue;if(![expectedBefore[e.name],expectedWorkshop[e.name],expectedTwoAgents[e.name],expectedRegistry[e.name],expectedAibot[e.name],expectedCurrentIntegration[e.name],expectedUploadBaseline[e.name],expectedEmployeeR1[e.name]].includes(current))fail('TARGET_HAS_UNEXPECTED_CONTENT');changes.push({...e,before});}
- else{if(expectedBefore[e.name])fail('BASELINE_FILE_MISSING');changes.push({...e,before:null});}
- }
- const manifest={name:'FILES.sha256',origin:path.join(source,'FILES.sha256'),target:path.join(destination,'FILES.sha256')};
- plain(manifest.origin);manifest.hash=hash(fs.readFileSync(manifest.origin));
- if(fs.existsSync(manifest.target)){
- plain(manifest.target);const before=fs.readFileSync(manifest.target),current=hash(before);
- if(current!==manifest.hash){if(!['6d0ab7383ef7556c820bcb322fd8e853273273b948f79e64113dc6050e6b2055','edcafe16e164eda1e6b3b8764b2ff26eee51b697516824138561894c61d79c3f'].includes(current))fail('TARGET_MANIFEST_HAS_UNEXPECTED_CONTENT');changes.push({...manifest,before});}
- }else changes.push({...manifest,before:null});
- entries.push(manifest);
- if(!apply)return{ok:true,readOnly:true,changed:changes.map(x=>x.name),destination,filesChecked:entries.length,credentialBindingPreserved:true,stateDirectoryPreserved:true};
- const backup=changes.length?fs.mkdtempSync(path.join(path.dirname(path.dirname(destination)),'ksf-workshop-upgrade-backup-')):null;
- const done=[];
- try{
- for(const e of changes.filter(x=>x.name!=='SKILL.md').concat(changes.filter(x=>x.name==='SKILL.md'))){
- if(e.before){const file=path.join(backup,e.name);fs.mkdirSync(path.dirname(file),{recursive:true});fs.writeFileSync(file,e.before,{flag:'wx',mode:0o600});}
- if(e.before?hash(fs.readFileSync(e.target))!==hash(e.before):fs.existsSync(e.target))fail('TARGET_CHANGED_DURING_INSTALL');
- const temp=e.target+'.update-'+crypto.randomUUID();
- try{fs.copyFileSync(e.origin,temp,fs.constants.COPYFILE_EXCL);if(e.before)fs.chmodSync(temp,fs.statSync(e.target).mode&0o777);fs.renameSync(temp,e.target);done.push(e);}finally{if(fs.existsSync(temp))fs.unlinkSync(temp);}
- }
- for(const e of entries)if(hash(fs.readFileSync(e.target))!==e.hash)fail('INSTALLED_HASH_MISMATCH');
- if(hash(fs.readFileSync(binding))!==bindingHash)fail('CREDENTIAL_BINDING_CHANGED');
- if(JSON.stringify(stateSnapshot(state))!==JSON.stringify(beforeState))fail('IDENTITY_PROFILES_CHANGED');
- return{ok:true,readOnly:false,changed:changes.map(x=>x.name),destination,backup,credentialBindingPreserved:true,agentProfilePreserved:true,participantProfilesPreserved:true,stateDirectoryPreserved:true,legacyEntryPreserved:true,r7Modified:false,gatewayModified:false,discoveryVerified:false,note:'请重新加载技能;已有档案直接复用。实名还依赖服务端扩展、通讯录配置及真实消息验收,安装成功不代表已取得实名。'};
- }catch(e){for(const item of done.reverse()){if(item.before)fs.writeFileSync(item.target,item.before);else fs.unlinkSync(item.target);}throw e;}
- }
- if(process.argv[1]&&path.resolve(process.argv[1])===fileURLToPath(import.meta.url)){
- try{if(process.platform!=='linux'||!((process.argv.length===2)||(process.argv.length===3&&process.argv[2]==='--check')))fail('LINUX_CHECK_OR_NO_ARGUMENTS_REQUIRED');const root=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..');console.log(JSON.stringify(installWorkshopSkill(path.join(root,'skills/ksf-workshop-runtime'),'/opt/data/skills/ksf-workshop-runtime',{apply:process.argv[2]!=='--check'}),null,2));}
- catch(e){console.error(JSON.stringify({ok:false,error:{code:/^[A-Z][A-Z0-9_]+$/.test(e.code||'')?e.code:'INSTALL_FAILED',message:'安装停止;保留现场,未输出凭据或原始异常。'}}));process.exitCode=1;}
- }
|