@agentunion/kite 1.4.0 → 1.6.0

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.
Files changed (718) hide show
  1. package/.claude/skills/kite/checklists/feature-checklist.md +496 -0
  2. package/.claude/skills/kite/references/event-patterns.md +180 -0
  3. package/.claude/skills/kite/references/health-check.md +202 -0
  4. package/.claude/skills/kite/references/http-service.md +199 -0
  5. package/.claude/skills/kite/references/module-md-spec.md +172 -0
  6. package/.claude/skills/kite/references/multi-connection.md +147 -0
  7. package/.claude/skills/kite/references/rpc-patterns.md +199 -0
  8. package/.claude/skills/kite/references/shutdown-sequence.md +146 -0
  9. package/.claude/skills/kite/references/stdin-protocol.md +147 -0
  10. package/.claude/skills/kite/references/test-center-integration.md +178 -0
  11. package/.claude/skills/kite/references/ws-lifecycle.md +301 -0
  12. package/.claude/skills/kite/skill.md +272 -0
  13. package/.claude/skills/kite/templates/go/README.md +20 -0
  14. package/.claude/skills/kite/templates/node/entry.js +134 -0
  15. package/.claude/skills/kite/templates/node/module.md +16 -0
  16. package/.claude/skills/kite/templates/node/server.js +351 -0
  17. package/.claude/skills/kite/templates/node/server_http.js +90 -0
  18. package/.claude/skills/kite/templates/python/entry.py +425 -0
  19. package/.claude/skills/kite/templates/python/module.md +26 -0
  20. package/.claude/skills/kite/templates/python/server.py +447 -0
  21. package/.claude/skills/kite/templates/python/server_http.py +433 -0
  22. package/CHANGELOG.md +102 -0
  23. package/cli.js +78 -5
  24. package/core/dependency_checker.py +250 -0
  25. package/core/env_checker.py +586 -0
  26. package/dependencies_lock.json +128 -0
  27. package/docs/05-/347/237/255/344/277/241/350/256/244/350/257/201/344/270/216/347/224/250/346/210/267/344/277/241/346/201/257/346/216/245/345/217/243/346/226/207/346/241/243.md +507 -0
  28. package/docs/ACP/345/215/217/350/256/256/345/205/274/345/256/271/346/226/271/346/241/210.md +138 -0
  29. package/docs/CI/344/270/216AI/350/207/252/345/212/250/345/214/226/346/265/213/350/257/225/346/226/271/346/241/210.md +75 -0
  30. package/docs/CLI/345/274/200/345/217/221/350/256/241/345/210/222.md +595 -0
  31. package/docs/ClaudeCode/350/277/234/347/250/213/345/215/217/344/275/234/347/263/273/347/273/237-/346/212/200/346/234/257/350/257/204/344/274/260.md +535 -0
  32. package/docs/ClaudeCode/350/277/234/347/250/213/345/215/217/344/275/234/347/263/273/347/273/237/350/256/276/350/256/241.md +631 -0
  33. package/docs/Evol-App/344/275/277/347/224/250KernelClient/346/224/271/351/200/240/345/256/214/346/210/220.md +342 -0
  34. package/docs/Evol/346/216/247/345/210/266/345/217/260/346/217/222/344/273/266/345/214/226/346/236/266/346/236/204/346/246/202/350/246/201.md +604 -0
  35. package/docs/Evol/346/216/247/345/210/266/345/217/260/346/217/222/344/273/266/345/214/226/346/236/266/346/236/204/350/256/276/350/256/241.md +1708 -0
  36. package/docs/Evol/346/250/241/345/235/227/350/256/276/350/256/241/346/226/271/346/241/210.md +1154 -0
  37. package/docs/Evol/351/241/265/351/235/242/346/217/222/344/273/266/345/214/226-Evol/346/250/241/345/235/227/345/256/236/346/226/275/346/214/207/345/215/227.md +403 -0
  38. package/docs/Evol/351/241/265/351/235/242/346/217/222/344/273/266/345/214/226-/345/244/226/351/203/250/346/250/241/345/235/227/346/216/245/345/205/245/346/214/207/345/215/227.md +468 -0
  39. package/docs/HTTP-RPC/350/277/201/347/247/273/345/210/260WebSocket/350/256/241/345/210/222.md +318 -0
  40. package/docs/INDEX.md +388 -0
  41. package/docs/KITE_DOCS_GUIDE.md +33 -0
  42. package/docs/Kernel-Client-Kite-Token/346/224/257/346/214/201/345/256/236/346/226/275/345/256/214/346/210/220.md +330 -0
  43. package/docs/Kernel/344/270/273/345/212/250Ping/346/234/272/345/210/266-/346/255/243/347/241/256/345/256/236/347/216/260.md +235 -0
  44. package/docs/Kernel/344/270/273/345/212/250Ping/346/234/272/345/210/266/345/256/236/346/226/275/346/200/273/347/273/223.md +204 -0
  45. package/docs/Kite/345/256/211/350/243/205/351/227/256/351/242/230/350/247/243/345/206/263/346/226/271/346/241/210.md +362 -0
  46. package/docs/Kite/346/216/247/345/210/266/345/217/260/346/217/222/344/273/266/345/214/226/346/236/266/346/236/204/350/256/276/350/256/241-/347/273/210/346/236/201/347/233/256/346/240/207.md +721 -0
  47. package/docs/Kite/346/216/247/345/210/266/345/217/260/347/273/237/344/270/200WebSocket/346/224/271/351/200/240/346/226/271/346/241/210.md +821 -0
  48. package/docs/Kite/346/241/206/346/236/266/350/256/276/350/256/241/01-/346/241/206/346/236/266/345/256/232/344/275/215.md +12 -0
  49. package/docs/Kite/346/241/206/346/236/266/350/256/276/350/256/241/02-/346/240/270/345/277/203/346/246/202/345/277/265.md +341 -0
  50. package/docs/Kite/346/241/206/346/236/266/350/256/276/350/256/241/03-/347/263/273/347/273/237/346/236/266/346/236/204.md +257 -0
  51. package/docs/Kite/346/241/206/346/236/266/350/256/276/350/256/241/04-/346/250/241/345/235/227/350/247/204/350/214/203.md +263 -0
  52. package/docs/Kite/346/241/206/346/236/266/350/256/276/350/256/241/05-/346/240/270/345/277/203/346/265/201/347/250/213-/346/226/260/347/211/210.md +267 -0
  53. package/docs/Kite/346/241/206/346/236/266/350/256/276/350/256/241/05-/346/240/270/345/277/203/346/265/201/347/250/213.md +149 -0
  54. package/docs/Kite/346/241/206/346/236/266/350/256/276/350/256/241/06-/347/233/256/345/275/225/347/273/223/346/236/204.md +231 -0
  55. package/docs/Kite/346/241/206/346/236/266/350/256/276/350/256/241/07-/346/225/260/346/215/256/346/250/241/345/236/213.md +68 -0
  56. package/docs/Kite/346/241/206/346/236/266/350/256/276/350/256/241/08-/346/211/251/345/261/225/346/200/247.md +34 -0
  57. package/docs/Kite/346/241/206/346/236/266/350/256/276/350/256/241/09-/344/270/216/345/205/267/344/275/223/345/272/224/347/224/250/347/232/204/345/205/263/347/263/273.md +22 -0
  58. package/docs/Kite/346/241/206/346/236/266/350/256/276/350/256/241/README.md +46 -0
  59. package/docs/Kite/347/263/273/347/273/237/345/220/257/345/212/250/346/265/201/347/250/213.md +567 -0
  60. package/docs/Launcher/345/220/257/345/212/250/345/231/250/346/226/207/346/241/243.md +745 -0
  61. package/docs/Polyglot/350/277/220/350/241/214/346/227/266/344/270/216Clawdbot/345/205/274/345/256/271/346/200/247/350/256/276/350/256/241.md +321 -0
  62. package/docs/Redis/344/270/216/346/250/241/345/235/227/345/244/232/345/256/236/344/276/213/346/226/271/346/241/210.md +438 -0
  63. package/docs/Relay-Kite-Token/350/256/244/350/257/201/345/256/236/346/226/275/345/256/214/346/210/220.md +178 -0
  64. package/docs/Relay-Token/346/235/203/351/231/220/351/205/215/347/275/256/351/252/214/350/257/201.md +113 -0
  65. package/docs/Watchdog/345/201/245/345/272/267/346/243/200/346/237/245/344/270/216WebSocket-Ping/346/234/272/345/210/266/345/210/206/346/236/220.md +367 -0
  66. package/docs/Watchdog/350/265/204/346/272/220/347/233/221/346/216/247/347/255/226/347/225/245.md +92 -0
  67. package/docs/WebSocket/346/216/245/346/224/266/345/276/252/347/216/257/346/255/273/351/224/201/351/230/262/350/214/203/350/247/204/350/214/203.md +357 -0
  68. package/docs/WebSocket/350/277/236/346/216/245/351/237/247/346/200/247/344/270/216/351/207/215/350/277/236/346/234/272/345/210/266/345/256/214/346/225/264/346/226/271/346/241/210.md +531 -0
  69. package/docs/WebSocket/350/277/236/346/216/245/351/237/247/346/200/247/346/226/271/346/241/210.md +169 -0
  70. package/docs/WebSocket/351/207/215/350/277/236/346/234/272/345/210/266/346/265/213/350/257/225/346/212/245/345/221/212.md +169 -0
  71. package/docs/WebSocket/351/207/215/350/277/236/351/200/200/351/201/277/346/234/272/345/210/266/346/226/271/346/241/210.md +394 -0
  72. package/docs/Web/346/250/241/345/235/227/344/270/216Evol/346/250/241/345/235/227/351/207/215/346/236/204/345/210/206/346/236/220.md +521 -0
  73. package/docs/audit-api-guide.md +68 -0
  74. package/docs/audit-module-design.md +315 -0
  75. package/docs/audit-module-implementation-summary.md +149 -0
  76. package/docs/llm-context-design.md +52 -0
  77. package/docs/llm-test-enhancement-plan.md +970 -0
  78. package/docs/logs-api-guide.md +42 -0
  79. package/docs/npm/345/214/205Python/347/216/257/345/242/203/347/256/241/347/220/206/346/226/271/346/241/210.md +302 -0
  80. package/docs/npm/345/217/221/345/270/203/344/270/216CLI/344/275/277/347/224/250/346/214/207/345/215/227.md +245 -0
  81. package/docs/stdio/344/270/216/347/253/257/345/217/243/345/217/221/347/216/260/351/207/215/346/236/204.md +480 -0
  82. package/docs/web/346/250/241/345/235/227/344/270/255/350/275/254/346/234/215/345/212/241/350/256/276/350/256/241/346/226/271/346/241/210.md +449 -0
  83. package/docs//344/272/213/344/273/266/345/244/204/347/220/206/346/234/272/345/210/266.md +388 -0
  84. package/docs//344/272/213/344/273/266/345/244/204/347/220/206/350/247/204/350/214/203.md +113 -0
  85. package/docs//344/272/213/344/273/266/350/256/242/351/230/205/351/200/232/351/205/215/347/254/246/350/247/204/350/214/203.md +256 -0
  86. package/docs//344/272/213/344/273/266/351/230/237/345/210/227/345/274/271/346/200/247/347/256/241/347/220/206.md +449 -0
  87. package/docs//344/272/244/344/272/222/345/274/217/347/273/210/347/253/257/346/216/247/345/210/266/346/226/271/346/241/210.md +301 -0
  88. package/docs//344/273/243/347/220/206/345/220/257/345/212/250/345/231/250/344/270/216/345/256/271/345/231/250/345/214/226.md +140 -0
  89. package/docs//344/273/243/347/240/201/347/273/237/350/256/241/345/267/245/345/205/267/344/275/277/347/224/250/350/257/264/346/230/216.md +217 -0
  90. package/docs//344/274/230/351/233/205/351/200/200/345/207/272/350/247/204/350/214/203.md +362 -0
  91. package/docs//344/276/235/350/265/226/347/256/241/347/220/206/350/257/264/346/230/216.md +141 -0
  92. package/docs//344/277/256/345/244/215/346/235/203/351/231/220/351/227/256/351/242/230-evol-RPC/346/235/203/351/231/220.md +268 -0
  93. package/docs//345/210/240/351/231/244kernel-client-example/345/256/214/346/210/220.md +309 -0
  94. package/docs//345/210/240/351/231/244ws-management/345/256/214/346/210/220.md +418 -0
  95. package/docs//345/220/257/345/212/250/344/274/230/345/214/226/346/226/271/346/241/210.md +522 -0
  96. package/docs//345/220/257/345/212/250/344/276/235/350/265/226/344/270/216/346/216/222/345/272/217.md +105 -0
  97. package/docs//345/256/211/350/243/205/350/204/232/346/234/254/345/274/200/345/217/221/346/226/207/346/241/243.md +643 -0
  98. package/docs//345/256/214/346/225/264/345/220/257/345/212/250/346/265/201/347/250/213/350/256/276/350/256/241.md +452 -0
  99. package/docs//345/256/236/347/216/260/350/247/204/345/210/222.md +195 -0
  100. package/docs//345/277/203/350/267/263/346/234/272/345/210/266/351/207/215/346/236/204/346/200/273/347/273/223.md +166 -0
  101. package/docs//346/217/241/346/211/213/350/256/244/350/257/201/346/226/271/346/241/210-/345/256/211/345/205/250/345/256/241/346/237/245.md +176 -0
  102. package/docs//346/217/241/346/211/213/350/256/244/350/257/201/346/226/271/346/241/210.md +908 -0
  103. package/docs//346/226/207/346/241/243/346/233/264/346/226/260/346/270/205/345/215/225.md +83 -0
  104. package/docs//346/227/245/345/277/227/344/270/216/345/274/202/345/270/270/345/244/204/347/220/206/350/247/204/350/214/203.md +829 -0
  105. package/docs//346/227/245/345/277/227/350/260/203/350/257/225/345/256/236/346/210/230/346/214/207/345/215/227.md +25 -0
  106. package/docs//346/236/266/346/236/204/345/200/237/351/211/264/346/214/207/345/215/227.md +977 -0
  107. package/docs//346/236/266/346/236/204/346/224/271/351/200/240-/345/256/214/346/210/220/346/200/273/347/273/223.md +440 -0
  108. package/docs//346/236/266/346/236/204/347/216/260/347/212/266/344/270/216/347/273/210/346/236/201/347/233/256/346/240/207/345/257/271/346/257/224/345/210/206/346/236/220.md +508 -0
  109. package/docs//346/250/241/345/235/227/345/244/232/350/277/236/346/216/245/346/216/247/345/210/266/347/255/226/347/225/245.md +220 -0
  110. package/docs//346/250/241/345/235/227/345/256/211/350/243/205/346/234/272/345/210/266/350/256/276/350/256/241.md +500 -0
  111. package/docs//346/250/241/345/235/227/345/274/200/345/217/221/346/214/207/345/215/227.md +1824 -0
  112. package/docs//346/250/241/345/235/227/347/203/255/346/233/264/346/226/260.md +89 -0
  113. package/docs//346/250/241/345/235/227/350/277/234/347/250/213/351/203/250/347/275/262/345/274/200/345/217/221/350/247/204/350/214/203.md +460 -0
  114. package/docs//346/250/241/345/235/227/351/200/200/345/207/272/346/234/272/345/210/266/345/256/214/346/225/264/346/226/271/346/241/210.md +303 -0
  115. package/docs//346/250/241/345/235/227/351/205/215/347/275/256/345/212/240/350/275/275/344/270/216/347/203/255/351/207/215/350/275/275/350/247/204/350/214/203.md +369 -0
  116. package/docs//346/265/213/350/257/225/344/270/255/345/277/203/346/267/273/345/212/240/346/250/241/345/235/227/346/265/213/350/257/225/346/214/207/345/215/227.md +147 -0
  117. package/docs//347/211/210/346/234/254/351/224/201/345/256/232/347/216/257/345/242/203/347/256/241/347/220/206/346/226/271/346/241/210.md +331 -0
  118. package/docs//347/216/257/345/242/203/345/217/230/351/207/217/344/270/216/350/277/220/350/241/214/346/227/266/347/233/256/345/275/225/350/256/276/350/256/241.md +499 -0
  119. package/docs//347/216/257/345/242/203/347/256/241/347/220/206/345/256/214/346/225/264/346/226/271/346/241/210.md +334 -0
  120. package/docs//350/231/232/346/213/237/346/250/241/345/235/227/344/270/255/350/275/254/346/234/215/345/212/241/345/256/214/346/225/264/350/256/276/350/256/241.md +1496 -0
  121. package/docs//350/231/232/346/213/237/347/216/257/345/242/203/345/267/245/344/275/234/345/216/237/347/220/206.md +163 -0
  122. package/docs//350/256/241/345/210/222/347/256/241/347/220/206/345/231/250/344/275/277/347/224/250/346/214/207/345/215/227.md +196 -0
  123. package/docs//350/256/244/350/257/201/346/250/241/345/235/227/344/270/216Gateway/350/256/276/350/256/241/346/226/271/346/241/210.md +765 -0
  124. package/docs//350/277/234/347/250/213/346/250/241/345/235/227/350/256/276/350/256/241-/346/227/247/347/211/210.md +1117 -0
  125. package/docs//350/277/234/347/250/213/346/250/241/345/235/227/350/256/276/350/256/241.md +451 -0
  126. package/docs//351/207/215/346/236/204/346/234/272/345/210/266/346/270/205/345/215/225.md +192 -0
  127. package/docs//351/223/276/350/267/257/350/277/275/350/270/252/346/226/271/346/241/210.md +242 -0
  128. package/docs//351/231/215/347/272/247/347/255/226/347/225/245/350/256/276/350/256/241/346/226/271/346/241/210.md +618 -0
  129. package/extensions/agents/assistant/entry.py +113 -14
  130. package/extensions/agents/assistant/module.md +27 -22
  131. package/extensions/agents/assistant/server.py +308 -106
  132. package/extensions/channels/acp_channel/entry.py +114 -16
  133. package/extensions/channels/acp_channel/module.md +4 -0
  134. package/extensions/channels/acp_channel/server.py +412 -105
  135. package/extensions/channels/phone_channel/__init__.py +1 -0
  136. package/extensions/channels/phone_channel/entry.py +503 -0
  137. package/extensions/channels/phone_channel/module.md +31 -0
  138. package/extensions/channels/phone_channel/server.py +686 -0
  139. package/extensions/event_hub_bench/entry.py +55 -12
  140. package/extensions/event_hub_bench/module.md +27 -27
  141. package/extensions/services/audit/README.md +134 -0
  142. package/extensions/services/audit/collector.py +73 -0
  143. package/extensions/services/audit/entry.py +444 -0
  144. package/extensions/services/audit/module.md +66 -0
  145. package/extensions/services/audit/query_audit.py +111 -0
  146. package/extensions/services/audit/routes/__init__.py +1 -0
  147. package/extensions/services/audit/routes/routes_audit.py +113 -0
  148. package/extensions/services/audit/schemas/__init__.py +5 -0
  149. package/extensions/services/audit/schemas/audit_event.py +92 -0
  150. package/extensions/services/audit/server.py +542 -0
  151. package/extensions/services/audit/storage.py +95 -0
  152. package/extensions/services/auth/entry.py +1054 -0
  153. package/extensions/services/auth/module.md +31 -0
  154. package/extensions/services/auth/token_store.py +185 -0
  155. package/extensions/services/auth/verifiers/evol_account.py +101 -0
  156. package/extensions/services/auth/verifiers/kite_token.py +38 -0
  157. package/extensions/services/auth/verifiers/pairing_code.py +71 -0
  158. package/extensions/services/backup/entry.py +505 -201
  159. package/extensions/services/backup/module.md +4 -2
  160. package/extensions/services/dataclaw/api/__init__.py +0 -0
  161. package/extensions/services/dataclaw/api/admin.py +367 -0
  162. package/extensions/services/dataclaw/api/copyright.py +175 -0
  163. package/extensions/services/dataclaw/api/credits.py +177 -0
  164. package/extensions/services/dataclaw/api/data.py +179 -0
  165. package/extensions/services/dataclaw/api/demands.py +269 -0
  166. package/extensions/services/dataclaw/api/feeds.py +262 -0
  167. package/extensions/services/dataclaw/api/identity.py +505 -0
  168. package/extensions/services/dataclaw/api/notifications.py +104 -0
  169. package/extensions/services/dataclaw/api/reviews.py +138 -0
  170. package/extensions/services/dataclaw/api/search.py +153 -0
  171. package/extensions/services/dataclaw/api/subscriptions.py +157 -0
  172. package/extensions/services/dataclaw/config.json5 +96 -0
  173. package/extensions/services/dataclaw/core/__init__.py +0 -0
  174. package/extensions/services/dataclaw/core/auth.py +95 -0
  175. package/extensions/services/dataclaw/core/config.py +50 -0
  176. package/extensions/services/dataclaw/core/database.py +70 -0
  177. package/extensions/services/dataclaw/entry.py +416 -0
  178. package/extensions/services/dataclaw/gofeed/351/241/271/347/233/256/346/211/200/346/234/211/346/235/203/350/275/254/347/247/273/346/265/201/347/250/213/350/257/264/346/230/216.md +309 -0
  179. package/extensions/services/dataclaw/migrate.py +283 -0
  180. package/extensions/services/dataclaw/models/__init__.py +0 -0
  181. package/extensions/services/dataclaw/module.md +49 -0
  182. package/extensions/services/dataclaw/requirements.txt +18 -0
  183. package/extensions/services/dataclaw/server.py +759 -0
  184. package/extensions/services/dataclaw/services/__init__.py +0 -0
  185. package/extensions/services/dataclaw/services/agent_service.py +132 -0
  186. package/extensions/services/dataclaw/services/credit_service.py +235 -0
  187. package/extensions/services/dataclaw/services/email_service.py +140 -0
  188. package/extensions/services/dataclaw/services/feed_service.py +259 -0
  189. package/extensions/services/dataclaw/services/notification_service.py +209 -0
  190. package/extensions/services/dataclaw/services/oauth_service.py +275 -0
  191. package/extensions/services/dataclaw/services/pricing.py +102 -0
  192. package/extensions/services/dataclaw/services/quality.py +79 -0
  193. package/extensions/services/dataclaw/services/reputation.py +142 -0
  194. package/extensions/services/dataclaw/services/sms_service.py +174 -0
  195. package/extensions/services/dataclaw/static/css/common.css +853 -0
  196. package/extensions/services/dataclaw/static/css/themes/blue.css +42 -0
  197. package/extensions/services/dataclaw/static/css/themes/dark.css +42 -0
  198. package/extensions/services/dataclaw/static/css/themes/light.css +35 -0
  199. package/extensions/services/dataclaw/static/js/api.js +103 -0
  200. package/extensions/services/dataclaw/static/js/common.js +321 -0
  201. package/extensions/services/dataclaw/static/js/i18n.js +95 -0
  202. package/extensions/services/dataclaw/static/js/pages/admin.js +152 -0
  203. package/extensions/services/dataclaw/static/js/pages/dashboard.js +82 -0
  204. package/extensions/services/dataclaw/static/js/pages/feed-detail.js +180 -0
  205. package/extensions/services/dataclaw/static/js/pages/feed-manage.js +158 -0
  206. package/extensions/services/dataclaw/static/js/theme.js +46 -0
  207. package/extensions/services/dataclaw/static/locales/en-US.json +464 -0
  208. package/extensions/services/dataclaw/static/locales/ja-JP.json +464 -0
  209. package/extensions/services/dataclaw/static/locales/zh-CN.json +464 -0
  210. package/extensions/services/dataclaw/templates/admin/index.html +90 -0
  211. package/extensions/services/dataclaw/templates/base.html +136 -0
  212. package/extensions/services/dataclaw/templates/credits/balance.html +106 -0
  213. package/extensions/services/dataclaw/templates/credits/deposit.html +164 -0
  214. package/extensions/services/dataclaw/templates/credits/history.html +90 -0
  215. package/extensions/services/dataclaw/templates/dashboard.html +52 -0
  216. package/extensions/services/dataclaw/templates/demands/create.html +78 -0
  217. package/extensions/services/dataclaw/templates/demands/detail.html +136 -0
  218. package/extensions/services/dataclaw/templates/demands/list.html +94 -0
  219. package/extensions/services/dataclaw/templates/feeds/create.html +95 -0
  220. package/extensions/services/dataclaw/templates/feeds/detail.html +110 -0
  221. package/extensions/services/dataclaw/templates/feeds/list.html +110 -0
  222. package/extensions/services/dataclaw/templates/feeds/manage.html +88 -0
  223. package/extensions/services/dataclaw/templates/index.html +185 -0
  224. package/extensions/services/dataclaw/templates/login.html +246 -0
  225. package/extensions/services/dataclaw/templates/register.html +164 -0
  226. package/extensions/services/dataclaw/templates/settings/notifications.html +96 -0
  227. package/extensions/services/dataclaw/templates/settings/profile.html +167 -0
  228. package/extensions/services/dataclaw/templates/subscriptions/list.html +64 -0
  229. package/extensions/services/dataclaw/tests/__init__.py +0 -0
  230. package/extensions/services/dataclaw/tests/conftest.py +68 -0
  231. package/extensions/services/dataclaw/tests/integration/__init__.py +0 -0
  232. package/extensions/services/dataclaw/tests/integration/test_workflows.py +239 -0
  233. package/extensions/services/dataclaw/tests/unit/__init__.py +0 -0
  234. package/extensions/services/dataclaw/tests/unit/test_admin.py +70 -0
  235. package/extensions/services/dataclaw/tests/unit/test_copyright.py +63 -0
  236. package/extensions/services/dataclaw/tests/unit/test_credits.py +80 -0
  237. package/extensions/services/dataclaw/tests/unit/test_data.py +98 -0
  238. package/extensions/services/dataclaw/tests/unit/test_demands.py +106 -0
  239. package/extensions/services/dataclaw/tests/unit/test_feeds.py +98 -0
  240. package/extensions/services/dataclaw/tests/unit/test_identity.py +88 -0
  241. package/extensions/services/dataclaw/tests/unit/test_notifications.py +36 -0
  242. package/extensions/services/dataclaw/tests/unit/test_reviews.py +68 -0
  243. package/extensions/services/dataclaw/tests/unit/test_search.py +64 -0
  244. package/extensions/services/dataclaw/tests/unit/test_subscriptions.py +65 -0
  245. package/extensions/services/dataclaw/tests/unit/test_system.py +106 -0
  246. package/extensions/services/dataclaw/utils/__init__.py +0 -0
  247. package/extensions/services/dataclaw/utils/crypto.py +38 -0
  248. package/extensions/services/dataclaw/utils/id_generator.py +52 -0
  249. package/extensions/services/dataclaw/ws/__init__.py +0 -0
  250. package/extensions/services/dataclaw/ws/handler.py +163 -0
  251. package/extensions/services/dataclaw//345/215/217/350/256/2561-/351/241/271/347/233/256/346/235/241/344/273/266/346/216/210/346/235/203/344/270/216/350/202/241/346/235/203/345/257/271/344/273/267/345/215/217/350/256/256.md +243 -0
  252. package/extensions/services/dataclaw//345/215/217/350/256/2562-/351/241/271/347/233/256/350/264/255/344/271/260/346/235/203/344/270/216/345/244/226/345/214/205/345/247/224/346/211/230/345/274/200/345/217/221/345/215/217/350/256/256.md +434 -0
  253. package/extensions/services/evol/__init__.py +1 -0
  254. package/extensions/services/evol/async_http.py +551 -0
  255. package/extensions/services/evol/auth_manager.py +602 -0
  256. package/extensions/services/evol/config.json5 +16 -0
  257. package/extensions/services/evol/config_loader.py +117 -0
  258. package/extensions/services/evol/entry.py +568 -0
  259. package/extensions/services/evol/evol_api.py +969 -0
  260. package/extensions/services/evol/evol_config.json5 +29 -0
  261. package/extensions/services/evol/mfa_totp.py +77 -0
  262. package/extensions/services/evol/migrate_tokens.py +122 -0
  263. package/extensions/services/evol/module.md +150 -0
  264. package/extensions/services/evol/nonce_pool.py +113 -0
  265. package/extensions/services/evol/oauth_manager.py +223 -0
  266. package/extensions/services/evol/pairing.py +251 -0
  267. package/extensions/services/evol/pairing_codes.jsonl +2 -0
  268. package/extensions/services/evol/relay.py +1031 -0
  269. package/extensions/services/evol/relay_config.json5 +85 -0
  270. package/extensions/services/evol/routes/__init__.py +1 -0
  271. package/extensions/services/evol/routes/routes_llm.py +231 -0
  272. package/extensions/services/evol/routes/routes_rpc.py +90 -0
  273. package/extensions/services/evol/routes/routes_test.py +68 -0
  274. package/extensions/services/evol/server.py +2426 -0
  275. package/extensions/services/evol/static/assets/CommissionView-Cs_ys6Gm.js +1 -0
  276. package/extensions/services/evol/static/assets/CommissionView-DACet_Oo.css +1 -0
  277. package/extensions/services/evol/static/assets/IframePage-DbO11U9G.js +1 -0
  278. package/extensions/services/evol/static/assets/IframePage-c572lT8i.css +1 -0
  279. package/extensions/services/evol/static/assets/TeamDetailView-DULrGD7k.css +1 -0
  280. package/extensions/services/evol/static/assets/TeamDetailView-gy_MBEqG.js +139 -0
  281. package/extensions/services/evol/static/assets/element-plus-Bd7pZkkM.js +63 -0
  282. package/extensions/services/evol/static/assets/index-CmMONKzG.css +1 -0
  283. package/extensions/services/evol/static/assets/index-D44bBe__.js +2 -0
  284. package/extensions/services/evol/static/assets/vue-vendor-DtF-__I4.js +29 -0
  285. package/extensions/services/evol/static/index.html +16 -0
  286. package/extensions/services/evol/static/logo.png +0 -0
  287. package/extensions/services/evol/stats_manager.py +243 -0
  288. package/extensions/services/evol/web/README.md +89 -0
  289. package/extensions/services/evol/web/build.bat +44 -0
  290. package/extensions/services/evol/web/index.html +13 -0
  291. package/extensions/services/evol/web/package-lock.json +1718 -0
  292. package/extensions/services/evol/web/package.json +26 -0
  293. package/extensions/services/evol/web/public/logo.png +0 -0
  294. package/extensions/services/evol/web/src/App.vue +7 -0
  295. package/extensions/services/evol/web/src/components/layout/AppHeader.vue +202 -0
  296. package/extensions/services/evol/web/src/components/layout/AppLayout.vue +61 -0
  297. package/extensions/services/evol/web/src/components/layout/AppSidebar.vue +115 -0
  298. package/extensions/services/evol/web/src/components/login/LoginPage.vue +271 -0
  299. package/extensions/services/evol/web/src/components/team/AddMemberModal.vue +181 -0
  300. package/extensions/services/evol/web/src/components/team/GroupTreeNode.vue +156 -0
  301. package/extensions/services/evol/web/src/components/team/TeamAlertConfig.vue +221 -0
  302. package/extensions/services/evol/web/src/components/team/TeamBillModal.vue +165 -0
  303. package/extensions/services/evol/web/src/components/team/TeamMembersAndGroups.vue +499 -0
  304. package/extensions/services/evol/web/src/components/team/TeamStatsPanel.vue +907 -0
  305. package/extensions/services/evol/web/src/components/team/TreeNode.vue +331 -0
  306. package/extensions/services/evol/web/src/components/team/stats/StatsExportProgress.vue +44 -0
  307. package/extensions/services/evol/web/src/components/team/stats/StatsHeader.vue +89 -0
  308. package/extensions/services/evol/web/src/components/team/stats/StatsMemberDetail.vue +415 -0
  309. package/extensions/services/evol/web/src/components/team/stats/StatsSummary.vue +42 -0
  310. package/extensions/services/evol/web/src/components/team/stats/helpers.ts +195 -0
  311. package/extensions/services/evol/web/src/components/team/stats/stats.css +741 -0
  312. package/extensions/services/evol/web/src/components/team/stats/useStatsApi.ts +114 -0
  313. package/extensions/services/evol/web/src/components/team/stats/useStatsCharts.ts +242 -0
  314. package/extensions/services/evol/web/src/components/team/stats/useStatsExport.ts +232 -0
  315. package/extensions/services/evol/web/src/composables/useFormatters.ts +42 -0
  316. package/extensions/services/evol/web/src/composables/useTheme.ts +52 -0
  317. package/extensions/services/evol/web/src/env.d.ts +7 -0
  318. package/extensions/services/evol/web/src/i18n/en.ts +361 -0
  319. package/extensions/services/evol/web/src/i18n/index.ts +36 -0
  320. package/extensions/services/evol/web/src/i18n/zh.ts +379 -0
  321. package/extensions/services/evol/web/src/main.ts +21 -0
  322. package/extensions/services/evol/web/src/router/index.ts +81 -0
  323. package/extensions/services/evol/web/src/services/kernel-client.ts +406 -0
  324. package/extensions/services/evol/web/src/stores/auth.ts +189 -0
  325. package/extensions/services/evol/web/src/stores/connection.ts +134 -0
  326. package/extensions/services/evol/web/src/stores/pages.ts +79 -0
  327. package/extensions/services/evol/web/src/styles/base.css +213 -0
  328. package/extensions/services/evol/web/src/styles/variables.css +138 -0
  329. package/extensions/services/evol/web/src/types/rpc.ts +35 -0
  330. package/extensions/services/evol/web/src/types/token.ts +87 -0
  331. package/extensions/services/evol/web/src/views/AccountView.vue +1532 -0
  332. package/extensions/services/evol/web/src/views/AiServiceView.vue +219 -0
  333. package/extensions/services/evol/web/src/views/CommissionView.vue +1220 -0
  334. package/extensions/services/evol/web/src/views/CreditsView.vue +131 -0
  335. package/extensions/services/evol/web/src/views/EndpointView.vue +163 -0
  336. package/extensions/services/evol/web/src/views/IframePage.vue +120 -0
  337. package/extensions/services/evol/web/src/views/TeamDetailView.vue +473 -0
  338. package/extensions/services/evol/web/src/views/TeamView.vue +332 -0
  339. package/extensions/services/evol/web/tsconfig.json +31 -0
  340. package/extensions/services/evol/web/tsconfig.node.json +10 -0
  341. package/extensions/services/evol/web/vite.config.ts +49 -0
  342. package/extensions/services/evolmem/__init__.py +0 -0
  343. package/extensions/services/evolmem/entry.py +387 -0
  344. package/extensions/services/evolmem/hooks/__init__.py +0 -0
  345. package/extensions/services/evolmem/hooks/assistant_stop.py +228 -0
  346. package/extensions/services/evolmem/hooks/common.py +76 -0
  347. package/extensions/services/evolmem/hooks/pre_tool_use.py +56 -0
  348. package/extensions/services/evolmem/hooks/session_end.py +133 -0
  349. package/extensions/services/evolmem/hooks/session_start.py +229 -0
  350. package/extensions/services/evolmem/hooks/user_prompt.py +122 -0
  351. package/extensions/services/evolmem/module.md +48 -0
  352. package/extensions/services/evolmem/prompts/00-server-info.md +28 -0
  353. package/extensions/services/evolmem/prompts/01-behavior.md +46 -0
  354. package/extensions/services/evolmem/prompts/02-summary-format.md +112 -0
  355. package/extensions/services/evolmem/prompts/03-file-query.md +92 -0
  356. package/extensions/services/evolmem/prompts/04-topic-stats.md +11 -0
  357. package/extensions/services/evolmem/prompts/05-recent-topics.md +84 -0
  358. package/extensions/services/evolmem/scripts/__init__.py +0 -0
  359. package/extensions/services/evolmem/scripts/extract_keywords.py +40 -0
  360. package/extensions/services/evolmem/scripts/search_topics.py +91 -0
  361. package/extensions/services/evolmem/server.py +641 -0
  362. package/extensions/services/gateway/entry.py +964 -0
  363. package/extensions/services/gateway/module.md +29 -0
  364. package/extensions/services/gateway/nonce_pool.py +65 -0
  365. package/extensions/services/gateway/relay.py +133 -0
  366. package/extensions/services/gateway/ws_server.py +285 -0
  367. package/extensions/services/kite_console/auth_manager.py +603 -0
  368. package/extensions/services/kite_console/config.json5 +19 -0
  369. package/extensions/services/kite_console/config_loader.py +117 -0
  370. package/extensions/services/kite_console/entry.py +528 -0
  371. package/extensions/services/kite_console/evol_api.py +179 -0
  372. package/extensions/services/kite_console/evol_config.json5 +29 -0
  373. package/extensions/services/kite_console/mfa_totp.py +77 -0
  374. package/extensions/services/kite_console/migrate_tokens.py +122 -0
  375. package/extensions/services/kite_console/module.md +37 -0
  376. package/extensions/services/kite_console/nonce_pool.py +113 -0
  377. package/extensions/services/kite_console/oauth_manager.py +223 -0
  378. package/extensions/services/kite_console/pairing.py +280 -0
  379. package/extensions/services/kite_console/pairing_codes.jsonl +2 -0
  380. package/extensions/services/kite_console/relay.py +1350 -0
  381. package/extensions/services/kite_console/relay_config.json5 +96 -0
  382. package/extensions/services/kite_console/routes/__init__.py +1 -0
  383. package/extensions/services/kite_console/routes/routes_llm.py +231 -0
  384. package/extensions/services/kite_console/routes/routes_proxy.py +115 -0
  385. package/extensions/services/kite_console/routes/routes_rpc.py +89 -0
  386. package/extensions/services/kite_console/routes/routes_test.py +68 -0
  387. package/extensions/services/kite_console/server.py +1742 -0
  388. package/extensions/services/kite_console/static/css/style.css +1854 -0
  389. package/extensions/services/kite_console/static/index.html +1524 -0
  390. package/extensions/services/kite_console/static/js/dialog.js +292 -0
  391. package/extensions/services/kite_console/static/js/evol-app.js +7740 -0
  392. package/extensions/services/kite_console/static/js/evol-app.js.backup +2777 -0
  393. package/extensions/services/kite_console/static/js/kernel-client.js +560 -0
  394. package/extensions/services/kite_console/static/js/kernel-client.js.backup +434 -0
  395. package/extensions/services/kite_console/static/js/registry-tests.js +592 -0
  396. package/extensions/services/kite_console/static/js/tests/ARCHITECTURE.md +67 -0
  397. package/extensions/services/kite_console/static/js/tests/README.md +140 -0
  398. package/extensions/services/kite_console/static/js/tests/index.js +161 -0
  399. package/extensions/services/kite_console/static/js/tests/integration/auth.js +120 -0
  400. package/extensions/services/kite_console/static/js/tests/integration/channel-interaction.js +188 -0
  401. package/extensions/services/kite_console/static/js/tests/integration/elastic-connection.js +115 -0
  402. package/extensions/services/kite_console/static/js/tests/integration/full-workflow.js +43 -0
  403. package/extensions/services/kite_console/static/js/tests/integration/multi-instance.js +304 -0
  404. package/extensions/services/kite_console/static/js/tests/integration/nested-rpc.js +266 -0
  405. package/extensions/services/kite_console/static/js/tests/integration/pingpong.js +25 -0
  406. package/extensions/services/kite_console/static/js/tests/integration/redis.js +227 -0
  407. package/extensions/services/kite_console/static/js/tests/integration/registry-core.js +52 -0
  408. package/extensions/services/kite_console/static/js/tests/integration/remote-deploy.js +85 -0
  409. package/extensions/services/kite_console/static/js/tests/integration/require-init.js +96 -0
  410. package/extensions/services/kite_console/static/js/tests/integration/scaling-control.js +193 -0
  411. package/extensions/services/kite_console/static/js/tests/integration/trace.js +109 -0
  412. package/extensions/services/kite_console/static/js/tests/modules/acp_channel.js +339 -0
  413. package/extensions/services/kite_console/static/js/tests/modules/auth.js +96 -0
  414. package/extensions/services/kite_console/static/js/tests/modules/backup.js +49 -0
  415. package/extensions/services/kite_console/static/js/tests/modules/gateway.js +41 -0
  416. package/extensions/services/kite_console/static/js/tests/modules/kernel.js +90 -0
  417. package/extensions/services/kite_console/static/js/tests/modules/launcher.js +75 -0
  418. package/extensions/services/kite_console/static/js/tests/modules/multi_instance.js +129 -0
  419. package/extensions/services/kite_console/static/js/tests/modules/phone_channel.js +364 -0
  420. package/extensions/services/kite_console/static/js/tests/modules/redis.js +178 -0
  421. package/extensions/services/kite_console/static/js/tests/modules/watchdog.js +60 -0
  422. package/extensions/services/kite_console/static/js/tests/modules/web.js +70 -0
  423. package/extensions/services/kite_console/static/js/tests/test-runner.js +123 -0
  424. package/extensions/services/kite_console/static/js/virtual-list.js +200 -0
  425. package/extensions/services/kite_console/static/pairing.html +248 -0
  426. package/extensions/services/kite_console/static/test_kernel_client_token.html +352 -0
  427. package/extensions/services/kite_console/static/test_registry.html +262 -0
  428. package/extensions/services/kite_console/static/test_relay.html +462 -0
  429. package/extensions/services/kite_console/stats_manager.py +247 -0
  430. package/extensions/services/logs/README.md +215 -0
  431. package/extensions/services/logs/api_logger.py +37 -0
  432. package/extensions/services/logs/baseline.py +121 -0
  433. package/extensions/services/logs/cleaner.py +76 -0
  434. package/extensions/services/logs/entry.py +449 -0
  435. package/extensions/services/logs/formatter.py +129 -0
  436. package/extensions/services/logs/module.md +38 -0
  437. package/extensions/services/logs/quick_diagnostic.py +128 -0
  438. package/extensions/services/logs/routes/__init__.py +1 -0
  439. package/extensions/services/logs/routes/routes_logs.py +218 -0
  440. package/extensions/services/logs/routes/routes_logs.py.backup +173 -0
  441. package/extensions/services/logs/scanner.py +100 -0
  442. package/extensions/services/logs/searcher.py +263 -0
  443. package/extensions/services/logs/server.py +553 -0
  444. package/extensions/services/logs.zip +0 -0
  445. package/extensions/services/model_service/config.json5 +30 -0
  446. package/extensions/services/model_service/entry.py +633 -162
  447. package/extensions/services/model_service/module.md +11 -2
  448. package/extensions/services/proxy/.claude/settings.local.json +13 -0
  449. package/extensions/services/proxy/__init__.py +0 -0
  450. package/extensions/services/proxy/agentcp/LICENCE +178 -0
  451. package/extensions/services/proxy/agentcp/README copy.md +85 -0
  452. package/extensions/services/proxy/agentcp/README.md +260 -0
  453. package/extensions/services/proxy/agentcp/__init__.py +16 -0
  454. package/extensions/services/proxy/agentcp/agent.py +4 -0
  455. package/extensions/services/proxy/agentcp/agentcp.py +2494 -0
  456. package/extensions/services/proxy/agentcp/agentprofile.json +89 -0
  457. package/extensions/services/proxy/agentcp/ap/__init__.py +16 -0
  458. package/extensions/services/proxy/agentcp/ap/ap_client.py +316 -0
  459. package/extensions/services/proxy/agentcp/assets/images/wechat_qr.png +0 -0
  460. package/extensions/services/proxy/agentcp/backup/metrics.json +31 -0
  461. package/extensions/services/proxy/agentcp/base/__init__.py +20 -0
  462. package/extensions/services/proxy/agentcp/base/auth_client.py +257 -0
  463. package/extensions/services/proxy/agentcp/base/client.py +112 -0
  464. package/extensions/services/proxy/agentcp/base/env.py +34 -0
  465. package/extensions/services/proxy/agentcp/base/html_util.py +336 -0
  466. package/extensions/services/proxy/agentcp/base/log.py +98 -0
  467. package/extensions/services/proxy/agentcp/ca/__init__.py +17 -0
  468. package/extensions/services/proxy/agentcp/ca/ca_client.py +414 -0
  469. package/extensions/services/proxy/agentcp/ca/ca_root.py +74 -0
  470. package/extensions/services/proxy/agentcp/context/__init__.py +20 -0
  471. package/extensions/services/proxy/agentcp/context/context.py +73 -0
  472. package/extensions/services/proxy/agentcp/context/exceptions.py +114 -0
  473. package/extensions/services/proxy/agentcp/create_profile.py +125 -0
  474. package/extensions/services/proxy/agentcp/create_profile_weather.py +125 -0
  475. package/extensions/services/proxy/agentcp/db/__init__.py +15 -0
  476. package/extensions/services/proxy/agentcp/db/db_mananger.py +550 -0
  477. package/extensions/services/proxy/agentcp/docs/UDP_HEARTBEAT_FIX_REPORT.md +265 -0
  478. package/extensions/services/proxy/agentcp/docs/heartbeat_issue_analysis.md +291 -0
  479. package/extensions/services/proxy/agentcp/file/__init__.py +16 -0
  480. package/extensions/services/proxy/agentcp/file/file_client.py +141 -0
  481. package/extensions/services/proxy/agentcp/file/wss_binary_message.py +137 -0
  482. package/extensions/services/proxy/agentcp/hcp.py +299 -0
  483. package/extensions/services/proxy/agentcp/heartbeat/__init__.py +16 -0
  484. package/extensions/services/proxy/agentcp/heartbeat/heartbeat_client.py +360 -0
  485. package/extensions/services/proxy/agentcp/improved_scheduler.py +498 -0
  486. package/extensions/services/proxy/agentcp/llm_agent_utils.py +249 -0
  487. package/extensions/services/proxy/agentcp/llm_server.py +172 -0
  488. package/extensions/services/proxy/agentcp/mermaid.py +210 -0
  489. package/extensions/services/proxy/agentcp/message.py +149 -0
  490. package/extensions/services/proxy/agentcp/metrics.py +256 -0
  491. package/extensions/services/proxy/agentcp/monitoring/__init__.py +20 -0
  492. package/extensions/services/proxy/agentcp/monitoring/global_monitor.py +27 -0
  493. package/extensions/services/proxy/agentcp/monitoring/metrics_store.py +325 -0
  494. package/extensions/services/proxy/agentcp/monitoring/monitoring_service.py +269 -0
  495. package/extensions/services/proxy/agentcp/monitoring/sliding_window.py +222 -0
  496. package/extensions/services/proxy/agentcp/monitoring/standalone_reader.py +224 -0
  497. package/extensions/services/proxy/agentcp/msg/__init__.py +21 -0
  498. package/extensions/services/proxy/agentcp/msg/connection_manager.py +456 -0
  499. package/extensions/services/proxy/agentcp/msg/message_client.py +2058 -0
  500. package/extensions/services/proxy/agentcp/msg/message_serialize.py +263 -0
  501. package/extensions/services/proxy/agentcp/msg/open_ai_message.py +88 -0
  502. package/extensions/services/proxy/agentcp/msg/session_manager.py +1062 -0
  503. package/extensions/services/proxy/agentcp/msg/stream_client.py +267 -0
  504. package/extensions/services/proxy/agentcp/msg/websocket_file_receiver.py +89 -0
  505. package/extensions/services/proxy/agentcp/msg/ws_logger.py +685 -0
  506. package/extensions/services/proxy/agentcp/msg/wss_binary_message.py +137 -0
  507. package/extensions/services/proxy/agentcp/requirements.txt +7 -0
  508. package/extensions/services/proxy/agentcp/samples/agent_graph/README.md +37 -0
  509. package/extensions/services/proxy/agentcp/samples/agent_graph/agentprofile.json +89 -0
  510. package/extensions/services/proxy/agentcp/samples/agent_graph/create_profile.py +138 -0
  511. package/extensions/services/proxy/agentcp/samples/agent_graph/main.py +164 -0
  512. package/extensions/services/proxy/agentcp/samples/agent_use/create_profile.py +123 -0
  513. package/extensions/services/proxy/agentcp/samples/agent_use/llm/create_profile.py +129 -0
  514. package/extensions/services/proxy/agentcp/samples/agent_use/llm/env.json +5 -0
  515. package/extensions/services/proxy/agentcp/samples/agent_use/llm/main.py +146 -0
  516. package/extensions/services/proxy/agentcp/samples/agent_use/main.py +123 -0
  517. package/extensions/services/proxy/agentcp/samples/agent_use/readme.md +379 -0
  518. package/extensions/services/proxy/agentcp/samples/agent_use/search/create_profile.py +129 -0
  519. package/extensions/services/proxy/agentcp/samples/agent_use/search/main.py +28 -0
  520. package/extensions/services/proxy/agentcp/samples/agent_use/tool/create_profile.py +129 -0
  521. package/extensions/services/proxy/agentcp/samples/agent_use/tool/main.py +20 -0
  522. package/extensions/services/proxy/agentcp/samples/ali_amap/README.md +97 -0
  523. package/extensions/services/proxy/agentcp/samples/ali_amap/amap_agent.py +88 -0
  524. package/extensions/services/proxy/agentcp/samples/ali_amap/create_profile.py +125 -0
  525. package/extensions/services/proxy/agentcp/samples/compute_agent/agent/powershell.py +228 -0
  526. package/extensions/services/proxy/agentcp/samples/compute_agent/agent/software.py +63 -0
  527. package/extensions/services/proxy/agentcp/samples/compute_agent/agent/tools.py +36 -0
  528. package/extensions/services/proxy/agentcp/samples/compute_agent/browser_user.py +41 -0
  529. package/extensions/services/proxy/agentcp/samples/deepseek/README.md +79 -0
  530. package/extensions/services/proxy/agentcp/samples/deepseek/create_profile.py +126 -0
  531. package/extensions/services/proxy/agentcp/samples/deepseek/deepseek.py +42 -0
  532. package/extensions/services/proxy/agentcp/samples/dify_chat/README.md +78 -0
  533. package/extensions/services/proxy/agentcp/samples/dify_chat/create_profile.py +126 -0
  534. package/extensions/services/proxy/agentcp/samples/dify_chat/dify_chat.py +47 -0
  535. package/extensions/services/proxy/agentcp/samples/dify_workflow/README.md +78 -0
  536. package/extensions/services/proxy/agentcp/samples/dify_workflow/create_profile.py +126 -0
  537. package/extensions/services/proxy/agentcp/samples/dify_workflow/dify_workflow.py +46 -0
  538. package/extensions/services/proxy/agentcp/samples/executor/README.md +44 -0
  539. package/extensions/services/proxy/agentcp/samples/executor/agentprofile.json +89 -0
  540. package/extensions/services/proxy/agentcp/samples/executor/create_profile.py +139 -0
  541. package/extensions/services/proxy/agentcp/samples/executor/main.py +160 -0
  542. package/extensions/services/proxy/agentcp/samples/filereader/README.md +45 -0
  543. package/extensions/services/proxy/agentcp/samples/filereader/agentprofile.json +90 -0
  544. package/extensions/services/proxy/agentcp/samples/filereader/create_profile.py +137 -0
  545. package/extensions/services/proxy/agentcp/samples/filereader/main.py +253 -0
  546. package/extensions/services/proxy/agentcp/samples/filewriter/README.md +38 -0
  547. package/extensions/services/proxy/agentcp/samples/filewriter/agentprofile.json +91 -0
  548. package/extensions/services/proxy/agentcp/samples/filewriter/create_profile.py +138 -0
  549. package/extensions/services/proxy/agentcp/samples/filewriter/main.py +289 -0
  550. package/extensions/services/proxy/agentcp/samples/hcp/README.md +85 -0
  551. package/extensions/services/proxy/agentcp/samples/hcp/acp_weather_agent.zip +0 -0
  552. package/extensions/services/proxy/agentcp/samples/hcp/create_profile.py +125 -0
  553. package/extensions/services/proxy/agentcp/samples/hcp/hcp.py +237 -0
  554. package/extensions/services/proxy/agentcp/samples/helloworld/README.md +68 -0
  555. package/extensions/services/proxy/agentcp/samples/helloworld/hello_world.py +40 -0
  556. package/extensions/services/proxy/agentcp/samples/llm_agent/MEADME.md +117 -0
  557. package/extensions/services/proxy/agentcp/samples/llm_agent/create_profile.py +125 -0
  558. package/extensions/services/proxy/agentcp/samples/llm_agent/qwen_agent.py +136 -0
  559. package/extensions/services/proxy/agentcp/samples/local_llm_agent/README.md +90 -0
  560. package/extensions/services/proxy/agentcp/samples/local_llm_agent/create_profile.py +125 -0
  561. package/extensions/services/proxy/agentcp/samples/local_llm_agent/main.py +49 -0
  562. package/extensions/services/proxy/agentcp/samples/query_llm_from_agent/README.md +55 -0
  563. package/extensions/services/proxy/agentcp/samples/query_llm_from_agent/create_profile.py +125 -0
  564. package/extensions/services/proxy/agentcp/samples/query_llm_from_agent/main.py +23 -0
  565. package/extensions/services/proxy/agentcp/samples/query_weather_api_agent/README.md +103 -0
  566. package/extensions/services/proxy/agentcp/samples/query_weather_api_agent/create_profile.py +125 -0
  567. package/extensions/services/proxy/agentcp/samples/query_weather_api_agent/main.py +69 -0
  568. package/extensions/services/proxy/agentcp/samples/query_weather_from_agent/README.md +58 -0
  569. package/extensions/services/proxy/agentcp/samples/query_weather_from_agent/create_profile.py +125 -0
  570. package/extensions/services/proxy/agentcp/samples/query_weather_from_agent/main.py +25 -0
  571. package/extensions/services/proxy/agentcp/samples/qwen3/README.md +71 -0
  572. package/extensions/services/proxy/agentcp/samples/qwen3/create_profile.py +126 -0
  573. package/extensions/services/proxy/agentcp/samples/qwen3/qwen3.py +37 -0
  574. package/extensions/services/proxy/agentcp/samples/qwen3_tools/README.md +133 -0
  575. package/extensions/services/proxy/agentcp/samples/qwen3_tools/create_profile.py +126 -0
  576. package/extensions/services/proxy/agentcp/samples/qwen3_tools/qwen3_tools.py +98 -0
  577. package/extensions/services/proxy/agentcp/samples/search/create_profile_qwen.py +125 -0
  578. package/extensions/services/proxy/agentcp/samples/search/create_profile_search.py +125 -0
  579. package/extensions/services/proxy/agentcp/samples/search/qwen_agent.py +136 -0
  580. package/extensions/services/proxy/agentcp/samples/search/search_agent.py +170 -0
  581. package/extensions/services/proxy/agentcp/samples/wrapper_agently_to_agent/README.md +89 -0
  582. package/extensions/services/proxy/agentcp/samples/wrapper_agently_to_agent/create_profile.py +125 -0
  583. package/extensions/services/proxy/agentcp/samples/wrapper_agently_to_agent/main.py +44 -0
  584. package/extensions/services/proxy/agentcp/utils/__init__.py +15 -0
  585. package/extensions/services/proxy/agentcp/utils/file_util.py +117 -0
  586. package/extensions/services/proxy/agentcp/utils/proxy_bypass.py +99 -0
  587. package/extensions/services/proxy/agentcp/workflow.py +203 -0
  588. package/extensions/services/proxy/aid_manager.py +419 -0
  589. package/extensions/services/proxy/auth_bridge.py +182 -0
  590. package/extensions/services/proxy/config_store.py +79 -0
  591. package/extensions/services/proxy/entry.py +528 -0
  592. package/extensions/services/proxy/evol/__init__.py +1 -0
  593. package/extensions/services/proxy/evol/config.py +37 -0
  594. package/extensions/services/proxy/evol/http/__init__.py +1 -0
  595. package/extensions/services/proxy/evol/http/async_http.py +551 -0
  596. package/extensions/services/proxy/evol/log.py +28 -0
  597. package/extensions/services/proxy/evol/presenter/__init__.py +2 -0
  598. package/extensions/services/proxy/evol/presenter/agentIdPresenter.py +1031 -0
  599. package/extensions/services/proxy/evol/presenter/apikeyPresenter.py +96 -0
  600. package/extensions/services/proxy/evol/presenter/configPresenter.py +234 -0
  601. package/extensions/services/proxy/evol/presenter/userPresenter.py +71 -0
  602. package/extensions/services/proxy/evol/server/__init__.py +1 -0
  603. package/extensions/services/proxy/evol/server/claude_proxy_async.py +3434 -0
  604. package/extensions/services/proxy/evol/server/openclaw_proxy.py +1861 -0
  605. package/extensions/services/proxy/evol/server/proxy_config.py +15 -0
  606. package/extensions/services/proxy/evol/server/proxy_engine.py +501 -0
  607. package/extensions/services/proxy/evol/version.py +24 -0
  608. package/extensions/services/proxy/module.md +151 -0
  609. package/extensions/services/proxy/server.py +952 -0
  610. package/extensions/services/redis/ALIGNMENT_CHECKLIST.md +121 -0
  611. package/extensions/services/redis/ALIGNMENT_STATUS.md +548 -0
  612. package/extensions/services/redis/config.json5 +8 -0
  613. package/extensions/services/redis/entry.py +1509 -0
  614. package/extensions/services/redis/entry.py.backup +405 -0
  615. package/extensions/services/redis/module.md +48 -0
  616. package/extensions/services/redis/redis_builtin.py +332 -0
  617. package/extensions/services/redis/redis_external.py +164 -0
  618. package/extensions/services/testUi/entry.py +446 -0
  619. package/extensions/services/testUi/module.md +18 -0
  620. package/extensions/services/testUi/ui/cards.html +131 -0
  621. package/extensions/services/testUi/ui/index.html +22 -0
  622. package/extensions/services/testUi/ui/particles.html +143 -0
  623. package/extensions/services/watchdog/entry.py +1258 -767
  624. package/extensions/services/watchdog/module.md +3 -0
  625. package/extensions/services/watchdog/monitor.py +483 -75
  626. package/extensions/services/web/auth_manager.py +602 -0
  627. package/extensions/services/web/config.json5 +11 -0
  628. package/extensions/services/web/entry.py +598 -478
  629. package/extensions/services/web/mfa_totp.py +77 -0
  630. package/extensions/services/web/module.md +17 -14
  631. package/extensions/services/web/nonce_pool.py +113 -0
  632. package/extensions/services/web/oauth_manager.py +223 -0
  633. package/extensions/services/web/pairing.py +3 -2
  634. package/extensions/services/web/pairing_codes.jsonl +1 -0
  635. package/extensions/services/web/relay.py +442 -63
  636. package/extensions/services/web/relay_config.json5 +1 -2
  637. package/extensions/services/web/routes/routes_rpc.py +6 -6
  638. package/extensions/services/web/server.py +380 -181
  639. package/extensions/services/web/static/index.html +1752 -1738
  640. package/extensions/services/web/static/js/app.js +32 -0
  641. package/extensions/services/web/static/js/kernel-client.js +48 -9
  642. package/extensions/services/web/static/js/token-manager.js +10 -10
  643. package/extensions/services/web/vendor/bluetooth/audio.py +1 -1
  644. package/extensions/services/web/vendor/config.py +2 -2
  645. package/extensions/services/web/vendor/storage/identity.py +1 -1
  646. package/kernel/entry.py +77 -23
  647. package/kernel/event_hub.py +1122 -74
  648. package/kernel/module.md +26 -1
  649. package/kernel/registry_store.py +209 -36
  650. package/kernel/rpc_router.py +1400 -465
  651. package/kernel/server.py +1084 -108
  652. package/kite_cli/builders/__init__.py +4 -0
  653. package/kite_cli/builders/base.py +67 -0
  654. package/kite_cli/builders/custom.py +31 -0
  655. package/kite_cli/builders/detector.py +56 -0
  656. package/kite_cli/builders/go.py +34 -0
  657. package/kite_cli/builders/gradle.py +41 -0
  658. package/kite_cli/builders/maven.py +36 -0
  659. package/kite_cli/builders/npm.py +44 -0
  660. package/kite_cli/builders/python.py +37 -0
  661. package/kite_cli/commands/BUILD_GUIDE.md +109 -0
  662. package/kite_cli/commands/build.py +142 -0
  663. package/kite_cli/commands/check.py +60 -0
  664. package/kite_cli/commands/config.py +156 -0
  665. package/kite_cli/commands/deps.py +58 -0
  666. package/kite_cli/commands/deps_install.py +67 -0
  667. package/kite_cli/commands/disable.py +162 -0
  668. package/kite_cli/commands/enable.py +162 -0
  669. package/kite_cli/commands/env_check.py +45 -0
  670. package/kite_cli/commands/export.py +96 -0
  671. package/kite_cli/commands/import_cmd.py +110 -0
  672. package/kite_cli/commands/install.py +50 -23
  673. package/kite_cli/commands/install_skill.py +107 -0
  674. package/kite_cli/commands/list.py +128 -31
  675. package/kite_cli/commands/outdated.py +202 -0
  676. package/kite_cli/commands/prepare.py +49 -0
  677. package/kite_cli/commands/search.py +33 -17
  678. package/kite_cli/commands/update.py +115 -2
  679. package/kite_cli/commands/venv_setup.py +56 -0
  680. package/kite_cli/commands/why.py +48 -0
  681. package/kite_cli/core/config_manager.py +145 -0
  682. package/kite_cli/core/downloader.py +32 -2
  683. package/kite_cli/main.py +179 -5
  684. package/kite_cli/utils/colors.py +153 -0
  685. package/kite_cli/utils/dependency_graph.py +209 -0
  686. package/kite_cli/utils/process.py +55 -0
  687. package/kite_cli/utils/progress.py +207 -0
  688. package/kite_cli/utils/table.py +101 -0
  689. package/launcher/count_lines.py +192 -43
  690. package/launcher/entry.py +4543 -2517
  691. package/launcher/logging_setup.py +54 -1
  692. package/launcher/module.md +37 -2
  693. package/launcher/module_scanner.py +103 -20
  694. package/launcher/process_manager.py +355 -76
  695. package/main.py +10 -1
  696. package/package.json +11 -1
  697. package/python_version.json +4 -0
  698. package/requirements.txt +41 -0
  699. package/scripts/auto-fix-deps.py +128 -0
  700. package/scripts/env-manager.js +351 -0
  701. package/scripts/final-test.js +78 -0
  702. package/scripts/python-env.js +79 -0
  703. package/scripts/scan_dependencies.py +461 -0
  704. package/scripts/setup-python-env.js +700 -0
  705. package/scripts/test-alluser.js +48 -0
  706. package/scripts/test-different-version.js +86 -0
  707. package/scripts/test-direct.js +63 -0
  708. package/scripts/test-extract-installer.js +28 -0
  709. package/scripts/test-install-log.js +54 -0
  710. package/scripts/test-installer.js +39 -0
  711. package/scripts/test-integration.js +250 -0
  712. package/scripts/test-real-install.js +210 -0
  713. package/scripts/test-targetdir.js +49 -0
  714. package/scripts/test-venv-real.js +47 -0
  715. package/scripts/test-venv-simple.js +57 -0
  716. package/scripts/test-wait.js +49 -0
  717. package/scripts/test-with-log.js +63 -0
  718. package/extensions/services/web/config.yaml +0 -149
@@ -0,0 +1,2494 @@
1
+ # Copyright 2025 AgentUnion Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ # 在Python文件开头明确指定编码声明
15
+ # -*- coding: utf-8 -*-
16
+ import abc
17
+ import asyncio
18
+ import hashlib
19
+ import json
20
+ import logging
21
+ from ntpath import exists
22
+ import os
23
+ import queue
24
+ import threading
25
+ import time
26
+ import typing
27
+ import urllib.parse
28
+ from concurrent.futures import ThreadPoolExecutor
29
+ from typing import Union
30
+
31
+ import requests
32
+ from cryptography.hazmat.primitives import serialization
33
+
34
+ from agentcp import utils
35
+ from agentcp.ap.ap_client import ApClient
36
+ from agentcp.base.html_util import parse_html
37
+ from agentcp.ca.ca_client import CAClient
38
+ from agentcp.ca.ca_root import CARoot
39
+ from agentcp.context import ErrorContext, exceptions
40
+ from agentcp.db.db_mananger import DBManager
41
+ from agentcp.heartbeat.heartbeat_client import HeartbeatClient
42
+ from agentcp.message import AgentInstructionBlock, AssistantMessageBlock
43
+ from agentcp.msg.session_manager import Session, SessionManager
44
+ from agentcp.file.file_client import FileClient
45
+ from .llm_server import add_llm_aid, add_llm_api_key, get_base_url, get_llm_api_key, llm_server_is_running, run_server
46
+ from agentcp.improved_scheduler import ImprovedMessageScheduler
47
+ from agentcp.metrics import MessageMetrics
48
+ from agentcp.base.log import log_info, log_error, log_exception, log_warning, log_debug,set_log_enabled
49
+
50
+ class _AgentCP(abc.ABC):
51
+ """
52
+ AgentCP类的抽象基类
53
+ """
54
+
55
+ def __init__(self):
56
+ self.shutdown_flag = threading.Event() # 初始化信号量
57
+ self.exit_hook_func = None
58
+
59
+ def register_signal_handler(self, exit_hook_func=None):
60
+ """
61
+ 注册信号处理函数
62
+ """
63
+ try:
64
+ import signal
65
+
66
+ signal.signal(signal.SIGTERM, self.signal_handle)
67
+ signal.signal(signal.SIGINT, self.signal_handle)
68
+ self.exit_hook_func = exit_hook_func
69
+ except Exception:
70
+ return
71
+
72
+ def serve_forever(self):
73
+ """ """
74
+ while not self.shutdown_flag.is_set():
75
+ time.sleep(1)
76
+
77
+ def signal_handle(self, signum, frame):
78
+ """
79
+ 信号处理函数
80
+ :param signum: 信号编号
81
+ :param frame: 当前栈帧
82
+ """
83
+ self.shutdown_flag.set() # 设置关闭标志
84
+ if self.exit_hook_func:
85
+ self.exit_hook_func(signum, frame)
86
+
87
+
88
+ class AgentID(abc.ABC):
89
+ def __init__(self, id: str, app_path: str, seed_password: str, ca_client, ep_url, debug=False):
90
+ super().__init__()
91
+ self.shutdown_flag = threading.Event() # 初始化信号量
92
+ self.public_data_path = os.path.join(app_path, "AIDs", id, "public")
93
+ self.private_data_path = os.path.join(app_path, "AIDs", id, "private")
94
+ os.makedirs(self.public_data_path, exist_ok=True)
95
+ os.makedirs(self.private_data_path, exist_ok=True)
96
+ self.ca_root_path = os.path.join(app_path, "Certs", "root")
97
+ os.makedirs(self.ca_root_path, exist_ok=True)
98
+ ca_root = CARoot()
99
+ ca_root.set_ca_root_crt(self.ca_root_path)
100
+ self.id = id
101
+ array = id.split(".")
102
+ self.ap = array[-2] + "." + array[-1]
103
+ self.name = ""
104
+ self.avaUrl = ""
105
+ self.description = ""
106
+ self.ap_client = None
107
+ self.session_manager = None
108
+ self.ca_client: CAClient = ca_client
109
+ self.ep_url = ep_url
110
+ self.seed_password = seed_password
111
+ self.message_from_acp = False
112
+ self.message_handlers = [] # 添加消息监听器属性
113
+ self.message_handlers_session_map = {} # 添加消息监听器属性
114
+ self.message_handlers_router_map = {} # 添加消息监听器属性
115
+ self.heartbeat_client = None
116
+ self.db_manager = DBManager(self.private_data_path, id)
117
+ self.debug = debug
118
+
119
+ # ✅ 使用新的改进调度器替代原有线程池
120
+ self.use_improved_scheduler = True # 可以通过参数控制是否启用
121
+ if self.use_improved_scheduler:
122
+ self.message_scheduler = ImprovedMessageScheduler(
123
+ core_workers=20, # 核心线程数
124
+ max_workers=50, # 最大线程数
125
+ max_tasks_per_worker=10 # 每线程最大异步任务数
126
+ )
127
+ else:
128
+ # 保留原有实现作为备份
129
+ self.thread_pool = ThreadPoolExecutor(max_workers=200)
130
+ self.task_queue = queue.Queue()
131
+ self.active_threads = 0
132
+ self.thread_lock = threading.Lock()
133
+
134
+ self.is_online_success = False
135
+ self.file_client = None
136
+
137
+ # ✅ 断开回调:当 WebSocket 连接断开时通知外部
138
+ self._disconnect_callback = None
139
+
140
+ # ✅ P1-3新增: 消息处理指标
141
+ self.metrics = MessageMetrics()
142
+
143
+ # ✅ 修复WebSocket阻塞: 消息派发队列(无阻塞,避免阻塞WebSocket线程)
144
+ self.message_dispatch_queue = queue.Queue(maxsize=10000) # 大容量队列
145
+ self._message_dispatcher_thread = None
146
+ self._message_dispatcher_running = False
147
+ self._start_message_dispatcher()
148
+
149
+ # ✅ Metrics定时同步到JSON文件
150
+ # 使用项目根目录的backup文件夹(app_path是./agentcp,需要获取其父目录作为项目根目录)
151
+ project_root = os.path.abspath(os.path.dirname(app_path)) # 从 "./agentcp" 获取项目根目录
152
+ backup_dir = os.path.join(project_root, "backup")
153
+ os.makedirs(backup_dir, exist_ok=True)
154
+ self.metrics_file_path = os.path.join(backup_dir, "metrics.json")
155
+ print(f"[DEBUG] AgentID初始化: app_path = {app_path}")
156
+ print(f"[DEBUG] AgentID初始化: project_root = {project_root}")
157
+ print(f"[DEBUG] AgentID初始化: metrics_file_path = {self.metrics_file_path}")
158
+ self._metrics_sync_thread = None
159
+ self._metrics_sync_running = False
160
+ print(f"[DEBUG] 即将启动metrics同步线程...")
161
+ self._start_metrics_sync()
162
+ print(f"[DEBUG] _start_metrics_sync()调用完成")
163
+
164
+ # ✅ 新增: 启动统一监控服务(集成滑动窗口和时间序列存储)
165
+ # ⚠️ 监控服务启动失败不应影响核心流程
166
+ self.monitoring_service = None
167
+ try:
168
+ from agentcp.monitoring.monitoring_service import MonitoringService
169
+ from agentcp.monitoring.global_monitor import set_global_monitoring_service
170
+
171
+ db_path = os.path.join(backup_dir, "metrics_timeseries.db")
172
+ self.monitoring_service = MonitoringService(
173
+ agent_id=id,
174
+ metrics_collector=self.metrics,
175
+ db_path=db_path,
176
+ snapshot_interval=10 # 每10秒采集一次
177
+ )
178
+ self.monitoring_service.start()
179
+
180
+ # ✅ 注册为全局监控服务(供Server访问)
181
+ set_global_monitoring_service(self.monitoring_service)
182
+
183
+ log_info(f"📊 [AgentID] 监控服务已启动: {id}")
184
+ except Exception as e:
185
+ log_error(f"⚠️ [AgentID] 监控服务启动失败(不影响核心功能): {e}")
186
+ self.monitoring_service = None
187
+
188
+ # 代理配置
189
+ self._use_system_proxy = False
190
+ self._proxy_config_path = os.path.join(self.private_data_path, "proxy_config.json")
191
+ self._load_proxy_config()
192
+
193
+
194
+ def get_app_path(self):
195
+ return self.public_data_path
196
+
197
+ def get_agent_public_path(self):
198
+ return self.public_data_path
199
+
200
+ def get_agent_private_path(self):
201
+ return self.private_data_path
202
+
203
+ def _load_proxy_config(self):
204
+ """从磁盘加载代理配置"""
205
+ try:
206
+ if os.path.exists(self._proxy_config_path):
207
+ with open(self._proxy_config_path, 'r', encoding='utf-8') as f:
208
+ config = json.load(f)
209
+ self._use_system_proxy = config.get('use_system_proxy', False)
210
+ log_info(f"[AgentID] 加载代理配置: use_system_proxy={self._use_system_proxy}")
211
+ else:
212
+ # 首次初始化,创建默认配置文件
213
+ self._save_proxy_config()
214
+ log_info(f"[AgentID] 创建默认代理配置文件: {self._proxy_config_path}")
215
+ except Exception as e:
216
+ log_error(f"[AgentID] 加载代理配置失败: {e}")
217
+ self._use_system_proxy = False
218
+
219
+ def _save_proxy_config(self):
220
+ """保存代理配置到磁盘"""
221
+ try:
222
+ config = {
223
+ 'use_system_proxy': self._use_system_proxy
224
+ }
225
+ with open(self._proxy_config_path, 'w', encoding='utf-8') as f:
226
+ json.dump(config, f, ensure_ascii=False, indent=2)
227
+ log_info(f"[AgentID] 保存代理配置: use_system_proxy={self._use_system_proxy}")
228
+ except Exception as e:
229
+ log_error(f"[AgentID] 保存代理配置失败: {e}")
230
+
231
+ def get_use_system_proxy(self) -> bool:
232
+ """获取是否使用系统代理"""
233
+ return self._use_system_proxy
234
+
235
+ def set_use_system_proxy(self, use_proxy: bool):
236
+ """设置是否使用系统代理
237
+
238
+ Args:
239
+ use_proxy: True表示使用系统代理,False表示不使用
240
+ """
241
+ # 先更新内存中的配置
242
+ self._use_system_proxy = use_proxy
243
+ # 再保存到磁盘
244
+ self._save_proxy_config()
245
+ log_info(f"[AgentID] 更新代理配置: use_system_proxy={use_proxy}")
246
+
247
+ def init_ap_client(self):
248
+ self.ap_client = ApClient(self.id, self.ep_url, self.ca_client.get_aid_certs_path(self.id), self.seed_password)
249
+ self.ap_client.set_agent_id_ref(self)
250
+ self.ap_client.initialize()
251
+
252
+ def online(self):
253
+ print(f"{self.id} online")
254
+ try:
255
+ if self.ap_client is None:
256
+ self.ap_client = ApClient(
257
+ self.id, self.ep_url, self.ca_client.get_aid_certs_path(self.id), self.seed_password
258
+ )
259
+ self.ap_client.set_agent_id_ref(self)
260
+ self.ap_client.initialize()
261
+ if self.ap_client.get_heartbeat_server() is None or self.ap_client.get_heartbeat_server() == "":
262
+ raise Exception("获取心跳服务器地址失败")
263
+
264
+ log_debug("initialzing heartbeat server")
265
+ if self.heartbeat_client is not None:
266
+ self.heartbeat_client.offline()
267
+ self.heartbeat_client.sign_out()
268
+ self.heartbeat_client = None
269
+
270
+ self.heartbeat_client = HeartbeatClient(
271
+ self.id,
272
+ self.ap_client.get_heartbeat_server(),
273
+ self.ca_client.get_aid_certs_path(self.id),
274
+ self.seed_password,
275
+ )
276
+ self.heartbeat_client.initialize()
277
+
278
+ if self.session_manager is not None:
279
+ try:
280
+ self.session_manager.close_all_session()
281
+ self.session_manager = None
282
+ except Exception as e:
283
+ log_exception(f"close session error: {e}")
284
+
285
+ self.session_manager = SessionManager(
286
+ self.id,
287
+ self.ap_client.get_message_server(),
288
+ self.ca_client.get_aid_certs_path(self.id),
289
+ self.seed_password,
290
+ self.db_manager,
291
+ agent_id_ref=self,
292
+ )
293
+ self.session_manager.set_on_message_receive(self.__agentid_message_listener)
294
+ self.session_manager.set_on_invite_ack(self.__on_invite_ack)
295
+ self.session_manager.set_on_session_message_ack(self.__on_session_message_ack)
296
+ self.session_manager.set_on_system_message(self.__on_system_message)
297
+ self.session_manager.set_on_member_list_receive(self.__on_member_list_receive)
298
+ self.__connect()
299
+ add_llm_aid(self)
300
+ self.is_online_success = True
301
+ except Exception as e:
302
+ log_exception(f"agent online error: {e}")
303
+ ErrorContext.publish(exceptions.SDKError(message=f"agent online error: {e}"))
304
+ self.is_online_success = False
305
+
306
+ def offline(self):
307
+ """离线状态"""
308
+ # ✅ 修复WebSocket阻塞: 先停止消息派发线程
309
+ if hasattr(self, '_stop_message_dispatcher'):
310
+ self._stop_message_dispatcher()
311
+
312
+ # ✅ 停止metrics同步线程
313
+ if hasattr(self, '_stop_metrics_sync'):
314
+ self._stop_metrics_sync()
315
+
316
+ # ✅ 停止监控服务(非阻塞,不影响主流程)
317
+ if hasattr(self, 'monitoring_service') and self.monitoring_service:
318
+ try:
319
+ self.monitoring_service.stop(wait=False) # 不等待线程结束
320
+ log_info(f"📊 [AgentID] 监控服务停止信号已发送: {self.id}")
321
+ except Exception as e:
322
+ log_error(f"⚠️ [AgentID] 停止监控服务异常(不影响核心功能): {e}")
323
+
324
+ if self.heartbeat_client:
325
+ self.heartbeat_client.offline()
326
+ self.heartbeat_client.sign_out()
327
+ self.heartbeat_client = None
328
+ if self.ap_client:
329
+ self.ap_client.sign_out()
330
+ self.ap_client = None
331
+ if self.session_manager:
332
+ self.session_manager.close_all_session()
333
+ self.session_manager = None
334
+
335
+ # ✅ 关闭改进的调度器
336
+ if self.use_improved_scheduler and hasattr(self, 'message_scheduler'):
337
+ log_info("正在关闭消息调度器...")
338
+ self.message_scheduler.print_stats()
339
+ self.message_scheduler.shutdown(wait=True)
340
+
341
+ def get_aid_info(self):
342
+ return {
343
+ "aid": self.id,
344
+ "name": self.name,
345
+ "description": self.description,
346
+ "avaUrl": self.avaUrl,
347
+ "ep_url": self.ep_url,
348
+ }
349
+
350
+ def get_metrics(self) -> dict:
351
+ """✅ P1-3新增: 获取消息处理指标
352
+
353
+ Returns:
354
+ 包含所有指标的字典
355
+ """
356
+ if hasattr(self, 'metrics'):
357
+ return self.metrics.get_summary()
358
+ return {}
359
+
360
+ def print_metrics(self):
361
+ """✅ P1-3新增: 打印消息处理指标"""
362
+ if hasattr(self, 'metrics'):
363
+ self.metrics.print_summary()
364
+ else:
365
+ print("⚠️ 指标收集未启用")
366
+
367
+ def delete_friend_agent(self, aid):
368
+ return self.db_manager.delete_friend_agent(aid)
369
+
370
+ def delete_session(self, session_id):
371
+ self.session_manager.close_session(session_id)
372
+ return self.db_manager.delete_session(session_id)
373
+
374
+ def get_message_list(self, session_id, page=1, page_size=10):
375
+ return self.db_manager.get_message_list(self.id, session_id, page, page_size)
376
+
377
+ def get_llm_message_list(self, session_id, page=1, page_size=10):
378
+ message_list = self.get_message_list(self.id, session_id, page, page_size)
379
+ if message_list is None or len(message_list) == 0:
380
+ return []
381
+ llm_message_list = []
382
+ for message in message_list:
383
+ sender = self.get_sender_from_message(message)
384
+ content = self.get_content_from_message(message)
385
+ reciver = self.get_receiver_from_message(message)
386
+ if sender != self.id and self.id not in reciver:
387
+ continue
388
+ if sender == self.id:
389
+ msg = {"role": "assistant", "content": content}
390
+ else:
391
+ msg = {"role": "user", "content": content}
392
+ llm_message_list.append(msg)
393
+ return llm_message_list
394
+
395
+ def add_message_handler(
396
+ self,
397
+ handler: typing.Callable[[dict], typing.Awaitable[None]],
398
+ session_id: str = "",
399
+ router: str = "",
400
+ from_acp: bool = False,
401
+ ):
402
+ """消息监听器装饰器"""
403
+ log_debug("add message handler")
404
+ if self.message_from_acp == False or (session_id == "" and router == ""):
405
+ self.message_from_acp = from_acp
406
+
407
+ if session_id == "" and router == "":
408
+ self.message_handlers.append(handler)
409
+ elif session_id != "":
410
+ self.message_handlers_session_map[session_id] = handler
411
+ else:
412
+ self.message_handlers_router_map[router] = handler
413
+
414
+ def remove_message_handler(self, handler: typing.Callable[[dict], typing.Awaitable[None]], session_id):
415
+ """移除消息监听器"""
416
+ if session_id == "":
417
+ if handler in self.message_handlers:
418
+ self.message_handlers.remove(handler)
419
+ else:
420
+ self.message_handlers_session_map.pop(session_id, None)
421
+
422
+ def create_session(self, name, subject, *, type="public"):
423
+ """创建与多个agent的会话
424
+ :param name: 群组名称
425
+ :param subject: 群组主题
426
+ :param to_aid_list: 目标agent ID列表
427
+ :return: 会话ID或None
428
+ """
429
+ log_debug(f"create session: {name}, subject: {subject}, type: {type}")
430
+ session = self.session_manager.create_session(name, subject, type)
431
+ if session is None:
432
+ log_error("failed to create session")
433
+ return None
434
+ self.__insert_session(self.id, session.session_id, session.identifying_code, name)
435
+ return session.session_id
436
+
437
+ def invite_member(self, session_id, to_aid):
438
+ if self.session_manager.invite_member(session_id, to_aid):
439
+ self.db_manager.invite_member(self.id, session_id, to_aid)
440
+ else:
441
+ log_error(f"failed to invite: {to_aid} -> {session_id}")
442
+
443
+ def get_online_status(self, aids):
444
+ return self.heartbeat_client.get_online_status(aids)
445
+
446
+ def get_conversation_list(self, page, page_size):
447
+ return self.db_manager.get_conversation_list(self.id, page, page_size)
448
+
449
+ # file/binary
450
+ async def create_stream(
451
+ self, session_id, to_aid_list, content_type: str = "text/event-stream", ref_msg_id: str = ""
452
+ ):
453
+ return await self.session_manager.create_stream(session_id, to_aid_list, content_type, ref_msg_id)
454
+
455
+ def close_session(self, session_id):
456
+ return self.session_manager.close_session(session_id)
457
+
458
+ def close_stream(self, session_id, stream_url):
459
+ return self.session_manager.close_stream(session_id, stream_url)
460
+
461
+ def send_chunk_to_stream(self, session_id, stream_url, chunk,type="text/event-stream"):
462
+ return self.session_manager.send_chunk_to_stream(session_id, stream_url, chunk, type = type)
463
+
464
+ def send_chunk_to_file_stream(self, session_id,push_url,offset: int, chunk: bytes):
465
+ return self.session_manager.send_chunk_to_file_stream(session_id,push_url,offset,chunk)
466
+
467
+
468
+ def __quick_send_message_base(self, to_aid, asnyc_message_result):
469
+ session_id = self.create_session("quick session", "")
470
+ if session_id is None:
471
+ raise Exception("failed to create session")
472
+
473
+ async def __asnyc_message_result(data):
474
+ self.remove_message_handler(__asnyc_message_result, session_id=session_id)
475
+ if asnyc_message_result is not None:
476
+ await asnyc_message_result(data)
477
+
478
+ self.invite_member(session_id, to_aid)
479
+ if asnyc_message_result is not None:
480
+ self.add_message_handler(__asnyc_message_result, session_id=session_id)
481
+ return session_id
482
+
483
+ def quick_send_message_content(self, to_aid: str, message_content: str, asnyc_message_result):
484
+ session_id = self.__quick_send_message_base(to_aid, asnyc_message_result)
485
+ return self.send_message_content(session_id, [to_aid], message_content)
486
+
487
+ def reply_message(self, msg: dict, message: Union[AssistantMessageBlock, list[AssistantMessageBlock], dict, str]):
488
+ session_id = msg.get("session_id", "")
489
+ if session_id == "":
490
+ log_error("failed to get session id")
491
+ return False
492
+ to_aid_list = [msg.get("sender", "")]
493
+ ref_msg_id = msg.get("message_id", "")
494
+ return self.send_message(session_id, to_aid_list, message, ref_msg_id)
495
+
496
+ def quick_send_message(
497
+ self,
498
+ to_aid: str,
499
+ message: Union[AssistantMessageBlock, list[AssistantMessageBlock], dict],
500
+ asnyc_message_result,
501
+ insert_message: bool = True,
502
+ ):
503
+ session_id = self.__quick_send_message_base(to_aid, asnyc_message_result)
504
+ self.send_message(session_id, [to_aid], message, insert_message=insert_message)
505
+ return session_id
506
+
507
+ def send_message_content(
508
+ self, session_id: str, to_aid_list: list, llm_content: str, ref_msg_id: str = "", message_id: str = ""
509
+ ):
510
+ # 处理对象转换为字典
511
+ if session_id == "" or session_id is None:
512
+ return
513
+ if llm_content == "" or llm_content is None:
514
+ return
515
+ msg_block = {
516
+ "type": "content",
517
+ "status": "success",
518
+ "timestamp": int(time.time() * 1000),
519
+ "content": llm_content,
520
+ }
521
+ return self.send_message(session_id, to_aid_list, msg_block, ref_msg_id, message_id)
522
+
523
+ def insert_message(
524
+ self,
525
+ role,
526
+ aid,
527
+ session_id,
528
+ to_aids,
529
+ message: Union[AssistantMessageBlock, list[AssistantMessageBlock], dict, str],
530
+ parent_message_id="",
531
+ message_id: str = "",
532
+ ):
533
+ # 处理对象转换为字典
534
+ if isinstance(message, (AssistantMessageBlock, dict)):
535
+ message_data = [message.__dict__ if hasattr(message, "__dict__") else message] # 将字典转换为列表
536
+ elif isinstance(message, list):
537
+ message_data = [msg.__dict__ if hasattr(msg, "__dict__") else msg for msg in message] # 保持列表类型
538
+ elif isinstance(message, str):
539
+ message_data = [
540
+ {"type": "content", "status": "success", "timestamp": int(time.time() * 1000), "content": message}
541
+ ] # 将字符串转换为包含单个字典的列表
542
+ if message_id == "" or message_id is None:
543
+ message_id = str(int(time.time() * 1000))
544
+ self.db_manager.insert_message(
545
+ role,
546
+ aid,
547
+ session_id,
548
+ aid,
549
+ parent_message_id,
550
+ ",".join(to_aids),
551
+ "",
552
+ json.dumps(message_data),
553
+ "sent",
554
+ message_id,
555
+ )
556
+
557
+ # 发送自定义指令消息
558
+ def send_instruction_message(
559
+ self, session_id: str, to_aid: str, agent_cmd_block: AgentInstructionBlock = None, ref_msg_id: str = ""
560
+ ):
561
+ # 处理对象转换为字典
562
+ if session_id == "" or session_id is None:
563
+ return
564
+ return self.send_message(session_id, [to_aid], None, agent_cmd_block=agent_cmd_block, ref_msg_id=ref_msg_id)
565
+
566
+ def send_form_message(self, session_id: str, to_aid_list: [], result: [], ref_msg_id: str):
567
+ try:
568
+ # 处理对象转换为字典
569
+ if session_id == "" or session_id is None:
570
+ return
571
+ save_message_list = self.db_manager.get_message_by_id(self.id, session_id, ref_msg_id)
572
+ if save_message_list is None or len(save_message_list) == 0:
573
+ return
574
+ msg = save_message_list[0]
575
+
576
+ msg_block = json.loads(msg["content"])[0]
577
+
578
+ if msg_block["type"] != "form":
579
+ return
580
+ form_list = msg_block["content"]
581
+ index = 0
582
+ for form in form_list:
583
+ form["result"] = json.dumps(result[index])
584
+ index = index + 1
585
+ msg["content"] = []
586
+ msg["content"].append(msg_block)
587
+ self.db_manager.update_message(msg)
588
+
589
+ msg_array = []
590
+ content = {"result": result}
591
+ msg_block_result = {"type": "form_result", "content": content}
592
+ msg_array.append(msg_block_result)
593
+ return self.session_manager.send_msg(session_id, msg_array, ";".join(to_aid_list), ref_msg_id, "", None)
594
+ except Exception as e:
595
+ log_exception(f"send_form_message failed: {e}")
596
+ return
597
+
598
+ def upload_file(self,full_path):
599
+ if self.file_client is None:
600
+ self.file_client = FileClient(self.ca_client.get_aid_certs_path(self.id),self.seed_password,self.id,self.ap,agent_id_ref=self)
601
+ self.file_client.sign_in()
602
+ return self.file_client.post_file(full_path)
603
+
604
+ def download_file(self,url,file_path):
605
+ if self.file_client is None:
606
+ domain = url.split("//")[1].split("/")[0]
607
+ # domain = 'oss.modelgate.us'
608
+ main_domain = '.'.join(domain.split('.')[1:])
609
+ self.file_client = FileClient(self.ca_client.get_aid_certs_path(self.id),self.seed_password,self.id,main_domain,agent_id_ref=self)
610
+ self.file_client.sign_in()
611
+ return self.file_client.download_file(url,file_path)
612
+
613
+ async def upload_file_async(self, full_path):
614
+ """异步上传文件方法
615
+
616
+ Args:
617
+ full_path: 文件的完整路径
618
+
619
+ Returns:
620
+ str: 上传成功后的文件URL,失败返回None
621
+ """
622
+ import aiohttp
623
+ import aiofiles
624
+ import os
625
+ from agentcp.file.file_client import FileClient
626
+
627
+ try:
628
+ # 初始化文件客户端
629
+ if self.file_client is None:
630
+ self.file_client = FileClient(self.ca_client.get_aid_certs_path(self.id),self.seed_password,self.id, self.ap,agent_id_ref=self)
631
+ # 注意:这里的sign_in仍然是同步的,如果需要完全异步,需要修改FileClient
632
+ self.file_client.sign_in()
633
+
634
+ if self.file_client.signature is None:
635
+ print("upload_file_async failed: signature is None")
636
+ return None
637
+
638
+ # 准备上传参数
639
+ params = {
640
+ 'agent_id': self.file_client.agent_id,
641
+ 'signature': self.file_client.signature,
642
+ 'file_name': os.path.basename(full_path)
643
+ }
644
+
645
+ upload_url = self.file_client.server_url + "/upload_file"
646
+
647
+ # 使用aiohttp进行异步文件上传
648
+ async with aiohttp.ClientSession() as session:
649
+ async with aiofiles.open(full_path, 'rb') as file:
650
+ file_content = await file.read()
651
+
652
+ data = aiohttp.FormData()
653
+ # 添加表单参数
654
+ for key, value in params.items():
655
+ data.add_field(key, value)
656
+ # 添加文件
657
+ data.add_field('file', file_content, filename=os.path.basename(full_path))
658
+
659
+ async with session.post(upload_url, data=data, ssl=False) as response:
660
+ if response.status == 200:
661
+ result = await response.json()
662
+ print('文件异步上传成功')
663
+ return result.get("url")
664
+ else:
665
+ print(f'文件异步上传失败,状态码: {response.status}')
666
+ return None
667
+
668
+ except FileNotFoundError:
669
+ print('文件未找到')
670
+ return None
671
+ except Exception as e:
672
+ print(f'异步上传发生错误: {e}')
673
+ return None
674
+
675
+
676
+ def send_message(
677
+ self,
678
+ sessionId: str,
679
+ to_aid_list: list,
680
+ message: Union[AssistantMessageBlock, list[Union[AssistantMessageBlock]], dict, str],
681
+ ref_msg_id: str = "",
682
+ message_id: str = "",
683
+ agent_cmd_block: AgentInstructionBlock = None,
684
+ insert_message: bool = True
685
+ ):
686
+ # 处理对象转换为字典
687
+ if self.is_online_success == False:
688
+ self.online()
689
+ if self.is_online_success == False:
690
+ return False
691
+
692
+ # 处理消息格式转换
693
+ if message == None or message == "":
694
+ message_data = []
695
+ elif isinstance(message, (AssistantMessageBlock, dict)):
696
+ message_data = [message.__dict__ if hasattr(message, "__dict__") else message] # 将字典转换为列表
697
+ elif isinstance(message, list):
698
+ message_data = [msg.__dict__ if hasattr(msg, "__dict__") else msg for msg in message] # 保持列表类型
699
+ elif isinstance(message, str):
700
+ message_data = [
701
+ {"type": "content", "status": "success", "timestamp": int(time.time() * 1000), "content": message}
702
+ ] # 将字符串转换为包含单个字典的列表
703
+ else:
704
+ message_data = []
705
+ if message_id == "" or message_id is None:
706
+ message_id = str(int(time.time() * 1000))
707
+ instruction = ""
708
+ if agent_cmd_block is not None:
709
+ instruction = json.dumps(agent_cmd_block)
710
+ if insert_message:
711
+ self.db_manager.insert_message(
712
+ "user",
713
+ self.id,
714
+ sessionId,
715
+ self.id,
716
+ ref_msg_id,
717
+ ",".join(to_aid_list),
718
+ instruction,
719
+ json.dumps(message_data),
720
+ "text",
721
+ "sent",
722
+ message_id,
723
+ )
724
+ return self.session_manager.send_msg(
725
+ sessionId, message_data, ";".join(to_aid_list), ref_msg_id, message_id, agent_cmd_block
726
+ )
727
+
728
+ def get_agent_profile(self, aid_str):
729
+ return self.ap_client.get_agent_profile(aid_str)
730
+
731
+ def get_agent_public_data(self):
732
+ return self.ap_client.get_agent_public_data(self.id)
733
+
734
+ def sync_public_files(self) -> bool:
735
+ return self.ap_client.sync_public_files(self.public_data_path)
736
+ # https://oss.aid.pub/api/oss/upload_file, post agent_id, signature, file
737
+ # def upload_file():
738
+
739
+
740
+ def get_my_profile_data(self):
741
+ path = os.path.join(self.public_data_path, "agentprofile.json")
742
+ try:
743
+ with open(path, "r", encoding="utf-8") as file:
744
+ return json.load(file)
745
+ except FileNotFoundError:
746
+ log_error(f"文件不存在: {path}")
747
+ return None
748
+ except json.JSONDecodeError:
749
+ log_error(f"文件格式错误: {path}")
750
+ return None
751
+ except Exception as e:
752
+ log_error(f"读取文件时出错: {path}, 错误: {e}")
753
+ return None
754
+
755
+ def get_publisher_info(self):
756
+ return {"publisherAid": self.id, "organization": self.ap, "certificationSignature": self.ap}
757
+
758
+ def create_agent_profile(self, json_data, supportDiscover=True):
759
+ check_result = self.__check_agent_profile(json_data)
760
+ if check_result == False:
761
+ raise Exception("agent profile check failed, please check your agent profile")
762
+ public_data_path = self.get_agent_public_path()
763
+ agent_profile_path = os.path.join(public_data_path, "agentprofile.json")
764
+ agent_html_path = os.path.join(public_data_path, "index.html")
765
+ agent_config_path = os.path.join(public_data_path, "config.json")
766
+ if not os.path.exists(agent_config_path):
767
+ self.__create_config_file(agent_config_path, public_data_path, supportDiscover)
768
+ # 如果文件存在,重命名为temp.json
769
+ self.__create_new_file(json_data, agent_profile_path, public_data_path)
770
+ self.__create_html_file(json_data, agent_html_path)
771
+ log_debug("agent profile created successfully")
772
+
773
+ def __create_config_file(self, agent_config_path, public_data_path, supportDiscover):
774
+ data = {
775
+ "homepage": "index.html",
776
+ "supportDiscover": supportDiscover,
777
+ }
778
+ self.__create_new_file(data, agent_config_path, public_data_path)
779
+
780
+ def __create_html_file(self, json_data, agent_html_path):
781
+ if os.path.exists(agent_html_path):
782
+ os.remove(agent_html_path)
783
+ html_content = parse_html(json_data)
784
+ # 将生成的 HTML 内容写入文件
785
+ with open(agent_html_path, "w", encoding="utf-8") as f:
786
+ f.write(html_content)
787
+
788
+ def __create_new_file(self, json_data, agent_profile_path, public_data_path):
789
+ os.path.exists(public_data_path) or os.mkdir(public_data_path)
790
+ # parse_html
791
+ temp_path = os.path.join(public_data_path, "temp.json")
792
+ if os.path.exists(agent_profile_path):
793
+ os.rename(agent_profile_path, temp_path)
794
+
795
+ str_data = json.dumps(json_data)
796
+ self.__write_to_file(str_data, agent_profile_path)
797
+ # 删除临时文件
798
+ if os.path.exists(temp_path):
799
+ os.remove(temp_path)
800
+
801
+ def __write_to_file(self, data, filename):
802
+ """将JSON数据写入文件,带错误处理"""
803
+ try:
804
+ # 将set类型转换为list
805
+ with open(filename, "w", encoding="utf-8") as file:
806
+ file.write(data)
807
+ log_info(f"成功写入JSON文件: {filename}")
808
+ except (IOError, TypeError) as e:
809
+ log_error(f"写入文件时出错: {e}")
810
+ except Exception as e:
811
+ log_error(f"未知错误: {e}")
812
+
813
+ def __check_agent_profile(self, json_data):
814
+ """创建智能体配置文件
815
+ :param json_data: 包含智能体配置信息的字典
816
+ :return: 如果验证通过返回True,否则返回False
817
+ """
818
+ required_fields = {
819
+ "publisherInfo": dict,
820
+ "version": str,
821
+ "lastUpdated": str,
822
+ "name": str,
823
+ "description": str,
824
+ "capabilities": dict,
825
+ "llm": dict,
826
+ "references": dict,
827
+ "authorization": dict,
828
+ "input": dict,
829
+ "output": dict,
830
+ "avaUrl": str,
831
+ "supportStream": bool,
832
+ "supportAsync": bool,
833
+ "permission": list,
834
+ }
835
+
836
+ if not isinstance(json_data, dict):
837
+ log_error("json_data 必须是一个字典")
838
+ return False
839
+
840
+ ava_url = json_data.get("avaUrl", "")
841
+ if ava_url == "" or (not ava_url.startswith("http://") and not ava_url.startswith("https://")):
842
+ json_data["avaUrl"] = "https://stzbtool.oss-cn-hangzhou.aliyuncs.com/modelunion/acp.png"
843
+
844
+ for field, field_type in required_fields.items():
845
+ if field not in json_data:
846
+ log_error(f"缺少必填字段: {field}")
847
+ return False
848
+ if not isinstance(json_data[field], field_type):
849
+ log_error(f"字段 {field} 类型错误,应为 {field_type}")
850
+ return False
851
+
852
+ # 检查嵌套字段
853
+ if not all(key in json_data["capabilities"] for key in ["core", "extended"]):
854
+ log_error("capabilities 字段缺少 core 或 extended")
855
+ return False
856
+
857
+ if not all(key in json_data["references"] for key in ["knowledgeBases", "tools", "companyInfo", "productInfo"]):
858
+ log_error("references 字段缺少必要子字段")
859
+ return False
860
+
861
+ if not all(key in json_data["authorization"] for key in ["modes", "fee", "description", "sla"]):
862
+ log_error("authorization 字段缺少必要子字段")
863
+ return False
864
+
865
+ if not all(
866
+ key in json_data["input"] for key in ["types", "formats", "examples", "semantics", "compatibleAids"]
867
+ ):
868
+ log_error("input 字段缺少必要子字段")
869
+ return False
870
+
871
+ if not all(
872
+ key in json_data["output"] for key in ["types", "formats", "examples", "semantics", "compatibleAids"]
873
+ ):
874
+ log_error("output 字段缺少必要子字段")
875
+ return False
876
+
877
+ log_info("json_data 验证通过")
878
+ return True
879
+
880
+ def save_public_file(self, file_path: str, filename: str):
881
+ self.ap_client.save_public_file(file_path, filename)
882
+
883
+ def delete_public_file(self, file_path: str):
884
+ try:
885
+ if os.path.exists(file_path):
886
+ os.remove(file_path)
887
+ log_info(f"成功删除文件: {file_path}")
888
+ self.ap_client.delete_public_file(file_path)
889
+ else:
890
+ log_error(f"文件不存在: {file_path}")
891
+ except Exception as e:
892
+ log_exception(f"删除文件时出错: {file_path}, 错误: {e}")
893
+
894
+ def add_friend_agent(self, aid, name, description, avaUrl):
895
+ self.db_manager.add_friend_agent(aid, name, description, avaUrl)
896
+
897
+ def set_friend_name(self, aid, name):
898
+ self.db_manager.set_friend_agent(aid, name)
899
+
900
+ def get_friend_agent_list(self):
901
+ return self.db_manager.get_friend_agent_list(self.id)
902
+
903
+ def __on_heartbeat_invite_message(self, invite_req):
904
+ session: Session = self.session_manager.join_session(invite_req)
905
+
906
+ def __run_message_listeners(self, data):
907
+ """
908
+ 运行消息监听器 (旧版本,兼容性保留)
909
+ 新版本使用 __async_run_message_listeners
910
+ """
911
+ loop = asyncio.new_event_loop()
912
+ asyncio.set_event_loop(loop)
913
+ try:
914
+ session_id = data["session_id"]
915
+ cmd = data.get("instruction", None)
916
+ if session_id in self.message_handlers_session_map:
917
+ tasks = [self.__safe_call(self.message_handlers_session_map[session_id], data)]
918
+ loop.run_until_complete(asyncio.gather(*tasks))
919
+ elif cmd != None and cmd["cmd"] in self.message_handlers_router_map:
920
+ tasks = [self.__safe_call(self.message_handlers_router_map[cmd["cmd"]], data)]
921
+ loop.run_until_complete(asyncio.gather(*tasks))
922
+ else:
923
+ tasks = [self.__safe_call(func, data) for func in self.message_handlers]
924
+ loop.run_until_complete(asyncio.gather(*tasks))
925
+ finally:
926
+ loop.close()
927
+
928
+ async def __async_run_message_listeners(self, data):
929
+ """✅ P1-3增强: 异步运行消息监听器(带指标收集)
930
+
931
+ 这个函数会在工作线程的事件循环中运行
932
+ """
933
+ # ✅ P1-3: 记录 handler 开始时间
934
+ handler_start_time = time.time()
935
+ handler_success = False
936
+
937
+ try:
938
+ session_id = data["session_id"]
939
+ cmd = data.get("instruction", None)
940
+
941
+ if session_id in self.message_handlers_session_map:
942
+ await self.__safe_call(
943
+ self.message_handlers_session_map[session_id],
944
+ data
945
+ )
946
+ elif cmd is not None and cmd.get("cmd") in self.message_handlers_router_map:
947
+ await self.__safe_call(
948
+ self.message_handlers_router_map[cmd["cmd"]],
949
+ data
950
+ )
951
+ else:
952
+ # 并发执行所有处理器
953
+ tasks = [
954
+ self.__safe_call(func, data)
955
+ for func in self.message_handlers
956
+ ]
957
+ await asyncio.gather(*tasks, return_exceptions=True)
958
+
959
+ # ✅ P1-3: Handler 成功执行
960
+ handler_success = True
961
+
962
+ except Exception as e:
963
+ log_exception(f"消息处理失败: {e}")
964
+ # ✅ P1-3: Handler 失败
965
+ handler_success = False
966
+
967
+ finally:
968
+ # ✅ P1-3: 记录 handler 指标
969
+ handler_latency_ms = (time.time() - handler_start_time) * 1000
970
+
971
+ if handler_success:
972
+ self.metrics.record_handler_success(handler_latency_ms)
973
+ else:
974
+ self.metrics.record_handler_failure()
975
+
976
+ async def __safe_call(self, func, data):
977
+ import inspect
978
+ import time
979
+
980
+ func_name = getattr(func, '__name__', str(func))
981
+ start_time = time.time()
982
+ task = None
983
+
984
+ try:
985
+ sig = inspect.signature(func)
986
+ num_params = len(sig.parameters)
987
+
988
+ # 检查函数是否为协程函数
989
+ is_coro = asyncio.iscoroutinefunction(func)
990
+ # 如果被装饰器包装,可能需要额外检查
991
+ if hasattr(func, "__wrapped__") and not is_coro:
992
+ is_coro = asyncio.iscoroutinefunction(func.__wrapped__)
993
+
994
+ if not is_coro:
995
+ try:
996
+ if num_params == 2:
997
+ func(self, data)
998
+ elif num_params == 1:
999
+ func(data)
1000
+ else:
1001
+ # Handle cases where parameter count doesn't match expected
1002
+ # Or raise an error, log a warning, etc.
1003
+ print(f"Warning: Function {func_name} has unexpected number of parameters: {num_params}")
1004
+ return
1005
+ except Exception as e:
1006
+ print(f"Error calling function: {e}")
1007
+
1008
+ # 处理协程函数(带优雅的超时处理)
1009
+ try:
1010
+ # 创建协程任务
1011
+ if num_params == 2:
1012
+ coro = func(self, data)
1013
+ elif num_params == 1:
1014
+ coro = func(data)
1015
+ else:
1016
+ # Handle cases where parameter count doesn't match expected
1017
+ print(f"Warning: Async function {func_name} has unexpected number of parameters: {num_params}")
1018
+ return
1019
+
1020
+ # 使用 wait_for 设置超时,并保存任务引用
1021
+ task = asyncio.create_task(coro)
1022
+ await asyncio.wait_for(asyncio.shield(task), timeout=600.0) # 10分钟超时
1023
+
1024
+ except asyncio.TimeoutError:
1025
+ # 超时处理:尝试优雅取消任务
1026
+ elapsed = time.time() - start_time
1027
+ print(f"⚠️ [AgentCP] 函数 {func_name} 执行超时 (600s), 实际耗时: {elapsed:.2f}s")
1028
+
1029
+ # 尝试取消任务
1030
+ if task and not task.done():
1031
+ print(f"⚠️ [AgentCP] 正在取消超时任务: {func_name}")
1032
+ task.cancel()
1033
+ try:
1034
+ # 等待任务取消完成(最多1秒)
1035
+ await asyncio.wait_for(task, timeout=1.0)
1036
+ except asyncio.CancelledError:
1037
+ print(f"✅ [AgentCP] 任务已成功取消: {func_name}")
1038
+ except asyncio.TimeoutError:
1039
+ print(f"⚠️ [AgentCP] 任务取消超时,强制结束: {func_name}")
1040
+ except Exception as cancel_error:
1041
+ print(f"⚠️ [AgentCP] 取消任务时出错: {cancel_error}")
1042
+
1043
+ # 记录超时信息(不记录完整堆栈,避免日志过长)
1044
+ session_id = data.get('session_id', 'unknown')
1045
+ message_id = data.get('message_id', 'unknown')
1046
+ print(f"⚠️ [AgentCP] 超时详情 - session: {session_id}, message: {message_id}")
1047
+
1048
+ except asyncio.CancelledError:
1049
+ # 任务被外部取消
1050
+ elapsed = time.time() - start_time
1051
+ print(f"⚠️ [AgentCP] 函数 {func_name} 被取消, 耗时: {elapsed:.2f}s")
1052
+ # 不重新抛出,避免影响 worker 线程
1053
+
1054
+ except Exception as e:
1055
+ # 其他异常
1056
+ elapsed = time.time() - start_time
1057
+ import traceback
1058
+ print(f"❌ [AgentCP] 函数 {func_name} 执行异常 (耗时: {elapsed:.2f}s)")
1059
+ print(f" 异常类型: {type(e).__name__}")
1060
+ print(f" 异常信息: {str(e)[:200]}") # 限制错误信息长度
1061
+ # 只在调试模式打印完整堆栈
1062
+ if os.getenv('DEBUG') == '1':
1063
+ print(f" 完整堆栈:\n{traceback.format_exc()}")
1064
+
1065
+ except Exception as e:
1066
+ # 最外层异常保护
1067
+ elapsed = time.time() - start_time
1068
+ print(f"❌ [AgentCP] __safe_call 异常保护触发 (func: {func_name}, elapsed: {elapsed:.2f}s): {e}")
1069
+ # 确保不影响 worker 线程运行
1070
+
1071
+ def __on_member_list_receive(self, data):
1072
+ log_info(f"__on_member_list_receive:{data}")
1073
+
1074
+ def fetch_stream_message(self, message_data: dict) -> str:
1075
+ session_id = message_data["session_id"]
1076
+ message_id = message_data["message_id"]
1077
+ message = json.loads(message_data["message"])
1078
+ message_list = [] # 修改变量名避免与内置list冲突
1079
+ message_temp = None
1080
+ if isinstance(message, list):
1081
+ message_list = message
1082
+ message_temp = message_list[0] if isinstance(message_list[0], dict) else json.loads(message_list[0])
1083
+ else:
1084
+ message_list.append(message)
1085
+ message_temp = message
1086
+ save_message_list = self.db_manager.get_message_by_id(self.id, session_id, message_id)
1087
+ if "text/event-stream" == message_temp.get("type", ""):
1088
+ pull_url = message_temp.get("content", "")
1089
+ log_info("pull_url:" + pull_url)
1090
+ if pull_url == "":
1091
+ return ""
1092
+ return self.__fetch_stream_data(pull_url, save_message_list, message_data, message_list)
1093
+ return ""
1094
+
1095
+ def __get_vaild_json(self, text):
1096
+ try:
1097
+ json_data = json.loads(text)
1098
+ return json_data
1099
+ except Exception:
1100
+ return None
1101
+
1102
+ def __fetch_stream_data(self, pull_url, save_message_list, data, message_list):
1103
+ """通过 HTTPS 请求拉取流式数据"""
1104
+ try:
1105
+ session_id = data["session_id"]
1106
+ message_id = data["message_id"]
1107
+ ref_msg_id = data["ref_msg_id"]
1108
+ sender = data["sender"]
1109
+ receiver = data["receiver"]
1110
+ message = message_list[0]
1111
+ message["type"] = "content"
1112
+ message["extra"] = pull_url
1113
+ message["content"] = ""
1114
+ if save_message_list is None or len(save_message_list) == 0:
1115
+ self.db_manager.insert_message(
1116
+ "assistant",
1117
+ self.id,
1118
+ session_id,
1119
+ sender,
1120
+ ref_msg_id,
1121
+ receiver,
1122
+ "",
1123
+ json.dumps(message_list),
1124
+ "text",
1125
+ "success",
1126
+ message_id,
1127
+ )
1128
+ save_message_list = self.db_manager.get_message_by_id(self.id, session_id, message_id)
1129
+ if save_message_list is None or len(save_message_list) == 0:
1130
+ log_error(f"插入消息失败: {pull_url}")
1131
+ return
1132
+ msg_block = json.loads(save_message_list[0]["content"])[0]
1133
+ pull_url = pull_url + "&agent_id=" + self.id
1134
+ # pull_url = pull_url.replace("https://agentunion.cn","https://ts.agentunion.cn")
1135
+ try:
1136
+ response = requests.get(
1137
+ pull_url, stream=True, verify=False, timeout=(60, 600), proxies={}
1138
+ ) # 连接超时60秒,读取超时10分钟
1139
+ response.raise_for_status() # 检查HTTP状态码
1140
+ content_text = ""
1141
+ is_end = False
1142
+ for line in response.iter_lines():
1143
+ if line is None:
1144
+ log_error("保持连接-等待1")
1145
+ continue
1146
+ decoded_line = line.decode("utf-8")
1147
+ if not decoded_line.startswith("data:") and not decoded_line.startswith("event:"):
1148
+ if decoded_line == ": keep-alive":
1149
+ log_error("保持连接-等待2")
1150
+ continue
1151
+ decoded_url = urllib.parse.unquote_plus(decoded_line)
1152
+ if decoded_url is None:
1153
+ log_error("保持连接-等待3")
1154
+ continue
1155
+
1156
+ chunk = self.__get_vaild_json(decoded_url)
1157
+ # print(chunk)
1158
+ if chunk is None:
1159
+ content_text = content_text + decoded_url
1160
+ else:
1161
+ is_continue = False
1162
+ try:
1163
+ if len(chunk.get("choices", [])) == 0:
1164
+ continue
1165
+ is_continue = True
1166
+ except Exception:
1167
+ content_text = content_text + decoded_url
1168
+ try:
1169
+ if is_continue:
1170
+ content_text = content_text + chunk.get("choices", [])[0].get("delta", {}).get(
1171
+ "content", ""
1172
+ )
1173
+ except Exception:
1174
+ log_error(f"content_text: {content_text}")
1175
+
1176
+ msg_block["content"] = content_text
1177
+ else:
1178
+ key, value = decoded_line.split(":", 1)
1179
+ key = key.strip()
1180
+ value = value.strip()
1181
+ if key == "event" and value == "done":
1182
+ log_info("接收到的消息仅为 'done'")
1183
+ is_end = True
1184
+ msg_block["status"] = "success"
1185
+ else:
1186
+ decoded_url = urllib.parse.unquote_plus(value)
1187
+ if decoded_url is None:
1188
+ log_error("保持连接-等待3")
1189
+ continue
1190
+ chunk = self.__get_vaild_json(decoded_url)
1191
+ if chunk is None:
1192
+ content_text = content_text + decoded_url
1193
+ else:
1194
+ is_continue = False
1195
+ try:
1196
+ if len(chunk.get("choices", [])) == 0:
1197
+ continue
1198
+ is_continue = True
1199
+ except Exception:
1200
+ content_text = content_text + decoded_url
1201
+ try:
1202
+ if is_continue:
1203
+ content_text = content_text + chunk.get("choices", [{}])[0].get(
1204
+ "delta", {}
1205
+ ).get("content", "")
1206
+ except Exception:
1207
+ log_error(f"content_text: {content_text}")
1208
+ msg_block["content"] = content_text
1209
+ message_list = []
1210
+ message_list.append(msg_block)
1211
+ save_message_list[0]["content"] = json.dumps(message_list)
1212
+ if is_end:
1213
+ log_info(f"结束拉取流,{msg_block}")
1214
+ self.db_manager.update_message(save_message_list[0])
1215
+ return msg_block["content"]
1216
+ except requests.exceptions.Timeout:
1217
+ log_error(f"请求超时: {pull_url}")
1218
+ return ""
1219
+ except requests.exceptions.RequestException as e:
1220
+ log_error(f"请求失败: {pull_url}, 错误: {str(e)}")
1221
+ msg_block["status"] = "error"
1222
+ msg_block["type"] = "error"
1223
+ msg_block["content"] = "拉取流失败"
1224
+ message_list = []
1225
+ message_list.append(msg_block)
1226
+ save_message_list[0]["content"] = json.dumps(message_list)
1227
+ self.db_manager.update_message(save_message_list[0])
1228
+ return ""
1229
+ except Exception as e:
1230
+ import traceback
1231
+
1232
+ log_error(f"拉取流式数据时发生错误: {str(e)}\n{traceback.format_exc()}")
1233
+ log_error(f"请求失败: {pull_url}, 错误: {str(e)}")
1234
+ msg_block["status"] = "error"
1235
+ msg_block["type"] = "error"
1236
+ msg_block["content"] = "拉取流失败"
1237
+ message_list = []
1238
+ message_list.append(msg_block)
1239
+ save_message_list[0]["content"] = json.dumps(message_list)
1240
+ self.db_manager.update_message(save_message_list[0])
1241
+ return ""
1242
+
1243
+ def check_stream_url_exists(self, push_url):
1244
+ return self.session_manager.check_stream_url_exists(push_url)
1245
+
1246
+ def __404_message_insert(self, data):
1247
+ session_id = data["session_id"]
1248
+ acceptor_id = data["acceptor_id"]
1249
+ message_list = []
1250
+ msg_block = {
1251
+ "type": "error",
1252
+ "status": "success",
1253
+ "timestamp": int(time.time() * 1000), # 使用毫秒时间戳
1254
+ "content": f"该模型的服务商{acceptor_id}不在线 请您前往模型列表确认模型在线状态,或选择该模型的其它服务商重试",
1255
+ "extra": "",
1256
+ }
1257
+ message_list.append(msg_block)
1258
+ time.sleep(0.3)
1259
+ message_data = {
1260
+ "session_id": session_id,
1261
+ "ref_msg_id": "",
1262
+ "sender": acceptor_id,
1263
+ "receiver": self.id,
1264
+ "message": json.dumps(message_list),
1265
+ }
1266
+ self.__run_message_listeners(message_data)
1267
+
1268
+ def __on_invite_ack(self, data):
1269
+ status = int(data["status_code"])
1270
+ log_info(f"__on_invite_ack:{data}")
1271
+ if status == 404:
1272
+ thread = threading.Thread(target=self.__404_message_insert, args=(data,))
1273
+ thread.start()
1274
+
1275
+ def __on_session_message_ack(self, data):
1276
+ status = int(data["status_code"])
1277
+ if status == 404:
1278
+ offline_receivers: list = data["offline_receivers"]
1279
+ log_info(f"offline_receivers:{data}")
1280
+ if offline_receivers == None or len(offline_receivers) == 0:
1281
+ return
1282
+ for receiver in offline_receivers:
1283
+ data["acceptor_id"] = receiver
1284
+ thread = threading.Thread(target=self.__404_message_insert, args=(data,))
1285
+ thread.start()
1286
+
1287
+ def __on_system_message(self, data):
1288
+ event_type = data["event_type"]
1289
+ session_id = data["session_id"]
1290
+ if "Session dismissed" == event_type:
1291
+ self.session_manager.leave_session(session_id)
1292
+
1293
+ def __ping_message(self, data):
1294
+ msg_array = self.get_content_array_from_message(data)
1295
+ if len(msg_array) == 0:
1296
+ return False
1297
+ if msg_array[0].get("type") == "ping":
1298
+ msg_block = {
1299
+ "type": "content",
1300
+ "status": "success",
1301
+ "timestamp": int(time.time() * 1000), # 使用毫秒时间戳
1302
+ "content": "ping_result",
1303
+ }
1304
+ self.reply_message(data, msg_block)
1305
+ return True
1306
+ return False
1307
+
1308
+ def _start_message_dispatcher(self):
1309
+ """✅ 修复WebSocket阻塞: 启动消息派发线程
1310
+
1311
+ 派发线程负责:
1312
+ 1. 从无阻塞队列中取消息
1313
+ 2. 调用scheduler提交(可能阻塞)
1314
+ 3. 执行数据库操作(可能阻塞)
1315
+ 4. 失败消息记录日志并丢弃
1316
+ """
1317
+ if self._message_dispatcher_thread and self._message_dispatcher_thread.is_alive():
1318
+ return
1319
+
1320
+ self._message_dispatcher_running = True
1321
+ self._message_dispatcher_thread = threading.Thread(
1322
+ target=self._message_dispatcher_main,
1323
+ daemon=True,
1324
+ name="MessageDispatcher"
1325
+ )
1326
+ self._message_dispatcher_thread.start()
1327
+ log_info("🚀 消息派发线程已启动")
1328
+
1329
+ def _message_dispatcher_main(self):
1330
+ """✅ 修复WebSocket阻塞: 消息派发线程主循环
1331
+
1332
+ 工作流程:
1333
+ 1. 从dispatch_queue取消息(阻塞等待)
1334
+ 2. 提交到scheduler(允许阻塞,不影响WebSocket)
1335
+ 3. 执行数据库操作(允许阻塞,不影响WebSocket)
1336
+ 4. 失败消息记录日志并丢弃
1337
+ """
1338
+ log_info("🚀 消息派发线程开始运行")
1339
+
1340
+ while self._message_dispatcher_running and not self.shutdown_flag.is_set():
1341
+ try:
1342
+ # 从队列取消息(阻塞等待,超时1秒)
1343
+ try:
1344
+ message_task = self.message_dispatch_queue.get(timeout=1.0)
1345
+ except queue.Empty:
1346
+ continue
1347
+
1348
+ data = message_task['data']
1349
+ message_id = data.get('message_id', 'unknown')
1350
+ session_id = data.get('session_id', 'unknown')
1351
+ is_stream_message = message_task.get('is_stream_message', False)
1352
+ message_list = message_task.get('message_list', [])
1353
+ instruction = message_task.get('instruction')
1354
+ sender = data.get('sender', '')
1355
+ receiver = data.get('receiver', '')
1356
+ ref_msg_id = data.get('ref_msg_id', '')
1357
+
1358
+ # ✅ 提交到scheduler(允许阻塞45秒,不影响WebSocket线程)
1359
+ max_retries = 3
1360
+ submit_success = False
1361
+ last_error = None
1362
+ dispatch_start_time = time.time()
1363
+
1364
+ for attempt in range(max_retries):
1365
+ try:
1366
+ if self.use_improved_scheduler:
1367
+ success = self.message_scheduler.submit_message(
1368
+ self.__async_run_message_listeners,
1369
+ data,
1370
+ raise_on_reject=False
1371
+ )
1372
+
1373
+ if success:
1374
+ submit_success = True
1375
+ dispatch_latency_ms = (time.time() - dispatch_start_time) * 1000
1376
+ self.metrics.record_dispatch_success(dispatch_latency_ms)
1377
+ log_info(f"✅ [Dispatcher] 消息已提交: message_id={message_id[:16]}...")
1378
+ break
1379
+ else:
1380
+ last_error = "调度器拒绝任务"
1381
+ else:
1382
+ # 旧实现
1383
+ def task():
1384
+ with self.thread_lock:
1385
+ self.active_threads += 1
1386
+ try:
1387
+ self.__run_message_listeners(data)
1388
+ except Exception as e:
1389
+ log_exception(f"消息处理失败: {e}")
1390
+ finally:
1391
+ with self.thread_lock:
1392
+ self.active_threads -= 1
1393
+
1394
+ self.thread_pool.submit(task)
1395
+ submit_success = True
1396
+ break
1397
+
1398
+ except Exception as e:
1399
+ last_error = str(e)
1400
+
1401
+ # 重试前退避等待
1402
+ if not submit_success and attempt < max_retries - 1:
1403
+ wait_time = 0.05 * (2 ** attempt)
1404
+ time.sleep(wait_time)
1405
+
1406
+ # ✅ 提交失败,记录日志并丢弃消息
1407
+ if not submit_success:
1408
+ self.metrics.record_dispatch_failure()
1409
+ log_error(
1410
+ f"❌ [Dispatcher] 消息提交最终失败,已丢弃: "
1411
+ f"message_id={message_id[:16]}... "
1412
+ f"session_id={session_id} "
1413
+ f"error={last_error}"
1414
+ )
1415
+ continue
1416
+
1417
+ # ✅ 提交成功,执行数据库操作(允许阻塞,不影响WebSocket线程)
1418
+ if not is_stream_message:
1419
+ try:
1420
+ save_message_list = self.db_manager.get_message_by_id(
1421
+ self.id, session_id, message_id
1422
+ )
1423
+
1424
+ instruction_str = ""
1425
+ if instruction is not None:
1426
+ instruction_str = json.dumps(instruction)
1427
+
1428
+ if save_message_list is None or len(save_message_list) == 0:
1429
+ self.db_manager.insert_message(
1430
+ "assistant",
1431
+ self.id,
1432
+ session_id,
1433
+ sender,
1434
+ ref_msg_id,
1435
+ receiver,
1436
+ instruction_str,
1437
+ json.dumps(message_list),
1438
+ "text",
1439
+ "success",
1440
+ message_id,
1441
+ )
1442
+ else:
1443
+ save_message = save_message_list[0]
1444
+ content = save_message["content"]
1445
+ if isinstance(content, list):
1446
+ content.append(message_list)
1447
+ elif isinstance(content, str):
1448
+ content_list = json.loads(content)
1449
+ content_list.append(message_list)
1450
+ save_message["content"] = json.dumps(content_list)
1451
+ self.db_manager.update_message(save_message)
1452
+
1453
+ except Exception as e:
1454
+ log_exception(f"⚠️ [Dispatcher] 数据库操作失败(消息已派发): {e}")
1455
+
1456
+ except Exception as e:
1457
+ log_exception(f"❌ [Dispatcher] 派发循环异常: {e}")
1458
+ time.sleep(0.1)
1459
+
1460
+ log_info("🚀 消息派发线程已停止")
1461
+
1462
+ def _stop_message_dispatcher(self):
1463
+ """✅ 修复WebSocket阻塞: 停止消息派发线程"""
1464
+ if not self._message_dispatcher_thread:
1465
+ return
1466
+
1467
+ self._message_dispatcher_running = False
1468
+
1469
+ if self._message_dispatcher_thread.is_alive():
1470
+ self._message_dispatcher_thread.join(timeout=5.0)
1471
+
1472
+ self._message_dispatcher_thread = None
1473
+ log_info("✅ 消息派发线程已停止")
1474
+
1475
+ def _start_metrics_sync(self):
1476
+ """✅ 启动Metrics定时同步线程
1477
+
1478
+ 每2分钟将metrics数据同步到JSON文件
1479
+ """
1480
+ print(f"[DEBUG] _start_metrics_sync() 被调用")
1481
+ if self._metrics_sync_thread and self._metrics_sync_thread.is_alive():
1482
+ print(f"[DEBUG] metrics同步线程已存在且运行中,跳过启动")
1483
+ return
1484
+
1485
+ print(f"[DEBUG] 准备创建metrics同步线程...")
1486
+ self._metrics_sync_running = True
1487
+ self._metrics_sync_thread = threading.Thread(
1488
+ target=self._metrics_sync_main,
1489
+ daemon=True,
1490
+ name="MetricsSync"
1491
+ )
1492
+ print(f"[DEBUG] metrics同步线程已创建,即将启动...")
1493
+ self._metrics_sync_thread.start()
1494
+ print(f"[DEBUG] metrics同步线程start()调用完成")
1495
+ log_info("📊 Metrics同步线程已启动")
1496
+
1497
+ def _metrics_sync_main(self):
1498
+ """✅ Metrics定时同步主循环
1499
+
1500
+ 启动时立即同步一次,然后每2分钟同步一次metrics数据到JSON文件
1501
+ """
1502
+ print(f"[DEBUG] _metrics_sync_main() 线程开始执行!")
1503
+ log_info("📊 Metrics同步线程开始运行")
1504
+
1505
+ # ✅ 立即同步一次(启动时)
1506
+ first_sync = True
1507
+ print(f"[DEBUG] 准备进入同步循环...")
1508
+
1509
+ while self._metrics_sync_running and not self.shutdown_flag.is_set():
1510
+ try:
1511
+ # 如果不是第一次同步,等待2分钟(120秒)
1512
+ if not first_sync:
1513
+ for _ in range(120):
1514
+ if not self._metrics_sync_running or self.shutdown_flag.is_set():
1515
+ break
1516
+ time.sleep(1)
1517
+
1518
+ if not self._metrics_sync_running:
1519
+ break
1520
+ else:
1521
+ first_sync = False
1522
+
1523
+ print(f"[DEBUG] 开始同步metrics数据...")
1524
+
1525
+ # 更新队列大小
1526
+ self.metrics.dispatch_queue_size = self.message_dispatch_queue.qsize()
1527
+ print(f"[DEBUG] 队列大小: {self.metrics.dispatch_queue_size}")
1528
+
1529
+ # 获取metrics摘要
1530
+ summary = self.metrics.get_summary()
1531
+ print(f"[DEBUG] metrics摘要: received={summary['received_total']}, success={summary['dispatched_success']}")
1532
+
1533
+ # 添加额外信息
1534
+ summary['agent_id'] = self.id
1535
+ summary['agent_name'] = self.name
1536
+ summary['timestamp'] = time.time()
1537
+ summary['timestamp_str'] = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
1538
+ print(f"[DEBUG] 准备写入文件: {self.metrics_file_path}")
1539
+
1540
+ # 写入JSON文件
1541
+ import json
1542
+ with open(self.metrics_file_path, 'w', encoding='utf-8') as f:
1543
+ json.dump(summary, f, indent=2, ensure_ascii=False)
1544
+ print(f"[DEBUG] 文件写入成功!")
1545
+
1546
+ log_info(
1547
+ f"📊 Metrics已同步到文件: {self.metrics_file_path} "
1548
+ f"(接收:{summary['received_total']}, "
1549
+ f"成功:{summary['dispatched_success']}, "
1550
+ f"失败:{summary['dispatched_failed']})"
1551
+ )
1552
+
1553
+ except Exception as e:
1554
+ print(f"[DEBUG] ❌ Metrics同步发生异常: {e}")
1555
+ import traceback
1556
+ traceback.print_exc()
1557
+ log_exception(f"❌ Metrics同步失败: {e}")
1558
+ time.sleep(10) # 失败后等待10秒再重试
1559
+
1560
+ print(f"[DEBUG] Metrics同步线程退出循环")
1561
+ log_info("📊 Metrics同步线程已停止")
1562
+
1563
+ def _stop_metrics_sync(self):
1564
+ """✅ 停止Metrics定时同步线程"""
1565
+ if not self._metrics_sync_thread:
1566
+ return
1567
+
1568
+ self._metrics_sync_running = False
1569
+
1570
+ if self._metrics_sync_thread.is_alive():
1571
+ self._metrics_sync_thread.join(timeout=5.0)
1572
+
1573
+ self._metrics_sync_thread = None
1574
+ log_info("✅ Metrics同步线程已停止")
1575
+
1576
+
1577
+ def __agentid_message_listener(self, data):
1578
+ """✅ 修复WebSocket阻塞: 快速入队,不阻塞WebSocket线程
1579
+
1580
+ 修改要点:
1581
+ 1. WebSocket线程只负责解析消息并快速入队
1582
+ 2. 不执行任何阻塞操作(scheduler提交、数据库操作)
1583
+ 3. 使用put_nowait避免阻塞,队列满时记录错误
1584
+ 4. 所有重操作由独立的dispatcher线程处理
1585
+ """
1586
+ # ✅ P1-3: 记录收到消息
1587
+ #print(f"[DEBUG] __agentid_message_listener 收到消息,长度: {len(data)}")
1588
+ self.metrics.record_received()
1589
+ #print(f"[DEBUG] metrics.record_received()调用完成,当前received_total: {self.metrics.received_total}")
1590
+
1591
+ log_info(f"received a message in agentcp: {len(data)}")
1592
+ if self.__ping_message(data):
1593
+ return
1594
+
1595
+ session_id = data.get("session_id", "unknown")
1596
+ message_id = data.get("message_id", "unknown")
1597
+
1598
+ # 快速解析消息内容
1599
+ try:
1600
+ message = json.loads(data["message"])
1601
+
1602
+ # 解析消息内容
1603
+ message_list = []
1604
+ message_temp = None
1605
+ if isinstance(message, list):
1606
+ message_list = message
1607
+ message_temp = message_list[0] if isinstance(message_list[0], dict) else json.loads(message_list[0])
1608
+ else:
1609
+ message_list.append(message)
1610
+ message_temp = message
1611
+
1612
+ # 判断是否为流消息
1613
+ is_stream_message = message_temp.get("type", "") == "text/event-stream"
1614
+
1615
+ # 构造任务对象
1616
+ message_task = {
1617
+ 'data': data,
1618
+ 'is_stream_message': is_stream_message,
1619
+ 'message_list': message_list,
1620
+ 'instruction': data.get("instruction", None)
1621
+ }
1622
+
1623
+ # ✅ 快速入队(无阻塞)
1624
+ try:
1625
+ self.message_dispatch_queue.put_nowait(message_task)
1626
+ log_debug(f"✅ [WebSocket] 消息已入队: message_id={message_id[:16]}... queue_size={self.message_dispatch_queue.qsize()}")
1627
+ except queue.Full:
1628
+ # 队列满,丢弃消息并记录错误
1629
+ self.metrics.record_dispatch_failure()
1630
+ log_error(
1631
+ f"❌ [WebSocket] 派发队列已满 (10000),消息丢弃: "
1632
+ f"message_id={message_id[:16]}... session_id={session_id}"
1633
+ )
1634
+ # 队列满时直接丢弃,不重试
1635
+
1636
+ except Exception as e:
1637
+ log_exception(f"❌ [WebSocket] 消息解析失败: {e}")
1638
+
1639
+ def __insert_session(self, aid, session_id, identifying_code, name):
1640
+ conversation = self.db_manager.get_conversation_by_id(aid, session_id)
1641
+ if conversation is None:
1642
+ # identifying_code,name, type,to_aid_list
1643
+ self.db_manager.create_session(aid, session_id, identifying_code, name, "public")
1644
+ return
1645
+
1646
+ def __connect(self):
1647
+ if not hasattr(self, "_heartbeat_thread") or not self._heartbeat_thread.is_alive():
1648
+ self._heartbeat_thread = threading.Thread(target=self.heartbeat_client.online)
1649
+ self._heartbeat_thread.start()
1650
+ self.heartbeat_client.set_on_recv_invite(self.__on_heartbeat_invite_message)
1651
+ log_info(f"agentid {self.id} is ready!")
1652
+
1653
+ def get_agent_list(self):
1654
+ """获取所有agentid列表"""
1655
+ return self.ap_client.get_agent_list()
1656
+
1657
+ def get_all_public_data(self):
1658
+ """获取所有agentid列表"""
1659
+ return self.ap_client.get_all_public_data()
1660
+
1661
+ def get_session_member_list(self, session_id):
1662
+ return self.db_manager.get_session_member_list(session_id)
1663
+
1664
+ def update_aid_info(self, aid, avaUrl, name, description):
1665
+ self.db_manager.update_aid_info(aid, avaUrl, name, description)
1666
+ return True
1667
+
1668
+ def message_handler(self, router: str = ""):
1669
+ def decorator(func):
1670
+ self.add_message_handler(func, router=router)
1671
+ return func
1672
+
1673
+ return decorator
1674
+
1675
+ def get_llm_url(self, target_aid: str):
1676
+ base_url = get_base_url(self, target_aid)
1677
+ return base_url
1678
+
1679
+ def get_llm_api_key(self):
1680
+ llm_app_key = get_llm_api_key(self.id)
1681
+ return llm_app_key
1682
+
1683
+ def add_llm_api_key(self, aid_str: str, llm_api_key: str):
1684
+ if aid_str != self.id:
1685
+ return False
1686
+ return add_llm_api_key(self, llm_api_key)
1687
+
1688
+ def reset(self, wait_timeout: float = 5.0) -> bool:
1689
+ """
1690
+ 彻底重置 AgentID,清理所有资源,恢复到初始状态
1691
+
1692
+ 重置后可以重新调用 online() 走完整初始化流程
1693
+
1694
+ 清理内容:
1695
+ 1. 停止所有内部线程(消息派发、metrics同步、监控服务)
1696
+ 2. 清理 session_manager(包括所有 MessageClient 和 WebSocket 连接)
1697
+ 3. 清理 heartbeat_client
1698
+ 4. 清空消息队列和 handlers
1699
+ 5. 重置在线状态
1700
+
1701
+ Args:
1702
+ wait_timeout: 等待线程结束的超时时间(秒)
1703
+
1704
+ Returns:
1705
+ bool: 重置是否成功完成(部分失败也返回 True,会记录警告)
1706
+ """
1707
+ log_info(f"🔄 [AgentID] 开始重置: {self.id}")
1708
+ print(f"[AgentID] ========== 开始重置 ==========")
1709
+
1710
+ reset_success = True
1711
+
1712
+ try:
1713
+ # 1. 标记为离线状态(立即生效,阻止新请求)
1714
+ self.is_online_success = False
1715
+ print(f"[AgentID] ✓ 已标记为离线状态")
1716
+
1717
+ # 2. 停止消息派发线程
1718
+ print(f"[AgentID] 正在停止消息派发线程...")
1719
+ try:
1720
+ if hasattr(self, '_stop_message_dispatcher'):
1721
+ self._stop_message_dispatcher()
1722
+ print(f"[AgentID] ✓ 消息派发线程已停止")
1723
+ except Exception as e:
1724
+ log_warning(f"[AgentID] 停止消息派发线程失败(继续重置): {e}")
1725
+ reset_success = False
1726
+
1727
+ # 3. 停止 metrics 同步线程
1728
+ print(f"[AgentID] 正在停止 metrics 同步线程...")
1729
+ try:
1730
+ if hasattr(self, '_stop_metrics_sync'):
1731
+ self._stop_metrics_sync()
1732
+ print(f"[AgentID] ✓ Metrics 同步线程已停止")
1733
+ except Exception as e:
1734
+ log_warning(f"[AgentID] 停止 metrics 同步线程失败(继续重置): {e}")
1735
+
1736
+ # 4. 停止监控服务
1737
+ print(f"[AgentID] 正在停止监控服务...")
1738
+ try:
1739
+ if hasattr(self, 'monitoring_service') and self.monitoring_service is not None:
1740
+ self.monitoring_service.stop(wait=False)
1741
+ self.monitoring_service = None
1742
+ print(f"[AgentID] ✓ 监控服务已停止")
1743
+ except Exception as e:
1744
+ log_warning(f"[AgentID] 停止监控服务失败(继续重置): {e}")
1745
+
1746
+ # 5. 清理 session_manager(包括所有 WebSocket 连接)
1747
+ print(f"[AgentID] 正在清理 session_manager...")
1748
+ try:
1749
+ if self.session_manager is not None:
1750
+ self._reset_session_manager(wait_timeout)
1751
+ self.session_manager = None
1752
+ print(f"[AgentID] ✓ Session manager 已清理")
1753
+ except Exception as e:
1754
+ log_error(f"[AgentID] 清理 session_manager 失败: {e}")
1755
+ reset_success = False
1756
+
1757
+ # 6. 清理 heartbeat_client
1758
+ print(f"[AgentID] 正在清理 heartbeat_client...")
1759
+ try:
1760
+ if self.heartbeat_client is not None:
1761
+ try:
1762
+ self.heartbeat_client.offline()
1763
+ except Exception:
1764
+ pass
1765
+ try:
1766
+ self.heartbeat_client.sign_out()
1767
+ except Exception:
1768
+ pass
1769
+ self.heartbeat_client = None
1770
+ print(f"[AgentID] ✓ Heartbeat client 已清理")
1771
+ except Exception as e:
1772
+ log_warning(f"[AgentID] 清理 heartbeat_client 失败(继续重置): {e}")
1773
+
1774
+ # 7. 清空消息派发队列
1775
+ print(f"[AgentID] 正在清空消息队列...")
1776
+ try:
1777
+ cleared_count = 0
1778
+ if hasattr(self, 'message_dispatch_queue'):
1779
+ while not self.message_dispatch_queue.empty():
1780
+ try:
1781
+ self.message_dispatch_queue.get_nowait()
1782
+ cleared_count += 1
1783
+ except queue.Empty:
1784
+ break
1785
+ print(f"[AgentID] ✓ 已清空 {cleared_count} 条待处理消息")
1786
+ except Exception as e:
1787
+ log_warning(f"[AgentID] 清空消息队列失败(继续重置): {e}")
1788
+
1789
+ # 8. 清空 message handlers 的 session 级别映射(保留全局 handlers)
1790
+ print(f"[AgentID] 正在清理 handler 映射...")
1791
+ try:
1792
+ self.message_handlers_session_map.clear()
1793
+ self.message_handlers_router_map.clear()
1794
+ print(f"[AgentID] ✓ Handler 映射已清理")
1795
+ except Exception as e:
1796
+ log_warning(f"[AgentID] 清理 handler 映射失败(继续重置): {e}")
1797
+
1798
+ # 9. 重新启动消息派发线程(为下次 online 做准备)
1799
+ print(f"[AgentID] 正在重新启动消息派发线程...")
1800
+ try:
1801
+ if hasattr(self, '_start_message_dispatcher'):
1802
+ self._start_message_dispatcher()
1803
+ print(f"[AgentID] ✓ 消息派发线程已重新启动")
1804
+ except Exception as e:
1805
+ log_error(f"[AgentID] 重新启动消息派发线程失败: {e}")
1806
+ reset_success = False
1807
+
1808
+ # 10. 重新启动 metrics 同步线程
1809
+ print(f"[AgentID] 正在重新启动 metrics 同步线程...")
1810
+ try:
1811
+ if hasattr(self, '_start_metrics_sync'):
1812
+ self._start_metrics_sync()
1813
+ print(f"[AgentID] ✓ Metrics 同步线程已重新启动")
1814
+ except Exception as e:
1815
+ log_warning(f"[AgentID] 重新启动 metrics 同步线程失败: {e}")
1816
+
1817
+ print(f"[AgentID] ========== 重置完成 ==========")
1818
+ log_info(f"✅ [AgentID] 重置完成: {self.id}, 成功={reset_success}")
1819
+
1820
+ return reset_success
1821
+
1822
+ except Exception as e:
1823
+ log_error(f"❌ [AgentID] 重置过程发生异常: {e}")
1824
+ import traceback
1825
+ traceback.print_exc()
1826
+ return False
1827
+
1828
+ def _reset_session_manager(self, wait_timeout: float):
1829
+ """
1830
+ 彻底重置 SessionManager,包括所有 MessageClient 和 WebSocket 连接
1831
+
1832
+ Args:
1833
+ wait_timeout: 等待线程结束的超时时间(秒)
1834
+ """
1835
+ if self.session_manager is None:
1836
+ return
1837
+
1838
+ sm = self.session_manager
1839
+
1840
+ # 1. 关闭所有 session
1841
+ print(f"[AgentID] 正在关闭所有 sessions...")
1842
+ try:
1843
+ sm.close_all_session()
1844
+ except Exception as e:
1845
+ log_warning(f"[AgentID] close_all_session 失败: {e}")
1846
+
1847
+ # 2. 停止所有 MessageClient 的 WebSocket 连接
1848
+ print(f"[AgentID] 正在停止所有 MessageClient...")
1849
+ if hasattr(sm, 'message_client_map'):
1850
+ for server_url, message_client in list(sm.message_client_map.items()):
1851
+ try:
1852
+ if message_client is not None:
1853
+ # 设置关闭标志
1854
+ message_client._shutdown_requested = True
1855
+ # 调用完全重置方法(如果存在)
1856
+ if hasattr(message_client, 'full_reset'):
1857
+ message_client.full_reset()
1858
+ else:
1859
+ message_client.stop_websocket_client()
1860
+ print(f"[AgentID] ✓ MessageClient 已停止: {server_url[:50]}...")
1861
+ except Exception as e:
1862
+ log_warning(f"[AgentID] 停止 MessageClient 失败: {e}")
1863
+
1864
+ # 3. 清空所有映射
1865
+ if hasattr(sm, 'sessions'):
1866
+ sm.sessions.clear()
1867
+ if hasattr(sm, 'message_client_map'):
1868
+ sm.message_client_map.clear()
1869
+ if hasattr(sm, 'message_server_map'):
1870
+ sm.message_server_map.clear()
1871
+ if hasattr(sm, 'create_session_queue_map'):
1872
+ sm.create_session_queue_map.clear()
1873
+
1874
+ print(f"[AgentID] ✓ SessionManager 所有映射已清空")
1875
+
1876
+ def reset_and_reconnect(self) -> bool:
1877
+ """
1878
+ 重置并重新连接的便捷方法
1879
+
1880
+ 等同于: reset() + online()
1881
+
1882
+ Returns:
1883
+ bool: 是否成功重新连接
1884
+ """
1885
+ log_info(f"🔄 [AgentID] 开始重置并重连: {self.id}")
1886
+
1887
+ # 1. 重置
1888
+ reset_ok = self.reset()
1889
+ if not reset_ok:
1890
+ log_warning("[AgentID] 重置部分失败,但继续尝试重连...")
1891
+
1892
+ # 2. 等待一小段时间确保资源完全释放
1893
+ time.sleep(0.5)
1894
+
1895
+ # 3. 重新上线
1896
+ try:
1897
+ self.online()
1898
+
1899
+ if self.is_online_success:
1900
+ log_info(f"✅ [AgentID] 重置并重连成功: {self.id}")
1901
+ return True
1902
+ else:
1903
+ log_error(f"❌ [AgentID] 重连后仍未在线: {self.id}")
1904
+ return False
1905
+
1906
+ except Exception as e:
1907
+ log_error(f"❌ [AgentID] 重连过程异常: {e}")
1908
+ import traceback
1909
+ traceback.print_exc()
1910
+ return False
1911
+
1912
+ def set_disconnect_callback(self, callback: callable) -> None:
1913
+ """设置 WebSocket 断开回调
1914
+
1915
+ 当任意 MessageClient 的 WebSocket 连接断开时,会调用此回调函数。
1916
+ 外部可以通过此回调实现自动重建逻辑。
1917
+
1918
+ 回调函数签名: callback(agent_id: str, server_url: str, code: int, reason: str)
1919
+
1920
+ Args:
1921
+ callback: 断开时调用的回调函数
1922
+
1923
+ 示例:
1924
+ def on_disconnect(agent_id, server_url, code, reason):
1925
+ print(f"连接断开: {agent_id} -> {server_url}, code={code}, reason={reason}")
1926
+ # 触发重建逻辑...
1927
+
1928
+ agentId.set_disconnect_callback(on_disconnect)
1929
+ """
1930
+ log_info(f"[AgentID] 设置断开回调: {callback}")
1931
+
1932
+ # 保存回调引用,用于新创建的 MessageClient
1933
+ self._disconnect_callback = callback
1934
+
1935
+ # 为所有现有的 MessageClient 设置回调
1936
+ if self.session_manager and hasattr(self.session_manager, 'message_client_map'):
1937
+ for server_url, mc in self.session_manager.message_client_map.items():
1938
+ if mc and hasattr(mc, 'set_disconnect_callback'):
1939
+ mc.set_disconnect_callback(callback)
1940
+ log_info(f"[AgentID] 已为 MessageClient({server_url}) 设置断开回调")
1941
+
1942
+ # ==================== MessageClient 管理 API ====================
1943
+
1944
+ def get_message_client(self, server_url: str = None):
1945
+ """✅ 获取 MessageClient 实例
1946
+
1947
+ Args:
1948
+ server_url: 消息服务器 URL(可选,不传则返回第一个)
1949
+
1950
+ Returns:
1951
+ MessageClient 实例,不存在返回 None
1952
+ """
1953
+ if not self.session_manager or not hasattr(self.session_manager, 'message_client_map'):
1954
+ return None
1955
+
1956
+ if server_url:
1957
+ return self.session_manager.message_client_map.get(server_url.rstrip("/"))
1958
+ else:
1959
+ # 返回第一个
1960
+ for mc in self.session_manager.message_client_map.values():
1961
+ return mc
1962
+ return None
1963
+
1964
+ def get_all_message_clients(self) -> dict:
1965
+ """✅ 获取所有 MessageClient 实例
1966
+
1967
+ Returns:
1968
+ {server_url: MessageClient} 字典
1969
+ """
1970
+ if not self.session_manager or not hasattr(self.session_manager, 'message_client_map'):
1971
+ return {}
1972
+ return dict(self.session_manager.message_client_map)
1973
+
1974
+ def is_connection_healthy(self, server_url: str = None) -> bool:
1975
+ """✅ 检查连接是否健康
1976
+
1977
+ Args:
1978
+ server_url: 消息服务器 URL(可选,不传则检查所有连接)
1979
+
1980
+ Returns:
1981
+ True: 连接健康
1982
+ False: 连接不健康或不存在
1983
+ """
1984
+ if server_url:
1985
+ mc = self.get_message_client(server_url)
1986
+ return mc.is_healthy() if mc else False
1987
+ else:
1988
+ # 检查所有连接
1989
+ for mc in self.get_all_message_clients().values():
1990
+ if not mc.is_healthy():
1991
+ return False
1992
+ return len(self.get_all_message_clients()) > 0
1993
+
1994
+ def get_connection_status(self, server_url: str = None) -> dict:
1995
+ """✅ 获取连接状态信息
1996
+
1997
+ Args:
1998
+ server_url: 消息服务器 URL(可选)
1999
+
2000
+ Returns:
2001
+ 连接状态信息字典
2002
+ """
2003
+ if server_url:
2004
+ mc = self.get_message_client(server_url)
2005
+ if mc:
2006
+ return mc.get_connection_info()
2007
+ return {"error": "连接不存在", "server_url": server_url}
2008
+ else:
2009
+ result = {
2010
+ "agent_id": self.id,
2011
+ "is_online": self.is_online_success,
2012
+ "connections": []
2013
+ }
2014
+ for url, mc in self.get_all_message_clients().items():
2015
+ result["connections"].append(mc.get_connection_info())
2016
+ return result
2017
+
2018
+ def get_connection_health_summary(self) -> str:
2019
+ """✅ 获取连接健康状态摘要(用于日志/调试)
2020
+
2021
+ Returns:
2022
+ 健康状态摘要字符串
2023
+ """
2024
+ clients = self.get_all_message_clients()
2025
+ if not clients:
2026
+ return f"🔴 {self.id}: 无连接"
2027
+
2028
+ summaries = []
2029
+ for url, mc in clients.items():
2030
+ summaries.append(f" [{url}] {mc.get_health_summary()}")
2031
+
2032
+ return f"📊 {self.id} 连接状态:\n" + "\n".join(summaries)
2033
+
2034
+ def rebuild_message_client(self, server_url: str = None) -> bool:
2035
+ """✅ 销毁并重建 MessageClient
2036
+
2037
+ 这会断开现有连接,创建新的 MessageClient 实例。
2038
+ 用于连接出现严重问题时的恢复。
2039
+
2040
+ Args:
2041
+ server_url: 消息服务器 URL(可选,不传则重建所有)
2042
+
2043
+ Returns:
2044
+ True: 重建成功
2045
+ False: 重建失败
2046
+ """
2047
+ if not self.session_manager:
2048
+ log_error("[AgentID] SessionManager 不存在,无法重建")
2049
+ return False
2050
+
2051
+ if server_url:
2052
+ return self._rebuild_single_message_client(server_url)
2053
+ else:
2054
+ # 重建所有
2055
+ success = True
2056
+ for url in list(self.get_all_message_clients().keys()):
2057
+ if not self._rebuild_single_message_client(url):
2058
+ success = False
2059
+ return success
2060
+
2061
+ def _rebuild_single_message_client(self, server_url: str) -> bool:
2062
+ """重建单个 MessageClient"""
2063
+ server_url = server_url.rstrip("/")
2064
+ log_info(f"[AgentID] 开始重建 MessageClient: {server_url}")
2065
+
2066
+ try:
2067
+ sm = self.session_manager
2068
+ old_mc = sm.message_client_map.get(server_url)
2069
+
2070
+ if old_mc:
2071
+ # 1. 停止旧连接
2072
+ log_info(f"[AgentID] 停止旧连接...")
2073
+ try:
2074
+ old_mc.stop_websocket_client()
2075
+ except Exception as e:
2076
+ log_warning(f"[AgentID] 停止旧连接异常: {e}")
2077
+
2078
+ # 2. 等待资源释放
2079
+ time.sleep(0.5)
2080
+
2081
+ # 3. 创建新连接
2082
+ log_info(f"[AgentID] 创建新连接...")
2083
+ from agentcp.msg.message_client import MessageClient
2084
+ cache_auth_client = sm.message_server_map.get(server_url)
2085
+
2086
+ new_mc = MessageClient(
2087
+ self.id,
2088
+ server_url,
2089
+ self.ca_client.get_aid_certs_path(self.id),
2090
+ self.seed_password,
2091
+ cache_auth_client
2092
+ )
2093
+ new_mc.initialize()
2094
+ new_mc.set_message_handler(sm)
2095
+
2096
+ # 4. 设置回调
2097
+ if self._disconnect_callback:
2098
+ new_mc.set_disconnect_callback(self._disconnect_callback)
2099
+
2100
+ # 5. 启动连接
2101
+ if new_mc.start_websocket_client():
2102
+ sm.message_client_map[server_url] = new_mc
2103
+ sm.message_server_map[server_url] = new_mc.auth_client
2104
+ log_info(f"✅ [AgentID] MessageClient 重建成功: {server_url}")
2105
+ return True
2106
+ else:
2107
+ log_error(f"❌ [AgentID] MessageClient 启动失败: {server_url}")
2108
+ return False
2109
+
2110
+ except Exception as e:
2111
+ log_error(f"❌ [AgentID] 重建 MessageClient 异常: {e}")
2112
+ import traceback
2113
+ traceback.print_exc()
2114
+ return False
2115
+
2116
+ def force_reconnect(self, server_url: str = None) -> bool:
2117
+ """✅ 强制触发重连
2118
+
2119
+ 不销毁 MessageClient,只是触发其重连逻辑。
2120
+ 比 rebuild_message_client 更轻量。
2121
+
2122
+ Args:
2123
+ server_url: 消息服务器 URL(可选,不传则重连所有)
2124
+
2125
+ Returns:
2126
+ True: 重连成功
2127
+ False: 重连失败
2128
+ """
2129
+ if server_url:
2130
+ mc = self.get_message_client(server_url)
2131
+ if not mc:
2132
+ return False
2133
+ log_info(f"[AgentID] 强制重连: {server_url}")
2134
+ mc.stop_websocket_client()
2135
+ time.sleep(0.2)
2136
+ return mc.start_websocket_client()
2137
+ else:
2138
+ success = True
2139
+ for url, mc in self.get_all_message_clients().items():
2140
+ log_info(f"[AgentID] 强制重连: {url}")
2141
+ mc.stop_websocket_client()
2142
+ time.sleep(0.2)
2143
+ if not mc.start_websocket_client():
2144
+ success = False
2145
+ return success
2146
+
2147
+ # ==================== 原有方法 ====================
2148
+
2149
+ def __repr__(self):
2150
+ return f"AgentId(aid={self.id})"
2151
+
2152
+ def get_sender_from_message(self, message):
2153
+ if isinstance(message, dict):
2154
+ return message.get("sender")
2155
+ return None # 如果不是字典,返回None或抛出异常,取决于你的需求
2156
+
2157
+ def get_session_id_from_message(self, message):
2158
+ if isinstance(message, dict):
2159
+ return message.get("session_id")
2160
+ return None # 如果不是字典,返回None或抛出异常,取决于你的需求
2161
+
2162
+ def get_receiver_from_message(self, message):
2163
+ if isinstance(message, dict):
2164
+ return message.get("receiver")
2165
+ return None # 如果不是字典,返回None或抛出异常,取决于你的需求
2166
+
2167
+ def get_content_from_message(self, message, message_type="content"):
2168
+ message_array = self.get_content_array_from_message(message)
2169
+ for item in message_array:
2170
+ if isinstance(item, dict) and item.get("type") == message_type:
2171
+ # 这里可以执行你需要的操作,例如打印 content 字段
2172
+ content = item.get("content", "")
2173
+ try:
2174
+ content_json = json.loads(content) # 尝试解析为 JSON
2175
+ if isinstance(content_json, dict) and "text" in content_json: # 检查是否为字典且包含 'text'
2176
+ return content_json["text"]
2177
+ except Exception:
2178
+ return content
2179
+ return content
2180
+ if message_type == "content":
2181
+ return self.get_content_from_message(message, message_type="text")
2182
+ return None # 如果不是字典,返回None或抛出异常,取决于你的需求
2183
+
2184
+ def __str__(self):
2185
+ return self.id
2186
+
2187
+ # 尝试解析 content 为 JSON 格式
2188
+ def get_content_array_from_message(self, message):
2189
+ # 消息数组
2190
+ message_content = message.get("message", "")
2191
+ message_array = []
2192
+ if isinstance(message_content, str):
2193
+ try:
2194
+ if message_content.strip(): # 检查内容是否非空
2195
+ llm_content_json_array = json.loads(message_content)
2196
+ if isinstance(llm_content_json_array, list) and len(llm_content_json_array) > 0:
2197
+ return llm_content_json_array # 返回整个数组而不是第一个元素的 conten
2198
+ else:
2199
+ message_array.append(llm_content_json_array)
2200
+ return message_array
2201
+ else:
2202
+ log_info("收到空消息内容")
2203
+ return []
2204
+ except json.JSONDecodeError:
2205
+ log_error(f"无法解析的消息内容: {message_content}")
2206
+ return []
2207
+ elif isinstance(message_content, list) and len(message_content) > 0:
2208
+ return message_content
2209
+ else:
2210
+ log_error("无效的消息格式")
2211
+ return []
2212
+
2213
+ async def send_stream_message(
2214
+ self, session_id: str, to_aid_list: list, response, type="text/event-stream", file_path:str = "",ref_msg_id: str = ""
2215
+ ):
2216
+ # 处理对象转换为字典
2217
+ if type == "file/binary" and (file_path == "" or not os.path.exists(file_path)):
2218
+ return False,"文件不存在"
2219
+ stream_result = await self.create_stream(session_id, to_aid_list, type, ref_msg_id)
2220
+ push_url, pull_url = stream_result
2221
+ if push_url is None:
2222
+ log_error(f"{pull_url}")
2223
+ msg_block = {
2224
+ "type": "error",
2225
+ "status": "success",
2226
+ "timestamp": int(time.time() * 1000),
2227
+ "content": f"{pull_url}",
2228
+ }
2229
+ self.send_message(session_id, to_aid_list, msg_block)
2230
+ return None
2231
+
2232
+ msg_block = {
2233
+ "type": type,
2234
+ "status": "loading",
2235
+ "timestamp": int(time.time() * 1000),
2236
+ "content": pull_url,
2237
+ }
2238
+
2239
+ if type == "file/binary":
2240
+ from agentcp.utils.file_util import get_file_info
2241
+ msg_block["extra"] = get_file_info(file_path)
2242
+
2243
+ self.send_message(session_id, to_aid_list, msg_block)
2244
+ if type=="text/event-stream":
2245
+ for chunk in response:
2246
+ chunk_str = json.dumps(chunk, default=lambda x: vars(x), ensure_ascii=False)
2247
+ log_info(f"chunk_str = {chunk_str}")
2248
+ self.send_chunk_to_stream(session_id, push_url, chunk_str,type = type)
2249
+ elif type=="file/binary":
2250
+ failed_counter = 0
2251
+ with open(file_path, "rb") as f:
2252
+ offset = 0
2253
+ for byte_block in iter(lambda: f.read(16384), b""):
2254
+ result = self.send_chunk_to_file_stream(session_id, push_url,offset, byte_block)
2255
+ offset += len(byte_block)
2256
+ if not result:
2257
+ failed_counter += 1
2258
+ log_error(f"send_chunk_to_file_stream failed, session {session_id} failed_counter={failed_counter}")
2259
+ time.sleep(failed_counter * 0.1)
2260
+ if failed_counter >= 10:
2261
+ break
2262
+ else:
2263
+ failed_counter = 0
2264
+ self.close_stream(session_id, push_url)
2265
+ return True
2266
+
2267
+ def ping_aid(self, aid: str):
2268
+ start_time = time.time()
2269
+ msg_block = {"type": "ping", "status": "success", "timestamp": int(time.time() * 1000), "content": "ping"}
2270
+ ping_queue = queue.Queue()
2271
+
2272
+ async def asnyc_message_result(message):
2273
+ end_time = time.time()
2274
+ cost_time = end_time - start_time
2275
+ ping_queue.put(cost_time)
2276
+
2277
+ self.quick_send_message(aid, msg_block, asnyc_message_result, insert_message=False)
2278
+ try:
2279
+ ping = ping_queue.get(timeout=10)
2280
+ except queue.Empty:
2281
+ log_info(f"ping_aid {aid} timeout")
2282
+ ping = 10000
2283
+ return ping
2284
+
2285
+
2286
+ class AgentCP(_AgentCP):
2287
+ def __init__(
2288
+ self,
2289
+ agent_data_path,
2290
+ certificate_path: str = "",
2291
+ seed_password: str = "",
2292
+ debug=False,
2293
+ log_level: int = logging.INFO,
2294
+ port: int = 0,
2295
+ run_proxy: bool = True,
2296
+ ) -> None:
2297
+ super().__init__()
2298
+ if agent_data_path == "" or agent_data_path is None:
2299
+ raise Exception("agent_data_path 不能为空")
2300
+ else:
2301
+ self.app_path = os.path.join(agent_data_path, "agentcp")
2302
+ self.seed_password = self.__get_sha256(seed_password)
2303
+ super().__init__()
2304
+ if agent_data_path == "" or agent_data_path is None:
2305
+ raise Exception("agent_data_path 不能为空")
2306
+ else:
2307
+ self.app_path = os.path.join(agent_data_path, "agentcp")
2308
+ self.seed_password = self.__get_sha256(seed_password)
2309
+ if certificate_path == "" or certificate_path is None:
2310
+ certificate_path = self.app_path
2311
+ self.aid_path = os.path.join(certificate_path, "AIDs")
2312
+ os.path.exists(self.aid_path) or os.makedirs(self.aid_path)
2313
+ set_log_enabled(debug, log_level)
2314
+ self.ca_client = None
2315
+ self.ep_url = None
2316
+ self.debug = debug
2317
+ self.aid_map = {}
2318
+ if run_proxy:
2319
+ self.run_llm_proxy(port)
2320
+
2321
+ def run_llm_proxy(self, port):
2322
+ if llm_server_is_running():
2323
+ log_info("本地服务已启动")
2324
+ return
2325
+ run_server(self.debug, port=port)
2326
+ # 等待local server 在异步线程中启动
2327
+ time.sleep(0.4)
2328
+
2329
+ def get_llm_url(self, target_aid: str):
2330
+ base_url = get_base_url(self, target_aid)
2331
+ return base_url
2332
+
2333
+ def get_llm_api_key(self, aid_str: str):
2334
+ return get_llm_api_key(aid_str)
2335
+
2336
+ def __enter__(self):
2337
+ """进入上下文时返回实例自身"""
2338
+ return self
2339
+
2340
+ def set_seed_password(self, seed_password: str):
2341
+ self.seed_password = self.__get_sha256(seed_password)
2342
+
2343
+ def modify_seed_password(self, seed_password: str):
2344
+ new_seed_password = self.__get_sha256(seed_password)
2345
+ aid_list = self.get_aid_list()
2346
+ for aid_str in aid_list:
2347
+ # 加载aid
2348
+ private_key = self.__load_aid_private_key(aid_str)
2349
+ if private_key is None:
2350
+ log_error(f"加载失败aid: {aid_str}")
2351
+ continue
2352
+ try:
2353
+ self.ca_client.modify_seed_password(aid_str, private_key, new_seed_password)
2354
+ log_error(f"修改密码种子成功aid: {aid_str}")
2355
+ except Exception as e:
2356
+ log_error(f"修改密码种子失败aid: {aid_str}, 错误: {str(e)}")
2357
+
2358
+ def __load_aid_private_key(self, agent_id: str):
2359
+ self.__build_url(agent_id)
2360
+ try:
2361
+ private_key = self.ca_client.load_private_key(agent_id)
2362
+ return private_key
2363
+ except Exception as e:
2364
+ log_exception(f"加载和验证密钥对时出错: {e}") # 调试用
2365
+ return None
2366
+
2367
+ def get_agent_data_path(self):
2368
+ return self.app_path
2369
+
2370
+ def __get_sha256(self, input_str: str) -> str:
2371
+ sha256_hash = hashlib.sha256()
2372
+ sha256_hash.update(input_str.encode("utf-8"))
2373
+ return sha256_hash.hexdigest()
2374
+
2375
+ def save_aid_info(self, agent_id: str, seed_password: str, private_key: str, cert: str) -> AgentID:
2376
+ private_key_ = serialization.load_pem_private_key(
2377
+ private_key.encode("utf-8"), password=self.__get_sha256(seed_password).encode("utf-8")
2378
+ )
2379
+ self.ca_client.save_private_key_to_file(agent_id, private_key_)
2380
+ self.ca_client.save_cert_to_file(agent_id, cert)
2381
+
2382
+ def __build_url(self, aid: str):
2383
+ aid_array = aid.split(".")
2384
+ if len(aid_array) < 3:
2385
+ raise RuntimeError("加载aid错误,请检查传入aid")
2386
+ end_str = f"{aid_array[-2]}.{aid_array[-1]}"
2387
+ self.ca_client = CAClient("https://acp3." + end_str, self.aid_path, self.seed_password)
2388
+ self.ep_url = "https://acp3." + end_str
2389
+
2390
+ def load_aid(self, agent_id: str) -> AgentID:
2391
+ self.__build_url(agent_id)
2392
+ try:
2393
+ log_debug(f"load agentid: {agent_id}")
2394
+ if self.ca_client.aid_is_not_exist(agent_id): # 检查返回结果是否有效
2395
+ log_error(f"未找到agent_id: {agent_id} 或数据不完整")
2396
+ return None
2397
+ aid = AgentID(agent_id, self.app_path, self.seed_password, self.ca_client, self.ep_url, debug=self.debug)
2398
+ ep_url = self.ca_client.resign_csr(agent_id)
2399
+ if ep_url:
2400
+ return aid
2401
+ return None
2402
+ except Exception as e:
2403
+ log_exception(f"加载和验证密钥对时出错: {e}") # 调试用
2404
+ return None
2405
+
2406
+ def read_private_key(self, agent_id: str):
2407
+ self.__build_url(agent_id)
2408
+ private_key = self.ca_client.load_private_key_str(agent_id, self.seed_password)
2409
+ return private_key
2410
+
2411
+ def read_certificate_pem(self, agent_id: str):
2412
+ self.__build_url(agent_id)
2413
+ private_key = self.ca_client.load_certificate_pem(agent_id)
2414
+ return private_key
2415
+
2416
+ def __build_id(self, id: str):
2417
+ ep = self.ep_url.split(".")
2418
+ end_str = f"{ep[-2]}.{ep[-1]}"
2419
+ if id.endswith(end_str):
2420
+ return id
2421
+ return f"{id}.{ep[-2]}.{ep[-1]}"
2422
+
2423
+ def get_guest_aid(self, ep_url: str):
2424
+ self.ca_client = CAClient("https://acp3." + ep_url, self.aid_path, self.seed_password)
2425
+ self.ep_url = "https://acp3." + ep_url
2426
+ guest_aid = self.ca_client.get_guest_aid()
2427
+ if guest_aid:
2428
+ return self.load_aid(guest_aid)
2429
+ raise RuntimeError("获取guest aid失败")
2430
+
2431
+ def create_aid(self, ap: str, agent_name: str) -> AgentID:
2432
+ if agent_name.startswith("guest"):
2433
+ return self.get_guest_aid(ap)
2434
+
2435
+ self.ca_client = CAClient("https://acp3." + ap, self.aid_path, self.seed_password)
2436
+ self.ep_url = "https://acp3." + ap
2437
+ if not self.ca_client.aid_is_not_exist(agent_name + "." + ap):
2438
+ return self.load_aid(agent_name + "." + ap)
2439
+
2440
+ agent_id = self.__build_id(agent_name)
2441
+ log_debug(f"create agentid: {agent_id}")
2442
+ result = self.ca_client.send_csr_to_server(agent_id)
2443
+ if result == True:
2444
+ return self.load_aid(agent_id)
2445
+ raise RuntimeError(result)
2446
+
2447
+
2448
+ def get_aid_list(self) -> list:
2449
+ path = os.path.join(self.aid_path)
2450
+ aid_list = []
2451
+ for entry in os.scandir(path):
2452
+ array = entry.name.split(".")
2453
+ if entry.is_dir() and len(array) == 3:
2454
+ aid_list.append(entry.name)
2455
+ return aid_list
2456
+
2457
+ def add_message_handler(self, handler: typing.Callable[[dict], typing.Awaitable[None]], aid_str: str):
2458
+ """消息监听器装饰器"""
2459
+ log_debug("add message handler")
2460
+ if not aid_str:
2461
+ raise ValueError("aid_str 不能为空")
2462
+ aid_acp_array = aid_str.split(".")
2463
+ if len(aid_acp_array) < 3:
2464
+ raise ValueError("aid_str 格式错误")
2465
+ ap = ".".join(aid_acp_array[1:])
2466
+ name = aid_acp_array[0]
2467
+ aid: AgentID = self.create_aid(ap, name)
2468
+ if aid is None:
2469
+ raise RuntimeError("加载aid失败")
2470
+ aid.online()
2471
+ self.aid_map[aid_str] = aid
2472
+ aid.add_message_handler(handler, from_acp=True)
2473
+
2474
+ def get_aid(self, aid_str: str) -> AgentID:
2475
+ return self.aid_map.get(aid_str)
2476
+
2477
+ def message_handler(self, aid_str):
2478
+ def decorator(func):
2479
+ self.add_message_handler(func, aid_str=aid_str)
2480
+ return func
2481
+
2482
+ return decorator
2483
+
2484
+ def __exit__(self, exc_type, exc_val, exc_tb):
2485
+ """退出上下文时执行资源清理"""
2486
+ # 触发关闭标志(继承自_AggentCP)
2487
+ self.shutdown_flag.set()
2488
+
2489
+ # 清理所有AgentID的资源
2490
+ for aid in self.aid_map.values():
2491
+ if hasattr(aid, "offline"):
2492
+ aid.offline()
2493
+ # 其他需要清理的资源(如日志、连接等)
2494
+ log_info("AgentCP上下文退出,资源已清理")