@agentunion/kite 1.4.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (718) hide show
  1. package/.claude/skills/kite/checklists/feature-checklist.md +496 -0
  2. package/.claude/skills/kite/references/event-patterns.md +180 -0
  3. package/.claude/skills/kite/references/health-check.md +202 -0
  4. package/.claude/skills/kite/references/http-service.md +199 -0
  5. package/.claude/skills/kite/references/module-md-spec.md +172 -0
  6. package/.claude/skills/kite/references/multi-connection.md +147 -0
  7. package/.claude/skills/kite/references/rpc-patterns.md +199 -0
  8. package/.claude/skills/kite/references/shutdown-sequence.md +146 -0
  9. package/.claude/skills/kite/references/stdin-protocol.md +147 -0
  10. package/.claude/skills/kite/references/test-center-integration.md +178 -0
  11. package/.claude/skills/kite/references/ws-lifecycle.md +301 -0
  12. package/.claude/skills/kite/skill.md +272 -0
  13. package/.claude/skills/kite/templates/go/README.md +20 -0
  14. package/.claude/skills/kite/templates/node/entry.js +134 -0
  15. package/.claude/skills/kite/templates/node/module.md +16 -0
  16. package/.claude/skills/kite/templates/node/server.js +351 -0
  17. package/.claude/skills/kite/templates/node/server_http.js +90 -0
  18. package/.claude/skills/kite/templates/python/entry.py +425 -0
  19. package/.claude/skills/kite/templates/python/module.md +26 -0
  20. package/.claude/skills/kite/templates/python/server.py +447 -0
  21. package/.claude/skills/kite/templates/python/server_http.py +433 -0
  22. package/CHANGELOG.md +102 -0
  23. package/cli.js +78 -5
  24. package/core/dependency_checker.py +250 -0
  25. package/core/env_checker.py +586 -0
  26. package/dependencies_lock.json +128 -0
  27. package/docs/05-/347/237/255/344/277/241/350/256/244/350/257/201/344/270/216/347/224/250/346/210/267/344/277/241/346/201/257/346/216/245/345/217/243/346/226/207/346/241/243.md +507 -0
  28. package/docs/ACP/345/215/217/350/256/256/345/205/274/345/256/271/346/226/271/346/241/210.md +138 -0
  29. package/docs/CI/344/270/216AI/350/207/252/345/212/250/345/214/226/346/265/213/350/257/225/346/226/271/346/241/210.md +75 -0
  30. package/docs/CLI/345/274/200/345/217/221/350/256/241/345/210/222.md +595 -0
  31. package/docs/ClaudeCode/350/277/234/347/250/213/345/215/217/344/275/234/347/263/273/347/273/237-/346/212/200/346/234/257/350/257/204/344/274/260.md +535 -0
  32. package/docs/ClaudeCode/350/277/234/347/250/213/345/215/217/344/275/234/347/263/273/347/273/237/350/256/276/350/256/241.md +631 -0
  33. package/docs/Evol-App/344/275/277/347/224/250KernelClient/346/224/271/351/200/240/345/256/214/346/210/220.md +342 -0
  34. package/docs/Evol/346/216/247/345/210/266/345/217/260/346/217/222/344/273/266/345/214/226/346/236/266/346/236/204/346/246/202/350/246/201.md +604 -0
  35. package/docs/Evol/346/216/247/345/210/266/345/217/260/346/217/222/344/273/266/345/214/226/346/236/266/346/236/204/350/256/276/350/256/241.md +1708 -0
  36. package/docs/Evol/346/250/241/345/235/227/350/256/276/350/256/241/346/226/271/346/241/210.md +1154 -0
  37. package/docs/Evol/351/241/265/351/235/242/346/217/222/344/273/266/345/214/226-Evol/346/250/241/345/235/227/345/256/236/346/226/275/346/214/207/345/215/227.md +403 -0
  38. package/docs/Evol/351/241/265/351/235/242/346/217/222/344/273/266/345/214/226-/345/244/226/351/203/250/346/250/241/345/235/227/346/216/245/345/205/245/346/214/207/345/215/227.md +468 -0
  39. package/docs/HTTP-RPC/350/277/201/347/247/273/345/210/260WebSocket/350/256/241/345/210/222.md +318 -0
  40. package/docs/INDEX.md +388 -0
  41. package/docs/KITE_DOCS_GUIDE.md +33 -0
  42. package/docs/Kernel-Client-Kite-Token/346/224/257/346/214/201/345/256/236/346/226/275/345/256/214/346/210/220.md +330 -0
  43. package/docs/Kernel/344/270/273/345/212/250Ping/346/234/272/345/210/266-/346/255/243/347/241/256/345/256/236/347/216/260.md +235 -0
  44. package/docs/Kernel/344/270/273/345/212/250Ping/346/234/272/345/210/266/345/256/236/346/226/275/346/200/273/347/273/223.md +204 -0
  45. package/docs/Kite/345/256/211/350/243/205/351/227/256/351/242/230/350/247/243/345/206/263/346/226/271/346/241/210.md +362 -0
  46. package/docs/Kite/346/216/247/345/210/266/345/217/260/346/217/222/344/273/266/345/214/226/346/236/266/346/236/204/350/256/276/350/256/241-/347/273/210/346/236/201/347/233/256/346/240/207.md +721 -0
  47. package/docs/Kite/346/216/247/345/210/266/345/217/260/347/273/237/344/270/200WebSocket/346/224/271/351/200/240/346/226/271/346/241/210.md +821 -0
  48. package/docs/Kite/346/241/206/346/236/266/350/256/276/350/256/241/01-/346/241/206/346/236/266/345/256/232/344/275/215.md +12 -0
  49. package/docs/Kite/346/241/206/346/236/266/350/256/276/350/256/241/02-/346/240/270/345/277/203/346/246/202/345/277/265.md +341 -0
  50. package/docs/Kite/346/241/206/346/236/266/350/256/276/350/256/241/03-/347/263/273/347/273/237/346/236/266/346/236/204.md +257 -0
  51. package/docs/Kite/346/241/206/346/236/266/350/256/276/350/256/241/04-/346/250/241/345/235/227/350/247/204/350/214/203.md +263 -0
  52. package/docs/Kite/346/241/206/346/236/266/350/256/276/350/256/241/05-/346/240/270/345/277/203/346/265/201/347/250/213-/346/226/260/347/211/210.md +267 -0
  53. package/docs/Kite/346/241/206/346/236/266/350/256/276/350/256/241/05-/346/240/270/345/277/203/346/265/201/347/250/213.md +149 -0
  54. package/docs/Kite/346/241/206/346/236/266/350/256/276/350/256/241/06-/347/233/256/345/275/225/347/273/223/346/236/204.md +231 -0
  55. package/docs/Kite/346/241/206/346/236/266/350/256/276/350/256/241/07-/346/225/260/346/215/256/346/250/241/345/236/213.md +68 -0
  56. package/docs/Kite/346/241/206/346/236/266/350/256/276/350/256/241/08-/346/211/251/345/261/225/346/200/247.md +34 -0
  57. package/docs/Kite/346/241/206/346/236/266/350/256/276/350/256/241/09-/344/270/216/345/205/267/344/275/223/345/272/224/347/224/250/347/232/204/345/205/263/347/263/273.md +22 -0
  58. package/docs/Kite/346/241/206/346/236/266/350/256/276/350/256/241/README.md +46 -0
  59. package/docs/Kite/347/263/273/347/273/237/345/220/257/345/212/250/346/265/201/347/250/213.md +567 -0
  60. package/docs/Launcher/345/220/257/345/212/250/345/231/250/346/226/207/346/241/243.md +745 -0
  61. package/docs/Polyglot/350/277/220/350/241/214/346/227/266/344/270/216Clawdbot/345/205/274/345/256/271/346/200/247/350/256/276/350/256/241.md +321 -0
  62. package/docs/Redis/344/270/216/346/250/241/345/235/227/345/244/232/345/256/236/344/276/213/346/226/271/346/241/210.md +438 -0
  63. package/docs/Relay-Kite-Token/350/256/244/350/257/201/345/256/236/346/226/275/345/256/214/346/210/220.md +178 -0
  64. package/docs/Relay-Token/346/235/203/351/231/220/351/205/215/347/275/256/351/252/214/350/257/201.md +113 -0
  65. package/docs/Watchdog/345/201/245/345/272/267/346/243/200/346/237/245/344/270/216WebSocket-Ping/346/234/272/345/210/266/345/210/206/346/236/220.md +367 -0
  66. package/docs/Watchdog/350/265/204/346/272/220/347/233/221/346/216/247/347/255/226/347/225/245.md +92 -0
  67. package/docs/WebSocket/346/216/245/346/224/266/345/276/252/347/216/257/346/255/273/351/224/201/351/230/262/350/214/203/350/247/204/350/214/203.md +357 -0
  68. package/docs/WebSocket/350/277/236/346/216/245/351/237/247/346/200/247/344/270/216/351/207/215/350/277/236/346/234/272/345/210/266/345/256/214/346/225/264/346/226/271/346/241/210.md +531 -0
  69. package/docs/WebSocket/350/277/236/346/216/245/351/237/247/346/200/247/346/226/271/346/241/210.md +169 -0
  70. package/docs/WebSocket/351/207/215/350/277/236/346/234/272/345/210/266/346/265/213/350/257/225/346/212/245/345/221/212.md +169 -0
  71. package/docs/WebSocket/351/207/215/350/277/236/351/200/200/351/201/277/346/234/272/345/210/266/346/226/271/346/241/210.md +394 -0
  72. package/docs/Web/346/250/241/345/235/227/344/270/216Evol/346/250/241/345/235/227/351/207/215/346/236/204/345/210/206/346/236/220.md +521 -0
  73. package/docs/audit-api-guide.md +68 -0
  74. package/docs/audit-module-design.md +315 -0
  75. package/docs/audit-module-implementation-summary.md +149 -0
  76. package/docs/llm-context-design.md +52 -0
  77. package/docs/llm-test-enhancement-plan.md +970 -0
  78. package/docs/logs-api-guide.md +42 -0
  79. package/docs/npm/345/214/205Python/347/216/257/345/242/203/347/256/241/347/220/206/346/226/271/346/241/210.md +302 -0
  80. package/docs/npm/345/217/221/345/270/203/344/270/216CLI/344/275/277/347/224/250/346/214/207/345/215/227.md +245 -0
  81. package/docs/stdio/344/270/216/347/253/257/345/217/243/345/217/221/347/216/260/351/207/215/346/236/204.md +480 -0
  82. package/docs/web/346/250/241/345/235/227/344/270/255/350/275/254/346/234/215/345/212/241/350/256/276/350/256/241/346/226/271/346/241/210.md +449 -0
  83. package/docs//344/272/213/344/273/266/345/244/204/347/220/206/346/234/272/345/210/266.md +388 -0
  84. package/docs//344/272/213/344/273/266/345/244/204/347/220/206/350/247/204/350/214/203.md +113 -0
  85. package/docs//344/272/213/344/273/266/350/256/242/351/230/205/351/200/232/351/205/215/347/254/246/350/247/204/350/214/203.md +256 -0
  86. package/docs//344/272/213/344/273/266/351/230/237/345/210/227/345/274/271/346/200/247/347/256/241/347/220/206.md +449 -0
  87. package/docs//344/272/244/344/272/222/345/274/217/347/273/210/347/253/257/346/216/247/345/210/266/346/226/271/346/241/210.md +301 -0
  88. package/docs//344/273/243/347/220/206/345/220/257/345/212/250/345/231/250/344/270/216/345/256/271/345/231/250/345/214/226.md +140 -0
  89. package/docs//344/273/243/347/240/201/347/273/237/350/256/241/345/267/245/345/205/267/344/275/277/347/224/250/350/257/264/346/230/216.md +217 -0
  90. package/docs//344/274/230/351/233/205/351/200/200/345/207/272/350/247/204/350/214/203.md +362 -0
  91. package/docs//344/276/235/350/265/226/347/256/241/347/220/206/350/257/264/346/230/216.md +141 -0
  92. package/docs//344/277/256/345/244/215/346/235/203/351/231/220/351/227/256/351/242/230-evol-RPC/346/235/203/351/231/220.md +268 -0
  93. package/docs//345/210/240/351/231/244kernel-client-example/345/256/214/346/210/220.md +309 -0
  94. package/docs//345/210/240/351/231/244ws-management/345/256/214/346/210/220.md +418 -0
  95. package/docs//345/220/257/345/212/250/344/274/230/345/214/226/346/226/271/346/241/210.md +522 -0
  96. package/docs//345/220/257/345/212/250/344/276/235/350/265/226/344/270/216/346/216/222/345/272/217.md +105 -0
  97. package/docs//345/256/211/350/243/205/350/204/232/346/234/254/345/274/200/345/217/221/346/226/207/346/241/243.md +643 -0
  98. package/docs//345/256/214/346/225/264/345/220/257/345/212/250/346/265/201/347/250/213/350/256/276/350/256/241.md +452 -0
  99. package/docs//345/256/236/347/216/260/350/247/204/345/210/222.md +195 -0
  100. package/docs//345/277/203/350/267/263/346/234/272/345/210/266/351/207/215/346/236/204/346/200/273/347/273/223.md +166 -0
  101. package/docs//346/217/241/346/211/213/350/256/244/350/257/201/346/226/271/346/241/210-/345/256/211/345/205/250/345/256/241/346/237/245.md +176 -0
  102. package/docs//346/217/241/346/211/213/350/256/244/350/257/201/346/226/271/346/241/210.md +908 -0
  103. package/docs//346/226/207/346/241/243/346/233/264/346/226/260/346/270/205/345/215/225.md +83 -0
  104. package/docs//346/227/245/345/277/227/344/270/216/345/274/202/345/270/270/345/244/204/347/220/206/350/247/204/350/214/203.md +829 -0
  105. package/docs//346/227/245/345/277/227/350/260/203/350/257/225/345/256/236/346/210/230/346/214/207/345/215/227.md +25 -0
  106. package/docs//346/236/266/346/236/204/345/200/237/351/211/264/346/214/207/345/215/227.md +977 -0
  107. package/docs//346/236/266/346/236/204/346/224/271/351/200/240-/345/256/214/346/210/220/346/200/273/347/273/223.md +440 -0
  108. package/docs//346/236/266/346/236/204/347/216/260/347/212/266/344/270/216/347/273/210/346/236/201/347/233/256/346/240/207/345/257/271/346/257/224/345/210/206/346/236/220.md +508 -0
  109. package/docs//346/250/241/345/235/227/345/244/232/350/277/236/346/216/245/346/216/247/345/210/266/347/255/226/347/225/245.md +220 -0
  110. package/docs//346/250/241/345/235/227/345/256/211/350/243/205/346/234/272/345/210/266/350/256/276/350/256/241.md +500 -0
  111. package/docs//346/250/241/345/235/227/345/274/200/345/217/221/346/214/207/345/215/227.md +1824 -0
  112. package/docs//346/250/241/345/235/227/347/203/255/346/233/264/346/226/260.md +89 -0
  113. package/docs//346/250/241/345/235/227/350/277/234/347/250/213/351/203/250/347/275/262/345/274/200/345/217/221/350/247/204/350/214/203.md +460 -0
  114. package/docs//346/250/241/345/235/227/351/200/200/345/207/272/346/234/272/345/210/266/345/256/214/346/225/264/346/226/271/346/241/210.md +303 -0
  115. package/docs//346/250/241/345/235/227/351/205/215/347/275/256/345/212/240/350/275/275/344/270/216/347/203/255/351/207/215/350/275/275/350/247/204/350/214/203.md +369 -0
  116. package/docs//346/265/213/350/257/225/344/270/255/345/277/203/346/267/273/345/212/240/346/250/241/345/235/227/346/265/213/350/257/225/346/214/207/345/215/227.md +147 -0
  117. package/docs//347/211/210/346/234/254/351/224/201/345/256/232/347/216/257/345/242/203/347/256/241/347/220/206/346/226/271/346/241/210.md +331 -0
  118. package/docs//347/216/257/345/242/203/345/217/230/351/207/217/344/270/216/350/277/220/350/241/214/346/227/266/347/233/256/345/275/225/350/256/276/350/256/241.md +499 -0
  119. package/docs//347/216/257/345/242/203/347/256/241/347/220/206/345/256/214/346/225/264/346/226/271/346/241/210.md +334 -0
  120. package/docs//350/231/232/346/213/237/346/250/241/345/235/227/344/270/255/350/275/254/346/234/215/345/212/241/345/256/214/346/225/264/350/256/276/350/256/241.md +1496 -0
  121. package/docs//350/231/232/346/213/237/347/216/257/345/242/203/345/267/245/344/275/234/345/216/237/347/220/206.md +163 -0
  122. package/docs//350/256/241/345/210/222/347/256/241/347/220/206/345/231/250/344/275/277/347/224/250/346/214/207/345/215/227.md +196 -0
  123. package/docs//350/256/244/350/257/201/346/250/241/345/235/227/344/270/216Gateway/350/256/276/350/256/241/346/226/271/346/241/210.md +765 -0
  124. package/docs//350/277/234/347/250/213/346/250/241/345/235/227/350/256/276/350/256/241-/346/227/247/347/211/210.md +1117 -0
  125. package/docs//350/277/234/347/250/213/346/250/241/345/235/227/350/256/276/350/256/241.md +451 -0
  126. package/docs//351/207/215/346/236/204/346/234/272/345/210/266/346/270/205/345/215/225.md +192 -0
  127. package/docs//351/223/276/350/267/257/350/277/275/350/270/252/346/226/271/346/241/210.md +242 -0
  128. package/docs//351/231/215/347/272/247/347/255/226/347/225/245/350/256/276/350/256/241/346/226/271/346/241/210.md +618 -0
  129. package/extensions/agents/assistant/entry.py +113 -14
  130. package/extensions/agents/assistant/module.md +27 -22
  131. package/extensions/agents/assistant/server.py +308 -106
  132. package/extensions/channels/acp_channel/entry.py +114 -16
  133. package/extensions/channels/acp_channel/module.md +4 -0
  134. package/extensions/channels/acp_channel/server.py +412 -105
  135. package/extensions/channels/phone_channel/__init__.py +1 -0
  136. package/extensions/channels/phone_channel/entry.py +503 -0
  137. package/extensions/channels/phone_channel/module.md +31 -0
  138. package/extensions/channels/phone_channel/server.py +686 -0
  139. package/extensions/event_hub_bench/entry.py +55 -12
  140. package/extensions/event_hub_bench/module.md +27 -27
  141. package/extensions/services/audit/README.md +134 -0
  142. package/extensions/services/audit/collector.py +73 -0
  143. package/extensions/services/audit/entry.py +444 -0
  144. package/extensions/services/audit/module.md +66 -0
  145. package/extensions/services/audit/query_audit.py +111 -0
  146. package/extensions/services/audit/routes/__init__.py +1 -0
  147. package/extensions/services/audit/routes/routes_audit.py +113 -0
  148. package/extensions/services/audit/schemas/__init__.py +5 -0
  149. package/extensions/services/audit/schemas/audit_event.py +92 -0
  150. package/extensions/services/audit/server.py +542 -0
  151. package/extensions/services/audit/storage.py +95 -0
  152. package/extensions/services/auth/entry.py +1054 -0
  153. package/extensions/services/auth/module.md +31 -0
  154. package/extensions/services/auth/token_store.py +185 -0
  155. package/extensions/services/auth/verifiers/evol_account.py +101 -0
  156. package/extensions/services/auth/verifiers/kite_token.py +38 -0
  157. package/extensions/services/auth/verifiers/pairing_code.py +71 -0
  158. package/extensions/services/backup/entry.py +505 -201
  159. package/extensions/services/backup/module.md +4 -2
  160. package/extensions/services/dataclaw/api/__init__.py +0 -0
  161. package/extensions/services/dataclaw/api/admin.py +367 -0
  162. package/extensions/services/dataclaw/api/copyright.py +175 -0
  163. package/extensions/services/dataclaw/api/credits.py +177 -0
  164. package/extensions/services/dataclaw/api/data.py +179 -0
  165. package/extensions/services/dataclaw/api/demands.py +269 -0
  166. package/extensions/services/dataclaw/api/feeds.py +262 -0
  167. package/extensions/services/dataclaw/api/identity.py +505 -0
  168. package/extensions/services/dataclaw/api/notifications.py +104 -0
  169. package/extensions/services/dataclaw/api/reviews.py +138 -0
  170. package/extensions/services/dataclaw/api/search.py +153 -0
  171. package/extensions/services/dataclaw/api/subscriptions.py +157 -0
  172. package/extensions/services/dataclaw/config.json5 +96 -0
  173. package/extensions/services/dataclaw/core/__init__.py +0 -0
  174. package/extensions/services/dataclaw/core/auth.py +95 -0
  175. package/extensions/services/dataclaw/core/config.py +50 -0
  176. package/extensions/services/dataclaw/core/database.py +70 -0
  177. package/extensions/services/dataclaw/entry.py +416 -0
  178. package/extensions/services/dataclaw/gofeed/351/241/271/347/233/256/346/211/200/346/234/211/346/235/203/350/275/254/347/247/273/346/265/201/347/250/213/350/257/264/346/230/216.md +309 -0
  179. package/extensions/services/dataclaw/migrate.py +283 -0
  180. package/extensions/services/dataclaw/models/__init__.py +0 -0
  181. package/extensions/services/dataclaw/module.md +49 -0
  182. package/extensions/services/dataclaw/requirements.txt +18 -0
  183. package/extensions/services/dataclaw/server.py +759 -0
  184. package/extensions/services/dataclaw/services/__init__.py +0 -0
  185. package/extensions/services/dataclaw/services/agent_service.py +132 -0
  186. package/extensions/services/dataclaw/services/credit_service.py +235 -0
  187. package/extensions/services/dataclaw/services/email_service.py +140 -0
  188. package/extensions/services/dataclaw/services/feed_service.py +259 -0
  189. package/extensions/services/dataclaw/services/notification_service.py +209 -0
  190. package/extensions/services/dataclaw/services/oauth_service.py +275 -0
  191. package/extensions/services/dataclaw/services/pricing.py +102 -0
  192. package/extensions/services/dataclaw/services/quality.py +79 -0
  193. package/extensions/services/dataclaw/services/reputation.py +142 -0
  194. package/extensions/services/dataclaw/services/sms_service.py +174 -0
  195. package/extensions/services/dataclaw/static/css/common.css +853 -0
  196. package/extensions/services/dataclaw/static/css/themes/blue.css +42 -0
  197. package/extensions/services/dataclaw/static/css/themes/dark.css +42 -0
  198. package/extensions/services/dataclaw/static/css/themes/light.css +35 -0
  199. package/extensions/services/dataclaw/static/js/api.js +103 -0
  200. package/extensions/services/dataclaw/static/js/common.js +321 -0
  201. package/extensions/services/dataclaw/static/js/i18n.js +95 -0
  202. package/extensions/services/dataclaw/static/js/pages/admin.js +152 -0
  203. package/extensions/services/dataclaw/static/js/pages/dashboard.js +82 -0
  204. package/extensions/services/dataclaw/static/js/pages/feed-detail.js +180 -0
  205. package/extensions/services/dataclaw/static/js/pages/feed-manage.js +158 -0
  206. package/extensions/services/dataclaw/static/js/theme.js +46 -0
  207. package/extensions/services/dataclaw/static/locales/en-US.json +464 -0
  208. package/extensions/services/dataclaw/static/locales/ja-JP.json +464 -0
  209. package/extensions/services/dataclaw/static/locales/zh-CN.json +464 -0
  210. package/extensions/services/dataclaw/templates/admin/index.html +90 -0
  211. package/extensions/services/dataclaw/templates/base.html +136 -0
  212. package/extensions/services/dataclaw/templates/credits/balance.html +106 -0
  213. package/extensions/services/dataclaw/templates/credits/deposit.html +164 -0
  214. package/extensions/services/dataclaw/templates/credits/history.html +90 -0
  215. package/extensions/services/dataclaw/templates/dashboard.html +52 -0
  216. package/extensions/services/dataclaw/templates/demands/create.html +78 -0
  217. package/extensions/services/dataclaw/templates/demands/detail.html +136 -0
  218. package/extensions/services/dataclaw/templates/demands/list.html +94 -0
  219. package/extensions/services/dataclaw/templates/feeds/create.html +95 -0
  220. package/extensions/services/dataclaw/templates/feeds/detail.html +110 -0
  221. package/extensions/services/dataclaw/templates/feeds/list.html +110 -0
  222. package/extensions/services/dataclaw/templates/feeds/manage.html +88 -0
  223. package/extensions/services/dataclaw/templates/index.html +185 -0
  224. package/extensions/services/dataclaw/templates/login.html +246 -0
  225. package/extensions/services/dataclaw/templates/register.html +164 -0
  226. package/extensions/services/dataclaw/templates/settings/notifications.html +96 -0
  227. package/extensions/services/dataclaw/templates/settings/profile.html +167 -0
  228. package/extensions/services/dataclaw/templates/subscriptions/list.html +64 -0
  229. package/extensions/services/dataclaw/tests/__init__.py +0 -0
  230. package/extensions/services/dataclaw/tests/conftest.py +68 -0
  231. package/extensions/services/dataclaw/tests/integration/__init__.py +0 -0
  232. package/extensions/services/dataclaw/tests/integration/test_workflows.py +239 -0
  233. package/extensions/services/dataclaw/tests/unit/__init__.py +0 -0
  234. package/extensions/services/dataclaw/tests/unit/test_admin.py +70 -0
  235. package/extensions/services/dataclaw/tests/unit/test_copyright.py +63 -0
  236. package/extensions/services/dataclaw/tests/unit/test_credits.py +80 -0
  237. package/extensions/services/dataclaw/tests/unit/test_data.py +98 -0
  238. package/extensions/services/dataclaw/tests/unit/test_demands.py +106 -0
  239. package/extensions/services/dataclaw/tests/unit/test_feeds.py +98 -0
  240. package/extensions/services/dataclaw/tests/unit/test_identity.py +88 -0
  241. package/extensions/services/dataclaw/tests/unit/test_notifications.py +36 -0
  242. package/extensions/services/dataclaw/tests/unit/test_reviews.py +68 -0
  243. package/extensions/services/dataclaw/tests/unit/test_search.py +64 -0
  244. package/extensions/services/dataclaw/tests/unit/test_subscriptions.py +65 -0
  245. package/extensions/services/dataclaw/tests/unit/test_system.py +106 -0
  246. package/extensions/services/dataclaw/utils/__init__.py +0 -0
  247. package/extensions/services/dataclaw/utils/crypto.py +38 -0
  248. package/extensions/services/dataclaw/utils/id_generator.py +52 -0
  249. package/extensions/services/dataclaw/ws/__init__.py +0 -0
  250. package/extensions/services/dataclaw/ws/handler.py +163 -0
  251. package/extensions/services/dataclaw//345/215/217/350/256/2561-/351/241/271/347/233/256/346/235/241/344/273/266/346/216/210/346/235/203/344/270/216/350/202/241/346/235/203/345/257/271/344/273/267/345/215/217/350/256/256.md +243 -0
  252. package/extensions/services/dataclaw//345/215/217/350/256/2562-/351/241/271/347/233/256/350/264/255/344/271/260/346/235/203/344/270/216/345/244/226/345/214/205/345/247/224/346/211/230/345/274/200/345/217/221/345/215/217/350/256/256.md +434 -0
  253. package/extensions/services/evol/__init__.py +1 -0
  254. package/extensions/services/evol/async_http.py +551 -0
  255. package/extensions/services/evol/auth_manager.py +602 -0
  256. package/extensions/services/evol/config.json5 +16 -0
  257. package/extensions/services/evol/config_loader.py +117 -0
  258. package/extensions/services/evol/entry.py +568 -0
  259. package/extensions/services/evol/evol_api.py +969 -0
  260. package/extensions/services/evol/evol_config.json5 +29 -0
  261. package/extensions/services/evol/mfa_totp.py +77 -0
  262. package/extensions/services/evol/migrate_tokens.py +122 -0
  263. package/extensions/services/evol/module.md +150 -0
  264. package/extensions/services/evol/nonce_pool.py +113 -0
  265. package/extensions/services/evol/oauth_manager.py +223 -0
  266. package/extensions/services/evol/pairing.py +251 -0
  267. package/extensions/services/evol/pairing_codes.jsonl +2 -0
  268. package/extensions/services/evol/relay.py +1031 -0
  269. package/extensions/services/evol/relay_config.json5 +85 -0
  270. package/extensions/services/evol/routes/__init__.py +1 -0
  271. package/extensions/services/evol/routes/routes_llm.py +231 -0
  272. package/extensions/services/evol/routes/routes_rpc.py +90 -0
  273. package/extensions/services/evol/routes/routes_test.py +68 -0
  274. package/extensions/services/evol/server.py +2426 -0
  275. package/extensions/services/evol/static/assets/CommissionView-Cs_ys6Gm.js +1 -0
  276. package/extensions/services/evol/static/assets/CommissionView-DACet_Oo.css +1 -0
  277. package/extensions/services/evol/static/assets/IframePage-DbO11U9G.js +1 -0
  278. package/extensions/services/evol/static/assets/IframePage-c572lT8i.css +1 -0
  279. package/extensions/services/evol/static/assets/TeamDetailView-DULrGD7k.css +1 -0
  280. package/extensions/services/evol/static/assets/TeamDetailView-gy_MBEqG.js +139 -0
  281. package/extensions/services/evol/static/assets/element-plus-Bd7pZkkM.js +63 -0
  282. package/extensions/services/evol/static/assets/index-CmMONKzG.css +1 -0
  283. package/extensions/services/evol/static/assets/index-D44bBe__.js +2 -0
  284. package/extensions/services/evol/static/assets/vue-vendor-DtF-__I4.js +29 -0
  285. package/extensions/services/evol/static/index.html +16 -0
  286. package/extensions/services/evol/static/logo.png +0 -0
  287. package/extensions/services/evol/stats_manager.py +243 -0
  288. package/extensions/services/evol/web/README.md +89 -0
  289. package/extensions/services/evol/web/build.bat +44 -0
  290. package/extensions/services/evol/web/index.html +13 -0
  291. package/extensions/services/evol/web/package-lock.json +1718 -0
  292. package/extensions/services/evol/web/package.json +26 -0
  293. package/extensions/services/evol/web/public/logo.png +0 -0
  294. package/extensions/services/evol/web/src/App.vue +7 -0
  295. package/extensions/services/evol/web/src/components/layout/AppHeader.vue +202 -0
  296. package/extensions/services/evol/web/src/components/layout/AppLayout.vue +61 -0
  297. package/extensions/services/evol/web/src/components/layout/AppSidebar.vue +115 -0
  298. package/extensions/services/evol/web/src/components/login/LoginPage.vue +271 -0
  299. package/extensions/services/evol/web/src/components/team/AddMemberModal.vue +181 -0
  300. package/extensions/services/evol/web/src/components/team/GroupTreeNode.vue +156 -0
  301. package/extensions/services/evol/web/src/components/team/TeamAlertConfig.vue +221 -0
  302. package/extensions/services/evol/web/src/components/team/TeamBillModal.vue +165 -0
  303. package/extensions/services/evol/web/src/components/team/TeamMembersAndGroups.vue +499 -0
  304. package/extensions/services/evol/web/src/components/team/TeamStatsPanel.vue +907 -0
  305. package/extensions/services/evol/web/src/components/team/TreeNode.vue +331 -0
  306. package/extensions/services/evol/web/src/components/team/stats/StatsExportProgress.vue +44 -0
  307. package/extensions/services/evol/web/src/components/team/stats/StatsHeader.vue +89 -0
  308. package/extensions/services/evol/web/src/components/team/stats/StatsMemberDetail.vue +415 -0
  309. package/extensions/services/evol/web/src/components/team/stats/StatsSummary.vue +42 -0
  310. package/extensions/services/evol/web/src/components/team/stats/helpers.ts +195 -0
  311. package/extensions/services/evol/web/src/components/team/stats/stats.css +741 -0
  312. package/extensions/services/evol/web/src/components/team/stats/useStatsApi.ts +114 -0
  313. package/extensions/services/evol/web/src/components/team/stats/useStatsCharts.ts +242 -0
  314. package/extensions/services/evol/web/src/components/team/stats/useStatsExport.ts +232 -0
  315. package/extensions/services/evol/web/src/composables/useFormatters.ts +42 -0
  316. package/extensions/services/evol/web/src/composables/useTheme.ts +52 -0
  317. package/extensions/services/evol/web/src/env.d.ts +7 -0
  318. package/extensions/services/evol/web/src/i18n/en.ts +361 -0
  319. package/extensions/services/evol/web/src/i18n/index.ts +36 -0
  320. package/extensions/services/evol/web/src/i18n/zh.ts +379 -0
  321. package/extensions/services/evol/web/src/main.ts +21 -0
  322. package/extensions/services/evol/web/src/router/index.ts +81 -0
  323. package/extensions/services/evol/web/src/services/kernel-client.ts +406 -0
  324. package/extensions/services/evol/web/src/stores/auth.ts +189 -0
  325. package/extensions/services/evol/web/src/stores/connection.ts +134 -0
  326. package/extensions/services/evol/web/src/stores/pages.ts +79 -0
  327. package/extensions/services/evol/web/src/styles/base.css +213 -0
  328. package/extensions/services/evol/web/src/styles/variables.css +138 -0
  329. package/extensions/services/evol/web/src/types/rpc.ts +35 -0
  330. package/extensions/services/evol/web/src/types/token.ts +87 -0
  331. package/extensions/services/evol/web/src/views/AccountView.vue +1532 -0
  332. package/extensions/services/evol/web/src/views/AiServiceView.vue +219 -0
  333. package/extensions/services/evol/web/src/views/CommissionView.vue +1220 -0
  334. package/extensions/services/evol/web/src/views/CreditsView.vue +131 -0
  335. package/extensions/services/evol/web/src/views/EndpointView.vue +163 -0
  336. package/extensions/services/evol/web/src/views/IframePage.vue +120 -0
  337. package/extensions/services/evol/web/src/views/TeamDetailView.vue +473 -0
  338. package/extensions/services/evol/web/src/views/TeamView.vue +332 -0
  339. package/extensions/services/evol/web/tsconfig.json +31 -0
  340. package/extensions/services/evol/web/tsconfig.node.json +10 -0
  341. package/extensions/services/evol/web/vite.config.ts +49 -0
  342. package/extensions/services/evolmem/__init__.py +0 -0
  343. package/extensions/services/evolmem/entry.py +387 -0
  344. package/extensions/services/evolmem/hooks/__init__.py +0 -0
  345. package/extensions/services/evolmem/hooks/assistant_stop.py +228 -0
  346. package/extensions/services/evolmem/hooks/common.py +76 -0
  347. package/extensions/services/evolmem/hooks/pre_tool_use.py +56 -0
  348. package/extensions/services/evolmem/hooks/session_end.py +133 -0
  349. package/extensions/services/evolmem/hooks/session_start.py +229 -0
  350. package/extensions/services/evolmem/hooks/user_prompt.py +122 -0
  351. package/extensions/services/evolmem/module.md +48 -0
  352. package/extensions/services/evolmem/prompts/00-server-info.md +28 -0
  353. package/extensions/services/evolmem/prompts/01-behavior.md +46 -0
  354. package/extensions/services/evolmem/prompts/02-summary-format.md +112 -0
  355. package/extensions/services/evolmem/prompts/03-file-query.md +92 -0
  356. package/extensions/services/evolmem/prompts/04-topic-stats.md +11 -0
  357. package/extensions/services/evolmem/prompts/05-recent-topics.md +84 -0
  358. package/extensions/services/evolmem/scripts/__init__.py +0 -0
  359. package/extensions/services/evolmem/scripts/extract_keywords.py +40 -0
  360. package/extensions/services/evolmem/scripts/search_topics.py +91 -0
  361. package/extensions/services/evolmem/server.py +641 -0
  362. package/extensions/services/gateway/entry.py +964 -0
  363. package/extensions/services/gateway/module.md +29 -0
  364. package/extensions/services/gateway/nonce_pool.py +65 -0
  365. package/extensions/services/gateway/relay.py +133 -0
  366. package/extensions/services/gateway/ws_server.py +285 -0
  367. package/extensions/services/kite_console/auth_manager.py +603 -0
  368. package/extensions/services/kite_console/config.json5 +19 -0
  369. package/extensions/services/kite_console/config_loader.py +117 -0
  370. package/extensions/services/kite_console/entry.py +528 -0
  371. package/extensions/services/kite_console/evol_api.py +179 -0
  372. package/extensions/services/kite_console/evol_config.json5 +29 -0
  373. package/extensions/services/kite_console/mfa_totp.py +77 -0
  374. package/extensions/services/kite_console/migrate_tokens.py +122 -0
  375. package/extensions/services/kite_console/module.md +37 -0
  376. package/extensions/services/kite_console/nonce_pool.py +113 -0
  377. package/extensions/services/kite_console/oauth_manager.py +223 -0
  378. package/extensions/services/kite_console/pairing.py +280 -0
  379. package/extensions/services/kite_console/pairing_codes.jsonl +2 -0
  380. package/extensions/services/kite_console/relay.py +1350 -0
  381. package/extensions/services/kite_console/relay_config.json5 +96 -0
  382. package/extensions/services/kite_console/routes/__init__.py +1 -0
  383. package/extensions/services/kite_console/routes/routes_llm.py +231 -0
  384. package/extensions/services/kite_console/routes/routes_proxy.py +115 -0
  385. package/extensions/services/kite_console/routes/routes_rpc.py +89 -0
  386. package/extensions/services/kite_console/routes/routes_test.py +68 -0
  387. package/extensions/services/kite_console/server.py +1742 -0
  388. package/extensions/services/kite_console/static/css/style.css +1854 -0
  389. package/extensions/services/kite_console/static/index.html +1524 -0
  390. package/extensions/services/kite_console/static/js/dialog.js +292 -0
  391. package/extensions/services/kite_console/static/js/evol-app.js +7740 -0
  392. package/extensions/services/kite_console/static/js/evol-app.js.backup +2777 -0
  393. package/extensions/services/kite_console/static/js/kernel-client.js +560 -0
  394. package/extensions/services/kite_console/static/js/kernel-client.js.backup +434 -0
  395. package/extensions/services/kite_console/static/js/registry-tests.js +592 -0
  396. package/extensions/services/kite_console/static/js/tests/ARCHITECTURE.md +67 -0
  397. package/extensions/services/kite_console/static/js/tests/README.md +140 -0
  398. package/extensions/services/kite_console/static/js/tests/index.js +161 -0
  399. package/extensions/services/kite_console/static/js/tests/integration/auth.js +120 -0
  400. package/extensions/services/kite_console/static/js/tests/integration/channel-interaction.js +188 -0
  401. package/extensions/services/kite_console/static/js/tests/integration/elastic-connection.js +115 -0
  402. package/extensions/services/kite_console/static/js/tests/integration/full-workflow.js +43 -0
  403. package/extensions/services/kite_console/static/js/tests/integration/multi-instance.js +304 -0
  404. package/extensions/services/kite_console/static/js/tests/integration/nested-rpc.js +266 -0
  405. package/extensions/services/kite_console/static/js/tests/integration/pingpong.js +25 -0
  406. package/extensions/services/kite_console/static/js/tests/integration/redis.js +227 -0
  407. package/extensions/services/kite_console/static/js/tests/integration/registry-core.js +52 -0
  408. package/extensions/services/kite_console/static/js/tests/integration/remote-deploy.js +85 -0
  409. package/extensions/services/kite_console/static/js/tests/integration/require-init.js +96 -0
  410. package/extensions/services/kite_console/static/js/tests/integration/scaling-control.js +193 -0
  411. package/extensions/services/kite_console/static/js/tests/integration/trace.js +109 -0
  412. package/extensions/services/kite_console/static/js/tests/modules/acp_channel.js +339 -0
  413. package/extensions/services/kite_console/static/js/tests/modules/auth.js +96 -0
  414. package/extensions/services/kite_console/static/js/tests/modules/backup.js +49 -0
  415. package/extensions/services/kite_console/static/js/tests/modules/gateway.js +41 -0
  416. package/extensions/services/kite_console/static/js/tests/modules/kernel.js +90 -0
  417. package/extensions/services/kite_console/static/js/tests/modules/launcher.js +75 -0
  418. package/extensions/services/kite_console/static/js/tests/modules/multi_instance.js +129 -0
  419. package/extensions/services/kite_console/static/js/tests/modules/phone_channel.js +364 -0
  420. package/extensions/services/kite_console/static/js/tests/modules/redis.js +178 -0
  421. package/extensions/services/kite_console/static/js/tests/modules/watchdog.js +60 -0
  422. package/extensions/services/kite_console/static/js/tests/modules/web.js +70 -0
  423. package/extensions/services/kite_console/static/js/tests/test-runner.js +123 -0
  424. package/extensions/services/kite_console/static/js/virtual-list.js +200 -0
  425. package/extensions/services/kite_console/static/pairing.html +248 -0
  426. package/extensions/services/kite_console/static/test_kernel_client_token.html +352 -0
  427. package/extensions/services/kite_console/static/test_registry.html +262 -0
  428. package/extensions/services/kite_console/static/test_relay.html +462 -0
  429. package/extensions/services/kite_console/stats_manager.py +247 -0
  430. package/extensions/services/logs/README.md +215 -0
  431. package/extensions/services/logs/api_logger.py +37 -0
  432. package/extensions/services/logs/baseline.py +121 -0
  433. package/extensions/services/logs/cleaner.py +76 -0
  434. package/extensions/services/logs/entry.py +449 -0
  435. package/extensions/services/logs/formatter.py +129 -0
  436. package/extensions/services/logs/module.md +38 -0
  437. package/extensions/services/logs/quick_diagnostic.py +128 -0
  438. package/extensions/services/logs/routes/__init__.py +1 -0
  439. package/extensions/services/logs/routes/routes_logs.py +218 -0
  440. package/extensions/services/logs/routes/routes_logs.py.backup +173 -0
  441. package/extensions/services/logs/scanner.py +100 -0
  442. package/extensions/services/logs/searcher.py +263 -0
  443. package/extensions/services/logs/server.py +553 -0
  444. package/extensions/services/logs.zip +0 -0
  445. package/extensions/services/model_service/config.json5 +30 -0
  446. package/extensions/services/model_service/entry.py +633 -162
  447. package/extensions/services/model_service/module.md +11 -2
  448. package/extensions/services/proxy/.claude/settings.local.json +13 -0
  449. package/extensions/services/proxy/__init__.py +0 -0
  450. package/extensions/services/proxy/agentcp/LICENCE +178 -0
  451. package/extensions/services/proxy/agentcp/README copy.md +85 -0
  452. package/extensions/services/proxy/agentcp/README.md +260 -0
  453. package/extensions/services/proxy/agentcp/__init__.py +16 -0
  454. package/extensions/services/proxy/agentcp/agent.py +4 -0
  455. package/extensions/services/proxy/agentcp/agentcp.py +2494 -0
  456. package/extensions/services/proxy/agentcp/agentprofile.json +89 -0
  457. package/extensions/services/proxy/agentcp/ap/__init__.py +16 -0
  458. package/extensions/services/proxy/agentcp/ap/ap_client.py +316 -0
  459. package/extensions/services/proxy/agentcp/assets/images/wechat_qr.png +0 -0
  460. package/extensions/services/proxy/agentcp/backup/metrics.json +31 -0
  461. package/extensions/services/proxy/agentcp/base/__init__.py +20 -0
  462. package/extensions/services/proxy/agentcp/base/auth_client.py +257 -0
  463. package/extensions/services/proxy/agentcp/base/client.py +112 -0
  464. package/extensions/services/proxy/agentcp/base/env.py +34 -0
  465. package/extensions/services/proxy/agentcp/base/html_util.py +336 -0
  466. package/extensions/services/proxy/agentcp/base/log.py +98 -0
  467. package/extensions/services/proxy/agentcp/ca/__init__.py +17 -0
  468. package/extensions/services/proxy/agentcp/ca/ca_client.py +414 -0
  469. package/extensions/services/proxy/agentcp/ca/ca_root.py +74 -0
  470. package/extensions/services/proxy/agentcp/context/__init__.py +20 -0
  471. package/extensions/services/proxy/agentcp/context/context.py +73 -0
  472. package/extensions/services/proxy/agentcp/context/exceptions.py +114 -0
  473. package/extensions/services/proxy/agentcp/create_profile.py +125 -0
  474. package/extensions/services/proxy/agentcp/create_profile_weather.py +125 -0
  475. package/extensions/services/proxy/agentcp/db/__init__.py +15 -0
  476. package/extensions/services/proxy/agentcp/db/db_mananger.py +550 -0
  477. package/extensions/services/proxy/agentcp/docs/UDP_HEARTBEAT_FIX_REPORT.md +265 -0
  478. package/extensions/services/proxy/agentcp/docs/heartbeat_issue_analysis.md +291 -0
  479. package/extensions/services/proxy/agentcp/file/__init__.py +16 -0
  480. package/extensions/services/proxy/agentcp/file/file_client.py +141 -0
  481. package/extensions/services/proxy/agentcp/file/wss_binary_message.py +137 -0
  482. package/extensions/services/proxy/agentcp/hcp.py +299 -0
  483. package/extensions/services/proxy/agentcp/heartbeat/__init__.py +16 -0
  484. package/extensions/services/proxy/agentcp/heartbeat/heartbeat_client.py +360 -0
  485. package/extensions/services/proxy/agentcp/improved_scheduler.py +498 -0
  486. package/extensions/services/proxy/agentcp/llm_agent_utils.py +249 -0
  487. package/extensions/services/proxy/agentcp/llm_server.py +172 -0
  488. package/extensions/services/proxy/agentcp/mermaid.py +210 -0
  489. package/extensions/services/proxy/agentcp/message.py +149 -0
  490. package/extensions/services/proxy/agentcp/metrics.py +256 -0
  491. package/extensions/services/proxy/agentcp/monitoring/__init__.py +20 -0
  492. package/extensions/services/proxy/agentcp/monitoring/global_monitor.py +27 -0
  493. package/extensions/services/proxy/agentcp/monitoring/metrics_store.py +325 -0
  494. package/extensions/services/proxy/agentcp/monitoring/monitoring_service.py +269 -0
  495. package/extensions/services/proxy/agentcp/monitoring/sliding_window.py +222 -0
  496. package/extensions/services/proxy/agentcp/monitoring/standalone_reader.py +224 -0
  497. package/extensions/services/proxy/agentcp/msg/__init__.py +21 -0
  498. package/extensions/services/proxy/agentcp/msg/connection_manager.py +456 -0
  499. package/extensions/services/proxy/agentcp/msg/message_client.py +2058 -0
  500. package/extensions/services/proxy/agentcp/msg/message_serialize.py +263 -0
  501. package/extensions/services/proxy/agentcp/msg/open_ai_message.py +88 -0
  502. package/extensions/services/proxy/agentcp/msg/session_manager.py +1062 -0
  503. package/extensions/services/proxy/agentcp/msg/stream_client.py +267 -0
  504. package/extensions/services/proxy/agentcp/msg/websocket_file_receiver.py +89 -0
  505. package/extensions/services/proxy/agentcp/msg/ws_logger.py +685 -0
  506. package/extensions/services/proxy/agentcp/msg/wss_binary_message.py +137 -0
  507. package/extensions/services/proxy/agentcp/requirements.txt +7 -0
  508. package/extensions/services/proxy/agentcp/samples/agent_graph/README.md +37 -0
  509. package/extensions/services/proxy/agentcp/samples/agent_graph/agentprofile.json +89 -0
  510. package/extensions/services/proxy/agentcp/samples/agent_graph/create_profile.py +138 -0
  511. package/extensions/services/proxy/agentcp/samples/agent_graph/main.py +164 -0
  512. package/extensions/services/proxy/agentcp/samples/agent_use/create_profile.py +123 -0
  513. package/extensions/services/proxy/agentcp/samples/agent_use/llm/create_profile.py +129 -0
  514. package/extensions/services/proxy/agentcp/samples/agent_use/llm/env.json +5 -0
  515. package/extensions/services/proxy/agentcp/samples/agent_use/llm/main.py +146 -0
  516. package/extensions/services/proxy/agentcp/samples/agent_use/main.py +123 -0
  517. package/extensions/services/proxy/agentcp/samples/agent_use/readme.md +379 -0
  518. package/extensions/services/proxy/agentcp/samples/agent_use/search/create_profile.py +129 -0
  519. package/extensions/services/proxy/agentcp/samples/agent_use/search/main.py +28 -0
  520. package/extensions/services/proxy/agentcp/samples/agent_use/tool/create_profile.py +129 -0
  521. package/extensions/services/proxy/agentcp/samples/agent_use/tool/main.py +20 -0
  522. package/extensions/services/proxy/agentcp/samples/ali_amap/README.md +97 -0
  523. package/extensions/services/proxy/agentcp/samples/ali_amap/amap_agent.py +88 -0
  524. package/extensions/services/proxy/agentcp/samples/ali_amap/create_profile.py +125 -0
  525. package/extensions/services/proxy/agentcp/samples/compute_agent/agent/powershell.py +228 -0
  526. package/extensions/services/proxy/agentcp/samples/compute_agent/agent/software.py +63 -0
  527. package/extensions/services/proxy/agentcp/samples/compute_agent/agent/tools.py +36 -0
  528. package/extensions/services/proxy/agentcp/samples/compute_agent/browser_user.py +41 -0
  529. package/extensions/services/proxy/agentcp/samples/deepseek/README.md +79 -0
  530. package/extensions/services/proxy/agentcp/samples/deepseek/create_profile.py +126 -0
  531. package/extensions/services/proxy/agentcp/samples/deepseek/deepseek.py +42 -0
  532. package/extensions/services/proxy/agentcp/samples/dify_chat/README.md +78 -0
  533. package/extensions/services/proxy/agentcp/samples/dify_chat/create_profile.py +126 -0
  534. package/extensions/services/proxy/agentcp/samples/dify_chat/dify_chat.py +47 -0
  535. package/extensions/services/proxy/agentcp/samples/dify_workflow/README.md +78 -0
  536. package/extensions/services/proxy/agentcp/samples/dify_workflow/create_profile.py +126 -0
  537. package/extensions/services/proxy/agentcp/samples/dify_workflow/dify_workflow.py +46 -0
  538. package/extensions/services/proxy/agentcp/samples/executor/README.md +44 -0
  539. package/extensions/services/proxy/agentcp/samples/executor/agentprofile.json +89 -0
  540. package/extensions/services/proxy/agentcp/samples/executor/create_profile.py +139 -0
  541. package/extensions/services/proxy/agentcp/samples/executor/main.py +160 -0
  542. package/extensions/services/proxy/agentcp/samples/filereader/README.md +45 -0
  543. package/extensions/services/proxy/agentcp/samples/filereader/agentprofile.json +90 -0
  544. package/extensions/services/proxy/agentcp/samples/filereader/create_profile.py +137 -0
  545. package/extensions/services/proxy/agentcp/samples/filereader/main.py +253 -0
  546. package/extensions/services/proxy/agentcp/samples/filewriter/README.md +38 -0
  547. package/extensions/services/proxy/agentcp/samples/filewriter/agentprofile.json +91 -0
  548. package/extensions/services/proxy/agentcp/samples/filewriter/create_profile.py +138 -0
  549. package/extensions/services/proxy/agentcp/samples/filewriter/main.py +289 -0
  550. package/extensions/services/proxy/agentcp/samples/hcp/README.md +85 -0
  551. package/extensions/services/proxy/agentcp/samples/hcp/acp_weather_agent.zip +0 -0
  552. package/extensions/services/proxy/agentcp/samples/hcp/create_profile.py +125 -0
  553. package/extensions/services/proxy/agentcp/samples/hcp/hcp.py +237 -0
  554. package/extensions/services/proxy/agentcp/samples/helloworld/README.md +68 -0
  555. package/extensions/services/proxy/agentcp/samples/helloworld/hello_world.py +40 -0
  556. package/extensions/services/proxy/agentcp/samples/llm_agent/MEADME.md +117 -0
  557. package/extensions/services/proxy/agentcp/samples/llm_agent/create_profile.py +125 -0
  558. package/extensions/services/proxy/agentcp/samples/llm_agent/qwen_agent.py +136 -0
  559. package/extensions/services/proxy/agentcp/samples/local_llm_agent/README.md +90 -0
  560. package/extensions/services/proxy/agentcp/samples/local_llm_agent/create_profile.py +125 -0
  561. package/extensions/services/proxy/agentcp/samples/local_llm_agent/main.py +49 -0
  562. package/extensions/services/proxy/agentcp/samples/query_llm_from_agent/README.md +55 -0
  563. package/extensions/services/proxy/agentcp/samples/query_llm_from_agent/create_profile.py +125 -0
  564. package/extensions/services/proxy/agentcp/samples/query_llm_from_agent/main.py +23 -0
  565. package/extensions/services/proxy/agentcp/samples/query_weather_api_agent/README.md +103 -0
  566. package/extensions/services/proxy/agentcp/samples/query_weather_api_agent/create_profile.py +125 -0
  567. package/extensions/services/proxy/agentcp/samples/query_weather_api_agent/main.py +69 -0
  568. package/extensions/services/proxy/agentcp/samples/query_weather_from_agent/README.md +58 -0
  569. package/extensions/services/proxy/agentcp/samples/query_weather_from_agent/create_profile.py +125 -0
  570. package/extensions/services/proxy/agentcp/samples/query_weather_from_agent/main.py +25 -0
  571. package/extensions/services/proxy/agentcp/samples/qwen3/README.md +71 -0
  572. package/extensions/services/proxy/agentcp/samples/qwen3/create_profile.py +126 -0
  573. package/extensions/services/proxy/agentcp/samples/qwen3/qwen3.py +37 -0
  574. package/extensions/services/proxy/agentcp/samples/qwen3_tools/README.md +133 -0
  575. package/extensions/services/proxy/agentcp/samples/qwen3_tools/create_profile.py +126 -0
  576. package/extensions/services/proxy/agentcp/samples/qwen3_tools/qwen3_tools.py +98 -0
  577. package/extensions/services/proxy/agentcp/samples/search/create_profile_qwen.py +125 -0
  578. package/extensions/services/proxy/agentcp/samples/search/create_profile_search.py +125 -0
  579. package/extensions/services/proxy/agentcp/samples/search/qwen_agent.py +136 -0
  580. package/extensions/services/proxy/agentcp/samples/search/search_agent.py +170 -0
  581. package/extensions/services/proxy/agentcp/samples/wrapper_agently_to_agent/README.md +89 -0
  582. package/extensions/services/proxy/agentcp/samples/wrapper_agently_to_agent/create_profile.py +125 -0
  583. package/extensions/services/proxy/agentcp/samples/wrapper_agently_to_agent/main.py +44 -0
  584. package/extensions/services/proxy/agentcp/utils/__init__.py +15 -0
  585. package/extensions/services/proxy/agentcp/utils/file_util.py +117 -0
  586. package/extensions/services/proxy/agentcp/utils/proxy_bypass.py +99 -0
  587. package/extensions/services/proxy/agentcp/workflow.py +203 -0
  588. package/extensions/services/proxy/aid_manager.py +419 -0
  589. package/extensions/services/proxy/auth_bridge.py +182 -0
  590. package/extensions/services/proxy/config_store.py +79 -0
  591. package/extensions/services/proxy/entry.py +528 -0
  592. package/extensions/services/proxy/evol/__init__.py +1 -0
  593. package/extensions/services/proxy/evol/config.py +37 -0
  594. package/extensions/services/proxy/evol/http/__init__.py +1 -0
  595. package/extensions/services/proxy/evol/http/async_http.py +551 -0
  596. package/extensions/services/proxy/evol/log.py +28 -0
  597. package/extensions/services/proxy/evol/presenter/__init__.py +2 -0
  598. package/extensions/services/proxy/evol/presenter/agentIdPresenter.py +1031 -0
  599. package/extensions/services/proxy/evol/presenter/apikeyPresenter.py +96 -0
  600. package/extensions/services/proxy/evol/presenter/configPresenter.py +234 -0
  601. package/extensions/services/proxy/evol/presenter/userPresenter.py +71 -0
  602. package/extensions/services/proxy/evol/server/__init__.py +1 -0
  603. package/extensions/services/proxy/evol/server/claude_proxy_async.py +3434 -0
  604. package/extensions/services/proxy/evol/server/openclaw_proxy.py +1861 -0
  605. package/extensions/services/proxy/evol/server/proxy_config.py +15 -0
  606. package/extensions/services/proxy/evol/server/proxy_engine.py +501 -0
  607. package/extensions/services/proxy/evol/version.py +24 -0
  608. package/extensions/services/proxy/module.md +151 -0
  609. package/extensions/services/proxy/server.py +952 -0
  610. package/extensions/services/redis/ALIGNMENT_CHECKLIST.md +121 -0
  611. package/extensions/services/redis/ALIGNMENT_STATUS.md +548 -0
  612. package/extensions/services/redis/config.json5 +8 -0
  613. package/extensions/services/redis/entry.py +1509 -0
  614. package/extensions/services/redis/entry.py.backup +405 -0
  615. package/extensions/services/redis/module.md +48 -0
  616. package/extensions/services/redis/redis_builtin.py +332 -0
  617. package/extensions/services/redis/redis_external.py +164 -0
  618. package/extensions/services/testUi/entry.py +446 -0
  619. package/extensions/services/testUi/module.md +18 -0
  620. package/extensions/services/testUi/ui/cards.html +131 -0
  621. package/extensions/services/testUi/ui/index.html +22 -0
  622. package/extensions/services/testUi/ui/particles.html +143 -0
  623. package/extensions/services/watchdog/entry.py +1258 -767
  624. package/extensions/services/watchdog/module.md +3 -0
  625. package/extensions/services/watchdog/monitor.py +483 -75
  626. package/extensions/services/web/auth_manager.py +602 -0
  627. package/extensions/services/web/config.json5 +11 -0
  628. package/extensions/services/web/entry.py +598 -478
  629. package/extensions/services/web/mfa_totp.py +77 -0
  630. package/extensions/services/web/module.md +17 -14
  631. package/extensions/services/web/nonce_pool.py +113 -0
  632. package/extensions/services/web/oauth_manager.py +223 -0
  633. package/extensions/services/web/pairing.py +3 -2
  634. package/extensions/services/web/pairing_codes.jsonl +1 -0
  635. package/extensions/services/web/relay.py +442 -63
  636. package/extensions/services/web/relay_config.json5 +1 -2
  637. package/extensions/services/web/routes/routes_rpc.py +6 -6
  638. package/extensions/services/web/server.py +380 -181
  639. package/extensions/services/web/static/index.html +1752 -1738
  640. package/extensions/services/web/static/js/app.js +32 -0
  641. package/extensions/services/web/static/js/kernel-client.js +48 -9
  642. package/extensions/services/web/static/js/token-manager.js +10 -10
  643. package/extensions/services/web/vendor/bluetooth/audio.py +1 -1
  644. package/extensions/services/web/vendor/config.py +2 -2
  645. package/extensions/services/web/vendor/storage/identity.py +1 -1
  646. package/kernel/entry.py +77 -23
  647. package/kernel/event_hub.py +1122 -74
  648. package/kernel/module.md +26 -1
  649. package/kernel/registry_store.py +209 -36
  650. package/kernel/rpc_router.py +1400 -465
  651. package/kernel/server.py +1084 -108
  652. package/kite_cli/builders/__init__.py +4 -0
  653. package/kite_cli/builders/base.py +67 -0
  654. package/kite_cli/builders/custom.py +31 -0
  655. package/kite_cli/builders/detector.py +56 -0
  656. package/kite_cli/builders/go.py +34 -0
  657. package/kite_cli/builders/gradle.py +41 -0
  658. package/kite_cli/builders/maven.py +36 -0
  659. package/kite_cli/builders/npm.py +44 -0
  660. package/kite_cli/builders/python.py +37 -0
  661. package/kite_cli/commands/BUILD_GUIDE.md +109 -0
  662. package/kite_cli/commands/build.py +142 -0
  663. package/kite_cli/commands/check.py +60 -0
  664. package/kite_cli/commands/config.py +156 -0
  665. package/kite_cli/commands/deps.py +58 -0
  666. package/kite_cli/commands/deps_install.py +67 -0
  667. package/kite_cli/commands/disable.py +162 -0
  668. package/kite_cli/commands/enable.py +162 -0
  669. package/kite_cli/commands/env_check.py +45 -0
  670. package/kite_cli/commands/export.py +96 -0
  671. package/kite_cli/commands/import_cmd.py +110 -0
  672. package/kite_cli/commands/install.py +50 -23
  673. package/kite_cli/commands/install_skill.py +107 -0
  674. package/kite_cli/commands/list.py +128 -31
  675. package/kite_cli/commands/outdated.py +202 -0
  676. package/kite_cli/commands/prepare.py +49 -0
  677. package/kite_cli/commands/search.py +33 -17
  678. package/kite_cli/commands/update.py +115 -2
  679. package/kite_cli/commands/venv_setup.py +56 -0
  680. package/kite_cli/commands/why.py +48 -0
  681. package/kite_cli/core/config_manager.py +145 -0
  682. package/kite_cli/core/downloader.py +32 -2
  683. package/kite_cli/main.py +179 -5
  684. package/kite_cli/utils/colors.py +153 -0
  685. package/kite_cli/utils/dependency_graph.py +209 -0
  686. package/kite_cli/utils/process.py +55 -0
  687. package/kite_cli/utils/progress.py +207 -0
  688. package/kite_cli/utils/table.py +101 -0
  689. package/launcher/count_lines.py +192 -43
  690. package/launcher/entry.py +4543 -2517
  691. package/launcher/logging_setup.py +54 -1
  692. package/launcher/module.md +37 -2
  693. package/launcher/module_scanner.py +103 -20
  694. package/launcher/process_manager.py +355 -76
  695. package/main.py +10 -1
  696. package/package.json +11 -1
  697. package/python_version.json +4 -0
  698. package/requirements.txt +41 -0
  699. package/scripts/auto-fix-deps.py +128 -0
  700. package/scripts/env-manager.js +351 -0
  701. package/scripts/final-test.js +78 -0
  702. package/scripts/python-env.js +79 -0
  703. package/scripts/scan_dependencies.py +461 -0
  704. package/scripts/setup-python-env.js +700 -0
  705. package/scripts/test-alluser.js +48 -0
  706. package/scripts/test-different-version.js +86 -0
  707. package/scripts/test-direct.js +63 -0
  708. package/scripts/test-extract-installer.js +28 -0
  709. package/scripts/test-install-log.js +54 -0
  710. package/scripts/test-installer.js +39 -0
  711. package/scripts/test-integration.js +250 -0
  712. package/scripts/test-real-install.js +210 -0
  713. package/scripts/test-targetdir.js +49 -0
  714. package/scripts/test-venv-real.js +47 -0
  715. package/scripts/test-venv-simple.js +57 -0
  716. package/scripts/test-wait.js +49 -0
  717. package/scripts/test-with-log.js +63 -0
  718. package/extensions/services/web/config.yaml +0 -149
package/kernel/server.py CHANGED
@@ -7,21 +7,26 @@ Merges Registry + Event Hub into a single process.
7
7
  import asyncio
8
8
  import json
9
9
  import os
10
+ import time
10
11
 
11
12
  from fastapi import FastAPI
12
13
  from starlette.websockets import WebSocket, WebSocketDisconnect
13
14
 
14
15
  from .registry_store import RegistryStore
15
- from .event_hub import EventHub
16
+ from .event_hub import EventHub, instance_key, parse_instance_key
16
17
  from .rpc_router import RpcRouter
17
18
 
18
19
  try:
19
20
  import orjson
20
21
  def _loads(raw: str):
21
22
  return orjson.loads(raw)
23
+ def _dumps(obj) -> str:
24
+ return orjson.dumps(obj).decode('utf-8')
22
25
  except ImportError:
23
26
  def _loads(raw: str):
24
27
  return json.loads(raw)
28
+ def _dumps(obj) -> str:
29
+ return json.dumps(obj)
25
30
 
26
31
 
27
32
  class KernelServer:
@@ -33,20 +38,33 @@ class KernelServer:
33
38
  - Event notifications (delivered to subscribers)
34
39
  """
35
40
 
36
- def __init__(self, launcher_token: str = None, advertise_ip: str = "127.0.0.1", module_id: str = None):
41
+ def __init__(self, launcher_token: str = None, advertise_ip: str = "127.0.0.1", module_id: str = None, boot_t0: float = 0, is_recover: bool = False):
37
42
  if module_id is None:
38
43
  raise ValueError("module_id is required")
39
44
  self.module_id = module_id
40
45
  self.advertise_ip = advertise_ip
41
46
  self.port: int = 0 # set by entry.py before uvicorn.run
47
+ self.boot_t0 = boot_t0 # Startup time for ready event
48
+ self.is_recover = is_recover # 恢复模式标记,用于 module.ready reason 字段
49
+
50
+ # TLS configuration
51
+ # 默认值根据环境变量:开发环境允许 WS,生产环境强制 WSS
52
+ env = os.environ.get("KITE_ENV", "development").lower()
53
+ self.require_tls = os.environ.get("KITE_REQUIRE_TLS", "true" if env == "production" else "false").lower() == "true"
42
54
 
43
55
  # Core components
44
56
  self.registry = RegistryStore(launcher_token) # Can be None
45
57
  self.event_hub = EventHub()
46
58
 
47
- # Shared connection table (module_id -> WebSocket)
59
+ # Shared connection table (module_id -> {slot -> WebSocket})
48
60
  # RpcRouter and EventHub both reference this
49
- self.connections: dict[str, WebSocket] = {}
61
+ self.connections: dict[str, dict[int, WebSocket]] = {}
62
+
63
+ # ── 多连接状态 ──
64
+ self._module_conn_elastic: dict[str, bool] = {} # mid → 是否弹性连接
65
+ self._offer_tasks: dict[str, asyncio.Task] = {} # mid → offer task
66
+ self._shrink_tasks: dict[str, asyncio.Task] = {} # mid → shrink task
67
+ self._release_timers: dict[str, asyncio.Task] = {} # mid → release timer
50
68
 
51
69
  # RPC router (pass self reference)
52
70
  self.rpc_router = RpcRouter(
@@ -57,7 +75,6 @@ class KernelServer:
57
75
  )
58
76
 
59
77
  # Background tasks
60
- self._ttl_task: asyncio.Task | None = None
61
78
  self._dedup_task: asyncio.Task | None = None
62
79
  self._uvicorn_server = None # set by entry.py for graceful shutdown
63
80
  self._shutting_down = False
@@ -67,19 +84,67 @@ class KernelServer:
67
84
  self._launcher_subscribed = False
68
85
  self._ready_published = False
69
86
 
87
+ # 恢复模式 require_init 门控:关闭时暂缓发送,等 Launcher 就绪后通过 RPC 打开
88
+ self._init_gate_open = True # 正常启动时打开,恢复模式由 entry.py 设为 False
89
+ self._init_pending: list[tuple] = [] # 门控关闭时暂存的 (module_id, ws) 对
90
+
70
91
  # Subscribe to events that Kernel needs to handle
71
92
  # Kernel 通过订阅机制接收事件(与其他模块一致)
72
- self.event_hub.handle_subscribe(self.module_id, ["module.shutdown"])
93
+ self.event_hub.handle_subscribe(self.module_id, ["module.shutdown", "system.pong", "system.ready"])
73
94
 
74
95
  # Register internal event callback for Kernel
75
96
  # Kernel 自身没有 WebSocket 连接,通过回调机制接收订阅的事件
76
97
  self.event_hub.register_internal_callback(self.module_id, self._handle_internal_event)
77
98
 
99
+ # 正在退出的模块集合(收到 module.shutdown 但尚未全部断开)
100
+ self._modules_exiting: set[str] = set()
101
+
78
102
  # Debounce timers for disconnected modules (module_id -> asyncio.Task)
79
103
  self._debounce_tasks: dict[str, asyncio.Task] = {}
80
104
  # Launcher loss timer (35s after launcher offline)
81
105
  self._launcher_loss_task: asyncio.Task | None = None
82
106
 
107
+ # Ping/Pong tracking
108
+ self._ping_sent_times: dict[str, float] = {} # module_id -> last ping sent time (t1)
109
+ self._pong_latencies: dict[str, dict] = {} # module_id -> {"outbound": ms, "inbound": ms, "last_update": timestamp}
110
+ self._pong_status: dict[str, str] = {} # module_id -> "ok" | "timeout" | "never"
111
+ self._ping_timeout_counts: dict[str, int] = {} # module_id -> consecutive timeout count
112
+ self._ping_task: asyncio.Task | None = None # Global ping broadcast task
113
+ self._first_ping_sent = False # 标记是否已发送第一次 ping
114
+ self._kernel_ping: float | None = None # kernel 自己的 ping 值(所有模块的最小值)
115
+
116
+ # ── Instance scaler (auto-scaling) ──
117
+ self._scaler_task: asyncio.Task | None = None
118
+ # 全局扩缩容冻结开关(Launcher 通过 kernel.set_scaling RPC 控制)
119
+ # 冻结时:_scaler_tick 不执行任何扩缩容操作
120
+ self._scaling_frozen: bool = False
121
+ # module_id → {max_instances, instance_elastic} from module.ready
122
+ self._module_instance_config: dict[str, dict] = {}
123
+ # module_id → timestamp when all instances first went overload
124
+ self._scale_overload_since: dict[str, float] = {}
125
+ # module_id → timestamp when instances first went idle (with count > 1)
126
+ self._scale_idle_since: dict[str, float] = {}
127
+ # module_id → no scaling actions until this time
128
+ self._scale_cooldown_until: dict[str, float] = {}
129
+ # module_id → number of instances pending startup (requested but not yet ready)
130
+ self._scale_pending: dict[str, int] = {}
131
+ # 防止 _scaler_tick 并发执行(多个 apply_module_config 可能同时触发)
132
+ self._scaler_running: bool = False
133
+ # rpc_id → asyncio.Future for Kernel→Launcher RPC responses
134
+ self._rpc_futures: dict[str, asyncio.Future] = {}
135
+
136
+ # 动态 ping 间隔控制
137
+ self._ping_interval = 60.0 # 当前 ping 间隔(秒),默认 60 秒
138
+ self._last_latencies_call = 0.0 # 最后一次调用 kernel.latencies 的时间
139
+ self._ping_interval_event = asyncio.Event() # 用于通知间隔变化
140
+
141
+ # Debug API (仅在 KITE_DEBUG=1 时启用)
142
+ self.debug_app: FastAPI | None = None
143
+ self.debug_port: int = 0
144
+ self._debug_server = None
145
+ if os.environ.get("KITE_DEBUG") == "1":
146
+ self.debug_app = self._create_debug_app()
147
+
83
148
  # Build FastAPI app
84
149
  self.app = self._create_app()
85
150
 
@@ -92,67 +157,190 @@ class KernelServer:
92
157
  @app.on_event("startup")
93
158
  async def _startup():
94
159
  server.event_hub.start_internal_senders()
95
- server._ttl_task = asyncio.create_task(server._ttl_loop())
96
160
  server._dedup_task = asyncio.create_task(server._dedup_loop())
161
+ server._ping_task = asyncio.create_task(server._ping_broadcast_loop())
162
+ server._scaler_task = asyncio.create_task(server._instance_scaler_loop())
97
163
 
98
164
  @app.on_event("shutdown")
99
165
  async def _shutdown():
100
- if server._ttl_task:
101
- server._ttl_task.cancel()
102
166
  if server._dedup_task:
103
167
  server._dedup_task.cancel()
168
+ if server._ping_task:
169
+ server._ping_task.cancel()
170
+ if server._scaler_task:
171
+ server._scaler_task.cancel()
104
172
 
105
173
  # ── WebSocket endpoint ──
106
174
 
107
175
  @app.websocket("/ws")
108
176
  async def ws_endpoint(ws: WebSocket):
109
- token = ws.query_params.get("token", "")
110
177
  mid_hint = ws.query_params.get("id", "")
111
178
 
112
- # Token verification (all modules including Launcher need token)
113
- module_id = server.registry.verify_token(token)
114
- if module_id is None:
115
- # Must accept before close (Starlette requirement)
116
- await ws.accept()
117
- print(f"[kernel] Auth failed: token={token[:8]}... hint={mid_hint}")
179
+ # Check TLS requirement
180
+ if server.require_tls:
181
+ # Check if connection is secure (WSS)
182
+ # WebSocket.url.scheme will be "wss" for secure connections
183
+ if ws.url.scheme != "wss":
184
+ await ws.close(code=1008, reason="TLS required")
185
+ print(f"[kernel] Rejected non-TLS connection from {ws.client.host if ws.client else 'unknown'}")
186
+ return
187
+
188
+ # Accept connection first (auth via first message)
189
+ await ws.accept()
190
+
191
+ # 关闭中拒绝新连接
192
+ if server._shutting_down:
193
+ await ws.close(code=1001, reason="Server shutting down")
194
+ return
195
+
196
+ # 速率限制检查(本地连接豁免,基于 module_id hint,无 hint 时用客户端 IP)
197
+ client_host = ws.client.host if ws.client else "unknown"
198
+ rate_key = mid_hint or client_host
199
+ is_local = client_host in ("127.0.0.1", "::1", "localhost")
200
+ if not is_local and not server.registry.check_auth_rate_limit(rate_key):
201
+ print(f"[kernel] Auth rate limited: key={rate_key}")
202
+ await ws.send_text(_dumps({
203
+ "jsonrpc": "2.0", "id": None,
204
+ "error": {"code": -32003, "message": "Rate limit exceeded"}
205
+ }))
206
+ await ws.close(code=4020, reason="Rate limit exceeded")
207
+ return
208
+
209
+ # Wait for auth message (5s timeout)
210
+ module_id = None
211
+ try:
212
+ raw = await asyncio.wait_for(ws.receive_text(), timeout=5.0)
213
+ auth_msg = _loads(raw)
214
+
215
+ # Validate auth request format
216
+ if (auth_msg.get("jsonrpc") != "2.0" or
217
+ auth_msg.get("method") != "auth" or
218
+ "params" not in auth_msg or
219
+ "token" not in auth_msg["params"]):
220
+ await ws.send_text(_dumps({
221
+ "jsonrpc": "2.0",
222
+ "id": auth_msg.get("id"),
223
+ "error": {"code": -32600, "message": "Invalid auth request"}
224
+ }))
225
+ await ws.close(code=4001, reason="Invalid auth request")
226
+ return
227
+
228
+ # Verify token
229
+ token = auth_msg["params"]["token"]
230
+ module_id = server.registry.verify_token(token)
231
+ slot = 0 # 默认 slot 0(主连接)
232
+ is_slot_auth = False
233
+
234
+ if module_id is None:
235
+ # 尝试 slot token 认证
236
+ slot_result = server.registry.verify_slot_token(token)
237
+ if slot_result:
238
+ module_id, slot = slot_result
239
+ is_slot_auth = True
240
+ else:
241
+ print(f"[kernel] Auth failed: token={token[:8]}... hint={mid_hint}")
242
+ await ws.send_text(_dumps({
243
+ "jsonrpc": "2.0",
244
+ "id": auth_msg.get("id"),
245
+ "error": {"code": -32003, "message": "Authentication failed"}
246
+ }))
247
+ await ws.close(code=4001, reason="Authentication failed")
248
+ return
249
+
250
+ # Use id hint for debug mode
251
+ if module_id == "debug" and mid_hint:
252
+ module_id = mid_hint
253
+
254
+ # inst_key is encoded in the token: instance 1 → "acp_channel", instance 2 → "acp_channel#2"
255
+ # parse_instance_key extracts (base_module_id, instance_num) so module_id stays clean
256
+ module_id, instance_num = parse_instance_key(module_id)
257
+ inst_key = instance_key(module_id, instance_num)
258
+
259
+ # For slot token auth, verify_slot_token returns the full inst_key (e.g. "acp_channel#2")
260
+ # parse_instance_key above already reconstructs inst_key correctly — no override needed
261
+
262
+ # Register connection in both EventHub and shared connections table
263
+ server.event_hub.add_connection(inst_key, ws, slot=slot)
264
+ if inst_key not in server.connections:
265
+ server.connections[inst_key] = {}
266
+ server.connections[inst_key][slot] = ws
267
+
268
+ # Get session info for auth response
269
+ session_id = server.event_hub._session_ids.get(inst_key, "")
270
+ current_seq = server.event_hub._session_seq.get(inst_key, 0)
271
+
272
+ # Send auth success response with session info
273
+ auth_result = {
274
+ "ok": True,
275
+ "session_id": session_id,
276
+ "seq": current_seq
277
+ }
278
+ if is_slot_auth:
279
+ auth_result["slot"] = slot
280
+ await ws.send_text(_dumps({
281
+ "jsonrpc": "2.0",
282
+ "id": auth_msg.get("id"),
283
+ "result": auth_result
284
+ }))
285
+
286
+ except asyncio.TimeoutError:
287
+ print(f"[kernel] Auth timeout: hint={mid_hint}")
118
288
  try:
119
- await ws.close(code=4001, reason="Authentication failed")
289
+ await ws.close(code=4002, reason="Auth timeout")
290
+ except Exception:
291
+ pass
292
+ return
293
+ except Exception as e:
294
+ print(f"[kernel] Auth error: {e}")
295
+ try:
296
+ await ws.close(code=4001, reason="Auth error")
120
297
  except Exception:
121
298
  pass
122
299
  return
123
300
 
124
- # Use id hint for debug mode
125
- if module_id == "debug" and mid_hint:
126
- module_id = mid_hint
127
-
128
- await ws.accept()
129
-
130
- # Cancel debounce timer if module is reconnecting within 5s window
131
- old_debounce = server._debounce_tasks.pop(module_id, None)
132
- if old_debounce:
133
- old_debounce.cancel()
134
- print(f"[kernel] {module_id} reconnected within debounce window")
135
-
136
- # Register connection in both EventHub and shared connections table
137
- server.event_hub.add_connection(module_id, ws)
138
- server.connections[module_id] = ws
139
-
140
- # Set connected status in registry (if module exists)
141
- server.registry.set_connected(module_id)
142
-
143
- # Track Launcher connection
144
- if module_id == "launcher":
145
- server._launcher_connected = True
146
- # Cancel launcher loss timer if reconnecting
147
- if server._launcher_loss_task:
148
- server._launcher_loss_task.cancel()
149
- server._launcher_loss_task = None
150
- print(f"[kernel] launcher reconnected, cancelled loss timer")
151
- print(f"[kernel] launcher connected")
152
-
153
- # Renew heartbeat on connect
154
- if module_id in server.registry.modules:
155
- server.registry.heartbeat(module_id)
301
+ # 主连接(slot 0):取消 debounce、更新状态、检查是否需要 require_init
302
+ if not is_slot_auth:
303
+ # Cancel debounce timer if instance is reconnecting within 5s window
304
+ old_debounce = server._debounce_tasks.pop(inst_key, None)
305
+ if old_debounce:
306
+ old_debounce.cancel()
307
+ print(f"[kernel] {inst_key} reconnected within debounce window")
308
+
309
+ # Set connected status in registry (module-level)
310
+ server.registry.set_connected(module_id)
311
+
312
+ # Track Launcher connection
313
+ if module_id == "launcher":
314
+ server._launcher_connected = True
315
+ if server._launcher_loss_task:
316
+ server._launcher_loss_task.cancel()
317
+ server._launcher_loss_task = None
318
+ print(f"[kernel] launcher reconnected, cancelled loss timer")
319
+
320
+ # 检查该实例是否需要注册/订阅(system.require_init)
321
+ if server.registry.needs_init(inst_key):
322
+ if server._init_gate_open or module_id == "launcher":
323
+ try:
324
+ await ws.send_text(_dumps({
325
+ "jsonrpc": "2.0",
326
+ "method": "event",
327
+ "params": {
328
+ "event": "system.require_init",
329
+ "source": server.module_id,
330
+ "data": {"module_id": module_id, "instance_num": instance_num},
331
+ },
332
+ }))
333
+ print(f"[kernel] Sent system.require_init to {inst_key}")
334
+ except Exception as e:
335
+ print(f"[kernel] Failed to send system.require_init to {inst_key}: {e}")
336
+ else:
337
+ # 门控关闭(恢复模式),暂存待 Launcher 就绪后统一发送
338
+ server._init_pending.append((inst_key, ws))
339
+ print(f"[kernel] Deferred system.require_init for {inst_key} (gate closed)")
340
+
341
+ # Initialize ping status for new connection (per instance_key)
342
+ if inst_key not in server._pong_status:
343
+ server._pong_status[inst_key] = "never"
156
344
 
157
345
  try:
158
346
  while True:
@@ -173,6 +361,20 @@ class KernelServer:
173
361
  if not isinstance(msg, dict):
174
362
  continue
175
363
 
364
+ # JSON-RPC 2.0 协议版本校验
365
+ if msg.get("jsonrpc") != "2.0":
366
+ msg_id = msg.get("id")
367
+ if msg_id is not None:
368
+ try:
369
+ await ws.send_text(json.dumps({
370
+ "jsonrpc": "2.0", "id": msg_id,
371
+ "error": {"code": -32600,
372
+ "message": 'Invalid Request: missing or wrong "jsonrpc" field, must be "2.0"'}
373
+ }))
374
+ except Exception:
375
+ pass
376
+ continue
377
+
176
378
  # Classify message type:
177
379
  has_method = "method" in msg
178
380
  has_id = "id" in msg
@@ -180,11 +382,22 @@ class KernelServer:
180
382
  has_error = "error" in msg
181
383
 
182
384
  if has_method and has_id:
183
- # RPC Request → dispatch to handler
184
- await server.rpc_router.dispatch(module_id, ws, msg)
385
+ # RPC Request → dispatch to handler (use inst_key as caller)
386
+ await server.rpc_router.dispatch(inst_key, ws, msg)
185
387
  elif has_id and (has_result or has_error):
186
- # RPC Response match to pending forward
187
- await server.rpc_router.handle_response(module_id, msg)
388
+ # Check if this is a response to Kernel's own RPC call
389
+ msg_id = msg.get("id", "")
390
+ if isinstance(msg_id, str) and msg_id.startswith("kernel-") and msg_id in server._rpc_futures:
391
+ result = msg.get("result")
392
+ error = msg.get("error")
393
+ if error:
394
+ err_msg = error.get("message", str(error)) if isinstance(error, dict) else str(error)
395
+ server._resolve_rpc_future(msg_id, error=err_msg)
396
+ else:
397
+ server._resolve_rpc_future(msg_id, result=result)
398
+ else:
399
+ # RPC Response → match to pending forward
400
+ await server.rpc_router.handle_response(inst_key, msg)
188
401
  # else: notification or unknown — ignore
189
402
 
190
403
  except WebSocketDisconnect:
@@ -192,21 +405,39 @@ class KernelServer:
192
405
  except Exception as e:
193
406
  err = str(e).lower()
194
407
  if "not connected" not in err and "closed" not in err:
195
- print(f"[kernel] WebSocket error for {module_id}: {e}")
408
+ print(f"[kernel] WebSocket error for {inst_key}: {e}")
196
409
  finally:
197
- # Cleanup connection but DON'T immediately set offline — debounce
198
- server.event_hub.remove_connection(module_id)
199
- server.connections.pop(module_id, None)
200
-
201
- # Cancel existing debounce for this module (if reconnecting fast)
202
- old_task = server._debounce_tasks.pop(module_id, None)
203
- if old_task:
204
- old_task.cancel()
205
-
206
- # Start 5s debounce timer
207
- server._debounce_tasks[module_id] = asyncio.create_task(
208
- server._debounce_offline(module_id)
209
- )
410
+ # 清理该 slot 的连接(使用 inst_key)
411
+ server.event_hub.remove_connection(inst_key, slot=slot)
412
+ slots_dict = server.connections.get(inst_key, {})
413
+ slots_dict.pop(slot, None)
414
+ if not slots_dict:
415
+ server.connections.pop(inst_key, None)
416
+
417
+ # 检查是否所有 slot 都已断开
418
+ remaining = server.connections.get(inst_key, {})
419
+ if not remaining:
420
+ # 所有 slot 断开 → 清理退出标记、启动 debounce
421
+ server._modules_exiting.discard(inst_key)
422
+ old_task = server._debounce_tasks.pop(inst_key, None)
423
+ if old_task:
424
+ old_task.cancel()
425
+ server._debounce_tasks[inst_key] = asyncio.create_task(
426
+ server._debounce_offline(inst_key)
427
+ )
428
+ else:
429
+ # 部分 slot 断开 → 不触发 debounce
430
+ # 冻结或模块正在退出则跳过 recovery offer
431
+ if server._scaling_frozen or inst_key in server._modules_exiting:
432
+ pass
433
+ # 非弹性模式:发 recovery offer 补充断掉的 slot
434
+ elif not server._module_conn_elastic.get(inst_key, False):
435
+ max_conns = server._get_max_connections(inst_key)
436
+ if max_conns > 1 and len(remaining) < max_conns:
437
+ missing = [s for s in range(1, max_conns)
438
+ if s not in remaining]
439
+ if missing:
440
+ server._send_connection_offer(inst_key, missing)
210
441
 
211
442
  # ── HTTP endpoints (debug only) ──
212
443
 
@@ -241,22 +472,295 @@ class KernelServer:
241
472
 
242
473
  return app
243
474
 
475
+ def _create_debug_app(self) -> FastAPI:
476
+ """创建 Debug API(仅在 KITE_DEBUG=1 时使用)"""
477
+ app = FastAPI(title="Kite Kernel Debug API", docs_url=None, redoc_url=None)
478
+ server = self
479
+
480
+ @app.get("/health")
481
+ async def debug_health():
482
+ """健康检查 — 返回 Kernel 是否 ready"""
483
+ return {
484
+ "status": "ready" if server._ready_published else "starting",
485
+ "module_count": len(server.registry.modules),
486
+ "online_count": sum(
487
+ 1 for m in server.registry.modules.values()
488
+ if m.get("status") in ("registered", "ready")
489
+ ),
490
+ }
491
+
492
+ @app.get("/status")
493
+ async def debug_status():
494
+ """详细状态 — 返回所有模块状态和事件统计"""
495
+ import time as _time
496
+ return {
497
+ "kernel": {
498
+ "ready": server._ready_published,
499
+ "port": server.port,
500
+ "advertise_ip": server.advertise_ip,
501
+ "boot_time": server.event_hub._start_time,
502
+ "uptime_seconds": round(_time.time() - server.event_hub._start_time),
503
+ },
504
+ "registry": {
505
+ "modules": {
506
+ mid: {
507
+ "status": data.get("status"),
508
+ "module_type": data.get("module_type"),
509
+ "registered_at": data.get("registered_at"),
510
+ "max_connections": data.get("max_connections"),
511
+ "port": data.get("port"),
512
+ }
513
+ for mid, data in server.registry.modules.items()
514
+ },
515
+ },
516
+ "event_hub": server.event_hub.get_stats(),
517
+ }
518
+
519
+ @app.get("/registry")
520
+ async def debug_registry():
521
+ """注册中心详情 — 返回所有模块的完整注册信息"""
522
+ return {
523
+ "modules": {
524
+ mid: {
525
+ "status": data.get("status"),
526
+ "module_type": data.get("module_type"),
527
+ "registered_at": data.get("registered_at"),
528
+ "max_connections": data.get("max_connections"),
529
+ "port": data.get("port"),
530
+ "base_url": data.get("base_url"),
531
+ "tools": list(data.get("tools", {}).keys()),
532
+ "events_subscribe": data.get("events_subscribe", []),
533
+ "events_publish": list(data.get("events_publish", {}).keys()),
534
+ "launcher_id": data.get("launcher_id"),
535
+ }
536
+ for mid, data in server.registry.modules.items()
537
+ },
538
+ "module_count": len(server.registry.modules),
539
+ "last_update_time": server.registry.last_update_time,
540
+ }
541
+
542
+ @app.get("/ws-url")
543
+ async def debug_ws_url():
544
+ """返回 Kernel WebSocket 连接地址"""
545
+ return {
546
+ "ws_url": f"ws://{server.advertise_ip}:{server.port}/ws",
547
+ "port": server.port,
548
+ }
549
+
550
+ @app.post("/shutdown")
551
+ async def debug_shutdown():
552
+ """触发 Kernel 优雅关闭"""
553
+ if server._shutting_down:
554
+ return {"status": "already_shutting_down"}
555
+
556
+ server._shutting_down = True
557
+ # 发布 module.shutdown 事件通知所有模块
558
+ server.event_hub.publish_internal(
559
+ "module.shutdown",
560
+ {"module_id": "kernel", "reason": "debug_api_shutdown"},
561
+ source="kernel"
562
+ )
563
+
564
+ # 延迟关闭 uvicorn(给事件传递留时间)
565
+ async def _delayed_shutdown():
566
+ await asyncio.sleep(1)
567
+ if server._uvicorn_server:
568
+ server._uvicorn_server.should_exit = True
569
+ if server._debug_server:
570
+ server._debug_server.should_exit = True
571
+
572
+ asyncio.create_task(_delayed_shutdown())
573
+ return {"status": "shutting_down"}
574
+
575
+ return app
576
+
244
577
  # ── Background loops ──
245
578
 
246
- async def _ttl_loop(self):
247
- """Check heartbeat TTL every 10s and publish offline events."""
579
+ async def _send_first_ping(self):
580
+ """发送第一次 ping(在 system.ready 后立即触发)"""
581
+ import time
582
+ if self._first_ping_sent:
583
+ return
584
+ self._first_ping_sent = True
585
+
586
+ t1 = time.time()
587
+ connected_modules = [mid for mid, slots in self.connections.items() if slots]
588
+ print(f"[kernel] _send_first_ping: {len(connected_modules)} modules connected")
589
+
590
+ if connected_modules:
591
+ for module_id in connected_modules:
592
+ self._ping_sent_times[module_id] = t1
593
+ self._pong_status[module_id] = "never"
594
+
595
+ # Broadcast system.ping event
596
+ self.event_hub.publish_internal(
597
+ "system.ping",
598
+ {"ping_time": t1},
599
+ source=self.module_id
600
+ )
601
+
602
+ async def _ping_broadcast_loop(self):
603
+ """Broadcast system.ping event with dynamic interval (5s when active, 60s when idle)."""
604
+ import time
605
+ import uuid
606
+
607
+ # 等待第一次 ping 发送(由 system.ready 触发)
608
+ while not self._first_ping_sent:
609
+ await asyncio.sleep(0.5)
610
+
611
+ # 第一次 ping 后等待 5 秒检查响应
612
+ await asyncio.sleep(5)
613
+ await self._check_ping_timeouts()
614
+
615
+ # 初始间隔:60 秒(空闲模式)
616
+ remaining_wait = 55.0 # 60 - 5 = 55 秒
617
+
248
618
  while True:
249
- await asyncio.sleep(10)
250
619
  try:
251
- expired = self.registry.check_ttl()
252
- for mid in expired:
620
+ # 动态调整间隔
621
+ current_time = time.time()
622
+ time_since_last_call = current_time - self._last_latencies_call
623
+
624
+ # 如果最近 30 秒内有调用 kernel.latencies,使用 5 秒间隔
625
+ if time_since_last_call < 30:
626
+ target_interval = 5.0
627
+ else:
628
+ target_interval = 60.0
629
+
630
+ # 如果间隔发生变化,调整等待时间
631
+ if target_interval != self._ping_interval:
632
+ old_interval = self._ping_interval
633
+ self._ping_interval = target_interval
634
+ print(f"[kernel] Ping interval changed: {old_interval:.0f}s → {target_interval:.0f}s")
635
+
636
+ # 等待剩余时间(可被中断)
637
+ try:
638
+ await asyncio.wait_for(
639
+ self._ping_interval_event.wait(),
640
+ timeout=remaining_wait
641
+ )
642
+ # 如果被唤醒(interval 变化),重新计算等待时间
643
+ self._ping_interval_event.clear()
644
+ remaining_wait = self._ping_interval - 5 # 减去检查超时的 5 秒
645
+ continue
646
+ except asyncio.TimeoutError:
647
+ # 正常超时,发送 ping
648
+ pass
649
+
650
+ # 发送 ping
651
+ t1 = time.time()
652
+ connected_modules = [mid for mid, slots in self.connections.items() if slots]
653
+
654
+ if connected_modules:
655
+ for module_id in connected_modules:
656
+ self._ping_sent_times[module_id] = t1
657
+
658
+ # Broadcast system.ping event
253
659
  self.event_hub.publish_internal(
254
- "module.offline", {"module_id": mid}, source=self.module_id)
660
+ "system.ping",
661
+ {"ping_time": t1},
662
+ source=self.module_id
663
+ )
664
+
665
+ # Check for timeouts after 5s
666
+ await asyncio.sleep(5)
667
+ await self._check_ping_timeouts()
668
+
669
+ # 计算下一次等待时间
670
+ remaining_wait = self._ping_interval - 5
671
+
672
+ except asyncio.CancelledError:
673
+ break
255
674
  except Exception as e:
256
- print(f"[kernel] TTL loop error: {e}")
675
+ print(f"[kernel] Ping broadcast loop error: {e}")
676
+
677
+ async def _check_ping_timeouts(self):
678
+ """检查 ping 超时并计算汇总统计,连续 3 次超时则强断连接"""
679
+ import time
680
+ t_check = time.time()
681
+ modules_to_disconnect = []
682
+
683
+ for module_id in list(self.connections.keys()):
684
+ if module_id in self._ping_sent_times:
685
+ # If no pong received within 5s, mark as timeout
686
+ if module_id not in self._pong_latencies or \
687
+ self._pong_latencies[module_id].get("last_update", 0) < self._ping_sent_times[module_id]:
688
+ if self._pong_status.get(module_id) == "never":
689
+ # Keep as "never" if never received
690
+ pass
691
+ else:
692
+ self._pong_status[module_id] = "timeout"
693
+ # 增加连续超时计数
694
+ self._ping_timeout_counts[module_id] = self._ping_timeout_counts.get(module_id, 0) + 1
695
+
696
+ # 连续 3 次超时 → 强断连接
697
+ if self._ping_timeout_counts[module_id] >= 3:
698
+ modules_to_disconnect.append(module_id)
699
+ else:
700
+ # 收到 pong,重置超时计数
701
+ self._ping_timeout_counts[module_id] = 0
702
+
703
+ # 强断超时连接(关闭所有 slot)
704
+ for module_id in modules_to_disconnect:
705
+ slots = self.connections.get(module_id, {})
706
+ if slots:
707
+ print(f"[kernel] {module_id} 连续 3 次 Ping 超时,强制断开连接 ({len(slots)} slots)")
708
+ for ws in list(slots.values()):
709
+ try:
710
+ await ws.close(code=4012, reason="Ping timeout")
711
+ except Exception:
712
+ pass
713
+
714
+ # 计算汇总统计
715
+ total_modules = len(self.connections)
716
+ ok_pings = []
717
+ timeout_modules = []
718
+ all_inbounds = []
719
+ all_outbounds = []
720
+
721
+ for module_id in self.connections.keys():
722
+ status = self._pong_status.get(module_id, "never")
723
+ if status == "ok":
724
+ latency = self._pong_latencies.get(module_id, {})
725
+ outbound = latency.get("outbound")
726
+ inbound = latency.get("inbound")
727
+ if outbound is not None:
728
+ ok_pings.append((module_id, outbound))
729
+ all_outbounds.append(outbound)
730
+ if inbound is not None:
731
+ all_inbounds.append(inbound)
732
+ elif status == "timeout":
733
+ timeout_modules.append(module_id)
734
+
735
+ # 计算 kernel 自己的 ping 数据并存储
736
+ if ok_pings:
737
+ min_module, min_ping = min(ok_pings, key=lambda x: x[1])
738
+ max_module, max_ping = max(ok_pings, key=lambda x: x[1])
739
+
740
+ # kernel 的延迟数据
741
+ kernel_outbound = min(all_inbounds) if all_inbounds else None
742
+ kernel_inbound = min(all_outbounds) if all_outbounds else None
743
+
744
+ if kernel_outbound is not None and kernel_inbound is not None:
745
+ self._pong_latencies["kernel"] = {
746
+ "outbound": kernel_outbound,
747
+ "inbound": kernel_inbound,
748
+ "last_update": list(self._ping_sent_times.values())[0] if self._ping_sent_times else t_check
749
+ }
750
+ self._pong_status["kernel"] = "ok"
751
+
752
+ # 打印汇总信息
753
+ timeout_info = f", 超时: {timeout_modules}" if timeout_modules else ""
754
+ print(f"[kernel] Ping 汇总: 发送 {total_modules} 个模块, 最小 {min_ping:.2f}ms ({min_module}), 最大 {max_ping:.2f}ms ({max_module}){timeout_info}")
755
+ else:
756
+ self._pong_latencies.pop("kernel", None)
757
+ self._pong_status.pop("kernel", None)
758
+ if total_modules > 0:
759
+ print(f"\033[91m[kernel] 警告: 所有模块 Ping 超时 ({total_modules} 个模块)\033[0m")
760
+
257
761
 
258
762
  async def _dedup_loop(self):
259
- """Clean up dedup table every 30s."""
763
+ """Clean up dedup table and throttle cache every 30s."""
260
764
  while True:
261
765
  await asyncio.sleep(30)
262
766
  try:
@@ -265,23 +769,168 @@ class KernelServer:
265
769
  except Exception as e:
266
770
  print(f"[kernel] Dedup cleanup error: {e}")
267
771
 
772
+ # 清理过期 slot token
773
+ try:
774
+ self.registry.cleanup_expired_slot_tokens()
775
+ except Exception as e:
776
+ print(f"[kernel] Slot token cleanup error: {e}")
777
+
778
+ # 清理节流缓存:移除 10s 前的条目,防止内存泄漏
779
+ try:
780
+ import time
781
+ cutoff = time.time() - 10
782
+ stale_keys = [k for k, t in self.event_hub._throttle_last.items() if t < cutoff]
783
+ for k in stale_keys:
784
+ del self.event_hub._throttle_last[k]
785
+ except Exception as e:
786
+ print(f"[kernel] Throttle cache cleanup error: {e}")
787
+
788
+ # ── 多连接:offer / release ──
789
+
790
+ def _get_max_connections(self, module_id: str) -> int:
791
+ """从 registry 读取模块的 max_connections 配置。module_id 可以是 instance_key。"""
792
+ base_mid = parse_instance_key(module_id)[0]
793
+ mod = self.registry.modules.get(base_mid, {})
794
+ return mod.get("max_connections", 1)
795
+
796
+ def _send_connection_offer(self, module_id: str, slots: list[int]):
797
+ """生成 slot token 并通过事件通知模块建立附加连接。"""
798
+ if self._shutting_down or module_id in self._modules_exiting:
799
+ return
800
+ tokens = self.registry.generate_slot_tokens(module_id, slots)
801
+ offer_data = {
802
+ "module_id": module_id,
803
+ "slots": {
804
+ str(s): {"token": t, "expires_in": 30}
805
+ for s, t in tokens.items()
806
+ }
807
+ }
808
+ self.event_hub.publish_internal(
809
+ "system.connection.offer", offer_data,
810
+ source=self.module_id
811
+ )
812
+ print(f"[kernel] Sent connection offer to {module_id}: slots={slots}")
813
+
814
+ def _send_connection_release(self, module_id: str, slots: list[int], reason: str = "idle_shrink"):
815
+ """请求模块释放指定 slot,超时强制关闭。"""
816
+ self.event_hub.publish_internal(
817
+ "system.connection.release",
818
+ {"module_id": module_id, "slots": slots, "reason": reason, "grace_period": 2},
819
+ source=self.module_id
820
+ )
821
+
822
+ # 启动 2 秒可取消定时器,超时强制关闭
823
+ async def _force_close():
824
+ try:
825
+ await asyncio.sleep(2)
826
+ for s in slots:
827
+ ws = self.connections.get(module_id, {}).get(s)
828
+ if ws:
829
+ try:
830
+ await ws.close(code=4014, reason=f"Release timeout: {reason}")
831
+ except Exception:
832
+ pass
833
+ except asyncio.CancelledError:
834
+ pass
835
+
836
+ timer_key = f"{module_id}:{','.join(map(str, slots))}"
837
+ old = self._release_timers.pop(timer_key, None)
838
+ if old:
839
+ old.cancel()
840
+ self._release_timers[timer_key] = asyncio.create_task(_force_close())
841
+ print(f"[kernel] Sent connection release to {module_id}: slots={slots} reason={reason}")
842
+
843
+ def _initiate_multi_connection(self, module_id: str, max_conns: int):
844
+ """启动多连接建立流程。"""
845
+ if max_conns <= 1:
846
+ return
847
+
848
+ if self._scaling_frozen:
849
+ return
850
+
851
+ if module_id in self._modules_exiting:
852
+ return
853
+
854
+ current_slots = len(self.connections.get(module_id, {}))
855
+ if current_slots >= max_conns:
856
+ return
857
+
858
+ is_elastic = self._module_conn_elastic.get(module_id, False)
859
+ missing_slots = [s for s in range(1, max_conns) if s not in self.connections.get(module_id, {})]
860
+ if not missing_slots:
861
+ return
862
+
863
+ if not is_elastic:
864
+ # 非弹性:立即发 offer 建满
865
+ self._send_connection_offer(module_id, missing_slots)
866
+ else:
867
+ # 弹性:先建 1 个附加连接,后续根据压力动态调整
868
+ self._send_connection_offer(module_id, missing_slots[:1])
869
+ # 启动弹性收缩监控
870
+ if module_id not in self._shrink_tasks:
871
+ self._shrink_tasks[module_id] = asyncio.create_task(
872
+ self._elastic_shrink_loop(module_id, max_conns)
873
+ )
874
+
875
+ async def _elastic_shrink_loop(self, module_id: str, max_conns: int):
876
+ """弹性收缩循环:idle 30s → release 1 个最空闲 slot,最少保留 2 个。"""
877
+ try:
878
+ while True:
879
+ await asyncio.sleep(30)
880
+ slots = self.connections.get(module_id, {})
881
+ if len(slots) <= 2:
882
+ continue # 最少保留 2 个
883
+
884
+ # 检查队列压力
885
+ level = self.event_hub._queue_levels.get(module_id, "idle")
886
+ if level != "idle":
887
+ continue # 非空闲不收缩
888
+
889
+ # 找最空闲的 slot(避开保序绑定的 slot 和 slot 0)
890
+ bound_slots = set()
891
+ for group in self.event_hub._ordering_groups.get(module_id, []):
892
+ if group.bound_slot is not None:
893
+ bound_slots.add(group.bound_slot)
894
+
895
+ candidates = [s for s in slots if s != 0 and s not in bound_slots]
896
+ if not candidates:
897
+ continue
898
+
899
+ # Release 最后一个(编号最大的)
900
+ to_release = max(candidates)
901
+ self._send_connection_release(module_id, [to_release])
902
+ except asyncio.CancelledError:
903
+ pass
904
+ finally:
905
+ self._shrink_tasks.pop(module_id, None)
906
+
268
907
  # ── Debounce & Launcher loss ──
269
908
 
270
909
  async def _debounce_offline(self, module_id: str):
271
- """Wait 5s after WS disconnect. If module doesn't reconnect, mark offline."""
910
+ """Wait 5s after WS disconnect. If instance doesn't reconnect, mark offline.
911
+ module_id here is actually instance_key (e.g. 'assistant' or 'assistant#2').
912
+ """
272
913
  try:
273
914
  await asyncio.sleep(5)
274
915
  except asyncio.CancelledError:
275
- return # Module reconnected within 5s — cancelled by ws_endpoint
916
+ return # Instance reconnected within 5s — cancelled by ws_endpoint
276
917
 
277
- # 5s elapsed, module did not reconnect — mark offline
918
+ # 5s elapsed, instance did not reconnect
278
919
  self._debounce_tasks.pop(module_id, None)
279
- self.registry.set_offline(module_id)
280
- self.event_hub.publish_internal("module.offline", {"module_id": module_id}, source=self.module_id)
281
- print(f"[kernel] {module_id} offline (5s debounce expired)")
920
+ base_mid = parse_instance_key(module_id)[0]
921
+
922
+ # Check if other instances of this module are still connected
923
+ if self.event_hub.has_other_instances(base_mid, module_id):
924
+ # Other instances alive — don't deregister module, just log
925
+ print(f"[kernel] {module_id} offline (other instances still active)")
926
+ else:
927
+ # Last instance → deregister module and publish offline event
928
+ self.registry.deregister_module(base_mid)
929
+ self.event_hub.publish_internal("module.offline", {"module_id": base_mid}, source=self.module_id)
930
+ print(f"[kernel] {module_id} offline → deregistered (5s debounce expired)")
282
931
 
283
932
  # If launcher went offline, start 35s launcher loss timer
284
- if module_id == "launcher":
933
+ if base_mid == "launcher":
285
934
  self._launcher_connected = False
286
935
  if not self._launcher_loss_task:
287
936
  self._launcher_loss_task = asyncio.create_task(
@@ -309,6 +958,251 @@ class KernelServer:
309
958
  # Shutdown Kernel itself
310
959
  await self.shutdown()
311
960
 
961
+ # ── Instance auto-scaler ──
962
+
963
+ # Thresholds (aligned with design doc 4.6)
964
+ _SCALE_CHECK_INTERVAL = 5 # seconds between scaler checks
965
+ _SCALE_UP_HOLD = 30 # all instances overload for this long → scale up
966
+ _SCALE_DOWN_HOLD = 30 # instances idle for this long → scale down
967
+ _SCALE_UP_COOLDOWN = 30 # seconds after scale-up before another scale-up
968
+ _SCALE_DOWN_COOLDOWN = 15 # seconds after scale-down before another scale-down
969
+
970
+ async def _instance_scaler_loop(self):
971
+ """Periodically check instance pressure and auto-scale elastic modules."""
972
+ try:
973
+ while True:
974
+ await asyncio.sleep(self._SCALE_CHECK_INTERVAL)
975
+ if self._shutting_down:
976
+ return
977
+ try:
978
+ await self._scaler_tick()
979
+ except Exception as e:
980
+ print(f"[kernel] Scaler error: {e}")
981
+ except asyncio.CancelledError:
982
+ pass
983
+
984
+ async def _scaler_tick(self):
985
+ """One iteration of the scaler loop."""
986
+ if self._scaling_frozen or self._scaler_running:
987
+ return
988
+ self._scaler_running = True
989
+ try:
990
+ await self._scaler_tick_inner()
991
+ finally:
992
+ self._scaler_running = False
993
+
994
+ async def _scaler_tick_inner(self):
995
+ """Actual scaler logic, guarded by _scaler_running flag."""
996
+ now = time.time()
997
+
998
+ for module_id, config in list(self._module_instance_config.items()):
999
+ max_inst = config.get("max_instances", 1)
1000
+
1001
+ # 远程模块(通过 Relay 连入)不受 Launcher 生命周期管理,跳过实例扩缩容
1002
+ # 多连接扩缩(_multi_conn_tick)不受影响
1003
+ if self.registry.is_remote_module(module_id):
1004
+ continue
1005
+
1006
+ inst_elastic = config.get("instance_elastic", False)
1007
+
1008
+ # Get all active instances and their pressure
1009
+ inst_keys = self.event_hub.get_instance_keys(module_id)
1010
+ # Exclude draining instances from active count
1011
+ active_keys = [k for k in inst_keys if not self.event_hub.is_draining(k)]
1012
+ current_count = len(active_keys) + self._scale_pending.get(module_id, 0)
1013
+
1014
+ # ── Fixed scale: 一次性扩/缩到目标数量 ──
1015
+ if not inst_elastic:
1016
+ if current_count < max_inst:
1017
+ while current_count < max_inst:
1018
+ await self._scale_up(module_id, current_count)
1019
+ current_count += 1
1020
+ # Fixed scale 一次性扩完,不需要 cooldown
1021
+ self._scale_cooldown_until.pop(module_id, None)
1022
+ elif current_count > max_inst:
1023
+ # 缩容:一次性 drain 所有超出的实例
1024
+ excess = current_count - max_inst
1025
+ for _ in range(excess):
1026
+ await self._scale_down(module_id, active_keys)
1027
+ # 更新 active_keys(排除刚标记为 draining 的)
1028
+ active_keys = [k for k in inst_keys if not self.event_hub.is_draining(k)]
1029
+ continue
1030
+
1031
+ # Cooldown check(仅弹性模式需要)
1032
+ if now < self._scale_cooldown_until.get(module_id, 0):
1033
+ continue
1034
+
1035
+ # ── Elastic scale ──
1036
+ pressures = {}
1037
+ for k in active_keys:
1038
+ p = self.event_hub.get_instance_pressure(k)
1039
+ if p:
1040
+ pressures[k] = p
1041
+
1042
+ # ── Scale UP check ──
1043
+ if current_count < max_inst:
1044
+ all_overload = (
1045
+ len(pressures) > 0 and
1046
+ all(p.get("level") in ("overload", "critical") for p in pressures.values())
1047
+ )
1048
+ if all_overload:
1049
+ since = self._scale_overload_since.get(module_id)
1050
+ if since is None:
1051
+ self._scale_overload_since[module_id] = now
1052
+ elif now - since >= self._SCALE_UP_HOLD:
1053
+ await self._scale_up(module_id, current_count)
1054
+ self._scale_overload_since.pop(module_id, None)
1055
+ else:
1056
+ self._scale_overload_since.pop(module_id, None)
1057
+
1058
+ # ── Scale DOWN check ──
1059
+ if len(active_keys) > 1:
1060
+ all_idle = (
1061
+ len(pressures) > 0 and
1062
+ all(p.get("level") == "idle" for p in pressures.values())
1063
+ )
1064
+ if all_idle:
1065
+ since = self._scale_idle_since.get(module_id)
1066
+ if since is None:
1067
+ self._scale_idle_since[module_id] = now
1068
+ elif now - since >= self._SCALE_DOWN_HOLD:
1069
+ await self._scale_down(module_id, active_keys)
1070
+ self._scale_idle_since.pop(module_id, None)
1071
+ else:
1072
+ self._scale_idle_since.pop(module_id, None)
1073
+
1074
+ async def _scale_up(self, module_id: str, current_count: int):
1075
+ """Request Launcher to start a new instance."""
1076
+ new_num = current_count + 1
1077
+ print(f"[kernel] Auto-scaling UP: {module_id} → instance #{new_num}")
1078
+
1079
+ self._scale_pending[module_id] = self._scale_pending.get(module_id, 0) + 1
1080
+ self._scale_cooldown_until[module_id] = time.time() + self._SCALE_UP_COOLDOWN
1081
+
1082
+ try:
1083
+ result = await self._call_launcher_rpc("start_module_instance", {
1084
+ "name": module_id,
1085
+ "instance_num": new_num,
1086
+ })
1087
+ if result:
1088
+ self.event_hub.publish_internal("system.instance.started", {
1089
+ "module_id": module_id,
1090
+ "instance_num": new_num,
1091
+ "reason": "auto_scale",
1092
+ }, source=self.module_id)
1093
+ print(f"[kernel] Scale-up requested: {module_id}#{new_num}")
1094
+ except Exception as e:
1095
+ print(f"[kernel] Scale-up failed for {module_id}: {e}")
1096
+ finally:
1097
+ pending = self._scale_pending.get(module_id, 1)
1098
+ self._scale_pending[module_id] = max(0, pending - 1)
1099
+
1100
+ async def _scale_down(self, module_id: str, inst_keys: list[str]):
1101
+ """Mark one instance as draining; stop it when its queue empties."""
1102
+ # 候选:排除 primary(#1)和已在 draining 的实例
1103
+ candidates = []
1104
+ for k in inst_keys:
1105
+ _, num = parse_instance_key(k)
1106
+ if num <= 1:
1107
+ continue # Never scale down the primary instance
1108
+ if self.event_hub.is_draining(k):
1109
+ continue # Already draining
1110
+ pressure = self.event_hub.get_instance_pressure(k) or {}
1111
+ depth = pressure.get("depth", 0)
1112
+ candidates.append((k, depth))
1113
+
1114
+ if not candidates:
1115
+ return
1116
+
1117
+ # 优先选队列最浅的实例(最接近空闲)
1118
+ candidates.sort(key=lambda x: x[1])
1119
+ target_key, _ = candidates[0]
1120
+ _, target_num = parse_instance_key(target_key)
1121
+
1122
+ print(f"[kernel] Scale-down: draining {target_key}")
1123
+ self.event_hub.set_draining(target_key)
1124
+ self._scale_cooldown_until[module_id] = time.time() + self._SCALE_DOWN_COOLDOWN
1125
+
1126
+ # 异步等待队列清空后关闭
1127
+ asyncio.ensure_future(self._drain_and_stop(module_id, target_key, target_num))
1128
+
1129
+ async def _drain_and_stop(self, module_id: str, inst_key: str, instance_num: int):
1130
+ """Wait for instance queue to empty, then stop it via Launcher."""
1131
+ try:
1132
+ # 首先等待至少收到一次压力报告,避免刚启动未上报就被误判为空
1133
+ for _ in range(6): # 最多等 6 * SCALE_CHECK_INTERVAL
1134
+ await asyncio.sleep(self._SCALE_CHECK_INTERVAL)
1135
+ if self._scaling_frozen:
1136
+ self.event_hub.clear_draining(inst_key)
1137
+ return
1138
+ if self.event_hub.get_instance_pressure(inst_key) is not None:
1139
+ break
1140
+
1141
+ while True:
1142
+ await asyncio.sleep(self._SCALE_CHECK_INTERVAL)
1143
+ if self._scaling_frozen:
1144
+ # 系统冻结(如关机),直接放弃 drain,由关闭流程处理
1145
+ self.event_hub.clear_draining(inst_key)
1146
+ return
1147
+ pressure = self.event_hub.get_instance_pressure(inst_key) or {}
1148
+ depth = pressure.get("depth", 0)
1149
+ if depth == 0:
1150
+ break
1151
+ # 队列已空,通知 Launcher 停止该实例
1152
+ print(f"[kernel] Scale-down: stopping {inst_key} (queue empty)")
1153
+ await self._call_launcher_rpc("stop_module_instance", {
1154
+ "name": module_id,
1155
+ "instance_num": instance_num,
1156
+ })
1157
+ self.event_hub.publish_internal("system.instance.stopped", {
1158
+ "module_id": module_id,
1159
+ "instance_num": instance_num,
1160
+ "reason": "auto_scale",
1161
+ }, source=self.module_id)
1162
+ print(f"[kernel] Scale-down completed: {inst_key}")
1163
+ except Exception as e:
1164
+ print(f"[kernel] Scale-down failed for {inst_key}: {e}")
1165
+ finally:
1166
+ self.event_hub.clear_draining(inst_key)
1167
+
1168
+ async def _call_launcher_rpc(self, method: str, params: dict, timeout: float = 10) -> dict | None:
1169
+ """Call a Launcher RPC method and wait for response."""
1170
+ import uuid
1171
+ launcher_slots = self.connections.get("launcher", {})
1172
+ if not launcher_slots:
1173
+ raise RuntimeError("Launcher not connected")
1174
+ ws = next(iter(launcher_slots.values()))
1175
+
1176
+ rpc_id = f"kernel-{uuid.uuid4().hex[:8]}"
1177
+ await ws.send_text(json.dumps({
1178
+ "jsonrpc": "2.0",
1179
+ "id": rpc_id,
1180
+ "method": method,
1181
+ "params": params,
1182
+ }))
1183
+
1184
+ # Wait for response via rpc_router pending mechanism
1185
+ # Use a simpler approach: the response will come back through handle_response
1186
+ # which routes back to Kernel. We use an asyncio.Event to wait.
1187
+ future = asyncio.get_event_loop().create_future()
1188
+ self._rpc_futures[rpc_id] = future
1189
+
1190
+ try:
1191
+ result = await asyncio.wait_for(future, timeout=timeout)
1192
+ return result
1193
+ except asyncio.TimeoutError:
1194
+ self._rpc_futures.pop(rpc_id, None)
1195
+ raise RuntimeError(f"Launcher RPC timeout: {method}")
1196
+
1197
+ def _resolve_rpc_future(self, rpc_id: str, result: dict = None, error: str = None):
1198
+ """Resolve a pending Kernel→Launcher RPC future."""
1199
+ future = self._rpc_futures.pop(rpc_id, None)
1200
+ if future and not future.done():
1201
+ if error:
1202
+ future.set_exception(RuntimeError(error))
1203
+ else:
1204
+ future.set_result(result)
1205
+
312
1206
  # ── Self-registration ──
313
1207
 
314
1208
  def self_register(self):
@@ -316,8 +1210,8 @@ class KernelServer:
316
1210
  self.registry.register_module({
317
1211
  "module_id": self.module_id,
318
1212
  "module_type": "infrastructure",
319
- "api_endpoint": f"http://{self.advertise_ip}:{self.port}",
320
- "health_endpoint": "/health",
1213
+ "base_url": f"http://{self.advertise_ip}:{self.port}",
1214
+ "health_path": "/health",
321
1215
  "metadata": {
322
1216
  "ws_endpoint": f"ws://{self.advertise_ip}:{self.port}/ws",
323
1217
  },
@@ -325,11 +1219,18 @@ class KernelServer:
325
1219
 
326
1220
  def publish_ready(self):
327
1221
  """Publish module.ready event for Kernel (internal, no WS needed)."""
1222
+ import time
1223
+ startup_time = time.monotonic() - self.boot_t0 if self.boot_t0 else 0
1224
+ reason = "recovery" if self.is_recover else "startup"
328
1225
  self.event_hub.publish_internal("module.ready", {
329
1226
  "module_id": self.module_id,
330
1227
  "ws_endpoint": f"ws://{self.advertise_ip}:{self.port}/ws",
331
1228
  "graceful_shutdown": True,
1229
+ "queue_elastic": True,
1230
+ "startup_time": startup_time,
1231
+ "reason": reason,
332
1232
  }, source=self.module_id)
1233
+ self._ready_published = True
333
1234
 
334
1235
  async def _handle_internal_event(self, event_type: str, data: dict):
335
1236
  """内部事件处理器(通过 EventHub 回调机制调用)
@@ -347,10 +1248,21 @@ class KernelServer:
347
1248
  # 处理 Kernel 订阅的事件
348
1249
  if event_type == "module.shutdown":
349
1250
  await self.handle_shutdown_event(data)
1251
+ elif event_type == "system.pong":
1252
+ await self._handle_pong_event(data)
1253
+ elif event_type == "system.ready":
1254
+ # 系统启动完成,立即发送第一次 ping
1255
+ print(f"[kernel] Received system.ready event, triggering first ping")
1256
+ # 检查 recovered 标记:崩溃恢复时重新启动 Ping/Pong
1257
+ if data.get("recovered"):
1258
+ self._first_ping_sent = False
1259
+ print(f"[kernel] system.ready (recovered=true), resetting ping state")
1260
+ await self._send_first_ping()
350
1261
  # 忽略系统广播事件(Kernel 没有订阅但会收到广播)
351
1262
  elif event_type in ("module.ready", "module.registered", "module.started",
352
1263
  "module.stopped", "module.crashed", "module.exiting",
353
- "module.offline", "system.ready", "registry.updated"):
1264
+ "module.offline", "module.degraded", "module.recovered",
1265
+ "registry.updated"):
354
1266
  # 这些是系统事件,会广播给所有模块,Kernel 收到是正常的
355
1267
  pass
356
1268
  else:
@@ -363,6 +1275,44 @@ class KernelServer:
363
1275
  traceback.print_exc()
364
1276
  raise # 重新抛出,让 EventHub 的包装器记录
365
1277
 
1278
+ async def _handle_pong_event(self, data: dict):
1279
+ """处理 system.pong 事件,计算往返延迟
1280
+
1281
+ Args:
1282
+ data: {
1283
+ "module_id": str,
1284
+ "ping_time": float, # t1 - Kernel 发送 ping 的时间
1285
+ "pong_time": float, # t2 - 模块收到 ping 并发送 pong 的时间
1286
+ }
1287
+ """
1288
+ import time
1289
+ module_id = data.get("module_id")
1290
+ t1 = data.get("ping_time") # Kernel 发送 ping 的时间
1291
+ t2 = data.get("pong_time") # 模块收到 ping 并发送 pong 的时间
1292
+ t3 = time.time() # Kernel 收到 pong 的时间
1293
+
1294
+ if not module_id or t1 is None or t2 is None:
1295
+ return
1296
+
1297
+ # 验证 ping_time 是否匹配
1298
+ if module_id not in self._ping_sent_times:
1299
+ return
1300
+
1301
+ expected_t1 = self._ping_sent_times[module_id]
1302
+ if abs(t1 - expected_t1) > 0.1: # 允许 100ms 误差
1303
+ return
1304
+
1305
+ # 计算两个延迟
1306
+ outbound_ms = (t2 - t1) * 1000 # 去程:Kernel → 模块
1307
+ inbound_ms = (t3 - t2) * 1000 # 回程:模块 → Kernel
1308
+
1309
+ self._pong_latencies[module_id] = {
1310
+ "outbound": round(outbound_ms, 2),
1311
+ "inbound": round(inbound_ms, 2),
1312
+ "last_update": t3,
1313
+ }
1314
+ self._pong_status[module_id] = "ok"
1315
+
366
1316
  async def handle_shutdown_event(self, event_data):
367
1317
  """处理 module.shutdown 事件(标准优雅退出流程)
368
1318
 
@@ -378,10 +1328,12 @@ class KernelServer:
378
1328
  print(f"[kernel] Warning: Invalid shutdown event data type: {type(event_data)}")
379
1329
  return
380
1330
 
381
- # 防御性检查:验证 module_id
1331
+ # 判断 shutdown 目标
382
1332
  target_module = event_data.get("module_id")
383
1333
  if target_module != self.module_id:
384
- print(f"[kernel] Warning: Received shutdown for wrong module: {target_module}")
1334
+ # 其他模块的 shutdown 通知:标记该模块正在退出,不再发 offer
1335
+ if target_module:
1336
+ self._modules_exiting.add(target_module)
385
1337
  return
386
1338
 
387
1339
  # 防御性检查:防止重复处理
@@ -422,13 +1374,14 @@ class KernelServer:
422
1374
  # Step 5: 等待一小段时间确保事件发送完成
423
1375
  await asyncio.sleep(0.2)
424
1376
 
425
- # Step 6: 关闭所有 WebSocket 连接(包括 Launcher)
1377
+ # Step 6: 关闭所有 WebSocket 连接(包括 Launcher,所有 slot
426
1378
  close_tasks = []
427
- for mid, ws in list(self.connections.items()):
428
- try:
429
- close_tasks.append(ws.close(code=1000, reason="Graceful shutdown complete"))
430
- except Exception as e:
431
- print(f"[kernel] 关闭 {mid} 连接失败: {e}")
1379
+ for mid, slots in list(self.connections.items()):
1380
+ for s, ws in list(slots.items()):
1381
+ try:
1382
+ close_tasks.append(ws.close(code=1000, reason="Graceful shutdown complete"))
1383
+ except Exception as e:
1384
+ print(f"[kernel] 关闭 {mid} slot {s} 连接失败: {e}")
432
1385
 
433
1386
  if close_tasks:
434
1387
  await asyncio.gather(*close_tasks, return_exceptions=True)
@@ -460,6 +1413,14 @@ class KernelServer:
460
1413
  self._shutting_down = True
461
1414
  print("[kernel] 执行清理工作...")
462
1415
 
1416
+ # 取消所有队列压力倒计时
1417
+ if self.event_hub._critical_timers:
1418
+ print(f"[kernel] 取消 {len(self.event_hub._critical_timers)} 个压力倒计时")
1419
+ for task in self.event_hub._critical_timers.values():
1420
+ if not task.done():
1421
+ task.cancel()
1422
+ self.event_hub._critical_timers.clear()
1423
+
463
1424
  # 取消所有 debounce 任务
464
1425
  if self._debounce_tasks:
465
1426
  print(f"[kernel] 取消 {len(self._debounce_tasks)} 个 debounce 任务")
@@ -474,15 +1435,28 @@ class KernelServer:
474
1435
  self._launcher_loss_task.cancel()
475
1436
  self._launcher_loss_task = None
476
1437
 
1438
+ # 取消多连接相关任务
1439
+ for name, task_dict in [("offer", self._offer_tasks),
1440
+ ("shrink", self._shrink_tasks),
1441
+ ("release", self._release_timers)]:
1442
+ if task_dict:
1443
+ print(f"[kernel] 取消 {len(task_dict)} 个 {name} 任务")
1444
+ for task in task_dict.values():
1445
+ if not task.done():
1446
+ task.cancel()
1447
+ task_dict.clear()
1448
+
477
1449
  # 关闭其他模块的 WebSocket 连接(保留 Launcher 连接)
478
- other_connections = {mid: ws for mid, ws in self.connections.items() if mid != "launcher"}
479
- if other_connections:
480
- print(f"[kernel] 关闭 {len(other_connections)} 个其他模块的 WebSocket 连接")
481
- for module_id, ws in other_connections.items():
482
- try:
483
- await ws.close(code=1001, reason="Server shutting down")
484
- except Exception as e:
485
- print(f"[kernel] 关闭连接失败 {module_id}: {e}")
1450
+ other_mids = [mid for mid in self.connections if mid != "launcher"]
1451
+ if other_mids:
1452
+ count = sum(len(self.connections.get(mid, {})) for mid in other_mids)
1453
+ print(f"[kernel] 关闭 {count} 个其他模块的 WebSocket 连接")
1454
+ for mid in other_mids:
1455
+ for slot, ws in list(self.connections.get(mid, {}).items()):
1456
+ try:
1457
+ await ws.close(code=1001, reason="Server shutting down")
1458
+ except Exception as e:
1459
+ print(f"[kernel] 关闭连接失败 {mid} slot {slot}: {e}")
486
1460
 
487
1461
  # 清空 RPC 转发队列
488
1462
  pending_count = len(self.rpc_router._pending)
@@ -517,13 +1491,15 @@ class KernelServer:
517
1491
  self._launcher_loss_task.cancel()
518
1492
  self._launcher_loss_task = None
519
1493
 
520
- # Close all WebSocket connections
521
- print(f"[kernel] Closing {len(self.connections)} WebSocket connections...")
522
- for module_id, ws in list(self.connections.items()):
523
- try:
524
- await ws.close(code=1001, reason="Server shutting down")
525
- except Exception as e:
526
- print(f"[kernel] Failed to close connection for {module_id}: {e}")
1494
+ # Close all WebSocket connections (multi-slot)
1495
+ total_slots = sum(len(s) for s in self.connections.values())
1496
+ print(f"[kernel] Closing {total_slots} WebSocket connections...")
1497
+ for module_id, slots in list(self.connections.items()):
1498
+ for slot, ws in list(slots.items()):
1499
+ try:
1500
+ await ws.close(code=1001, reason="Server shutting down")
1501
+ except Exception as e:
1502
+ print(f"[kernel] Failed to close connection for {module_id} slot {slot}: {e}")
527
1503
  self.connections.clear()
528
1504
 
529
1505
  # Clear pending RPC forwards