@agentunion/kite 1.5.0 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/kite/checklists/feature-checklist.md +496 -0
- package/.claude/skills/kite/references/event-patterns.md +180 -0
- package/.claude/skills/kite/references/health-check.md +202 -0
- package/.claude/skills/kite/references/http-service.md +199 -0
- package/.claude/skills/kite/references/module-md-spec.md +172 -0
- package/.claude/skills/kite/references/multi-connection.md +147 -0
- package/.claude/skills/kite/references/rpc-patterns.md +199 -0
- package/.claude/skills/kite/references/shutdown-sequence.md +146 -0
- package/.claude/skills/kite/references/stdin-protocol.md +147 -0
- package/.claude/skills/kite/references/test-center-integration.md +178 -0
- package/.claude/skills/kite/references/ws-lifecycle.md +301 -0
- package/.claude/skills/kite/skill.md +272 -0
- package/.claude/skills/kite/templates/go/README.md +20 -0
- package/.claude/skills/kite/templates/node/entry.js +134 -0
- package/.claude/skills/kite/templates/node/module.md +16 -0
- package/.claude/skills/kite/templates/node/server.js +351 -0
- package/.claude/skills/kite/templates/node/server_http.js +90 -0
- package/.claude/skills/kite/templates/python/entry.py +425 -0
- package/.claude/skills/kite/templates/python/module.md +26 -0
- package/.claude/skills/kite/templates/python/server.py +447 -0
- package/.claude/skills/kite/templates/python/server_http.py +433 -0
- package/cli.js +38 -4
- package/core/env_checker.py +96 -0
- 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
- package/docs/ACP/345/215/217/350/256/256/345/205/274/345/256/271/346/226/271/346/241/210.md +138 -0
- 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
- package/docs/CLI/345/274/200/345/217/221/350/256/241/345/210/222.md +595 -0
- 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
- 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
- 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
- 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
- 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
- package/docs/Evol/346/250/241/345/235/227/350/256/276/350/256/241/346/226/271/346/241/210.md +1154 -0
- 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
- 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
- package/docs/HTTP-RPC/350/277/201/347/247/273/345/210/260WebSocket/350/256/241/345/210/222.md +318 -0
- package/docs/INDEX.md +388 -0
- package/docs/KITE_DOCS_GUIDE.md +33 -0
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- package/docs/Kite/346/241/206/346/236/266/350/256/276/350/256/241/README.md +46 -0
- package/docs/Kite/347/263/273/347/273/237/345/220/257/345/212/250/346/265/201/347/250/213.md +567 -0
- package/docs/Launcher/345/220/257/345/212/250/345/231/250/346/226/207/346/241/243.md +745 -0
- 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
- 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
- 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
- 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
- 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
- package/docs/Watchdog/350/265/204/346/272/220/347/233/221/346/216/247/347/255/226/347/225/245.md +92 -0
- 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
- 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
- package/docs/WebSocket/350/277/236/346/216/245/351/237/247/346/200/247/346/226/271/346/241/210.md +169 -0
- 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
- 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
- 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
- package/docs/audit-api-guide.md +68 -0
- package/docs/audit-module-design.md +315 -0
- package/docs/audit-module-implementation-summary.md +149 -0
- package/docs/llm-context-design.md +52 -0
- package/docs/llm-test-enhancement-plan.md +970 -0
- package/docs/logs-api-guide.md +42 -0
- 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
- 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
- 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
- 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
- package/docs//344/272/213/344/273/266/345/244/204/347/220/206/346/234/272/345/210/266.md +388 -0
- package/docs//344/272/213/344/273/266/345/244/204/347/220/206/350/247/204/350/214/203.md +113 -0
- 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
- 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
- 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
- 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
- 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
- package/docs//344/274/230/351/233/205/351/200/200/345/207/272/350/247/204/350/214/203.md +362 -0
- package/docs//344/276/235/350/265/226/347/256/241/347/220/206/350/257/264/346/230/216.md +141 -0
- 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
- package/docs//345/210/240/351/231/244kernel-client-example/345/256/214/346/210/220.md +309 -0
- package/docs//345/210/240/351/231/244ws-management/345/256/214/346/210/220.md +418 -0
- package/docs//345/220/257/345/212/250/344/274/230/345/214/226/346/226/271/346/241/210.md +522 -0
- 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
- 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
- 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
- package/docs//345/256/236/347/216/260/350/247/204/345/210/222.md +195 -0
- 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
- 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
- package/docs//346/217/241/346/211/213/350/256/244/350/257/201/346/226/271/346/241/210.md +908 -0
- package/docs//346/226/207/346/241/243/346/233/264/346/226/260/346/270/205/345/215/225.md +83 -0
- 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
- 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
- package/docs//346/236/266/346/236/204/345/200/237/351/211/264/346/214/207/345/215/227.md +977 -0
- 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
- 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
- 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
- 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
- package/docs//346/250/241/345/235/227/345/274/200/345/217/221/346/214/207/345/215/227.md +1824 -0
- package/docs//346/250/241/345/235/227/347/203/255/346/233/264/346/226/260.md +89 -0
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- package/docs//350/277/234/347/250/213/346/250/241/345/235/227/350/256/276/350/256/241.md +451 -0
- package/docs//351/207/215/346/236/204/346/234/272/345/210/266/346/270/205/345/215/225.md +192 -0
- package/docs//351/223/276/350/267/257/350/277/275/350/270/252/346/226/271/346/241/210.md +242 -0
- 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
- package/extensions/agents/assistant/entry.py +113 -14
- package/extensions/agents/assistant/module.md +27 -22
- package/extensions/agents/assistant/server.py +291 -105
- package/extensions/channels/acp_channel/entry.py +114 -16
- package/extensions/channels/acp_channel/module.md +4 -0
- package/extensions/channels/acp_channel/server.py +396 -105
- package/extensions/channels/phone_channel/__init__.py +1 -0
- package/extensions/channels/phone_channel/entry.py +503 -0
- package/extensions/channels/phone_channel/module.md +31 -0
- package/extensions/channels/phone_channel/server.py +686 -0
- package/extensions/event_hub_bench/entry.py +55 -12
- package/extensions/event_hub_bench/module.md +27 -27
- package/extensions/services/audit/README.md +134 -0
- package/extensions/services/audit/collector.py +73 -0
- package/extensions/services/audit/entry.py +444 -0
- package/extensions/services/audit/module.md +66 -0
- package/extensions/services/audit/query_audit.py +111 -0
- package/extensions/services/audit/routes/__init__.py +1 -0
- package/extensions/services/audit/routes/routes_audit.py +113 -0
- package/extensions/services/audit/schemas/__init__.py +5 -0
- package/extensions/services/audit/schemas/audit_event.py +92 -0
- package/extensions/services/audit/server.py +542 -0
- package/extensions/services/audit/storage.py +95 -0
- package/extensions/services/auth/entry.py +1054 -0
- package/extensions/services/auth/module.md +31 -0
- package/extensions/services/auth/token_store.py +185 -0
- package/extensions/services/auth/verifiers/evol_account.py +101 -0
- package/extensions/services/auth/verifiers/kite_token.py +38 -0
- package/extensions/services/auth/verifiers/pairing_code.py +71 -0
- package/extensions/services/backup/entry.py +494 -197
- package/extensions/services/backup/module.md +4 -2
- package/extensions/services/dataclaw/api/__init__.py +0 -0
- package/extensions/services/dataclaw/api/admin.py +367 -0
- package/extensions/services/dataclaw/api/copyright.py +175 -0
- package/extensions/services/dataclaw/api/credits.py +177 -0
- package/extensions/services/dataclaw/api/data.py +179 -0
- package/extensions/services/dataclaw/api/demands.py +269 -0
- package/extensions/services/dataclaw/api/feeds.py +262 -0
- package/extensions/services/dataclaw/api/identity.py +505 -0
- package/extensions/services/dataclaw/api/notifications.py +104 -0
- package/extensions/services/dataclaw/api/reviews.py +138 -0
- package/extensions/services/dataclaw/api/search.py +153 -0
- package/extensions/services/dataclaw/api/subscriptions.py +157 -0
- package/extensions/services/dataclaw/config.json5 +96 -0
- package/extensions/services/dataclaw/core/__init__.py +0 -0
- package/extensions/services/dataclaw/core/auth.py +95 -0
- package/extensions/services/dataclaw/core/config.py +50 -0
- package/extensions/services/dataclaw/core/database.py +70 -0
- package/extensions/services/dataclaw/entry.py +416 -0
- 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
- package/extensions/services/dataclaw/migrate.py +283 -0
- package/extensions/services/dataclaw/models/__init__.py +0 -0
- package/extensions/services/dataclaw/module.md +49 -0
- package/extensions/services/dataclaw/requirements.txt +18 -0
- package/extensions/services/dataclaw/server.py +759 -0
- package/extensions/services/dataclaw/services/__init__.py +0 -0
- package/extensions/services/dataclaw/services/agent_service.py +132 -0
- package/extensions/services/dataclaw/services/credit_service.py +235 -0
- package/extensions/services/dataclaw/services/email_service.py +140 -0
- package/extensions/services/dataclaw/services/feed_service.py +259 -0
- package/extensions/services/dataclaw/services/notification_service.py +209 -0
- package/extensions/services/dataclaw/services/oauth_service.py +275 -0
- package/extensions/services/dataclaw/services/pricing.py +102 -0
- package/extensions/services/dataclaw/services/quality.py +79 -0
- package/extensions/services/dataclaw/services/reputation.py +142 -0
- package/extensions/services/dataclaw/services/sms_service.py +174 -0
- package/extensions/services/dataclaw/static/css/common.css +853 -0
- package/extensions/services/dataclaw/static/css/themes/blue.css +42 -0
- package/extensions/services/dataclaw/static/css/themes/dark.css +42 -0
- package/extensions/services/dataclaw/static/css/themes/light.css +35 -0
- package/extensions/services/dataclaw/static/js/api.js +103 -0
- package/extensions/services/dataclaw/static/js/common.js +321 -0
- package/extensions/services/dataclaw/static/js/i18n.js +95 -0
- package/extensions/services/dataclaw/static/js/pages/admin.js +152 -0
- package/extensions/services/dataclaw/static/js/pages/dashboard.js +82 -0
- package/extensions/services/dataclaw/static/js/pages/feed-detail.js +180 -0
- package/extensions/services/dataclaw/static/js/pages/feed-manage.js +158 -0
- package/extensions/services/dataclaw/static/js/theme.js +46 -0
- package/extensions/services/dataclaw/static/locales/en-US.json +464 -0
- package/extensions/services/dataclaw/static/locales/ja-JP.json +464 -0
- package/extensions/services/dataclaw/static/locales/zh-CN.json +464 -0
- package/extensions/services/dataclaw/templates/admin/index.html +90 -0
- package/extensions/services/dataclaw/templates/base.html +136 -0
- package/extensions/services/dataclaw/templates/credits/balance.html +106 -0
- package/extensions/services/dataclaw/templates/credits/deposit.html +164 -0
- package/extensions/services/dataclaw/templates/credits/history.html +90 -0
- package/extensions/services/dataclaw/templates/dashboard.html +52 -0
- package/extensions/services/dataclaw/templates/demands/create.html +78 -0
- package/extensions/services/dataclaw/templates/demands/detail.html +136 -0
- package/extensions/services/dataclaw/templates/demands/list.html +94 -0
- package/extensions/services/dataclaw/templates/feeds/create.html +95 -0
- package/extensions/services/dataclaw/templates/feeds/detail.html +110 -0
- package/extensions/services/dataclaw/templates/feeds/list.html +110 -0
- package/extensions/services/dataclaw/templates/feeds/manage.html +88 -0
- package/extensions/services/dataclaw/templates/index.html +185 -0
- package/extensions/services/dataclaw/templates/login.html +246 -0
- package/extensions/services/dataclaw/templates/register.html +164 -0
- package/extensions/services/dataclaw/templates/settings/notifications.html +96 -0
- package/extensions/services/dataclaw/templates/settings/profile.html +167 -0
- package/extensions/services/dataclaw/templates/subscriptions/list.html +64 -0
- package/extensions/services/dataclaw/tests/__init__.py +0 -0
- package/extensions/services/dataclaw/tests/conftest.py +68 -0
- package/extensions/services/dataclaw/tests/integration/__init__.py +0 -0
- package/extensions/services/dataclaw/tests/integration/test_workflows.py +239 -0
- package/extensions/services/dataclaw/tests/unit/__init__.py +0 -0
- package/extensions/services/dataclaw/tests/unit/test_admin.py +70 -0
- package/extensions/services/dataclaw/tests/unit/test_copyright.py +63 -0
- package/extensions/services/dataclaw/tests/unit/test_credits.py +80 -0
- package/extensions/services/dataclaw/tests/unit/test_data.py +98 -0
- package/extensions/services/dataclaw/tests/unit/test_demands.py +106 -0
- package/extensions/services/dataclaw/tests/unit/test_feeds.py +98 -0
- package/extensions/services/dataclaw/tests/unit/test_identity.py +88 -0
- package/extensions/services/dataclaw/tests/unit/test_notifications.py +36 -0
- package/extensions/services/dataclaw/tests/unit/test_reviews.py +68 -0
- package/extensions/services/dataclaw/tests/unit/test_search.py +64 -0
- package/extensions/services/dataclaw/tests/unit/test_subscriptions.py +65 -0
- package/extensions/services/dataclaw/tests/unit/test_system.py +106 -0
- package/extensions/services/dataclaw/utils/__init__.py +0 -0
- package/extensions/services/dataclaw/utils/crypto.py +38 -0
- package/extensions/services/dataclaw/utils/id_generator.py +52 -0
- package/extensions/services/dataclaw/ws/__init__.py +0 -0
- package/extensions/services/dataclaw/ws/handler.py +163 -0
- 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
- 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
- package/extensions/services/evol/__init__.py +1 -0
- package/extensions/services/evol/async_http.py +551 -0
- package/extensions/services/evol/auth_manager.py +602 -443
- package/extensions/services/evol/config.json5 +16 -0
- package/extensions/services/evol/entry.py +568 -406
- package/extensions/services/evol/evol_api.py +969 -173
- package/extensions/services/evol/mfa_totp.py +77 -0
- package/extensions/services/evol/module.md +150 -32
- package/extensions/services/evol/nonce_pool.py +113 -0
- package/extensions/services/evol/oauth_manager.py +223 -0
- package/extensions/services/evol/pairing.py +3 -2
- package/extensions/services/evol/pairing_codes.jsonl +1 -0
- package/extensions/services/evol/relay.py +1031 -682
- package/extensions/services/evol/relay_config.json5 +85 -67
- package/extensions/services/evol/routes/routes_llm.py +231 -0
- package/extensions/services/evol/routes/routes_rpc.py +90 -89
- package/extensions/services/evol/routes/routes_test.py +11 -4
- package/extensions/services/evol/server.py +2426 -875
- package/extensions/services/evol/static/assets/CommissionView-Cs_ys6Gm.js +1 -0
- package/extensions/services/evol/static/assets/CommissionView-DACet_Oo.css +1 -0
- package/extensions/services/evol/static/assets/IframePage-DbO11U9G.js +1 -0
- package/extensions/services/evol/static/assets/IframePage-c572lT8i.css +1 -0
- package/extensions/services/evol/static/assets/TeamDetailView-DULrGD7k.css +1 -0
- package/extensions/services/evol/static/assets/TeamDetailView-gy_MBEqG.js +139 -0
- package/extensions/services/evol/static/assets/element-plus-Bd7pZkkM.js +63 -0
- package/extensions/services/evol/static/assets/index-CmMONKzG.css +1 -0
- package/extensions/services/evol/static/assets/index-D44bBe__.js +2 -0
- package/extensions/services/evol/static/assets/vue-vendor-DtF-__I4.js +29 -0
- package/extensions/services/evol/static/index.html +16 -781
- package/extensions/services/evol/static/logo.png +0 -0
- package/extensions/services/evol/stats_manager.py +243 -240
- package/extensions/services/evol/web/README.md +89 -0
- package/extensions/services/evol/web/build.bat +44 -0
- package/extensions/services/evol/web/index.html +13 -0
- package/extensions/services/evol/web/package-lock.json +1718 -0
- package/extensions/services/evol/web/package.json +26 -0
- package/extensions/services/evol/web/public/logo.png +0 -0
- package/extensions/services/evol/web/src/App.vue +7 -0
- package/extensions/services/evol/web/src/components/layout/AppHeader.vue +202 -0
- package/extensions/services/evol/web/src/components/layout/AppLayout.vue +61 -0
- package/extensions/services/evol/web/src/components/layout/AppSidebar.vue +115 -0
- package/extensions/services/evol/web/src/components/login/LoginPage.vue +271 -0
- package/extensions/services/evol/web/src/components/team/AddMemberModal.vue +181 -0
- package/extensions/services/evol/web/src/components/team/GroupTreeNode.vue +156 -0
- package/extensions/services/evol/web/src/components/team/TeamAlertConfig.vue +221 -0
- package/extensions/services/evol/web/src/components/team/TeamBillModal.vue +165 -0
- package/extensions/services/evol/web/src/components/team/TeamMembersAndGroups.vue +499 -0
- package/extensions/services/evol/web/src/components/team/TeamStatsPanel.vue +907 -0
- package/extensions/services/evol/web/src/components/team/TreeNode.vue +331 -0
- package/extensions/services/evol/web/src/components/team/stats/StatsExportProgress.vue +44 -0
- package/extensions/services/evol/web/src/components/team/stats/StatsHeader.vue +89 -0
- package/extensions/services/evol/web/src/components/team/stats/StatsMemberDetail.vue +415 -0
- package/extensions/services/evol/web/src/components/team/stats/StatsSummary.vue +42 -0
- package/extensions/services/evol/web/src/components/team/stats/helpers.ts +195 -0
- package/extensions/services/evol/web/src/components/team/stats/stats.css +741 -0
- package/extensions/services/evol/web/src/components/team/stats/useStatsApi.ts +114 -0
- package/extensions/services/evol/web/src/components/team/stats/useStatsCharts.ts +242 -0
- package/extensions/services/evol/web/src/components/team/stats/useStatsExport.ts +232 -0
- package/extensions/services/evol/web/src/composables/useFormatters.ts +42 -0
- package/extensions/services/evol/web/src/composables/useTheme.ts +52 -0
- package/extensions/services/evol/web/src/env.d.ts +7 -0
- package/extensions/services/evol/web/src/i18n/en.ts +361 -0
- package/extensions/services/evol/web/src/i18n/index.ts +36 -0
- package/extensions/services/evol/web/src/i18n/zh.ts +379 -0
- package/extensions/services/evol/web/src/main.ts +21 -0
- package/extensions/services/evol/web/src/router/index.ts +81 -0
- package/extensions/services/evol/web/src/services/kernel-client.ts +406 -0
- package/extensions/services/evol/web/src/stores/auth.ts +189 -0
- package/extensions/services/evol/web/src/stores/connection.ts +134 -0
- package/extensions/services/evol/web/src/stores/pages.ts +79 -0
- package/extensions/services/evol/web/src/styles/base.css +213 -0
- package/extensions/services/evol/web/src/styles/variables.css +138 -0
- package/extensions/services/evol/web/src/types/rpc.ts +35 -0
- package/extensions/services/evol/web/src/types/token.ts +87 -0
- package/extensions/services/evol/web/src/views/AccountView.vue +1532 -0
- package/extensions/services/evol/web/src/views/AiServiceView.vue +219 -0
- package/extensions/services/evol/web/src/views/CommissionView.vue +1220 -0
- package/extensions/services/evol/web/src/views/CreditsView.vue +131 -0
- package/extensions/services/evol/web/src/views/EndpointView.vue +163 -0
- package/extensions/services/evol/web/src/views/IframePage.vue +120 -0
- package/extensions/services/evol/web/src/views/TeamDetailView.vue +473 -0
- package/extensions/services/evol/web/src/views/TeamView.vue +332 -0
- package/extensions/services/evol/web/tsconfig.json +31 -0
- package/extensions/services/evol/web/tsconfig.node.json +10 -0
- package/extensions/services/evol/web/vite.config.ts +49 -0
- package/extensions/services/evolmem/__init__.py +0 -0
- package/extensions/services/evolmem/entry.py +387 -0
- package/extensions/services/evolmem/hooks/__init__.py +0 -0
- package/extensions/services/evolmem/hooks/assistant_stop.py +228 -0
- package/extensions/services/evolmem/hooks/common.py +76 -0
- package/extensions/services/evolmem/hooks/pre_tool_use.py +56 -0
- package/extensions/services/evolmem/hooks/session_end.py +133 -0
- package/extensions/services/evolmem/hooks/session_start.py +229 -0
- package/extensions/services/evolmem/hooks/user_prompt.py +122 -0
- package/extensions/services/evolmem/module.md +48 -0
- package/extensions/services/evolmem/prompts/00-server-info.md +28 -0
- package/extensions/services/evolmem/prompts/01-behavior.md +46 -0
- package/extensions/services/evolmem/prompts/02-summary-format.md +112 -0
- package/extensions/services/evolmem/prompts/03-file-query.md +92 -0
- package/extensions/services/evolmem/prompts/04-topic-stats.md +11 -0
- package/extensions/services/evolmem/prompts/05-recent-topics.md +84 -0
- package/extensions/services/evolmem/scripts/__init__.py +0 -0
- package/extensions/services/evolmem/scripts/extract_keywords.py +40 -0
- package/extensions/services/evolmem/scripts/search_topics.py +91 -0
- package/extensions/services/evolmem/server.py +641 -0
- package/extensions/services/gateway/entry.py +964 -0
- package/extensions/services/gateway/module.md +29 -0
- package/extensions/services/gateway/nonce_pool.py +65 -0
- package/extensions/services/gateway/relay.py +133 -0
- package/extensions/services/gateway/ws_server.py +285 -0
- package/extensions/services/kite_console/auth_manager.py +603 -0
- package/extensions/services/kite_console/config.json5 +19 -0
- package/extensions/services/kite_console/config_loader.py +117 -0
- package/extensions/services/kite_console/entry.py +528 -0
- package/extensions/services/kite_console/evol_api.py +179 -0
- package/extensions/services/kite_console/evol_config.json5 +29 -0
- package/extensions/services/kite_console/mfa_totp.py +77 -0
- package/extensions/services/kite_console/migrate_tokens.py +122 -0
- package/extensions/services/kite_console/module.md +37 -0
- package/extensions/services/kite_console/nonce_pool.py +113 -0
- package/extensions/services/kite_console/oauth_manager.py +223 -0
- package/extensions/services/kite_console/pairing.py +280 -0
- package/extensions/services/kite_console/pairing_codes.jsonl +2 -0
- package/extensions/services/kite_console/relay.py +1350 -0
- package/extensions/services/kite_console/relay_config.json5 +96 -0
- package/extensions/services/kite_console/routes/__init__.py +1 -0
- package/extensions/services/kite_console/routes/routes_llm.py +231 -0
- package/extensions/services/kite_console/routes/routes_proxy.py +115 -0
- package/extensions/services/kite_console/routes/routes_rpc.py +89 -0
- package/extensions/services/kite_console/routes/routes_test.py +68 -0
- package/extensions/services/kite_console/server.py +1742 -0
- package/extensions/services/{evol → kite_console}/static/css/style.css +656 -2
- package/extensions/services/kite_console/static/index.html +1524 -0
- package/extensions/services/{evol → kite_console}/static/js/dialog.js +11 -4
- package/extensions/services/kite_console/static/js/evol-app.js +7740 -0
- package/extensions/services/{evol/static/js/evol-app.js → kite_console/static/js/evol-app.js.backup} +2777 -1949
- package/extensions/services/kite_console/static/js/kernel-client.js +560 -0
- package/extensions/services/{evol/static/js/kernel-client.js → kite_console/static/js/kernel-client.js.backup} +41 -3
- package/extensions/services/{evol → kite_console}/static/js/registry-tests.js +7 -0
- package/extensions/services/kite_console/static/js/tests/ARCHITECTURE.md +67 -0
- package/extensions/services/kite_console/static/js/tests/README.md +140 -0
- package/extensions/services/kite_console/static/js/tests/index.js +161 -0
- package/extensions/services/kite_console/static/js/tests/integration/auth.js +120 -0
- package/extensions/services/kite_console/static/js/tests/integration/channel-interaction.js +188 -0
- package/extensions/services/kite_console/static/js/tests/integration/elastic-connection.js +115 -0
- package/extensions/services/kite_console/static/js/tests/integration/full-workflow.js +43 -0
- package/extensions/services/kite_console/static/js/tests/integration/multi-instance.js +304 -0
- package/extensions/services/kite_console/static/js/tests/integration/nested-rpc.js +266 -0
- package/extensions/services/kite_console/static/js/tests/integration/pingpong.js +25 -0
- package/extensions/services/kite_console/static/js/tests/integration/redis.js +227 -0
- package/extensions/services/kite_console/static/js/tests/integration/registry-core.js +52 -0
- package/extensions/services/kite_console/static/js/tests/integration/remote-deploy.js +85 -0
- package/extensions/services/kite_console/static/js/tests/integration/require-init.js +96 -0
- package/extensions/services/kite_console/static/js/tests/integration/scaling-control.js +193 -0
- package/extensions/services/kite_console/static/js/tests/integration/trace.js +109 -0
- package/extensions/services/kite_console/static/js/tests/modules/acp_channel.js +339 -0
- package/extensions/services/kite_console/static/js/tests/modules/auth.js +96 -0
- package/extensions/services/kite_console/static/js/tests/modules/backup.js +49 -0
- package/extensions/services/kite_console/static/js/tests/modules/gateway.js +41 -0
- package/extensions/services/kite_console/static/js/tests/modules/kernel.js +90 -0
- package/extensions/services/kite_console/static/js/tests/modules/launcher.js +75 -0
- package/extensions/services/kite_console/static/js/tests/modules/multi_instance.js +129 -0
- package/extensions/services/kite_console/static/js/tests/modules/phone_channel.js +364 -0
- package/extensions/services/kite_console/static/js/tests/modules/redis.js +178 -0
- package/extensions/services/kite_console/static/js/tests/modules/watchdog.js +60 -0
- package/extensions/services/kite_console/static/js/tests/modules/web.js +70 -0
- package/extensions/services/kite_console/static/js/tests/test-runner.js +123 -0
- package/extensions/services/kite_console/static/js/virtual-list.js +200 -0
- package/extensions/services/kite_console/static/test_kernel_client_token.html +352 -0
- package/extensions/services/kite_console/stats_manager.py +247 -0
- package/extensions/services/logs/README.md +215 -0
- package/extensions/services/logs/api_logger.py +37 -0
- package/extensions/services/logs/baseline.py +121 -0
- package/extensions/services/logs/cleaner.py +76 -0
- package/extensions/services/logs/entry.py +449 -0
- package/extensions/services/logs/formatter.py +129 -0
- package/extensions/services/logs/module.md +38 -0
- package/extensions/services/logs/quick_diagnostic.py +128 -0
- package/extensions/services/logs/routes/__init__.py +1 -0
- package/extensions/services/logs/routes/routes_logs.py +218 -0
- package/extensions/services/logs/routes/routes_logs.py.backup +173 -0
- package/extensions/services/logs/scanner.py +100 -0
- package/extensions/services/logs/searcher.py +263 -0
- package/extensions/services/logs/server.py +553 -0
- package/extensions/services/logs.zip +0 -0
- package/extensions/services/model_service/config.json5 +30 -0
- package/extensions/services/model_service/entry.py +620 -171
- package/extensions/services/model_service/module.md +11 -2
- package/extensions/services/proxy/__init__.py +0 -0
- package/extensions/services/proxy/aid_manager.py +419 -0
- package/extensions/services/proxy/auth_bridge.py +182 -0
- package/extensions/services/proxy/config_store.py +79 -0
- package/extensions/services/proxy/entry.py +528 -0
- package/extensions/services/proxy/evol/presenter/agentIdPresenter.py +2 -2
- package/extensions/services/proxy/evol/presenter/apikeyPresenter.py +18 -28
- package/extensions/services/proxy/evol/presenter/configPresenter.py +80 -1127
- package/extensions/services/proxy/evol/presenter/userPresenter.py +71 -477
- package/extensions/services/proxy/evol/server/claude_proxy_async.py +11 -7
- package/extensions/services/proxy/module.md +151 -0
- package/extensions/services/proxy/server.py +952 -271
- package/extensions/services/redis/ALIGNMENT_CHECKLIST.md +121 -0
- package/extensions/services/redis/ALIGNMENT_STATUS.md +548 -0
- package/extensions/services/redis/config.json5 +8 -0
- package/extensions/services/redis/entry.py +1509 -0
- package/extensions/services/redis/entry.py.backup +405 -0
- package/extensions/services/redis/module.md +48 -0
- package/extensions/services/redis/redis_builtin.py +332 -0
- package/extensions/services/redis/redis_external.py +164 -0
- package/extensions/services/testUi/entry.py +446 -0
- package/extensions/services/testUi/module.md +18 -0
- package/extensions/services/testUi/ui/cards.html +131 -0
- package/extensions/services/testUi/ui/index.html +22 -0
- package/extensions/services/testUi/ui/particles.html +143 -0
- package/extensions/services/watchdog/entry.py +1258 -793
- package/extensions/services/watchdog/module.md +2 -0
- package/extensions/services/watchdog/monitor.py +465 -87
- package/extensions/services/web/auth_manager.py +602 -0
- package/extensions/services/web/config.json5 +11 -0
- package/extensions/services/web/entry.py +598 -478
- package/extensions/services/web/mfa_totp.py +77 -0
- package/extensions/services/web/module.md +16 -13
- package/extensions/services/web/nonce_pool.py +113 -0
- package/extensions/services/web/oauth_manager.py +223 -0
- package/extensions/services/web/pairing.py +3 -2
- package/extensions/services/web/pairing_codes.jsonl +1 -0
- package/extensions/services/web/relay.py +442 -63
- package/extensions/services/web/relay_config.json5 +1 -2
- package/extensions/services/web/routes/routes_rpc.py +6 -6
- package/extensions/services/web/server.py +360 -173
- package/extensions/services/web/static/index.html +1752 -1738
- package/extensions/services/web/static/js/app.js +32 -0
- package/extensions/services/web/static/js/kernel-client.js +48 -9
- package/extensions/services/web/vendor/bluetooth/audio.py +1 -1
- package/extensions/services/web/vendor/config.py +2 -2
- package/extensions/services/web/vendor/storage/identity.py +1 -1
- package/kernel/entry.py +77 -23
- package/kernel/event_hub.py +1122 -74
- package/kernel/module.md +2 -1
- package/kernel/registry_store.py +208 -11
- package/kernel/rpc_router.py +1400 -491
- package/kernel/server.py +1021 -134
- package/kite_cli/__init__.py +9 -1
- package/kite_cli/builders/__init__.py +4 -0
- package/kite_cli/builders/base.py +67 -0
- package/kite_cli/builders/custom.py +31 -0
- package/kite_cli/builders/detector.py +56 -0
- package/kite_cli/builders/go.py +34 -0
- package/kite_cli/builders/gradle.py +41 -0
- package/kite_cli/builders/maven.py +36 -0
- package/kite_cli/builders/npm.py +44 -0
- package/kite_cli/builders/python.py +37 -0
- package/kite_cli/commands/BUILD_GUIDE.md +109 -0
- package/kite_cli/commands/build.py +142 -0
- package/kite_cli/commands/check.py +60 -0
- package/kite_cli/commands/config.py +156 -0
- package/kite_cli/commands/deps.py +58 -0
- package/kite_cli/commands/deps_install.py +7 -7
- package/kite_cli/commands/disable.py +162 -0
- package/kite_cli/commands/enable.py +162 -0
- package/kite_cli/commands/export.py +96 -0
- package/kite_cli/commands/import_cmd.py +110 -0
- package/kite_cli/commands/install.py +50 -23
- package/kite_cli/commands/install_skill.py +107 -0
- package/kite_cli/commands/list.py +128 -31
- package/kite_cli/commands/outdated.py +202 -0
- package/kite_cli/commands/search.py +33 -17
- package/kite_cli/commands/update.py +115 -2
- package/kite_cli/commands/venv_setup.py +6 -6
- package/kite_cli/commands/why.py +48 -0
- package/kite_cli/core/config_manager.py +145 -0
- package/kite_cli/core/downloader.py +32 -2
- package/kite_cli/main.py +153 -7
- package/kite_cli/utils/colors.py +153 -0
- package/kite_cli/utils/dependency_graph.py +209 -0
- package/kite_cli/utils/process.py +55 -0
- package/kite_cli/utils/progress.py +207 -0
- package/kite_cli/utils/table.py +101 -0
- package/launcher/count_lines.py +192 -43
- package/launcher/entry.py +4543 -2802
- package/launcher/logging_setup.py +54 -1
- package/launcher/module.md +32 -6
- package/launcher/module_scanner.py +93 -20
- package/launcher/process_manager.py +355 -76
- package/main.py +6 -0
- package/package.json +4 -1
- package/requirements.txt +41 -38
- package/scripts/auto-fix-deps.py +128 -0
- package/scripts/env-manager.js +25 -2
- package/scripts/final-test.js +78 -0
- package/scripts/setup-python-env.js +700 -191
- package/scripts/test-alluser.js +48 -0
- package/scripts/test-different-version.js +86 -0
- package/scripts/test-direct.js +63 -0
- package/scripts/test-extract-installer.js +28 -0
- package/scripts/test-install-log.js +54 -0
- package/scripts/test-installer.js +39 -0
- package/scripts/test-integration.js +250 -0
- package/scripts/test-real-install.js +210 -0
- package/scripts/test-targetdir.js +49 -0
- package/scripts/test-venv-real.js +47 -0
- package/scripts/test-venv-simple.js +57 -0
- package/scripts/test-wait.js +49 -0
- package/scripts/test-with-log.js +63 -0
- package/extensions/services/evol/config.yaml +0 -149
- package/extensions/services/evol/routes/routes_management_ws.py +0 -127
- package/extensions/services/evol/static/index_evol.html +0 -14
- package/extensions/services/evol/static/js/app.js +0 -6304
- package/extensions/services/evol/static/js/auth.js +0 -326
- package/extensions/services/evol/static/js/evol-app-fixed.js +0 -50
- package/extensions/services/evol/static/js/evol-app.js.bak +0 -1800
- package/extensions/services/evol/static/js/kernel-client-example.js +0 -228
- package/extensions/services/evol/static/js/main.js +0 -141
- package/extensions/services/evol/static/js/stats.js +0 -217
- package/extensions/services/evol/static/js/token-manager.js +0 -175
- package/extensions/services/proxy/CHANGELOG_20260308.md +0 -258
- package/extensions/services/proxy/_fix_prints.py +0 -133
- package/extensions/services/proxy/_fix_prints2.py +0 -87
- package/extensions/services/proxy/console_auth.py +0 -109
- package/extensions/services/proxy/logs/websocket.log +0 -260
- package/extensions/services/proxy/main.py +0 -240
- package/extensions/services/proxy/requirements.txt +0 -13
- package/extensions/services/web/config.yaml +0 -149
- /package/extensions/services/{evol → kite_console}/static/pairing.html +0 -0
- /package/extensions/services/{evol → kite_console}/static/test_registry.html +0 -0
- /package/extensions/services/{evol → kite_console}/static/test_relay.html +0 -0
|
@@ -8,6 +8,7 @@ stdio lifecycle:
|
|
|
8
8
|
- Structured messages: JSON lines with "kite" field, dispatched via callback
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
+
import collections
|
|
11
12
|
import glob
|
|
12
13
|
import json
|
|
13
14
|
import os
|
|
@@ -16,6 +17,7 @@ import subprocess
|
|
|
16
17
|
import sys
|
|
17
18
|
import threading
|
|
18
19
|
import time
|
|
20
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
19
21
|
from dataclasses import dataclass, asdict
|
|
20
22
|
from typing import Callable
|
|
21
23
|
|
|
@@ -34,21 +36,48 @@ class ProcessRecord:
|
|
|
34
36
|
cmd: list
|
|
35
37
|
module_dir: str
|
|
36
38
|
started_at: float
|
|
39
|
+
instance_num: int = 1
|
|
37
40
|
|
|
38
41
|
|
|
39
42
|
class ProcessManager:
|
|
40
43
|
"""Manage child processes for all Kite modules."""
|
|
41
44
|
|
|
45
|
+
@staticmethod
|
|
46
|
+
def _instance_key(name: str, instance_num: int = 1) -> str:
|
|
47
|
+
"""Build internal dict key: 'name' for instance 1, 'name#N' for N>1."""
|
|
48
|
+
return name if instance_num <= 1 else f"{name}#{instance_num}"
|
|
49
|
+
|
|
50
|
+
@staticmethod
|
|
51
|
+
def parse_instance_key(key: str) -> tuple[str, int]:
|
|
52
|
+
"""Parse internal key back to (module_name, instance_num)."""
|
|
53
|
+
if "#" in key:
|
|
54
|
+
name, num_str = key.rsplit("#", 1)
|
|
55
|
+
try:
|
|
56
|
+
return name, int(num_str)
|
|
57
|
+
except ValueError:
|
|
58
|
+
pass
|
|
59
|
+
return key, 1
|
|
60
|
+
|
|
61
|
+
LOG_BUFFER_SIZE = 1000 # lines per module
|
|
62
|
+
|
|
42
63
|
def __init__(self, kite_token: str, instance_id: str = "",
|
|
43
|
-
on_kite_message: Callable[[str, dict], None] | None = None
|
|
64
|
+
on_kite_message: Callable[[str, dict], None] | None = None,
|
|
65
|
+
on_log_line: Callable[[str, float, str], None] | None = None):
|
|
44
66
|
"""
|
|
45
67
|
Args:
|
|
46
68
|
on_kite_message: callback(module_name, msg_dict) for structured stdout messages.
|
|
47
69
|
Called from stdout reader thread — must be thread-safe.
|
|
70
|
+
on_log_line: callback(module_name, timestamp, line) for every log line.
|
|
71
|
+
Called from stdout reader thread — must be thread-safe.
|
|
48
72
|
"""
|
|
49
73
|
self.kite_token = kite_token
|
|
50
74
|
self.instance_id = instance_id or str(os.getpid())
|
|
51
75
|
self._on_kite_message = on_kite_message
|
|
76
|
+
self._on_log_line = on_log_line
|
|
77
|
+
# Per-module ring buffers: name -> deque of (seq, ts, line)
|
|
78
|
+
self._log_buffers: dict[str, collections.deque] = {}
|
|
79
|
+
self._log_seq: dict[str, int] = {} # name -> next seq number
|
|
80
|
+
self._log_lock = threading.Lock()
|
|
52
81
|
# Use KITE_MODULE_DATA (set by Launcher for itself)
|
|
53
82
|
launcher_dir = os.environ.get("KITE_MODULE_DATA") or os.path.join(os.environ["KITE_INSTANCE_DIR"], "launcher")
|
|
54
83
|
self.data_dir = os.path.join(launcher_dir, "state")
|
|
@@ -249,10 +278,24 @@ class ProcessManager:
|
|
|
249
278
|
"""
|
|
250
279
|
my_pid = os.getpid()
|
|
251
280
|
inst_name = os.path.basename(os.environ.get("KITE_INSTANCE_DIR", "")) or "unknown"
|
|
252
|
-
|
|
281
|
+
|
|
282
|
+
# Collect all processes*.json files
|
|
253
283
|
pattern = os.path.join(self.data_dir, "processes*.json")
|
|
254
|
-
|
|
255
|
-
|
|
284
|
+
files_to_cleanup = list(glob.glob(pattern))
|
|
285
|
+
|
|
286
|
+
if not files_to_cleanup:
|
|
287
|
+
return {}
|
|
288
|
+
|
|
289
|
+
# Parallel cleanup: each file runs its own 3-phase cleanup independently
|
|
290
|
+
killed = 0
|
|
291
|
+
with ThreadPoolExecutor(max_workers=len(files_to_cleanup)) as executor:
|
|
292
|
+
futures = [executor.submit(self._cleanup_one_leftover_file, filepath, my_pid) for filepath in files_to_cleanup]
|
|
293
|
+
for future in as_completed(futures):
|
|
294
|
+
try:
|
|
295
|
+
killed += future.result()
|
|
296
|
+
except Exception as e:
|
|
297
|
+
print(f"[launcher] 清理残留文件时出错: {e}")
|
|
298
|
+
|
|
256
299
|
return {inst_name: killed} if killed else {}
|
|
257
300
|
|
|
258
301
|
def cleanup_global_leftovers(self) -> dict[str, int]:
|
|
@@ -274,15 +317,35 @@ class ProcessManager:
|
|
|
274
317
|
except OSError:
|
|
275
318
|
return {}
|
|
276
319
|
|
|
320
|
+
# Collect all processes*.json files
|
|
321
|
+
files_to_cleanup = []
|
|
277
322
|
for entry in entries:
|
|
278
323
|
state_dir = os.path.join(workspace, entry, "launcher", "state")
|
|
279
324
|
if not os.path.isdir(state_dir):
|
|
280
325
|
continue
|
|
281
326
|
pattern = os.path.join(state_dir, "processes*.json")
|
|
282
327
|
for filepath in glob.glob(pattern):
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
328
|
+
files_to_cleanup.append((filepath, entry))
|
|
329
|
+
|
|
330
|
+
# Parallel cleanup: each file runs its own 3-phase cleanup independently
|
|
331
|
+
if not files_to_cleanup:
|
|
332
|
+
return result
|
|
333
|
+
|
|
334
|
+
with ThreadPoolExecutor(max_workers=len(files_to_cleanup)) as executor:
|
|
335
|
+
futures = {}
|
|
336
|
+
for filepath, entry in files_to_cleanup:
|
|
337
|
+
future = executor.submit(self._cleanup_one_leftover_file, filepath, my_pid)
|
|
338
|
+
futures[future] = entry
|
|
339
|
+
|
|
340
|
+
for future in as_completed(futures):
|
|
341
|
+
entry = futures[future]
|
|
342
|
+
try:
|
|
343
|
+
killed = future.result()
|
|
344
|
+
if killed:
|
|
345
|
+
result[entry] = result.get(entry, 0) + killed
|
|
346
|
+
except Exception as e:
|
|
347
|
+
print(f"[launcher] 清理实例 {entry} 时出错: {e}")
|
|
348
|
+
|
|
286
349
|
return result
|
|
287
350
|
|
|
288
351
|
def _cleanup_one_leftover_file(self, filepath: str, my_pid: int) -> int:
|
|
@@ -290,7 +353,10 @@ class ProcessManager:
|
|
|
290
353
|
|
|
291
354
|
- Skip if launcher_pid matches my_pid (our own records).
|
|
292
355
|
- Skip if launcher_pid is still alive (parallel instance).
|
|
293
|
-
- Otherwise clean up child processes
|
|
356
|
+
- Otherwise clean up child processes in 3 phases:
|
|
357
|
+
1. Kill all watchdog processes (serial)
|
|
358
|
+
2. Kill all middle-tier processes (parallel)
|
|
359
|
+
3. Kill all kernel processes (serial)
|
|
294
360
|
Returns number of processes killed.
|
|
295
361
|
"""
|
|
296
362
|
try:
|
|
@@ -327,30 +393,87 @@ class ProcessManager:
|
|
|
327
393
|
return 0
|
|
328
394
|
|
|
329
395
|
# Dead launcher (or old format) — clean up its child processes
|
|
330
|
-
#
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
return
|
|
396
|
+
# New approach: All processes start cleanup in parallel with dependency checks
|
|
397
|
+
all_procs = records
|
|
398
|
+
if not all_procs:
|
|
399
|
+
try:
|
|
400
|
+
os.remove(filepath)
|
|
401
|
+
except OSError:
|
|
402
|
+
pass
|
|
403
|
+
return 0
|
|
338
404
|
|
|
339
|
-
|
|
405
|
+
# Shared state for dependency tracking (instance-local)
|
|
406
|
+
killed_names = set() # Track which process names have been killed
|
|
407
|
+
killed_lock = threading.Lock()
|
|
408
|
+
killed_condition = threading.Condition(killed_lock) # Notify when any process is killed
|
|
340
409
|
|
|
341
|
-
|
|
342
|
-
|
|
410
|
+
def cleanup_with_deps(entry: dict) -> bool:
|
|
411
|
+
"""Cleanup a single process with dependency checking."""
|
|
412
|
+
name = entry.get("name", "")
|
|
343
413
|
pid = entry.get("pid", 0)
|
|
344
414
|
cmd = entry.get("cmd", [])
|
|
345
|
-
|
|
415
|
+
|
|
346
416
|
if not pid:
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
killed += 1
|
|
352
|
-
else:
|
|
417
|
+
return False
|
|
418
|
+
|
|
419
|
+
# Step 1: Verify process is alive
|
|
420
|
+
if not self._is_pid_alive(pid):
|
|
353
421
|
print(f"[launcher] 残留进程 {name} (PID {pid}) 已不存在")
|
|
422
|
+
with killed_condition:
|
|
423
|
+
killed_names.add(name)
|
|
424
|
+
killed_condition.notify_all() # Notify all waiters
|
|
425
|
+
return False
|
|
426
|
+
|
|
427
|
+
# Step 2: Verify command line (防止 PID 复用误杀)
|
|
428
|
+
if not self._cmd_matches(pid, cmd):
|
|
429
|
+
print(f"[launcher] 残留进程 {name} (PID {pid}) 命令行不匹配,跳过")
|
|
430
|
+
with killed_condition:
|
|
431
|
+
killed_names.add(name) # Mark as "handled" to unblock dependents
|
|
432
|
+
killed_condition.notify_all()
|
|
433
|
+
return False
|
|
434
|
+
|
|
435
|
+
# Step 3: Wait for dependencies (instance-local, event-driven)
|
|
436
|
+
dependencies = self._get_process_dependencies(name, all_procs)
|
|
437
|
+
if dependencies:
|
|
438
|
+
with killed_condition:
|
|
439
|
+
while True:
|
|
440
|
+
# Check if all dependencies are satisfied
|
|
441
|
+
remaining = dependencies - killed_names
|
|
442
|
+
if not remaining:
|
|
443
|
+
break
|
|
444
|
+
# Wait for notification (releases lock, reacquires on wake)
|
|
445
|
+
killed_condition.wait()
|
|
446
|
+
|
|
447
|
+
# Step 4: Kill the process
|
|
448
|
+
print(f"[launcher] 正在清理残留进程: {name} (PID {pid}) [{os.path.basename(filepath)}]")
|
|
449
|
+
self._force_kill(pid)
|
|
450
|
+
|
|
451
|
+
# Step 5: Verify process is dead (wait up to 100ms)
|
|
452
|
+
for i in range(10):
|
|
453
|
+
if not self._is_pid_alive(pid):
|
|
454
|
+
break
|
|
455
|
+
import time
|
|
456
|
+
time.sleep(0.01)
|
|
457
|
+
else:
|
|
458
|
+
print(f"[launcher] 警告: 进程 {name} (PID {pid}) 在 100ms 后仍未退出")
|
|
459
|
+
|
|
460
|
+
# Step 6: Mark as killed and notify waiters
|
|
461
|
+
with killed_condition:
|
|
462
|
+
killed_names.add(name)
|
|
463
|
+
killed_condition.notify_all() # Wake up all waiting threads
|
|
464
|
+
|
|
465
|
+
return True
|
|
466
|
+
|
|
467
|
+
# Launch all cleanup tasks in parallel
|
|
468
|
+
killed = 0
|
|
469
|
+
with ThreadPoolExecutor(max_workers=len(all_procs)) as executor:
|
|
470
|
+
futures = [executor.submit(cleanup_with_deps, entry) for entry in all_procs]
|
|
471
|
+
for future in as_completed(futures):
|
|
472
|
+
try:
|
|
473
|
+
if future.result():
|
|
474
|
+
killed += 1
|
|
475
|
+
except Exception as e:
|
|
476
|
+
print(f"[launcher] 并行清理进程时出错: {e}")
|
|
354
477
|
|
|
355
478
|
try:
|
|
356
479
|
os.remove(filepath)
|
|
@@ -358,6 +481,61 @@ class ProcessManager:
|
|
|
358
481
|
pass
|
|
359
482
|
return killed
|
|
360
483
|
|
|
484
|
+
def _get_process_dependencies(self, name: str, all_procs: list) -> set:
|
|
485
|
+
"""Get the set of process names that must be killed before this one.
|
|
486
|
+
|
|
487
|
+
Dependency rules (instance-local):
|
|
488
|
+
- watchdog: no dependencies
|
|
489
|
+
- kernel: depends on all other processes in this instance
|
|
490
|
+
- others: depend on watchdog (if exists)
|
|
491
|
+
"""
|
|
492
|
+
if name == "watchdog":
|
|
493
|
+
return set()
|
|
494
|
+
elif name == "kernel":
|
|
495
|
+
# Kernel depends on all other processes in this instance
|
|
496
|
+
return {p.get("name", "") for p in all_procs if p.get("name") != "kernel"}
|
|
497
|
+
else:
|
|
498
|
+
# Middle-tier processes depend on watchdog (if exists in this instance)
|
|
499
|
+
watchdog_exists = any(p.get("name") == "watchdog" for p in all_procs)
|
|
500
|
+
return {"watchdog"} if watchdog_exists else set()
|
|
501
|
+
|
|
502
|
+
def _kill_one_process(self, entry: dict, filepath: str, skip_cmd_check: bool = False) -> bool:
|
|
503
|
+
"""Kill a single process. Returns True if killed, False otherwise.
|
|
504
|
+
|
|
505
|
+
Args:
|
|
506
|
+
entry: Process record dict with pid, cmd, name
|
|
507
|
+
filepath: Path to processes*.json (for logging)
|
|
508
|
+
skip_cmd_check: If True, skip command line verification (faster, for leftover cleanup)
|
|
509
|
+
"""
|
|
510
|
+
import time
|
|
511
|
+
pid = entry.get("pid", 0)
|
|
512
|
+
cmd = entry.get("cmd", [])
|
|
513
|
+
name = entry.get("name", "?")
|
|
514
|
+
if not pid:
|
|
515
|
+
return False
|
|
516
|
+
|
|
517
|
+
# Check if process is alive
|
|
518
|
+
if not self._is_pid_alive(pid):
|
|
519
|
+
print(f"[launcher] 残留进程 {name} (PID {pid}) 已不存在")
|
|
520
|
+
return False
|
|
521
|
+
|
|
522
|
+
# Verify command line (unless skipped for performance)
|
|
523
|
+
if not skip_cmd_check and not self._cmd_matches(pid, cmd):
|
|
524
|
+
print(f"[launcher] 残留进程 {name} (PID {pid}) 命令行不匹配,跳过")
|
|
525
|
+
return False
|
|
526
|
+
|
|
527
|
+
print(f"[launcher] 正在清理残留进程: {name} (PID {pid}) [{os.path.basename(filepath)}]")
|
|
528
|
+
self._force_kill(pid)
|
|
529
|
+
|
|
530
|
+
# Verify the process is actually dead (wait up to 100ms)
|
|
531
|
+
for i in range(10):
|
|
532
|
+
if not self._is_pid_alive(pid):
|
|
533
|
+
return True
|
|
534
|
+
time.sleep(0.01)
|
|
535
|
+
|
|
536
|
+
print(f"[launcher] 警告: 进程 {name} (PID {pid}) 在 100ms 后仍未退出")
|
|
537
|
+
return True
|
|
538
|
+
|
|
361
539
|
def _is_pid_alive(self, pid: int) -> bool:
|
|
362
540
|
"""Check if a process with given PID exists.
|
|
363
541
|
|
|
@@ -604,8 +782,18 @@ class ProcessManager:
|
|
|
604
782
|
"""Force kill a process by PID."""
|
|
605
783
|
try:
|
|
606
784
|
if IS_WINDOWS:
|
|
607
|
-
|
|
608
|
-
|
|
785
|
+
# Use TerminateProcess API directly (faster than taskkill)
|
|
786
|
+
PROCESS_TERMINATE = 0x0001
|
|
787
|
+
handle = ctypes.windll.kernel32.OpenProcess(PROCESS_TERMINATE, False, pid)
|
|
788
|
+
if handle:
|
|
789
|
+
try:
|
|
790
|
+
ctypes.windll.kernel32.TerminateProcess(handle, 1)
|
|
791
|
+
finally:
|
|
792
|
+
ctypes.windll.kernel32.CloseHandle(handle)
|
|
793
|
+
else:
|
|
794
|
+
# Fallback to taskkill if OpenProcess fails
|
|
795
|
+
subprocess.run(["taskkill", "/F", "/PID", str(pid)],
|
|
796
|
+
capture_output=True, timeout=5)
|
|
609
797
|
else:
|
|
610
798
|
import signal
|
|
611
799
|
os.kill(pid, signal.SIGKILL)
|
|
@@ -614,29 +802,37 @@ class ProcessManager:
|
|
|
614
802
|
|
|
615
803
|
# ── Start module ──
|
|
616
804
|
|
|
617
|
-
def start_module(self, info, boot_info: dict = None) -> bool:
|
|
805
|
+
def start_module(self, info, boot_info: dict = None, instance_num: int = 1) -> bool:
|
|
618
806
|
"""Start a module as a subprocess. Returns True on success.
|
|
619
807
|
If boot_info is provided, it is written as JSON to the child's stdin pipe.
|
|
620
808
|
stdin is kept open after boot_info for Launcher to send additional messages.
|
|
809
|
+
|
|
810
|
+
Args:
|
|
811
|
+
instance_num: Instance number (1=primary, >1=extra instance).
|
|
621
812
|
"""
|
|
622
813
|
from .module_scanner import ModuleInfo
|
|
623
814
|
info: ModuleInfo
|
|
624
815
|
|
|
625
|
-
|
|
626
|
-
|
|
816
|
+
key = self._instance_key(info.name, instance_num)
|
|
817
|
+
label = info.name if instance_num <= 1 else f"{info.name}#{instance_num}"
|
|
818
|
+
|
|
819
|
+
if key in self._processes:
|
|
820
|
+
proc = self._processes[key]
|
|
627
821
|
if proc.poll() is None:
|
|
628
|
-
print(f"[launcher] {
|
|
822
|
+
print(f"[launcher] {label} 已在运行 (PID {proc.pid})")
|
|
629
823
|
return True
|
|
630
824
|
|
|
631
825
|
cmd = self._build_cmd(info)
|
|
632
826
|
if not cmd:
|
|
633
|
-
print(f"[launcher] 错误: 无法构建 {
|
|
827
|
+
print(f"[launcher] 错误: 无法构建 {label} 的启动命令")
|
|
634
828
|
return False
|
|
635
829
|
|
|
636
830
|
env = os.environ.copy()
|
|
637
831
|
env["PYTHONUTF8"] = "1" # Force UTF-8 in child Python processes
|
|
638
832
|
env["PYTHONUNBUFFERED"] = "1" # Disable output buffering for real-time logs
|
|
639
833
|
env["KITE_MODULE_DATA"] = os.path.join(os.environ["KITE_INSTANCE_DIR"], info.name)
|
|
834
|
+
if instance_num > 1:
|
|
835
|
+
env["KITE_INSTANCE_NUM"] = str(instance_num)
|
|
640
836
|
if info.launch.env:
|
|
641
837
|
env.update(info.launch.env)
|
|
642
838
|
|
|
@@ -644,8 +840,6 @@ class ProcessManager:
|
|
|
644
840
|
if IS_WINDOWS:
|
|
645
841
|
creation_flags = subprocess.CREATE_NO_WINDOW
|
|
646
842
|
|
|
647
|
-
use_stdin = boot_info is not None and info.launch.boot_stdin
|
|
648
|
-
|
|
649
843
|
cwd = info.module_dir
|
|
650
844
|
if info.launch.cwd:
|
|
651
845
|
cwd = os.path.normpath(os.path.join(info.module_dir, info.launch.cwd))
|
|
@@ -655,48 +849,50 @@ class ProcessManager:
|
|
|
655
849
|
cmd,
|
|
656
850
|
cwd=cwd,
|
|
657
851
|
env=env,
|
|
658
|
-
stdin=subprocess.PIPE
|
|
852
|
+
stdin=subprocess.PIPE, # stdin 管道始终创建,解耦 boot_info
|
|
659
853
|
stdout=subprocess.PIPE,
|
|
660
854
|
stderr=subprocess.STDOUT,
|
|
661
855
|
creationflags=creation_flags,
|
|
662
856
|
)
|
|
663
857
|
except Exception as e:
|
|
664
|
-
print(f"[launcher] 错误: 启动 {
|
|
858
|
+
print(f"[launcher] 错误: 启动 {label} 失败: {e}")
|
|
665
859
|
return False
|
|
666
860
|
|
|
667
861
|
# Write boot_info to stdin — keep stdin open for additional messages
|
|
668
|
-
if
|
|
862
|
+
if boot_info is not None and info.launch.boot_stdin and proc.stdin:
|
|
669
863
|
try:
|
|
670
864
|
proc.stdin.write(json.dumps(boot_info).encode() + b"\n")
|
|
671
865
|
proc.stdin.flush()
|
|
672
866
|
except Exception as e:
|
|
673
|
-
print(f"[launcher] 警告: 向 {
|
|
867
|
+
print(f"[launcher] 警告: 向 {label} 写入 boot_info 失败: {e}")
|
|
674
868
|
|
|
675
|
-
self._processes[
|
|
676
|
-
self._records[
|
|
869
|
+
self._processes[key] = proc
|
|
870
|
+
self._records[key] = ProcessRecord(
|
|
677
871
|
name=info.name,
|
|
678
872
|
pid=proc.pid,
|
|
679
873
|
cmd=cmd,
|
|
680
874
|
module_dir=info.module_dir,
|
|
681
875
|
started_at=time.time(),
|
|
876
|
+
instance_num=instance_num,
|
|
682
877
|
)
|
|
683
878
|
|
|
684
879
|
# Daemon thread to read stdout: structured messages + log lines
|
|
685
880
|
t = threading.Thread(
|
|
686
|
-
target=self._read_stdout, args=(
|
|
881
|
+
target=self._read_stdout, args=(label, proc),
|
|
687
882
|
daemon=True,
|
|
688
883
|
)
|
|
689
884
|
t.start()
|
|
690
885
|
|
|
691
|
-
print(f"[launcher] 已启动 {
|
|
886
|
+
print(f"[launcher] 已启动 {label} (PID {proc.pid})")
|
|
692
887
|
return True
|
|
693
888
|
|
|
694
|
-
def write_stdin(self, name: str, data: dict) -> bool:
|
|
889
|
+
def write_stdin(self, name: str, data: dict, instance_num: int = 1) -> bool:
|
|
695
890
|
"""Write a structured JSON message to a module's stdin.
|
|
696
891
|
Used by Launcher to send additional messages after boot_info (e.g. launcher_ws_token).
|
|
697
892
|
Returns True on success.
|
|
698
893
|
"""
|
|
699
|
-
|
|
894
|
+
key = self._instance_key(name, instance_num)
|
|
895
|
+
proc = self._processes.get(key)
|
|
700
896
|
if not proc or not proc.stdin or proc.stdin.closed:
|
|
701
897
|
return False
|
|
702
898
|
try:
|
|
@@ -707,13 +903,14 @@ class ProcessManager:
|
|
|
707
903
|
print(f"[launcher] 警告: 向 {name} 写入 stdin 失败: {e}")
|
|
708
904
|
return False
|
|
709
905
|
|
|
710
|
-
def close_stdio(self, name: str):
|
|
906
|
+
def close_stdio(self, name: str, instance_num: int = 1):
|
|
711
907
|
"""Close stdin for a module. Called by Launcher after module.ready → module.started.
|
|
712
908
|
Only closes stdin (no more messages to send). stdout is left open so the reader
|
|
713
909
|
thread can continue relaying logs — it exits naturally when the process dies.
|
|
714
910
|
On Windows, closing stdout while a reader thread is blocked on readline() can deadlock.
|
|
715
911
|
"""
|
|
716
|
-
|
|
912
|
+
key = self._instance_key(name, instance_num)
|
|
913
|
+
proc = self._processes.get(key)
|
|
717
914
|
if not proc:
|
|
718
915
|
return
|
|
719
916
|
if proc.stdin and not proc.stdin.closed:
|
|
@@ -769,37 +966,94 @@ class ProcessManager:
|
|
|
769
966
|
print(text)
|
|
770
967
|
else:
|
|
771
968
|
print(f"[{name}] {text}")
|
|
969
|
+
# Note: terminal buffering is handled by _tprint hook in logging_setup,
|
|
970
|
+
# so the buffer contains the full timestamped+colored output line.
|
|
772
971
|
except Exception:
|
|
773
972
|
pass
|
|
774
973
|
|
|
974
|
+
def get_log_history(self, name: str, offset: int = 0, count: int = 200) -> dict:
|
|
975
|
+
"""Return buffered log lines for a module.
|
|
976
|
+
|
|
977
|
+
Args:
|
|
978
|
+
name: module name (label)
|
|
979
|
+
offset: return lines with seq >= offset; -1 means last `count` lines
|
|
980
|
+
count: max lines to return
|
|
981
|
+
Returns:
|
|
982
|
+
{"lines": [{"seq", "ts", "text"}], "total": int, "module": name}
|
|
983
|
+
"""
|
|
984
|
+
with self._log_lock:
|
|
985
|
+
buf = self._log_buffers.get(name)
|
|
986
|
+
if buf is None:
|
|
987
|
+
return {"lines": [], "total": 0, "module": name}
|
|
988
|
+
all_lines = list(buf)
|
|
989
|
+
if offset < 0:
|
|
990
|
+
selected = all_lines[-count:]
|
|
991
|
+
else:
|
|
992
|
+
selected = [e for e in all_lines if e[0] >= offset][:count]
|
|
993
|
+
return {
|
|
994
|
+
"lines": [{"seq": e[0], "ts": e[1], "text": e[2]} for e in selected],
|
|
995
|
+
"total": len(all_lines),
|
|
996
|
+
"module": name,
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
def list_log_modules(self) -> list[str]:
|
|
1000
|
+
"""Return list of modules that have log buffer entries."""
|
|
1001
|
+
with self._log_lock:
|
|
1002
|
+
return list(self._log_buffers.keys())
|
|
1003
|
+
|
|
775
1004
|
# ── Stop module ──
|
|
776
1005
|
|
|
777
|
-
def stop_module(self, name: str, timeout: float = 5) -> bool:
|
|
1006
|
+
def stop_module(self, name: str, timeout: float = 5, instance_num: int = 1) -> bool:
|
|
778
1007
|
"""Stop a module by name. Returns True if stopped successfully."""
|
|
779
|
-
|
|
1008
|
+
key = self._instance_key(name, instance_num)
|
|
1009
|
+
label = name if instance_num <= 1 else f"{name}#{instance_num}"
|
|
1010
|
+
proc = self._processes.get(key)
|
|
780
1011
|
if not proc or proc.poll() is not None:
|
|
781
|
-
self._processes.pop(
|
|
782
|
-
self._records.pop(
|
|
1012
|
+
self._processes.pop(key, None)
|
|
1013
|
+
self._records.pop(key, None)
|
|
783
1014
|
return True
|
|
784
1015
|
|
|
785
1016
|
# Close stdio before terminating
|
|
786
|
-
self.close_stdio(name)
|
|
1017
|
+
self.close_stdio(name, instance_num)
|
|
787
1018
|
|
|
788
|
-
print(f"[launcher] 正在停止 {
|
|
1019
|
+
print(f"[launcher] 正在停止 {label} (PID {proc.pid})...")
|
|
789
1020
|
proc.terminate()
|
|
790
1021
|
try:
|
|
791
1022
|
proc.wait(timeout=timeout)
|
|
792
1023
|
except subprocess.TimeoutExpired:
|
|
793
|
-
print(f"[launcher] {
|
|
1024
|
+
print(f"[launcher] {label} 在 {timeout}s 内未退出,强制终止")
|
|
794
1025
|
proc.kill()
|
|
795
1026
|
try:
|
|
796
1027
|
proc.wait(timeout=3)
|
|
797
1028
|
except subprocess.TimeoutExpired:
|
|
798
1029
|
self._force_kill(proc.pid)
|
|
799
1030
|
|
|
800
|
-
self._processes.pop(
|
|
801
|
-
self._records.pop(
|
|
802
|
-
print(f"[launcher] 已停止 {
|
|
1031
|
+
self._processes.pop(key, None)
|
|
1032
|
+
self._records.pop(key, None)
|
|
1033
|
+
print(f"[launcher] 已停止 {label}")
|
|
1034
|
+
return True
|
|
1035
|
+
|
|
1036
|
+
def ensure_exited(self, name: str, timeout: float = 3, instance_num: int = 1) -> bool:
|
|
1037
|
+
"""确保进程已完全退出(用于崩溃后确认端口释放)。"""
|
|
1038
|
+
key = self._instance_key(name, instance_num)
|
|
1039
|
+
proc = self._processes.get(key)
|
|
1040
|
+
if not proc:
|
|
1041
|
+
return True
|
|
1042
|
+
if proc.poll() is not None:
|
|
1043
|
+
self._processes.pop(key, None)
|
|
1044
|
+
self._records.pop(key, None)
|
|
1045
|
+
return True
|
|
1046
|
+
# 还活着,等待或强杀
|
|
1047
|
+
try:
|
|
1048
|
+
proc.wait(timeout=timeout)
|
|
1049
|
+
except subprocess.TimeoutExpired:
|
|
1050
|
+
proc.kill()
|
|
1051
|
+
try:
|
|
1052
|
+
proc.wait(timeout=2)
|
|
1053
|
+
except subprocess.TimeoutExpired:
|
|
1054
|
+
self._force_kill(proc.pid)
|
|
1055
|
+
self._processes.pop(key, None)
|
|
1056
|
+
self._records.pop(key, None)
|
|
803
1057
|
return True
|
|
804
1058
|
|
|
805
1059
|
def stop_all(self, timeout: float = 10):
|
|
@@ -808,7 +1062,7 @@ class ProcessManager:
|
|
|
808
1062
|
return
|
|
809
1063
|
|
|
810
1064
|
# Filter to only still-running processes
|
|
811
|
-
alive = [
|
|
1065
|
+
alive = [k for k, p in self._processes.items() if p.poll() is None]
|
|
812
1066
|
if not alive:
|
|
813
1067
|
# All already exited — just clean up bookkeeping
|
|
814
1068
|
self._processes.clear()
|
|
@@ -818,16 +1072,17 @@ class ProcessManager:
|
|
|
818
1072
|
print(f"[launcher] 正在停止所有模块: {', '.join(alive)}")
|
|
819
1073
|
|
|
820
1074
|
# Phase 1: close stdio and send terminate to all
|
|
821
|
-
for
|
|
822
|
-
self.
|
|
823
|
-
|
|
1075
|
+
for key in alive:
|
|
1076
|
+
mod_name, inst_num = self.parse_instance_key(key)
|
|
1077
|
+
self.close_stdio(mod_name, inst_num)
|
|
1078
|
+
proc = self._processes.get(key)
|
|
824
1079
|
if proc and proc.poll() is None:
|
|
825
1080
|
proc.terminate()
|
|
826
1081
|
|
|
827
1082
|
# Phase 2: wait for all to exit
|
|
828
1083
|
deadline = time.time() + timeout
|
|
829
|
-
for
|
|
830
|
-
proc = self._processes.get(
|
|
1084
|
+
for key in alive:
|
|
1085
|
+
proc = self._processes.get(key)
|
|
831
1086
|
if not proc:
|
|
832
1087
|
continue
|
|
833
1088
|
remaining = max(0.1, deadline - time.time())
|
|
@@ -837,10 +1092,10 @@ class ProcessManager:
|
|
|
837
1092
|
pass
|
|
838
1093
|
|
|
839
1094
|
# Phase 3: force kill survivors
|
|
840
|
-
for
|
|
841
|
-
proc = self._processes.get(
|
|
1095
|
+
for key in alive:
|
|
1096
|
+
proc = self._processes.get(key)
|
|
842
1097
|
if proc and proc.poll() is None:
|
|
843
|
-
print(f"[launcher] 强制终止 {
|
|
1098
|
+
print(f"[launcher] 强制终止 {key} (PID {proc.pid})")
|
|
844
1099
|
proc.kill()
|
|
845
1100
|
try:
|
|
846
1101
|
proc.wait(timeout=3)
|
|
@@ -855,33 +1110,57 @@ class ProcessManager:
|
|
|
855
1110
|
|
|
856
1111
|
def persist_records(self):
|
|
857
1112
|
"""Write current process records to processes.json (with launcher_pid for multi-instance)."""
|
|
1113
|
+
import time as _time
|
|
1114
|
+
t0 = _time.monotonic()
|
|
858
1115
|
data = {
|
|
859
1116
|
"launcher_pid": os.getpid(),
|
|
860
1117
|
"debug": os.environ.get("KITE_DEBUG") == "1",
|
|
861
1118
|
"cwd": os.environ.get("KITE_CWD", ""),
|
|
862
1119
|
"records": [asdict(r) for r in self._records.values()],
|
|
863
1120
|
}
|
|
1121
|
+
t1 = _time.monotonic()
|
|
864
1122
|
self._write_records_file(data)
|
|
1123
|
+
t2 = _time.monotonic()
|
|
1124
|
+
serialize_ms = (t1 - t0) * 1000
|
|
1125
|
+
write_ms = (t2 - t1) * 1000
|
|
1126
|
+
if serialize_ms + write_ms > 50:
|
|
1127
|
+
print(f"[launcher] persist_records 耗时: serialize={serialize_ms:.0f}ms, write={write_ms:.0f}ms")
|
|
865
1128
|
|
|
866
1129
|
def check_exited(self) -> list[tuple[str, int]]:
|
|
867
|
-
"""Non-blocking check for exited child processes. Returns [(
|
|
1130
|
+
"""Non-blocking check for exited child processes. Returns [(key, returncode)].
|
|
1131
|
+
Key is 'name' for primary instances, 'name#N' for extra instances.
|
|
1132
|
+
Use parse_instance_key() to extract (module_name, instance_num).
|
|
1133
|
+
"""
|
|
868
1134
|
exited = []
|
|
869
|
-
for
|
|
870
|
-
proc = self._processes[
|
|
1135
|
+
for key in list(self._processes.keys()):
|
|
1136
|
+
proc = self._processes[key]
|
|
871
1137
|
rc = proc.poll()
|
|
872
1138
|
if rc is not None:
|
|
873
|
-
exited.append((
|
|
874
|
-
self._processes.pop(
|
|
875
|
-
self._records.pop(
|
|
1139
|
+
exited.append((key, rc))
|
|
1140
|
+
self._processes.pop(key, None)
|
|
1141
|
+
self._records.pop(key, None)
|
|
876
1142
|
return exited
|
|
877
1143
|
|
|
878
|
-
def get_record(self, name: str) -> ProcessRecord | None:
|
|
879
|
-
|
|
1144
|
+
def get_record(self, name: str, instance_num: int = 1) -> ProcessRecord | None:
|
|
1145
|
+
key = self._instance_key(name, instance_num)
|
|
1146
|
+
return self._records.get(key)
|
|
880
1147
|
|
|
881
|
-
def is_running(self, name: str) -> bool:
|
|
882
|
-
|
|
1148
|
+
def is_running(self, name: str, instance_num: int = 1) -> bool:
|
|
1149
|
+
key = self._instance_key(name, instance_num)
|
|
1150
|
+
proc = self._processes.get(key)
|
|
883
1151
|
return proc is not None and proc.poll() is None
|
|
884
1152
|
|
|
1153
|
+
def get_module_instance_keys(self, name: str) -> list[str]:
|
|
1154
|
+
"""Return all active instance keys for a given module name."""
|
|
1155
|
+
keys = []
|
|
1156
|
+
for key in self._processes:
|
|
1157
|
+
mod_name, _ = self.parse_instance_key(key)
|
|
1158
|
+
if mod_name == name:
|
|
1159
|
+
proc = self._processes[key]
|
|
1160
|
+
if proc.poll() is None:
|
|
1161
|
+
keys.append(key)
|
|
1162
|
+
return keys
|
|
1163
|
+
|
|
885
1164
|
def _write_records_file(self, data):
|
|
886
1165
|
"""Write data to the instance's processes JSON file."""
|
|
887
1166
|
try:
|