@agentunion/kite 1.5.0 → 1.6.1

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 (574) 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/cli.js +38 -4
  23. package/core/env_checker.py +96 -0
  24. 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
  25. package/docs/ACP/345/215/217/350/256/256/345/205/274/345/256/271/346/226/271/346/241/210.md +138 -0
  26. 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
  27. package/docs/CLI/345/274/200/345/217/221/350/256/241/345/210/222.md +595 -0
  28. 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
  29. 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
  30. 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
  31. 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
  32. 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
  33. package/docs/Evol/346/250/241/345/235/227/350/256/276/350/256/241/346/226/271/346/241/210.md +1154 -0
  34. 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
  35. 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
  36. package/docs/HTTP-RPC/350/277/201/347/247/273/345/210/260WebSocket/350/256/241/345/210/222.md +318 -0
  37. package/docs/INDEX.md +388 -0
  38. package/docs/KITE_DOCS_GUIDE.md +33 -0
  39. 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
  40. 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
  41. 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
  42. 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
  43. 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
  44. 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
  45. 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
  46. 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
  47. 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
  48. 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
  49. 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
  50. 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
  51. 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
  52. 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
  53. 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
  54. 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
  55. package/docs/Kite/346/241/206/346/236/266/350/256/276/350/256/241/README.md +46 -0
  56. package/docs/Kite/347/263/273/347/273/237/345/220/257/345/212/250/346/265/201/347/250/213.md +567 -0
  57. package/docs/Launcher/345/220/257/345/212/250/345/231/250/346/226/207/346/241/243.md +745 -0
  58. 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
  59. 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
  60. 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
  61. 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
  62. 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
  63. package/docs/Watchdog/350/265/204/346/272/220/347/233/221/346/216/247/347/255/226/347/225/245.md +92 -0
  64. 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
  65. 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
  66. package/docs/WebSocket/350/277/236/346/216/245/351/237/247/346/200/247/346/226/271/346/241/210.md +169 -0
  67. 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
  68. 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
  69. 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
  70. package/docs/audit-api-guide.md +68 -0
  71. package/docs/audit-module-design.md +315 -0
  72. package/docs/audit-module-implementation-summary.md +149 -0
  73. package/docs/llm-context-design.md +52 -0
  74. package/docs/llm-test-enhancement-plan.md +970 -0
  75. package/docs/logs-api-guide.md +42 -0
  76. 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
  77. 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
  78. 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
  79. 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
  80. package/docs//344/272/213/344/273/266/345/244/204/347/220/206/346/234/272/345/210/266.md +388 -0
  81. package/docs//344/272/213/344/273/266/345/244/204/347/220/206/350/247/204/350/214/203.md +113 -0
  82. 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
  83. 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
  84. 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
  85. 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
  86. 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
  87. package/docs//344/274/230/351/233/205/351/200/200/345/207/272/350/247/204/350/214/203.md +362 -0
  88. package/docs//344/276/235/350/265/226/347/256/241/347/220/206/350/257/264/346/230/216.md +141 -0
  89. 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
  90. package/docs//345/210/240/351/231/244kernel-client-example/345/256/214/346/210/220.md +309 -0
  91. package/docs//345/210/240/351/231/244ws-management/345/256/214/346/210/220.md +418 -0
  92. package/docs//345/220/257/345/212/250/344/274/230/345/214/226/346/226/271/346/241/210.md +522 -0
  93. 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
  94. 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
  95. 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
  96. package/docs//345/256/236/347/216/260/350/247/204/345/210/222.md +195 -0
  97. 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
  98. 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
  99. package/docs//346/217/241/346/211/213/350/256/244/350/257/201/346/226/271/346/241/210.md +908 -0
  100. package/docs//346/226/207/346/241/243/346/233/264/346/226/260/346/270/205/345/215/225.md +83 -0
  101. 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
  102. 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
  103. package/docs//346/236/266/346/236/204/345/200/237/351/211/264/346/214/207/345/215/227.md +977 -0
  104. 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
  105. 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
  106. 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
  107. 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
  108. package/docs//346/250/241/345/235/227/345/274/200/345/217/221/346/214/207/345/215/227.md +1824 -0
  109. package/docs//346/250/241/345/235/227/347/203/255/346/233/264/346/226/260.md +89 -0
  110. 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
  111. 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
  112. 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
  113. 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
  114. 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
  115. 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
  116. 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
  117. 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
  118. 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
  119. 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
  120. 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
  121. 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
  122. package/docs//350/277/234/347/250/213/346/250/241/345/235/227/350/256/276/350/256/241.md +451 -0
  123. package/docs//351/207/215/346/236/204/346/234/272/345/210/266/346/270/205/345/215/225.md +192 -0
  124. package/docs//351/223/276/350/267/257/350/277/275/350/270/252/346/226/271/346/241/210.md +242 -0
  125. 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
  126. package/extensions/agents/assistant/entry.py +113 -14
  127. package/extensions/agents/assistant/module.md +27 -22
  128. package/extensions/agents/assistant/server.py +291 -105
  129. package/extensions/channels/acp_channel/entry.py +114 -16
  130. package/extensions/channels/acp_channel/module.md +4 -0
  131. package/extensions/channels/acp_channel/server.py +396 -105
  132. package/extensions/channels/phone_channel/__init__.py +1 -0
  133. package/extensions/channels/phone_channel/entry.py +503 -0
  134. package/extensions/channels/phone_channel/module.md +31 -0
  135. package/extensions/channels/phone_channel/server.py +686 -0
  136. package/extensions/event_hub_bench/entry.py +55 -12
  137. package/extensions/event_hub_bench/module.md +27 -27
  138. package/extensions/services/audit/README.md +134 -0
  139. package/extensions/services/audit/collector.py +73 -0
  140. package/extensions/services/audit/entry.py +444 -0
  141. package/extensions/services/audit/module.md +66 -0
  142. package/extensions/services/audit/query_audit.py +111 -0
  143. package/extensions/services/audit/routes/__init__.py +1 -0
  144. package/extensions/services/audit/routes/routes_audit.py +113 -0
  145. package/extensions/services/audit/schemas/__init__.py +5 -0
  146. package/extensions/services/audit/schemas/audit_event.py +92 -0
  147. package/extensions/services/audit/server.py +542 -0
  148. package/extensions/services/audit/storage.py +95 -0
  149. package/extensions/services/auth/entry.py +1054 -0
  150. package/extensions/services/auth/module.md +31 -0
  151. package/extensions/services/auth/token_store.py +185 -0
  152. package/extensions/services/auth/verifiers/evol_account.py +101 -0
  153. package/extensions/services/auth/verifiers/kite_token.py +38 -0
  154. package/extensions/services/auth/verifiers/pairing_code.py +71 -0
  155. package/extensions/services/backup/entry.py +494 -197
  156. package/extensions/services/backup/module.md +4 -2
  157. package/extensions/services/dataclaw/api/__init__.py +0 -0
  158. package/extensions/services/dataclaw/api/admin.py +367 -0
  159. package/extensions/services/dataclaw/api/copyright.py +175 -0
  160. package/extensions/services/dataclaw/api/credits.py +177 -0
  161. package/extensions/services/dataclaw/api/data.py +179 -0
  162. package/extensions/services/dataclaw/api/demands.py +269 -0
  163. package/extensions/services/dataclaw/api/feeds.py +262 -0
  164. package/extensions/services/dataclaw/api/identity.py +505 -0
  165. package/extensions/services/dataclaw/api/notifications.py +104 -0
  166. package/extensions/services/dataclaw/api/reviews.py +138 -0
  167. package/extensions/services/dataclaw/api/search.py +153 -0
  168. package/extensions/services/dataclaw/api/subscriptions.py +157 -0
  169. package/extensions/services/dataclaw/config.json5 +96 -0
  170. package/extensions/services/dataclaw/core/__init__.py +0 -0
  171. package/extensions/services/dataclaw/core/auth.py +95 -0
  172. package/extensions/services/dataclaw/core/config.py +50 -0
  173. package/extensions/services/dataclaw/core/database.py +70 -0
  174. package/extensions/services/dataclaw/entry.py +416 -0
  175. 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
  176. package/extensions/services/dataclaw/migrate.py +283 -0
  177. package/extensions/services/dataclaw/models/__init__.py +0 -0
  178. package/extensions/services/dataclaw/module.md +49 -0
  179. package/extensions/services/dataclaw/requirements.txt +18 -0
  180. package/extensions/services/dataclaw/server.py +759 -0
  181. package/extensions/services/dataclaw/services/__init__.py +0 -0
  182. package/extensions/services/dataclaw/services/agent_service.py +132 -0
  183. package/extensions/services/dataclaw/services/credit_service.py +235 -0
  184. package/extensions/services/dataclaw/services/email_service.py +140 -0
  185. package/extensions/services/dataclaw/services/feed_service.py +259 -0
  186. package/extensions/services/dataclaw/services/notification_service.py +209 -0
  187. package/extensions/services/dataclaw/services/oauth_service.py +275 -0
  188. package/extensions/services/dataclaw/services/pricing.py +102 -0
  189. package/extensions/services/dataclaw/services/quality.py +79 -0
  190. package/extensions/services/dataclaw/services/reputation.py +142 -0
  191. package/extensions/services/dataclaw/services/sms_service.py +174 -0
  192. package/extensions/services/dataclaw/static/css/common.css +853 -0
  193. package/extensions/services/dataclaw/static/css/themes/blue.css +42 -0
  194. package/extensions/services/dataclaw/static/css/themes/dark.css +42 -0
  195. package/extensions/services/dataclaw/static/css/themes/light.css +35 -0
  196. package/extensions/services/dataclaw/static/js/api.js +103 -0
  197. package/extensions/services/dataclaw/static/js/common.js +321 -0
  198. package/extensions/services/dataclaw/static/js/i18n.js +95 -0
  199. package/extensions/services/dataclaw/static/js/pages/admin.js +152 -0
  200. package/extensions/services/dataclaw/static/js/pages/dashboard.js +82 -0
  201. package/extensions/services/dataclaw/static/js/pages/feed-detail.js +180 -0
  202. package/extensions/services/dataclaw/static/js/pages/feed-manage.js +158 -0
  203. package/extensions/services/dataclaw/static/js/theme.js +46 -0
  204. package/extensions/services/dataclaw/static/locales/en-US.json +464 -0
  205. package/extensions/services/dataclaw/static/locales/ja-JP.json +464 -0
  206. package/extensions/services/dataclaw/static/locales/zh-CN.json +464 -0
  207. package/extensions/services/dataclaw/templates/admin/index.html +90 -0
  208. package/extensions/services/dataclaw/templates/base.html +136 -0
  209. package/extensions/services/dataclaw/templates/credits/balance.html +106 -0
  210. package/extensions/services/dataclaw/templates/credits/deposit.html +164 -0
  211. package/extensions/services/dataclaw/templates/credits/history.html +90 -0
  212. package/extensions/services/dataclaw/templates/dashboard.html +52 -0
  213. package/extensions/services/dataclaw/templates/demands/create.html +78 -0
  214. package/extensions/services/dataclaw/templates/demands/detail.html +136 -0
  215. package/extensions/services/dataclaw/templates/demands/list.html +94 -0
  216. package/extensions/services/dataclaw/templates/feeds/create.html +95 -0
  217. package/extensions/services/dataclaw/templates/feeds/detail.html +110 -0
  218. package/extensions/services/dataclaw/templates/feeds/list.html +110 -0
  219. package/extensions/services/dataclaw/templates/feeds/manage.html +88 -0
  220. package/extensions/services/dataclaw/templates/index.html +185 -0
  221. package/extensions/services/dataclaw/templates/login.html +246 -0
  222. package/extensions/services/dataclaw/templates/register.html +164 -0
  223. package/extensions/services/dataclaw/templates/settings/notifications.html +96 -0
  224. package/extensions/services/dataclaw/templates/settings/profile.html +167 -0
  225. package/extensions/services/dataclaw/templates/subscriptions/list.html +64 -0
  226. package/extensions/services/dataclaw/tests/__init__.py +0 -0
  227. package/extensions/services/dataclaw/tests/conftest.py +68 -0
  228. package/extensions/services/dataclaw/tests/integration/__init__.py +0 -0
  229. package/extensions/services/dataclaw/tests/integration/test_workflows.py +239 -0
  230. package/extensions/services/dataclaw/tests/unit/__init__.py +0 -0
  231. package/extensions/services/dataclaw/tests/unit/test_admin.py +70 -0
  232. package/extensions/services/dataclaw/tests/unit/test_copyright.py +63 -0
  233. package/extensions/services/dataclaw/tests/unit/test_credits.py +80 -0
  234. package/extensions/services/dataclaw/tests/unit/test_data.py +98 -0
  235. package/extensions/services/dataclaw/tests/unit/test_demands.py +106 -0
  236. package/extensions/services/dataclaw/tests/unit/test_feeds.py +98 -0
  237. package/extensions/services/dataclaw/tests/unit/test_identity.py +88 -0
  238. package/extensions/services/dataclaw/tests/unit/test_notifications.py +36 -0
  239. package/extensions/services/dataclaw/tests/unit/test_reviews.py +68 -0
  240. package/extensions/services/dataclaw/tests/unit/test_search.py +64 -0
  241. package/extensions/services/dataclaw/tests/unit/test_subscriptions.py +65 -0
  242. package/extensions/services/dataclaw/tests/unit/test_system.py +106 -0
  243. package/extensions/services/dataclaw/utils/__init__.py +0 -0
  244. package/extensions/services/dataclaw/utils/crypto.py +38 -0
  245. package/extensions/services/dataclaw/utils/id_generator.py +52 -0
  246. package/extensions/services/dataclaw/ws/__init__.py +0 -0
  247. package/extensions/services/dataclaw/ws/handler.py +163 -0
  248. 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
  249. 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
  250. package/extensions/services/evol/__init__.py +1 -0
  251. package/extensions/services/evol/async_http.py +551 -0
  252. package/extensions/services/evol/auth_manager.py +602 -443
  253. package/extensions/services/evol/config.json5 +16 -0
  254. package/extensions/services/evol/entry.py +568 -406
  255. package/extensions/services/evol/evol_api.py +969 -173
  256. package/extensions/services/evol/mfa_totp.py +77 -0
  257. package/extensions/services/evol/module.md +150 -32
  258. package/extensions/services/evol/nonce_pool.py +113 -0
  259. package/extensions/services/evol/oauth_manager.py +223 -0
  260. package/extensions/services/evol/pairing.py +3 -2
  261. package/extensions/services/evol/pairing_codes.jsonl +1 -0
  262. package/extensions/services/evol/relay.py +1031 -682
  263. package/extensions/services/evol/relay_config.json5 +85 -67
  264. package/extensions/services/evol/routes/routes_llm.py +231 -0
  265. package/extensions/services/evol/routes/routes_rpc.py +90 -89
  266. package/extensions/services/evol/routes/routes_test.py +11 -4
  267. package/extensions/services/evol/server.py +2426 -875
  268. package/extensions/services/evol/static/assets/CommissionView-Cs_ys6Gm.js +1 -0
  269. package/extensions/services/evol/static/assets/CommissionView-DACet_Oo.css +1 -0
  270. package/extensions/services/evol/static/assets/IframePage-DbO11U9G.js +1 -0
  271. package/extensions/services/evol/static/assets/IframePage-c572lT8i.css +1 -0
  272. package/extensions/services/evol/static/assets/TeamDetailView-DULrGD7k.css +1 -0
  273. package/extensions/services/evol/static/assets/TeamDetailView-gy_MBEqG.js +139 -0
  274. package/extensions/services/evol/static/assets/element-plus-Bd7pZkkM.js +63 -0
  275. package/extensions/services/evol/static/assets/index-CmMONKzG.css +1 -0
  276. package/extensions/services/evol/static/assets/index-D44bBe__.js +2 -0
  277. package/extensions/services/evol/static/assets/vue-vendor-DtF-__I4.js +29 -0
  278. package/extensions/services/evol/static/index.html +16 -781
  279. package/extensions/services/evol/static/logo.png +0 -0
  280. package/extensions/services/evol/stats_manager.py +243 -240
  281. package/extensions/services/evol/web/README.md +89 -0
  282. package/extensions/services/evol/web/build.bat +44 -0
  283. package/extensions/services/evol/web/index.html +13 -0
  284. package/extensions/services/evol/web/package-lock.json +1718 -0
  285. package/extensions/services/evol/web/package.json +26 -0
  286. package/extensions/services/evol/web/public/logo.png +0 -0
  287. package/extensions/services/evol/web/src/App.vue +7 -0
  288. package/extensions/services/evol/web/src/components/layout/AppHeader.vue +202 -0
  289. package/extensions/services/evol/web/src/components/layout/AppLayout.vue +61 -0
  290. package/extensions/services/evol/web/src/components/layout/AppSidebar.vue +115 -0
  291. package/extensions/services/evol/web/src/components/login/LoginPage.vue +271 -0
  292. package/extensions/services/evol/web/src/components/team/AddMemberModal.vue +181 -0
  293. package/extensions/services/evol/web/src/components/team/GroupTreeNode.vue +156 -0
  294. package/extensions/services/evol/web/src/components/team/TeamAlertConfig.vue +221 -0
  295. package/extensions/services/evol/web/src/components/team/TeamBillModal.vue +165 -0
  296. package/extensions/services/evol/web/src/components/team/TeamMembersAndGroups.vue +499 -0
  297. package/extensions/services/evol/web/src/components/team/TeamStatsPanel.vue +907 -0
  298. package/extensions/services/evol/web/src/components/team/TreeNode.vue +331 -0
  299. package/extensions/services/evol/web/src/components/team/stats/StatsExportProgress.vue +44 -0
  300. package/extensions/services/evol/web/src/components/team/stats/StatsHeader.vue +89 -0
  301. package/extensions/services/evol/web/src/components/team/stats/StatsMemberDetail.vue +415 -0
  302. package/extensions/services/evol/web/src/components/team/stats/StatsSummary.vue +42 -0
  303. package/extensions/services/evol/web/src/components/team/stats/helpers.ts +195 -0
  304. package/extensions/services/evol/web/src/components/team/stats/stats.css +741 -0
  305. package/extensions/services/evol/web/src/components/team/stats/useStatsApi.ts +114 -0
  306. package/extensions/services/evol/web/src/components/team/stats/useStatsCharts.ts +242 -0
  307. package/extensions/services/evol/web/src/components/team/stats/useStatsExport.ts +232 -0
  308. package/extensions/services/evol/web/src/composables/useFormatters.ts +42 -0
  309. package/extensions/services/evol/web/src/composables/useTheme.ts +52 -0
  310. package/extensions/services/evol/web/src/env.d.ts +7 -0
  311. package/extensions/services/evol/web/src/i18n/en.ts +361 -0
  312. package/extensions/services/evol/web/src/i18n/index.ts +36 -0
  313. package/extensions/services/evol/web/src/i18n/zh.ts +379 -0
  314. package/extensions/services/evol/web/src/main.ts +21 -0
  315. package/extensions/services/evol/web/src/router/index.ts +81 -0
  316. package/extensions/services/evol/web/src/services/kernel-client.ts +406 -0
  317. package/extensions/services/evol/web/src/stores/auth.ts +189 -0
  318. package/extensions/services/evol/web/src/stores/connection.ts +134 -0
  319. package/extensions/services/evol/web/src/stores/pages.ts +79 -0
  320. package/extensions/services/evol/web/src/styles/base.css +213 -0
  321. package/extensions/services/evol/web/src/styles/variables.css +138 -0
  322. package/extensions/services/evol/web/src/types/rpc.ts +35 -0
  323. package/extensions/services/evol/web/src/types/token.ts +87 -0
  324. package/extensions/services/evol/web/src/views/AccountView.vue +1532 -0
  325. package/extensions/services/evol/web/src/views/AiServiceView.vue +219 -0
  326. package/extensions/services/evol/web/src/views/CommissionView.vue +1220 -0
  327. package/extensions/services/evol/web/src/views/CreditsView.vue +131 -0
  328. package/extensions/services/evol/web/src/views/EndpointView.vue +163 -0
  329. package/extensions/services/evol/web/src/views/IframePage.vue +120 -0
  330. package/extensions/services/evol/web/src/views/TeamDetailView.vue +473 -0
  331. package/extensions/services/evol/web/src/views/TeamView.vue +332 -0
  332. package/extensions/services/evol/web/tsconfig.json +31 -0
  333. package/extensions/services/evol/web/tsconfig.node.json +10 -0
  334. package/extensions/services/evol/web/vite.config.ts +49 -0
  335. package/extensions/services/evolmem/__init__.py +0 -0
  336. package/extensions/services/evolmem/entry.py +387 -0
  337. package/extensions/services/evolmem/hooks/__init__.py +0 -0
  338. package/extensions/services/evolmem/hooks/assistant_stop.py +228 -0
  339. package/extensions/services/evolmem/hooks/common.py +76 -0
  340. package/extensions/services/evolmem/hooks/pre_tool_use.py +56 -0
  341. package/extensions/services/evolmem/hooks/session_end.py +133 -0
  342. package/extensions/services/evolmem/hooks/session_start.py +229 -0
  343. package/extensions/services/evolmem/hooks/user_prompt.py +122 -0
  344. package/extensions/services/evolmem/module.md +48 -0
  345. package/extensions/services/evolmem/prompts/00-server-info.md +28 -0
  346. package/extensions/services/evolmem/prompts/01-behavior.md +46 -0
  347. package/extensions/services/evolmem/prompts/02-summary-format.md +112 -0
  348. package/extensions/services/evolmem/prompts/03-file-query.md +92 -0
  349. package/extensions/services/evolmem/prompts/04-topic-stats.md +11 -0
  350. package/extensions/services/evolmem/prompts/05-recent-topics.md +84 -0
  351. package/extensions/services/evolmem/scripts/__init__.py +0 -0
  352. package/extensions/services/evolmem/scripts/extract_keywords.py +40 -0
  353. package/extensions/services/evolmem/scripts/search_topics.py +91 -0
  354. package/extensions/services/evolmem/server.py +641 -0
  355. package/extensions/services/gateway/entry.py +964 -0
  356. package/extensions/services/gateway/module.md +29 -0
  357. package/extensions/services/gateway/nonce_pool.py +65 -0
  358. package/extensions/services/gateway/relay.py +133 -0
  359. package/extensions/services/gateway/ws_server.py +285 -0
  360. package/extensions/services/kite_console/auth_manager.py +603 -0
  361. package/extensions/services/kite_console/config.json5 +19 -0
  362. package/extensions/services/kite_console/config_loader.py +117 -0
  363. package/extensions/services/kite_console/entry.py +528 -0
  364. package/extensions/services/kite_console/evol_api.py +179 -0
  365. package/extensions/services/kite_console/evol_config.json5 +29 -0
  366. package/extensions/services/kite_console/mfa_totp.py +77 -0
  367. package/extensions/services/kite_console/migrate_tokens.py +122 -0
  368. package/extensions/services/kite_console/module.md +37 -0
  369. package/extensions/services/kite_console/nonce_pool.py +113 -0
  370. package/extensions/services/kite_console/oauth_manager.py +223 -0
  371. package/extensions/services/kite_console/pairing.py +280 -0
  372. package/extensions/services/kite_console/pairing_codes.jsonl +2 -0
  373. package/extensions/services/kite_console/relay.py +1350 -0
  374. package/extensions/services/kite_console/relay_config.json5 +96 -0
  375. package/extensions/services/kite_console/routes/__init__.py +1 -0
  376. package/extensions/services/kite_console/routes/routes_llm.py +231 -0
  377. package/extensions/services/kite_console/routes/routes_proxy.py +115 -0
  378. package/extensions/services/kite_console/routes/routes_rpc.py +89 -0
  379. package/extensions/services/kite_console/routes/routes_test.py +68 -0
  380. package/extensions/services/kite_console/server.py +1742 -0
  381. package/extensions/services/{evol → kite_console}/static/css/style.css +656 -2
  382. package/extensions/services/kite_console/static/index.html +1524 -0
  383. package/extensions/services/{evol → kite_console}/static/js/dialog.js +11 -4
  384. package/extensions/services/kite_console/static/js/evol-app.js +7740 -0
  385. package/extensions/services/{evol/static/js/evol-app.js → kite_console/static/js/evol-app.js.backup} +2777 -1949
  386. package/extensions/services/kite_console/static/js/kernel-client.js +560 -0
  387. package/extensions/services/{evol/static/js/kernel-client.js → kite_console/static/js/kernel-client.js.backup} +41 -3
  388. package/extensions/services/{evol → kite_console}/static/js/registry-tests.js +7 -0
  389. package/extensions/services/kite_console/static/js/tests/ARCHITECTURE.md +67 -0
  390. package/extensions/services/kite_console/static/js/tests/README.md +140 -0
  391. package/extensions/services/kite_console/static/js/tests/index.js +161 -0
  392. package/extensions/services/kite_console/static/js/tests/integration/auth.js +120 -0
  393. package/extensions/services/kite_console/static/js/tests/integration/channel-interaction.js +188 -0
  394. package/extensions/services/kite_console/static/js/tests/integration/elastic-connection.js +115 -0
  395. package/extensions/services/kite_console/static/js/tests/integration/full-workflow.js +43 -0
  396. package/extensions/services/kite_console/static/js/tests/integration/multi-instance.js +304 -0
  397. package/extensions/services/kite_console/static/js/tests/integration/nested-rpc.js +266 -0
  398. package/extensions/services/kite_console/static/js/tests/integration/pingpong.js +25 -0
  399. package/extensions/services/kite_console/static/js/tests/integration/redis.js +227 -0
  400. package/extensions/services/kite_console/static/js/tests/integration/registry-core.js +52 -0
  401. package/extensions/services/kite_console/static/js/tests/integration/remote-deploy.js +85 -0
  402. package/extensions/services/kite_console/static/js/tests/integration/require-init.js +96 -0
  403. package/extensions/services/kite_console/static/js/tests/integration/scaling-control.js +193 -0
  404. package/extensions/services/kite_console/static/js/tests/integration/trace.js +109 -0
  405. package/extensions/services/kite_console/static/js/tests/modules/acp_channel.js +339 -0
  406. package/extensions/services/kite_console/static/js/tests/modules/auth.js +96 -0
  407. package/extensions/services/kite_console/static/js/tests/modules/backup.js +49 -0
  408. package/extensions/services/kite_console/static/js/tests/modules/gateway.js +41 -0
  409. package/extensions/services/kite_console/static/js/tests/modules/kernel.js +90 -0
  410. package/extensions/services/kite_console/static/js/tests/modules/launcher.js +75 -0
  411. package/extensions/services/kite_console/static/js/tests/modules/multi_instance.js +129 -0
  412. package/extensions/services/kite_console/static/js/tests/modules/phone_channel.js +364 -0
  413. package/extensions/services/kite_console/static/js/tests/modules/redis.js +178 -0
  414. package/extensions/services/kite_console/static/js/tests/modules/watchdog.js +60 -0
  415. package/extensions/services/kite_console/static/js/tests/modules/web.js +70 -0
  416. package/extensions/services/kite_console/static/js/tests/test-runner.js +123 -0
  417. package/extensions/services/kite_console/static/js/virtual-list.js +200 -0
  418. package/extensions/services/kite_console/static/test_kernel_client_token.html +352 -0
  419. package/extensions/services/kite_console/stats_manager.py +247 -0
  420. package/extensions/services/logs/README.md +215 -0
  421. package/extensions/services/logs/api_logger.py +37 -0
  422. package/extensions/services/logs/baseline.py +121 -0
  423. package/extensions/services/logs/cleaner.py +76 -0
  424. package/extensions/services/logs/entry.py +449 -0
  425. package/extensions/services/logs/formatter.py +129 -0
  426. package/extensions/services/logs/module.md +38 -0
  427. package/extensions/services/logs/quick_diagnostic.py +128 -0
  428. package/extensions/services/logs/routes/__init__.py +1 -0
  429. package/extensions/services/logs/routes/routes_logs.py +218 -0
  430. package/extensions/services/logs/routes/routes_logs.py.backup +173 -0
  431. package/extensions/services/logs/scanner.py +100 -0
  432. package/extensions/services/logs/searcher.py +263 -0
  433. package/extensions/services/logs/server.py +553 -0
  434. package/extensions/services/logs.zip +0 -0
  435. package/extensions/services/model_service/config.json5 +30 -0
  436. package/extensions/services/model_service/entry.py +620 -171
  437. package/extensions/services/model_service/module.md +11 -2
  438. package/extensions/services/proxy/__init__.py +0 -0
  439. package/extensions/services/proxy/aid_manager.py +419 -0
  440. package/extensions/services/proxy/auth_bridge.py +182 -0
  441. package/extensions/services/proxy/config_store.py +79 -0
  442. package/extensions/services/proxy/entry.py +528 -0
  443. package/extensions/services/proxy/evol/presenter/agentIdPresenter.py +2 -2
  444. package/extensions/services/proxy/evol/presenter/apikeyPresenter.py +18 -28
  445. package/extensions/services/proxy/evol/presenter/configPresenter.py +80 -1127
  446. package/extensions/services/proxy/evol/presenter/userPresenter.py +71 -477
  447. package/extensions/services/proxy/evol/server/claude_proxy_async.py +11 -7
  448. package/extensions/services/proxy/module.md +151 -0
  449. package/extensions/services/proxy/server.py +952 -271
  450. package/extensions/services/redis/ALIGNMENT_CHECKLIST.md +121 -0
  451. package/extensions/services/redis/ALIGNMENT_STATUS.md +548 -0
  452. package/extensions/services/redis/config.json5 +8 -0
  453. package/extensions/services/redis/entry.py +1509 -0
  454. package/extensions/services/redis/entry.py.backup +405 -0
  455. package/extensions/services/redis/module.md +48 -0
  456. package/extensions/services/redis/redis_builtin.py +332 -0
  457. package/extensions/services/redis/redis_external.py +164 -0
  458. package/extensions/services/testUi/entry.py +446 -0
  459. package/extensions/services/testUi/module.md +18 -0
  460. package/extensions/services/testUi/ui/cards.html +131 -0
  461. package/extensions/services/testUi/ui/index.html +22 -0
  462. package/extensions/services/testUi/ui/particles.html +143 -0
  463. package/extensions/services/watchdog/entry.py +1258 -793
  464. package/extensions/services/watchdog/module.md +2 -0
  465. package/extensions/services/watchdog/monitor.py +465 -87
  466. package/extensions/services/web/auth_manager.py +602 -0
  467. package/extensions/services/web/config.json5 +11 -0
  468. package/extensions/services/web/entry.py +598 -478
  469. package/extensions/services/web/mfa_totp.py +77 -0
  470. package/extensions/services/web/module.md +16 -13
  471. package/extensions/services/web/nonce_pool.py +113 -0
  472. package/extensions/services/web/oauth_manager.py +223 -0
  473. package/extensions/services/web/pairing.py +3 -2
  474. package/extensions/services/web/pairing_codes.jsonl +1 -0
  475. package/extensions/services/web/relay.py +442 -63
  476. package/extensions/services/web/relay_config.json5 +1 -2
  477. package/extensions/services/web/routes/routes_rpc.py +6 -6
  478. package/extensions/services/web/server.py +360 -173
  479. package/extensions/services/web/static/index.html +1752 -1738
  480. package/extensions/services/web/static/js/app.js +32 -0
  481. package/extensions/services/web/static/js/kernel-client.js +48 -9
  482. package/extensions/services/web/vendor/bluetooth/audio.py +1 -1
  483. package/extensions/services/web/vendor/config.py +2 -2
  484. package/extensions/services/web/vendor/storage/identity.py +1 -1
  485. package/kernel/entry.py +77 -23
  486. package/kernel/event_hub.py +1122 -74
  487. package/kernel/module.md +2 -1
  488. package/kernel/registry_store.py +208 -11
  489. package/kernel/rpc_router.py +1400 -491
  490. package/kernel/server.py +1021 -134
  491. package/kite_cli/__init__.py +9 -1
  492. package/kite_cli/builders/__init__.py +4 -0
  493. package/kite_cli/builders/base.py +67 -0
  494. package/kite_cli/builders/custom.py +31 -0
  495. package/kite_cli/builders/detector.py +56 -0
  496. package/kite_cli/builders/go.py +34 -0
  497. package/kite_cli/builders/gradle.py +41 -0
  498. package/kite_cli/builders/maven.py +36 -0
  499. package/kite_cli/builders/npm.py +44 -0
  500. package/kite_cli/builders/python.py +37 -0
  501. package/kite_cli/commands/BUILD_GUIDE.md +109 -0
  502. package/kite_cli/commands/build.py +142 -0
  503. package/kite_cli/commands/check.py +60 -0
  504. package/kite_cli/commands/config.py +156 -0
  505. package/kite_cli/commands/deps.py +58 -0
  506. package/kite_cli/commands/deps_install.py +7 -7
  507. package/kite_cli/commands/disable.py +162 -0
  508. package/kite_cli/commands/enable.py +162 -0
  509. package/kite_cli/commands/export.py +96 -0
  510. package/kite_cli/commands/import_cmd.py +110 -0
  511. package/kite_cli/commands/install.py +50 -23
  512. package/kite_cli/commands/install_skill.py +107 -0
  513. package/kite_cli/commands/list.py +128 -31
  514. package/kite_cli/commands/outdated.py +202 -0
  515. package/kite_cli/commands/search.py +33 -17
  516. package/kite_cli/commands/update.py +115 -2
  517. package/kite_cli/commands/venv_setup.py +6 -6
  518. package/kite_cli/commands/why.py +48 -0
  519. package/kite_cli/core/config_manager.py +145 -0
  520. package/kite_cli/core/downloader.py +32 -2
  521. package/kite_cli/main.py +153 -7
  522. package/kite_cli/utils/colors.py +153 -0
  523. package/kite_cli/utils/dependency_graph.py +209 -0
  524. package/kite_cli/utils/process.py +55 -0
  525. package/kite_cli/utils/progress.py +207 -0
  526. package/kite_cli/utils/table.py +101 -0
  527. package/launcher/count_lines.py +192 -43
  528. package/launcher/entry.py +4543 -2802
  529. package/launcher/logging_setup.py +54 -1
  530. package/launcher/module.md +32 -6
  531. package/launcher/module_scanner.py +93 -20
  532. package/launcher/process_manager.py +355 -76
  533. package/main.py +6 -0
  534. package/package.json +4 -1
  535. package/requirements.txt +41 -38
  536. package/scripts/auto-fix-deps.py +128 -0
  537. package/scripts/env-manager.js +25 -2
  538. package/scripts/final-test.js +78 -0
  539. package/scripts/setup-python-env.js +700 -191
  540. package/scripts/test-alluser.js +48 -0
  541. package/scripts/test-different-version.js +86 -0
  542. package/scripts/test-direct.js +63 -0
  543. package/scripts/test-extract-installer.js +28 -0
  544. package/scripts/test-install-log.js +54 -0
  545. package/scripts/test-installer.js +39 -0
  546. package/scripts/test-integration.js +250 -0
  547. package/scripts/test-real-install.js +210 -0
  548. package/scripts/test-targetdir.js +49 -0
  549. package/scripts/test-venv-real.js +47 -0
  550. package/scripts/test-venv-simple.js +57 -0
  551. package/scripts/test-wait.js +49 -0
  552. package/scripts/test-with-log.js +63 -0
  553. package/extensions/services/evol/config.yaml +0 -149
  554. package/extensions/services/evol/routes/routes_management_ws.py +0 -127
  555. package/extensions/services/evol/static/index_evol.html +0 -14
  556. package/extensions/services/evol/static/js/app.js +0 -6304
  557. package/extensions/services/evol/static/js/auth.js +0 -326
  558. package/extensions/services/evol/static/js/evol-app-fixed.js +0 -50
  559. package/extensions/services/evol/static/js/evol-app.js.bak +0 -1800
  560. package/extensions/services/evol/static/js/kernel-client-example.js +0 -228
  561. package/extensions/services/evol/static/js/main.js +0 -141
  562. package/extensions/services/evol/static/js/stats.js +0 -217
  563. package/extensions/services/evol/static/js/token-manager.js +0 -175
  564. package/extensions/services/proxy/CHANGELOG_20260308.md +0 -258
  565. package/extensions/services/proxy/_fix_prints.py +0 -133
  566. package/extensions/services/proxy/_fix_prints2.py +0 -87
  567. package/extensions/services/proxy/console_auth.py +0 -109
  568. package/extensions/services/proxy/logs/websocket.log +0 -260
  569. package/extensions/services/proxy/main.py +0 -240
  570. package/extensions/services/proxy/requirements.txt +0 -13
  571. package/extensions/services/web/config.yaml +0 -149
  572. /package/extensions/services/{evol → kite_console}/static/pairing.html +0 -0
  573. /package/extensions/services/{evol → kite_console}/static/test_registry.html +0 -0
  574. /package/extensions/services/{evol → kite_console}/static/test_relay.html +0 -0
@@ -1,682 +1,1031 @@
1
- """
2
- Kernel WebSocket 中转服务
3
-
4
- 功能:
5
- - 接受前端 WebSocket 连接
6
- - 处理配对流程(通过 WebSocket)
7
- - 为每个前端创建到 Kernel 的连接
8
- - 双向转发 JSON-RPC 消息
9
- - 管理前端模块的生命周期(注册、重连、优雅退出)
10
- - RPC 权限控制(白名单)
11
-
12
- 零共享代码依赖 - 此文件可以独立拷贝到其他模块使用。
13
- """
14
-
15
- import asyncio
16
- import json
17
- import secrets
18
- import time
19
- import uuid
20
- from datetime import datetime, timezone
21
- from typing import Optional
22
-
23
- import websockets
24
- from fastapi import WebSocket, WebSocketDisconnect
25
-
26
-
27
- class SessionInfo:
28
- """前端会话信息"""
29
-
30
- def __init__(
31
- self,
32
- session_token: str,
33
- module_id: str,
34
- kernel_ws,
35
- client_ws: WebSocket,
36
- frontend_token: str,
37
- role: str,
38
- kernel_token: str,
39
- ):
40
- self.session_token = session_token
41
- self.module_id = module_id
42
- self.kernel_ws = kernel_ws
43
- self.client_ws: Optional[WebSocket] = client_ws
44
- self.frontend_token = frontend_token
45
- self.role = role
46
- self.kernel_token = kernel_token
47
- self.created_at = time.time()
48
- self.last_active = time.time()
49
- self.status = "active" # active | waiting_reconnect
50
-
51
-
52
- class KernelRelay:
53
- """Kernel WebSocket 中转服务"""
54
-
55
- def __init__(
56
- self,
57
- kernel_host: str,
58
- kernel_port: int,
59
- kernel_token: str,
60
- base_module_id: str,
61
- reconnect_timeout: int,
62
- permissions: dict,
63
- pairing_manager,
64
- evol_server, # 新增:web server 实例,用于发送事件
65
- ):
66
- """
67
- 初始化中转服务。
68
-
69
- Args:
70
- kernel_host: Kernel 主机地址
71
- kernel_port: Kernel 端口
72
- kernel_token: Kernel 认证 token
73
- base_module_id: 基础模块 ID(实际 module_id 会加上 session 后缀)
74
- reconnect_timeout: 重连超时时间(秒)
75
- permissions: 权限配置(角色 → RPC 白名单)
76
- pairing_manager: 配对管理器实例
77
- evol_server: web server 实例
78
- """
79
- self.kernel_host = kernel_host
80
- self.kernel_port = kernel_port
81
- self.kernel_token = kernel_token
82
- self.base_module_id = base_module_id
83
- self.reconnect_timeout = reconnect_timeout
84
- self.permissions = permissions
85
- self.pairing_manager = pairing_manager
86
- self.evol_server = evol_server
87
-
88
- # session_token → SessionInfo
89
- self.sessions: dict[str, SessionInfo] = {}
90
-
91
- # 待重连的 session(超时清理)
92
- self.reconnect_timers: dict[str, asyncio.Task] = {}
93
-
94
- async def handle_client(self, client_ws: WebSocket):
95
- """
96
- 处理客户端连接。
97
-
98
- 流程:
99
- 1. 接受 WebSocket 连接
100
- 2. 接收认证/配对/请求配对码消息
101
- 3. 验证身份
102
- 4. 创建或恢复 Kernel 连接
103
- 5. 双向转发消息
104
- """
105
- await client_ws.accept()
106
-
107
- try:
108
- # 接收认证/配对/请求配对码消息
109
- raw = await client_ws.receive_text()
110
- msg = json.loads(raw)
111
- msg_type = msg.get("type")
112
-
113
- if msg_type == "request_code":
114
- # 请求配对码
115
- await self._handle_request_code(client_ws, msg)
116
- elif msg_type == "pair":
117
- # 配对流程
118
- await self._handle_pair(client_ws, msg)
119
- elif msg_type == "auth":
120
- # 认证流程(已有 token)
121
- await self._handle_auth(client_ws, msg)
122
- else:
123
- await client_ws.send_json({
124
- "type": "error",
125
- "message": "Invalid message type, expected 'request_code', 'pair' or 'auth'"
126
- })
127
- await client_ws.close(code=4000, reason="Invalid message type")
128
-
129
- except WebSocketDisconnect:
130
- pass
131
- except Exception as e:
132
- print(f"[relay] Client connection error: {e}")
133
- try:
134
- await client_ws.close(code=1011, reason="Internal error")
135
- except Exception:
136
- pass
137
-
138
- async def _handle_request_code(self, client_ws: WebSocket, msg: dict):
139
- """处理请求配对码"""
140
- # 生成配对码
141
- code = self.pairing_manager.generate_pairing_code(role="admin")
142
-
143
- # 发送事件给 Launcher(通过 Kernel)
144
- if self.evol_server and self.evol_server._ws:
145
- try:
146
- await self.evol_server._publish_event({
147
- "event": "pairing.status",
148
- "data": {
149
- "step": "code_generated",
150
- "success": True,
151
- "code": code,
152
- "expires_in": 300,
153
- "module_id": "evol"
154
- }
155
- })
156
- except Exception as e:
157
- print(f"[relay] Failed to publish pairing event: {e}")
158
-
159
- # 返回配对码给前端
160
- await client_ws.send_json({
161
- "type": "code_generated",
162
- "code": code
163
- })
164
-
165
- # 关闭连接(前端会重新连接进行配对)
166
- await client_ws.close()
167
-
168
- async def _handle_pair(self, client_ws: WebSocket, msg: dict):
169
- """处理配对请求"""
170
- code = msg.get("code")
171
- if not code:
172
- await client_ws.send_json({
173
- "type": "error",
174
- "message": "Missing pairing code"
175
- })
176
- await client_ws.close(code=4000, reason="Missing pairing code")
177
- return
178
-
179
- # 验证配对码
180
- result = self.pairing_manager.pair(code)
181
- if not result:
182
- # 发送配对失败事件
183
- if self.evol_server and self.evol_server._ws:
184
- try:
185
- await self.evol_server._publish_event({
186
- "event": "pairing.status",
187
- "data": {
188
- "step": "completed",
189
- "success": False,
190
- "reason": "Invalid pairing code"
191
- }
192
- })
193
- except Exception as e:
194
- print(f"[relay] Failed to publish pairing failed event: {e}")
195
-
196
- await client_ws.send_json({
197
- "type": "error",
198
- "message": "Invalid pairing code"
199
- })
200
- await client_ws.close(code=4001, reason="Invalid pairing code")
201
- return
202
-
203
- # 生成 session_token
204
- session_token = "sess_" + secrets.token_urlsafe(6)[:6]
205
-
206
- # 创建新连接
207
- await self._create_new_connection(
208
- client_ws,
209
- session_token,
210
- result["token"],
211
- result["role"],
212
- is_pairing=True
213
- )
214
-
215
- async def _handle_auth(self, client_ws: WebSocket, msg: dict):
216
- """处理认证请求(已有 token)"""
217
- frontend_token = msg.get("token")
218
- session_token = msg.get("session_token")
219
-
220
- if not frontend_token or not session_token:
221
- await client_ws.send_json({
222
- "type": "error",
223
- "message": "Missing token or session_token"
224
- })
225
- await client_ws.close(code=4000, reason="Missing credentials")
226
- return
227
-
228
- # 验证 frontend_token
229
- token_info = self.pairing_manager.verify_token(frontend_token)
230
- if not token_info:
231
- await client_ws.send_json({
232
- "type": "error",
233
- "message": "Invalid or expired token"
234
- })
235
- await client_ws.close(code=4001, reason="Invalid token")
236
- return
237
-
238
- # 检查是否是重连
239
- if session_token in self.sessions:
240
- await self._handle_reconnect(client_ws, session_token, token_info)
241
- else:
242
- await self._create_new_connection(
243
- client_ws,
244
- session_token,
245
- frontend_token,
246
- token_info["role"]
247
- )
248
-
249
- async def _create_new_connection(
250
- self,
251
- client_ws: WebSocket,
252
- session_token: str,
253
- frontend_token: str,
254
- role: str,
255
- is_pairing: bool = False
256
- ):
257
- """创建新的 Kernel 连接"""
258
- # 生成 module_id
259
- suffix = session_token.replace("sess_", "")
260
- module_id = f"{self.base_module_id}-{suffix}"
261
-
262
- try:
263
- # Launcher 申请 kernel_token
264
- kernel_token = await self._request_kernel_token(module_id)
265
-
266
- # 连接 Kernel
267
- kernel_ws = await self._connect_kernel(module_id, kernel_token)
268
-
269
- # 创建 session
270
- session = SessionInfo(
271
- session_token=session_token,
272
- module_id=module_id,
273
- kernel_ws=kernel_ws,
274
- client_ws=client_ws,
275
- frontend_token=frontend_token,
276
- role=role,
277
- kernel_token=kernel_token,
278
- )
279
- self.sessions[session_token] = session
280
-
281
- # 返回认证成功
282
- await client_ws.send_json({
283
- "type": "paired" if is_pairing else "authenticated",
284
- "token": frontend_token,
285
- "session_token": session_token,
286
- "module_id": module_id,
287
- "role": role
288
- })
289
-
290
- print(f"[relay] New connection: {module_id} (role: {role})")
291
-
292
- # 如果是配对,发送配对成功事件给 Launcher
293
- if is_pairing and self.evol_server and self.evol_server._ws:
294
- try:
295
- await self.evol_server._publish_event({
296
- "event": "pairing.status",
297
- "data": {
298
- "step": "completed",
299
- "success": True,
300
- "module_id": module_id,
301
- "role": role
302
- }
303
- })
304
- except Exception as e:
305
- print(f"[relay] Failed to publish pairing success event: {e}")
306
-
307
- # 开始双向转发
308
- await self._relay_messages(session)
309
-
310
- except Exception as e:
311
- error_msg = str(e)
312
- print(f"[relay] Failed to create connection: {error_msg}")
313
-
314
- # 判断错误类型
315
- is_fatal = self._is_fatal_error(error_msg)
316
- error_code = 1011 # Internal Error (default)
317
-
318
- if is_fatal:
319
- # 永久性错误(权限、配置错误)
320
- error_code = 1008 # Policy Violation
321
- print(f"[relay] Fatal error detected, client should not retry")
322
-
323
- await client_ws.send_json({
324
- "type": "error",
325
- "message": f"Failed to connect to Kernel: {error_msg}",
326
- "fatal": is_fatal, # 告知前端是否应该重试
327
- "code": error_code
328
- })
329
- await client_ws.close(code=error_code, reason="Kernel connection failed")
330
-
331
- def _is_fatal_error(self, error_msg: str) -> bool:
332
- """判断是否为永久性错误(不应重试)"""
333
- fatal_keywords = [
334
- "Permission denied",
335
- "not in relay_modules whitelist",
336
- "module_id must start with",
337
- "Invalid module_id",
338
- "Token limit reached",
339
- ]
340
- return any(keyword in error_msg for keyword in fatal_keywords)
341
-
342
- async def _handle_reconnect(
343
- self,
344
- client_ws: WebSocket,
345
- session_token: str,
346
- token_info: dict
347
- ):
348
- """处理重连"""
349
- session = self.sessions[session_token]
350
-
351
- # 取消超时计时器
352
- if session_token in self.reconnect_timers:
353
- self.reconnect_timers[session_token].cancel()
354
- del self.reconnect_timers[session_token]
355
-
356
- # 更新 client_ws
357
- session.client_ws = client_ws
358
- session.status = "active"
359
- session.last_active = time.time()
360
-
361
- # 返回重连成功
362
- await client_ws.send_json({
363
- "type": "reconnected",
364
- "module_id": session.module_id,
365
- "role": session.role
366
- })
367
-
368
- print(f"[relay] Reconnected: {session.module_id}")
369
-
370
- # 继续双向转发
371
- await self._relay_messages(session)
372
-
373
- async def _request_kernel_token(self, module_id: str) -> str:
374
- """向 Launcher 申请 kernel_token"""
375
- if not self.evol_server or not self.evol_server._ws:
376
- raise RuntimeError("evol_server not connected to Kernel")
377
-
378
- print(f"[relay] Requesting kernel_token for {module_id} from Launcher")
379
-
380
- try:
381
- result = await self.evol_server._rpc_call(
382
- "launcher.request_client_token",
383
- {"module_id": module_id}
384
- )
385
- token = result.get("token")
386
- if not token:
387
- raise RuntimeError(f"Launcher returned no token: {result}")
388
-
389
- print(f"[relay] Received kernel_token for {module_id}")
390
- return token
391
-
392
- except Exception as e:
393
- print(f"[relay] Failed to request kernel_token: {e}")
394
- raise
395
-
396
- async def _connect_kernel(self, module_id: str, kernel_token: str):
397
- """连接到 Kernel"""
398
- url = f"ws://{self.kernel_host}:{self.kernel_port}/ws?token={kernel_token}&id={module_id}"
399
- print(f"[relay] DEBUG: Connecting to Kernel with module_id={module_id}, url={url}")
400
- kernel_ws = await websockets.connect(
401
- url,
402
- open_timeout=5,
403
- ping_interval=None,
404
- close_timeout=10
405
- )
406
- print(f"[relay] DEBUG: Connected to Kernel")
407
-
408
- # 注册模块(不预订阅事件,由前端自己决定)
409
- await self._send_to_kernel(kernel_ws, {
410
- "jsonrpc": "2.0",
411
- "id": str(uuid.uuid4()),
412
- "method": "registry.register",
413
- "params": {
414
- "module_id": module_id,
415
- "module_type": "web_client",
416
- }
417
- })
418
-
419
- # 发送 module.ready
420
- await self._send_to_kernel(kernel_ws, {
421
- "jsonrpc": "2.0",
422
- "id": str(uuid.uuid4()),
423
- "method": "event.publish",
424
- "params": {
425
- "event_id": str(uuid.uuid4()),
426
- "event": "module.ready",
427
- "data": {
428
- "module_id": module_id,
429
- "graceful_shutdown": True,
430
- }
431
- }
432
- })
433
-
434
- return kernel_ws
435
-
436
- async def _relay_messages(self, session: SessionInfo):
437
- """双向转发消息"""
438
- try:
439
- # 创建两个任务:client → kernel, kernel → client
440
- client_to_kernel = asyncio.create_task(
441
- self._forward_client_to_kernel(session)
442
- )
443
- kernel_to_client = asyncio.create_task(
444
- self._forward_kernel_to_client(session)
445
- )
446
-
447
- # 等待任一任务完成(断开)
448
- done, pending = await asyncio.wait(
449
- [client_to_kernel, kernel_to_client],
450
- return_when=asyncio.FIRST_COMPLETED
451
- )
452
-
453
- # 取消未完成的任务
454
- for task in pending:
455
- task.cancel()
456
- try:
457
- await task
458
- except asyncio.CancelledError:
459
- pass
460
-
461
- # 检查已完成任务的异常
462
- for task in done:
463
- try:
464
- task.result()
465
- except Exception:
466
- pass # 忽略异常,因为断开连接是正常的
467
-
468
- except Exception as e:
469
- print(f"[relay] Relay error: {e}")
470
-
471
- finally:
472
- # 客户端断开,启动重连等待
473
- await self._on_client_disconnect(session)
474
-
475
- async def _forward_client_to_kernel(self, session: SessionInfo):
476
- """转发客户端消息到 Kernel(带权限检查)"""
477
- while True:
478
- raw = await session.client_ws.receive_text()
479
- msg = json.loads(raw)
480
-
481
- print(f"[relay] Client → Kernel: {msg.get('method', msg.get('type', 'unknown'))}")
482
-
483
- # 处理心跳 ping
484
- if msg.get("type") == "ping":
485
- await session.client_ws.send_json({"type": "pong"})
486
- continue
487
-
488
- # 检查权限
489
- if not self._check_permission(session.role, msg):
490
- method = msg.get("method", "")
491
- # 红色高亮显示权限错误
492
- print(f"\033[91m[relay] 权限被拒绝: {method} (角色: {session.role})\033[0m")
493
- # 返回权限错误
494
- if "id" in msg:
495
- await session.client_ws.send_json({
496
- "jsonrpc": "2.0",
497
- "id": msg["id"],
498
- "error": {
499
- "code": -32000,
500
- "message": f"Permission denied: {method} (role: {session.role})"
501
- }
502
- })
503
- continue
504
-
505
- # 检查是否是 web.* RPC 调用
506
- method = msg.get("method", "")
507
- if method.startswith("web.") and "id" in msg:
508
- print(f"[relay] Intercepted web RPC: {method}")
509
- # 拦截并处理 web.* RPC 调用
510
- await self._handle_web_rpc(session, msg)
511
- continue
512
-
513
- # 转发到 Kernel
514
- print(f"[relay] Forwarding to Kernel: {method}")
515
- await self._send_to_kernel(session.kernel_ws, msg)
516
-
517
- async def _forward_kernel_to_client(self, session: SessionInfo):
518
- """转发 Kernel 消息到客户端"""
519
- async for raw in session.kernel_ws:
520
- print(f"[relay] Kernel → Client: {len(raw)} bytes")
521
- await session.client_ws.send_text(raw)
522
-
523
- def _check_permission(self, role: str, msg: dict) -> bool:
524
- """检查 RPC 权限"""
525
- method = msg.get("method")
526
- if not method:
527
- return True # RPC 请求,放行
528
-
529
- # 获取角色的权限列表
530
- allowed = self.permissions.get(role, [])
531
-
532
- # 检查是否匹配
533
- for pattern in allowed:
534
- if pattern.endswith(".*"):
535
- # 通配符匹配
536
- prefix = pattern[:-2]
537
- if method.startswith(prefix + "."):
538
- return True
539
- elif pattern == method:
540
- # 精确匹配
541
- return True
542
-
543
- return False
544
-
545
- async def _on_client_disconnect(self, session: SessionInfo):
546
- """客户端断开,启动重连等待"""
547
- session.status = "waiting_reconnect"
548
- session.client_ws = None
549
-
550
- print(f"[relay] Client disconnected: {session.module_id}, waiting {self.reconnect_timeout}s for reconnect")
551
-
552
- # 启动超时计时器
553
- timer = asyncio.create_task(self._reconnect_timeout(session))
554
- self.reconnect_timers[session.session_token] = timer
555
-
556
- async def _reconnect_timeout(self, session: SessionInfo):
557
- """重连超时,执行优雅退出"""
558
- await asyncio.sleep(self.reconnect_timeout)
559
-
560
- # 超时后仍未重连,执行优雅退出
561
- if session.status == "waiting_reconnect":
562
- await self._graceful_shutdown(session)
563
-
564
- async def _graceful_shutdown(self, session: SessionInfo):
565
- """代表前端执行优雅退出"""
566
- print(f"[relay] Graceful shutdown: {session.module_id}")
567
-
568
- try:
569
- # 发送 module.exiting 事件(带 token_revoked 标记)
570
- await self._send_to_kernel(session.kernel_ws, {
571
- "jsonrpc": "2.0",
572
- "id": str(uuid.uuid4()),
573
- "method": "event.publish",
574
- "params": {
575
- "event_id": str(uuid.uuid4()),
576
- "event": "module.exiting",
577
- "data": {
578
- "module_id": session.module_id,
579
- "action": "none",
580
- "token_revoked": True
581
- }
582
- }
583
- })
584
-
585
- # 发送 module.shutdown.ready 事件
586
- await self._send_to_kernel(session.kernel_ws, {
587
- "jsonrpc": "2.0",
588
- "id": str(uuid.uuid4()),
589
- "method": "event.publish",
590
- "params": {
591
- "event_id": str(uuid.uuid4()),
592
- "event": "module.shutdown.ready",
593
- "data": {
594
- "module_id": session.module_id
595
- }
596
- }
597
- })
598
-
599
- # 断开 Kernel 连接
600
- await session.kernel_ws.close()
601
-
602
- except Exception as e:
603
- print(f"[relay] Graceful shutdown error: {e}")
604
-
605
- finally:
606
- # 删除 session
607
- if session.session_token in self.sessions:
608
- del self.sessions[session.session_token]
609
- if session.session_token in self.reconnect_timers:
610
- del self.reconnect_timers[session.session_token]
611
-
612
- async def _send_to_kernel(self, kernel_ws, msg: dict):
613
- """发送消息到 Kernel"""
614
- await kernel_ws.send(json.dumps(msg))
615
-
616
- async def _handle_web_rpc(self, session: SessionInfo, msg: dict):
617
- """处理 web.* RPC 调用"""
618
- rpc_id = msg.get("id")
619
- method = msg.get("method", "")
620
- params = msg.get("params", {})
621
-
622
- print(f"[relay] Handling web RPC: {method} (id={rpc_id})")
623
-
624
- # 去掉 web. 前缀
625
- if method.startswith("web."):
626
- method = method[4:]
627
-
628
- try:
629
- # 调用 evol_server 的 RPC 处理器
630
- if method == "list_tokens":
631
- print(f"[relay] Calling list_tokens")
632
- result = await self.evol_server._rpc_list_tokens()
633
- elif method == "revoke_token":
634
- print(f"[relay] Calling revoke_token with params: {params}")
635
- result = await self.evol_server._rpc_revoke_token(params)
636
- else:
637
- raise ValueError(f"Unknown method: web.{method}")
638
-
639
- print(f"[relay] RPC success: {method}, result keys: {list(result.keys()) if isinstance(result, dict) else type(result)}")
640
-
641
- # 返回结果
642
- await session.client_ws.send_json({
643
- "jsonrpc": "2.0",
644
- "id": rpc_id,
645
- "result": result
646
- })
647
- except Exception as e:
648
- # 红色高亮显示 RPC 错误
649
- print(f"\033[91m[relay] RPC 错误: {method}, 错误: {e}\033[0m")
650
- # 返回错误
651
- await session.client_ws.send_json({
652
- "jsonrpc": "2.0",
653
- "id": rpc_id,
654
- "error": {
655
- "code": -32603,
656
- "message": str(e)
657
- }
658
- })
659
-
660
- async def close_all_sessions(self):
661
- """优雅关闭所有会话(用于 shutdown)"""
662
- print(f"[relay] Closing {len(self.sessions)} active sessions...")
663
-
664
- # 取消所有重连定时器
665
- for timer in self.reconnect_timers.values():
666
- timer.cancel()
667
- self.reconnect_timers.clear()
668
-
669
- # 关闭所有会话
670
- for session in list(self.sessions.values()):
671
- try:
672
- # 关闭客户端连接
673
- if session.client_ws:
674
- await session.client_ws.close(code=1001, reason="Server shutting down")
675
- # 关闭 Kernel 连接
676
- if session.kernel_ws:
677
- await session.kernel_ws.close()
678
- except Exception as e:
679
- print(f"[relay] Error closing session {session.session_token}: {e}")
680
-
681
- self.sessions.clear()
682
- print(f"[relay] All sessions closed")
1
+ """
2
+ Kernel WebSocket 中转服务
3
+
4
+ 功能:
5
+ - 接受前端 WebSocket 连接
6
+ - 处理配对流程(通过 WebSocket)
7
+ - 为每个前端创建到 Kernel 的连接
8
+ - 双向转发 JSON-RPC 消息
9
+ - 管理前端模块的生命周期(注册、重连、优雅退出)
10
+ - RPC 权限控制(白名单)
11
+
12
+ 零共享代码依赖 - 此文件可以独立拷贝到其他模块使用。
13
+ """
14
+
15
+ import asyncio
16
+ import json
17
+ import os
18
+ import secrets
19
+ import time
20
+ import uuid
21
+ from datetime import datetime, timezone
22
+ from typing import Optional
23
+
24
+ import websockets
25
+ from fastapi import WebSocket, WebSocketDisconnect
26
+
27
+ from extensions.services.kite_console.nonce_pool import NoncePool
28
+ from extensions.services.kite_console.mfa_totp import TOTPVerifier
29
+
30
+
31
+ class SessionInfo:
32
+ """前端会话信息"""
33
+
34
+ def __init__(
35
+ self,
36
+ session_token: str,
37
+ module_id: str,
38
+ kernel_ws,
39
+ client_ws: WebSocket,
40
+ frontend_token: str,
41
+ role: str,
42
+ kernel_token: str,
43
+ ):
44
+ self.session_token = session_token
45
+ self.module_id = module_id
46
+ self.kernel_ws = kernel_ws
47
+ self.client_ws: Optional[WebSocket] = client_ws
48
+ self.frontend_token = frontend_token
49
+ self.role = role
50
+ self.kernel_token = kernel_token
51
+ self.created_at = time.time()
52
+ self.last_active = time.time()
53
+ self.status = "active" # active | waiting_reconnect
54
+
55
+
56
+ class KernelRelay:
57
+ """Kernel WebSocket 中转服务"""
58
+
59
+ def __init__(
60
+ self,
61
+ kernel_host: str,
62
+ kernel_port: int,
63
+ kernel_token: str,
64
+ base_module_id: str,
65
+ reconnect_timeout: int,
66
+ permissions: dict,
67
+ pairing_manager,
68
+ evol_server, # 新增:web server 实例,用于发送事件
69
+ auth_manager=None, # 新增:AuthManager 实例,用于 Kite Token 认证
70
+ oauth_manager=None, # 新增:OAuthManager 实例,用于 OAuth 认证
71
+ ):
72
+ """
73
+ 初始化中转服务。
74
+
75
+ Args:
76
+ kernel_host: Kernel 主机地址
77
+ kernel_port: Kernel 端口
78
+ kernel_token: Kernel 认证 token
79
+ base_module_id: 基础模块 ID(实际 module_id 会加上 session 后缀)
80
+ reconnect_timeout: 重连超时时间(秒)
81
+ permissions: 权限配置(角色 → RPC 白名单)
82
+ pairing_manager: 配对管理器实例
83
+ evol_server: web server 实例
84
+ auth_manager: AuthManager 实例(可选,用于 Kite Token 认证)
85
+ oauth_manager: OAuthManager 实例(可选,用于 OAuth auth_ticket 认证)
86
+ """
87
+ self.kernel_host = kernel_host
88
+ self.kernel_port = kernel_port
89
+ self.kernel_token = kernel_token
90
+ self.base_module_id = base_module_id
91
+ self.reconnect_timeout = reconnect_timeout
92
+ self.permissions = permissions
93
+ self.pairing_manager = pairing_manager
94
+ self.evol_server = evol_server
95
+ self.auth_manager = auth_manager
96
+ self.oauth_manager = oauth_manager
97
+
98
+ # Nonce 池(challenge-response 防重放)
99
+ self.nonce_pool = NoncePool(max_size=10000, ttl=600)
100
+
101
+ # TLS configuration
102
+ # 默认值根据环境变量:开发环境允许 WS,生产环境强制 WSS
103
+ env = os.environ.get("KITE_ENV", "development").lower()
104
+ self.require_tls = os.environ.get("KITE_REQUIRE_TLS", "true" if env == "production" else "false").lower() == "true"
105
+
106
+ # session_token → SessionInfo
107
+ self.sessions: dict[str, SessionInfo] = {}
108
+
109
+ # 待重连的 session(超时清理)
110
+ self.reconnect_timers: dict[str, asyncio.Task] = {}
111
+
112
+ # 速率限制:(ip, device_id) → [timestamp, timestamp, ...]
113
+ self._rate_limits: dict[tuple[str, str], list[float]] = {}
114
+ self._rate_limit_max = 20 # 10 秒内最多 20 次
115
+ self._rate_limit_window = 10.0 # 秒
116
+
117
+ # MFA/TOTP 验证器
118
+ self._totp = TOTPVerifier()
119
+ # MFA 密钥存储:token → totp_secret(由外部配置注入)
120
+ self._mfa_secrets: dict[str, str] = {}
121
+
122
+ async def handle_client(self, client_ws: WebSocket):
123
+ """
124
+ 处理客户端连接。
125
+
126
+ 流程:
127
+ 1. 接受 WebSocket 连接
128
+ 2. 接收认证/配对/请求配对码消息
129
+ 3. 验证身份
130
+ 4. 创建或恢复 Kernel 连接
131
+ 5. 双向转发消息
132
+ """
133
+ # Check TLS requirement before accepting
134
+ if self.require_tls:
135
+ # Check if connection is secure (WSS)
136
+ if client_ws.url.scheme != "wss":
137
+ await client_ws.close(code=1008, reason="TLS required")
138
+ print(f"[relay] Rejected non-TLS connection from {client_ws.client.host if client_ws.client else 'unknown'}")
139
+ return
140
+
141
+ await client_ws.accept()
142
+
143
+ try:
144
+ # 速率限制检查
145
+ client_ip = client_ws.client.host if client_ws.client else "unknown"
146
+ if not self._check_rate_limit(client_ip, "unknown"):
147
+ await client_ws.send_json({
148
+ "type": "error",
149
+ "message": "Rate limit exceeded"
150
+ })
151
+ await client_ws.close(code=4020, reason="Rate limit exceeded")
152
+ await self._audit_log("auth.rate_limited", {"ip": client_ip})
153
+ return
154
+
155
+ # 接收认证/配对/请求配对码消息
156
+ raw = await client_ws.receive_text()
157
+ msg = json.loads(raw)
158
+ msg_type = msg.get("type")
159
+
160
+ if msg_type == "request_code":
161
+ # 请求配对码
162
+ await self._handle_request_code(client_ws, msg)
163
+ elif msg_type == "challenge":
164
+ # Challenge-response 握手(第一步:客户端请求 challenge)
165
+ await self._handle_challenge(client_ws, msg)
166
+ elif msg_type == "pair":
167
+ # 配对流程
168
+ await self._handle_pair(client_ws, msg)
169
+ elif msg_type == "auth":
170
+ # 认证流程(已有 token)
171
+ await self._handle_auth(client_ws, msg)
172
+ else:
173
+ await client_ws.send_json({
174
+ "type": "error",
175
+ "message": "Invalid message type, expected 'challenge', 'request_code', 'pair' or 'auth'"
176
+ })
177
+ await client_ws.close(code=4000, reason="Invalid message type")
178
+
179
+ except WebSocketDisconnect:
180
+ pass
181
+ except Exception as e:
182
+ print(f"[relay] Client connection error: {e}")
183
+ try:
184
+ await client_ws.close(code=1011, reason="Internal error")
185
+ except Exception:
186
+ pass
187
+
188
+ async def _handle_challenge(self, client_ws: WebSocket, msg: dict):
189
+ """处理 challenge 请求(握手第一步)"""
190
+ client_ip = client_ws.client.host if client_ws.client else "unknown"
191
+ nonce = self.nonce_pool.generate(metadata={"client_ip": client_ip})
192
+
193
+ await client_ws.send_json({
194
+ "type": "challenge",
195
+ "nonce": nonce,
196
+ "protocol_version": "1.0",
197
+ "auth_methods": ["pairing_code", "kite_token", "oauth"],
198
+ "expires_in": self.nonce_pool.ttl,
199
+ "server_time": time.time(), # 客户端可据此校准时钟偏移
200
+ })
201
+
202
+ # 等待客户端的 connect 消息(带 nonce)
203
+ try:
204
+ raw = await asyncio.wait_for(client_ws.receive_text(), timeout=30)
205
+ connect_msg = json.loads(raw)
206
+
207
+ if connect_msg.get("type") != "connect":
208
+ await client_ws.send_json({
209
+ "type": "error",
210
+ "message": "Expected 'connect' message after challenge"
211
+ })
212
+ await client_ws.close(code=4000, reason="Protocol error")
213
+ return
214
+
215
+ # 验证 nonce
216
+ returned_nonce = connect_msg.get("nonce")
217
+ if not returned_nonce:
218
+ await client_ws.send_json({
219
+ "type": "error",
220
+ "message": "Missing nonce in connect message"
221
+ })
222
+ await client_ws.close(code=4000, reason="Missing nonce")
223
+ return
224
+
225
+ nonce_meta = self.nonce_pool.verify(returned_nonce)
226
+ if nonce_meta is None:
227
+ await client_ws.send_json({
228
+ "type": "error",
229
+ "message": "Invalid or expired nonce"
230
+ })
231
+ await client_ws.close(code=4001, reason="Invalid nonce")
232
+ return
233
+
234
+ # Nonce 验证通过,根据 auth_method 分发
235
+ auth_method = connect_msg.get("auth_method", "")
236
+
237
+ if auth_method == "pairing_code":
238
+ code = connect_msg.get("code")
239
+ if not code:
240
+ await client_ws.send_json({"type": "error", "message": "Missing pairing code"})
241
+ await client_ws.close(code=4000, reason="Missing code")
242
+ return
243
+ await self._handle_pair(client_ws, {"code": code})
244
+
245
+ elif auth_method == "kite_token":
246
+ token = connect_msg.get("token")
247
+ session_token = connect_msg.get("session_token")
248
+ if not token or not session_token:
249
+ await client_ws.send_json({"type": "error", "message": "Missing token or session_token"})
250
+ await client_ws.close(code=4000, reason="Missing credentials")
251
+ return
252
+ await self._handle_auth(client_ws, {"token": token, "session_token": session_token})
253
+
254
+ elif auth_method == "oauth":
255
+ auth_ticket = connect_msg.get("auth_ticket")
256
+ if not auth_ticket:
257
+ await client_ws.send_json({"type": "error", "message": "Missing auth_ticket"})
258
+ await client_ws.close(code=4000, reason="Missing auth_ticket")
259
+ return
260
+ await self._handle_auth(client_ws, {
261
+ "method": "oauth",
262
+ "auth_ticket": auth_ticket,
263
+ "session_token": connect_msg.get("session_token", "")
264
+ })
265
+
266
+ else:
267
+ await client_ws.send_json({
268
+ "type": "error",
269
+ "message": f"Unsupported auth_method: {auth_method}"
270
+ })
271
+ await client_ws.close(code=4000, reason="Unsupported auth method")
272
+
273
+ except asyncio.TimeoutError:
274
+ await client_ws.send_json({"type": "error", "message": "Connect timeout"})
275
+ await client_ws.close(code=4000, reason="Timeout")
276
+
277
+ async def _handle_request_code(self, client_ws: WebSocket, msg: dict):
278
+ """处理请求配对码"""
279
+ # 生成配对码
280
+ code = self.pairing_manager.generate_pairing_code(role="admin")
281
+
282
+ # 发送事件给 Launcher(通过 Kernel)
283
+ if self.evol_server and self.evol_server._ws:
284
+ try:
285
+ await self.evol_server._publish_event(
286
+ self.evol_server._ws,
287
+ "pairing.status",
288
+ {
289
+ "step": "code_generated",
290
+ "success": True,
291
+ "code": code,
292
+ "expires_in": 300,
293
+ "module_id": "evol"
294
+ }
295
+ )
296
+ except Exception as e:
297
+ print(f"[relay] Failed to publish pairing event: {e}")
298
+
299
+ # 返回配对码给前端
300
+ await client_ws.send_json({
301
+ "type": "code_generated",
302
+ "code": code
303
+ })
304
+
305
+ # 关闭连接(前端会重新连接进行配对)
306
+ await client_ws.close()
307
+
308
+ async def _handle_pair(self, client_ws: WebSocket, msg: dict):
309
+ """处理配对请求"""
310
+ code = msg.get("code")
311
+ if not code:
312
+ await client_ws.send_json({
313
+ "type": "error",
314
+ "message": "Missing pairing code"
315
+ })
316
+ await client_ws.close(code=4000, reason="Missing pairing code")
317
+ return
318
+
319
+ # 验证配对码
320
+ result = self.pairing_manager.pair(code)
321
+ if not result:
322
+ # 发送配对失败事件
323
+ if self.evol_server and self.evol_server._ws:
324
+ try:
325
+ await self.evol_server._publish_event(
326
+ self.evol_server._ws,
327
+ "pairing.status",
328
+ {
329
+ "step": "completed",
330
+ "success": False,
331
+ "reason": "Invalid pairing code"
332
+ }
333
+ )
334
+ except Exception as e:
335
+ print(f"[relay] Failed to publish pairing failed event: {e}")
336
+
337
+ await client_ws.send_json({
338
+ "type": "error",
339
+ "message": "Invalid pairing code"
340
+ })
341
+ await client_ws.close(code=4001, reason="Invalid pairing code")
342
+ await self._audit_log("auth.pair_failed", {"reason": "invalid_code"})
343
+ return
344
+
345
+ # 生成 session_token
346
+ session_token = "sess_" + secrets.token_urlsafe(6)[:6]
347
+
348
+ # 创建新连接
349
+ await self._create_new_connection(
350
+ client_ws,
351
+ session_token,
352
+ result["token"],
353
+ result["role"],
354
+ is_pairing=True
355
+ )
356
+
357
+ async def _handle_auth(self, client_ws: WebSocket, msg: dict):
358
+ """处理认证请求(已有 token)"""
359
+ frontend_token = msg.get("token")
360
+ session_token = msg.get("session_token")
361
+ auth_method = msg.get("method", "") # 可选:oauth
362
+
363
+ # OAuth auth_ticket 认证
364
+ if auth_method == "oauth":
365
+ auth_ticket = msg.get("auth_ticket")
366
+ if not auth_ticket:
367
+ await client_ws.send_json({
368
+ "type": "error",
369
+ "message": "Missing auth_ticket for OAuth auth"
370
+ })
371
+ await client_ws.close(code=4000, reason="Missing auth_ticket")
372
+ return
373
+
374
+ if not self.oauth_manager:
375
+ await client_ws.send_json({
376
+ "type": "error",
377
+ "message": "OAuth not configured"
378
+ })
379
+ await client_ws.close(code=4000, reason="OAuth not configured")
380
+ return
381
+
382
+ ticket_info = self.oauth_manager.verify_auth_ticket(auth_ticket)
383
+ if not ticket_info:
384
+ await client_ws.send_json({
385
+ "type": "error",
386
+ "message": "Invalid or expired auth_ticket"
387
+ })
388
+ await client_ws.close(code=4001, reason="Invalid auth_ticket")
389
+ await self._audit_log("auth.failed", {"reason": "invalid_auth_ticket", "auth_type": "oauth"})
390
+ return
391
+ print(f"[Relay] Authenticated via OAuth ({ticket_info['provider']}), user={ticket_info['user_info'].get('name', 'unknown')}")
392
+ await self._audit_log("auth.login", {
393
+ "auth_type": "oauth",
394
+ "provider": ticket_info["provider"],
395
+ "user": ticket_info["user_info"].get("name", "unknown"),
396
+ "role": role,
397
+ })
398
+
399
+ # 生成 session_token
400
+ if not session_token:
401
+ session_token = "sess_" + secrets.token_urlsafe(6)[:6]
402
+
403
+ await self._create_new_connection(
404
+ client_ws,
405
+ session_token,
406
+ f"oauth:{ticket_info['provider']}:{ticket_info['user_info'].get('id', '')}",
407
+ role
408
+ )
409
+ return
410
+
411
+ if not frontend_token or not session_token:
412
+ await client_ws.send_json({
413
+ "type": "error",
414
+ "message": "Missing token or session_token"
415
+ })
416
+ await client_ws.close(code=4000, reason="Missing credentials")
417
+ return
418
+
419
+ # 1. 尝试配对码认证
420
+ token_info = self.pairing_manager.verify_token(frontend_token)
421
+ if token_info:
422
+ role = token_info["role"]
423
+ auth_type = "pairing"
424
+ print(f"[Relay] Authenticated via pairing code, role={role}")
425
+
426
+ # 2. 尝试 Kite Token 认证
427
+ elif self.auth_manager and self.auth_manager.verify_kite_token(frontend_token):
428
+ role = "admin" # Kite Token 用户默认为 admin
429
+ auth_type = "kite_token"
430
+ print(f"[Relay] Authenticated via Kite Token, role={role}")
431
+ # 构造 token_info 格式以保持兼容
432
+ token_info = {"role": role}
433
+
434
+ # 3. 认证失败
435
+ else:
436
+ await client_ws.send_json({
437
+ "type": "error",
438
+ "message": "Invalid or expired token"
439
+ })
440
+ await client_ws.close(code=4001, reason="Invalid token")
441
+ await self._audit_log("auth.failed", {"reason": "invalid_token", "session_token": session_token})
442
+ return
443
+
444
+ # 审计:认证成功
445
+ await self._audit_log("auth.login", {"auth_type": auth_type, "role": role, "session_token": session_token})
446
+
447
+ # MFA 检查(可选:如果该 token 配置了 MFA 密钥)
448
+ mfa_secret = self._mfa_secrets.get(frontend_token)
449
+ if mfa_secret:
450
+ mfa_code = msg.get("mfa_code", "")
451
+ if not mfa_code or not self._totp.verify(mfa_secret, mfa_code):
452
+ await client_ws.send_json({
453
+ "type": "error",
454
+ "message": "MFA verification failed"
455
+ })
456
+ await client_ws.close(code=4004, reason="MFA failed")
457
+ await self._audit_log("auth.mfa_failed", {"session_token": session_token})
458
+ return
459
+
460
+ # 检查是否是重连
461
+ if session_token in self.sessions:
462
+ await self._handle_reconnect(client_ws, session_token, token_info)
463
+ else:
464
+ await self._create_new_connection(
465
+ client_ws,
466
+ session_token,
467
+ frontend_token,
468
+ role
469
+ )
470
+
471
+ async def _create_new_connection(
472
+ self,
473
+ client_ws: WebSocket,
474
+ session_token: str,
475
+ frontend_token: str,
476
+ role: str,
477
+ is_pairing: bool = False
478
+ ):
479
+ """创建新的 Kernel 连接"""
480
+ # 生成 module_id
481
+ suffix = session_token.replace("sess_", "")
482
+ module_id = f"{self.base_module_id}-{suffix}"
483
+
484
+ try:
485
+ # Launcher 申请 kernel_token
486
+ kernel_token = await self._request_kernel_token(module_id)
487
+
488
+ # 连接 Kernel
489
+ kernel_ws = await self._connect_kernel(module_id, kernel_token)
490
+
491
+ # 创建 session
492
+ session = SessionInfo(
493
+ session_token=session_token,
494
+ module_id=module_id,
495
+ kernel_ws=kernel_ws,
496
+ client_ws=client_ws,
497
+ frontend_token=frontend_token,
498
+ role=role,
499
+ kernel_token=kernel_token,
500
+ )
501
+ self.sessions[session_token] = session
502
+
503
+ # 返回认证成功
504
+ await client_ws.send_json({
505
+ "type": "paired" if is_pairing else "authenticated",
506
+ "token": frontend_token,
507
+ "session_token": session_token,
508
+ "module_id": module_id,
509
+ "role": role
510
+ })
511
+
512
+ print(f"[relay] New connection: {module_id} (role: {role})")
513
+
514
+ # 如果是配对,发送配对成功事件给 Launcher
515
+ if is_pairing and self.evol_server and self.evol_server._ws:
516
+ try:
517
+ await self.evol_server._publish_event(
518
+ self.evol_server._ws,
519
+ "pairing.status",
520
+ {
521
+ "step": "completed",
522
+ "success": True,
523
+ "module_id": module_id,
524
+ "role": role
525
+ }
526
+ )
527
+ except Exception as e:
528
+ print(f"[relay] Failed to publish pairing success event: {e}")
529
+
530
+ # 开始双向转发
531
+ await self._relay_messages(session)
532
+
533
+ except Exception as e:
534
+ error_msg = str(e)
535
+
536
+ # 判断错误类型
537
+ is_fatal = self._is_fatal_error(error_msg)
538
+ error_code = 1011 # Internal Error (default)
539
+
540
+ if is_fatal:
541
+ # 永久性错误(权限、配置错误)
542
+ error_code = 1008 # Policy Violation
543
+ print(f"\033[31m[relay] 致命错误: {error_msg}\033[0m")
544
+ if "relay_modules whitelist" in error_msg:
545
+ print(f"\033[31m[relay] 请在 launcher/module.md relay.modules 中添加本模块名\033[0m")
546
+ else:
547
+ print(f"[relay] Failed to create connection: {error_msg}")
548
+
549
+ await client_ws.send_json({
550
+ "type": "error",
551
+ "message": f"Failed to connect to Kernel: {error_msg}",
552
+ "fatal": is_fatal, # 告知前端是否应该重试
553
+ "code": error_code
554
+ })
555
+ await client_ws.close(code=error_code, reason="Kernel connection failed")
556
+
557
+ def _is_fatal_error(self, error_msg: str) -> bool:
558
+ """判断是否为永久性错误(不应重试)"""
559
+ fatal_keywords = [
560
+ "Permission denied",
561
+ "not in relay_modules whitelist",
562
+ "module_id must start with",
563
+ "Invalid module_id",
564
+ "Token limit reached",
565
+ ]
566
+ return any(keyword in error_msg for keyword in fatal_keywords)
567
+
568
+ async def _handle_reconnect(
569
+ self,
570
+ client_ws: WebSocket,
571
+ session_token: str,
572
+ token_info: dict
573
+ ):
574
+ """处理重连"""
575
+ session = self.sessions[session_token]
576
+
577
+ # 取消超时计时器
578
+ if session_token in self.reconnect_timers:
579
+ self.reconnect_timers[session_token].cancel()
580
+ del self.reconnect_timers[session_token]
581
+
582
+ # 更新 client_ws
583
+ session.client_ws = client_ws
584
+ session.status = "active"
585
+ session.last_active = time.time()
586
+
587
+ # 返回重连成功
588
+ await client_ws.send_json({
589
+ "type": "reconnected",
590
+ "module_id": session.module_id,
591
+ "role": session.role
592
+ })
593
+
594
+ print(f"[relay] Reconnected: {session.module_id}")
595
+
596
+ # 继续双向转发
597
+ await self._relay_messages(session)
598
+
599
+ async def _request_kernel_token(self, module_id: str) -> str:
600
+ """向 Launcher 申请 kernel_token"""
601
+ if not self.evol_server or not self.evol_server._ws:
602
+ raise RuntimeError("evol_server not connected to Kernel")
603
+
604
+ print(f"[relay] Requesting kernel_token for {module_id} from Launcher")
605
+
606
+ try:
607
+ resp = await self.evol_server._rpc_call(
608
+ self.evol_server._ws,
609
+ "launcher.request_client_token",
610
+ {"module_id": module_id}
611
+ )
612
+ # _rpc_call 返回完整 JSON-RPC 响应,需要解包 result 层
613
+ inner = resp.get("result", resp)
614
+ token = inner.get("token")
615
+ if not token:
616
+ raise RuntimeError(f"Launcher returned no token: {result}")
617
+
618
+ print(f"[relay] Received kernel_token for {module_id}")
619
+ return token
620
+
621
+ except Exception as e:
622
+ print(f"[relay] Failed to request kernel_token: {e}")
623
+ raise
624
+
625
+ async def _connect_kernel(self, module_id: str, kernel_token: str):
626
+ """连接到 Kernel"""
627
+ url = f"ws://{self.kernel_host}:{self.kernel_port}/ws?id={module_id}"
628
+ print(f"[relay] DEBUG: Connecting to Kernel with module_id={module_id}")
629
+ kernel_ws = await websockets.connect(
630
+ url,
631
+ open_timeout=5,
632
+ ping_interval=None,
633
+ close_timeout=10
634
+ )
635
+ print(f"[relay] DEBUG: Connected to Kernel")
636
+
637
+ # 1. 先发送认证请求(Kernel 要求第一条消息必须是 auth)
638
+ auth_id = str(uuid.uuid4())
639
+ await self._send_to_kernel(kernel_ws, {
640
+ "jsonrpc": "2.0",
641
+ "id": auth_id,
642
+ "method": "auth",
643
+ "params": {
644
+ "token": kernel_token
645
+ }
646
+ })
647
+
648
+ # 等待认证响应
649
+ auth_response = await asyncio.wait_for(kernel_ws.recv(), timeout=5.0)
650
+ auth_msg = json.loads(auth_response)
651
+ if "error" in auth_msg:
652
+ raise RuntimeError(f"Kernel auth failed: {auth_msg['error']}")
653
+ print(f"[relay] DEBUG: Kernel auth success")
654
+
655
+ # 2. 注册模块(不预订阅事件,由前端自己决定)
656
+ await self._send_to_kernel(kernel_ws, {
657
+ "jsonrpc": "2.0",
658
+ "id": str(uuid.uuid4()),
659
+ "method": "registry.register",
660
+ "params": {
661
+ "module_id": module_id,
662
+ "module_type": "web_client",
663
+ }
664
+ })
665
+
666
+ # 发送 module.ready
667
+ await self._send_to_kernel(kernel_ws, {
668
+ "jsonrpc": "2.0",
669
+ "id": str(uuid.uuid4()),
670
+ "method": "event.publish",
671
+ "params": {
672
+ "event_id": str(uuid.uuid4()),
673
+ "event": "module.ready",
674
+ "data": {
675
+ "module_id": module_id,
676
+ "graceful_shutdown": True,
677
+ }
678
+ }
679
+ })
680
+
681
+ return kernel_ws
682
+
683
+ async def _relay_messages(self, session: SessionInfo):
684
+ """双向转发消息"""
685
+ try:
686
+ # 创建两个任务:client → kernel, kernel → client
687
+ client_to_kernel = asyncio.create_task(
688
+ self._forward_client_to_kernel(session)
689
+ )
690
+ kernel_to_client = asyncio.create_task(
691
+ self._forward_kernel_to_client(session)
692
+ )
693
+
694
+ # 等待任一任务完成(断开)
695
+ done, pending = await asyncio.wait(
696
+ [client_to_kernel, kernel_to_client],
697
+ return_when=asyncio.FIRST_COMPLETED
698
+ )
699
+
700
+ # 取消未完成的任务
701
+ for task in pending:
702
+ task.cancel()
703
+ try:
704
+ await task
705
+ except asyncio.CancelledError:
706
+ pass
707
+
708
+ # 检查已完成任务的异常
709
+ for task in done:
710
+ try:
711
+ task.result()
712
+ except Exception:
713
+ pass # 忽略异常,因为断开连接是正常的
714
+
715
+ except Exception as e:
716
+ print(f"[relay] Relay error: {e}")
717
+
718
+ finally:
719
+ # 客户端断开,启动重连等待
720
+ await self._on_client_disconnect(session)
721
+
722
+ async def _forward_client_to_kernel(self, session: SessionInfo):
723
+ """转发客户端消息到 Kernel(带权限检查)"""
724
+ try:
725
+ while True:
726
+ raw = await session.client_ws.receive_text()
727
+ msg = json.loads(raw)
728
+
729
+ # print(f"[relay] Client → Kernel: {msg.get('method', msg.get('type', 'unknown'))}")
730
+
731
+ # 处理心跳 ping
732
+ if msg.get("type") == "ping":
733
+ await session.client_ws.send_json({"type": "pong"})
734
+ continue
735
+
736
+ # 检查权限
737
+ if not self._check_permission(session.role, msg):
738
+ method = msg.get("method", "")
739
+ # 红色高亮显示权限错误
740
+ print(f"\033[91m[relay] ✗ 权限被拒绝: {method} (角色: {session.role})\033[0m")
741
+ # 返回权限错误
742
+ if "id" in msg:
743
+ await session.client_ws.send_json({
744
+ "jsonrpc": "2.0",
745
+ "id": msg["id"],
746
+ "error": {
747
+ "code": -32000,
748
+ "message": f"Permission denied: {method} (role: {session.role})"
749
+ }
750
+ })
751
+ continue
752
+
753
+ # 检查是否是 web.* RPC 调用
754
+ method = msg.get("method", "")
755
+ if method.startswith("web.") and "id" in msg:
756
+ print(f"[relay] Intercepted web RPC: {method}")
757
+ # 拦截并处理 web.* RPC 调用
758
+ await self._handle_web_rpc(session, msg)
759
+ continue
760
+
761
+ # 转发到 Kernel(包括 evol.* 调用)
762
+ # print(f"[relay] Forwarding to Kernel: {method}")
763
+ await self._send_to_kernel(session.kernel_ws, msg)
764
+ except (WebSocketDisconnect, AttributeError, asyncio.CancelledError,
765
+ websockets.exceptions.ConnectionClosedOK, websockets.exceptions.ConnectionClosedError):
766
+ pass # 客户端断开或 Kernel 连接关闭,正常退出
767
+
768
+ async def _forward_kernel_to_client(self, session: SessionInfo):
769
+ """转发 Kernel 消息到客户端"""
770
+ try:
771
+ async for raw in session.kernel_ws:
772
+ # 解析消息内容用于日志
773
+ # try:
774
+ # msg = json.loads(raw)
775
+ # if "method" in msg:
776
+ # print(f"[relay] Kernel → Client: {msg['method']} ({len(raw)} bytes)")
777
+ # elif "result" in msg:
778
+ # print(f"[relay] Kernel → Client: response id={msg.get('id')} ({len(raw)} bytes)")
779
+ # elif "error" in msg:
780
+ # print(f"[relay] Kernel → Client: error id={msg.get('id')} ({len(raw)} bytes)")
781
+ # else:
782
+ # print(f"[relay] Kernel → Client: {len(raw)} bytes")
783
+ # except:
784
+ # print(f"[relay] Kernel → Client: {len(raw)} bytes")
785
+ await session.client_ws.send_text(raw)
786
+ except (websockets.exceptions.ConnectionClosedOK, websockets.exceptions.ConnectionClosedError):
787
+ pass # Kernel 连接关闭,正常退出
788
+ except Exception:
789
+ pass # client_ws 断开或被置 None,正常退出
790
+
791
+ def _check_permission(self, role: str, msg: dict) -> bool:
792
+ """检查 RPC 权限"""
793
+ method = msg.get("method")
794
+ if not method:
795
+ return True # 非 RPC 请求,放行
796
+
797
+ # 获取角色的权限列表
798
+ allowed = self.permissions.get(role, [])
799
+
800
+ # 检查是否匹配
801
+ for pattern in allowed:
802
+ if pattern.endswith(".*"):
803
+ # 通配符匹配
804
+ prefix = pattern[:-2]
805
+ if method.startswith(prefix + "."):
806
+ return True
807
+ elif pattern == method:
808
+ # 精确匹配
809
+ return True
810
+
811
+ return False
812
+
813
+ async def _on_client_disconnect(self, session: SessionInfo):
814
+ """客户端断开,启动重连等待"""
815
+ session.status = "waiting_reconnect"
816
+ session.client_ws = None
817
+
818
+ print(f"[relay] Client disconnected: {session.module_id}, waiting {self.reconnect_timeout}s for reconnect")
819
+
820
+ # 启动超时计时器
821
+ timer = asyncio.create_task(self._reconnect_timeout(session))
822
+ self.reconnect_timers[session.session_token] = timer
823
+
824
+ async def _reconnect_timeout(self, session: SessionInfo):
825
+ """重连超时,执行优雅退出"""
826
+ await asyncio.sleep(self.reconnect_timeout)
827
+
828
+ # 超时后仍未重连,执行优雅退出
829
+ if session.status == "waiting_reconnect":
830
+ await self._graceful_shutdown(session)
831
+
832
+ async def _graceful_shutdown(self, session: SessionInfo):
833
+ """代表前端执行优雅退出"""
834
+ print(f"[relay] Graceful shutdown: {session.module_id}")
835
+ await self._audit_log("auth.logout", {"module_id": session.module_id, "reason": "reconnect_timeout"})
836
+
837
+ try:
838
+ # 发送 module.exiting 事件(带 token_revoked 标记)
839
+ await self._send_to_kernel(session.kernel_ws, {
840
+ "jsonrpc": "2.0",
841
+ "id": str(uuid.uuid4()),
842
+ "method": "event.publish",
843
+ "params": {
844
+ "event_id": str(uuid.uuid4()),
845
+ "event": "module.exiting",
846
+ "data": {
847
+ "module_id": session.module_id,
848
+ "action": "none",
849
+ "token_revoked": True
850
+ }
851
+ }
852
+ })
853
+
854
+ # 发送 module.shutdown.ready 事件
855
+ await self._send_to_kernel(session.kernel_ws, {
856
+ "jsonrpc": "2.0",
857
+ "id": str(uuid.uuid4()),
858
+ "method": "event.publish",
859
+ "params": {
860
+ "event_id": str(uuid.uuid4()),
861
+ "event": "module.shutdown.ready",
862
+ "data": {
863
+ "module_id": session.module_id
864
+ }
865
+ }
866
+ })
867
+
868
+ # 断开 Kernel 连接
869
+ await session.kernel_ws.close()
870
+
871
+ except Exception as e:
872
+ print(f"[relay] Graceful shutdown error: {e}")
873
+
874
+ finally:
875
+ # 删除 session
876
+ if session.session_token in self.sessions:
877
+ del self.sessions[session.session_token]
878
+ if session.session_token in self.reconnect_timers:
879
+ del self.reconnect_timers[session.session_token]
880
+
881
+ async def _send_to_kernel(self, kernel_ws, msg: dict):
882
+ """发送消息到 Kernel"""
883
+ await kernel_ws.send(json.dumps(msg))
884
+
885
+ async def _handle_web_rpc(self, session: SessionInfo, msg: dict):
886
+ """处理 web.* RPC 调用"""
887
+ rpc_id = msg.get("id")
888
+ method = msg.get("method", "")
889
+ params = msg.get("params", {})
890
+
891
+ print(f"[relay] Handling web RPC: {method} (id={rpc_id})")
892
+
893
+ # 去掉 web. 前缀
894
+ if method.startswith("web."):
895
+ method = method[4:]
896
+
897
+ try:
898
+ # 调用 evol_server 的 RPC 处理器
899
+ if method == "list_tokens":
900
+ print(f"[relay] Calling list_tokens")
901
+ result = await self.evol_server._rpc_list_tokens()
902
+ elif method == "revoke_token":
903
+ print(f"[relay] Calling revoke_token with params: {params}")
904
+ result = await self.evol_server._rpc_revoke_token(params)
905
+ else:
906
+ raise ValueError(f"Unknown method: web.{method}")
907
+
908
+ print(f"[relay] RPC success: {method}, result keys: {list(result.keys()) if isinstance(result, dict) else type(result)}")
909
+
910
+ # 返回结果
911
+ if session.client_ws:
912
+ await session.client_ws.send_json({
913
+ "jsonrpc": "2.0",
914
+ "id": rpc_id,
915
+ "result": result
916
+ })
917
+ except Exception as e:
918
+ # 红色高亮显示 RPC 错误
919
+ print(f"\033[91m[relay] ✗ RPC 错误: {method}, 错误: {e}\033[0m")
920
+ # 返回错误(仅当连接存在时)
921
+ if session.client_ws:
922
+ await session.client_ws.send_json({
923
+ "jsonrpc": "2.0",
924
+ "id": rpc_id,
925
+ "error": {
926
+ "code": -32603,
927
+ "message": str(e)
928
+ }
929
+ })
930
+
931
+ async def broadcast_to_clients(self, message: dict):
932
+ """广播消息给所有已连接的前端客户端"""
933
+ for session in list(self.sessions.values()):
934
+ if session.client_ws and session.status == "active":
935
+ try:
936
+ await session.client_ws.send_json(message)
937
+ except Exception as e:
938
+ print(f"[relay] Failed to broadcast to {session.module_id}: {e}")
939
+
940
+ async def close_all_sessions(self):
941
+ """优雅关闭所有会话(用于 shutdown)"""
942
+ print(f"[relay] Closing {len(self.sessions)} active sessions...")
943
+
944
+ # 取消所有重连定时器
945
+ for timer in self.reconnect_timers.values():
946
+ timer.cancel()
947
+ self.reconnect_timers.clear()
948
+
949
+ # 关闭所有会话
950
+ for session in list(self.sessions.values()):
951
+ try:
952
+ # 关闭客户端连接
953
+ if session.client_ws:
954
+ await session.client_ws.close(code=1001, reason="Server shutting down")
955
+ # 关闭 Kernel 连接
956
+ if session.kernel_ws:
957
+ await session.kernel_ws.close()
958
+ except Exception as e:
959
+ print(f"[relay] Error closing session {session.session_token}: {e}")
960
+
961
+ self.sessions.clear()
962
+ print(f"[relay] All sessions closed")
963
+
964
+ # ── 审计日志 ──
965
+
966
+ async def _audit_log(self, event: str, data: dict):
967
+ """
968
+ 发布 auth.* 审计事件到 Kernel。
969
+
970
+ Args:
971
+ event: 事件名(如 auth.login, auth.logout, auth.failed)
972
+ data: 事件数据
973
+ """
974
+ if not self.evol_server or not self.evol_server._ws:
975
+ return
976
+ try:
977
+ await self.evol_server._publish_event(
978
+ self.evol_server._ws,
979
+ event,
980
+ {
981
+ **data,
982
+ "timestamp": time.time(),
983
+ "source": "relay",
984
+ }
985
+ )
986
+ except Exception as e:
987
+ print(f"[relay] Audit log failed: {e}")
988
+
989
+ # ── 速率限制 ──
990
+
991
+ def _check_rate_limit(self, ip: str, device_id: str) -> bool:
992
+ """
993
+ 检查 (IP, device_id) 是否超出速率限制。
994
+
995
+ Returns:
996
+ True 表示允许,False 表示被限制
997
+ """
998
+ key = (ip, device_id)
999
+ now = time.time()
1000
+ cutoff = now - self._rate_limit_window
1001
+
1002
+ # 获取或创建时间戳列表
1003
+ timestamps = self._rate_limits.get(key, [])
1004
+
1005
+ # 清理过期的时间戳
1006
+ timestamps = [t for t in timestamps if t > cutoff]
1007
+
1008
+ # 检查是否超限
1009
+ if len(timestamps) >= self._rate_limit_max:
1010
+ self._rate_limits[key] = timestamps
1011
+ return False
1012
+
1013
+ # 记录本次请求
1014
+ timestamps.append(now)
1015
+ self._rate_limits[key] = timestamps
1016
+
1017
+ # 定期清理长期不活跃的 key(避免内存泄漏)
1018
+ if len(self._rate_limits) > 1000:
1019
+ self._cleanup_rate_limits(now)
1020
+
1021
+ return True
1022
+
1023
+ def _cleanup_rate_limits(self, now: float):
1024
+ """清理不活跃的速率限制条目"""
1025
+ cutoff = now - self._rate_limit_window * 10 # 10 个窗口期未活跃则清理
1026
+ expired = [
1027
+ k for k, ts_list in self._rate_limits.items()
1028
+ if not ts_list or ts_list[-1] < cutoff
1029
+ ]
1030
+ for k in expired:
1031
+ del self._rate_limits[k]