@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,1509 @@
1
+ """
2
+ Redis entry point.
3
+ Reads boot_info from stdin, registers to Registry, starts Redis service.
4
+ """
5
+
6
+ import builtins
7
+ import json
8
+ import os
9
+ import sys
10
+ import threading
11
+ import re
12
+ import time
13
+ from datetime import datetime, timezone
14
+
15
+ import asyncio
16
+ import traceback
17
+ import uuid
18
+ import random
19
+
20
+ import websockets
21
+
22
+
23
+ # System broadcast events (received by all modules, may not need handling)
24
+ SYSTEM_BROADCAST_EVENTS = {
25
+ "module.ready", "module.registered", "module.started", "module.stopped",
26
+ "module.crashed", "module.exiting", "module.offline",
27
+ "module.shutdown.ack", "module.shutdown.ready",
28
+ "system.ready", "registry.updated",
29
+ "system.instance.started", "system.instance.stopped",
30
+ }
31
+
32
+
33
+ # ── Safe stdout/stderr: ignore BrokenPipeError after Launcher closes stdio ──
34
+
35
+
36
+ # ── Module configuration ──
37
+
38
+ def _load_module_config() -> dict:
39
+ """Load module configuration from module.md frontmatter.
40
+
41
+ Returns:
42
+ Dict with keys: name, preferred_port, advertise_ip
43
+
44
+ Raises:
45
+ SystemExit: If module.md is invalid or name is non-compliant
46
+ """
47
+ _this_dir = os.path.dirname(os.path.abspath(__file__))
48
+ module_md = os.path.join(_this_dir, "module.md")
49
+
50
+ # Calculate relative path for error messages
51
+ project_root = os.environ.get("KITE_PROJECT", "")
52
+ if project_root and _this_dir.startswith(project_root):
53
+ rel_path = os.path.relpath(_this_dir, project_root)
54
+ else:
55
+ rel_path = _this_dir
56
+
57
+ # Default values (will be overridden if valid config exists)
58
+ result = {
59
+ "name": "",
60
+ "preferred_port": 0,
61
+ "advertise_ip": "0.0.0.0"
62
+ }
63
+
64
+ # Check if module.md exists
65
+ if not os.path.exists(module_md):
66
+ print(f"[{rel_path}] ERROR: Invalid module configuration in module.md")
67
+ print(f" Path: {rel_path}/module.md")
68
+ print(f" Reason: File not found")
69
+ sys.exit(1)
70
+
71
+ try:
72
+ with open(module_md, encoding="utf-8") as f:
73
+ text = f.read()
74
+
75
+ # Extract YAML frontmatter (between --- markers)
76
+ import re
77
+ m = re.match(r'^---\s*\n(.*?)\n---', text, re.DOTALL)
78
+ if not m:
79
+ print(f"[{rel_path}] ERROR: Invalid module configuration in module.md")
80
+ print(f" Path: {rel_path}/module.md")
81
+ print(f" Reason: Missing YAML frontmatter")
82
+ sys.exit(1)
83
+
84
+ # Parse YAML frontmatter
85
+ try:
86
+ import yaml
87
+ fm = yaml.safe_load(m.group(1)) or {}
88
+ except ImportError:
89
+ print(f"[{rel_path}] ERROR: PyYAML not installed, cannot parse module.md")
90
+ sys.exit(1)
91
+ except Exception as e:
92
+ print(f"[{rel_path}] ERROR: Invalid module configuration in module.md")
93
+ print(f" Path: {rel_path}/module.md")
94
+ print(f" Reason: YAML parse error: {e}")
95
+ sys.exit(1)
96
+
97
+ # Validate 'name' field (required)
98
+ if "name" not in fm:
99
+ print(f"[{rel_path}] ERROR: Invalid module configuration in module.md")
100
+ print(f" Path: {rel_path}/module.md")
101
+ print(f" Reason: Missing 'name' field")
102
+ sys.exit(1)
103
+
104
+ raw_name = str(fm["name"]).strip()
105
+
106
+ if not raw_name:
107
+ print(f"[{rel_path}] ERROR: Invalid module configuration in module.md")
108
+ print(f" Path: {rel_path}/module.md")
109
+ print(f" Reason: Empty module name")
110
+ sys.exit(1)
111
+
112
+ # Validate name characters
113
+ sanitized = re.sub(r'[^a-zA-Z0-9_\-]', '', raw_name)
114
+
115
+ if sanitized != raw_name:
116
+ invalid_chars = ''.join(sorted(set(c for c in raw_name if c not in sanitized)))
117
+ print(f"[{rel_path}] ERROR: Invalid module configuration in module.md")
118
+ print(f" Path: {rel_path}/module.md")
119
+ print(f" Reason: Invalid characters in name '{raw_name}': {repr(invalid_chars)}")
120
+ sys.exit(1)
121
+
122
+ result["name"] = sanitized
123
+
124
+ # Extract optional fields
125
+ if "preferred_port" in fm:
126
+ try:
127
+ result["preferred_port"] = int(fm["preferred_port"])
128
+ except (ValueError, TypeError):
129
+ pass
130
+
131
+ if "advertise_ip" in fm:
132
+ result["advertise_ip"] = str(fm["advertise_ip"])
133
+
134
+ # max_connections(弹性连接上限)
135
+ try:
136
+ result["max_connections"] = max(1, min(10, int(fm.get("max_connections", 1))))
137
+ except (ValueError, TypeError):
138
+ result["max_connections"] = 1
139
+
140
+ # business_config(从 businesses 数组取)
141
+ if "businesses" in fm:
142
+ businesses = fm["businesses"]
143
+ if isinstance(businesses, list) and businesses:
144
+ config_file = businesses[0].get("config_file") if isinstance(businesses[0], dict) else None
145
+ if config_file:
146
+ result["business_config"] = str(config_file).strip()
147
+
148
+ except SystemExit:
149
+ raise # Re-raise exit to prevent catching by outer except
150
+ except Exception as e:
151
+ print(f"[{rel_path}] ERROR: Failed to read module.md: {e}")
152
+ sys.exit(1)
153
+
154
+ return result
155
+
156
+ _module_config = _load_module_config()
157
+ MODULE_NAME = _module_config["name"]
158
+
159
+
160
+ class _SafeWriter:
161
+ """Wraps a stream to silently swallow BrokenPipeError on write/flush."""
162
+ def __init__(self, stream):
163
+ self._stream = stream
164
+
165
+ def write(self, s):
166
+ try:
167
+ self._stream.write(s)
168
+ except (BrokenPipeError, OSError):
169
+ pass
170
+
171
+ def flush(self):
172
+ try:
173
+ self._stream.flush()
174
+ except (BrokenPipeError, OSError):
175
+ pass
176
+
177
+ def __getattr__(self, name):
178
+ return getattr(self._stream, name)
179
+
180
+ sys.stdout = _SafeWriter(sys.stdout)
181
+ sys.stderr = _SafeWriter(sys.stderr)
182
+
183
+
184
+ # ── Timestamped print + log file writer ──
185
+ # Independent implementation per module (no shared code dependency)
186
+
187
+ _builtin_print = builtins.print
188
+ _start_ts = time.monotonic()
189
+ _last_ts = time.monotonic()
190
+ _ANSI_RE = re.compile(r"\033\[[0-9;]*m")
191
+ _log_lock = threading.Lock()
192
+ _log_latest_path = None
193
+ _log_daily_path = None
194
+ _log_daily_date = ""
195
+ _log_dir = None
196
+ _crash_log_path = None
197
+
198
+ def _strip_ansi(s: str) -> str:
199
+ return _ANSI_RE.sub("", s)
200
+
201
+ def _resolve_daily_log_path():
202
+ """Resolve daily log path based on current date."""
203
+ global _log_daily_path, _log_daily_date
204
+ if not _log_dir:
205
+ return
206
+ today = datetime.now().strftime("%Y-%m-%d")
207
+ if today == _log_daily_date and _log_daily_path:
208
+ return
209
+ month_dir = os.path.join(_log_dir, today[:7])
210
+ os.makedirs(month_dir, exist_ok=True)
211
+ _log_daily_path = os.path.join(month_dir, f"{today}.log")
212
+ _log_daily_date = today
213
+
214
+ def _write_log(plain_line: str):
215
+ """Write a plain-text line to both latest.log and daily log."""
216
+ with _log_lock:
217
+ if _log_latest_path:
218
+ try:
219
+ with open(_log_latest_path, "a", encoding="utf-8") as f:
220
+ f.write(plain_line)
221
+ except Exception:
222
+ pass
223
+ _resolve_daily_log_path()
224
+ if _log_daily_path:
225
+ try:
226
+ with open(_log_daily_path, "a", encoding="utf-8") as f:
227
+ f.write(plain_line)
228
+ except Exception:
229
+ pass
230
+
231
+ def _write_crash(exc_type, exc_value, exc_tb, thread_name=None, severity="critical", handled=False):
232
+ """Write crash record to crashes.jsonl + daily crash archive."""
233
+ record = {
234
+ "timestamp": datetime.now(timezone.utc).isoformat(),
235
+ "module": MODULE_NAME,
236
+ "thread": thread_name or threading.current_thread().name,
237
+ "exception_type": exc_type.__name__ if exc_type else "Unknown",
238
+ "exception_message": str(exc_value),
239
+ "traceback": "".join(traceback.format_exception(exc_type, exc_value, exc_tb)),
240
+ "severity": severity,
241
+ "handled": handled,
242
+ "process_id": os.getpid(),
243
+ "platform": sys.platform,
244
+ "runtime_version": f"Python {sys.version.split()[0]}",
245
+ }
246
+
247
+ if exc_tb:
248
+ tb_entries = traceback.extract_tb(exc_tb)
249
+ if tb_entries:
250
+ last = tb_entries[-1]
251
+ record["context"] = {
252
+ "function": last.name,
253
+ "file": os.path.basename(last.filename),
254
+ "line": last.lineno,
255
+ }
256
+
257
+ line = json.dumps(record, ensure_ascii=False) + "\n"
258
+
259
+ if _crash_log_path:
260
+ try:
261
+ with open(_crash_log_path, "a", encoding="utf-8") as f:
262
+ f.write(line)
263
+ except Exception:
264
+ pass
265
+
266
+ if _log_dir:
267
+ try:
268
+ today = datetime.now().strftime("%Y-%m-%d")
269
+ archive_dir = os.path.join(_log_dir, "crashes", today[:7])
270
+ os.makedirs(archive_dir, exist_ok=True)
271
+ archive_path = os.path.join(archive_dir, f"{today}.jsonl")
272
+ with open(archive_path, "a", encoding="utf-8") as f:
273
+ f.write(line)
274
+ except Exception:
275
+ pass
276
+
277
+ def _print_crash_summary(exc_type, exc_tb, thread_name=None):
278
+ """Print crash summary to console (red highlight)."""
279
+ RED = "\033[91m"
280
+ RESET = "\033[0m"
281
+
282
+ if exc_tb:
283
+ tb_entries = traceback.extract_tb(exc_tb)
284
+ if tb_entries:
285
+ last = tb_entries[-1]
286
+ location = f"{os.path.basename(last.filename)}:{last.lineno}"
287
+ else:
288
+ location = "unknown"
289
+ else:
290
+ location = "unknown"
291
+
292
+ prefix = f"[{MODULE_NAME}]"
293
+ if thread_name:
294
+ _builtin_print(f"{prefix} {RED}线程 {thread_name} 崩溃: "
295
+ f"{exc_type.__name__} in {location}{RESET}")
296
+ else:
297
+ _builtin_print(f"{prefix} {RED}崩溃: {exc_type.__name__} in {location}{RESET}")
298
+ if _crash_log_path:
299
+ _builtin_print(f"{prefix} 崩溃日志: {_crash_log_path}")
300
+
301
+ def _setup_exception_hooks():
302
+ """Set up global exception hooks."""
303
+ _orig_excepthook = sys.excepthook
304
+
305
+ def _excepthook(exc_type, exc_value, exc_tb):
306
+ _write_crash(exc_type, exc_value, exc_tb, severity="critical", handled=False)
307
+ _print_crash_summary(exc_type, exc_tb)
308
+ _orig_excepthook(exc_type, exc_value, exc_tb)
309
+
310
+ sys.excepthook = _excepthook
311
+
312
+ if hasattr(threading, "excepthook"):
313
+ def _thread_excepthook(args):
314
+ _write_crash(args.exc_type, args.exc_value, args.exc_traceback,
315
+ thread_name=args.thread.name if args.thread else "unknown",
316
+ severity="error", handled=False)
317
+ _print_crash_summary(args.exc_type, args.exc_traceback,
318
+ thread_name=args.thread.name if args.thread else None)
319
+
320
+ threading.excepthook = _thread_excepthook
321
+
322
+ def _tprint(*args, **kwargs):
323
+ """Timestamped print that adds [timestamp] HH:MM:SS.mmm +delta prefix."""
324
+ global _last_ts
325
+ now = time.monotonic()
326
+ elapsed = now - _start_ts
327
+ delta = now - _last_ts
328
+ _last_ts = now
329
+
330
+ if elapsed < 1:
331
+ elapsed_str = f"{elapsed * 1000:.0f}ms"
332
+ elif elapsed < 100:
333
+ elapsed_str = f"{elapsed:.1f}s"
334
+ else:
335
+ elapsed_str = f"{elapsed:.0f}s"
336
+
337
+ if delta < 0.001:
338
+ delta_str = ""
339
+ elif delta < 1:
340
+ delta_str = f"+{delta * 1000:.0f}ms"
341
+ elif delta < 100:
342
+ delta_str = f"+{delta:.1f}s"
343
+ else:
344
+ delta_str = f"+{delta:.0f}s"
345
+
346
+ ts = datetime.now().strftime("%H:%M:%S.%f")[:-3]
347
+
348
+ _builtin_print(*args, **kwargs)
349
+
350
+ if _log_latest_path or _log_daily_path:
351
+ sep = kwargs.get("sep", " ")
352
+ end = kwargs.get("end", "\n")
353
+ text = sep.join(str(a) for a in args)
354
+ prefix = f"[{elapsed_str:>6}] {ts} {delta_str:>8} "
355
+ _write_log(prefix + _strip_ansi(text) + end)
356
+
357
+ builtins.print = _tprint
358
+
359
+ # Ensure project root is on sys.path (set by main.py or cli.js)
360
+ _project_root = os.environ.get("KITE_PROJECT") or os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
361
+ if _project_root not in sys.path:
362
+ sys.path.insert(0, _project_root)
363
+
364
+
365
+
366
+ def _fmt_elapsed(t0: float) -> str:
367
+ d = time.monotonic() - t0
368
+ if d < 1:
369
+ return f"{d * 1000:.0f}ms"
370
+ if d < 10:
371
+ return f"{d:.1f}s"
372
+ return f"{d:.0f}s"
373
+
374
+
375
+ def _read_stdin_kite_message(expected_type: str, timeout: float = 10) -> dict | None:
376
+ """Read a single kite message of expected type from stdin with timeout."""
377
+ result = [None]
378
+
379
+ def _read():
380
+ try:
381
+ line = sys.stdin.readline().strip()
382
+ if line:
383
+ msg = json.loads(line)
384
+ if isinstance(msg, dict) and msg.get("kite") == expected_type:
385
+ result[0] = msg
386
+ except Exception:
387
+ pass
388
+
389
+ t = threading.Thread(target=_read, daemon=True)
390
+ t.start()
391
+ t.join(timeout=timeout)
392
+ return result[0]
393
+
394
+
395
+ # Global WS reference for publish_event callback
396
+ _ws_global = None
397
+ _shutting_down = False
398
+ _exit_code = 0 # Exit code for main() to use
399
+
400
+ # 弹性多连接
401
+ _extra_ws: dict = {} # slot → WebSocket(附加连接)
402
+ _extra_ws_tasks: dict = {} # slot → recv loop Task
403
+ _kernel_port = "" # 缓存 kernel_port,供 offer handler 使用
404
+ _pending_rpc: dict[str, asyncio.Future] = {} # rpc_id → Future(等待响应)
405
+
406
+ _has_registered = False
407
+
408
+ # Redis 特有
409
+ _redis_impl = None # Redis 实现实例(外部或内置)
410
+ _redis_ready = False
411
+
412
+
413
+ def _is_auth_failure(e: Exception) -> bool:
414
+ """Check if a WebSocket exception indicates authentication failure."""
415
+ if hasattr(e, 'rcvd') and e.rcvd is not None:
416
+ code = e.rcvd.code if hasattr(e.rcvd, 'code') else 0
417
+ return code in (4001, 4003)
418
+ return False
419
+
420
+
421
+ async def main():
422
+ global _ws_global, _shutting_down
423
+ if _startup_config.get("mode") == "remote":
424
+ await _remote_mode_loop(_startup_config["gateway_url"], _startup_config["kite_token"], _startup_config["t0"])
425
+ else:
426
+ await _ws_loop(_startup_config["token"], _startup_config["kernel_port"], _startup_config["t0"])
427
+
428
+
429
+ async def _ws_loop(token: str, kernel_port: int, _t0: float):
430
+ """Connect to Kernel with exponential backoff reconnection."""
431
+ global _shutting_down, _exit_code
432
+ retry_delay = 0.5
433
+ max_delay = 10.0
434
+ attempt = 0
435
+ cooldown_attempts = 0
436
+
437
+ while not _shutting_down:
438
+ try:
439
+ await _ws_connect(token, kernel_port, _t0)
440
+ retry_delay = 0.5
441
+ attempt = 0
442
+ cooldown_attempts = 0
443
+ except asyncio.CancelledError:
444
+ return
445
+ except Exception as e:
446
+ if _shutting_down:
447
+ return
448
+
449
+ code = _get_close_code(e)
450
+
451
+ # never: 永不重连
452
+ if code in (4001, 4003, 4004, 1008, 4010):
453
+ print(f"[redis] 致命错误 (code {code}),退出")
454
+ _exit_code = 1
455
+ _shutting_down = True
456
+ return
457
+
458
+ # cooldown: 速率限制
459
+ if code == 4020:
460
+ cooldown_attempts += 1
461
+ if cooldown_attempts >= 5:
462
+ print(f"[redis] 速率限制重试 5 次,退出")
463
+ _exit_code = 1
464
+ _shutting_down = True
465
+ return
466
+ print(f"[redis] 速率限制,10.0s 后重试 ({cooldown_attempts}/5)")
467
+ await asyncio.sleep(10.0)
468
+ continue
469
+
470
+ # standard: 指数退避 + jitter
471
+ attempt += 1
472
+ jitter = retry_delay * 0.2 * random.random()
473
+ sleep_time = retry_delay + jitter
474
+ _write_crash(type(e), e, e.__traceback__, severity="error", handled=True)
475
+ print(f"[redis] 连接错误: {e}, {sleep_time:.1f}s 后重试 (attempt {attempt})")
476
+
477
+ _ws_global_clear()
478
+ if _shutting_down:
479
+ return
480
+ await asyncio.sleep(sleep_time if 'sleep_time' in locals() else retry_delay)
481
+ retry_delay = min(retry_delay * 2, max_delay)
482
+
483
+
484
+ def _get_close_code(e: Exception) -> int:
485
+ """从 websockets 异常中提取关闭码"""
486
+ if hasattr(e, 'rcvd') and e.rcvd is not None:
487
+ return getattr(e.rcvd, 'code', 0)
488
+ return 0
489
+
490
+
491
+ def _ws_global_clear():
492
+ global _ws_global
493
+ _ws_global = None
494
+
495
+
496
+ async def _do_init(ws):
497
+ """收到 system.require_init 后执行:启动 Redis → subscribe + register + module.ready"""
498
+ global _has_registered, _redis_impl, _redis_ready
499
+ reason = "startup" if not _has_registered else "recovery"
500
+ print(f"[redis] Received system.require_init (reason={reason})")
501
+
502
+ # 0. 启动 Redis 实现(如果还未启动)
503
+ if not _redis_impl:
504
+ await _start_redis_impl()
505
+
506
+ # 1. Subscribe to events
507
+ await _rpc_call(ws, "event.subscribe", {
508
+ "events": [
509
+ "system.ping",
510
+ "module.started",
511
+ "module.stopped",
512
+ "module.shutdown",
513
+ ],
514
+ })
515
+ print(f"[redis] Subscribed to events")
516
+
517
+ # 2. Register to Kernel Registry via RPC
518
+ registry_data = {
519
+ "module_id": "redis",
520
+ "module_type": "service",
521
+ "launcher_id": "launcher",
522
+ "tools": {
523
+ "rpc": {
524
+ "module": {
525
+ "health": {"method": "health", "description": "健康检查"},
526
+ "status": {"method": "status", "description": "状态查询"},
527
+ "config": {
528
+ "get": {"method": "get_settings", "description": "获取配置"},
529
+ "update": {"method": "update_settings", "description": "更新配置"},
530
+ "reset": {"method": "reset_settings", "description": "恢复默认配置"},
531
+ }
532
+ },
533
+ "redis": {
534
+ "ping": {"method": "ping", "description": "Ping Redis"},
535
+ "get": {"method": "get", "description": "GET key"},
536
+ "set": {"method": "set", "description": "SET key value"},
537
+ "del": {"method": "del", "description": "DEL key"},
538
+ "expire": {"method": "expire", "description": "EXPIRE key seconds"},
539
+ "hget": {"method": "hget", "description": "HGET key field"},
540
+ "hset": {"method": "hset", "description": "HSET key field value"},
541
+ "hdel": {"method": "hdel", "description": "HDEL key field"},
542
+ "hgetall": {"method": "hgetall", "description": "HGETALL key"},
543
+ "sadd": {"method": "sadd", "description": "SADD key members"},
544
+ "srem": {"method": "srem", "description": "SREM key members"},
545
+ "smembers": {"method": "smembers", "description": "SMEMBERS key"},
546
+ "publish": {"method": "publish", "description": "PUBLISH channel message"},
547
+ "info": {"method": "info", "description": "Redis 连接信息"},
548
+ }
549
+ }
550
+ },
551
+ "events_publish": {
552
+ "system": {
553
+ "redis_ready": {"description": "Redis 服务已就绪"},
554
+ }
555
+ },
556
+ "events_subscribe": [
557
+ "module.started",
558
+ "module.stopped",
559
+ "module.shutdown",
560
+ ],
561
+ }
562
+
563
+ # 添加 Redis 连接信息(供模块直连)
564
+ if _redis_ready and _redis_impl:
565
+ registry_data["services"] = {
566
+ "redis": {
567
+ "host": _redis_impl.host,
568
+ "port": _redis_impl.port,
569
+ "protocol": "RESP",
570
+ "type": "external" if hasattr(_redis_impl, "_client") else "builtin",
571
+ }
572
+ }
573
+
574
+ await _rpc_call(ws, "registry.register", registry_data)
575
+ print(f"[redis] Registered to Kernel")
576
+
577
+ # 3. Publish module.ready
578
+ if not _shutting_down:
579
+ startup_time = time.monotonic() - _start_ts
580
+ await _publish_event(ws, "module.ready", {
581
+ "module_id": "redis",
582
+ "graceful_shutdown": True,
583
+ "startup_time": startup_time,
584
+ "reason": reason,
585
+ })
586
+ print(f"[redis] module.ready published (reason={reason})")
587
+
588
+ _has_registered = True
589
+
590
+ # Start test event loop in background
591
+ asyncio.create_task(_test_event_loop(ws))
592
+
593
+
594
+ async def _start_redis_impl():
595
+ """启动 Redis 实现(外部或内置)"""
596
+ global _redis_impl, _redis_ready
597
+
598
+ # 从 module.md 读取业务配置文件名
599
+ config_file = _module_config.get("business_config")
600
+ config = {}
601
+ if config_file:
602
+ config_path = os.path.join(os.path.dirname(__file__), config_file)
603
+ if os.path.exists(config_path):
604
+ try:
605
+ import json5
606
+ with open(config_path, 'r', encoding='utf-8') as f:
607
+ config = json5.load(f)
608
+ except Exception as e:
609
+ print(f"[redis] 配置文件读取失败: {e}")
610
+
611
+ redis_mode = config.get("redis_mode", "builtin")
612
+ redis_url = config.get("redis_url") or os.getenv("KITE_REDIS_URL")
613
+ allow_fallback = config.get("allow_fallback", True)
614
+ retry_initial = config.get("redis_retry_initial", 1.0)
615
+ retry_max = config.get("redis_retry_max", 30.0)
616
+
617
+ try:
618
+ if redis_mode == "external" and redis_url:
619
+ print(f"[redis] 尝试连接外部 Redis: {redis_url}")
620
+ from redis_external import ExternalRedis
621
+ redis_password = config.get("redis_password") or os.getenv("KITE_REDIS_PASSWORD") or None
622
+ _redis_impl = ExternalRedis(redis_url, password=redis_password, retry_initial=retry_initial, retry_max=retry_max)
623
+ # allow_fallback 时有限重试(5次),否则无限重试
624
+ max_attempts = 5 if allow_fallback else 0
625
+ await _redis_impl.start(max_attempts=max_attempts)
626
+ _redis_ready = True
627
+ GREEN = "\033[92m"
628
+ RESET = "\033[0m"
629
+ print(f"{GREEN}[redis] ✓ Redis 已就绪: 外部 Redis | {_redis_impl.host}:{_redis_impl.port}{RESET}")
630
+ else:
631
+ print(f"[redis] 使用内置 Redis")
632
+ from redis_builtin import BuiltinRedis
633
+ _redis_impl = BuiltinRedis()
634
+ await _redis_impl.start()
635
+ _redis_ready = True
636
+ GREEN = "\033[92m"
637
+ RESET = "\033[0m"
638
+ print(f"{GREEN}[redis] ✓ Redis 已就绪: 内置 Redis | {_redis_impl.host}:{_redis_impl.port}{RESET}")
639
+
640
+ except Exception as e:
641
+ if redis_mode == "external" and allow_fallback:
642
+ print(f"[redis] 外部 Redis 连接失败,降级到内置 Redis: {e}")
643
+ try:
644
+ from redis_builtin import BuiltinRedis
645
+ _redis_impl = BuiltinRedis()
646
+ await _redis_impl.start()
647
+ _redis_ready = True
648
+ YELLOW = "\033[93m"
649
+ RESET = "\033[0m"
650
+ print(f"{YELLOW}[redis] ⚠ Redis 已降级: 内置 Redis | {_redis_impl.host}:{_redis_impl.port}{RESET}")
651
+ except Exception as e2:
652
+ RED = "\033[91m"
653
+ RESET = "\033[0m"
654
+ print(f"{RED}[redis] ✗ Redis 启动失败: {e2}{RESET}")
655
+ _write_crash(type(e2), e2, e2.__traceback__, severity="error", handled=True)
656
+ sys.exit(1)
657
+ else:
658
+ RED = "\033[91m"
659
+ RESET = "\033[0m"
660
+ print(f"{RED}[redis] ✗ Redis 启动失败(不允许降级): {e}{RESET}")
661
+ _write_crash(type(e), e, e.__traceback__, severity="error", handled=True)
662
+ sys.exit(1)
663
+
664
+ # 发布 system.redis_ready 事件
665
+ if _redis_ready and _redis_impl and _ws_global:
666
+ await _publish_event(_ws_global, "system.redis_ready", {
667
+ "host": _redis_impl.host,
668
+ "port": _redis_impl.port,
669
+ "protocol": "RESP",
670
+ })
671
+ print(f"[redis] Published system.redis_ready")
672
+
673
+
674
+ async def _ws_connect(token: str, kernel_port: int, _t0: float):
675
+ """Single WebSocket session: connect → auth → start receiver → wait for system.require_init."""
676
+ global _ws_global, _kernel_port
677
+ _kernel_port = kernel_port
678
+
679
+ ws_url = f"ws://127.0.0.1:{kernel_port}/ws?id=redis"
680
+ print(f"[redis] Connecting to Kernel: {ws_url}")
681
+
682
+ async with websockets.connect(ws_url, open_timeout=5, ping_interval=None, close_timeout=10) as ws:
683
+ # Send auth message first
684
+ auth_req = {
685
+ "jsonrpc": "2.0",
686
+ "id": "auth",
687
+ "method": "auth",
688
+ "params": {"token": token}
689
+ }
690
+ await ws.send(json.dumps(auth_req))
691
+
692
+ # Wait for auth response
693
+ auth_resp_raw = await asyncio.wait_for(ws.recv(), timeout=5)
694
+ auth_resp = json.loads(auth_resp_raw)
695
+ if "error" in auth_resp:
696
+ raise Exception(f"Auth failed: {auth_resp['error']}")
697
+
698
+ _ws_global = ws
699
+ print(f"[redis] Connected to Kernel ({_fmt_elapsed(_t0)})")
700
+
701
+ # Start receiver task — wait for system.require_init to trigger _do_init
702
+ receiver_task = asyncio.create_task(_ws_receiver(ws))
703
+ try:
704
+ # Wait for receiver to finish (connection closed or error)
705
+ await receiver_task
706
+ finally:
707
+ receiver_task.cancel()
708
+ for fut in _pending_rpc.values():
709
+ if not fut.done():
710
+ fut.set_result({"error": {"code": -32001, "message": "Connection lost"}})
711
+ _pending_rpc.clear()
712
+
713
+
714
+ async def _ws_receiver(ws):
715
+ """Main connection receive loop: route events, RPC requests, and RPC responses.
716
+ Detects system.require_init to trigger _do_init."""
717
+ async for raw in ws:
718
+ try:
719
+ msg = json.loads(raw)
720
+ except (json.JSONDecodeError, TypeError):
721
+ continue
722
+
723
+ try:
724
+ has_method = "method" in msg
725
+ has_id = "id" in msg
726
+
727
+ if has_method and not has_id:
728
+ # 检测 system.require_init 事件
729
+ params = msg.get("params", {})
730
+ event_type = params.get("event", "")
731
+ if event_type == "system.require_init":
732
+ asyncio.create_task(_do_init(ws))
733
+ continue
734
+ # Event Notification — run in background to prevent deadlock
735
+ asyncio.create_task(_handle_event_notification(msg))
736
+ elif has_method and has_id:
737
+ # Incoming RPC request — run in background to prevent deadlock
738
+ asyncio.create_task(_handle_rpc_request(ws, msg))
739
+ elif not has_method and has_id:
740
+ _handle_rpc_response(msg)
741
+ except Exception as e:
742
+ print(f"[redis] 消息处理异常(已忽略): {e}")
743
+
744
+
745
+ async def _rpc_call(ws, method: str, params: dict = None,
746
+ wait_response: bool = True, timeout: float = 3.0) -> dict:
747
+ """JSON-RPC 2.0 request。默认等待响应。"""
748
+ rpc_id = str(uuid.uuid4())
749
+ msg = {"jsonrpc": "2.0", "id": rpc_id, "method": method}
750
+ if params:
751
+ msg["params"] = params
752
+
753
+ if not wait_response:
754
+ await ws.send(json.dumps(msg))
755
+ return {}
756
+
757
+ future = asyncio.get_event_loop().create_future()
758
+ _pending_rpc[rpc_id] = future
759
+ await ws.send(json.dumps(msg))
760
+ try:
761
+ return await asyncio.wait_for(future, timeout=timeout)
762
+ except asyncio.TimeoutError:
763
+ _pending_rpc.pop(rpc_id, None)
764
+ return {"error": {"code": -32000, "message": f"RPC timeout: {method} ({timeout}s)"}}
765
+
766
+
767
+ def _handle_rpc_response(msg: dict):
768
+ """Route JSON-RPC response to pending future."""
769
+ rpc_id = msg.get("id")
770
+ future = _pending_rpc.pop(rpc_id, None)
771
+ if future and not future.done():
772
+ future.set_result(msg)
773
+
774
+
775
+ async def _publish_event(ws, event: str, data: dict = None):
776
+ """Publish event via event.publish RPC (fire-and-forget)."""
777
+ await _rpc_call(ws, "event.publish", {
778
+ "event_id": str(uuid.uuid4()),
779
+ "event": event,
780
+ "data": data or {},
781
+ }, wait_response=False)
782
+
783
+
784
+ async def _handle_ping_event(data: dict):
785
+ """Handle system.ping event and reply with system.pong."""
786
+ t1 = data.get("ping_time")
787
+ t2 = time.time()
788
+
789
+ await _publish_event(_ws_global, "system.pong", {
790
+ "module_id": MODULE_NAME,
791
+ "ping_time": t1,
792
+ "pong_time": t2,
793
+ })
794
+
795
+
796
+ async def _handle_event_notification(msg: dict):
797
+ """Handle an event notification (JSON-RPC 2.0 Notification with method='event')."""
798
+ params = msg.get("params", {})
799
+ event_type = params.get("event", "")
800
+ data = params.get("data", {})
801
+
802
+ # Handle system.ping event
803
+ if event_type == "system.ping":
804
+ await _handle_ping_event(data)
805
+ return
806
+
807
+ # 弹性连接 offer/release
808
+ if event_type == "system.connection.offer":
809
+ asyncio.create_task(_handle_connection_offer(data))
810
+ return
811
+ if event_type == "system.connection.release":
812
+ asyncio.create_task(_handle_connection_release(data))
813
+ return
814
+
815
+ # Special handling for module.shutdown
816
+ if event_type == "module.shutdown":
817
+ target = data.get("module_id", "")
818
+ reason = data.get("reason", "")
819
+ # Handle both targeted shutdown (module_id == "redis") and broadcast shutdown (no module_id or launcher_lost)
820
+ if target == "redis" or not target or reason == "launcher_lost":
821
+ await _handle_shutdown()
822
+ return
823
+
824
+ # Layer 2: 忽略系统广播事件
825
+ if event_type in SYSTEM_BROADCAST_EVENTS:
826
+ return
827
+
828
+ # Layer 3: 警告未知事件(仅开发环境)
829
+ if os.environ.get("KITE_ENV") == "development":
830
+ print(f"[redis] Debug: Unhandled event: {event_type}")
831
+
832
+
833
+ async def _handle_rpc_request(ws, msg: dict):
834
+ """Handle an incoming RPC request (redis.* methods)."""
835
+ rpc_id = msg.get("id", "")
836
+ method = msg.get("method", "")
837
+ params = msg.get("params", {})
838
+
839
+ handlers = {
840
+ "health": lambda p: _rpc_health(),
841
+ "status": lambda p: _rpc_status(),
842
+ "get_settings": lambda p: _rpc_get_settings(p),
843
+ "update_settings": lambda p: _rpc_update_settings(p),
844
+ "reset_settings": lambda p: _rpc_reset_settings(p),
845
+ "ping": lambda p: _rpc_redis_ping(p),
846
+ "get": lambda p: _rpc_redis_get(p),
847
+ "set": lambda p: _rpc_redis_set(p),
848
+ "del": lambda p: _rpc_redis_del(p),
849
+ "expire": lambda p: _rpc_redis_expire(p),
850
+ "hget": lambda p: _rpc_redis_hget(p),
851
+ "hset": lambda p: _rpc_redis_hset(p),
852
+ "hdel": lambda p: _rpc_redis_hdel(p),
853
+ "hgetall": lambda p: _rpc_redis_hgetall(p),
854
+ "sadd": lambda p: _rpc_redis_sadd(p),
855
+ "srem": lambda p: _rpc_redis_srem(p),
856
+ "smembers": lambda p: _rpc_redis_smembers(p),
857
+ "publish": lambda p: _rpc_redis_publish(p),
858
+ "info": lambda p: _rpc_redis_info(p),
859
+ }
860
+ handler = handlers.get(method)
861
+ if handler:
862
+ try:
863
+ result = await handler(params)
864
+ await ws.send(json.dumps({"jsonrpc": "2.0", "id": rpc_id, "result": result}))
865
+ except Exception as e:
866
+ await ws.send(json.dumps({
867
+ "jsonrpc": "2.0", "id": rpc_id,
868
+ "error": {"code": -32603, "message": str(e)},
869
+ }))
870
+ else:
871
+ await ws.send(json.dumps({
872
+ "jsonrpc": "2.0", "id": rpc_id,
873
+ "error": {"code": -32601, "message": f"Method not found: {method}"},
874
+ }))
875
+
876
+
877
+ async def _rpc_health() -> dict:
878
+ """RPC handler for redis.health."""
879
+ return {
880
+ "status": "healthy",
881
+ "redis_ready": _redis_ready,
882
+ "details": {
883
+ "uptime_seconds": round(time.monotonic() - _start_ts),
884
+ },
885
+ }
886
+
887
+
888
+ async def _rpc_status() -> dict:
889
+ """RPC handler for redis.status."""
890
+ return {
891
+ "module_id": "redis",
892
+ "status": "running",
893
+ "redis_ready": _redis_ready,
894
+ "uptime": round(time.monotonic() - _start_ts),
895
+ "uptime_seconds": round(time.monotonic() - _start_ts),
896
+ }
897
+
898
+
899
+ # ── Configuration management helpers ──
900
+
901
+ def _read_module_md() -> tuple[dict, str]:
902
+ """读取 module.md,返回 (frontmatter, body)"""
903
+ from pathlib import Path
904
+ md_path = Path(__file__).parent / "module.md"
905
+ text = md_path.read_text(encoding="utf-8")
906
+
907
+ # 提取 YAML frontmatter (--- ... ---)
908
+ m = re.match(r'^---\s*\n(.*?)\n---\s*\n?(.*)', text, re.DOTALL)
909
+ if not m:
910
+ return {}, text
911
+
912
+ import yaml
913
+ frontmatter = yaml.safe_load(m.group(1)) or {}
914
+ body = m.group(2)
915
+ return frontmatter, body
916
+
917
+
918
+ def _write_module_md(frontmatter: dict, body: str):
919
+ """写入 module.md"""
920
+ from pathlib import Path
921
+ import yaml
922
+ md_path = Path(__file__).parent / "module.md"
923
+ fm_str = yaml.dump(frontmatter, allow_unicode=True, sort_keys=False, default_flow_style=False).rstrip()
924
+ content = f"---\n{fm_str}\n---\n{body}"
925
+ md_path.write_text(content, encoding="utf-8")
926
+
927
+
928
+ # ── RPC handlers for configuration management ──
929
+
930
+ async def _rpc_get_settings(params: dict) -> dict:
931
+ """RPC handler for redis.get_settings — 获取模块的所有设置"""
932
+ frontmatter, _ = _read_module_md()
933
+
934
+ config = None
935
+ _businesses = frontmatter.get("businesses") or []
936
+ config_file = (_businesses[0].get("config_file") if isinstance(_businesses[0], dict) else None) if _businesses else None
937
+ if config_file:
938
+ config_path = os.path.join(os.path.dirname(__file__), config_file)
939
+ if os.path.exists(config_path):
940
+ try:
941
+ import json5
942
+ with open(config_path, 'r', encoding='utf-8') as f:
943
+ config = json5.load(f)
944
+ except Exception as e:
945
+ print(f"[redis] 读取业务配置失败: {e}")
946
+
947
+ return {
948
+ "name": frontmatter.get("name", "redis"),
949
+ "display_name": frontmatter.get("display_name", ""),
950
+ "type": frontmatter.get("type", ""),
951
+ "state": frontmatter.get("state", "enabled"),
952
+ "version": frontmatter.get("version", ""),
953
+ "runtime": frontmatter.get("runtime", ""),
954
+ "entry": frontmatter.get("entry", ""),
955
+ "preferred_port": frontmatter.get("preferred_port"),
956
+ "advertise_ip": frontmatter.get("advertise_ip"),
957
+ "monitor": frontmatter.get("monitor"),
958
+ "events": frontmatter.get("events"),
959
+ "subscriptions": frontmatter.get("subscriptions"),
960
+ "depends_on": frontmatter.get("depends_on"),
961
+ "has_config": config is not None,
962
+ "config": config,
963
+ }
964
+
965
+
966
+ async def _rpc_update_settings(params: dict) -> dict:
967
+ """RPC handler for redis.update_settings — 更新模块设置"""
968
+ metadata = params.get("metadata", {})
969
+ config = params.get("config", {})
970
+
971
+ # 更新 module.md frontmatter
972
+ if metadata:
973
+ frontmatter, body = _read_module_md()
974
+ for key, value in metadata.items():
975
+ frontmatter[key] = value
976
+ _write_module_md(frontmatter, body)
977
+
978
+ # 更新业务配置文件
979
+ if config:
980
+ frontmatter, _ = _read_module_md()
981
+ _businesses = frontmatter.get("businesses") or []
982
+ config_file = (_businesses[0].get("config_file") if isinstance(_businesses[0], dict) else None) if _businesses else None
983
+ if config_file:
984
+ config_path = os.path.join(os.path.dirname(__file__), config_file)
985
+ existing = {}
986
+ if os.path.exists(config_path):
987
+ try:
988
+ import json5
989
+ with open(config_path, 'r', encoding='utf-8') as f:
990
+ existing = json5.load(f) or {}
991
+ except Exception:
992
+ existing = {}
993
+
994
+ for flat_key, value in config.items():
995
+ keys = flat_key.split('.')
996
+ target = existing
997
+ for k in keys[:-1]:
998
+ if k not in target or not isinstance(target[k], dict):
999
+ target[k] = {}
1000
+ target = target[k]
1001
+ target[keys[-1]] = value
1002
+
1003
+ import json
1004
+ with open(config_path, 'w', encoding='utf-8') as f:
1005
+ f.write(json.dumps(existing, ensure_ascii=False, indent=2))
1006
+
1007
+ # 返回更新后的完整设置
1008
+ return await _rpc_get_settings({})
1009
+
1010
+
1011
+ async def _rpc_reset_settings(params: dict) -> dict:
1012
+ """RPC handler for redis.reset_settings — 恢复默认值"""
1013
+ fields = params.get("fields", [])
1014
+ reset_all = params.get("all", False)
1015
+
1016
+ # 默认值定义
1017
+ defaults = {
1018
+ "state": "enabled",
1019
+ "preferred_port": 20000,
1020
+ "advertise_ip": "127.0.0.1",
1021
+ "monitor": True,
1022
+ }
1023
+
1024
+ frontmatter, body = _read_module_md()
1025
+
1026
+ if reset_all:
1027
+ # 恢复所有字段
1028
+ for key, value in defaults.items():
1029
+ frontmatter[key] = value
1030
+ else:
1031
+ # 恢复指定字段
1032
+ for field in fields:
1033
+ if field in defaults:
1034
+ frontmatter[field] = defaults[field]
1035
+
1036
+ _write_module_md(frontmatter, body)
1037
+
1038
+ # 返回恢复后的设置
1039
+ return await _rpc_get_settings({})
1040
+
1041
+
1042
+ # ── Redis RPC handlers ──
1043
+
1044
+ async def _rpc_redis_ping(params: dict) -> dict:
1045
+ """RPC: redis.ping"""
1046
+ if not _redis_ready:
1047
+ raise Exception("Redis not ready")
1048
+ return {"status": "ok", "message": "PONG"}
1049
+
1050
+
1051
+ async def _rpc_redis_get(params: dict) -> dict:
1052
+ """RPC: redis.get"""
1053
+ if not _redis_ready or not _redis_impl:
1054
+ raise Exception("Redis not ready")
1055
+ key = params.get("key")
1056
+ if not key:
1057
+ raise Exception("Missing key parameter")
1058
+ value = await _redis_impl.get(key)
1059
+ return {"key": key, "value": value}
1060
+
1061
+
1062
+ async def _rpc_redis_set(params: dict) -> dict:
1063
+ """RPC: redis.set"""
1064
+ if not _redis_ready or not _redis_impl:
1065
+ raise Exception("Redis not ready")
1066
+ key = params.get("key")
1067
+ value = params.get("value")
1068
+ if not key:
1069
+ raise Exception("Missing key parameter")
1070
+ await _redis_impl.set(key, value)
1071
+ return {"status": "ok"}
1072
+
1073
+
1074
+ async def _rpc_redis_hget(params: dict) -> dict:
1075
+ """RPC: redis.hget"""
1076
+ if not _redis_ready or not _redis_impl:
1077
+ raise Exception("Redis not ready")
1078
+ key = params.get("key")
1079
+ field = params.get("field")
1080
+ if not key or not field:
1081
+ raise Exception("Missing key or field parameter")
1082
+ value = await _redis_impl.hget(key, field)
1083
+ return {"key": key, "field": field, "value": value}
1084
+
1085
+
1086
+ async def _rpc_redis_hset(params: dict) -> dict:
1087
+ """RPC: redis.hset"""
1088
+ if not _redis_ready or not _redis_impl:
1089
+ raise Exception("Redis not ready")
1090
+ key = params.get("key")
1091
+ field = params.get("field")
1092
+ value = params.get("value")
1093
+ if not key or not field:
1094
+ raise Exception("Missing key or field parameter")
1095
+ await _redis_impl.hset(key, field, value)
1096
+ return {"status": "ok"}
1097
+
1098
+
1099
+ async def _rpc_redis_info(params: dict) -> dict:
1100
+ """RPC: redis.info"""
1101
+ if not _redis_ready or not _redis_impl:
1102
+ raise Exception("Redis not ready")
1103
+ return {
1104
+ "host": _redis_impl.host,
1105
+ "port": _redis_impl.port,
1106
+ "protocol": "RESP",
1107
+ "type": "external" if hasattr(_redis_impl, "_client") else "builtin",
1108
+ }
1109
+
1110
+
1111
+ async def _rpc_redis_del(params: dict) -> dict:
1112
+ """RPC: redis.del"""
1113
+ if not _redis_ready or not _redis_impl:
1114
+ raise Exception("Redis not ready")
1115
+ key = params.get("key")
1116
+ if not key:
1117
+ raise Exception("Missing key parameter")
1118
+ deleted = await _redis_impl.delete(key)
1119
+ return {"deleted": deleted}
1120
+
1121
+
1122
+ async def _rpc_redis_expire(params: dict) -> dict:
1123
+ """RPC: redis.expire"""
1124
+ if not _redis_ready or not _redis_impl:
1125
+ raise Exception("Redis not ready")
1126
+ key = params.get("key")
1127
+ seconds = params.get("seconds")
1128
+ if not key or seconds is None:
1129
+ raise Exception("Missing key or seconds parameter")
1130
+ result = await _redis_impl.expire(key, int(seconds))
1131
+ return {"result": result}
1132
+
1133
+
1134
+ async def _rpc_redis_hdel(params: dict) -> dict:
1135
+ """RPC: redis.hdel"""
1136
+ if not _redis_ready or not _redis_impl:
1137
+ raise Exception("Redis not ready")
1138
+ key = params.get("key")
1139
+ field = params.get("field")
1140
+ if not key or not field:
1141
+ raise Exception("Missing key or field parameter")
1142
+ deleted = await _redis_impl.hdel(key, field)
1143
+ return {"deleted": deleted}
1144
+
1145
+
1146
+ async def _rpc_redis_hgetall(params: dict) -> dict:
1147
+ """RPC: redis.hgetall"""
1148
+ if not _redis_ready or not _redis_impl:
1149
+ raise Exception("Redis not ready")
1150
+ key = params.get("key")
1151
+ if not key:
1152
+ raise Exception("Missing key parameter")
1153
+ result = await _redis_impl.hgetall(key)
1154
+ # 内置返回 list [k,v,k,v], 外部返回 dict — 统一为 dict
1155
+ if isinstance(result, list):
1156
+ d = {}
1157
+ for i in range(0, len(result), 2):
1158
+ d[result[i]] = result[i + 1]
1159
+ return {"key": key, "data": d}
1160
+ return {"key": key, "data": result if isinstance(result, dict) else {}}
1161
+
1162
+
1163
+ async def _rpc_redis_sadd(params: dict) -> dict:
1164
+ """RPC: redis.sadd"""
1165
+ if not _redis_ready or not _redis_impl:
1166
+ raise Exception("Redis not ready")
1167
+ key = params.get("key")
1168
+ members = params.get("members") or []
1169
+ member = params.get("member")
1170
+ if member and not members:
1171
+ members = [member]
1172
+ if not key or not members:
1173
+ raise Exception("Missing key or member/members parameter")
1174
+ added = await _redis_impl.sadd(key, *members)
1175
+ return {"added": added}
1176
+
1177
+
1178
+ async def _rpc_redis_srem(params: dict) -> dict:
1179
+ """RPC: redis.srem"""
1180
+ if not _redis_ready or not _redis_impl:
1181
+ raise Exception("Redis not ready")
1182
+ key = params.get("key")
1183
+ members = params.get("members") or []
1184
+ member = params.get("member")
1185
+ if member and not members:
1186
+ members = [member]
1187
+ if not key or not members:
1188
+ raise Exception("Missing key or member/members parameter")
1189
+ removed = await _redis_impl.srem(key, *members)
1190
+ return {"removed": removed}
1191
+
1192
+
1193
+ async def _rpc_redis_smembers(params: dict) -> dict:
1194
+ """RPC: redis.smembers"""
1195
+ if not _redis_ready or not _redis_impl:
1196
+ raise Exception("Redis not ready")
1197
+ key = params.get("key")
1198
+ if not key:
1199
+ raise Exception("Missing key parameter")
1200
+ members = await _redis_impl.smembers(key)
1201
+ return {"key": key, "members": list(members) if members else []}
1202
+
1203
+
1204
+ async def _rpc_redis_publish(params: dict) -> dict:
1205
+ """RPC: redis.publish"""
1206
+ if not _redis_ready or not _redis_impl:
1207
+ raise Exception("Redis not ready")
1208
+ channel = params.get("channel")
1209
+ message = params.get("message")
1210
+ if not channel or message is None:
1211
+ raise Exception("Missing channel or message parameter")
1212
+ receivers = await _redis_impl.publish(channel, message)
1213
+ return {"receivers": receivers}
1214
+
1215
+
1216
+ async def _handle_connection_offer(data):
1217
+ """处理 Kernel 下发的 slot token,建立附加连接。"""
1218
+ slots = data.get("slots", {})
1219
+ for slot_str, info in slots.items():
1220
+ slot = int(slot_str)
1221
+ token = info.get("token", "")
1222
+ if not token or slot in _extra_ws:
1223
+ continue
1224
+ asyncio.create_task(_connect_slot(slot, token))
1225
+
1226
+
1227
+ async def _connect_slot(slot, token):
1228
+ """建立单个 slot 附加连接。"""
1229
+ ws_url = f"ws://127.0.0.1:{_kernel_port}/ws"
1230
+ try:
1231
+ ws = await websockets.connect(ws_url, open_timeout=5, ping_interval=None, close_timeout=5)
1232
+ auth_req = {"jsonrpc": "2.0", "id": f"auth-slot-{slot}", "method": "auth", "params": {"token": token}}
1233
+ await ws.send(json.dumps(auth_req))
1234
+ resp = json.loads(await asyncio.wait_for(ws.recv(), timeout=5))
1235
+ if "error" in resp:
1236
+ await ws.close()
1237
+ return
1238
+ _extra_ws[slot] = ws
1239
+ _extra_ws_tasks[slot] = asyncio.create_task(_slot_recv_loop(slot, ws))
1240
+ print(f"[redis] Slot {slot} connected")
1241
+ except Exception as e:
1242
+ print(f"[redis] Slot {slot} connect failed: {e}")
1243
+
1244
+
1245
+ async def _slot_recv_loop(slot, ws):
1246
+ """附加连接的接收循环:与主连接平等处理所有消息。"""
1247
+ try:
1248
+ async for raw in ws:
1249
+ try:
1250
+ msg = json.loads(raw)
1251
+ except (json.JSONDecodeError, TypeError):
1252
+ continue
1253
+ try:
1254
+ has_method = "method" in msg
1255
+ has_id = "id" in msg
1256
+
1257
+ if has_method and not has_id:
1258
+ asyncio.create_task(_handle_event_notification(msg))
1259
+ elif has_method and has_id:
1260
+ asyncio.create_task(_handle_rpc_request(ws, msg))
1261
+ elif not has_method and has_id:
1262
+ _handle_rpc_response(msg)
1263
+ except Exception as e:
1264
+ print(f"[redis] Slot {slot} 消息处理异常(已忽略): {e}")
1265
+ except Exception as e:
1266
+ print(f"[redis] Slot {slot} 接收循环异常: {e}")
1267
+ finally:
1268
+ _extra_ws.pop(slot, None)
1269
+ _extra_ws_tasks.pop(slot, None)
1270
+
1271
+
1272
+ async def _handle_connection_release(data):
1273
+ """Kernel 请求释放 slot,优雅关闭。"""
1274
+ for slot in data.get("slots", []):
1275
+ ws = _extra_ws.pop(slot, None)
1276
+ task = _extra_ws_tasks.pop(slot, None)
1277
+ if ws:
1278
+ try:
1279
+ await ws.close(code=1000, reason="release")
1280
+ except Exception:
1281
+ pass
1282
+ if task:
1283
+ task.cancel()
1284
+
1285
+
1286
+ async def _handle_shutdown():
1287
+ """Handle module.shutdown event — ack → exiting → cleanup → ready → exit."""
1288
+ global _shutting_down
1289
+ print("[redis] Received shutdown request")
1290
+ _shutting_down = True
1291
+ # Step 1: Send ack (立即确认收到)
1292
+ await _publish_event(_ws_global, "module.shutdown.ack", {"module_id": "redis"})
1293
+ # Step 2: Send module.exiting (开始清理)
1294
+ await _publish_event(_ws_global, "module.exiting", {
1295
+ "module_id": "redis",
1296
+ "type": "passive",
1297
+ "reason": "shutdown_requested",
1298
+ "restart": "auto",
1299
+ "action": "none",
1300
+ "timeout": 2.0,
1301
+ "restart_delay": 0.0,
1302
+ })
1303
+ # Step 3: Cleanup - 关闭 Redis 实现
1304
+ if _redis_impl:
1305
+ try:
1306
+ await _redis_impl.close()
1307
+ print("[redis] Redis implementation closed")
1308
+ except Exception as e:
1309
+ print(f"[redis] Failed to close Redis: {e}")
1310
+
1311
+ # 关闭所有附加连接
1312
+ for _s, _w in list(_extra_ws.items()):
1313
+ try:
1314
+ await _w.close(code=1000, reason="shutdown")
1315
+ except Exception:
1316
+ pass
1317
+ for _t in _extra_ws_tasks.values():
1318
+ _t.cancel()
1319
+ _extra_ws.clear()
1320
+ _extra_ws_tasks.clear()
1321
+ # Step 4: Send ready (清理完成)
1322
+ await _publish_event(_ws_global, "module.shutdown.ready", {"module_id": "redis"})
1323
+ print("[redis] Shutdown ready, exiting")
1324
+
1325
+ # 等待 Kernel 处理 shutdown.ready(防止 Close 帧抢先)
1326
+ await asyncio.sleep(0.1)
1327
+
1328
+ # Step 5: Close WebSocket connection gracefully
1329
+ if _ws_global:
1330
+ try:
1331
+ await _ws_global.close(code=1000, reason="Graceful shutdown")
1332
+ print("[redis] WebSocket closed")
1333
+ except Exception as e:
1334
+ print(f"[redis] Failed to close WebSocket: {e}")
1335
+
1336
+ # Note: Do NOT call sys.exit() in async context
1337
+ # Let the event loop naturally complete
1338
+
1339
+
1340
+ async def _test_event_loop(ws):
1341
+ """Publish a test event every 10 seconds."""
1342
+ try:
1343
+ while True:
1344
+ await asyncio.sleep(10)
1345
+ await _publish_event(ws, "redis.test", {
1346
+ "message": "test event from redis",
1347
+ "timestamp": datetime.now(timezone.utc).isoformat(),
1348
+ })
1349
+ except Exception:
1350
+ pass
1351
+
1352
+
1353
+ _startup_config = {} # populated by _validate_and_prepare()
1354
+
1355
+
1356
+ def _validate_and_prepare() -> dict:
1357
+ """同步启动验证:日志初始化、读取 token 和 kernel_port。
1358
+ 失败直接 sys.exit(1),成功返回启动参数。在 asyncio.run() 之前调用。"""
1359
+ t0 = time.monotonic()
1360
+ kernel_port = os.environ.get("KITE_KERNEL_PORT")
1361
+ if kernel_port:
1362
+ return _prepare_local_mode(int(kernel_port), t0)
1363
+ config = _load_module_config()
1364
+ gateway_url = config.get("gateway_url") or os.environ.get("KITE_GATEWAY_URL")
1365
+ if gateway_url:
1366
+ return _prepare_remote_mode(gateway_url, t0)
1367
+ print(f"[{MODULE_NAME}] ERROR: No KITE_KERNEL_PORT and no gateway_url")
1368
+ sys.exit(1)
1369
+
1370
+ def _prepare_local_mode(kernel_port: int, t0: float) -> dict:
1371
+ global _log_dir, _log_latest_path, _crash_log_path
1372
+ module_data = os.environ.get("KITE_MODULE_DATA")
1373
+ if module_data:
1374
+ _log_dir = os.path.join(module_data, "log")
1375
+ os.makedirs(_log_dir, exist_ok=True)
1376
+ suffix = os.environ.get("KITE_INSTANCE_SUFFIX", "")
1377
+ _log_latest_path = os.path.join(_log_dir, f"latest{suffix}.log")
1378
+ try:
1379
+ with open(_log_latest_path, "w", encoding="utf-8") as f: pass
1380
+ except Exception: _log_latest_path = None
1381
+ _crash_log_path = os.path.join(_log_dir, f"crashes{suffix}.jsonl")
1382
+ try:
1383
+ with open(_crash_log_path, "w", encoding="utf-8") as f: pass
1384
+ except Exception: _crash_log_path = None
1385
+ _resolve_daily_log_path()
1386
+ _setup_exception_hooks()
1387
+ line = sys.stdin.readline().strip()
1388
+ if not line:
1389
+ print(f"[{MODULE_NAME}] ERROR: stdin closed")
1390
+ sys.exit(1)
1391
+ try: msg = json.loads(line)
1392
+ except json.JSONDecodeError as e:
1393
+ print(f"[{MODULE_NAME}] ERROR: Invalid JSON: {e}")
1394
+ sys.exit(1)
1395
+ if "error" in msg:
1396
+ print(f"[{MODULE_NAME}] 启动失败: {msg.get('message')}")
1397
+ sys.exit(1)
1398
+ token = msg.get("token", "")
1399
+ if not token:
1400
+ print(f"[{MODULE_NAME}] ERROR: No token")
1401
+ sys.exit(1)
1402
+ print(f"[{MODULE_NAME}] Local mode: port={kernel_port}")
1403
+ return {"mode": "local", "token": token, "kernel_port": kernel_port, "t0": t0}
1404
+
1405
+ def _prepare_remote_mode(gateway_url: str, t0: float) -> dict:
1406
+ global _log_dir, _log_latest_path, _crash_log_path
1407
+ home = os.environ.get("HOME") or os.environ.get("USERPROFILE") or os.path.expanduser("~")
1408
+ data_dir = os.path.join(home, ".kite", "remote", MODULE_NAME)
1409
+ os.makedirs(data_dir, exist_ok=True)
1410
+ _log_dir = os.path.join(data_dir, "log")
1411
+ os.makedirs(_log_dir, exist_ok=True)
1412
+ _log_latest_path = os.path.join(_log_dir, "latest.log")
1413
+ try:
1414
+ with open(_log_latest_path, "w", encoding="utf-8") as f: pass
1415
+ except Exception: _log_latest_path = None
1416
+ _crash_log_path = os.path.join(_log_dir, "crashes.jsonl")
1417
+ try:
1418
+ with open(_crash_log_path, "w", encoding="utf-8") as f: pass
1419
+ except Exception: _crash_log_path = None
1420
+ _resolve_daily_log_path()
1421
+ _setup_exception_hooks()
1422
+ kite_token = _get_kite_token(MODULE_NAME, gateway_url)
1423
+ print(f"[{MODULE_NAME}] Remote mode: gateway={gateway_url}")
1424
+ return {"mode": "remote", "gateway_url": gateway_url, "kite_token": kite_token, "t0": t0}
1425
+
1426
+ def _gateway_to_filename(gateway_url: str) -> str:
1427
+ try:
1428
+ from urllib.parse import urlparse
1429
+ parsed = urlparse(gateway_url)
1430
+ host = parsed.hostname or "unknown"
1431
+ port = parsed.port or (443 if parsed.scheme == "wss" else 80)
1432
+ return f"{host}-{port}.json".replace(":", "-").replace("/", "-")
1433
+ except Exception: return "default.json"
1434
+
1435
+ def _load_token_cache(module_name: str, gateway_url: str) -> dict | None:
1436
+ home = os.environ.get("HOME") or os.environ.get("USERPROFILE") or os.path.expanduser("~")
1437
+ token_file = os.path.join(home, ".kite", "remote", module_name, "tokens", _gateway_to_filename(gateway_url))
1438
+ if not os.path.exists(token_file): return None
1439
+ try:
1440
+ with open(token_file, "r") as f: return json.load(f)
1441
+ except Exception: return None
1442
+
1443
+ def _clear_token_cache(module_name: str, gateway_url: str):
1444
+ home = os.environ.get("HOME") or os.environ.get("USERPROFILE") or os.path.expanduser("~")
1445
+ token_file = os.path.join(home, ".kite", "remote", module_name, "tokens", _gateway_to_filename(gateway_url))
1446
+ if os.path.exists(token_file):
1447
+ try: os.remove(token_file)
1448
+ except Exception: pass
1449
+
1450
+ def _get_kite_token(module_name: str, gateway_url: str) -> str:
1451
+ token = os.environ.get("KITE_TOKEN")
1452
+ if token: return token
1453
+ cache = _load_token_cache(module_name, gateway_url)
1454
+ if cache: return cache["token"]
1455
+ print(f"[{module_name}] No token for {gateway_url}")
1456
+ print(f" export KITE_TOKEN=<token>")
1457
+ sys.exit(1)
1458
+
1459
+
1460
+ async def _remote_mode_loop(gateway_url: str, kite_token: str, _t0: float):
1461
+ global _shutting_down, _exit_code
1462
+ retry_count = 0
1463
+ while not _shutting_down and retry_count < 10:
1464
+ try:
1465
+ await _remote_connect(gateway_url, kite_token, _t0)
1466
+ retry_count = 0
1467
+ except asyncio.CancelledError: break
1468
+ except Exception as e:
1469
+ if _shutting_down: break
1470
+ retry_count += 1
1471
+ await asyncio.sleep(min(2 ** retry_count, 60))
1472
+ if retry_count >= 10: _exit_code = 1
1473
+
1474
+ async def _remote_connect(gateway_url: str, kite_token: str, _t0: float):
1475
+ global _ws_global
1476
+ async with websockets.connect(gateway_url, open_timeout=10, ping_interval=30) as ws:
1477
+ challenge = json.loads(await ws.recv())
1478
+ auth_req = {"jsonrpc": "2.0", "id": "auth", "method": "auth.connect",
1479
+ "params": {"nonce": challenge["params"]["nonce"], "module_id": MODULE_NAME,
1480
+ "auth": {"method": "kite_token", "token": kite_token},
1481
+ "client": {"type": "module", "platform": sys.platform}}}
1482
+ await ws.send(json.dumps(auth_req))
1483
+ hello_resp = json.loads(await ws.recv())
1484
+ if "error" in hello_resp:
1485
+ error = hello_resp["error"]
1486
+ if error.get("code") in (4001, 4010, 4011):
1487
+ _clear_token_cache(MODULE_NAME, gateway_url)
1488
+ print(f"[{MODULE_NAME}] Token invalid, re-authenticate")
1489
+ sys.exit(1)
1490
+ raise Exception(f"Connection failed: {error}")
1491
+ print(f"[{MODULE_NAME}] Connected via Gateway")
1492
+ _ws_global = ws
1493
+
1494
+ # Start receiver task — wait for system.require_init to trigger _do_init
1495
+ receiver_task = asyncio.create_task(_ws_receiver(ws))
1496
+ try:
1497
+ await receiver_task
1498
+ finally:
1499
+ receiver_task.cancel()
1500
+ for fut in _pending_rpc.values():
1501
+ if not fut.done():
1502
+ fut.set_result({"error": {"code": -32001, "message": "Connection lost"}})
1503
+ _pending_rpc.clear()
1504
+
1505
+
1506
+ if __name__ == "__main__":
1507
+ _startup_config = _validate_and_prepare()
1508
+ asyncio.run(main())
1509
+ sys.exit(_exit_code)