@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
@@ -0,0 +1,1742 @@
1
+ """
2
+ Evol HTTP Server
3
+ Evol account management with full Kite module management UI.
4
+ """
5
+
6
+ import asyncio
7
+ import json
8
+ import logging
9
+ import os
10
+ import time
11
+ import uuid
12
+ import random
13
+ from datetime import datetime, timezone
14
+ from pathlib import Path
15
+
16
+ import websockets
17
+ from fastapi import FastAPI, WebSocket, Request
18
+ from fastapi.staticfiles import StaticFiles
19
+ from fastapi.responses import FileResponse, JSONResponse
20
+
21
+ from extensions.services.kite_console.evol_api import EvolAPI
22
+ from extensions.services.kite_console.auth_manager import AuthManager
23
+ from extensions.services.kite_console.stats_manager import StatsManager
24
+ from extensions.services.kite_console.routes.routes_rpc import router as rpc_router, set_evol_server
25
+ from extensions.services.kite_console.routes.routes_test import router as test_router
26
+ from extensions.services.kite_console.routes.routes_llm import router as llm_router
27
+ from extensions.services.kite_console.routes.routes_proxy import router as proxy_router, set_proxy_server
28
+ from extensions.services.kite_console.config_loader import load_business_configs
29
+ from extensions.services.kite_console.pairing import PairingManager
30
+ from extensions.services.kite_console.relay import KernelRelay
31
+ from extensions.services.kite_console.oauth_manager import OAuthManager
32
+
33
+
34
+ def _fmt_elapsed(t0: float) -> str:
35
+ """Format elapsed time since t0."""
36
+ d = time.monotonic() - t0
37
+ if d < 1:
38
+ return f"{d * 1000:.0f}ms"
39
+ if d < 10:
40
+ return f"{d:.1f}s"
41
+ return f"{d:.0f}s"
42
+
43
+
44
+ logger = logging.getLogger(__name__)
45
+
46
+ # System broadcast events
47
+ SYSTEM_BROADCAST_EVENTS = {
48
+ "module.ready", "module.registered", "module.started", "module.stopped",
49
+ "module.crashed", "module.exiting", "module.offline",
50
+ "module.shutdown.ack", "module.shutdown.ready",
51
+ "system.ready", "registry.updated",
52
+ "system.instance.started", "system.instance.stopped",
53
+ }
54
+
55
+
56
+ class EvolServer:
57
+ def __init__(self, module_name: str, token: str, kernel_port: int, host: str, port: int, boot_t0: float, max_connections: int = 1,
58
+ gateway_url: str = "", kite_token: str = ""):
59
+ self.module_name = module_name
60
+ self.token = token
61
+ self.kernel_port = kernel_port
62
+ self.host = host
63
+ self.port = port
64
+ self.boot_t0 = boot_t0
65
+ self.max_connections = max_connections
66
+ self.gateway_url = gateway_url
67
+ self.kite_token = kite_token
68
+ self._ws_task: asyncio.Task | None = None
69
+ self._test_task: asyncio.Task | None = None
70
+ self._ws: object | None = None
71
+ self._shutting_down = False
72
+ self._exit_code = 0
73
+ self._auth_failed = False
74
+ self._uvicorn_server = None
75
+ self._start_time = time.time()
76
+ self._pending_rpc = {}
77
+ self._extra_ws: dict = {} # slot → WebSocket
78
+ self._extra_ws_tasks: dict = {} # slot → recv loop Task
79
+ self._has_registered = False
80
+ # Kernel 重连状态(供 relay 报告给前端)
81
+ self._kernel_reconnect_info = {
82
+ "state": "connected", # connected / reconnecting / fatal / exiting
83
+ "attempt": 0,
84
+ "max_attempts": 10,
85
+ "next_retry_ms": 0,
86
+ }
87
+
88
+ # 用户信息缓存(10秒有效期)
89
+ self._user_info_cache = {} # {evol_token: {"data": ..., "timestamp": ...}}
90
+ self._cache_ttl = 30 # 缓存有效期(秒)
91
+
92
+ # Business data directory
93
+ data_dir = os.environ.get("KITE_DATA", os.path.expanduser("~/.kite/data"))
94
+ console_data_dir = os.path.join(data_dir, "kite_console")
95
+ os.makedirs(console_data_dir, exist_ok=True)
96
+
97
+ self.evol_api = EvolAPI()
98
+ self.auth_manager = AuthManager(console_data_dir)
99
+ self.stats_manager = StatsManager(console_data_dir, self.evol_api, self.auth_manager)
100
+
101
+ # OAuth manager(从 config.json5 加载配置)
102
+ self.oauth_manager = self._init_oauth_manager()
103
+
104
+ self.app = self._create_app()
105
+
106
+ def _init_oauth_manager(self) -> OAuthManager:
107
+ """从 config.json5 加载 OAuth 配置并初始化 OAuthManager"""
108
+ import json5
109
+ config_path = Path(__file__).parent / "config.json5"
110
+ oauth_cfg = {}
111
+ if config_path.exists():
112
+ try:
113
+ with open(config_path, "r", encoding="utf-8") as f:
114
+ cfg = json5.load(f)
115
+ oauth_cfg = cfg.get("oauth", {})
116
+ except Exception as e:
117
+ logger.warning(f"Failed to load OAuth config: {e}")
118
+
119
+ return OAuthManager(
120
+ state_mode=oauth_cfg.get("state_mode", "memory"),
121
+ state_ttl=oauth_cfg.get("state_ttl", 60),
122
+ jwt_secret=oauth_cfg.get("jwt_secret", ""),
123
+ )
124
+
125
+ def _create_app(self) -> FastAPI:
126
+ app = FastAPI(title="Kite Console", docs_url="/docs", redoc_url=None)
127
+ server = self
128
+
129
+ @app.on_event("startup")
130
+ async def _startup():
131
+ # Token already set in entry.py, no need to read from stdin
132
+ if not server.token:
133
+ print(f"\033[31mERROR: Missing token, cannot connect to Kernel\033[0m")
134
+ return
135
+
136
+ # Start Kernel WS connection (module.ready depends on this)
137
+ if server.kernel_port:
138
+ server._ws_task = asyncio.create_task(server._ws_loop())
139
+ server._test_task = asyncio.create_task(server._test_event_loop())
140
+
141
+ # Business initialization in background (does not block module.ready)
142
+ asyncio.create_task(_deferred_init())
143
+
144
+ async def _deferred_init():
145
+ """Business initialization that runs after WS connection starts."""
146
+ await server.stats_manager.start()
147
+
148
+ # Load business configurations
149
+ module_dir = Path(__file__).parent
150
+ try:
151
+ business_configs = load_business_configs(str(module_dir))
152
+ except Exception as e:
153
+ logger.error(f"Failed to load business configs: {e}")
154
+ business_configs = {}
155
+
156
+ # Get relay service config
157
+ relay_business = business_configs.get('relay_service')
158
+ if relay_business:
159
+ try:
160
+ relay_config = relay_business['config']
161
+
162
+ # Initialize pairing manager
163
+ auth_config = relay_config['auth']
164
+ pairing_file = module_dir / auth_config['pairing_code_file']
165
+ pairing_manager = PairingManager(
166
+ pairing_file=str(pairing_file),
167
+ code_length=auth_config['pairing_code_length'],
168
+ token_expiry=auth_config['token_expiry']
169
+ )
170
+ app.state.pairing_manager = pairing_manager
171
+ logger.info("Pairing manager initialized")
172
+
173
+ # Initialize relay service
174
+ relay_service = KernelRelay(
175
+ kernel_host="127.0.0.1",
176
+ kernel_port=server.kernel_port,
177
+ kernel_token=server.token,
178
+ base_module_id=relay_config['relay']['base_module_id'],
179
+ reconnect_timeout=relay_config['relay']['reconnect_timeout'],
180
+ permissions=relay_config['permissions'],
181
+ pairing_manager=pairing_manager,
182
+ evol_server=server,
183
+ auth_manager=server.auth_manager,
184
+ oauth_manager=server.oauth_manager,
185
+ )
186
+ app.state.relay_service = relay_service
187
+ server.relay = relay_service # server.py 中用于通知 relay kernel 状态变化
188
+ logger.info("Relay service initialized")
189
+ except KeyError as e:
190
+ logger.error(f"Missing required config field for relay_service: {e}")
191
+ logger.warning("Relay service disabled due to config error")
192
+ except Exception as e:
193
+ logger.error(f"Failed to initialize relay_service: {e}", exc_info=True)
194
+ logger.warning("Relay service disabled due to initialization error")
195
+ else:
196
+ logger.info("Relay service not configured (no 'relay_service' in businesses)")
197
+
198
+ @app.on_event("shutdown")
199
+ async def _shutdown():
200
+ await server.stats_manager.stop()
201
+ if server._ws_task:
202
+ server._ws_task.cancel()
203
+ if server._test_task:
204
+ server._test_task.cancel()
205
+ if server._ws:
206
+ await server._ws.close()
207
+ print(f"Shutdown complete")
208
+
209
+ # Health and status endpoints
210
+ @app.get("/health")
211
+ async def health():
212
+ return {
213
+ "status": "healthy",
214
+ "details": {
215
+ "kernel_connected": server._ws is not None,
216
+ "uptime_seconds": round(time.time() - server._start_time),
217
+ },
218
+ }
219
+
220
+ @app.get("/status")
221
+ async def status():
222
+ return {
223
+ "module": server.module_name,
224
+ "status": "running",
225
+ "kernel_connected": server._ws is not None,
226
+ "uptime_seconds": round(time.time() - server._start_time),
227
+ }
228
+
229
+ @app.get("/api/system_info")
230
+ async def system_info():
231
+ """获取系统版本和环境信息"""
232
+ # 从环境变量读取版本号和环境类型
233
+ version = os.environ.get("KITE_VERSION", "unknown")
234
+ env = os.environ.get("KITE_ENV", "development")
235
+
236
+ return {
237
+ "success": True,
238
+ "data": {
239
+ "version": version,
240
+ "environment": env
241
+ }
242
+ }
243
+
244
+ # Evol API routes
245
+ @app.post("/api/send_sms")
246
+ async def send_sms(request: Request):
247
+ data = await request.json()
248
+ phone = data.get("phone", "")
249
+ result = await server.evol_api.send_sms(phone)
250
+ return JSONResponse(result)
251
+
252
+ @app.post("/api/verify_sms")
253
+ async def verify_sms(request: Request):
254
+ data = await request.json()
255
+ phone = data.get("phone", "")
256
+ code = data.get("code", "")
257
+ device_info = data.get("deviceInfo", {})
258
+
259
+ result = await server.evol_api.verify_sms(phone, code)
260
+ if not result.get("success"):
261
+ return JSONResponse(result)
262
+
263
+ evol_data = result["data"]
264
+ evol_token = evol_data.get("token", "")
265
+ server.auth_manager.save_evol_token(phone, evol_token, evol_data)
266
+
267
+ # 生成 Kite Token
268
+ kite_token = server.auth_manager.generate_kite_token(device_info)
269
+
270
+ # 绑定 Kite Token 到手机号
271
+ server.auth_manager.bind_kite_token_to_phone(kite_token, phone)
272
+
273
+ return JSONResponse({
274
+ "success": True,
275
+ "kiteToken": kite_token,
276
+ "data": {
277
+ "userInfo": evol_data.get("userInfo", {}),
278
+ "apiKey": evol_data.get("apiKey", ""),
279
+ "credits": evol_data.get("credits", 0)
280
+ }
281
+ })
282
+
283
+ # Mount module management routes
284
+ app.include_router(rpc_router, prefix="/api")
285
+ app.include_router(test_router, prefix="/api")
286
+ app.include_router(llm_router, prefix="/api")
287
+ app.include_router(proxy_router, prefix="/api")
288
+
289
+ # ── OAuth routes ──
290
+
291
+ @app.get("/auth/oauth/{provider}/authorize")
292
+ async def oauth_authorize(provider: str, redirect_uri: str = ""):
293
+ """发起 OAuth 授权,返回重定向 URL"""
294
+ import json5
295
+ config_path = Path(__file__).parent / "config.json5"
296
+ oauth_cfg = {}
297
+ if config_path.exists():
298
+ try:
299
+ with open(config_path, "r", encoding="utf-8") as f:
300
+ cfg = json5.load(f)
301
+ oauth_cfg = cfg.get("oauth", {})
302
+ except Exception:
303
+ pass
304
+
305
+ providers = oauth_cfg.get("providers", {})
306
+ provider_cfg = providers.get(provider)
307
+ if not provider_cfg or not provider_cfg.get("client_id"):
308
+ return JSONResponse(
309
+ {"error": f"OAuth provider '{provider}' not configured"},
310
+ status_code=400
311
+ )
312
+
313
+ # 生成 state(CSRF 防护)
314
+ state = server.oauth_manager.generate_state(provider, redirect_uri)
315
+
316
+ # 构建授权 URL
317
+ client_id = provider_cfg["client_id"]
318
+ cb_uri = provider_cfg.get("redirect_uri", "")
319
+
320
+ if provider == "github":
321
+ auth_url = (
322
+ f"https://github.com/login/oauth/authorize"
323
+ f"?client_id={client_id}&state={state}"
324
+ f"&redirect_uri={cb_uri}&scope=read:user,user:email"
325
+ )
326
+ elif provider == "google":
327
+ auth_url = (
328
+ f"https://accounts.google.com/o/oauth2/v2/auth"
329
+ f"?client_id={client_id}&state={state}"
330
+ f"&redirect_uri={cb_uri}&scope=openid+email+profile"
331
+ f"&response_type=code&access_type=offline"
332
+ )
333
+ else:
334
+ return JSONResponse(
335
+ {"error": f"Unsupported OAuth provider: {provider}"},
336
+ status_code=400
337
+ )
338
+
339
+ return JSONResponse({"authorize_url": auth_url, "state": state})
340
+
341
+ @app.get("/auth/oauth/callback")
342
+ async def oauth_callback(request: Request):
343
+ """OAuth 回调,用 code 换 token,生成 auth_ticket"""
344
+ import httpx
345
+
346
+ code = request.query_params.get("code")
347
+ state = request.query_params.get("state")
348
+
349
+ if not code or not state:
350
+ return JSONResponse({"error": "Missing code or state"}, status_code=400)
351
+
352
+ # 验证 state
353
+ state_info = server.oauth_manager.verify_state(state)
354
+ if not state_info:
355
+ return JSONResponse({"error": "Invalid or expired state"}, status_code=400)
356
+
357
+ provider = state_info["provider"]
358
+
359
+ # 加载 provider 配置
360
+ import json5
361
+ config_path = Path(__file__).parent / "config.json5"
362
+ oauth_cfg = {}
363
+ if config_path.exists():
364
+ try:
365
+ with open(config_path, "r", encoding="utf-8") as f:
366
+ cfg = json5.load(f)
367
+ oauth_cfg = cfg.get("oauth", {})
368
+ except Exception:
369
+ pass
370
+
371
+ provider_cfg = oauth_cfg.get("providers", {}).get(provider, {})
372
+ client_id = provider_cfg.get("client_id", "")
373
+ client_secret = provider_cfg.get("client_secret", "")
374
+ cb_uri = provider_cfg.get("redirect_uri", "")
375
+
376
+ if not client_id or not client_secret:
377
+ return JSONResponse({"error": "OAuth provider not properly configured"}, status_code=500)
378
+
379
+ try:
380
+ async with httpx.AsyncClient(timeout=10) as client:
381
+ # 用 code 换 access_token
382
+ if provider == "github":
383
+ token_resp = await client.post(
384
+ "https://github.com/login/oauth/access_token",
385
+ json={"client_id": client_id, "client_secret": client_secret, "code": code, "redirect_uri": cb_uri},
386
+ headers={"Accept": "application/json"}
387
+ )
388
+ token_data = token_resp.json()
389
+ access_token = token_data.get("access_token")
390
+ if not access_token:
391
+ return JSONResponse({"error": "Failed to get access token", "detail": token_data}, status_code=400)
392
+
393
+ # 获取用户信息
394
+ user_resp = await client.get(
395
+ "https://api.github.com/user",
396
+ headers={"Authorization": f"Bearer {access_token}", "Accept": "application/json"}
397
+ )
398
+ user_data = user_resp.json()
399
+ user_info = {
400
+ "id": str(user_data.get("id", "")),
401
+ "name": user_data.get("name") or user_data.get("login", ""),
402
+ "email": user_data.get("email", ""),
403
+ "provider": "github",
404
+ }
405
+
406
+ elif provider == "google":
407
+ token_resp = await client.post(
408
+ "https://oauth2.googleapis.com/token",
409
+ data={"client_id": client_id, "client_secret": client_secret, "code": code, "redirect_uri": cb_uri, "grant_type": "authorization_code"}
410
+ )
411
+ token_data = token_resp.json()
412
+ access_token = token_data.get("access_token")
413
+ if not access_token:
414
+ return JSONResponse({"error": "Failed to get access token", "detail": token_data}, status_code=400)
415
+
416
+ user_resp = await client.get(
417
+ "https://www.googleapis.com/oauth2/v2/userinfo",
418
+ headers={"Authorization": f"Bearer {access_token}"}
419
+ )
420
+ user_data = user_resp.json()
421
+ user_info = {
422
+ "id": user_data.get("id", ""),
423
+ "name": user_data.get("name", ""),
424
+ "email": user_data.get("email", ""),
425
+ "provider": "google",
426
+ }
427
+ else:
428
+ return JSONResponse({"error": f"Unsupported provider: {provider}"}, status_code=400)
429
+
430
+ except httpx.HTTPError as e:
431
+ return JSONResponse({"error": f"OAuth exchange failed: {e}"}, status_code=502)
432
+
433
+ # 生成一次性 auth_ticket
434
+ ticket = server.oauth_manager.generate_auth_ticket(provider, user_info)
435
+
436
+ # 返回 ticket(前端用此 ticket 通过 WebSocket 认证)
437
+ redirect_uri = state_info.get("redirect_uri", "")
438
+ if redirect_uri:
439
+ # 重定向回前端,附带 ticket
440
+ from urllib.parse import urlencode
441
+ sep = "&" if "?" in redirect_uri else "?"
442
+ return JSONResponse(
443
+ {"redirect": f"{redirect_uri}{sep}{urlencode({'auth_ticket': ticket})}"},
444
+ status_code=200
445
+ )
446
+
447
+ return JSONResponse({"auth_ticket": ticket})
448
+
449
+ # Relay WebSocket endpoint
450
+ @app.websocket("/ws/relay")
451
+ async def relay_endpoint(ws: WebSocket):
452
+ relay_service = getattr(app.state, 'relay_service', None)
453
+ if relay_service:
454
+ await relay_service.handle_client(ws)
455
+ else:
456
+ await ws.close(code=1011, reason="Relay service not initialized")
457
+
458
+ # Set evol server reference for RPC forwarding
459
+ set_evol_server(server)
460
+ set_proxy_server(server)
461
+
462
+ # Serve frontend static files
463
+ static_dir = Path(__file__).parent / "static"
464
+ if static_dir.exists():
465
+ @app.get("/")
466
+ async def serve_index():
467
+ index_path = static_dir / "index.html"
468
+ if index_path.exists():
469
+ return FileResponse(index_path)
470
+ return {"message": "Kite Evol Module"}
471
+
472
+ @app.get("/pairing.html")
473
+ async def serve_pairing():
474
+ pairing_path = static_dir / "pairing.html"
475
+ if pairing_path.exists():
476
+ return FileResponse(pairing_path)
477
+ return JSONResponse({"error": "Not found"}, status_code=404)
478
+
479
+ @app.get("/test_registry.html")
480
+ async def serve_test_registry():
481
+ test_path = static_dir / "test_registry.html"
482
+ if test_path.exists():
483
+ return FileResponse(test_path)
484
+ return JSONResponse({"error": "Not found"}, status_code=404)
485
+
486
+ @app.get("/test_relay.html")
487
+ async def serve_test_relay():
488
+ test_path = static_dir / "test_relay.html"
489
+ if test_path.exists():
490
+ return FileResponse(test_path)
491
+ return JSONResponse({"error": "Not found"}, status_code=404)
492
+
493
+ app.mount("/static", StaticFiles(directory=str(static_dir)), name="static")
494
+
495
+ @app.get("/js/{file_path:path}")
496
+ async def serve_js(file_path: str):
497
+ file = static_dir / "js" / file_path
498
+ if file.exists() and file.is_file():
499
+ return FileResponse(file)
500
+ return JSONResponse({"error": "Not found"}, status_code=404)
501
+
502
+ @app.get("/css/{file_path:path}")
503
+ async def serve_css(file_path: str):
504
+ file = static_dir / "css" / file_path
505
+ if file.exists() and file.is_file():
506
+ return FileResponse(file)
507
+ return JSONResponse({"error": "Not found"}, status_code=404)
508
+
509
+ return app
510
+
511
+ # ── system.require_init 处理 ──
512
+
513
+ async def _do_init(self, ws):
514
+ """收到 system.require_init 后执行:订阅 + 注册 + module.ready"""
515
+ reason = "startup" if not self._has_registered else "recovery"
516
+ print(f"Received system.require_init (reason={reason})")
517
+
518
+ try:
519
+ # Subscribe to events
520
+ await self._rpc_call(ws, "event.subscribe", {
521
+ "events": [
522
+ "module.started",
523
+ "module.stopped",
524
+ "module.crashed",
525
+ "module.ready",
526
+ "module.exiting",
527
+ "module.shutdown",
528
+ "module.shutdown.ack",
529
+ "module.shutdown.ready",
530
+ ],
531
+ })
532
+
533
+ # Register to Kernel
534
+ elapsed = time.monotonic() - self.boot_t0 if self.boot_t0 else 0
535
+ elapsed_str = f" ({elapsed:.1f}s)" if elapsed else ""
536
+ await self._rpc_call(ws, "registry.register", {
537
+ "module_id": self.module_name,
538
+ "module_type": "service",
539
+ "base_url": f"http://127.0.0.1:{self.port}",
540
+ "health_path": "/health",
541
+ "display": {
542
+ "urls": {
543
+ "Kite 控制台": f"http://127.0.0.1:{self.port}"
544
+ }
545
+ },
546
+ "tools": {
547
+ "rpc": {
548
+ "module": {
549
+ "health": {"method": "health", "description": "健康检查"},
550
+ "status": {"method": "status", "description": "状态查询"}
551
+ },
552
+ "console": {
553
+ "list_tokens": {"method": "list_tokens", "description": "列出所有令牌"},
554
+ "list_kite_tokens": {"method": "list_kite_tokens", "description": "列出 Kite 令牌"},
555
+ "list_evol_tokens": {"method": "list_evol_tokens", "description": "列出 Evol 令牌"},
556
+ "revoke_token": {"method": "revoke_token", "description": "撤销 Kite 令牌"},
557
+ "revoke_evol_token": {"method": "revoke_evol_token", "description": "撤销 Evol 令牌"},
558
+ "llm_sessions_list": {"method": "llm_sessions_list", "description": "列出 LLM 会话"},
559
+ "llm_sessions_get": {"method": "llm_sessions_get", "description": "获取 LLM 会话详情"},
560
+ "llm_sessions_save": {"method": "llm_sessions_save", "description": "保存 LLM 会话"},
561
+ "llm_sessions_delete": {"method": "llm_sessions_delete", "description": "删除 LLM 会话"},
562
+ "llm_sessions_sync": {"method": "llm_sessions_sync", "description": "同步 LLM 会话"},
563
+ "get_system_context": {"method": "get_system_context", "description": "获取系统上下文(CLAUDE.md + module.md + 注册中心)"},
564
+ "chat": {"method": "chat", "description": "LLM 聊天(转发到大模型 API)"},
565
+ "file_read": {
566
+ "method": "file_read",
567
+ "description": "读取项目文件内容(路径限制在项目目录内)",
568
+ "parameters": {
569
+ "type": "object",
570
+ "properties": {
571
+ "path": {"type": "string", "description": "文件路径(相对项目根目录)"},
572
+ "lines": {"type": "number", "description": "最大读取行数(默认 200)"}
573
+ },
574
+ "required": ["path"]
575
+ }
576
+ },
577
+ "llm_config_get": {"method": "llm_config_get", "description": "获取 LLM 测试配置"},
578
+ "llm_config_save": {"method": "llm_config_save", "description": "保存 LLM 测试配置"}
579
+ }
580
+ }
581
+ },
582
+ "events_publish": {
583
+ "kite_console": {
584
+ "test": {"description": "Test event from kite_console module"},
585
+ "started": {"description": "Kite Console UI started with access URL"},
586
+ "chat.chunk": {"description": "LLM chat response chunk (or final result)"},
587
+ }
588
+ },
589
+ "events_subscribe": [
590
+ "module.started",
591
+ "module.stopped",
592
+ "module.crashed",
593
+ "module.ready",
594
+ "module.exiting",
595
+ "module.shutdown",
596
+ "module.shutdown.ack",
597
+ "module.shutdown.ready",
598
+ ],
599
+ })
600
+ print(f"Registered to Kernel{elapsed_str}")
601
+
602
+ # Send module.ready
603
+ if not self._shutting_down:
604
+ startup_time = time.monotonic() - self.boot_t0 if self.boot_t0 else 0
605
+ await self._publish_event(ws, "module.ready", {
606
+ "module_id": self.module_name,
607
+ "graceful_shutdown": True,
608
+ "startup_time": startup_time,
609
+ "reason": reason,
610
+ })
611
+ elapsed_str = _fmt_elapsed(self.boot_t0)
612
+ print(f"module.ready published ({elapsed_str})")
613
+
614
+ # Publish kite_console.started event
615
+ display_host = "localhost" if self.host == "0.0.0.0" else self.host
616
+ access_url = f"http://{display_host}:{self.port}"
617
+ await self._publish_event(ws, "kite_console.started", {
618
+ "module_id": self.module_name,
619
+ "url": access_url,
620
+ "host": self.host,
621
+ "port": self.port,
622
+ })
623
+
624
+ self._has_registered = True
625
+
626
+ # 通知 relay kernel 已上线(模块完成注册,可以接受客户端连接)
627
+ relay = getattr(self, 'relay', None)
628
+ if relay:
629
+ asyncio.create_task(relay.set_kernel_offline(False))
630
+
631
+ except Exception as e:
632
+ print(f"_do_init failed: {e}")
633
+
634
+ # ── Kernel WebSocket client ──
635
+
636
+ async def _ws_loop(self):
637
+ retry_delay = 0.5
638
+ max_delay = 10.0
639
+ attempt = 0
640
+ cooldown_attempts = 0
641
+ conn_refused_count = 0
642
+
643
+ while not self._shutting_down:
644
+ try:
645
+ await self._ws_connect()
646
+ retry_delay = 0.5
647
+ attempt = 0
648
+ cooldown_attempts = 0
649
+ conn_refused_count = 0
650
+ self._kernel_reconnect_info = {"state": "connected", "attempt": 0, "max_attempts": 10, "next_retry_ms": 0}
651
+ except asyncio.CancelledError:
652
+ print(f"WS loop cancelled")
653
+ return
654
+ except Exception as e:
655
+ if self._shutting_down:
656
+ return
657
+
658
+ code = self._get_close_code(e)
659
+
660
+ # never: 永不重连
661
+ if code in (4001, 4003, 4004, 1008, 4010):
662
+ print(f"\033[31m致命错误 (code {code}),退出\033[0m")
663
+ self._kernel_reconnect_info = {"state": "fatal", "attempt": 0, "max_attempts": 0, "next_retry_ms": 0}
664
+ self._exit_code = 1
665
+ self._auth_failed = True
666
+ self._shutting_down = True
667
+ if self._uvicorn_server:
668
+ self._uvicorn_server.should_exit = True
669
+ return
670
+
671
+ # cooldown: 速率限制
672
+ if code == 4020:
673
+ cooldown_attempts += 1
674
+ if cooldown_attempts >= 5:
675
+ print(f"\033[31m速率限制重试 5 次,退出\033[0m")
676
+ self._kernel_reconnect_info = {"state": "fatal", "attempt": cooldown_attempts, "max_attempts": 5, "next_retry_ms": 0}
677
+ self._exit_code = 1
678
+ self._shutting_down = True
679
+ if self._uvicorn_server:
680
+ self._uvicorn_server.should_exit = True
681
+ return
682
+ self._kernel_reconnect_info = {"state": "cooldown", "attempt": cooldown_attempts, "max_attempts": 5, "next_retry_ms": 10000}
683
+ self._update_relay_reconnect_info()
684
+ print(f"\033[33m速率限制,10.0s 后重试 ({cooldown_attempts}/5)\033[0m")
685
+ await asyncio.sleep(10.0)
686
+ continue
687
+
688
+ # 连接被拒绝/重置 — Kernel 可能正在关闭
689
+ # Windows 的 [WinError 1225] 是 OSError 不是 ConnectionRefusedError
690
+ _is_conn_refused = isinstance(e, (ConnectionRefusedError, ConnectionResetError))
691
+ if not _is_conn_refused and isinstance(e, OSError) and getattr(e, 'winerror', None) == 1225:
692
+ _is_conn_refused = True
693
+ if _is_conn_refused:
694
+ conn_refused_count += 1
695
+ if conn_refused_count >= 3:
696
+ print(f"Kernel 持续不可达 ({conn_refused_count} 次),正常退出")
697
+ self._kernel_reconnect_info = {"state": "exiting", "attempt": conn_refused_count, "max_attempts": 3, "next_retry_ms": 0}
698
+ self._shutting_down = True
699
+ if self._uvicorn_server:
700
+ self._uvicorn_server.should_exit = True
701
+ return
702
+ jitter = retry_delay * 0.2 * random.random()
703
+ sleep_time = retry_delay + jitter
704
+ self._kernel_reconnect_info = {"state": "reconnecting", "attempt": conn_refused_count, "max_attempts": 3, "next_retry_ms": int(sleep_time * 1000)}
705
+ self._update_relay_reconnect_info()
706
+ print(f"Kernel 连接失败: {type(e).__name__}, {sleep_time:.1f}s 后重试 ({conn_refused_count}/3)")
707
+ else:
708
+ conn_refused_count = 0
709
+
710
+ # standard: 指数退避 + jitter
711
+ attempt += 1
712
+ jitter = retry_delay * 0.2 * random.random()
713
+ sleep_time = retry_delay + jitter
714
+ print(f"\033[31mKernel connection error: {e}, retrying in {sleep_time:.1f}s (attempt {attempt})\033[0m")
715
+ if attempt >= 10:
716
+ print(f"连续 {attempt} 次连接失败,正常退出")
717
+ self._kernel_reconnect_info = {"state": "exiting", "attempt": attempt, "max_attempts": 10, "next_retry_ms": 0}
718
+ self._shutting_down = True
719
+ if self._uvicorn_server:
720
+ self._uvicorn_server.should_exit = True
721
+ return
722
+ self._kernel_reconnect_info = {"state": "reconnecting", "attempt": attempt, "max_attempts": 10, "next_retry_ms": int(sleep_time * 1000)}
723
+ self._update_relay_reconnect_info()
724
+
725
+ self._ws = None
726
+ if self._shutting_down:
727
+ return
728
+ await asyncio.sleep(sleep_time if 'sleep_time' in locals() else retry_delay)
729
+ retry_delay = min(retry_delay * 2, max_delay)
730
+
731
+ def _update_relay_reconnect_info(self):
732
+ """将当前 Kernel 重连状态同步到 relay"""
733
+ relay = getattr(self, 'relay', None)
734
+ if relay:
735
+ relay.kernel_reconnect_info = self._kernel_reconnect_info.copy()
736
+
737
+ def _get_close_code(self, e: Exception) -> int:
738
+ """从 websockets 异常中提取关闭码"""
739
+ if hasattr(e, 'rcvd') and e.rcvd is not None:
740
+ return getattr(e.rcvd, 'code', 0)
741
+ return 0
742
+
743
+ async def _ws_receiver(self, ws):
744
+ """WebSocket 接收循环(后台任务)"""
745
+ try:
746
+ async for raw in ws:
747
+ try:
748
+ msg = json.loads(raw)
749
+ except (json.JSONDecodeError, TypeError):
750
+ continue
751
+
752
+ try:
753
+ has_method = "method" in msg
754
+ has_id = "id" in msg
755
+ has_result_or_error = "result" in msg or "error" in msg
756
+
757
+ if has_method and not has_id:
758
+ # 检测 system.require_init 事件
759
+ params = msg.get("params", {})
760
+ event_type = params.get("event", "")
761
+ if event_type == "system.require_init":
762
+ asyncio.create_task(self._do_init(ws))
763
+ continue
764
+ # 事件通知也需要异步处理,避免阻塞接收循环
765
+ asyncio.create_task(self._handle_event_notification(msg))
766
+ elif has_method and has_id:
767
+ asyncio.create_task(self._handle_rpc_request(ws, msg))
768
+ elif has_id and has_result_or_error:
769
+ self._handle_rpc_response(msg)
770
+ except Exception as e:
771
+ print(f"消息处理异常(已忽略): {e}")
772
+ except Exception as e:
773
+ print(f"Receive loop exited with exception: {e}")
774
+ finally:
775
+ print(f"Receive loop ended")
776
+
777
+ async def _ws_connect(self):
778
+ url = f"ws://127.0.0.1:{self.kernel_port}/ws?id={self.module_name}"
779
+ print(f"WS connecting to Kernel")
780
+ try:
781
+ async with websockets.connect(url, open_timeout=5, ping_interval=None, close_timeout=10) as ws:
782
+ # Send auth message first
783
+ auth_req = {
784
+ "jsonrpc": "2.0",
785
+ "id": "auth",
786
+ "method": "auth",
787
+ "params": {"token": self.token}
788
+ }
789
+ await ws.send(json.dumps(auth_req))
790
+
791
+ # Wait for auth response
792
+ auth_resp_raw = await asyncio.wait_for(ws.recv(), timeout=5)
793
+ auth_resp = json.loads(auth_resp_raw)
794
+ if "error" in auth_resp:
795
+ raise Exception(f"Auth failed: {auth_resp['error']}")
796
+
797
+ self._ws = ws
798
+ elapsed = time.monotonic() - self.boot_t0 if self.boot_t0 else 0
799
+ elapsed_str = f" ({elapsed:.1f}s)" if elapsed else ""
800
+ print(f"Connected to Kernel{elapsed_str}")
801
+
802
+ # 启动接收循环(后台任务,等待 system.require_init 触发 _do_init)
803
+ receiver_task = asyncio.create_task(self._ws_receiver(ws))
804
+ print(f"Receiver task started")
805
+
806
+ try:
807
+ # 等待接收循环结束(连接断开)
808
+ await receiver_task
809
+ except Exception as e:
810
+ # 取消接收任务
811
+ receiver_task.cancel()
812
+ try:
813
+ await receiver_task
814
+ except asyncio.CancelledError:
815
+ pass
816
+ raise
817
+ except Exception as e:
818
+ print(f"\033[31mWebSocket connection error: {e}\033[0m")
819
+ raise
820
+ finally:
821
+ print(f"WebSocket connection closed")
822
+ self._ws = None
823
+ # 通知 relay kernel 离线(先同步重连状态)
824
+ relay = getattr(self, 'relay', None)
825
+ if relay:
826
+ relay.kernel_reconnect_info = self._kernel_reconnect_info.copy()
827
+ asyncio.create_task(relay.set_kernel_offline(True))
828
+ # 清理所有未完成的 RPC future
829
+ for fut in self._pending_rpc.values():
830
+ if not fut.done():
831
+ fut.set_exception(ConnectionError("WebSocket disconnected"))
832
+ self._pending_rpc.clear()
833
+
834
+ async def _rpc_call(self, ws, method: str, params: dict = None,
835
+ wait_response: bool = True, timeout: float = 3.0) -> dict:
836
+ """JSON-RPC 2.0 request。默认等待响应。"""
837
+ rpc_id = str(uuid.uuid4())
838
+ msg = {"jsonrpc": "2.0", "id": rpc_id, "method": method}
839
+ if params:
840
+ msg["params"] = params
841
+ if not wait_response:
842
+ await ws.send(json.dumps(msg))
843
+ return
844
+ future = asyncio.get_event_loop().create_future()
845
+ self._pending_rpc[rpc_id] = future
846
+ await ws.send(json.dumps(msg))
847
+ try:
848
+ return await asyncio.wait_for(future, timeout=timeout)
849
+ except asyncio.TimeoutError:
850
+ self._pending_rpc.pop(rpc_id, None)
851
+ return {"error": {"code": -32000, "message": f"RPC timeout: {method} ({timeout}s)"}}
852
+
853
+ def _handle_rpc_response(self, msg: dict):
854
+ """处理 RPC 响应(id + result/error)"""
855
+ rpc_id = msg.get("id")
856
+ future = self._pending_rpc.pop(rpc_id, None)
857
+ if future and not future.done():
858
+ future.set_result(msg)
859
+
860
+ async def _handle_ping_event(self, data: dict):
861
+ """Handle system.ping event and reply with system.pong."""
862
+ import time
863
+ t1 = data.get("ping_time")
864
+ t2 = time.time()
865
+
866
+ if self._ws:
867
+ await self._publish_event(self._ws, "system.pong", {
868
+ "module_id": self.module_name,
869
+ "ping_time": t1,
870
+ "pong_time": t2,
871
+ })
872
+
873
+ async def _handle_event_notification(self, msg: dict):
874
+ params = msg.get("params", {})
875
+ event_type = params.get("event", "")
876
+ data = params.get("data", {})
877
+
878
+ # Handle system.ping event
879
+ if event_type == "system.ping":
880
+ await self._handle_ping_event(data)
881
+ return
882
+
883
+ # 弹性连接 offer/release
884
+ if event_type == "system.connection.offer":
885
+ asyncio.create_task(self._handle_connection_offer(data))
886
+ return
887
+ if event_type == "system.connection.release":
888
+ asyncio.create_task(self._handle_connection_release(data))
889
+ return
890
+
891
+ print(f"Event received: {event_type}, data: {data}")
892
+
893
+ if event_type == "module.shutdown":
894
+ target = data.get("module_id", "")
895
+ reason = data.get("reason", "")
896
+ if target == self.module_name or not target or reason == "launcher_lost":
897
+ await self._handle_shutdown()
898
+ return
899
+
900
+ # Module status events are now handled by Kernel event system
901
+ # Clients subscribe via /ws/relay
902
+ if event_type in (
903
+ "module.started", "module.stopped", "module.crashed",
904
+ "module.ready", "module.exiting",
905
+ "module.shutdown.ack", "module.shutdown.ready",
906
+ ):
907
+ return
908
+
909
+ if event_type in SYSTEM_BROADCAST_EVENTS:
910
+ return
911
+
912
+ if os.environ.get("KITE_ENV") == "development":
913
+ print(f"Debug: Unhandled event: {event_type}")
914
+
915
+ async def _handle_rpc_request(self, ws, msg: dict):
916
+ rpc_id = msg.get("id", "")
917
+ method = msg.get("method", "")
918
+ params = msg.get("params", {})
919
+
920
+ if method.startswith("console."):
921
+ method = method[8:]
922
+
923
+ handlers = {
924
+ "health": lambda p: self._rpc_health(),
925
+ "status": lambda p: self._rpc_status(),
926
+ "list_tokens": lambda p: self._rpc_list_tokens(),
927
+ "list_kite_tokens": lambda p: self._rpc_list_kite_tokens(),
928
+ "list_evol_tokens": lambda p: self._rpc_list_evol_tokens(),
929
+ "revoke_token": lambda p: self._rpc_revoke_token(p),
930
+ "revoke_evol_token": lambda p: self._rpc_revoke_evol_token(p),
931
+ "subscribe_events": lambda p: self._rpc_subscribe_events(p),
932
+ "get_user_info": lambda p: self._rpc_get_user_info(p),
933
+ "get_credits_stats": lambda p: self._rpc_get_credits_stats(p),
934
+ "logout": lambda p: self._rpc_logout(p),
935
+ "llm_sessions_list": lambda p: self._rpc_llm_sessions_list(p),
936
+ "llm_sessions_get": lambda p: self._rpc_llm_sessions_get(p),
937
+ "llm_sessions_save": lambda p: self._rpc_llm_sessions_save(p),
938
+ "llm_sessions_delete": lambda p: self._rpc_llm_sessions_delete(p),
939
+ "llm_sessions_sync": lambda p: self._rpc_llm_sessions_sync(p),
940
+ "get_system_context": lambda p: self._rpc_get_system_context(p),
941
+ "chat": lambda p: self._rpc_chat(p),
942
+ "file_read": lambda p: self._rpc_file_read(p),
943
+ "llm_config_get": lambda p: self._rpc_llm_config_get(p),
944
+ "llm_config_save": lambda p: self._rpc_llm_config_save(p),
945
+ }
946
+ handler = handlers.get(method)
947
+ if handler:
948
+ try:
949
+ result = await handler(params)
950
+ await ws.send(json.dumps({"jsonrpc": "2.0", "id": rpc_id, "result": result}))
951
+ except Exception as e:
952
+ await ws.send(json.dumps({
953
+ "jsonrpc": "2.0", "id": rpc_id,
954
+ "error": {"code": -32603, "message": str(e)},
955
+ }))
956
+ else:
957
+ await ws.send(json.dumps({
958
+ "jsonrpc": "2.0", "id": rpc_id,
959
+ "error": {"code": -32601, "message": f"Method not found: {method}"},
960
+ }))
961
+
962
+ async def _rpc_health(self) -> dict:
963
+ return {
964
+ "status": "healthy",
965
+ "details": {
966
+ "uptime_seconds": round(time.time() - self._start_time),
967
+ },
968
+ }
969
+
970
+ async def _rpc_status(self) -> dict:
971
+ return {
972
+ "module": self.module_name,
973
+ "status": "running",
974
+ "uptime_seconds": round(time.time() - self._start_time),
975
+ }
976
+
977
+ async def _rpc_list_tokens(self) -> dict:
978
+ """列出所有 Kite Token(从 AuthManager 读取)"""
979
+ latest_tokens = self.auth_manager._get_latest_tokens()
980
+ now = time.time()
981
+
982
+ tokens = []
983
+ for token, info in latest_tokens.items():
984
+ # 只返回有效且未过期的 token
985
+ if info.get("isValid", True) and now <= info.get("expiresAt", 0):
986
+ tokens.append({
987
+ "token": token,
988
+ "deviceId": info.get("deviceId", "unknown"),
989
+ "deviceName": info.get("deviceName", "Unknown Device"),
990
+ "phone": info.get("phone"), # 添加绑定的手机号
991
+ "createdAt": info.get("createdAt_human", ""),
992
+ "lastUsedAt": info.get("lastUsedAt_human", ""),
993
+ "expiresAt": info.get("expiresAt_human", ""),
994
+ })
995
+
996
+ return {"tokens": tokens}
997
+
998
+ async def _rpc_list_kite_tokens(self) -> dict:
999
+ """列出所有 Kite Token(前端配对令牌)"""
1000
+ return await self._rpc_list_tokens()
1001
+
1002
+ async def _rpc_list_evol_tokens(self) -> dict:
1003
+ """列出所有 Evol Token(Evol 云端令牌)"""
1004
+ evol_records = self.auth_manager.list_all_evol_tokens()
1005
+
1006
+ if not evol_records:
1007
+ return {"tokens": []}
1008
+
1009
+ tokens = []
1010
+ for evol_record in evol_records:
1011
+ # 提取用户信息
1012
+ user_info = evol_record.get("userInfo", {})
1013
+ account_info = evol_record.get("accountInfo", {})
1014
+
1015
+ tokens.append({
1016
+ "token": evol_record.get("token", ""),
1017
+ "phone": evol_record.get("phone", ""),
1018
+ "nickName": user_info.get("nickName", ""),
1019
+ "userName": user_info.get("userName", ""),
1020
+ "credits": account_info.get("credits", 0),
1021
+ "creditsLimit": account_info.get("creditsLimit", 0),
1022
+ "vipType": account_info.get("vipType", 0),
1023
+ "vipTypeName": account_info.get("vipTypeName", "Unknown"),
1024
+ "vipExpireTime": account_info.get("vipExpireTime", ""),
1025
+ "vipRemainingDays": account_info.get("vipRemainingDays", 0),
1026
+ "obtainedAt": evol_record.get("obtainedAt_human", ""),
1027
+ "lastUsedAt": evol_record.get("lastUsedAt_human", ""),
1028
+ "expiresAt": evol_record.get("expiresAt_human", ""),
1029
+ })
1030
+
1031
+ return {"tokens": tokens}
1032
+
1033
+ async def _rpc_revoke_token(self, params: dict) -> dict:
1034
+ """吊销 Kite Token(使用 AuthManager)"""
1035
+ token = params.get("token")
1036
+ if not token:
1037
+ raise ValueError("Missing token parameter")
1038
+
1039
+ # 验证 token 是否存在
1040
+ latest_tokens = self.auth_manager._get_latest_tokens()
1041
+ if token not in latest_tokens:
1042
+ raise ValueError("Token not found")
1043
+
1044
+ # 吊销 token
1045
+ self.auth_manager.revoke_kite_token(token)
1046
+
1047
+ return {"success": True, "message": "Token revoked successfully"}
1048
+
1049
+ async def _rpc_revoke_evol_token(self, params: dict) -> dict:
1050
+ """吊销 Evol Token(使用 AuthManager)"""
1051
+ token = params.get("token")
1052
+ if not token:
1053
+ raise ValueError("Missing token parameter")
1054
+
1055
+ # 吊销 token
1056
+ self.auth_manager.revoke_evol_token_by_token(token)
1057
+
1058
+ return {"success": True, "message": "Evol Token revoked successfully"}
1059
+
1060
+ async def _rpc_get_user_info(self, params: dict) -> dict:
1061
+ """获取用户信息(RPC 版本)"""
1062
+ kite_token = params.get("kiteToken", "")
1063
+
1064
+ if not self.auth_manager.verify_kite_token(kite_token):
1065
+ return {
1066
+ "success": False,
1067
+ "msg": "Kite Token 无效或已过期",
1068
+ "code": "INVALID_TOKEN"
1069
+ }
1070
+
1071
+ # 根据 Kite Token 获取绑定的手机号
1072
+ phone = self.auth_manager.get_phone_by_kite_token(kite_token)
1073
+ if not phone:
1074
+ return {
1075
+ "success": False,
1076
+ "msg": "未登录 Evol,请先登录",
1077
+ "code": "NOT_LOGGED_IN"
1078
+ }
1079
+
1080
+ # 根据手机号获取 Evol Token
1081
+ evol_record = self.auth_manager.get_evol_token(phone)
1082
+ if not evol_record:
1083
+ return {
1084
+ "success": False,
1085
+ "msg": "Evol Token 已过期,请重新登录",
1086
+ "code": "EVOL_TOKEN_EXPIRED"
1087
+ }
1088
+
1089
+ # 更新 Evol Token 使用时间
1090
+ self.auth_manager.update_evol_token_usage(phone)
1091
+
1092
+ evol_token = evol_record["token"]
1093
+
1094
+ # 检查缓存
1095
+ now = time.time()
1096
+ if evol_token in self._user_info_cache:
1097
+ cached = self._user_info_cache[evol_token]
1098
+ if now - cached["timestamp"] < self._cache_ttl:
1099
+ logger.info(f"Using cached user info (age: {now - cached['timestamp']:.1f}s)")
1100
+ return cached["data"]
1101
+
1102
+ # 缓存未命中或已过期,从云端获取
1103
+ result = await self.evol_api.get_user_info(evol_token)
1104
+ if not result.get("success"):
1105
+ return result
1106
+
1107
+ # 手动触发账户信息采集
1108
+ await self.stats_manager.collect_manual()
1109
+
1110
+ # 统一返回格式
1111
+ user_data = result["data"]
1112
+ response_data = {
1113
+ "success": True,
1114
+ "data": user_data
1115
+ }
1116
+
1117
+ # 更新缓存
1118
+ self._user_info_cache[evol_token] = {
1119
+ "data": response_data,
1120
+ "timestamp": now
1121
+ }
1122
+
1123
+ return response_data
1124
+
1125
+ async def _rpc_get_credits_stats(self, params: dict) -> dict:
1126
+ """获取积分统计(RPC 版本)"""
1127
+ kite_token = params.get("kiteToken", "")
1128
+ period = params.get("period", "day")
1129
+ date = params.get("date")
1130
+
1131
+ if not self.auth_manager.verify_kite_token(kite_token):
1132
+ return {
1133
+ "success": False,
1134
+ "msg": "Kite Token 无效或已过期",
1135
+ "code": "INVALID_TOKEN"
1136
+ }
1137
+
1138
+ result = self.stats_manager.get_stats(period, date)
1139
+ return result
1140
+
1141
+ async def _rpc_logout(self, params: dict) -> dict:
1142
+ """退出登录(RPC 版本)"""
1143
+ kite_token = params.get("kiteToken", "")
1144
+
1145
+ if not self.auth_manager.verify_kite_token(kite_token):
1146
+ return {"success": False, "msg": "Kite Token 无效"}
1147
+
1148
+ # 获取绑定的手机号
1149
+ phone = self.auth_manager.get_phone_by_kite_token(kite_token)
1150
+
1151
+ # 吊销 Evol Token(如果已绑定手机号)
1152
+ if phone:
1153
+ self.auth_manager.revoke_evol_token(phone)
1154
+
1155
+ # 吊销 Kite Token(解除绑定)
1156
+ self.auth_manager.revoke_kite_token(kite_token)
1157
+
1158
+ return {"success": True, "msg": "已退出登录"}
1159
+
1160
+ async def _rpc_subscribe_events(self, params: dict) -> dict:
1161
+ """动态订阅事件(通过 Kernel)"""
1162
+ events = params.get("events", [])
1163
+ if not events:
1164
+ raise ValueError("Missing events parameter")
1165
+
1166
+ if not self._ws:
1167
+ raise RuntimeError("Not connected to Kernel")
1168
+
1169
+ # 调用 Kernel 的 event.subscribe
1170
+ await self._rpc_call(self._ws, "event.subscribe", {"events": events}, wait_response=False)
1171
+
1172
+ print(f"Subscribed to events: {events}")
1173
+ return {"success": True, "events": events}
1174
+
1175
+ # ---- LLM Sessions RPC ----
1176
+
1177
+ def _get_phone_from_params(self, params: dict) -> str:
1178
+ """从 params 中获取手机号,优先用 kiteToken 解析"""
1179
+ kite_token = params.get("kiteToken", "")
1180
+ if kite_token:
1181
+ phone = self.auth_manager.get_phone_by_kite_token(kite_token)
1182
+ if phone:
1183
+ return phone
1184
+ phone = params.get("phone", "")
1185
+ if not phone:
1186
+ raise ValueError("Missing phone or kiteToken")
1187
+ return phone
1188
+
1189
+ def _sessions_dir(self, phone: str) -> Path:
1190
+ """获取用户会话存储目录"""
1191
+ data_dir = os.environ.get("KITE_DATA", os.path.expanduser("~/.kite/data"))
1192
+ d = Path(data_dir) / "llm_sessions" / phone
1193
+ d.mkdir(parents=True, exist_ok=True)
1194
+ return d
1195
+
1196
+ def _read_sessions_index(self, phone: str) -> list:
1197
+ """读取会话索引"""
1198
+ f = self._sessions_dir(phone) / "sessions.json"
1199
+ if f.exists():
1200
+ try:
1201
+ return json.loads(f.read_text(encoding="utf-8"))
1202
+ except Exception:
1203
+ return []
1204
+ return []
1205
+
1206
+ def _write_sessions_index(self, phone: str, sessions: list):
1207
+ """写入会话索引"""
1208
+ f = self._sessions_dir(phone) / "sessions.json"
1209
+ f.write_text(json.dumps(sessions, ensure_ascii=False, indent=2), encoding="utf-8")
1210
+
1211
+ async def _rpc_llm_sessions_list(self, params: dict) -> dict:
1212
+ """列出用户的所有会话(仅元数据)"""
1213
+ phone = self._get_phone_from_params(params)
1214
+ sessions = self._read_sessions_index(phone)
1215
+ return {"success": True, "data": sessions}
1216
+
1217
+ async def _rpc_llm_sessions_get(self, params: dict) -> dict:
1218
+ """获取单个会话的完整消息"""
1219
+ phone = self._get_phone_from_params(params)
1220
+ session_id = params.get("session_id", "")
1221
+ if not session_id:
1222
+ raise ValueError("Missing session_id")
1223
+
1224
+ f = self._sessions_dir(phone) / f"{session_id}.json"
1225
+ if not f.exists():
1226
+ return {"success": False, "error": "Session not found"}
1227
+
1228
+ try:
1229
+ data = json.loads(f.read_text(encoding="utf-8"))
1230
+ return {"success": True, "data": data}
1231
+ except Exception as e:
1232
+ return {"success": False, "error": str(e)}
1233
+
1234
+ async def _rpc_llm_sessions_save(self, params: dict) -> dict:
1235
+ """保存会话(创建或更新)"""
1236
+ phone = self._get_phone_from_params(params)
1237
+ session = params.get("session")
1238
+ if not session or not session.get("id"):
1239
+ raise ValueError("Missing session or session.id")
1240
+
1241
+ now = datetime.now(timezone.utc).isoformat()
1242
+ session["updated_at"] = now
1243
+ if not session.get("created_at"):
1244
+ session["created_at"] = now
1245
+
1246
+ # 写入会话文件
1247
+ d = self._sessions_dir(phone)
1248
+ session_file = d / f"{session['id']}.json"
1249
+ session_file.write_text(json.dumps(session, ensure_ascii=False, indent=2), encoding="utf-8")
1250
+
1251
+ # 更新索引
1252
+ sessions = self._read_sessions_index(phone)
1253
+ meta = {
1254
+ "id": session["id"],
1255
+ "name": session.get("name", ""),
1256
+ "created_at": session.get("created_at", now),
1257
+ "updated_at": session["updated_at"],
1258
+ "message_count": len(session.get("messages", []))
1259
+ }
1260
+ idx = next((i for i, s in enumerate(sessions) if s["id"] == session["id"]), None)
1261
+ if idx is not None:
1262
+ sessions[idx] = meta
1263
+ else:
1264
+ sessions.insert(0, meta)
1265
+ self._write_sessions_index(phone, sessions)
1266
+
1267
+ return {"success": True, "data": meta}
1268
+
1269
+ async def _rpc_llm_sessions_delete(self, params: dict) -> dict:
1270
+ """删除会话"""
1271
+ phone = self._get_phone_from_params(params)
1272
+ session_id = params.get("session_id", "")
1273
+ if not session_id:
1274
+ raise ValueError("Missing session_id")
1275
+
1276
+ d = self._sessions_dir(phone)
1277
+ session_file = d / f"{session_id}.json"
1278
+ if session_file.exists():
1279
+ session_file.unlink()
1280
+
1281
+ sessions = self._read_sessions_index(phone)
1282
+ sessions = [s for s in sessions if s["id"] != session_id]
1283
+ self._write_sessions_index(phone, sessions)
1284
+
1285
+ return {"success": True}
1286
+
1287
+ async def _rpc_llm_sessions_sync(self, params: dict) -> dict:
1288
+ """同步会话 — 比较前后端时间戳"""
1289
+ phone = self._get_phone_from_params(params)
1290
+ local_sessions = params.get("sessions", [])
1291
+
1292
+ server_sessions = self._read_sessions_index(phone)
1293
+ server_map = {s["id"]: s for s in server_sessions}
1294
+ local_map = {s["id"]: s for s in local_sessions}
1295
+
1296
+ to_upload = []
1297
+ to_download = []
1298
+
1299
+ for loc in local_sessions:
1300
+ srv = server_map.get(loc["id"])
1301
+ if not srv or loc.get("updated_at", "") > srv.get("updated_at", ""):
1302
+ to_upload.append(loc["id"])
1303
+
1304
+ for srv in server_sessions:
1305
+ loc = local_map.get(srv["id"])
1306
+ if not loc or srv.get("updated_at", "") > loc.get("updated_at", ""):
1307
+ to_download.append(srv["id"])
1308
+
1309
+ return {
1310
+ "success": True,
1311
+ "data": {
1312
+ "to_upload": to_upload,
1313
+ "to_download": to_download,
1314
+ "server_sessions": server_sessions
1315
+ }
1316
+ }
1317
+
1318
+ async def _rpc_get_system_context(self, params: dict) -> dict:
1319
+ """获取系统上下文(CLAUDE.md + module.md + 注册中心)"""
1320
+ context = {
1321
+ "claude_md": {"content": "", "bytes": 0},
1322
+ "modules": [],
1323
+ "registry": {"content": "", "bytes": 0, "tools_count": 0},
1324
+ "total_bytes": 0
1325
+ }
1326
+
1327
+ # 1. 读取 CLAUDE.md
1328
+ root_dir = Path(os.environ.get("KITE_ROOT_DIR", "."))
1329
+ claude_md_path = root_dir / "CLAUDE.md"
1330
+ if claude_md_path.exists():
1331
+ try:
1332
+ content = claude_md_path.read_text(encoding="utf-8")
1333
+ context["claude_md"]["content"] = content
1334
+ context["claude_md"]["bytes"] = len(content.encode("utf-8"))
1335
+ except Exception as e:
1336
+ logger.warning(f"Failed to read CLAUDE.md: {e}")
1337
+
1338
+ # 2. 获取注册中心信息(registry.lookup 无参数 = 列出所有在线模块)
1339
+ try:
1340
+ lookup_resp = await self._rpc_call(self._ws, "registry.lookup", {})
1341
+ lookup_result = lookup_resp.get("result", {})
1342
+ module_ids = [r["module"] for r in lookup_result.get("results", [])]
1343
+
1344
+ modules_data = []
1345
+ tools_data = []
1346
+ for mid in module_ids:
1347
+ try:
1348
+ get_resp = await self._rpc_call(self._ws, "registry.get", {"path": mid})
1349
+ get_result = get_resp.get("result", {})
1350
+ mod_data = get_result.get("value", {})
1351
+ modules_data.append({
1352
+ "module_id": mid,
1353
+ "module_type": mod_data.get("module_type", "unknown"),
1354
+ "status": mod_data.get("status", "unknown"),
1355
+ })
1356
+ mod_tools = mod_data.get("tools", {})
1357
+ if mod_tools:
1358
+ tools_data.append({"module_id": mid, "tools": mod_tools})
1359
+ except Exception:
1360
+ modules_data.append({"module_id": mid, "status": "unknown"})
1361
+
1362
+ registry_content = json.dumps({"modules": modules_data, "tools": tools_data}, ensure_ascii=False, indent=2)
1363
+ context["registry"]["content"] = registry_content
1364
+ context["registry"]["bytes"] = len(registry_content.encode("utf-8"))
1365
+ context["registry"]["tools_count"] = len(tools_data)
1366
+
1367
+ # 3. 读取每个模块的 module.md(搜索多个可能的路径)
1368
+ for mid in module_ids:
1369
+ candidates = [
1370
+ root_dir / mid / "module.md",
1371
+ root_dir / "extensions" / "services" / mid / "module.md",
1372
+ root_dir / "extensions" / "agents" / mid / "module.md",
1373
+ root_dir / "extensions" / "channels" / mid / "module.md",
1374
+ root_dir / "extensions" / mid / "module.md",
1375
+ ]
1376
+ for md_path in candidates:
1377
+ if md_path.exists():
1378
+ try:
1379
+ content = md_path.read_text(encoding="utf-8")
1380
+ context["modules"].append({
1381
+ "module_id": mid,
1382
+ "content": content,
1383
+ "bytes": len(content.encode("utf-8"))
1384
+ })
1385
+ except Exception as e:
1386
+ logger.warning(f"Failed to read {mid}/module.md: {e}")
1387
+ break
1388
+ except Exception as e:
1389
+ logger.warning(f"Failed to get registry info: {e}")
1390
+
1391
+ # 计算总字节数
1392
+ context["total_bytes"] = (
1393
+ context["claude_md"]["bytes"] +
1394
+ context["registry"]["bytes"] +
1395
+ sum(m["bytes"] for m in context["modules"])
1396
+ )
1397
+
1398
+ return {"success": True, "data": context}
1399
+
1400
+ async def _rpc_llm_config_get(self, params: dict) -> dict:
1401
+ """获取 LLM 测试配置"""
1402
+ phone = self._get_phone_from_params(params)
1403
+ config_path = self._sessions_dir(phone) / "config.json"
1404
+
1405
+ if config_path.exists():
1406
+ try:
1407
+ data = json.loads(config_path.read_text(encoding="utf-8"))
1408
+ return {"success": True, "data": data}
1409
+ except Exception as e:
1410
+ logger.warning(f"Failed to read LLM config: {e}")
1411
+
1412
+ return {"success": True, "data": {}}
1413
+
1414
+ async def _rpc_llm_config_save(self, params: dict) -> dict:
1415
+ """保存 LLM 测试配置"""
1416
+ phone = self._get_phone_from_params(params)
1417
+ config = params.get("config", {})
1418
+
1419
+ sessions_dir = self._sessions_dir(phone)
1420
+ sessions_dir.mkdir(parents=True, exist_ok=True)
1421
+
1422
+ config_path = sessions_dir / "config.json"
1423
+ config_path.write_text(json.dumps(config, ensure_ascii=False, indent=2), encoding="utf-8")
1424
+
1425
+ return {"success": True}
1426
+
1427
+ async def _rpc_chat(self, params: dict) -> dict:
1428
+ """LLM 聊天 — 异步事件机制,RPC 立即返回 request_id"""
1429
+ request_id = str(uuid.uuid4())[:8]
1430
+
1431
+ messages = params.get("messages", [])
1432
+ model = params.get("model", "gpt-3.5-turbo")
1433
+ base_url = params.get("base_url", "https://api.openai.com/v1")
1434
+ api_key = params.get("api_key", "")
1435
+ temperature = params.get("temperature", 0.7)
1436
+ max_tokens = params.get("max_tokens", 1024)
1437
+ tools = params.get("tools")
1438
+
1439
+ logger.info(f"[LLM Chat] 收到请求 {request_id}: model={model}, messages={len(messages)}, tools={len(tools) if tools else 0}")
1440
+
1441
+ if not api_key:
1442
+ return {"success": False, "error": "API key is required"}
1443
+
1444
+ # 启动后台任务处理 LLM 调用
1445
+ asyncio.create_task(self._chat_worker(
1446
+ request_id, messages, model, base_url, api_key,
1447
+ temperature, max_tokens, tools
1448
+ ))
1449
+
1450
+ return {"success": True, "data": {"request_id": request_id}}
1451
+
1452
+ async def _chat_worker(self, request_id: str, messages: list, model: str,
1453
+ base_url: str, api_key: str, temperature: float,
1454
+ max_tokens: int, tools: list | None):
1455
+ """后台执行 LLM 调用,通过事件推送结果"""
1456
+ import httpx
1457
+
1458
+ # 发送 status=processing 事件
1459
+ await self._publish_chat_event(request_id, "processing", {"message": "正在调用模型..."})
1460
+
1461
+ # 启动心跳:每 5 秒发一次 processing 事件
1462
+ heartbeat_running = True
1463
+
1464
+ async def heartbeat():
1465
+ elapsed = 0
1466
+ while heartbeat_running:
1467
+ await asyncio.sleep(5)
1468
+ elapsed += 5
1469
+ if heartbeat_running:
1470
+ await self._publish_chat_event(request_id, "processing", {
1471
+ "message": f"等待模型响应... ({elapsed}s)"
1472
+ })
1473
+
1474
+ heartbeat_task = asyncio.create_task(heartbeat())
1475
+
1476
+ payload = {
1477
+ "model": model,
1478
+ "messages": messages,
1479
+ "temperature": temperature,
1480
+ "max_tokens": max_tokens
1481
+ }
1482
+
1483
+ if tools:
1484
+ payload["tools"] = tools
1485
+
1486
+ # 保存上下文到文件
1487
+ debug_dir = Path("data/llm_debug")
1488
+ debug_dir.mkdir(parents=True, exist_ok=True)
1489
+ debug_file = debug_dir / f"{request_id}.json"
1490
+ try:
1491
+ debug_file.write_text(json.dumps({
1492
+ "request_id": request_id,
1493
+ "timestamp": time.time(),
1494
+ "model": model,
1495
+ "base_url": base_url,
1496
+ "payload": payload
1497
+ }, ensure_ascii=False, indent=2), encoding="utf-8")
1498
+ logger.info(f"[LLM Chat] {request_id} 上下文已保存: {debug_file}")
1499
+ except Exception as e:
1500
+ logger.warning(f"[LLM Chat] {request_id} 保存上下文失败: {e}")
1501
+
1502
+ try:
1503
+ url_base = base_url.rstrip('/')
1504
+ if not url_base.endswith('/v1'):
1505
+ url_base += '/v1'
1506
+ api_url = f"{url_base}/chat/completions"
1507
+ logger.info(f"[LLM Chat] {request_id} 调用 API: {api_url}")
1508
+
1509
+ async with httpx.AsyncClient(timeout=120.0) as client:
1510
+ response = await client.post(
1511
+ api_url,
1512
+ json=payload,
1513
+ headers={"Authorization": f"Bearer {api_key}"}
1514
+ )
1515
+ response.raise_for_status()
1516
+ result = response.json()
1517
+
1518
+ has_tool_calls = bool(result["choices"][0]["message"].get("tool_calls"))
1519
+ logger.info(f"[LLM Chat] {request_id} 调用成功: has_tool_calls={has_tool_calls}, usage={result.get('usage')}")
1520
+
1521
+ # 发送 status=done 事件
1522
+ heartbeat_running = False
1523
+ heartbeat_task.cancel()
1524
+ await self._publish_chat_event(request_id, "done", {
1525
+ "content": result["choices"][0]["message"].get("content", ""),
1526
+ "tool_calls": result["choices"][0]["message"].get("tool_calls"),
1527
+ "usage": result.get("usage")
1528
+ })
1529
+
1530
+ except Exception as e:
1531
+ heartbeat_running = False
1532
+ heartbeat_task.cancel()
1533
+ logger.error(f"[LLM Chat] {request_id} 调用失败: {e}")
1534
+ await self._publish_chat_event(request_id, "error", {"error": str(e)})
1535
+
1536
+ async def _publish_chat_event(self, request_id: str, status: str, data: dict):
1537
+ """发布 chat 事件"""
1538
+ if not self._ws:
1539
+ return
1540
+ await self._publish_event(self._ws, "kite_console.chat.chunk", {
1541
+ "request_id": request_id,
1542
+ "status": status,
1543
+ **data
1544
+ })
1545
+
1546
+ async def _rpc_file_read(self, params: dict) -> dict:
1547
+ """读取项目文件(路径限制在项目目录内)"""
1548
+ path = params.get("path", "")
1549
+ max_lines = params.get("lines", 200)
1550
+ if not path:
1551
+ return {"success": False, "error": "缺少 path 参数"}
1552
+
1553
+ root_dir = Path(os.environ.get("KITE_ROOT_DIR", ".")).resolve()
1554
+ target = (root_dir / path).resolve()
1555
+ if not str(target).startswith(str(root_dir)):
1556
+ return {"success": False, "error": "路径超出项目目录"}
1557
+ if not target.exists():
1558
+ return {"success": False, "error": f"文件不存在: {path}"}
1559
+
1560
+ try:
1561
+ all_lines = target.read_text(encoding="utf-8").splitlines()
1562
+ if max_lines and len(all_lines) > max_lines:
1563
+ content = "\n".join(all_lines[:max_lines]) + f"\n\n... (截断,共 {len(all_lines)} 行)"
1564
+ else:
1565
+ content = "\n".join(all_lines)
1566
+ return {"success": True, "data": {"content": content, "total_lines": len(all_lines)}}
1567
+ except Exception as e:
1568
+ return {"success": False, "error": f"读取失败: {e}"}
1569
+
1570
+ async def _handle_connection_offer(self, data):
1571
+ """处理 Kernel 下发的 slot token,建立附加连接。"""
1572
+ slots = data.get("slots", {})
1573
+ for slot_str, info in slots.items():
1574
+ slot = int(slot_str)
1575
+ token = info.get("token", "")
1576
+ if not token or slot in self._extra_ws:
1577
+ continue
1578
+ asyncio.create_task(self._connect_slot(slot, token))
1579
+
1580
+ async def _connect_slot(self, slot, token):
1581
+ """建立单个 slot 附加连接。"""
1582
+ ws_url = f"ws://127.0.0.1:{self.kernel_port}/ws"
1583
+ try:
1584
+ ws = await websockets.connect(ws_url, open_timeout=5, ping_interval=None, close_timeout=5)
1585
+ auth_req = {"jsonrpc": "2.0", "id": f"auth-slot-{slot}", "method": "auth", "params": {"token": token}}
1586
+ await ws.send(json.dumps(auth_req))
1587
+ resp = json.loads(await asyncio.wait_for(ws.recv(), timeout=5))
1588
+ if "error" in resp:
1589
+ await ws.close()
1590
+ return
1591
+ self._extra_ws[slot] = ws
1592
+ self._extra_ws_tasks[slot] = asyncio.create_task(self._slot_recv_loop(slot, ws))
1593
+ print(f"[kite_console] Slot {slot} connected")
1594
+ except Exception as e:
1595
+ print(f"[kite_console] Slot {slot} connect failed: {e}")
1596
+
1597
+ async def _slot_recv_loop(self, slot, ws):
1598
+ """附加连接的接收循环:与主连接平等处理所有消息。"""
1599
+ try:
1600
+ async for raw in ws:
1601
+ try:
1602
+ msg = json.loads(raw)
1603
+ except (json.JSONDecodeError, TypeError):
1604
+ continue
1605
+
1606
+ try:
1607
+ has_method = "method" in msg
1608
+ has_id = "id" in msg
1609
+ has_result_or_error = "result" in msg or "error" in msg
1610
+
1611
+ if has_method and not has_id:
1612
+ asyncio.create_task(self._handle_event_notification(msg))
1613
+ elif has_method and has_id:
1614
+ asyncio.create_task(self._handle_rpc_request(ws, msg))
1615
+ elif has_id and has_result_or_error:
1616
+ self._handle_rpc_response(msg)
1617
+ except Exception as e:
1618
+ print(f"[kite_console] Slot {slot} 消息处理异常: {e}")
1619
+ except Exception as e:
1620
+ print(f"[kite_console] Slot {slot} 接收循环异常: {e}")
1621
+ finally:
1622
+ self._extra_ws.pop(slot, None)
1623
+ self._extra_ws_tasks.pop(slot, None)
1624
+
1625
+ async def _handle_connection_release(self, data):
1626
+ """Kernel 请求释放 slot,优雅关闭。"""
1627
+ for slot in data.get("slots", []):
1628
+ ws = self._extra_ws.pop(slot, None)
1629
+ task = self._extra_ws_tasks.pop(slot, None)
1630
+ if ws:
1631
+ try:
1632
+ await ws.close(code=1000, reason="release")
1633
+ except Exception:
1634
+ pass
1635
+ if task:
1636
+ task.cancel()
1637
+
1638
+ async def _handle_shutdown(self):
1639
+ print(f"Received module.shutdown")
1640
+ self._shutting_down = True
1641
+
1642
+ # 使用短超时发送 shutdown 事件,避免阻塞
1643
+ try:
1644
+ if self._ws:
1645
+ await self._publish_event(self._ws, "module.shutdown.ack", {
1646
+ "module_id": self.module_name,
1647
+ })
1648
+ except Exception as e:
1649
+ print(f"\033[31mFailed to send shutdown.ack: {e}\033[0m")
1650
+
1651
+ try:
1652
+ if self._ws:
1653
+ await self._publish_event(self._ws, "module.exiting", {
1654
+ "module_id": self.module_name,
1655
+ "type": "passive",
1656
+ "reason": "shutdown_requested",
1657
+ "restart": "auto",
1658
+ "action": "none",
1659
+ "timeout": 3.0,
1660
+ "restart_delay": 0.0,
1661
+ })
1662
+ except Exception as e:
1663
+ print(f"\033[31mFailed to send module.exiting: {e}\033[0m")
1664
+
1665
+ # Send shutdown.ready(必须在关闭连接之前发送)
1666
+ try:
1667
+ if self._ws:
1668
+ await self._publish_event(self._ws, "module.shutdown.ready", {
1669
+ "module_id": self.module_name,
1670
+ })
1671
+ except Exception as e:
1672
+ print(f"\033[31mFailed to send shutdown.ready: {e}\033[0m")
1673
+
1674
+ # 等待 Kernel 处理 shutdown.ready
1675
+ await asyncio.sleep(0.01)
1676
+
1677
+ if self._test_task:
1678
+ self._test_task.cancel()
1679
+
1680
+ # Close WebSocket connections
1681
+ if hasattr(self.app.state, 'relay_service'):
1682
+ try:
1683
+ await asyncio.wait_for(
1684
+ self.app.state.relay_service.close_all_sessions(),
1685
+ timeout=1.0
1686
+ )
1687
+ except asyncio.TimeoutError:
1688
+ print(f"Relay service close timeout")
1689
+ except Exception as e:
1690
+ print(f"\033[31mFailed to close relay sessions: {e}\033[0m")
1691
+
1692
+ # 关闭所有附加连接
1693
+ for _s, _w in list(self._extra_ws.items()):
1694
+ try:
1695
+ await _w.close(code=1000, reason="shutdown")
1696
+ except Exception:
1697
+ pass
1698
+ for _t in self._extra_ws_tasks.values():
1699
+ _t.cancel()
1700
+ self._extra_ws.clear()
1701
+ self._extra_ws_tasks.clear()
1702
+
1703
+ # 关闭 Kernel WebSocket 连接
1704
+ # 先清理未完成的 RPC future
1705
+ for fut in self._pending_rpc.values():
1706
+ if not fut.done():
1707
+ fut.cancel()
1708
+ self._pending_rpc.clear()
1709
+
1710
+ if self._ws:
1711
+ try:
1712
+ await self._ws.close(code=1000, reason="Graceful shutdown")
1713
+ print(f"Kernel WebSocket closed")
1714
+ except Exception as e:
1715
+ print(f"\033[31mFailed to close Kernel WebSocket: {e}\033[0m")
1716
+
1717
+ # 触发 uvicorn 优雅关闭(让 uvicorn 自然退出,不要 sys.exit)
1718
+ if self._uvicorn_server:
1719
+ print(f"Triggering uvicorn graceful shutdown")
1720
+ self._uvicorn_server.should_exit = True
1721
+ else:
1722
+ print(f"\033[31mWarning: uvicorn_server not set\033[0m")
1723
+
1724
+ async def _publish_event(self, ws, event: str, data: dict = None):
1725
+ """Publish event via event.publish RPC (fire-and-forget)."""
1726
+ await self._rpc_call(ws, "event.publish", {
1727
+ "event_id": str(uuid.uuid4()),
1728
+ "event": event,
1729
+ "data": data or {},
1730
+ }, wait_response=False)
1731
+
1732
+ async def _test_event_loop(self):
1733
+ try:
1734
+ while True:
1735
+ await asyncio.sleep(10)
1736
+ if self._ws:
1737
+ await self._publish_event(self._ws, "kite_console.test", {
1738
+ "message": "test event from kite_console",
1739
+ "timestamp": datetime.now(timezone.utc).isoformat(),
1740
+ })
1741
+ except Exception:
1742
+ pass