@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.
- 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/CHANGELOG.md +102 -0
- package/cli.js +78 -5
- package/core/dependency_checker.py +250 -0
- package/core/env_checker.py +586 -0
- package/dependencies_lock.json +128 -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 +308 -106
- 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 +412 -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 +505 -201
- 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 -0
- package/extensions/services/evol/config.json5 +16 -0
- package/extensions/services/evol/config_loader.py +117 -0
- package/extensions/services/evol/entry.py +568 -0
- package/extensions/services/evol/evol_api.py +969 -0
- package/extensions/services/evol/evol_config.json5 +29 -0
- package/extensions/services/evol/mfa_totp.py +77 -0
- package/extensions/services/evol/migrate_tokens.py +122 -0
- package/extensions/services/evol/module.md +150 -0
- package/extensions/services/evol/nonce_pool.py +113 -0
- package/extensions/services/evol/oauth_manager.py +223 -0
- package/extensions/services/evol/pairing.py +251 -0
- package/extensions/services/evol/pairing_codes.jsonl +2 -0
- package/extensions/services/evol/relay.py +1031 -0
- package/extensions/services/evol/relay_config.json5 +85 -0
- package/extensions/services/evol/routes/__init__.py +1 -0
- package/extensions/services/evol/routes/routes_llm.py +231 -0
- package/extensions/services/evol/routes/routes_rpc.py +90 -0
- package/extensions/services/evol/routes/routes_test.py +68 -0
- package/extensions/services/evol/server.py +2426 -0
- 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 -0
- package/extensions/services/evol/static/logo.png +0 -0
- package/extensions/services/evol/stats_manager.py +243 -0
- 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/kite_console/static/css/style.css +1854 -0
- package/extensions/services/kite_console/static/index.html +1524 -0
- package/extensions/services/kite_console/static/js/dialog.js +292 -0
- package/extensions/services/kite_console/static/js/evol-app.js +7740 -0
- package/extensions/services/kite_console/static/js/evol-app.js.backup +2777 -0
- package/extensions/services/kite_console/static/js/kernel-client.js +560 -0
- package/extensions/services/kite_console/static/js/kernel-client.js.backup +434 -0
- package/extensions/services/kite_console/static/js/registry-tests.js +592 -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/pairing.html +248 -0
- package/extensions/services/kite_console/static/test_kernel_client_token.html +352 -0
- package/extensions/services/kite_console/static/test_registry.html +262 -0
- package/extensions/services/kite_console/static/test_relay.html +462 -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 +633 -162
- package/extensions/services/model_service/module.md +11 -2
- package/extensions/services/proxy/.claude/settings.local.json +13 -0
- package/extensions/services/proxy/__init__.py +0 -0
- package/extensions/services/proxy/agentcp/LICENCE +178 -0
- package/extensions/services/proxy/agentcp/README copy.md +85 -0
- package/extensions/services/proxy/agentcp/README.md +260 -0
- package/extensions/services/proxy/agentcp/__init__.py +16 -0
- package/extensions/services/proxy/agentcp/agent.py +4 -0
- package/extensions/services/proxy/agentcp/agentcp.py +2494 -0
- package/extensions/services/proxy/agentcp/agentprofile.json +89 -0
- package/extensions/services/proxy/agentcp/ap/__init__.py +16 -0
- package/extensions/services/proxy/agentcp/ap/ap_client.py +316 -0
- package/extensions/services/proxy/agentcp/assets/images/wechat_qr.png +0 -0
- package/extensions/services/proxy/agentcp/backup/metrics.json +31 -0
- package/extensions/services/proxy/agentcp/base/__init__.py +20 -0
- package/extensions/services/proxy/agentcp/base/auth_client.py +257 -0
- package/extensions/services/proxy/agentcp/base/client.py +112 -0
- package/extensions/services/proxy/agentcp/base/env.py +34 -0
- package/extensions/services/proxy/agentcp/base/html_util.py +336 -0
- package/extensions/services/proxy/agentcp/base/log.py +98 -0
- package/extensions/services/proxy/agentcp/ca/__init__.py +17 -0
- package/extensions/services/proxy/agentcp/ca/ca_client.py +414 -0
- package/extensions/services/proxy/agentcp/ca/ca_root.py +74 -0
- package/extensions/services/proxy/agentcp/context/__init__.py +20 -0
- package/extensions/services/proxy/agentcp/context/context.py +73 -0
- package/extensions/services/proxy/agentcp/context/exceptions.py +114 -0
- package/extensions/services/proxy/agentcp/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/create_profile_weather.py +125 -0
- package/extensions/services/proxy/agentcp/db/__init__.py +15 -0
- package/extensions/services/proxy/agentcp/db/db_mananger.py +550 -0
- package/extensions/services/proxy/agentcp/docs/UDP_HEARTBEAT_FIX_REPORT.md +265 -0
- package/extensions/services/proxy/agentcp/docs/heartbeat_issue_analysis.md +291 -0
- package/extensions/services/proxy/agentcp/file/__init__.py +16 -0
- package/extensions/services/proxy/agentcp/file/file_client.py +141 -0
- package/extensions/services/proxy/agentcp/file/wss_binary_message.py +137 -0
- package/extensions/services/proxy/agentcp/hcp.py +299 -0
- package/extensions/services/proxy/agentcp/heartbeat/__init__.py +16 -0
- package/extensions/services/proxy/agentcp/heartbeat/heartbeat_client.py +360 -0
- package/extensions/services/proxy/agentcp/improved_scheduler.py +498 -0
- package/extensions/services/proxy/agentcp/llm_agent_utils.py +249 -0
- package/extensions/services/proxy/agentcp/llm_server.py +172 -0
- package/extensions/services/proxy/agentcp/mermaid.py +210 -0
- package/extensions/services/proxy/agentcp/message.py +149 -0
- package/extensions/services/proxy/agentcp/metrics.py +256 -0
- package/extensions/services/proxy/agentcp/monitoring/__init__.py +20 -0
- package/extensions/services/proxy/agentcp/monitoring/global_monitor.py +27 -0
- package/extensions/services/proxy/agentcp/monitoring/metrics_store.py +325 -0
- package/extensions/services/proxy/agentcp/monitoring/monitoring_service.py +269 -0
- package/extensions/services/proxy/agentcp/monitoring/sliding_window.py +222 -0
- package/extensions/services/proxy/agentcp/monitoring/standalone_reader.py +224 -0
- package/extensions/services/proxy/agentcp/msg/__init__.py +21 -0
- package/extensions/services/proxy/agentcp/msg/connection_manager.py +456 -0
- package/extensions/services/proxy/agentcp/msg/message_client.py +2058 -0
- package/extensions/services/proxy/agentcp/msg/message_serialize.py +263 -0
- package/extensions/services/proxy/agentcp/msg/open_ai_message.py +88 -0
- package/extensions/services/proxy/agentcp/msg/session_manager.py +1062 -0
- package/extensions/services/proxy/agentcp/msg/stream_client.py +267 -0
- package/extensions/services/proxy/agentcp/msg/websocket_file_receiver.py +89 -0
- package/extensions/services/proxy/agentcp/msg/ws_logger.py +685 -0
- package/extensions/services/proxy/agentcp/msg/wss_binary_message.py +137 -0
- package/extensions/services/proxy/agentcp/requirements.txt +7 -0
- package/extensions/services/proxy/agentcp/samples/agent_graph/README.md +37 -0
- package/extensions/services/proxy/agentcp/samples/agent_graph/agentprofile.json +89 -0
- package/extensions/services/proxy/agentcp/samples/agent_graph/create_profile.py +138 -0
- package/extensions/services/proxy/agentcp/samples/agent_graph/main.py +164 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/create_profile.py +123 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/llm/create_profile.py +129 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/llm/env.json +5 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/llm/main.py +146 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/main.py +123 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/readme.md +379 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/search/create_profile.py +129 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/search/main.py +28 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/tool/create_profile.py +129 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/tool/main.py +20 -0
- package/extensions/services/proxy/agentcp/samples/ali_amap/README.md +97 -0
- package/extensions/services/proxy/agentcp/samples/ali_amap/amap_agent.py +88 -0
- package/extensions/services/proxy/agentcp/samples/ali_amap/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/samples/compute_agent/agent/powershell.py +228 -0
- package/extensions/services/proxy/agentcp/samples/compute_agent/agent/software.py +63 -0
- package/extensions/services/proxy/agentcp/samples/compute_agent/agent/tools.py +36 -0
- package/extensions/services/proxy/agentcp/samples/compute_agent/browser_user.py +41 -0
- package/extensions/services/proxy/agentcp/samples/deepseek/README.md +79 -0
- package/extensions/services/proxy/agentcp/samples/deepseek/create_profile.py +126 -0
- package/extensions/services/proxy/agentcp/samples/deepseek/deepseek.py +42 -0
- package/extensions/services/proxy/agentcp/samples/dify_chat/README.md +78 -0
- package/extensions/services/proxy/agentcp/samples/dify_chat/create_profile.py +126 -0
- package/extensions/services/proxy/agentcp/samples/dify_chat/dify_chat.py +47 -0
- package/extensions/services/proxy/agentcp/samples/dify_workflow/README.md +78 -0
- package/extensions/services/proxy/agentcp/samples/dify_workflow/create_profile.py +126 -0
- package/extensions/services/proxy/agentcp/samples/dify_workflow/dify_workflow.py +46 -0
- package/extensions/services/proxy/agentcp/samples/executor/README.md +44 -0
- package/extensions/services/proxy/agentcp/samples/executor/agentprofile.json +89 -0
- package/extensions/services/proxy/agentcp/samples/executor/create_profile.py +139 -0
- package/extensions/services/proxy/agentcp/samples/executor/main.py +160 -0
- package/extensions/services/proxy/agentcp/samples/filereader/README.md +45 -0
- package/extensions/services/proxy/agentcp/samples/filereader/agentprofile.json +90 -0
- package/extensions/services/proxy/agentcp/samples/filereader/create_profile.py +137 -0
- package/extensions/services/proxy/agentcp/samples/filereader/main.py +253 -0
- package/extensions/services/proxy/agentcp/samples/filewriter/README.md +38 -0
- package/extensions/services/proxy/agentcp/samples/filewriter/agentprofile.json +91 -0
- package/extensions/services/proxy/agentcp/samples/filewriter/create_profile.py +138 -0
- package/extensions/services/proxy/agentcp/samples/filewriter/main.py +289 -0
- package/extensions/services/proxy/agentcp/samples/hcp/README.md +85 -0
- package/extensions/services/proxy/agentcp/samples/hcp/acp_weather_agent.zip +0 -0
- package/extensions/services/proxy/agentcp/samples/hcp/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/samples/hcp/hcp.py +237 -0
- package/extensions/services/proxy/agentcp/samples/helloworld/README.md +68 -0
- package/extensions/services/proxy/agentcp/samples/helloworld/hello_world.py +40 -0
- package/extensions/services/proxy/agentcp/samples/llm_agent/MEADME.md +117 -0
- package/extensions/services/proxy/agentcp/samples/llm_agent/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/samples/llm_agent/qwen_agent.py +136 -0
- package/extensions/services/proxy/agentcp/samples/local_llm_agent/README.md +90 -0
- package/extensions/services/proxy/agentcp/samples/local_llm_agent/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/samples/local_llm_agent/main.py +49 -0
- package/extensions/services/proxy/agentcp/samples/query_llm_from_agent/README.md +55 -0
- package/extensions/services/proxy/agentcp/samples/query_llm_from_agent/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/samples/query_llm_from_agent/main.py +23 -0
- package/extensions/services/proxy/agentcp/samples/query_weather_api_agent/README.md +103 -0
- package/extensions/services/proxy/agentcp/samples/query_weather_api_agent/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/samples/query_weather_api_agent/main.py +69 -0
- package/extensions/services/proxy/agentcp/samples/query_weather_from_agent/README.md +58 -0
- package/extensions/services/proxy/agentcp/samples/query_weather_from_agent/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/samples/query_weather_from_agent/main.py +25 -0
- package/extensions/services/proxy/agentcp/samples/qwen3/README.md +71 -0
- package/extensions/services/proxy/agentcp/samples/qwen3/create_profile.py +126 -0
- package/extensions/services/proxy/agentcp/samples/qwen3/qwen3.py +37 -0
- package/extensions/services/proxy/agentcp/samples/qwen3_tools/README.md +133 -0
- package/extensions/services/proxy/agentcp/samples/qwen3_tools/create_profile.py +126 -0
- package/extensions/services/proxy/agentcp/samples/qwen3_tools/qwen3_tools.py +98 -0
- package/extensions/services/proxy/agentcp/samples/search/create_profile_qwen.py +125 -0
- package/extensions/services/proxy/agentcp/samples/search/create_profile_search.py +125 -0
- package/extensions/services/proxy/agentcp/samples/search/qwen_agent.py +136 -0
- package/extensions/services/proxy/agentcp/samples/search/search_agent.py +170 -0
- package/extensions/services/proxy/agentcp/samples/wrapper_agently_to_agent/README.md +89 -0
- package/extensions/services/proxy/agentcp/samples/wrapper_agently_to_agent/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/samples/wrapper_agently_to_agent/main.py +44 -0
- package/extensions/services/proxy/agentcp/utils/__init__.py +15 -0
- package/extensions/services/proxy/agentcp/utils/file_util.py +117 -0
- package/extensions/services/proxy/agentcp/utils/proxy_bypass.py +99 -0
- package/extensions/services/proxy/agentcp/workflow.py +203 -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/__init__.py +1 -0
- package/extensions/services/proxy/evol/config.py +37 -0
- package/extensions/services/proxy/evol/http/__init__.py +1 -0
- package/extensions/services/proxy/evol/http/async_http.py +551 -0
- package/extensions/services/proxy/evol/log.py +28 -0
- package/extensions/services/proxy/evol/presenter/__init__.py +2 -0
- package/extensions/services/proxy/evol/presenter/agentIdPresenter.py +1031 -0
- package/extensions/services/proxy/evol/presenter/apikeyPresenter.py +96 -0
- package/extensions/services/proxy/evol/presenter/configPresenter.py +234 -0
- package/extensions/services/proxy/evol/presenter/userPresenter.py +71 -0
- package/extensions/services/proxy/evol/server/__init__.py +1 -0
- package/extensions/services/proxy/evol/server/claude_proxy_async.py +3434 -0
- package/extensions/services/proxy/evol/server/openclaw_proxy.py +1861 -0
- package/extensions/services/proxy/evol/server/proxy_config.py +15 -0
- package/extensions/services/proxy/evol/server/proxy_engine.py +501 -0
- package/extensions/services/proxy/evol/version.py +24 -0
- package/extensions/services/proxy/module.md +151 -0
- package/extensions/services/proxy/server.py +952 -0
- 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 -767
- package/extensions/services/watchdog/module.md +3 -0
- package/extensions/services/watchdog/monitor.py +483 -75
- 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 +17 -14
- 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 +380 -181
- 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/static/js/token-manager.js +10 -10
- 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 +26 -1
- package/kernel/registry_store.py +209 -36
- package/kernel/rpc_router.py +1400 -465
- package/kernel/server.py +1084 -108
- 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 +67 -0
- package/kite_cli/commands/disable.py +162 -0
- package/kite_cli/commands/enable.py +162 -0
- package/kite_cli/commands/env_check.py +45 -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/prepare.py +49 -0
- package/kite_cli/commands/search.py +33 -17
- package/kite_cli/commands/update.py +115 -2
- package/kite_cli/commands/venv_setup.py +56 -0
- 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 +179 -5
- 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 -2517
- package/launcher/logging_setup.py +54 -1
- package/launcher/module.md +37 -2
- package/launcher/module_scanner.py +103 -20
- package/launcher/process_manager.py +355 -76
- package/main.py +10 -1
- package/package.json +11 -1
- package/python_version.json +4 -0
- package/requirements.txt +41 -0
- package/scripts/auto-fix-deps.py +128 -0
- package/scripts/env-manager.js +351 -0
- package/scripts/final-test.js +78 -0
- package/scripts/python-env.js +79 -0
- package/scripts/scan_dependencies.py +461 -0
- package/scripts/setup-python-env.js +700 -0
- 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/web/config.yaml +0 -149
|
@@ -0,0 +1,1824 @@
|
|
|
1
|
+
# 模块开发指南
|
|
2
|
+
|
|
3
|
+
本指南面向 Kite 框架的模块开发者,从零开始讲解如何创建、注册和运行一个模块。
|
|
4
|
+
|
|
5
|
+
## 1. 概述
|
|
6
|
+
|
|
7
|
+
Kite 中的**模块**是一个独立进程,通过 WebSocket JSON-RPC 2.0 协议与 Kernel 通信。模块可以用 Python、Node.js 或编译型语言编写——框架不关心语言,只关心协议。
|
|
8
|
+
|
|
9
|
+
每个模块有一个类型,决定它在系统中的角色:
|
|
10
|
+
|
|
11
|
+
| 类型 | 说明 |
|
|
12
|
+
|------|------|
|
|
13
|
+
| `infrastructure` | 基础设施(Kernel、Launcher) |
|
|
14
|
+
| `service` | 后台服务(Watchdog、Model Service) |
|
|
15
|
+
| `channel` | 消息渠道(WebChat、电话) |
|
|
16
|
+
| `agent` | AI 代理(对话、群组) |
|
|
17
|
+
| `tool` | 工具型模块(一次性任务、基准测试) |
|
|
18
|
+
|
|
19
|
+
前置知识:了解 WebSocket、JSON-RPC 2.0 基础即可。
|
|
20
|
+
|
|
21
|
+
## 2. 快速开始
|
|
22
|
+
|
|
23
|
+
5 分钟创建一个最小 Python 模块。
|
|
24
|
+
|
|
25
|
+
### 2.1 创建目录和文件
|
|
26
|
+
|
|
27
|
+
在 `tests/modules/` 下创建模块目录:
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
tests/modules/my_module/
|
|
31
|
+
├── entry.py # 入口文件(必须)
|
|
32
|
+
└── module.md # 模块元数据(必须)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### 2.2 编写 module.md
|
|
36
|
+
|
|
37
|
+
```markdown
|
|
38
|
+
---
|
|
39
|
+
name: my_module
|
|
40
|
+
display_name: My Module
|
|
41
|
+
type: service
|
|
42
|
+
state: enabled
|
|
43
|
+
runtime: python
|
|
44
|
+
entry: entry.py
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
# My Module
|
|
48
|
+
|
|
49
|
+
一个最小的示例模块。
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 2.3 编写 entry.py
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
"""最小 Kite 模块示例 - 异步非阻塞版本。"""
|
|
56
|
+
import asyncio
|
|
57
|
+
import json
|
|
58
|
+
import os
|
|
59
|
+
import sys
|
|
60
|
+
import uuid
|
|
61
|
+
|
|
62
|
+
import websockets
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# ── SafeWriter:防止 Launcher 关闭 stdio 后 BrokenPipeError ──
|
|
66
|
+
class _SafeWriter:
|
|
67
|
+
def __init__(self, stream):
|
|
68
|
+
self._stream = stream
|
|
69
|
+
def write(self, s):
|
|
70
|
+
try: self._stream.write(s)
|
|
71
|
+
except (BrokenPipeError, OSError): pass
|
|
72
|
+
def flush(self):
|
|
73
|
+
try: self._stream.flush()
|
|
74
|
+
except (BrokenPipeError, OSError): pass
|
|
75
|
+
def __getattr__(self, name):
|
|
76
|
+
return getattr(self._stream, name)
|
|
77
|
+
|
|
78
|
+
sys.stdout = _SafeWriter(sys.stdout)
|
|
79
|
+
sys.stderr = _SafeWriter(sys.stderr)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
async def main():
|
|
83
|
+
TAG = "[my_module]"
|
|
84
|
+
|
|
85
|
+
# ① 读取 boot_info(stdin 第一行 JSON)
|
|
86
|
+
line = sys.stdin.readline().strip()
|
|
87
|
+
if not line:
|
|
88
|
+
print(f"{TAG} ERROR: No boot_info received")
|
|
89
|
+
sys.exit(1)
|
|
90
|
+
boot_info = json.loads(line)
|
|
91
|
+
token = boot_info["token"]
|
|
92
|
+
|
|
93
|
+
# ② 读取 kernel_port(stdin 第二行 JSON 或环境变量)
|
|
94
|
+
kernel_port = os.environ.get("KITE_KERNEL_PORT")
|
|
95
|
+
if not kernel_port:
|
|
96
|
+
line = sys.stdin.readline().strip()
|
|
97
|
+
if line:
|
|
98
|
+
msg = json.loads(line)
|
|
99
|
+
if msg.get("kite") == "kernel_port":
|
|
100
|
+
kernel_port = msg["kernel_port"]
|
|
101
|
+
|
|
102
|
+
if not kernel_port:
|
|
103
|
+
print(f"{TAG} ERROR: KITE_KERNEL_PORT not set")
|
|
104
|
+
sys.exit(1)
|
|
105
|
+
|
|
106
|
+
kernel_port = int(kernel_port)
|
|
107
|
+
print(f"{TAG} Token received, kernel port: {kernel_port}")
|
|
108
|
+
|
|
109
|
+
# ③ 连接 Kernel WebSocket
|
|
110
|
+
ws_url = f"ws://127.0.0.1:{kernel_port}/ws?token={token}&id=my_module"
|
|
111
|
+
async with websockets.connect(ws_url, open_timeout=5, ping_interval=None) as ws:
|
|
112
|
+
print(f"{TAG} Connected to Kernel")
|
|
113
|
+
|
|
114
|
+
# ④ 订阅事件
|
|
115
|
+
subscribe_msg = {
|
|
116
|
+
"jsonrpc": "2.0",
|
|
117
|
+
"id": str(uuid.uuid4()),
|
|
118
|
+
"method": "event.subscribe",
|
|
119
|
+
"params": {"events": ["module.>"]}
|
|
120
|
+
}
|
|
121
|
+
await ws.send(json.dumps(subscribe_msg))
|
|
122
|
+
await ws.recv() # 等待响应
|
|
123
|
+
print(f"{TAG} Subscribed to events")
|
|
124
|
+
|
|
125
|
+
# ⑤ 注册到 Kernel
|
|
126
|
+
register_msg = {
|
|
127
|
+
"jsonrpc": "2.0",
|
|
128
|
+
"id": str(uuid.uuid4()),
|
|
129
|
+
"method": "registry.register",
|
|
130
|
+
"params": {
|
|
131
|
+
"module_id": "my_module",
|
|
132
|
+
"module_type": "service",
|
|
133
|
+
"name": "My Module",
|
|
134
|
+
"graceful_shutdown": True,
|
|
135
|
+
"events_subscribe": ["module.>"],
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
await ws.send(json.dumps(register_msg))
|
|
139
|
+
resp = json.loads(await ws.recv())
|
|
140
|
+
if "error" in resp:
|
|
141
|
+
print(f"{TAG} ERROR: Registration failed: {resp['error']}")
|
|
142
|
+
sys.exit(1)
|
|
143
|
+
print(f"{TAG} Registered to Kernel")
|
|
144
|
+
|
|
145
|
+
# ⑥ 发送 module.ready
|
|
146
|
+
ready_msg = {
|
|
147
|
+
"jsonrpc": "2.0",
|
|
148
|
+
"id": str(uuid.uuid4()),
|
|
149
|
+
"method": "event.publish",
|
|
150
|
+
"params": {
|
|
151
|
+
"event_id": str(uuid.uuid4()),
|
|
152
|
+
"event": "module.ready",
|
|
153
|
+
"data": {
|
|
154
|
+
"module_id": "my_module",
|
|
155
|
+
"graceful_shutdown": True,
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
await ws.send(json.dumps(ready_msg))
|
|
160
|
+
await ws.recv() # 等待响应
|
|
161
|
+
print(f"{TAG} Published module.ready")
|
|
162
|
+
|
|
163
|
+
# ⑦ 消息循环(异步非阻塞)
|
|
164
|
+
print(f"{TAG} Entering message loop")
|
|
165
|
+
try:
|
|
166
|
+
async for raw in ws:
|
|
167
|
+
msg = json.loads(raw)
|
|
168
|
+
|
|
169
|
+
has_method = "method" in msg
|
|
170
|
+
has_id = "id" in msg
|
|
171
|
+
|
|
172
|
+
if has_method and not has_id:
|
|
173
|
+
# 事件通知 — 异步处理(关键!)
|
|
174
|
+
asyncio.create_task(handle_event(msg))
|
|
175
|
+
|
|
176
|
+
elif has_method and has_id:
|
|
177
|
+
# RPC 请求 — 异步处理(关键!)
|
|
178
|
+
asyncio.create_task(handle_rpc(ws, msg))
|
|
179
|
+
|
|
180
|
+
except KeyboardInterrupt:
|
|
181
|
+
print(f"{TAG} Interrupted, exiting")
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
async def handle_event(msg: dict):
|
|
185
|
+
"""处理事件通知(异步)"""
|
|
186
|
+
event = msg["params"]["event"]
|
|
187
|
+
print(f"[my_module] Received event: {event}")
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
async def handle_rpc(ws, msg: dict):
|
|
191
|
+
"""处理 RPC 请求(异步)"""
|
|
192
|
+
method = msg["method"]
|
|
193
|
+
msg_id = msg["id"]
|
|
194
|
+
|
|
195
|
+
if method == "my_module.health":
|
|
196
|
+
# 健康检查
|
|
197
|
+
response = {
|
|
198
|
+
"jsonrpc": "2.0",
|
|
199
|
+
"id": msg_id,
|
|
200
|
+
"result": {"status": "healthy"}
|
|
201
|
+
}
|
|
202
|
+
await ws.send(json.dumps(response))
|
|
203
|
+
|
|
204
|
+
elif method == "my_module.shutdown":
|
|
205
|
+
# 优雅关闭
|
|
206
|
+
response = {
|
|
207
|
+
"jsonrpc": "2.0",
|
|
208
|
+
"id": msg_id,
|
|
209
|
+
"result": {"status": "shutting_down"}
|
|
210
|
+
}
|
|
211
|
+
await ws.send(json.dumps(response))
|
|
212
|
+
print("[my_module] Shutdown requested, closing connection")
|
|
213
|
+
await ws.close(code=1000, reason="Graceful shutdown")
|
|
214
|
+
|
|
215
|
+
else:
|
|
216
|
+
# 未知方法
|
|
217
|
+
error_response = {
|
|
218
|
+
"jsonrpc": "2.0",
|
|
219
|
+
"id": msg_id,
|
|
220
|
+
"error": {
|
|
221
|
+
"code": -32601,
|
|
222
|
+
"message": f"Method not found: {method}"
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
await ws.send(json.dumps(error_response))
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
if __name__ == "__main__":
|
|
229
|
+
asyncio.run(main())
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
> **关键点**:
|
|
233
|
+
> - 使用 `asyncio.create_task()` 异步处理事件和 RPC 请求
|
|
234
|
+
> - 接收循环只负责分发消息,不执行业务逻辑
|
|
235
|
+
> - 详见 **[WebSocket 接收循环死锁防范规范](./WebSocket接收循环死锁防范规范.md)**
|
|
236
|
+
|
|
237
|
+
### 2.4 放置与启动
|
|
238
|
+
|
|
239
|
+
将模块目录放在 Kite 可以扫描到的位置:
|
|
240
|
+
|
|
241
|
+
- `extensions/my_module/` — 内置扫描路径,无需额外配置
|
|
242
|
+
- `tests/modules/my_module/` — 需要在 Launcher 的 `module.md` 中配置 `discovery` 扫描此目录
|
|
243
|
+
|
|
244
|
+
启动 Kite 后,检查 Launcher 日志中是否出现:
|
|
245
|
+
|
|
246
|
+
```
|
|
247
|
+
[launcher] Module 'my_module' is ready
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## 3. 模块结构
|
|
251
|
+
|
|
252
|
+
标准目录布局:
|
|
253
|
+
|
|
254
|
+
```
|
|
255
|
+
my_module/
|
|
256
|
+
├── entry.py # 入口文件(必须)
|
|
257
|
+
├── module.md # 模块元数据(必须)
|
|
258
|
+
├── tools/ # 提供的工具定义(可选)
|
|
259
|
+
│ └── tool_name/
|
|
260
|
+
│ ├── tool.md # 工具描述(frontmatter + 使用说明)
|
|
261
|
+
│ └── handler.py# 工具实现
|
|
262
|
+
├── skills/ # 技能定义(可选)
|
|
263
|
+
│ └── skill_name/
|
|
264
|
+
│ ├── skill.md
|
|
265
|
+
│ └── ...
|
|
266
|
+
├── permissions/ # 权限配置(可选)
|
|
267
|
+
│ └── tool_name.json
|
|
268
|
+
└── ... # 模块自有代码
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
只有 `entry.py` 和 `module.md` 是必须的。模块可以包含任意额外的代码文件。
|
|
272
|
+
|
|
273
|
+
## 4. module.md 详解
|
|
274
|
+
|
|
275
|
+
`module.md` 使用 YAML frontmatter 定义元数据,正文是 Markdown 格式的模块说明文档(LLM 可阅读)。
|
|
276
|
+
|
|
277
|
+
### 4.1 字段说明
|
|
278
|
+
|
|
279
|
+
| 字段 | 必填 | 默认值 | 说明 |
|
|
280
|
+
|------|------|--------|------|
|
|
281
|
+
| `name` | 是 | — | 模块唯一标识,用于注册和 API 调用 |
|
|
282
|
+
| `display_name` | 否 | 同 name | 显示名称 |
|
|
283
|
+
| `version` | 否 | `"1.0"` | 版本号 |
|
|
284
|
+
| `type` | 否 | `"infrastructure"` | 模块类型:infrastructure / service / channel / agent / tool |
|
|
285
|
+
| `state` | 否 | `"enabled"` | 启动状态(见下文) |
|
|
286
|
+
| `runtime` | 否 | `"python"` | 运行时:python / node / binary |
|
|
287
|
+
| `entry` | 否 | `"entry.py"` | 入口文件名(相对于模块目录) |
|
|
288
|
+
| `preferred_port` | 否 | `0` | 首选端口。0 = OS 动态分配,非零则优先尝试 |
|
|
289
|
+
| `depends_on` | 否 | `[]` | 启动依赖的模块列表 |
|
|
290
|
+
| `events` | 否 | `[]` | 本模块会发出的事件类型 |
|
|
291
|
+
| `subscriptions` | 否 | `[]` | 本模块订阅的事件类型 |
|
|
292
|
+
| `monitor` | 否 | `true` | Watchdog 是否监控此模块 |
|
|
293
|
+
| `launch` | 否 | — | 自定义启动参数(见下文) |
|
|
294
|
+
|
|
295
|
+
**state 取值:**
|
|
296
|
+
|
|
297
|
+
| 值 | 说明 |
|
|
298
|
+
|-----|------|
|
|
299
|
+
| `enabled` | Kite 启动时自动启动 |
|
|
300
|
+
| `manual` | 不自动启动,需通过 API 手动启动 |
|
|
301
|
+
| `disabled` | 禁用,需 owner 修改后才能启动 |
|
|
302
|
+
|
|
303
|
+
### 4.2 launch 配置块
|
|
304
|
+
|
|
305
|
+
可选,用于自定义启动方式。所有子字段均可选:
|
|
306
|
+
|
|
307
|
+
```yaml
|
|
308
|
+
launch:
|
|
309
|
+
cmd: ["python3.11", "-u", "main.py", "--mode", "prod"] # 覆盖 runtime+entry
|
|
310
|
+
env: # 额外环境变量
|
|
311
|
+
MY_VAR: "value"
|
|
312
|
+
cwd: "./subdir" # 工作目录(相对于模块目录)
|
|
313
|
+
boot_stdin: false # 是否通过 stdin 传 boot_info(默认 true)
|
|
314
|
+
timeout: 60 # 等待 module.ready 的超时秒数(默认 30)
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
| 字段 | 默认值 | 说明 |
|
|
318
|
+
|------|--------|------|
|
|
319
|
+
| `cmd` | `[]`(用 runtime+entry) | 非空时覆盖默认启动命令 |
|
|
320
|
+
| `env` | `{}` | 合并到进程环境变量 |
|
|
321
|
+
| `cwd` | `""`(用模块目录) | 相对路径基于模块目录解析 |
|
|
322
|
+
| `boot_stdin` | `true` | 设为 false 时模块需自行获取 token |
|
|
323
|
+
| `timeout` | `30` | 等待 module.ready 的秒数 |
|
|
324
|
+
|
|
325
|
+
### 4.3 完整示例
|
|
326
|
+
|
|
327
|
+
参考 `extensions/services/watchdog/module.md`:
|
|
328
|
+
|
|
329
|
+
```markdown
|
|
330
|
+
---
|
|
331
|
+
name: watchdog
|
|
332
|
+
display_name: Watchdog
|
|
333
|
+
version: "1.0"
|
|
334
|
+
type: service
|
|
335
|
+
state: enabled
|
|
336
|
+
runtime: python
|
|
337
|
+
entry: entry.py
|
|
338
|
+
events:
|
|
339
|
+
- watchdog.module.unhealthy
|
|
340
|
+
- watchdog.module.recovered
|
|
341
|
+
- watchdog.alert
|
|
342
|
+
subscriptions:
|
|
343
|
+
- module.started
|
|
344
|
+
- module.stopped
|
|
345
|
+
---
|
|
346
|
+
|
|
347
|
+
# Watchdog(保活模块)
|
|
348
|
+
|
|
349
|
+
应用层健康监控扩展模块。
|
|
350
|
+
|
|
351
|
+
- 心跳检测 — 定期检查各模块的 `/health` 端点
|
|
352
|
+
- 资源监控 — 检测模块健康状态中的异常指标
|
|
353
|
+
- 非 core 模块重启 — 发现异常时通过 Launcher API 重启模块
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### 4.4 正文部分
|
|
357
|
+
|
|
358
|
+
frontmatter 之后的 Markdown 正文是模块的说明文档。LLM 可以通过阅读本文档了解模块的功能和使用方法。建议包含功能说明、使用示例和注意事项。
|
|
359
|
+
|
|
360
|
+
## 5. 启动协议
|
|
361
|
+
|
|
362
|
+
这是模块开发的核心。模块启动后需要完成以下步骤才能被框架正式接纳。
|
|
363
|
+
|
|
364
|
+
### 5.1 读取 boot_info
|
|
365
|
+
|
|
366
|
+
Launcher 通过 stdin 管道向模块传入一行 JSON:
|
|
367
|
+
|
|
368
|
+
```json
|
|
369
|
+
{"token": "tok_abc123..."}
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
**注意事项:**
|
|
373
|
+
- 必须用 **readline**(逐行读取),不能等 EOF — stdin 保持打开直到 module.ready 后才断开
|
|
374
|
+
- token 是模块的身份凭证,后续所有 Registry API 调用都需要它
|
|
375
|
+
|
|
376
|
+
**Python:**
|
|
377
|
+
```python
|
|
378
|
+
import sys, json
|
|
379
|
+
line = sys.stdin.readline().strip()
|
|
380
|
+
boot_info = json.loads(line)
|
|
381
|
+
token = boot_info["token"]
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
**Node.js:**
|
|
385
|
+
```javascript
|
|
386
|
+
const readline = require('readline');
|
|
387
|
+
const rl = readline.createInterface({ input: process.stdin });
|
|
388
|
+
rl.once('line', (line) => {
|
|
389
|
+
const { token } = JSON.parse(line.trim());
|
|
390
|
+
rl.close();
|
|
391
|
+
start(token);
|
|
392
|
+
});
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
**错误写法(会死锁):**
|
|
396
|
+
```javascript
|
|
397
|
+
// ❌ 等待 EOF — stdin 不关闭则永远挂起
|
|
398
|
+
process.stdin.on('end', () => { ... });
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
### 5.2 SafeWriter 包装
|
|
402
|
+
|
|
403
|
+
Launcher 在收到 module.ready 后会关闭 stdio 管道。此后模块的 stdout/stderr 写入会触发 BrokenPipeError。必须包装:
|
|
404
|
+
|
|
405
|
+
```python
|
|
406
|
+
class _SafeWriter:
|
|
407
|
+
def __init__(self, stream):
|
|
408
|
+
self._stream = stream
|
|
409
|
+
def write(self, s):
|
|
410
|
+
try: self._stream.write(s)
|
|
411
|
+
except (BrokenPipeError, OSError): pass
|
|
412
|
+
def flush(self):
|
|
413
|
+
try: self._stream.flush()
|
|
414
|
+
except (BrokenPipeError, OSError): pass
|
|
415
|
+
def __getattr__(self, name):
|
|
416
|
+
return getattr(self._stream, name)
|
|
417
|
+
|
|
418
|
+
sys.stdout = _SafeWriter(sys.stdout)
|
|
419
|
+
sys.stderr = _SafeWriter(sys.stderr)
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
Node.js 中 stdout 写入失败不会抛异常,通常无需额外处理。
|
|
423
|
+
|
|
424
|
+
### 5.3 读取 kernel_port
|
|
425
|
+
|
|
426
|
+
Launcher 通过 stdin 第二行 JSON 或环境变量传递 Kernel 端口:
|
|
427
|
+
|
|
428
|
+
```python
|
|
429
|
+
kernel_port = os.environ.get("KITE_KERNEL_PORT")
|
|
430
|
+
if not kernel_port:
|
|
431
|
+
line = sys.stdin.readline().strip()
|
|
432
|
+
if line:
|
|
433
|
+
msg = json.loads(line)
|
|
434
|
+
if msg.get("kite") == "kernel_port":
|
|
435
|
+
kernel_port = msg["kernel_port"]
|
|
436
|
+
|
|
437
|
+
kernel_port = int(kernel_port)
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
### 5.4 读取环境变量
|
|
441
|
+
|
|
442
|
+
Launcher 通过环境变量向模块传递系统信息:
|
|
443
|
+
|
|
444
|
+
| 环境变量 | 说明 | 示例 |
|
|
445
|
+
|---------|------|------|
|
|
446
|
+
| `KITE_KERNEL_PORT` | Kernel WebSocket 端口 | `52340` |
|
|
447
|
+
| `KITE_PROJECT` | 框架代码根目录 | `/path/to/Kite` |
|
|
448
|
+
| `KITE_CWD` | 用户工作目录 | `/home/user/project` |
|
|
449
|
+
| `KITE_INSTANCE_DIR` | 当前实例工作区 | `~/.kite/workspace/project/` |
|
|
450
|
+
| `KITE_DATA` | 用户级共享数据目录 | `~/.kite/data/` |
|
|
451
|
+
| `KITE_MODULE_DATA` | 本模块的实例级数据目录 | `~/.kite/workspace/project/my_module/` |
|
|
452
|
+
| `KITE_INSTANCE` | 实例标识 | `abc123` |
|
|
453
|
+
| `KITE_DEBUG` | 调试模式(`1` = 开启) | `1` |
|
|
454
|
+
| `KITE_ENV` | 运行环境 | `development` / `production` |
|
|
455
|
+
|
|
456
|
+
最常用的是 `KITE_KERNEL_PORT`,它是连接 Kernel 的唯一入口:
|
|
457
|
+
|
|
458
|
+
```python
|
|
459
|
+
kernel_port = int(os.environ["KITE_KERNEL_PORT"])
|
|
460
|
+
ws_url = f"ws://127.0.0.1:{kernel_port}/ws?token={token}&id={module_id}"
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
### 5.5 连接 Kernel WebSocket
|
|
464
|
+
|
|
465
|
+
使用 token 和 module_id 连接 Kernel:
|
|
466
|
+
|
|
467
|
+
```python
|
|
468
|
+
import websockets.sync.client as ws_sync
|
|
469
|
+
|
|
470
|
+
ws_url = f"ws://127.0.0.1:{kernel_port}/ws?token={token}&id=my_module"
|
|
471
|
+
ws = ws_sync.connect(ws_url)
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
**Node.js:**
|
|
475
|
+
```javascript
|
|
476
|
+
const WebSocket = require('ws');
|
|
477
|
+
const ws = new WebSocket(`ws://127.0.0.1:${kernelPort}/ws?token=${token}&id=my_module`);
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
**连接失败处理:** 首次连接也使用与重连相同的退避策略(初始 0.3s、上限 5s、最多 10 次)。超过重试次数后 `sys.exit(1)`。详见 5.11 连接韧性。
|
|
481
|
+
|
|
482
|
+
### 5.6 订阅事件(先于注册)
|
|
483
|
+
|
|
484
|
+
**必须先订阅再注册。** 注册是报告,不能报告还没做的事。
|
|
485
|
+
|
|
486
|
+
通过 JSON-RPC 调用 `event.subscribe`:
|
|
487
|
+
|
|
488
|
+
```json
|
|
489
|
+
{
|
|
490
|
+
"jsonrpc": "2.0",
|
|
491
|
+
"id": "uuid",
|
|
492
|
+
"method": "event.subscribe",
|
|
493
|
+
"params": {
|
|
494
|
+
"events": ["module.>", "channel.message.*"]
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
订阅模式语法详见 6.2 节。
|
|
500
|
+
|
|
501
|
+
### 5.7 注册到 Kernel(在订阅之后)
|
|
502
|
+
|
|
503
|
+
通过 JSON-RPC 调用 `registry.register`:
|
|
504
|
+
|
|
505
|
+
```json
|
|
506
|
+
{
|
|
507
|
+
"jsonrpc": "2.0",
|
|
508
|
+
"id": "uuid",
|
|
509
|
+
"method": "registry.register",
|
|
510
|
+
"params": {
|
|
511
|
+
"module_id": "my_module",
|
|
512
|
+
"module_type": "service",
|
|
513
|
+
"name": "My Module",
|
|
514
|
+
"graceful_shutdown": true,
|
|
515
|
+
"events_subscribe": ["module.>"]
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
注册返回:`{"ok": true, "ttl": 60, "heartbeat_interval": 30}`
|
|
521
|
+
|
|
522
|
+
**Payload 字段说明:**
|
|
523
|
+
|
|
524
|
+
| 字段 | 必填 | 说明 |
|
|
525
|
+
|------|------|------|
|
|
526
|
+
| `action` | 是 | `"register"` |
|
|
527
|
+
| `module_id` | 是 | 模块唯一标识(来自 module.md 的 `name`) |
|
|
528
|
+
| `module_type` | 是 | 模块类型 |
|
|
529
|
+
| `name` | 否 | 显示名称 |
|
|
530
|
+
| `base_url` | 否 | HTTP 服务完整地址(如 `http://127.0.0.1:8080`) |
|
|
531
|
+
| `health_path` | 否 | 健康检查的相对路径(如 `/health`) |
|
|
532
|
+
| `display` | 否 | 展示信息,含 `urls` 子字段(如 `{"urls": {"Web 管理后台": "http://..."}}` |
|
|
533
|
+
| `tools` | 否 | 字典:工具名 → {endpoint, description, parameters} |
|
|
534
|
+
| `hooks` | 否 | 字典:hook 点名 → {endpoint, priority} |
|
|
535
|
+
| `events_publish` | 否 | 字典:事件类型名 → 扩展信息 |
|
|
536
|
+
| `events_subscribe` | 否 | 字典/列表:订阅的事件类型 |
|
|
537
|
+
| `metadata` | 否 | 自定义元数据 |
|
|
538
|
+
|
|
539
|
+
### 5.8 发送 module.ready
|
|
540
|
+
|
|
541
|
+
**这是必须的。** Launcher 以收到此事件作为"启动成功"的判定标准。
|
|
542
|
+
|
|
543
|
+
通过 JSON-RPC 调用 `event.publish` 发布 `module.ready`:
|
|
544
|
+
|
|
545
|
+
```json
|
|
546
|
+
{
|
|
547
|
+
"event": "module.ready",
|
|
548
|
+
"data": {
|
|
549
|
+
"module_id": "my_module",
|
|
550
|
+
"graceful_shutdown": true
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
`data.graceful_shutdown: true` 声明模块支持优雅退出(详见第 7 节)。不声明或为 false 时,Launcher 停止模块时会直接 terminate。
|
|
556
|
+
|
|
557
|
+
Launcher 收到 module.ready 后:
|
|
558
|
+
1. 发布 `module.started` 事件(通知其他模块)
|
|
559
|
+
2. 关闭该模块的 stdin 管道
|
|
560
|
+
3. 后续通信全部走 Kernel WebSocket JSON-RPC
|
|
561
|
+
|
|
562
|
+
### 5.9 心跳
|
|
563
|
+
|
|
564
|
+
Kernel 通过 WebSocket 连接状态判断模块在线。模块可选实现应用层心跳,调用 `kernel.ping`:
|
|
565
|
+
|
|
566
|
+
```json
|
|
567
|
+
{"jsonrpc": "2.0", "id": "uuid", "method": "kernel.ping", "params": {}}
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
每 30 秒发送一次,超过 60 秒无心跳会被标记为 offline。
|
|
571
|
+
|
|
572
|
+
### 5.10 完整启动流程概览
|
|
573
|
+
|
|
574
|
+
```mermaid
|
|
575
|
+
flowchart TD
|
|
576
|
+
Start([entry.py 启动]) --> Wrap[包装 SafeWriter<br/>防 BrokenPipeError]
|
|
577
|
+
Wrap --> ReadStdin[从 stdin 读取 token]
|
|
578
|
+
ReadStdin --> ReadPort[读取 kernel_port<br/>env 优先 / stdin 备选]
|
|
579
|
+
ReadPort --> Connect[连接 Kernel WebSocket<br/>ws://127.0.0.1:port/ws?token=...&id=...]
|
|
580
|
+
Connect --> Subscribe[④ RPC event.subscribe<br/>订阅需要的事件 — 先做!]
|
|
581
|
+
Subscribe --> Register[⑤ RPC registry.register<br/>注册到 Kernel — 后做,注册是报告]
|
|
582
|
+
Register --> Ready[⑥ RPC event.publish<br/>发布 module.ready — 必须!]
|
|
583
|
+
Ready --> Loop([⑦ 进入主循环<br/>监听事件 + 处理 RPC])
|
|
584
|
+
|
|
585
|
+
style Subscribe fill:#e1f5ff
|
|
586
|
+
style Register fill:#fff4e1
|
|
587
|
+
style Ready fill:#e8f5e9
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
**文字版(方便搜索):**
|
|
591
|
+
|
|
592
|
+
```
|
|
593
|
+
entry.py 启动
|
|
594
|
+
│
|
|
595
|
+
├─ ① 读 stdin → 拿到 token + kernel_port
|
|
596
|
+
├─ ② 包装 SafeWriter(防 BrokenPipeError)
|
|
597
|
+
├─ ③ 连接 Kernel WebSocket(ws://127.0.0.1:{port}/ws?token={token}&id={module_id})
|
|
598
|
+
├─ ④ RPC event.subscribe → 订阅事件(先做!)
|
|
599
|
+
├─ ⑤ RPC registry.register → 注册到 Kernel(后做,注册是报告)
|
|
600
|
+
├─ ⑥ RPC event.publish → 发送 module.ready 事件(必须)
|
|
601
|
+
├─ ⑦ 进入主循环(监听事件 + 处理 RPC 请求)
|
|
602
|
+
└─ ⑧ 退出时 RPC registry.deregister
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
**关键设计原则:**
|
|
606
|
+
|
|
607
|
+
- **先订阅,再注册** — 订阅是实际操作("请给我发事件"),注册是声明报告("我订阅了这些事件")。不能报告还没做的事
|
|
608
|
+
- **Kernel 生成所有 token** — 谁需要验证谁生成。Launcher 的 token 由 Kernel 通过 stdout 传递,其他模块的 token 由 Kernel 通过 RPC 生成后经 Launcher stdin 分发
|
|
609
|
+
- **module.ready 必须发** — Launcher 以此为"启动成功"的判定标准
|
|
610
|
+
|
|
611
|
+
### 5.11 连接韧性
|
|
612
|
+
|
|
613
|
+
模块与 Kernel 的 WebSocket 连接可能因网络抖动、Kernel 重启等原因断开。所有模块必须实现自动重连机制,确保短暂断开后能恢复正常工作。
|
|
614
|
+
|
|
615
|
+
#### 5.11.1 重连策略
|
|
616
|
+
|
|
617
|
+
**参数:**
|
|
618
|
+
|
|
619
|
+
| 参数 | 值 | 说明 |
|
|
620
|
+
|------|-----|------|
|
|
621
|
+
| 初始延迟 | 0.3s | 首次重连等待 |
|
|
622
|
+
| 退避方式 | 指数翻倍 | 0.3 → 0.6 → 1.2 → 2.4 → 4.8 → 5 → 5 → ... |
|
|
623
|
+
| 延迟上限 | 5s | 不超过此值 |
|
|
624
|
+
| 最大重试次数 | 10 | 超过后 `sys.exit(1)` |
|
|
625
|
+
| 总最大耗时 | ≈34s | 10 次重试的总等待时间 |
|
|
626
|
+
|
|
627
|
+
**重连成功后必须重新执行完整握手:**
|
|
628
|
+
|
|
629
|
+
1. `event.subscribe` — 重新订阅事件(Kernel 断开后订阅表已清空)
|
|
630
|
+
2. `registry.register` — 重新注册(Kernel 断开后注册表已清空)
|
|
631
|
+
3. `event.publish module.ready` — 重新发送就绪通知
|
|
632
|
+
|
|
633
|
+
**成功连接后重置计数器:** `retry_delay = 0.3`,`attempt = 0`。
|
|
634
|
+
|
|
635
|
+
#### 5.11.2 不重连的情况
|
|
636
|
+
|
|
637
|
+
以下情况断开后应直接退出,不进行重连:
|
|
638
|
+
|
|
639
|
+
- **收到 `module.shutdown` 后断开** — 被主动关闭,重连无意义
|
|
640
|
+
- **认证失败** — Kernel 返回 WebSocket 关闭码 `4001`(认证失败)或 `4003`(权限拒绝),token 已失效,重连也会被拒绝
|
|
641
|
+
|
|
642
|
+
#### 5.11.3 module.exiting 发送
|
|
643
|
+
|
|
644
|
+
**所有退出路径都必须在退出前发送 `module.exiting` 事件,** 否则 Launcher 会将退出视为崩溃并触发重启。
|
|
645
|
+
|
|
646
|
+
需要发送 `module.exiting` 的场景:
|
|
647
|
+
- 优雅退出(`_handle_shutdown` 流程中,在 ack 之前)
|
|
648
|
+
- 主动退出(任务完成、缺少条件等)
|
|
649
|
+
- 重连耗尽退出(`_ws_loop` 中 `max_retries` 超限时)
|
|
650
|
+
|
|
651
|
+
不需要发送的场景:
|
|
652
|
+
- 崩溃退出(未捕获异常)— 此时 WS 已断开,无法发送
|
|
653
|
+
|
|
654
|
+
#### 5.11.4 module.ready 可重复性
|
|
655
|
+
|
|
656
|
+
`module.ready` 是一个**可重复的生命周期事件**,不应使用一次性标记(如 `_ready_sent`)限制发送。
|
|
657
|
+
|
|
658
|
+
模块可能经历多次断开→重连循环。每次重连成功后都必须重新发送 `module.ready`,让 Kernel 和其他模块更新对该模块的可用性认知。
|
|
659
|
+
|
|
660
|
+
**错误写法:**
|
|
661
|
+
```python
|
|
662
|
+
# ❌ 只发一次,重连后不再发送
|
|
663
|
+
if not self._ready_sent:
|
|
664
|
+
await publish_module_ready()
|
|
665
|
+
self._ready_sent = True
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
**正确写法:**
|
|
669
|
+
```python
|
|
670
|
+
# ✅ 每次连接成功后都发送
|
|
671
|
+
if not self._shutting_down:
|
|
672
|
+
await publish_module_ready()
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
#### 5.11.5 消息循环与 RPC 调用模式
|
|
676
|
+
|
|
677
|
+
**消息循环三分支路由:** 模块的 WebSocket 消息循环需区分三类消息:
|
|
678
|
+
|
|
679
|
+
| 消息类型 | 特征 | 处理方式 |
|
|
680
|
+
|----------|------|----------|
|
|
681
|
+
| 事件通知 | 有 `method`,无 `id` | 转发给事件处理器 |
|
|
682
|
+
| RPC 请求 | 有 `method`,有 `id` | 执行并返回响应 |
|
|
683
|
+
| RPC 响应 | 有 `id`,无 `method` | 路由到对应的等待者 |
|
|
684
|
+
|
|
685
|
+
**RPC 调用两种模式:**
|
|
686
|
+
|
|
687
|
+
- **Fire-and-forget(只发不等)** — 发送后不等响应。适用于 `event.publish`、`event.subscribe` 等不需要返回值的调用
|
|
688
|
+
- **Request-Response(发送并等待)** — 发送后通过 waiter 表等待响应匹配。适用于 `registry.lookup`、`launcher.list_modules` 等需要读取返回值的调用。超时默认 5s,超时返回错误 dict
|
|
689
|
+
|
|
690
|
+
**选择依据:** 需要读取响应 `result` 字段 → Request-Response;仅发送通知 → Fire-and-forget。
|
|
691
|
+
|
|
692
|
+
> **参考实现:** `extensions/services/watchdog/entry.py` 中的 `_rpc_call`、`_rpc_call_with_response`、消息循环三分支路由。其他模块(web、assistant、acp_channel)也遵循相同模式。
|
|
693
|
+
|
|
694
|
+
## 5.5 RPC 死锁防范(CRITICAL)
|
|
695
|
+
|
|
696
|
+
**这是所有模块必须遵守的架构规范,违反会导致 RPC 超时死锁。**
|
|
697
|
+
|
|
698
|
+
### 问题根源
|
|
699
|
+
|
|
700
|
+
WebSocket 接收循环是单线程的 `async for` 循环,处理三种消息:
|
|
701
|
+
|
|
702
|
+
1. **事件通知**(无 id)— 单向推送,不需要响应
|
|
703
|
+
2. **入站 RPC 请求**(有 id + method)— 需要执行 handler 并返回响应
|
|
704
|
+
3. **出站 RPC 响应**(有 id + result/error)— 匹配之前发出的请求
|
|
705
|
+
|
|
706
|
+
**死锁场景:** 如果在处理入站 RPC 时 `await` 了 handler,而 handler 内部又调用 `rpc_call_with_response()` 发出站请求并等待响应,那么:
|
|
707
|
+
|
|
708
|
+
- 接收循环被 `await handler()` 阻塞
|
|
709
|
+
- 出站 RPC 的响应到达,但接收循环无法处理(正在等 handler 返回)
|
|
710
|
+
- handler 在等出站响应,出站响应在等接收循环 → **死锁,5s 超时**
|
|
711
|
+
|
|
712
|
+
### 正确处理策略
|
|
713
|
+
|
|
714
|
+
**所有模块的接收循环必须遵循以下模式:**
|
|
715
|
+
|
|
716
|
+
```python
|
|
717
|
+
# CRITICAL: RPC 死锁防范
|
|
718
|
+
# - 入站 RPC 请求必须用 create_task() 异步执行,不可 await
|
|
719
|
+
# - 原因:如果 handler 内部调用 rpc_call() 发出站请求,出站响应需要本接收循环来分发
|
|
720
|
+
# - 如果接收循环被 await handler 阻塞,出站响应永远收不到 → 超时死锁
|
|
721
|
+
# - 事件通知和 RPC 响应可以同步处理(它们不会反向调用 rpc_call)
|
|
722
|
+
|
|
723
|
+
async for raw in ws:
|
|
724
|
+
try:
|
|
725
|
+
msg = json.loads(raw)
|
|
726
|
+
except (json.JSONDecodeError, TypeError):
|
|
727
|
+
continue
|
|
728
|
+
|
|
729
|
+
try:
|
|
730
|
+
has_method = "method" in msg
|
|
731
|
+
has_id = "id" in msg
|
|
732
|
+
|
|
733
|
+
if has_method and not has_id:
|
|
734
|
+
# 事件通知 — 同步处理(快速,不会发 RPC)
|
|
735
|
+
await handle_event_notification(msg)
|
|
736
|
+
elif has_method and has_id:
|
|
737
|
+
# 入站 RPC 请求 — 异步执行,防止死锁
|
|
738
|
+
asyncio.create_task(handle_rpc_request(ws, msg))
|
|
739
|
+
elif has_id:
|
|
740
|
+
# 出站 RPC 响应 — 同步匹配 waiter
|
|
741
|
+
match_rpc_response(msg)
|
|
742
|
+
except Exception as e:
|
|
743
|
+
print(f"[module] 消息处理异常(已忽略): {e}")
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
### 关键点
|
|
747
|
+
|
|
748
|
+
1. **入站 RPC 必须 `create_task`**,不可 `await`
|
|
749
|
+
2. **事件通知可以 `await`**(它们不会反向发 RPC)
|
|
750
|
+
3. **RPC 响应匹配必须同步**(直接写 dict + set Event)
|
|
751
|
+
4. **所有模块都要遵守**,即使当前 handler 不发 RPC(未来可能会加)
|
|
752
|
+
|
|
753
|
+
### 已知案例
|
|
754
|
+
|
|
755
|
+
- **Launcher** — `_rpc_restart_module` 调用 `kernel.register_tokens`,修复前超时 5s
|
|
756
|
+
- **Watchdog** — `_restart_module_by_id` 调用 `launcher.restart_module`,修复前超时 5s
|
|
757
|
+
|
|
758
|
+
### 参考实现
|
|
759
|
+
|
|
760
|
+
所有现有模块已修复:
|
|
761
|
+
- `launcher/entry.py:665` — Launcher 接收循环
|
|
762
|
+
- `extensions/services/watchdog/entry.py:460` — Watchdog 接收循环
|
|
763
|
+
- `extensions/services/web/server.py:266` — Web 接收循环
|
|
764
|
+
- `extensions/services/backup/entry.py:426` — Backup 接收循环
|
|
765
|
+
- `extensions/services/model_service/entry.py:426` — Model Service 接收循环
|
|
766
|
+
- `extensions/agents/assistant/server.py:122` — Assistant 接收循环
|
|
767
|
+
- `extensions/channels/acp_channel/server.py:122` — ACP Channel 接收循环
|
|
768
|
+
|
|
769
|
+
**新模块开发时,直接复制上述任一模块的接收循环结构即可。**
|
|
770
|
+
|
|
771
|
+
## 6. 事件系统
|
|
772
|
+
|
|
773
|
+
### 6.1 发布事件
|
|
774
|
+
|
|
775
|
+
通过 Kernel WebSocket 发送 JSON-RPC 请求:
|
|
776
|
+
|
|
777
|
+
```json
|
|
778
|
+
{
|
|
779
|
+
"jsonrpc": "2.0",
|
|
780
|
+
"id": "uuid-string",
|
|
781
|
+
"method": "event.publish",
|
|
782
|
+
"params": {
|
|
783
|
+
"event_id": "uuid-string",
|
|
784
|
+
"event": "my_module.something_happened",
|
|
785
|
+
"data": {
|
|
786
|
+
"key": "value"
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
```
|
|
791
|
+
|
|
792
|
+
| 字段 | 必填 | 说明 |
|
|
793
|
+
|------|------|------|
|
|
794
|
+
| `event_id` | 是 | 唯一 ID(UUID),用于去重 |
|
|
795
|
+
| `event` | 是 | 事件类型(点分层级,如 `channel.message.received`) |
|
|
796
|
+
| `data` | 否 | 事件数据 |
|
|
797
|
+
|
|
798
|
+
Kernel 返回 JSON-RPC 响应:`{"jsonrpc": "2.0", "id": "uuid", "result": {}}`
|
|
799
|
+
|
|
800
|
+
### 6.2 订阅模式
|
|
801
|
+
|
|
802
|
+
使用 NATS 风格通配符,事件类型以 `.` 分隔:
|
|
803
|
+
|
|
804
|
+
| 模式 | 说明 | 匹配示例 | 不匹配 |
|
|
805
|
+
|------|------|---------|--------|
|
|
806
|
+
| `session.started` | 精确匹配 | `session.started` | `session.ended` |
|
|
807
|
+
| `channel.*` | `*` 匹配恰好一段 | `channel.connected` | `channel.message.received` |
|
|
808
|
+
| `channel.>` | `>` 匹配一段或多段(只能在末尾) | `channel.connected`、`channel.message.received` | `session.started` |
|
|
809
|
+
| `>` | 匹配所有事件 | 任何事件 | — |
|
|
810
|
+
|
|
811
|
+
订阅和取消订阅:
|
|
812
|
+
|
|
813
|
+
```json
|
|
814
|
+
{"jsonrpc": "2.0", "id": "uuid", "method": "event.subscribe", "params": {"events": ["channel.message.*", "session.*"]}}
|
|
815
|
+
{"jsonrpc": "2.0", "id": "uuid", "method": "event.unsubscribe", "params": {"events": ["channel.message.*"]}}
|
|
816
|
+
```
|
|
817
|
+
|
|
818
|
+
### 6.3 发布确认
|
|
819
|
+
|
|
820
|
+
发布事件是 JSON-RPC 请求,Kernel 返回标准 JSON-RPC 响应作为确认:
|
|
821
|
+
|
|
822
|
+
```json
|
|
823
|
+
{"jsonrpc": "2.0", "id": "请求id", "result": {"ok": true, "routed": 3}}
|
|
824
|
+
```
|
|
825
|
+
|
|
826
|
+
如果发布失败(格式错误、去重等),返回 error:
|
|
827
|
+
|
|
828
|
+
```json
|
|
829
|
+
{"jsonrpc": "2.0", "id": "请求id", "error": {"code": -32602, "message": "duplicate event_id"}}
|
|
830
|
+
```
|
|
831
|
+
|
|
832
|
+
### 6.4 接收事件
|
|
833
|
+
|
|
834
|
+
订阅的事件以 **JSON-RPC Notification** 形式推送给模块(有 `method` 字段,无 `id` 字段):
|
|
835
|
+
|
|
836
|
+
```json
|
|
837
|
+
{
|
|
838
|
+
"jsonrpc": "2.0",
|
|
839
|
+
"method": "event",
|
|
840
|
+
"params": {
|
|
841
|
+
"event_id": "uuid",
|
|
842
|
+
"event": "channel.message.received",
|
|
843
|
+
"source": "acp_channel",
|
|
844
|
+
"timestamp": "2026-03-03T12:00:00Z",
|
|
845
|
+
"data": {"message": "hello"}
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
```
|
|
849
|
+
|
|
850
|
+
消息循环需区分三类消息(事件通知 / RPC 请求 / RPC 响应),判断方法和处理模式详见 5.11.5。
|
|
851
|
+
|
|
852
|
+
### 6.5 常用系统事件
|
|
853
|
+
|
|
854
|
+
| 事件 | 来源 | 说明 |
|
|
855
|
+
|------|------|------|
|
|
856
|
+
| `module.starting` | Launcher | 模块即将启动 |
|
|
857
|
+
| `module.ready` | 模块 | 模块就绪(必须发送) |
|
|
858
|
+
| `module.started` | Launcher | 模块启动成功 |
|
|
859
|
+
| `module.stopped` | Launcher | 模块已停止 |
|
|
860
|
+
| `module.shutdown` | Launcher | 通知模块准备退出 |
|
|
861
|
+
| `module.shutdown.ack` | 模块 | 确认收到关闭通知 |
|
|
862
|
+
| `module.shutdown.ready` | 模块 | 清理完成,可以终止 |
|
|
863
|
+
| `module.exiting` | 模块 | 模块主动退出通知 |
|
|
864
|
+
| `module.offline` | Kernel | 模块 WS 断开超过 5s 防抖后确认离线(系统事件,自动广播给所有模块,无需显式订阅) |
|
|
865
|
+
| `session.started` | Kernel | 会话开始 |
|
|
866
|
+
| `session.ended` | Kernel | 会话结束 |
|
|
867
|
+
| `channel.message.received` | Channel | 渠道收到消息 |
|
|
868
|
+
| `channel.message.sent` | Channel | 渠道发出消息 |
|
|
869
|
+
| `channel.connected` | Channel | 渠道上线 |
|
|
870
|
+
| `channel.disconnected` | Channel | 渠道断线 |
|
|
871
|
+
|
|
872
|
+
### 6.6 事件处理与 RPC 调用的并发陷阱
|
|
873
|
+
|
|
874
|
+
**核心原则:不要在事件处理函数中直接 await RPC 调用,否则会阻塞消息接收循环,导致后续事件(如 shutdown)无法及时处理。**
|
|
875
|
+
|
|
876
|
+
#### 问题场景
|
|
877
|
+
|
|
878
|
+
假设你在事件处理函数中调用 RPC:
|
|
879
|
+
|
|
880
|
+
```python
|
|
881
|
+
async def handle_event(event):
|
|
882
|
+
if event["event"] == "module.started":
|
|
883
|
+
# ❌ 错误:直接 await RPC 调用会阻塞消息循环
|
|
884
|
+
result = await rpc_call("launcher.list_modules")
|
|
885
|
+
# 在等待 RPC 响应期间,WebSocket 消息队列中的其他事件(如 shutdown)无法被处理
|
|
886
|
+
```
|
|
887
|
+
|
|
888
|
+
**后果:**
|
|
889
|
+
- 如果 RPC 响应延迟(如对方正在处理其他任务),消息接收循环会被阻塞
|
|
890
|
+
- 在此期间到达的 `module.shutdown` 等关键事件会积压在队列中
|
|
891
|
+
- 可能导致模块无法及时响应 shutdown,被 Launcher 强制 kill
|
|
892
|
+
|
|
893
|
+
#### 解决方案
|
|
894
|
+
|
|
895
|
+
**方案 1:使用 asyncio.create_task 异步执行**
|
|
896
|
+
|
|
897
|
+
```python
|
|
898
|
+
async def handle_event(event):
|
|
899
|
+
if event["event"] == "module.started":
|
|
900
|
+
# ✅ 正确:将 RPC 调用放到后台任务中
|
|
901
|
+
asyncio.create_task(self._handle_module_started())
|
|
902
|
+
|
|
903
|
+
async def _handle_module_started(self):
|
|
904
|
+
result = await rpc_call("launcher.list_modules")
|
|
905
|
+
# 处理结果...
|
|
906
|
+
```
|
|
907
|
+
|
|
908
|
+
**方案 2:将 RPC 调用放到独立的循环中**
|
|
909
|
+
|
|
910
|
+
```python
|
|
911
|
+
# 主消息循环:只处理事件,不阻塞
|
|
912
|
+
async for raw in ws:
|
|
913
|
+
msg = json.loads(raw)
|
|
914
|
+
if msg.get("method") == "event":
|
|
915
|
+
await handle_event(msg["params"]) # 快速处理,不阻塞
|
|
916
|
+
|
|
917
|
+
# 独立的后台任务:定期执行 RPC 调用
|
|
918
|
+
async def background_task():
|
|
919
|
+
while running:
|
|
920
|
+
result = await rpc_call("some.method")
|
|
921
|
+
# 处理结果...
|
|
922
|
+
await asyncio.sleep(interval)
|
|
923
|
+
|
|
924
|
+
# 启动时创建后台任务
|
|
925
|
+
asyncio.create_task(background_task())
|
|
926
|
+
```
|
|
927
|
+
|
|
928
|
+
#### 架构建议
|
|
929
|
+
|
|
930
|
+
- **消息接收循环**:只负责接收和分发消息,保持快速响应
|
|
931
|
+
- **事件处理函数**:只做轻量级处理(更新状态、触发回调),不阻塞
|
|
932
|
+
- **RPC 调用**:放到独立的后台任务或使用 `create_task` 异步执行
|
|
933
|
+
- **关键事件**(如 `module.shutdown`):必须在事件处理函数中立即处理,不能延迟
|
|
934
|
+
|
|
935
|
+
#### 实际案例
|
|
936
|
+
|
|
937
|
+
Watchdog 模块的修复:
|
|
938
|
+
|
|
939
|
+
```python
|
|
940
|
+
# ❌ 旧代码:阻塞消息循环
|
|
941
|
+
async def handle_event(event):
|
|
942
|
+
if event["event"] == "module.started":
|
|
943
|
+
await self.discover_modules() # RPC 调用,阻塞 10 秒
|
|
944
|
+
|
|
945
|
+
# ✅ 新代码:异步执行
|
|
946
|
+
async def handle_event(event):
|
|
947
|
+
if event["event"] == "module.started":
|
|
948
|
+
asyncio.create_task(self.discover_modules()) # 不阻塞
|
|
949
|
+
```
|
|
950
|
+
|
|
951
|
+
修复后,Watchdog 能够在 RPC 调用期间正常接收 shutdown 事件,退出时间从 5.38s 降到 1.36s。
|
|
952
|
+
|
|
953
|
+
## 7. 优雅退出
|
|
954
|
+
|
|
955
|
+
优雅退出让模块有机会完成清理工作后再退出,避免数据丢失和资源泄漏。所有模块**建议**实现,不实现的模块仍可运行,但会被直接终止。
|
|
956
|
+
|
|
957
|
+
### 7.1 声明支持
|
|
958
|
+
|
|
959
|
+
在 `module.ready` 事件的 `data` 中声明 `graceful_shutdown: true`:
|
|
960
|
+
|
|
961
|
+
```json
|
|
962
|
+
{
|
|
963
|
+
"event": "module.ready",
|
|
964
|
+
"data": {
|
|
965
|
+
"module_id": "my_module",
|
|
966
|
+
"graceful_shutdown": true
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
```
|
|
970
|
+
|
|
971
|
+
不声明或为 `false` 时,Launcher 停止时直接 terminate,不走事件通知流程。
|
|
972
|
+
|
|
973
|
+
### 7.2 停止流程总览
|
|
974
|
+
|
|
975
|
+
```mermaid
|
|
976
|
+
sequenceDiagram
|
|
977
|
+
participant L as Launcher
|
|
978
|
+
participant K as Kernel
|
|
979
|
+
participant M as 模块
|
|
980
|
+
|
|
981
|
+
L->>K: event.publish: module.shutdown<br/>(target: my_module, timeout: 10)
|
|
982
|
+
K-->>M: 转发 module.shutdown 通知
|
|
983
|
+
|
|
984
|
+
M->>K: event.publish: module.shutdown.ack<br/>(estimated_cleanup: 5)
|
|
985
|
+
K-->>L: 转发 ack
|
|
986
|
+
|
|
987
|
+
Note over M: 执行清理工作...
|
|
988
|
+
|
|
989
|
+
M->>K: event.publish: module.shutdown.ready<br/>(module_id: my_module)
|
|
990
|
+
K-->>L: 转发 ready
|
|
991
|
+
|
|
992
|
+
Note over L: 收到 ready → 终止进程
|
|
993
|
+
```
|
|
994
|
+
|
|
995
|
+
### 7.3 Launcher 侧决策逻辑
|
|
996
|
+
|
|
997
|
+
```
|
|
998
|
+
Launcher 要停止模块 X:
|
|
999
|
+
│
|
|
1000
|
+
├─ Step 0: 检查 X 是否声明了 graceful_shutdown
|
|
1001
|
+
│ ├─ 未声明 → 直接 proc.terminate(),等 5s,超时 proc.kill(),结束
|
|
1002
|
+
│ └─ 已声明 → 进入 Step 1
|
|
1003
|
+
│
|
|
1004
|
+
├─ Step 1: 通过 Kernel 发送 module.shutdown 事件
|
|
1005
|
+
│
|
|
1006
|
+
├─ Step 2: 等待 module.shutdown.ack(超时 3s)
|
|
1007
|
+
│ ├─ 收到 ack:
|
|
1008
|
+
│ │ wait_time = min(ack.estimated_cleanup, launcher_timeout)
|
|
1009
|
+
│ │ → 进入 Step 3
|
|
1010
|
+
│ │
|
|
1011
|
+
│ └─ 未收到 ack(3s 超时):
|
|
1012
|
+
│ → proc.terminate(),等 5s,超时 proc.kill()
|
|
1013
|
+
│
|
|
1014
|
+
├─ Step 3: 等待 module.shutdown.ready 或超时
|
|
1015
|
+
│ ├─ 收到 ready → 立即 proc.kill(),结束
|
|
1016
|
+
│ └─ 超时 → proc.terminate(),等 3s,超时 proc.kill()
|
|
1017
|
+
│
|
|
1018
|
+
└─ 清理: 从进程表移除,发 module.stopped 事件
|
|
1019
|
+
```
|
|
1020
|
+
|
|
1021
|
+
### 7.4 事件定义
|
|
1022
|
+
|
|
1023
|
+
#### module.shutdown(Launcher / Kernel → 模块)
|
|
1024
|
+
|
|
1025
|
+
`module.shutdown` 有两种使用模式:
|
|
1026
|
+
|
|
1027
|
+
**模式 1:定向关闭**(Launcher 关闭特定模块)
|
|
1028
|
+
|
|
1029
|
+
```json
|
|
1030
|
+
{
|
|
1031
|
+
"event": "module.shutdown",
|
|
1032
|
+
"data": {
|
|
1033
|
+
"module_id": "my_module",
|
|
1034
|
+
"reason": "stop_requested",
|
|
1035
|
+
"timeout": 10
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
```
|
|
1039
|
+
|
|
1040
|
+
**模式 2:广播关闭**(Kernel 通知所有模块系统即将关闭)
|
|
1041
|
+
|
|
1042
|
+
```json
|
|
1043
|
+
{
|
|
1044
|
+
"event": "module.shutdown",
|
|
1045
|
+
"data": {
|
|
1046
|
+
"reason": "launcher_lost"
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
```
|
|
1050
|
+
|
|
1051
|
+
注意:广播模式**没有 `module_id` 字段**,所有模块都应响应。
|
|
1052
|
+
|
|
1053
|
+
**模块接收判断逻辑:**
|
|
1054
|
+
|
|
1055
|
+
```python
|
|
1056
|
+
if event_type == "module.shutdown":
|
|
1057
|
+
target = data.get("module_id", "")
|
|
1058
|
+
reason = data.get("reason", "")
|
|
1059
|
+
# 定向关闭(target == 自己)或广播关闭(无 target 或 launcher_lost)
|
|
1060
|
+
if target == "my_module" or not target or reason == "launcher_lost":
|
|
1061
|
+
await handle_shutdown()
|
|
1062
|
+
```
|
|
1063
|
+
|
|
1064
|
+
**reason 取值:**
|
|
1065
|
+
|
|
1066
|
+
| reason | 模式 | 说明 |
|
|
1067
|
+
|--------|------|------|
|
|
1068
|
+
| `stop_requested` | 定向 | 手动停止(API 调用) |
|
|
1069
|
+
| `system_shutdown` | 定向 | 系统退出(Ctrl+C / SIGTERM) |
|
|
1070
|
+
| `hot_update` | 定向 | 热更新 |
|
|
1071
|
+
| `restart` | 定向 | 重启 |
|
|
1072
|
+
| `resource_critical` | 定向 | 资源异常(Watchdog 触发) |
|
|
1073
|
+
| `launcher_lost` | 广播 | Launcher 异常终止超过 35s,Kernel 触发系统关闭 |
|
|
1074
|
+
|
|
1075
|
+
#### module.shutdown.ack(模块 → Launcher)
|
|
1076
|
+
|
|
1077
|
+
```json
|
|
1078
|
+
{
|
|
1079
|
+
"event": "module.shutdown.ack",
|
|
1080
|
+
"data": {
|
|
1081
|
+
"module_id": "my_module",
|
|
1082
|
+
"estimated_cleanup": 5
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
```
|
|
1086
|
+
|
|
1087
|
+
`estimated_cleanup`:模块预估的清理时间(秒)。Launcher 取 `min(estimated_cleanup, launcher_timeout)` 作为实际等待时间。
|
|
1088
|
+
|
|
1089
|
+
#### module.shutdown.ready(模块 → Launcher)
|
|
1090
|
+
|
|
1091
|
+
```json
|
|
1092
|
+
{
|
|
1093
|
+
"event": "module.shutdown.ready",
|
|
1094
|
+
"data": {
|
|
1095
|
+
"module_id": "my_module"
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
```
|
|
1099
|
+
|
|
1100
|
+
模块发出此事件后应立即退出进程(exit code 0)。
|
|
1101
|
+
|
|
1102
|
+
### 7.5 模块实现清单
|
|
1103
|
+
|
|
1104
|
+
收到 `module.shutdown` 后,模块应按顺序执行:
|
|
1105
|
+
|
|
1106
|
+
1. **立即回复 ack** — 含合理的 `estimated_cleanup` 秒数
|
|
1107
|
+
2. **停止接受新请求**
|
|
1108
|
+
3. **完成进行中的请求**(有超时保护)
|
|
1109
|
+
4. **flush 缓冲区**(日志等)
|
|
1110
|
+
5. **关闭网络连接**(WebSocket、HTTP)
|
|
1111
|
+
6. **持久化必要状态**
|
|
1112
|
+
7. **释放系统资源**(文件句柄、临时文件等)
|
|
1113
|
+
8. **发送 shutdown.ready**
|
|
1114
|
+
9. **立即退出进程**(exit code 0)
|
|
1115
|
+
|
|
1116
|
+
### 7.6 参考实现
|
|
1117
|
+
|
|
1118
|
+
- **Python**:`extensions/services/watchdog/entry.py` 中的 `_handle_shutdown()`
|
|
1119
|
+
- **Node.js**:`test_modules/hello_node/entry.js` 中的 shutdown 处理
|
|
1120
|
+
|
|
1121
|
+
核心流程:收到 `module.shutdown` → 发 `module.exiting` → 发 `module.shutdown.ack` → 清理 → 发 `module.shutdown.ready` → `exit(0)`。
|
|
1122
|
+
|
|
1123
|
+
### 7.8 不实现的后果
|
|
1124
|
+
|
|
1125
|
+
- Launcher 直接 `proc.terminate()`(Linux 发 SIGTERM,Windows 调 TerminateProcess)
|
|
1126
|
+
- 等待 5 秒后 `proc.kill()`(强杀)
|
|
1127
|
+
- 无清理机会,可能丢失未持久化的数据
|
|
1128
|
+
|
|
1129
|
+
详细规范见 [优雅退出规范](优雅退出规范.md)。
|
|
1130
|
+
|
|
1131
|
+
### 7.9 异步退出的常见陷阱与最佳实践
|
|
1132
|
+
|
|
1133
|
+
**问题:在异步协程中调用 `sys.exit()` 导致未捕获异常**
|
|
1134
|
+
|
|
1135
|
+
当 `_handle_shutdown()` 是通过 `asyncio.create_task()` 启动的后台任务时,在其中调用 `sys.exit()` 会抛出 `SystemExit` 异常,导致 "Task exception was never retrieved" 错误。虽然进程仍会退出,但会留下错误日志。
|
|
1136
|
+
|
|
1137
|
+
**错误示例:**
|
|
1138
|
+
|
|
1139
|
+
```python
|
|
1140
|
+
async def _handle_shutdown(self, ws):
|
|
1141
|
+
# ... 清理逻辑 ...
|
|
1142
|
+
await self._rpc_call(ws, "event.publish", {
|
|
1143
|
+
"event": "module.shutdown.ready",
|
|
1144
|
+
"data": {"module_id": "my_module"},
|
|
1145
|
+
})
|
|
1146
|
+
sys.exit(0) # ❌ 在协程中直接退出,抛出 SystemExit 异常
|
|
1147
|
+
```
|
|
1148
|
+
|
|
1149
|
+
**正确做法:根据模块类型选择退出方式**
|
|
1150
|
+
|
|
1151
|
+
#### 纯 WebSocket 客户端模块
|
|
1152
|
+
|
|
1153
|
+
特点:只连接 Kernel WebSocket,无 HTTP 服务(如 assistant、backup、model_service、watchdog)
|
|
1154
|
+
|
|
1155
|
+
```python
|
|
1156
|
+
async def _handle_shutdown(self, ws):
|
|
1157
|
+
# Step 1-4: 发送 ack → exiting → 清理 → ready
|
|
1158
|
+
await self._rpc_call(ws, "event.publish", {
|
|
1159
|
+
"event": "module.shutdown.ready",
|
|
1160
|
+
"data": {"module_id": "my_module"},
|
|
1161
|
+
})
|
|
1162
|
+
|
|
1163
|
+
# Step 5: 关闭 WebSocket 连接
|
|
1164
|
+
try:
|
|
1165
|
+
await ws.close(code=1000, reason="Graceful shutdown")
|
|
1166
|
+
print("[my_module] WebSocket closed")
|
|
1167
|
+
except Exception as e:
|
|
1168
|
+
print(f"[my_module] Failed to close WebSocket: {e}")
|
|
1169
|
+
|
|
1170
|
+
# ✅ 不调用 sys.exit(),让事件循环自然结束
|
|
1171
|
+
```
|
|
1172
|
+
|
|
1173
|
+
#### HTTP 服务模块
|
|
1174
|
+
|
|
1175
|
+
特点:提供 HTTP API,使用 uvicorn 运行(如 audit、logs)
|
|
1176
|
+
|
|
1177
|
+
```python
|
|
1178
|
+
async def _handle_shutdown(self):
|
|
1179
|
+
# Step 1-4: 发送 ack → exiting → 清理 → ready
|
|
1180
|
+
if self._ws:
|
|
1181
|
+
await self._rpc_call(self._ws, "event.publish", {
|
|
1182
|
+
"event": "module.shutdown.ready",
|
|
1183
|
+
"data": {"module_id": "my_module"},
|
|
1184
|
+
})
|
|
1185
|
+
|
|
1186
|
+
# Step 5: 触发 uvicorn 优雅关闭
|
|
1187
|
+
if self._uvicorn_server:
|
|
1188
|
+
self._uvicorn_server.should_exit = True
|
|
1189
|
+
|
|
1190
|
+
# ✅ 让 uvicorn 自然退出,不调用 sys.exit()
|
|
1191
|
+
```
|
|
1192
|
+
|
|
1193
|
+
#### HTTP + WebSocket 混合模块(最佳实践)
|
|
1194
|
+
|
|
1195
|
+
特点:提供 HTTP API + 服务端 WebSocket + 连接 Kernel WebSocket(如 web、evol)
|
|
1196
|
+
|
|
1197
|
+
```python
|
|
1198
|
+
async def _handle_shutdown(self):
|
|
1199
|
+
# Step 1-2: 发送 ack → exiting
|
|
1200
|
+
|
|
1201
|
+
# Step 3: 清理后台任务
|
|
1202
|
+
if self._test_task:
|
|
1203
|
+
self._test_task.cancel()
|
|
1204
|
+
|
|
1205
|
+
# Step 4: 关闭服务端 WebSocket 连接(带超时保护)
|
|
1206
|
+
if hasattr(self.app.state, 'relay_service'):
|
|
1207
|
+
try:
|
|
1208
|
+
await asyncio.wait_for(
|
|
1209
|
+
self.app.state.relay_service.close_all_sessions(),
|
|
1210
|
+
timeout=1.0 # ✅ 超时保护,防止挂起
|
|
1211
|
+
)
|
|
1212
|
+
except asyncio.TimeoutError:
|
|
1213
|
+
print("[my_module] Relay service close timeout")
|
|
1214
|
+
except Exception as e:
|
|
1215
|
+
print(f"[my_module] Failed to close relay sessions: {e}")
|
|
1216
|
+
|
|
1217
|
+
# Step 5: 发送 ready
|
|
1218
|
+
await self._rpc_call(self._ws, "event.publish", {
|
|
1219
|
+
"event": "module.shutdown.ready",
|
|
1220
|
+
"data": {"module_id": "my_module"},
|
|
1221
|
+
})
|
|
1222
|
+
|
|
1223
|
+
# Step 6: 等待事件发送完成
|
|
1224
|
+
await asyncio.sleep(0.1)
|
|
1225
|
+
|
|
1226
|
+
# Step 7: 关闭 Kernel WebSocket
|
|
1227
|
+
if self._ws:
|
|
1228
|
+
try:
|
|
1229
|
+
await self._ws.close(code=1000, reason="Graceful shutdown")
|
|
1230
|
+
except Exception as e:
|
|
1231
|
+
print(f"[my_module] Failed to close Kernel WebSocket: {e}")
|
|
1232
|
+
|
|
1233
|
+
# Step 8: 触发 uvicorn 优雅关闭
|
|
1234
|
+
if self._uvicorn_server:
|
|
1235
|
+
self._uvicorn_server.should_exit = True
|
|
1236
|
+
|
|
1237
|
+
# ✅ 让 uvicorn 自然退出,不调用 sys.exit()
|
|
1238
|
+
```
|
|
1239
|
+
|
|
1240
|
+
**关键要点:**
|
|
1241
|
+
|
|
1242
|
+
1. **永远不要在异步协程中调用 `sys.exit()`** — 让事件循环自然结束
|
|
1243
|
+
2. **关闭 WebSocket 连接要加异常处理** — 防止关闭失败导致挂起
|
|
1244
|
+
3. **关闭服务端 WebSocket 要加超时保护** — 使用 `asyncio.wait_for(timeout=1.0)`
|
|
1245
|
+
4. **HTTP 服务模块使用 `uvicorn_server.should_exit = True`** — 触发优雅关闭
|
|
1246
|
+
5. **发送 ready 后等待一小段时间** — 确保事件发送完成(0.1s 足够)
|
|
1247
|
+
|
|
1248
|
+
**参考实现:**
|
|
1249
|
+
|
|
1250
|
+
- 纯 WS 客户端:`extensions/agents/assistant/server.py`
|
|
1251
|
+
- HTTP 服务:`extensions/services/audit/server.py`
|
|
1252
|
+
- 混合模块(最佳实践):`extensions/services/evol/server.py`
|
|
1253
|
+
|
|
1254
|
+
## 8. 模块主动退出
|
|
1255
|
+
|
|
1256
|
+
当模块自身决定退出时,**必须**先发送 `module.exiting` 事件,避免被 Launcher 当作崩溃重启。
|
|
1257
|
+
|
|
1258
|
+
**所有退出路径都要发 `module.exiting`:** 无论是任务完成退出、缺少条件退出,还是重连耗尽退出,只要 WS 连接仍然可用,就必须在退出前发送此事件。详见 5.11.3。
|
|
1259
|
+
|
|
1260
|
+
`module.exiting` 事件 `data` 字段:
|
|
1261
|
+
|
|
1262
|
+
```json
|
|
1263
|
+
{
|
|
1264
|
+
"module_id": "my_module",
|
|
1265
|
+
"reason": "task complete",
|
|
1266
|
+
"action": "none"
|
|
1267
|
+
}
|
|
1268
|
+
```
|
|
1269
|
+
|
|
1270
|
+
**action 取值:**
|
|
1271
|
+
|
|
1272
|
+
| 值 | 说明 |
|
|
1273
|
+
|----|------|
|
|
1274
|
+
| `none` | 不重启(默认),Launcher 设 desired_state=stopped |
|
|
1275
|
+
| `restart` | 请求立即重启 |
|
|
1276
|
+
| `restart_delay` | 延迟重启(默认 5s) |
|
|
1277
|
+
|
|
1278
|
+
适用场景:一次性任务完成后退出、缺少必要条件主动退出、自我升级需要重启。
|
|
1279
|
+
|
|
1280
|
+
## 9. 提供工具
|
|
1281
|
+
|
|
1282
|
+
### 9.1 工具定义
|
|
1283
|
+
|
|
1284
|
+
在模块的 `tools/` 目录下创建工具子目录:
|
|
1285
|
+
|
|
1286
|
+
```
|
|
1287
|
+
my_module/
|
|
1288
|
+
└── tools/
|
|
1289
|
+
└── my_tool/
|
|
1290
|
+
├── tool.md # 工具定义
|
|
1291
|
+
└── handler.py # 工具实现
|
|
1292
|
+
```
|
|
1293
|
+
|
|
1294
|
+
**tool.md** 示例:
|
|
1295
|
+
|
|
1296
|
+
```markdown
|
|
1297
|
+
---
|
|
1298
|
+
name: my_tool
|
|
1299
|
+
description: "一句话描述工具功能"
|
|
1300
|
+
parameters:
|
|
1301
|
+
arg1:
|
|
1302
|
+
type: string
|
|
1303
|
+
description: "参数说明"
|
|
1304
|
+
required: true
|
|
1305
|
+
arg2:
|
|
1306
|
+
type: integer
|
|
1307
|
+
description: "可选参数"
|
|
1308
|
+
---
|
|
1309
|
+
|
|
1310
|
+
# My Tool
|
|
1311
|
+
|
|
1312
|
+
详细使用说明...
|
|
1313
|
+
```
|
|
1314
|
+
|
|
1315
|
+
### 9.2 handler.py 签名
|
|
1316
|
+
|
|
1317
|
+
```python
|
|
1318
|
+
async def execute(args: dict, context: dict) -> str:
|
|
1319
|
+
"""执行工具,返回结果字符串。"""
|
|
1320
|
+
arg1 = args.get("arg1", "")
|
|
1321
|
+
# 处理逻辑...
|
|
1322
|
+
return "工具执行结果"
|
|
1323
|
+
|
|
1324
|
+
|
|
1325
|
+
async def check_permission(args: dict, context: dict) -> str | None:
|
|
1326
|
+
"""权限检查。返回 None 表示允许,返回字符串表示拒绝原因。"""
|
|
1327
|
+
return None # 允许所有调用
|
|
1328
|
+
```
|
|
1329
|
+
|
|
1330
|
+
**context 包含的字段:**
|
|
1331
|
+
|
|
1332
|
+
| 字段 | 说明 |
|
|
1333
|
+
|------|------|
|
|
1334
|
+
| `task_info` | 当前任务信息 |
|
|
1335
|
+
| `webhook` | Webhook 回调 |
|
|
1336
|
+
| `engine` | 对话引擎实例 |
|
|
1337
|
+
| `data_dir` | 数据目录 |
|
|
1338
|
+
| `root_dir` | 项目根目录 |
|
|
1339
|
+
| `is_owner` | 是否为 owner |
|
|
1340
|
+
| `permission_files` | 权限文件列表 |
|
|
1341
|
+
|
|
1342
|
+
### 9.3 在 Registry 注册工具
|
|
1343
|
+
|
|
1344
|
+
注册 payload 的 `tools` 字段中声明工具:
|
|
1345
|
+
|
|
1346
|
+
```json
|
|
1347
|
+
{
|
|
1348
|
+
"tools": {
|
|
1349
|
+
"my_tool": {
|
|
1350
|
+
"endpoint": "/tools/my_tool",
|
|
1351
|
+
"description": "工具描述",
|
|
1352
|
+
"parameters": {"arg1": "string"}
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
```
|
|
1357
|
+
|
|
1358
|
+
其他模块(如 Agent)通过 Registry 的 `/lookup?field=tools.my_tool` 发现工具,再通过 HTTP 调用 `base_url + endpoint`。
|
|
1359
|
+
|
|
1360
|
+
## 10. 启动依赖
|
|
1361
|
+
|
|
1362
|
+
### 10.1 声明依赖
|
|
1363
|
+
|
|
1364
|
+
在 `module.md` 中使用 `depends_on`:
|
|
1365
|
+
|
|
1366
|
+
```yaml
|
|
1367
|
+
---
|
|
1368
|
+
name: my_module
|
|
1369
|
+
depends_on:
|
|
1370
|
+
- event_hub
|
|
1371
|
+
- kernel
|
|
1372
|
+
---
|
|
1373
|
+
```
|
|
1374
|
+
|
|
1375
|
+
### 10.2 排序保证
|
|
1376
|
+
|
|
1377
|
+
Launcher 按拓扑序启动模块:依赖模块的 `module.ready` 收到后,才启动当前模块。
|
|
1378
|
+
|
|
1379
|
+
core 模块(Kernel)的启动顺序由 Launcher 硬编码保证,不需要在 `depends_on` 中声明。
|
|
1380
|
+
|
|
1381
|
+
如果依赖的模块是 `manual` 状态,Launcher 会自动启动它。`disabled` 状态的模块不可被依赖,会报错。
|
|
1382
|
+
|
|
1383
|
+
## 11. 模块发现与放置
|
|
1384
|
+
|
|
1385
|
+
### 11.1 内置扫描路径
|
|
1386
|
+
|
|
1387
|
+
Launcher 自动扫描以下目录(无需配置):
|
|
1388
|
+
|
|
1389
|
+
| 路径 | 深度 | 说明 |
|
|
1390
|
+
|------|------|------|
|
|
1391
|
+
| `extensions/` | 2 | 扩展模块(可嵌套一层,如 `extensions/services/watchdog/`) |
|
|
1392
|
+
|
|
1393
|
+
### 11.2 用户扩展路径
|
|
1394
|
+
|
|
1395
|
+
`KITE_MODULES` 环境变量指向用户安装的扩展模块目录(如 `~/.kite/modules/`)。
|
|
1396
|
+
|
|
1397
|
+
### 11.3 自定义发现源
|
|
1398
|
+
|
|
1399
|
+
在 Launcher 的 `module.md` 中配置 `discovery`,添加额外的扫描目录或模块列表文件:
|
|
1400
|
+
|
|
1401
|
+
```yaml
|
|
1402
|
+
discovery:
|
|
1403
|
+
test:
|
|
1404
|
+
type: scan_dir
|
|
1405
|
+
path: test_modules
|
|
1406
|
+
enabled: true
|
|
1407
|
+
extra:
|
|
1408
|
+
type: module_list
|
|
1409
|
+
path: data/modules.txt
|
|
1410
|
+
enabled: false
|
|
1411
|
+
```
|
|
1412
|
+
|
|
1413
|
+
- `scan_dir` — 递归扫描目录,找到 `module.md` 即视为模块
|
|
1414
|
+
- `module_list` — 逐行读取文件,每行一个模块目录路径
|
|
1415
|
+
|
|
1416
|
+
### 11.4 模块名唯一性
|
|
1417
|
+
|
|
1418
|
+
以 `module.md` 中的 `name` 字段(非目录名)为准。重名模块只加载先发现的,后续跳过并打印警告。
|
|
1419
|
+
|
|
1420
|
+
## 12. 数据存储
|
|
1421
|
+
|
|
1422
|
+
模块有三个可用的存储位置:
|
|
1423
|
+
|
|
1424
|
+
| 位置 | 环境变量 | 生命周期 | 用途 |
|
|
1425
|
+
|------|---------|---------|------|
|
|
1426
|
+
| 实例级数据 | `KITE_MODULE_DATA` | 每次运行隔离 | 日志、缓存、运行时状态 |
|
|
1427
|
+
| 共享级数据 | `KITE_DATA` | 跨实例持久 | 用户数据、全局配置 |
|
|
1428
|
+
| 用户可见 | `KITE_CWD/.kite/` | 用户项目目录 | 用户需要查看的文件 |
|
|
1429
|
+
|
|
1430
|
+
```python
|
|
1431
|
+
import os
|
|
1432
|
+
|
|
1433
|
+
# 实例级(写运行日志、临时状态)
|
|
1434
|
+
module_data = os.environ.get("KITE_MODULE_DATA", "")
|
|
1435
|
+
my_state = os.path.join(module_data, "state.json")
|
|
1436
|
+
|
|
1437
|
+
# 共享级(写持久数据)
|
|
1438
|
+
data_dir = os.environ.get("KITE_DATA", "")
|
|
1439
|
+
my_persist = os.path.join(data_dir, "my_module", "history.jsonl")
|
|
1440
|
+
|
|
1441
|
+
# 用户可见(写用户需要查看的内容)
|
|
1442
|
+
cwd = os.environ.get("KITE_CWD", "")
|
|
1443
|
+
user_log = os.path.join(cwd, ".kite", "logs", "my_module.log")
|
|
1444
|
+
```
|
|
1445
|
+
|
|
1446
|
+
## 12.5 日志与异常处理
|
|
1447
|
+
|
|
1448
|
+
每个模块必须实现 `latest.log`(运行日志)和 `crashes.jsonl`(崩溃日志),均位于 `{KITE_MODULE_DATA}/log/` 下,每次启动时清空。Python 模块可直接使用 `core/kite_log.py` 工具库:
|
|
1449
|
+
|
|
1450
|
+
```python
|
|
1451
|
+
from core.kite_log import init_module_log, setup_exception_hooks, install_log_hook
|
|
1452
|
+
|
|
1453
|
+
def main():
|
|
1454
|
+
init_module_log("my_module") # 初始化 latest.log + crashes.jsonl
|
|
1455
|
+
setup_exception_hooks("my_module") # 全局异常钩子(主线程 + 子线程)
|
|
1456
|
+
install_log_hook() # stdout 自动写入日志文件
|
|
1457
|
+
# ... 模块业务逻辑 ...
|
|
1458
|
+
```
|
|
1459
|
+
|
|
1460
|
+
完整规范(日志目录结构、JSONL 字段定义、module.crash 事件、控制台输出格式、Node.js 实现示例等)见 [日志与异常处理规范](日志与异常处理规范.md)。
|
|
1461
|
+
|
|
1462
|
+
## 13. 业务配置
|
|
1463
|
+
|
|
1464
|
+
模块可以有自己的业务配置文件,与框架层面的模块配置(module.md)分离。
|
|
1465
|
+
|
|
1466
|
+
### 13.1 配置分层
|
|
1467
|
+
|
|
1468
|
+
Kite 框架中有两层配置:
|
|
1469
|
+
|
|
1470
|
+
| 配置层 | 文件 | 用途 | 格式 |
|
|
1471
|
+
|--------|------|------|------|
|
|
1472
|
+
| **模块配置** | `module.md` | 框架层面的元数据(name、type、runtime、entry 等),用于 Launcher 发现和启动模块 | YAML frontmatter |
|
|
1473
|
+
| **业务配置** | `*.json5` | 模块自身的业务逻辑配置,与框架无关 | JSON5 |
|
|
1474
|
+
|
|
1475
|
+
**设计原则:**
|
|
1476
|
+
- 模块配置是框架契约,变更需谨慎
|
|
1477
|
+
- 业务配置是模块内部实现细节,可以自由调整
|
|
1478
|
+
- 两者分离,职责清晰
|
|
1479
|
+
|
|
1480
|
+
### 13.2 声明业务配置
|
|
1481
|
+
|
|
1482
|
+
在 `module.md` 的 frontmatter 中添加 `businesses` 块:
|
|
1483
|
+
|
|
1484
|
+
```yaml
|
|
1485
|
+
---
|
|
1486
|
+
name: web
|
|
1487
|
+
display_name: Web Management
|
|
1488
|
+
type: service
|
|
1489
|
+
state: enabled
|
|
1490
|
+
runtime: python
|
|
1491
|
+
entry: entry.py
|
|
1492
|
+
|
|
1493
|
+
# 业务配置列表
|
|
1494
|
+
businesses:
|
|
1495
|
+
- name: web_server
|
|
1496
|
+
type: http_service
|
|
1497
|
+
description: Web 管理界面和 API 服务
|
|
1498
|
+
config_file: web_config.json5
|
|
1499
|
+
|
|
1500
|
+
- name: relay_service
|
|
1501
|
+
type: kernel_relay
|
|
1502
|
+
description: Kernel WebSocket 中转服务
|
|
1503
|
+
config_file: relay_config.json5
|
|
1504
|
+
---
|
|
1505
|
+
```
|
|
1506
|
+
|
|
1507
|
+
**字段说明:**
|
|
1508
|
+
|
|
1509
|
+
| 字段 | 必填 | 说明 |
|
|
1510
|
+
|------|------|------|
|
|
1511
|
+
| `name` | 是 | 业务名称(唯一标识) |
|
|
1512
|
+
| `type` | 否 | 业务类型(自定义,用于分类) |
|
|
1513
|
+
| `description` | 否 | 业务描述 |
|
|
1514
|
+
| `config_file` | 是 | 配置文件路径(相对于模块目录) |
|
|
1515
|
+
|
|
1516
|
+
### 13.3 配置文件格式
|
|
1517
|
+
|
|
1518
|
+
推荐使用 **JSON5** 格式(支持注释和尾随逗号):
|
|
1519
|
+
|
|
1520
|
+
```json5
|
|
1521
|
+
// web_config.json5
|
|
1522
|
+
{
|
|
1523
|
+
// Web 服务配置
|
|
1524
|
+
server: {
|
|
1525
|
+
host: "0.0.0.0",
|
|
1526
|
+
port: 18766,
|
|
1527
|
+
ssl: false,
|
|
1528
|
+
ssl_cert: null,
|
|
1529
|
+
ssl_key: null
|
|
1530
|
+
},
|
|
1531
|
+
|
|
1532
|
+
// 静态文件配置
|
|
1533
|
+
static: {
|
|
1534
|
+
directory: "static",
|
|
1535
|
+
cache_max_age: 3600 // 秒
|
|
1536
|
+
},
|
|
1537
|
+
|
|
1538
|
+
// CORS 配置
|
|
1539
|
+
cors: {
|
|
1540
|
+
enabled: false,
|
|
1541
|
+
origins: ["*"]
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
```
|
|
1545
|
+
|
|
1546
|
+
### 13.4 加载业务配置
|
|
1547
|
+
|
|
1548
|
+
使用 `config_loader.py` 工具(零共享代码依赖,可复制到任何模块):
|
|
1549
|
+
|
|
1550
|
+
```python
|
|
1551
|
+
from pathlib import Path
|
|
1552
|
+
from config_loader import load_business_configs, get_business_config
|
|
1553
|
+
|
|
1554
|
+
# 方式 1:加载所有业务配置
|
|
1555
|
+
module_dir = Path(__file__).parent
|
|
1556
|
+
configs = load_business_configs(str(module_dir))
|
|
1557
|
+
|
|
1558
|
+
# 返回格式:
|
|
1559
|
+
# {
|
|
1560
|
+
# "web_server": {
|
|
1561
|
+
# "metadata": {"name": "web_server", "type": "http_service", ...},
|
|
1562
|
+
# "config": { ... } # JSON5 配置内容
|
|
1563
|
+
# },
|
|
1564
|
+
# "relay_service": { ... }
|
|
1565
|
+
# }
|
|
1566
|
+
|
|
1567
|
+
# 方式 2:加载单个业务配置
|
|
1568
|
+
relay_config = get_business_config(str(module_dir), "relay_service")
|
|
1569
|
+
if relay_config:
|
|
1570
|
+
config = relay_config['config']
|
|
1571
|
+
print(f"Relay port: {config['relay']['reconnect_timeout']}")
|
|
1572
|
+
```
|
|
1573
|
+
|
|
1574
|
+
### 13.5 config_loader.py 实现
|
|
1575
|
+
|
|
1576
|
+
这是一个独立的工具文件,可以直接复制到任何模块使用:
|
|
1577
|
+
|
|
1578
|
+
```python
|
|
1579
|
+
"""
|
|
1580
|
+
配置加载工具
|
|
1581
|
+
|
|
1582
|
+
用于加载模块的业务配置。支持从 module.md 读取业务配置块,
|
|
1583
|
+
并动态加载对应的 JSON5 配置文件。
|
|
1584
|
+
|
|
1585
|
+
零共享代码依赖 - 此文件可以独立拷贝到其他模块使用。
|
|
1586
|
+
"""
|
|
1587
|
+
|
|
1588
|
+
import os
|
|
1589
|
+
import re
|
|
1590
|
+
import json5
|
|
1591
|
+
import yaml
|
|
1592
|
+
|
|
1593
|
+
|
|
1594
|
+
def load_module_metadata(module_dir: str) -> dict:
|
|
1595
|
+
"""读取 module.md 的 YAML frontmatter。"""
|
|
1596
|
+
md_path = os.path.join(module_dir, "module.md")
|
|
1597
|
+
if not os.path.exists(md_path):
|
|
1598
|
+
return {}
|
|
1599
|
+
|
|
1600
|
+
try:
|
|
1601
|
+
with open(md_path, "r", encoding="utf-8") as f:
|
|
1602
|
+
text = f.read()
|
|
1603
|
+
# 提取 YAML frontmatter (--- ... ---)
|
|
1604
|
+
m = re.match(r'^---\s*\n(.*?)\n---', text, re.DOTALL)
|
|
1605
|
+
if m:
|
|
1606
|
+
return yaml.safe_load(m.group(1)) or {}
|
|
1607
|
+
except Exception as e:
|
|
1608
|
+
print(f"[config_loader] Error loading module.md: {e}")
|
|
1609
|
+
return {}
|
|
1610
|
+
|
|
1611
|
+
|
|
1612
|
+
def load_business_configs(module_dir: str) -> dict:
|
|
1613
|
+
"""
|
|
1614
|
+
加载所有业务配置。
|
|
1615
|
+
|
|
1616
|
+
Returns:
|
|
1617
|
+
业务配置字典,格式:
|
|
1618
|
+
{
|
|
1619
|
+
"business_name": {
|
|
1620
|
+
"metadata": {...},
|
|
1621
|
+
"config": {...}
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
"""
|
|
1625
|
+
metadata = load_module_metadata(module_dir)
|
|
1626
|
+
businesses = metadata.get('businesses', [])
|
|
1627
|
+
|
|
1628
|
+
configs = {}
|
|
1629
|
+
for business in businesses:
|
|
1630
|
+
name = business.get('name')
|
|
1631
|
+
config_file = business.get('config_file')
|
|
1632
|
+
|
|
1633
|
+
if not name or not config_file:
|
|
1634
|
+
print(f"[config_loader] Warning: Invalid business entry: {business}")
|
|
1635
|
+
continue
|
|
1636
|
+
|
|
1637
|
+
config_path = os.path.join(module_dir, config_file)
|
|
1638
|
+
if os.path.exists(config_path):
|
|
1639
|
+
try:
|
|
1640
|
+
with open(config_path, "r", encoding="utf-8") as f:
|
|
1641
|
+
configs[name] = {
|
|
1642
|
+
'metadata': business,
|
|
1643
|
+
'config': json5.load(f)
|
|
1644
|
+
}
|
|
1645
|
+
except Exception as e:
|
|
1646
|
+
print(f"[config_loader] Error loading {config_file}: {e}")
|
|
1647
|
+
else:
|
|
1648
|
+
print(f"[config_loader] Warning: Config file not found: {config_file}")
|
|
1649
|
+
|
|
1650
|
+
return configs
|
|
1651
|
+
|
|
1652
|
+
|
|
1653
|
+
def get_business_config(module_dir: str, business_name: str) -> dict | None:
|
|
1654
|
+
"""获取指定业务的配置。"""
|
|
1655
|
+
configs = load_business_configs(module_dir)
|
|
1656
|
+
return configs.get(business_name)
|
|
1657
|
+
```
|
|
1658
|
+
|
|
1659
|
+
### 13.6 使用示例
|
|
1660
|
+
|
|
1661
|
+
参考 `extensions/services/web/` 模块:
|
|
1662
|
+
|
|
1663
|
+
**目录结构:**
|
|
1664
|
+
```
|
|
1665
|
+
web/
|
|
1666
|
+
├── module.md # 模块配置 + businesses 声明
|
|
1667
|
+
├── entry.py # 模块入口
|
|
1668
|
+
├── config_loader.py # 配置加载工具
|
|
1669
|
+
├── web_config.json5 # Web 服务配置
|
|
1670
|
+
├── relay_config.json5 # 中转服务配置
|
|
1671
|
+
└── server.py # 业务逻辑
|
|
1672
|
+
```
|
|
1673
|
+
|
|
1674
|
+
**在 server.py 中使用:**
|
|
1675
|
+
```python
|
|
1676
|
+
from pathlib import Path
|
|
1677
|
+
from config_loader import load_business_configs
|
|
1678
|
+
|
|
1679
|
+
module_dir = Path(__file__).parent
|
|
1680
|
+
business_configs = load_business_configs(str(module_dir))
|
|
1681
|
+
|
|
1682
|
+
# 获取 Web 服务配置
|
|
1683
|
+
web_config = business_configs.get('web_server')['config']
|
|
1684
|
+
host = web_config['server']['host']
|
|
1685
|
+
port = web_config['server']['port']
|
|
1686
|
+
|
|
1687
|
+
# 获取中转服务配置
|
|
1688
|
+
relay_config = business_configs.get('relay_service')['config']
|
|
1689
|
+
reconnect_timeout = relay_config['relay']['reconnect_timeout']
|
|
1690
|
+
```
|
|
1691
|
+
|
|
1692
|
+
### 13.7 配置验证(可选)
|
|
1693
|
+
|
|
1694
|
+
推荐使用 JSON Schema 验证配置文件:
|
|
1695
|
+
|
|
1696
|
+
```python
|
|
1697
|
+
import jsonschema
|
|
1698
|
+
|
|
1699
|
+
# 定义 schema
|
|
1700
|
+
web_schema = {
|
|
1701
|
+
"type": "object",
|
|
1702
|
+
"properties": {
|
|
1703
|
+
"server": {
|
|
1704
|
+
"type": "object",
|
|
1705
|
+
"properties": {
|
|
1706
|
+
"host": {"type": "string"},
|
|
1707
|
+
"port": {"type": "integer", "minimum": 1, "maximum": 65535}
|
|
1708
|
+
},
|
|
1709
|
+
"required": ["host", "port"]
|
|
1710
|
+
}
|
|
1711
|
+
},
|
|
1712
|
+
"required": ["server"]
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
# 验证配置
|
|
1716
|
+
try:
|
|
1717
|
+
jsonschema.validate(config, web_schema)
|
|
1718
|
+
except jsonschema.ValidationError as e:
|
|
1719
|
+
print(f"配置验证失败: {e.message}")
|
|
1720
|
+
```
|
|
1721
|
+
|
|
1722
|
+
### 13.8 配置热重载(可选)
|
|
1723
|
+
|
|
1724
|
+
如果需要运行时更新配置,可以监听配置文件变化:
|
|
1725
|
+
|
|
1726
|
+
```python
|
|
1727
|
+
import time
|
|
1728
|
+
from watchdog.observers import Observer
|
|
1729
|
+
from watchdog.events import FileSystemEventHandler
|
|
1730
|
+
|
|
1731
|
+
class ConfigReloadHandler(FileSystemEventHandler):
|
|
1732
|
+
def on_modified(self, event):
|
|
1733
|
+
if event.src_path.endswith('.json5'):
|
|
1734
|
+
print(f"配置文件变更: {event.src_path}")
|
|
1735
|
+
# 重新加载配置
|
|
1736
|
+
reload_config()
|
|
1737
|
+
|
|
1738
|
+
observer = Observer()
|
|
1739
|
+
observer.schedule(ConfigReloadHandler(), path=module_dir, recursive=False)
|
|
1740
|
+
observer.start()
|
|
1741
|
+
```
|
|
1742
|
+
|
|
1743
|
+
### 13.9 注意事项
|
|
1744
|
+
|
|
1745
|
+
1. **零共享代码依赖** — `config_loader.py` 可以复制到任何模块,不依赖框架代码
|
|
1746
|
+
2. **配置文件位置** — 业务配置文件放在模块目录下,不要放在 `data/` 目录(那是运行时数据)
|
|
1747
|
+
3. **敏感信息** — API Key 等敏感信息不要写在配置文件中,使用环境变量或单独的 secrets 文件
|
|
1748
|
+
4. **版本兼容** — 配置文件格式变更时,注意向后兼容或提供迁移脚本
|
|
1749
|
+
|
|
1750
|
+
## 14. 跨平台与跨语言注意事项
|
|
1751
|
+
|
|
1752
|
+
### 14.1 Windows 差异
|
|
1753
|
+
|
|
1754
|
+
- Windows 没有 SIGTERM — 使用事件机制替代(module.shutdown 事件),不要依赖信号处理
|
|
1755
|
+
- `proc.terminate()` 在 Windows 上等同 SIGKILL(无法捕获),优雅退出必须走事件
|
|
1756
|
+
- 路径分隔符 — 使用 `os.path.join()` 或 `pathlib`,不要硬编码 `/`
|
|
1757
|
+
|
|
1758
|
+
### 14.2 多语言支持
|
|
1759
|
+
|
|
1760
|
+
| runtime | 启动命令 | entry 文件 |
|
|
1761
|
+
|---------|---------|-----------|
|
|
1762
|
+
| `python` | `python entry.py` | `.py` |
|
|
1763
|
+
| `node` | `node entry.js` | `.js` |
|
|
1764
|
+
| `binary` | `./entry` | 可执行文件 |
|
|
1765
|
+
|
|
1766
|
+
通信协议完全语言无关:stdin JSON + WebSocket JSON-RPC 2.0。任何能读 stdin、连 WebSocket 的语言都可以实现 Kite 模块。
|
|
1767
|
+
|
|
1768
|
+
## 15. 常见问题
|
|
1769
|
+
|
|
1770
|
+
**模块启动后 Launcher 报 "未发送 module.ready"**
|
|
1771
|
+
|
|
1772
|
+
原因:模块没发布 module.ready 事件或事件格式错误。检查:
|
|
1773
|
+
1. 是否成功连接 Kernel WebSocket
|
|
1774
|
+
2. 是否通过 JSON-RPC `event.publish` 发布了 `module.ready` 事件
|
|
1775
|
+
3. `data.module_id` 是否与注册时一致
|
|
1776
|
+
4. 是否先订阅再注册(顺序错误可能导致 Kernel 无法正确路由事件)
|
|
1777
|
+
|
|
1778
|
+
**stdout 报 BrokenPipeError**
|
|
1779
|
+
|
|
1780
|
+
原因:Launcher 收到 module.ready 后关闭了 stdio 管道。解决:在入口文件最开头包装 SafeWriter(见 5.2 节)。
|
|
1781
|
+
|
|
1782
|
+
**Kernel WebSocket 连接被拒绝(close code 4001)**
|
|
1783
|
+
|
|
1784
|
+
原因:token 验证失败。确认使用的是 boot_info 中的 token。模块重启后 token 会变,请确保每次启动都从 stdin 读取最新 token。
|
|
1785
|
+
|
|
1786
|
+
**模块被当作崩溃反复重启**
|
|
1787
|
+
|
|
1788
|
+
原因:模块主动退出但没发 `module.exiting` 事件。解决:退出前发送 module.exiting(见第 8 节),或声明 `graceful_shutdown` 并正确处理 shutdown 流程。
|
|
1789
|
+
|
|
1790
|
+
**模块的 /health 端点被 Watchdog 标记为 unhealthy**
|
|
1791
|
+
|
|
1792
|
+
确保 `/health` 返回 JSON 格式,status 字段为 `"healthy"` / `"degraded"` / `"unhealthy"`:
|
|
1793
|
+
```json
|
|
1794
|
+
{"status": "healthy", "details": {}}
|
|
1795
|
+
```
|
|
1796
|
+
|
|
1797
|
+
## 16. 附录:完整示例代码
|
|
1798
|
+
|
|
1799
|
+
### 16.1 Python 完整模块模板
|
|
1800
|
+
|
|
1801
|
+
见第 2.3 节的 `entry.py`,包含完整启动协议。
|
|
1802
|
+
|
|
1803
|
+
生产模块参考:`extensions/services/watchdog/entry.py`(异步 + 重连 + 日志 + 崩溃处理)。
|
|
1804
|
+
|
|
1805
|
+
### 16.2 Node.js 完整模块模板
|
|
1806
|
+
|
|
1807
|
+
参考 `test_modules/hello_node/entry.js`,包含:
|
|
1808
|
+
|
|
1809
|
+
- 读取 boot_info(readline)
|
|
1810
|
+
- HTTP 服务 + `/health` 端点
|
|
1811
|
+
- Kernel WebSocket 连接 + 订阅 + 注册 + module.ready
|
|
1812
|
+
- 消息循环(事件通知 + RPC 请求)
|
|
1813
|
+
- 优雅退出处理
|
|
1814
|
+
|
|
1815
|
+
### 16.3 一次性任务模块示例
|
|
1816
|
+
|
|
1817
|
+
参考 `extensions/event_hub_bench/entry.py` — 一个完成基准测试后自动退出的模块:
|
|
1818
|
+
|
|
1819
|
+
1. 读取 boot_info(token + kernel_port)
|
|
1820
|
+
2. 连接 Kernel WebSocket
|
|
1821
|
+
3. 订阅 → 注册 → 发送 module.ready
|
|
1822
|
+
4. 执行业务逻辑(跑基准测试)
|
|
1823
|
+
5. 发送 `module.exiting`(action: `"none"`)
|
|
1824
|
+
6. 退出进程
|