package-new-workshop-agent.py 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. """Package the new workshop Agent handoff documents; perform no deployment."""
  2. from pathlib import Path
  3. import hashlib
  4. import json
  5. import os
  6. import re
  7. import tarfile
  8. import zipfile
  9. ROOT = Path(__file__).resolve().parents[1]
  10. DOCS = Path(os.environ.get('KSF_NEW_AGENT_HANDOFF_DIR', 'F:/文档/新数字人交接'))
  11. DEST = ROOT / 'work/delivery-by-recipient-20261102'
  12. DOCUMENT_NAMES = [
  13. 'README.md',
  14. '00-你需要手动做什么.txt',
  15. '01-发给运维001的接入说明.md',
  16. '02-新数字人角色说明.txt',
  17. '03-新数字人工作手册.md',
  18. '04-现有成果与接手范围.md',
  19. '05-验收与回报清单.md',
  20. '06-安装材料索引.json',
  21. ]
  22. def digest(data):
  23. return hashlib.sha256(data).hexdigest()
  24. def require(condition, message):
  25. if not condition:
  26. raise ValueError(message)
  27. def manifest_entries(data):
  28. result = {}
  29. for line in data.decode('utf-8-sig').splitlines():
  30. if not line.strip():
  31. continue
  32. match = re.fullmatch(r'([a-f0-9]{64}) (.+)', line)
  33. require(match is not None, 'Malformed manifest')
  34. sha, name = match.groups()
  35. require(name not in result, 'Duplicate manifest entry')
  36. require(not name.startswith('/') and '..' not in name.split('/'), 'Unsafe member')
  37. result[name] = sha
  38. return result
  39. def read_package(path, expected_sha, expected_bytes, manifest_name, expected_count, prefix=''):
  40. data = path.read_bytes()
  41. require(len(data) == expected_bytes and digest(data) == expected_sha, f'Package mismatch: {path.name}')
  42. if path.suffix == '.tgz':
  43. with tarfile.open(path) as archive:
  44. members = archive.getmembers()
  45. require(all(not m.issym() and not m.islnk() for m in members), 'Unexpected archive link')
  46. files = {m.name: archive.extractfile(m).read() for m in members if m.isfile()}
  47. else:
  48. with zipfile.ZipFile(path) as archive:
  49. require(archive.testzip() is None, 'Corrupt input ZIP')
  50. require(len(archive.namelist()) == len(set(archive.namelist())), 'Duplicate ZIP members')
  51. files = {name: archive.read(name) for name in archive.namelist() if not name.endswith('/')}
  52. entries = manifest_entries(files[manifest_name])
  53. require(len(entries) == expected_count, 'Unexpected manifest size')
  54. for name, sha in entries.items():
  55. require(digest(files[prefix + name]) == sha, f'Member mismatch: {name}')
  56. return files, {
  57. 'filename': path.name,
  58. 'bytes': len(data),
  59. 'sha256': digest(data),
  60. 'knownLocalPath': path.as_posix(),
  61. 'manifestPath': manifest_name,
  62. 'manifestVerified': f'{len(entries)}/{expected_count}',
  63. 'includedInHandoffZip': False,
  64. }
  65. def main():
  66. v4, v4_info = read_package(
  67. Path('D:/下载/WXWork/1688857450794210/Cache/File/2026-11/ksf-adapter-patch-v4-20261102.tgz'),
  68. 'fc1888c99160e8af28a69355e78c91d1f4f8b3f612c1cc0f0fb0d1f4f675f0a5', 65842,
  69. 'adapter-patch-v4/MANIFEST.sha256', 21, 'adapter-patch-v4/',
  70. )
  71. dedup, dedup_info = read_package(
  72. DEST / 'ksf-workshop-dedup-20261102.zip',
  73. '96fc5c10557b7601b63767e293bcfc33fcea1058f21961949348c105cd75afde', 339939,
  74. 'FILES.sha256', 34,
  75. )
  76. hotfix, hotfix_info = read_package(
  77. DEST / 'ksf-wecom-tool-result-hotfix-20261102.zip',
  78. 'ea1cd7e2663b8d67f0ca12381c131bddd4edd51e45518fb55fb04c4d836538e8', 39426,
  79. 'FILES.sha256', 14,
  80. )
  81. skill_prefix = 'skills/ksf-workshop-runtime/'
  82. skill_entries = manifest_entries(dedup[skill_prefix + 'FILES.sha256'])
  83. require(len(skill_entries) == 10, 'Skill manifest count changed')
  84. for name, sha in skill_entries.items():
  85. require(digest(dedup[skill_prefix + name]) == sha, 'Skill manifest mismatch')
  86. final_files = []
  87. for package_info, files, member, target in [
  88. (v4_info, v4, 'adapter-patch-v4/aibot_identity.py', '/opt/hermes/plugins/platforms/wecom/aibot_identity.py'),
  89. (v4_info, v4, 'adapter-patch-v4/patches/adapter.py.patched', '/opt/hermes/plugins/platforms/wecom/adapter.py'),
  90. (v4_info, v4, 'adapter-patch-v4/plugin/wecom-identity/plugin.yaml', '/opt/data/plugins/wecom-identity/plugin.yaml'),
  91. (v4_info, v4, 'adapter-patch-v4/plugin/wecom-identity/__init__.py', '/opt/data/plugins/wecom-identity/__init__.py'),
  92. (hotfix_info, hotfix, 'integrations/wecom-identity/identity_bridge.py', '/opt/hermes/plugins/platforms/wecom/identity_bridge.py'),
  93. (hotfix_info, hotfix, 'integrations/wecom-identity/workshop_registration.py', '/opt/hermes/plugins/platforms/wecom/workshop_registration.py'),
  94. ]:
  95. final_files.append({'package': package_info['filename'], 'member': member,
  96. 'targetForVerifiedLayout': target, 'sha256': digest(files[member])})
  97. index = {
  98. 'version': 'ksf.new-workshop-agent-handoff.v1',
  99. 'preparedDate': '2026-11-02',
  100. 'purpose': '新实例接入材料索引;只核对原件与清单,不是首次开通器或部署脚本',
  101. 'suggestedNameOnly': '康师傅工作坊助手001',
  102. 'newAgentCreated': False,
  103. 'newAgentRegistered': False,
  104. 'newTargetChanged': False,
  105. 'configuredNameTriggersRegistration': False,
  106. 'platform': 'Hermes + WeCom aibot WebSocket',
  107. 'verifiedLayout': {'hermesVersion': '0.21.3', 'gatewayUser': 'hermes', 'uid': 10000,
  108. 'hermesHome': '/opt/data', 'requiresNewTargetVerification': True},
  109. 'packages': [v4_info, dedup_info, hotfix_info],
  110. 'adapterRequiredBaselineSha256': digest(v4['adapter-patch-v4/baseline/adapter.py']),
  111. 'finalPlatformFiles': final_files,
  112. 'skill': {'package': dedup_info['filename'], 'prefix': skill_prefix,
  113. 'files': skill_entries, 'manifestVerified': '10/10',
  114. 'metadataManifestSha256': digest(dedup[skill_prefix + 'FILES.sha256'])},
  115. 'toolNames': ['wecom_identity_current', 'ksf_register_current_employee'],
  116. 'contractMarkers': ['ksf.agent-self.v1', 'ksf.wecom-aibot-sender.v1', 'ksf.ingest-dedup.v1'],
  117. 'operatorNotes': [
  118. '原安装包不包含在本交接ZIP;运维可按名称和完整指纹复用已收到的原件。',
  119. '旧收据的未部署状态仅指打包时;旧测试实例已验,新实例仍待验。',
  120. '现有后台先回读,不运行旧身份部署覆盖当前查重合同。',
  121. 'install-workshop-skill.mjs是升级器;新实例运行时/独立凭据须经受控首次开通。',
  122. '新bot账号与最终名称需一次受控Agent建档及registration.json绑定;名称本身不触发登记。',
  123. '不复制旧私钥、信封、credential-binding、Agent/员工状态、instance或context-K。',
  124. '全流程与查重脚本含旧固定任务键;新实例不得原样重放。',
  125. '本次核验是档案和指纹核验;不代表新实例功能验收已通过。',
  126. ],
  127. }
  128. (DOCS / '06-安装材料索引.json').write_text(json.dumps(index, ensure_ascii=False, indent=2) + '\n', encoding='utf-8')
  129. files = {name: (DOCS / name).read_bytes() for name in DOCUMENT_NAMES}
  130. require(all(data for data in files.values()), 'Empty handoff document')
  131. for name in ['00-你需要手动做什么.txt', '01-发给运维001的接入说明.md',
  132. '02-新数字人角色说明.txt', '03-新数字人工作手册.md']:
  133. text = files[name].decode('utf-8')
  134. require('ksf_register_current_employee' in text, 'Missing real tool name')
  135. require('ksfregistercurrent' not in text, 'Incorrect tool alias')
  136. for text in files.values():
  137. require(b'-----BEGIN PRIVATE KEY-----' not in text, 'Unexpected private key')
  138. files['FILES.sha256'] = ''.join(f'{digest(data)} {name}\n' for name, data in files.items()).encode('utf-8')
  139. (DOCS / 'FILES.sha256').write_bytes(files['FILES.sha256'])
  140. archive_path = DOCS / 'ksf-new-workshop-agent-handoff-20261102.zip'
  141. with zipfile.ZipFile(archive_path, 'w', compression=zipfile.ZIP_DEFLATED) as archive:
  142. for name, data in files.items():
  143. info = zipfile.ZipInfo(name, date_time=(2026, 11, 2, 0, 0, 0))
  144. info.compress_type = zipfile.ZIP_DEFLATED
  145. info.external_attr = 0o100644 << 16
  146. archive.writestr(info, data)
  147. with zipfile.ZipFile(archive_path) as archive:
  148. require(archive.testzip() is None, 'Corrupt output ZIP')
  149. require(set(archive.namelist()) == set(files), 'Unexpected output member')
  150. entries = manifest_entries(archive.read('FILES.sha256'))
  151. require(len(entries) == 8, 'Unexpected handoff document count')
  152. for name, sha in entries.items():
  153. require(digest(archive.read(name)) == sha, 'Handoff manifest mismatch')
  154. receipt = {
  155. 'file': archive_path.name, 'bytes': archive_path.stat().st_size,
  156. 'sha256': digest(archive_path.read_bytes()), 'archiveIntegrity': True,
  157. 'manifestVerified': '8/8', 'zipEntries': 9,
  158. 'originalPackageManifestsVerified': ['21/21', '34/34', '14/14'],
  159. 'skillManifestVerified': '10/10', 'contents': 'handoff-documents-only',
  160. 'includesInstallationArchives': False, 'includesCredentials': False,
  161. 'newAgentCreated': False, 'newAgentRegistered': False, 'newTargetChanged': False,
  162. }
  163. (DOCS / 'ksf-new-workshop-agent-handoff-20261102.receipt.json').write_text(
  164. json.dumps(receipt, ensure_ascii=False, indent=2) + '\n', encoding='utf-8')
  165. print(json.dumps(receipt, ensure_ascii=False, indent=2))
  166. if __name__ == '__main__':
  167. main()